@rindle/wasm 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,43 +4,32 @@ The WASM backend for [`@rindle/client`](../client): the Rust IVM engine (a port
4
4
  dataflow engine) compiled to WebAssembly, running queries **in-process**. It re-exports
5
5
  `@rindle/client`, so a local app can import everything from here.
6
6
 
7
- ## Quick start
8
-
9
7
  ```ts
10
8
  import { table, string, number, boolean, createSchema, createWasmStore } from "@rindle/wasm";
11
9
 
12
10
  const issue = table("issue")
13
11
  .columns({ id: number(), title: string(), closed: boolean() })
14
12
  .primaryKey("id");
15
- const comment = table("comment")
16
- .columns({ id: number(), issueID: number(), body: string() })
17
- .primaryKey("id");
18
- const schema = createSchema({ tables: [issue, comment] });
13
+ const schema = createSchema({ tables: [issue] });
19
14
 
20
15
  const store = await createWasmStore(schema); // inits the wasm + wires the local backend
21
16
 
22
- const view = store.query.issue
23
- .where.closed(false)
24
- .sub("comments", comment, { parent: ["id"], child: ["issueID"] }, (c) => c.orderBy("id", "asc"))
25
- .materialize();
26
-
27
- view.subscribe((data) => render(data)); // typed, nested, reference-stable tree
28
- await store.write((tx) => tx.add("comment", { id: 1, issueID: 1, body: "hi" }));
29
-
30
- // `.one()` materializes a single row (`data` is the row or `null`, not an array):
31
- const top = store.query.issue.orderBy("priority", "desc").one().materialize();
32
- top.subscribe((row) => render(row)); // row: Issue | null
33
-
34
- // `.start(cursor, { exclusive })` pages from a cursor over the sort columns:
35
- const page2 = store.query.issue.orderBy("id", "asc").start({ id: lastId }, { exclusive: true }).limit(20).materialize();
17
+ const view = store.query.issue.where.closed(false).materialize();
18
+ view.subscribe((data) => render(data)); // typed, reference-stable, always == a fresh query
19
+ await store.write((tx) => tx.add("issue", { id: 1, title: "hi", closed: false }));
36
20
  ```
37
21
 
38
- ## How it works
22
+ `createWasmStore(schema)` calls `initWasm()` for you. If you build a `WasmBackend` directly,
23
+ `await initWasm()` first — and pass `initWasm(moduleOrPath)` to supply the wasm yourself (a
24
+ `WebAssembly.Module`, URL, or bytes).
25
+
26
+ ## Docs
39
27
 
40
- `store.query.<table>…materialize()` registers the query with the in-process engine and folds
41
- its **flat change** stream into a live `ArrayView`. `store.write(...)` applies row mutations and
42
- the affected views update. The engine derives true incremental changes — `view.data` always
43
- equals a freshly-run query.
28
+ Full docs the standalone browser engine, query surface, and the change model:
29
+ **[rindle.sh/docs/wasm-client](https://rindle.sh/docs/wasm-client)** · markdown mirror:
30
+ [`wasm-client.md`](https://rindle.sh/docs/wasm-client.md) · query shapes:
31
+ [supported-queries-ts](https://rindle.sh/docs/supported-queries-ts) · for agents:
32
+ [llms.txt](https://rindle.sh/llms.txt)
44
33
 
45
34
  ## Building & publishing
46
35
 
@@ -67,16 +56,3 @@ the TS source directly (via the `@rindle/source` export condition), so `pnpm tes
67
56
  `pnpm test:browser` builds `dist/`, then loads the package in **headless Chrome** (taking the real
68
57
  browser `fetch` + `WebAssembly.instantiateStreaming` init path) and runs a query. Needs system
69
58
  Chrome/Chromium (`CHROME_BIN` to override); skipped with a message when none is installed.
70
-
71
- ## Initialization
72
-
73
- `createWasmStore(schema)` calls `initWasm()` for you. If you build a `WasmBackend` directly,
74
- `await initWasm()` first:
75
-
76
- ```ts
77
- import { initWasm, WasmBackend, Store } from "@rindle/wasm";
78
- await initWasm(); // browser: fetches the wasm; Node: reads it
79
- const store = new Store(schema, new WasmBackend(schema));
80
- ```
81
-
82
- Pass `initWasm(moduleOrPath)` to supply the wasm yourself (a `WebAssembly.Module`, URL, or bytes).
package/dist/index.d.ts CHANGED
@@ -1,6 +1,29 @@
1
1
  import { Store } from "@rindle/client";
2
- import type { Ast, Backend, ChangeEvent, ColsMap, Mutation, QueryId, Schema } from "@rindle/client";
2
+ import type { Ast, Backend, ChangeEvent, ColsMap, Condition, Mutation, QueryId, Schema } from "@rindle/client";
3
3
  export * from "@rindle/client";
4
+ /** A serializable writable-scope descriptor: how the merge decides, per row, whether an attached
5
+ * room may write it (the room copy then wins, RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §5.2).
6
+ * `"all"` = the room owns every row it holds; `"none"` = a context table the daemon stays
7
+ * authoritative for; `"colEq"` = writable iff `row[col] === value` (harness stand-in). The REAL
8
+ * shape (Slice G) is `"predicate"`: `where` is the SAME wire {@link Condition} JSON a query AST
9
+ * carries, restricted to what evaluates against a single row — `simple` column-vs-literal leaves
10
+ * under `and`/`or`; a `correlatedSubquery` (or any other non-row-local node) is rejected loudly at
11
+ * `addRoomSource` time. It compiles Rust-side through the engine's OWN filter lowering
12
+ * (`create_predicate`), so writable evaluation can never disagree with query-filter semantics.
13
+ * NOTE: room join-key columns (Slice H) deliberately do NOT cross this boundary — they stay
14
+ * TS-side. */
15
+ export type WritableDescriptor = {
16
+ kind: "all";
17
+ } | {
18
+ kind: "none";
19
+ } | {
20
+ kind: "colEq";
21
+ col: number;
22
+ value: unknown;
23
+ } | {
24
+ kind: "predicate";
25
+ where: Condition;
26
+ };
4
27
  /** A raw staged write transaction over the wasm engine — the surface an optimistic client
5
28
  * mutator runs against (`get` reads the live state under this txn's staged overlay, so a
6
29
  * read-dependent mutator sees its own writes; OPTIMISTIC-WRITES-DESIGN.md §4.1). */
@@ -19,6 +42,17 @@ export interface WasmWriteTxn {
19
42
  queryId: number;
20
43
  events: unknown[];
21
44
  }>;
45
+ /** Like {@link commit}, additionally returning the per-change touched-source key list in staged
46
+ * order (`sources`, e.g. `"daemon"` / `"room:doc:1"`) — the provenance each staged write routed
47
+ * through, captured pre-apply (Slice E-iii-b, design Q1). `batches` is exactly {@link commit}'s
48
+ * return; the source list is additive. */
49
+ commitTracked(): {
50
+ batches: Array<{
51
+ queryId: number;
52
+ events: unknown[];
53
+ }>;
54
+ sources: string[];
55
+ };
22
56
  rollback(): void;
23
57
  }
24
58
  /** One base-table row op of a coherent server delta (the §1.3 `D`), bare cells. */
@@ -61,8 +95,10 @@ export declare class WasmBackend<S extends ColsMap> implements Backend {
61
95
  }): void;
62
96
  /** Direct-commit a batch of LOCAL-only writes (`201-LOCAL-ONLY-TABLES-DESIGN.md` §6): push them
63
97
  * straight through the engine on the ordinary delivery path, OUTSIDE any optimistic cycle (a
64
- * local table is untracked, so it never rebases). Rejects a synced/tracked table (M2). */
65
- writeLocal(mutations: Mutation[]): void;
98
+ * local table is untracked, so it never rebases). Rejects a synced/tracked table (M2).
99
+ * `onCommitted` fires between the engine commit and subscriber delivery ({@link writeWith})
100
+ * the post-commit anchor the persistence tap needs (207 §5.1). */
101
+ writeLocal(mutations: Mutation[], onCommitted?: () => void): void;
66
102
  /** Remove a synthetic aggregate table registered by {@link registerTable}, once the last
67
103
  * query reading it has been unregistered (`AGGREGATE-SYNC-DESIGN.md` §4): frees the engine
68
104
  * source + its optimistic baseline so aggregate state is reclaimed, not permanent. Throws
@@ -91,18 +127,85 @@ export declare class WasmBackend<S extends ColsMap> implements Backend {
91
127
  /** Run `f` against a raw staged write txn (with the §4.1 `get` read path), commit, and
92
128
  * dispatch the resulting batches on the ordinary event stream. Inside an open server
93
129
  * batch the engine buffers the events into the cycle instead (commit returns `[]`),
94
- * so re-invocations dispatch nothing here — delivery is `serverBatchEnd`'s. */
95
- writeWith(f: (tx: WasmWriteTxn) => void): void;
96
- /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds
97
- * (optimistic layer un-applied, delta folded in, `sync` re-forked) and starts
98
- * buffering. Re-invoke the still-pending mutators via {@link writeWith}, then call
99
- * {@link serverBatchEnd}. On error the optimistic state is poisoned discard the
100
- * backend and re-hydrate. */
101
- serverBatchBegin(deltas: ServerDeltaOp[]): void;
130
+ * so re-invocations dispatch nothing here — delivery is `serverBatchEnd`'s.
131
+ *
132
+ * `onCommitted` runs after `tx.commit()` returns but before `dispatch` delivers to
133
+ * subscribers: a throw from `f` (pre-commit — engine untouched) skips it; a subscriber
134
+ * throw re-raised by `dispatch` happens after it. It is the only point where "the commit
135
+ * is applied" is knowable to a caller that must not confuse the two failure modes.
136
+ *
137
+ * Returns the per-change **touched-source key list** in staged-write order (`"daemon"` /
138
+ * `"room:doc:X"`, Slice E-iii-b design Q1) — additive; every current caller (`mutate`,
139
+ * `writeLocal`, `@rindle/optimistic`) ignores it. The per-source reconcile (Slice E-iii-c) zips
140
+ * it with the mutation's write-set for the per-pk hold-back source. */
141
+ writeWith(f: (tx: WasmWriteTxn) => void, onCommitted?: () => void): string[];
142
+ /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds the
143
+ * per-source rebase (optimistic layer un-applied, delta folded in, the source's baseline
144
+ * re-forked) and starts buffering. `sourceKey` names the authority the delta confirms —
145
+ * `"daemon"` today (single-domain); a `room:doc:X` key once the room feed lands. Re-invoke the
146
+ * still-pending mutators via {@link writeWith}, then call {@link serverBatchEnd}. On error the
147
+ * rebase state is poisoned — discard the backend and re-hydrate. */
148
+ serverBatchBegin(sourceKey: string, deltas: ServerDeltaOp[]): void;
102
149
  /** Close the cycle: the whole buffered stream (rewind + re-invocations) coalesces to
103
150
  * the minimal net per query (§3 — a confirmed-correct prediction delivers nothing)
104
151
  * and dispatches as ONE batch per affected query on the ordinary event stream. */
105
152
  serverBatchEnd(): void;
153
+ /** Attach a room source to `table`, compiling `writable` into the merge's per-row writable scope.
154
+ * Promotes the table Collapsed → Merged on the first room (§5.2); the room seeds empty, so no
155
+ * visible blip. Throws on an untracked (local-only) table or a duplicate room. */
156
+ addRoomSource(table: string, roomKey: string, writable: WritableDescriptor): void;
157
+ /** Detach a room source, emitting the compensating visible deltas on the ordinary event stream. */
158
+ removeRoomSource(table: string, roomKey: string): void;
159
+ /** Park an echoed `row` inertly on `sourceKey`'s slice of `table` (the confirm→echo hold-back
160
+ * window, design §7.3): keeps the row visible so the next per-source rewind of `sourceKey` does
161
+ * not flash-revert a confirmed-elsewhere write before its data echo lands. `sourceKey` is
162
+ * `"daemon"` or a `"room:doc:X"` key. */
163
+ holdBack(table: string, sourceKey: string, row: unknown[]): void;
164
+ /** Park `oldRow`'s pk as ABSENT on `sourceKey`'s slice of `table` — the §7.3 tombstone, the
165
+ * remove-sibling of {@link holdBack} (Slice G-ii): a confirmed cross-slice REMOVE whose delete
166
+ * echo has not landed on `sourceKey` must not resurrect through that source's next rewind. The
167
+ * overlay masks the pk (overlay-first) until the source's re-forked baseline lacks it (the
168
+ * delete echoed), then drops. `oldRow` is the full-width PRE-IMAGE row (same positional
169
+ * bare-cell marshaling as {@link holdBack}); it carries the pk and, later (Slice H), the cells
170
+ * writable-scope predicates evaluate on. */
171
+ holdBackAbsent(table: string, sourceKey: string, oldRow: unknown[]): void;
172
+ /** Drop one parked §7.3 hold-back at `probe`'s pk on `sourceKey`'s slice and reconcile the
173
+ * pk's visible winner — the §301 FENCE-driven drop (`301-ECHO-FENCE-DESIGN.md` §2.2), fired
174
+ * by the optimistic backend's pin registry once `sourceKey`'s stream has provably delivered
175
+ * through the pinned write's commit. Unlike the park-time seams (whose install is a no-op by
176
+ * construction) a drop is exactly the moment a masked authoritative row becomes visible, so
177
+ * the engine's compensating deltas are DELIVERED here: dispatched on the ordinary event
178
+ * stream (or folded into an open server-batch cycle's one delivery). No-op when nothing is
179
+ * parked at the pk — the rewind's state-match fallback may have raced it. */
180
+ dropHeldBack(table: string, sourceKey: string, probe: unknown[]): void;
181
+ /** The parked hold-back at `probe`'s pk on `sourceKey`'s slice, or `undefined` (§301
182
+ * introspection): `absent` names the tombstone variant; `baseline` is the source's CURRENT
183
+ * confirmed-baseline row at the pk (`undefined` when the baseline lacks it) — the pin
184
+ * registry's lazy-prune probe and the §2.5 stuck-pin tripwire input. Non-throwing on an
185
+ * unknown source (a removed room), so pruning after a whole-source removal just works. */
186
+ heldBackState(table: string, sourceKey: string, probe: unknown[]): {
187
+ absent: boolean;
188
+ baseline: unknown[] | undefined;
189
+ } | undefined;
190
+ /** Freeze a physical source — the D4 downgrade-ghost stub (wins-if-present in the merge). */
191
+ freezeSource(table: string, sourceKey: string): void;
192
+ /** The source currently serving `row`'s pk on `table` — `"daemon"`, a room key, or `undefined`
193
+ * when no source holds the pk (Slice H-i routing probe #1, design §3.2 #3). `row` is a
194
+ * full-width positional row (the pk derives from it — same marshaling as {@link holdBack});
195
+ * an unknown table throws loudly. Reads the LIVE visible state, overlay-first (§7.3): an open
196
+ * write txn's staged-but-uncommitted rows are NOT consulted (they have not routed yet), which
197
+ * is exactly the pre-commit view the §3 routing proof decides on — a present read is proven
198
+ * room-covered iff this reports the occupied room. */
199
+ provenanceOf(table: string, row: unknown[]): string | undefined;
200
+ /** Evaluate the REGISTERED writable scope of `table`'s room source `roomKey` against a
201
+ * caller-supplied full-width row (Slice H-i routing probe #2, design §3.1): a prediction-run
202
+ * write is proven room-covered iff this is `true` on the written row. Runs the SAME compiled
203
+ * predicate the merge's winner tiering uses (the {@link WritableDescriptor} registered at
204
+ * {@link addRoomSource}), so routing can never disagree with the engine; `all` ⇒ always true,
205
+ * `none` ⇒ always false, and a frozen source still answers (the predicate is static). Throws
206
+ * loudly on an unknown table, a room key not attached to the table, a wrong-width row, or
207
+ * `"daemon"` (which has no writable scope). */
208
+ writableMatches(table: string, roomKey: string, row: unknown[]): boolean;
106
209
  }
107
210
  /** Convenience: init the wasm + return a ready local {@link Store}. */
108
211
  export declare function createWasmStore<S extends ColsMap>(schema: Schema<S>): Promise<Store<S>>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -66,8 +66,10 @@ export class WasmBackend {
66
66
  }
67
67
  /** Direct-commit a batch of LOCAL-only writes (`201-LOCAL-ONLY-TABLES-DESIGN.md` §6): push them
68
68
  * straight through the engine on the ordinary delivery path, OUTSIDE any optimistic cycle (a
69
- * local table is untracked, so it never rebases). Rejects a synced/tracked table (M2). */
70
- writeLocal(mutations) {
69
+ * local table is untracked, so it never rebases). Rejects a synced/tracked table (M2).
70
+ * `onCommitted` fires between the engine commit and subscriber delivery ({@link writeWith})
71
+ * the post-commit anchor the persistence tap needs (207 §5.1). */
72
+ writeLocal(mutations, onCommitted) {
71
73
  for (const m of mutations) {
72
74
  if (!this.localTables.has(m.table)) {
73
75
  throw new Error(`writeLocal: table "${m.table}" is not local-only — a direct commit to a synced table is reverted on the next rewind (M2).`);
@@ -82,7 +84,7 @@ export class WasmBackend {
82
84
  else
83
85
  tx.edit(m.table, m.old, m.new);
84
86
  }
85
- });
87
+ }, onCommitted);
86
88
  }
87
89
  /** Remove a synthetic aggregate table registered by {@link registerTable}, once the last
88
90
  * query reading it has been unregistered (`AGGREGATE-SYNC-DESIGN.md` §4): frees the engine
@@ -184,19 +186,33 @@ export class WasmBackend {
184
186
  /** Run `f` against a raw staged write txn (with the §4.1 `get` read path), commit, and
185
187
  * dispatch the resulting batches on the ordinary event stream. Inside an open server
186
188
  * batch the engine buffers the events into the cycle instead (commit returns `[]`),
187
- * so re-invocations dispatch nothing here — delivery is `serverBatchEnd`'s. */
188
- writeWith(f) {
189
+ * so re-invocations dispatch nothing here — delivery is `serverBatchEnd`'s.
190
+ *
191
+ * `onCommitted` runs after `tx.commit()` returns but before `dispatch` delivers to
192
+ * subscribers: a throw from `f` (pre-commit — engine untouched) skips it; a subscriber
193
+ * throw re-raised by `dispatch` happens after it. It is the only point where "the commit
194
+ * is applied" is knowable to a caller that must not confuse the two failure modes.
195
+ *
196
+ * Returns the per-change **touched-source key list** in staged-write order (`"daemon"` /
197
+ * `"room:doc:X"`, Slice E-iii-b design Q1) — additive; every current caller (`mutate`,
198
+ * `writeLocal`, `@rindle/optimistic`) ignores it. The per-source reconcile (Slice E-iii-c) zips
199
+ * it with the mutation's write-set for the per-pk hold-back source. */
200
+ writeWith(f, onCommitted) {
189
201
  const tx = this.db.write();
190
202
  f(tx);
191
- this.dispatch(tx.commit());
203
+ const { batches, sources } = tx.commitTracked();
204
+ onCommitted?.();
205
+ this.dispatch(batches);
206
+ return sources;
192
207
  }
193
- /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds
194
- * (optimistic layer un-applied, delta folded in, `sync` re-forked) and starts
195
- * buffering. Re-invoke the still-pending mutators via {@link writeWith}, then call
196
- * {@link serverBatchEnd}. On error the optimistic state is poisoned discard the
197
- * backend and re-hydrate. */
198
- serverBatchBegin(deltas) {
199
- this.db.serverBatchBegin(deltas);
208
+ /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds the
209
+ * per-source rebase (optimistic layer un-applied, delta folded in, the source's baseline
210
+ * re-forked) and starts buffering. `sourceKey` names the authority the delta confirms
211
+ * `"daemon"` today (single-domain); a `room:doc:X` key once the room feed lands. Re-invoke the
212
+ * still-pending mutators via {@link writeWith}, then call {@link serverBatchEnd}. On error the
213
+ * rebase state is poisoned — discard the backend and re-hydrate. */
214
+ serverBatchBegin(sourceKey, deltas) {
215
+ this.db.serverBatchBegin(sourceKey, deltas);
200
216
  }
201
217
  /** Close the cycle: the whole buffered stream (rewind + re-invocations) coalesces to
202
218
  * the minimal net per query (§3 — a confirmed-correct prediction delivers nothing)
@@ -204,6 +220,84 @@ export class WasmBackend {
204
220
  serverBatchEnd() {
205
221
  this.dispatch(this.db.serverBatchEnd());
206
222
  }
223
+ // --- multi-source seams (Slice E-iii-b harness → Slice G-ii ABI) ------------------
224
+ //
225
+ // Thin pass-throughs to the engine's `Merge` (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §5).
226
+ // `addRoomSource`'s {@link WritableDescriptor} now carries the real row-local `predicate` arm
227
+ // (a wire-Condition `where`, compiled Rust-side through the engine's own filter lowering)
228
+ // alongside the narrow `all`/`none`/`colEq` harness shapes. Single-domain behavior is untouched
229
+ // unless `addRoomSource` is called (a table stays Collapsed = today's daemon-only path).
230
+ /** Attach a room source to `table`, compiling `writable` into the merge's per-row writable scope.
231
+ * Promotes the table Collapsed → Merged on the first room (§5.2); the room seeds empty, so no
232
+ * visible blip. Throws on an untracked (local-only) table or a duplicate room. */
233
+ addRoomSource(table, roomKey, writable) {
234
+ this.db.addRoomSource(table, roomKey, writable);
235
+ }
236
+ /** Detach a room source, emitting the compensating visible deltas on the ordinary event stream. */
237
+ removeRoomSource(table, roomKey) {
238
+ this.db.removeRoomSource(table, roomKey);
239
+ }
240
+ /** Park an echoed `row` inertly on `sourceKey`'s slice of `table` (the confirm→echo hold-back
241
+ * window, design §7.3): keeps the row visible so the next per-source rewind of `sourceKey` does
242
+ * not flash-revert a confirmed-elsewhere write before its data echo lands. `sourceKey` is
243
+ * `"daemon"` or a `"room:doc:X"` key. */
244
+ holdBack(table, sourceKey, row) {
245
+ this.db.holdBack(table, sourceKey, row);
246
+ }
247
+ /** Park `oldRow`'s pk as ABSENT on `sourceKey`'s slice of `table` — the §7.3 tombstone, the
248
+ * remove-sibling of {@link holdBack} (Slice G-ii): a confirmed cross-slice REMOVE whose delete
249
+ * echo has not landed on `sourceKey` must not resurrect through that source's next rewind. The
250
+ * overlay masks the pk (overlay-first) until the source's re-forked baseline lacks it (the
251
+ * delete echoed), then drops. `oldRow` is the full-width PRE-IMAGE row (same positional
252
+ * bare-cell marshaling as {@link holdBack}); it carries the pk and, later (Slice H), the cells
253
+ * writable-scope predicates evaluate on. */
254
+ holdBackAbsent(table, sourceKey, oldRow) {
255
+ this.db.holdBackAbsent(table, sourceKey, oldRow);
256
+ }
257
+ /** Drop one parked §7.3 hold-back at `probe`'s pk on `sourceKey`'s slice and reconcile the
258
+ * pk's visible winner — the §301 FENCE-driven drop (`301-ECHO-FENCE-DESIGN.md` §2.2), fired
259
+ * by the optimistic backend's pin registry once `sourceKey`'s stream has provably delivered
260
+ * through the pinned write's commit. Unlike the park-time seams (whose install is a no-op by
261
+ * construction) a drop is exactly the moment a masked authoritative row becomes visible, so
262
+ * the engine's compensating deltas are DELIVERED here: dispatched on the ordinary event
263
+ * stream (or folded into an open server-batch cycle's one delivery). No-op when nothing is
264
+ * parked at the pk — the rewind's state-match fallback may have raced it. */
265
+ dropHeldBack(table, sourceKey, probe) {
266
+ this.dispatch(this.db.dropHeldBack(table, sourceKey, probe));
267
+ }
268
+ /** The parked hold-back at `probe`'s pk on `sourceKey`'s slice, or `undefined` (§301
269
+ * introspection): `absent` names the tombstone variant; `baseline` is the source's CURRENT
270
+ * confirmed-baseline row at the pk (`undefined` when the baseline lacks it) — the pin
271
+ * registry's lazy-prune probe and the §2.5 stuck-pin tripwire input. Non-throwing on an
272
+ * unknown source (a removed room), so pruning after a whole-source removal just works. */
273
+ heldBackState(table, sourceKey, probe) {
274
+ return this.db.heldBackState(table, sourceKey, probe);
275
+ }
276
+ /** Freeze a physical source — the D4 downgrade-ghost stub (wins-if-present in the merge). */
277
+ freezeSource(table, sourceKey) {
278
+ this.db.freezeSource(table, sourceKey);
279
+ }
280
+ /** The source currently serving `row`'s pk on `table` — `"daemon"`, a room key, or `undefined`
281
+ * when no source holds the pk (Slice H-i routing probe #1, design §3.2 #3). `row` is a
282
+ * full-width positional row (the pk derives from it — same marshaling as {@link holdBack});
283
+ * an unknown table throws loudly. Reads the LIVE visible state, overlay-first (§7.3): an open
284
+ * write txn's staged-but-uncommitted rows are NOT consulted (they have not routed yet), which
285
+ * is exactly the pre-commit view the §3 routing proof decides on — a present read is proven
286
+ * room-covered iff this reports the occupied room. */
287
+ provenanceOf(table, row) {
288
+ return this.db.provenanceOf(table, row);
289
+ }
290
+ /** Evaluate the REGISTERED writable scope of `table`'s room source `roomKey` against a
291
+ * caller-supplied full-width row (Slice H-i routing probe #2, design §3.1): a prediction-run
292
+ * write is proven room-covered iff this is `true` on the written row. Runs the SAME compiled
293
+ * predicate the merge's winner tiering uses (the {@link WritableDescriptor} registered at
294
+ * {@link addRoomSource}), so routing can never disagree with the engine; `all` ⇒ always true,
295
+ * `none` ⇒ always false, and a frozen source still answers (the predicate is static). Throws
296
+ * loudly on an unknown table, a room key not attached to the table, a wrong-width row, or
297
+ * `"daemon"` (which has no writable scope). */
298
+ writableMatches(table, roomKey, row) {
299
+ return this.db.writableMatches(table, roomKey, row);
300
+ }
207
301
  }
208
302
  /** Convenience: init the wasm + return a ready local {@link Store}. */
209
303
  export async function createWasmStore(schema) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rindle/wasm",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  "README.md"
30
30
  ],
31
31
  "dependencies": {
32
- "@rindle/client": "0.4.3"
32
+ "@rindle/client": "0.5.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^22.10.0",
@@ -39,7 +39,7 @@
39
39
  "build": "tsc -p tsconfig.build.json",
40
40
  "build:wasm": "./build.sh",
41
41
  "typecheck": "tsc --noEmit",
42
- "test": "node test/bundle-size.mjs && node --conditions=@rindle/source test/e2e.mjs && node --conditions=@rindle/source test/one-start.e2e.mjs && node --conditions=@rindle/source test/repro-high.e2e.mjs && node --conditions=@rindle/source test/partial-write.e2e.mjs",
42
+ "test": "node test/bundle-size.mjs && node --conditions=@rindle/source test/e2e.mjs && node --conditions=@rindle/source test/one-start.e2e.mjs && node --conditions=@rindle/source test/repro-high.e2e.mjs && node --conditions=@rindle/source test/partial-write.e2e.mjs && node --conditions=@rindle/source test/room-source.e2e.mjs && node --conditions=@rindle/source test/predicate-agreement.e2e.mjs",
43
43
  "test:browser": "node test/browser-smoke.mjs"
44
44
  }
45
45
  }
package/pkg/rindle.d.ts CHANGED
@@ -7,13 +7,69 @@
7
7
  export class Db {
8
8
  free(): void;
9
9
  [Symbol.dispose](): void;
10
+ /**
11
+ * Attach a room source to `table`, compiling `writable` into the merge's per-row
12
+ * [`WritableScope`] ([`parse_writable_scope`]): `{ kind: "all" }` = the room may write every row
13
+ * (room wins); `{ kind: "none" }` = a context table (daemon stays authoritative);
14
+ * `{ kind: "colEq", col, value }` = writable iff `row[col] == value` (harness stand-in);
15
+ * `{ kind: "predicate", where }` = the row-local wire-`Condition` predicate (Slice G-ii),
16
+ * lowered by the engine's own [`create_predicate`] against `table`'s schema.
17
+ */
18
+ addRoomSource(table: string, room_key: string, writable: any): void;
10
19
  /**
11
20
  * Tear down a registered query: drop its sink + reclaim its pipeline (disconnect from
12
21
  * the shared sources, free its slots — [`Graph::destroy_pipeline`]). Other queries
13
22
  * and the shared sources are untouched. A no-op for an unknown id.
14
23
  */
15
24
  destroyQuery(query_id: number): void;
25
+ /**
26
+ * Drop one parked hold-back at `probe`'s pk ([`Db::drop_held_back`] — the §301 fence-driven
27
+ * drop) and **deliver** the compensating deltas: returns the same
28
+ * `[{ queryId, events: FlatChange[] }]` shape as `WriteTxn.commit` (empty inside an open
29
+ * server-batch cycle, where the events join the cycle's one delivery instead) — unlike the
30
+ * park-time seams, whose install reconcile is a no-op by construction, a fence drop is
31
+ * exactly the moment a masked authoritative row becomes visible, so it must reach the
32
+ * views. `probe` is a full-width positional row (the pk derives from it — the same
33
+ * marshalling as [`holdBack`](Self::hold_back_js)).
34
+ */
35
+ dropHeldBack(table: string, source_key: string, probe: any): any;
36
+ /**
37
+ * Freeze a physical source — the D4 downgrade-ghost stub ([`Db::freeze_source`]).
38
+ */
39
+ freezeSource(table: string, source_key: string): void;
40
+ /**
41
+ * The parked hold-back at `probe`'s pk on `source_key`'s slice, as
42
+ * `{ absent: boolean, baseline: row | undefined } | undefined`
43
+ * ([`Db::held_back_state`], §301): `undefined` when nothing is parked — including an
44
+ * unknown source (non-throwing, so the client registry prunes lazily after a state-match
45
+ * drop or a room removal); `baseline` is the source's current confirmed-baseline row at
46
+ * the pk (the §2.5 stuck-pin tripwire input). An unknown TABLE still throws (the row
47
+ * cannot even be marshalled).
48
+ */
49
+ heldBackState(table: string, source_key: string, probe: any): any;
50
+ /**
51
+ * Park an echoed `row` inertly on `source_key`'s slice of `table` ([`Db::hold_back`]).
52
+ */
53
+ holdBack(table: string, source_key: string, row: any): void;
54
+ /**
55
+ * Park `old_row`'s pk as **absent** on `source_key`'s slice of `table`
56
+ * ([`Db::hold_back_absent`]) — the §7.3 tombstone for a confirmed cross-slice REMOVE.
57
+ * `old_row` is the full pre-image row, marshalled exactly like [`holdBack`](Self::hold_back_js)'s
58
+ * `row` (a full-width positional array of bare cells).
59
+ */
60
+ holdBackAbsent(table: string, source_key: string, old_row: any): void;
16
61
  constructor();
62
+ /**
63
+ * The winning source currently serving `row`'s pk on `table`, as its wasm-boundary key
64
+ * string (`"daemon"` / the room key, [`source_key_to_string`]), or `undefined` when no
65
+ * source holds the pk ([`Db::provenance`], §3.2 #3 — Slice H-i routing probe #1). `row` is
66
+ * a full-width positional row (the pk derives from it — the same marshalling as
67
+ * [`holdBack`](Self::hold_back_js)); an unknown table throws loudly. Reads the LIVE visible
68
+ * state, **overlay-first** (§7.3): an open [`WriteTxn`]'s staged-but-uncommitted writes are
69
+ * not consulted (they have not routed yet), which is exactly the pre-commit view the §3
70
+ * routing proof decides on.
71
+ */
72
+ provenanceOf(table: string, row: any): any;
17
73
  /**
18
74
  * Register a live query from a Zero-wire AST under the caller's opaque `query_id`.
19
75
  * Lowers it into the shared graph, sinks it in a change-sink, and hydrates. Returns
@@ -38,6 +94,10 @@ export class Db {
38
94
  * (`createSchema`, N1) has a loud backstop instead of a silent one.
39
95
  */
40
96
  registerTable(table: string, schema: any, local: boolean): void;
97
+ /**
98
+ * Detach a room source, emitting the compensating visible deltas ([`Db::remove_room_source`]).
99
+ */
100
+ removeRoomSource(table: string, room_key: string): void;
41
101
  /**
42
102
  * Open a §1.3 reconcile cycle against the coherent server delta `deltas` —
43
103
  * an array of base-table row ops `{ table, type: "add"|"remove"|"edit", row,
@@ -49,11 +109,15 @@ export class Db {
49
109
  * events buffer into the cycle), then calls
50
110
  * [`server_batch_end`](Db::server_batch_end) for the one coalesced delivery.
51
111
  *
112
+ * `source_key` names the authority these `deltas` confirm — `"daemon"` at the wasm boundary
113
+ * today (single-domain); a `room:doc:X` string once the room feed lands (Slice E-iii/G). It
114
+ * selects which physical source [`Merge::rewind`] folds the delta into.
115
+ *
52
116
  * Errors if a cycle is already open, on an unknown table, or on a push failure
53
- * — after which the optimistic state is poisoned (a partial rewind may have
117
+ * — after which the per-source rebase state is poisoned (a partial rewind may have
54
118
  * applied): the only safe recovery is to discard this `Db` and re-hydrate.
55
119
  */
56
- serverBatchBegin(deltas: any): void;
120
+ serverBatchBegin(source_key: string, deltas: any): void;
57
121
  /**
58
122
  * Close the open cycle: deliver the whole buffered event stream (rewind +
59
123
  * re-invocations) per query, **in order**, as `[{ queryId, events: FlatChange[] }]`
@@ -81,6 +145,17 @@ export class Db {
81
145
  * holds).
82
146
  */
83
147
  unregisterTable(table: string): void;
148
+ /**
149
+ * Evaluate the REGISTERED writable scope of `table`'s room source `room_key` against a
150
+ * caller-supplied full-width `row` ([`Db::writable_matches`] — Slice H-i routing probe #2):
151
+ * the §3 proof's write-set rule, run through the SAME compiled predicate the merge's winner
152
+ * tiering uses (the G-ii descriptor `addRoomSource` registered), so routing can never
153
+ * disagree with the engine. `{kind:"all"}` ⇒ always `true`; `{kind:"none"}` ⇒ always
154
+ * `false`; a frozen source still answers (the predicate is static). Loud errors: unknown
155
+ * table, a room key not attached to the table, a wrong-width row, and `"daemon"` (reserved
156
+ * by [`parse_source_key`]; the daemon has no writable scope).
157
+ */
158
+ writableMatches(table: string, room_key: string, row: any): boolean;
84
159
  /**
85
160
  * Open a write transaction. Stage row ops with [`WriteTxn::add`]/`remove`/`edit`,
86
161
  * then [`WriteTxn::commit`] to apply them as one batch and get the per-query flat
@@ -166,6 +241,16 @@ export class WriteTxn {
166
241
  * `serverBatchEnd`.
167
242
  */
168
243
  commit(): any;
244
+ /**
245
+ * Like [`commit`](WriteTxn::commit), but ALSO returns the per-change **touched-source key list**
246
+ * in staged order as `{ batches, sources: string[] }` (Slice E-iii-b, design Q1). Each `sources`
247
+ * entry is the [`SourceKey`] the corresponding staged change routed through — the pk's provenance
248
+ * captured **pre-apply** (a `remove` erases the row, so provenance is unrecoverable afterward),
249
+ * `"daemon"` for a new/unknown pk or any Collapsed table. The order aligns 1:1 with the staged
250
+ * writes, so the optimistic layer can zip it with a mutation's write-set (per-pk hold-back
251
+ * source, Slice E-iii-c). `batches` is exactly what [`commit`](WriteTxn::commit) returns.
252
+ */
253
+ commitTracked(): any;
169
254
  /**
170
255
  * Stage an edit of `old` → `new_row` in `table` (both width-checked).
171
256
  *
@@ -236,13 +321,22 @@ export interface InitOutput {
236
321
  readonly __wbg_db_free: (a: number, b: number) => void;
237
322
  readonly __wbg_rindleview_free: (a: number, b: number) => void;
238
323
  readonly __wbg_writetxn_free: (a: number, b: number) => void;
324
+ readonly db_addRoomSource: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
239
325
  readonly db_destroyQuery: (a: number, b: number) => void;
326
+ readonly db_dropHeldBack: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
327
+ readonly db_freezeSource: (a: number, b: number, c: number, d: number, e: number) => [number, number];
328
+ readonly db_heldBackState: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
329
+ readonly db_holdBack: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
330
+ readonly db_holdBackAbsent: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
240
331
  readonly db_new: () => number;
332
+ readonly db_provenanceOf: (a: number, b: number, c: number, d: any) => [number, number, number];
241
333
  readonly db_query: (a: number, b: number, c: any) => [number, number, number];
242
334
  readonly db_registerTable: (a: number, b: number, c: number, d: any, e: number) => [number, number];
243
- readonly db_serverBatchBegin: (a: number, b: any) => [number, number];
335
+ readonly db_removeRoomSource: (a: number, b: number, c: number, d: number, e: number) => [number, number];
336
+ readonly db_serverBatchBegin: (a: number, b: number, c: number, d: any) => [number, number];
244
337
  readonly db_serverBatchEnd: (a: number) => [number, number, number];
245
338
  readonly db_unregisterTable: (a: number, b: number, c: number) => [number, number];
339
+ readonly db_writableMatches: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
246
340
  readonly db_write: (a: number) => number;
247
341
  readonly rindleview_build: (a: any, b: any, c: any) => [number, number, number];
248
342
  readonly rindleview_data: (a: number) => any;
@@ -253,6 +347,7 @@ export interface InitOutput {
253
347
  readonly rindleview_subscribe: (a: number, b: any) => number;
254
348
  readonly writetxn_add: (a: number, b: number, c: number, d: any) => [number, number];
255
349
  readonly writetxn_commit: (a: number) => [number, number, number];
350
+ readonly writetxn_commitTracked: (a: number) => [number, number, number];
256
351
  readonly writetxn_edit: (a: number, b: number, c: number, d: any, e: any) => [number, number];
257
352
  readonly writetxn_get: (a: number, b: number, c: number, d: any) => [number, number, number];
258
353
  readonly writetxn_query: (a: number, b: any) => [number, number, number];
package/pkg/rindle.js CHANGED
@@ -14,6 +14,27 @@ export class Db {
14
14
  const ptr = this.__destroy_into_raw();
15
15
  wasm.__wbg_db_free(ptr, 0);
16
16
  }
17
+ /**
18
+ * Attach a room source to `table`, compiling `writable` into the merge's per-row
19
+ * [`WritableScope`] ([`parse_writable_scope`]): `{ kind: "all" }` = the room may write every row
20
+ * (room wins); `{ kind: "none" }` = a context table (daemon stays authoritative);
21
+ * `{ kind: "colEq", col, value }` = writable iff `row[col] == value` (harness stand-in);
22
+ * `{ kind: "predicate", where }` = the row-local wire-`Condition` predicate (Slice G-ii),
23
+ * lowered by the engine's own [`create_predicate`] against `table`'s schema.
24
+ * @param {string} table
25
+ * @param {string} room_key
26
+ * @param {any} writable
27
+ */
28
+ addRoomSource(table, room_key, writable) {
29
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
30
+ const len0 = WASM_VECTOR_LEN;
31
+ const ptr1 = passStringToWasm0(room_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
32
+ const len1 = WASM_VECTOR_LEN;
33
+ const ret = wasm.db_addRoomSource(this.__wbg_ptr, ptr0, len0, ptr1, len1, writable);
34
+ if (ret[1]) {
35
+ throw takeFromExternrefTable0(ret[0]);
36
+ }
37
+ }
17
38
  /**
18
39
  * Tear down a registered query: drop its sink + reclaim its pipeline (disconnect from
19
40
  * the shared sources, free its slots — [`Graph::destroy_pipeline`]). Other queries
@@ -23,12 +44,133 @@ export class Db {
23
44
  destroyQuery(query_id) {
24
45
  wasm.db_destroyQuery(this.__wbg_ptr, query_id);
25
46
  }
47
+ /**
48
+ * Drop one parked hold-back at `probe`'s pk ([`Db::drop_held_back`] — the §301 fence-driven
49
+ * drop) and **deliver** the compensating deltas: returns the same
50
+ * `[{ queryId, events: FlatChange[] }]` shape as `WriteTxn.commit` (empty inside an open
51
+ * server-batch cycle, where the events join the cycle's one delivery instead) — unlike the
52
+ * park-time seams, whose install reconcile is a no-op by construction, a fence drop is
53
+ * exactly the moment a masked authoritative row becomes visible, so it must reach the
54
+ * views. `probe` is a full-width positional row (the pk derives from it — the same
55
+ * marshalling as [`holdBack`](Self::hold_back_js)).
56
+ * @param {string} table
57
+ * @param {string} source_key
58
+ * @param {any} probe
59
+ * @returns {any}
60
+ */
61
+ dropHeldBack(table, source_key, probe) {
62
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
63
+ const len0 = WASM_VECTOR_LEN;
64
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
65
+ const len1 = WASM_VECTOR_LEN;
66
+ const ret = wasm.db_dropHeldBack(this.__wbg_ptr, ptr0, len0, ptr1, len1, probe);
67
+ if (ret[2]) {
68
+ throw takeFromExternrefTable0(ret[1]);
69
+ }
70
+ return takeFromExternrefTable0(ret[0]);
71
+ }
72
+ /**
73
+ * Freeze a physical source — the D4 downgrade-ghost stub ([`Db::freeze_source`]).
74
+ * @param {string} table
75
+ * @param {string} source_key
76
+ */
77
+ freezeSource(table, source_key) {
78
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
79
+ const len0 = WASM_VECTOR_LEN;
80
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
81
+ const len1 = WASM_VECTOR_LEN;
82
+ const ret = wasm.db_freezeSource(this.__wbg_ptr, ptr0, len0, ptr1, len1);
83
+ if (ret[1]) {
84
+ throw takeFromExternrefTable0(ret[0]);
85
+ }
86
+ }
87
+ /**
88
+ * The parked hold-back at `probe`'s pk on `source_key`'s slice, as
89
+ * `{ absent: boolean, baseline: row | undefined } | undefined`
90
+ * ([`Db::held_back_state`], §301): `undefined` when nothing is parked — including an
91
+ * unknown source (non-throwing, so the client registry prunes lazily after a state-match
92
+ * drop or a room removal); `baseline` is the source's current confirmed-baseline row at
93
+ * the pk (the §2.5 stuck-pin tripwire input). An unknown TABLE still throws (the row
94
+ * cannot even be marshalled).
95
+ * @param {string} table
96
+ * @param {string} source_key
97
+ * @param {any} probe
98
+ * @returns {any}
99
+ */
100
+ heldBackState(table, source_key, probe) {
101
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
102
+ const len0 = WASM_VECTOR_LEN;
103
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
104
+ const len1 = WASM_VECTOR_LEN;
105
+ const ret = wasm.db_heldBackState(this.__wbg_ptr, ptr0, len0, ptr1, len1, probe);
106
+ if (ret[2]) {
107
+ throw takeFromExternrefTable0(ret[1]);
108
+ }
109
+ return takeFromExternrefTable0(ret[0]);
110
+ }
111
+ /**
112
+ * Park an echoed `row` inertly on `source_key`'s slice of `table` ([`Db::hold_back`]).
113
+ * @param {string} table
114
+ * @param {string} source_key
115
+ * @param {any} row
116
+ */
117
+ holdBack(table, source_key, row) {
118
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
119
+ const len0 = WASM_VECTOR_LEN;
120
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
121
+ const len1 = WASM_VECTOR_LEN;
122
+ const ret = wasm.db_holdBack(this.__wbg_ptr, ptr0, len0, ptr1, len1, row);
123
+ if (ret[1]) {
124
+ throw takeFromExternrefTable0(ret[0]);
125
+ }
126
+ }
127
+ /**
128
+ * Park `old_row`'s pk as **absent** on `source_key`'s slice of `table`
129
+ * ([`Db::hold_back_absent`]) — the §7.3 tombstone for a confirmed cross-slice REMOVE.
130
+ * `old_row` is the full pre-image row, marshalled exactly like [`holdBack`](Self::hold_back_js)'s
131
+ * `row` (a full-width positional array of bare cells).
132
+ * @param {string} table
133
+ * @param {string} source_key
134
+ * @param {any} old_row
135
+ */
136
+ holdBackAbsent(table, source_key, old_row) {
137
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
138
+ const len0 = WASM_VECTOR_LEN;
139
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
140
+ const len1 = WASM_VECTOR_LEN;
141
+ const ret = wasm.db_holdBackAbsent(this.__wbg_ptr, ptr0, len0, ptr1, len1, old_row);
142
+ if (ret[1]) {
143
+ throw takeFromExternrefTable0(ret[0]);
144
+ }
145
+ }
26
146
  constructor() {
27
147
  const ret = wasm.db_new();
28
148
  this.__wbg_ptr = ret;
29
149
  DbFinalization.register(this, this.__wbg_ptr, this);
30
150
  return this;
31
151
  }
152
+ /**
153
+ * The winning source currently serving `row`'s pk on `table`, as its wasm-boundary key
154
+ * string (`"daemon"` / the room key, [`source_key_to_string`]), or `undefined` when no
155
+ * source holds the pk ([`Db::provenance`], §3.2 #3 — Slice H-i routing probe #1). `row` is
156
+ * a full-width positional row (the pk derives from it — the same marshalling as
157
+ * [`holdBack`](Self::hold_back_js)); an unknown table throws loudly. Reads the LIVE visible
158
+ * state, **overlay-first** (§7.3): an open [`WriteTxn`]'s staged-but-uncommitted writes are
159
+ * not consulted (they have not routed yet), which is exactly the pre-commit view the §3
160
+ * routing proof decides on.
161
+ * @param {string} table
162
+ * @param {any} row
163
+ * @returns {any}
164
+ */
165
+ provenanceOf(table, row) {
166
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
167
+ const len0 = WASM_VECTOR_LEN;
168
+ const ret = wasm.db_provenanceOf(this.__wbg_ptr, ptr0, len0, row);
169
+ if (ret[2]) {
170
+ throw takeFromExternrefTable0(ret[1]);
171
+ }
172
+ return takeFromExternrefTable0(ret[0]);
173
+ }
32
174
  /**
33
175
  * Register a live query from a Zero-wire AST under the caller's opaque `query_id`.
34
176
  * Lowers it into the shared graph, sinks it in a change-sink, and hydrates. Returns
@@ -72,6 +214,21 @@ export class Db {
72
214
  throw takeFromExternrefTable0(ret[0]);
73
215
  }
74
216
  }
217
+ /**
218
+ * Detach a room source, emitting the compensating visible deltas ([`Db::remove_room_source`]).
219
+ * @param {string} table
220
+ * @param {string} room_key
221
+ */
222
+ removeRoomSource(table, room_key) {
223
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
224
+ const len0 = WASM_VECTOR_LEN;
225
+ const ptr1 = passStringToWasm0(room_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
226
+ const len1 = WASM_VECTOR_LEN;
227
+ const ret = wasm.db_removeRoomSource(this.__wbg_ptr, ptr0, len0, ptr1, len1);
228
+ if (ret[1]) {
229
+ throw takeFromExternrefTable0(ret[0]);
230
+ }
231
+ }
75
232
  /**
76
233
  * Open a §1.3 reconcile cycle against the coherent server delta `deltas` —
77
234
  * an array of base-table row ops `{ table, type: "add"|"remove"|"edit", row,
@@ -83,13 +240,20 @@ export class Db {
83
240
  * events buffer into the cycle), then calls
84
241
  * [`server_batch_end`](Db::server_batch_end) for the one coalesced delivery.
85
242
  *
243
+ * `source_key` names the authority these `deltas` confirm — `"daemon"` at the wasm boundary
244
+ * today (single-domain); a `room:doc:X` string once the room feed lands (Slice E-iii/G). It
245
+ * selects which physical source [`Merge::rewind`] folds the delta into.
246
+ *
86
247
  * Errors if a cycle is already open, on an unknown table, or on a push failure
87
- * — after which the optimistic state is poisoned (a partial rewind may have
248
+ * — after which the per-source rebase state is poisoned (a partial rewind may have
88
249
  * applied): the only safe recovery is to discard this `Db` and re-hydrate.
250
+ * @param {string} source_key
89
251
  * @param {any} deltas
90
252
  */
91
- serverBatchBegin(deltas) {
92
- const ret = wasm.db_serverBatchBegin(this.__wbg_ptr, deltas);
253
+ serverBatchBegin(source_key, deltas) {
254
+ const ptr0 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
255
+ const len0 = WASM_VECTOR_LEN;
256
+ const ret = wasm.db_serverBatchBegin(this.__wbg_ptr, ptr0, len0, deltas);
93
257
  if (ret[1]) {
94
258
  throw takeFromExternrefTable0(ret[0]);
95
259
  }
@@ -136,6 +300,31 @@ export class Db {
136
300
  throw takeFromExternrefTable0(ret[0]);
137
301
  }
138
302
  }
303
+ /**
304
+ * Evaluate the REGISTERED writable scope of `table`'s room source `room_key` against a
305
+ * caller-supplied full-width `row` ([`Db::writable_matches`] — Slice H-i routing probe #2):
306
+ * the §3 proof's write-set rule, run through the SAME compiled predicate the merge's winner
307
+ * tiering uses (the G-ii descriptor `addRoomSource` registered), so routing can never
308
+ * disagree with the engine. `{kind:"all"}` ⇒ always `true`; `{kind:"none"}` ⇒ always
309
+ * `false`; a frozen source still answers (the predicate is static). Loud errors: unknown
310
+ * table, a room key not attached to the table, a wrong-width row, and `"daemon"` (reserved
311
+ * by [`parse_source_key`]; the daemon has no writable scope).
312
+ * @param {string} table
313
+ * @param {string} room_key
314
+ * @param {any} row
315
+ * @returns {boolean}
316
+ */
317
+ writableMatches(table, room_key, row) {
318
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
319
+ const len0 = WASM_VECTOR_LEN;
320
+ const ptr1 = passStringToWasm0(room_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
321
+ const len1 = WASM_VECTOR_LEN;
322
+ const ret = wasm.db_writableMatches(this.__wbg_ptr, ptr0, len0, ptr1, len1, row);
323
+ if (ret[2]) {
324
+ throw takeFromExternrefTable0(ret[1]);
325
+ }
326
+ return ret[0] !== 0;
327
+ }
139
328
  /**
140
329
  * Open a write transaction. Stage row ops with [`WriteTxn::add`]/`remove`/`edit`,
141
330
  * then [`WriteTxn::commit`] to apply them as one batch and get the per-query flat
@@ -309,6 +498,24 @@ export class WriteTxn {
309
498
  }
310
499
  return takeFromExternrefTable0(ret[0]);
311
500
  }
501
+ /**
502
+ * Like [`commit`](WriteTxn::commit), but ALSO returns the per-change **touched-source key list**
503
+ * in staged order as `{ batches, sources: string[] }` (Slice E-iii-b, design Q1). Each `sources`
504
+ * entry is the [`SourceKey`] the corresponding staged change routed through — the pk's provenance
505
+ * captured **pre-apply** (a `remove` erases the row, so provenance is unrecoverable afterward),
506
+ * `"daemon"` for a new/unknown pk or any Collapsed table. The order aligns 1:1 with the staged
507
+ * writes, so the optimistic layer can zip it with a mutation's write-set (per-pk hold-back
508
+ * source, Slice E-iii-c). `batches` is exactly what [`commit`](WriteTxn::commit) returns.
509
+ * @returns {any}
510
+ */
511
+ commitTracked() {
512
+ const ptr = this.__destroy_into_raw();
513
+ const ret = wasm.writetxn_commitTracked(ptr);
514
+ if (ret[2]) {
515
+ throw takeFromExternrefTable0(ret[1]);
516
+ }
517
+ return takeFromExternrefTable0(ret[0]);
518
+ }
312
519
  /**
313
520
  * Stage an edit of `old` → `new_row` in `table` (both width-checked).
314
521
  *
Binary file
@@ -5,13 +5,22 @@ export const __rindle_wasm_start: () => void;
5
5
  export const __wbg_db_free: (a: number, b: number) => void;
6
6
  export const __wbg_rindleview_free: (a: number, b: number) => void;
7
7
  export const __wbg_writetxn_free: (a: number, b: number) => void;
8
+ export const db_addRoomSource: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
8
9
  export const db_destroyQuery: (a: number, b: number) => void;
10
+ export const db_dropHeldBack: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
11
+ export const db_freezeSource: (a: number, b: number, c: number, d: number, e: number) => [number, number];
12
+ export const db_heldBackState: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
13
+ export const db_holdBack: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
14
+ export const db_holdBackAbsent: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
9
15
  export const db_new: () => number;
16
+ export const db_provenanceOf: (a: number, b: number, c: number, d: any) => [number, number, number];
10
17
  export const db_query: (a: number, b: number, c: any) => [number, number, number];
11
18
  export const db_registerTable: (a: number, b: number, c: number, d: any, e: number) => [number, number];
12
- export const db_serverBatchBegin: (a: number, b: any) => [number, number];
19
+ export const db_removeRoomSource: (a: number, b: number, c: number, d: number, e: number) => [number, number];
20
+ export const db_serverBatchBegin: (a: number, b: number, c: number, d: any) => [number, number];
13
21
  export const db_serverBatchEnd: (a: number) => [number, number, number];
14
22
  export const db_unregisterTable: (a: number, b: number, c: number) => [number, number];
23
+ export const db_writableMatches: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
15
24
  export const db_write: (a: number) => number;
16
25
  export const rindleview_build: (a: any, b: any, c: any) => [number, number, number];
17
26
  export const rindleview_data: (a: number) => any;
@@ -22,6 +31,7 @@ export const rindleview_setResultType: (a: number, b: number, c: number) => void
22
31
  export const rindleview_subscribe: (a: number, b: any) => number;
23
32
  export const writetxn_add: (a: number, b: number, c: number, d: any) => [number, number];
24
33
  export const writetxn_commit: (a: number) => [number, number, number];
34
+ export const writetxn_commitTracked: (a: number) => [number, number, number];
25
35
  export const writetxn_edit: (a: number, b: number, c: number, d: any, e: any) => [number, number];
26
36
  export const writetxn_get: (a: number, b: number, c: number, d: any) => [number, number, number];
27
37
  export const writetxn_query: (a: number, b: any) => [number, number, number];
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  Backend,
14
14
  ChangeEvent,
15
15
  ColsMap,
16
+ Condition,
16
17
  Mutation,
17
18
  QueryId,
18
19
  Schema,
@@ -20,6 +21,23 @@ import type {
20
21
 
21
22
  export * from "@rindle/client";
22
23
 
24
+ /** A serializable writable-scope descriptor: how the merge decides, per row, whether an attached
25
+ * room may write it (the room copy then wins, RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §5.2).
26
+ * `"all"` = the room owns every row it holds; `"none"` = a context table the daemon stays
27
+ * authoritative for; `"colEq"` = writable iff `row[col] === value` (harness stand-in). The REAL
28
+ * shape (Slice G) is `"predicate"`: `where` is the SAME wire {@link Condition} JSON a query AST
29
+ * carries, restricted to what evaluates against a single row — `simple` column-vs-literal leaves
30
+ * under `and`/`or`; a `correlatedSubquery` (or any other non-row-local node) is rejected loudly at
31
+ * `addRoomSource` time. It compiles Rust-side through the engine's OWN filter lowering
32
+ * (`create_predicate`), so writable evaluation can never disagree with query-filter semantics.
33
+ * NOTE: room join-key columns (Slice H) deliberately do NOT cross this boundary — they stay
34
+ * TS-side. */
35
+ export type WritableDescriptor =
36
+ | { kind: "all" }
37
+ | { kind: "none" }
38
+ | { kind: "colEq"; col: number; value: unknown }
39
+ | { kind: "predicate"; where: Condition };
40
+
23
41
  // The wasm Db surface this backend uses (kept local so it doesn't depend on the generated .d.ts).
24
42
  interface WasmDb {
25
43
  registerTable(table: string, spec: { columns: string[]; primaryKey: number[] }, local: boolean): void;
@@ -27,8 +45,23 @@ interface WasmDb {
27
45
  query(queryId: number, ast: Ast): { comparatorVersion: number; schema: unknown; snapshot: unknown[] };
28
46
  destroyQuery(queryId: number): void;
29
47
  write(): WasmWriteTxn;
30
- serverBatchBegin(deltas: ServerDeltaOp[]): void;
48
+ serverBatchBegin(sourceKey: string, deltas: ServerDeltaOp[]): void;
31
49
  serverBatchEnd(): Array<{ queryId: number; events: unknown[] }>;
50
+ // Multi-source seams (Slice E-iii-b harness → Slice G-ii ABI — see WritableDescriptor).
51
+ addRoomSource(table: string, roomKey: string, writable: WritableDescriptor): void;
52
+ removeRoomSource(table: string, roomKey: string): void;
53
+ holdBack(table: string, sourceKey: string, row: unknown[]): void;
54
+ holdBackAbsent(table: string, sourceKey: string, oldRow: unknown[]): void;
55
+ dropHeldBack(table: string, sourceKey: string, probe: unknown[]): unknown;
56
+ heldBackState(
57
+ table: string,
58
+ sourceKey: string,
59
+ probe: unknown[],
60
+ ): { absent: boolean; baseline: unknown[] | undefined } | undefined;
61
+ freezeSource(table: string, sourceKey: string): void;
62
+ // Slice H-i routing probes (design §3.1/§3.2 — see the WasmBackend mirrors below).
63
+ provenanceOf(table: string, row: unknown[]): string | undefined;
64
+ writableMatches(table: string, roomKey: string, row: unknown[]): boolean;
32
65
  }
33
66
 
34
67
  /** A raw staged write transaction over the wasm engine — the surface an optimistic client
@@ -46,6 +79,11 @@ export interface WasmWriteTxn {
46
79
  * shape to a `view.data` row), in the query's order. */
47
80
  query(ast: Ast): unknown[];
48
81
  commit(): Array<{ queryId: number; events: unknown[] }>;
82
+ /** Like {@link commit}, additionally returning the per-change touched-source key list in staged
83
+ * order (`sources`, e.g. `"daemon"` / `"room:doc:1"`) — the provenance each staged write routed
84
+ * through, captured pre-apply (Slice E-iii-b, design Q1). `batches` is exactly {@link commit}'s
85
+ * return; the source list is additive. */
86
+ commitTracked(): { batches: Array<{ queryId: number; events: unknown[] }>; sources: string[] };
49
87
  rollback(): void;
50
88
  }
51
89
 
@@ -121,8 +159,10 @@ export class WasmBackend<S extends ColsMap> implements Backend {
121
159
 
122
160
  /** Direct-commit a batch of LOCAL-only writes (`201-LOCAL-ONLY-TABLES-DESIGN.md` §6): push them
123
161
  * straight through the engine on the ordinary delivery path, OUTSIDE any optimistic cycle (a
124
- * local table is untracked, so it never rebases). Rejects a synced/tracked table (M2). */
125
- writeLocal(mutations: Mutation[]): void {
162
+ * local table is untracked, so it never rebases). Rejects a synced/tracked table (M2).
163
+ * `onCommitted` fires between the engine commit and subscriber delivery ({@link writeWith})
164
+ * the post-commit anchor the persistence tap needs (207 §5.1). */
165
+ writeLocal(mutations: Mutation[], onCommitted?: () => void): void {
126
166
  for (const m of mutations) {
127
167
  if (!this.localTables.has(m.table)) {
128
168
  throw new Error(`writeLocal: table "${m.table}" is not local-only — a direct commit to a synced table is reverted on the next rewind (M2).`);
@@ -134,7 +174,7 @@ export class WasmBackend<S extends ColsMap> implements Backend {
134
174
  else if (m.op === "remove") tx.remove(m.table, m.row);
135
175
  else tx.edit(m.table, m.old, m.new);
136
176
  }
137
- });
177
+ }, onCommitted);
138
178
  }
139
179
 
140
180
  /** Remove a synthetic aggregate table registered by {@link registerTable}, once the last
@@ -235,20 +275,34 @@ export class WasmBackend<S extends ColsMap> implements Backend {
235
275
  /** Run `f` against a raw staged write txn (with the §4.1 `get` read path), commit, and
236
276
  * dispatch the resulting batches on the ordinary event stream. Inside an open server
237
277
  * batch the engine buffers the events into the cycle instead (commit returns `[]`),
238
- * so re-invocations dispatch nothing here — delivery is `serverBatchEnd`'s. */
239
- writeWith(f: (tx: WasmWriteTxn) => void): void {
278
+ * so re-invocations dispatch nothing here — delivery is `serverBatchEnd`'s.
279
+ *
280
+ * `onCommitted` runs after `tx.commit()` returns but before `dispatch` delivers to
281
+ * subscribers: a throw from `f` (pre-commit — engine untouched) skips it; a subscriber
282
+ * throw re-raised by `dispatch` happens after it. It is the only point where "the commit
283
+ * is applied" is knowable to a caller that must not confuse the two failure modes.
284
+ *
285
+ * Returns the per-change **touched-source key list** in staged-write order (`"daemon"` /
286
+ * `"room:doc:X"`, Slice E-iii-b design Q1) — additive; every current caller (`mutate`,
287
+ * `writeLocal`, `@rindle/optimistic`) ignores it. The per-source reconcile (Slice E-iii-c) zips
288
+ * it with the mutation's write-set for the per-pk hold-back source. */
289
+ writeWith(f: (tx: WasmWriteTxn) => void, onCommitted?: () => void): string[] {
240
290
  const tx = this.db.write();
241
291
  f(tx);
242
- this.dispatch(tx.commit() as BatchSet);
292
+ const { batches, sources } = tx.commitTracked() as { batches: BatchSet; sources: string[] };
293
+ onCommitted?.();
294
+ this.dispatch(batches);
295
+ return sources;
243
296
  }
244
297
 
245
- /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds
246
- * (optimistic layer un-applied, delta folded in, `sync` re-forked) and starts
247
- * buffering. Re-invoke the still-pending mutators via {@link writeWith}, then call
248
- * {@link serverBatchEnd}. On error the optimistic state is poisoned discard the
249
- * backend and re-hydrate. */
250
- serverBatchBegin(deltas: ServerDeltaOp[]): void {
251
- this.db.serverBatchBegin(deltas);
298
+ /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds the
299
+ * per-source rebase (optimistic layer un-applied, delta folded in, the source's baseline
300
+ * re-forked) and starts buffering. `sourceKey` names the authority the delta confirms
301
+ * `"daemon"` today (single-domain); a `room:doc:X` key once the room feed lands. Re-invoke the
302
+ * still-pending mutators via {@link writeWith}, then call {@link serverBatchEnd}. On error the
303
+ * rebase state is poisoned — discard the backend and re-hydrate. */
304
+ serverBatchBegin(sourceKey: string, deltas: ServerDeltaOp[]): void {
305
+ this.db.serverBatchBegin(sourceKey, deltas);
252
306
  }
253
307
 
254
308
  /** Close the cycle: the whole buffered stream (rewind + re-invocations) coalesces to
@@ -257,6 +311,98 @@ export class WasmBackend<S extends ColsMap> implements Backend {
257
311
  serverBatchEnd(): void {
258
312
  this.dispatch(this.db.serverBatchEnd() as BatchSet);
259
313
  }
314
+
315
+ // --- multi-source seams (Slice E-iii-b harness → Slice G-ii ABI) ------------------
316
+ //
317
+ // Thin pass-throughs to the engine's `Merge` (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §5).
318
+ // `addRoomSource`'s {@link WritableDescriptor} now carries the real row-local `predicate` arm
319
+ // (a wire-Condition `where`, compiled Rust-side through the engine's own filter lowering)
320
+ // alongside the narrow `all`/`none`/`colEq` harness shapes. Single-domain behavior is untouched
321
+ // unless `addRoomSource` is called (a table stays Collapsed = today's daemon-only path).
322
+
323
+ /** Attach a room source to `table`, compiling `writable` into the merge's per-row writable scope.
324
+ * Promotes the table Collapsed → Merged on the first room (§5.2); the room seeds empty, so no
325
+ * visible blip. Throws on an untracked (local-only) table or a duplicate room. */
326
+ addRoomSource(table: string, roomKey: string, writable: WritableDescriptor): void {
327
+ this.db.addRoomSource(table, roomKey, writable);
328
+ }
329
+
330
+ /** Detach a room source, emitting the compensating visible deltas on the ordinary event stream. */
331
+ removeRoomSource(table: string, roomKey: string): void {
332
+ this.db.removeRoomSource(table, roomKey);
333
+ }
334
+
335
+ /** Park an echoed `row` inertly on `sourceKey`'s slice of `table` (the confirm→echo hold-back
336
+ * window, design §7.3): keeps the row visible so the next per-source rewind of `sourceKey` does
337
+ * not flash-revert a confirmed-elsewhere write before its data echo lands. `sourceKey` is
338
+ * `"daemon"` or a `"room:doc:X"` key. */
339
+ holdBack(table: string, sourceKey: string, row: unknown[]): void {
340
+ this.db.holdBack(table, sourceKey, row);
341
+ }
342
+
343
+ /** Park `oldRow`'s pk as ABSENT on `sourceKey`'s slice of `table` — the §7.3 tombstone, the
344
+ * remove-sibling of {@link holdBack} (Slice G-ii): a confirmed cross-slice REMOVE whose delete
345
+ * echo has not landed on `sourceKey` must not resurrect through that source's next rewind. The
346
+ * overlay masks the pk (overlay-first) until the source's re-forked baseline lacks it (the
347
+ * delete echoed), then drops. `oldRow` is the full-width PRE-IMAGE row (same positional
348
+ * bare-cell marshaling as {@link holdBack}); it carries the pk and, later (Slice H), the cells
349
+ * writable-scope predicates evaluate on. */
350
+ holdBackAbsent(table: string, sourceKey: string, oldRow: unknown[]): void {
351
+ this.db.holdBackAbsent(table, sourceKey, oldRow);
352
+ }
353
+
354
+ /** Drop one parked §7.3 hold-back at `probe`'s pk on `sourceKey`'s slice and reconcile the
355
+ * pk's visible winner — the §301 FENCE-driven drop (`301-ECHO-FENCE-DESIGN.md` §2.2), fired
356
+ * by the optimistic backend's pin registry once `sourceKey`'s stream has provably delivered
357
+ * through the pinned write's commit. Unlike the park-time seams (whose install is a no-op by
358
+ * construction) a drop is exactly the moment a masked authoritative row becomes visible, so
359
+ * the engine's compensating deltas are DELIVERED here: dispatched on the ordinary event
360
+ * stream (or folded into an open server-batch cycle's one delivery). No-op when nothing is
361
+ * parked at the pk — the rewind's state-match fallback may have raced it. */
362
+ dropHeldBack(table: string, sourceKey: string, probe: unknown[]): void {
363
+ this.dispatch(this.db.dropHeldBack(table, sourceKey, probe) as BatchSet);
364
+ }
365
+
366
+ /** The parked hold-back at `probe`'s pk on `sourceKey`'s slice, or `undefined` (§301
367
+ * introspection): `absent` names the tombstone variant; `baseline` is the source's CURRENT
368
+ * confirmed-baseline row at the pk (`undefined` when the baseline lacks it) — the pin
369
+ * registry's lazy-prune probe and the §2.5 stuck-pin tripwire input. Non-throwing on an
370
+ * unknown source (a removed room), so pruning after a whole-source removal just works. */
371
+ heldBackState(
372
+ table: string,
373
+ sourceKey: string,
374
+ probe: unknown[],
375
+ ): { absent: boolean; baseline: unknown[] | undefined } | undefined {
376
+ return this.db.heldBackState(table, sourceKey, probe);
377
+ }
378
+
379
+ /** Freeze a physical source — the D4 downgrade-ghost stub (wins-if-present in the merge). */
380
+ freezeSource(table: string, sourceKey: string): void {
381
+ this.db.freezeSource(table, sourceKey);
382
+ }
383
+
384
+ /** The source currently serving `row`'s pk on `table` — `"daemon"`, a room key, or `undefined`
385
+ * when no source holds the pk (Slice H-i routing probe #1, design §3.2 #3). `row` is a
386
+ * full-width positional row (the pk derives from it — same marshaling as {@link holdBack});
387
+ * an unknown table throws loudly. Reads the LIVE visible state, overlay-first (§7.3): an open
388
+ * write txn's staged-but-uncommitted rows are NOT consulted (they have not routed yet), which
389
+ * is exactly the pre-commit view the §3 routing proof decides on — a present read is proven
390
+ * room-covered iff this reports the occupied room. */
391
+ provenanceOf(table: string, row: unknown[]): string | undefined {
392
+ return this.db.provenanceOf(table, row);
393
+ }
394
+
395
+ /** Evaluate the REGISTERED writable scope of `table`'s room source `roomKey` against a
396
+ * caller-supplied full-width row (Slice H-i routing probe #2, design §3.1): a prediction-run
397
+ * write is proven room-covered iff this is `true` on the written row. Runs the SAME compiled
398
+ * predicate the merge's winner tiering uses (the {@link WritableDescriptor} registered at
399
+ * {@link addRoomSource}), so routing can never disagree with the engine; `all` ⇒ always true,
400
+ * `none` ⇒ always false, and a frozen source still answers (the predicate is static). Throws
401
+ * loudly on an unknown table, a room key not attached to the table, a wrong-width row, or
402
+ * `"daemon"` (which has no writable scope). */
403
+ writableMatches(table: string, roomKey: string, row: unknown[]): boolean {
404
+ return this.db.writableMatches(table, roomKey, row);
405
+ }
260
406
  }
261
407
 
262
408
  /** Convenience: init the wasm + return a ready local {@link Store}. */