PG Table Types Guide
Use this guide when defining or updating table types with @faasjs/pg declaration merging.
Applicable Scenarios
- Defining application table shapes for
@faasjs/pg - Adding columns or JSONB type shapes
- Checking that query inference flows naturally from table definitions
- Extracting helpers that depend on
TableType,ColumnName, orColumnValue
Default Workflow
- Put the augmentation in an app-owned type file such as
src/db/tables/<table_name>.ts, where<table_name>mirrors the DB table name insnake_case(e.g.user_roles.tsfor theuser_rolestable). Make suretsconfig.jsonincludes that file before expecting inference to change. - In a
.d.tsfile, import@faasjs/pgfirst, then extendTableswithdeclare module '@faasjs/pg'. - Model each table as its runtime row shape, and keep JSON and JSONB object shapes in that merged interface.
- Let
client.query,TableType,ColumnName, andColumnValueinfer from that source instead of forcing result types withas. - Use a narrow assertion only when converting data returned by
client.raw(...)or another raw boundary into an app-specific shape. - Add or update
expectTypeOf(...)coverage when a shared helper or package change affects inference.
Minimal Example
// src/db/tables/user_roles.ts
declare module '@faasjs/pg' {
interface Tables {
user_roles: {
id: number
name: string
metadata: {
age: number
timezone?: string
}
}
}
}
Rules
1. Treat Tables as the source of truth
Tablesdrives table-name inference and column-level type inference.- When a table shape changes, update the merged interface before adjusting query code.
- Keep the type definition close to the application boundary that owns the table.
- Keep the augmentation in a
.d.tsor.tsfile that TypeScript already includes for the app. - When the augmentation lives in a
.d.ts, import@faasjs/pgfirst so TypeScript augments the real module instead of declaring a separate ambient one. - Define JSON and JSONB column shapes in
declare module '@faasjs/pg'instead of scattering duplicate aliases elsewhere. - If inference does not update, check
tsconfig.jsoninclude,files, and project references before adding casts.
2. Keep row shapes concrete
- Model row fields with their actual runtime names and value shapes.
- Prefer exact object types for JSON or JSONB columns instead of
any. - Include optional properties only when the stored JSON shape is genuinely optional.
3. Prefer inference over assertions
- Let typed query results flow from
Tables,select(...),first(), and shared helper generics. - Do not use
asto force query result types unless the data comes fromclient.raw(...)or another raw boundary. - If typed query code seems to need a cast, fix the table definition, selected columns, or helper signature instead.
4. Preserve the consumer extension pattern
- App code should keep using module augmentation on
Tables. - Do not replace declaration merging with an app-specific registry or runtime-only typing.
- When contributing to
@faasjs/pg, keep fallback behavior for untyped tables deliberate and documented.
5. Keep helper types aligned
TableType<T>should represent the row shape for a known table.ColumnName<T>should stay aligned with actual keys of the table type.ColumnValue<T, C>should resolve to the value type for that column.
6. Update type coverage when the surface changes
- Add or update
expectTypeOf(...)assertions when changing declaration merging, shared helpers, or query inference. - If a new query-builder feature affects result shape, test both runtime output and inferred types.
- Prefer targeted type assertions over broad snapshots of unrelated inferred types.
See Also
- PG Query Builder and Raw SQL Guide — building typed queries against these table definitions
- PG Schema and Migrations Guide — creating tables in migrations
- PG Testing Guide — testing with
PgVitestPlugin()
Review Checklist
Tablescontains the new or changed table shape- the augmentation file lives in a source or type root included by
tsconfig.json - JSON and JSONB columns use concrete object types defined in
declare module '@faasjs/pg' - typed query results do not rely on
asoutside raw boundaries - declaration merging still works from consumer code
- helper types stay aligned with the merged table definition
- public or shared type changes include
expectTypeOf(...)coverage