Skip to content

Commit 005b61f

Browse files
authored
Merge pull request #914 from supabase/fix/functions-typescript-type-definition
fix(typescript): function only have union over args
2 parents 1be9d45 + 91407ee commit 005b61f

File tree

3 files changed

+184
-176
lines changed

3 files changed

+184
-176
lines changed

src/server/templates/typescript.ts

+63-77
Original file line numberDiff line numberDiff line change
@@ -277,88 +277,74 @@ export type Database = {
277277
278278
return Object.entries(schemaFunctionsGroupedByName).map(
279279
([fnName, fns]) =>
280-
`${JSON.stringify(fnName)}: ${fns
281-
.map(
282-
({
283-
args,
284-
return_type_id,
285-
return_type_relation_id,
286-
is_set_returning_function,
287-
}) => `{
288-
Args: ${(() => {
289-
const inArgs = args.filter(({ mode }) => mode === 'in')
290-
291-
if (inArgs.length === 0) {
292-
return 'Record<PropertyKey, never>'
293-
}
294-
295-
const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => {
296-
const type = types.find(({ id }) => id === type_id)
297-
let tsType = 'unknown'
298-
if (type) {
299-
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
300-
}
301-
return { name, type: tsType, has_default }
302-
})
280+
`${JSON.stringify(fnName)}: {
281+
Args: ${fns
282+
.map(({ args }) => {
283+
const inArgs = args.filter(({ mode }) => mode === 'in')
303284
304-
return `{
305-
${argsNameAndType.map(
306-
({ name, type, has_default }) =>
307-
`${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`
308-
)}
309-
}`
310-
})()}
311-
Returns: (${(() => {
312-
// Case 1: `returns table`.
313-
const tableArgs = args.filter(({ mode }) => mode === 'table')
314-
if (tableArgs.length > 0) {
315-
const argsNameAndType = tableArgs.map(({ name, type_id }) => {
316-
const type = types.find(({ id }) => id === type_id)
317-
let tsType = 'unknown'
318-
if (type) {
319-
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
285+
if (inArgs.length === 0) {
286+
return 'Record<PropertyKey, never>'
287+
}
288+
289+
const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => {
290+
const type = types.find(({ id }) => id === type_id)
291+
let tsType = 'unknown'
292+
if (type) {
293+
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
294+
}
295+
return { name, type: tsType, has_default }
296+
})
297+
return `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }`
298+
})
299+
// A function can have multiples definitions with differents args, but will always return the same type
300+
.join(' | ')}
301+
Returns: ${(() => {
302+
// Case 1: `returns table`.
303+
const tableArgs = fns[0].args.filter(({ mode }) => mode === 'table')
304+
if (tableArgs.length > 0) {
305+
const argsNameAndType = tableArgs.map(({ name, type_id }) => {
306+
const type = types.find(({ id }) => id === type_id)
307+
let tsType = 'unknown'
308+
if (type) {
309+
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
310+
}
311+
return { name, type: tsType }
312+
})
313+
314+
return `{
315+
${argsNameAndType.map(
316+
({ name, type }) => `${JSON.stringify(name)}: ${type}`
317+
)}
318+
}`
320319
}
321-
return { name, type: tsType }
322-
})
323320
324-
return `{
325-
${argsNameAndType.map(
326-
({ name, type }) => `${JSON.stringify(name)}: ${type}`
327-
)}
328-
}`
329-
}
330-
331-
// Case 2: returns a relation's row type.
332-
const relation = [...tables, ...views].find(
333-
({ id }) => id === return_type_relation_id
334-
)
335-
if (relation) {
336-
return `{
337-
${columnsByTableId[relation.id].map(
338-
(column) =>
339-
`${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, {
340-
types,
341-
schemas,
342-
tables,
343-
views,
344-
})} ${column.is_nullable ? '| null' : ''}`
345-
)}
346-
}`
347-
}
321+
// Case 2: returns a relation's row type.
322+
const relation = [...tables, ...views].find(
323+
({ id }) => id === fns[0].return_type_relation_id
324+
)
325+
if (relation) {
326+
return `{
327+
${columnsByTableId[relation.id].map(
328+
(column) =>
329+
`${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, {
330+
types,
331+
schemas,
332+
tables,
333+
views,
334+
})} ${column.is_nullable ? '| null' : ''}`
335+
)}
336+
}`
337+
}
348338
349-
// Case 3: returns base/array/composite/enum type.
350-
const type = types.find(({ id }) => id === return_type_id)
351-
if (type) {
352-
return pgTypeToTsType(type.name, { types, schemas, tables, views })
353-
}
339+
// Case 3: returns base/array/composite/enum type.
340+
const type = types.find(({ id }) => id === fns[0].return_type_id)
341+
if (type) {
342+
return pgTypeToTsType(type.name, { types, schemas, tables, views })
343+
}
354344
355-
return 'unknown'
356-
})()})${is_set_returning_function ? '[]' : ''}
357-
}`
358-
)
359-
// We only sorted by name on schemaFunctions - here we sort by arg names, arg types, and return type.
360-
.sort()
361-
.join('|')}`
345+
return 'unknown'
346+
})()}${fns[0].is_set_returning_function ? '[]' : ''}
347+
}`
362348
)
363349
})()}
364350
}

test/db/00-init.sql

+22
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,25 @@ second_user AS (
159159
)
160160
SELECT * from initial_user iu
161161
cross join second_user su;
162+
163+
CREATE OR REPLACE FUNCTION public.get_user_audit_setof_single_row(user_row users)
164+
RETURNS SETOF users_audit
165+
LANGUAGE SQL STABLE
166+
ROWS 1
167+
AS $$
168+
SELECT * FROM public.users_audit WHERE user_id = user_row.id;
169+
$$;
170+
171+
CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(user_row users)
172+
RETURNS SETOF todos
173+
LANGUAGE SQL STABLE
174+
AS $$
175+
SELECT * FROM public.todos WHERE "user-id" = user_row.id;
176+
$$;
177+
178+
CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(todo_row todos)
179+
RETURNS SETOF todos
180+
LANGUAGE SQL STABLE
181+
AS $$
182+
SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id";
183+
$$;

0 commit comments

Comments
 (0)