@@ -284,101 +284,284 @@ export type Database = {
284284 { } as Record < string , PostgresFunction [ ] >
285285 )
286286
287- return Object . entries ( schemaFunctionsGroupedByName ) . map (
288- ( [ fnName , fns ] ) =>
289- `${ JSON . stringify ( fnName ) } : {
290- Args: ${ fns
291- . map ( ( { args } ) => {
292- const inArgs = args . filter ( ( { mode } ) => mode === 'in' )
293-
294- if ( inArgs . length === 0 ) {
295- return 'Record<PropertyKey, never>'
296- }
297- const argsNameAndType = inArgs . map ( ( { name, type_id, has_default } ) => {
298- const type = typesById [ type_id ]
287+ return Object . entries ( schemaFunctionsGroupedByName ) . map ( ( [ fnName , fns ] ) => {
288+ // Group functions by their argument names signature to detect conflicts
289+ const fnsByArgNames = new Map < string , PostgresFunction [ ] > ( )
290+
291+ fns . forEach ( ( fn ) => {
292+ const namedInArgs = fn . args
293+ . filter (
294+ ( { mode, name } ) => [ 'in' , 'inout' , 'variadic' ] . includes ( mode ) && name !== ''
295+ )
296+ . map ( ( arg ) => arg . name )
297+ . sort ( )
298+ . join ( ',' )
299+
300+ if ( ! fnsByArgNames . has ( namedInArgs ) ) {
301+ fnsByArgNames . set ( namedInArgs , [ ] )
302+ }
303+ fnsByArgNames . get ( namedInArgs ) ! . push ( fn )
304+ } )
305+
306+ // For each group of functions sharing the same argument names, check if they have conflicting types
307+ const conflictingSignatures = new Set < string > ( )
308+ fnsByArgNames . forEach ( ( groupedFns , argNames ) => {
309+ if ( groupedFns . length > 1 ) {
310+ // Check if any args have different types within this group
311+ const firstFn = groupedFns [ 0 ]
312+ const firstFnArgTypes = new Map (
313+ firstFn . args
314+ . filter (
315+ ( { mode, name } ) =>
316+ [ 'in' , 'inout' , 'variadic' ] . includes ( mode ) && name !== ''
317+ )
318+ . map ( ( arg ) => [ arg . name , String ( arg . type_id ) ] )
319+ )
320+
321+ const hasConflict = groupedFns . some ( ( fn ) => {
322+ const fnArgTypes = new Map (
323+ fn . args
324+ . filter (
325+ ( { mode, name } ) =>
326+ [ 'in' , 'inout' , 'variadic' ] . includes ( mode ) && name !== ''
327+ )
328+ . map ( ( arg ) => [ arg . name , String ( arg . type_id ) ] )
329+ )
330+
331+ return [ ...firstFnArgTypes . entries ( ) ] . some (
332+ ( [ name , typeId ] ) => fnArgTypes . get ( name ) !== typeId
333+ )
334+ } )
335+
336+ if ( hasConflict ) {
337+ conflictingSignatures . add ( argNames )
338+ }
339+ }
340+ } )
341+
342+ // Generate all possible function signatures as a union
343+ const signatures = ( ( ) => {
344+ // Special case: if any function has a single unnamed parameter
345+ const unnamedFns = fns . filter ( ( fn ) => fn . args . some ( ( { name } ) => name === '' ) )
346+ if ( unnamedFns . length > 0 ) {
347+ // Take only the first function with unnamed parameters
348+ const firstUnnamedFn = unnamedFns [ 0 ]
349+ const firstArgType = typesById [ firstUnnamedFn . args [ 0 ] . type_id ]
350+ const tsType = firstArgType
351+ ? pgTypeToTsType ( firstArgType . name , { types, schemas, tables, views } )
352+ : 'unknown'
353+
354+ const returnType = ( ( ) => {
355+ // Case 1: `returns table`.
356+ const tableArgs = firstUnnamedFn . args . filter ( ( { mode } ) => mode === 'table' )
357+ if ( tableArgs . length > 0 ) {
358+ const argsNameAndType = tableArgs
359+ . map ( ( { name, type_id } ) => {
360+ const type = types . find ( ( { id } ) => id === type_id )
299361 let tsType = 'unknown'
300362 if ( type ) {
301363 tsType = pgTypeToTsType ( type . name , { types, schemas, tables, views } )
302364 }
303- return { name, type : tsType , has_default }
365+ return { name, type : tsType }
304366 } )
305- return `{ ${ argsNameAndType . map ( ( { name, type, has_default } ) => `${ JSON . stringify ( name ) } ${ has_default ? '?' : '' } : ${ type } ` ) } }`
367+ . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
368+
369+ return `{
370+ ${ argsNameAndType . map (
371+ ( { name, type } ) => `${ JSON . stringify ( name ) } : ${ type } `
372+ ) }
373+ }`
374+ }
375+
376+ // Case 2: returns a relation's row type.
377+ const relation = [ ...tables , ...views ] . find (
378+ ( { id } ) => id === firstUnnamedFn . return_type_relation_id
379+ )
380+ if ( relation ) {
381+ return `{
382+ ${ columnsByTableId [ relation . id ]
383+ . map (
384+ ( column ) =>
385+ `${ JSON . stringify ( column . name ) } : ${ pgTypeToTsType ( column . format , {
386+ types,
387+ schemas,
388+ tables,
389+ views,
390+ } ) } ${ column . is_nullable ? '| null' : '' } `
391+ )
392+ . sort ( )
393+ . join ( ',\n' ) }
394+ }`
395+ }
396+
397+ // Case 3: returns base/array/composite/enum type.
398+ const returnType = types . find (
399+ ( { id } ) => id === firstUnnamedFn . return_type_id
400+ )
401+ if ( returnType ) {
402+ return pgTypeToTsType ( returnType . name , { types, schemas, tables, views } )
403+ }
404+
405+ return 'unknown'
406+ } ) ( )
407+
408+ return [
409+ `{
410+ Args: { "": ${ tsType } },
411+ Returns: ${ returnType } ${ firstUnnamedFn . is_set_returning_function && firstUnnamedFn . returns_multiple_rows ? '[]' : '' }
412+ ${
413+ firstUnnamedFn . returns_set_of_table
414+ ? `,
415+ SetofOptions: {
416+ from: ${
417+ firstUnnamedFn . args . length > 0 && firstUnnamedFn . args [ 0 ] . table_name
418+ ? JSON . stringify ( typesById [ firstUnnamedFn . args [ 0 ] . type_id ] . format )
419+ : '"*"'
420+ } ,
421+ to: ${ JSON . stringify ( firstUnnamedFn . return_table_name ) } ,
422+ isOneToOne: ${ firstUnnamedFn . returns_multiple_rows ? false : true }
423+ }`
424+ : ''
425+ }
426+ }` ,
427+ ]
428+ }
429+
430+ // For functions with named parameters, generate all signatures
431+ const namedFns = fns . filter ( ( fn ) => ! fn . args . some ( ( { name } ) => name === '' ) )
432+ return namedFns . map ( ( fn ) => {
433+ const inArgs = fn . args . filter ( ( { mode } ) => mode === 'in' )
434+ const namedInArgs = inArgs
435+ . filter ( ( arg ) => arg . name !== '' )
436+ . map ( ( arg ) => arg . name )
437+ . sort ( )
438+ . join ( ',' )
439+
440+ // If this argument combination would cause a conflict, return an error type signature
441+ if ( conflictingSignatures . has ( namedInArgs ) ) {
442+ const conflictingFns = fnsByArgNames . get ( namedInArgs ) !
443+ const conflictDesc = conflictingFns
444+ . map ( ( cfn ) => {
445+ const argsStr = cfn . args
446+ . filter ( ( { mode } ) => mode === 'in' )
447+ . map ( ( arg ) => {
448+ const type = typesById [ arg . type_id ]
449+ return `${ arg . name } => ${ type ?. name || 'unknown' } `
450+ } )
451+ . sort ( )
452+ . join ( ', ' )
453+ return `${ fnName } (${ argsStr } )`
306454 } )
307- . toSorted ( )
308- // A function can have multiples definitions with differents args, but will always return the same type
309- . join ( ' | ' ) }
310- Returns: ${ ( ( ) => {
311- // Case 1: `returns table`.
312- const tableArgs = fns [ 0 ] . args . filter ( ( { mode } ) => mode === 'table' )
313- if ( tableArgs . length > 0 ) {
314- const argsNameAndType = tableArgs . map ( ( { name, type_id } ) => {
455+ . sort ( )
456+ . join ( ', ' )
457+
458+ return `{
459+ Args: { ${ inArgs
460+ . map ( ( arg ) => `${ JSON . stringify ( arg . name ) } : unknown` )
461+ . sort ( )
462+ . join ( ', ' ) } },
463+ 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"
464+ }`
465+ }
466+
467+ // Generate normal function signature
468+ const returnType = ( ( ) => {
469+ // Case 1: `returns table`.
470+ const tableArgs = fn . args . filter ( ( { mode } ) => mode === 'table' )
471+ if ( tableArgs . length > 0 ) {
472+ const argsNameAndType = tableArgs
473+ . map ( ( { name, type_id } ) => {
315474 const type = types . find ( ( { id } ) => id === type_id )
316475 let tsType = 'unknown'
317476 if ( type ) {
318477 tsType = pgTypeToTsType ( type . name , { types, schemas, tables, views } )
319478 }
320479 return { name, type : tsType }
321480 } )
481+ . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
322482
323- return `{
324- ${ argsNameAndType . map (
325- ( { name, type } ) => `${ JSON . stringify ( name ) } : ${ type } `
326- ) }
327- }`
328- }
483+ return `{
484+ ${ argsNameAndType . map (
485+ ( { name, type } ) => `${ JSON . stringify ( name ) } : ${ type } `
486+ ) }
487+ }`
488+ }
329489
330- // Case 2: returns a relation's row type.
331- const relation = [ ...tables , ...views ] . find (
332- ( { id } ) => id === fns [ 0 ] . return_type_relation_id
333- )
334- if ( relation ) {
335- return `{
336- ${ columnsByTableId [ relation . id ] . map (
490+ // Case 2: returns a relation's row type.
491+ const relation = [ ...tables , ...views ] . find (
492+ ( { id } ) => id === fn . return_type_relation_id
493+ )
494+ if ( relation ) {
495+ return `{
496+ ${ columnsByTableId [ relation . id ]
497+ . map (
337498 ( column ) =>
338499 `${ JSON . stringify ( column . name ) } : ${ pgTypeToTsType ( column . format , {
339500 types,
340501 schemas,
341502 tables,
342503 views,
343504 } ) } ${ column . is_nullable ? '| null' : '' } `
344- ) }
345- }`
346- }
347-
348- // Case 3: returns base/array/composite/enum type.
349- const type = types . find ( ( { id } ) => id === fns [ 0 ] . return_type_id )
350- if ( type ) {
351- return pgTypeToTsType ( type . name , { types, schemas, tables, views } )
352- }
353-
354- return 'unknown'
355- } ) ( ) } ${ fns [ 0 ] . is_set_returning_function && fns [ 0 ] . returns_multiple_rows ? '[]' : '' }
356- ${
357- // if the function return a set of a table and some definition take in parameter another table
358- fns [ 0 ] . returns_set_of_table
359- ? `SetofOptions: {
360- from: ${ fns
361- . map ( ( fnd ) => {
362- if ( fnd . args . length > 0 && fnd . args [ 0 ] . table_name ) {
363- const tableType = typesById [ fnd . args [ 0 ] . type_id ]
364- return JSON . stringify ( tableType . format )
365- } else {
366- // If the function can be called with scalars or without any arguments, then add a * matching everything
367- return '"*"'
368- }
369- } )
370- // Dedup before join
371- . filter ( ( value , index , self ) => self . indexOf ( value ) === index )
372- . toSorted ( )
373- . join ( ' | ' ) }
374- to: ${ JSON . stringify ( fns [ 0 ] . return_table_name ) }
375- isOneToOne: ${ fns [ 0 ] . returns_multiple_rows ? false : true }
505+ )
506+ . sort ( )
507+ . join ( ',\n' ) }
508+ }`
376509 }
377- `
510+
511+ // Case 3: returns base/array/composite/enum type.
512+ const type = types . find ( ( { id } ) => id === fn . return_type_id )
513+ if ( type ) {
514+ return pgTypeToTsType ( type . name , { types, schemas, tables, views } )
515+ }
516+
517+ return 'unknown'
518+ } ) ( )
519+
520+ return `{
521+ Args: ${
522+ inArgs . length === 0
523+ ? 'Record<PropertyKey, never>'
524+ : `{ ${ inArgs
525+ . map ( ( { name, type_id, has_default } ) => {
526+ const type = typesById [ type_id ]
527+ let tsType = 'unknown'
528+ if ( type ) {
529+ tsType = pgTypeToTsType ( type . name , {
530+ types,
531+ schemas,
532+ tables,
533+ views,
534+ } )
535+ }
536+ return `${ JSON . stringify ( name ) } ${ has_default ? '?' : '' } : ${ tsType } `
537+ } )
538+ . sort ( )
539+ . join ( ', ' ) } }`
540+ } ,
541+ Returns: ${ returnType } ${ fn . is_set_returning_function && fn . returns_multiple_rows ? '[]' : '' }
542+ ${
543+ fn . returns_set_of_table
544+ ? `,
545+ SetofOptions: {
546+ from: ${
547+ fn . args . length > 0 && fn . args [ 0 ] . table_name
548+ ? JSON . stringify ( typesById [ fn . args [ 0 ] . type_id ] . format )
549+ : '"*"'
550+ } ,
551+ to: ${ JSON . stringify ( fn . return_table_name ) } ,
552+ isOneToOne: ${ fn . returns_multiple_rows ? false : true }
553+ }`
378554 : ''
379555 }
380556 }`
381- )
557+ } )
558+ } ) ( )
559+
560+ // Remove duplicates, sort, and join with |
561+ return `${ JSON . stringify ( fnName ) } : ${ Array . from ( new Set ( signatures ) )
562+ . sort ( )
563+ . join ( '\n | ' ) } `
564+ } )
382565 } ) ( ) }
383566 }
384567 Enums: {
0 commit comments