Skip to content

Commit c9fbb77

Browse files
committed
wip
1 parent e643201 commit c9fbb77

File tree

4 files changed

+1065
-647
lines changed

4 files changed

+1065
-647
lines changed

src/server/templates/typescript.ts

Lines changed: 68 additions & 276 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ import type {
88
PostgresView,
99
} from '../../lib/index.js'
1010
import type { GeneratorMetadata } from '../../lib/generators.js'
11-
import {
12-
GENERATE_TYPES_DEFAULT_SCHEMA,
13-
VALID_FUNCTION_ARGS_MODE,
14-
VALID_UNNAMED_FUNCTION_ARG_TYPES,
15-
} from '../constants.js'
11+
import { GENERATE_TYPES_DEFAULT_SCHEMA, VALID_FUNCTION_ARGS_MODE } from '../constants.js'
1612

1713
export const apply = async ({
1814
schemas,
@@ -45,17 +41,18 @@ export const apply = async ({
4541
},
4642
{} as Record<number, (typeof types)[number]>
4743
)
44+
4845
const getFunctionTsReturnType = (fn: PostgresFunction, returnType: string) => {
4946
return `${returnType}${fn.is_set_returning_function && fn.returns_multiple_rows ? '[]' : ''}
50-
${
51-
fn.returns_set_of_table && fn.args.length === 1 && fn.args[0].table_name
52-
? `SetofOptions: {
53-
from: ${JSON.stringify(typesById[fn.args[0].type_id].format)}
54-
to: ${JSON.stringify(fn.return_table_name)}
55-
isOneToOne: ${fn.returns_multiple_rows ? false : true}
47+
${
48+
fn.returns_set_of_table && fn.args.length === 1 && fn.args[0].table_name
49+
? `SetofOptions: {
50+
from: ${JSON.stringify(typesById[fn.args[0].type_id].format)}
51+
to: ${JSON.stringify(fn.return_table_name)}
52+
isOneToOne: ${fn.returns_multiple_rows ? false : true}
53+
}`
54+
: ''
5655
}`
57-
: ''
58-
}`
5956
}
6057

6158
const getFunctionReturnType = (schema: PostgresSchema, fn: PostgresFunction): string => {
@@ -373,42 +370,42 @@ export type Database = {
373370
return '[_ in never]: never'
374371
}
375372
const schemaFunctionsGroupedByName = schemaFunctions
376-
.filter((func) => {
377-
// Get all input args (in, inout, variadic modes)
378-
const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode))
379-
// Case 1: Function has no parameters
380-
if (inArgs.length === 0) {
381-
return true
382-
}
383-
384-
// Case 2: All input args are named
385-
if (!inArgs.some(({ name }) => name === '')) {
386-
return true
387-
}
388-
389-
// Case 3: All unnamed args have default values
390-
if (inArgs.every((arg) => (arg.name === '' ? arg.has_default : true))) {
391-
return true
392-
}
393-
394-
// Case 4: Single unnamed parameter of valid type (json, jsonb, text)
395-
// Exclude all functions definitions that have only one single argument unnamed argument that isn't
396-
// a json/jsonb/text as it won't be considered by PostgREST
397-
if (
398-
(inArgs.length === 1 &&
399-
inArgs[0].name === '' &&
400-
VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id)) ||
401-
// OR if the function have a single unnamed args which is another table (embeded function)
402-
(inArgs.length === 1 &&
403-
inArgs[0].name === '' &&
404-
inArgs[0].table_name &&
405-
func.return_table_name)
406-
) {
407-
return true
408-
}
409-
410-
return false
411-
})
373+
// .filter((func) => {
374+
// // Get all input args (in, inout, variadic modes)
375+
// const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode))
376+
// // Case 1: Function has no parameters
377+
// if (inArgs.length === 0) {
378+
// return true
379+
// }
380+
381+
// // Case 2: All input args are named
382+
// if (!inArgs.some(({ name }) => name === '')) {
383+
// return true
384+
// }
385+
386+
// // Case 3: All unnamed args have default values
387+
// if (inArgs.every((arg) => (arg.name === '' ? arg.has_default : true))) {
388+
// return true
389+
// }
390+
391+
// // Case 4: Single unnamed parameter of valid type (json, jsonb, text)
392+
// // Exclude all functions definitions that have only one single argument unnamed argument that isn't
393+
// // a json/jsonb/text as it won't be considered by PostgREST
394+
// if (
395+
// (inArgs.length === 1 &&
396+
// inArgs[0].name === '' &&
397+
// VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id)) ||
398+
// // OR if the function have a single unnamed args which is another table (embeded function)
399+
// (inArgs.length === 1 &&
400+
// inArgs[0].name === '' &&
401+
// inArgs[0].table_name &&
402+
// func.return_table_name)
403+
// ) {
404+
// return true
405+
// }
406+
407+
// return false
408+
// })
412409
.reduce(
413410
(acc, curr) => {
414411
acc[curr.name] ??= []
@@ -417,238 +414,33 @@ export type Database = {
417414
},
418415
{} as Record<string, PostgresFunction[]>
419416
)
420-
const sortedGroupdSchemaFunctions = Object.entries(
421-
schemaFunctionsGroupedByName
422-
).toSorted((a, b) => a[0].localeCompare(b[0]))
423-
424-
return sortedGroupdSchemaFunctions.map(([fnName, fns]) => {
425-
// Group functions by their argument names signature to detect conflicts
426-
const fnsByArgNames = new Map<string, PostgresFunction[]>()
427-
fns.sort((fn1, fn2) => fn1.id - fn2.id)
428-
429-
fns.forEach((fn) => {
430-
const namedInArgs = fn.args
431-
.filter(({ mode, name }) => VALID_FUNCTION_ARGS_MODE.has(mode) && name !== '')
432-
.map((arg) => arg.name)
433-
.sort()
434-
.join(',')
435-
436-
if (!fnsByArgNames.has(namedInArgs)) {
437-
fnsByArgNames.set(namedInArgs, [])
438-
}
439-
fnsByArgNames.get(namedInArgs)!.push(fn)
440-
})
441417
442-
// For each group of functions sharing the same argument names, check if they have conflicting types
443-
const conflictingSignatures = new Set<string>()
444-
fnsByArgNames.forEach((groupedFns, argNames) => {
445-
if (groupedFns.length > 1) {
446-
// Check if any args have different types within this group
447-
const firstFn = groupedFns[0]
448-
const firstFnArgTypes = new Map(
449-
firstFn.args
450-
.filter(
451-
({ mode, name }) => VALID_FUNCTION_ARGS_MODE.has(mode) && name !== ''
452-
)
453-
.map((arg) => [arg.name, String(arg.type_id)])
454-
)
455-
456-
const hasConflict = groupedFns.some((fn) => {
457-
const fnArgTypes = new Map(
458-
fn.args
459-
.filter(
460-
({ mode, name }) => VALID_FUNCTION_ARGS_MODE.has(mode) && name !== ''
461-
)
462-
.map((arg) => [arg.name, String(arg.type_id)])
463-
)
464-
465-
return [...firstFnArgTypes.entries()].some(
466-
([name, typeId]) => fnArgTypes.get(name) !== typeId
467-
)
418+
return Object.entries(schemaFunctionsGroupedByName).map(([fnName, fns]) => {
419+
const functionSignatures = fns.map((fn) => {
420+
const inArgs = fn.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode))
421+
422+
let argsType = 'Record<PropertyKey, never>'
423+
if (inArgs.length > 0) {
424+
const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => {
425+
const type = types.find(({ id }) => id === type_id)
426+
let tsType = 'unknown'
427+
if (type) {
428+
tsType = pgTypeToTsType(schema, type.name, {
429+
types,
430+
schemas,
431+
tables,
432+
views,
433+
})
434+
}
435+
return { name, type: tsType, has_default }
468436
})
469-
470-
if (hasConflict) {
471-
conflictingSignatures.add(argNames)
472-
}
437+
argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? '?' : ''}: ${type}`)} }`
473438
}
474-
})
475439
476-
// Generate all possible function signatures as a union
477-
const signatures = (() => {
478-
const allSignatures: string[] = []
479-
480-
// First check if we have a no-param function
481-
const noParamFns = fns.filter(
482-
(fn) =>
483-
fn.args.length === 0 ||
484-
// If all the params of a function have non potgrest proxyable arguments
485-
fn.args.every((arg) => !VALID_FUNCTION_ARGS_MODE.has(arg.mode))
486-
)
487-
const unnamedFns = fns.filter((fn) => {
488-
// Only include unnamed functions that:
489-
// 1. Have a single unnamed parameter
490-
// 2. The parameter is of a valid type (json, jsonb, text)
491-
// 3. All parameters have default values
492-
const inArgs = fn.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode))
493-
return (
494-
inArgs.length === 1 &&
495-
inArgs[0].name === '' &&
496-
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id) ||
497-
inArgs[0].has_default) &&
498-
!fn.return_table_name
499-
)
500-
})
501-
502-
// Special case: one no-param function and unnamed param function exist
503-
if (noParamFns.length > 0) {
504-
const noParamFn = noParamFns[0]
505-
const unnamedWithDefaultsFn = unnamedFns.find((fn) =>
506-
fn.args.every((arg) => arg.has_default)
507-
)
508-
509-
// If we have a function with unnamed params that all have defaults, it creates a conflict
510-
if (unnamedWithDefaultsFn) {
511-
// Only generate the error signature in this case
512-
const conflictDesc = [
513-
`${fnName}()`,
514-
`${fnName}( => ${typesById[unnamedWithDefaultsFn.args[0].type_id]?.name || 'unknown'})`,
515-
]
516-
.sort()
517-
.join(', ')
518-
519-
allSignatures.push(`{
520-
Args: Record<PropertyKey, never>
521-
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"
522-
}`)
523-
} else {
524-
// No conflict - just add the no params signature
525-
allSignatures.push(`{
526-
Args: Record<PropertyKey, never>
527-
Returns: ${getFunctionTsReturnType(noParamFn, getFunctionReturnType(schema, noParamFn))}
528-
}`)
529-
}
530-
}
531-
if (unnamedFns.length > 0) {
532-
// If we don't have a no-param function, process the unnamed args
533-
// Take only the first function with unnamed parameters that has a valid type
534-
const validUnnamedFn = unnamedFns.find(
535-
(fn) =>
536-
fn.args.length === 1 &&
537-
fn.args[0].name === '' &&
538-
VALID_UNNAMED_FUNCTION_ARG_TYPES.has(fn.args[0].type_id)
539-
)
540-
541-
if (validUnnamedFn) {
542-
const firstArgType = typesById[validUnnamedFn.args[0].type_id]
543-
const tsType = firstArgType
544-
? pgTypeToTsType(schema, firstArgType.name, {
545-
types,
546-
schemas,
547-
tables,
548-
views,
549-
})
550-
: 'unknown'
551-
552-
allSignatures.push(`{
553-
Args: { "": ${tsType} }
554-
Returns: ${getFunctionTsReturnType(validUnnamedFn, getFunctionReturnType(schema, validUnnamedFn))}
555-
}`)
556-
}
557-
}
558-
const unnamedSetofFunctions = fns.filter((fn) => {
559-
// Only include unnamed functions that:
560-
// 1. Have a single unnamed parameter
561-
// 2. The parameter is of a valid type (json, jsonb, text)
562-
// 3. All parameters have default values
563-
const inArgs = fn.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode))
564-
return inArgs.length === 1 && inArgs[0].name === '' && fn.return_table_name
565-
})
566-
if (unnamedSetofFunctions.length > 0) {
567-
const unnamedEmbededFunctionsSignatures = unnamedSetofFunctions.map((fn) => {
568-
const firstArgType = typesById[fn.args[0].type_id]
569-
const tsType = firstArgType
570-
? pgTypeToTsType(schema, firstArgType.name, {
571-
types,
572-
schemas,
573-
tables,
574-
views,
575-
})
576-
: 'Record<PropertyKey, never>'
577-
return `{ Args: ${tsType}, Returns: ${getFunctionTsReturnType(fn, getFunctionReturnType(schema, fn))} }`
578-
})
579-
allSignatures.push(...unnamedEmbededFunctionsSignatures)
580-
}
440+
return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(fn, getFunctionReturnType(schema, fn))} }`
441+
})
581442
582-
// For functions with named parameters, generate all signatures
583-
const namedFns = fns.filter((fn) => !fn.args.some(({ name }) => name === ''))
584-
namedFns.forEach((fn) => {
585-
const inArgs = fn.args.filter(({ mode }) => mode === 'in')
586-
const namedInArgs = inArgs
587-
.filter((arg) => arg.name !== '')
588-
.map((arg) => arg.name)
589-
.sort()
590-
.join(',')
591-
592-
// If this argument combination would cause a conflict, return an error type signature
593-
if (conflictingSignatures.has(namedInArgs)) {
594-
const conflictingFns = fnsByArgNames.get(namedInArgs)!
595-
const conflictDesc = conflictingFns
596-
.map((cfn) => {
597-
const argsStr = cfn.args
598-
.filter(({ mode }) => mode === 'in')
599-
.map((arg) => {
600-
const type = typesById[arg.type_id]
601-
return `${arg.name} => ${type?.name || 'unknown'}`
602-
})
603-
.sort()
604-
.join(', ')
605-
return `${fnName}(${argsStr})`
606-
})
607-
.sort()
608-
.join(', ')
609-
610-
allSignatures.push(`{
611-
Args: { ${inArgs
612-
.map((arg) => `${JSON.stringify(arg.name)}: unknown`)
613-
.sort()
614-
.join(', ')} }
615-
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"
616-
}`)
617-
} else if (inArgs.length > 0) {
618-
// Generate normal function signature
619-
const returnType = getFunctionReturnType(schema, fn)
620-
allSignatures.push(`{
621-
Args: ${`{ ${inArgs
622-
.map(({ name, type_id, has_default }) => {
623-
const type = typesById[type_id]
624-
let tsType = 'unknown'
625-
if (type) {
626-
tsType = pgTypeToTsType(schema, type.name, {
627-
types,
628-
schemas,
629-
tables,
630-
views,
631-
})
632-
}
633-
return `${JSON.stringify(name)}${has_default ? '?' : ''}: ${tsType}`
634-
})
635-
.sort()
636-
.join(', ')} }`}
637-
Returns: ${getFunctionTsReturnType(fn, returnType)}
638-
}`)
639-
}
640-
})
641-
642-
// Remove duplicates and sort
643-
return Array.from(new Set(allSignatures)).sort()
644-
})()
645-
646-
if (signatures.length > 0) {
647-
// Remove duplicates, sort, and join with |
648-
return `${JSON.stringify(fnName)}: ${signatures.join('\n | ')}`
649-
} else {
650-
return `${JSON.stringify(fnName)}: ${fns.map((fn) => `{ Args: {}, Returns: ${getFunctionTsReturnType(fn, getFunctionReturnType(schema, fn))} }`).join('\n |')}`
651-
}
443+
return `${JSON.stringify(fnName)}:\n${functionSignatures.map((sig) => `| ${sig}`).join('\n')}`
652444
})
653445
})()}
654446
}

0 commit comments

Comments
 (0)