@@ -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