@@ -284,101 +284,284 @@ export type Database = {
284
284
{ } as Record < string , PostgresFunction [ ] >
285
285
)
286
286
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 )
299
361
let tsType = 'unknown'
300
362
if ( type ) {
301
363
tsType = pgTypeToTsType ( type . name , { types, schemas, tables, views } )
302
364
}
303
- return { name, type : tsType , has_default }
365
+ return { name, type : tsType }
304
366
} )
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 } )`
306
454
} )
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 } ) => {
315
474
const type = types . find ( ( { id } ) => id === type_id )
316
475
let tsType = 'unknown'
317
476
if ( type ) {
318
477
tsType = pgTypeToTsType ( type . name , { types, schemas, tables, views } )
319
478
}
320
479
return { name, type : tsType }
321
480
} )
481
+ . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
322
482
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
+ }
329
489
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 (
337
498
( column ) =>
338
499
`${ JSON . stringify ( column . name ) } : ${ pgTypeToTsType ( column . format , {
339
500
types,
340
501
schemas,
341
502
tables,
342
503
views,
343
504
} ) } ${ 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
+ }`
376
509
}
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
+ }`
378
554
: ''
379
555
}
380
556
}`
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
+ } )
382
565
} ) ( ) }
383
566
}
384
567
Enums: {
0 commit comments