@rindle/client 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.
- package/LICENSE +201 -0
- package/README.md +65 -0
- package/dist/ast.d.ts +83 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +5 -0
- package/dist/ast.js.map +1 -0
- package/dist/compare.d.ts +17 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +81 -0
- package/dist/compare.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/key.d.ts +2 -0
- package/dist/key.d.ts.map +1 -0
- package/dist/key.js +26 -0
- package/dist/key.js.map +1 -0
- package/dist/operators.d.ts +39 -0
- package/dist/operators.d.ts.map +1 -0
- package/dist/operators.js +45 -0
- package/dist/operators.js.map +1 -0
- package/dist/query.d.ts +280 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +348 -0
- package/dist/query.js.map +1 -0
- package/dist/schema.d.ts +111 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +92 -0
- package/dist/schema.js.map +1 -0
- package/dist/ssr.d.ts +73 -0
- package/dist/ssr.d.ts.map +1 -0
- package/dist/ssr.js +66 -0
- package/dist/ssr.js.map +1 -0
- package/dist/store.d.ts +90 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +225 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +250 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/view.d.ts +88 -0
- package/dist/view.d.ts.map +1 -0
- package/dist/view.js +294 -0
- package/dist/view.js.map +1 -0
- package/package.json +36 -0
- package/src/ast.ts +94 -0
- package/src/compare.ts +85 -0
- package/src/index.ts +57 -0
- package/src/key.ts +23 -0
- package/src/operators.ts +68 -0
- package/src/query.ts +734 -0
- package/src/schema.ts +181 -0
- package/src/ssr.ts +115 -0
- package/src/store.ts +279 -0
- package/src/types.ts +234 -0
- package/src/view.ts +348 -0
package/dist/view.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// The JS ArrayView — the materialized, typed result tree a backend's ChangeEvent stream
|
|
2
|
+
// folds into (WASM-CLIENT-DESIGN.md §7, §8). A faithful port of the View's `apply_change`
|
|
3
|
+
// (src/view.rs / src/flat_receiver.rs), the §6 receiver contract of FLAT-CHANGES-DESIGN.md:
|
|
4
|
+
// descend the path with the in-view gate, then Add / Remove / Edit (rc + the edit-move
|
|
5
|
+
// ghost/adjusted-position/merge protocol) located by value-driven binary search.
|
|
6
|
+
//
|
|
7
|
+
// Reads are cheap and reference-stable: each node memoizes its projected output (`out`),
|
|
8
|
+
// invalidated only along the root→mutation spine, so `.data` re-projects only what changed
|
|
9
|
+
// and untouched subtrees keep their object identity (React/Solid memoize on it).
|
|
10
|
+
import { compareRows } from "./compare.js";
|
|
11
|
+
/** Wrap a plural {@link FlatArrayView} as a {@link SingularArrayView} for a `.one()` query
|
|
12
|
+
* (the engine caps it to `limit = 1`, so the top list holds at most one node). */
|
|
13
|
+
export class SingularView {
|
|
14
|
+
inner;
|
|
15
|
+
constructor(inner) {
|
|
16
|
+
this.inner = inner;
|
|
17
|
+
}
|
|
18
|
+
get data() {
|
|
19
|
+
return this.inner.data[0] ?? null;
|
|
20
|
+
}
|
|
21
|
+
get resultType() {
|
|
22
|
+
return this.inner.resultType;
|
|
23
|
+
}
|
|
24
|
+
subscribe(listener) {
|
|
25
|
+
return this.inner.subscribe((d) => listener(d[0] ?? null));
|
|
26
|
+
}
|
|
27
|
+
destroy() {
|
|
28
|
+
this.inner.destroy();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function binarySearch(list, row, sort) {
|
|
32
|
+
let lo = 0;
|
|
33
|
+
let hi = list.length;
|
|
34
|
+
while (lo < hi) {
|
|
35
|
+
const mid = (lo + hi) >>> 1;
|
|
36
|
+
const c = compareRows(list[mid].row, row, sort);
|
|
37
|
+
if (c < 0)
|
|
38
|
+
lo = mid + 1;
|
|
39
|
+
else if (c > 0)
|
|
40
|
+
hi = mid;
|
|
41
|
+
else
|
|
42
|
+
return { found: true, index: mid };
|
|
43
|
+
}
|
|
44
|
+
return { found: false, index: lo };
|
|
45
|
+
}
|
|
46
|
+
/** Build a fresh node (rc = 1) with its inline subtree, mirroring `init_rels_for_new_entry`:
|
|
47
|
+
* one list per declared slot; fill in-view slots from the payload (children inserted in the
|
|
48
|
+
* order shipped — NEVER re-sorted — folding same-sort-key dups to rc += 1). */
|
|
49
|
+
function buildNode(wnode, schema) {
|
|
50
|
+
const rels = schema.relationships.map(() => []);
|
|
51
|
+
for (const { rel, children } of wnode.rels) {
|
|
52
|
+
const child = schema.relationships[rel]?.child;
|
|
53
|
+
if (!child)
|
|
54
|
+
continue; // join-only / gating slot — not materialized
|
|
55
|
+
const built = [];
|
|
56
|
+
for (const c of children) {
|
|
57
|
+
const at = binarySearch(built, c.row, child.sort);
|
|
58
|
+
if (at.found)
|
|
59
|
+
built[at.index].rc += 1;
|
|
60
|
+
else
|
|
61
|
+
built.splice(at.index, 0, buildNode(c, child));
|
|
62
|
+
}
|
|
63
|
+
rels[rel] = built;
|
|
64
|
+
}
|
|
65
|
+
return { row: wnode.row, rc: 1, rels, out: null };
|
|
66
|
+
}
|
|
67
|
+
function cloneNode(n) {
|
|
68
|
+
return { row: n.row.slice(), rc: n.rc, rels: n.rels.map((r) => r.map(cloneNode)), out: null };
|
|
69
|
+
}
|
|
70
|
+
const EMPTY = Object.freeze([]);
|
|
71
|
+
export class FlatArrayView {
|
|
72
|
+
// `null` ⇒ PENDING (no schema yet): a remote backend's `hello` arrives async, so the view
|
|
73
|
+
// exists (and reads as `[]`) before its schema lands. Set by `reset` (first hello / re-hydrate).
|
|
74
|
+
schema;
|
|
75
|
+
types;
|
|
76
|
+
// SSR first-paint seed (SSR-DESIGN.md §6): pre-projected rows installed by `seed`, returned by
|
|
77
|
+
// `data` WHILE pending (no live schema yet). Cleared on `reset` — the first live `hello` —
|
|
78
|
+
// so the maintained tree takes over and the live snapshot reconciles. `null` ⇒ no seed.
|
|
79
|
+
seeded = null;
|
|
80
|
+
top = [];
|
|
81
|
+
dirty = true;
|
|
82
|
+
cached = null;
|
|
83
|
+
// `complete` by default: a backend with no server lifecycle (the in-process engine) never pushes
|
|
84
|
+
// a resultType, so its views read as authoritative. The Store overrides this for backends that do
|
|
85
|
+
// (the optimistic backend: `unknown` until hydrated, etc.).
|
|
86
|
+
rt = "complete";
|
|
87
|
+
listeners = new Set();
|
|
88
|
+
constructor(schema, types) {
|
|
89
|
+
this.schema = schema ?? null;
|
|
90
|
+
this.types = types;
|
|
91
|
+
}
|
|
92
|
+
get resultType() {
|
|
93
|
+
return this.rt;
|
|
94
|
+
}
|
|
95
|
+
/** Set the query's lifecycle state (the Store routes the backend's per-query signal here).
|
|
96
|
+
* Notifies subscribers on a change so a status-bound listener (React `useQueryStatus`) re-reads,
|
|
97
|
+
* WITHOUT re-projecting data (it is unchanged). */
|
|
98
|
+
setResultType(rt) {
|
|
99
|
+
if (this.rt === rt)
|
|
100
|
+
return;
|
|
101
|
+
this.rt = rt;
|
|
102
|
+
for (const l of this.listeners)
|
|
103
|
+
l(this.data);
|
|
104
|
+
}
|
|
105
|
+
/** (Re)bind to a schema and clear the tree IN PLACE. The first `hello` (pending → ready)
|
|
106
|
+
* and a re-hydrate (gap → new epoch — FLAT-CHANGES-DESIGN.md §2.3) both go through here, so
|
|
107
|
+
* the caller's view reference and its subscribers survive a re-subscribe. Does NOT notify —
|
|
108
|
+
* the snapshot that follows (`applyChanges`) does, avoiding an empty-then-filled flicker. */
|
|
109
|
+
reset(schema, types) {
|
|
110
|
+
this.schema = schema;
|
|
111
|
+
this.types = types;
|
|
112
|
+
this.seeded = null; // live data takes over; the seed was first-paint only
|
|
113
|
+
this.top = [];
|
|
114
|
+
this.cached = null;
|
|
115
|
+
this.dirty = true;
|
|
116
|
+
}
|
|
117
|
+
/** Install a pre-projected SSR first-paint snapshot (SSR-DESIGN.md §6). The rows are already
|
|
118
|
+
* in result shape (json columns parsed, relationships nested), so a view with no live backend
|
|
119
|
+
* (the server one-shot Store) reads them directly, and a browser view shows them until its
|
|
120
|
+
* first live `hello` (`reset`) swaps in the maintained tree. Does NOT notify — it is set at
|
|
121
|
+
* materialize time, before any subscriber, and the live snapshot that follows notifies. */
|
|
122
|
+
seed(rows) {
|
|
123
|
+
this.seeded = rows;
|
|
124
|
+
}
|
|
125
|
+
/** Apply a batch (the hydrate snapshot or one transaction's events) in order, then
|
|
126
|
+
* notify subscribers once. Order is significant (FLAT-CHANGES-DESIGN.md §5.4). A no-op
|
|
127
|
+
* while pending (changes never precede the `hello` that resets the schema). */
|
|
128
|
+
applyChanges(events) {
|
|
129
|
+
if (this.schema === null)
|
|
130
|
+
return;
|
|
131
|
+
for (const e of events)
|
|
132
|
+
this.applyAt(this.top, this.schema, e.path, e.op, 0);
|
|
133
|
+
this.dirty = true;
|
|
134
|
+
this.notify();
|
|
135
|
+
}
|
|
136
|
+
get data() {
|
|
137
|
+
if (this.schema === null)
|
|
138
|
+
return this.seeded ?? EMPTY;
|
|
139
|
+
if (!this.dirty && this.cached !== null)
|
|
140
|
+
return this.cached;
|
|
141
|
+
this.cached = this.top.map((n) => this.project(n, this.schema, this.types));
|
|
142
|
+
this.dirty = false;
|
|
143
|
+
return this.cached;
|
|
144
|
+
}
|
|
145
|
+
subscribe(listener) {
|
|
146
|
+
this.listeners.add(listener);
|
|
147
|
+
listener(this.data);
|
|
148
|
+
return () => {
|
|
149
|
+
this.listeners.delete(listener);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
destroy() {
|
|
153
|
+
this.listeners.clear();
|
|
154
|
+
this.top = [];
|
|
155
|
+
}
|
|
156
|
+
// --- apply -------------------------------------------------------------------
|
|
157
|
+
applyAt(list, schema, path, op, depth) {
|
|
158
|
+
if (depth === path.length) {
|
|
159
|
+
this.applyOp(list, schema, op);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const seg = path[depth];
|
|
163
|
+
const child = schema.relationships[seg.rel]?.child;
|
|
164
|
+
if (!child)
|
|
165
|
+
return; // in-view gate: a gating slot drops the whole change
|
|
166
|
+
const { found, index } = binarySearch(list, seg.parentRow, schema.sort);
|
|
167
|
+
if (!found)
|
|
168
|
+
throw new Error("flat ArrayView: parent not found at path hop (inconsistent stream)");
|
|
169
|
+
const node = list[index];
|
|
170
|
+
node.out = null; // a descendant changed → this node must re-project its rel array
|
|
171
|
+
this.applyAt(node.rels[seg.rel], child, path, op, depth + 1);
|
|
172
|
+
}
|
|
173
|
+
applyOp(list, schema, op) {
|
|
174
|
+
if (op.tag === "add")
|
|
175
|
+
this.applyAdd(list, schema, op.node);
|
|
176
|
+
else if (op.tag === "remove")
|
|
177
|
+
this.applyRemove(list, schema, op.row);
|
|
178
|
+
else
|
|
179
|
+
this.applyEdit(list, schema, op.old, op.new);
|
|
180
|
+
}
|
|
181
|
+
applyAdd(list, schema, wnode) {
|
|
182
|
+
const at = binarySearch(list, wnode.row, schema.sort);
|
|
183
|
+
if (at.found)
|
|
184
|
+
list[at.index].rc += 1; // duplicate path to an existing row; ignore subtree
|
|
185
|
+
else
|
|
186
|
+
list.splice(at.index, 0, buildNode(wnode, schema));
|
|
187
|
+
}
|
|
188
|
+
applyRemove(list, schema, row) {
|
|
189
|
+
const at = binarySearch(list, row, schema.sort);
|
|
190
|
+
if (!at.found)
|
|
191
|
+
throw new Error("flat ArrayView: remove of a non-existent node");
|
|
192
|
+
if (list[at.index].rc === 1)
|
|
193
|
+
list.splice(at.index, 1);
|
|
194
|
+
else
|
|
195
|
+
list[at.index].rc -= 1;
|
|
196
|
+
}
|
|
197
|
+
applyEdit(list, schema, oldRow, newRow) {
|
|
198
|
+
const sort = schema.sort;
|
|
199
|
+
if (compareRows(oldRow, newRow, sort) === 0) {
|
|
200
|
+
// Sort key unchanged → edit in place (keeps position + children + rc).
|
|
201
|
+
const at = binarySearch(list, oldRow, sort);
|
|
202
|
+
if (!at.found)
|
|
203
|
+
throw new Error("flat ArrayView: edit of a non-existent node");
|
|
204
|
+
list[at.index].row = newRow;
|
|
205
|
+
list[at.index].out = null;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// Sort key changed → the row may move; rc may be > 1.
|
|
209
|
+
const oldAt = binarySearch(list, oldRow, sort);
|
|
210
|
+
if (!oldAt.found)
|
|
211
|
+
throw new Error("flat ArrayView: edit old node does not exist");
|
|
212
|
+
const oldPos = oldAt.index;
|
|
213
|
+
const oldRc = list[oldPos].rc;
|
|
214
|
+
const raw = binarySearch(list, newRow, sort);
|
|
215
|
+
const found = raw.found;
|
|
216
|
+
const pos = raw.index;
|
|
217
|
+
const oldEntry = cloneNode(list[oldPos]); // capture (with children) BEFORE mutating
|
|
218
|
+
// Fast path: rc==1 and the row lands in the same slot after removal → edit in place.
|
|
219
|
+
if (oldRc === 1 && (pos === oldPos || pos - 1 === oldPos)) {
|
|
220
|
+
list[oldPos].row = newRow;
|
|
221
|
+
list[oldPos].out = null;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// General move.
|
|
225
|
+
const newRc = oldRc - 1;
|
|
226
|
+
let adjusted;
|
|
227
|
+
if (newRc === 0) {
|
|
228
|
+
list.splice(oldPos, 1);
|
|
229
|
+
adjusted = oldPos < pos ? pos - 1 : pos;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
list[oldPos].rc = newRc; // ghost survives for the other path(s); its row is unchanged
|
|
233
|
+
adjusted = pos;
|
|
234
|
+
}
|
|
235
|
+
if (found) {
|
|
236
|
+
// Merge into the existing destination entry (keep its children); bump its rc.
|
|
237
|
+
const existingRc = list[adjusted].rc;
|
|
238
|
+
list[adjusted].row = newRow;
|
|
239
|
+
list[adjusted].rc = existingRc + 1;
|
|
240
|
+
list[adjusted].out = null;
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
// Move the (edited) old entry — keeping its children — to the new position.
|
|
244
|
+
oldEntry.row = newRow;
|
|
245
|
+
oldEntry.rc = 1;
|
|
246
|
+
list.splice(adjusted, 0, oldEntry);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// --- projection (memoized, structurally shared) -----------------------------
|
|
250
|
+
project(node, schema, types) {
|
|
251
|
+
if (node.out !== null)
|
|
252
|
+
return node.out;
|
|
253
|
+
const obj = {};
|
|
254
|
+
for (let i = 0; i < schema.columns.length; i++) {
|
|
255
|
+
const v = node.row[i];
|
|
256
|
+
// A projected query carries an ABSENT cell (`undefined`, PROJECTION-SUPPORT-DESIGN.md
|
|
257
|
+
// §5.3) for a column it did not sync; omit it from the result object so a query never
|
|
258
|
+
// resolves a row from columns it did not select (§6). A present-and-null cell is `null`
|
|
259
|
+
// and is kept. For a `'*'` query no cell is ever `undefined`, so this is inert (§7).
|
|
260
|
+
if (v === undefined)
|
|
261
|
+
continue;
|
|
262
|
+
// convert-once: a json column's raw string is parsed to an object on store (cached in `out`).
|
|
263
|
+
obj[schema.columns[i]] = types?.columnTypes[i] === "json" && typeof v === "string" ? JSON.parse(v) : v;
|
|
264
|
+
}
|
|
265
|
+
for (const rel of schema.relationships) {
|
|
266
|
+
if (rel.child === null)
|
|
267
|
+
continue; // gating slot — not part of .data
|
|
268
|
+
const childList = node.rels[rel.slot] ?? [];
|
|
269
|
+
const ct = types?.rels[rel.slot];
|
|
270
|
+
if (rel.project) {
|
|
271
|
+
// A scalar-projected relationship aggregate (REDUCE-DESIGN.md §9): unwrap the one-row
|
|
272
|
+
// child to a bare scalar (its project.col cell), substituting the aggregate identity
|
|
273
|
+
// when empty (a childless parent). A json-typed projected column is parsed like a column.
|
|
274
|
+
const cell = childList.length > 0 ? childList[0].row[rel.project.col] : rel.project.identity;
|
|
275
|
+
const projType = ct?.columnTypes[rel.project.col];
|
|
276
|
+
obj[rel.name] = projType === "json" && typeof cell === "string" ? JSON.parse(cell) : cell;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
obj[rel.name] = rel.child.singular
|
|
280
|
+
? childList.length > 0
|
|
281
|
+
? this.project(childList[0], rel.child, ct)
|
|
282
|
+
: null
|
|
283
|
+
: childList.map((c) => this.project(c, rel.child, ct));
|
|
284
|
+
}
|
|
285
|
+
node.out = obj;
|
|
286
|
+
return obj;
|
|
287
|
+
}
|
|
288
|
+
notify() {
|
|
289
|
+
const d = this.data;
|
|
290
|
+
for (const l of this.listeners)
|
|
291
|
+
l(d);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=view.js.map
|
package/dist/view.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rindle/client",
|
|
3
|
+
"version": "0.1.0-rc.5",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/rindle-sh/rindle.git",
|
|
8
|
+
"directory": "packages/client"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"description": "Backend-agnostic flat-change client core: typed schema, query builder, ArrayView, comparator, Backend seam.",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"@rindle/source": "./src/index.ts",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.10.0",
|
|
29
|
+
"typescript": "^5.7.0"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.build.json",
|
|
33
|
+
"test": "tsc --noEmit && node --test test/*.test.ts",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/ast.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// The Zero-wire query AST — the exact JSON shape the Rust `Ast` deserializes
|
|
2
|
+
// (src/ast.rs). The builder emits these; the wasm `Db.query` (and a remote server) parse
|
|
3
|
+
// them. camelCase keys, `type`-tagged unions, ops as SQL-ish strings, untagged literals.
|
|
4
|
+
|
|
5
|
+
export type Dir = "asc" | "desc";
|
|
6
|
+
|
|
7
|
+
/** One `(field, direction)` ordering — serializes as the 2-tuple `["id", "asc"]`. */
|
|
8
|
+
export type OrderPart = [field: string, dir: Dir];
|
|
9
|
+
|
|
10
|
+
/** An untagged literal (`null | bool | number | string | array`). Numbers are one f64. */
|
|
11
|
+
export type LitValue = null | boolean | number | string | LitValue[];
|
|
12
|
+
|
|
13
|
+
/** A column reference or a literal — `type`-tagged. */
|
|
14
|
+
export type ValuePosition =
|
|
15
|
+
| { type: "column"; name: string }
|
|
16
|
+
| { type: "literal"; value: LitValue };
|
|
17
|
+
|
|
18
|
+
/** The wire comparison operators (exact strings, `src/ast.rs` `Op`). */
|
|
19
|
+
export type SimpleOp =
|
|
20
|
+
| "="
|
|
21
|
+
| "!="
|
|
22
|
+
| "<"
|
|
23
|
+
| "<="
|
|
24
|
+
| ">"
|
|
25
|
+
| ">="
|
|
26
|
+
| "IS"
|
|
27
|
+
| "IS NOT"
|
|
28
|
+
| "LIKE"
|
|
29
|
+
| "NOT LIKE"
|
|
30
|
+
| "ILIKE"
|
|
31
|
+
| "NOT ILIKE"
|
|
32
|
+
| "IN"
|
|
33
|
+
| "NOT IN";
|
|
34
|
+
|
|
35
|
+
export type ExistsOp = "EXISTS" | "NOT EXISTS";
|
|
36
|
+
|
|
37
|
+
/** The filter tree — `type`-tagged; recursive. There is no generic NOT node (negation is
|
|
38
|
+
* via the negated operators and `NOT EXISTS`, matching `src/ast.rs` `Condition`). */
|
|
39
|
+
export type Condition =
|
|
40
|
+
| { type: "simple"; op: SimpleOp; left: ValuePosition; right: ValuePosition }
|
|
41
|
+
| { type: "and"; conditions: Condition[] }
|
|
42
|
+
| { type: "or"; conditions: Condition[] }
|
|
43
|
+
| { type: "correlatedSubquery"; related: CorrelatedSubquery; op: ExistsOp; flip?: boolean; scalar?: boolean };
|
|
44
|
+
|
|
45
|
+
export interface Correlation {
|
|
46
|
+
parentField: string[];
|
|
47
|
+
childField: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A paging lower bound (`Bound`, `src/ast.rs`). `row` is a *partial* wire row — the bound
|
|
51
|
+
* columns by name (only the sort columns are read by the engine's `Skip` comparator).
|
|
52
|
+
* `exclusive` ⇒ start *after* the bound row (`Basis::After`); else *at* it (`Basis::At`). */
|
|
53
|
+
export interface Bound {
|
|
54
|
+
row: Record<string, LitValue>;
|
|
55
|
+
exclusive: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CorrelatedSubquery {
|
|
59
|
+
correlation: Correlation;
|
|
60
|
+
subquery: Ast;
|
|
61
|
+
system?: "client" | "permissions" | "test";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** An aggregate over a (correlated) subquery's rows (`REDUCE-DESIGN.md`). v1: `count(*)`.
|
|
65
|
+
* Set on a `related` subquery (`Ast.aggregate`), it marks that relationship a count
|
|
66
|
+
* aggregate the builder lowers to a scalar-projected singular relationship (§9). */
|
|
67
|
+
export type Aggregate = "count";
|
|
68
|
+
|
|
69
|
+
/** The query AST. `table` is the only required field; the builder omits empty/false fields
|
|
70
|
+
* (matching the Rust `skip_serializing_if`), which the deserializer treats as absent. */
|
|
71
|
+
export interface Ast {
|
|
72
|
+
table: string;
|
|
73
|
+
alias?: string;
|
|
74
|
+
/** Projection (PROJECTION-SUPPORT-DESIGN.md §6). Absent ⇒ select all columns; present ⇒
|
|
75
|
+
* project to just these (drives what syncs + what the view reports). Serializes only when
|
|
76
|
+
* set, matching the Rust `Ast.select`'s `skip_serializing_if`. */
|
|
77
|
+
select?: string[];
|
|
78
|
+
where?: Condition;
|
|
79
|
+
related?: CorrelatedSubquery[];
|
|
80
|
+
start?: Bound;
|
|
81
|
+
limit?: number;
|
|
82
|
+
one?: boolean;
|
|
83
|
+
/** Aggregate this (sub)query's rows instead of materializing them (`REDUCE-DESIGN.md`
|
|
84
|
+
* §9). `count` on a `related` subquery surfaces a scalar `commentCount` field; the
|
|
85
|
+
* builder lowers it to a scalar-projected singular relationship. Absent ⇒ ordinary rows. */
|
|
86
|
+
aggregate?: Aggregate;
|
|
87
|
+
/** The {@link Ast.aggregate} value is **precomputed** — supplied as rows of a (synthetic)
|
|
88
|
+
* source table rather than reduced from child rows. Set by the normalized client's AST
|
|
89
|
+
* rewrite (`AGGREGATE-SYNC-DESIGN.md` §3.3) so the local engine reads the server's count
|
|
90
|
+
* with a plain singular join + the same projection instead of a `reduce`. Only meaningful
|
|
91
|
+
* with `aggregate`; absent ⇒ `false`. */
|
|
92
|
+
aggregatePrecomputed?: boolean;
|
|
93
|
+
orderBy?: OrderPart[];
|
|
94
|
+
}
|
package/src/compare.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// The comparator — a faithful port of the engine's `compare_values` / `compare_rows`
|
|
2
|
+
// (src/value.rs §4 of FLAT-CHANGES-DESIGN.md), the single most correctness-critical unit
|
|
3
|
+
// (WASM-CLIENT-DESIGN.md §8.1). One fixed total order, NO collation.
|
|
4
|
+
//
|
|
5
|
+
// It is **value-driven** — it switches on the runtime JS type, exactly as the engine
|
|
6
|
+
// switches on the `OwnedValue` variant. Bare wire values collapse Int/Float → number and
|
|
7
|
+
// Str/Json → string, but within a sort column values are homogeneous, and number→total_cmp
|
|
8
|
+
// / string→bytewise cover both, so this matches the engine without needing column types:
|
|
9
|
+
//
|
|
10
|
+
// - null sorts FIRST (null < anything; null == null)
|
|
11
|
+
// - number: IEEE-754 totalOrder (=== Rust `f64::total_cmp`) — NOT `<` / `-`
|
|
12
|
+
// - boolean: false < true
|
|
13
|
+
// - string: UTF-8 BYTEWISE (=== SQLite BINARY / Rust `&str` Ord) — NOT `localeCompare`/`<`
|
|
14
|
+
|
|
15
|
+
import type { WireValue } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
const enc = new TextEncoder();
|
|
18
|
+
|
|
19
|
+
/** The `compare_values` / `compare_rows` algorithm-contract version (=== the engine's
|
|
20
|
+
* `wire_schema::COMPARATOR_VERSION`). A remote subscriber hard-rejects a `hello` whose
|
|
21
|
+
* `comparatorVersion` differs — the total order is a code contract, not data, so a schema
|
|
22
|
+
* fingerprint can't cover it. Bump in lockstep with the Rust constant if the order changes. */
|
|
23
|
+
export const COMPARATOR_VERSION = 1;
|
|
24
|
+
|
|
25
|
+
const SIGN = 0x8000000000000000n;
|
|
26
|
+
const ALL = 0xffffffffffffffffn;
|
|
27
|
+
|
|
28
|
+
/** Map an f64 to an unsigned 64-bit key whose unsigned order is IEEE-754 totalOrder
|
|
29
|
+
* (=== Rust `f64::total_cmp`): flip all bits for negatives (incl. -0 / -NaN), else set
|
|
30
|
+
* the sign bit. */
|
|
31
|
+
function orderedKey(x: number): bigint {
|
|
32
|
+
const dv = new DataView(new ArrayBuffer(8));
|
|
33
|
+
dv.setFloat64(0, x);
|
|
34
|
+
const bits = dv.getBigUint64(0);
|
|
35
|
+
return bits & SIGN ? bits ^ ALL : bits | SIGN;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** number compare with `f64::total_cmp` semantics (NaN deterministic & last; -0 < +0). */
|
|
39
|
+
export function compareNumber(a: number, b: number): -1 | 0 | 1 {
|
|
40
|
+
const ka = orderedKey(a);
|
|
41
|
+
const kb = orderedKey(b);
|
|
42
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** UTF-8 bytewise string compare (=== SQLite `BINARY` / Rust `&str` Ord). Correct for
|
|
46
|
+
* supplementary-plane code points, where JS `<` / `localeCompare` (UTF-16) would disagree. */
|
|
47
|
+
export function compareString(a: string, b: string): -1 | 0 | 1 {
|
|
48
|
+
const ba = enc.encode(a);
|
|
49
|
+
const bb = enc.encode(b);
|
|
50
|
+
const n = Math.min(ba.length, bb.length);
|
|
51
|
+
for (let i = 0; i < n; i++) {
|
|
52
|
+
if (ba[i] !== bb[i]) return ba[i] < bb[i] ? -1 : 1;
|
|
53
|
+
}
|
|
54
|
+
return ba.length === bb.length ? 0 : ba.length < bb.length ? -1 : 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Compare two bare cells, dispatching on the runtime type (null sorts first). */
|
|
58
|
+
export function compareValue(a: WireValue, b: WireValue): -1 | 0 | 1 {
|
|
59
|
+
const an = a === null || a === undefined;
|
|
60
|
+
const bn = b === null || b === undefined;
|
|
61
|
+
if (an && bn) return 0;
|
|
62
|
+
if (an) return -1;
|
|
63
|
+
if (bn) return 1;
|
|
64
|
+
switch (typeof a) {
|
|
65
|
+
case "number":
|
|
66
|
+
return compareNumber(a, b as number);
|
|
67
|
+
case "boolean":
|
|
68
|
+
return a === b ? 0 : a ? 1 : -1;
|
|
69
|
+
case "string":
|
|
70
|
+
return compareString(a, b as string);
|
|
71
|
+
default:
|
|
72
|
+
// A parsed-JSON object in a sort column (rare): compare its text bytewise.
|
|
73
|
+
return compareString(JSON.stringify(a), JSON.stringify(b));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Compare two rows by a resolved sort (`[columnIndex, ascending]` pairs). First non-equal
|
|
78
|
+
* column wins; a descending column negates. */
|
|
79
|
+
export function compareRows(a: WireValue[], b: WireValue[], sort: [number, boolean][]): -1 | 0 | 1 {
|
|
80
|
+
for (const [col, asc] of sort) {
|
|
81
|
+
const c = compareValue(a[col], b[col]);
|
|
82
|
+
if (c !== 0) return asc ? c : ((-c) as -1 | 1);
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @rindle/client — the backend-agnostic flat-change client core: typed schema, query builder,
|
|
2
|
+
// comparator, Backend seam, and the ArrayView contract. (The ArrayView folding impl + the
|
|
3
|
+
// Store/backend wiring land in later slices.)
|
|
4
|
+
|
|
5
|
+
export * from "./schema.ts"; // table/columns/primaryKey, createSchema, string/number/boolean/json, Col, RowOf, Row, TableDef, tableSpec, SCHEMA
|
|
6
|
+
export * from "./operators.ts"; // eq/ne/gt/ge/lt/le/like/notLike/ilike/notIlike/inList/notInList/is/isNot, and/or, Pred, Cond, Arg
|
|
7
|
+
export * from "./query.ts"; // Query, QueryRoot, queries/newQueryBuilder, defineQuery/NamedQuery, exists, notExists, defineFragment/Fragment/FragmentRef/isFragment
|
|
8
|
+
export * from "./view.ts"; // ArrayView, SingularArrayView, FlatArrayView, SingularView, ViewTypes
|
|
9
|
+
export * from "./store.ts"; // Store, WriteTx, AssembledNode, DehydratedQuery, DehydratedState
|
|
10
|
+
export * from "./ssr.ts"; // createServerStore, ServerStore, OneShotBackend, OneShotQueryFn, OneShotResult, ServerStoreOptions
|
|
11
|
+
|
|
12
|
+
export { stableKey } from "./key.ts"; // canonical viewKey for an AST (shared with @rindle/react)
|
|
13
|
+
|
|
14
|
+
export { COMPARATOR_VERSION, compareNumber, compareRows, compareString, compareValue } from "./compare.ts";
|
|
15
|
+
|
|
16
|
+
export type {
|
|
17
|
+
Backend,
|
|
18
|
+
ChangeEvent,
|
|
19
|
+
ColType,
|
|
20
|
+
FlatChange,
|
|
21
|
+
FlatOp,
|
|
22
|
+
Mutation,
|
|
23
|
+
MutationEnvelope,
|
|
24
|
+
NormalizedEvent,
|
|
25
|
+
NormalizedOp,
|
|
26
|
+
NormalizedSource,
|
|
27
|
+
NormalizedTableSchema,
|
|
28
|
+
OptimisticSource,
|
|
29
|
+
PathSeg,
|
|
30
|
+
ProgressFrame,
|
|
31
|
+
QueryId,
|
|
32
|
+
RemoteQuery,
|
|
33
|
+
ResultType,
|
|
34
|
+
WireNode,
|
|
35
|
+
WireProjection,
|
|
36
|
+
WireRel,
|
|
37
|
+
WireSchema,
|
|
38
|
+
WireValue,
|
|
39
|
+
} from "./types.ts";
|
|
40
|
+
|
|
41
|
+
export { CLIENT_MUTATIONS_SCHEMA, CLIENT_MUTATIONS_TABLE, LMID_QUERY_NAME } from "./types.ts";
|
|
42
|
+
|
|
43
|
+
// The Zero-wire AST types (for backends / advanced use).
|
|
44
|
+
export type {
|
|
45
|
+
Aggregate,
|
|
46
|
+
Ast,
|
|
47
|
+
Bound,
|
|
48
|
+
Condition,
|
|
49
|
+
CorrelatedSubquery,
|
|
50
|
+
Correlation,
|
|
51
|
+
Dir,
|
|
52
|
+
ExistsOp,
|
|
53
|
+
LitValue,
|
|
54
|
+
OrderPart,
|
|
55
|
+
SimpleOp,
|
|
56
|
+
ValuePosition,
|
|
57
|
+
} from "./ast.ts";
|
package/src/key.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// A stable, canonical JSON string for a query AST — the `viewKey` shared by the React cache
|
|
2
|
+
// (one cached view per AST) and the SSR dehydrate/hydrate map (seed lookup by AST). Both sides
|
|
3
|
+
// MUST agree byte-for-byte, so the one canonical serializer lives here. Object keys are sorted;
|
|
4
|
+
// `undefined` is encoded distinctly (so `{a:undefined}` ≠ `{}`); cycles throw.
|
|
5
|
+
|
|
6
|
+
export function stableKey(value: unknown, seen = new WeakSet<object>()): string {
|
|
7
|
+
if (value === undefined) return '{"$undefined":true}';
|
|
8
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
9
|
+
if (seen.has(value)) throw new TypeError("Rindle query keys must be acyclic JSON values");
|
|
10
|
+
seen.add(value);
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
const out = `[${value.map((v) => stableKey(v, seen)).join(",")}]`;
|
|
13
|
+
seen.delete(value);
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
const obj = value as Record<string, unknown>;
|
|
17
|
+
const out = `{${Object.keys(obj)
|
|
18
|
+
.sort()
|
|
19
|
+
.map((k) => `${JSON.stringify(k)}:${stableKey(obj[k], seen)}`)
|
|
20
|
+
.join(",")}}`;
|
|
21
|
+
seen.delete(value);
|
|
22
|
+
return out;
|
|
23
|
+
}
|