Skip to content

Commit 24d5599

Browse files
authored
chore: refactor typegen to reduce loops (#992)
2 parents dcb8e9b + 2b82470 commit 24d5599

File tree

2 files changed

+125
-65
lines changed

2 files changed

+125
-65
lines changed

src/server/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ export const GENERATE_TYPES_SWIFT_ACCESS_CONTROL = process.env
5151
? (process.env.PG_META_GENERATE_TYPES_SWIFT_ACCESS_CONTROL as AccessControl)
5252
: 'internal'
5353

54+
// json/jsonb/text types
55+
export const VALID_UNNAMED_FUNCTION_ARG_TYPES = new Set([114, 3802, 25])
56+
export const VALID_FUNCTION_ARGS_MODE = new Set(['in', 'inout', 'variadic'])
57+
5458
export const PG_META_MAX_RESULT_SIZE = process.env.PG_META_MAX_RESULT_SIZE_MB
5559
? // Node-postgres get a maximum size in bytes make the conversion from the env variable
5660
// from MB to Bytes

src/server/templates/typescript.ts

Lines changed: 121 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
PostgresView,
99
} from '../../lib/index.js'
1010
import type { GeneratorMetadata } from '../../lib/generators.js'
11-
import { GENERATE_TYPES_DEFAULT_SCHEMA } from '../constants.js'
11+
import { GENERATE_TYPES_DEFAULT_SCHEMA, VALID_FUNCTION_ARGS_MODE } from '../constants.js'
1212

1313
export const apply = async ({
1414
schemas,
@@ -26,15 +26,99 @@ export const apply = async ({
2626
detectOneToOneRelationships: boolean
2727
postgrestVersion?: string
2828
}): Promise<string> => {
29+
schemas.sort((a, b) => a.name.localeCompare(b.name))
30+
2931
const columnsByTableId = Object.fromEntries<PostgresColumn[]>(
3032
[...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []])
3133
)
32-
columns
33-
.filter((c) => c.table_id in columnsByTableId)
34-
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
35-
.forEach((c) => {
36-
columnsByTableId[c.table_id].push(c)
37-
})
34+
for (const column of columns) {
35+
if (column.table_id in columnsByTableId) {
36+
columnsByTableId[column.table_id].push(column)
37+
}
38+
}
39+
for (const tableId in columnsByTableId) {
40+
columnsByTableId[tableId].sort((a, b) => a.name.localeCompare(b.name))
41+
}
42+
43+
const introspectionBySchema = Object.fromEntries<{
44+
tables: Pick<PostgresTable, 'id' | 'name' | 'schema' | 'columns'>[]
45+
views: PostgresView[]
46+
functions: { fn: PostgresFunction; inArgs: PostgresFunction['args'] }[]
47+
enums: PostgresType[]
48+
compositeTypes: PostgresType[]
49+
}>(
50+
schemas.map((s) => [
51+
s.name,
52+
{ tables: [], views: [], functions: [], enums: [], compositeTypes: [] },
53+
])
54+
)
55+
for (const table of tables) {
56+
if (table.schema in introspectionBySchema) {
57+
introspectionBySchema[table.schema].tables.push(table)
58+
}
59+
}
60+
for (const table of foreignTables) {
61+
if (table.schema in introspectionBySchema) {
62+
introspectionBySchema[table.schema].tables.push(table)
63+
}
64+
}
65+
for (const view of views) {
66+
if (view.schema in introspectionBySchema) {
67+
introspectionBySchema[view.schema].views.push(view)
68+
}
69+
}
70+
for (const materializedView of materializedViews) {
71+
if (materializedView.schema in introspectionBySchema) {
72+
introspectionBySchema[materializedView.schema].views.push({
73+
...materializedView,
74+
is_updatable: false,
75+
})
76+
}
77+
}
78+
for (const func of functions) {
79+
if (func.schema in introspectionBySchema) {
80+
func.args.sort((a, b) => a.name.localeCompare(b.name))
81+
// Either:
82+
// 1. All input args are be named, or
83+
// 2. There is only one input arg which is unnamed
84+
const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode))
85+
86+
if (
87+
// Case 1: Function has a single parameter
88+
inArgs.length === 1 ||
89+
// Case 2: All input args are named
90+
!inArgs.some(({ name }) => name === '')
91+
) {
92+
introspectionBySchema[func.schema].functions.push({ fn: func, inArgs })
93+
}
94+
}
95+
}
96+
for (const type of types) {
97+
if (type.schema in introspectionBySchema) {
98+
if (type.enums.length > 0) {
99+
introspectionBySchema[type.schema].enums.push(type)
100+
}
101+
if (type.attributes.length > 0) {
102+
introspectionBySchema[type.schema].compositeTypes.push(type)
103+
}
104+
}
105+
}
106+
for (const schema in introspectionBySchema) {
107+
introspectionBySchema[schema].tables.sort((a, b) => a.name.localeCompare(b.name))
108+
introspectionBySchema[schema].views.sort((a, b) => a.name.localeCompare(b.name))
109+
introspectionBySchema[schema].functions.sort((a, b) => a.fn.name.localeCompare(b.fn.name))
110+
introspectionBySchema[schema].enums.sort((a, b) => a.name.localeCompare(b.name))
111+
introspectionBySchema[schema].compositeTypes.sort((a, b) => a.name.localeCompare(b.name))
112+
}
113+
114+
// group types by id for quicker lookup
115+
const typesById = types.reduce(
116+
(acc, type) => {
117+
acc[type.id] = type
118+
return acc
119+
},
120+
{} as Record<number, (typeof types)[number]>
121+
)
38122

39123
const internal_supabase_schema = postgrestVersion
40124
? `// Allows to automatically instantiate createClient with right options
@@ -49,44 +133,15 @@ export type Json = string | number | boolean | null | { [key: string]: Json | un
49133
50134
export type Database = {
51135
${internal_supabase_schema}
52-
${schemas
53-
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
54-
.map((schema) => {
55-
const schemaTables = [...tables, ...foreignTables]
56-
.filter((table) => table.schema === schema.name)
57-
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
58-
const schemaViews = [...views, ...materializedViews]
59-
.filter((view) => view.schema === schema.name)
60-
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
61-
const schemaFunctions = functions
62-
.filter((func) => {
63-
if (func.schema !== schema.name) {
64-
return false
65-
}
66-
67-
// Either:
68-
// 1. All input args are be named, or
69-
// 2. There is only one input arg which is unnamed
70-
const inArgs = func.args.filter(({ mode }) => ['in', 'inout', 'variadic'].includes(mode))
71-
72-
if (!inArgs.some(({ name }) => name === '')) {
73-
return true
74-
}
75-
76-
if (inArgs.length === 1) {
77-
return true
78-
}
79-
80-
return false
81-
})
82-
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
83-
const schemaEnums = types
84-
.filter((type) => type.schema === schema.name && type.enums.length > 0)
85-
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
86-
const schemaCompositeTypes = types
87-
.filter((type) => type.schema === schema.name && type.attributes.length > 0)
88-
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
89-
return `${JSON.stringify(schema.name)}: {
136+
${schemas.map((schema) => {
137+
const {
138+
tables: schemaTables,
139+
views: schemaViews,
140+
functions: schemaFunctions,
141+
enums: schemaEnums,
142+
compositeTypes: schemaCompositeTypes,
143+
} = introspectionBySchema[schema.name]
144+
return `${JSON.stringify(schema.name)}: {
90145
Tables: {
91146
${
92147
schemaTables.length === 0
@@ -105,9 +160,9 @@ export type Database = {
105160
})} ${column.is_nullable ? '| null' : ''}`
106161
),
107162
...schemaFunctions
108-
.filter((fn) => fn.argument_types === table.name)
109-
.map((fn) => {
110-
const type = types.find(({ id }) => id === fn.return_type_id)
163+
.filter(({ fn }) => fn.argument_types === table.name)
164+
.map(({ fn }) => {
165+
const type = typesById[fn.return_type_id]
111166
let tsType = 'unknown'
112167
if (type) {
113168
tsType = pgTypeToTsType(schema, type.name, {
@@ -226,7 +281,7 @@ export type Database = {
226281
)}
227282
}
228283
${
229-
'is_updatable' in view && view.is_updatable
284+
view.is_updatable
230285
? `Insert: {
231286
${columnsByTableId[view.id].map((column) => {
232287
let output = JSON.stringify(column.name)
@@ -306,28 +361,29 @@ export type Database = {
306361
307362
const schemaFunctionsGroupedByName = schemaFunctions.reduce(
308363
(acc, curr) => {
309-
acc[curr.name] ??= []
310-
acc[curr.name].push(curr)
364+
acc[curr.fn.name] ??= []
365+
acc[curr.fn.name].push(curr)
311366
return acc
312367
},
313-
{} as Record<string, PostgresFunction[]>
368+
{} as Record<string, typeof schemaFunctions>
314369
)
370+
for (const fnName in schemaFunctionsGroupedByName) {
371+
schemaFunctionsGroupedByName[fnName].sort((a, b) =>
372+
b.fn.definition.localeCompare(a.fn.definition)
373+
)
374+
}
315375
316376
return Object.entries(schemaFunctionsGroupedByName).map(
317377
([fnName, fns]) =>
318378
`${JSON.stringify(fnName)}: {
319379
Args: ${fns
320-
.map(({ args }) => {
321-
const inArgs = args
322-
.toSorted((a, b) => a.name.localeCompare(b.name))
323-
.filter(({ mode }) => mode === 'in')
324-
380+
.map(({ inArgs }) => {
325381
if (inArgs.length === 0) {
326382
return 'Record<PropertyKey, never>'
327383
}
328384
329385
const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => {
330-
const type = types.find(({ id }) => id === type_id)
386+
const type = typesById[type_id]
331387
let tsType = 'unknown'
332388
if (type) {
333389
tsType = pgTypeToTsType(schema, type.name, {
@@ -346,10 +402,10 @@ export type Database = {
346402
.join(' | ')}
347403
Returns: ${(() => {
348404
// Case 1: `returns table`.
349-
const tableArgs = fns[0].args.filter(({ mode }) => mode === 'table')
405+
const tableArgs = fns[0].fn.args.filter(({ mode }) => mode === 'table')
350406
if (tableArgs.length > 0) {
351407
const argsNameAndType = tableArgs.map(({ name, type_id }) => {
352-
const type = types.find(({ id }) => id === type_id)
408+
const type = typesById[type_id]
353409
let tsType = 'unknown'
354410
if (type) {
355411
tsType = pgTypeToTsType(schema, type.name, {
@@ -371,7 +427,7 @@ export type Database = {
371427
372428
// Case 2: returns a relation's row type.
373429
const relation = [...tables, ...views].find(
374-
({ id }) => id === fns[0].return_type_relation_id
430+
({ id }) => id === fns[0].fn.return_type_relation_id
375431
)
376432
if (relation) {
377433
return `{
@@ -394,7 +450,7 @@ export type Database = {
394450
}
395451
396452
// Case 3: returns base/array/composite/enum type.
397-
const type = types.find(({ id }) => id === fns[0].return_type_id)
453+
const type = typesById[fns[0].fn.return_type_id]
398454
if (type) {
399455
return pgTypeToTsType(schema, type.name, {
400456
types,
@@ -405,7 +461,7 @@ export type Database = {
405461
}
406462
407463
return 'unknown'
408-
})()}${fns[0].is_set_returning_function ? '[]' : ''}
464+
})()}${fns[0].fn.is_set_returning_function ? '[]' : ''}
409465
}`
410466
)
411467
})()}
@@ -430,7 +486,7 @@ export type Database = {
430486
({ name, attributes }) =>
431487
`${JSON.stringify(name)}: {
432488
${attributes.map(({ name, type_id }) => {
433-
const type = types.find(({ id }) => id === type_id)
489+
const type = typesById[type_id]
434490
let tsType = 'unknown'
435491
if (type) {
436492
tsType = `${pgTypeToTsType(schema, type.name, {
@@ -447,7 +503,7 @@ export type Database = {
447503
}
448504
}
449505
}`
450-
})}
506+
})}
451507
}
452508
453509
type DatabaseWithoutInternals = Omit<Database, '__InternalSupabase'>

0 commit comments

Comments
 (0)