This is the full 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",
},
});
```
Note
`vector` and `trigram` are mutually exclusive on a single `list()` call. The string WHERE operators `$similarity` / `$wordSimilarity` / `$strictWordSimilarity` (see [Filtering & WHERE operators](/reference/filtering-operators/)) are a lighter-weight alternative for inline trigram filtering.
# 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 |
# Generated API example (CONTRACT.md)
> A real CONTRACT.md produced by running postgresdk against the fixture schema.
Generated file — do not edit by hand
This page is generated from `test/schema.sql → postgresdk gen → CONTRACT.md` by `task docs:gen`. Edit the source and regenerate; manual changes are overwritten.
This page is a **real, unedited `CONTRACT.md`** produced by running postgresdk against the project’s fixture schema ([`test/schema.sql`](https://github.com/adpharm/postgresdk/blob/main/test/schema.sql)) — a small library domain (`authors` → `books`, `books` ↔ `tags`, plus `users`/`products` with `pgvector` and `pg_trgm`). Use it to see exactly what tables, types, methods, and endpoints postgresdk emits for your own schema.
Reading the examples below
* **Import paths are relative to the generated *client* directory** (e.g. `./client`). In the guides we use the default `outDir` of `{ client: "./api/client", server: "./api/server" }`, so your imports there would start `./api/client`. Adjust to wherever your `outDir` points.
* Some feature snippets (e.g. vector search) use **placeholder table names** to illustrate the shape of a call — match them to the real tables in *your* schema.
***
## API & SDK Contract
[Section titled “API & SDK Contract”](#api--sdk-contract)
Unified API and SDK contract - your one-stop reference for all operations
**Version:** 2.0.0
### SDK Setup
[Section titled “SDK Setup”](#sdk-setup)
#### Installation
[Section titled “Installation”](#installation)
```bash
# The SDK is generated in the client/ directory
# Import it directly from your generated code
```
#### Initialization
[Section titled “Initialization”](#initialization)
**Basic initialization:**
```typescript
import { SDK } from './client';
const sdk = new SDK({
baseUrl: 'http://localhost:3000'
});
```
**With authentication:**
```typescript
import { SDK } from './client';
const sdk = new SDK({
baseUrl: 'https://api.example.com',
auth: {
apiKey: process.env.API_KEY
}
});
```
**With custom fetch (for Node.js < 18):**
```typescript
import { SDK } from './client';
import fetch from 'node-fetch';
const sdk = new SDK({
baseUrl: 'https://api.example.com',
fetch: fetch as any
});
```
#### Authentication
[Section titled “Authentication”](#authentication)
**No authentication required:**
```typescript
const sdk = new SDK({
baseUrl: 'http://localhost:3000'
});
```
**Custom headers provider:**
```typescript
const sdk = new SDK({
baseUrl: 'https://api.example.com',
auth: async () => ({
'Authorization': 'Bearer ' + await getToken(),
'X-Request-ID': generateRequestId()
})
});
```
### Filtering
[Section titled “Filtering”](#filtering)
Type-safe WHERE clauses. Root-level keys are AND’d; use `$or`/`$and` for logic (2 levels max).
```typescript
await sdk.users.list({
where: {
status: { $in: ['active', 'pending'] },
age: { $gte: 18, $lt: 65 },
email: { $ilike: '%@company.com' },
deleted_at: { $is: null },
meta: { $jsonbContains: { tag: 'vip' } },
$or: [{ role: 'admin' }, { role: 'mod' }]
}
});
```
| Operator | SQL | Types |
| ------------------------------------------------------------------------------------------------------ | --------------------- | ------------ |
| `$eq` `$ne` | = ≠ | All |
| `$gt` `$gte` `$lt` `$lte` | > ≥ < ≤ | Number, Date |
| `$in` `$nin` | IN / NOT IN | All |
| `$like` `$ilike` | LIKE / ILIKE | String |
| `$is` `$isNot` | IS NULL / IS NOT NULL | Nullable |
| `$jsonbContains` `$jsonbContainedBy` `$jsonbHasKey` `$jsonbHasAnyKeys` `$jsonbHasAllKeys` `$jsonbPath` | JSONB ops | JSONB |
| `$or` `$and` | OR / AND (2 levels) | — |
### Sorting
[Section titled “Sorting”](#sorting)
`orderBy` accepts a column name or array; `order` accepts `'asc'`/`'desc'` or a per-column array.
```typescript
await sdk.users.list({ orderBy: ['status', 'created_at'], order: ['asc', 'desc'] });
```
### Vector Search
[Section titled “Vector Search”](#vector-search)
For tables with `vector` columns (requires pgvector). Results include a `_distance` field.
```typescript
const results = await sdk.embeddings.list({
vector: { field: 'embedding', query: [0.1, 0.2, 0.3, /* ... */], metric: 'cosine', maxDistance: 0.5 },
where: { status: 'published' },
limit: 10
}); // results.data[0]._distance
```
### Resources
[Section titled “Resources”](#resources)
#### Authors
[Section titled “Authors”](#authors)
Resource for authors operations
##### SDK Methods
[Section titled “SDK Methods”](#sdk-methods)
Access via: `sdk.authors`
**list**
* Signature: `list(params?: ListParams): Promise>`
* List authors with filtering, sorting, and pagination. Returns paginated results with metadata.
* API: `GET /v1/authors`
```typescript
const result = await sdk.authors.list({
where: { id: { $ilike: '%value%' } },
orderBy: 'id',
order: 'desc',
limit: 20,
offset: 0
}); // result.data, result.total, result.hasMore
```
**getByPk**
* Signature: `getByPk(id: string): Promise`
* Get a single authors by primary key
* API: `GET /v1/authors/:id`
```typescript
const item = await sdk.authors.getByPk('id'); // null if not found
```
**create**
* Signature: `create(data: InsertAuthors): Promise`
* Create a new authors
* API: `POST /v1/authors`
```typescript
const created = await sdk.authors.create({
name: 'John Doe'
});
```
**update**
* Signature: `update(id: string, data: UpdateAuthors): Promise`
* Update an existing authors
* API: `PATCH /v1/authors/:id`
```typescript
const updated = await sdk.authors.update('id', {
name: 'John Doe'
});
```
**upsert**
* Signature: `upsert(args: { where: UpdateAuthors; create: InsertAuthors; update: UpdateAuthors }): Promise`
* Insert or update a authors based on a conflict target. The ‘where’ keys define the unique conflict columns (must be a unique constraint). ‘create’ is used if no conflict; ‘update’ is applied if a conflict occurs.
* API: `POST /v1/authors/upsert`
```typescript
const result = await sdk.authors.upsert({
where: { id: 'some-id' },
create: { name: 'John Doe' },
update: { name: 'John Doe' },
});
```
**hardDelete**
* Signature: `hardDelete(id: string): Promise`
* Permanently delete a authors
* API: `DELETE /v1/authors/:id`
```typescript
const deleted = await sdk.authors.hardDelete('id');
```
**listWithBooks**
* Signature: `listWithBooks(params?: ListParams): PaginatedResponse`
* Get authors with included books data
* API: `POST /v1/authors/list`
**getByPkWithBooks**
* Signature: `getByPkWithBooks(id: string): SelectAuthors & { books: SelectBooks[] } | null`
* Get authors with included books data
* API: `POST /v1/authors/list`
**listWithBooksAndTags**
* Signature: `listWithBooksAndTags(params?: ListParams): PaginatedResponse`
* Get authors with included books, tags data
* API: `POST /v1/authors/list`
**getByPkWithBooksAndTags**
* Signature: `getByPkWithBooksAndTags(id: string): SelectAuthors & { books: (SelectBooks & { tags: SelectTags[] })[] } | null`
* Get authors with included books, tags data
* API: `POST /v1/authors/list`
##### API Endpoints
[Section titled “API Endpoints”](#api-endpoints)
* `GET /v1/authors`
* List all authors records with pagination metadata
* Response: `PaginatedResponse`
* `GET /v1/authors/:id`
* Get authors by ID
* Response: `Authors`
* `POST /v1/authors`
* Create new authors
* Request: `InsertAuthors`
* Response: `Authors`
* `PATCH /v1/authors/:id`
* Update authors
* Request: `UpdateAuthors`
* Response: `Authors`
* `POST /v1/authors/upsert`
* Upsert authors — insert if no conflict on ‘where’ columns, update otherwise
* Request: `{ where: UpdateAuthors; create: InsertAuthors; update: UpdateAuthors }`
* Response: `Authors`
* `DELETE /v1/authors/:id`
* Delete authors
* Response: `Authors`
##### Fields
[Section titled “Fields”](#fields)
| Field | Type | TypeScript | Required | Description |
| ----- | ------ | ---------- | -------- | ----------- |
| id | uuid | `string` | | Primary key |
| name | string | `string` | ✓ | name |
#### BookTags
[Section titled “BookTags”](#booktags)
Resource for book\_tags operations
##### SDK Methods
[Section titled “SDK Methods”](#sdk-methods-1)
Access via: `sdk.book_tags`
**list**
* Signature: `list(params?: ListParams): Promise>`
* List book\_tags with filtering, sorting, and pagination. Returns paginated results with metadata.
* API: `GET /v1/book_tags`
```typescript
const result = await sdk.book_tags.list({
where: { book_id: { $ilike: '%value%' } },
orderBy: 'book_id',
order: 'desc',
limit: 20,
offset: 0
}); // result.data, result.total, result.hasMore
```
**create**
* Signature: `create(data: InsertBookTags): Promise`
* Create a new book\_tags
* API: `POST /v1/book_tags`
```typescript
const created = await sdk.book_tags.create({
book_id: 'related-id-123',
tag_id: 'related-id-123'
});
```
##### API Endpoints
[Section titled “API Endpoints”](#api-endpoints-1)
* `GET /v1/book_tags`
* List all book\_tags records with pagination metadata
* Response: `PaginatedResponse`
* `POST /v1/book_tags`
* Create new book\_tags
* Request: `InsertBookTags`
* Response: `BookTags`
##### Fields
[Section titled “Fields”](#fields-1)
| Field | Type | TypeScript | Required | Description |
| -------- | ---- | ---------- | -------- | --------------------------- |
| book\_id | uuid | `string` | ✓ | Foreign key to book → books |
| tag\_id | uuid | `string` | ✓ | Foreign key to tag → tags |
#### Books
[Section titled “Books”](#books)
Resource for books operations
##### SDK Methods
[Section titled “SDK Methods”](#sdk-methods-2)
Access via: `sdk.books`
**list**
* Signature: `list(params?: ListParams): Promise>`
* List books with filtering, sorting, and pagination. Returns paginated results with metadata.
* API: `GET /v1/books`
```typescript
const result = await sdk.books.list({
where: { id: { $ilike: '%value%' } },
orderBy: 'id',
order: 'desc',
limit: 20,
offset: 0
}); // result.data, result.total, result.hasMore
```
**getByPk**
* Signature: `getByPk(id: string): Promise`
* Get a single books by primary key
* API: `GET /v1/books/:id`
```typescript
const item = await sdk.books.getByPk('id'); // null if not found
```
**create**
* Signature: `create(data: InsertBooks): Promise`
* Create a new books
* API: `POST /v1/books`
```typescript
const created = await sdk.books.create({
author_id: 'related-id-123',
title: 'Example Title'
});
```
**update**
* Signature: `update(id: string, data: UpdateBooks): Promise`
* Update an existing books
* API: `PATCH /v1/books/:id`
```typescript
const updated = await sdk.books.update('id', {
author_id: 'related-id-123',
title: 'Example Title'
});
```
**upsert**
* Signature: `upsert(args: { where: UpdateBooks; create: InsertBooks; update: UpdateBooks }): Promise`
* Insert or update a books based on a conflict target. The ‘where’ keys define the unique conflict columns (must be a unique constraint). ‘create’ is used if no conflict; ‘update’ is applied if a conflict occurs.
* API: `POST /v1/books/upsert`
```typescript
const result = await sdk.books.upsert({
where: { id: 'some-id' },
create: { author_id: 'related-id-123',
title: 'Example Title' },
update: { author_id: 'related-id-123',
title: 'Example Title' },
});
```
**hardDelete**
* Signature: `hardDelete(id: string): Promise`
* Permanently delete a books
* API: `DELETE /v1/books/:id`
```typescript
const deleted = await sdk.books.hardDelete('id');
```
**listWithAuthor**
* Signature: `listWithAuthor(params?: ListParams): PaginatedResponse`
* Get books with included author data
* API: `POST /v1/books/list`
**getByPkWithAuthor**
* Signature: `getByPkWithAuthor(id: string): SelectBooks & { author: SelectAuthors | null } | null`
* Get books with included author data
* API: `POST /v1/books/list`
**listWithTags**
* Signature: `listWithTags(params?: ListParams): PaginatedResponse`
* Get books with included tags data
* API: `POST /v1/books/list`
**getByPkWithTags**
* Signature: `getByPkWithTags(id: string): SelectBooks & { tags: SelectTags[] } | null`
* Get books with included tags data
* API: `POST /v1/books/list`
**listWithAuthorAndTags**
* Signature: `listWithAuthorAndTags(params?: ListParams): PaginatedResponse`
* Get books with included author, tags data
* API: `POST /v1/books/list`
**getByPkWithAuthorAndTags**
* Signature: `getByPkWithAuthorAndTags(id: string): SelectBooks & { author: SelectAuthors | null; tags: SelectTags[] } | null`
* Get books with included author, tags data
* API: `POST /v1/books/list`
##### API Endpoints
[Section titled “API Endpoints”](#api-endpoints-2)
* `GET /v1/books`
* List all books records with pagination metadata
* Response: `PaginatedResponse`
* `GET /v1/books/:id`
* Get books by ID
* Response: `Books`
* `POST /v1/books`
* Create new books
* Request: `InsertBooks`
* Response: `Books`
* `PATCH /v1/books/:id`
* Update books
* Request: `UpdateBooks`
* Response: `Books`
* `POST /v1/books/upsert`
* Upsert books — insert if no conflict on ‘where’ columns, update otherwise
* Request: `{ where: UpdateBooks; create: InsertBooks; update: UpdateBooks }`
* Response: `Books`
* `DELETE /v1/books/:id`
* Delete books
* Response: `Books`
##### Fields
[Section titled “Fields”](#fields-2)
| Field | Type | TypeScript | Required | Description |
| ---------- | ------ | ---------- | -------- | ----------- |
| id | uuid | `string` | | Primary key |
| author\_id | uuid | \`string | null\` | |
| title | string | `string` | ✓ | title |
#### Products
[Section titled “Products”](#products)
Resource for products operations
##### SDK Methods
[Section titled “SDK Methods”](#sdk-methods-3)
Access via: `sdk.products`
**list**
* Signature: `list(params?: ListParams): Promise>`
* List products with filtering, sorting, and pagination. Returns paginated results with metadata.
* API: `GET /v1/products`
```typescript
const result = await sdk.products.list({
where: { id: { $ilike: '%value%' } },
orderBy: 'id',
order: 'desc',
limit: 20,
offset: 0
}); // result.data, result.total, result.hasMore
```
**getByPk**
* Signature: `getByPk(id: string): Promise`
* Get a single products by primary key
* API: `GET /v1/products/:id`
```typescript
const item = await sdk.products.getByPk('id'); // null if not found
```
**create**
* Signature: `create(data: InsertProducts): Promise`
* Create a new products
* API: `POST /v1/products`
```typescript
const created = await sdk.products.create({
name: 'John Doe',
metadata: { key: 'value' },
tags: { key: 'value' }
});
```
**update**
* Signature: `update(id: string, data: UpdateProducts): Promise`
* Update an existing products
* API: `PATCH /v1/products/:id`
```typescript
const updated = await sdk.products.update('id', {
name: 'John Doe',
metadata: { key: 'value' }
});
```
**upsert**
* Signature: `upsert(args: { where: UpdateProducts; create: InsertProducts; update: UpdateProducts }): Promise`
* Insert or update a products based on a conflict target. The ‘where’ keys define the unique conflict columns (must be a unique constraint). ‘create’ is used if no conflict; ‘update’ is applied if a conflict occurs.
* API: `POST /v1/products/upsert`
```typescript
const result = await sdk.products.upsert({
where: { id: 'some-id' },
create: { name: 'John Doe',
metadata: { key: 'value' },
tags: { key: 'value' } },
update: { name: 'John Doe',
metadata: { key: 'value' } },
});
```
**hardDelete**
* Signature: `hardDelete(id: string): Promise`
* Permanently delete a products
* API: `DELETE /v1/products/:id`
```typescript
const deleted = await sdk.products.hardDelete('id');
```
##### API Endpoints
[Section titled “API Endpoints”](#api-endpoints-3)
* `GET /v1/products`
* List all products records with pagination metadata
* Response: `PaginatedResponse`
* `GET /v1/products/:id`
* Get products by ID
* Response: `Products`
* `POST /v1/products`
* Create new products
* Request: `InsertProducts`
* Response: `Products`
* `PATCH /v1/products/:id`
* Update products
* Request: `UpdateProducts`
* Response: `Products`
* `POST /v1/products/upsert`
* Upsert products — insert if no conflict on ‘where’ columns, update otherwise
* Request: `{ where: UpdateProducts; create: InsertProducts; update: UpdateProducts }`
* Response: `Products`
* `DELETE /v1/products/:id`
* Delete products
* Response: `Products`
##### Fields
[Section titled “Fields”](#fields-3)
| Field | Type | TypeScript | Required | Description |
| -------- | ------ | ----------- | -------- | ----------- |
| id | uuid | `string` | | Primary key |
| name | string | `string` | ✓ | name |
| metadata | object | \`JsonValue | null\` | |
| tags | object | \`JsonValue | null\` | |
| settings | object | \`JsonValue | null\` | |
#### Tags
[Section titled “Tags”](#tags)
Resource for tags operations
##### SDK Methods
[Section titled “SDK Methods”](#sdk-methods-4)
Access via: `sdk.tags`
**list**
* Signature: `list(params?: ListParams): Promise>`
* List tags with filtering, sorting, and pagination. Returns paginated results with metadata.
* API: `GET /v1/tags`
```typescript
const result = await sdk.tags.list({
where: { id: { $ilike: '%value%' } },
orderBy: 'id',
order: 'desc',
limit: 20,
offset: 0
}); // result.data, result.total, result.hasMore
```
**getByPk**
* Signature: `getByPk(id: string): Promise`
* Get a single tags by primary key
* API: `GET /v1/tags/:id`
```typescript
const item = await sdk.tags.getByPk('id'); // null if not found
```
**create**
* Signature: `create(data: InsertTags): Promise`
* Create a new tags
* API: `POST /v1/tags`
```typescript
const created = await sdk.tags.create({
name: 'John Doe'
});
```
**update**
* Signature: `update(id: string, data: UpdateTags): Promise`
* Update an existing tags
* API: `PATCH /v1/tags/:id`
```typescript
const updated = await sdk.tags.update('id', {
name: 'John Doe'
});
```
**upsert**
* Signature: `upsert(args: { where: UpdateTags; create: InsertTags; update: UpdateTags }): Promise`
* Insert or update a tags based on a conflict target. The ‘where’ keys define the unique conflict columns (must be a unique constraint). ‘create’ is used if no conflict; ‘update’ is applied if a conflict occurs.
* API: `POST /v1/tags/upsert`
```typescript
const result = await sdk.tags.upsert({
where: { id: 'some-id' },
create: { name: 'John Doe' },
update: { name: 'John Doe' },
});
```
**hardDelete**
* Signature: `hardDelete(id: string): Promise`
* Permanently delete a tags
* API: `DELETE /v1/tags/:id`
```typescript
const deleted = await sdk.tags.hardDelete('id');
```
**listWithBooks**
* Signature: `listWithBooks(params?: ListParams): PaginatedResponse`
* Get tags with included books data
* API: `POST /v1/tags/list`
**getByPkWithBooks**
* Signature: `getByPkWithBooks(id: string): SelectTags & { books: SelectBooks[] } | null`
* Get tags with included books data
* API: `POST /v1/tags/list`
**listWithBooksAndAuthor**
* Signature: `listWithBooksAndAuthor(params?: ListParams): PaginatedResponse`
* Get tags with included books, author data
* API: `POST /v1/tags/list`
**getByPkWithBooksAndAuthor**
* Signature: `getByPkWithBooksAndAuthor(id: string): SelectTags & { books: (SelectBooks & { author: SelectAuthors | null })[] } | null`
* Get tags with included books, author data
* API: `POST /v1/tags/list`
##### API Endpoints
[Section titled “API Endpoints”](#api-endpoints-4)
* `GET /v1/tags`
* List all tags records with pagination metadata
* Response: `PaginatedResponse`
* `GET /v1/tags/:id`
* Get tags by ID
* Response: `Tags`
* `POST /v1/tags`
* Create new tags
* Request: `InsertTags`
* Response: `Tags`
* `PATCH /v1/tags/:id`
* Update tags
* Request: `UpdateTags`
* Response: `Tags`
* `POST /v1/tags/upsert`
* Upsert tags — insert if no conflict on ‘where’ columns, update otherwise
* Request: `{ where: UpdateTags; create: InsertTags; update: UpdateTags }`
* Response: `Tags`
* `DELETE /v1/tags/:id`
* Delete tags
* Response: `Tags`
##### Fields
[Section titled “Fields”](#fields-4)
| Field | Type | TypeScript | Required | Description |
| ----- | ------ | ---------- | -------- | ----------- |
| id | uuid | `string` | | Primary key |
| name | string | `string` | ✓ | name |
#### Users
[Section titled “Users”](#users)
Resource for users operations
##### SDK Methods
[Section titled “SDK Methods”](#sdk-methods-5)
Access via: `sdk.users`
**list**
* Signature: `list(params?: ListParams): Promise>`
* List users with filtering, sorting, and pagination. Returns paginated results with metadata.
* API: `GET /v1/users`
```typescript
const result = await sdk.users.list({
where: { id: { $ilike: '%value%' } },
orderBy: 'id',
order: 'desc',
limit: 20,
offset: 0
}); // result.data, result.total, result.hasMore
```
**getByPk**
* Signature: `getByPk(id: string): Promise`
* Get a single users by primary key
* API: `GET /v1/users/:id`
```typescript
const item = await sdk.users.getByPk('id'); // null if not found
```
**create**
* Signature: `create(data: InsertUsers): Promise`
* Create a new users
* API: `POST /v1/users`
```typescript
const created = await sdk.users.create({
email: 'user@example.com',
profile: { key: 'value' },
preferences: { key: 'value' }
});
```
**update**
* Signature: `update(id: string, data: UpdateUsers): Promise`
* Update an existing users
* API: `PATCH /v1/users/:id`
```typescript
const updated = await sdk.users.update('id', {
email: 'user@example.com',
profile: { key: 'value' }
});
```
**upsert**
* Signature: `upsert(args: { where: UpdateUsers; create: InsertUsers; update: UpdateUsers }): Promise`
* Insert or update a users based on a conflict target. The ‘where’ keys define the unique conflict columns (must be a unique constraint). ‘create’ is used if no conflict; ‘update’ is applied if a conflict occurs.
* API: `POST /v1/users/upsert`
```typescript
const result = await sdk.users.upsert({
where: { id: 'some-id' },
create: { email: 'user@example.com',
profile: { key: 'value' },
preferences: { key: 'value' } },
update: { email: 'user@example.com',
profile: { key: 'value' } },
});
```
**hardDelete**
* Signature: `hardDelete(id: string): Promise`
* Permanently delete a users
* API: `DELETE /v1/users/:id`
```typescript
const deleted = await sdk.users.hardDelete('id');
```
##### API Endpoints
[Section titled “API Endpoints”](#api-endpoints-5)
* `GET /v1/users`
* List all users records with pagination metadata
* Response: `PaginatedResponse`
* `GET /v1/users/:id`
* Get users by ID
* Response: `Users`
* `POST /v1/users`
* Create new users
* Request: `InsertUsers`
* Response: `Users`
* `PATCH /v1/users/:id`
* Update users
* Request: `UpdateUsers`
* Response: `Users`
* `POST /v1/users/upsert`
* Upsert users — insert if no conflict on ‘where’ columns, update otherwise
* Request: `{ where: UpdateUsers; create: InsertUsers; update: UpdateUsers }`
* Response: `Users`
* `DELETE /v1/users/:id`
* Delete users
* Response: `Users`
##### Fields
[Section titled “Fields”](#fields-5)
| Field | Type | TypeScript | Required | Description |
| ----------- | ------ | ----------- | -------- | ----------- |
| id | uuid | `string` | | Primary key |
| email | string | `string` | ✓ | email |
| profile | object | \`JsonValue | null\` | |
| preferences | object | \`JsonValue | null\` | |
#### VideoSections
[Section titled “VideoSections”](#videosections)
Resource for video\_sections operations
##### SDK Methods
[Section titled “SDK Methods”](#sdk-methods-6)
Access via: `sdk.video_sections`
**list**
* Signature: `list(params?: ListParams): Promise>`
* List video\_sections with filtering, sorting, and pagination. Returns paginated results with metadata.
* API: `GET /v1/video_sections`
```typescript
const result = await sdk.video_sections.list({
where: { id: { $ilike: '%value%' } },
orderBy: 'id',
order: 'desc',
limit: 20,
offset: 0
}); // result.data, result.total, result.hasMore
```
**getByPk**
* Signature: `getByPk(id: string): Promise`
* Get a single video\_sections by primary key
* API: `GET /v1/video_sections/:id`
```typescript
const item = await sdk.video_sections.getByPk('id'); // null if not found
```
**create**
* Signature: `create(data: InsertVideoSections): Promise`
* Create a new video\_sections
* API: `POST /v1/video_sections`
```typescript
const created = await sdk.video_sections.create({
title: 'Example Title',
status: 'active',
vision_embedding: 'example value'
});
```
**update**
* Signature: `update(id: string, data: UpdateVideoSections): Promise`
* Update an existing video\_sections
* API: `PATCH /v1/video_sections/:id`
```typescript
const updated = await sdk.video_sections.update('id', {
title: 'Example Title',
status: 'active'
});
```
**upsert**
* Signature: `upsert(args: { where: UpdateVideoSections; create: InsertVideoSections; update: UpdateVideoSections }): Promise`
* Insert or update a video\_sections based on a conflict target. The ‘where’ keys define the unique conflict columns (must be a unique constraint). ‘create’ is used if no conflict; ‘update’ is applied if a conflict occurs.
* API: `POST /v1/video_sections/upsert`
```typescript
const result = await sdk.video_sections.upsert({
where: { id: 'some-id' },
create: { title: 'Example Title',
status: 'active',
vision_embedding: 'example value' },
update: { title: 'Example Title',
status: 'active' },
});
```
**hardDelete**
* Signature: `hardDelete(id: string): Promise`
* Permanently delete a video\_sections
* API: `DELETE /v1/video_sections/:id`
```typescript
const deleted = await sdk.video_sections.hardDelete('id');
```
##### API Endpoints
[Section titled “API Endpoints”](#api-endpoints-6)
* `GET /v1/video_sections`
* List all video\_sections records with pagination metadata
* Response: `PaginatedResponse`
* `GET /v1/video_sections/:id`
* Get video\_sections by ID
* Response: `VideoSections`
* `POST /v1/video_sections`
* Create new video\_sections
* Request: `InsertVideoSections`
* Response: `VideoSections`
* `PATCH /v1/video_sections/:id`
* Update video\_sections
* Request: `UpdateVideoSections`
* Response: `VideoSections`
* `POST /v1/video_sections/upsert`
* Upsert video\_sections — insert if no conflict on ‘where’ columns, update otherwise
* Request: `{ where: UpdateVideoSections; create: InsertVideoSections; update: UpdateVideoSections }`
* Response: `VideoSections`
* `DELETE /v1/video_sections/:id`
* Delete video\_sections
* Response: `VideoSections`
##### Fields
[Section titled “Fields”](#fields-6)
| Field | Type | TypeScript | Required | Description |
| ----------------- | ------------- | ----------- | -------- | ----------- |
| id | uuid | `string` | | Primary key |
| title | string | `string` | ✓ | title |
| status | string | \`string | null\` | |
| vision\_embedding | number\[] | \`number\[] | null\` | |
| text\_embedding | number\[] | \`number\[] | null\` | |
| created\_at | date/datetime | \`string | null\` | |
#### Websites
[Section titled “Websites”](#websites)
Resource for websites operations
##### SDK Methods
[Section titled “SDK Methods”](#sdk-methods-7)
Access via: `sdk.websites`
**list**
* Signature: `list(params?: ListParams): Promise>`
* List websites with filtering, sorting, and pagination. Returns paginated results with metadata.
* API: `GET /v1/websites`
```typescript
const result = await sdk.websites.list({
where: { id: { $ilike: '%value%' } },
orderBy: 'id',
order: 'desc',
limit: 20,
offset: 0
}); // result.data, result.total, result.hasMore
```
**getByPk**
* Signature: `getByPk(id: string): Promise`
* Get a single websites by primary key
* API: `GET /v1/websites/:id`
```typescript
const item = await sdk.websites.getByPk('id'); // null if not found
```
**create**
* Signature: `create(data: InsertWebsites): Promise`
* Create a new websites
* API: `POST /v1/websites`
```typescript
const created = await sdk.websites.create({
name: 'John Doe',
url: 'https://example.com'
});
```
**update**
* Signature: `update(id: string, data: UpdateWebsites): Promise`
* Update an existing websites
* API: `PATCH /v1/websites/:id`
```typescript
const updated = await sdk.websites.update('id', {
name: 'John Doe',
url: 'https://example.com'
});
```
**upsert**
* Signature: `upsert(args: { where: UpdateWebsites; create: InsertWebsites; update: UpdateWebsites }): Promise`
* Insert or update a websites based on a conflict target. The ‘where’ keys define the unique conflict columns (must be a unique constraint). ‘create’ is used if no conflict; ‘update’ is applied if a conflict occurs.
* API: `POST /v1/websites/upsert`
```typescript
const result = await sdk.websites.upsert({
where: { id: 'some-id' },
create: { name: 'John Doe',
url: 'https://example.com' },
update: { name: 'John Doe',
url: 'https://example.com' },
});
```
**hardDelete**
* Signature: `hardDelete(id: string): Promise`
* Permanently delete a websites
* API: `DELETE /v1/websites/:id`
```typescript
const deleted = await sdk.websites.hardDelete('id');
```
##### API Endpoints
[Section titled “API Endpoints”](#api-endpoints-7)
* `GET /v1/websites`
* List all websites records with pagination metadata
* Response: `PaginatedResponse`
* `GET /v1/websites/:id`
* Get websites by ID
* Response: `Websites`
* `POST /v1/websites`
* Create new websites
* Request: `InsertWebsites`
* Response: `Websites`
* `PATCH /v1/websites/:id`
* Update websites
* Request: `UpdateWebsites`
* Response: `Websites`
* `POST /v1/websites/upsert`
* Upsert websites — insert if no conflict on ‘where’ columns, update otherwise
* Request: `{ where: UpdateWebsites; create: InsertWebsites; update: UpdateWebsites }`
* Response: `Websites`
* `DELETE /v1/websites/:id`
* Delete websites
* Response: `Websites`
##### Fields
[Section titled “Fields”](#fields-7)
| Field | Type | TypeScript | Required | Description |
| ----- | ------ | ---------- | -------- | ----------- |
| id | uuid | `string` | | Primary key |
| name | string | `string` | ✓ | name |
| url | string | `string` | ✓ | url |
### Relationships
[Section titled “Relationships”](#relationships)
* **book\_tags** → **books** (many-to-one): Each book\_tags belongs to one books
* **book\_tags** → **tags** (many-to-one): Each book\_tags belongs to one tags
* **books** → **authors** (many-to-one): Each books belongs to one authors
### Type Imports
[Section titled “Type Imports”](#type-imports)
```typescript
import { SDK } from './client';
import type { SelectTableName, InsertTableName, UpdateTableName } from './client/types/table_name';
import type * as Types from './client/types';
```