Skip to content

Commit 2aa4dbc

Browse files
committed
feat: handle priority and no-params and default params conflict
1 parent b69e594 commit 2aa4dbc

File tree

1 file changed

+154
-183
lines changed

1 file changed

+154
-183
lines changed

src/server/templates/typescript.ts

Lines changed: 154 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,55 @@ export const apply = async ({
3939
},
4040
{} as Record<string, (typeof types)[number]>
4141
)
42+
43+
const getReturnType = (fn: PostgresFunction): string => {
44+
// Case 1: `returns table`.
45+
const tableArgs = fn.args.filter(({ mode }) => mode === 'table')
46+
if (tableArgs.length > 0) {
47+
const argsNameAndType = tableArgs
48+
.map(({ name, type_id }) => {
49+
const type = types.find(({ id }) => id === type_id)
50+
let tsType = 'unknown'
51+
if (type) {
52+
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
53+
}
54+
return { name, type: tsType }
55+
})
56+
.sort((a, b) => a.name.localeCompare(b.name))
57+
58+
return `{
59+
${argsNameAndType.map(({ name, type }) => `${JSON.stringify(name)}: ${type}`)}
60+
}`
61+
}
62+
63+
// Case 2: returns a relation's row type.
64+
const relation = [...tables, ...views].find(({ id }) => id === fn.return_type_relation_id)
65+
if (relation) {
66+
return `{
67+
${columnsByTableId[relation.id]
68+
.map(
69+
(column) =>
70+
`${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, {
71+
types,
72+
schemas,
73+
tables,
74+
views,
75+
})} ${column.is_nullable ? '| null' : ''}`
76+
)
77+
.sort()
78+
.join(',\n')}
79+
}`
80+
}
81+
82+
// Case 3: returns base/array/composite/enum type.
83+
const type = types.find(({ id }) => id === fn.return_type_id)
84+
if (type) {
85+
return pgTypeToTsType(type.name, { types, schemas, tables, views })
86+
}
87+
88+
return 'unknown'
89+
}
90+
4291
columns
4392
.filter((c) => c.table_id in columnsByTableId)
4493
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
@@ -106,14 +155,7 @@ export type Database = {
106155
),
107156
...schemaFunctions
108157
.filter((fn) => fn.argument_types === table.name)
109-
.map((fn) => {
110-
const type = typesById[fn.return_type_id]
111-
let tsType = 'unknown'
112-
if (type) {
113-
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
114-
}
115-
return `${JSON.stringify(fn.name)}: ${tsType} | null`
116-
}),
158+
.map((fn) => `${JSON.stringify(fn.name)}: ${getReturnType(fn)} | null`),
117159
]}
118160
}
119161
Insert: {
@@ -374,95 +416,79 @@ export type Database = {
374416
375417
// Generate all possible function signatures as a union
376418
const signatures = (() => {
377-
// Special case: if any function has a single unnamed parameter
378-
const unnamedFns = fns.filter((fn) => fn.args.some(({ name }) => name === ''))
379-
if (unnamedFns.length > 0) {
380-
// Take only the first function with unnamed parameters
381-
const firstUnnamedFn = unnamedFns[0]
382-
const firstArgType = typesById[firstUnnamedFn.args[0].type_id]
383-
const tsType = firstArgType
384-
? pgTypeToTsType(firstArgType.name, { types, schemas, tables, views })
385-
: 'unknown'
386-
387-
const returnType = (() => {
388-
// Case 1: `returns table`.
389-
const tableArgs = firstUnnamedFn.args.filter(({ mode }) => mode === 'table')
390-
if (tableArgs.length > 0) {
391-
const argsNameAndType = tableArgs
392-
.map(({ name, type_id }) => {
393-
const type = types.find(({ id }) => id === type_id)
394-
let tsType = 'unknown'
395-
if (type) {
396-
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
397-
}
398-
return { name, type: tsType }
399-
})
400-
.sort((a, b) => a.name.localeCompare(b.name))
419+
const allSignatures: string[] = []
420+
421+
// First check if we have a no-param function
422+
const noParamFns = fns.filter((fn) => fn.args.length === 0)
423+
const unnamedFns = fns.filter((fn) => {
424+
// Only include unnamed functions that:
425+
// 1. Have a single unnamed parameter
426+
// 2. The parameter is of a valid type (json, jsonb, text)
427+
// 3. All parameters have default values
428+
const inArgs = fn.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode))
429+
return (
430+
inArgs.length === 1 &&
431+
inArgs[0].name === '' &&
432+
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id) ||
433+
inArgs[0].has_default)
434+
)
435+
})
401436
402-
return `{
403-
${argsNameAndType.map(
404-
({ name, type }) => `${JSON.stringify(name)}: ${type}`
405-
)}
406-
}`
407-
}
437+
// Special case: one no-param function and unnamed param function exist
438+
if (noParamFns.length === 1) {
439+
const noParamFn = noParamFns[0]
440+
const unnamedWithDefaultsFn = unnamedFns.find((fn) =>
441+
fn.args.every((arg) => arg.has_default)
442+
)
408443
409-
// Case 2: returns a relation's row type.
410-
const relation = [...tables, ...views].find(
411-
({ id }) => id === firstUnnamedFn.return_type_relation_id
412-
)
413-
if (relation) {
414-
return `{
415-
${columnsByTableId[relation.id]
416-
.map(
417-
(column) =>
418-
`${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, {
419-
types,
420-
schemas,
421-
tables,
422-
views,
423-
})} ${column.is_nullable ? '| null' : ''}`
424-
)
425-
.sort()
426-
.join(',\n')}
427-
}`
428-
}
444+
// If we have a function with unnamed params that all have defaults, it creates a conflict
445+
if (unnamedWithDefaultsFn) {
446+
// Only generate the error signature in this case
447+
const conflictDesc = [
448+
`${fnName}()`,
449+
`${fnName}( => ${typesById[unnamedWithDefaultsFn.args[0].type_id]?.name || 'unknown'})`,
450+
]
451+
.sort()
452+
.join(', ')
429453
430-
// Case 3: returns base/array/composite/enum type.
431-
const returnType = types.find(
432-
({ id }) => id === firstUnnamedFn.return_type_id
433-
)
434-
if (returnType) {
435-
return pgTypeToTsType(returnType.name, { types, schemas, tables, views })
436-
}
454+
allSignatures.push(`{
455+
Args: Record<PropertyKey, never>
456+
Returns: { error: true } & "Could not choose the best candidate function between: ${conflictDesc}. Try renaming the parameters or the function itself in the database so function overloading can be resolved"
457+
}`)
458+
} else {
459+
// No conflict - just add the no params signature
460+
allSignatures.push(`{
461+
Args: Record<PropertyKey, never>
462+
Returns: ${getReturnType(noParamFn)}${noParamFn.is_set_returning_function && noParamFn.returns_multiple_rows ? '[]' : ''}
463+
}`)
464+
}
465+
}
466+
if (unnamedFns.length > 0) {
467+
// If we don't have a no-param function, process the unnamed args
468+
// Take only the first function with unnamed parameters that has a valid type
469+
const validUnnamedFn = unnamedFns.find(
470+
(fn) =>
471+
fn.args.length === 1 &&
472+
fn.args[0].name === '' &&
473+
VALID_UNNAMED_FUNCTION_ARG_TYPES.has(fn.args[0].type_id)
474+
)
437475
438-
return 'unknown'
439-
})()
440-
441-
return [
442-
`{
443-
Args: { "": ${tsType} },
444-
Returns: ${returnType}${firstUnnamedFn.is_set_returning_function && firstUnnamedFn.returns_multiple_rows ? '[]' : ''}
445-
${
446-
firstUnnamedFn.returns_set_of_table
447-
? `,
448-
SetofOptions: {
449-
from: ${
450-
firstUnnamedFn.args.length > 0 && firstUnnamedFn.args[0].table_name
451-
? JSON.stringify(typesById[firstUnnamedFn.args[0].type_id].format)
452-
: '"*"'
453-
},
454-
to: ${JSON.stringify(firstUnnamedFn.return_table_name)},
455-
isOneToOne: ${firstUnnamedFn.returns_multiple_rows ? false : true}
456-
}`
457-
: ''
458-
}
459-
}`,
460-
]
476+
if (validUnnamedFn) {
477+
const firstArgType = typesById[validUnnamedFn.args[0].type_id]
478+
const tsType = firstArgType
479+
? pgTypeToTsType(firstArgType.name, { types, schemas, tables, views })
480+
: 'unknown'
481+
482+
allSignatures.push(`{
483+
Args: { "": ${tsType} }
484+
Returns: ${getReturnType(validUnnamedFn)}${validUnnamedFn.is_set_returning_function && validUnnamedFn.returns_multiple_rows ? '[]' : ''}
485+
}`)
486+
}
461487
}
462488
463489
// For functions with named parameters, generate all signatures
464490
const namedFns = fns.filter((fn) => !fn.args.some(({ name }) => name === ''))
465-
return namedFns.map((fn) => {
491+
namedFns.forEach((fn) => {
466492
const inArgs = fn.args.filter(({ mode }) => mode === 'in')
467493
const namedInArgs = inArgs
468494
.filter((arg) => arg.name !== '')
@@ -488,112 +514,57 @@ export type Database = {
488514
.sort()
489515
.join(', ')
490516
491-
return `{
517+
allSignatures.push(`{
492518
Args: { ${inArgs
493519
.map((arg) => `${JSON.stringify(arg.name)}: unknown`)
494520
.sort()
495-
.join(', ')} },
521+
.join(', ')} }
496522
Returns: { error: true } & "Could not choose the best candidate function between: ${conflictDesc}. Try renaming the parameters or the function itself in the database so function overloading can be resolved"
497-
}`
498-
}
499-
500-
// Generate normal function signature
501-
const returnType = (() => {
502-
// Case 1: `returns table`.
503-
const tableArgs = fn.args.filter(({ mode }) => mode === 'table')
504-
if (tableArgs.length > 0) {
505-
const argsNameAndType = tableArgs
506-
.map(({ name, type_id }) => {
507-
const type = types.find(({ id }) => id === type_id)
523+
}`)
524+
} else if (inArgs.length > 0) {
525+
// Generate normal function signature
526+
const returnType = getReturnType(fn)
527+
allSignatures.push(`{
528+
Args: ${`{ ${inArgs
529+
.map(({ name, type_id, has_default }) => {
530+
const type = typesById[type_id]
508531
let tsType = 'unknown'
509532
if (type) {
510-
tsType = pgTypeToTsType(type.name, { types, schemas, tables, views })
533+
tsType = pgTypeToTsType(type.name, {
534+
types,
535+
schemas,
536+
tables,
537+
views,
538+
})
511539
}
512-
return { name, type: tsType }
540+
return `${JSON.stringify(name)}${has_default ? '?' : ''}: ${tsType}`
513541
})
514-
.sort((a, b) => a.name.localeCompare(b.name))
515-
516-
return `{
517-
${argsNameAndType.map(
518-
({ name, type }) => `${JSON.stringify(name)}: ${type}`
519-
)}
520-
}`
521-
}
522-
523-
// Case 2: returns a relation's row type.
524-
const relation = [...tables, ...views].find(
525-
({ id }) => id === fn.return_type_relation_id
526-
)
527-
if (relation) {
528-
return `{
529-
${columnsByTableId[relation.id]
530-
.map(
531-
(column) =>
532-
`${JSON.stringify(column.name)}: ${pgTypeToTsType(column.format, {
533-
types,
534-
schemas,
535-
tables,
536-
views,
537-
})} ${column.is_nullable ? '| null' : ''}`
538-
)
539-
.sort()
540-
.join(',\n')}
541-
}`
542-
}
543-
544-
// Case 3: returns base/array/composite/enum type.
545-
const type = types.find(({ id }) => id === fn.return_type_id)
546-
if (type) {
547-
return pgTypeToTsType(type.name, { types, schemas, tables, views })
548-
}
549-
550-
return 'unknown'
551-
})()
552-
553-
return `{
554-
Args: ${
555-
inArgs.length === 0
556-
? 'Record<PropertyKey, never>'
557-
: `{ ${inArgs
558-
.map(({ name, type_id, has_default }) => {
559-
const type = typesById[type_id]
560-
let tsType = 'unknown'
561-
if (type) {
562-
tsType = pgTypeToTsType(type.name, {
563-
types,
564-
schemas,
565-
tables,
566-
views,
567-
})
568-
}
569-
return `${JSON.stringify(name)}${has_default ? '?' : ''}: ${tsType}`
570-
})
571-
.sort()
572-
.join(', ')} }`
573-
},
574-
Returns: ${returnType}${fn.is_set_returning_function && fn.returns_multiple_rows ? '[]' : ''}
575-
${
576-
fn.returns_set_of_table
577-
? `,
578-
SetofOptions: {
579-
from: ${
580-
fn.args.length > 0 && fn.args[0].table_name
581-
? JSON.stringify(typesById[fn.args[0].type_id].format)
582-
: '"*"'
583-
},
584-
to: ${JSON.stringify(fn.return_table_name)},
585-
isOneToOne: ${fn.returns_multiple_rows ? false : true}
586-
}`
587-
: ''
588-
}
589-
}`
542+
.sort()
543+
.join(', ')} }`}
544+
Returns: ${returnType}${fn.is_set_returning_function && fn.returns_multiple_rows ? '[]' : ''}
545+
${
546+
fn.returns_set_of_table
547+
? `SetofOptions: {
548+
from: ${
549+
fn.args.length > 0 && fn.args[0].table_name
550+
? JSON.stringify(typesById[fn.args[0].type_id].format)
551+
: '"*"'
552+
}
553+
to: ${JSON.stringify(fn.return_table_name)}
554+
isOneToOne: ${fn.returns_multiple_rows ? false : true}
555+
}`
556+
: ''
557+
}
558+
}`)
559+
}
590560
})
561+
562+
// Remove duplicates and sort
563+
return Array.from(new Set(allSignatures)).sort()
591564
})()
592565
593566
// Remove duplicates, sort, and join with |
594-
return `${JSON.stringify(fnName)}: ${Array.from(new Set(signatures))
595-
.sort()
596-
.join('\n | ')}`
567+
return `${JSON.stringify(fnName)}: ${signatures.join('\n | ')}`
597568
})
598569
})()}
599570
}

0 commit comments

Comments
 (0)