@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 +14 -38
- package/dist/index.d.ts +114 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +107 -13
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/pkg/rindle.d.ts +98 -3
- package/pkg/rindle.js +210 -3
- package/pkg/rindle_bg.wasm +0 -0
- package/pkg/rindle_bg.wasm.d.ts +11 -1
- package/src/index.ts +160 -14
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
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
|
|
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>>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAmB,KAAK,EAAa,MAAM,gBAAgB,CAAC;AACnE,OAAO,KAAK,EACV,GAAG,EACH,OAAO,EACP,WAAW,EACX,OAAO,EACP,QAAQ,EACR,OAAO,EACP,MAAM,EACP,MAAM,gBAAgB,CAAC;AAExB,cAAc,gBAAgB,CAAC;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
195
|
-
* buffering.
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
|
|
199
|
-
|
|
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":"AAAA,yFAAyF;AACzF,kGAAkG;AAClG,6CAA6C;AAC7C,EAAE;AACF,yFAAyF;AACzF,yFAAyF;AACzF,yFAAyF;AAEzF,OAAO,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
*
|
package/pkg/rindle_bg.wasm
CHANGED
|
Binary file
|
package/pkg/rindle_bg.wasm.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
247
|
-
* buffering.
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
|
|
251
|
-
|
|
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}. */
|