API index and search · Build metadata
Supporting declarations
packages/client/src/mutation-ops.ts. These declarations explain referenced types. Only package-page symbols are package exports.
KeyedRow
/** A keyed row: column name → cell. The ergonomic write shape (validated against the schema at
* runtime). JSON columns carry their raw JSON string (a {@link WireValue}), never a parsed object. */
export type KeyedRow = Record<string, WireValue>;MutationOp
/** One structured write intent — the discriminated union that mirrors the write half of the client
* `MutationTx` 1:1. Keyed (column-name addressed), so it is independent of column order. */
export type MutationOp = {
kind: "insert";
table: string;
row: KeyedRow;
} | {
kind: "upsert";
table: string;
row: KeyedRow;
} | {
kind: "insertIgnore";
table: string;
row: KeyedRow;
} | {
kind: "update";
table: string;
row: KeyedRow;
} | {
kind: "delete";
table: string;
pk: KeyedRow;
};ServerWriteTx
/** The ASYNC server-side write surface a mutator runs against. Semantically the twin of the client's
* synchronous `MutationTx` write methods — same names, same keyed-row arguments — but every op is a
* Promise. Reads see this transaction's own writes on both Postgres and the daemon; the daemon
* opens an interactive transaction when a read is needed. Inserts require every non-nullable
* column and fill omitted nullable columns with `null`. Updates and deletes require the PK. */
export interface ServerWriteTx {
/** Insert a row. Missing required columns and unknown columns throw; omitted nullable
* columns become `null`, rather than using database defaults. */
insert(table: string, row: KeyedRow): Promise<void>;
/** Update the row identified by the pk columns; only the named non-pk columns change. A missing
* row is a NO-OP. */
update(table: string, row: KeyedRow): Promise<void>;
/** Insert, or replace non-PK columns on PK conflict, using the same insert shape. */
upsert(table: string, row: KeyedRow): Promise<void>;
/** Insert with the same nullable-column rules as `insert`, or do nothing on PK conflict. */
insertIgnore(table: string, row: KeyedRow): Promise<void>;
/** Delete the row identified by the pk columns. A missing row is a NO-OP. */
delete(table: string, pk: KeyedRow): Promise<void>;
/** Read one row by primary key, through the OPEN transaction on both backends
* (read-your-writes: live on Postgres; via an interactive mutation session on the daemon,
* DAEMON-INTERACTIVE-TXN-DESIGN.md §5.3). */
row(table: string, pk: KeyedRow): Promise<KeyedRow | undefined>;
}ReadEffect
/** A point read a generator mutator yields; the driver resolves it and feeds the row back through
* `gen.next(row)`. Read-your-writes on every tier: the client reads its local engine, and both
* server backends read the open transaction (the daemon via an interactive mutation session). */
export type ReadEffect = {
kind: "row";
table: string;
pk: KeyedRow;
};BatchEffect
/** A fan-out a generator mutator yields: run several effects "together". The server driver resolves
* them with `Promise.all`; the client driver runs them in array order (already synchronous). Results
* return in the same order on both tiers. Each result has its effect's shape: a row, query rows,
* `undefined` for a write, or an array for a nested batch. Cast the yielded result to that shape;
* the generator's declared next-type covers only point reads. */
export type BatchEffect = {
kind: "all";
effects: readonly YieldEffect[];
};QueryResultRow
/** A row returned by a {@link QueryEffect}: column name → cell, plus each materialized relationship
* name → its nested row(s) — an array (a plural relationship) or a single row / `null` (a `.one()`
* relationship). Recursive. Presented identically on both tiers (a `view.data` row of the same
* query). */
export type QueryResultRow = {
[key: string]: WireValue | QueryResultRow | QueryResultRow[];
};QueryArg
/** What {@link IsoTx.query} accepts: a query handle whose `.ast()` lowers to the wire {@link Ast} —
* exactly what the typed query builder produces (`newQueryBuilder(schema).<table>…`, the tier-agnostic
* server-scope builder both tiers can construct because it performs no I/O). Structural so the seam
* need not carry the builder's heavy generics. */
export type QueryArg = {
ast(): Ast;
};QueryEffect
/** A full-shape read a generator mutator yields — a `where`/`orderBy`/`limit`/join query over the
* state this mutator is mutating (read-your-writes, like {@link ReadEffect} but an arbitrary shape).
* Evaluates to {@link QueryResultRow}`[]` — ALWAYS an array of the matching rows, in the query's
* order, on BOTH tiers (a root `.one()` is not unwrapped here: take `[0]`). The single next-type is
* a row, so cast the yield (`(yield tx.query(q)) as unknown as QueryResultRow[]`), same as `all`. */
export type QueryEffect = {
kind: "query";
query: QueryArg;
};YieldEffect
/** Everything a generator mutator may `yield`: a write {@link MutationOp}, a point {@link ReadEffect},
* a full-query {@link QueryEffect}, or a {@link BatchEffect} fan-out. */
export type YieldEffect = MutationOp | ReadEffect | BatchEffect | QueryEffect;IsoTx
/** The tier-AGNOSTIC effect factory a generator mutator writes against. Every method just BUILDS an
* effect to `yield` — it performs no I/O and holds no state, so the single {@link isoTx} instance is
* shared by every mutator on both tiers; only the driver differs. `insert`/`upsert`/`insertIgnore`
* require non-nullable columns and permit nullable omissions, which become `null`.
* `update`/`delete` require the PK columns; `update` also names the columns to change. */
export interface IsoTx<S extends ColsMap = ColsMap, P extends Record<string, string> = PkMap<S>> {
/** Insert a row. Omitted nullable columns become `null`; database defaults are not applied. */
insert<N extends keyof S & string>(table: N, row: InsertOf<S[N]>): MutationOp;
/** Update the row identified by its pk columns (REQUIRED); only the named non-pk columns change. */
update<N extends keyof S & string>(table: N, row: UpdateOf<S[N], PkColsOf<S, P, N>>): MutationOp;
/** Insert, or replace non-PK columns on PK conflict, with {@link IsoTx.insert}'s omission rules. */
upsert<N extends keyof S & string>(table: N, row: InsertOf<S[N]>): MutationOp;
/** Insert with {@link IsoTx.insert}'s omission rules, or do nothing on PK conflict. */
insertIgnore<N extends keyof S & string>(table: N, row: InsertOf<S[N]>): MutationOp;
/** Delete the row identified by its pk columns. */
delete<N extends keyof S & string>(table: N, pk: PkOf<S[N], PkColsOf<S, P, N>>): MutationOp;
/** Read one row by primary key (read-your-writes). */
row<N extends keyof S & string>(table: N, pk: PkOf<S[N], PkColsOf<S, P, N>>): ReadEffect;
/** Run a full query (`where`/`orderBy`/`limit`/join) over the state this mutator is mutating —
* read-your-writes, like {@link row} but for arbitrary shapes. Pass a query from the tier-agnostic
* builder, e.g. `tx.query(q.issue.where("ownerId", "=", ctx.user))` where `q = newQueryBuilder(schema)`.
* The `yield` evaluates to {@link QueryResultRow}`[]` (cast it — the generator's single next-type is a row). */
query(query: QueryArg): QueryEffect;
all(effects: readonly YieldEffect[]): BatchEffect;
}isoTx
/** The one shared effect factory (stateless — see {@link IsoTx}). Its methods just BUILD a
* {@link MutationOp}, so the single instance serves every schema; the generic {@link IsoTx} view is
* applied at the authoring site (a `json<T>` cell is a parsed object here and is stringified by the
* funnels, {@link toCell}), hence the cast — the runtime shape is schema-agnostic. */
export declare const isoTx: IsoTx;MutatorCtx
/** The minimal per-invocation context a shared mutator sees on BOTH tiers: the acting principal. The
* server injects its AUTHENTICATED user; the client injects its local user. (The server's own
* `MutationContext` is a superset of this.) */
export interface MutatorCtx {
user: string;
}MutationGen
/** What a generator mutator IS: `yield tx.<op>()` on every side effect; a `yield tx.row()` expression
* evaluates to the row. Neither sync nor async — the tier's driver decides, which is what lets one
* body run synchronously on the client and against a live async transaction on the server. */
export type MutationGen = Generator<YieldEffect, void, KeyedRow | undefined>;ArgSchema
/** The minimal arg validator a shared mutator can carry so the SERVER can parse UNTRUSTED wire args
* before driving it (the client trusts its typed callsites and never calls this). Structural on
* purpose — a zod schema satisfies it as-is — so `@rindle/client` stays validator-library-agnostic. */
export interface ArgSchema<Args> {
parse(raw: unknown): Args;
}IsoTxOf
/** Bind a schema to the mutator authoring surface: `const { shared } = defineMutators(schema)`.
*
* The returned `shared` is the schema-typed twin of the bare {@link shared} — its `tx` is an
* {@link IsoTx} parameterized by THIS schema, so `tx.insert`/`update`/`upsert`/`insertIgnore`/`delete`
* check the table name, every column name, each column's value type, nullable-omit on inserts, and
* the exact primary-key columns on `update`/`delete` — all at compile time. It registers identically
* to the bare form (same runtime value, still `satisfies ClientRegistry`, still an isomorphic
* generator the server drives): only the AUTHORING types tighten. `schema` is read for its TYPE only
* (never at runtime). Reads (`yield tx.row(...)`) stay loosely typed — a generator's single
* next-type can't carry a per-table row. */
/** The typed {@link IsoTx} for a given schema — `IsoTxOf<typeof schema>`. Use it to annotate a helper
* that a mutator body passes its `tx` to (e.g. `const ensureUser = (tx: IsoTxOf<typeof schema>, …)`),
* so the helper gets the same table/column/pk typing the `defineMutators` `shared` callback does. */
export type IsoTxOf<Sch extends Schema> = Sch extends Schema<infer S, infer P> ? IsoTx<S, P> : never;defineMutators
export declare function defineMutators<S extends ColsMap, P extends Record<string, string>>(_schema: Schema<S, P>): {
shared<Args, Ctx extends MutatorCtx = MutatorCtx>(args: ArgSchema<Args>, run: (tx: IsoTx<S, P>, args: Args, ctx: Ctx) => MutationGen): SharedMutatorWithArgs<Args, Ctx>;
};isGeneratorMutator
/** True iff `fn` is a generator function (a shared/isomorphic mutator) rather than a plain function —
* the driver accepts both forms. Detected structurally (native `GeneratorFunction`). */
export declare function isGeneratorMutator(fn: unknown): fn is (...args: never[]) => MutationGen;SyncEffectExec
/** A tier's SYNCHRONOUS effect executor (the browser wasm engine): apply a write, resolve a read —
* both immediate. */
export interface SyncEffectExec {
apply(op: MutationOp): void;
read(table: string, pk: KeyedRow): KeyedRow | undefined;
query(q: QueryArg): QueryResultRow[];
}driveMutationSync
/** Drive a generator mutator SYNCHRONOUSLY (the client, inside the wasm write transaction). Each
* yielded write applies immediately; each read (point or query) is resolved and fed back; a batch
* runs in order. */
export declare function driveMutationSync(gen: MutationGen, exec: SyncEffectExec): void;AsyncEffectExec
/** A tier's ASYNCHRONOUS effect executor (the server transaction): every op is a Promise. */
export interface AsyncEffectExec {
apply(op: MutationOp): Promise<void>;
read(table: string, pk: KeyedRow): Promise<KeyedRow | undefined>;
query(q: QueryArg): Promise<QueryResultRow[]>;
}driveMutationAsync
/** Drive a generator mutator ASYNCHRONOUSLY (the server, against the open transaction). Writes are
* awaited (harmless: a single interactive Postgres connection serializes statements anyway, and the
* daemon backend resolves them instantly), reads (point or query) suspend, and a batch fans out with
* `Promise.all`. */
export declare function driveMutationAsync(gen: MutationGen, exec: AsyncEffectExec): Promise<void>;