diff --git a/src/server/templates/typescript.ts b/src/server/templates/typescript.ts index a46143a4..6d2efde8 100644 --- a/src/server/templates/typescript.ts +++ b/src/server/templates/typescript.ts @@ -277,88 +277,74 @@ export type Database = { return Object.entries(schemaFunctionsGroupedByName).map( ([fnName, fns]) => - `${JSON.stringify(fnName)}: ${fns - .map( - ({ - args, - return_type_id, - return_type_relation_id, - is_set_returning_function, - }) => `{ - Args: ${(() => { - const inArgs = args.filter(({ mode }) => mode === 'in') - - if (inArgs.length === 0) { - return 'Record' - } - - const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { - const type = types.find(({ id }) => id === type_id) - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(type.name, { types, schemas, tables, views }) - } - return { name, type: tsType, has_default } - }) + `${JSON.stringify(fnName)}: { + Args: ${fns + .map(({ args }) => { + const inArgs = args.filter(({ mode }) => mode === 'in') - return `{ - ${argsNameAndType.map( - ({ name, type, has_default }) => - `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}` - )} - }` - })()} - Returns: (${(() => { - // Case 1: `returns table`. - const tableArgs = args.filter(({ mode }) => mode === 'table') - if (tableArgs.length > 0) { - const argsNameAndType = tableArgs.map(({ name, type_id }) => { - const type = types.find(({ id }) => id === type_id) - let tsType = 'unknown' - if (type) { - tsType = pgTypeToTsType(type.name, { types, schemas, tables, views }) + if (inArgs.length === 0) { + return 'Record' + } + + const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => { + const type = types.find(({ id }) => id === type_id) + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(type.name, { types, schemas, tables, views }) + } + return { name, type: tsType, has_default } + }) + return `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }` + }) + // A function can have multiples definitions with differents args, but will always return the same type + .join(' | ')} + Returns: ${(() => { + // Case 1: `returns table`. + const tableArgs = fns[0].args.filter(({ mode }) => mode === 'table') + if (tableArgs.length > 0) { + const argsNameAndType = tableArgs.map(({ name, type_id }) => { + const type = types.find(({ id }) => id === type_id) + let tsType = 'unknown' + if (type) { + tsType = pgTypeToTsType(type.name, { types, schemas, tables, views }) + } + return { name, type: tsType } + }) + + return `{ + ${argsNameAndType.map( + ({ name, type }) => `${JSON.stringify(name)}: ${type}` + )} + }` } - return { name, type: tsType } - }) - return `{ - ${argsNameAndType.map( - ({ name, type }) => `${JSON.stringify(name)}: ${type}` - )} - }` - } - - // Case 2: returns a relation's row type. - const relation = [...tables, ...views].find( - ({ id }) => id === return_type_relation_id - ) - if (relation) { - return `{ - ${columnsByTableId[relation.id].map( - (column) => - `${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, { - types, - schemas, - tables, - views, - })} ${column.is_nullable ? '| null' : ''}` - )} - }` - } + // Case 2: returns a relation's row type. + const relation = [...tables, ...views].find( + ({ id }) => id === fns[0].return_type_relation_id + ) + if (relation) { + return `{ + ${columnsByTableId[relation.id].map( + (column) => + `${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, { + types, + schemas, + tables, + views, + })} ${column.is_nullable ? '| null' : ''}` + )} + }` + } - // Case 3: returns base/array/composite/enum type. - const type = types.find(({ id }) => id === return_type_id) - if (type) { - return pgTypeToTsType(type.name, { types, schemas, tables, views }) - } + // Case 3: returns base/array/composite/enum type. + const type = types.find(({ id }) => id === fns[0].return_type_id) + if (type) { + return pgTypeToTsType(type.name, { types, schemas, tables, views }) + } - return 'unknown' - })()})${is_set_returning_function ? '[]' : ''} - }` - ) - // We only sorted by name on schemaFunctions - here we sort by arg names, arg types, and return type. - .sort() - .join('|')}` + return 'unknown' + })()}${fns[0].is_set_returning_function ? '[]' : ''} + }` ) })()} } diff --git a/test/db/00-init.sql b/test/db/00-init.sql index 5dec1082..00c6a472 100644 --- a/test/db/00-init.sql +++ b/test/db/00-init.sql @@ -159,3 +159,25 @@ second_user AS ( ) SELECT * from initial_user iu cross join second_user su; + +CREATE OR REPLACE FUNCTION public.get_user_audit_setof_single_row(user_row users) +RETURNS SETOF users_audit +LANGUAGE SQL STABLE +ROWS 1 +AS $$ + SELECT * FROM public.users_audit WHERE user_id = user_row.id; +$$; + +CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(user_row users) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = user_row.id; +$$; + +CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(todo_row todos) +RETURNS SETOF todos +LANGUAGE SQL STABLE +AS $$ + SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id"; +$$; diff --git a/test/server/typegen.ts b/test/server/typegen.ts index a5faa8d9..f62631e0 100644 --- a/test/server/typegen.ts +++ b/test/server/typegen.ts @@ -3,7 +3,8 @@ import { app } from './utils' test('typegen: typescript', async () => { const { body } = await app.inject({ method: 'GET', path: '/generators/typescript' }) - expect(body).toMatchInlineSnapshot(` + expect(body).toMatchInlineSnapshot( + ` "export type Json = | string | number @@ -389,33 +390,23 @@ test('typegen: typescript', async () => { } Functions: { blurb: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string } blurb_varchar: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string } details_is_long: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: boolean } details_length: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: number } details_words: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string[] } function_returning_row: { @@ -441,23 +432,31 @@ test('typegen: typescript', async () => { name: string }[] } - polymorphic_function: - | { - Args: { - "": boolean - } - Returns: undefined - } - | { - Args: { - "": string - } - Returns: undefined - } + get_todos_setof_rows: { + Args: + | { user_row: Database["public"]["Tables"]["users"]["Row"] } + | { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + get_user_audit_setof_single_row: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + }[] + } + polymorphic_function: { + Args: { "": string } | { "": boolean } + Returns: undefined + } postgres_fdw_disconnect: { - Args: { - "": string - } + Args: { "": string } Returns: boolean } postgres_fdw_disconnect_all: { @@ -606,7 +605,8 @@ test('typegen: typescript', async () => { }, } as const " - `) + ` + ) }) test('typegen w/ one-to-one relationships', async () => { @@ -615,7 +615,8 @@ test('typegen w/ one-to-one relationships', async () => { path: '/generators/typescript', query: { detect_one_to_one_relationships: 'true' }, }) - expect(body).toMatchInlineSnapshot(` + expect(body).toMatchInlineSnapshot( + ` "export type Json = | string | number @@ -1022,33 +1023,23 @@ test('typegen w/ one-to-one relationships', async () => { } Functions: { blurb: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string } blurb_varchar: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string } details_is_long: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: boolean } details_length: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: number } details_words: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string[] } function_returning_row: { @@ -1074,23 +1065,31 @@ test('typegen w/ one-to-one relationships', async () => { name: string }[] } - polymorphic_function: - | { - Args: { - "": boolean - } - Returns: undefined - } - | { - Args: { - "": string - } - Returns: undefined - } + get_todos_setof_rows: { + Args: + | { user_row: Database["public"]["Tables"]["users"]["Row"] } + | { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + get_user_audit_setof_single_row: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + }[] + } + polymorphic_function: { + Args: { "": string } | { "": boolean } + Returns: undefined + } postgres_fdw_disconnect: { - Args: { - "": string - } + Args: { "": string } Returns: boolean } postgres_fdw_disconnect_all: { @@ -1239,7 +1238,8 @@ test('typegen w/ one-to-one relationships', async () => { }, } as const " - `) + ` + ) }) test('typegen: typescript w/ one-to-one relationships', async () => { @@ -1248,7 +1248,8 @@ test('typegen: typescript w/ one-to-one relationships', async () => { path: '/generators/typescript', query: { detect_one_to_one_relationships: 'true' }, }) - expect(body).toMatchInlineSnapshot(` + expect(body).toMatchInlineSnapshot( + ` "export type Json = | string | number @@ -1655,33 +1656,23 @@ test('typegen: typescript w/ one-to-one relationships', async () => { } Functions: { blurb: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string } blurb_varchar: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string } details_is_long: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: boolean } details_length: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: number } details_words: { - Args: { - "": Database["public"]["Tables"]["todos"]["Row"] - } + Args: { "": Database["public"]["Tables"]["todos"]["Row"] } Returns: string[] } function_returning_row: { @@ -1707,23 +1698,31 @@ test('typegen: typescript w/ one-to-one relationships', async () => { name: string }[] } - polymorphic_function: - | { - Args: { - "": boolean - } - Returns: undefined - } - | { - Args: { - "": string - } - Returns: undefined - } + get_todos_setof_rows: { + Args: + | { user_row: Database["public"]["Tables"]["users"]["Row"] } + | { todo_row: Database["public"]["Tables"]["todos"]["Row"] } + Returns: { + details: string | null + id: number + "user-id": number + }[] + } + get_user_audit_setof_single_row: { + Args: { user_row: Database["public"]["Tables"]["users"]["Row"] } + Returns: { + created_at: string | null + id: number + previous_value: Json | null + user_id: number | null + }[] + } + polymorphic_function: { + Args: { "": string } | { "": boolean } + Returns: undefined + } postgres_fdw_disconnect: { - Args: { - "": string - } + Args: { "": string } Returns: boolean } postgres_fdw_disconnect_all: { @@ -1872,7 +1871,8 @@ test('typegen: typescript w/ one-to-one relationships', async () => { }, } as const " - `) + ` + ) }) test('typegen: go', async () => {