Hi! This PR is a proposal for a new transform mode in pgtyped ("ts-pg-promise") which adds support for generating types for pg-promise queries without requiring any runtime dependencies[^1]. In our codebase, we use pg-promise a lot and wanted an easy way to get better type safety for SQL queries, and after a bit of research it turned out pgtyped was pretty close to what we would like to have.
The generator searches for tx.{none,one,oneOrNone,many,manyOrNone} calls in source files (where the name tx is configurable as an array of possible names) and inspects the first argument of the call.
If the query is a string literal, the query is parsed and processed as usual.
Otherwise, the generator invokes TS type checker to get the type of the argument.
If the type is a union of string literals, it finds the constituent with the largest number of parameters and uses that as the query text. The parameters of all other constituents must appear in the largest query; otherwise it wouldn't be possible to generate a sensible parameter type. If this condition is not satisfied, the query is skipped and a warning is printed.
Otherwise, the query is skipped and a warning is printed.
Queries are automatically named based on the name of the enclosing function. If a function contains several queries or if the function name is not unique across all source files, the conflicting names are disambiguated with a number.
The generator creates an interface containing method signatures for each query so that:
the first parameter type is the query literal (or a union of literals if the query is dynamic)
if the query has any parameters, the second parameter is the input parameter object type
The generator adds 5 fallback overloads to make error messages a little less bad (but they are still not great). Based on some testing, TypeScript always reports the last overload if the argument types don't match, so it would probably require changes to TS compiler itself to improve error messages. Ideally, the overload candidate should be the one whose first parameter passes type check.
Raw and identifier bindings (e.g. $(foo^), $(bar~)) are not supported (but they are still parsed so that a proper warning message is printed).
The generated parameter field types are non-optional and by default not nullable. It seems there was no existing mechanism for adjusting nullabilities of individual parameters, so for this mode we've added a magic comment /*nullable*/ so you can have for example:
SELECT foo
FROM bar
WHERE baz = $(baz)/*nullable*/
Configuration options for ts-pg-promise mode:
include: Glob pattern for source files to include
exclude: Glob pattern for source files to exclude
emitFileName: Path to output file
tsconfigPath: Path to tsconfig.json
maxMethodParameterUnionTypeLength (default: 10000): For dynamic queries, the union type can sometimes become very long because TS cross multiplies each union in an interpolated position in a template literal type. If the total length exceeds this value, the method signature is skipped and the result/parameter interface must be imported to the query manually.
variableNames: Array of variable names to search for in source files
interfaceName: Name of the generated interface that contains all the query method signatures
argumentTypeWarning (default: true): If set to true, a warning is printed if the type of the query argument is incompatible with type generation
parameterKindWarning (default: true): If set to true, a warning is printed if the query contains unsupported raw or identifier parameters
There's also some other new general configuration options:
enums.style (default: type): Style of enums to generate, can be enum to generate TS enums or type to generate a union of string literals. For enum style, a few additional options are available:
enums.nameCase: pascal to make enum name pascal case or keep to not transform the database name at all
enums.keyCase: upper, lower or sameAsValue
enums.dropNameSuffix: If the database enum name ends with this string, it is stripped off
interfaceComments (default: true): If set to false, skips generation of comments
db.schema: Database schema (search path)
anonymousColumnWarning (default: true): If set to false, suppresses warning about anonymous columns
Documentation and tests are incomplete/lacking, but we wanted to send this PR for initial feedback now. Sorry for not opening an issue first, and thanks for reading! =)
[^1]: The only required runtime change is that trailing ! or ? in result column names must be stripped off because pgtyped uses this syntax for adjusting result column nullabilities manually.
Hi! This PR is a proposal for a new transform mode in pgtyped ("ts-pg-promise") which adds support for generating types for pg-promise queries without requiring any runtime dependencies[^1]. In our codebase, we use pg-promise a lot and wanted an easy way to get better type safety for SQL queries, and after a bit of research it turned out pgtyped was pretty close to what we would like to have.
The generator searches for
tx.{none,one,oneOrNone,many,manyOrNone}
calls in source files (where the nametx
is configurable as an array of possible names) and inspects the first argument of the call.Queries are automatically named based on the name of the enclosing function. If a function contains several queries or if the function name is not unique across all source files, the conflicting names are disambiguated with a number.
The generator creates an interface containing method signatures for each query so that:
The generator adds 5 fallback overloads to make error messages a little less bad (but they are still not great). Based on some testing, TypeScript always reports the last overload if the argument types don't match, so it would probably require changes to TS compiler itself to improve error messages. Ideally, the overload candidate should be the one whose first parameter passes type check.
Raw and identifier bindings (e.g.
$(foo^)
,$(bar~)
) are not supported (but they are still parsed so that a proper warning message is printed).The generated parameter field types are non-optional and by default not nullable. It seems there was no existing mechanism for adjusting nullabilities of individual parameters, so for this mode we've added a magic comment
/*nullable*/
so you can have for example:Configuration options for
ts-pg-promise
mode:include
: Glob pattern for source files to includeexclude
: Glob pattern for source files to excludeemitFileName
: Path to output filetsconfigPath
: Path to tsconfig.jsonmaxMethodParameterUnionTypeLength
(default:10000
): For dynamic queries, the union type can sometimes become very long because TS cross multiplies each union in an interpolated position in a template literal type. If the total length exceeds this value, the method signature is skipped and the result/parameter interface must be imported to the query manually.variableNames
: Array of variable names to search for in source filesinterfaceName
: Name of the generated interface that contains all the query method signaturesargumentTypeWarning
(default:true
): If set to true, a warning is printed if the type of the query argument is incompatible with type generationparameterKindWarning
(default:true
): If set to true, a warning is printed if the query contains unsupported raw or identifier parametersThere's also some other new general configuration options:
enums.style
(default:type
): Style of enums to generate, can beenum
to generate TS enums ortype
to generate a union of string literals. Forenum
style, a few additional options are available:enums.nameCase
:pascal
to make enum name pascal case orkeep
to not transform the database name at allenums.keyCase
:upper
,lower
orsameAsValue
enums.dropNameSuffix
: If the database enum name ends with this string, it is stripped offinterfaceComments
(default:true
): If set to false, skips generation of commentsdb.schema
: Database schema (search path)anonymousColumnWarning
(default:true
): If set to false, suppresses warning about anonymous columnsDocumentation and tests are incomplete/lacking, but we wanted to send this PR for initial feedback now. Sorry for not opening an issue first, and thanks for reading! =)
[^1]: The only required runtime change is that trailing
!
or?
in result column names must be stripped off because pgtyped uses this syntax for adjusting result column nullabilities manually.