From 627240ecf8ff011c552294ad18e6315eaef64ddc Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 12 Jun 2024 16:22:21 -0300 Subject: [PATCH 1/6] feat(typegen): add swift template --- package.json | 1 + src/server/constants.ts | 4 +- src/server/routes/generators/swift.ts | 40 ++++ src/server/routes/index.ts | 2 + src/server/server.ts | 7 + src/server/templates/swift.ts | 283 +++++++++++++++++++++++ test/server/typegen.ts | 315 ++++++++++++++++++++++++++ 7 files changed, 651 insertions(+), 1 deletion(-) create mode 100644 src/server/routes/generators/swift.ts create mode 100644 src/server/templates/swift.ts diff --git a/package.json b/package.json index 365de941..45b3532a 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "docs:export": "PG_META_EXPORT_DOCS=true node --loader ts-node/esm src/server/server.ts > openapi.json", "gen:types:typescript": "PG_META_GENERATE_TYPES=typescript node --loader ts-node/esm src/server/server.ts", "gen:types:go": "PG_META_GENERATE_TYPES=go node --loader ts-node/esm src/server/server.ts", + "gen:types:swift": "PG_META_GENERATE_TYPES=swift node --loader ts-node/esm src/server/server.ts", "start": "node dist/server/server.js", "dev": "trap 'npm run db:clean' INT && run-s db:clean db:run && nodemon --exec node --loader ts-node/esm src/server/server.ts | pino-pretty --colorize", "test": "run-s db:clean db:run test:run db:clean", diff --git a/src/server/constants.ts b/src/server/constants.ts index 86415b2a..f7447c45 100644 --- a/src/server/constants.ts +++ b/src/server/constants.ts @@ -1,6 +1,7 @@ import crypto from 'crypto' import { PoolConfig } from 'pg' import { getSecret } from '../lib/secrets.js' +import { AccessControl } from './templates/swift.js' export const PG_META_HOST = process.env.PG_META_HOST || '0.0.0.0' export const PG_META_PORT = Number(process.env.PG_META_PORT || 1337) @@ -40,7 +41,8 @@ export const GENERATE_TYPES_INCLUDED_SCHEMAS = GENERATE_TYPES : [] export const GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS = process.env.PG_META_GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS === 'true' - +export const GENERATE_TYPES_SWIFT_ACCESS_CONTROL = + (process.env.PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL as AccessControl) || 'internal' export const DEFAULT_POOL_CONFIG: PoolConfig = { max: 1, connectionTimeoutMillis: PG_CONN_TIMEOUT_SECS * 1000, diff --git a/src/server/routes/generators/swift.ts b/src/server/routes/generators/swift.ts new file mode 100644 index 00000000..2c802fe2 --- /dev/null +++ b/src/server/routes/generators/swift.ts @@ -0,0 +1,40 @@ +import type { FastifyInstance } from 'fastify' +import { PostgresMeta } from '../../../lib/index.js' +import { DEFAULT_POOL_CONFIG } from '../../constants.js' +import { extractRequestForLogging } from '../../utils.js' +import { apply as applySwiftTemplate, AccessControl } from '../../templates/swift.js' +import { getGeneratorMetadata } from '../../../lib/generators.js' + +export default async (fastify: FastifyInstance) => { + fastify.get<{ + Headers: { pg: string } + Querystring: { + excluded_schemas?: string + included_schemas?: string + access_control?: AccessControl + } + }>('/', async (request, reply) => { + const connectionString = request.headers.pg + const excludedSchemas = + request.query.excluded_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const includedSchemas = + request.query.included_schemas?.split(',').map((schema) => schema.trim()) ?? [] + const accessControl = request.query.access_control ?? 'internal' + + const pgMeta: PostgresMeta = new PostgresMeta({ ...DEFAULT_POOL_CONFIG, connectionString }) + const { data: generatorMeta, error: generatorMetaError } = await getGeneratorMetadata(pgMeta, { + includedSchemas, + excludedSchemas, + }) + if (generatorMetaError) { + request.log.error({ error: generatorMetaError, request: extractRequestForLogging(request) }) + reply.code(500) + return { error: generatorMetaError.message } + } + + return applySwiftTemplate({ + ...generatorMeta, + accessControl, + }) + }) +} diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index 99678515..55b85618 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -20,6 +20,7 @@ import TypesRoute from './types.js' import ViewsRoute from './views.js' import TypeScriptTypeGenRoute from './generators/typescript.js' import GoTypeGenRoute from './generators/go.js' +import SwiftTypeGenRoute from './generators/swift.js' import { PG_CONNECTION, CRYPTO_KEY } from '../constants.js' export default async (fastify: FastifyInstance) => { @@ -65,4 +66,5 @@ export default async (fastify: FastifyInstance) => { fastify.register(ViewsRoute, { prefix: '/views' }) fastify.register(TypeScriptTypeGenRoute, { prefix: '/generators/typescript' }) fastify.register(GoTypeGenRoute, { prefix: '/generators/go' }) + fastify.register(SwiftTypeGenRoute, { prefix: '/generators/swift' }) } diff --git a/src/server/server.ts b/src/server/server.ts index 12371e8a..1c915f33 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -9,12 +9,14 @@ import { GENERATE_TYPES, GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS, GENERATE_TYPES_INCLUDED_SCHEMAS, + GENERATE_TYPES_SWIFT_ACCESS_CONTROL, PG_CONNECTION, PG_META_HOST, PG_META_PORT, } from './constants.js' import { apply as applyTypescriptTemplate } from './templates/typescript.js' import { apply as applyGoTemplate } from './templates/go.js' +import { apply as applySwiftTemplate } from './templates/swift.js' import { getGeneratorMetadata } from '../lib/generators.js' const logger = pino({ @@ -52,6 +54,11 @@ async function getTypeOutput(): Promise { detectOneToOneRelationships: GENERATE_TYPES_DETECT_ONE_TO_ONE_RELATIONSHIPS, }) break + case 'swift': + output = await applySwiftTemplate({ + ...generatorMetadata, + accessControl: GENERATE_TYPES_SWIFT_ACCESS_CONTROL, + }) case 'go': output = applyGoTemplate(generatorMetadata) break diff --git a/src/server/templates/swift.ts b/src/server/templates/swift.ts new file mode 100644 index 00000000..a3b787ce --- /dev/null +++ b/src/server/templates/swift.ts @@ -0,0 +1,283 @@ +import prettier from 'prettier' +import type { + PostgresColumn, + PostgresFunction, + PostgresMaterializedView, + PostgresSchema, + PostgresTable, + PostgresType, + PostgresView, +} from '../../lib/index.js' +import type { GeneratorMetadata } from '../../lib/generators.js' + +type Operation = 'Select' | 'Insert' | 'Update' +export type AccessControl = 'internal' | 'public' | 'private' | 'package' + +type SwiftGeneratorOptions = { + accessControl: AccessControl +} + +function generateEnum(enum_: PostgresType, options: SwiftGeneratorOptions): string { + return ` +${options.accessControl} enum ${toUpperCamelCase(enum_.name)}: String, Codable, Hashable, Sendable { +${enum_.enums.map((case_) => `${ident(1)}case ${toLowerCamelCase(case_)} = "${case_}"`).join('\n')} +} +`.trim() +} + +function generateTableStructsForOperations( + schema: PostgresSchema, + schemas: PostgresSchema[], + table: PostgresTable | PostgresView | PostgresMaterializedView, + tables: PostgresTable[], + views: PostgresView[], + columns: PostgresColumn[] | undefined, + types: PostgresType[], + operations: Operation[], + options: SwiftGeneratorOptions +): string[] { + return operations.map((operation) => + generateTableStruct(schema, schemas, table, tables, views, columns, types, operation, options) + ) +} + +function generateTableStruct( + schema: PostgresSchema, + schemas: PostgresSchema[], + table: PostgresTable | PostgresView | PostgresMaterializedView, + tables: PostgresTable[], + views: PostgresView[], + columns: PostgresColumn[] | undefined, + types: PostgresType[], + operation: Operation, + options: SwiftGeneratorOptions +): string { + const columnEntries: { + raw_name: string + formatted_name: string + type: string + nullable: boolean + }[] = + columns?.map((column) => { + let nullable: boolean + + if (operation === 'Insert') { + nullable = + column.is_nullable || column.is_identity || column.is_generated || !!column.default_value + } else if (operation === 'Update') { + nullable = true + } else { + nullable = column.is_nullable + } + + return { + raw_name: column.name, + formatted_name: toLowerCamelCase(column.name), + type: pgTypeToSwiftType(column.format, { types, schemas, tables, views }), + nullable, + } + }) ?? [] + + const identity = columns?.find((column) => column.is_identity) + const structName = `${toUpperCamelCase(table.name)}${operation}` + + let output = ` +extension ${toUpperCamelCase(schema.name)}Schema { +${ident(1)}${options.accessControl} struct ${structName}: Codable, Hashable, Sendable { +${columnEntries.map(({ formatted_name, type, nullable }) => `${ident(2)}${options.accessControl} let ${formatted_name}: ${type}${nullable ? '?' : ''}`).join('\n')} + +${ident(2)}${options.accessControl} enum CodingKeys: String, CodingKey { +${columnEntries.map(({ raw_name, formatted_name }) => `${ident(3)}case ${formatted_name} = "${raw_name}"`).join('\n')} +${ident(2)}} +} +` + + if (operation === 'Select' && identity) { + const identityEntry = columnEntries.find((entry) => entry.raw_name === identity.name) + if (identityEntry) { + output += `extension ${toUpperCamelCase(schema.name)}Schema.${structName}: Identifiable { +${identityEntry.formatted_name !== 'id' ? `${ident(2)}${options.accessControl} var id: ${identityEntry.type} { ${identityEntry.formatted_name} }` : ''} +} + ` + } + } + + return output.trim() +} + +export const apply = async ({ + schemas, + tables, + views, + columns, + types, + accessControl, +}: GeneratorMetadata & SwiftGeneratorOptions): Promise => { + const columnsByTableId = columns + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + .reduce( + (acc, curr) => { + acc[curr.table_id] ??= [] + acc[curr.table_id].push(curr) + return acc + }, + {} as Record + ) + + const compositeTypes = types.filter((type) => type.attributes.length > 0) + const enums = types + .filter((type) => type.enums.length > 0) + .sort(({ name: a }, { name: b }) => a.localeCompare(b)) + + let output = ` +import Foundation +import Supabase + +// MARK: - Enums +${enums.map((enum_) => generateEnum(enum_, { accessControl })).join('\n')} + +// MARK: - Schemas +${schemas.map((schema) => `${accessControl} enum ${toUpperCamelCase(schema.name)}Schema {}`).join('\n')} + +// MARK: - Tables +${tables + .flatMap((table) => + generateTableStructsForOperations( + schemas.find((schema) => schema.name === table.schema)!, + schemas, + table, + tables, + views, + columnsByTableId[table.id], + types, + ['Select', 'Insert', 'Update'], + { accessControl } + ) + ) + .join('\n')} +`.trim() + + return output +} + +// TODO: Make this more robust. Currently doesn't handle range types - returns them as unknown. +const pgTypeToSwiftType = ( + pgType: string, + { + types, + schemas, + tables, + views, + }: { + types: PostgresType[] + schemas: PostgresSchema[] + tables: PostgresTable[] + views: PostgresView[] + } +): string => { + if (pgType === 'bool') { + return 'Bool' + } else if (pgType === 'int2') { + return 'Int16' + } else if (pgType === 'int4') { + return 'Int32' + } else if (pgType === 'int8') { + return 'Int64' + } else if (pgType === 'float4') { + return 'Float' + } else if (pgType === 'float8') { + return 'Double' + } else if (pgType === 'uuid') { + return 'UUID' + } else if ( + [ + 'bytea', + 'bpchar', + 'varchar', + 'date', + 'text', + 'citext', + 'time', + 'timetz', + 'timestamp', + 'timestamptz', + 'vector', + ].includes(pgType) + ) { + return 'String' + } else if (['json', 'jsonb'].includes(pgType)) { + return 'AnyJSON' + } else if (pgType === 'void') { + return 'Void' + } else if (pgType === 'record') { + return 'JSONObject' + } else if (pgType.startsWith('_')) { + return `[${pgTypeToSwiftType(pgType.substring(1), { types, schemas, tables, views })}]` + } else { + const enumType = types.find((type) => type.name === pgType && type.enums.length > 0) + + if (enumType) { + return `${toUpperCamelCase(enumType.name)}` + } + + const compositeType = types.find((type) => type.name === pgType && type.attributes.length > 0) + if (compositeType) { + return `${toUpperCamelCase(compositeType.name)}` + } + + const tableRowType = tables.find((table) => table.name === pgType) + if (tableRowType) { + return `${toUpperCamelCase(tableRowType.name)}` + } + + const viewRowType = views.find((view) => view.name === pgType) + if (viewRowType) { + return `${toUpperCamelCase(viewRowType.name)}` + } + + return 'unknown' + } +} + +function ident(level: number, options: { width: number } = { width: 2 }): string { + return ' '.repeat(level * options.width) +} + +function toLowerCamelCase(input: string): string { + // Split the input string by spaces and non-alphanumeric characters + const words = input.split(/[\s\-_]+/) + + // Map over the words array to transform each word + const camelCaseWords = words.map((word, index) => { + // Lowercase the entire word + const lowerCasedWord = word.toLowerCase() + + // Capitalize the first letter if it's not the first word + if (index !== 0) { + return lowerCasedWord.charAt(0).toUpperCase() + lowerCasedWord.slice(1) + } + + // Return the word as-is if it's the first word + return lowerCasedWord + }) + + // Join the words back together + return camelCaseWords.join('') +} + +function toUpperCamelCase(input: string): string { + // Split the input string by spaces and non-alphanumeric characters + const words = input.split(/[\s\-_]+/) + + // Map over the words array to transform each word + const camelCaseWords = words.map((word) => { + // Lowercase the entire word + const lowerCasedWord = word.toLowerCase() + + // Capitalize the first letter of each word + return lowerCasedWord.charAt(0).toUpperCase() + lowerCasedWord.slice(1) + }) + + // Join the words back together + return camelCaseWords.join('') +} diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 4614d434..76af21ed 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -1701,3 +1701,318 @@ type PublicCompositeTypeWithArrayAttribute struct { }" `) }) + +test('typegen: swift', async () => { + const { body } = await app.inject({ method: 'GET', path: '/generators/swift' }) + expect(body).toMatchInlineSnapshot(` + "import Foundation + import Supabase + + // MARK: - Enums + internal enum MemeStatus: String, Codable, Hashable, Sendable { + case new = "new" + case old = "old" + case retired = "retired" + } + internal enum UserStatus: String, Codable, Hashable, Sendable { + case active = "ACTIVE" + case inactive = "INACTIVE" + } + + // MARK: - Schemas + internal enum PublicSchema {} + + // MARK: - Tables + extension PublicSchema { + internal struct UsersSelect: Codable, Hashable, Sendable { + internal let id: Int64 + internal let name: String? + internal let status: UserStatus? + + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + extension PublicSchema.UsersSelect: Identifiable { + + } + extension PublicSchema { + internal struct UsersInsert: Codable, Hashable, Sendable { + internal let id: Int64? + internal let name: String? + internal let status: UserStatus? + + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + extension PublicSchema { + internal struct UsersUpdate: Codable, Hashable, Sendable { + internal let id: Int64? + internal let name: String? + internal let status: UserStatus? + + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + extension PublicSchema { + internal struct TodosSelect: Codable, Hashable, Sendable { + internal let details: String? + internal let id: Int64 + internal let userId: Int64 + + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + extension PublicSchema.TodosSelect: Identifiable { + + } + extension PublicSchema { + internal struct TodosInsert: Codable, Hashable, Sendable { + internal let details: String? + internal let id: Int64? + internal let userId: Int64 + + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + extension PublicSchema { + internal struct TodosUpdate: Codable, Hashable, Sendable { + internal let details: String? + internal let id: Int64? + internal let userId: Int64? + + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + extension PublicSchema { + internal struct UsersAuditSelect: Codable, Hashable, Sendable { + internal let createdAt: String? + internal let id: Int64 + internal let previousValue: AnyJSON? + internal let userId: Int64? + + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + extension PublicSchema.UsersAuditSelect: Identifiable { + + } + extension PublicSchema { + internal struct UsersAuditInsert: Codable, Hashable, Sendable { + internal let createdAt: String? + internal let id: Int64? + internal let previousValue: AnyJSON? + internal let userId: Int64? + + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + extension PublicSchema { + internal struct UsersAuditUpdate: Codable, Hashable, Sendable { + internal let createdAt: String? + internal let id: Int64? + internal let previousValue: AnyJSON? + internal let userId: Int64? + + internal enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + extension PublicSchema { + internal struct UserDetailsSelect: Codable, Hashable, Sendable { + internal let details: String? + internal let userId: Int64 + + internal enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + extension PublicSchema { + internal struct UserDetailsInsert: Codable, Hashable, Sendable { + internal let details: String? + internal let userId: Int64 + + internal enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + extension PublicSchema { + internal struct UserDetailsUpdate: Codable, Hashable, Sendable { + internal let details: String? + internal let userId: Int64? + + internal enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + extension PublicSchema { + internal struct EmptySelect: Codable, Hashable, Sendable { + + + internal enum CodingKeys: String, CodingKey { + + } + } + extension PublicSchema { + internal struct EmptyInsert: Codable, Hashable, Sendable { + + + internal enum CodingKeys: String, CodingKey { + + } + } + extension PublicSchema { + internal struct EmptyUpdate: Codable, Hashable, Sendable { + + + internal enum CodingKeys: String, CodingKey { + + } + } + extension PublicSchema { + internal struct TableWithOtherTablesRowTypeSelect: Codable, Hashable, Sendable { + internal let col1: UserDetails? + internal let col2: AView? + + internal enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + extension PublicSchema { + internal struct TableWithOtherTablesRowTypeInsert: Codable, Hashable, Sendable { + internal let col1: UserDetails? + internal let col2: AView? + + internal enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + extension PublicSchema { + internal struct TableWithOtherTablesRowTypeUpdate: Codable, Hashable, Sendable { + internal let col1: UserDetails? + internal let col2: AView? + + internal enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + extension PublicSchema { + internal struct CategorySelect: Codable, Hashable, Sendable { + internal let id: Int32 + internal let name: String + + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + extension PublicSchema { + internal struct CategoryInsert: Codable, Hashable, Sendable { + internal let id: Int32? + internal let name: String + + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + extension PublicSchema { + internal struct CategoryUpdate: Codable, Hashable, Sendable { + internal let id: Int32? + internal let name: String? + + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + extension PublicSchema { + internal struct MemesSelect: Codable, Hashable, Sendable { + internal let category: Int32? + internal let createdAt: String + internal let id: Int32 + internal let metadata: AnyJSON? + internal let name: String + internal let status: MemeStatus? + + internal enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + extension PublicSchema { + internal struct MemesInsert: Codable, Hashable, Sendable { + internal let category: Int32? + internal let createdAt: String + internal let id: Int32? + internal let metadata: AnyJSON? + internal let name: String + internal let status: MemeStatus? + + internal enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + extension PublicSchema { + internal struct MemesUpdate: Codable, Hashable, Sendable { + internal let category: Int32? + internal let createdAt: String? + internal let id: Int32? + internal let metadata: AnyJSON? + internal let name: String? + internal let status: MemeStatus? + + internal enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + }" + `) +}) From f0461d4db581e38bb8bc7caddfa4ce30c30202da Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 12 Jun 2024 16:35:52 -0300 Subject: [PATCH 2/6] test: add test case for table with identity other than id --- test/db/00-init.sql | 5 + test/server/typegen.ts | 429 +++++++++++++++++++++++++---------------- 2 files changed, 266 insertions(+), 168 deletions(-) diff --git a/test/db/00-init.sql b/test/db/00-init.sql index 4a09c445..e28a0b16 100644 --- a/test/db/00-init.sql +++ b/test/db/00-init.sql @@ -132,3 +132,8 @@ create table table_with_other_tables_row_type ( col1 user_details, col2 a_view ); + +create table table_with_primary_key_other_than_id ( + other_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + name text +); diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 76af21ed..47d8759f 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -103,6 +103,21 @@ test('typegen: typescript', async () => { } Relationships: [] } + table_with_primary_key_other_than_id: { + Row: { + name: string | null + other_id: number + } + Insert: { + name?: string | null + other_id?: number + } + Update: { + name?: string | null + other_id?: number + } + Relationships: [] + } todos: { Row: { details: string | null @@ -605,6 +620,21 @@ test('typegen w/ one-to-one relationships', async () => { } Relationships: [] } + table_with_primary_key_other_than_id: { + Row: { + name: string | null + other_id: number + } + Insert: { + name?: string | null + other_id?: number + } + Update: { + name?: string | null + other_id?: number + } + Relationships: [] + } todos: { Row: { details: string | null @@ -1119,6 +1149,21 @@ test('typegen: typescript w/ one-to-one relationships', async () => { } Relationships: [] } + table_with_primary_key_other_than_id: { + Row: { + name: string | null + other_id: number + } + Insert: { + name?: string | null + other_id?: number + } + Update: { + name?: string | null + other_id?: number + } + Relationships: [] + } todos: { Row: { details: string | null @@ -1531,174 +1576,189 @@ test('typegen: go', async () => { expect(body).toMatchInlineSnapshot(` "package database -import "database/sql" - -type PublicUsersSelect struct { - Id int64 \`json:"id"\` - Name sql.NullString \`json:"name"\` - Status sql.NullString \`json:"status"\` -} - -type PublicUsersInsert struct { - Id sql.NullInt64 \`json:"id"\` - Name sql.NullString \`json:"name"\` - Status sql.NullString \`json:"status"\` -} - -type PublicUsersUpdate struct { - Id sql.NullInt64 \`json:"id"\` - Name sql.NullString \`json:"name"\` - Status sql.NullString \`json:"status"\` -} - -type PublicTodosSelect struct { - Details sql.NullString \`json:"details"\` - Id int64 \`json:"id"\` - UserId int64 \`json:"user-id"\` -} - -type PublicTodosInsert struct { - Details sql.NullString \`json:"details"\` - Id sql.NullInt64 \`json:"id"\` - UserId int64 \`json:"user-id"\` -} - -type PublicTodosUpdate struct { - Details sql.NullString \`json:"details"\` - Id sql.NullInt64 \`json:"id"\` - UserId sql.NullInt64 \`json:"user-id"\` -} - -type PublicUsersAuditSelect struct { - CreatedAt sql.NullString \`json:"created_at"\` - Id int64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId sql.NullInt64 \`json:"user_id"\` -} - -type PublicUsersAuditInsert struct { - CreatedAt sql.NullString \`json:"created_at"\` - Id sql.NullInt64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId sql.NullInt64 \`json:"user_id"\` -} - -type PublicUsersAuditUpdate struct { - CreatedAt sql.NullString \`json:"created_at"\` - Id sql.NullInt64 \`json:"id"\` - PreviousValue interface{} \`json:"previous_value"\` - UserId sql.NullInt64 \`json:"user_id"\` -} - -type PublicUserDetailsSelect struct { - Details sql.NullString \`json:"details"\` - UserId int64 \`json:"user_id"\` -} - -type PublicUserDetailsInsert struct { - Details sql.NullString \`json:"details"\` - UserId int64 \`json:"user_id"\` -} - -type PublicUserDetailsUpdate struct { - Details sql.NullString \`json:"details"\` - UserId sql.NullInt64 \`json:"user_id"\` -} - -type PublicEmptySelect struct { - -} - -type PublicEmptyInsert struct { - -} - -type PublicEmptyUpdate struct { - -} - -type PublicTableWithOtherTablesRowTypeSelect struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicTableWithOtherTablesRowTypeInsert struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicTableWithOtherTablesRowTypeUpdate struct { - Col1 interface{} \`json:"col1"\` - Col2 interface{} \`json:"col2"\` -} - -type PublicCategorySelect struct { - Id int32 \`json:"id"\` - Name string \`json:"name"\` -} - -type PublicCategoryInsert struct { - Id sql.NullInt32 \`json:"id"\` - Name string \`json:"name"\` -} - -type PublicCategoryUpdate struct { - Id sql.NullInt32 \`json:"id"\` - Name sql.NullString \`json:"name"\` -} - -type PublicMemesSelect struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - Id int32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status sql.NullString \`json:"status"\` -} - -type PublicMemesInsert struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt string \`json:"created_at"\` - Id sql.NullInt32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name string \`json:"name"\` - Status sql.NullString \`json:"status"\` -} - -type PublicMemesUpdate struct { - Category sql.NullInt32 \`json:"category"\` - CreatedAt sql.NullString \`json:"created_at"\` - Id sql.NullInt32 \`json:"id"\` - Metadata interface{} \`json:"metadata"\` - Name sql.NullString \`json:"name"\` - Status sql.NullString \`json:"status"\` -} - -type PublicTodosViewSelect struct { - Details sql.NullString \`json:"details"\` - Id sql.NullInt64 \`json:"id"\` - UserId sql.NullInt64 \`json:"user-id"\` -} - -type PublicUsersViewSelect struct { - Id sql.NullInt64 \`json:"id"\` - Name sql.NullString \`json:"name"\` - Status sql.NullString \`json:"status"\` -} - -type PublicAViewSelect struct { - Id sql.NullInt64 \`json:"id"\` -} - -type PublicTodosMatviewSelect struct { - Details sql.NullString \`json:"details"\` - Id sql.NullInt64 \`json:"id"\` - UserId sql.NullInt64 \`json:"user-id"\` -} - -type PublicCompositeTypeWithArrayAttribute struct { - MyTextArray interface{} \`json:"my_text_array"\` -}" + import "database/sql" + + type PublicUsersSelect struct { + Id int64 \`json:"id"\` + Name sql.NullString \`json:"name"\` + Status sql.NullString \`json:"status"\` + } + + type PublicUsersInsert struct { + Id sql.NullInt64 \`json:"id"\` + Name sql.NullString \`json:"name"\` + Status sql.NullString \`json:"status"\` + } + + type PublicUsersUpdate struct { + Id sql.NullInt64 \`json:"id"\` + Name sql.NullString \`json:"name"\` + Status sql.NullString \`json:"status"\` + } + + type PublicTodosSelect struct { + Details sql.NullString \`json:"details"\` + Id int64 \`json:"id"\` + UserId int64 \`json:"user-id"\` + } + + type PublicTodosInsert struct { + Details sql.NullString \`json:"details"\` + Id sql.NullInt64 \`json:"id"\` + UserId int64 \`json:"user-id"\` + } + + type PublicTodosUpdate struct { + Details sql.NullString \`json:"details"\` + Id sql.NullInt64 \`json:"id"\` + UserId sql.NullInt64 \`json:"user-id"\` + } + + type PublicUsersAuditSelect struct { + CreatedAt sql.NullString \`json:"created_at"\` + Id int64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId sql.NullInt64 \`json:"user_id"\` + } + + type PublicUsersAuditInsert struct { + CreatedAt sql.NullString \`json:"created_at"\` + Id sql.NullInt64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId sql.NullInt64 \`json:"user_id"\` + } + + type PublicUsersAuditUpdate struct { + CreatedAt sql.NullString \`json:"created_at"\` + Id sql.NullInt64 \`json:"id"\` + PreviousValue interface{} \`json:"previous_value"\` + UserId sql.NullInt64 \`json:"user_id"\` + } + + type PublicUserDetailsSelect struct { + Details sql.NullString \`json:"details"\` + UserId int64 \`json:"user_id"\` + } + + type PublicUserDetailsInsert struct { + Details sql.NullString \`json:"details"\` + UserId int64 \`json:"user_id"\` + } + + type PublicUserDetailsUpdate struct { + Details sql.NullString \`json:"details"\` + UserId sql.NullInt64 \`json:"user_id"\` + } + + type PublicEmptySelect struct { + + } + + type PublicEmptyInsert struct { + + } + + type PublicEmptyUpdate struct { + + } + + type PublicTableWithOtherTablesRowTypeSelect struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithOtherTablesRowTypeInsert struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithOtherTablesRowTypeUpdate struct { + Col1 interface{} \`json:"col1"\` + Col2 interface{} \`json:"col2"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdSelect struct { + Name sql.NullString \`json:"name"\` + OtherId int64 \`json:"other_id"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdInsert struct { + Name sql.NullString \`json:"name"\` + OtherId sql.NullInt64 \`json:"other_id"\` + } + + type PublicTableWithPrimaryKeyOtherThanIdUpdate struct { + Name sql.NullString \`json:"name"\` + OtherId sql.NullInt64 \`json:"other_id"\` + } + + type PublicCategorySelect struct { + Id int32 \`json:"id"\` + Name string \`json:"name"\` + } + + type PublicCategoryInsert struct { + Id sql.NullInt32 \`json:"id"\` + Name string \`json:"name"\` + } + + type PublicCategoryUpdate struct { + Id sql.NullInt32 \`json:"id"\` + Name sql.NullString \`json:"name"\` + } + + type PublicMemesSelect struct { + Category sql.NullInt32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + Id int32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status sql.NullString \`json:"status"\` + } + + type PublicMemesInsert struct { + Category sql.NullInt32 \`json:"category"\` + CreatedAt string \`json:"created_at"\` + Id sql.NullInt32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name string \`json:"name"\` + Status sql.NullString \`json:"status"\` + } + + type PublicMemesUpdate struct { + Category sql.NullInt32 \`json:"category"\` + CreatedAt sql.NullString \`json:"created_at"\` + Id sql.NullInt32 \`json:"id"\` + Metadata interface{} \`json:"metadata"\` + Name sql.NullString \`json:"name"\` + Status sql.NullString \`json:"status"\` + } + + type PublicTodosViewSelect struct { + Details sql.NullString \`json:"details"\` + Id sql.NullInt64 \`json:"id"\` + UserId sql.NullInt64 \`json:"user-id"\` + } + + type PublicUsersViewSelect struct { + Id sql.NullInt64 \`json:"id"\` + Name sql.NullString \`json:"name"\` + Status sql.NullString \`json:"status"\` + } + + type PublicAViewSelect struct { + Id sql.NullInt64 \`json:"id"\` + } + + type PublicTodosMatviewSelect struct { + Details sql.NullString \`json:"details"\` + Id sql.NullInt64 \`json:"id"\` + UserId sql.NullInt64 \`json:"user-id"\` + } + + type PublicCompositeTypeWithArrayAttribute struct { + MyTextArray interface{} \`json:"my_text_array"\` + }" `) }) @@ -1930,6 +1990,39 @@ test('typegen: swift', async () => { case col2 = "col2" } } + extension PublicSchema { + internal struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable { + internal let name: String? + internal let otherId: Int64 + + internal enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + extension PublicSchema.TableWithPrimaryKeyOtherThanIdSelect: Identifiable { + internal var id: Int64 { otherId } + } + extension PublicSchema { + internal struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable { + internal let name: String? + internal let otherId: Int64? + + internal enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + extension PublicSchema { + internal struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable { + internal let name: String? + internal let otherId: Int64? + + internal enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } extension PublicSchema { internal struct CategorySelect: Codable, Hashable, Sendable { internal let id: Int32 From 79f62be1699a68b952747976aa2c915bbeed1583 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 12 Jun 2024 16:41:11 -0300 Subject: [PATCH 3/6] update test snapshots --- test/lib/tables.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/lib/tables.ts b/test/lib/tables.ts index 6b588d24..c4c934e7 100644 --- a/test/lib/tables.ts +++ b/test/lib/tables.ts @@ -114,19 +114,19 @@ test('list', async () => { ], "relationships": [ { - "constraint_name": "user_details_user_id_fkey", - "source_column_name": "user_id", + "constraint_name": "todos_user-id_fkey", + "source_column_name": "user-id", "source_schema": "public", - "source_table_name": "user_details", + "source_table_name": "todos", "target_column_name": "id", "target_table_name": "users", "target_table_schema": "public", }, { - "constraint_name": "todos_user-id_fkey", - "source_column_name": "user-id", + "constraint_name": "user_details_user_id_fkey", + "source_column_name": "user_id", "source_schema": "public", - "source_table_name": "todos", + "source_table_name": "user_details", "target_column_name": "id", "target_table_name": "users", "target_table_schema": "public", @@ -178,19 +178,19 @@ test('list without columns', async () => { ], "relationships": [ { - "constraint_name": "user_details_user_id_fkey", - "source_column_name": "user_id", + "constraint_name": "todos_user-id_fkey", + "source_column_name": "user-id", "source_schema": "public", - "source_table_name": "user_details", + "source_table_name": "todos", "target_column_name": "id", "target_table_name": "users", "target_table_schema": "public", }, { - "constraint_name": "todos_user-id_fkey", - "source_column_name": "user-id", + "constraint_name": "user_details_user_id_fkey", + "source_column_name": "user_id", "source_schema": "public", - "source_table_name": "todos", + "source_table_name": "user_details", "target_column_name": "id", "target_table_name": "users", "target_table_schema": "public", From a75f2485345fa0ae0abd31465bb3a0bbfe72150e Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 12 Jun 2024 16:48:58 -0300 Subject: [PATCH 4/6] test: add public access control test case for swift generator --- test/server/typegen.ts | 352 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 47d8759f..f5ee3591 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -2109,3 +2109,355 @@ test('typegen: swift', async () => { }" `) }) + +test('typegen: swift w/ public access control', async () => { + const { body } = await app.inject({ + method: 'GET', + path: '/generators/swift', + query: { access_control: 'public' }, + }) + expect(body).toMatchInlineSnapshot(` + "import Foundation + import Supabase + + // MARK: - Enums + public enum MemeStatus: String, Codable, Hashable, Sendable { + case new = "new" + case old = "old" + case retired = "retired" + } + public enum UserStatus: String, Codable, Hashable, Sendable { + case active = "ACTIVE" + case inactive = "INACTIVE" + } + + // MARK: - Schemas + public enum PublicSchema {} + + // MARK: - Tables + extension PublicSchema { + public struct UsersSelect: Codable, Hashable, Sendable { + public let id: Int64 + public let name: String? + public let status: UserStatus? + + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + extension PublicSchema.UsersSelect: Identifiable { + + } + extension PublicSchema { + public struct UsersInsert: Codable, Hashable, Sendable { + public let id: Int64? + public let name: String? + public let status: UserStatus? + + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + extension PublicSchema { + public struct UsersUpdate: Codable, Hashable, Sendable { + public let id: Int64? + public let name: String? + public let status: UserStatus? + + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + extension PublicSchema { + public struct TodosSelect: Codable, Hashable, Sendable { + public let details: String? + public let id: Int64 + public let userId: Int64 + + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + extension PublicSchema.TodosSelect: Identifiable { + + } + extension PublicSchema { + public struct TodosInsert: Codable, Hashable, Sendable { + public let details: String? + public let id: Int64? + public let userId: Int64 + + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + extension PublicSchema { + public struct TodosUpdate: Codable, Hashable, Sendable { + public let details: String? + public let id: Int64? + public let userId: Int64? + + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + extension PublicSchema { + public struct UsersAuditSelect: Codable, Hashable, Sendable { + public let createdAt: String? + public let id: Int64 + public let previousValue: AnyJSON? + public let userId: Int64? + + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + extension PublicSchema.UsersAuditSelect: Identifiable { + + } + extension PublicSchema { + public struct UsersAuditInsert: Codable, Hashable, Sendable { + public let createdAt: String? + public let id: Int64? + public let previousValue: AnyJSON? + public let userId: Int64? + + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + extension PublicSchema { + public struct UsersAuditUpdate: Codable, Hashable, Sendable { + public let createdAt: String? + public let id: Int64? + public let previousValue: AnyJSON? + public let userId: Int64? + + public enum CodingKeys: String, CodingKey { + case createdAt = "created_at" + case id = "id" + case previousValue = "previous_value" + case userId = "user_id" + } + } + extension PublicSchema { + public struct UserDetailsSelect: Codable, Hashable, Sendable { + public let details: String? + public let userId: Int64 + + public enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + extension PublicSchema { + public struct UserDetailsInsert: Codable, Hashable, Sendable { + public let details: String? + public let userId: Int64 + + public enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + extension PublicSchema { + public struct UserDetailsUpdate: Codable, Hashable, Sendable { + public let details: String? + public let userId: Int64? + + public enum CodingKeys: String, CodingKey { + case details = "details" + case userId = "user_id" + } + } + extension PublicSchema { + public struct EmptySelect: Codable, Hashable, Sendable { + + + public enum CodingKeys: String, CodingKey { + + } + } + extension PublicSchema { + public struct EmptyInsert: Codable, Hashable, Sendable { + + + public enum CodingKeys: String, CodingKey { + + } + } + extension PublicSchema { + public struct EmptyUpdate: Codable, Hashable, Sendable { + + + public enum CodingKeys: String, CodingKey { + + } + } + extension PublicSchema { + public struct TableWithOtherTablesRowTypeSelect: Codable, Hashable, Sendable { + public let col1: UserDetails? + public let col2: AView? + + public enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + extension PublicSchema { + public struct TableWithOtherTablesRowTypeInsert: Codable, Hashable, Sendable { + public let col1: UserDetails? + public let col2: AView? + + public enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + extension PublicSchema { + public struct TableWithOtherTablesRowTypeUpdate: Codable, Hashable, Sendable { + public let col1: UserDetails? + public let col2: AView? + + public enum CodingKeys: String, CodingKey { + case col1 = "col1" + case col2 = "col2" + } + } + extension PublicSchema { + public struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable { + public let name: String? + public let otherId: Int64 + + public enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + extension PublicSchema.TableWithPrimaryKeyOtherThanIdSelect: Identifiable { + public var id: Int64 { otherId } + } + extension PublicSchema { + public struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable { + public let name: String? + public let otherId: Int64? + + public enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + extension PublicSchema { + public struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable { + public let name: String? + public let otherId: Int64? + + public enum CodingKeys: String, CodingKey { + case name = "name" + case otherId = "other_id" + } + } + extension PublicSchema { + public struct CategorySelect: Codable, Hashable, Sendable { + public let id: Int32 + public let name: String + + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + extension PublicSchema { + public struct CategoryInsert: Codable, Hashable, Sendable { + public let id: Int32? + public let name: String + + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + extension PublicSchema { + public struct CategoryUpdate: Codable, Hashable, Sendable { + public let id: Int32? + public let name: String? + + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + } + } + extension PublicSchema { + public struct MemesSelect: Codable, Hashable, Sendable { + public let category: Int32? + public let createdAt: String + public let id: Int32 + public let metadata: AnyJSON? + public let name: String + public let status: MemeStatus? + + public enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + extension PublicSchema { + public struct MemesInsert: Codable, Hashable, Sendable { + public let category: Int32? + public let createdAt: String + public let id: Int32? + public let metadata: AnyJSON? + public let name: String + public let status: MemeStatus? + + public enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + } + extension PublicSchema { + public struct MemesUpdate: Codable, Hashable, Sendable { + public let category: Int32? + public let createdAt: String? + public let id: Int32? + public let metadata: AnyJSON? + public let name: String? + public let status: MemeStatus? + + public enum CodingKeys: String, CodingKey { + case category = "category" + case createdAt = "created_at" + case id = "id" + case metadata = "metadata" + case name = "name" + case status = "status" + } + }" + `) +}) From 93c7e4761eca235082efdc2709d8cd4587a904a0 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Thu, 13 Jun 2024 10:46:06 -0300 Subject: [PATCH 5/6] feat: generate materializedviews, views and composite types for swift --- src/server/templates/swift.ts | 284 +++++++++++++++++++++------------- test/server/typegen.ts | 168 +++++++++++++++++--- 2 files changed, 323 insertions(+), 129 deletions(-) diff --git a/src/server/templates/swift.ts b/src/server/templates/swift.ts index a3b787ce..66e469fb 100644 --- a/src/server/templates/swift.ts +++ b/src/server/templates/swift.ts @@ -19,44 +19,45 @@ type SwiftGeneratorOptions = { function generateEnum(enum_: PostgresType, options: SwiftGeneratorOptions): string { return ` -${options.accessControl} enum ${toUpperCamelCase(enum_.name)}: String, Codable, Hashable, Sendable { -${enum_.enums.map((case_) => `${ident(1)}case ${toLowerCamelCase(case_)} = "${case_}"`).join('\n')} +${options.accessControl} enum ${formatForSwiftTypeName(enum_.name)}: String, Codable, Hashable, Sendable { +${enum_.enums.map((case_) => `${ident(1)}case ${formatForSwiftPropertyName(case_)} = "${case_}"`).join('\n')} } `.trim() } function generateTableStructsForOperations( schema: PostgresSchema, - schemas: PostgresSchema[], table: PostgresTable | PostgresView | PostgresMaterializedView, - tables: PostgresTable[], - views: PostgresView[], columns: PostgresColumn[] | undefined, - types: PostgresType[], operations: Operation[], - options: SwiftGeneratorOptions + options: SwiftGeneratorOptions, + { + types, + views, + tables, + }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } ): string[] { return operations.map((operation) => - generateTableStruct(schema, schemas, table, tables, views, columns, types, operation, options) + generateTableStruct(schema, table, columns, operation, options, { types, views, tables }) ) } function generateTableStruct( schema: PostgresSchema, - schemas: PostgresSchema[], table: PostgresTable | PostgresView | PostgresMaterializedView, - tables: PostgresTable[], - views: PostgresView[], columns: PostgresColumn[] | undefined, - types: PostgresType[], operation: Operation, - options: SwiftGeneratorOptions + options: SwiftGeneratorOptions, + { + types, + views, + tables, + }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } ): string { const columnEntries: { raw_name: string formatted_name: string type: string - nullable: boolean }[] = columns?.map((column) => { let nullable: boolean @@ -72,19 +73,18 @@ function generateTableStruct( return { raw_name: column.name, - formatted_name: toLowerCamelCase(column.name), - type: pgTypeToSwiftType(column.format, { types, schemas, tables, views }), - nullable, + formatted_name: formatForSwiftPropertyName(column.name), + type: pgTypeToSwiftType(column.format, nullable, { types, views, tables }), } }) ?? [] const identity = columns?.find((column) => column.is_identity) - const structName = `${toUpperCamelCase(table.name)}${operation}` + const structName = `${formatForSwiftTypeName(table.name)}${operation}` let output = ` -extension ${toUpperCamelCase(schema.name)}Schema { +extension ${formatForSwiftTypeName(schema.name)}Schema { ${ident(1)}${options.accessControl} struct ${structName}: Codable, Hashable, Sendable { -${columnEntries.map(({ formatted_name, type, nullable }) => `${ident(2)}${options.accessControl} let ${formatted_name}: ${type}${nullable ? '?' : ''}`).join('\n')} +${columnEntries.map(({ formatted_name, type }) => `${ident(2)}${options.accessControl} let ${formatted_name}: ${type}`).join('\n')} ${ident(2)}${options.accessControl} enum CodingKeys: String, CodingKey { ${columnEntries.map(({ raw_name, formatted_name }) => `${ident(3)}case ${formatted_name} = "${raw_name}"`).join('\n')} @@ -95,7 +95,7 @@ ${ident(2)}} if (operation === 'Select' && identity) { const identityEntry = columnEntries.find((entry) => entry.raw_name === identity.name) if (identityEntry) { - output += `extension ${toUpperCamelCase(schema.name)}Schema.${structName}: Identifiable { + output += `extension ${formatForSwiftTypeName(schema.name)}Schema.${structName}: Identifiable { ${identityEntry.formatted_name !== 'id' ? `${ident(2)}${options.accessControl} var id: ${identityEntry.type} { ${identityEntry.formatted_name} }` : ''} } ` @@ -105,10 +105,62 @@ ${identityEntry.formatted_name !== 'id' ? `${ident(2)}${options.accessControl} v return output.trim() } +function gnerateCompositeTypeStruct( + schema: PostgresSchema, + type: PostgresType, + options: SwiftGeneratorOptions, + { + types, + views, + tables, + }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } +): string { + const typeWithRetrievedAttributes = { + ...type, + attributes: type.attributes.map((attribute) => { + const type = types.find((type) => type.id === attribute.type_id) + return { + ...attribute, + type, + } + }), + } + + const attributeEntries: { + formatted_name: string + type: string + raw_name: string + }[] = typeWithRetrievedAttributes.attributes.map((attribute) => { + return { + formatted_name: formatForSwiftTypeName(attribute.name), + type: pgTypeToSwiftType(attribute.type!.format, false, { types, views, tables }), + raw_name: attribute.name, + } + }) + + let output = `extension ${formatForSwiftTypeName(schema.name)}Schema { +${ident(1)}${options.accessControl} struct ${formatForSwiftTypeName(type.name)}: Codable, Hashable, Sendable { +${attributeEntries + .map((entry) => `${ident(2)}${options.accessControl} let ${entry.formatted_name}: ${entry.type}`) + .join('\n')} + +${ident(2)}${options.accessControl} enum CodingKeys: String, CodingKey { +${attributeEntries + .map((entry) => `${ident(3)}case ${entry.formatted_name} = "${entry.raw_name}"`) + .join('\n')} +${ident(2)}} +${ident(1)}} +} +}` + + return output.trim() +} + export const apply = async ({ schemas, tables, views, + materializedViews, columns, types, accessControl, @@ -134,61 +186,95 @@ import Foundation import Supabase // MARK: - Enums -${enums.map((enum_) => generateEnum(enum_, { accessControl })).join('\n')} +${enums.map((enum_) => generateEnum(enum_, { accessControl })).join('\n\n')} // MARK: - Schemas -${schemas.map((schema) => `${accessControl} enum ${toUpperCamelCase(schema.name)}Schema {}`).join('\n')} +${schemas.map((schema) => `${accessControl} enum ${formatForSwiftTypeName(schema.name)}Schema {}`).join('\n\n')} // MARK: - Tables ${tables .flatMap((table) => generateTableStructsForOperations( schemas.find((schema) => schema.name === table.schema)!, - schemas, table, - tables, - views, columnsByTableId[table.id], - types, ['Select', 'Insert', 'Update'], - { accessControl } + { accessControl }, + { types, views, tables } ) ) - .join('\n')} + .join('\n\n')} + +// MARK: - Views +${views + .flatMap((view) => { + generateTableStructsForOperations( + schemas.find((schema) => schema.name === view.schema)!, + view, + columnsByTableId[view.id], + ['Select'], + { accessControl }, + { types, views, tables } + ) + }) + .join('\n\n')} + +// MARK: - Materialized Views +${materializedViews + .flatMap((materializedView) => + generateTableStructsForOperations( + schemas.find((schema) => schema.name === materializedView.schema)!, + materializedView, + columnsByTableId[materializedView.id], + ['Select'], + { accessControl }, + { types, views, tables } + ) + ) + .join('\n\n')} + +// MARK: - Composite Types +${compositeTypes + .map((compositeType) => + gnerateCompositeTypeStruct( + schemas.find((schema) => schema.name === compositeType.schema)!, + compositeType, + { accessControl }, + { types, views, tables } + ) + ) + .join('\n\n')} `.trim() return output } -// TODO: Make this more robust. Currently doesn't handle range types - returns them as unknown. +// TODO: Make this more robust. Currently doesn't handle range types - returns them as string. const pgTypeToSwiftType = ( pgType: string, + nullable: boolean, { types, - schemas, - tables, views, - }: { - types: PostgresType[] - schemas: PostgresSchema[] - tables: PostgresTable[] - views: PostgresView[] - } + tables, + }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } ): string => { + let swiftType: string + if (pgType === 'bool') { - return 'Bool' + swiftType = 'Bool' } else if (pgType === 'int2') { - return 'Int16' + swiftType = 'Int16' } else if (pgType === 'int4') { - return 'Int32' + swiftType = 'Int32' } else if (pgType === 'int8') { - return 'Int64' + swiftType = 'Int64' } else if (pgType === 'float4') { - return 'Float' + swiftType = 'Float' } else if (pgType === 'float8') { - return 'Double' + swiftType = 'Double' } else if (pgType === 'uuid') { - return 'UUID' + swiftType = 'UUID' } else if ( [ 'bytea', @@ -204,80 +290,68 @@ const pgTypeToSwiftType = ( 'vector', ].includes(pgType) ) { - return 'String' + swiftType = 'String' } else if (['json', 'jsonb'].includes(pgType)) { - return 'AnyJSON' + swiftType = 'AnyJSON' } else if (pgType === 'void') { - return 'Void' + swiftType = 'Void' } else if (pgType === 'record') { - return 'JSONObject' + swiftType = 'JSONObject' } else if (pgType.startsWith('_')) { - return `[${pgTypeToSwiftType(pgType.substring(1), { types, schemas, tables, views })}]` + swiftType = `[${pgTypeToSwiftType(pgType.substring(1), false, { types, views, tables })}]` } else { - const enumType = types.find((type) => type.name === pgType && type.enums.length > 0) - - if (enumType) { - return `${toUpperCamelCase(enumType.name)}` - } + const allTypes: { name: string; schema: string }[] = [...types, ...views, ...tables] + const type = allTypes.find((type) => type.name === pgType) - const compositeType = types.find((type) => type.name === pgType && type.attributes.length > 0) - if (compositeType) { - return `${toUpperCamelCase(compositeType.name)}` + if (type) { + swiftType = `${formatForSwiftTypeName(type.schema)}Schema.${formatForSwiftTypeName(type.name)}` + } else { + swiftType = 'AnyJSON' } - - const tableRowType = tables.find((table) => table.name === pgType) - if (tableRowType) { - return `${toUpperCamelCase(tableRowType.name)}` - } - - const viewRowType = views.find((view) => view.name === pgType) - if (viewRowType) { - return `${toUpperCamelCase(viewRowType.name)}` - } - - return 'unknown' } + + return `${swiftType}${nullable ? '?' : ''}` } function ident(level: number, options: { width: number } = { width: 2 }): string { return ' '.repeat(level * options.width) } -function toLowerCamelCase(input: string): string { - // Split the input string by spaces and non-alphanumeric characters - const words = input.split(/[\s\-_]+/) - - // Map over the words array to transform each word - const camelCaseWords = words.map((word, index) => { - // Lowercase the entire word - const lowerCasedWord = word.toLowerCase() - - // Capitalize the first letter if it's not the first word - if (index !== 0) { - return lowerCasedWord.charAt(0).toUpperCase() + lowerCasedWord.slice(1) - } - - // Return the word as-is if it's the first word - return lowerCasedWord - }) - - // Join the words back together - return camelCaseWords.join('') +/** + * Converts a Postgres name to PascalCase. + * + * @example + * ```ts + * formatForSwiftTypeName('pokedex') // Pokedex + * formatForSwiftTypeName('pokemon_center') // PokemonCenter + * formatForSwiftTypeName('victory-road') // VictoryRoad + * formatForSwiftTypeName('pokemon league') // PokemonLeague + * ``` + */ +function formatForSwiftTypeName(name: string): string { + return name + .split(/[^a-zA-Z0-9]/) + .map((word) => `${word[0].toUpperCase()}${word.slice(1)}`) + .join('') } -function toUpperCamelCase(input: string): string { - // Split the input string by spaces and non-alphanumeric characters - const words = input.split(/[\s\-_]+/) - - // Map over the words array to transform each word - const camelCaseWords = words.map((word) => { - // Lowercase the entire word - const lowerCasedWord = word.toLowerCase() - - // Capitalize the first letter of each word - return lowerCasedWord.charAt(0).toUpperCase() + lowerCasedWord.slice(1) - }) - - // Join the words back together - return camelCaseWords.join('') +/** + * Converts a Postgres name to pascalCase. + * + * @example + * ```ts + * formatForSwiftTypeName('pokedex') // pokedex + * formatForSwiftTypeName('pokemon_center') // pokemonCenter + * formatForSwiftTypeName('victory-road') // victoryRoad + * formatForSwiftTypeName('pokemon league') // pokemonLeague + * ``` + */ +function formatForSwiftPropertyName(name: string): string { + return name + .split(/[^a-zA-Z0-9]/) + .map((word, index) => { + const lowerWord = word.toLowerCase() + return index !== 0 ? lowerWord.charAt(0).toUpperCase() + lowerWord.slice(1) : lowerWord + }) + .join('') } diff --git a/test/server/typegen.ts b/test/server/typegen.ts index f5ee3591..717358a3 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -1774,6 +1774,7 @@ test('typegen: swift', async () => { case old = "old" case retired = "retired" } + internal enum UserStatus: String, Codable, Hashable, Sendable { case active = "ACTIVE" case inactive = "INACTIVE" @@ -1787,7 +1788,7 @@ test('typegen: swift', async () => { internal struct UsersSelect: Codable, Hashable, Sendable { internal let id: Int64 internal let name: String? - internal let status: UserStatus? + internal let status: PublicSchema.UserStatus? internal enum CodingKeys: String, CodingKey { case id = "id" @@ -1798,11 +1799,12 @@ test('typegen: swift', async () => { extension PublicSchema.UsersSelect: Identifiable { } + extension PublicSchema { internal struct UsersInsert: Codable, Hashable, Sendable { internal let id: Int64? internal let name: String? - internal let status: UserStatus? + internal let status: PublicSchema.UserStatus? internal enum CodingKeys: String, CodingKey { case id = "id" @@ -1810,11 +1812,12 @@ test('typegen: swift', async () => { case status = "status" } } + extension PublicSchema { internal struct UsersUpdate: Codable, Hashable, Sendable { internal let id: Int64? internal let name: String? - internal let status: UserStatus? + internal let status: PublicSchema.UserStatus? internal enum CodingKeys: String, CodingKey { case id = "id" @@ -1822,6 +1825,7 @@ test('typegen: swift', async () => { case status = "status" } } + extension PublicSchema { internal struct TodosSelect: Codable, Hashable, Sendable { internal let details: String? @@ -1837,6 +1841,7 @@ test('typegen: swift', async () => { extension PublicSchema.TodosSelect: Identifiable { } + extension PublicSchema { internal struct TodosInsert: Codable, Hashable, Sendable { internal let details: String? @@ -1849,6 +1854,7 @@ test('typegen: swift', async () => { case userId = "user-id" } } + extension PublicSchema { internal struct TodosUpdate: Codable, Hashable, Sendable { internal let details: String? @@ -1861,6 +1867,7 @@ test('typegen: swift', async () => { case userId = "user-id" } } + extension PublicSchema { internal struct UsersAuditSelect: Codable, Hashable, Sendable { internal let createdAt: String? @@ -1878,6 +1885,7 @@ test('typegen: swift', async () => { extension PublicSchema.UsersAuditSelect: Identifiable { } + extension PublicSchema { internal struct UsersAuditInsert: Codable, Hashable, Sendable { internal let createdAt: String? @@ -1892,6 +1900,7 @@ test('typegen: swift', async () => { case userId = "user_id" } } + extension PublicSchema { internal struct UsersAuditUpdate: Codable, Hashable, Sendable { internal let createdAt: String? @@ -1906,6 +1915,7 @@ test('typegen: swift', async () => { case userId = "user_id" } } + extension PublicSchema { internal struct UserDetailsSelect: Codable, Hashable, Sendable { internal let details: String? @@ -1916,6 +1926,7 @@ test('typegen: swift', async () => { case userId = "user_id" } } + extension PublicSchema { internal struct UserDetailsInsert: Codable, Hashable, Sendable { internal let details: String? @@ -1926,6 +1937,7 @@ test('typegen: swift', async () => { case userId = "user_id" } } + extension PublicSchema { internal struct UserDetailsUpdate: Codable, Hashable, Sendable { internal let details: String? @@ -1936,6 +1948,7 @@ test('typegen: swift', async () => { case userId = "user_id" } } + extension PublicSchema { internal struct EmptySelect: Codable, Hashable, Sendable { @@ -1944,6 +1957,7 @@ test('typegen: swift', async () => { } } + extension PublicSchema { internal struct EmptyInsert: Codable, Hashable, Sendable { @@ -1952,6 +1966,7 @@ test('typegen: swift', async () => { } } + extension PublicSchema { internal struct EmptyUpdate: Codable, Hashable, Sendable { @@ -1960,36 +1975,40 @@ test('typegen: swift', async () => { } } + extension PublicSchema { internal struct TableWithOtherTablesRowTypeSelect: Codable, Hashable, Sendable { - internal let col1: UserDetails? - internal let col2: AView? + internal let col1: PublicSchema.UserDetails? + internal let col2: PublicSchema.AView? internal enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } } + extension PublicSchema { internal struct TableWithOtherTablesRowTypeInsert: Codable, Hashable, Sendable { - internal let col1: UserDetails? - internal let col2: AView? + internal let col1: PublicSchema.UserDetails? + internal let col2: PublicSchema.AView? internal enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } } + extension PublicSchema { internal struct TableWithOtherTablesRowTypeUpdate: Codable, Hashable, Sendable { - internal let col1: UserDetails? - internal let col2: AView? + internal let col1: PublicSchema.UserDetails? + internal let col2: PublicSchema.AView? internal enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } } + extension PublicSchema { internal struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable { internal let name: String? @@ -2003,6 +2022,7 @@ test('typegen: swift', async () => { extension PublicSchema.TableWithPrimaryKeyOtherThanIdSelect: Identifiable { internal var id: Int64 { otherId } } + extension PublicSchema { internal struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable { internal let name: String? @@ -2013,6 +2033,7 @@ test('typegen: swift', async () => { case otherId = "other_id" } } + extension PublicSchema { internal struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable { internal let name: String? @@ -2023,6 +2044,7 @@ test('typegen: swift', async () => { case otherId = "other_id" } } + extension PublicSchema { internal struct CategorySelect: Codable, Hashable, Sendable { internal let id: Int32 @@ -2033,6 +2055,7 @@ test('typegen: swift', async () => { case name = "name" } } + extension PublicSchema { internal struct CategoryInsert: Codable, Hashable, Sendable { internal let id: Int32? @@ -2043,6 +2066,7 @@ test('typegen: swift', async () => { case name = "name" } } + extension PublicSchema { internal struct CategoryUpdate: Codable, Hashable, Sendable { internal let id: Int32? @@ -2053,6 +2077,7 @@ test('typegen: swift', async () => { case name = "name" } } + extension PublicSchema { internal struct MemesSelect: Codable, Hashable, Sendable { internal let category: Int32? @@ -2060,7 +2085,7 @@ test('typegen: swift', async () => { internal let id: Int32 internal let metadata: AnyJSON? internal let name: String - internal let status: MemeStatus? + internal let status: PublicSchema.MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" @@ -2071,6 +2096,7 @@ test('typegen: swift', async () => { case status = "status" } } + extension PublicSchema { internal struct MemesInsert: Codable, Hashable, Sendable { internal let category: Int32? @@ -2078,7 +2104,7 @@ test('typegen: swift', async () => { internal let id: Int32? internal let metadata: AnyJSON? internal let name: String - internal let status: MemeStatus? + internal let status: PublicSchema.MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" @@ -2089,6 +2115,7 @@ test('typegen: swift', async () => { case status = "status" } } + extension PublicSchema { internal struct MemesUpdate: Codable, Hashable, Sendable { internal let category: Int32? @@ -2096,7 +2123,7 @@ test('typegen: swift', async () => { internal let id: Int32? internal let metadata: AnyJSON? internal let name: String? - internal let status: MemeStatus? + internal let status: PublicSchema.MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" @@ -2106,6 +2133,39 @@ test('typegen: swift', async () => { case name = "name" case status = "status" } + } + + // MARK: - Views + + + + + + + // MARK: - Materialized Views + extension PublicSchema { + internal struct TodosMatviewSelect: Codable, Hashable, Sendable { + internal let details: String? + internal let id: Int64? + internal let userId: Int64? + + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + + // MARK: - Composite Types + extension PublicSchema { + internal struct CompositeTypeWithArrayAttribute: Codable, Hashable, Sendable { + internal let MyTextArray: AnyJSON + + internal enum CodingKeys: String, CodingKey { + case MyTextArray = "my_text_array" + } + } + } }" `) }) @@ -2126,6 +2186,7 @@ test('typegen: swift w/ public access control', async () => { case old = "old" case retired = "retired" } + public enum UserStatus: String, Codable, Hashable, Sendable { case active = "ACTIVE" case inactive = "INACTIVE" @@ -2139,7 +2200,7 @@ test('typegen: swift w/ public access control', async () => { public struct UsersSelect: Codable, Hashable, Sendable { public let id: Int64 public let name: String? - public let status: UserStatus? + public let status: PublicSchema.UserStatus? public enum CodingKeys: String, CodingKey { case id = "id" @@ -2150,11 +2211,12 @@ test('typegen: swift w/ public access control', async () => { extension PublicSchema.UsersSelect: Identifiable { } + extension PublicSchema { public struct UsersInsert: Codable, Hashable, Sendable { public let id: Int64? public let name: String? - public let status: UserStatus? + public let status: PublicSchema.UserStatus? public enum CodingKeys: String, CodingKey { case id = "id" @@ -2162,11 +2224,12 @@ test('typegen: swift w/ public access control', async () => { case status = "status" } } + extension PublicSchema { public struct UsersUpdate: Codable, Hashable, Sendable { public let id: Int64? public let name: String? - public let status: UserStatus? + public let status: PublicSchema.UserStatus? public enum CodingKeys: String, CodingKey { case id = "id" @@ -2174,6 +2237,7 @@ test('typegen: swift w/ public access control', async () => { case status = "status" } } + extension PublicSchema { public struct TodosSelect: Codable, Hashable, Sendable { public let details: String? @@ -2189,6 +2253,7 @@ test('typegen: swift w/ public access control', async () => { extension PublicSchema.TodosSelect: Identifiable { } + extension PublicSchema { public struct TodosInsert: Codable, Hashable, Sendable { public let details: String? @@ -2201,6 +2266,7 @@ test('typegen: swift w/ public access control', async () => { case userId = "user-id" } } + extension PublicSchema { public struct TodosUpdate: Codable, Hashable, Sendable { public let details: String? @@ -2213,6 +2279,7 @@ test('typegen: swift w/ public access control', async () => { case userId = "user-id" } } + extension PublicSchema { public struct UsersAuditSelect: Codable, Hashable, Sendable { public let createdAt: String? @@ -2230,6 +2297,7 @@ test('typegen: swift w/ public access control', async () => { extension PublicSchema.UsersAuditSelect: Identifiable { } + extension PublicSchema { public struct UsersAuditInsert: Codable, Hashable, Sendable { public let createdAt: String? @@ -2244,6 +2312,7 @@ test('typegen: swift w/ public access control', async () => { case userId = "user_id" } } + extension PublicSchema { public struct UsersAuditUpdate: Codable, Hashable, Sendable { public let createdAt: String? @@ -2258,6 +2327,7 @@ test('typegen: swift w/ public access control', async () => { case userId = "user_id" } } + extension PublicSchema { public struct UserDetailsSelect: Codable, Hashable, Sendable { public let details: String? @@ -2268,6 +2338,7 @@ test('typegen: swift w/ public access control', async () => { case userId = "user_id" } } + extension PublicSchema { public struct UserDetailsInsert: Codable, Hashable, Sendable { public let details: String? @@ -2278,6 +2349,7 @@ test('typegen: swift w/ public access control', async () => { case userId = "user_id" } } + extension PublicSchema { public struct UserDetailsUpdate: Codable, Hashable, Sendable { public let details: String? @@ -2288,6 +2360,7 @@ test('typegen: swift w/ public access control', async () => { case userId = "user_id" } } + extension PublicSchema { public struct EmptySelect: Codable, Hashable, Sendable { @@ -2296,6 +2369,7 @@ test('typegen: swift w/ public access control', async () => { } } + extension PublicSchema { public struct EmptyInsert: Codable, Hashable, Sendable { @@ -2304,6 +2378,7 @@ test('typegen: swift w/ public access control', async () => { } } + extension PublicSchema { public struct EmptyUpdate: Codable, Hashable, Sendable { @@ -2312,36 +2387,40 @@ test('typegen: swift w/ public access control', async () => { } } + extension PublicSchema { public struct TableWithOtherTablesRowTypeSelect: Codable, Hashable, Sendable { - public let col1: UserDetails? - public let col2: AView? + public let col1: PublicSchema.UserDetails? + public let col2: PublicSchema.AView? public enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } } + extension PublicSchema { public struct TableWithOtherTablesRowTypeInsert: Codable, Hashable, Sendable { - public let col1: UserDetails? - public let col2: AView? + public let col1: PublicSchema.UserDetails? + public let col2: PublicSchema.AView? public enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } } + extension PublicSchema { public struct TableWithOtherTablesRowTypeUpdate: Codable, Hashable, Sendable { - public let col1: UserDetails? - public let col2: AView? + public let col1: PublicSchema.UserDetails? + public let col2: PublicSchema.AView? public enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } } + extension PublicSchema { public struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable { public let name: String? @@ -2355,6 +2434,7 @@ test('typegen: swift w/ public access control', async () => { extension PublicSchema.TableWithPrimaryKeyOtherThanIdSelect: Identifiable { public var id: Int64 { otherId } } + extension PublicSchema { public struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable { public let name: String? @@ -2365,6 +2445,7 @@ test('typegen: swift w/ public access control', async () => { case otherId = "other_id" } } + extension PublicSchema { public struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable { public let name: String? @@ -2375,6 +2456,7 @@ test('typegen: swift w/ public access control', async () => { case otherId = "other_id" } } + extension PublicSchema { public struct CategorySelect: Codable, Hashable, Sendable { public let id: Int32 @@ -2385,6 +2467,7 @@ test('typegen: swift w/ public access control', async () => { case name = "name" } } + extension PublicSchema { public struct CategoryInsert: Codable, Hashable, Sendable { public let id: Int32? @@ -2395,6 +2478,7 @@ test('typegen: swift w/ public access control', async () => { case name = "name" } } + extension PublicSchema { public struct CategoryUpdate: Codable, Hashable, Sendable { public let id: Int32? @@ -2405,6 +2489,7 @@ test('typegen: swift w/ public access control', async () => { case name = "name" } } + extension PublicSchema { public struct MemesSelect: Codable, Hashable, Sendable { public let category: Int32? @@ -2412,7 +2497,7 @@ test('typegen: swift w/ public access control', async () => { public let id: Int32 public let metadata: AnyJSON? public let name: String - public let status: MemeStatus? + public let status: PublicSchema.MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" @@ -2423,6 +2508,7 @@ test('typegen: swift w/ public access control', async () => { case status = "status" } } + extension PublicSchema { public struct MemesInsert: Codable, Hashable, Sendable { public let category: Int32? @@ -2430,7 +2516,7 @@ test('typegen: swift w/ public access control', async () => { public let id: Int32? public let metadata: AnyJSON? public let name: String - public let status: MemeStatus? + public let status: PublicSchema.MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" @@ -2441,6 +2527,7 @@ test('typegen: swift w/ public access control', async () => { case status = "status" } } + extension PublicSchema { public struct MemesUpdate: Codable, Hashable, Sendable { public let category: Int32? @@ -2448,7 +2535,7 @@ test('typegen: swift w/ public access control', async () => { public let id: Int32? public let metadata: AnyJSON? public let name: String? - public let status: MemeStatus? + public let status: PublicSchema.MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" @@ -2458,6 +2545,39 @@ test('typegen: swift w/ public access control', async () => { case name = "name" case status = "status" } + } + + // MARK: - Views + + + + + + + // MARK: - Materialized Views + extension PublicSchema { + public struct TodosMatviewSelect: Codable, Hashable, Sendable { + public let details: String? + public let id: Int64? + public let userId: Int64? + + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + + // MARK: - Composite Types + extension PublicSchema { + public struct CompositeTypeWithArrayAttribute: Codable, Hashable, Sendable { + public let MyTextArray: AnyJSON + + public enum CodingKeys: String, CodingKey { + case MyTextArray = "my_text_array" + } + } + } }" `) }) From d54fa21642f6ebecec8953d61778dac94e88184b Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Fri, 14 Jun 2024 09:59:43 -0300 Subject: [PATCH 6/6] refactor swift typegen --- src/server/templates/swift.ts | 330 +++++++++++--------- test/server/typegen.ts | 558 +++++++++++----------------------- 2 files changed, 364 insertions(+), 524 deletions(-) diff --git a/src/server/templates/swift.ts b/src/server/templates/swift.ts index 66e469fb..a10972e4 100644 --- a/src/server/templates/swift.ts +++ b/src/server/templates/swift.ts @@ -17,48 +17,56 @@ type SwiftGeneratorOptions = { accessControl: AccessControl } -function generateEnum(enum_: PostgresType, options: SwiftGeneratorOptions): string { - return ` -${options.accessControl} enum ${formatForSwiftTypeName(enum_.name)}: String, Codable, Hashable, Sendable { -${enum_.enums.map((case_) => `${ident(1)}case ${formatForSwiftPropertyName(case_)} = "${case_}"`).join('\n')} +type SwiftEnumCase = { + formattedName: string + rawValue: string } -`.trim() + +type SwiftEnum = { + formattedEnumName: string + protocolConformances: string[] + cases: SwiftEnumCase[] } -function generateTableStructsForOperations( - schema: PostgresSchema, - table: PostgresTable | PostgresView | PostgresMaterializedView, - columns: PostgresColumn[] | undefined, - operations: Operation[], - options: SwiftGeneratorOptions, - { - types, - views, - tables, - }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } -): string[] { - return operations.map((operation) => - generateTableStruct(schema, table, columns, operation, options, { types, views, tables }) - ) +type SwiftAttribute = { + formattedAttributeName: string + formattedType: string + rawName: string + isIdentity: boolean +} + +type SwiftStruct = { + formattedStructName: string + protocolConformances: string[] + attributes: SwiftAttribute[] + codingKeysEnum: SwiftEnum | undefined +} + +function formatForSwiftSchemaName(schema: string): string { + return `${formatForSwiftTypeName(schema)}Schema` +} + +function pgEnumToSwiftEnum(pgEnum: PostgresType): SwiftEnum { + return { + formattedEnumName: formatForSwiftTypeName(pgEnum.name), + protocolConformances: ['String', 'Codable', 'Hashable', 'Sendable'], + cases: pgEnum.enums.map((case_) => { + return { formattedName: formatForSwiftPropertyName(case_), rawValue: case_ } + }), + } } -function generateTableStruct( - schema: PostgresSchema, +function pgTypeToSwiftStruct( table: PostgresTable | PostgresView | PostgresMaterializedView, columns: PostgresColumn[] | undefined, operation: Operation, - options: SwiftGeneratorOptions, { types, views, tables, }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } -): string { - const columnEntries: { - raw_name: string - formatted_name: string - type: string - }[] = +): SwiftStruct { + const columnEntries: SwiftAttribute[] = columns?.map((column) => { let nullable: boolean @@ -72,49 +80,44 @@ function generateTableStruct( } return { - raw_name: column.name, - formatted_name: formatForSwiftPropertyName(column.name), - type: pgTypeToSwiftType(column.format, nullable, { types, views, tables }), + rawName: column.name, + formattedAttributeName: formatForSwiftPropertyName(column.name), + formattedType: pgTypeToSwiftType(column.format, nullable, { types, views, tables }), + isIdentity: column.is_identity, } }) ?? [] - const identity = columns?.find((column) => column.is_identity) - const structName = `${formatForSwiftTypeName(table.name)}${operation}` - - let output = ` -extension ${formatForSwiftTypeName(schema.name)}Schema { -${ident(1)}${options.accessControl} struct ${structName}: Codable, Hashable, Sendable { -${columnEntries.map(({ formatted_name, type }) => `${ident(2)}${options.accessControl} let ${formatted_name}: ${type}`).join('\n')} - -${ident(2)}${options.accessControl} enum CodingKeys: String, CodingKey { -${columnEntries.map(({ raw_name, formatted_name }) => `${ident(3)}case ${formatted_name} = "${raw_name}"`).join('\n')} -${ident(2)}} -} -` - - if (operation === 'Select' && identity) { - const identityEntry = columnEntries.find((entry) => entry.raw_name === identity.name) - if (identityEntry) { - output += `extension ${formatForSwiftTypeName(schema.name)}Schema.${structName}: Identifiable { -${identityEntry.formatted_name !== 'id' ? `${ident(2)}${options.accessControl} var id: ${identityEntry.type} { ${identityEntry.formatted_name} }` : ''} -} - ` - } + return { + formattedStructName: `${formatForSwiftTypeName(table.name)}${operation}`, + attributes: columnEntries, + protocolConformances: ['Codable', 'Hashable', 'Sendable'], + codingKeysEnum: generateCodingKeysEnumFromAttributes(columnEntries), } +} - return output.trim() +function generateCodingKeysEnumFromAttributes(attributes: SwiftAttribute[]): SwiftEnum | undefined { + return attributes.length > 0 + ? { + formattedEnumName: 'CodingKeys', + protocolConformances: ['String', 'CodingKey'], + cases: attributes.map((attribute) => { + return { + formattedName: attribute.formattedAttributeName, + rawValue: attribute.rawName, + } + }), + } + : undefined } -function gnerateCompositeTypeStruct( - schema: PostgresSchema, +function pgCompositeTypeToSwiftStruct( type: PostgresType, - options: SwiftGeneratorOptions, { types, views, tables, }: { types: PostgresType[]; views: PostgresView[]; tables: PostgresTable[] } -): string { +): SwiftStruct { const typeWithRetrievedAttributes = { ...type, attributes: type.attributes.map((attribute) => { @@ -126,34 +129,77 @@ function gnerateCompositeTypeStruct( }), } - const attributeEntries: { - formatted_name: string - type: string - raw_name: string - }[] = typeWithRetrievedAttributes.attributes.map((attribute) => { - return { - formatted_name: formatForSwiftTypeName(attribute.name), - type: pgTypeToSwiftType(attribute.type!.format, false, { types, views, tables }), - raw_name: attribute.name, + const attributeEntries: SwiftAttribute[] = typeWithRetrievedAttributes.attributes.map( + (attribute) => { + return { + formattedAttributeName: formatForSwiftTypeName(attribute.name), + formattedType: pgTypeToSwiftType(attribute.type!.format, false, { types, views, tables }), + rawName: attribute.name, + isIdentity: false, + } } - }) + ) + + return { + formattedStructName: formatForSwiftTypeName(type.name), + attributes: attributeEntries, + protocolConformances: ['Codable', 'Hashable', 'Sendable'], + codingKeysEnum: generateCodingKeysEnumFromAttributes(attributeEntries), + } +} + +function generateProtocolConformances(protocols: string[]): string { + return protocols.length === 0 ? '' : `: ${protocols.join(', ')}` +} - let output = `extension ${formatForSwiftTypeName(schema.name)}Schema { -${ident(1)}${options.accessControl} struct ${formatForSwiftTypeName(type.name)}: Codable, Hashable, Sendable { -${attributeEntries - .map((entry) => `${ident(2)}${options.accessControl} let ${entry.formatted_name}: ${entry.type}`) - .join('\n')} - -${ident(2)}${options.accessControl} enum CodingKeys: String, CodingKey { -${attributeEntries - .map((entry) => `${ident(3)}case ${entry.formatted_name} = "${entry.raw_name}"`) - .join('\n')} -${ident(2)}} -${ident(1)}} +function generateEnum( + enum_: SwiftEnum, + { accessControl, level }: SwiftGeneratorOptions & { level: number } +): string[] { + return [ + `${ident(level)}${accessControl} enum ${enum_.formattedEnumName}${generateProtocolConformances(enum_.protocolConformances)} {`, + ...enum_.cases.map( + (case_) => `${ident(level + 1)}case ${case_.formattedName} = "${case_.rawValue}"` + ), + `${ident(level)}}`, + ] } -}` - return output.trim() +function generateStruct( + struct: SwiftStruct, + { accessControl, level }: SwiftGeneratorOptions & { level: number } +): string[] { + const identity = struct.attributes.find((column) => column.isIdentity) + + let protocolConformances = struct.protocolConformances + if (identity) { + protocolConformances.push('Identifiable') + } + + let output = [ + `${ident(level)}${accessControl} struct ${struct.formattedStructName}${generateProtocolConformances(struct.protocolConformances)} {`, + ] + + if (identity && identity.formattedAttributeName !== 'id') { + output.push( + `${ident(level + 1)}${accessControl} var id: ${identity.formattedType} { ${identity.formattedAttributeName} }` + ) + } + + output.push( + ...struct.attributes.map( + (attribute) => + `${ident(level + 1)}${accessControl} let ${attribute.formattedAttributeName}: ${attribute.formattedType}` + ) + ) + + if (struct.codingKeysEnum) { + output.push(...generateEnum(struct.codingKeysEnum, { accessControl, level: level + 1 })) + } + + output.push(`${ident(level)}}`) + + return output } export const apply = async ({ @@ -181,72 +227,56 @@ export const apply = async ({ .filter((type) => type.enums.length > 0) .sort(({ name: a }, { name: b }) => a.localeCompare(b)) - let output = ` -import Foundation -import Supabase - -// MARK: - Enums -${enums.map((enum_) => generateEnum(enum_, { accessControl })).join('\n\n')} - -// MARK: - Schemas -${schemas.map((schema) => `${accessControl} enum ${formatForSwiftTypeName(schema.name)}Schema {}`).join('\n\n')} - -// MARK: - Tables -${tables - .flatMap((table) => - generateTableStructsForOperations( - schemas.find((schema) => schema.name === table.schema)!, - table, - columnsByTableId[table.id], - ['Select', 'Insert', 'Update'], - { accessControl }, - { types, views, tables } - ) - ) - .join('\n\n')} - -// MARK: - Views -${views - .flatMap((view) => { - generateTableStructsForOperations( - schemas.find((schema) => schema.name === view.schema)!, - view, - columnsByTableId[view.id], - ['Select'], - { accessControl }, - { types, views, tables } - ) + const swiftEnums = enums.map((enum_) => { + return { schema: enum_.schema, enum_: pgEnumToSwiftEnum(enum_) } }) - .join('\n\n')} - -// MARK: - Materialized Views -${materializedViews - .flatMap((materializedView) => - generateTableStructsForOperations( - schemas.find((schema) => schema.name === materializedView.schema)!, - materializedView, - columnsByTableId[materializedView.id], - ['Select'], - { accessControl }, - { types, views, tables } + + const swiftStructForTables = tables.flatMap((table) => + (['Select', 'Insert', 'Update'] as Operation[]).map((operation) => + pgTypeToSwiftStruct(table, columnsByTableId[table.id], operation, { types, views, tables }) ) ) - .join('\n\n')} - -// MARK: - Composite Types -${compositeTypes - .map((compositeType) => - gnerateCompositeTypeStruct( - schemas.find((schema) => schema.name === compositeType.schema)!, - compositeType, - { accessControl }, - { types, views, tables } - ) + + const swiftStructForViews = views.map((view) => + pgTypeToSwiftStruct(view, columnsByTableId[view.id], 'Select', { types, views, tables }) ) - .join('\n\n')} -`.trim() - return output + const swiftStructForMaterializedViews = materializedViews.map((materializedView) => + pgTypeToSwiftStruct(materializedView, columnsByTableId[materializedView.id], 'Select', { + types, + views, + tables, + }) + ) + + const swiftStructForCompositeTypes = compositeTypes.map((type) => + pgCompositeTypeToSwiftStruct(type, { types, views, tables }) + ) + + let output = [ + 'import Foundation', + 'import Supabase', + '', + ...schemas.flatMap((schema) => [ + `${accessControl} enum ${formatForSwiftSchemaName(schema.name)} {`, + ...swiftEnums.flatMap(({ enum_ }) => generateEnum(enum_, { accessControl, level: 1 })), + ...swiftStructForTables.flatMap((struct) => + generateStruct(struct, { accessControl, level: 1 }) + ), + ...swiftStructForViews.flatMap((struct) => + generateStruct(struct, { accessControl, level: 1 }) + ), + ...swiftStructForMaterializedViews.flatMap((struct) => + generateStruct(struct, { accessControl, level: 1 }) + ), + ...swiftStructForCompositeTypes.flatMap((struct) => + generateStruct(struct, { accessControl, level: 1 }) + ), + '}', + ]), + ] + + return output.join('\n') } // TODO: Make this more robust. Currently doesn't handle range types - returns them as string. @@ -300,11 +330,15 @@ const pgTypeToSwiftType = ( } else if (pgType.startsWith('_')) { swiftType = `[${pgTypeToSwiftType(pgType.substring(1), false, { types, views, tables })}]` } else { - const allTypes: { name: string; schema: string }[] = [...types, ...views, ...tables] - const type = allTypes.find((type) => type.name === pgType) + const enumType = types.find((type) => type.name === pgType && type.enums.length > 0) + + const compositeTypes = [...types, ...views, ...tables].find((type) => type.name === pgType) - if (type) { - swiftType = `${formatForSwiftTypeName(type.schema)}Schema.${formatForSwiftTypeName(type.name)}` + if (enumType) { + swiftType = `${formatForSwiftTypeName(enumType.name)}` + } else if (compositeTypes) { + // Append a `Select` to the composite type, as that is how is named in the generated struct. + swiftType = `${formatForSwiftTypeName(compositeTypes.name)}Select` } else { swiftType = 'AnyJSON' } diff --git a/test/server/typegen.ts b/test/server/typegen.ts index 717358a3..19744628 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -1768,325 +1768,224 @@ test('typegen: swift', async () => { "import Foundation import Supabase - // MARK: - Enums - internal enum MemeStatus: String, Codable, Hashable, Sendable { - case new = "new" - case old = "old" - case retired = "retired" - } - - internal enum UserStatus: String, Codable, Hashable, Sendable { - case active = "ACTIVE" - case inactive = "INACTIVE" - } - - // MARK: - Schemas - internal enum PublicSchema {} - - // MARK: - Tables - extension PublicSchema { - internal struct UsersSelect: Codable, Hashable, Sendable { + internal enum PublicSchema { + internal enum MemeStatus: String, Codable, Hashable, Sendable { + case new = "new" + case old = "old" + case retired = "retired" + } + internal enum UserStatus: String, Codable, Hashable, Sendable { + case active = "ACTIVE" + case inactive = "INACTIVE" + } + internal struct UsersSelect: Codable, Hashable, Sendable, Identifiable { internal let id: Int64 internal let name: String? - internal let status: PublicSchema.UserStatus? - + internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case status = "status" } - } - extension PublicSchema.UsersSelect: Identifiable { - - } - - extension PublicSchema { - internal struct UsersInsert: Codable, Hashable, Sendable { + } + internal struct UsersInsert: Codable, Hashable, Sendable, Identifiable { internal let id: Int64? internal let name: String? - internal let status: PublicSchema.UserStatus? - + internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case status = "status" } - } - - extension PublicSchema { - internal struct UsersUpdate: Codable, Hashable, Sendable { + } + internal struct UsersUpdate: Codable, Hashable, Sendable, Identifiable { internal let id: Int64? internal let name: String? - internal let status: PublicSchema.UserStatus? - + internal let status: UserStatus? internal enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case status = "status" } - } - - extension PublicSchema { - internal struct TodosSelect: Codable, Hashable, Sendable { + } + internal struct TodosSelect: Codable, Hashable, Sendable, Identifiable { internal let details: String? internal let id: Int64 internal let userId: Int64 - internal enum CodingKeys: String, CodingKey { case details = "details" case id = "id" case userId = "user-id" } - } - extension PublicSchema.TodosSelect: Identifiable { - - } - - extension PublicSchema { - internal struct TodosInsert: Codable, Hashable, Sendable { + } + internal struct TodosInsert: Codable, Hashable, Sendable, Identifiable { internal let details: String? internal let id: Int64? internal let userId: Int64 - internal enum CodingKeys: String, CodingKey { case details = "details" case id = "id" case userId = "user-id" } - } - - extension PublicSchema { - internal struct TodosUpdate: Codable, Hashable, Sendable { + } + internal struct TodosUpdate: Codable, Hashable, Sendable, Identifiable { internal let details: String? internal let id: Int64? internal let userId: Int64? - internal enum CodingKeys: String, CodingKey { case details = "details" case id = "id" case userId = "user-id" } - } - - extension PublicSchema { - internal struct UsersAuditSelect: Codable, Hashable, Sendable { + } + internal struct UsersAuditSelect: Codable, Hashable, Sendable, Identifiable { internal let createdAt: String? internal let id: Int64 internal let previousValue: AnyJSON? internal let userId: Int64? - internal enum CodingKeys: String, CodingKey { case createdAt = "created_at" case id = "id" case previousValue = "previous_value" case userId = "user_id" } - } - extension PublicSchema.UsersAuditSelect: Identifiable { - - } - - extension PublicSchema { - internal struct UsersAuditInsert: Codable, Hashable, Sendable { + } + internal struct UsersAuditInsert: Codable, Hashable, Sendable, Identifiable { internal let createdAt: String? internal let id: Int64? internal let previousValue: AnyJSON? internal let userId: Int64? - internal enum CodingKeys: String, CodingKey { case createdAt = "created_at" case id = "id" case previousValue = "previous_value" case userId = "user_id" } - } - - extension PublicSchema { - internal struct UsersAuditUpdate: Codable, Hashable, Sendable { + } + internal struct UsersAuditUpdate: Codable, Hashable, Sendable, Identifiable { internal let createdAt: String? internal let id: Int64? internal let previousValue: AnyJSON? internal let userId: Int64? - internal enum CodingKeys: String, CodingKey { case createdAt = "created_at" case id = "id" case previousValue = "previous_value" case userId = "user_id" } - } - - extension PublicSchema { + } internal struct UserDetailsSelect: Codable, Hashable, Sendable { internal let details: String? internal let userId: Int64 - internal enum CodingKeys: String, CodingKey { case details = "details" case userId = "user_id" } - } - - extension PublicSchema { + } internal struct UserDetailsInsert: Codable, Hashable, Sendable { internal let details: String? internal let userId: Int64 - internal enum CodingKeys: String, CodingKey { case details = "details" case userId = "user_id" } - } - - extension PublicSchema { + } internal struct UserDetailsUpdate: Codable, Hashable, Sendable { internal let details: String? internal let userId: Int64? - internal enum CodingKeys: String, CodingKey { case details = "details" case userId = "user_id" } - } - - extension PublicSchema { + } internal struct EmptySelect: Codable, Hashable, Sendable { - - - internal enum CodingKeys: String, CodingKey { - - } - } - - extension PublicSchema { + } internal struct EmptyInsert: Codable, Hashable, Sendable { - - - internal enum CodingKeys: String, CodingKey { - - } - } - - extension PublicSchema { + } internal struct EmptyUpdate: Codable, Hashable, Sendable { - - - internal enum CodingKeys: String, CodingKey { - - } - } - - extension PublicSchema { + } internal struct TableWithOtherTablesRowTypeSelect: Codable, Hashable, Sendable { - internal let col1: PublicSchema.UserDetails? - internal let col2: PublicSchema.AView? - + internal let col1: UserDetailsSelect? + internal let col2: AViewSelect? internal enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } - } - - extension PublicSchema { + } internal struct TableWithOtherTablesRowTypeInsert: Codable, Hashable, Sendable { - internal let col1: PublicSchema.UserDetails? - internal let col2: PublicSchema.AView? - + internal let col1: UserDetailsSelect? + internal let col2: AViewSelect? internal enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } - } - - extension PublicSchema { + } internal struct TableWithOtherTablesRowTypeUpdate: Codable, Hashable, Sendable { - internal let col1: PublicSchema.UserDetails? - internal let col2: PublicSchema.AView? - + internal let col1: UserDetailsSelect? + internal let col2: AViewSelect? internal enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } - } - - extension PublicSchema { - internal struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable { + } + internal struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable, Identifiable { + internal var id: Int64 { otherId } internal let name: String? internal let otherId: Int64 - internal enum CodingKeys: String, CodingKey { case name = "name" case otherId = "other_id" } - } - extension PublicSchema.TableWithPrimaryKeyOtherThanIdSelect: Identifiable { - internal var id: Int64 { otherId } - } - - extension PublicSchema { - internal struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable { + } + internal struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable, Identifiable { + internal var id: Int64? { otherId } internal let name: String? internal let otherId: Int64? - internal enum CodingKeys: String, CodingKey { case name = "name" case otherId = "other_id" } - } - - extension PublicSchema { - internal struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable { + } + internal struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable, Identifiable { + internal var id: Int64? { otherId } internal let name: String? internal let otherId: Int64? - internal enum CodingKeys: String, CodingKey { case name = "name" case otherId = "other_id" } - } - - extension PublicSchema { + } internal struct CategorySelect: Codable, Hashable, Sendable { internal let id: Int32 internal let name: String - internal enum CodingKeys: String, CodingKey { case id = "id" case name = "name" } - } - - extension PublicSchema { + } internal struct CategoryInsert: Codable, Hashable, Sendable { internal let id: Int32? internal let name: String - internal enum CodingKeys: String, CodingKey { case id = "id" case name = "name" } - } - - extension PublicSchema { + } internal struct CategoryUpdate: Codable, Hashable, Sendable { internal let id: Int32? internal let name: String? - internal enum CodingKeys: String, CodingKey { case id = "id" case name = "name" } - } - - extension PublicSchema { + } internal struct MemesSelect: Codable, Hashable, Sendable { internal let category: Int32? internal let createdAt: String internal let id: Int32 internal let metadata: AnyJSON? internal let name: String - internal let status: PublicSchema.MemeStatus? - + internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" @@ -2095,17 +1994,14 @@ test('typegen: swift', async () => { case name = "name" case status = "status" } - } - - extension PublicSchema { + } internal struct MemesInsert: Codable, Hashable, Sendable { internal let category: Int32? internal let createdAt: String internal let id: Int32? internal let metadata: AnyJSON? internal let name: String - internal let status: PublicSchema.MemeStatus? - + internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" @@ -2114,17 +2010,14 @@ test('typegen: swift', async () => { case name = "name" case status = "status" } - } - - extension PublicSchema { + } internal struct MemesUpdate: Codable, Hashable, Sendable { internal let category: Int32? internal let createdAt: String? internal let id: Int32? internal let metadata: AnyJSON? internal let name: String? - internal let status: PublicSchema.MemeStatus? - + internal let status: MemeStatus? internal enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" @@ -2133,39 +2026,49 @@ test('typegen: swift', async () => { case name = "name" case status = "status" } - } - - // MARK: - Views - - - - - - - // MARK: - Materialized Views - extension PublicSchema { + } + internal struct TodosViewSelect: Codable, Hashable, Sendable { + internal let details: String? + internal let id: Int64? + internal let userId: Int64? + internal enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + internal struct UsersViewSelect: Codable, Hashable, Sendable { + internal let id: Int64? + internal let name: String? + internal let status: UserStatus? + internal enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + internal struct AViewSelect: Codable, Hashable, Sendable { + internal let id: Int64? + internal enum CodingKeys: String, CodingKey { + case id = "id" + } + } internal struct TodosMatviewSelect: Codable, Hashable, Sendable { internal let details: String? internal let id: Int64? internal let userId: Int64? - internal enum CodingKeys: String, CodingKey { case details = "details" case id = "id" case userId = "user-id" } - } - - // MARK: - Composite Types - extension PublicSchema { + } internal struct CompositeTypeWithArrayAttribute: Codable, Hashable, Sendable { internal let MyTextArray: AnyJSON - internal enum CodingKeys: String, CodingKey { case MyTextArray = "my_text_array" } } - } }" `) }) @@ -2180,325 +2083,224 @@ test('typegen: swift w/ public access control', async () => { "import Foundation import Supabase - // MARK: - Enums - public enum MemeStatus: String, Codable, Hashable, Sendable { - case new = "new" - case old = "old" - case retired = "retired" - } - - public enum UserStatus: String, Codable, Hashable, Sendable { - case active = "ACTIVE" - case inactive = "INACTIVE" - } - - // MARK: - Schemas - public enum PublicSchema {} - - // MARK: - Tables - extension PublicSchema { - public struct UsersSelect: Codable, Hashable, Sendable { + public enum PublicSchema { + public enum MemeStatus: String, Codable, Hashable, Sendable { + case new = "new" + case old = "old" + case retired = "retired" + } + public enum UserStatus: String, Codable, Hashable, Sendable { + case active = "ACTIVE" + case inactive = "INACTIVE" + } + public struct UsersSelect: Codable, Hashable, Sendable, Identifiable { public let id: Int64 public let name: String? - public let status: PublicSchema.UserStatus? - + public let status: UserStatus? public enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case status = "status" } - } - extension PublicSchema.UsersSelect: Identifiable { - - } - - extension PublicSchema { - public struct UsersInsert: Codable, Hashable, Sendable { + } + public struct UsersInsert: Codable, Hashable, Sendable, Identifiable { public let id: Int64? public let name: String? - public let status: PublicSchema.UserStatus? - + public let status: UserStatus? public enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case status = "status" } - } - - extension PublicSchema { - public struct UsersUpdate: Codable, Hashable, Sendable { + } + public struct UsersUpdate: Codable, Hashable, Sendable, Identifiable { public let id: Int64? public let name: String? - public let status: PublicSchema.UserStatus? - + public let status: UserStatus? public enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case status = "status" } - } - - extension PublicSchema { - public struct TodosSelect: Codable, Hashable, Sendable { + } + public struct TodosSelect: Codable, Hashable, Sendable, Identifiable { public let details: String? public let id: Int64 public let userId: Int64 - public enum CodingKeys: String, CodingKey { case details = "details" case id = "id" case userId = "user-id" } - } - extension PublicSchema.TodosSelect: Identifiable { - - } - - extension PublicSchema { - public struct TodosInsert: Codable, Hashable, Sendable { + } + public struct TodosInsert: Codable, Hashable, Sendable, Identifiable { public let details: String? public let id: Int64? public let userId: Int64 - public enum CodingKeys: String, CodingKey { case details = "details" case id = "id" case userId = "user-id" } - } - - extension PublicSchema { - public struct TodosUpdate: Codable, Hashable, Sendable { + } + public struct TodosUpdate: Codable, Hashable, Sendable, Identifiable { public let details: String? public let id: Int64? public let userId: Int64? - public enum CodingKeys: String, CodingKey { case details = "details" case id = "id" case userId = "user-id" } - } - - extension PublicSchema { - public struct UsersAuditSelect: Codable, Hashable, Sendable { + } + public struct UsersAuditSelect: Codable, Hashable, Sendable, Identifiable { public let createdAt: String? public let id: Int64 public let previousValue: AnyJSON? public let userId: Int64? - public enum CodingKeys: String, CodingKey { case createdAt = "created_at" case id = "id" case previousValue = "previous_value" case userId = "user_id" } - } - extension PublicSchema.UsersAuditSelect: Identifiable { - - } - - extension PublicSchema { - public struct UsersAuditInsert: Codable, Hashable, Sendable { + } + public struct UsersAuditInsert: Codable, Hashable, Sendable, Identifiable { public let createdAt: String? public let id: Int64? public let previousValue: AnyJSON? public let userId: Int64? - public enum CodingKeys: String, CodingKey { case createdAt = "created_at" case id = "id" case previousValue = "previous_value" case userId = "user_id" } - } - - extension PublicSchema { - public struct UsersAuditUpdate: Codable, Hashable, Sendable { + } + public struct UsersAuditUpdate: Codable, Hashable, Sendable, Identifiable { public let createdAt: String? public let id: Int64? public let previousValue: AnyJSON? public let userId: Int64? - public enum CodingKeys: String, CodingKey { case createdAt = "created_at" case id = "id" case previousValue = "previous_value" case userId = "user_id" } - } - - extension PublicSchema { + } public struct UserDetailsSelect: Codable, Hashable, Sendable { public let details: String? public let userId: Int64 - public enum CodingKeys: String, CodingKey { case details = "details" case userId = "user_id" } - } - - extension PublicSchema { + } public struct UserDetailsInsert: Codable, Hashable, Sendable { public let details: String? public let userId: Int64 - public enum CodingKeys: String, CodingKey { case details = "details" case userId = "user_id" } - } - - extension PublicSchema { + } public struct UserDetailsUpdate: Codable, Hashable, Sendable { public let details: String? public let userId: Int64? - public enum CodingKeys: String, CodingKey { case details = "details" case userId = "user_id" } - } - - extension PublicSchema { + } public struct EmptySelect: Codable, Hashable, Sendable { - - - public enum CodingKeys: String, CodingKey { - - } - } - - extension PublicSchema { + } public struct EmptyInsert: Codable, Hashable, Sendable { - - - public enum CodingKeys: String, CodingKey { - - } - } - - extension PublicSchema { + } public struct EmptyUpdate: Codable, Hashable, Sendable { - - - public enum CodingKeys: String, CodingKey { - - } - } - - extension PublicSchema { + } public struct TableWithOtherTablesRowTypeSelect: Codable, Hashable, Sendable { - public let col1: PublicSchema.UserDetails? - public let col2: PublicSchema.AView? - + public let col1: UserDetailsSelect? + public let col2: AViewSelect? public enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } - } - - extension PublicSchema { + } public struct TableWithOtherTablesRowTypeInsert: Codable, Hashable, Sendable { - public let col1: PublicSchema.UserDetails? - public let col2: PublicSchema.AView? - + public let col1: UserDetailsSelect? + public let col2: AViewSelect? public enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } - } - - extension PublicSchema { + } public struct TableWithOtherTablesRowTypeUpdate: Codable, Hashable, Sendable { - public let col1: PublicSchema.UserDetails? - public let col2: PublicSchema.AView? - + public let col1: UserDetailsSelect? + public let col2: AViewSelect? public enum CodingKeys: String, CodingKey { case col1 = "col1" case col2 = "col2" } - } - - extension PublicSchema { - public struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable { + } + public struct TableWithPrimaryKeyOtherThanIdSelect: Codable, Hashable, Sendable, Identifiable { + public var id: Int64 { otherId } public let name: String? public let otherId: Int64 - public enum CodingKeys: String, CodingKey { case name = "name" case otherId = "other_id" } - } - extension PublicSchema.TableWithPrimaryKeyOtherThanIdSelect: Identifiable { - public var id: Int64 { otherId } - } - - extension PublicSchema { - public struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable { + } + public struct TableWithPrimaryKeyOtherThanIdInsert: Codable, Hashable, Sendable, Identifiable { + public var id: Int64? { otherId } public let name: String? public let otherId: Int64? - public enum CodingKeys: String, CodingKey { case name = "name" case otherId = "other_id" } - } - - extension PublicSchema { - public struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable { + } + public struct TableWithPrimaryKeyOtherThanIdUpdate: Codable, Hashable, Sendable, Identifiable { + public var id: Int64? { otherId } public let name: String? public let otherId: Int64? - public enum CodingKeys: String, CodingKey { case name = "name" case otherId = "other_id" } - } - - extension PublicSchema { + } public struct CategorySelect: Codable, Hashable, Sendable { public let id: Int32 public let name: String - public enum CodingKeys: String, CodingKey { case id = "id" case name = "name" } - } - - extension PublicSchema { + } public struct CategoryInsert: Codable, Hashable, Sendable { public let id: Int32? public let name: String - public enum CodingKeys: String, CodingKey { case id = "id" case name = "name" } - } - - extension PublicSchema { + } public struct CategoryUpdate: Codable, Hashable, Sendable { public let id: Int32? public let name: String? - public enum CodingKeys: String, CodingKey { case id = "id" case name = "name" } - } - - extension PublicSchema { + } public struct MemesSelect: Codable, Hashable, Sendable { public let category: Int32? public let createdAt: String public let id: Int32 public let metadata: AnyJSON? public let name: String - public let status: PublicSchema.MemeStatus? - + public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" @@ -2507,17 +2309,14 @@ test('typegen: swift w/ public access control', async () => { case name = "name" case status = "status" } - } - - extension PublicSchema { + } public struct MemesInsert: Codable, Hashable, Sendable { public let category: Int32? public let createdAt: String public let id: Int32? public let metadata: AnyJSON? public let name: String - public let status: PublicSchema.MemeStatus? - + public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" @@ -2526,17 +2325,14 @@ test('typegen: swift w/ public access control', async () => { case name = "name" case status = "status" } - } - - extension PublicSchema { + } public struct MemesUpdate: Codable, Hashable, Sendable { public let category: Int32? public let createdAt: String? public let id: Int32? public let metadata: AnyJSON? public let name: String? - public let status: PublicSchema.MemeStatus? - + public let status: MemeStatus? public enum CodingKeys: String, CodingKey { case category = "category" case createdAt = "created_at" @@ -2545,39 +2341,49 @@ test('typegen: swift w/ public access control', async () => { case name = "name" case status = "status" } - } - - // MARK: - Views - - - - - - - // MARK: - Materialized Views - extension PublicSchema { + } + public struct TodosViewSelect: Codable, Hashable, Sendable { + public let details: String? + public let id: Int64? + public let userId: Int64? + public enum CodingKeys: String, CodingKey { + case details = "details" + case id = "id" + case userId = "user-id" + } + } + public struct UsersViewSelect: Codable, Hashable, Sendable { + public let id: Int64? + public let name: String? + public let status: UserStatus? + public enum CodingKeys: String, CodingKey { + case id = "id" + case name = "name" + case status = "status" + } + } + public struct AViewSelect: Codable, Hashable, Sendable { + public let id: Int64? + public enum CodingKeys: String, CodingKey { + case id = "id" + } + } public struct TodosMatviewSelect: Codable, Hashable, Sendable { public let details: String? public let id: Int64? public let userId: Int64? - public enum CodingKeys: String, CodingKey { case details = "details" case id = "id" case userId = "user-id" } - } - - // MARK: - Composite Types - extension PublicSchema { + } public struct CompositeTypeWithArrayAttribute: Codable, Hashable, Sendable { public let MyTextArray: AnyJSON - public enum CodingKeys: String, CodingKey { case MyTextArray = "my_text_array" } } - } }" `) })