pgt.tableColumns
The column-shape declaration helper. The two-call form — pgt.tableColumns<Meta>()(columns) — pins a Meta generic for your column-meta-type while preserving the exact column shape on the return value, so PgTableRow infers cleanly downstream.
Signature
The first call binds the Meta generic — the column-meta-type your render-components consume (label, sortable, align, etc.). The second call captures the columns object literal and returns it unchanged: pgTable does not transform it, it just narrows the type.
// signature pgt.tableColumns<Meta = {}>()(columns: Record<string, { type: z.ZodType } & Meta>) // returns the same object, narrowed to the exact input shape
Simple-table usage
In a simple-table the table-config lives inline in the <name>-table.tsx file. Each column needs at least a type field carrying a Zod schema; the row type is derived from those schemas through PgTableRow<typeof columns>.
import z from 'zod' import { pgt } from '@westopp/pgtable' const charactersColumns = pgt.tableColumns()({ id: { type: z.string() }, name: { type: z.string() }, power: { type: z.number() }, }) // PgTableRow<typeof charactersColumns> is { id: string; name: string; power: number }
With a column-meta-type
The Meta generic is your contract with your own render-components. Add label, sortable, align, hidden, an icon name, a cell type — whatever your headers and cells want to read off columnInfo. pgTable hands the object back unchanged; the column-meta-type is yours.
// Moderate-table config: column-meta-type pinned via the generic. // pgTable does not read these fields — your render-components do. import z from 'zod' import { pgt } from '@westopp/pgtable' type CharacterColumnMeta = { label: string sortable?: boolean align?: 'left' | 'right' hidden?: boolean } export const charactersColumns = pgt.tableColumns<CharacterColumnMeta>()({ id: { type: z.string(), label: 'ID', hidden: true }, name: { type: z.string(), label: 'Name', sortable: true }, power: { type: z.number(), label: 'Power', sortable: true, align: 'right' }, status: { type: z.enum(['active', 'dormant', 'fallen']), label: 'Status', sortable: true }, })
Consuming columns in a render-component
tableApi.getColumnsArray() walks the columns in declaration order and gives you { columnName, columnInfo } entries. A render-component takes these via props and casts columnInfo to its column-meta-type at the read site.
// Render-component: function of its props, no hooks, no globals. // Headers come from tableApi.getColumnsArray() in the composition-component // above and arrive here as an already-walked array. type HeaderProps = { columns: { columnName: string; columnInfo: CharacterColumnMeta & { type: unknown } }[] } export const CharactersTableHeaders = ({ columns }: HeaderProps) => ( <tr> {columns .filter((c) => !c.columnInfo.hidden) .map((c) => ( <th key={c.columnName} style={{ textAlign: c.columnInfo.align ?? 'left' }}> {c.columnInfo.label} </th> ))} </tr> )
Sharing columns via a column-registry
When the same column definition appears in two or more table-configs with the same semantic meaning — id, createdAt, an actions cell — hoist it to a column-registry and spread it back in. pgt.columnRegistry<Meta>()({...}) is generic over the same Meta as the consuming tableColumns call, so the column-meta-type stays consistent across every table that reuses it.
// Shared building blocks live in tables/shared/. A single-use column stays // inline in its table-config; the registry is for what is actually reused. import z from 'zod' import { pgt } from '@westopp/pgtable' export type SharedColumnMeta = { label: string sortable: boolean align?: 'left' | 'right' hidden?: boolean } export const sharedColumnRegistry = pgt.columnRegistry<SharedColumnMeta>()({ id: { type: z.string(), label: 'ID', sortable: false, hidden: true }, createdAt: { type: z.string(), label: 'Created', sortable: true }, updatedAt: { type: z.string(), label: 'Updated', sortable: true }, })