@rindle/client 0.1.5 → 0.2.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 +70 -5
- package/dist/query.d.ts +96 -31
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +157 -7
- package/dist/query.js.map +1 -1
- package/dist/store.d.ts +11 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +58 -1
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js +32 -19
- package/dist/view.js.map +1 -1
- package/package.json +1 -1
- package/src/query.ts +371 -47
- package/src/store.ts +70 -1
- package/src/types.ts +5 -2
- package/src/view.ts +36 -20
package/src/types.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// Cells cross the boundary BARE (the host has a typed schema, so per-column types are
|
|
4
4
|
// known here, not per-cell). JSON columns cross as their raw JSON string.
|
|
5
5
|
|
|
6
|
+
import type { Ast } from "./ast.ts";
|
|
7
|
+
|
|
6
8
|
/** A bare wire cell. (JSON columns arrive as their raw JSON string.) */
|
|
7
9
|
export type WireValue = number | string | boolean | null;
|
|
8
10
|
|
|
@@ -143,8 +145,9 @@ export interface Backend {
|
|
|
143
145
|
unregisterQuery(queryId: QueryId): void;
|
|
144
146
|
/** Optional split path for local-first backends: retain/release a named remote footprint
|
|
145
147
|
* without creating another local materialized view. `localQueryId`, when provided, names
|
|
146
|
-
* the local AST view this remote footprint feeds.
|
|
147
|
-
|
|
148
|
+
* the local AST view this remote footprint feeds. `ast`, when provided, gives the backend
|
|
149
|
+
* the local schema context for sync-only retains that still need aggregate/table setup. */
|
|
150
|
+
retainRemoteQuery?(queryId: QueryId, remote: RemoteQuery, localQueryId?: QueryId, ast?: Ast): void;
|
|
148
151
|
releaseRemoteQuery?(queryId: QueryId): void;
|
|
149
152
|
/** Local: applies now (changes flow back on the stream). Remote: sends to the server. */
|
|
150
153
|
mutate(mutations: Mutation[]): Promise<void>;
|
package/src/view.ts
CHANGED
|
@@ -124,6 +124,10 @@ function cloneNode(n: Node): Node {
|
|
|
124
124
|
return { row: n.row.slice(), rc: n.rc, rels: n.rels.map((r) => r.map(cloneNode)), out: null };
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
function rowsEqual(a: WireValue[], b: WireValue[]): boolean {
|
|
128
|
+
return a.length === b.length && a.every((v, i) => Object.is(v, b[i]));
|
|
129
|
+
}
|
|
130
|
+
|
|
127
131
|
const EMPTY: readonly never[] = Object.freeze([]);
|
|
128
132
|
|
|
129
133
|
export class FlatArrayView<R = unknown> implements ArrayView<R> {
|
|
@@ -189,7 +193,9 @@ export class FlatArrayView<R = unknown> implements ArrayView<R> {
|
|
|
189
193
|
* while pending (changes never precede the `hello` that resets the schema). */
|
|
190
194
|
applyChanges(events: FlatChange[]): void {
|
|
191
195
|
if (this.schema === null) return;
|
|
192
|
-
|
|
196
|
+
let changed = false;
|
|
197
|
+
for (const e of events) changed = this.applyAt(this.top, this.schema, e.path, e.op, 0) || changed;
|
|
198
|
+
if (!changed) return;
|
|
193
199
|
this.dirty = true;
|
|
194
200
|
this.notify();
|
|
195
201
|
}
|
|
@@ -217,41 +223,50 @@ export class FlatArrayView<R = unknown> implements ArrayView<R> {
|
|
|
217
223
|
|
|
218
224
|
// --- apply -------------------------------------------------------------------
|
|
219
225
|
|
|
220
|
-
private applyAt(list: Node[], schema: WireSchema, path: FlatChange["path"], op: FlatOp, depth: number):
|
|
226
|
+
private applyAt(list: Node[], schema: WireSchema, path: FlatChange["path"], op: FlatOp, depth: number): boolean {
|
|
221
227
|
if (depth === path.length) {
|
|
222
|
-
this.applyOp(list, schema, op);
|
|
223
|
-
return;
|
|
228
|
+
return this.applyOp(list, schema, op);
|
|
224
229
|
}
|
|
225
230
|
const seg = path[depth];
|
|
226
231
|
const child = schema.relationships[seg.rel]?.child;
|
|
227
|
-
if (!child) return; // in-view gate: a gating slot drops the whole change
|
|
232
|
+
if (!child) return false; // in-view gate: a gating slot drops the whole change
|
|
228
233
|
const { found, index } = binarySearch(list, seg.parentRow, schema.sort);
|
|
229
234
|
if (!found) throw new Error("flat ArrayView: parent not found at path hop (inconsistent stream)");
|
|
230
235
|
const node = list[index];
|
|
231
|
-
|
|
232
|
-
|
|
236
|
+
const changed = this.applyAt(node.rels[seg.rel], child, path, op, depth + 1);
|
|
237
|
+
if (changed) node.out = null; // a descendant changed → this node must re-project its rel array
|
|
238
|
+
return changed;
|
|
233
239
|
}
|
|
234
240
|
|
|
235
|
-
private applyOp(list: Node[], schema: WireSchema, op: FlatOp):
|
|
236
|
-
if (op.tag === "add") this.applyAdd(list, schema, op.node);
|
|
237
|
-
|
|
238
|
-
|
|
241
|
+
private applyOp(list: Node[], schema: WireSchema, op: FlatOp): boolean {
|
|
242
|
+
if (op.tag === "add") return this.applyAdd(list, schema, op.node);
|
|
243
|
+
if (op.tag === "remove") return this.applyRemove(list, schema, op.row);
|
|
244
|
+
return this.applyEdit(list, schema, op.old, op.new);
|
|
239
245
|
}
|
|
240
246
|
|
|
241
|
-
private applyAdd(list: Node[], schema: WireSchema, wnode: WireNode):
|
|
247
|
+
private applyAdd(list: Node[], schema: WireSchema, wnode: WireNode): boolean {
|
|
242
248
|
const at = binarySearch(list, wnode.row, schema.sort);
|
|
243
|
-
if (at.found)
|
|
244
|
-
|
|
249
|
+
if (at.found) {
|
|
250
|
+
list[at.index].rc += 1; // duplicate path to an existing row; ignore subtree
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
list.splice(at.index, 0, buildNode(wnode, schema));
|
|
254
|
+
return true;
|
|
245
255
|
}
|
|
246
256
|
|
|
247
|
-
private applyRemove(list: Node[], schema: WireSchema, row: WireValue[]):
|
|
257
|
+
private applyRemove(list: Node[], schema: WireSchema, row: WireValue[]): boolean {
|
|
248
258
|
const at = binarySearch(list, row, schema.sort);
|
|
249
259
|
if (!at.found) throw new Error("flat ArrayView: remove of a non-existent node");
|
|
250
|
-
if (list[at.index].rc === 1)
|
|
251
|
-
|
|
260
|
+
if (list[at.index].rc === 1) {
|
|
261
|
+
list.splice(at.index, 1);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
list[at.index].rc -= 1;
|
|
265
|
+
return false;
|
|
252
266
|
}
|
|
253
267
|
|
|
254
|
-
private applyEdit(list: Node[], schema: WireSchema, oldRow: WireValue[], newRow: WireValue[]):
|
|
268
|
+
private applyEdit(list: Node[], schema: WireSchema, oldRow: WireValue[], newRow: WireValue[]): boolean {
|
|
269
|
+
if (rowsEqual(oldRow, newRow)) return false;
|
|
255
270
|
const sort = schema.sort;
|
|
256
271
|
if (compareRows(oldRow, newRow, sort) === 0) {
|
|
257
272
|
// Sort key unchanged → edit in place (keeps position + children + rc).
|
|
@@ -259,7 +274,7 @@ export class FlatArrayView<R = unknown> implements ArrayView<R> {
|
|
|
259
274
|
if (!at.found) throw new Error("flat ArrayView: edit of a non-existent node");
|
|
260
275
|
list[at.index].row = newRow;
|
|
261
276
|
list[at.index].out = null;
|
|
262
|
-
return;
|
|
277
|
+
return true;
|
|
263
278
|
}
|
|
264
279
|
|
|
265
280
|
// Sort key changed → the row may move; rc may be > 1.
|
|
@@ -276,7 +291,7 @@ export class FlatArrayView<R = unknown> implements ArrayView<R> {
|
|
|
276
291
|
if (oldRc === 1 && (pos === oldPos || pos - 1 === oldPos)) {
|
|
277
292
|
list[oldPos].row = newRow;
|
|
278
293
|
list[oldPos].out = null;
|
|
279
|
-
return;
|
|
294
|
+
return true;
|
|
280
295
|
}
|
|
281
296
|
|
|
282
297
|
// General move.
|
|
@@ -301,6 +316,7 @@ export class FlatArrayView<R = unknown> implements ArrayView<R> {
|
|
|
301
316
|
oldEntry.rc = 1;
|
|
302
317
|
list.splice(adjusted, 0, oldEntry);
|
|
303
318
|
}
|
|
319
|
+
return true;
|
|
304
320
|
}
|
|
305
321
|
|
|
306
322
|
// --- projection (memoized, structurally shared) -----------------------------
|