@rayfold/client 0.1.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/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +50 -0
- package/cache.d.ts +115 -0
- package/cache.js +464 -0
- package/cache.js.map +1 -0
- package/client.d.ts +152 -0
- package/client.js +425 -0
- package/client.js.map +1 -0
- package/index.d.ts +6 -0
- package/index.js +7 -0
- package/index.js.map +1 -0
- package/offline.d.ts +61 -0
- package/offline.js +119 -0
- package/offline.js.map +1 -0
- package/package.json +46 -0
- package/transport.d.ts +38 -0
- package/transport.js +109 -0
- package/transport.js.map +1 -0
- package/types.d.ts +5 -0
- package/types.js +48 -0
- package/types.js.map +1 -0
- package/ws-transport.d.ts +16 -0
- package/ws-transport.js +154 -0
- package/ws-transport.js.map +1 -0
package/cache.js
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
export function entityKey(obj) {
|
|
2
|
+
const t = obj["$type"];
|
|
3
|
+
const id = obj["id"];
|
|
4
|
+
return typeof t === "string" && (typeof id === "string" || typeof id === "number") ? `${t}:${id}` : null;
|
|
5
|
+
}
|
|
6
|
+
export function isRef(v) {
|
|
7
|
+
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
8
|
+
return false;
|
|
9
|
+
const o = v;
|
|
10
|
+
if (typeof o["$ref"] !== "string")
|
|
11
|
+
return false;
|
|
12
|
+
return Object.keys(o).every((k) => k === "$ref" || k === "$sel");
|
|
13
|
+
}
|
|
14
|
+
export class RayfoldCache {
|
|
15
|
+
now;
|
|
16
|
+
mergeOf;
|
|
17
|
+
entities = new Map();
|
|
18
|
+
results = new Map();
|
|
19
|
+
staleKeys = new Set();
|
|
20
|
+
listeners = new Set();
|
|
21
|
+
/** Optimistic predictions, oldest first. `entities` shows the server's values with these applied on top. */
|
|
22
|
+
layers = [];
|
|
23
|
+
/** The server's own values of the entities a prediction covers, for as long as one does. */
|
|
24
|
+
shadow = new Map();
|
|
25
|
+
constructor(now = Date.now,
|
|
26
|
+
/**
|
|
27
|
+
* A field's conflict policy, read from the schema when the client was given one. Without a policy a prediction
|
|
28
|
+
* stands until its command settles, which is what the sub-profile has always done.
|
|
29
|
+
*/
|
|
30
|
+
mergeOf = () => undefined) {
|
|
31
|
+
this.now = now;
|
|
32
|
+
this.mergeOf = mergeOf;
|
|
33
|
+
}
|
|
34
|
+
// ----------------------------------------------------------- entities
|
|
35
|
+
get(key) {
|
|
36
|
+
return this.entities.get(key);
|
|
37
|
+
}
|
|
38
|
+
has(key) {
|
|
39
|
+
return this.entities.has(key);
|
|
40
|
+
}
|
|
41
|
+
isStale(key) {
|
|
42
|
+
return this.staleKeys.has(key);
|
|
43
|
+
}
|
|
44
|
+
get size() {
|
|
45
|
+
return this.entities.size;
|
|
46
|
+
}
|
|
47
|
+
/** Merge fields into an entity (creating it), normalizing nested entities. Returns touched keys. */
|
|
48
|
+
merge(key, fields, touched = new Set()) {
|
|
49
|
+
const existing = this.baseOf(key) ?? {};
|
|
50
|
+
const next = { ...existing };
|
|
51
|
+
for (const [k, v] of Object.entries(fields))
|
|
52
|
+
next[k] = this.normalizeValue(v, touched).value;
|
|
53
|
+
if (!("$type" in next))
|
|
54
|
+
next["$type"] = key.slice(0, key.indexOf(":"));
|
|
55
|
+
if (!("id" in next))
|
|
56
|
+
next["id"] = key.slice(key.indexOf(":") + 1);
|
|
57
|
+
this.setBase(key, next);
|
|
58
|
+
this.staleKeys.delete(key);
|
|
59
|
+
touched.add(key);
|
|
60
|
+
// A field the server has just spoken for leaves any prediction that also set it, when the field's policy says so
|
|
61
|
+
// (spec 08 section 5). `lww` settles the same way here: the server's write is the later one.
|
|
62
|
+
const type = key.slice(0, key.indexOf(":"));
|
|
63
|
+
let overruled = false;
|
|
64
|
+
for (const layer of this.layers) {
|
|
65
|
+
for (const op of layer.ops) {
|
|
66
|
+
if (op.set !== key)
|
|
67
|
+
continue;
|
|
68
|
+
for (const field of Object.keys(fields)) {
|
|
69
|
+
if (!(field in op.value))
|
|
70
|
+
continue;
|
|
71
|
+
const policy = this.mergeOf(type, field);
|
|
72
|
+
if (policy !== "serverWins" && policy !== "lww")
|
|
73
|
+
continue;
|
|
74
|
+
delete op.value[field];
|
|
75
|
+
overruled = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (overruled)
|
|
80
|
+
this.rebuild(key);
|
|
81
|
+
return touched;
|
|
82
|
+
}
|
|
83
|
+
/** Merge entities found anywhere in `data` and notify watchers (used for conflict repairs). */
|
|
84
|
+
mergeEntities(data) {
|
|
85
|
+
const keys = new Set();
|
|
86
|
+
this.normalizeValue(data, keys);
|
|
87
|
+
this.emit(keys, new Set());
|
|
88
|
+
}
|
|
89
|
+
/** Replace entity objects with `{ $ref, $sel }` skeleton refs, storing the entities. */
|
|
90
|
+
normalize(data, touched = new Set()) {
|
|
91
|
+
return this.normalizeValue(data, touched).value;
|
|
92
|
+
}
|
|
93
|
+
normalizeValue(v, touched) {
|
|
94
|
+
if (v === null || typeof v !== "object")
|
|
95
|
+
return { value: v, sel: true };
|
|
96
|
+
if (Array.isArray(v)) {
|
|
97
|
+
let sel = true;
|
|
98
|
+
const value = v.map((x) => {
|
|
99
|
+
const r = this.normalizeValue(x, touched);
|
|
100
|
+
if (r.sel !== true)
|
|
101
|
+
sel = sel === true ? r.sel : mergeSel(sel, r.sel);
|
|
102
|
+
return r.value;
|
|
103
|
+
});
|
|
104
|
+
return { value, sel };
|
|
105
|
+
}
|
|
106
|
+
if (isRef(v)) {
|
|
107
|
+
touched.add(v.$ref);
|
|
108
|
+
return { value: v, sel: v.$sel ?? true };
|
|
109
|
+
}
|
|
110
|
+
const o = v;
|
|
111
|
+
const key = entityKey(o);
|
|
112
|
+
const sel = {};
|
|
113
|
+
const out = {};
|
|
114
|
+
for (const [k, x] of Object.entries(o)) {
|
|
115
|
+
const r = this.normalizeValue(x, touched);
|
|
116
|
+
out[k] = r.value;
|
|
117
|
+
sel[k] = r.sel;
|
|
118
|
+
}
|
|
119
|
+
if (key) {
|
|
120
|
+
this.storeEntity(key, out, touched);
|
|
121
|
+
return { value: { $ref: key, $sel: sel }, sel };
|
|
122
|
+
}
|
|
123
|
+
return { value: out, sel };
|
|
124
|
+
}
|
|
125
|
+
storeEntity(key, normalizedFields, touched) {
|
|
126
|
+
const existing = this.baseOf(key) ?? {};
|
|
127
|
+
this.setBase(key, { ...existing, ...normalizedFields });
|
|
128
|
+
this.staleKeys.delete(key);
|
|
129
|
+
touched.add(key);
|
|
130
|
+
}
|
|
131
|
+
/** Resolve refs back into plain objects, honouring each ref's selection. Cycles are cut at `maxDepth`. */
|
|
132
|
+
denormalize(data, maxDepth = 16) {
|
|
133
|
+
const walk = (v, sel, depth) => {
|
|
134
|
+
if (v === null || typeof v !== "object")
|
|
135
|
+
return v;
|
|
136
|
+
if (Array.isArray(v))
|
|
137
|
+
return v.map((x) => walk(x, sel, depth));
|
|
138
|
+
if (isRef(v)) {
|
|
139
|
+
const e = this.entities.get(v.$ref);
|
|
140
|
+
if (!e)
|
|
141
|
+
return { $ref: v.$ref };
|
|
142
|
+
const s = v.$sel ?? sel;
|
|
143
|
+
if (depth >= maxDepth)
|
|
144
|
+
return { $type: e["$type"], id: e["id"] };
|
|
145
|
+
if (s === undefined || s === true)
|
|
146
|
+
return walk(e, undefined, depth + 1);
|
|
147
|
+
const out = {};
|
|
148
|
+
if ("$type" in e)
|
|
149
|
+
out["$type"] = e["$type"];
|
|
150
|
+
for (const [k, sub] of Object.entries(s))
|
|
151
|
+
if (k in e)
|
|
152
|
+
out[k] = walk(e[k], sub, depth + 1);
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
const out = {};
|
|
156
|
+
for (const [k, x] of Object.entries(v)) {
|
|
157
|
+
const sub = sel && sel !== true ? sel[k] : undefined;
|
|
158
|
+
out[k] = walk(x, sub, depth);
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
};
|
|
162
|
+
return walk(data, undefined, 0);
|
|
163
|
+
}
|
|
164
|
+
// ------------------------------------------------------------ results
|
|
165
|
+
static resultKey(op, args, shape, vars) {
|
|
166
|
+
return JSON.stringify([op, canonical(args ?? {}), shape ?? "", canonical(vars ?? {})]);
|
|
167
|
+
}
|
|
168
|
+
putResult(key, op, data) {
|
|
169
|
+
const keys = new Set();
|
|
170
|
+
const normalized = this.normalizeValue(data, keys).value;
|
|
171
|
+
const r = { data: normalized, op, keys, storedAt: this.now(), stale: false };
|
|
172
|
+
this.results.set(key, r);
|
|
173
|
+
this.emit(keys, new Set([op]));
|
|
174
|
+
return r;
|
|
175
|
+
}
|
|
176
|
+
getResult(key) {
|
|
177
|
+
return this.results.get(key);
|
|
178
|
+
}
|
|
179
|
+
/** Apply a deferred delta at a path inside a stored result (spec 04 §3). */
|
|
180
|
+
mergeAt(key, path, delta) {
|
|
181
|
+
const r = this.results.get(key);
|
|
182
|
+
if (!r || !delta || typeof delta !== "object")
|
|
183
|
+
return;
|
|
184
|
+
const touched = new Set();
|
|
185
|
+
const norm = this.normalizeValue(delta, touched);
|
|
186
|
+
const target = path === "" ? r.data : this.getPath(r.data, path.split("."));
|
|
187
|
+
if (isRef(target)) {
|
|
188
|
+
const e = this.baseOf(target.$ref);
|
|
189
|
+
if (e)
|
|
190
|
+
this.setBase(target.$ref, { ...e, ...norm.value });
|
|
191
|
+
target.$sel = mergeSel(target.$sel ?? {}, norm.sel);
|
|
192
|
+
}
|
|
193
|
+
else if (target && typeof target === "object" && !Array.isArray(target)) {
|
|
194
|
+
Object.assign(target, norm.value);
|
|
195
|
+
}
|
|
196
|
+
for (const k of touched)
|
|
197
|
+
r.keys.add(k);
|
|
198
|
+
// a new record for the same data, so a watcher comparing records sees that this result changed
|
|
199
|
+
this.results.set(key, { ...r });
|
|
200
|
+
this.emit(touched, new Set([r.op]));
|
|
201
|
+
}
|
|
202
|
+
// ------------------------------------------------------------ patches
|
|
203
|
+
/**
|
|
204
|
+
* `set`, `del`, `inv` and `invOp` act on the whole cache. `at` and `list` describe one stored result and are
|
|
205
|
+
* applied only when the frame's result key is known (spec 04 section 2b).
|
|
206
|
+
*/
|
|
207
|
+
applyPatch(ops, resultKey) {
|
|
208
|
+
const keys = new Set();
|
|
209
|
+
const opNames = new Set();
|
|
210
|
+
for (const p of ops) {
|
|
211
|
+
if ("set" in p)
|
|
212
|
+
this.merge(p.set, p.value, keys);
|
|
213
|
+
else if ("list" in p)
|
|
214
|
+
this.applyList(resultKey, p, keys, opNames);
|
|
215
|
+
else if ("at" in p)
|
|
216
|
+
this.applyAt(resultKey, p.at, p.value, keys, opNames);
|
|
217
|
+
else if ("del" in p) {
|
|
218
|
+
this.setBase(p.del, undefined);
|
|
219
|
+
keys.add(p.del);
|
|
220
|
+
for (const r of this.results.values())
|
|
221
|
+
if (r.keys.has(p.del))
|
|
222
|
+
r.data = dropRef(r.data, p.del);
|
|
223
|
+
// Lists nested inside other entities (Book.reviews, Author.books) hold refs too.
|
|
224
|
+
for (const k of new Set([...this.entities.keys(), ...this.shadow.keys()])) {
|
|
225
|
+
const e = this.baseOf(k);
|
|
226
|
+
if (!e || !containsRef(e, p.del))
|
|
227
|
+
continue;
|
|
228
|
+
this.setBase(k, dropRef(e, p.del));
|
|
229
|
+
keys.add(k);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
else if ("inv" in p) {
|
|
233
|
+
for (const k of p.inv) {
|
|
234
|
+
this.staleKeys.add(k);
|
|
235
|
+
keys.add(k);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else if ("invOp" in p) {
|
|
239
|
+
for (const op of p.invOp) {
|
|
240
|
+
opNames.add(op);
|
|
241
|
+
for (const r of this.results.values())
|
|
242
|
+
if (r.op === op)
|
|
243
|
+
r.stale = true;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const r of this.results.values()) {
|
|
248
|
+
for (const k of keys)
|
|
249
|
+
if (r.keys.has(k))
|
|
250
|
+
opNames.add(r.op);
|
|
251
|
+
}
|
|
252
|
+
this.emit(keys, opNames);
|
|
253
|
+
}
|
|
254
|
+
/** Merge fields into the plain object at a path inside one stored result. */
|
|
255
|
+
applyAt(resultKey, path, value, touched, opNames) {
|
|
256
|
+
const r = resultKey === undefined ? undefined : this.results.get(resultKey);
|
|
257
|
+
if (!r)
|
|
258
|
+
return;
|
|
259
|
+
const target = path === "" ? r.data : this.getPath(r.data, path.split("."));
|
|
260
|
+
if (!target || typeof target !== "object" || Array.isArray(target))
|
|
261
|
+
return;
|
|
262
|
+
const norm = this.normalizeValue(value, touched);
|
|
263
|
+
Object.assign(target, norm.value);
|
|
264
|
+
for (const k of touched)
|
|
265
|
+
r.keys.add(k);
|
|
266
|
+
opNames.add(r.op);
|
|
267
|
+
this.results.set(resultKey, { ...r });
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Remove the named old positions of a list inside one stored result, then insert the carried elements at their
|
|
271
|
+
* new positions. Insertions carry the projected element, so normalizing them stores the entity and records
|
|
272
|
+
* which fields this result selected.
|
|
273
|
+
*/
|
|
274
|
+
applyList(resultKey, op, touched, opNames) {
|
|
275
|
+
const r = resultKey === undefined ? undefined : this.results.get(resultKey);
|
|
276
|
+
if (!r)
|
|
277
|
+
return;
|
|
278
|
+
const arr = op.list === "" ? r.data : this.getPath(r.data, op.list.split("."));
|
|
279
|
+
if (!Array.isArray(arr))
|
|
280
|
+
return;
|
|
281
|
+
for (const n of [...(op.del ?? [])].sort((a, b) => b - a))
|
|
282
|
+
arr.splice(n, 1);
|
|
283
|
+
for (const x of op.ins ?? [])
|
|
284
|
+
arr.splice(x.at, 0, this.normalizeValue(x.value, touched).value);
|
|
285
|
+
for (const k of touched)
|
|
286
|
+
r.keys.add(k);
|
|
287
|
+
opNames.add(r.op);
|
|
288
|
+
this.results.set(resultKey, { ...r });
|
|
289
|
+
}
|
|
290
|
+
// --------------------------------------------------------- predictions
|
|
291
|
+
/**
|
|
292
|
+
* Shows a prediction (tagged by the command's idempotency key) on top of the server's values until
|
|
293
|
+
* [removeLayer]. The server's patches keep landing underneath, so removing the prediction leaves exactly what the
|
|
294
|
+
* server said: that is the rebase after success and the rollback after failure.
|
|
295
|
+
*/
|
|
296
|
+
addLayer(id, ops) {
|
|
297
|
+
for (const op of ops) {
|
|
298
|
+
const type = op.set.slice(0, op.set.indexOf(":"));
|
|
299
|
+
for (const field of Object.keys(op.value)) {
|
|
300
|
+
const policy = this.mergeOf(type, field);
|
|
301
|
+
// Declared but not implemented: predicting such a field would need a merge this client cannot perform.
|
|
302
|
+
if (policy === "crdtText" || policy === "custom") {
|
|
303
|
+
throw new Error(`@merge(${policy}) is not implemented: ${type}.${field} cannot be predicted optimistically`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const keys = new Set();
|
|
308
|
+
for (const op of ops) {
|
|
309
|
+
if (!this.shadow.has(op.set))
|
|
310
|
+
this.shadow.set(op.set, this.entities.get(op.set));
|
|
311
|
+
keys.add(op.set);
|
|
312
|
+
}
|
|
313
|
+
this.layers.push({ id, ops });
|
|
314
|
+
for (const k of keys)
|
|
315
|
+
this.rebuild(k);
|
|
316
|
+
this.emit(keys, new Set());
|
|
317
|
+
}
|
|
318
|
+
removeLayer(id) {
|
|
319
|
+
const i = this.layers.findIndex((l) => l.id === id);
|
|
320
|
+
if (i < 0)
|
|
321
|
+
return;
|
|
322
|
+
const [layer] = this.layers.splice(i, 1);
|
|
323
|
+
const keys = new Set(layer.ops.map((o) => o.set));
|
|
324
|
+
for (const k of keys) {
|
|
325
|
+
if (this.layers.some((l) => l.ops.some((o) => o.set === k))) {
|
|
326
|
+
this.rebuild(k);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const base = this.shadow.get(k);
|
|
330
|
+
this.shadow.delete(k);
|
|
331
|
+
if (base)
|
|
332
|
+
this.entities.set(k, base);
|
|
333
|
+
else
|
|
334
|
+
this.entities.delete(k);
|
|
335
|
+
}
|
|
336
|
+
this.emit(keys, new Set());
|
|
337
|
+
}
|
|
338
|
+
/** Predictions not yet settled, by their command keys. */
|
|
339
|
+
get predictions() {
|
|
340
|
+
return this.layers.map((l) => l.id);
|
|
341
|
+
}
|
|
342
|
+
/** The server's value of an entity, below any prediction. */
|
|
343
|
+
baseOf(key) {
|
|
344
|
+
return this.shadow.has(key) ? this.shadow.get(key) : this.entities.get(key);
|
|
345
|
+
}
|
|
346
|
+
setBase(key, value) {
|
|
347
|
+
if (this.shadow.has(key)) {
|
|
348
|
+
this.shadow.set(key, value);
|
|
349
|
+
this.rebuild(key);
|
|
350
|
+
}
|
|
351
|
+
else if (value)
|
|
352
|
+
this.entities.set(key, value);
|
|
353
|
+
else
|
|
354
|
+
this.entities.delete(key);
|
|
355
|
+
}
|
|
356
|
+
rebuild(key) {
|
|
357
|
+
const base = this.shadow.get(key);
|
|
358
|
+
let e = base ? { ...base } : undefined;
|
|
359
|
+
for (const l of this.layers) {
|
|
360
|
+
for (const op of l.ops) {
|
|
361
|
+
if (op.set === key)
|
|
362
|
+
e = { ...(e ?? { $type: key.slice(0, key.indexOf(":")), id: key.slice(key.indexOf(":") + 1) }), ...op.value };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (e)
|
|
366
|
+
this.entities.set(key, e);
|
|
367
|
+
else
|
|
368
|
+
this.entities.delete(key);
|
|
369
|
+
}
|
|
370
|
+
// ---------------------------------------------------------- listeners
|
|
371
|
+
subscribe(fn) {
|
|
372
|
+
this.listeners.add(fn);
|
|
373
|
+
return () => this.listeners.delete(fn);
|
|
374
|
+
}
|
|
375
|
+
pending = null;
|
|
376
|
+
/** Coalesce every notification produced inside `fn` into one. */
|
|
377
|
+
transaction(fn) {
|
|
378
|
+
if (this.pending)
|
|
379
|
+
return fn();
|
|
380
|
+
this.pending = { keys: new Set(), ops: new Set() };
|
|
381
|
+
try {
|
|
382
|
+
fn();
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
const p = this.pending;
|
|
386
|
+
this.pending = null;
|
|
387
|
+
this.emit(p.keys, p.ops);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
emit(keys, ops) {
|
|
391
|
+
if (!keys.size && !ops.size)
|
|
392
|
+
return;
|
|
393
|
+
if (this.pending) {
|
|
394
|
+
for (const k of keys)
|
|
395
|
+
this.pending.keys.add(k);
|
|
396
|
+
for (const o of ops)
|
|
397
|
+
this.pending.ops.add(o);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
for (const fn of this.listeners)
|
|
401
|
+
fn({ keys, ops });
|
|
402
|
+
}
|
|
403
|
+
clear() {
|
|
404
|
+
this.entities.clear();
|
|
405
|
+
this.results.clear();
|
|
406
|
+
this.staleKeys.clear();
|
|
407
|
+
this.layers.length = 0;
|
|
408
|
+
this.shadow.clear();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function mergeSel(a, b) {
|
|
412
|
+
if (a === true)
|
|
413
|
+
return b;
|
|
414
|
+
if (b === true)
|
|
415
|
+
return a;
|
|
416
|
+
const out = { ...a };
|
|
417
|
+
for (const [k, v] of Object.entries(b))
|
|
418
|
+
out[k] = k in out ? mergeSel(out[k], v) : v;
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
function containsRef(data, key) {
|
|
422
|
+
if (data === null || typeof data !== "object")
|
|
423
|
+
return false;
|
|
424
|
+
if (Array.isArray(data))
|
|
425
|
+
return data.some((x) => containsRef(x, key));
|
|
426
|
+
if (isRef(data))
|
|
427
|
+
return data.$ref === key;
|
|
428
|
+
return Object.values(data).some((x) => containsRef(x, key));
|
|
429
|
+
}
|
|
430
|
+
function dropRef(data, key) {
|
|
431
|
+
if (data === null || typeof data !== "object")
|
|
432
|
+
return data;
|
|
433
|
+
if (Array.isArray(data))
|
|
434
|
+
return data.filter((x) => !(isRef(x) && x.$ref === key)).map((x) => dropRef(x, key));
|
|
435
|
+
if (isRef(data))
|
|
436
|
+
return data.$ref === key ? null : data;
|
|
437
|
+
const out = {};
|
|
438
|
+
for (const [k, v] of Object.entries(data))
|
|
439
|
+
out[k] = dropRef(v, key);
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
RayfoldCache.prototype.getPath = function (v, path) {
|
|
443
|
+
let cur = v;
|
|
444
|
+
for (const p of path) {
|
|
445
|
+
if (isRef(cur))
|
|
446
|
+
cur = this.get(cur.$ref);
|
|
447
|
+
if (cur === null || cur === undefined || typeof cur !== "object")
|
|
448
|
+
return undefined;
|
|
449
|
+
cur = Array.isArray(cur) ? cur[Number(p)] : cur[p];
|
|
450
|
+
}
|
|
451
|
+
return cur;
|
|
452
|
+
};
|
|
453
|
+
function canonical(v) {
|
|
454
|
+
if (v === null || typeof v !== "object")
|
|
455
|
+
return JSON.stringify(v);
|
|
456
|
+
if (Array.isArray(v))
|
|
457
|
+
return `[${v.map(canonical).join(",")}]`;
|
|
458
|
+
const o = v;
|
|
459
|
+
return `{${Object.keys(o)
|
|
460
|
+
.sort()
|
|
461
|
+
.map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`)
|
|
462
|
+
.join(",")}}`;
|
|
463
|
+
}
|
|
464
|
+
//# sourceMappingURL=cache.js.map
|
package/cache.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAqCA,MAAM,UAAU,SAAS,CAAC,GAA4B;IACpD,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACrB,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3G,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,CAAU;IAC9B,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAClE,MAAM,CAAC,GAAG,CAA4B,CAAC;IACvC,IAAI,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAChD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,OAAO,YAAY;IAWJ,GAAG;IAKH,OAAO;IAfT,QAAQ,GAAG,IAAI,GAAG,EAAsC,CAAC;IACzD,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC1C,SAAS,GAAG,IAAI,GAAG,EAAa,CAAC;IACjC,SAAS,GAAG,IAAI,GAAG,EAAiB,CAAC;IACtD,4GAA4G;IAC3F,MAAM,GAA+C,EAAE,CAAC;IACzE,4FAA4F;IAC3E,MAAM,GAAG,IAAI,GAAG,EAAkD,CAAC;IAEpF,YACmB,GAAG,GAAiB,IAAI,CAAC,GAAG;IAC7C;;;OAGG;IACc,OAAO,GAA6D,GAAG,EAAE,CAAC,SAAS;mBALnF,GAAG;uBAKH,OAAO;IACvB,CAAC;IAEJ,uEAAuE;IAEvE,GAAG,CAAC,GAAc;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,GAAG,CAAC,GAAc;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,CAAC,GAAc;QACpB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IACD,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,oGAAoG;IACpG,KAAK,CAAC,GAAc,EAAE,MAA+B,EAAE,OAAO,GAAG,IAAI,GAAG,EAAa;QACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,GAA4B,EAAE,GAAG,QAAQ,EAAE,CAAC;QACtD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC;QAC7F,IAAI,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,iHAAiH;QACjH,6FAA6F;QAC7F,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5C,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;gBAC3B,IAAI,EAAE,CAAC,GAAG,KAAK,GAAG;oBAAE,SAAS;gBAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;oBACxC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC;wBAAE,SAAS;oBACnC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;oBACzC,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,KAAK;wBAAE,SAAS;oBAC1D,OAAO,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBACvB,SAAS,GAAG,IAAI,CAAC;gBACnB,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,SAAS;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,+FAA+F;IAC/F,aAAa,CAAC,IAAa;QACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAa,CAAC;QAClC,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;IAC7B,CAAC;IAED,wFAAwF;IACxF,SAAS,CAAC,IAAa,EAAE,OAAO,GAAG,IAAI,GAAG,EAAa;QACrD,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC;IAClD,CAAC;IAEO,cAAc,CAAC,CAAU,EAAE,OAAuB;QACxD,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QACxE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YACrB,IAAI,GAAG,GAAQ,IAAI,CAAC;YACpB,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACxB,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;gBAC1C,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI;oBAAE,GAAG,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;gBACtE,OAAO,CAAC,CAAC,KAAK,CAAC;YACjB,CAAC,CAAC,CAAC;YACH,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QACxB,CAAC;QACD,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACpB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QAC3C,CAAC;QACD,MAAM,CAAC,GAAG,CAA4B,CAAC;QACvC,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,GAAG,GAAyB,EAAE,CAAC;QACrC,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YAC1C,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;YACjB,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;QACjB,CAAC;QACD,IAAI,GAAG,EAAE,CAAC;YACR,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YACpC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC;QAClD,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC7B,CAAC;IAEO,WAAW,CAAC,GAAc,EAAE,gBAAyC,EAAE,OAAuB;QACpG,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,QAAQ,EAAE,GAAG,gBAAgB,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAED,0GAA0G;IAC1G,WAAW,CAAC,IAAa,EAAE,QAAQ,GAAG,EAAE;QACtC,MAAM,IAAI,GAAG,CAAC,CAAU,EAAE,GAAoB,EAAE,KAAa,EAAW,EAAE;YACxE,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,OAAO,CAAC,CAAC;YAClD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;YAC/D,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACb,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBACpC,IAAI,CAAC,CAAC;oBAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAChC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC;gBACxB,IAAI,KAAK,IAAI,QAAQ;oBAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjE,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI;oBAAE,OAAO,IAAI,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBACxE,MAAM,GAAG,GAA4B,EAAE,CAAC;gBACxC,IAAI,OAAO,IAAI,CAAC;oBAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;gBAC5C,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;oBAAE,IAAI,CAAC,IAAI,CAAC;wBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBAC1F,OAAO,GAAG,CAAC;YACb,CAAC;YACD,MAAM,GAAG,GAA4B,EAAE,CAAC;YACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAA4B,CAAC,EAAE,CAAC;gBAClE,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBACrD,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC/B,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,uEAAuE;IAEvE,MAAM,CAAC,SAAS,CAAC,EAAU,EAAE,IAAa,EAAE,KAAyB,EAAE,IAAa;QAClF,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,KAAK,IAAI,EAAE,EAAE,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACzF,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,EAAU,EAAE,IAAa;QAC9C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAa,CAAC;QAClC,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC;QACzD,MAAM,CAAC,GAAiB,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC3F,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/B,OAAO,CAAC,CAAC;IACX,CAAC;IAED,SAAS,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,4EAA4E;IAC5E,OAAO,CAAC,GAAW,EAAE,IAAY,EAAE,KAAc;QAC/C,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO;QACtD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5E,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YAClB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,EAAE,GAAI,IAAI,CAAC,KAAiC,EAAE,CAAC,CAAC;YACvF,MAAM,CAAC,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACtD,CAAC;aAAM,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1E,MAAM,CAAC,MAAM,CAAC,MAAiC,EAAE,IAAI,CAAC,KAAgC,CAAC,CAAC;QAC1F,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvC,+FAA+F;QAC/F,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,uEAAuE;IAEvE;;;OAGG;IACH,UAAU,CAAC,GAAc,EAAE,SAAkB;QAC3C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAa,CAAC;QAClC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;YACpB,IAAI,KAAK,IAAI,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;iBAC5C,IAAI,MAAM,IAAI,CAAC;gBAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;iBAC7D,IAAI,IAAI,IAAI,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;iBACrE,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAC/B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBAChB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;oBAAE,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;wBAAE,CAAC,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC9F,iFAAiF;gBACjF,KAAK,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;oBAC1E,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;oBACzB,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC;wBAAE,SAAS;oBAC3C,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAA4B,CAAC,CAAC;oBAC9D,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACd,CAAC;YACH,CAAC;iBAAM,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBACtB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;oBACtB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACtB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACd,CAAC;YACH,CAAC;iBAAM,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;gBACxB,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;oBACzB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBAChB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;wBAAE,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE;4BAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;gBACzE,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YACtC,KAAK,MAAM,CAAC,IAAI,IAAI;gBAAE,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;oBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,6EAA6E;IACrE,OAAO,CAAC,SAA6B,EAAE,IAAY,EAAE,KAA8B,EAAE,OAAuB,EAAE,OAAoB;QACxI,MAAM,CAAC,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5E,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,MAAM,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5E,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO;QAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,MAAiC,EAAE,IAAI,CAAC,KAAgC,CAAC,CAAC;QACxF,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAU,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;;OAIG;IACK,SAAS,CAAC,SAA6B,EAAE,EAAiF,EAAE,OAAuB,EAAE,OAAoB;QAC/K,MAAM,CAAC,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5E,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO;QAChC,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5E,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE;YAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;QAC/F,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAU,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,wEAAwE;IAExE;;;;OAIG;IACH,QAAQ,CAAC,EAAU,EAAE,GAAmB;QACtC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAClD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBACzC,uGAAuG;gBACvG,IAAI,MAAM,KAAK,UAAU,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;oBACjD,MAAM,IAAI,KAAK,CAAC,UAAU,MAAM,yBAAyB,IAAI,IAAI,KAAK,qCAAqC,CAAC,CAAC;gBAC/G,CAAC;YACH,CAAC;QACH,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAa,CAAC;QAClC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YACjF,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,IAAI;YAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;IAC7B,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO;QAClB,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,KAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACnD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5D,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAChC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,IAAI;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;;gBAChC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;IAC7B,CAAC;IAED,0DAA0D;IAC1D,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,6DAA6D;IACrD,MAAM,CAAC,GAAc;QAC3B,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9E,CAAC;IAEO,OAAO,CAAC,GAAc,EAAE,KAA0C;QACxE,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC5B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,KAAK;YAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;;YAC3C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAEO,OAAO,CAAC,GAAc;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,GAAwC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5E,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5B,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;gBACvB,IAAI,EAAE,CAAC,GAAG,KAAK,GAAG;oBAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC;YACpI,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;;YAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,uEAAuE;IAEvE,SAAS,CAAC,EAAiB;QACzB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAEO,OAAO,GAAsD,IAAI,CAAC;IAE1E,iEAAiE;IACjE,WAAW,CAAC,EAAc;QACxB,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;QACnD,IAAI,CAAC;YACH,EAAE,EAAE,CAAC;QACP,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;YACvB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAEO,IAAI,CAAC,IAAoB,EAAE,GAAgB;QACjD,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI;YAAE,OAAO;QACpC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,KAAK,MAAM,CAAC,IAAI,IAAI;gBAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC7C,OAAO;QACT,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS;YAAE,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,CAAM,EAAE,CAAM;IAC9B,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IACzB,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IACzB,MAAM,GAAG,GAAyB,EAAE,GAAG,CAAC,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,WAAW,CAAC,IAAa,EAAE,GAAc;IAChD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACtE,IAAI,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC;IAC1C,OAAO,MAAM,CAAC,MAAM,CAAC,IAA+B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AACzF,CAAC;AAED,SAAS,OAAO,CAAC,IAAa,EAAE,GAAc;IAC5C,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3D,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9G,IAAI,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/F,OAAO,GAAG,CAAC;AACb,CAAC;AAMD,YAAY,CAAC,SAAS,CAAC,OAAO,GAAG,UAA8B,CAAU,EAAE,IAAc;IACvF,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,KAAK,CAAC,GAAG,CAAC;YAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACnF,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,GAA+B,CAAC,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,SAAS,SAAS,CAAC,CAAU;IAC3B,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC/D,MAAM,CAAC,GAAG,CAA4B,CAAC;IACvC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;SACtB,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACrD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Normalized entity cache. Spec: spec/07-cache.md §3, spec/04 §2 (patches).\n * Entities live once, keyed by \"$type:id\". A stored result is a *skeleton*: entities are replaced by\n * `{ $ref, $sel }` where `$sel` records which fields that result selected, so reading a result back\n * yields exactly the requested shape while the field values always come from the shared entity.\n */\nimport type { PatchOp } from \"@rayfold/server/protocol\";\n\nexport type EntityKey = string;\n/** Selection skeleton: `true` = scalar/leaf, object = nested selection (lists use the element selection). */\nexport type Sel = true | { [field: string]: Sel };\nexport interface Ref {\n $ref: EntityKey;\n $sel?: Sel;\n}\n\nexport interface CachedResult {\n /** skeleton: entities replaced by refs */\n data: unknown;\n op: string;\n /** entity keys contained in the result */\n keys: Set<EntityKey>;\n storedAt: number;\n stale: boolean;\n}\n\nexport type CacheListener = (changed: { keys: Set<EntityKey>; ops: Set<string> }) => void;\n\n/** How a field settles when a prediction and the server disagree (spec 08 section 5, `@merge`). */\nexport type MergePolicy = \"serverWins\" | \"keepLocal\" | \"lww\" | \"crdtText\" | \"custom\";\n\n/** A predicted change to one entity's fields (sub-profile `sync`, spec 08 section 5). */\nexport interface OptimisticOp {\n set: EntityKey;\n value: Record<string, unknown>;\n}\n\nexport function entityKey(obj: Record<string, unknown>): EntityKey | null {\n const t = obj[\"$type\"];\n const id = obj[\"id\"];\n return typeof t === \"string\" && (typeof id === \"string\" || typeof id === \"number\") ? `${t}:${id}` : null;\n}\n\nexport function isRef(v: unknown): v is Ref {\n if (!v || typeof v !== \"object\" || Array.isArray(v)) return false;\n const o = v as Record<string, unknown>;\n if (typeof o[\"$ref\"] !== \"string\") return false;\n return Object.keys(o).every((k) => k === \"$ref\" || k === \"$sel\");\n}\n\nexport class RayfoldCache {\n private readonly entities = new Map<EntityKey, Record<string, unknown>>();\n private readonly results = new Map<string, CachedResult>();\n private readonly staleKeys = new Set<EntityKey>();\n private readonly listeners = new Set<CacheListener>();\n /** Optimistic predictions, oldest first. `entities` shows the server's values with these applied on top. */\n private readonly layers: Array<{ id: string; ops: OptimisticOp[] }> = [];\n /** The server's own values of the entities a prediction covers, for as long as one does. */\n private readonly shadow = new Map<EntityKey, Record<string, unknown> | undefined>();\n\n constructor(\n private readonly now: () => number = Date.now,\n /**\n * A field's conflict policy, read from the schema when the client was given one. Without a policy a prediction\n * stands until its command settles, which is what the sub-profile has always done.\n */\n private readonly mergeOf: (type: string, field: string) => MergePolicy | undefined = () => undefined,\n ) {}\n\n // ----------------------------------------------------------- entities\n\n get(key: EntityKey): Record<string, unknown> | undefined {\n return this.entities.get(key);\n }\n has(key: EntityKey): boolean {\n return this.entities.has(key);\n }\n isStale(key: EntityKey): boolean {\n return this.staleKeys.has(key);\n }\n get size(): number {\n return this.entities.size;\n }\n\n /** Merge fields into an entity (creating it), normalizing nested entities. Returns touched keys. */\n merge(key: EntityKey, fields: Record<string, unknown>, touched = new Set<EntityKey>()): Set<EntityKey> {\n const existing = this.baseOf(key) ?? {};\n const next: Record<string, unknown> = { ...existing };\n for (const [k, v] of Object.entries(fields)) next[k] = this.normalizeValue(v, touched).value;\n if (!(\"$type\" in next)) next[\"$type\"] = key.slice(0, key.indexOf(\":\"));\n if (!(\"id\" in next)) next[\"id\"] = key.slice(key.indexOf(\":\") + 1);\n this.setBase(key, next);\n this.staleKeys.delete(key);\n touched.add(key);\n // A field the server has just spoken for leaves any prediction that also set it, when the field's policy says so\n // (spec 08 section 5). `lww` settles the same way here: the server's write is the later one.\n const type = key.slice(0, key.indexOf(\":\"));\n let overruled = false;\n for (const layer of this.layers) {\n for (const op of layer.ops) {\n if (op.set !== key) continue;\n for (const field of Object.keys(fields)) {\n if (!(field in op.value)) continue;\n const policy = this.mergeOf(type, field);\n if (policy !== \"serverWins\" && policy !== \"lww\") continue;\n delete op.value[field];\n overruled = true;\n }\n }\n }\n if (overruled) this.rebuild(key);\n return touched;\n }\n\n /** Merge entities found anywhere in `data` and notify watchers (used for conflict repairs). */\n mergeEntities(data: unknown): void {\n const keys = new Set<EntityKey>();\n this.normalizeValue(data, keys);\n this.emit(keys, new Set());\n }\n\n /** Replace entity objects with `{ $ref, $sel }` skeleton refs, storing the entities. */\n normalize(data: unknown, touched = new Set<EntityKey>()): unknown {\n return this.normalizeValue(data, touched).value;\n }\n\n private normalizeValue(v: unknown, touched: Set<EntityKey>): { value: unknown; sel: Sel } {\n if (v === null || typeof v !== \"object\") return { value: v, sel: true };\n if (Array.isArray(v)) {\n let sel: Sel = true;\n const value = v.map((x) => {\n const r = this.normalizeValue(x, touched);\n if (r.sel !== true) sel = sel === true ? r.sel : mergeSel(sel, r.sel);\n return r.value;\n });\n return { value, sel };\n }\n if (isRef(v)) {\n touched.add(v.$ref);\n return { value: v, sel: v.$sel ?? true };\n }\n const o = v as Record<string, unknown>;\n const key = entityKey(o);\n const sel: { [k: string]: Sel } = {};\n const out: Record<string, unknown> = {};\n for (const [k, x] of Object.entries(o)) {\n const r = this.normalizeValue(x, touched);\n out[k] = r.value;\n sel[k] = r.sel;\n }\n if (key) {\n this.storeEntity(key, out, touched);\n return { value: { $ref: key, $sel: sel }, sel };\n }\n return { value: out, sel };\n }\n\n private storeEntity(key: EntityKey, normalizedFields: Record<string, unknown>, touched: Set<EntityKey>): void {\n const existing = this.baseOf(key) ?? {};\n this.setBase(key, { ...existing, ...normalizedFields });\n this.staleKeys.delete(key);\n touched.add(key);\n }\n\n /** Resolve refs back into plain objects, honouring each ref's selection. Cycles are cut at `maxDepth`. */\n denormalize(data: unknown, maxDepth = 16): unknown {\n const walk = (v: unknown, sel: Sel | undefined, depth: number): unknown => {\n if (v === null || typeof v !== \"object\") return v;\n if (Array.isArray(v)) return v.map((x) => walk(x, sel, depth));\n if (isRef(v)) {\n const e = this.entities.get(v.$ref);\n if (!e) return { $ref: v.$ref };\n const s = v.$sel ?? sel;\n if (depth >= maxDepth) return { $type: e[\"$type\"], id: e[\"id\"] };\n if (s === undefined || s === true) return walk(e, undefined, depth + 1);\n const out: Record<string, unknown> = {};\n if (\"$type\" in e) out[\"$type\"] = e[\"$type\"];\n for (const [k, sub] of Object.entries(s)) if (k in e) out[k] = walk(e[k], sub, depth + 1);\n return out;\n }\n const out: Record<string, unknown> = {};\n for (const [k, x] of Object.entries(v as Record<string, unknown>)) {\n const sub = sel && sel !== true ? sel[k] : undefined;\n out[k] = walk(x, sub, depth);\n }\n return out;\n };\n return walk(data, undefined, 0);\n }\n\n // ------------------------------------------------------------ results\n\n static resultKey(op: string, args: unknown, shape: string | undefined, vars: unknown): string {\n return JSON.stringify([op, canonical(args ?? {}), shape ?? \"\", canonical(vars ?? {})]);\n }\n\n putResult(key: string, op: string, data: unknown): CachedResult {\n const keys = new Set<EntityKey>();\n const normalized = this.normalizeValue(data, keys).value;\n const r: CachedResult = { data: normalized, op, keys, storedAt: this.now(), stale: false };\n this.results.set(key, r);\n this.emit(keys, new Set([op]));\n return r;\n }\n\n getResult(key: string): CachedResult | undefined {\n return this.results.get(key);\n }\n\n /** Apply a deferred delta at a path inside a stored result (spec 04 §3). */\n mergeAt(key: string, path: string, delta: unknown): void {\n const r = this.results.get(key);\n if (!r || !delta || typeof delta !== \"object\") return;\n const touched = new Set<EntityKey>();\n const norm = this.normalizeValue(delta, touched);\n const target = path === \"\" ? r.data : this.getPath(r.data, path.split(\".\"));\n if (isRef(target)) {\n const e = this.baseOf(target.$ref);\n if (e) this.setBase(target.$ref, { ...e, ...(norm.value as Record<string, unknown>) });\n target.$sel = mergeSel(target.$sel ?? {}, norm.sel);\n } else if (target && typeof target === \"object\" && !Array.isArray(target)) {\n Object.assign(target as Record<string, unknown>, norm.value as Record<string, unknown>);\n }\n for (const k of touched) r.keys.add(k);\n // a new record for the same data, so a watcher comparing records sees that this result changed\n this.results.set(key, { ...r });\n this.emit(touched, new Set([r.op]));\n }\n\n // ------------------------------------------------------------ patches\n\n /**\n * `set`, `del`, `inv` and `invOp` act on the whole cache. `at` and `list` describe one stored result and are\n * applied only when the frame's result key is known (spec 04 section 2b).\n */\n applyPatch(ops: PatchOp[], resultKey?: string): void {\n const keys = new Set<EntityKey>();\n const opNames = new Set<string>();\n for (const p of ops) {\n if (\"set\" in p) this.merge(p.set, p.value, keys);\n else if (\"list\" in p) this.applyList(resultKey, p, keys, opNames);\n else if (\"at\" in p) this.applyAt(resultKey, p.at, p.value, keys, opNames);\n else if (\"del\" in p) {\n this.setBase(p.del, undefined);\n keys.add(p.del);\n for (const r of this.results.values()) if (r.keys.has(p.del)) r.data = dropRef(r.data, p.del);\n // Lists nested inside other entities (Book.reviews, Author.books) hold refs too.\n for (const k of new Set([...this.entities.keys(), ...this.shadow.keys()])) {\n const e = this.baseOf(k);\n if (!e || !containsRef(e, p.del)) continue;\n this.setBase(k, dropRef(e, p.del) as Record<string, unknown>);\n keys.add(k);\n }\n } else if (\"inv\" in p) {\n for (const k of p.inv) {\n this.staleKeys.add(k);\n keys.add(k);\n }\n } else if (\"invOp\" in p) {\n for (const op of p.invOp) {\n opNames.add(op);\n for (const r of this.results.values()) if (r.op === op) r.stale = true;\n }\n }\n }\n for (const r of this.results.values()) {\n for (const k of keys) if (r.keys.has(k)) opNames.add(r.op);\n }\n this.emit(keys, opNames);\n }\n\n /** Merge fields into the plain object at a path inside one stored result. */\n private applyAt(resultKey: string | undefined, path: string, value: Record<string, unknown>, touched: Set<EntityKey>, opNames: Set<string>): void {\n const r = resultKey === undefined ? undefined : this.results.get(resultKey);\n if (!r) return;\n const target = path === \"\" ? r.data : this.getPath(r.data, path.split(\".\"));\n if (!target || typeof target !== \"object\" || Array.isArray(target)) return;\n const norm = this.normalizeValue(value, touched);\n Object.assign(target as Record<string, unknown>, norm.value as Record<string, unknown>);\n for (const k of touched) r.keys.add(k);\n opNames.add(r.op);\n this.results.set(resultKey!, { ...r });\n }\n\n /**\n * Remove the named old positions of a list inside one stored result, then insert the carried elements at their\n * new positions. Insertions carry the projected element, so normalizing them stores the entity and records\n * which fields this result selected.\n */\n private applyList(resultKey: string | undefined, op: { list: string; del?: number[]; ins?: Array<{ at: number; value: unknown }> }, touched: Set<EntityKey>, opNames: Set<string>): void {\n const r = resultKey === undefined ? undefined : this.results.get(resultKey);\n if (!r) return;\n const arr = op.list === \"\" ? r.data : this.getPath(r.data, op.list.split(\".\"));\n if (!Array.isArray(arr)) return;\n for (const n of [...(op.del ?? [])].sort((a, b) => b - a)) arr.splice(n, 1);\n for (const x of op.ins ?? []) arr.splice(x.at, 0, this.normalizeValue(x.value, touched).value);\n for (const k of touched) r.keys.add(k);\n opNames.add(r.op);\n this.results.set(resultKey!, { ...r });\n }\n\n // --------------------------------------------------------- predictions\n\n /**\n * Shows a prediction (tagged by the command's idempotency key) on top of the server's values until\n * [removeLayer]. The server's patches keep landing underneath, so removing the prediction leaves exactly what the\n * server said: that is the rebase after success and the rollback after failure.\n */\n addLayer(id: string, ops: OptimisticOp[]): void {\n for (const op of ops) {\n const type = op.set.slice(0, op.set.indexOf(\":\"));\n for (const field of Object.keys(op.value)) {\n const policy = this.mergeOf(type, field);\n // Declared but not implemented: predicting such a field would need a merge this client cannot perform.\n if (policy === \"crdtText\" || policy === \"custom\") {\n throw new Error(`@merge(${policy}) is not implemented: ${type}.${field} cannot be predicted optimistically`);\n }\n }\n }\n const keys = new Set<EntityKey>();\n for (const op of ops) {\n if (!this.shadow.has(op.set)) this.shadow.set(op.set, this.entities.get(op.set));\n keys.add(op.set);\n }\n this.layers.push({ id, ops });\n for (const k of keys) this.rebuild(k);\n this.emit(keys, new Set());\n }\n\n removeLayer(id: string): void {\n const i = this.layers.findIndex((l) => l.id === id);\n if (i < 0) return;\n const [layer] = this.layers.splice(i, 1);\n const keys = new Set(layer!.ops.map((o) => o.set));\n for (const k of keys) {\n if (this.layers.some((l) => l.ops.some((o) => o.set === k))) {\n this.rebuild(k);\n continue;\n }\n const base = this.shadow.get(k);\n this.shadow.delete(k);\n if (base) this.entities.set(k, base);\n else this.entities.delete(k);\n }\n this.emit(keys, new Set());\n }\n\n /** Predictions not yet settled, by their command keys. */\n get predictions(): string[] {\n return this.layers.map((l) => l.id);\n }\n\n /** The server's value of an entity, below any prediction. */\n private baseOf(key: EntityKey): Record<string, unknown> | undefined {\n return this.shadow.has(key) ? this.shadow.get(key) : this.entities.get(key);\n }\n\n private setBase(key: EntityKey, value: Record<string, unknown> | undefined): void {\n if (this.shadow.has(key)) {\n this.shadow.set(key, value);\n this.rebuild(key);\n } else if (value) this.entities.set(key, value);\n else this.entities.delete(key);\n }\n\n private rebuild(key: EntityKey): void {\n const base = this.shadow.get(key);\n let e: Record<string, unknown> | undefined = base ? { ...base } : undefined;\n for (const l of this.layers) {\n for (const op of l.ops) {\n if (op.set === key) e = { ...(e ?? { $type: key.slice(0, key.indexOf(\":\")), id: key.slice(key.indexOf(\":\") + 1) }), ...op.value };\n }\n }\n if (e) this.entities.set(key, e);\n else this.entities.delete(key);\n }\n\n // ---------------------------------------------------------- listeners\n\n subscribe(fn: CacheListener): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n private pending: { keys: Set<EntityKey>; ops: Set<string> } | null = null;\n\n /** Coalesce every notification produced inside `fn` into one. */\n transaction(fn: () => void): void {\n if (this.pending) return fn();\n this.pending = { keys: new Set(), ops: new Set() };\n try {\n fn();\n } finally {\n const p = this.pending;\n this.pending = null;\n this.emit(p.keys, p.ops);\n }\n }\n\n private emit(keys: Set<EntityKey>, ops: Set<string>): void {\n if (!keys.size && !ops.size) return;\n if (this.pending) {\n for (const k of keys) this.pending.keys.add(k);\n for (const o of ops) this.pending.ops.add(o);\n return;\n }\n for (const fn of this.listeners) fn({ keys, ops });\n }\n\n clear(): void {\n this.entities.clear();\n this.results.clear();\n this.staleKeys.clear();\n this.layers.length = 0;\n this.shadow.clear();\n }\n}\n\nfunction mergeSel(a: Sel, b: Sel): Sel {\n if (a === true) return b;\n if (b === true) return a;\n const out: { [k: string]: Sel } = { ...a };\n for (const [k, v] of Object.entries(b)) out[k] = k in out ? mergeSel(out[k]!, v) : v;\n return out;\n}\n\nfunction containsRef(data: unknown, key: EntityKey): boolean {\n if (data === null || typeof data !== \"object\") return false;\n if (Array.isArray(data)) return data.some((x) => containsRef(x, key));\n if (isRef(data)) return data.$ref === key;\n return Object.values(data as Record<string, unknown>).some((x) => containsRef(x, key));\n}\n\nfunction dropRef(data: unknown, key: EntityKey): unknown {\n if (data === null || typeof data !== \"object\") return data;\n if (Array.isArray(data)) return data.filter((x) => !(isRef(x) && x.$ref === key)).map((x) => dropRef(x, key));\n if (isRef(data)) return data.$ref === key ? null : data;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(data as Record<string, unknown>)) out[k] = dropRef(v, key);\n return out;\n}\n\nexport interface RayfoldCache {\n /** @internal path lookup that follows refs into stored entities */\n getPath(v: unknown, path: string[]): unknown;\n}\nRayfoldCache.prototype.getPath = function (this: RayfoldCache, v: unknown, path: string[]): unknown {\n let cur = v;\n for (const p of path) {\n if (isRef(cur)) cur = this.get(cur.$ref);\n if (cur === null || cur === undefined || typeof cur !== \"object\") return undefined;\n cur = Array.isArray(cur) ? cur[Number(p)] : (cur as Record<string, unknown>)[p];\n }\n return cur;\n};\n\nfunction canonical(v: unknown): string {\n if (v === null || typeof v !== \"object\") return JSON.stringify(v);\n if (Array.isArray(v)) return `[${v.map(canonical).join(\",\")}]`;\n const o = v as Record<string, unknown>;\n return `{${Object.keys(o)\n .sort()\n .map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`)\n .join(\",\")}}`;\n}\n"]}
|