doxum 0.1.7 → 0.1.9
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 +217 -296
- package/dist/contract-BNStLbSE.d.ts +441 -0
- package/dist/contract-CIU5FCC1.d.cts +441 -0
- package/dist/driver-BlR81Dqg.js +200 -0
- package/dist/driver-BlR81Dqg.js.map +1 -0
- package/dist/driver-xOIkwrB8.cjs +241 -0
- package/dist/driver-xOIkwrB8.cjs.map +1 -0
- package/dist/index.cjs +666 -2222
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -20
- package/dist/index.d.ts +10 -18
- package/dist/index.js +634 -2189
- package/dist/index.js.map +1 -1
- package/dist/{integration-CZwCwFBS.cjs → integration-C87tjRop.cjs} +4 -9
- package/dist/integration-C87tjRop.cjs.map +1 -0
- package/dist/{integration-B56u1l9V.js → integration-D5XCBLJ8.js} +2 -7
- package/dist/integration-D5XCBLJ8.js.map +1 -0
- package/dist/integration.cjs +11 -11
- package/dist/integration.d.cts +10 -4
- package/dist/integration.d.ts +10 -4
- package/dist/integration.js +4 -3
- package/dist/issue-DVaGQGeP.js +576 -0
- package/dist/issue-DVaGQGeP.js.map +1 -0
- package/dist/issue-DhrNdQNg.cjs +797 -0
- package/dist/issue-DhrNdQNg.cjs.map +1 -0
- package/dist/local-sync.cjs +67 -39
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +10 -10
- package/dist/local-sync.d.ts +10 -10
- package/dist/local-sync.js +66 -38
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +23 -4
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +8 -3
- package/dist/react.d.ts +8 -3
- package/dist/react.js +21 -6
- package/dist/react.js.map +1 -1
- package/dist/store-1Uob0Ghk.cjs +2750 -0
- package/dist/store-1Uob0Ghk.cjs.map +1 -0
- package/dist/store-CD0KdGsq.d.cts +262 -0
- package/dist/store-D7QH6Rzw.js +2511 -0
- package/dist/store-D7QH6Rzw.js.map +1 -0
- package/dist/store-cp5CpfCy.d.ts +262 -0
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +26 -44
- package/skills/doxum-runtime/references/guide.en.md +95 -359
- package/skills/doxum-runtime/references/guide.zh-CN.md +80 -285
- package/skills/doxum-runtime/references/invariants.en.md +42 -165
- package/skills/doxum-runtime/references/invariants.zh-CN.md +34 -97
- package/skills/doxum-runtime/references/patterns.en.md +73 -225
- package/skills/doxum-runtime/references/patterns.zh-CN.md +73 -179
- package/dist/chunk-pbuEa-1d.js +0 -13
- package/dist/contract-DNZ4D53r.d.ts +0 -563
- package/dist/contract-j3SLGAwh.d.cts +0 -563
- package/dist/driver-CNxqMVFH.cjs +0 -69
- package/dist/driver-CNxqMVFH.cjs.map +0 -1
- package/dist/driver-CbzfW5MR.js +0 -46
- package/dist/driver-CbzfW5MR.js.map +0 -1
- package/dist/integration-B56u1l9V.js.map +0 -1
- package/dist/integration-CZwCwFBS.cjs.map +0 -1
- package/dist/ownership-CY0nPXGF.cjs +0 -304
- package/dist/ownership-CY0nPXGF.cjs.map +0 -1
- package/dist/ownership-CduRygE7.js +0 -245
- package/dist/ownership-CduRygE7.js.map +0 -1
- package/dist/runtime-0mOFbe_H.cjs +0 -2439
- package/dist/runtime-0mOFbe_H.cjs.map +0 -1
- package/dist/runtime-B22tuj9A.d.cts +0 -150
- package/dist/runtime-BFmhzPpZ.d.ts +0 -150
- package/dist/runtime-DF1q9Gje.js +0 -2129
- package/dist/runtime-DF1q9Gje.js.map +0 -1
package/dist/runtime-DF1q9Gje.js
DELETED
|
@@ -1,2129 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-pbuEa-1d.js";
|
|
2
|
-
import { i as isRecord$1, t as cloneValue, u as profile } from "./ownership-CduRygE7.js";
|
|
3
|
-
//#region core/src/schema.ts
|
|
4
|
-
const node = (value) => Object.freeze(value);
|
|
5
|
-
const field = () => node({ kind: "field" });
|
|
6
|
-
const optional = (value) => {
|
|
7
|
-
if (![
|
|
8
|
-
"field",
|
|
9
|
-
"variant",
|
|
10
|
-
"dict",
|
|
11
|
-
"list",
|
|
12
|
-
"tree"
|
|
13
|
-
].includes(value.kind)) throw new TypeError("Only field, variant, dict, list and tree nodes support optional presence.");
|
|
14
|
-
return node({
|
|
15
|
-
...value,
|
|
16
|
-
optional: true
|
|
17
|
-
});
|
|
18
|
-
};
|
|
19
|
-
const object = (shape) => node({
|
|
20
|
-
kind: "object",
|
|
21
|
-
shape
|
|
22
|
-
});
|
|
23
|
-
const variant = (tag, variants) => node({
|
|
24
|
-
kind: "variant",
|
|
25
|
-
tag,
|
|
26
|
-
variants
|
|
27
|
-
});
|
|
28
|
-
const table = (value) => node({
|
|
29
|
-
kind: "table",
|
|
30
|
-
value
|
|
31
|
-
});
|
|
32
|
-
const map = (value) => node({
|
|
33
|
-
kind: "map",
|
|
34
|
-
value
|
|
35
|
-
});
|
|
36
|
-
const dict = () => node({ kind: "dict" });
|
|
37
|
-
const list = (config) => ({
|
|
38
|
-
kind: "list",
|
|
39
|
-
keyOf: config.keyOf
|
|
40
|
-
});
|
|
41
|
-
const tree = () => node({ kind: "tree" });
|
|
42
|
-
const paths = /* @__PURE__ */ new WeakMap();
|
|
43
|
-
const collectionSchemas = /* @__PURE__ */ new WeakMap();
|
|
44
|
-
const collectionSchema = (selector) => {
|
|
45
|
-
const node = collectionSchemas.get(selector);
|
|
46
|
-
if (!node) throw new TypeError("Expected a schema-owned collection selector.");
|
|
47
|
-
return node;
|
|
48
|
-
};
|
|
49
|
-
const pathProxy = (nodes, address, owner) => {
|
|
50
|
-
const proxy = new Proxy({}, { get: (_target, property) => {
|
|
51
|
-
if (typeof property !== "string") return void 0;
|
|
52
|
-
if (nodes.every((node) => node.kind === "table" || node.kind === "map") && property === "item") return (id) => {
|
|
53
|
-
if (typeof id !== "string") throw new TypeError("Collection keys must be strings.");
|
|
54
|
-
return pathProxy(nodes.map((node) => node.value), [...address, id], owner);
|
|
55
|
-
};
|
|
56
|
-
const children = nodes.flatMap((node) => {
|
|
57
|
-
if (node.kind === "object") return node.shape[property] ? [node.shape[property]] : [];
|
|
58
|
-
if (node.kind !== "variant") return [];
|
|
59
|
-
if (property === node.tag) return [field()];
|
|
60
|
-
return Object.values(node.variants).flatMap((branch) => branch.shape[property] ? [branch.shape[property]] : []);
|
|
61
|
-
});
|
|
62
|
-
if (!children.length) throw new TypeError(`Invalid schema path segment: ${property}`);
|
|
63
|
-
return pathProxy(children, [...address, property], owner);
|
|
64
|
-
} });
|
|
65
|
-
paths.set(proxy, {
|
|
66
|
-
nodes,
|
|
67
|
-
address,
|
|
68
|
-
owner
|
|
69
|
-
});
|
|
70
|
-
return proxy;
|
|
71
|
-
};
|
|
72
|
-
const select = (owner, kind, pick) => {
|
|
73
|
-
const scope = {};
|
|
74
|
-
const value = pick(pathProxy([object(owner.shape)], [], scope));
|
|
75
|
-
const selected = typeof value === "object" && value !== null ? paths.get(value) : void 0;
|
|
76
|
-
if (!selected || selected.owner !== scope) throw new TypeError("Selector must return a path from its callback.");
|
|
77
|
-
if (kind === "collection" && !selected.nodes.every((node) => node.kind === "table" || node.kind === "map")) throw new TypeError("Collection selectors require a table or map path.");
|
|
78
|
-
const result = Object.freeze({
|
|
79
|
-
kind,
|
|
80
|
-
schema: owner,
|
|
81
|
-
address: Object.freeze([...selected.address])
|
|
82
|
-
});
|
|
83
|
-
if (kind === "collection") collectionSchemas.set(result, selected.nodes[0]);
|
|
84
|
-
return result;
|
|
85
|
-
};
|
|
86
|
-
const schema = (shape) => {
|
|
87
|
-
const result = {
|
|
88
|
-
kind: "schema",
|
|
89
|
-
shape,
|
|
90
|
-
collection: (pick) => select(result, "collection", pick),
|
|
91
|
-
value: (pick) => select(result, "value", pick)
|
|
92
|
-
};
|
|
93
|
-
return Object.freeze(result);
|
|
94
|
-
};
|
|
95
|
-
//#endregion
|
|
96
|
-
//#region core/src/address.ts
|
|
97
|
-
const registries = /* @__PURE__ */ new WeakMap();
|
|
98
|
-
const registryNode = () => ({ static: /* @__PURE__ */ new Map() });
|
|
99
|
-
const registryFor = (schema) => {
|
|
100
|
-
const cached = registries.get(schema);
|
|
101
|
-
if (cached) return cached;
|
|
102
|
-
const registry = {
|
|
103
|
-
root: registryNode(),
|
|
104
|
-
nextPath: 0
|
|
105
|
-
};
|
|
106
|
-
registries.set(schema, registry);
|
|
107
|
-
return registry;
|
|
108
|
-
};
|
|
109
|
-
const hashText = (value, seed) => {
|
|
110
|
-
let hash = seed;
|
|
111
|
-
for (let index = 0; index < value.length; index += 1) hash = Math.imul(hash ^ value.charCodeAt(index), 16777619);
|
|
112
|
-
return hash >>> 0;
|
|
113
|
-
};
|
|
114
|
-
const appendAddressHash = (hash, segment) => hashText(segment, hashText("/", hash));
|
|
115
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
116
|
-
const variantNode = (node, value) => {
|
|
117
|
-
const tag = isRecord(value) && typeof value[node.tag] === "string" ? value[node.tag] : void 0;
|
|
118
|
-
return node.variants[String(tag ?? Object.keys(node.variants)[0] ?? "")];
|
|
119
|
-
};
|
|
120
|
-
const schemaRoot$1 = (schema) => ({
|
|
121
|
-
kind: "object",
|
|
122
|
-
shape: schema.shape
|
|
123
|
-
});
|
|
124
|
-
const step = (nodeInput, value, segment) => {
|
|
125
|
-
let node = nodeInput;
|
|
126
|
-
if (!node) return {
|
|
127
|
-
node: void 0,
|
|
128
|
-
dynamic: false
|
|
129
|
-
};
|
|
130
|
-
if (node.kind === "variant") node = variantNode(node, value);
|
|
131
|
-
if (!node) return {
|
|
132
|
-
node: void 0,
|
|
133
|
-
dynamic: false
|
|
134
|
-
};
|
|
135
|
-
if (node.kind === "object") return {
|
|
136
|
-
node: node.shape[segment],
|
|
137
|
-
dynamic: false
|
|
138
|
-
};
|
|
139
|
-
if (node.kind === "table" || node.kind === "map") return {
|
|
140
|
-
node: node.value,
|
|
141
|
-
dynamic: true
|
|
142
|
-
};
|
|
143
|
-
if (node.kind === "dict" || node.kind === "list" || node.kind === "tree") return {
|
|
144
|
-
node,
|
|
145
|
-
dynamic: true
|
|
146
|
-
};
|
|
147
|
-
return {
|
|
148
|
-
node: void 0,
|
|
149
|
-
dynamic: false
|
|
150
|
-
};
|
|
151
|
-
};
|
|
152
|
-
const pathFor = (schema, address, document) => {
|
|
153
|
-
const registry = registryFor(schema);
|
|
154
|
-
let trie = registry.root;
|
|
155
|
-
let node = schemaRoot$1(schema);
|
|
156
|
-
let value = document;
|
|
157
|
-
for (const segment of address) {
|
|
158
|
-
profile.address.schemaStep();
|
|
159
|
-
profile.address.documentStep();
|
|
160
|
-
const resolved = step(node, value, segment);
|
|
161
|
-
if (!resolved.node) return void 0;
|
|
162
|
-
trie = resolved.dynamic ? trie.dynamic ??= registryNode() : (() => {
|
|
163
|
-
const existing = trie.static.get(segment);
|
|
164
|
-
if (existing) return existing;
|
|
165
|
-
const created = registryNode();
|
|
166
|
-
trie.static.set(segment, created);
|
|
167
|
-
return created;
|
|
168
|
-
})();
|
|
169
|
-
node = resolved.node;
|
|
170
|
-
value = readSegment(value, segment);
|
|
171
|
-
}
|
|
172
|
-
if (trie.path === void 0) trie.path = registry.nextPath++;
|
|
173
|
-
return trie.path;
|
|
174
|
-
};
|
|
175
|
-
const resolveAddress = (schema, address, document) => {
|
|
176
|
-
if (!Array.isArray(address)) return void 0;
|
|
177
|
-
for (const segment of address) if (typeof segment !== "string") return void 0;
|
|
178
|
-
profile.address.arrayCopied();
|
|
179
|
-
const owned = Object.freeze(address.slice());
|
|
180
|
-
const path = pathFor(schema, owned, document);
|
|
181
|
-
return path === void 0 ? void 0 : {
|
|
182
|
-
path,
|
|
183
|
-
address: owned
|
|
184
|
-
};
|
|
185
|
-
};
|
|
186
|
-
const nodeAt = (schema, address, document) => {
|
|
187
|
-
let node = schemaRoot$1(schema);
|
|
188
|
-
let value = document;
|
|
189
|
-
for (const segment of address) {
|
|
190
|
-
node = step(node, value, segment).node;
|
|
191
|
-
value = readSegment(value, segment);
|
|
192
|
-
if (!node) return void 0;
|
|
193
|
-
}
|
|
194
|
-
return node;
|
|
195
|
-
};
|
|
196
|
-
const readSegment = (value, segment) => {
|
|
197
|
-
if (!isRecord(value) && !Array.isArray(value)) return void 0;
|
|
198
|
-
if (isRecord(value) && "byId" in value && isRecord(value.byId)) {
|
|
199
|
-
const entity = value.byId[segment];
|
|
200
|
-
if (entity !== void 0 || Object.prototype.hasOwnProperty.call(value.byId, segment)) return entity;
|
|
201
|
-
}
|
|
202
|
-
return value[segment];
|
|
203
|
-
};
|
|
204
|
-
const read = (root, address) => {
|
|
205
|
-
let value = root;
|
|
206
|
-
for (const segment of address) {
|
|
207
|
-
value = readSegment(value, segment);
|
|
208
|
-
if (value === void 0) return void 0;
|
|
209
|
-
}
|
|
210
|
-
return value;
|
|
211
|
-
};
|
|
212
|
-
/** Resolves schema and document location in one address traversal. */
|
|
213
|
-
const resolveLocated = (schema, root, address) => {
|
|
214
|
-
if (address.length === 0) return void 0;
|
|
215
|
-
let node = schemaRoot$1(schema);
|
|
216
|
-
let current = root;
|
|
217
|
-
let collection;
|
|
218
|
-
let addressHash = 2166136261;
|
|
219
|
-
for (let index = 0; index < address.length - 1; index += 1) {
|
|
220
|
-
profile.address.schemaStep();
|
|
221
|
-
profile.address.documentStep();
|
|
222
|
-
const resolved = step(node, current, address[index]);
|
|
223
|
-
if (!resolved.node) return void 0;
|
|
224
|
-
if (node?.kind === "table" || node?.kind === "map") collection = {
|
|
225
|
-
address: address.slice(0, index),
|
|
226
|
-
addressHash,
|
|
227
|
-
id: address[index],
|
|
228
|
-
...collection ? { parent: collection } : {}
|
|
229
|
-
};
|
|
230
|
-
addressHash = appendAddressHash(addressHash, address[index]);
|
|
231
|
-
node = resolved.node;
|
|
232
|
-
current = readSegment(current, address[index]);
|
|
233
|
-
if (!node) return void 0;
|
|
234
|
-
}
|
|
235
|
-
profile.address.schemaStep();
|
|
236
|
-
profile.address.documentStep();
|
|
237
|
-
const last = address[address.length - 1];
|
|
238
|
-
const resolved = step(node, current, last);
|
|
239
|
-
if (!resolved.node || !isRecord(current) && !Array.isArray(current)) return void 0;
|
|
240
|
-
if (node?.kind === "table" || node?.kind === "map") collection = {
|
|
241
|
-
address: address.slice(0, -1),
|
|
242
|
-
addressHash,
|
|
243
|
-
id: last,
|
|
244
|
-
...collection ? { parent: collection } : {}
|
|
245
|
-
};
|
|
246
|
-
addressHash = appendAddressHash(addressHash, last);
|
|
247
|
-
if (isRecord(current) && "byId" in current && isRecord(current.byId) && last in current.byId) return {
|
|
248
|
-
addressHash,
|
|
249
|
-
node: resolved.node,
|
|
250
|
-
parent: current.byId,
|
|
251
|
-
key: last,
|
|
252
|
-
value: current.byId[last],
|
|
253
|
-
...collection ? { collection } : {}
|
|
254
|
-
};
|
|
255
|
-
return {
|
|
256
|
-
addressHash,
|
|
257
|
-
node: resolved.node,
|
|
258
|
-
parent: current,
|
|
259
|
-
key: Array.isArray(current) && /^\d+$/.test(last) ? Number(last) : last,
|
|
260
|
-
value: readSegment(current, last),
|
|
261
|
-
...collection ? { collection } : {}
|
|
262
|
-
};
|
|
263
|
-
};
|
|
264
|
-
const locate = (root, address) => {
|
|
265
|
-
if (address.length === 0) return void 0;
|
|
266
|
-
let current = root;
|
|
267
|
-
for (let index = 0; index < address.length - 1; index += 1) current = readSegment(current, address[index]);
|
|
268
|
-
if (!isRecord(current) && !Array.isArray(current)) return void 0;
|
|
269
|
-
const last = address[address.length - 1];
|
|
270
|
-
if (isRecord(current) && "byId" in current && isRecord(current.byId) && last in current.byId) return {
|
|
271
|
-
parent: current.byId,
|
|
272
|
-
key: last,
|
|
273
|
-
value: current.byId[last]
|
|
274
|
-
};
|
|
275
|
-
return {
|
|
276
|
-
parent: current,
|
|
277
|
-
key: Array.isArray(current) && /^\d+$/.test(last) ? Number(last) : last,
|
|
278
|
-
value: readSegment(current, last)
|
|
279
|
-
};
|
|
280
|
-
};
|
|
281
|
-
const set = (root, address, value) => {
|
|
282
|
-
const target = locate(root, address);
|
|
283
|
-
if (!target) return false;
|
|
284
|
-
target.parent[target.key] = value;
|
|
285
|
-
return true;
|
|
286
|
-
};
|
|
287
|
-
const contains = (parent, child) => {
|
|
288
|
-
profile.address.prefixComparison();
|
|
289
|
-
if (parent.length > child.length) return false;
|
|
290
|
-
for (let index = 0; index < parent.length; index += 1) {
|
|
291
|
-
profile.address.segmentCompared();
|
|
292
|
-
if (parent[index] !== child[index]) return false;
|
|
293
|
-
}
|
|
294
|
-
return true;
|
|
295
|
-
};
|
|
296
|
-
const overlaps = (a, b) => contains(a, b) || contains(b, a);
|
|
297
|
-
const debugKey = (address) => {
|
|
298
|
-
let result = "";
|
|
299
|
-
for (let index = 0; index < address.length; index += 1) {
|
|
300
|
-
if (index > 0) result += "/";
|
|
301
|
-
result += address[index].replaceAll("~", "~~").replaceAll("/", "~/");
|
|
302
|
-
}
|
|
303
|
-
return result;
|
|
304
|
-
};
|
|
305
|
-
//#endregion
|
|
306
|
-
//#region core/src/impact-target.ts
|
|
307
|
-
var impact_target_exports = /* @__PURE__ */ __exportAll({
|
|
308
|
-
address: () => address$1,
|
|
309
|
-
belongs: () => belongs,
|
|
310
|
-
bucket: () => bucket,
|
|
311
|
-
id: () => id,
|
|
312
|
-
same: () => same
|
|
313
|
-
});
|
|
314
|
-
const address$1 = (target) => "at" in target ? target.at : target.address;
|
|
315
|
-
const id = (target) => target.kind === "collection" && "id" in target ? target.id : void 0;
|
|
316
|
-
const belongs = (target, schema) => !("schema" in target) || target.schema === schema;
|
|
317
|
-
const same = (left, right) => {
|
|
318
|
-
if (left.kind !== right.kind) return false;
|
|
319
|
-
if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
|
|
320
|
-
const leftAddress = address$1(left);
|
|
321
|
-
const rightAddress = address$1(right);
|
|
322
|
-
if (leftAddress.length !== rightAddress.length) return false;
|
|
323
|
-
for (let index = 0; index < leftAddress.length; index += 1) if (leftAddress[index] !== rightAddress[index]) return false;
|
|
324
|
-
return left.kind !== "collection" || id(left) === id(right);
|
|
325
|
-
};
|
|
326
|
-
const bucket = (target) => address$1(target)[0];
|
|
327
|
-
//#endregion
|
|
328
|
-
//#region core/src/runtime/contract.ts
|
|
329
|
-
var DocumentReentrancyError = class extends Error {
|
|
330
|
-
constructor() {
|
|
331
|
-
super("Document writes cannot be re-entered during an update or notification.");
|
|
332
|
-
this.name = "DocumentReentrancyError";
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
var DocumentDisposedError = class extends Error {
|
|
336
|
-
constructor() {
|
|
337
|
-
super("Doxum runtime has been disposed.");
|
|
338
|
-
this.name = "DocumentDisposedError";
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
//#endregion
|
|
342
|
-
//#region core/src/mutation/anchor.ts
|
|
343
|
-
const length = (keys) => keys.length;
|
|
344
|
-
const keyAt = (keys, index) => Array.isArray(keys) ? keys[index] : keys.at(index);
|
|
345
|
-
const keyIndex = (keys, key) => Array.isArray(keys) ? keys.indexOf(key) : keys.index(key);
|
|
346
|
-
const keys = (values, keyOf) => ({
|
|
347
|
-
length: values.length,
|
|
348
|
-
at: (index) => index >= 0 && index < values.length ? keyOf(values[index]) : void 0,
|
|
349
|
-
index: (key) => {
|
|
350
|
-
for (let index = 0; index < values.length; index += 1) if (keyOf(values[index]) === key) return index;
|
|
351
|
-
return -1;
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
const index = (keys, anchor) => {
|
|
355
|
-
if (!anchor) return length(keys);
|
|
356
|
-
if ("at" in anchor) return anchor.at === "start" ? 0 : length(keys);
|
|
357
|
-
if ("before" in anchor) return keyIndex(keys, anchor.before);
|
|
358
|
-
return keyIndex(keys, anchor.after) + 1;
|
|
359
|
-
};
|
|
360
|
-
const at = (keys, position) => position <= 0 ? { at: "start" } : { after: keyAt(keys, position - 1) };
|
|
361
|
-
const valid = (keys, anchor) => {
|
|
362
|
-
if (!anchor) return true;
|
|
363
|
-
if (typeof anchor !== "object" || anchor === null) return false;
|
|
364
|
-
if (Number("at" in anchor) + Number("before" in anchor) + Number("after" in anchor) !== 1) return false;
|
|
365
|
-
if ("at" in anchor) return anchor.at === "start" || anchor.at === "end";
|
|
366
|
-
return keyIndex(keys, "before" in anchor ? anchor.before : anchor.after) >= 0;
|
|
367
|
-
};
|
|
368
|
-
const afterRemove = (keys, removedIndex, anchor) => {
|
|
369
|
-
const position = index(keys, anchor);
|
|
370
|
-
return position > removedIndex ? position - 1 : position;
|
|
371
|
-
};
|
|
372
|
-
const validPositions = (length, positions) => {
|
|
373
|
-
let previous = -1;
|
|
374
|
-
for (let index = 0; index < positions.length; index += 1) {
|
|
375
|
-
const position = positions[index];
|
|
376
|
-
if (!Number.isInteger(position) || position < 0 || position <= previous || position > length + index) return false;
|
|
377
|
-
previous = position;
|
|
378
|
-
}
|
|
379
|
-
return true;
|
|
380
|
-
};
|
|
381
|
-
const restore = (keys, inserted, positions) => {
|
|
382
|
-
const currentLength = keys.length;
|
|
383
|
-
if (inserted.length !== positions.length || !validPositions(currentLength, positions)) throw new Error("Ordered restore positions are invalid.");
|
|
384
|
-
let readIndex = currentLength - 1;
|
|
385
|
-
let insertedIndex = inserted.length - 1;
|
|
386
|
-
keys.length = currentLength + inserted.length;
|
|
387
|
-
for (let writeIndex = keys.length - 1; writeIndex >= 0; writeIndex -= 1) if (insertedIndex >= 0 && positions[insertedIndex] === writeIndex) {
|
|
388
|
-
keys[writeIndex] = inserted[insertedIndex];
|
|
389
|
-
insertedIndex -= 1;
|
|
390
|
-
} else {
|
|
391
|
-
keys[writeIndex] = keys[readIndex];
|
|
392
|
-
readIndex -= 1;
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region core/src/access/reader.ts
|
|
397
|
-
const schemaRoot = (schema) => object(schema.shape);
|
|
398
|
-
const assertActive = (context) => {
|
|
399
|
-
if (!context.active()) throw new Error("Document reader is no longer active.");
|
|
400
|
-
};
|
|
401
|
-
const readAt = (context, address) => {
|
|
402
|
-
assertActive(context);
|
|
403
|
-
profile.reader.lookup();
|
|
404
|
-
return read(context.root(), address);
|
|
405
|
-
};
|
|
406
|
-
const collectValue = (context, address) => {
|
|
407
|
-
context.dependencies?.record({
|
|
408
|
-
kind: "value",
|
|
409
|
-
at: address
|
|
410
|
-
});
|
|
411
|
-
};
|
|
412
|
-
const collectCollection = (context, address, id) => {
|
|
413
|
-
const target = {
|
|
414
|
-
kind: "collection",
|
|
415
|
-
at: address,
|
|
416
|
-
...id === void 0 ? {} : { id }
|
|
417
|
-
};
|
|
418
|
-
context.dependencies?.record(target);
|
|
419
|
-
};
|
|
420
|
-
const readerFor = (node, context, address = []) => {
|
|
421
|
-
if (node.kind === "field") return { get: () => {
|
|
422
|
-
collectValue(context, address);
|
|
423
|
-
return readAt(context, address);
|
|
424
|
-
} };
|
|
425
|
-
if (node.kind === "dict") return {
|
|
426
|
-
get: (key) => {
|
|
427
|
-
collectValue(context, address);
|
|
428
|
-
const value = readAt(context, address);
|
|
429
|
-
return cloneValue(isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, key) ? value[key] : void 0, "reader");
|
|
430
|
-
},
|
|
431
|
-
has: (key) => {
|
|
432
|
-
collectValue(context, address);
|
|
433
|
-
const value = readAt(context, address);
|
|
434
|
-
return isRecord$1(value) && Object.prototype.hasOwnProperty.call(value, key);
|
|
435
|
-
},
|
|
436
|
-
keys: () => {
|
|
437
|
-
collectValue(context, address);
|
|
438
|
-
const value = readAt(context, address);
|
|
439
|
-
return isRecord$1(value) ? Object.keys(value) : [];
|
|
440
|
-
},
|
|
441
|
-
values: () => {
|
|
442
|
-
collectValue(context, address);
|
|
443
|
-
profile.reader.structuralSnapshot();
|
|
444
|
-
return cloneValue(readAt(context, address) ?? {}, "reader");
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
if (node.kind === "list") return {
|
|
448
|
-
get: (key) => {
|
|
449
|
-
collectValue(context, address);
|
|
450
|
-
const value = readAt(context, address);
|
|
451
|
-
if (!Array.isArray(value)) return void 0;
|
|
452
|
-
return cloneValue(value[keys(value, node.keyOf).index(key)], "reader");
|
|
453
|
-
},
|
|
454
|
-
has: (key) => {
|
|
455
|
-
collectValue(context, address);
|
|
456
|
-
const value = readAt(context, address);
|
|
457
|
-
return Array.isArray(value) && keys(value, node.keyOf).index(key) >= 0;
|
|
458
|
-
},
|
|
459
|
-
values: () => {
|
|
460
|
-
collectValue(context, address);
|
|
461
|
-
const value = readAt(context, address);
|
|
462
|
-
profile.reader.structuralSnapshot();
|
|
463
|
-
return cloneValue(Array.isArray(value) ? value : [], "reader");
|
|
464
|
-
},
|
|
465
|
-
length: () => {
|
|
466
|
-
collectValue(context, address);
|
|
467
|
-
const value = readAt(context, address);
|
|
468
|
-
return Array.isArray(value) ? value.length : 0;
|
|
469
|
-
},
|
|
470
|
-
at: (index) => {
|
|
471
|
-
collectValue(context, address);
|
|
472
|
-
const value = readAt(context, address);
|
|
473
|
-
profile.reader.structuralSnapshot();
|
|
474
|
-
return cloneValue(Array.isArray(value) ? value[index] : void 0, "reader");
|
|
475
|
-
}
|
|
476
|
-
};
|
|
477
|
-
if (node.kind === "tree") return {
|
|
478
|
-
rootId: () => {
|
|
479
|
-
collectValue(context, address);
|
|
480
|
-
const value = readAt(context, address);
|
|
481
|
-
return isRecord$1(value) && typeof value.rootId === "string" ? value.rootId : void 0;
|
|
482
|
-
},
|
|
483
|
-
has: (id) => {
|
|
484
|
-
collectValue(context, address);
|
|
485
|
-
const value = readAt(context, address);
|
|
486
|
-
return isRecord$1(value) && isRecord$1(value.nodes) && Object.prototype.hasOwnProperty.call(value.nodes, id);
|
|
487
|
-
},
|
|
488
|
-
value: (id) => {
|
|
489
|
-
collectValue(context, address);
|
|
490
|
-
const value = readAt(context, address);
|
|
491
|
-
profile.reader.structuralSnapshot();
|
|
492
|
-
return cloneValue(isRecord$1(value) && isRecord$1(value.nodes) && isRecord$1(value.nodes[id]) ? value.nodes[id].value : void 0, "reader");
|
|
493
|
-
},
|
|
494
|
-
parent: (id) => {
|
|
495
|
-
collectValue(context, address);
|
|
496
|
-
const value = readAt(context, address);
|
|
497
|
-
const entry = isRecord$1(value) && isRecord$1(value.nodes) ? value.nodes[id] : void 0;
|
|
498
|
-
return isRecord$1(entry) && typeof entry.parentId === "string" ? entry.parentId : void 0;
|
|
499
|
-
},
|
|
500
|
-
children: (id) => {
|
|
501
|
-
collectValue(context, address);
|
|
502
|
-
const value = readAt(context, address);
|
|
503
|
-
const entry = isRecord$1(value) && isRecord$1(value.nodes) ? value.nodes[id] : void 0;
|
|
504
|
-
return isRecord$1(entry) && Array.isArray(entry.children) ? [...entry.children] : [];
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
if (node.kind === "table" || node.kind === "map") {
|
|
508
|
-
let items;
|
|
509
|
-
return {
|
|
510
|
-
ids: () => {
|
|
511
|
-
collectCollection(context, address);
|
|
512
|
-
const value = readAt(context, address);
|
|
513
|
-
const ids = node.kind === "table" && isRecord$1(value) && Array.isArray(value.ids) ? [...value.ids] : isRecord$1(value) ? Object.keys(value) : [];
|
|
514
|
-
profile.reader.collectionIds(ids.length);
|
|
515
|
-
return ids;
|
|
516
|
-
},
|
|
517
|
-
has: (id) => {
|
|
518
|
-
collectCollection(context, address, id);
|
|
519
|
-
const value = readAt(context, address);
|
|
520
|
-
const byId = node.kind === "table" && isRecord$1(value) && isRecord$1(value.byId) ? value.byId : value;
|
|
521
|
-
return isRecord$1(byId) && Object.prototype.hasOwnProperty.call(byId, id);
|
|
522
|
-
},
|
|
523
|
-
get: (id) => {
|
|
524
|
-
collectCollection(context, address, id);
|
|
525
|
-
const value = readAt(context, address);
|
|
526
|
-
const byId = node.kind === "table" && isRecord$1(value) && isRecord$1(value.byId) ? value.byId : value;
|
|
527
|
-
if (!isRecord$1(byId) || !Object.prototype.hasOwnProperty.call(byId, id)) return void 0;
|
|
528
|
-
const cached = items?.get(id);
|
|
529
|
-
if (cached) return cached;
|
|
530
|
-
const reader = readerFor(node.value, context, [...address, id]);
|
|
531
|
-
(items ??= /* @__PURE__ */ new Map()).set(id, reader);
|
|
532
|
-
return reader;
|
|
533
|
-
}
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
if (node.kind === "variant") return { get: () => {
|
|
537
|
-
collectValue(context, address);
|
|
538
|
-
return cloneValue(readAt(context, address), "reader");
|
|
539
|
-
} };
|
|
540
|
-
let children;
|
|
541
|
-
return new Proxy({}, { get: (_target, property) => {
|
|
542
|
-
if (typeof property === "symbol") return void 0;
|
|
543
|
-
const child = node.shape[property];
|
|
544
|
-
if (!child) return void 0;
|
|
545
|
-
const cached = children?.get(property);
|
|
546
|
-
if (cached) return cached;
|
|
547
|
-
const reader = readerFor(child, context, [...address, property]);
|
|
548
|
-
(children ??= /* @__PURE__ */ new Map()).set(property, reader);
|
|
549
|
-
return reader;
|
|
550
|
-
} });
|
|
551
|
-
};
|
|
552
|
-
const documentReader = (schema, root, active, dependencies) => {
|
|
553
|
-
profile.reader.session();
|
|
554
|
-
return readerFor(schemaRoot(schema), {
|
|
555
|
-
root,
|
|
556
|
-
active,
|
|
557
|
-
dependencies
|
|
558
|
-
});
|
|
559
|
-
};
|
|
560
|
-
//#endregion
|
|
561
|
-
//#region core/src/mutation/footprint.ts
|
|
562
|
-
const sameAddress = (left, right) => left.length === right.length && left.every((segment, index) => segment === right[index]);
|
|
563
|
-
const containsAddress = (parent, child) => parent.length <= child.length && parent.every((segment, index) => segment === child[index]);
|
|
564
|
-
const address = (value) => Array.isArray(value) && value.every((segment) => typeof segment === "string") ? Object.freeze(value.slice()) : void 0;
|
|
565
|
-
const targetKey = (target) => {
|
|
566
|
-
const at = target.at.join("\0");
|
|
567
|
-
if (target.kind === "dictionary-key" || target.kind === "list-item") return `${target.kind}:${at}:${target.key}`;
|
|
568
|
-
if (target.kind === "entity" || target.kind === "tree-node") return `${target.kind}:${at}:${target.id}`;
|
|
569
|
-
return `${target.kind}:${at}`;
|
|
570
|
-
};
|
|
571
|
-
const freezeTarget = (target) => Object.freeze({
|
|
572
|
-
...target,
|
|
573
|
-
at: Object.freeze(target.at.slice())
|
|
574
|
-
});
|
|
575
|
-
const add = (targets, target) => {
|
|
576
|
-
const stable = freezeTarget(target);
|
|
577
|
-
targets.set(targetKey(stable), stable);
|
|
578
|
-
};
|
|
579
|
-
const commandFootprint = (operations) => {
|
|
580
|
-
const targets = /* @__PURE__ */ new Map();
|
|
581
|
-
for (const operation of operations) switch (operation.type) {
|
|
582
|
-
case "field.set":
|
|
583
|
-
case "field.clear":
|
|
584
|
-
case "value.clear":
|
|
585
|
-
case "variant.replace":
|
|
586
|
-
add(targets, {
|
|
587
|
-
kind: "value",
|
|
588
|
-
at: operation.at
|
|
589
|
-
});
|
|
590
|
-
break;
|
|
591
|
-
case "dict.replace":
|
|
592
|
-
add(targets, {
|
|
593
|
-
kind: "dictionary",
|
|
594
|
-
at: operation.at
|
|
595
|
-
});
|
|
596
|
-
break;
|
|
597
|
-
case "dict.set":
|
|
598
|
-
case "dict.delete":
|
|
599
|
-
add(targets, {
|
|
600
|
-
kind: "dictionary-key",
|
|
601
|
-
at: operation.at,
|
|
602
|
-
key: operation.key
|
|
603
|
-
});
|
|
604
|
-
break;
|
|
605
|
-
case "entity.create":
|
|
606
|
-
add(targets, {
|
|
607
|
-
kind: "collection",
|
|
608
|
-
at: operation.at
|
|
609
|
-
});
|
|
610
|
-
for (const entry of operation.entries) add(targets, {
|
|
611
|
-
kind: "entity",
|
|
612
|
-
at: operation.at,
|
|
613
|
-
id: entry.id
|
|
614
|
-
});
|
|
615
|
-
break;
|
|
616
|
-
case "entity.remove":
|
|
617
|
-
add(targets, {
|
|
618
|
-
kind: "collection",
|
|
619
|
-
at: operation.at
|
|
620
|
-
});
|
|
621
|
-
for (const id of operation.ids) add(targets, {
|
|
622
|
-
kind: "entity",
|
|
623
|
-
at: operation.at,
|
|
624
|
-
id
|
|
625
|
-
});
|
|
626
|
-
break;
|
|
627
|
-
case "entity.move":
|
|
628
|
-
add(targets, {
|
|
629
|
-
kind: "collection",
|
|
630
|
-
at: operation.at
|
|
631
|
-
});
|
|
632
|
-
add(targets, {
|
|
633
|
-
kind: "entity",
|
|
634
|
-
at: operation.at,
|
|
635
|
-
id: operation.id
|
|
636
|
-
});
|
|
637
|
-
break;
|
|
638
|
-
case "list.insert":
|
|
639
|
-
case "list.move":
|
|
640
|
-
case "list.remove":
|
|
641
|
-
add(targets, {
|
|
642
|
-
kind: "list",
|
|
643
|
-
at: operation.at
|
|
644
|
-
});
|
|
645
|
-
add(targets, {
|
|
646
|
-
kind: "list-item",
|
|
647
|
-
at: operation.at,
|
|
648
|
-
key: operation.key
|
|
649
|
-
});
|
|
650
|
-
break;
|
|
651
|
-
case "list.replace":
|
|
652
|
-
add(targets, {
|
|
653
|
-
kind: "list",
|
|
654
|
-
at: operation.at
|
|
655
|
-
});
|
|
656
|
-
for (const key of operation.keys) add(targets, {
|
|
657
|
-
kind: "list-item",
|
|
658
|
-
at: operation.at,
|
|
659
|
-
key
|
|
660
|
-
});
|
|
661
|
-
break;
|
|
662
|
-
case "tree.insert":
|
|
663
|
-
case "tree.move":
|
|
664
|
-
case "tree.remove":
|
|
665
|
-
case "tree.set":
|
|
666
|
-
add(targets, {
|
|
667
|
-
kind: "tree",
|
|
668
|
-
at: operation.at
|
|
669
|
-
});
|
|
670
|
-
add(targets, {
|
|
671
|
-
kind: "tree-node",
|
|
672
|
-
at: operation.at,
|
|
673
|
-
id: operation.treeNodeId
|
|
674
|
-
});
|
|
675
|
-
break;
|
|
676
|
-
case "tree.replace":
|
|
677
|
-
add(targets, {
|
|
678
|
-
kind: "tree",
|
|
679
|
-
at: operation.at
|
|
680
|
-
});
|
|
681
|
-
break;
|
|
682
|
-
}
|
|
683
|
-
return Object.freeze(Array.from(targets.values()));
|
|
684
|
-
};
|
|
685
|
-
const overlapsValueAndEntity = (value, entity) => containsAddress([...entity.at, entity.id], value.at) || containsAddress(value.at, [...entity.at, entity.id]);
|
|
686
|
-
const targetsOverlap = (left, right) => {
|
|
687
|
-
if (left.kind === "value" && right.kind === "value") return containsAddress(left.at, right.at) || containsAddress(right.at, left.at);
|
|
688
|
-
if (left.kind === "value" && right.kind === "entity") return overlapsValueAndEntity(left, right);
|
|
689
|
-
if (left.kind === "entity" && right.kind === "value") return overlapsValueAndEntity(right, left);
|
|
690
|
-
if (left.kind === "dictionary" && right.kind === "dictionary") return sameAddress(left.at, right.at);
|
|
691
|
-
if (left.kind === "dictionary" && right.kind === "dictionary-key") return sameAddress(left.at, right.at);
|
|
692
|
-
if (left.kind === "dictionary-key" && right.kind === "dictionary") return sameAddress(left.at, right.at);
|
|
693
|
-
if (left.kind === "dictionary-key" && right.kind === "dictionary-key") return sameAddress(left.at, right.at) && left.key === right.key;
|
|
694
|
-
if (left.kind === "collection" && right.kind === "collection") return sameAddress(left.at, right.at);
|
|
695
|
-
if (left.kind === "collection" && right.kind === "entity") return sameAddress(left.at, right.at);
|
|
696
|
-
if (left.kind === "entity" && right.kind === "collection") return sameAddress(left.at, right.at);
|
|
697
|
-
if (left.kind === "entity" && right.kind === "entity") return sameAddress(left.at, right.at) && left.id === right.id;
|
|
698
|
-
if (left.kind === "list" && (right.kind === "list" || right.kind === "list-item")) return sameAddress(left.at, right.at);
|
|
699
|
-
if (right.kind === "list" && left.kind === "list-item") return sameAddress(left.at, right.at);
|
|
700
|
-
if (left.kind === "list-item" && right.kind === "list-item") return sameAddress(left.at, right.at) && left.key === right.key;
|
|
701
|
-
if (left.kind === "tree" && (right.kind === "tree" || right.kind === "tree-node")) return sameAddress(left.at, right.at);
|
|
702
|
-
if (right.kind === "tree" && left.kind === "tree-node") return sameAddress(left.at, right.at);
|
|
703
|
-
if (left.kind === "tree-node" && right.kind === "tree-node") return sameAddress(left.at, right.at) && left.id === right.id;
|
|
704
|
-
return false;
|
|
705
|
-
};
|
|
706
|
-
const footprintsOverlap = (left, right) => left.some((leftTarget) => right.some((rightTarget) => targetsOverlap(leftTarget, rightTarget)));
|
|
707
|
-
const decodedTarget = (value) => {
|
|
708
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
709
|
-
const record = value;
|
|
710
|
-
const at = address(record.at);
|
|
711
|
-
if (!at || typeof record.kind !== "string") return void 0;
|
|
712
|
-
switch (record.kind) {
|
|
713
|
-
case "value":
|
|
714
|
-
case "dictionary":
|
|
715
|
-
case "collection":
|
|
716
|
-
case "list":
|
|
717
|
-
case "tree": return freezeTarget({
|
|
718
|
-
kind: record.kind,
|
|
719
|
-
at
|
|
720
|
-
});
|
|
721
|
-
case "dictionary-key":
|
|
722
|
-
case "list-item": return typeof record.key === "string" ? freezeTarget({
|
|
723
|
-
kind: record.kind,
|
|
724
|
-
at,
|
|
725
|
-
key: record.key
|
|
726
|
-
}) : void 0;
|
|
727
|
-
case "entity":
|
|
728
|
-
case "tree-node": return typeof record.id === "string" ? freezeTarget({
|
|
729
|
-
kind: record.kind,
|
|
730
|
-
at,
|
|
731
|
-
id: record.id
|
|
732
|
-
}) : void 0;
|
|
733
|
-
default: return;
|
|
734
|
-
}
|
|
735
|
-
};
|
|
736
|
-
const decodeCommandFootprint = (value) => {
|
|
737
|
-
if (!Array.isArray(value)) return void 0;
|
|
738
|
-
const targets = /* @__PURE__ */ new Map();
|
|
739
|
-
for (const entry of value) {
|
|
740
|
-
const target = decodedTarget(entry);
|
|
741
|
-
if (!target) return void 0;
|
|
742
|
-
targets.set(targetKey(target), target);
|
|
743
|
-
}
|
|
744
|
-
return Object.freeze(Array.from(targets.values()));
|
|
745
|
-
};
|
|
746
|
-
//#endregion
|
|
747
|
-
//#region core/src/runtime/access.ts
|
|
748
|
-
const states = /* @__PURE__ */ new WeakMap();
|
|
749
|
-
const bindRuntimeAccess = (runtime, state) => {
|
|
750
|
-
states.set(runtime, state);
|
|
751
|
-
};
|
|
752
|
-
const accessOf = (runtime) => {
|
|
753
|
-
const state = states.get(runtime);
|
|
754
|
-
if (!state) throw new Error("Unknown Doxum runtime.");
|
|
755
|
-
return state;
|
|
756
|
-
};
|
|
757
|
-
const readWith = (runtime, run, dependencies) => {
|
|
758
|
-
const state = accessOf(runtime);
|
|
759
|
-
if (state.disposed) throw new DocumentDisposedError();
|
|
760
|
-
let active = true;
|
|
761
|
-
const reader = documentReader(state.schema, () => state.document, () => active, dependencies);
|
|
762
|
-
try {
|
|
763
|
-
return run(reader);
|
|
764
|
-
} finally {
|
|
765
|
-
active = false;
|
|
766
|
-
}
|
|
767
|
-
};
|
|
768
|
-
//#endregion
|
|
769
|
-
//#region core/src/runtime/notification.ts
|
|
770
|
-
const ROOT_BUCKET = Symbol("document-root");
|
|
771
|
-
const notifications = /* @__PURE__ */ new WeakMap();
|
|
772
|
-
const readableOwners = /* @__PURE__ */ new WeakMap();
|
|
773
|
-
const bindDocumentReadable = (readable, runtime) => {
|
|
774
|
-
readableOwners.set(readable, runtime);
|
|
775
|
-
};
|
|
776
|
-
const documentReadableOwner = (readable) => readableOwners.get(readable);
|
|
777
|
-
const key = (value) => bucket(value) ?? ROOT_BUCKET;
|
|
778
|
-
const createNotification = (runtime) => {
|
|
779
|
-
const notification = {
|
|
780
|
-
root: /* @__PURE__ */ new Set(),
|
|
781
|
-
filtered: /* @__PURE__ */ new Set(),
|
|
782
|
-
buckets: /* @__PURE__ */ new Map(),
|
|
783
|
-
processors: [],
|
|
784
|
-
candidates: /* @__PURE__ */ new Set(),
|
|
785
|
-
rootSnapshot: [],
|
|
786
|
-
notifying: false
|
|
787
|
-
};
|
|
788
|
-
notifications.set(runtime, notification);
|
|
789
|
-
return notification;
|
|
790
|
-
};
|
|
791
|
-
const notificationOf = (runtime) => {
|
|
792
|
-
const value = notifications.get(runtime);
|
|
793
|
-
if (!value) throw new Error("Unknown Doxum runtime.");
|
|
794
|
-
return value;
|
|
795
|
-
};
|
|
796
|
-
const shareNotification = (source, target) => {
|
|
797
|
-
notifications.set(target, notificationOf(source));
|
|
798
|
-
};
|
|
799
|
-
const attachProjection = (runtime, processor) => {
|
|
800
|
-
const notification = notificationOf(runtime);
|
|
801
|
-
const entry = {
|
|
802
|
-
processor,
|
|
803
|
-
active: true
|
|
804
|
-
};
|
|
805
|
-
notification.processors.push(entry);
|
|
806
|
-
return () => {
|
|
807
|
-
if (!entry.active) return;
|
|
808
|
-
entry.active = false;
|
|
809
|
-
if (!notification.notifying) {
|
|
810
|
-
const index = notification.processors.indexOf(entry);
|
|
811
|
-
if (index >= 0) notification.processors.splice(index, 1);
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
};
|
|
815
|
-
const subscribeRoot = (notification, listener) => {
|
|
816
|
-
const entry = {
|
|
817
|
-
listener,
|
|
818
|
-
active: true
|
|
819
|
-
};
|
|
820
|
-
notification.root.add(entry);
|
|
821
|
-
return () => {
|
|
822
|
-
entry.active = false;
|
|
823
|
-
if (!notification.notifying) notification.root.delete(entry);
|
|
824
|
-
};
|
|
825
|
-
};
|
|
826
|
-
const subscribeTargets = (notification, targets, listener) => {
|
|
827
|
-
const entry = {
|
|
828
|
-
targets: Object.freeze(targets.slice()),
|
|
829
|
-
listener,
|
|
830
|
-
active: true
|
|
831
|
-
};
|
|
832
|
-
notification.filtered.add(entry);
|
|
833
|
-
const keys = new Set(targets.map(key));
|
|
834
|
-
keys.forEach((key) => {
|
|
835
|
-
const bucket = notification.buckets.get(key) ?? /* @__PURE__ */ new Set();
|
|
836
|
-
bucket.add(entry);
|
|
837
|
-
notification.buckets.set(key, bucket);
|
|
838
|
-
});
|
|
839
|
-
return () => {
|
|
840
|
-
if (!entry.active) return;
|
|
841
|
-
entry.active = false;
|
|
842
|
-
if (notification.notifying) return;
|
|
843
|
-
notification.filtered.delete(entry);
|
|
844
|
-
keys.forEach((key) => {
|
|
845
|
-
const bucket = notification.buckets.get(key);
|
|
846
|
-
bucket?.delete(entry);
|
|
847
|
-
if (bucket?.size === 0) notification.buckets.delete(key);
|
|
848
|
-
});
|
|
849
|
-
};
|
|
850
|
-
};
|
|
851
|
-
const notify = (notification, commit, afterSettle) => {
|
|
852
|
-
notification.notifying = true;
|
|
853
|
-
const errors = [];
|
|
854
|
-
const call = (listener) => {
|
|
855
|
-
try {
|
|
856
|
-
listener(commit);
|
|
857
|
-
} catch (error) {
|
|
858
|
-
errors.push(Object.freeze({
|
|
859
|
-
phase: "listener",
|
|
860
|
-
error
|
|
861
|
-
}));
|
|
862
|
-
}
|
|
863
|
-
};
|
|
864
|
-
try {
|
|
865
|
-
const processorCount = notification.processors.length;
|
|
866
|
-
for (let index = 0; index < processorCount; index += 1) {
|
|
867
|
-
const entry = notification.processors[index];
|
|
868
|
-
if (!entry?.active) continue;
|
|
869
|
-
try {
|
|
870
|
-
entry.processor.capture(commit);
|
|
871
|
-
} catch (error) {
|
|
872
|
-
errors.push(Object.freeze({
|
|
873
|
-
phase: "processor",
|
|
874
|
-
error
|
|
875
|
-
}));
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
for (let index = 0; index < processorCount; index += 1) {
|
|
879
|
-
const entry = notification.processors[index];
|
|
880
|
-
if (!entry?.active) continue;
|
|
881
|
-
try {
|
|
882
|
-
entry.processor.settle();
|
|
883
|
-
} catch (error) {
|
|
884
|
-
errors.push(Object.freeze({
|
|
885
|
-
phase: "processor",
|
|
886
|
-
error
|
|
887
|
-
}));
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
for (let index = 0; index < processorCount; index += 1) {
|
|
891
|
-
const entry = notification.processors[index];
|
|
892
|
-
if (!entry?.active) continue;
|
|
893
|
-
try {
|
|
894
|
-
errors.push(...entry.processor.flush());
|
|
895
|
-
} catch (error) {
|
|
896
|
-
errors.push(Object.freeze({
|
|
897
|
-
phase: "flush",
|
|
898
|
-
error
|
|
899
|
-
}));
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
if (afterSettle) errors.push(...afterSettle());
|
|
903
|
-
const candidates = notification.candidates;
|
|
904
|
-
candidates.clear();
|
|
905
|
-
if (commit.impact.kind === "reset") notification.filtered.forEach((entry) => candidates.add(entry));
|
|
906
|
-
else for (const operation of commit.operations) {
|
|
907
|
-
const key = operation.at[0] ?? ROOT_BUCKET;
|
|
908
|
-
notification.buckets.get(key)?.forEach((entry) => candidates.add(entry));
|
|
909
|
-
notification.buckets.get(ROOT_BUCKET)?.forEach((entry) => candidates.add(entry));
|
|
910
|
-
}
|
|
911
|
-
candidates.forEach((entry) => {
|
|
912
|
-
if (entry.active && entry.targets.some(commit.impact.affects)) call(entry.listener);
|
|
913
|
-
});
|
|
914
|
-
const rootSnapshot = notification.rootSnapshot;
|
|
915
|
-
rootSnapshot.length = 0;
|
|
916
|
-
notification.root.forEach((entry) => rootSnapshot.push(entry));
|
|
917
|
-
for (const entry of rootSnapshot) if (entry.active) call(entry.listener);
|
|
918
|
-
} finally {
|
|
919
|
-
notification.notifying = false;
|
|
920
|
-
let writeIndex = 0;
|
|
921
|
-
for (const entry of notification.processors) if (entry.active) notification.processors[writeIndex++] = entry;
|
|
922
|
-
notification.processors.length = writeIndex;
|
|
923
|
-
notification.root.forEach((entry) => {
|
|
924
|
-
if (!entry.active) notification.root.delete(entry);
|
|
925
|
-
});
|
|
926
|
-
notification.filtered.forEach((entry) => {
|
|
927
|
-
if (!entry.active) notification.filtered.delete(entry);
|
|
928
|
-
});
|
|
929
|
-
notification.buckets.forEach((bucket, key) => {
|
|
930
|
-
bucket.forEach((entry) => {
|
|
931
|
-
if (!entry.active) bucket.delete(entry);
|
|
932
|
-
});
|
|
933
|
-
if (bucket.size === 0) notification.buckets.delete(key);
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
return Object.freeze(errors);
|
|
937
|
-
};
|
|
938
|
-
const disposeNotification = (notification) => {
|
|
939
|
-
const attachments = notification.processors.slice();
|
|
940
|
-
notification.root.clear();
|
|
941
|
-
notification.filtered.clear();
|
|
942
|
-
notification.buckets.clear();
|
|
943
|
-
notification.processors.length = 0;
|
|
944
|
-
notification.candidates.clear();
|
|
945
|
-
notification.rootSnapshot.length = 0;
|
|
946
|
-
const errors = [];
|
|
947
|
-
attachments.forEach((entry) => {
|
|
948
|
-
if (!entry.active) return;
|
|
949
|
-
try {
|
|
950
|
-
entry.processor.dispose();
|
|
951
|
-
} catch (error) {
|
|
952
|
-
errors.push(error);
|
|
953
|
-
}
|
|
954
|
-
});
|
|
955
|
-
if (errors.length) throw new AggregateError(errors, "Projection disposal notification failed.");
|
|
956
|
-
};
|
|
957
|
-
//#endregion
|
|
958
|
-
//#region core/src/projection/contract.ts
|
|
959
|
-
var ProjectionError = class extends Error {
|
|
960
|
-
constructor(phase, identity, revisions, cause) {
|
|
961
|
-
super(`Projection ${phase} failed: ${identity}`, { cause });
|
|
962
|
-
this.phase = phase;
|
|
963
|
-
this.identity = identity;
|
|
964
|
-
this.revisions = revisions;
|
|
965
|
-
this.name = "ProjectionError";
|
|
966
|
-
}
|
|
967
|
-
};
|
|
968
|
-
var ProjectionDisposedError = class extends Error {
|
|
969
|
-
constructor() {
|
|
970
|
-
super("Projection has been disposed.");
|
|
971
|
-
this.name = "ProjectionDisposedError";
|
|
972
|
-
}
|
|
973
|
-
};
|
|
974
|
-
//#endregion
|
|
975
|
-
//#region core/src/projection/scheduler.ts
|
|
976
|
-
const projectionHandles = /* @__PURE__ */ new WeakSet();
|
|
977
|
-
const createScheduler = (onError) => {
|
|
978
|
-
let disposed = false;
|
|
979
|
-
let depth = 0;
|
|
980
|
-
let phase = "idle";
|
|
981
|
-
let sequence = 0;
|
|
982
|
-
const records = /* @__PURE__ */ new Map();
|
|
983
|
-
const nodes = /* @__PURE__ */ new Set();
|
|
984
|
-
const pending = /* @__PURE__ */ new Set();
|
|
985
|
-
const heap = [];
|
|
986
|
-
const queued = /* @__PURE__ */ new Set();
|
|
987
|
-
const completed = [];
|
|
988
|
-
const emissions = /* @__PURE__ */ new Set();
|
|
989
|
-
const errors = [];
|
|
990
|
-
let reportingFailures = [];
|
|
991
|
-
const guards = /* @__PURE__ */ new Set();
|
|
992
|
-
const cleanups = /* @__PURE__ */ new Set();
|
|
993
|
-
const assertActive = () => {
|
|
994
|
-
if (disposed) throw new ProjectionDisposedError();
|
|
995
|
-
};
|
|
996
|
-
const assertIdle = () => {
|
|
997
|
-
assertActive();
|
|
998
|
-
if (phase !== "idle") throw new Error("Projection cannot be re-entered during processing or notification.");
|
|
999
|
-
};
|
|
1000
|
-
const lock = (value) => guards.forEach((guard) => guard(value));
|
|
1001
|
-
const enqueue = (node) => {
|
|
1002
|
-
if (node.disposed || queued.has(node)) return;
|
|
1003
|
-
queued.add(node);
|
|
1004
|
-
profile.projection("scheduledNodes");
|
|
1005
|
-
let i = heap.length;
|
|
1006
|
-
heap.push(node);
|
|
1007
|
-
while (i > 0) {
|
|
1008
|
-
const p = i - 1 >> 1;
|
|
1009
|
-
if (heap[p].order < node.order) break;
|
|
1010
|
-
heap[i] = heap[p];
|
|
1011
|
-
i = p;
|
|
1012
|
-
}
|
|
1013
|
-
heap[i] = node;
|
|
1014
|
-
};
|
|
1015
|
-
const pop = () => {
|
|
1016
|
-
const first = heap[0];
|
|
1017
|
-
const last = heap.pop();
|
|
1018
|
-
if (heap.length) {
|
|
1019
|
-
let i = 0;
|
|
1020
|
-
while (i * 2 + 1 < heap.length) {
|
|
1021
|
-
let c = i * 2 + 1;
|
|
1022
|
-
if (c + 1 < heap.length && heap[c + 1].order < heap[c].order) c++;
|
|
1023
|
-
if (last.order < heap[c].order) break;
|
|
1024
|
-
heap[i] = heap[c];
|
|
1025
|
-
i = c;
|
|
1026
|
-
}
|
|
1027
|
-
heap[i] = last;
|
|
1028
|
-
}
|
|
1029
|
-
return first;
|
|
1030
|
-
};
|
|
1031
|
-
const errorFor = (node, cause, kind = "processor") => new ProjectionError(kind, node.name, node.sources.map((source) => source.revision()), cause);
|
|
1032
|
-
const capture = (source) => {
|
|
1033
|
-
if (disposed) return;
|
|
1034
|
-
if (phase !== "idle") {
|
|
1035
|
-
source.fault = new ProjectionError("source", "reentrant source", [source.revision()], /* @__PURE__ */ new Error("Source changed while projection was running."));
|
|
1036
|
-
errors.push(source.fault);
|
|
1037
|
-
throw source.fault;
|
|
1038
|
-
}
|
|
1039
|
-
pending.add(source);
|
|
1040
|
-
profile.projection("sourceEvents");
|
|
1041
|
-
if (source.fault) errors.push(source.fault);
|
|
1042
|
-
};
|
|
1043
|
-
const settle = () => {
|
|
1044
|
-
if (disposed || depth || phase !== "idle") return;
|
|
1045
|
-
phase = "compute";
|
|
1046
|
-
lock(true);
|
|
1047
|
-
try {
|
|
1048
|
-
pending.forEach((source) => source.consumers.forEach(enqueue));
|
|
1049
|
-
while (heap.length) {
|
|
1050
|
-
const node = pop();
|
|
1051
|
-
if (node.disposed) continue;
|
|
1052
|
-
const wasFaulted = node.fault !== void 0;
|
|
1053
|
-
profile.projection("processedNodes");
|
|
1054
|
-
let changed = false;
|
|
1055
|
-
const blocked = node.sources.find((source) => source.fault || source.disposed);
|
|
1056
|
-
if (blocked) node.fault = errorFor(node, blocked.fault ?? new ProjectionDisposedError(), "blocked");
|
|
1057
|
-
else {
|
|
1058
|
-
const build = node.forced || wasFaulted || node.sources.some((source) => source.reset());
|
|
1059
|
-
try {
|
|
1060
|
-
changed = node.evaluate(build);
|
|
1061
|
-
const failure = node.sources.find((source) => source.fault)?.fault;
|
|
1062
|
-
if (failure) throw failure;
|
|
1063
|
-
node.fault = void 0;
|
|
1064
|
-
} catch (cause) {
|
|
1065
|
-
const error = errorFor(node, cause);
|
|
1066
|
-
errors.push(error);
|
|
1067
|
-
node.fault = error;
|
|
1068
|
-
if (!build) try {
|
|
1069
|
-
changed = node.evaluate(true);
|
|
1070
|
-
const failure = node.sources.find((source) => source.fault)?.fault;
|
|
1071
|
-
if (failure) throw failure;
|
|
1072
|
-
node.fault = void 0;
|
|
1073
|
-
} catch (cause) {
|
|
1074
|
-
node.fault = errorFor(node, cause);
|
|
1075
|
-
errors.push(node.fault);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
node.forced = false;
|
|
1080
|
-
node.statusChanged = wasFaulted !== (node.fault !== void 0);
|
|
1081
|
-
completed.push(node);
|
|
1082
|
-
if (changed || wasFaulted || node.fault) {
|
|
1083
|
-
profile.projection("publishedNodes");
|
|
1084
|
-
emissions.add(node);
|
|
1085
|
-
node.consumers.forEach(enqueue);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
completed.forEach((node) => node.publish());
|
|
1089
|
-
} finally {
|
|
1090
|
-
lock(false);
|
|
1091
|
-
phase = "idle";
|
|
1092
|
-
}
|
|
1093
|
-
};
|
|
1094
|
-
const flush = () => {
|
|
1095
|
-
if (disposed || depth) return [];
|
|
1096
|
-
if (pending.size || completed.length) profile.projection("flushes");
|
|
1097
|
-
phase = "notify";
|
|
1098
|
-
lock(true);
|
|
1099
|
-
const reportFailures = [];
|
|
1100
|
-
try {
|
|
1101
|
-
emissions.forEach((node) => node.emit((listener) => {
|
|
1102
|
-
try {
|
|
1103
|
-
listener();
|
|
1104
|
-
} catch (cause) {
|
|
1105
|
-
errors.push(errorFor(node, cause, "listener"));
|
|
1106
|
-
}
|
|
1107
|
-
}));
|
|
1108
|
-
for (const error of errors.slice()) try {
|
|
1109
|
-
onError(error);
|
|
1110
|
-
} catch (cause) {
|
|
1111
|
-
reportFailures.push(cause);
|
|
1112
|
-
}
|
|
1113
|
-
return Object.freeze([...errors.map((error) => Object.freeze({
|
|
1114
|
-
phase: error.phase === "listener" ? "listener" : "processor",
|
|
1115
|
-
error
|
|
1116
|
-
})), ...reportFailures.map((error) => Object.freeze({
|
|
1117
|
-
phase: "listener",
|
|
1118
|
-
error
|
|
1119
|
-
}))]);
|
|
1120
|
-
} finally {
|
|
1121
|
-
pending.forEach((source) => source.clear());
|
|
1122
|
-
completed.forEach((node) => node.clear());
|
|
1123
|
-
pending.clear();
|
|
1124
|
-
queued.clear();
|
|
1125
|
-
completed.length = 0;
|
|
1126
|
-
emissions.clear();
|
|
1127
|
-
errors.length = 0;
|
|
1128
|
-
reportingFailures = reportFailures;
|
|
1129
|
-
lock(false);
|
|
1130
|
-
phase = "idle";
|
|
1131
|
-
}
|
|
1132
|
-
};
|
|
1133
|
-
const run = () => {
|
|
1134
|
-
settle();
|
|
1135
|
-
const result = flush();
|
|
1136
|
-
const failures = reportingFailures;
|
|
1137
|
-
reportingFailures = [];
|
|
1138
|
-
if (failures.length) throw new AggregateError(failures, "Projection error reporter failed.");
|
|
1139
|
-
return result;
|
|
1140
|
-
};
|
|
1141
|
-
return {
|
|
1142
|
-
assertActive,
|
|
1143
|
-
assertIdle,
|
|
1144
|
-
capture,
|
|
1145
|
-
settle,
|
|
1146
|
-
flush,
|
|
1147
|
-
run,
|
|
1148
|
-
records,
|
|
1149
|
-
guards,
|
|
1150
|
-
cleanups,
|
|
1151
|
-
get active() {
|
|
1152
|
-
return !disposed;
|
|
1153
|
-
},
|
|
1154
|
-
register(handle, record) {
|
|
1155
|
-
assertIdle();
|
|
1156
|
-
records.set(handle, record);
|
|
1157
|
-
projectionHandles.add(handle);
|
|
1158
|
-
},
|
|
1159
|
-
unregisterSource(handle) {
|
|
1160
|
-
const record = records.get(handle);
|
|
1161
|
-
if (record) {
|
|
1162
|
-
record.disposed = true;
|
|
1163
|
-
pending.delete(record);
|
|
1164
|
-
record.clear();
|
|
1165
|
-
records.delete(handle);
|
|
1166
|
-
}
|
|
1167
|
-
},
|
|
1168
|
-
source(handle) {
|
|
1169
|
-
assertActive();
|
|
1170
|
-
const source = records.get(handle);
|
|
1171
|
-
if (!source || source.disposed) throw new Error("Unknown, disposed, or foreign projection source.");
|
|
1172
|
-
return source;
|
|
1173
|
-
},
|
|
1174
|
-
addNode(node) {
|
|
1175
|
-
nodes.add(node);
|
|
1176
|
-
node.sources.forEach((source) => source.consumers.add(node));
|
|
1177
|
-
},
|
|
1178
|
-
order: () => sequence++,
|
|
1179
|
-
initialize(run) {
|
|
1180
|
-
assertIdle();
|
|
1181
|
-
phase = "compute";
|
|
1182
|
-
lock(true);
|
|
1183
|
-
try {
|
|
1184
|
-
return run();
|
|
1185
|
-
} finally {
|
|
1186
|
-
lock(false);
|
|
1187
|
-
phase = "idle";
|
|
1188
|
-
}
|
|
1189
|
-
},
|
|
1190
|
-
rebuild(node) {
|
|
1191
|
-
assertIdle();
|
|
1192
|
-
if (node.disposed) throw new ProjectionDisposedError();
|
|
1193
|
-
node.forced = true;
|
|
1194
|
-
enqueue(node);
|
|
1195
|
-
run();
|
|
1196
|
-
},
|
|
1197
|
-
disposeNode(node) {
|
|
1198
|
-
if (node.disposed) return;
|
|
1199
|
-
assertIdle();
|
|
1200
|
-
if (node.consumers.size) throw new Error("Cannot dispose a projection node with downstream consumers.");
|
|
1201
|
-
node.disposed = true;
|
|
1202
|
-
node.sources.forEach((source) => source.consumers.delete(node));
|
|
1203
|
-
nodes.delete(node);
|
|
1204
|
-
node.release();
|
|
1205
|
-
for (const [handle, record] of records) if (record === node) records.delete(handle);
|
|
1206
|
-
},
|
|
1207
|
-
batch(callback) {
|
|
1208
|
-
assertIdle();
|
|
1209
|
-
depth++;
|
|
1210
|
-
let failed = false;
|
|
1211
|
-
try {
|
|
1212
|
-
const value = callback();
|
|
1213
|
-
assertSynchronous(value);
|
|
1214
|
-
return value;
|
|
1215
|
-
} catch (error) {
|
|
1216
|
-
failed = true;
|
|
1217
|
-
throw error;
|
|
1218
|
-
} finally {
|
|
1219
|
-
depth--;
|
|
1220
|
-
if (!depth) if (failed) try {
|
|
1221
|
-
run();
|
|
1222
|
-
} catch {}
|
|
1223
|
-
else run();
|
|
1224
|
-
}
|
|
1225
|
-
},
|
|
1226
|
-
dispose() {
|
|
1227
|
-
if (disposed) return;
|
|
1228
|
-
assertIdle();
|
|
1229
|
-
disposed = true;
|
|
1230
|
-
const failures = [];
|
|
1231
|
-
cleanups.forEach((cleanup) => {
|
|
1232
|
-
try {
|
|
1233
|
-
cleanup();
|
|
1234
|
-
} catch (error) {
|
|
1235
|
-
failures.push(error);
|
|
1236
|
-
}
|
|
1237
|
-
});
|
|
1238
|
-
cleanups.clear();
|
|
1239
|
-
nodes.forEach((node) => {
|
|
1240
|
-
node.disposed = true;
|
|
1241
|
-
node.release();
|
|
1242
|
-
node.consumers.clear();
|
|
1243
|
-
});
|
|
1244
|
-
records.forEach((source) => {
|
|
1245
|
-
source.disposed = true;
|
|
1246
|
-
source.consumers.clear();
|
|
1247
|
-
source.clear();
|
|
1248
|
-
});
|
|
1249
|
-
nodes.clear();
|
|
1250
|
-
records.clear();
|
|
1251
|
-
pending.clear();
|
|
1252
|
-
heap.length = 0;
|
|
1253
|
-
completed.length = 0;
|
|
1254
|
-
queued.clear();
|
|
1255
|
-
emissions.clear();
|
|
1256
|
-
guards.clear();
|
|
1257
|
-
errors.length = 0;
|
|
1258
|
-
reportingFailures = [];
|
|
1259
|
-
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
1260
|
-
},
|
|
1261
|
-
debug: () => ({
|
|
1262
|
-
nodes: nodes.size,
|
|
1263
|
-
sources: records.size - nodes.size,
|
|
1264
|
-
subscriptions: cleanups.size,
|
|
1265
|
-
pending: pending.size + heap.length
|
|
1266
|
-
})
|
|
1267
|
-
};
|
|
1268
|
-
};
|
|
1269
|
-
const assertSynchronous = (value) => {
|
|
1270
|
-
if (value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") throw new TypeError("Projection callbacks must be synchronous.");
|
|
1271
|
-
};
|
|
1272
|
-
const assertScope = (active) => {
|
|
1273
|
-
if (!active()) throw new Error("Projection reader or writer is no longer active.");
|
|
1274
|
-
};
|
|
1275
|
-
//#endregion
|
|
1276
|
-
//#region core/src/projection/node.ts
|
|
1277
|
-
const createNode = (scheduler, spec, behavior) => {
|
|
1278
|
-
scheduler.assertIdle();
|
|
1279
|
-
const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
|
|
1280
|
-
const order = scheduler.order();
|
|
1281
|
-
const node = {
|
|
1282
|
-
...behavior,
|
|
1283
|
-
publish: () => {
|
|
1284
|
-
if (node.fault) behavior.clear();
|
|
1285
|
-
else behavior.publish();
|
|
1286
|
-
},
|
|
1287
|
-
order,
|
|
1288
|
-
name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
|
|
1289
|
-
sources: entries.map(([, source]) => source),
|
|
1290
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1291
|
-
disposed: false,
|
|
1292
|
-
fault: void 0,
|
|
1293
|
-
forced: false,
|
|
1294
|
-
statusChanged: false,
|
|
1295
|
-
evaluate: (build) => {
|
|
1296
|
-
let active = true;
|
|
1297
|
-
try {
|
|
1298
|
-
const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(() => active)]));
|
|
1299
|
-
return behavior.evaluate(inputs, build, () => active);
|
|
1300
|
-
} finally {
|
|
1301
|
-
active = false;
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
};
|
|
1305
|
-
scheduler.initialize(() => {
|
|
1306
|
-
const invalid = node.sources.find((source) => source.fault || source.disposed);
|
|
1307
|
-
if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
|
|
1308
|
-
node.evaluate(true);
|
|
1309
|
-
node.publish();
|
|
1310
|
-
node.clear();
|
|
1311
|
-
});
|
|
1312
|
-
const check = () => {
|
|
1313
|
-
if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
|
|
1314
|
-
if (node.fault) throw node.fault;
|
|
1315
|
-
};
|
|
1316
|
-
return {
|
|
1317
|
-
node,
|
|
1318
|
-
check,
|
|
1319
|
-
install(handle) {
|
|
1320
|
-
scheduler.register(handle, node);
|
|
1321
|
-
scheduler.addNode(node);
|
|
1322
|
-
},
|
|
1323
|
-
rebuild: () => scheduler.rebuild(node),
|
|
1324
|
-
dispose: () => scheduler.disposeNode(node)
|
|
1325
|
-
};
|
|
1326
|
-
};
|
|
1327
|
-
//#endregion
|
|
1328
|
-
//#region core/src/projection/collection.ts
|
|
1329
|
-
const collectionHandles = /* @__PURE__ */ new WeakSet();
|
|
1330
|
-
const readonlySet = (values) => {
|
|
1331
|
-
const set = new Set(values);
|
|
1332
|
-
const view = Object.freeze({
|
|
1333
|
-
get size() {
|
|
1334
|
-
return set.size;
|
|
1335
|
-
},
|
|
1336
|
-
has: (value) => set.has(value),
|
|
1337
|
-
entries: () => set.entries(),
|
|
1338
|
-
keys: () => set.keys(),
|
|
1339
|
-
values: () => set.values(),
|
|
1340
|
-
[Symbol.iterator]: () => set.values(),
|
|
1341
|
-
forEach: (callback, thisArg) => set.forEach((value) => callback.call(thisArg, value, value, view))
|
|
1342
|
-
});
|
|
1343
|
-
return view;
|
|
1344
|
-
};
|
|
1345
|
-
const createCollection = (scheduler, spec) => {
|
|
1346
|
-
const values = /* @__PURE__ */ new Map();
|
|
1347
|
-
let ids = Object.freeze([]);
|
|
1348
|
-
let staged = /* @__PURE__ */ new Map();
|
|
1349
|
-
let nextIds = ids;
|
|
1350
|
-
let change;
|
|
1351
|
-
let instance;
|
|
1352
|
-
let initialized = false;
|
|
1353
|
-
let reset = false;
|
|
1354
|
-
let revision = 0;
|
|
1355
|
-
let idsRevision = 0;
|
|
1356
|
-
let all;
|
|
1357
|
-
const itemVersions = /* @__PURE__ */ new Map();
|
|
1358
|
-
const items = /* @__PURE__ */ new Map();
|
|
1359
|
-
const itemListeners = /* @__PURE__ */ new Map();
|
|
1360
|
-
const idsListeners = /* @__PURE__ */ new Set();
|
|
1361
|
-
const allListeners = /* @__PURE__ */ new Set();
|
|
1362
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1363
|
-
const changedKeys = /* @__PURE__ */ new Set();
|
|
1364
|
-
let orderChanged = false;
|
|
1365
|
-
const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
|
|
1366
|
-
const getNext = (key) => {
|
|
1367
|
-
const entry = staged.get(key);
|
|
1368
|
-
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
1369
|
-
};
|
|
1370
|
-
const owner = createNode(scheduler, spec, {
|
|
1371
|
-
evaluate: (sources, build, active) => {
|
|
1372
|
-
staged = /* @__PURE__ */ new Map();
|
|
1373
|
-
nextIds = ids;
|
|
1374
|
-
change = void 0;
|
|
1375
|
-
changedKeys.clear();
|
|
1376
|
-
let explicitOrder;
|
|
1377
|
-
let cleared = false;
|
|
1378
|
-
const nextHas = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
|
|
1379
|
-
const nextGet = (key) => {
|
|
1380
|
-
const entry = staged.get(key);
|
|
1381
|
-
return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
|
|
1382
|
-
};
|
|
1383
|
-
const deriveIds = () => {
|
|
1384
|
-
const result = cleared ? [] : ids.filter(nextHas);
|
|
1385
|
-
for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
|
|
1386
|
-
return result;
|
|
1387
|
-
};
|
|
1388
|
-
const read = (next) => ({
|
|
1389
|
-
get: (key) => {
|
|
1390
|
-
assertScope(active);
|
|
1391
|
-
return next ? nextGet(key) : values.get(key);
|
|
1392
|
-
},
|
|
1393
|
-
has: (key) => {
|
|
1394
|
-
assertScope(active);
|
|
1395
|
-
return next ? nextHas(key) : values.has(key);
|
|
1396
|
-
},
|
|
1397
|
-
ids: () => {
|
|
1398
|
-
assertScope(active);
|
|
1399
|
-
return next ? Object.freeze(explicitOrder ? explicitOrder.slice() : deriveIds()) : ids;
|
|
1400
|
-
}
|
|
1401
|
-
});
|
|
1402
|
-
const writer = {
|
|
1403
|
-
set: (key, value) => {
|
|
1404
|
-
assertScope(active);
|
|
1405
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
1406
|
-
staged.set(key, {
|
|
1407
|
-
present: true,
|
|
1408
|
-
value
|
|
1409
|
-
});
|
|
1410
|
-
},
|
|
1411
|
-
remove: (key) => {
|
|
1412
|
-
assertScope(active);
|
|
1413
|
-
staged.set(key, { present: false });
|
|
1414
|
-
},
|
|
1415
|
-
order: (order) => {
|
|
1416
|
-
assertScope(active);
|
|
1417
|
-
explicitOrder = order.slice();
|
|
1418
|
-
},
|
|
1419
|
-
replace: (entries) => {
|
|
1420
|
-
assertScope(active);
|
|
1421
|
-
staged.clear();
|
|
1422
|
-
cleared = true;
|
|
1423
|
-
const order = [];
|
|
1424
|
-
for (const [key, value] of entries) {
|
|
1425
|
-
if (staged.has(key)) throw new TypeError("Duplicate projection key.");
|
|
1426
|
-
writer.set(key, value);
|
|
1427
|
-
order.push(key);
|
|
1428
|
-
}
|
|
1429
|
-
explicitOrder = order;
|
|
1430
|
-
}
|
|
1431
|
-
};
|
|
1432
|
-
const input = {
|
|
1433
|
-
sources,
|
|
1434
|
-
previous: read(false),
|
|
1435
|
-
next: read(true),
|
|
1436
|
-
writer
|
|
1437
|
-
};
|
|
1438
|
-
if (build || !instance) {
|
|
1439
|
-
profile.materialized.rebuilt();
|
|
1440
|
-
instance = void 0;
|
|
1441
|
-
cleared = true;
|
|
1442
|
-
const built = spec.build(input);
|
|
1443
|
-
assertSynchronous(built);
|
|
1444
|
-
instance = built;
|
|
1445
|
-
} else {
|
|
1446
|
-
profile.materialized.updated();
|
|
1447
|
-
const result = instance.update(input);
|
|
1448
|
-
assertSynchronous(result);
|
|
1449
|
-
if (result?.kind === "rebuild") {
|
|
1450
|
-
staged.clear();
|
|
1451
|
-
cleared = true;
|
|
1452
|
-
explicitOrder = void 0;
|
|
1453
|
-
instance = void 0;
|
|
1454
|
-
const built = spec.build(input);
|
|
1455
|
-
assertSynchronous(built);
|
|
1456
|
-
instance = built;
|
|
1457
|
-
build = true;
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
if (cleared) {
|
|
1461
|
-
for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
|
|
1462
|
-
}
|
|
1463
|
-
const added = /* @__PURE__ */ new Set();
|
|
1464
|
-
const removed = /* @__PURE__ */ new Set();
|
|
1465
|
-
const updated = /* @__PURE__ */ new Set();
|
|
1466
|
-
profile.projection("touchedKeys", staged.size);
|
|
1467
|
-
for (const [key, entry] of staged) {
|
|
1468
|
-
const existed = values.has(key);
|
|
1469
|
-
if (entry.present) if (!existed) added.add(key);
|
|
1470
|
-
else if (!(spec.isEqual ?? Object.is)(values.get(key), entry.value)) updated.add(key);
|
|
1471
|
-
else staged.delete(key);
|
|
1472
|
-
else if (existed) removed.add(key);
|
|
1473
|
-
else staged.delete(key);
|
|
1474
|
-
}
|
|
1475
|
-
if (explicitOrder || added.size || removed.size) {
|
|
1476
|
-
const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
|
|
1477
|
-
if (new Set(order).size !== order.length || order.length !== values.size + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
|
|
1478
|
-
nextIds = ids.length === order.length && ids.every((key, i) => key === order[i]) ? ids : Object.freeze(order.slice());
|
|
1479
|
-
}
|
|
1480
|
-
orderChanged = nextIds !== ids;
|
|
1481
|
-
if (added.size || removed.size || updated.size || orderChanged) {
|
|
1482
|
-
let commonOrderChanged = false;
|
|
1483
|
-
if (orderChanged) {
|
|
1484
|
-
const before = ids.filter((key) => !removed.has(key));
|
|
1485
|
-
const after = nextIds.filter((key) => !added.has(key));
|
|
1486
|
-
commonOrderChanged = before.some((key, i) => after[i] !== key);
|
|
1487
|
-
}
|
|
1488
|
-
change = Object.freeze({
|
|
1489
|
-
kind: "incremental",
|
|
1490
|
-
added: readonlySet(added),
|
|
1491
|
-
removed: readonlySet(removed),
|
|
1492
|
-
updated: readonlySet(updated),
|
|
1493
|
-
orderChanged: commonOrderChanged
|
|
1494
|
-
});
|
|
1495
|
-
[
|
|
1496
|
-
...added,
|
|
1497
|
-
...removed,
|
|
1498
|
-
...updated
|
|
1499
|
-
].forEach((key) => changedKeys.add(key));
|
|
1500
|
-
profile.projection("changedKeys", changedKeys.size);
|
|
1501
|
-
}
|
|
1502
|
-
reset = build;
|
|
1503
|
-
return change !== void 0;
|
|
1504
|
-
},
|
|
1505
|
-
context: (active) => {
|
|
1506
|
-
const input = {
|
|
1507
|
-
get: (key) => {
|
|
1508
|
-
assertScope(active);
|
|
1509
|
-
return getNext(key);
|
|
1510
|
-
},
|
|
1511
|
-
has: (key) => {
|
|
1512
|
-
assertScope(active);
|
|
1513
|
-
return hasNext(key);
|
|
1514
|
-
},
|
|
1515
|
-
ids: () => {
|
|
1516
|
-
assertScope(active);
|
|
1517
|
-
return nextIds;
|
|
1518
|
-
},
|
|
1519
|
-
change,
|
|
1520
|
-
revision: revision + (change ? 1 : 0),
|
|
1521
|
-
reset
|
|
1522
|
-
};
|
|
1523
|
-
return Object.freeze(input);
|
|
1524
|
-
},
|
|
1525
|
-
revision: () => revision,
|
|
1526
|
-
reset: () => reset,
|
|
1527
|
-
publish: () => {
|
|
1528
|
-
for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
|
|
1529
|
-
else values.delete(key);
|
|
1530
|
-
if (change) {
|
|
1531
|
-
if (initialized) revision++;
|
|
1532
|
-
if (orderChanged && initialized) idsRevision++;
|
|
1533
|
-
all = void 0;
|
|
1534
|
-
changedKeys.forEach((key) => {
|
|
1535
|
-
if (items.has(key)) itemVersions.set(key, (itemVersions.get(key) ?? 0) + (initialized ? 1 : 0));
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
ids = nextIds;
|
|
1539
|
-
initialized = true;
|
|
1540
|
-
},
|
|
1541
|
-
emit: (call) => {
|
|
1542
|
-
profile.materialized.notification();
|
|
1543
|
-
const fault = owner.node.fault !== void 0;
|
|
1544
|
-
const resetEvent = Object.freeze({ kind: "reset" });
|
|
1545
|
-
Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
|
|
1546
|
-
if (orderChanged || !change || fault || owner.node.statusChanged) Array.from(idsListeners).forEach((listener) => call(listener));
|
|
1547
|
-
Array.from(allListeners).forEach((listener) => call(listener));
|
|
1548
|
-
const keys = !change || fault || owner.node.statusChanged ? itemListeners.keys() : changedKeys;
|
|
1549
|
-
for (const key of keys) Array.from(itemListeners.get(key) ?? []).forEach((listener) => call(listener));
|
|
1550
|
-
},
|
|
1551
|
-
clear: () => {
|
|
1552
|
-
staged.clear();
|
|
1553
|
-
nextIds = ids;
|
|
1554
|
-
change = void 0;
|
|
1555
|
-
reset = false;
|
|
1556
|
-
changedKeys.clear();
|
|
1557
|
-
orderChanged = false;
|
|
1558
|
-
},
|
|
1559
|
-
release: () => {
|
|
1560
|
-
values.clear();
|
|
1561
|
-
staged.clear();
|
|
1562
|
-
items.clear();
|
|
1563
|
-
itemVersions.clear();
|
|
1564
|
-
itemListeners.clear();
|
|
1565
|
-
idsListeners.clear();
|
|
1566
|
-
allListeners.clear();
|
|
1567
|
-
listeners.clear();
|
|
1568
|
-
ids = nextIds = Object.freeze([]);
|
|
1569
|
-
all = void 0;
|
|
1570
|
-
instance = void 0;
|
|
1571
|
-
}
|
|
1572
|
-
});
|
|
1573
|
-
const handle = {
|
|
1574
|
-
ids: {
|
|
1575
|
-
current: () => {
|
|
1576
|
-
owner.check();
|
|
1577
|
-
return ids;
|
|
1578
|
-
},
|
|
1579
|
-
revision: () => {
|
|
1580
|
-
owner.check();
|
|
1581
|
-
return idsRevision;
|
|
1582
|
-
},
|
|
1583
|
-
subscribe: (listener) => {
|
|
1584
|
-
owner.check();
|
|
1585
|
-
idsListeners.add(listener);
|
|
1586
|
-
return () => {
|
|
1587
|
-
idsListeners.delete(listener);
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
},
|
|
1591
|
-
all: {
|
|
1592
|
-
current: () => {
|
|
1593
|
-
owner.check();
|
|
1594
|
-
if (!all) {
|
|
1595
|
-
profile.collectionView.arrayCopied();
|
|
1596
|
-
all = Object.freeze(ids.map((key) => values.get(key)));
|
|
1597
|
-
}
|
|
1598
|
-
return all;
|
|
1599
|
-
},
|
|
1600
|
-
revision: () => {
|
|
1601
|
-
owner.check();
|
|
1602
|
-
return revision;
|
|
1603
|
-
},
|
|
1604
|
-
subscribe: (listener) => {
|
|
1605
|
-
owner.check();
|
|
1606
|
-
allListeners.add(listener);
|
|
1607
|
-
return () => {
|
|
1608
|
-
allListeners.delete(listener);
|
|
1609
|
-
};
|
|
1610
|
-
}
|
|
1611
|
-
},
|
|
1612
|
-
item: (key) => {
|
|
1613
|
-
owner.check();
|
|
1614
|
-
const old = items.get(key);
|
|
1615
|
-
if (old) return old;
|
|
1616
|
-
itemVersions.set(key, 0);
|
|
1617
|
-
const readable = Object.freeze({
|
|
1618
|
-
current: () => {
|
|
1619
|
-
owner.check();
|
|
1620
|
-
return values.get(key);
|
|
1621
|
-
},
|
|
1622
|
-
revision: () => {
|
|
1623
|
-
owner.check();
|
|
1624
|
-
return itemVersions.get(key) ?? 0;
|
|
1625
|
-
},
|
|
1626
|
-
subscribe: (listener) => {
|
|
1627
|
-
owner.check();
|
|
1628
|
-
const set = itemListeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
1629
|
-
set.add(listener);
|
|
1630
|
-
itemListeners.set(key, set);
|
|
1631
|
-
return () => {
|
|
1632
|
-
set.delete(listener);
|
|
1633
|
-
if (!set.size) itemListeners.delete(key);
|
|
1634
|
-
};
|
|
1635
|
-
}
|
|
1636
|
-
});
|
|
1637
|
-
projectionHandles.add(readable);
|
|
1638
|
-
items.set(key, readable);
|
|
1639
|
-
return readable;
|
|
1640
|
-
},
|
|
1641
|
-
revision: () => {
|
|
1642
|
-
owner.check();
|
|
1643
|
-
return revision;
|
|
1644
|
-
},
|
|
1645
|
-
subscribe: (listener) => {
|
|
1646
|
-
owner.check();
|
|
1647
|
-
listeners.add(listener);
|
|
1648
|
-
return () => {
|
|
1649
|
-
listeners.delete(listener);
|
|
1650
|
-
};
|
|
1651
|
-
},
|
|
1652
|
-
rebuild: owner.rebuild,
|
|
1653
|
-
dispose: owner.dispose
|
|
1654
|
-
};
|
|
1655
|
-
projectionHandles.add(handle.ids);
|
|
1656
|
-
projectionHandles.add(handle.all);
|
|
1657
|
-
collectionHandles.add(handle);
|
|
1658
|
-
owner.install(handle);
|
|
1659
|
-
return Object.freeze(handle);
|
|
1660
|
-
};
|
|
1661
|
-
//#endregion
|
|
1662
|
-
//#region core/src/projection/source.ts
|
|
1663
|
-
const createSources = (scheduler) => {
|
|
1664
|
-
const documents = /* @__PURE__ */ new Map();
|
|
1665
|
-
const readables = /* @__PURE__ */ new Map();
|
|
1666
|
-
const collections = /* @__PURE__ */ new WeakSet();
|
|
1667
|
-
const document = (runtime) => {
|
|
1668
|
-
scheduler.assertIdle();
|
|
1669
|
-
const state = accessOf(runtime);
|
|
1670
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1671
|
-
const existing = documents.get(state);
|
|
1672
|
-
if (existing) return existing;
|
|
1673
|
-
const bindings = [];
|
|
1674
|
-
const make = (targets, collection) => {
|
|
1675
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1676
|
-
targets.forEach((value) => {
|
|
1677
|
-
if (!belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
|
|
1678
|
-
});
|
|
1679
|
-
const old = bindings.find((entry) => Boolean(collection) === collections.has(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => same(value, targets[i])));
|
|
1680
|
-
if (old) return old.handle;
|
|
1681
|
-
let commits = [];
|
|
1682
|
-
let reset = false;
|
|
1683
|
-
let orderDirty = false;
|
|
1684
|
-
const keys = /* @__PURE__ */ new Set();
|
|
1685
|
-
let candidates;
|
|
1686
|
-
const record = {
|
|
1687
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1688
|
-
disposed: false,
|
|
1689
|
-
fault: void 0,
|
|
1690
|
-
revision: runtime.revision,
|
|
1691
|
-
reset: () => reset,
|
|
1692
|
-
clear: () => {
|
|
1693
|
-
commits = [];
|
|
1694
|
-
reset = orderDirty = false;
|
|
1695
|
-
keys.clear();
|
|
1696
|
-
candidates = void 0;
|
|
1697
|
-
},
|
|
1698
|
-
context: (active) => {
|
|
1699
|
-
assertScope(active);
|
|
1700
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1701
|
-
const read = collection ? readerFor(nodeAt(state.schema, collection.address, state.document) ?? collectionSchema(collection), {
|
|
1702
|
-
root: () => state.document,
|
|
1703
|
-
active
|
|
1704
|
-
}, collection.address) : documentReader(state.schema, () => state.document, active);
|
|
1705
|
-
return Object.freeze({
|
|
1706
|
-
read,
|
|
1707
|
-
target: collection,
|
|
1708
|
-
revision: runtime.revision(),
|
|
1709
|
-
commits: Object.freeze(commits.slice()),
|
|
1710
|
-
reset: record.reset(),
|
|
1711
|
-
...collection ? { candidates: candidates ??= Object.freeze({
|
|
1712
|
-
keys: Object.freeze([...keys]),
|
|
1713
|
-
orderDirty
|
|
1714
|
-
}) } : {}
|
|
1715
|
-
});
|
|
1716
|
-
}
|
|
1717
|
-
};
|
|
1718
|
-
const handle = collection ? {} : {
|
|
1719
|
-
collection: (pick) => {
|
|
1720
|
-
scheduler.assertIdle();
|
|
1721
|
-
const selector = state.schema.collection(pick);
|
|
1722
|
-
return make([selector], selector);
|
|
1723
|
-
},
|
|
1724
|
-
targets: (...selected) => {
|
|
1725
|
-
scheduler.assertIdle();
|
|
1726
|
-
if (!selected.length) throw new TypeError("Expected at least one target.");
|
|
1727
|
-
return make(selected);
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
Object.freeze(handle);
|
|
1731
|
-
bindings.push({
|
|
1732
|
-
handle,
|
|
1733
|
-
targets,
|
|
1734
|
-
record
|
|
1735
|
-
});
|
|
1736
|
-
if (collection) collections.add(handle);
|
|
1737
|
-
scheduler.register(handle, record);
|
|
1738
|
-
receivers.set(record, (commit) => {
|
|
1739
|
-
commits.push(commit);
|
|
1740
|
-
reset ||= commit.kind === "replace";
|
|
1741
|
-
if (collection) {
|
|
1742
|
-
const change = commit.impact.collection(collection);
|
|
1743
|
-
if (change.kind === "reset") reset = true;
|
|
1744
|
-
else {
|
|
1745
|
-
change.added.forEach((key) => keys.add(key));
|
|
1746
|
-
change.removed.forEach((key) => keys.add(key));
|
|
1747
|
-
change.updated.forEach((key) => keys.add(key));
|
|
1748
|
-
orderDirty ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
|
|
1749
|
-
}
|
|
1750
|
-
candidates = void 0;
|
|
1751
|
-
}
|
|
1752
|
-
});
|
|
1753
|
-
return handle;
|
|
1754
|
-
};
|
|
1755
|
-
const receivers = /* @__PURE__ */ new Map();
|
|
1756
|
-
const handle = make([]);
|
|
1757
|
-
const guard = (locked) => {
|
|
1758
|
-
state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
|
|
1759
|
-
};
|
|
1760
|
-
scheduler.guards.add(guard);
|
|
1761
|
-
const unsubscribe = attachProjection(runtime, {
|
|
1762
|
-
capture: (commit) => {
|
|
1763
|
-
for (const binding of bindings) if (!binding.targets.length || binding.targets.some(commit.impact.affects)) {
|
|
1764
|
-
receivers.get(binding.record)(commit);
|
|
1765
|
-
scheduler.capture(binding.record);
|
|
1766
|
-
}
|
|
1767
|
-
},
|
|
1768
|
-
settle: scheduler.settle,
|
|
1769
|
-
flush: scheduler.flush,
|
|
1770
|
-
dispose: () => {
|
|
1771
|
-
bindings.forEach(({ record }) => {
|
|
1772
|
-
record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1773
|
-
scheduler.capture(record);
|
|
1774
|
-
});
|
|
1775
|
-
scheduler.run();
|
|
1776
|
-
}
|
|
1777
|
-
});
|
|
1778
|
-
scheduler.cleanups.add(() => {
|
|
1779
|
-
unsubscribe();
|
|
1780
|
-
scheduler.guards.delete(guard);
|
|
1781
|
-
});
|
|
1782
|
-
documents.set(state, handle);
|
|
1783
|
-
return handle;
|
|
1784
|
-
};
|
|
1785
|
-
const valueSource = (initial, equal) => {
|
|
1786
|
-
let value = initial;
|
|
1787
|
-
let previous = initial;
|
|
1788
|
-
let revision = 0;
|
|
1789
|
-
const handle = Object.freeze({});
|
|
1790
|
-
const record = {
|
|
1791
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1792
|
-
disposed: false,
|
|
1793
|
-
fault: void 0,
|
|
1794
|
-
revision: () => revision,
|
|
1795
|
-
reset: () => false,
|
|
1796
|
-
clear: () => {
|
|
1797
|
-
previous = value;
|
|
1798
|
-
},
|
|
1799
|
-
context: (active) => {
|
|
1800
|
-
assertScope(active);
|
|
1801
|
-
return Object.freeze({
|
|
1802
|
-
value,
|
|
1803
|
-
previous,
|
|
1804
|
-
revision,
|
|
1805
|
-
changed: !equal(previous, value),
|
|
1806
|
-
reset: false
|
|
1807
|
-
});
|
|
1808
|
-
}
|
|
1809
|
-
};
|
|
1810
|
-
scheduler.register(handle, record);
|
|
1811
|
-
const accept = (next) => {
|
|
1812
|
-
scheduler.assertIdle();
|
|
1813
|
-
const recovering = record.fault !== void 0;
|
|
1814
|
-
if (equal(value, next) && !recovering) return;
|
|
1815
|
-
record.fault = void 0;
|
|
1816
|
-
value = next;
|
|
1817
|
-
revision++;
|
|
1818
|
-
scheduler.capture(record);
|
|
1819
|
-
};
|
|
1820
|
-
return {
|
|
1821
|
-
source: handle,
|
|
1822
|
-
accept,
|
|
1823
|
-
set(next) {
|
|
1824
|
-
accept(next);
|
|
1825
|
-
scheduler.run();
|
|
1826
|
-
}
|
|
1827
|
-
};
|
|
1828
|
-
};
|
|
1829
|
-
return {
|
|
1830
|
-
dispose: () => {
|
|
1831
|
-
documents.clear();
|
|
1832
|
-
readables.clear();
|
|
1833
|
-
},
|
|
1834
|
-
document,
|
|
1835
|
-
input: (initial, options) => {
|
|
1836
|
-
const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
|
|
1837
|
-
return Object.freeze({
|
|
1838
|
-
source,
|
|
1839
|
-
set
|
|
1840
|
-
});
|
|
1841
|
-
},
|
|
1842
|
-
fromReadable: (readable, options) => {
|
|
1843
|
-
scheduler.assertIdle();
|
|
1844
|
-
if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
|
|
1845
|
-
const equal = options?.isEqual ?? Object.is;
|
|
1846
|
-
let bindings = readables.get(readable);
|
|
1847
|
-
const old = bindings?.get(equal);
|
|
1848
|
-
if (old) return old.source;
|
|
1849
|
-
const input = valueSource(readable.current(), equal);
|
|
1850
|
-
if (bindings) {
|
|
1851
|
-
bindings.set(equal, {
|
|
1852
|
-
source: input.source,
|
|
1853
|
-
accept: (value) => input.accept(value)
|
|
1854
|
-
});
|
|
1855
|
-
return input.source;
|
|
1856
|
-
}
|
|
1857
|
-
bindings = new Map([[equal, {
|
|
1858
|
-
source: input.source,
|
|
1859
|
-
accept: (value) => input.accept(value)
|
|
1860
|
-
}]]);
|
|
1861
|
-
const members = bindings;
|
|
1862
|
-
const receive = (settle = true) => {
|
|
1863
|
-
if (!scheduler.active) return;
|
|
1864
|
-
let value;
|
|
1865
|
-
try {
|
|
1866
|
-
value = readable.current();
|
|
1867
|
-
} catch (cause) {
|
|
1868
|
-
for (const member of members.values()) {
|
|
1869
|
-
const record = scheduler.source(member.source);
|
|
1870
|
-
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
1871
|
-
scheduler.capture(record);
|
|
1872
|
-
}
|
|
1873
|
-
if (settle) scheduler.run();
|
|
1874
|
-
return;
|
|
1875
|
-
}
|
|
1876
|
-
for (const member of members.values()) try {
|
|
1877
|
-
member.accept(value);
|
|
1878
|
-
} catch (cause) {
|
|
1879
|
-
const record = scheduler.source(member.source);
|
|
1880
|
-
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
1881
|
-
scheduler.capture(record);
|
|
1882
|
-
}
|
|
1883
|
-
if (settle) scheduler.run();
|
|
1884
|
-
};
|
|
1885
|
-
let unsubscribe;
|
|
1886
|
-
let detach;
|
|
1887
|
-
try {
|
|
1888
|
-
const owner = documentReadableOwner(readable);
|
|
1889
|
-
if (owner) {
|
|
1890
|
-
document(owner);
|
|
1891
|
-
detach = attachProjection(owner, {
|
|
1892
|
-
capture: () => receive(false),
|
|
1893
|
-
settle: scheduler.settle,
|
|
1894
|
-
flush: scheduler.flush,
|
|
1895
|
-
dispose: () => {
|
|
1896
|
-
for (const member of members.values()) {
|
|
1897
|
-
const record = scheduler.source(member.source);
|
|
1898
|
-
record.fault = new ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1899
|
-
scheduler.capture(record);
|
|
1900
|
-
}
|
|
1901
|
-
scheduler.run();
|
|
1902
|
-
}
|
|
1903
|
-
});
|
|
1904
|
-
}
|
|
1905
|
-
unsubscribe = readable.subscribe(() => receive());
|
|
1906
|
-
input.accept(readable.current());
|
|
1907
|
-
} catch (error) {
|
|
1908
|
-
try {
|
|
1909
|
-
unsubscribe?.();
|
|
1910
|
-
detach?.();
|
|
1911
|
-
} finally {
|
|
1912
|
-
scheduler.unregisterSource(input.source);
|
|
1913
|
-
}
|
|
1914
|
-
throw error;
|
|
1915
|
-
}
|
|
1916
|
-
scheduler.cleanups.add(unsubscribe);
|
|
1917
|
-
if (detach) scheduler.cleanups.add(detach);
|
|
1918
|
-
readables.set(readable, bindings);
|
|
1919
|
-
scheduler.run();
|
|
1920
|
-
return input.source;
|
|
1921
|
-
},
|
|
1922
|
-
assertCollection: (handle) => {
|
|
1923
|
-
scheduler.source(handle);
|
|
1924
|
-
if (!collections.has(handle) && !collectionHandles.has(handle)) throw new Error("map requires a collection source.");
|
|
1925
|
-
}
|
|
1926
|
-
};
|
|
1927
|
-
};
|
|
1928
|
-
//#endregion
|
|
1929
|
-
//#region core/src/projection/value.ts
|
|
1930
|
-
const createValue = (scheduler, spec, options) => {
|
|
1931
|
-
let instance;
|
|
1932
|
-
let value;
|
|
1933
|
-
let next;
|
|
1934
|
-
let previous;
|
|
1935
|
-
let initialized = false;
|
|
1936
|
-
let revision = 0;
|
|
1937
|
-
let changed = false;
|
|
1938
|
-
let reset = false;
|
|
1939
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1940
|
-
const owner = createNode(scheduler, spec, {
|
|
1941
|
-
evaluate: (sources, build) => {
|
|
1942
|
-
let candidate;
|
|
1943
|
-
if (build || !instance) {
|
|
1944
|
-
profile.materialized.rebuilt();
|
|
1945
|
-
instance = void 0;
|
|
1946
|
-
const built = spec.build(sources);
|
|
1947
|
-
assertSynchronous(built);
|
|
1948
|
-
instance = built;
|
|
1949
|
-
candidate = built.value;
|
|
1950
|
-
} else {
|
|
1951
|
-
profile.materialized.updated();
|
|
1952
|
-
const result = instance.update(sources);
|
|
1953
|
-
assertSynchronous(result);
|
|
1954
|
-
if (result.kind === "rebuild") {
|
|
1955
|
-
instance = void 0;
|
|
1956
|
-
const built = spec.build(sources);
|
|
1957
|
-
assertSynchronous(built);
|
|
1958
|
-
instance = built;
|
|
1959
|
-
candidate = built.value;
|
|
1960
|
-
build = true;
|
|
1961
|
-
} else candidate = result.kind === "changed" ? result.value : value;
|
|
1962
|
-
}
|
|
1963
|
-
previous = value;
|
|
1964
|
-
changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
|
|
1965
|
-
next = changed ? candidate : value;
|
|
1966
|
-
reset = build;
|
|
1967
|
-
return changed;
|
|
1968
|
-
},
|
|
1969
|
-
context: (active) => {
|
|
1970
|
-
assertScope(active);
|
|
1971
|
-
return Object.freeze({
|
|
1972
|
-
value: next,
|
|
1973
|
-
previous,
|
|
1974
|
-
changed,
|
|
1975
|
-
revision: revision + (changed ? 1 : 0),
|
|
1976
|
-
reset
|
|
1977
|
-
});
|
|
1978
|
-
},
|
|
1979
|
-
revision: () => revision,
|
|
1980
|
-
reset: () => reset,
|
|
1981
|
-
publish: () => {
|
|
1982
|
-
if (changed && initialized) revision++;
|
|
1983
|
-
value = next;
|
|
1984
|
-
initialized = true;
|
|
1985
|
-
},
|
|
1986
|
-
emit: (call) => {
|
|
1987
|
-
profile.materialized.notification();
|
|
1988
|
-
Array.from(listeners).forEach((listener) => call(listener));
|
|
1989
|
-
},
|
|
1990
|
-
clear: () => {
|
|
1991
|
-
next = value;
|
|
1992
|
-
previous = value;
|
|
1993
|
-
changed = false;
|
|
1994
|
-
reset = false;
|
|
1995
|
-
},
|
|
1996
|
-
release: () => {
|
|
1997
|
-
listeners.clear();
|
|
1998
|
-
instance = void 0;
|
|
1999
|
-
value = next = previous = void 0;
|
|
2000
|
-
}
|
|
2001
|
-
});
|
|
2002
|
-
const handle = Object.freeze({
|
|
2003
|
-
current: () => {
|
|
2004
|
-
owner.check();
|
|
2005
|
-
return value;
|
|
2006
|
-
},
|
|
2007
|
-
revision: () => {
|
|
2008
|
-
owner.check();
|
|
2009
|
-
return revision;
|
|
2010
|
-
},
|
|
2011
|
-
subscribe: (listener) => {
|
|
2012
|
-
owner.check();
|
|
2013
|
-
listeners.add(listener);
|
|
2014
|
-
return () => {
|
|
2015
|
-
listeners.delete(listener);
|
|
2016
|
-
};
|
|
2017
|
-
},
|
|
2018
|
-
rebuild: owner.rebuild,
|
|
2019
|
-
dispose: owner.dispose
|
|
2020
|
-
});
|
|
2021
|
-
owner.install(handle);
|
|
2022
|
-
return handle;
|
|
2023
|
-
};
|
|
2024
|
-
//#endregion
|
|
2025
|
-
//#region core/src/projection/runtime.ts
|
|
2026
|
-
const debug = /* @__PURE__ */ new WeakMap();
|
|
2027
|
-
const projectionDebug = (runtime) => {
|
|
2028
|
-
const read = debug.get(runtime);
|
|
2029
|
-
if (!read) throw new Error("Unknown projection runtime.");
|
|
2030
|
-
return Object.freeze(read());
|
|
2031
|
-
};
|
|
2032
|
-
const createProjectionRuntime = (options) => {
|
|
2033
|
-
const scheduler = createScheduler(options.onError);
|
|
2034
|
-
const sources = createSources(scheduler);
|
|
2035
|
-
function value(input, computeOrOptions, options) {
|
|
2036
|
-
if (typeof computeOrOptions !== "function") return createValue(scheduler, input, computeOrOptions);
|
|
2037
|
-
const compute = (input) => {
|
|
2038
|
-
const result = computeOrOptions(input);
|
|
2039
|
-
assertSynchronous(result);
|
|
2040
|
-
return result;
|
|
2041
|
-
};
|
|
2042
|
-
return createValue(scheduler, {
|
|
2043
|
-
sources: input,
|
|
2044
|
-
build: (input) => ({
|
|
2045
|
-
value: compute(input),
|
|
2046
|
-
update: (input) => ({
|
|
2047
|
-
kind: "changed",
|
|
2048
|
-
value: compute(input)
|
|
2049
|
-
})
|
|
2050
|
-
})
|
|
2051
|
-
}, options);
|
|
2052
|
-
}
|
|
2053
|
-
const map = (source, mapper, options) => {
|
|
2054
|
-
sources.assertCollection(source);
|
|
2055
|
-
const calculate = (id, entry) => {
|
|
2056
|
-
const value = mapper(id, entry);
|
|
2057
|
-
assertSynchronous(value);
|
|
2058
|
-
return value;
|
|
2059
|
-
};
|
|
2060
|
-
return createCollection(scheduler, {
|
|
2061
|
-
sources: { source },
|
|
2062
|
-
isEqual: options?.isEqual,
|
|
2063
|
-
build: ({ sources, writer }) => {
|
|
2064
|
-
const input = sources.source;
|
|
2065
|
-
const read = "read" in input ? input.read : input;
|
|
2066
|
-
const ids = read.ids();
|
|
2067
|
-
profile.collectionView.idsScanned(ids.length);
|
|
2068
|
-
for (const id of ids) {
|
|
2069
|
-
profile.collectionView.mapped();
|
|
2070
|
-
writer.set(id, calculate(id, read.get(id)));
|
|
2071
|
-
}
|
|
2072
|
-
writer.order(ids);
|
|
2073
|
-
return { update: ({ sources, writer }) => {
|
|
2074
|
-
const input = sources.source;
|
|
2075
|
-
if (input.reset) return { kind: "rebuild" };
|
|
2076
|
-
const read = "read" in input ? input.read : input;
|
|
2077
|
-
let keys;
|
|
2078
|
-
let structural;
|
|
2079
|
-
if ("candidates" in input) {
|
|
2080
|
-
keys = input.candidates.keys;
|
|
2081
|
-
structural = input.candidates.orderDirty;
|
|
2082
|
-
} else {
|
|
2083
|
-
const change = input.change;
|
|
2084
|
-
if (!change) return;
|
|
2085
|
-
if (change.kind === "reset") return { kind: "rebuild" };
|
|
2086
|
-
keys = new Set([
|
|
2087
|
-
...change.added,
|
|
2088
|
-
...change.removed,
|
|
2089
|
-
...change.updated
|
|
2090
|
-
]);
|
|
2091
|
-
structural = Boolean(change.added.size || change.removed.size || change.orderChanged);
|
|
2092
|
-
}
|
|
2093
|
-
for (const key of keys) if (!read.has(key)) writer.remove(key);
|
|
2094
|
-
else {
|
|
2095
|
-
profile.collectionView.mapped();
|
|
2096
|
-
writer.set(key, calculate(key, read.get(key)));
|
|
2097
|
-
}
|
|
2098
|
-
if (structural) {
|
|
2099
|
-
const ids = read.ids();
|
|
2100
|
-
profile.collectionView.idsScanned(ids.length);
|
|
2101
|
-
writer.order(ids);
|
|
2102
|
-
}
|
|
2103
|
-
} };
|
|
2104
|
-
}
|
|
2105
|
-
});
|
|
2106
|
-
};
|
|
2107
|
-
const runtime = {
|
|
2108
|
-
document: sources.document,
|
|
2109
|
-
input: sources.input,
|
|
2110
|
-
fromReadable: sources.fromReadable,
|
|
2111
|
-
value,
|
|
2112
|
-
collection: () => (spec) => createCollection(scheduler, spec),
|
|
2113
|
-
map,
|
|
2114
|
-
batch: scheduler.batch,
|
|
2115
|
-
dispose: () => {
|
|
2116
|
-
try {
|
|
2117
|
-
scheduler.dispose();
|
|
2118
|
-
} finally {
|
|
2119
|
-
if (!scheduler.active) sources.dispose();
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
};
|
|
2123
|
-
debug.set(runtime, scheduler.debug);
|
|
2124
|
-
return Object.freeze(runtime);
|
|
2125
|
-
};
|
|
2126
|
-
//#endregion
|
|
2127
|
-
export { id as A, set as B, restore as C, DocumentReentrancyError as D, DocumentDisposedError as E, locate as F, object as G, field as H, overlaps as I, table as J, optional as K, read as L, same as M, contains as N, address$1 as O, debugKey as P, resolveAddress as R, keys as S, validPositions as T, list as U, dict as V, map as W, variant as X, tree as Y, footprintsOverlap as _, bindDocumentReadable as a, at as b, notify as c, subscribeTargets as d, accessOf as f, decodeCommandFootprint as g, commandFootprint as h, ProjectionError as i, impact_target_exports as j, belongs as k, shareNotification as l, readWith as m, projectionDebug as n, createNotification as o, bindRuntimeAccess as p, schema as q, ProjectionDisposedError as r, disposeNotification as s, createProjectionRuntime as t, subscribeRoot as u, documentReader as v, valid as w, index as x, afterRemove as y, resolveLocated as z };
|
|
2128
|
-
|
|
2129
|
-
//# sourceMappingURL=runtime-DF1q9Gje.js.map
|