389 lines
29 KiB
Plaintext
389 lines
29 KiB
Plaintext
page wrnexusdb {
|
|
seo {
|
|
title = "@wrnexus/db"
|
|
description = "Database adapters, typed queries, models, migrations, and sessions."
|
|
}
|
|
|
|
view {
|
|
<div class="docs-shell">
|
|
<SkipLink label="Skip to content" href="#main" class="docs-skip-link" />
|
|
<header class="topbar">
|
|
<a class="brand" href="/"><span>W</span> WRNexusJS</a>
|
|
<nav aria-label="Primary"><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a></nav>
|
|
<div class="topbar-actions"><a class="preview-pill" href="/access">Private preview · v0.5.11</a><button data-wire-theme-toggle class="theme-button" aria-label="Toggle color theme" title="Toggle color theme">◐</button></div>
|
|
</header>
|
|
<div class="mobile-doc-nav"><details><summary>Browse documentation</summary><nav><a href="/getting-started">Get started</a><a href="/packages">Packages</a><a href="https://component.wrnexusjs.dev/">Components</a><a href="/language">Language</a><a href="/architecture">Architecture</a><a href="/tutorial">Tutorial</a><a href="/guides/project-structure">Guides</a><a href="/examples">Examples</a><a href="/search">Search</a></nav></details></div>
|
|
<main class="portal-main docs-layout">
|
|
<article id="main" class="documentation prose standalone package-document"><nav class="breadcrumbs" aria-label="Breadcrumb"><a href="/">Home</a><span>/</span><a href="/packages">Packages</a><span>/</span><span aria-current="page">@wrnexus/db</span></nav><section class="doc-intro"><span class="eyebrow">Data · Package reference</span><h1>@wrnexus/db</h1><p>Database adapters, typed queries, models, migrations, and sessions.</p><div class="doc-meta"><span>v0.5.11</span><span>Private registry</span><span>Data</span></div><section id="access" class="access-callout"><h2>Install the package</h2><p>After WorkRoot approves private registry access, install the release-aligned package:</p><pre><code>bun add @wrnexus/db@0.5.11</code><button type="button" class="copy-button" aria-label="Copy installation command">Copy</button></pre><p><a href="/access">Request preview access</a>. Never put registry tokens in source control.</p></section></section><section id="guide"><blockquote>The database layer for WRNexusJS: TS models as the single source of truth for DDL, validation, and result typing, plus a driver-based <code>Db</code> client, migrations, and a sqlc-style query generator.</blockquote>
|
|
<p>Part of the <strong>WRNexusJS</strong> framework — an SSR-first, Bun-native full-stack web framework.</p>
|
|
<h3 id="overview">Overview</h3>
|
|
<p><code>@wrnexus/db</code> is the server-side data layer. You describe tables as TypeScript models (the <code>v</code> column builder + <code>table()</code>); those models drive migrations, coerce raw DB rows into typed objects, and feed the query generator. A thin <code>Driver</code> interface is implemented by adapters for SQLite (<code>bun:sqlite</code>), Postgres/MySQL (<code>Bun.SQL</code>), and MongoDB. The <code>Db</code> client adds ergonomics — model-mapped <code>all</code>/<code>one</code>, transactions, <code>createTable</code>, pagination, and batched relation loading. A process-wide registry (<code>getDb</code>/<code>setDb</code>) exposes configured connections to pages and API routes. Reach for it whenever a WRNexusJS app needs persistence.</p>
|
|
<pre data-language="bash"><code>bun add @wrnexus/db</code></pre>
|
|
<blockquote>Private package — the machine must be authenticated to the <code>wrnexus</code> npm org</blockquote>
|
|
<blockquote>(a read token in <code>~/.npmrc</code>). Requires <strong>Bun</strong> (Node is not supported).</blockquote>
|
|
<h3 id="api">API</h3>
|
|
<p>The core entry (<code>@wrnexus/db</code>) is dependency-free; adapters and connectors live in subpaths so importing the core doesn't pull in every driver.</p>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Subpath</th><th>Exports</th></tr></thead>
|
|
<tbody><tr><td><code>@wrnexus/db</code></td><td><code>v</code>, <code>table</code>, <code>Column</code>, <code>createDb</code>, <code>createTableSql</code>, the client registry (<code>setDb</code>/<code>getDb</code>/…), migrations, the query generator, and query helpers</td></tr><tr><td><code>@wrnexus/db/connect</code></td><td><code>connectFromConfig</code>, <code>resolveDbUrl</code>, <code>DbConfig</code> — resolve a config to a live SQL <code>Db</code></td></tr><tr><td><code>@wrnexus/db/session</code></td><td><code>sqliteSessionStore</code> — a <code>bun:sqlite</code> session backend for <code>@wrnexus/core</code></td></tr><tr><td><code>@wrnexus/db/sqlite</code></td><td><code>sqlite(url?)</code> driver</td></tr><tr><td><code>@wrnexus/db/postgres</code></td><td><code>postgres(url)</code> driver</td></tr><tr><td><code>@wrnexus/db/mysql</code></td><td><code>mysql(url)</code> driver</td></tr><tr><td><code>@wrnexus/db/mongo</code></td><td><code>mongo(url, dbName?)</code> document API</td></tr></tbody></table></div>
|
|
<h4 id="schema-v-table-column">Schema — <code>v</code>, <code>table</code>, <code>Column</code></h4>
|
|
<p><code>table(name, columns)</code> returns a <code>Model<T></code>. Columns are built with <code>v</code>:</p>
|
|
<pre data-language="ts"><code>import { v, table } from "@wrnexus/db";
|
|
|
|
const users = table("users", {
|
|
id: v.id(), // auto-increment primary key
|
|
email: v.text().unique(),
|
|
name: v.text().optional(), // NULLable
|
|
age: v.int().default(0),
|
|
active: v.bool().default(true),
|
|
createdAt: v.timestamp().default("now"), // CURRENT_TIMESTAMP
|
|
});</code></pre>
|
|
<p>Column builders: <code>v.id</code>, <code>v.text</code> (alias <code>v.string</code>), <code>v.int</code>, <code>v.real</code> (alias <code>v.number</code>), <code>v.bool</code> (alias <code>v.boolean</code>), <code>v.timestamp</code>, <code>v.json</code>. <code>BaseType</code> values are <code>"id" | "text" | "int" | "real" | "bool" | "timestamp" | "json"</code>.</p>
|
|
<p><code>Column</code> modifiers (chainable): <code>.optional()</code>, <code>.unique()</code>, <code>.default(value)</code> (use the sentinel <code>"now"</code> for a current-timestamp default), <code>.primaryKey()</code>, <code>.references(table, column = "id")</code>. <code>.coerce(raw)</code> converts a raw DB value to its JS type.</p>
|
|
<p>A <code>Model<T></code> exposes: <code>name</code>, <code>columns</code>, <code>parse(row)</code> (coerces a raw row into a typed <code>T</code>; unknown columns pass through), and <code>describe()</code> (returns each column's <code>ColumnDef</code>, for migrations and the generator).</p>
|
|
<h4 id="driver-client-createdb-db-driver">Driver & client — <code>createDb</code>, <code>Db</code>, <code>Driver</code></h4>
|
|
<pre data-language="ts"><code>createDb(driver: Driver): Db</code></pre>
|
|
<p>A <code>Driver</code> (implemented by adapters) exposes <code>dialect</code>, <code>query(sql, params?)</code>, <code>exec(sql, params?)</code>, <code>transaction(fn)</code>, and <code>close()</code>. <code>createDb</code> wraps it in a <code>Db</code>:</p>
|
|
<ul>
|
|
<li><code>all<T>(sql, params?, model?)</code> — all rows, mapped through <code>model.parse</code> when a model is given.</li>
|
|
<li><code>one<T>(sql, params?, model?)</code> — first row or <code>null</code>.</li>
|
|
<li><code>exec(sql, params?)</code> — <code>Promise<ExecResult></code> (<code>{ changes, lastInsertId? }</code>).</li>
|
|
<li><code>tx(fn)</code> — run <code>fn(db)</code> in a transaction; rolls back on throw. Nested <code>tx</code> reuses the current transaction.</li>
|
|
<li><code>createTable(model)</code> — runs the model's <code>CREATE TABLE IF NOT EXISTS</code> DDL.</li>
|
|
<li><code>close()</code>.</li>
|
|
</ul>
|
|
<p>Every query is parameterized (positional params). <code>createTableSql(model, dialect, ifNotExists?)</code> renders <code>CREATE TABLE</code> directly; <code>Dialect</code> is <code>"sqlite" | "postgres" | "mysql"</code>.</p>
|
|
<h4 id="client-registry-getdb-setdb">Client registry — <code>getDb</code> / <code>setDb</code></h4>
|
|
<p>A process-wide registry the runtime configures at startup from <code>wrnexus.config.ts</code> (the <code>db</code> setting is the default; <code>databases.<name></code> entries are named).</p>
|
|
<ul>
|
|
<li><code>setDb(db)</code> / <code>setDb(name, db)</code> — set the default or a named connection.</li>
|
|
<li><code>registerDb(name, db)</code> — alias of <code>setDb(name, db)</code>.</li>
|
|
<li><code>getDb(name = "default")</code> — the default or a named <code>Db</code> (throws if unconfigured).</li>
|
|
<li><code>hasDb(name?)</code>, <code>databaseNames()</code>, <code>closeDatabases()</code>.</li>
|
|
</ul>
|
|
<pre data-language="ts"><code>const users = await getDb().all("SELECT * FROM users");
|
|
const events = await getDb("analytics").all("SELECT * FROM hits");</code></pre>
|
|
<h4 id="adapters">Adapters</h4>
|
|
<ul>
|
|
<li><code>@wrnexus/db/sqlite</code> — <code>sqlite(url = ":memory:")</code>. <code>url</code> may be <code>file:./dev.db</code>, a raw path, or <code>:memory:</code>. Built on <code>bun:sqlite</code>; no external service.</li>
|
|
<li><code>@wrnexus/db/postgres</code> — <code>postgres(url)</code> (e.g. <code>postgres://user:pass@host:5432/db</code>, placeholders <code>$N</code>).</li>
|
|
<li><code>@wrnexus/db/mysql</code> — <code>mysql(url)</code> (e.g. <code>mysql://user:pass@host:3306/db</code>, placeholders <code>?</code>). Postgres/MySQL both use Bun's native <code>Bun.SQL</code> client and its pooled <code>begin()</code> for transactions.</li>
|
|
<li><code>@wrnexus/db/mongo</code> — <code>mongo(url, dbName?)</code>. A document API, not SQL: <code>db.collection(model)</code> returns a <code>MongoRepo<T></code> with <code>find</code>, <code>findOne</code>, <code>insert</code>, <code>insertMany</code>, <code>update</code>, <code>delete</code>, <code>count</code>. Reads are coerced through <code>model.parse</code> (<code>_id</code> is mapped to <code>id</code>). The <code>mongodb</code> driver is imported lazily — install it to use Mongo.</li>
|
|
</ul>
|
|
<h4 id="migrations">Migrations</h4>
|
|
<p>Migrations are <code>.sql</code> files (in e.g. <code>app/db/migrations</code>), each split into <code>-- +up</code> and <code>-- +down</code> sections. A file with no markers is treated entirely as <code>up</code>. Applied names are recorded in a <code>_wire_migrations</code> table so each runs once.</p>
|
|
<ul>
|
|
<li><code>parseMigration(name, content)</code> → <code>Migration</code> (<code>{ name, up, down }</code>).</li>
|
|
<li><code>loadMigrations(dir)</code> — parse all <code>.sql</code> files, sorted by filename.</li>
|
|
<li><code>appliedMigrations(db)</code> — applied names, oldest first.</li>
|
|
<li><code>migrate(db, dir)</code> — apply all pending (each in a transaction); returns applied names.</li>
|
|
<li><code>rollback(db, dir)</code> — roll back the most recent; returns its name or <code>null</code>.</li>
|
|
<li><code>status(db, dir)</code> — <code>{ name, applied }[]</code> for every migration file.</li>
|
|
<li><code>scaffoldMigration(dir, name, dialect, models?)</code> — write a new numbered migration; with <code>models</code> it generates <code>CREATE</code>/<code>DROP</code> for every table (referenced tables first via topological sort). Returns the file path.</li>
|
|
</ul>
|
|
<h4 id="query-generator-sqlc-style">Query generator (sqlc-style)</h4>
|
|
<p>Turns annotated SQL into typed TS functions; params and result types are inferred from the models, and rows map back through <code>model.parse</code> when the selected columns are model columns.</p>
|
|
<ul>
|
|
<li><code>parseQueries(content)</code> → <code>QueryDef[]</code> from <code>-- name: X :one|:many|:exec</code> blocks.</li>
|
|
<li><code>generateQueriesFile(queries, models, dialect)</code> → the <code>queries.gen.ts</code> source. <code>models</code> is a <code>ModelRef[]</code> (<code>{ varName, model }</code>). Rewrites <code>:name</code> placeholders to positional (<code>$N</code>/<code>?</code>) form.</li>
|
|
</ul>
|
|
<p><code>QueryKind</code> is <code>"one" | "many" | "exec"</code>.</p>
|
|
<h4 id="query-helpers">Query helpers</h4>
|
|
<ul>
|
|
<li><code>paginate(db, { sql, params?, countSql?, model? }, opts?)</code> — offset pagination. Pass the base SELECT <strong>without</strong> a LIMIT; it appends the page window and derives <code>total</code> via a COUNT subquery. <code>PageOptions</code>: <code>{ page?, perPage?, maxPerPage? }</code> (defaults page 1, perPage 20, maxPerPage 100). Returns <code>Paginated<T></code> (<code>items, page, perPage, total, totalPages, hasNext, hasPrev</code>).</li>
|
|
<li><code>loadRelated(db, parents, opts)</code> — load a relation for many parents in ONE query and attach it (no N+1). <code>RelationOptions</code>: <code>{ table, foreignKey, as, localKey?, single?, model? }</code> — <code>single: true</code> attaches one child (belongsTo), otherwise an array (hasMany). Table/foreign-key names are validated as identifiers.</li>
|
|
</ul>
|
|
<h4 id="session-store">Session store</h4>
|
|
<p><code>@wrnexus/db/session</code> exports <code>sqliteSessionStore(path = "sessions.db")</code>, a persistent, process-shared <code>SessionBackend</code> (from <code>@wrnexus/core</code>) backed by <code>bun:sqlite</code> (WAL mode). Sessions survive restarts and are shared by every worker on the same file.</p>
|
|
<h3 id="usage">Usage</h3>
|
|
<p>Define models, connect, create tables, and query with typed results:</p>
|
|
<pre data-language="ts"><code>import { v, table, createDb } from "@wrnexus/db";
|
|
import { sqlite } from "@wrnexus/db/sqlite";
|
|
|
|
const users = table<{ id: number; email: string; name: string | null }>("users", {
|
|
id: v.id(),
|
|
email: v.text().unique(),
|
|
name: v.text().optional(),
|
|
createdAt: v.timestamp().default("now"),
|
|
});
|
|
|
|
const db = createDb(sqlite("file:./dev.db"));
|
|
await db.createTable(users);
|
|
|
|
await db.exec("INSERT INTO users (email) VALUES (?)", ["a@b.com"]);
|
|
const list = await db.all("SELECT * FROM users", [], users); // rows typed + coerced
|
|
const one = await db.one("SELECT * FROM users WHERE id = ?", [1], users);
|
|
|
|
await db.tx(async (tx) => {
|
|
await tx.exec("UPDATE users SET name = ? WHERE id = ?", ["Ada", 1]);
|
|
});</code></pre>
|
|
<p>Resolve a config to a live SQL <code>Db</code>, and register it:</p>
|
|
<pre data-language="ts"><code>import { connectFromConfig } from "@wrnexus/db/connect";
|
|
import { setDb, getDb } from "@wrnexus/db";
|
|
|
|
setDb(connectFromConfig({ driver: "sqlite", url: "file:./dev.db" }, process.cwd()));
|
|
const rows = await getDb().all("SELECT * FROM users");</code></pre>
|
|
<p>Run migrations and paginate:</p>
|
|
<pre data-language="ts"><code>import { migrate, paginate } from "@wrnexus/db";
|
|
|
|
await migrate(db, "app/db/migrations");
|
|
const pageTwo = await paginate(
|
|
db,
|
|
{ sql: "SELECT * FROM users ORDER BY id", model: users },
|
|
{ page: 2 },
|
|
);</code></pre>
|
|
<p>MongoDB (document API):</p>
|
|
<pre data-language="ts"><code>import { mongo } from "@wrnexus/db/mongo";
|
|
|
|
const mdb = await mongo(process.env.MONGO_URL!, "app");
|
|
const repo = mdb.collection(users);
|
|
await repo.insert({ email: "a@b.com" });
|
|
const active = await repo.find({ active: true });</code></pre>
|
|
<h3 id="configuration">Configuration</h3>
|
|
<p><code>connectFromConfig</code> (and the runtime) read a <code>DbConfig</code> (<code>{ driver, url }</code>) where <code>driver</code> is <code>sqlite | postgres | mysql</code>. <code>resolveDbUrl(url, appRoot?)</code> resolves a relative <code>file:</code>/<code>sqlite:</code> URL against the app root. MongoDB is not a SQL driver — use <code>@wrnexus/db/mongo</code> directly.</p>
|
|
<h3 id="requirements-notes">Requirements / Notes</h3>
|
|
<ul>
|
|
<li><strong>Bun-only.</strong> Uses <code>bun:sqlite</code> (SQLite adapter + session store) and <code>Bun.SQL</code></li>
|
|
<p>(Postgres/MySQL). Migrations/scaffolding use <code>node:fs</code>/<code>node:path</code>.</p>
|
|
<li>Works with <code>@wrnexus/core</code> — <code>sqliteSessionStore</code> implements its</li>
|
|
<p><code>SessionBackend</code>; <code>getDb</code>/<code>setDb</code> are wired by the WRNexusJS runtime from <code>wrnexus.config.ts</code>.</p>
|
|
<li>The <code>mongodb</code> npm package is an optional, lazily-imported peer — install it</li>
|
|
<p>only if you use <code>@wrnexus/db/mongo</code>. The core package stays dependency-free.</p>
|
|
</ul></section><section id="api" class="api"><h2>Complete TypeScript API</h2><p>Generated from the exact installed package declarations.</p><pre data-language="typescript"><code>import { M as Model } from './schema-tVurYsbL.js';
|
|
export { B as BaseType, C as Column, a as ColumnDef, b as Columns, t as table, v } from './schema-tVurYsbL.js';
|
|
import { a as Db, b as Dialect, R as Row } from './driver-DA53QHkO.js';
|
|
export { D as Driver, E as ExecResult, T as TxHandle, c as createDb, d as createTableSql } from './driver-DA53QHkO.js';
|
|
|
|
/**
|
|
* A process-wide database registry. The framework configures it at server
|
|
* startup from `wrnexus.config.ts`: the `db` setting becomes the **default**
|
|
* connection, and each entry under `databases` becomes a **named** connection.
|
|
* Pages and API routes then call `getDb()` for the default, or `getDb("<name>")`
|
|
* for a named one, to run queries (including the generated typed functions).
|
|
*
|
|
* const users = await getDb().all("SELECT * FROM users"); // default db
|
|
* const events = await getDb("analytics").all("SELECT * FROM hits"); // named db
|
|
*/
|
|
|
|
type DbFactory = () => Db;
|
|
/** Set the default database (called by the runtime at startup). */
|
|
declare function setDb(db: Db): Db;
|
|
/** Set a named database (from `databases.<name>` in config). */
|
|
declare function setDb(name: string, db: Db): Db;
|
|
/** Register a named database. Alias of `setDb(name, db)` for readability. */
|
|
declare function registerDb(name: string, db: Db): Db;
|
|
/**
|
|
* Register a named database without opening its connection pool. The first
|
|
* `getDb(name)` call creates and caches the connection.
|
|
*/
|
|
declare function registerLazyDb(name: string, factory: DbFactory): void;
|
|
/** The default database, or a named one. Throws if it isn't configured. */
|
|
declare function getDb(name?: string): Db;
|
|
/** Whether the default (or a named) database has been configured. */
|
|
declare function hasDb(name?: string): boolean;
|
|
/** Names of all configured databases (the default appears as "default"). */
|
|
declare function databaseNames(): string[];
|
|
/** Close every configured database and clear the registry. */
|
|
declare function closeDatabases(): Promise<void>;
|
|
|
|
/**
|
|
* Migration runner. Migrations are `.sql` files in `app/db/migrations`, each
|
|
* split into `-- +up` and `-- +down` sections. Applied migrations are recorded
|
|
* in a `_wire_migrations` table so they run exactly once, newest-last.
|
|
*
|
|
* `scaffoldMigration(..., models)` writes an initial migration straight from the
|
|
* TS models — the source of truth — so you don't hand-write the first schema.
|
|
*/
|
|
|
|
interface Migration {
|
|
name: string;
|
|
up: string;
|
|
down: string;
|
|
}
|
|
/** Split a migration file into its `up` and `down` SQL sections. */
|
|
declare function parseMigration(name: string, content: string): Migration;
|
|
/** Load and parse all migration files in a directory, sorted by filename. */
|
|
declare function loadMigrations(dir: string): Migration[];
|
|
/** Names of already-applied migrations, oldest first. */
|
|
declare function appliedMigrations(db: Db): Promise<string[]>;
|
|
/** Apply an ordered migration list (each in a transaction). Returns applied names. */
|
|
declare function applyMigrations(db: Db, migrations: readonly Migration[]): Promise<string[]>;
|
|
/** Apply all pending migrations from a directory. */
|
|
declare function migrate(db: Db, dir: string): Promise<string[]>;
|
|
/** Roll back the most recently applied migration. Returns its name, or null. */
|
|
declare function rollback(db: Db, dir: string): Promise<string | null>;
|
|
/** Full status: every migration file with whether it has been applied. */
|
|
declare function status(db: Db, dir: string): Promise<{
|
|
name: string;
|
|
applied: boolean;
|
|
}[]>;
|
|
/**
|
|
* Write a new migration file. With `models`, the `up`/`down` are generated from
|
|
* the TS models (create/drop every table); otherwise empty stubs are written.
|
|
* Returns the created file path.
|
|
*/
|
|
declare function scaffoldMigration(dir: string, name: string, dialect: Dialect, models?: Model[]): string;
|
|
|
|
/**
|
|
* sqlc-style query generator. Annotated SQL in `app/db/queries/*.sql` becomes
|
|
* typed TS functions whose params + results are inferred from the TS models and
|
|
* whose rows are mapped back through `model.parse`.
|
|
*
|
|
* -- name: GetUserByEmail :one
|
|
* SELECT * FROM users WHERE email = :email;
|
|
*
|
|
* → GetUserByEmail(db, { email: string }): Promise<{…} | null>
|
|
*
|
|
* Type inference is best-effort (comparisons + INSERT column lists + SELECT list
|
|
* vs the model); anything it can't resolve becomes `unknown`.
|
|
*/
|
|
|
|
type QueryKind = "one" | "many" | "exec";
|
|
interface QueryDef {
|
|
name: string;
|
|
kind: QueryKind;
|
|
sql: string;
|
|
}
|
|
/** A model plus the variable name it is exported under (for imports). */
|
|
interface ModelRef {
|
|
varName: string;
|
|
model: Model;
|
|
}
|
|
/** Parse annotated queries from one `.sql` file's contents. */
|
|
declare function parseQueries(content: string): QueryDef[];
|
|
/** Generate the full `queries.gen.ts` source. */
|
|
declare function generateQueriesFile(queries: QueryDef[], models: ModelRef[], dialect: Dialect): string;
|
|
|
|
/**
|
|
* Query ergonomics built on the `Db` client: offset pagination and a batched
|
|
* relation loader (avoids N+1). Both are dialect-aware — placeholders follow the
|
|
* driver's style (`$N` for Postgres, `?` for SQLite/MySQL).
|
|
*/
|
|
|
|
interface PageOptions {
|
|
page?: number;
|
|
perPage?: number;
|
|
/** Upper bound on perPage. Default 100. */
|
|
maxPerPage?: number;
|
|
}
|
|
interface Paginated<T> {
|
|
items: T[];
|
|
page: number;
|
|
perPage: number;
|
|
total: number;
|
|
totalPages: number;
|
|
hasNext: boolean;
|
|
hasPrev: boolean;
|
|
}
|
|
/**
|
|
* Paginate a SELECT. Pass the base query WITHOUT a LIMIT; the helper appends the
|
|
* page window and derives the total with a COUNT over the same query.
|
|
*
|
|
* await paginate(db, { sql: "SELECT * FROM users ORDER BY name", model: users }, { page: 2 })
|
|
*/
|
|
declare function paginate<T = Row>(db: Db, query: {
|
|
sql: string;
|
|
params?: unknown[];
|
|
countSql?: string;
|
|
model?: Model<T>;
|
|
}, opts?: PageOptions): Promise<Paginated<T>>;
|
|
interface RelationOptions<C> {
|
|
/** Parent field whose value matches the child's foreign key. Default "id". */
|
|
localKey?: string;
|
|
/** Child table to load from. */
|
|
table: string;
|
|
/** Child column that references the parent. */
|
|
foreignKey: string;
|
|
/** Property name to attach on each parent. */
|
|
as: string;
|
|
/** true → attach a single child (belongsTo); false → an array (hasMany). */
|
|
single?: boolean;
|
|
/** Map child rows through a model. */
|
|
model?: Model<C>;
|
|
}
|
|
/**
|
|
* Load a relation for a set of parent rows in ONE query and attach it to each
|
|
* parent (no N+1). Returns the same parents, each with `opts.as` populated.
|
|
*
|
|
* await loadRelated(db, users, { table: "posts", foreignKey: "userId", as: "posts" })
|
|
*/
|
|
declare function loadRelated<P extends Row, C extends Row = Row>(db: Db, parents: P[], opts: RelationOptions<C>): Promise<(P & Record<string, C | C[] | null>)[]>;
|
|
|
|
interface CursorPageOptions {
|
|
limit?: number;
|
|
after?: string;
|
|
before?: string;
|
|
column?: string;
|
|
direction?: "asc" | "desc";
|
|
maxLimit?: number;
|
|
}
|
|
interface CursorPage<T> {
|
|
items: T[];
|
|
nextCursor?: string;
|
|
previousCursor?: string;
|
|
hasMore: boolean;
|
|
}
|
|
declare function cursorPaginate<T extends Row = Row>(db: Db, query: {
|
|
sql: string;
|
|
params?: unknown[];
|
|
model?: Model<T>;
|
|
}, options?: CursorPageOptions): Promise<CursorPage<T>>;
|
|
declare function optimisticUpdate(db: Db, input: {
|
|
table: string;
|
|
idColumn?: string;
|
|
id: unknown;
|
|
versionColumn?: string;
|
|
version: number;
|
|
values: Record<string, unknown>;
|
|
}): Promise<number>;
|
|
declare function tenantScope(sql: string, tenantId: unknown, dialect: Dialect, existingParams?: number, column?: string): {
|
|
sql: string;
|
|
params: unknown[];
|
|
};
|
|
declare function softDeleteClause(column?: string): string;
|
|
|
|
export { type CursorPage, type CursorPageOptions, Db, Dialect, type Migration, Model, type ModelRef, type PageOptions, type Paginated, type QueryDef, type QueryKind, type RelationOptions, Row, appliedMigrations, applyMigrations, closeDatabases, cursorPaginate, databaseNames, generateQueriesFile, getDb, hasDb, loadMigrations, loadRelated, migrate, optimisticUpdate, paginate, parseMigration, parseQueries, registerDb, registerLazyDb, rollback, scaffoldMigration, setDb, softDeleteClause, status, tenantScope };
|
|
</code></pre></section><section id="examples" class="examples"><h2>Examples</h2><p>Copy-ready examples from the installed package documentation.</p><div class="example-grid"><article class="example-card"><h3>Define models, connect, create tables, and query with typed results</h3><pre data-language="ts"><code>import { v, table, createDb } from "@wrnexus/db";
|
|
import { sqlite } from "@wrnexus/db/sqlite";
|
|
|
|
const users = table<{ id: number; email: string; name: string | null }>("users", {
|
|
id: v.id(),
|
|
email: v.text().unique(),
|
|
name: v.text().optional(),
|
|
createdAt: v.timestamp().default("now"),
|
|
});
|
|
|
|
const db = createDb(sqlite("file:./dev.db"));
|
|
await db.createTable(users);
|
|
|
|
await db.exec("INSERT INTO users (email) VALUES (?)", ["a@b.com"]);
|
|
const list = await db.all("SELECT * FROM users", [], users); // rows typed + coerced
|
|
const one = await db.one("SELECT * FROM users WHERE id = ?", [1], users);
|
|
|
|
await db.tx(async (tx) => {
|
|
await tx.exec("UPDATE users SET name = ? WHERE id = ?", ["Ada", 1]);
|
|
});</code></pre></article><article class="example-card"><h3>Resolve a config to a live SQL Db, and register it</h3><pre data-language="ts"><code>import { connectFromConfig } from "@wrnexus/db/connect";
|
|
import { setDb, getDb } from "@wrnexus/db";
|
|
|
|
setDb(connectFromConfig({ driver: "sqlite", url: "file:./dev.db" }, process.cwd()));
|
|
const rows = await getDb().all("SELECT * FROM users");</code></pre></article><article class="example-card"><h3>Run migrations and paginate</h3><pre data-language="ts"><code>import { migrate, paginate } from "@wrnexus/db";
|
|
|
|
await migrate(db, "app/db/migrations");
|
|
const pageTwo = await paginate(
|
|
db,
|
|
{ sql: "SELECT * FROM users ORDER BY id", model: users },
|
|
{ page: 2 },
|
|
);</code></pre></article><article class="example-card"><h3>MongoDB (document API)</h3><pre data-language="ts"><code>import { mongo } from "@wrnexus/db/mongo";
|
|
|
|
const mdb = await mongo(process.env.MONGO_URL!, "app");
|
|
const repo = mdb.collection(users);
|
|
await repo.insert({ email: "a@b.com" });
|
|
const active = await repo.find({ active: true });</code></pre></article></div></section></article>
|
|
<aside class="on-this-page"><h2>On this page</h2><nav><a class="toc-level-2" href="#guide">Guide</a><a class="toc-level-3" href="#overview">Overview</a><a class="toc-level-3" href="#api">API</a><a class="toc-level-4" href="#schema-v-table-column">Schema — v, table, Column</a><a class="toc-level-4" href="#driver-client-createdb-db-driver">Driver & client — createDb, Db, Driver</a><a class="toc-level-4" href="#client-registry-getdb-setdb">Client registry — getDb / setDb</a><a class="toc-level-4" href="#adapters">Adapters</a><a class="toc-level-4" href="#migrations">Migrations</a><a class="toc-level-4" href="#query-generator-sqlc-style">Query generator (sqlc-style)</a><a class="toc-level-4" href="#query-helpers">Query helpers</a><a class="toc-level-4" href="#session-store">Session store</a><a class="toc-level-3" href="#usage">Usage</a><a class="toc-level-3" href="#configuration">Configuration</a><a class="toc-level-3" href="#requirements-notes">Requirements / Notes</a><a class="toc-level-2" href="#api">Complete API</a><a class="toc-level-2" href="#examples">Examples</a></nav></aside>
|
|
</main>
|
|
<footer><div class="footer-brand"><span class="footer-mark" aria-hidden="true">W</span><p><strong>WRNexusJS 0.5.11</strong><span>Complete API documentation generated from installed package declarations.</span></p></div><nav aria-label="Footer"><a href="/packages">All packages</a><a href="/getting-started">Get started</a><a href="/security">Security</a><a href="/support">Support</a><a href="/llms.txt">AI guide</a></nav><p class="footer-meta">Private Developer Preview · Bun-native</p></footer>
|
|
<BackToTop />
|
|
</div>
|
|
}
|
|
}
|