This is the abridged developer documentation for postgresdk
# postgresdk
> Point it at a Postgres schema. Get a fully typed Hono API server and TypeScript client SDK — includes, Zod, transactions, vector & trigram search, soft-delete, and auth.
## What it is [Section titled “What it is”](#what-it-is) `postgresdk` is a CLI + library that **introspects your PostgreSQL schema** and generates two things: * A **Hono API server** — typed routes, Zod validation, auth middleware, soft/hard delete, vector & trigram search. * A **TypeScript client SDK** — typed CRUD, eager-loading `include`s, atomic transactions, type-safe `where` filtering, and pagination. Regenerate any time your schema changes. No hand-written glue, no drift. Code is the source of truth The reference docs are **generated from postgresdk’s own source** — the CLI, the `Config` type, and the WHERE operators — so they can’t go stale. Built for agents Every page is available as plain Markdown, plus [`/llms.txt`](/llms.txt), [`/llms-full.txt`](/llms-full.txt), and [`/llms-small.txt`](/llms-small.txt) for downstream agents to read verbatim. See real output The [Generated API example](/reference/generated-api-example/) is a real `CONTRACT.md` produced by running postgresdk against a fixture schema. Typed end to end Nested includes infer their own return types. `where` operators reject the wrong column types at compile time. ## For LLMs & agent skills [Section titled “For LLMs & agent skills”](#for-llms--agent-skills) This site is designed to be consumed by agents that build skills on top of postgresdk. The [llms.txt](https://llmstxt.org/) files are generated at build time: * [`/llms.txt`](/llms.txt) — index + project summary * [`/llms-full.txt`](/llms-full.txt) — the entire docs as one file * [`/llms-small.txt`](/llms-small.txt) — a trimmed bundle for smaller context windows
# Quick start
> Install postgresdk, generate an API + SDK from your schema, and wire up a server and client.
This walks through generating a typed API server and client SDK from an existing Postgres database. For every option, see the [Configuration reference](/reference/configuration/); for every command, see the [CLI reference](/reference/cli/). ## 1. Initialize [Section titled “1. Initialize”](#1-initialize)
```bash
bunx postgresdk@latest init
```
This writes a `postgresdk.config.ts` with all options documented. ## 2. Configure your connection [Section titled “2. Configure your connection”](#2-configure-your-connection) The only required field is `connectionString`:
```ts
import type { Config } from "postgresdk";
export default {
connectionString: process.env.DATABASE_URL!,
// outDir defaults to { client: "./api/client", server: "./api/server" }
} satisfies Config;
```
## 3. Generate [Section titled “3. Generate”](#3-generate)
```bash
bunx postgresdk@latest generate # alias: gen
```
postgresdk introspects the schema and writes the server + client code (and a `CONTRACT.md` describing everything it emitted — see a [real example](/reference/generated-api-example/)). ## 4. Set up the server [Section titled “4. Set up the server”](#4-set-up-the-server)
```ts
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { Client } from "pg";
import { createRouter } from "./api/server/router"; // path depends on your outDir
const app = new Hono();
const pg = new Client({ connectionString: process.env.DATABASE_URL });
await pg.connect();
app.route("/", createRouter({ pg }));
serve({ fetch: app.fetch, port: 3000 });
```
See [Server setup](/guides/server-setup/) for database drivers, the `onRequest` hook, and auth. ## 5. Use the client SDK [Section titled “5. Use the client SDK”](#5-use-the-client-sdk)
```ts
import { SDK } from "./api/client"; // path depends on your outDir
const sdk = new SDK({ baseUrl: "http://localhost:3000" });
const user = await sdk.users.create({ name: "Alice", email: "alice@example.com" });
const { data } = await sdk.users.list({ where: { status: "active" }, include: { posts: true } });
await sdk.users.update(user.id, { name: "Alice Smith" });
```
See [Client SDK usage](/guides/client-usage/) for CRUD, includes, transactions, and [Filtering & WHERE operators](/reference/filtering-operators/) for queries. ## Requirements [Section titled “Requirements”](#requirements) * Node.js ≥ 18.17 (or Bun) * A reachable PostgreSQL database for introspection * Currently generates **Hono** server code (see [`serverFramework`](/reference/configuration/#config))
# Client SDK usage
> CRUD, eager-loading includes, typed include methods, and atomic transactions with the generated SDK.
The generated client SDK gives every table a typed set of operations. Initialize it with your API base URL (and auth, if configured).
```ts
import { SDK } from "./api/client"; // or "./src/sdk" when pulled — see SDK distribution
const sdk = new SDK({ baseUrl: "http://localhost:3000" });
```
## CRUD [Section titled “CRUD”](#crud)
```ts
const user = await sdk.users.create({ name: "Bob", email: "bob@example.com" });
const one = await sdk.users.getByPk("user-id"); // primary keys are strings; null if not found
const { data } = await sdk.users.list(); // list() returns a paginated result
const updated = await sdk.users.update("user-id", { name: "Robert" }); // null if not found
// Upsert (Prisma-style); `where` must target a unique constraint
const upserted = await sdk.users.upsert({
where: { email: "alice@example.com" },
create: { email: "alice@example.com", name: "Alice" },
update: { name: "Alice Updated" },
});
await sdk.users.hardDelete("user-id"); // permanent
// await sdk.users.softDelete("user-id"); // when softDeleteColumn is configured
```
`getByPk`, `update`, `softDelete`, and `hardDelete` return `null` when no row matches the id (the underlying request 404s). Narrow before using the result. ## Soft vs hard delete [Section titled “Soft vs hard delete”](#soft-vs-hard-delete) When a [`softDeleteColumn`](/reference/configuration/#deleteconfig) is configured: * `softDelete(id)` sets that column (e.g. `deleted_at = NOW()`); `hardDelete(id)` permanently deletes (unless [`exposeHardDelete: false`](/reference/configuration/#deleteconfig)). * Soft-deleted rows are **automatically hidden** from `list()` and `getByPk()`. * Pass `includeSoftDeleted: true` to include them:
```ts
const { data } = await sdk.users.list({ includeSoftDeleted: true });
```
Without a `softDeleteColumn`, only `hardDelete` exists. ## Relationships & eager loading [Section titled “Relationships & eager loading”](#relationships--eager-loading) Use `include` to load related rows. Return types are inferred automatically — no casts.
```ts
const { data: authors } = await sdk.authors.list({ include: { books: true } });
// authors[0].books is typed as SelectBooks[]
// Nested includes
const { data } = await sdk.authors.list({
include: { books: { tags: true } },
});
// data[0].books[0].tags is typed as SelectTags[]
```
### Typed include methods [Section titled “Typed include methods”](#typed-include-methods) For common patterns the SDK also generates `listWith*` / `getByPkWith*` helpers with per-relation options. How deep these go is controlled by [`includeMethodsDepth`](/reference/configuration/#config).
```ts
const top = await sdk.authors.listWithBooks({
limit: 10,
booksInclude: { orderBy: "published_at", order: "desc", limit: 5 },
});
const author = await sdk.authors.getByPkWithBooks("author-id", {
booksInclude: { orderBy: "published_at", limit: 3 },
});
```
## Atomic transactions [Section titled “Atomic transactions”](#atomic-transactions) Build operations lazily with the `$`-prefixed methods, then run them in one transaction. All ops are Zod-validated **before** `BEGIN`, and any failure rolls everything back.
```ts
const [order, updatedUser] = await sdk.$transaction([
sdk.orders.$create({ user_id: user.id, total: 99 }),
sdk.users.$update(user.id, { last_order_at: new Date().toISOString() }),
]);
// inferred as [SelectOrders, SelectUsers | null]
```
`$create`, `$update`, `$upsert`, `$softDelete`, and `$hardDelete` are the lazy builders. The whole batch posts to `POST /v1/transaction`. On failure the thrown error carries a `.failedAt` index and, for validation failures, an `.issues` array (the Zod issues).
```ts
try {
await sdk.$transaction([
sdk.inventory.$update(itemId, { stock: newStock }),
sdk.orders.$create({ item_id: itemId, qty: 1 }),
]);
} catch (err: any) {
console.error(`failed at op ${err.failedAt}:`, err.message, err.issues);
}
```
## Errors & response shapes [Section titled “Errors & response shapes”](#errors--response-shapes) * Non-2xx responses throw `Error(" failed: ")`. * `404` is special-cased to return `null` (so `getByPk`/`update`/`softDelete`/`hardDelete` resolve to `null` rather than throwing when the row is absent). * `list()` always returns the [paginated shape](/guides/querying/#pagination). ## Generated types & enums [Section titled “Generated types & enums”](#generated-types--enums) Each table emits `Select`, `Insert`, and `Update` types, plus Zod schemas. Import paths depend on your `outDir`:
```ts
import type { SelectUsers, InsertUsers, UpdateUsers } from "./api/client/types/users";
import type { PaginatedResponse } from "./api/client/types/shared";
import { InsertUsersSchema, UpdateUsersSchema } from "./api/client/zod/users";
```
Postgres **enum** columns become TypeScript string-literal unions (and `z.enum(...)` in the Zod schemas) — e.g. a `status` enum of `active|inactive` is typed as `"active" | "inactive"`, not a bare `string`. For filtering, sorting, selection, and search see [Querying & pagination](/guides/querying/). For the exact methods, types, and endpoints generated for *your* schema, see the [Generated API example](/reference/generated-api-example/).
# Deployment
> Connection-pool sizing for serverless vs. traditional servers.
The generated server is a standard Hono app — deploy it anywhere Hono runs. The main thing to get right is **connection-pool sizing**. ## Serverless (Vercel, Netlify, Cloudflare Workers) [Section titled “Serverless (Vercel, Netlify, Cloudflare Workers)”](#serverless-vercel-netlify-cloudflare-workers) Each instance is ephemeral and handles one request at a time, so use `max: 1` — pooling provides no benefit and wastes database connections.
```ts
import { Pool } from "@neondatabase/serverless";
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 1 });
const router = createRouter({ pg: pool });
```
For edge runtimes, set [`useJsExtensions`](/reference/configuration/#config) so generated imports include `.js` extensions. ## Traditional servers (Railway, Render, VPS) [Section titled “Traditional servers (Railway, Render, VPS)”](#traditional-servers-railway-render-vps) Long-running servers handle many concurrent requests; pool to reuse connections.
```ts
import { Pool } from "@neondatabase/serverless"; // or "pg"
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 10 });
const router = createRouter({ pg: pool });
```
# Querying & pagination
> Filter, sort, paginate, select fields, DISTINCT ON, and run vector & trigram search with the generated SDK.
`list()` accepts `where`, `orderBy`/`order`, `limit`, `offset`, `select`/`exclude`, `distinctOn`, and (when your schema supports them) `vector` / `trigram`. It returns the records plus pagination metadata. The full operator set lives in [Filtering & WHERE operators](/reference/filtering-operators/). ## Filtering [Section titled “Filtering”](#filtering) A field maps to a direct value (equality) or an operator object. Root-level keys are AND’d; use `$or`/`$and` for explicit logic (two nesting levels max). Operators are checked at compile time against the column’s type.
```ts
const { data } = await sdk.users.list({
where: {
status: { $in: ["active", "pending"] },
age: { $gte: 18, $lt: 65 },
name: { $ilike: "%alice%" },
deleted_at: { $is: null },
$or: [{ role: "admin" }, { role: "owner" }],
},
});
```
## Sorting [Section titled “Sorting”](#sorting)
```ts
// single column
await sdk.users.list({ orderBy: "created_at", order: "desc" });
// multi-column (per-column direction, positionally matched to orderBy)
await sdk.users.list({
orderBy: ["status", "created_at"],
order: ["asc", "desc"], // or a single direction applied to all columns
});
```
## DISTINCT ON [Section titled “DISTINCT ON”](#distinct-on) Return one row per distinct value of the given column(s). Pair it with `orderBy` to control which row wins. When you order by a column outside the `distinctOn` set, the SDK wraps the query in a subquery so the ordering still applies.
```ts
// latest event per user
const latestPerUser = await sdk.events.list({
distinctOn: "user_id",
orderBy: "created_at",
order: "desc",
});
// multiple distinct columns
await sdk.events.list({ distinctOn: ["user_id", "type"] });
```
## Selecting fields [Section titled “Selecting fields”](#selecting-fields) Return a subset of columns with `select`, or everything except some with `exclude`. These also work on single-record operations and on included relations.
```ts
// only these columns
await sdk.users.list({ select: ["id", "email", "name"] });
// all columns except these
await sdk.users.list({ exclude: ["password_hash", "secret_token"] });
// single-record operations accept the same options
await sdk.users.getByPk("user-id", { select: ["id", "name"] });
await sdk.users.create(data, { select: ["id", "email"] });
await sdk.users.update("user-id", patch, { exclude: ["updated_at"] });
// scope select/exclude to an included relation
await sdk.authors.list({
select: ["id", "name"],
include: { books: { select: ["id", "title"], orderBy: "published_at", limit: 5 } },
});
```
Caution `select` and `exclude` are mutually exclusive on the same operation — passing both throws at runtime. Pick one. ## Pagination [Section titled “Pagination”](#pagination)
```ts
const result = await sdk.users.list({ where: { status: "active" }, limit: 20, offset: 40 });
```
Every `list()` returns this shape:
```ts
{
data: T[]; // the records
total: number; // total matching rows (respects `where`)
limit?: number; // page size used — absent when no limit was given
offset: number; // offset used
hasMore: boolean; // more pages available (false when no limit)
}
```
Omitting `limit` returns all matching rows, capped by [`maxLimit`](/reference/configuration/#config) (default `1000`). Soft-deleted rows are excluded from `list()`/`getByPk()` automatically — see [Soft vs hard delete](/guides/client-usage/#soft-vs-hard-delete) for `includeSoftDeleted`. ## Vector search (pgvector) [Section titled “Vector search (pgvector)”](#vector-search-pgvector) For tables with a `vector` column, pass a `vector` block to `list()`. Matching rows come back sorted by distance, each with an added `_distance` field. Combine it with a normal `where` clause.
```ts
const results = await sdk.video_sections.list({
vector: {
field: "vision_embedding", // the vector column
query: embeddingArray, // number[] — your query embedding
metric: "cosine", // "cosine" | "l2" | "inner"
maxDistance: 0.5, // optional cutoff
},
where: { status: "published" },
limit: 10,
});
results.data[0]._distance; // number
```
## Trigram search (pg\_trgm) [Section titled “Trigram search (pg\_trgm)”](#trigram-search-pg_trgm) For typo-tolerant text search, pass a `trigram` block. Matching rows come back with an added `_similarity` field.
```ts
const results = await sdk.books.list({
trigram: {
field: "title",
query: "postgrs", // typo-tolerant
metric: "similarity", // "similarity" | "wordSimilarity" | "strictWordSimilarity"
threshold: 0.3, // minimum score, 0–1
},
limit: 10,
});
results.data[0]._similarity; // number
```
Multi-field variants:
```ts
// best score across fields
await sdk.books.list({
trigram: { fields: ["title", "subtitle"], strategy: "greatest", query: "postgrs" },
});
// weighted fields
await sdk.books.list({
trigram: {
fields: [{ field: "title", weight: 2 }, { field: "subtitle", weight: 1 }],
query: "postgrs",
},
});
```
# SDK distribution (pull)
> Serve the generated client SDK over HTTP and pull it into client apps.
When you run `generate`, the client SDK is bundled into the server output and served over HTTP, so client apps can **pull** it directly from your running API. ## On the server [Section titled “On the server”](#on-the-server) The generator embeds the SDK as `sdk-bundle.ts` and exposes: * `GET /_psdk/sdk/manifest` — lists files and metadata * `GET /_psdk/sdk/download` — the complete bundle * `GET /_psdk/sdk/files/:path` — individual files * `GET /_psdk/contract.md` — the API contract as Markdown * `GET /_psdk/contract.json` — the API contract as JSON The `contract.md` / `contract.json` endpoints are handy for agents that want the live schema contract straight from a running API. Protect these endpoints by setting [`pullToken`](/reference/configuration/#config) (use the `env:` form). If unset, they’re public. ## On the client [Section titled “On the client”](#on-the-client) Pull with flags:
```bash
bunx postgresdk@latest pull --from=https://api.myapp.com --output=./src/sdk
```
Or, recommended, configure `pull` in `postgresdk.config.ts` and run `pull` with no args:
```ts
// postgresdk.config.ts in the client app
export default {
pull: {
from: "https://api.myapp.com",
output: "./src/sdk",
pullToken: "env:POSTGRESDK_PULL_TOKEN", // if the server sets one
},
};
```
```bash
bunx postgresdk@latest pull
```
Then use it like any generated SDK:
```ts
import { SDK } from "./src/sdk";
const sdk = new SDK({ baseUrl: "https://api.myapp.com" });
```
See [`PullConfig`](/reference/configuration/#pullconfig) for all options. ## Stale-file cleanup [Section titled “Stale-file cleanup”](#stale-file-cleanup) Both `generate` and `pull` remove files no longer part of the SDK. Interactive terminals prompt per deletion; pass `--force` (or `-y`) to skip prompts. In CI (non-interactive), stale files are skipped with a warning unless `--force` is given.
# Server setup
> Mount the generated Hono router, choose a database driver, use the onRequest hook, and configure auth.
The generator emits a `createRouter` factory. You provide a Postgres client; it returns a Hono router with every table’s routes mounted. Import paths below assume the default `outDir`. ## Mount the router [Section titled “Mount the router”](#mount-the-router)
```ts
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { Client } from "pg";
import { createRouter } from "./api/server/router";
const app = new Hono();
const pg = new Client({ connectionString: process.env.DATABASE_URL });
await pg.connect();
app.route("/", createRouter({ pg }));
serve({ fetch: app.fetch, port: 3000 });
```
Routes are prefixed with [`apiPathPrefix`](/reference/configuration/#config) (default `/v1`). ## Database drivers [Section titled “Database drivers”](#database-drivers) The generated code works with any client exposing a simple `query` interface.
```ts
// Node.js pg driver
import { Client } from "pg";
const pg = new Client({ connectionString: process.env.DATABASE_URL });
await pg.connect();
const router = createRouter({ pg });
```
```ts
// Neon serverless driver (edge-compatible)
import { Pool } from "@neondatabase/serverless";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const router = createRouter({ pg: pool });
```
## The `onRequest` hook [Section titled “The onRequest hook”](#the-onrequest-hook) `onRequest` runs before every operation — ideal for audit session variables or Row-Level Security. It receives the Hono `Context` (fully typed) and the `pg` client.
```ts
const router = createRouter({
pg,
onRequest: async (c, pg) => {
const auth = c.get("auth");
if (auth?.kind === "jwt" && auth.claims?.sub) {
await pg.query(`SET LOCAL app.user_id = '${auth.claims.sub}'`);
}
},
});
```
## Authentication [Section titled “Authentication”](#authentication) Auth is configured in `postgresdk.config.ts`. See the full shape in the [Configuration reference](/reference/configuration/#authconfig). ### API key [Section titled “API key”](#api-key) postgresdk.config.ts
```ts
export default {
connectionString: "...",
auth: { apiKey: process.env.API_KEY },
};
```
```ts
// client
const sdk = new SDK({ baseUrl, auth: { apiKey: process.env.API_KEY } });
```
### JWT (HS256) [Section titled “JWT (HS256)”](#jwt-hs256) Secrets **must** use the `env:` prefix — the generator rewrites `"env:JWT_SECRET"` to `process.env.JWT_SECRET` in the generated code. Never inline a literal secret. postgresdk.config.ts
```ts
export default {
connectionString: "...",
auth: {
jwt: {
services: [
{ issuer: "web-app", secret: "env:WEB_APP_SECRET" },
{ issuer: "mobile-app", secret: "env:MOBILE_SECRET" },
],
audience: "my-api", // optional: validates the aud claim
},
},
};
```
```ts
// client — the JWT must include an `iss` claim matching a configured service
const sdk = new SDK({ baseUrl, auth: { jwt: "eyJhbGciOiJIUzI1NiIs..." } });
```
For service-to-service authorization, put scopes in JWT claims and enforce them in `onRequest` rather than in config.
# CLI reference
> Every postgresdk CLI command and option, captured from `postgresdk help`.
Generated file — do not edit by hand This page is generated from `src/cli.ts (via `postgresdk help`)` by `task docs:gen`. Edit the source and regenerate; manual changes are overwritten. `postgresdk` is a code generator: it introspects a PostgreSQL schema and emits a typed Hono API server and TypeScript client SDK. Run it with `bunx postgresdk@latest ` (or `npx` / `pnpm dlx`). ## Commands [Section titled “Commands”](#commands) | Command | Description | | --------------- | ---------------------------------- | | `init` | Create a postgresdk.config.ts file | | `generate, gen` | Generate SDK from database | | `pull` | Pull SDK from API endpoint | | `version` | Show version | | `help` | Show help | ## Full help output [Section titled “Full help output”](#full-help-output) The following is the verbatim output of `postgresdk help`:
```text
postgresdk - Generate typed SDK from PostgreSQL
Usage:
postgresdk [options]
Commands:
init Create a postgresdk.config.ts file
generate, gen Generate SDK from database
pull Pull SDK from API endpoint
version Show version
help Show help
Init Options:
(no options)
Generate Options:
-c, --config Path to config file (default: postgresdk.config.ts)
--force, -y Delete stale files without prompting
Pull Options:
--from API URL to pull SDK from
--output Output directory (default: ./src/sdk)
--token Authentication token
--force, -y Delete stale files without prompting
-c, --config Path to config file with pull settings
Examples:
postgresdk init # Create config file
postgresdk generate # Generate using postgresdk.config.ts
postgresdk gen # Short alias for generate
postgresdk generate -c custom.config.ts
postgresdk pull --from=https://api.com --output=./src/sdk
postgresdk pull # Pull using config file
```
# Configuration reference
> Every postgresdk.config.ts option, generated from the Config type.
Generated file — do not edit by hand This page is generated from `src/types.ts` by `task docs:gen`. Edit the source and regenerate; manual changes are overwritten. postgresdk reads a `postgresdk.config.ts` that default-exports a [`Config`](#config) object. Use `postgresdk init` to scaffold one.
```ts
import type { Config } from "postgresdk";
export default {
connectionString: process.env.DATABASE_URL!,
outDir: { client: "./api/client", server: "./api/server" },
} satisfies Config;
```
## `Config` [Section titled “Config”](#config) The default export of your `postgresdk.config.ts`. Only `connectionString` is required. | Option | Type | Default | Description | | ------------------------ | ---------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `connectionString` | `string` | — | Postgres connection string used to introspect the schema (e.g. `"postgres://user:pass@host:5432/db"`). Read it from an env var in real configs. | | `schema?` | `string` | `"public"` | Postgres schema to introspect. | | `outDir?` | `string \| { client: string; server: string }` | `{ client: "./api/client", server: "./api/server" }` | Where generated code is written. A single string is used for both server and client (the client SDK lands in an `sdk/` subdirectory); an object sets each separately. | | `delete?` | `DeleteConfig` | — | Soft/hard delete behavior. | | `numericMode?` | `"string" \| "number" \| "auto"` | `"auto"` | How numeric columns are typed. `"auto"` maps `int2`/`int4` → `number` and `int8`/`numeric` → `string` (to avoid precision loss). | | `includeMethodsDepth?` | `number` | `2` | How deep to generate eager-loading `include` helper methods. | | `skipJunctionTables?` | `boolean` | `true` | Skip junction (M:N) tables when generating include methods. | | `serverFramework?` | `"hono" \| "express" \| "fastify"` | `"hono"` | Server framework for the generated routes. Only `"hono"` is implemented today; `"express"`/`"fastify"` are reserved. | | `apiPathPrefix?` | `string` | `"/v1"` | Path prefix for the generated table routes. | | `maxLimit?` | `number` | `1000` | Maximum allowed value for the `limit` parameter in list operations. Set to `0` to disable the cap. | | `auth?` | `AuthConfigInput` | — | API authentication. Omit for no auth. Accepts the API-key shorthand or a full AuthConfig. | | `pullToken?` | `string` | — | Token protecting the `/_psdk/*` SDK-distribution endpoints. Use the `"env:VAR_NAME"` form. If unset, those endpoints are public. | | `pull?` | `PullConfig` | — | Pull configuration for client repos that consume a generated SDK over HTTP. | | `useJsExtensions?` | `boolean` | `false` | Emit `.js` import extensions in generated server code (needed for Vercel Edge). | | `useJsExtensionsClient?` | `boolean` | `false` | Emit `.js` import extensions in generated client SDK code (for certain bundlers/runtimes). | | `clean?` | `boolean` | `true` | Delete generated files for tables/items no longer present in the schema. | | `tests?` | `object — see below` | — | Generated test-suite configuration. | ## `Config.tests` [Section titled “Config.tests”](#configtests) | Option | Type | Default | Description | | ------------ | ----------------------------- | --------------- | --------------------------------------- | | `generate?` | `boolean` | `false` | Generate test files. | | `output?` | `string` | `"./api/tests"` | Output directory for generated tests. | | `framework?` | `"vitest" \| "jest" \| "bun"` | `"vitest"` | Test framework for the generated tests. | ## `DeleteConfig` [Section titled “DeleteConfig”](#deleteconfig) Shape of `Config.delete`. | Option | Type | Default | Description | | ---------------------------- | -------------------------------- | ------- | ------------------------------------------------------------------------------------ | | `softDeleteColumn?` | `string` | — | Column name for soft deletes (e.g. `"deleted_at"`). Absence means hard deletes only. | | `exposeHardDelete?` | `boolean` | `true` | Whether to also expose `hardDelete` when soft delete is configured. | | `softDeleteColumnOverrides?` | `Record` | — | Per-table overrides. Use `null` to disable soft delete for a specific table. | ## `AuthConfig` [Section titled “AuthConfig”](#authconfig) Full shape of `Config.auth`. An API-key shorthand (`{ apiKey: "..." }`) is also accepted and normalized to this. | Option | Type | Default | Description | | --------------- | -------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------- | | `apiKeyHeader?` | `string` | `"x-api-key"` | Header to read the API key from. | | `apiKeys?` | `string[]` | — | Accepted API keys. A value may use the `"env:MY_KEY_LIST"` form to read a comma-separated list from the environment. | | `jwt?` | `object — see below` | — | JWT (HS256) verification config. Its presence selects the JWT auth strategy. | ## `AuthConfig.jwt` [Section titled “AuthConfig.jwt”](#authconfigjwt) | Option | Type | Default | Description | | ----------- | -------------------- | ------- | ---------------------------------------- | | `services` | `object — see below` | — | — | | `audience?` | `string` | — | When set, validates the JWT `aud` claim. | ## `AuthConfig.jwt.services` [Section titled “AuthConfig.jwt.services”](#authconfigjwtservices) | Option | Type | Default | Description | | -------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `issuer` | `string` | — | Identifies the calling service. Must match the JWT `iss` claim. | | `secret` | `string` | — | Signing secret. MUST use the `"env:VAR_NAME"` form (e.g. `"env:JWT_SECRET"`). SECURITY: never inline `process.env.X` or a literal secret here. The generator rewrites `"env:JWT_SECRET"` to `process.env.JWT_SECRET` in the generated code. | ## `PullConfig` [Section titled “PullConfig”](#pullconfig) Shape of `Config.pull`, used by client repos that pull a generated SDK over HTTP. | Option | Type | Default | Description | | ------------ | -------- | ------------- | ----------------------------------------------------------------------- | | `from` | `string` | — | API URL to pull the SDK from. | | `output?` | `string` | `"./src/sdk"` | Output directory for the pulled SDK. | | `pullToken?` | `string` | — | Auth token for the `/_psdk/*` endpoints. Use the `"env:VAR_NAME"` form. |
# Filtering & WHERE operators
> Every where-clause operator, generated from postgresdk's emitWhereTypes().
Generated file — do not edit by hand This page is generated from `src/emit-where-types.ts (via `emitWhereTypes()`)` by `task docs:gen`. Edit the source and regenerate; manual changes are overwritten. List operations accept a type-safe `where` clause. A field maps either to a direct value (equality) or to an **operator object**. Operators are validated at compile time — e.g. string-only operators reject non-string columns, and JSONB operators are only offered on `object`/`unknown` columns.
```ts
await sdk.users.list({
where: {
status: "active", // direct value → equality
age: { $gte: 18, $lt: 65 }, // operator object
name: { $ilike: "%alice%" }, // case-insensitive LIKE
$or: [{ role: "admin" }, { role: "owner" }],
},
});
```
## Field operators [Section titled “Field operators”](#field-operators) Use these inside an operator object on a single column. | Operator | Description | | ----------------------- | -------------------------------------------------------------------------------------------- | | `$eq` | Equal to | | `$ne` | Not equal to | | `$gt` | Greater than | | `$gte` | Greater than or equal to | | `$lt` | Less than | | `$lte` | Less than or equal to | | `$in` | In array | | `$nin` | Not in array | | `$like` | LIKE pattern match (strings only) | | `$ilike` | Case-insensitive LIKE (strings only) | | `$similarity` | Trigram similarity match - “col” % value (pg\_trgm required, uses similarity\_threshold GUC) | | `$wordSimilarity` | Word trigram similarity match - value <% “col” (pg\_trgm required) | | `$strictWordSimilarity` | Strict word trigram similarity match - value <<% “col” (pg\_trgm required) | | `$is` | IS NULL | | `$isNot` | IS NOT NULL | | `$jsonbContains` | JSONB contains (@>) - check if column contains the specified JSON structure | | `$jsonbContainedBy` | JSONB contained by (<@) - check if column is contained by the specified JSON | | `$jsonbHasKey` | JSONB has key (?) - check if top-level key exists | | `$jsonbHasAnyKeys` | JSONB has any keys (?\|) - check if any of the specified keys exist | | `$jsonbHasAllKeys` | JSONB has all keys (?&) - check if all of the specified keys exist | | `$jsonbPath` | JSONB path query - query nested values. For multiple paths on same column, use $and | ## Logical operators [Section titled “Logical operators”](#logical-operators) Combine field conditions. `$or`/`$and` nest up to two levels. | Operator | Description | | -------- | -------------------------------------------------------------------------- | | `$or` | OR - at least one condition must be true | | `$and` | AND - all conditions must be true (alternative to implicit root-level AND) | ## JSONB path query [Section titled “JSONB path query”](#jsonb-path-query) Shape of the `$jsonbPath` operator value. | Operator | Description | | ---------- | ------------------------------------------------------------------- | | `path` | Array of keys to traverse (e.g., \[‘user’, ‘preferences’, ‘theme’]) | | `operator` | Operator to apply to the value at the path (defaults to ‘$eq’) | | `value` | Value to compare against |