@rindle/normalized 0.1.0-rc.5

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.
@@ -0,0 +1,259 @@
1
+ // NormalizedBackend — the composition that turns a NORMALIZED server stream into the flat
2
+ // `Backend` seam the existing `Store` already drives (NORMALIZED-CHANGES-DESIGN.md §5/§7).
3
+ //
4
+ // A normalized client runs its OWN local engine and queries over its base tables. This
5
+ // backend wires that up by composing three pieces behind one `Backend`:
6
+ // - a local `@rindle/wasm` engine (the base tables + local IVM that materializes results),
7
+ // - `NormalizedSync` (cross-query refcount → net base-table mutations), and
8
+ // - a `NormalizedSource` (the server's per-query normalized stream).
9
+ //
10
+ // The flow per query: `registerQuery` registers it BOTH on the local engine (→ an empty
11
+ // `FlatArrayView`) and on the server (→ its normalized footprint stream). Each normalized
12
+ // batch folds through `NormalizedSync` into net base mutations, which are applied to the
13
+ // local engine — whose own flat change stream then updates every affected view. Because the
14
+ // local engine fans a base write to ALL local queries, a row synced for one query updates
15
+ // any other query that reads it (the "local query resolution" the design is built for).
16
+ //
17
+ // Since this implements `Backend`, `new Store(schema, new NormalizedBackend(...))` reuses the
18
+ // whole Store/ArrayView machinery unchanged — the normalized path adds composition, not a
19
+ // second materialization layer.
20
+ import { normalizedTableSchemas, Store, tableSpec } from "@rindle/client";
21
+ import { WasmBackend } from "@rindle/wasm";
22
+ import { aggTableSchemas, rewriteAggregates } from "./agg-table.js";
23
+ import { NormalizedSync } from "./sync.js";
24
+ /** Each table's primary-key column indices, from the typed schema. */
25
+ function pkColsFromSchema(schema) {
26
+ const out = {};
27
+ for (const name of Object.keys(schema.tables))
28
+ out[name] = tableSpec(schema.tables[name]).primaryKey;
29
+ return out;
30
+ }
31
+ /** Each table's FULL column count (the union-row width), from the typed schema. */
32
+ function colCountsFromSchema(schema) {
33
+ const out = {};
34
+ for (const name of Object.keys(schema.tables))
35
+ out[name] = tableSpec(schema.tables[name]).columns.length;
36
+ return out;
37
+ }
38
+ /** Each table's column name → base ColId, from the typed schema (for mapping a projected
39
+ * hello's columns back to base positions, PROJECTION-SUPPORT-DESIGN.md §5.2). */
40
+ function colIndexFromSchema(schema) {
41
+ const out = {};
42
+ for (const name of Object.keys(schema.tables)) {
43
+ const cols = tableSpec(schema.tables[name]).columns;
44
+ out[name] = new Map(cols.map((c, i) => [c, i]));
45
+ }
46
+ return out;
47
+ }
48
+ export class NormalizedBackend {
49
+ local;
50
+ sync;
51
+ source;
52
+ handler = () => { };
53
+ remoteSubs = new Map();
54
+ localToRemote = new Map();
55
+ /** The client's OWN typed per-table schemas (for CRIT#4 validation), fixed at construction. */
56
+ clientTables;
57
+ /** Synthetic aggregate tables (`__agg_*`) registered so far, by name (§3.3). Per aggregate
58
+ * DEFINITION (not per query), so two queries over the same count share one table. */
59
+ synthetic = new Map();
60
+ /** table → (column name → base ColId), for mapping a projected hello to base positions. */
61
+ colIndex;
62
+ /** table → full column count, to detect whether a hello's table is projected. */
63
+ colCounts;
64
+ /** Synthetic table name → how many registered queries reference it. Materialized on `0→1`,
65
+ * reclaimed (engine source + refcount layer) on `1→0` — so aggregate state is not permanent (§4). */
66
+ syntheticRefs = new Map();
67
+ /** queryId → the synthetic tables it referenced at registration, to decrement on teardown. */
68
+ queryAggTables = new Map();
69
+ constructor(schema, source) {
70
+ this.local = new WasmBackend(schema);
71
+ // The local engine's flat stream IS this backend's stream upward (→ the Store's views).
72
+ this.local.onEvent((qid, ev) => this.handler(qid, ev));
73
+ this.colIndex = colIndexFromSchema(schema);
74
+ this.colCounts = colCountsFromSchema(schema);
75
+ this.sync = new NormalizedSync(pkColsFromSchema(schema), this.colCounts);
76
+ this.source = source;
77
+ // Hand the source our OWN typed schema so it validates each server hello against it and
78
+ // rejects a column-order / PK skew instead of silently transposing positional cells (CRIT#4).
79
+ this.clientTables = normalizedTableSchemas(schema);
80
+ this.source.expectClientSchema?.(this.clientTables);
81
+ this.source.onNormalized((qid, ev) => this.onNormalized(qid, ev));
82
+ }
83
+ registerQuery(qid, ast, remote) {
84
+ // A relationship aggregate (`count(child)`) is DISPLAYED from a server-authoritative
85
+ // synthetic base table, not recomputed locally (AGGREGATE-SYNC-DESIGN.md §3.3): register
86
+ // that table (engine + refcount layer + hello validation), then drive the local engine
87
+ // off a rewritten AST whose `count` relationships read it with a plain projected join.
88
+ this.ensureSyntheticTables(qid, ast);
89
+ // Local first: builds the (empty) view synchronously. Then the server stream hydrates it.
90
+ this.local.registerQuery(qid, rewriteAggregates(ast));
91
+ if (remote)
92
+ this.retainRemote(qid, remote);
93
+ }
94
+ /** Register every synthetic aggregate table `ast` needs that we haven't seen yet: on the
95
+ * local engine (so it can join to it), on `NormalizedSync` (so its rows refcount/GC by
96
+ * group key), and into the source's expected-schema set (so the server's `hello` — which
97
+ * advertises the same table — passes CRIT#4 validation, the client deriving the schema
98
+ * identically). Idempotent across queries that share an aggregate definition. */
99
+ ensureSyntheticTables(qid, ast) {
100
+ if (this.queryAggTables.has(qid))
101
+ return; // idempotent per qid
102
+ let added = false;
103
+ const names = [];
104
+ for (const t of aggTableSchemas(ast)) {
105
+ names.push(t.name);
106
+ const prev = this.syntheticRefs.get(t.name) ?? 0;
107
+ this.syntheticRefs.set(t.name, prev + 1);
108
+ if (prev > 0)
109
+ continue; // another query already materialized it — just refcount
110
+ this.synthetic.set(t.name, t);
111
+ this.local.registerTable(t.name, { columns: t.columns, primaryKey: t.primaryKey });
112
+ this.sync.registerTable(t.name, t.primaryKey);
113
+ added = true;
114
+ }
115
+ if (names.length)
116
+ this.queryAggTables.set(qid, names);
117
+ if (added)
118
+ this.source.expectClientSchema?.([...this.clientTables, ...this.synthetic.values()]);
119
+ }
120
+ /** Decrement each synthetic table query `qid` referenced; remove the ones that reach 0 (no
121
+ * reader left) from the engine + refcount layer — aggregate state reclaimed, not permanent
122
+ * (§4). Runs AFTER `local.unregisterQuery(qid)` so the source has no live connection when
123
+ * `unregisterTable` frees it. */
124
+ releaseSyntheticTables(qid) {
125
+ const names = this.queryAggTables.get(qid);
126
+ if (!names)
127
+ return;
128
+ this.queryAggTables.delete(qid);
129
+ let removed = false;
130
+ for (const name of names) {
131
+ const next = (this.syntheticRefs.get(name) ?? 1) - 1;
132
+ if (next > 0) {
133
+ this.syntheticRefs.set(name, next);
134
+ continue;
135
+ }
136
+ this.syntheticRefs.delete(name);
137
+ this.local.unregisterTable(name);
138
+ this.sync.unregisterTable(name);
139
+ this.synthetic.delete(name);
140
+ removed = true;
141
+ }
142
+ if (removed)
143
+ this.source.expectClientSchema?.([...this.clientTables, ...this.synthetic.values()]);
144
+ }
145
+ unregisterQuery(qid) {
146
+ const remoteQid = this.releaseRemote(qid);
147
+ if (remoteQid !== undefined) {
148
+ // Drop this remote footprint's refcounts; rows referenced by no other named query are
149
+ // GC'd from the local base tables (→ other views shrink if they shared them).
150
+ const muts = this.sync.dropQuery(remoteQid);
151
+ if (muts.length)
152
+ void this.local.mutate(muts);
153
+ }
154
+ this.local.unregisterQuery(qid);
155
+ // Pipeline gone (no live conn) + `__agg` rows GC'd above → free any now-unread synthetic table.
156
+ this.releaseSyntheticTables(qid);
157
+ }
158
+ retainRemoteQuery(qid, remote) {
159
+ this.retainRemote(qid, remote);
160
+ }
161
+ releaseRemoteQuery(qid) {
162
+ const remoteQid = this.releaseRemote(qid);
163
+ if (remoteQid === undefined)
164
+ return;
165
+ const muts = this.sync.dropQuery(remoteQid);
166
+ if (muts.length)
167
+ void this.local.mutate(muts);
168
+ }
169
+ /** Writes are authoritative-only here: send to the server, the stream reconciles locally.
170
+ * (Optimistic local apply + rebase is Slice 6.) */
171
+ mutate(mutations) {
172
+ return this.source.mutate(mutations);
173
+ }
174
+ onEvent(handler) {
175
+ this.handler = handler;
176
+ }
177
+ onNormalized(qid, ev) {
178
+ // The slim hello carries table schemas + fingerprint; envelope validation (epoch/seq/gap)
179
+ // is the source's job (the ws Subscriber, §5.3) — it precedes every (re)hydrate snapshot.
180
+ if (ev.type === "hello") {
181
+ // Learn this query's per-table column map (PROJECTION-SUPPORT-DESIGN.md §5.2): map each
182
+ // advertised column to its base ColId BY NAME. The hello may carry FEWER columns than the
183
+ // client's schema (a projection) or MORE (an EXPANDED server table mid an
184
+ // `expand-then-contract` migration) — a column the client lacks maps to `-1`, a DROP
185
+ // sentinel the sync layer discards. Register the map so the sync layer scatters the rows
186
+ // into the shared full-width union; a table whose map is an in-order, drop-free full width
187
+ // IS '*' (a verbatim full row) and is left unregistered. Idempotent across re-hydrate epochs.
188
+ for (const t of ev.tables) {
189
+ const full = this.colCounts[t.name];
190
+ if (full === undefined)
191
+ continue; // unknown/synthetic table → '*' (full presence)
192
+ const index = this.colIndex[t.name];
193
+ const cols = t.columns.map((name) => index?.get(name) ?? -1);
194
+ // Register a non-trivial map; otherwise revert to '*' — and CLEAR any stale map a prior
195
+ // epoch left (a server that expanded then contracted back), so the now-exact rows don't
196
+ // scatter through a `-1`-bearing layout (silent cell corruption).
197
+ if (cols.length !== full || cols.some((c, i) => c !== i))
198
+ this.sync.registerProjection(qid, t.name, cols);
199
+ else
200
+ this.sync.unregisterProjection(qid, t.name);
201
+ }
202
+ return;
203
+ }
204
+ // A `snapshot` is the seq-0 baseline — initial OR a re-hydrate under a new epoch (§5.3). Both
205
+ // go through `rehydrate` (a footprint DIFF): for the first one the footprint is empty so it
206
+ // degenerates to all-adds; for a re-hydrate it removes rows that left during the gap. A
207
+ // `batch` is an incremental delta → `applyBatch`.
208
+ const muts = ev.type === "snapshot" ? this.sync.rehydrate(qid, ev.ops) : this.sync.applyBatch(qid, ev.ops);
209
+ if (muts.length)
210
+ void this.local.mutate(muts); // → local engine → flat stream → views
211
+ }
212
+ retainRemote(localQid, remote) {
213
+ const key = remoteKey(remote);
214
+ let sub = this.remoteSubs.get(key);
215
+ if (!sub) {
216
+ sub = { sourceQid: localQid, remote, refCount: 0 };
217
+ this.remoteSubs.set(key, sub);
218
+ this.source.registerQuery(sub.sourceQid, remote);
219
+ }
220
+ sub.refCount++;
221
+ this.localToRemote.set(localQid, key);
222
+ }
223
+ releaseRemote(localQid) {
224
+ const key = this.localToRemote.get(localQid);
225
+ if (!key)
226
+ return undefined;
227
+ this.localToRemote.delete(localQid);
228
+ const sub = this.remoteSubs.get(key);
229
+ if (!sub)
230
+ return undefined;
231
+ sub.refCount--;
232
+ if (sub.refCount > 0)
233
+ return undefined;
234
+ this.source.unregisterQuery(sub.sourceQid);
235
+ this.remoteSubs.delete(key);
236
+ return sub.sourceQid;
237
+ }
238
+ }
239
+ function remoteKey(remote) {
240
+ return stableJson([remote.name, remote.args]);
241
+ }
242
+ function stableJson(value) {
243
+ if (value === null || typeof value !== "object")
244
+ return JSON.stringify(value);
245
+ if (Array.isArray(value))
246
+ return `[${value.map(stableJson).join(",")}]`;
247
+ const obj = value;
248
+ return `{${Object.keys(obj)
249
+ .sort()
250
+ .map((k) => `${JSON.stringify(k)}:${stableJson(obj[k])}`)
251
+ .join(",")}}`;
252
+ }
253
+ /** A local-first {@link Store} whose base tables are fed by a server's normalized stream.
254
+ * The returned Store is the ordinary `@rindle/client` Store — `store.query…materialize()` and
255
+ * `store.write(…)` work as always; only the backend composition differs. */
256
+ export function createNormalizedStore(schema, source) {
257
+ return new Store(schema, new NormalizedBackend(schema, source));
258
+ }
259
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { NormalizedSync } from "./sync.ts";
2
+ export type { ColCounts, NormalizedOp, PkCols } from "./sync.ts";
3
+ export { aggTableName, aggTableSchemas, rewriteAggregates } from "./agg-table.ts";
4
+ export { NormalizedBackend, createNormalizedStore } from "./backend.ts";
5
+ export type { NormalizedEvent, NormalizedSource, NormalizedTableSchema } from "./backend.ts";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAIjE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAElF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACxE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // @rindle/normalized — the normalized local-first client glue (NORMALIZED-CHANGES-DESIGN.md §5/§7).
2
+ //
3
+ // - `NormalizedSync` (Slice 4): the cross-query refcount + GC layer + the `NormalizedOp` wire type.
4
+ // - `NormalizedBackend` / `createNormalizedStore` (Slice 5): compose a NORMALIZED server stream
5
+ // (a `NormalizedSource`) with a local `@rindle/wasm` engine, so the ordinary `Store`/`ArrayView`
6
+ // materializes results from base tables fed by the server.
7
+ export { NormalizedSync } from "./sync.js";
8
+ // Synthetic aggregate tables (§3.3): the byte-exact twin of Rust `agg_table_name` /
9
+ // `agg_table_schemas`, plus the local-engine AST rewrite. Exported for tests + advanced glue.
10
+ export { aggTableName, aggTableSchemas, rewriteAggregates } from "./agg-table.js";
11
+ export { NormalizedBackend, createNormalizedStore } from "./backend.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,EAAE;AACF,oGAAoG;AACpG,gGAAgG;AAChG,mGAAmG;AACnG,6DAA6D;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C,oFAAoF;AACpF,8FAA8F;AAC9F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAElF,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC"}
package/dist/sync.d.ts ADDED
@@ -0,0 +1,127 @@
1
+ import type { Mutation, NormalizedOp, QueryId, WireValue } from "@rindle/client";
2
+ export type { NormalizedOp } from "@rindle/client";
3
+ /** Per-table primary-key column indices (into a positional row) — how `(table, pk)` is keyed. */
4
+ export type PkCols = Record<string, number[]>;
5
+ /** Per-table FULL column count — the width of a base/union row. Needed to scatter a projected
6
+ * (narrower) wire row into the shared positional layout (PROJECTION-SUPPORT-DESIGN.md §5.3). */
7
+ export type ColCounts = Record<string, number>;
8
+ /** A union-row cell: a present wire value, or `ABSENT` (`undefined`) when the column is not
9
+ * present on the shared row. */
10
+ type UnionCell = WireValue | undefined;
11
+ export declare class NormalizedSync {
12
+ private readonly pkCols;
13
+ /** Per-table full width, for scattering projected rows. Empty ⇒ projection unsupported
14
+ * (every query must be `'*'`), which is the pre-projection behavior. */
15
+ private readonly colCounts;
16
+ /** `(table, pk)` key → the one shared base entry. */
17
+ private readonly base;
18
+ /** queryId → the set of `(table, pk)` keys that query currently footprints. */
19
+ private readonly qfoot;
20
+ /** queryId → (table → the base ColIds it contributes for that table, in the order its wire
21
+ * rows are positional against — its `required_cols` for the table, §4.1). A query spans
22
+ * multiple base tables (root + related/EXISTS children), each projected independently; a
23
+ * table absent from the inner map ⇒ that query syncs it `'*'` (all columns, full width). */
24
+ private readonly qcols;
25
+ constructor(pkCols: PkCols, colCounts?: ColCounts);
26
+ /**
27
+ * Register a query's projection for one base table (PROJECTION-SUPPORT-DESIGN.md §4.1): the
28
+ * base ColIds it contributes for `table`, in the order that table's wire rows are positional
29
+ * against. Omit a table (or never call) for a `'*'` table — it then contributes every column
30
+ * and its rows are full width. Must be called before the query's first batch.
31
+ *
32
+ * A `cols[i] < 0` entry is a DROP sentinel: wire column `i` is an EXPANDED server column the
33
+ * client doesn't have (the server side of an `expand-then-contract` migration), so its cell is
34
+ * discarded rather than scattered. The query still contributes only its real (`>= 0`) ColIds.
35
+ */
36
+ registerProjection(queryId: QueryId, table: string, cols: number[]): void;
37
+ /**
38
+ * Drop a query's projection for one table, reverting it to `'*'` (full presence, rows scattered
39
+ * verbatim). The inverse of {@link registerProjection}; a no-op if none was registered. Needed
40
+ * because `qcols` persists across re-hydrate epochs: if a live subscription's hello narrows from
41
+ * an EXPANDED layout (a `-1`-bearing map) back to an exact full-width one, the stale map must be
42
+ * cleared or it would mis-scatter the now-exact rows. A no-op for an unprojected (`'*'`) table.
43
+ */
44
+ unregisterProjection(queryId: QueryId, table: string): void;
45
+ /**
46
+ * Register a table's primary-key columns after construction — for a **synthetic aggregate
47
+ * table** (`AGGREGATE-SYNC-DESIGN.md` §3.3): a relationship `count` is synced as a
48
+ * server-authoritative `__agg_*` base table that is not in the client's typed schema, so
49
+ * the backend registers it here (and on the local engine) as queries that use it arrive.
50
+ * Idempotent — re-registering the same table (a second query over the same aggregate)
51
+ * is a no-op. Once registered, its rows refcount/GC exactly like any base table.
52
+ */
53
+ registerTable(table: string, primaryKey: number[]): void;
54
+ /**
55
+ * The inverse of {@link registerTable} for a synthetic aggregate table whose last
56
+ * referencing query is gone (`AGGREGATE-SYNC-DESIGN.md` §4): drop its primary-key
57
+ * registration so the table is unknown again. A balanced stream has already GC'd its rows
58
+ * at the `1→0` transition (via {@link dropQuery}); defensively this also sweeps any residual
59
+ * base rows + per-query footprint entries for the table, so a later re-registration of the
60
+ * same name starts clean. A no-op for an unregistered table.
61
+ */
62
+ unregisterTable(table: string): void;
63
+ /**
64
+ * Apply one query's normalized batch (its hydrate snapshot or one transaction's ops) and
65
+ * return the NET base-table mutations to commit to the wasm `Db` in a single transaction.
66
+ * Cross-query refcount + per-query dedup + column union (§4, §5):
67
+ * - `add`: counted into this query's footprint once; on the base `0→1` transition the row
68
+ * enters at this query's projection; on a `1→N` transition the query's columns are merged
69
+ * into the shared union (widen) and an `edit` is forwarded if the union changed.
70
+ * - `remove`: on the base `N→0` transition the row leaves; on `N→M>0` the union is recomputed
71
+ * from the remaining queries and an `edit` narrows the shared row.
72
+ * - `edit`: column-merge this query's cells into the shared union; forward once (idempotent
73
+ * across queries — same source row, same values).
74
+ */
75
+ applyBatch(queryId: QueryId, ops: NormalizedOp[]): Mutation[];
76
+ /**
77
+ * Re-hydrate one query under a new epoch (§5.3): the server re-sent `queryId`'s whole
78
+ * footprint as seq-0 `add`s. Diff the new footprint against the query's current one —
79
+ * rows only in the old set leave (refcount out, GC/narrow), rows only in the new set enter
80
+ * (refcount in / widen), and an intersecting row whose value changed during the gap is an
81
+ * edit. Only `queryId`'s references move; other queries' counts are untouched. Returns the
82
+ * net mutations to commit.
83
+ */
84
+ rehydrate(queryId: QueryId, snapshot: NormalizedOp[]): Mutation[];
85
+ /**
86
+ * Drop a query (§5.1): decrement its footprint's refcounts, GC each row at the last
87
+ * reference (or narrow the union if others remain), and forget the query. `O(footprint)`,
88
+ * no per-row scan. Returns the net mutations. (The caller also tells the server to
89
+ * deregister the stream.)
90
+ */
91
+ dropQuery(queryId: QueryId): Mutation[];
92
+ /** The number of distinct base rows currently synced into the local store. */
93
+ baseSize(): number;
94
+ /** How many queries currently reference `(table, row)` (0 if absent). The lookup keys by PK,
95
+ * so a projected `row` need only carry its PK columns at the base positions. */
96
+ refCount(table: string, row: UnionCell[]): number;
97
+ /**
98
+ * The synced (server-authoritative) row currently held for `(table, pkCells)`, or
99
+ * `undefined` if no query references it. `pkCells` are the primary-key cells in
100
+ * `primaryKey` order (NOT a full row) — the same key the cross-query refcount uses.
101
+ *
102
+ * The optimistic aggregate overlay (`AGGREGATE-SYNC-DESIGN.md` §4) reads the server's
103
+ * `__agg` count cell through this so it can compute `displayed = server_base ⊕ delta`
104
+ * from the authoritative base rather than re-deriving it from the local engine's head
105
+ * (which already carries the optimistic layer — a torn read).
106
+ */
107
+ baseRow(table: string, pkCells: WireValue[]): (WireValue | undefined)[] | undefined;
108
+ private footOf;
109
+ /** Scatter a query's (possibly narrower) wire row for `table` into the shared full-width
110
+ * positional layout. No registered projection for `(queryId, table)` ⇒ a `'*'` table whose
111
+ * row is already full width (returned as-is). Otherwise allocate a full-width row of
112
+ * `ABSENT` and place `row[i]` at `cols[i]`. */
113
+ private scatter;
114
+ /** Column-merge `incoming`'s present cells over `e.row` (a widen / pure-value change) and
115
+ * forward a single `edit` if the union changed. Both rows are full width. */
116
+ private mergeInto;
117
+ private add;
118
+ private remove;
119
+ private removeKey;
120
+ private edit;
121
+ /** The columns present on a `table` row footprinted by `queries`: the union of their
122
+ * projections for that table. `null` ⇒ all columns present (some referencing query syncs
123
+ * the table `'*'`), so the row is full. */
124
+ private presentCols;
125
+ private key;
126
+ }
127
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIjF,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,iGAAiG;AACjG,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAE9C;iGACiG;AACjG,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAU/C;iCACiC;AACjC,KAAK,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAmBvC,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC;6EACyE;IACzE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,qDAAqD;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IACrD,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IACzD;;;iGAG6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6C;gBAEvD,MAAM,EAAE,MAAM,EAAE,SAAS,GAAE,SAAc;IAKrD;;;;;;;;;OASG;IACH,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IASzE;;;;;;OAMG;IACH,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3D;;;;;;;OAOG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI;IAIxD;;;;;;;OAOG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAWpC;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,QAAQ,EAAE;IAa7D;;;;;;;OAOG;IACH,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,QAAQ,EAAE;IA2BjE;;;;;OAKG;IACH,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,EAAE;IAYvC,8EAA8E;IAC9E,QAAQ,IAAI,MAAM;IAIlB;qFACiF;IACjF,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,MAAM;IAIjD;;;;;;;;;OASG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,EAAE,GAAG,SAAS;IAYnF,OAAO,CAAC,MAAM;IASd;;;oDAGgD;IAChD,OAAO,CAAC,OAAO;IAef;kFAC8E;IAC9E,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,GAAG;IAiBX,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,IAAI;IAQZ;;gDAE4C;IAC5C,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,GAAG;CAKZ"}