-
Notifications
You must be signed in to change notification settings - Fork 326
-
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?
Beta Was this translation helpful? Give feedback.
All reactions
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
-
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 😁)
Beta Was this translation helpful? Give feedback.
All reactions
-
That was really helpful, thanks!
Beta Was this translation helpful? Give feedback.