Rindle docs and package mapSkip to main content

rindle/
lib.rs

1//! # rindle — an incremental view maintenance (IVM) engine
2//!
3//! Build a query, hydrate its result, and push source changes to update that result
4//! incrementally. This crate provides the raw graph, query builder, memory source,
5//! and optional WebAssembly bindings. It is based on Zero's ZQL dataflow engine.
6//!
7//! Applications that need SQLite persistence and automatic SQL change capture should
8//! start with `rindle-replica`. Custom SQLite sources live in the separate
9//! `rindle-sqlite` crate. This core crate does not link SQLite or require a C toolchain
10//! with its default features.
11//!
12//! ## Public entry points
13//!
14//! - **Build a query** — [`Query`] / [`table`] (fluent) or an [`Ast`] (Zero's wire
15//!   JSON, via `serde`), then [`build_pipeline`] to lower it into a wired arena
16//!   [`Graph`](crate::graph::Graph).
17//! - **Run it** — hydrate the [`Graph`](crate::graph::Graph) over a [`MemorySource`],
18//!   push [`SourceChange`](crate::change::SourceChange)s, and read the maintained
19//!   [`View`] / [`ViewData`]. A `rindle-sqlite::TableSource` can supply a SQLite leaf.
20//! - **Import core types** — use `rindle::graph::{Graph, NodeId}`,
21//!   `rindle::change::SourceChange`, and `rindle::value::{OwnedRow, OwnedValue}`.
22//!   The fluent [`table`] builder is re-exported at the crate root.
23//! - **Errors** — fallible paths return [`RindleError`] (see *Fault model* below).
24//!
25//! ## Feature flags
26//!
27//! `memory` is enabled by default and required by this crate. There is no `sqlite`
28//! feature; use the `rindle-sqlite` crate for that backend. `serde` enables AST
29//! serialization, and `wasm` adds the JavaScript bindings plus `memory` and `serde`.
30//! `observe` enables tracing, `metrics` enables process counters, and `testkit`
31//! exposes the test oracle. `fast-alloc` opts into mimalloc and its native toolchain.
32//!
33//! ## Design
34//!
35//! The arena + `NodeId` operator graph, RAII (`Drop`) cursor cleanup, the COW
36//! `Arc<Node>` B+tree, and the reentrant-fetch-during-push thesis that motivates them
37//! are written up in `docs/DESIGN.md`.
38//!
39//! # Fault model (productionization WS02)
40//!
41//! The engine is built to be **survivable in a server**: no data-reachable input or
42//! transient backend error should abort the process or silently corrupt view state.
43//!
44//! - **Use the `try_*` entry points in production** — [`Graph::try_source_push`](crate::graph::Graph::try_source_push),
45//!   [`Graph::try_hydrate`](crate::graph::Graph::try_hydrate), [`Graph::try_add_source`](crate::graph::Graph::try_add_source) — which return [`RindleError`].
46//!   Their infallible peers ([`Graph::source_push`](crate::graph::Graph::source_push) / [`Graph::hydrate`](crate::graph::Graph::hydrate) /
47//!   [`Graph::add_source`](crate::graph::Graph::add_source)) `.expect` the result and are for tests/prototyping.
48//! - **Strict change validation** ([`Graph::set_validate_changes`](crate::graph::Graph::set_validate_changes), WS02.2): off by
49//!   default (hot-path parity). When on, a malformed change stream (ADD of an
50//!   existing row, REMOVE/EDIT of an absent row) returns
51//!   [`RindleError::ConsistencyViolation`] — in release, not a stripped `debug_assert!`.
52//! - **Ingest validation** (WS02.4): [`Graph::try_add_source`](crate::graph::Graph::try_add_source) rejects a wrong-width
53//!   row with [`RindleError::SchemaViolation`] before it reaches an unchecked index.
54//! - **Predicates never crash** on a column/literal type mismatch (WS02.4).
55//! - **SQLite operator-storage errors** (the spill backend) park a [`RindleError`] on
56//!   the graph's runtime-error sink and surface via `take_runtime_error` instead of
57//!   `.expect`-aborting; spilled blobs carry a format-version byte and a mismatch is
58//!   a typed error (rebuild-from-source), not a panic (WS02.3 / WS09.3).
59//! - **Panic taxonomy** (the `invariant` module, WS02.1): every remaining panic is
60//!   a build-time / internal invariant; data-reachable failures are [`RindleError`].
61//! - **Server fault isolation** (WS02.5): build the server with
62//!   `--profile release-server` (`panic = "unwind"`) and drive mutations through
63//!   [`Graph::source_push_isolated`](crate::graph::Graph::source_push_isolated) to contain a residual panic to one mutation
64//!   (then discard + rebuild the view — never reuse a torn one). The wasm client
65//!   keeps `panic = "abort"` plus a console panic hook.
66
67//! # Client (wasm/JS)
68//!
69//! With the `wasm` feature, `wasm::RindleView` drives the engine from JavaScript over a
70//! 5-call lifecycle (the `wasm` module has the full rustdoc):
71//!
72//! 1. `RindleView.build(astJson, schemas, data)` — lower an AST (Zero's wire JSON) + a
73//!    `{ table: SchemaSpec }` map (+ optional `{ table: row[][] }` initial data),
74//!    hydrate, return a handle. `BuildError`/`RindleError` throw as a JS `Error` with a
75//!    `.kind` tag — never a wasm abort.
76//! 2. `view.data()` — the materialized tree as a JS value: `{ <col>: v, …, <relName>:
77//!    [child…] | child | null }`, in-view relationships only.
78//! 3. `view.push(table, { type: 'add'|'remove'|'edit', row, old? })` — one source
79//!    change (does **not** flush).
80//! 4. `view.flush()` — notify subscribers if the view changed. This is a notification
81//!    boundary, not a durable SQL transaction.
82//! 5. `view.subscribe((data, resultType) => …)` — fired once immediately, then when
83//!    a changed view flushes or its result type changes. Release the generated JS
84//!    handle with `.free()` when finished.
85//!
86//! JavaScript numbers enter the number domain; the boundary rejects `bigint` and
87//! outgoing exact integers outside the JS safe-integer range. Strings stay strings;
88//! objects and arrays become JSON cells. Build the artifact with
89//! `pnpm run build:wasm` from the repository root. **Concurrency:** one `Graph`/`RindleView` per thread
90//! (it is `!Send`); scale with N independent graphs and message passing, never a
91//! shared `Arc<Mutex<Graph>>`.
92
93// Broken intra-doc links fail the build (WS08.6): the published rustdoc must not ship
94// dangling references. CI also runs `RUSTDOCFLAGS="-D warnings" cargo doc` on both
95// feature axes to catch the rest (e.g. redundant link targets).
96#![deny(rustdoc::broken_intra_doc_links)]
97
98#[cfg(not(feature = "memory"))]
99compile_error!("enable the `memory` feature (the only built-in source backend; the SQLite backend lives in the `rindle-sqlite` crate)");
100
101pub(crate) mod ast;
102pub mod btree;
103pub(crate) mod builder;
104pub mod change;
105/// Owned, fully-materialized change events off the dataflow pipeline
106/// ([`CaughtChange`](changes::CaughtChange) / [`CaughtNode`](changes::CaughtNode) +
107/// [`expand_change`](changes::expand_change)). Graduated out of the test oracle so the
108/// production change-stream sink ([`Graph::add_change_sink`](graph::Graph::add_change_sink))
109/// and out-of-crate consumers can use them. See `src/changes.rs`.
110pub(crate) mod changes;
111// Moved to `rindle-value`; re-exported so `crate::error::…` and the public
112// `rindle::RindleError` path below are both unchanged.
113pub(crate) use rindle_value::error;
114/// Parameterized query families (design 310 §4): [`BindingSet`](family::BindingSet) —
115/// the binding-set handle a family root connection's membership predicate reads — and
116/// [`FamilyPipeline`](family::FamilyPipeline), what [`build_family_pipeline`] returns
117/// and what `Graph::bind_family_partition` / `unbind_family_partition` /
118/// `hydrate_family` drive. See `src/family.rs`.
119pub mod family;
120/// Flat change events for cross-process / cross-language view reconstruction
121/// ([`FlatChange`](flat::FlatChange) + [`flatten`](flat::flatten)): the nested
122/// [`CaughtChange`](changes::CaughtChange) tree linearized into root→leaf paths so a
123/// remote receiver can rebuild the exact `ArrayView`. See `src/flat.rs` and
124/// `FLAT-CHANGES-DESIGN.md` (the wire format + receiver `apply_change` contract).
125pub(crate) mod flat;
126/// The flat-change **subscription protocol** ([`Hello`](flat_protocol::Hello) /
127/// [`Batch`](flat_protocol::Batch) / [`Publisher`](flat_protocol::Publisher) /
128/// [`Subscriber`](flat_protocol::Subscriber)): the batch envelope, epoch/seq framing,
129/// and the receiver-side safety rules (in-order apply, gap → re-hydrate, at-most-once,
130/// epoch/schema/comparator validation). See `src/flat_protocol.rs` and
131/// `FLAT-CHANGES-DESIGN.md` §5.4/§5.5/§2.3.
132pub(crate) mod flat_protocol;
133/// A reference [`Receiver`](flat_receiver::Receiver): a host-agnostic port of the
134/// View's `apply_change`, fed by [`FlatChange`](flat::FlatChange)s, that reconstructs
135/// the exact `ArrayView` tree (rc multi-path + edit-move, no COW). The executable form
136/// of `FLAT-CHANGES-DESIGN.md` §6 and the conformance oracle. See `src/flat_receiver.rs`.
137pub(crate) mod flat_receiver;
138pub mod graph;
139/// Panic taxonomy + the `invariant!` macro (WS02.1): the one rule separating
140/// build-time/internal invariants (keep as panic) from data-reachable failures
141/// (convert to [`error::RindleError`]). See `src/invariant.rs`.
142pub(crate) mod invariant;
143/// The cross-process **journal frame envelope**
144/// ([`FrameHeader`](journal_frame::FrameHeader) + [`FrameKind`](journal_frame::FrameKind)):
145/// the typed header (kind / committed_at / run identity + totals) in front of an opaque
146/// change payload wherever journal entries live — the hctree master's `hct_journal`
147/// payload column and the S3 archival segments (`rindle-backup`). Pure bytes, no deps.
148/// See `src/journal_frame.rs`, `designs-implemented/211-HCTREE-LEADER-CDC-DESIGN.md` §4.1, and
149/// `designs-implemented/212-JOURNAL-S3-SHIPPING-DESIGN.md` §2.2.
150pub mod journal_frame;
151/// JS-boundary safe-integer walkers (productionization 09.8, design 226 Stage A): the
152/// opt-in `strict_i64` check that refuses an out-of-`Number.MAX_SAFE_INTEGER` `Int`
153/// crossing to JS with a typed error instead of silently rounding it.
154pub mod js_safe;
155pub(crate) mod memory_source;
156/// Build-gated process metrics (WS03, the `metrics` feature): `metric_inc!`/… fold
157/// each seam into a relaxed atomic add on a process-global registry the daemon's
158/// Prometheus endpoint reads, and to argument-consuming no-ops (no global linked)
159/// otherwise. The scrape-path sibling of `observe`; see `src/metrics.rs`.
160pub mod metrics;
161/// Feature-gated observability shim (WS03.1): `obs_span!`/`obs_event!`/… expand to
162/// `tracing` when `--features observe`, and to argument-consuming no-ops (no
163/// `tracing` dependency) otherwise — keeping the default/wasm build lean.
164pub(crate) mod observe;
165pub mod op;
166/// The optimistic-writes fork/rebase loop ([`OptimisticTables`](optimistic::OptimisticTables)
167/// — per-table `sync` forks + the §1.3 rewind over the one live pipeline). The cycle's
168/// buffered event stream is forwarded raw to the consumer (the §3 `Coalescer` was removed —
169/// see the module docs). See `src/optimistic.rs` and `OPTIMISTIC-WRITES-DESIGN.md`.
170// Only the wasm `Db` drives this. While the module was `pub` that was invisible;
171// closing it makes the non-wasm build see an unused module, so scope the allow to
172// exactly the builds where it is genuinely unreachable.
173#[cfg_attr(not(feature = "wasm"), allow(dead_code))]
174pub(crate) mod optimistic;
175pub(crate) mod predicate;
176/// The guarded push fan-out reverse index (`designs/205-GUARDED-PUSH-FANOUT-DESIGN.md`):
177/// prunes a source write's per-connection fan-out to the connections whose
178/// equality-shaped `where` guard could match. A conservative superset index —
179/// never under-approximates — so `filter_push` stays the exact gate.
180pub mod push_index;
181pub(crate) mod query;
182pub(crate) mod scalar;
183pub mod source_common;
184pub mod storage;
185/// The spec-`11` operator test harness (`Catch`/`Snitch`/runners/fingerprint).
186/// Exported only for crate tests or the `testkit` cargo feature so production
187/// library builds do not expose harness helpers. Use via `rindle::testkit::*`.
188#[cfg(any(test, feature = "testkit"))]
189pub mod testkit;
190/// The canonical value-equivalence key ([`CanonVal`](canon::CanonVal) /
191/// [`CanonKey`](canon::CanonKey)) — the join family's grouping key, the limiters'
192/// partition identity, and a parameterized query family's binding (design 310, D1).
193/// Lives in `rindle-value` (below the engine, so `rindle-wire` can name it) and is
194/// re-exported here as the `rindle::canon` namespace.
195pub use rindle_value::canon;
196/// The value vocabulary — moved to the dependency-free `rindle-value` crate and
197/// re-exported here, so `rindle::value::…` is unchanged for every consumer. Crates that
198/// need only cells (the write plane, the CDC apply plane) depend on `rindle-value`
199/// directly and therefore cannot reach the engine at all.
200pub use rindle_value::value;
201/// The production materialization sink (`09`): the `Arc`-shared, reference-stable
202/// `ArrayView` + immutable tree differ `apply_change`. See `src/view.rs`.
203pub(crate) mod view;
204/// The wasm/JS client boundary (WS01): `RindleView` + value/error/schema/view-tree
205/// marshalling. Only compiled for the `wasm` feature. See `src/wasm/`.
206#[cfg(feature = "wasm")]
207pub mod wasm;
208/// The view schema on the wire + content fingerprint
209/// ([`WireSchema`](wire_schema::WireSchema) / [`schema_fp`](wire_schema::schema_fp) +
210/// [`COMPARATOR_VERSION`](wire_schema::COMPARATOR_VERSION)): the hierarchical view
211/// [`Schema`](value::Schema) reduced to what a receiver needs (names + resolved sort +
212/// nested relationships), shipped once. See `src/wire_schema.rs` and
213/// `FLAT-CHANGES-DESIGN.md` §5.2/§5.5.
214pub(crate) mod wire_schema;
215
216#[cfg(kani)]
217mod kani_proofs;
218
219// Crate-root re-export of the observability shim macros (WS03.1 step) so the
220// instrumented seams (WS03.2–03.5) can call `obs_span!`/… without a module path.
221// Kept `pub(crate)` — these are internal instrumentation, not public API (WS01.7
222// audits the public surface). Unreferenced until the seams land.
223#[allow(unused_imports)]
224pub(crate) use observe::{obs_counter_inc, obs_event, obs_gauge_set, obs_span};
225
226// Crate-root re-export of the metrics instrumentation macros so the seams can call
227// `metric_inc!`/… unqualified. `pub(crate)` — internal instrumentation, not public API.
228#[allow(unused_imports)]
229pub(crate) use metrics::{
230    metric_add, metric_build_err, metric_change_kind, metric_changes_inc, metric_inc, metric_timer,
231};
232
233// ===========================================================================
234// Public API — ONE CANONICAL PATH PER ITEM.
235//
236// The query you build (`Ast`/`Query`/`table`), the engine you drive
237// (`graph::Graph` + `try_*`), the values you push (`value::OwnedValue` /
238// `change::SourceChange` / `value::Schema`), the view you read
239// (`View`/`ViewData`/`Entry`), and the error you handle (`RindleError`).
240//
241// The rule: every public item is reachable at EXACTLY ONE path. A module is
242// therefore either
243//   * `pub(crate) mod` — implementation; selected items re-exported here, so the
244//     canonical path is `rindle::Item`; or
245//   * `pub mod` — a namespace (`value`, `change`, `graph`, `source_common`,
246//     `storage`, `btree`, `journal_frame`, `op`, `metrics`, `push_index`,
247//     `js_safe`, `testkit`, `wasm`) — and then NOTHING from it is re-exported
248//     here, so the canonical path is `rindle::module::Item`.
249// Re-exporting from a `pub mod` puts an item at two addresses and is the one
250// thing not to do below.
251//
252// NOTE: `#[doc(hidden)]` does NOT reduce the surface — it only hides an item from
253// the rendered docs. `cargo public-api` still reports every hidden item, and a
254// `pub` item in a `pub` module stays reachable regardless of the attribute. Only
255// module/item visibility shrinks the surface. `scripts/public-api.sh --check`
256// diffs the result against the committed snapshot in `rust/public-api/`.
257// ===========================================================================
258
259pub use ast::{
260    canon_of_lit, Aggregate, Ast, Bound, Condition, CorrelatedSubquery,
261    CorrelatedSubqueryCondition, Correlation, Dir, ExistsOp, Lit, Op, OrderPart, SimpleCondition,
262    System, ValuePosition,
263};
264pub use builder::{build_family_pipeline, build_pipeline, view_schema, BuildError};
265pub use changes::{CaughtChange, CaughtNode};
266pub use error::RindleError;
267pub use flat::{flatten, flatten_all, FlatChange, FlatOp, PathSeg, WireNode, WireRow};
268pub use flat_protocol::{
269    Applied, Batch, Hello, ProtocolError, Publisher, SnapStatus, SnapshotChunk, Subscriber,
270};
271pub use flat_receiver::{Receiver, RecvNode};
272pub use memory_source::MemorySource;
273pub use predicate::LikeMatcher;
274pub use query::{table, Cond, ExistsOpts, Parent, ParentRow, Query};
275pub use scalar::{has_scalar_subquery, resolve_scalars, ScalarCatalog, ScalarSource};
276pub use view::{Entry, EntryList, Listener, ResultType, View, ViewData};
277pub use wire_schema::{schema_fp, to_schema, to_wire, WireRel, WireSchema, COMPARATOR_VERSION};
278
279// --- internal plumbing: reachable because a public signature or an out-of-crate
280//     test needs it. `#[doc(hidden)]` keeps it out of the RENDERED DOCS only — it is
281//     still public surface and still appears in the `cargo public-api` snapshot. ---
282#[doc(hidden)]
283pub use builder::{
284    complete_ordering, create_predicate, normalize_pipeline_ast, query_local_slot_names,
285    schema_primary_key_names, transform_filters,
286};
287#[doc(hidden)]
288pub use memory_source::{build_scan_start, constrained_index_sort};
289#[doc(hidden)]
290pub use predicate::{CmpOp, CompiledPredicate, ValueSet};
291// Reachable ONLY from this crate's own `tests/` (which compile as external consumers,
292// so `pub(crate)` is not enough). They are not API; the right fix is to move those two
293// tests inward as unit tests, which would let both of these become `pub(crate)`.
294#[doc(hidden)]
295pub use ast::canonicalize_wire_number_lits;
296#[doc(hidden)]
297pub use view::Col0Node;
298#[doc(hidden)]
299pub use view::{apply_change, EntryId, EntryListInner, Mutate, TxnDirty, TxnGen, REL_ROOT};