Skip to content

Commit 2ddd618

Browse files
Alex Allysoedirgo
Alex Ally
andauthored
fix: Add capability for typescript type gen to reference table row types (#533)
Co-authored-by: Bobbie Soedirgo <[email protected]>
1 parent ae27189 commit 2ddd618

File tree

4 files changed

+105
-36
lines changed

4 files changed

+105
-36
lines changed

src/server/templates/typescript.ts

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import type {
33
PostgresColumn,
44
PostgresFunction,
55
PostgresSchema,
6+
PostgresTable,
67
PostgresType,
8+
PostgresView,
79
} from '../../lib/index.js'
810
import type { GeneratorMetadata } from '../../lib/generators.js'
911

@@ -80,19 +82,20 @@ export type Database = {
8082
${[
8183
...columnsByTableId[table.id].map(
8284
(column) =>
83-
`${JSON.stringify(column.name)}: ${pgTypeToTsType(
84-
column.format,
85+
`${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, {
8586
types,
86-
schemas
87-
)} ${column.is_nullable ? '| null' : ''}`
87+
schemas,
88+
tables,
89+
views,
90+
})} ${column.is_nullable ? '| null' : ''}`
8891
),
8992
...schemaFunctions
9093
.filter((fn) => fn.argument_types === table.name)
9194
.map((fn) => {
9295
const type = types.find(({ id }) => id === fn.return_type_id)
9396
let tsType = 'unknown'
9497
if (type) {
95-
tsType = pgTypeToTsType(type.name, types, schemas)
98+
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
9699
}
97100
return `${JSON.stringify(fn.name)}: ${tsType} | null`
98101
}),
@@ -116,7 +119,7 @@ export type Database = {
116119
output += ':'
117120
}
118121
119-
output += pgTypeToTsType(column.format, types, schemas)
122+
output += pgTypeToTsType(column.format, { types, schemas, tables, views })
120123
121124
if (column.is_nullable) {
122125
output += '| null'
@@ -133,7 +136,7 @@ export type Database = {
133136
return `${output}?: never`
134137
}
135138
136-
output += `?: ${pgTypeToTsType(column.format, types, schemas)}`
139+
output += `?: ${pgTypeToTsType(column.format, { types, schemas, tables, views })}`
137140
138141
if (column.is_nullable) {
139142
output += '| null'
@@ -180,11 +183,12 @@ export type Database = {
180183
Row: {
181184
${columnsByTableId[view.id].map(
182185
(column) =>
183-
`${JSON.stringify(column.name)}: ${pgTypeToTsType(
184-
column.format,
186+
`${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, {
185187
types,
186-
schemas
187-
)} ${column.is_nullable ? '| null' : ''}`
188+
schemas,
189+
tables,
190+
views,
191+
})} ${column.is_nullable ? '| null' : ''}`
188192
)}
189193
}
190194
${
@@ -197,7 +201,7 @@ export type Database = {
197201
return `${output}?: never`
198202
}
199203
200-
output += `?: ${pgTypeToTsType(column.format, types, schemas)} | null`
204+
output += `?: ${pgTypeToTsType(column.format, { types, schemas, tables, views })} | null`
201205
202206
return output
203207
})}
@@ -210,7 +214,7 @@ export type Database = {
210214
return `${output}?: never`
211215
}
212216
213-
output += `?: ${pgTypeToTsType(column.format, types, schemas)} | null`
217+
output += `?: ${pgTypeToTsType(column.format, { types, schemas, tables, views })} | null`
214218
215219
return output
216220
})}
@@ -279,7 +283,7 @@ export type Database = {
279283
const type = types.find(({ id }) => id === type_id)
280284
let tsType = 'unknown'
281285
if (type) {
282-
tsType = pgTypeToTsType(type.name, types, schemas)
286+
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
283287
}
284288
return { name, type: tsType, has_default }
285289
})
@@ -299,7 +303,7 @@ export type Database = {
299303
const type = types.find(({ id }) => id === type_id)
300304
let tsType = 'unknown'
301305
if (type) {
302-
tsType = pgTypeToTsType(type.name, types, schemas)
306+
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
303307
}
304308
return { name, type: tsType }
305309
})
@@ -319,19 +323,20 @@ export type Database = {
319323
return `{
320324
${columnsByTableId[relation.id].map(
321325
(column) =>
322-
`${JSON.stringify(column.name)}: ${pgTypeToTsType(
323-
column.format,
326+
`${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, {
324327
types,
325-
schemas
326-
)} ${column.is_nullable ? '| null' : ''}`
328+
schemas,
329+
tables,
330+
views,
331+
})} ${column.is_nullable ? '| null' : ''}`
327332
)}
328333
}`
329334
}
330335
331336
// Case 3: returns base/array/composite/enum type.
332337
const type = types.find(({ id }) => id === return_type_id)
333338
if (type) {
334-
return pgTypeToTsType(type.name, types, schemas)
339+
return pgTypeToTsType(type.name, { types, schemas, tables, views })
335340
}
336341
337342
return 'unknown'
@@ -367,7 +372,7 @@ export type Database = {
367372
const type = types.find(({ id }) => id === type_id)
368373
let tsType = 'unknown'
369374
if (type) {
370-
tsType = `${pgTypeToTsType(type.name, types, schemas)} | null`
375+
tsType = `${pgTypeToTsType(type.name, { types, schemas, tables, views })} | null`
371376
}
372377
return `${JSON.stringify(name)}: ${tsType}`
373378
})}
@@ -470,8 +475,17 @@ export type Enums<
470475
// TODO: Make this more robust. Currently doesn't handle range types - returns them as unknown.
471476
const pgTypeToTsType = (
472477
pgType: string,
473-
types: PostgresType[],
474-
schemas: PostgresSchema[]
478+
{
479+
types,
480+
schemas,
481+
tables,
482+
views,
483+
}: {
484+
types: PostgresType[]
485+
schemas: PostgresSchema[]
486+
tables: PostgresTable[]
487+
views: PostgresView[]
488+
}
475489
): string => {
476490
if (pgType === 'bool') {
477491
return 'boolean'
@@ -501,7 +515,7 @@ const pgTypeToTsType = (
501515
} else if (pgType === 'record') {
502516
return 'Record<string, unknown>'
503517
} else if (pgType.startsWith('_')) {
504-
return `(${pgTypeToTsType(pgType.substring(1), types, schemas)})[]`
518+
return `(${pgTypeToTsType(pgType.substring(1), { types, schemas, tables, views })})[]`
505519
} else {
506520
const enumType = types.find((type) => type.name === pgType && type.enums.length > 0)
507521
if (enumType) {
@@ -523,6 +537,26 @@ const pgTypeToTsType = (
523537
return 'unknown'
524538
}
525539

540+
const tableRowType = tables.find((table) => table.name === pgType)
541+
if (tableRowType) {
542+
if (schemas.some(({ name }) => name === tableRowType.schema)) {
543+
return `Database[${JSON.stringify(tableRowType.schema)}]['Tables'][${JSON.stringify(
544+
tableRowType.name
545+
)}]['Row']`
546+
}
547+
return 'unknown'
548+
}
549+
550+
const viewRowType = views.find((view) => view.name === pgType)
551+
if (viewRowType) {
552+
if (schemas.some(({ name }) => name === viewRowType.schema)) {
553+
return `Database[${JSON.stringify(viewRowType.schema)}]['Views'][${JSON.stringify(
554+
viewRowType.name
555+
)}]['Row']`
556+
}
557+
return 'unknown'
558+
}
559+
526560
return 'unknown'
527561
}
528562
}

test/db/00-init.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,8 @@ create table user_details (
127127
create view a_view as select id from users;
128128

129129
create table empty();
130+
131+
create table table_with_other_tables_row_type (
132+
col1 user_details,
133+
col2 a_view
134+
);

test/lib/tables.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,19 @@ test('list', async () => {
114114
],
115115
"relationships": [
116116
{
117-
"constraint_name": "todos_user-id_fkey",
118-
"source_column_name": "user-id",
117+
"constraint_name": "user_details_user_id_fkey",
118+
"source_column_name": "user_id",
119119
"source_schema": "public",
120-
"source_table_name": "todos",
120+
"source_table_name": "user_details",
121121
"target_column_name": "id",
122122
"target_table_name": "users",
123123
"target_table_schema": "public",
124124
},
125125
{
126-
"constraint_name": "user_details_user_id_fkey",
127-
"source_column_name": "user_id",
126+
"constraint_name": "todos_user-id_fkey",
127+
"source_column_name": "user-id",
128128
"source_schema": "public",
129-
"source_table_name": "user_details",
129+
"source_table_name": "todos",
130130
"target_column_name": "id",
131131
"target_table_name": "users",
132132
"target_table_schema": "public",
@@ -178,19 +178,19 @@ test('list without columns', async () => {
178178
],
179179
"relationships": [
180180
{
181-
"constraint_name": "todos_user-id_fkey",
182-
"source_column_name": "user-id",
181+
"constraint_name": "user_details_user_id_fkey",
182+
"source_column_name": "user_id",
183183
"source_schema": "public",
184-
"source_table_name": "todos",
184+
"source_table_name": "user_details",
185185
"target_column_name": "id",
186186
"target_table_name": "users",
187187
"target_table_schema": "public",
188188
},
189189
{
190-
"constraint_name": "user_details_user_id_fkey",
191-
"source_column_name": "user_id",
190+
"constraint_name": "todos_user-id_fkey",
191+
"source_column_name": "user-id",
192192
"source_schema": "public",
193-
"source_table_name": "user_details",
193+
"source_table_name": "todos",
194194
"target_column_name": "id",
195195
"target_table_name": "users",
196196
"target_table_schema": "public",

test/server/typegen.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,21 @@ test('typegen', async () => {
7070
},
7171
]
7272
}
73+
table_with_other_tables_row_type: {
74+
Row: {
75+
col1: Database["public"]["Tables"]["user_details"]["Row"] | null
76+
col2: Database["public"]["Views"]["a_view"]["Row"] | null
77+
}
78+
Insert: {
79+
col1?: Database["public"]["Tables"]["user_details"]["Row"] | null
80+
col2?: Database["public"]["Views"]["a_view"]["Row"] | null
81+
}
82+
Update: {
83+
col1?: Database["public"]["Tables"]["user_details"]["Row"] | null
84+
col2?: Database["public"]["Views"]["a_view"]["Row"] | null
85+
}
86+
Relationships: []
87+
}
7388
todos: {
7489
Row: {
7590
details: string | null
@@ -539,6 +554,21 @@ test('typegen w/ one-to-one relationships', async () => {
539554
},
540555
]
541556
}
557+
table_with_other_tables_row_type: {
558+
Row: {
559+
col1: Database["public"]["Tables"]["user_details"]["Row"] | null
560+
col2: Database["public"]["Views"]["a_view"]["Row"] | null
561+
}
562+
Insert: {
563+
col1?: Database["public"]["Tables"]["user_details"]["Row"] | null
564+
col2?: Database["public"]["Views"]["a_view"]["Row"] | null
565+
}
566+
Update: {
567+
col1?: Database["public"]["Tables"]["user_details"]["Row"] | null
568+
col2?: Database["public"]["Views"]["a_view"]["Row"] | null
569+
}
570+
Relationships: []
571+
}
542572
todos: {
543573
Row: {
544574
details: string | null

0 commit comments

Comments
 (0)