Skip to content

Commit 5d002df

Browse files
committed
chore: refactor typegen for prorows only
1 parent 445f1ff commit 5d002df

File tree

1 file changed

+60
-33
lines changed

1 file changed

+60
-33
lines changed

src/server/templates/typescript.ts

Lines changed: 60 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,22 @@ export const apply = async ({
3737
}): Promise<string> => {
3838
schemas.sort((a, b) => a.name.localeCompare(b.name))
3939

40-
const columnsByTableId = Object.fromEntries<PostgresColumn[]>(
41-
[...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []])
40+
const columnsByTableId: Record<number, PostgresColumn[]> = {}
41+
const tablesNamesByTableId: Record<number, string> = {}
42+
// group types by id for quicker lookup
43+
const typesById = types.reduce(
44+
(acc, type) => {
45+
acc[type.id] = type
46+
return acc
47+
},
48+
{} as Record<number, (typeof types)[number]>
4249
)
50+
const tablesLike = [...tables, ...foreignTables, ...views, ...materializedViews]
51+
52+
for (const tableLike of tablesLike) {
53+
columnsByTableId[tableLike.id] = []
54+
tablesNamesByTableId[tableLike.id] = tableLike.name
55+
}
4356
for (const column of columns) {
4457
if (column.table_id in columnsByTableId) {
4558
columnsByTableId[column.table_id].push(column)
@@ -134,6 +147,30 @@ export const apply = async ({
134147
})
135148
}
136149
}
150+
for (const type of types) {
151+
if (type.schema in introspectionBySchema) {
152+
if (type.enums.length > 0) {
153+
introspectionBySchema[type.schema].enums.push(type)
154+
}
155+
if (type.attributes.length > 0) {
156+
introspectionBySchema[type.schema].compositeTypes.push(type)
157+
}
158+
}
159+
}
160+
// Helper function to get table/view name from relation id
161+
const getTableNameFromRelationId = (
162+
relationId: number | null,
163+
returnTypeId: number | null
164+
): string | null => {
165+
if (!relationId) return null
166+
167+
if (tablesNamesByTableId[relationId]) return tablesNamesByTableId[relationId]
168+
// if it's a composite type we use the type name as relation name to allow sub-selecting fields of the composite type
169+
if (returnTypeId && typesById[returnTypeId] && typesById[returnTypeId].attributes.length > 0)
170+
return typesById[returnTypeId].name
171+
return null
172+
}
173+
137174
for (const func of functions) {
138175
if (func.schema in introspectionBySchema) {
139176
func.args.sort((a, b) => a.name.localeCompare(b.name))
@@ -159,24 +196,16 @@ export const apply = async ({
159196
inArgs[0].name === '' &&
160197
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id) ||
161198
// OR if the function have a single unnamed args which is another table (embeded function)
162-
(inArgs[0].table_name && func.return_table_name) ||
199+
(inArgs[0].table_name &&
200+
getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id)) ||
163201
// OR if the function takes a table row but doesn't qualify as embedded (for error reporting)
164-
(inArgs[0].table_name && !func.return_table_name)))
202+
(inArgs[0].table_name &&
203+
!getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id))))
165204
) {
166205
introspectionBySchema[func.schema].functions.push({ fn: func, inArgs })
167206
}
168207
}
169208
}
170-
for (const type of types) {
171-
if (type.schema in introspectionBySchema) {
172-
if (type.enums.length > 0) {
173-
introspectionBySchema[type.schema].enums.push(type)
174-
}
175-
if (type.attributes.length > 0) {
176-
introspectionBySchema[type.schema].compositeTypes.push(type)
177-
}
178-
}
179-
}
180209
for (const schema in introspectionBySchema) {
181210
introspectionBySchema[schema].tables.sort((a, b) => a.table.name.localeCompare(b.table.name))
182211
introspectionBySchema[schema].views.sort((a, b) => a.view.name.localeCompare(b.view.name))
@@ -185,35 +214,33 @@ export const apply = async ({
185214
introspectionBySchema[schema].compositeTypes.sort((a, b) => a.name.localeCompare(b.name))
186215
}
187216

188-
// group types by id for quicker lookup
189-
const typesById = types.reduce(
190-
(acc, type) => {
191-
acc[type.id] = type
192-
return acc
193-
},
194-
{} as Record<number, (typeof types)[number]>
195-
)
196-
197217
const getFunctionTsReturnType = (fn: PostgresFunction, returnType: string) => {
198218
// Determine if this function should have SetofOptions
199219
let setofOptionsInfo = ''
200220

221+
const returnTableName = getTableNameFromRelationId(
222+
fn.return_type_relation_id,
223+
fn.return_type_id
224+
)
225+
const returnsSetOfTable = fn.is_set_returning_function && fn.return_type_relation_id !== null
226+
const returnsMultipleRows = fn.prorows !== null && fn.prorows > 1
227+
201228
// Only add SetofOptions for functions with table arguments (embedded functions)
202229
// or specific functions that RETURNS table-name
203230
if (fn.args.length === 1 && fn.args[0].table_name) {
204231
// Case 1: Standard embedded function with proper setof detection
205-
if (fn.returns_set_of_table && fn.return_table_name) {
232+
if (returnsSetOfTable && returnTableName) {
206233
setofOptionsInfo = `SetofOptions: {
207234
from: ${JSON.stringify(typesById[fn.args[0].type_id].format)}
208-
to: ${JSON.stringify(fn.return_table_name)}
209-
isOneToOne: ${Boolean(!fn.returns_multiple_rows)}
235+
to: ${JSON.stringify(returnTableName)}
236+
isOneToOne: ${Boolean(!returnsMultipleRows)}
210237
isSetofReturn: true
211238
}`
212239
}
213240
// Case 2: Handle RETURNS table-name those are always a one to one relationship
214-
else if (fn.return_table_name && !fn.returns_set_of_table) {
241+
else if (returnTableName && !returnsSetOfTable) {
215242
const sourceTable = typesById[fn.args[0].type_id].format
216-
const targetTable = fn.return_table_name
243+
const targetTable = returnTableName
217244
setofOptionsInfo = `SetofOptions: {
218245
from: ${JSON.stringify(sourceTable)}
219246
to: ${JSON.stringify(targetTable)}
@@ -224,16 +251,16 @@ export const apply = async ({
224251
}
225252
// Case 3: Special case for functions without table arguments still returning a table
226253
// Those can be used in rpc to select sub fields of a table
227-
else if (fn.return_table_name) {
254+
else if (returnTableName) {
228255
setofOptionsInfo = `SetofOptions: {
229256
from: "*"
230-
to: ${JSON.stringify(fn.return_table_name)}
231-
isOneToOne: ${Boolean(!fn.returns_multiple_rows)}
257+
to: ${JSON.stringify(returnTableName)}
258+
isOneToOne: ${Boolean(!returnsMultipleRows)}
232259
isSetofReturn: ${fn.is_set_returning_function}
233260
}`
234261
}
235262

236-
return `${returnType}${fn.is_set_returning_function && fn.returns_multiple_rows ? '[]' : ''}
263+
return `${returnType}${fn.is_set_returning_function && returnsMultipleRows ? '[]' : ''}
237264
${setofOptionsInfo ? `${setofOptionsInfo}` : ''}`
238265
}
239266

@@ -311,7 +338,7 @@ export const apply = async ({
311338
inArgs.length === 1 &&
312339
inArgs[0].name === '' &&
313340
inArgs[0].table_name &&
314-
!fn.return_table_name
341+
!getTableNameFromRelationId(fn.return_type_relation_id, fn.return_type_id)
315342
) {
316343
return true
317344
}

0 commit comments

Comments
 (0)