Rindle

API index and search · Build metadata

Supporting declarations

packages/client/src/schema.ts. These declarations explain referenced types. Only package-page symbols are package exports.

Exact source

Col

/** A column descriptor. `type` drives the comparator + JSON parsing; `__t` is a phantom. A
 *  nullable column is a `Col<T | null>` — that is the ONLY thing nullability changes at the type
 *  level, so `RowOf` (and the field-condition factory) widen automatically.
 *
 *  `optional` is the runtime companion of that phantom: set by {@link ColBuilder.nullable}, it is
 *  what the write funnels read to make a nullable column omittable from an insert (filled with
 *  `null`, design 206 §6.2). It mirrors the engine's `ColumnDef.optional`
 *  (`pragma_table_info.notnull == 0`); absent ⇒ `NOT NULL` / required. */
export interface Col<T> {
    readonly type: ColType;
    readonly optional?: boolean;
    readonly __t?: T;
}

ColT

export type ColT<X> = X extends Col<infer T> ? T : never;

AnyCols

export type AnyCols = Record<string, Col<unknown>>;

RowOf

export type RowOf<C extends AnyCols> = {
    [K in keyof C]: ColT<C[K]>;
};

ColBuilder

/** The chainable form returned by the column factories: a {@link Col} plus `.nullable()`.
 *
 *  `.nullable()` widens the column's value type to `T | null` — its `Row<…>` field becomes
 *  `T | null` — and sets the runtime {@link Col.optional} marker so it may be omitted from an insert
 *  (design 206 §6.2). `rindle schema gen` emits `.nullable()` for every nullable (non-`NOT NULL`) SQL
 *  column; you can also call it by hand on a local-only table's columns. It is idempotent and stays
 *  chainable. */
export interface ColBuilder<T> extends Col<T> {
    nullable(): ColBuilder<T | null>;
}

string

export declare const string: <T extends string = string>() => ColBuilder<T>;

number

export declare const number: <T extends number = number>() => ColBuilder<T>;

boolean

export declare const boolean: () => ColBuilder<boolean>;

json

export declare const json: <T = unknown>() => ColBuilder<T>;

int64

/** The exact-i64 column plane (design 226, `BIGINT`/`INT8` decltype): typed `bigint` in
 *  application code. The vocabulary exists from Stage C4 so generated schemas can name it;
 *  no exact integer cell enters the browser IVM until Stage E — until then the daemon
 *  refuses IVM queries whose footprint touches the column, and the SQL plane carries it. */
export declare const int64: <T extends bigint = bigint>() => ColBuilder<T>;

SCHEMA

/** Metadata key on a {@link TableDef} (a `unique symbol`, so no column name collides). */
export declare const SCHEMA: unique symbol;

TableMeta

export interface TableMeta<N extends string = string, C extends AnyCols = AnyCols, PK extends string = string> {
    readonly name: N;
    readonly columns: C;
    readonly primaryKey: readonly PK[];
    /** A **local-only** table (`201-LOCAL-ONLY-TABLES-DESIGN.md`): client-authoritative,
     *  never synced/tracked/rebased. The single marker the whole design keys off (§4) — it is
     *  immutable for the table's lifetime (N2) and never crosses the wire (C2). Absent ⇒ an
     *  ordinary synced table.
     *
     *  `true` ⇒ eligible for the local-persistence plane (durable + cross-tab when the client
     *  enables `persistLocal`, `207-LOCAL-TABLE-PERSISTENCE-DESIGN.md`). `"session"` ⇒ local but
     *  EPHEMERAL: outside the plane entirely — never persisted, never replicated across tabs,
     *  per-client-instance state that empties on reload (201's original behavior) even when
     *  `persistLocal` is on. Every OTHER locality rule (untracked source, M1/M2 guards, E3/Q1
     *  wire exclusion) treats both variants identically. */
    readonly local?: boolean | "session";
}

TableOptions

/** Options for {@link table}. */
export interface TableOptions {
    /** Declare a {@link TableMeta.local local-only} table (selection state, draft text, view
     *  prefs, scratch rows): client-authoritative, never synced or rebased. See
     *  `201-LOCAL-ONLY-TABLES-DESIGN.md`. Pass `"session"` for a local table that must stay
     *  EPHEMERAL and per-tab even when the client enables `persistLocal` — e.g. selection state
     *  that should not follow the user across tabs or reloads (207 §5.4). */
    local?: boolean | "session";
}

TableDef

export type TableDef<N extends string, C extends AnyCols, PK extends string = string> = {
    readonly [SCHEMA]: TableMeta<N, C, PK>;
} & {
    readonly [K in keyof C]: (arg: Arg<ColT<C[K]>>) => Cond<RowOf<C>>;
};

TableLike

/** Any table, for positions that only read its metadata. The field-factory part of a
 *  `TableDef` is invariant in `C`, so callers constrain on the `[SCHEMA]` meta only — to
 *  which every concrete `TableDef<N, C>` is assignable. */
export type TableLike<C extends AnyCols> = {
    readonly [SCHEMA]: TableMeta<string, C>;
};

AnyTable

export type AnyTable = TableLike<AnyCols>;

Row

/** The row type of a table definition: `Row<typeof issue>` → `{ id: string; … }`. The
 *  whole-table ergonomic form of {@link RowOf}, so app code derives its row interfaces from
 *  the schema instead of hand-maintaining a parallel twin. */
export type Row<T extends AnyTable> = RowOf<T[typeof SCHEMA]["columns"]>;

Simplify

/** Flatten an intersection of mapped types into a single object type (preserving `?`/`readonly`) so
 *  {@link InsertOf} reads as one clean shape in editor hovers, not `A & B`. */
type Simplify<T> = {
    [K in keyof T]: T[K];
};

IsTop

/** True for the top types `unknown`/`any` (`unknown extends any` too), where `[null] extends [T]` is
 *  vacuously true and so can't tell a `.nullable()` apart. */
type IsTop<T> = unknown extends T ? true : false;

InsertOptional

/** Whether column `X` may be OMITTED from an insert: it admits `null` at the type level — EXCLUDING
 *  the top types. A bare `json()` is `json<unknown>()`, and `unknown | null` collapses back to
 *  `unknown`, erasing whether `.nullable()` was applied; treating it as required is the safe
 *  direction (a `NOT NULL` json column is never wrongly made optional). Declare `json<T>()` to make a
 *  nullable json column omittable. The runtime companion is `Col.optional` (design 206 §6.2/§7). */
type InsertOptional<X> = IsTop<ColT<X>> extends true ? false : [
    null
] extends [
    ColT<X>
] ? true : false;

InsertOf

/** The INSERT shape of a column map: {@link RowOf} with every NULLABLE column made OPTIONAL (`?`) —
 *  it may be omitted and is filled with `null` by both write funnels (design 206 §6.2/§7). `NOT NULL`
 *  columns stay required. The insert-side twin of {@link RowOf}, mirroring Drizzle's `$inferInsert`
 *  vs `$inferSelect` split. */
export type InsertOf<C extends AnyCols> = Simplify<{
    [K in keyof C as InsertOptional<C[K]> extends true ? never : K]: ColT<C[K]>;
} & {
    [K in keyof C as InsertOptional<C[K]> extends true ? K : never]?: ColT<C[K]>;
}>;

Insert

/** The insert type of a table def: `Insert<typeof issue>` → `{ id: string; … assignee?: string | null }`.
 *  The whole-table ergonomic form of {@link InsertOf} (the insert-side twin of {@link Row}). */
export type Insert<T extends AnyTable> = InsertOf<T[typeof SCHEMA]["columns"]>;

PkOf

/** The exact PRIMARY-KEY columns of a table (each typed), required — the identity a `delete`/`row`,
 *  and the WHERE half of an `update`, take. `PK` is the table's pk-column union (the schema's `__pk`). */
export type PkOf<C extends AnyCols, PK extends string> = {
    [K in PK & keyof C]: ColT<C[K & keyof C]>;
};

UpdateOf

/** The UPDATE shape of a table: its {@link PkOf primary-key columns} REQUIRED (they identify the row)
 *  plus every non-pk column OPTIONAL (`?`) and typed — a nullable one stays `T | null` (settable to
 *  null), a `NOT NULL` one is `T`. Omitted non-pk columns are left unchanged. */
export type UpdateOf<C extends AnyCols, PK extends string> = Simplify<PkOf<C, PK> & {
    [K in Exclude<keyof C, PK>]?: ColT<C[K]>;
}>;

PkColsOf

/** The primary-key column union for table `N` of a schema, read from its `__pk` map `P` and narrowed
 *  to `N`'s actual columns (falling back to all columns when `P` doesn't name `N` — e.g. the loose
 *  default schema). Feeds {@link PkOf}/{@link UpdateOf} in the typed mutator tx. */
export type PkColsOf<S extends ColsMap, P extends Record<string, string>, N extends keyof S> = (N extends keyof P ? P[N] : keyof S[N] & string) & keyof S[N] & string;

table

/** `table("issue").columns({ id: string(), … }).primaryKey("id")`. Pass `{ local: true }` for a
 *  {@link TableMeta.local local-only} table (`201-LOCAL-ONLY-TABLES-DESIGN.md`). */
export declare function table<N extends string>(name: N, opts?: TableOptions): {
    columns<C extends AnyCols>(cols: C): {
        primaryKey<K extends keyof C & string>(...keys: K[]): TableDef<N, C, K>;
    };
};

tableMeta

/** Read a table's metadata (columns / PK / name). */
export declare function tableMeta(t: AnyTable): TableMeta;

SchemaOf

/** name → columns, derived from the tables array (for typing `store.query.<table>`). */
export type SchemaOf<T extends readonly AnyTable[]> = {
    [E in T[number] as E[typeof SCHEMA]["name"]]: E[typeof SCHEMA]["columns"];
};

PkMapOf

/** name → its primary-key column union, derived from the tables array (the PK captured by the
 *  `primaryKey(...)` builder). Powers the mutator tx's typed `update`/`delete`/`row` pk args. */
export type PkMapOf<T extends readonly AnyTable[]> = {
    [E in T[number] as E[typeof SCHEMA]["name"]]: E[typeof SCHEMA]["primaryKey"][number];
};

ColsMap

/** name → columns, the resolved schema map carried in the {@link Schema} type. */
export type ColsMap = Record<string, AnyCols>;

PkMap

/** name → (some subset of its column names): the loose shape a {@link Schema}'s pk-map satisfies. The
 *  fallback when a schema wasn't built through {@link createSchema} — every column could be the pk. */
export type PkMap<S extends ColsMap> = {
    [N in keyof S]: keyof S[N] & string;
};

Schema

export interface Schema<S extends ColsMap = ColsMap, P extends Record<string, string> = PkMap<S>> {
    readonly tables: Readonly<Record<string, TableMeta>>;
    /** Phantom carrying name→columns for query-root inference (never read at runtime). */
    readonly __cols: S;
    /** Phantom carrying name→pk-column-union for the typed mutator tx (never read at runtime). */
    readonly __pk: P;
}

RESERVED_TABLE_PREFIXES

/** Table-name prefixes reserved by the engine for SYNTHETIC tables — `__agg_<fnv>` aggregate
 *  bases (`AGGREGATE-SYNC-DESIGN.md` §3.3) and `_rindle_*` system tables (e.g. the lmid table).
 *  A user table (synced OR local) under one of these would shadow a synthetic source — and since
 *  `registerTable` is idempotent-on-name and base tables register before synthetics, the later
 *  synthetic registration would silently no-op and the agg join would read the user table as the
 *  count (silent corruption, `201-LOCAL-ONLY-TABLES-DESIGN.md` N1). The ban makes that
 *  structurally impossible — checked once, statically, at {@link createSchema}. */
export declare const RESERVED_TABLE_PREFIXES: readonly [
    "__agg_",
    "_rindle_"
];

isReservedTableName

/** Whether `name` collides with an engine-reserved synthetic prefix ({@link RESERVED_TABLE_PREFIXES}). */
export declare function isReservedTableName(name: string): boolean;

createSchema

export declare function createSchema<const T extends readonly AnyTable[]>(opts: {
    tables: T;
}): Schema<SchemaOf<T>, PkMapOf<T>>;

extendSchema

/** Extend a generated/synced schema with client-authoritative local-only tables.
 *
 *  This is the ergonomic path for SQL-first apps: keep `schema.gen.ts` fully generated, define
 *  private UI tables in a tiny hand-written file, then hand the combined schema to the browser
 *  client. The added tables MUST be `table(name, { local: true })`: `extendSchema` deliberately
 *  refuses to append ordinary synced tables, because those need to come from daemon introspection
 *  (`rindle schema gen`) so the server and client cannot drift. */
export declare function extendSchema<S extends ColsMap, P extends Record<string, string>, const T extends readonly AnyTable[]>(base: Schema<S, P>, opts: {
    tables: T;
}): Schema<S & SchemaOf<T>, P & PkMapOf<T>>;

ColRefinements

/** Per-column narrowings for {@link refineTable}: each entry must keep the column's kind and narrow
 *  its TS type (`Col<T2>` with `T2 extends T`) — `json<Meta>()` on a json column, a literal-union
 *  `string<"a" | "b">()` on a string column. Kind changes are rejected (cross-kind at compile time,
 *  same-phantom kind flips like `string()` on a json column at runtime). */
export type ColRefinements<C extends AnyCols> = {
    readonly [K in keyof C]?: Col<ColT<C[K]>>;
};

RefinedCols

/** `C` with the columns named in `R` re-typed to their refined `Col`s. */
export type RefinedCols<C extends AnyCols, R extends ColRefinements<C>> = {
    [K in keyof C]: K extends keyof R ? (R[K] extends Col<unknown> ? R[K] : C[K]) : C[K];
};

refineTable

/** Narrow a generated table's column TYPES without touching its runtime shape.
 *
 *  Returns the SAME def, re-typed (identity, after validating that every refined column exists and
 *  keeps its kind) — so conditions built from it, `rel(...)`s anchored on it, and `Row<typeof t>`
 *  all see the narrowed types. Pass the result to {@link refineSchema} so query roots narrow too. */
export declare function refineTable<N extends string, C extends AnyCols, R extends ColRefinements<C>>(base: TableDef<N, C>, cols: R): TableDef<N, RefinedCols<C, R>>;

RefinableTable

/** A table def acceptable to {@link refineSchema} over `Schema<S>`: its NAME must be one of `S`'s
 *  tables (a def for an unknown table is a compile error at the call site). */
export type RefinableTable<S extends ColsMap> = {
    readonly [SCHEMA]: TableMeta<Extract<keyof S, string>, AnyCols>;
};

RefinedColsMap

/** `S` with each table named in `T` re-typed to that def's (refined) columns. (The inner
 *  `extends AnyCols` guard is how the checker proves the remapped `SchemaOf<T>[K]` is a column
 *  map while `T` is still generic; it always holds for a concrete `T`.) */
export type RefinedColsMap<S extends ColsMap, T extends readonly AnyTable[]> = {
    [K in keyof S]: K extends keyof SchemaOf<T> ? (SchemaOf<T>[K] extends AnyCols ? SchemaOf<T>[K] : S[K]) : S[K];
};

refineSchema

/** Swap {@link refineTable}-narrowed table defs into a generated schema, re-typing those tables for
 *  everything downstream of the schema (`newQueryBuilder`/`queries` roots, store row types).
 *
 *  Runtime-validated identity: each def must name a table already in the schema and match its
 *  runtime shape exactly (same columns, kinds, primary key, and locality) — refinement narrows TS
 *  types, never what's on the wire. Composes with {@link extendSchema} in either order. */
export declare function refineSchema<S extends ColsMap, P extends Record<string, string>, const T extends readonly RefinableTable<S>[]>(base: Schema<S, P>, opts: {
    tables: T;
}): Schema<RefinedColsMap<S, T>, P>;

isLocalTable

/** Whether `table` is a {@link TableMeta.local local-only} table in `schema` (an unknown table
 *  reads as non-local). The single locality predicate the backends key off. BOTH variants —
 *  `true` and `"session"` — are local here; the persisted/ephemeral split matters only to the
 *  persistence plane ({@link persistedLocalTableNames}). */
export declare function isLocalTable<S extends ColsMap>(schema: Schema<S>, table: string): boolean;

localTableNames

/** The set of local-only table names in `schema` (`201-LOCAL-ONLY-TABLES-DESIGN.md` §4) — BOTH
 *  variants (`true` and `"session"`); every locality rule except persistence keys off this set. */
export declare function localTableNames<S extends ColsMap>(schema: Schema<S>): Set<string>;

persistedLocalTableNames

/** The subset of {@link localTableNames} eligible for the persistence plane — `local: true` only.
 *  A `local: "session"` table stays outside it: never persisted, never replicated across tabs
 *  (`207-LOCAL-TABLE-PERSISTENCE-DESIGN.md` §5.4). */
export declare function persistedLocalTableNames<S extends ColsMap>(schema: Schema<S>): Set<string>;

localSchemaHash

/** A stable fingerprint of the schema's PERSISTED local tables only — the persistence gate's
 *  `schemaHash` (`207-LOCAL-TABLE-PERSISTENCE-DESIGN.md` §3.3 / P7). Per `local: true` table:
 *  `(name, ordered column names + types + optionality, pk columns)`. Column order is kept (rows are
 *  positional); tables are sorted by name so registration order can't skew it; synced-table AND
 *  `local: "session"` changes never move it (reshaping an ephemeral table must not wipe durable
 *  data). The value is the canonical descriptor itself, not a digest — local-table sets are tiny,
 *  and exactness (no collision can ever skip a P7 clear) beats compactness here. */
export declare function localSchemaHash<S extends ColsMap>(schema: Schema<S>): string;

RELATIONSHIP_BRAND

/** Brand on a {@link Relationship} value (a `unique symbol`, distinct from a {@link TableDef}). */
declare const RELATIONSHIP_BRAND: unique symbol;

Relationship

/**
 * A reusable, typed JOIN between two tables — the correlation declared once (design §4). Built with
 * {@link rel}; parameterized by the parent columns `PC` (so a `sub` checks the relationship belongs to
 * the query's table) and the child columns `CC` (which flow into the nested result type). Pass it to
 * `sub`/`countAs`/`exists` in place of an explicit `child` + `{ parent, child }` correlation.
 */
export interface Relationship<PC extends AnyCols, CC extends AnyCols> {
    /** The child table the relationship points at. */
    readonly child: TableLike<CC>;
    /** Correlation keys: `parent[i]` (a parent column) joins to `child[i]` (a child column). */
    readonly correlation: {
        readonly parent: readonly string[];
        readonly child: readonly string[];
    };
    /** Phantom binding the parent columns so `Query<C>.sub(alias, rel)` rejects a rel for another table. */
    readonly __parent?: PC;
    readonly [RELATIONSHIP_BRAND]: true;
}

AnyRelationship

/** Any relationship, for positions that only read its correlation / child table. */
export type AnyRelationship = Relationship<AnyCols, AnyCols>;

rel

/**
 * Declare a relationship once: `rel(issue, user, { ownerId: "id" })` means `issue.ownerId → user.id`.
 * `mapping` is `{ [parentColumn]: childColumn }` (a composite join is multiple entries). The `parent`
 * table is used only to type-check the keys; pass the result to `sub`/`countAs`/`exists`.
 */
export declare function rel<PC extends AnyCols, CC extends AnyCols>(_parent: TableLike<PC>, child: TableLike<CC>, mapping: Partial<Record<keyof PC & string, keyof CC & string>>): Relationship<PC, CC>;

defineRelationships

/** A typed registry of named {@link Relationship}s — `defineRelationships({ issueOwner: rel(...) })`.
 *  A thin identity helper that names the bag and constrains its values; the keys are yours to choose. */
export declare function defineRelationships<R extends Record<string, AnyRelationship>>(rels: R): R;

isRelationship

/** Runtime guard: is `v` a {@link Relationship} value (not a table or a plain object)? */
export declare function isRelationship(v: unknown): v is AnyRelationship;

tableSpec

/** The `SchemaSpec` (`columns` + `primaryKey` indices) the wasm `Db.registerTable` wants. */
export declare function tableSpec(meta: TableMeta): {
    columns: string[];
    primaryKey: number[];
};

InsertPlan

/** A table's insert-completeness plan, derived once from its `Col` markers and shared by BOTH write
 *  funnels — the client `trackingTx` and the server `renderOp` — so their required-sets can't drift
 *  (design 206 §6.1/§6.2). A `NOT NULL` column is `required`; a nullable column (`.nullable()` set
 *  `Col.optional`) may be omitted and is filled with `null` (see {@link insertCell}). PK columns are
 *  never nullable (introspection forces them non-null), so they are always required. */
export interface InsertPlan {
    /** Every column, in wire order. */
    readonly columns: string[];
    /** Columns that MUST be present on a full insert — the non-nullable ones. */
    readonly required: string[];
    /** The nullable (omittable-to-null) columns, for a fast membership test in the fill. */
    readonly nullable: ReadonlySet<string>;
}

insertPlan

/** Derive a table's {@link InsertPlan} from its column markers. */
export declare function insertPlan(meta: TableMeta): InsertPlan;

insertCell

/** The cell a full insert writes for column `c`: the given value, or `null` when a nullable column
 *  is omitted (design 206 §6.2). The caller's completeness check ({@link InsertPlan.required})
 *  guarantees a non-nullable column is present, so its omission never reaches here. */
export declare function insertCell(row: Record<string, WireValue>, c: string): WireValue;

toCell

/** Encode a keyed-row cell to its wire {@link WireValue} for a column KIND — the one place both write
 *  funnels stringify a `json<T>` object (the typed mutator surface / design 206 §7). An
 *  already-stringified json value (a `string`) or any non-json cell passes through unchanged, so a
 *  mutator may pass EITHER a parsed object OR a JSON string. Mirrors `store.positionalize`. */
export declare function toCell(v: WireValue | object, type: ColType): WireValue;

normalizedTableSchemas

/** The client's per-table flat schema (name + column order + PK indices), the shape a
 *  normalized `hello` advertises (NORMALIZED-CHANGES-DESIGN.md §3). Used to validate a
 *  server hello against the CLIENT's own typed schema so a column-order / PK skew is caught
 *  instead of silently transposing positional cells (CRIT#4). Sorted by name for stable
 *  ordering.
 *
 *  Local-only tables are **omitted** (`201-LOCAL-ONLY-TABLES-DESIGN.md` E1): the client never
 *  claims them to the server, the server never expects/ships them, and they stay out of the schema
 *  fingerprint (`normalizedFp`) so a local table can't skew it vs. the server's. */
export declare function normalizedTableSchemas<S extends ColsMap>(schema: Schema<S>): {
    name: string;
    columns: string[];
    primaryKey: number[];
}[];