Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

How to pass Sql object to a function in TypeScript? #384

Answered by Minigugus
ariofrio asked this question in Q&A
Discussion options

I'm trying to encapsulate some logic into a function like so:

function doThings(sql: Sql<??>): ... {
 ...
}

However, I don't know what to put instead of ??. In particular, the constraint on TTypes is JSToPostgresTypeMap, which is not exported. For now, I've copied and pasted its definition, but this seems brittle and prone to breaking in future non-major releases:

function doThings<TTypes extends extends { [name: string]: unknown }>(sql: Sql<TTypes>): ... {
 ...
}

It also adds noise to what should be a straightforward definition, and seems to concern itself with implementation details of the postgres type definitions.

Does anyone know if there's a better way and if I'm missing something important? Alternatively, what do we think about exporting the constraints on Sql generic arguments?

You must be logged in to vote

TTypes is intended to pass custom types information to the sql tagged template string, in order to provide rich completion for parameters (e.g. sql`SELECT ${1n}`; only works if the postgres.BigInt custom type is passed to the postgres() function; the same apply to any user-defined custom types). Without TTypes, it wouldn't be possible to validate parameters correctly (using any everywhere), and thus not possible neither to validate different helpers formats (because of conflicting function signatures), resulting in a type definition like type Sql = (...args: any) => any, which is quite useless...

TTypes just map custom types names to the value they accept/provide, so in your case, you cas...

Replies: 1 comment 1 reply

Comment options

TTypes is intended to pass custom types information to the sql tagged template string, in order to provide rich completion for parameters (e.g. sql`SELECT ${1n}`; only works if the postgres.BigInt custom type is passed to the postgres() function; the same apply to any user-defined custom types). Without TTypes, it wouldn't be possible to validate parameters correctly (using any everywhere), and thus not possible neither to validate different helpers formats (because of conflicting function signatures), resulting in a type definition like type Sql = (...args: any) => any, which is quite useless...

TTypes just map custom types names to the value they accept/provide, so in your case, you case directly pass custom types you expect the sql support instead of using a generic TTypes:

// no custom types
function doThings0(sql: postgres.Sql<{}>) {
 sql`${1n}` // error: `1n` not assignable to `SerializableParameter<never>`
}
// with custom types
function doThings1(sql: postgres.Sql<{ bigint: bigint }>) {
 sql`${1n}` // ok
 sql`${sql.typed.bigint(1n)}` // ok
 sql`${sql.typed.notfound(() => { throw 'wut'; })}` // error: `notfound` doesn't exists on `{ bigint: (value: bigint) => Parameter<bigint> }`
 sql`${() => { throw 'wut'; }}` // error: `() => never` not assignable to `SerializableParameter<bigint>`
}
// disabling parameters validation entirely
function doThings2(sql: postgres.Sql<any>) {
 sql`${1n}` // ok
 sql`${sql.typed.bigint(1n)}` // ok
 sql`${sql.typed.notfound(() => { throw 'wut'; })}` // ok
 sql`${() => { throw 'wut'; }}` // ok
}

Anyway, I don't think you need to make doThings generic over TTypes. Just define custom types here, so that you can validate parameters used in doThings while enforcing the caller to provide a sql instance that support custom types you need.

If you want to get accepted parameters types for a specific instance of Sql, you can use postgres.SerializableParameters<TTypes[keyof TTypes]>, for instance like:

async function select<
 T extends {}, // custom types supported by the user-provided `Sql<T>`
 U extends postgres.SerializableParameter<T[keyof T]> // parameters types supported by `postgres` (the `<T[keyof T]>` part is optional but adds support for user-defined custom types)
>(
 sql: postgres.Sql<T>,
 value: U
) {
 const results = await sql<[{ value: U }]>`SELECT ${value} AS value`
 return results[0].value // `select` returns `Promise<U>` then
}
const sql = postgres({
 types: {
 BigInt: postgres.BigInt
 }
})
const bigints: bigint = await select(sql, 42n) // ok: `select(sql, 42n)` return `Promise<bigint>`

I hope it helped you 👍 I agree types might look strange in postgres, but because sql supports a lot of features that aren't easy to type well (e.g. custom types, undefined transform, implicit JSON, multiple helpers format depending on SQL context, ...). Maybe when I'll have time, I'll add jsdoc on types (PR welcomed by the way 😁)

You must be logged in to vote
1 reply
Comment options

That was really helpful, thanks!

Answer selected by ariofrio
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /