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
|
@@ -0,0 +1,2511 @@
|
|
|
1
|
+
import { A as insert$2, C as validate, D as equal, E as afterRemove, I as valid, L as profile, M as keys, O as index, P as move$2, T as isRecord, a as compiledShape, b as contains, g as resolveLocated, i as AddressIndex, k as indexedKeys, m as resolveChild, n as fail, o as contains$1, u as nodeAt, w as isPlainObject, x as parent, y as children } from "./issue-DVaGQGeP.js";
|
|
2
|
+
//#region core/src/schema-value.ts
|
|
3
|
+
var ParseError = class extends TypeError {
|
|
4
|
+
issues;
|
|
5
|
+
constructor(issue) {
|
|
6
|
+
super(`${issue.message} at ${JSON.stringify(issue.address)}`);
|
|
7
|
+
this.name = "ParseError";
|
|
8
|
+
this.issues = Object.freeze([Object.freeze({
|
|
9
|
+
...issue,
|
|
10
|
+
address: Object.freeze([...issue.address])
|
|
11
|
+
})]);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const issue = (address, message, code = "invalid-value") => ({
|
|
15
|
+
address,
|
|
16
|
+
message,
|
|
17
|
+
code
|
|
18
|
+
});
|
|
19
|
+
const checkScalar = (validator, value, address, strict) => {
|
|
20
|
+
if (!validator) return strict ? issue(address, "A validator is required to parse an opaque value.", "missing-validator") : void 0;
|
|
21
|
+
try {
|
|
22
|
+
const result = typeof validator === "function" ? validator(value) : validator["~standard"].validate(value);
|
|
23
|
+
if (result && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function") return issue(address, "Validators must be synchronous.");
|
|
24
|
+
if (typeof validator !== "function") {
|
|
25
|
+
if (!isRecord(result)) return issue(address, "Malformed validator result.");
|
|
26
|
+
if (Array.isArray(result.issues)) {
|
|
27
|
+
const first = result.issues[0];
|
|
28
|
+
return issue([...address, ...(first?.path ?? []).map((part) => String(typeof part === "object" ? part.key : part))], first?.message ?? "Value validation failed.");
|
|
29
|
+
}
|
|
30
|
+
if (!Object.hasOwn(result, "value")) return issue(address, "Malformed validator result.");
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return issue(address, error instanceof Error ? error.message : "Value validation failed.");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const checkKey = (validator, value, address) => {
|
|
38
|
+
if (typeof value !== "string") return issue(address, "Collection keys must be strings.", "invalid-key");
|
|
39
|
+
const failure = checkScalar(validator, value, address, false);
|
|
40
|
+
return failure ? {
|
|
41
|
+
...failure,
|
|
42
|
+
code: "invalid-key"
|
|
43
|
+
} : void 0;
|
|
44
|
+
};
|
|
45
|
+
const checkValue = (node, value, address = [], strict = false) => {
|
|
46
|
+
if (node.optional && value === void 0) return void 0;
|
|
47
|
+
if (node.kind === "field") return checkScalar(node.validator, value, address, strict);
|
|
48
|
+
if (node.kind === "object" || node.kind === "variant") {
|
|
49
|
+
if (!isPlainObject(value)) return issue(address, "Expected an object.");
|
|
50
|
+
if (node.kind === "variant") {
|
|
51
|
+
const tag = value[node.tag];
|
|
52
|
+
if (!Object.hasOwn(value, node.tag) || typeof tag !== "string" || !Object.hasOwn(node.variants, tag)) return issue([...address, node.tag], "Unknown variant tag.");
|
|
53
|
+
}
|
|
54
|
+
const { members } = compiledShape(node, value);
|
|
55
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
56
|
+
if (node.kind === "variant" && key === node.tag) continue;
|
|
57
|
+
if (typeof key !== "string" || !members.has(key)) return issue([...address, String(key)], "Property is not declared in the object schema.");
|
|
58
|
+
}
|
|
59
|
+
for (const [key, member] of members) {
|
|
60
|
+
const child = member.node;
|
|
61
|
+
if (!Object.prototype.hasOwnProperty.call(value, key) && !child.optional) return issue([...address, key], "Required property is missing.");
|
|
62
|
+
const failure = checkValue(child, value[key], [...address, key], strict);
|
|
63
|
+
if (failure) return failure;
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (node.kind === "table" || node.kind === "map") {
|
|
68
|
+
if (!isPlainObject(value)) return issue(address, "Expected a collection object.");
|
|
69
|
+
const entries = node.kind === "table" ? value.byId : value;
|
|
70
|
+
if (!isPlainObject(entries)) return issue(address, "Expected a byId object.");
|
|
71
|
+
if (node.kind === "table") {
|
|
72
|
+
if (!Array.isArray(value.ids) || value.ids.length !== Object.keys(entries).length) return issue(address, "Table ids and entries must agree.");
|
|
73
|
+
const seen = /* @__PURE__ */ new Set();
|
|
74
|
+
for (const id of value.ids) {
|
|
75
|
+
const failure = checkKey(node.key, id, address);
|
|
76
|
+
if (failure) return failure;
|
|
77
|
+
if (seen.has(id) || !Object.prototype.hasOwnProperty.call(entries, id)) return issue(address, "Table ids must be unique and reference existing entries.");
|
|
78
|
+
seen.add(id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const id of Object.keys(entries)) {
|
|
82
|
+
const failure = checkKey(node.key, id, [...address, id]) ?? checkValue(node.value, entries[id], [...address, id], strict);
|
|
83
|
+
if (failure) return failure;
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (node.kind === "list") {
|
|
88
|
+
if (!Array.isArray(value)) return issue(address, "Expected a list.");
|
|
89
|
+
const seen = /* @__PURE__ */ new Set();
|
|
90
|
+
const order = keys(value, node.keyOf);
|
|
91
|
+
for (let i = 0; i < value.length; i++) {
|
|
92
|
+
const failure = checkValue(node.value, value[i], [...address, String(i)], strict);
|
|
93
|
+
if (failure) return failure;
|
|
94
|
+
let key;
|
|
95
|
+
try {
|
|
96
|
+
key = order.at(i);
|
|
97
|
+
} catch {
|
|
98
|
+
return issue(address, "List keyOf failed.");
|
|
99
|
+
}
|
|
100
|
+
if (typeof key !== "string" || seen.has(key)) return issue(address, "List keys must be unique strings.", "invalid-key");
|
|
101
|
+
seen.add(key);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!validate(value)) return issue(address, "Invalid tree structure.", "invalid-tree");
|
|
106
|
+
if (isRecord(value) && isRecord(value.nodes)) for (const id of Object.keys(value.nodes)) {
|
|
107
|
+
const entry = value.nodes[id];
|
|
108
|
+
if (isRecord(entry) && Object.prototype.hasOwnProperty.call(entry, "value")) {
|
|
109
|
+
const failure = checkValue(node.value, entry.value, [...address, id], strict);
|
|
110
|
+
if (failure) return failure;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
/** Copy editable structure; atomic payloads retain their immutable ownership contract. */
|
|
115
|
+
const copyValue = (node, value) => {
|
|
116
|
+
if (value === void 0 || node.kind === "field") return value;
|
|
117
|
+
profile.copy.structure();
|
|
118
|
+
if ((node.kind === "object" || node.kind === "variant") && isRecord(value)) {
|
|
119
|
+
const result = Object.create(Object.getPrototypeOf(value));
|
|
120
|
+
const { members } = compiledShape(node, value);
|
|
121
|
+
for (const key of Object.getOwnPropertyNames(value)) Object.defineProperty(result, key, {
|
|
122
|
+
value: node.kind === "variant" && key === node.tag ? value[key] : copyValue(members.get(key).node, value[key]),
|
|
123
|
+
writable: true,
|
|
124
|
+
enumerable: Object.getOwnPropertyDescriptor(value, key)?.enumerable,
|
|
125
|
+
configurable: true
|
|
126
|
+
});
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
if ((node.kind === "table" || node.kind === "map") && isRecord(value)) {
|
|
130
|
+
const entries = node.kind === "table" ? value.byId : value;
|
|
131
|
+
const result = Object.fromEntries(Object.keys(entries).map((id) => [id, copyValue(node.value, entries[id])]));
|
|
132
|
+
return node.kind === "table" ? {
|
|
133
|
+
ids: [...value.ids],
|
|
134
|
+
byId: result
|
|
135
|
+
} : result;
|
|
136
|
+
}
|
|
137
|
+
if (node.kind === "list") return [...value];
|
|
138
|
+
if (node.kind === "tree" && isRecord(value)) {
|
|
139
|
+
const entries = value.nodes;
|
|
140
|
+
return {
|
|
141
|
+
...value,
|
|
142
|
+
nodes: Object.fromEntries(Object.entries(entries).map(([id, n]) => [id, {
|
|
143
|
+
...n,
|
|
144
|
+
children: [...n.children]
|
|
145
|
+
}]))
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
};
|
|
150
|
+
const equalValue = (node, left, right) => {
|
|
151
|
+
if (Object.is(left, right)) return true;
|
|
152
|
+
if (node.kind === "field" || left === void 0 || right === void 0) return false;
|
|
153
|
+
if ((node.kind === "object" || node.kind === "variant") && isRecord(left) && isRecord(right)) {
|
|
154
|
+
if (node.kind === "variant" && left[node.tag] !== right[node.tag]) return false;
|
|
155
|
+
for (const [key, member] of compiledShape(node, left).members) {
|
|
156
|
+
if (Object.hasOwn(left, key) !== Object.hasOwn(right, key)) return false;
|
|
157
|
+
if (!equalValue(member.node, left[key], right[key])) return false;
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (node.kind === "list" && Array.isArray(left) && Array.isArray(right)) return left.length === right.length && left.every((v, i) => equalValue(node.value, v, right[i]));
|
|
162
|
+
if (node.kind === "table" && isRecord(left) && isRecord(right)) return equal(left.ids, right.ids) && equalValue({
|
|
163
|
+
kind: "map",
|
|
164
|
+
value: node.value
|
|
165
|
+
}, left.byId, right.byId);
|
|
166
|
+
if (node.kind === "tree" && isRecord(left) && isRecord(right)) {
|
|
167
|
+
if (left.rootId !== right.rootId) return false;
|
|
168
|
+
const a = left.nodes, b = right.nodes;
|
|
169
|
+
return Object.keys(a).length === Object.keys(b).length && Object.keys(a).every((id) => Object.hasOwn(b, id) && a[id].parentId === b[id].parentId && equal(a[id].children, b[id].children) && Object.hasOwn(a[id], "value") === Object.hasOwn(b[id], "value") && equalValue(node.value, a[id].value, b[id].value));
|
|
170
|
+
}
|
|
171
|
+
if (node.kind === "map" && isRecord(left) && isRecord(right)) {
|
|
172
|
+
const keys = Object.keys(left);
|
|
173
|
+
return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && equalValue(node.value, left[key], right[key]));
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
};
|
|
177
|
+
const parse = (node, input) => {
|
|
178
|
+
const valueNode = node;
|
|
179
|
+
const failure = checkValue(valueNode, input, [], true);
|
|
180
|
+
if (failure) throw new ParseError(failure);
|
|
181
|
+
return copyValue(valueNode, input);
|
|
182
|
+
};
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region core/src/schema.ts
|
|
185
|
+
const node$1 = (value) => Object.freeze(value);
|
|
186
|
+
const field = (validator) => node$1({
|
|
187
|
+
kind: "field",
|
|
188
|
+
...validator ? { validator } : {}
|
|
189
|
+
});
|
|
190
|
+
const optional = (value) => {
|
|
191
|
+
if (![
|
|
192
|
+
"field",
|
|
193
|
+
"variant",
|
|
194
|
+
"map",
|
|
195
|
+
"list",
|
|
196
|
+
"tree"
|
|
197
|
+
].includes(value.kind)) throw new TypeError("Only field, variant, map, list and tree nodes support optional presence.");
|
|
198
|
+
return node$1({
|
|
199
|
+
...value,
|
|
200
|
+
optional: true
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
const object = (shape) => node$1({
|
|
204
|
+
kind: "object",
|
|
205
|
+
shape: Object.freeze({ ...shape })
|
|
206
|
+
});
|
|
207
|
+
const variant = (tag, variants) => node$1({
|
|
208
|
+
kind: "variant",
|
|
209
|
+
tag,
|
|
210
|
+
variants: Object.freeze({ ...variants })
|
|
211
|
+
});
|
|
212
|
+
const table = (value, options) => node$1({
|
|
213
|
+
kind: "table",
|
|
214
|
+
value,
|
|
215
|
+
...options
|
|
216
|
+
});
|
|
217
|
+
const map = (value, options) => node$1({
|
|
218
|
+
kind: "map",
|
|
219
|
+
value,
|
|
220
|
+
...options
|
|
221
|
+
});
|
|
222
|
+
const list = (value, config) => node$1({
|
|
223
|
+
kind: "list",
|
|
224
|
+
keyOf: config.keyOf,
|
|
225
|
+
value
|
|
226
|
+
});
|
|
227
|
+
const tree = (value) => node$1({
|
|
228
|
+
kind: "tree",
|
|
229
|
+
value
|
|
230
|
+
});
|
|
231
|
+
const paths = /* @__PURE__ */ new WeakMap();
|
|
232
|
+
const pathProxy = (nodes, address, owner) => {
|
|
233
|
+
const proxy = new Proxy({}, { get: (_target, property) => {
|
|
234
|
+
if (typeof property !== "string") return void 0;
|
|
235
|
+
if (nodes.every((node) => node.kind === "table" || node.kind === "map") && property === "item") return (id) => {
|
|
236
|
+
if (typeof id !== "string") throw new TypeError("Collection keys must be strings.");
|
|
237
|
+
for (const node of nodes) if (node.kind === "table" || node.kind === "map") {
|
|
238
|
+
const invalid = checkKey(node.key, id, [...address, id]);
|
|
239
|
+
if (invalid) throw new TypeError(invalid.message);
|
|
240
|
+
}
|
|
241
|
+
return pathProxy(nodes.map((node) => node.value), [...address, id], owner);
|
|
242
|
+
};
|
|
243
|
+
const children = nodes.flatMap((node) => {
|
|
244
|
+
if (node.kind === "object") return Object.hasOwn(node.shape, property) ? [node.shape[property]] : [];
|
|
245
|
+
if (node.kind !== "variant") return [];
|
|
246
|
+
if (property === node.tag) return [field()];
|
|
247
|
+
return Object.values(node.variants).flatMap((branch) => Object.hasOwn(branch.shape, property) ? [branch.shape[property]] : []);
|
|
248
|
+
});
|
|
249
|
+
if (!children.length) throw new TypeError(`Invalid schema path segment: ${property}`);
|
|
250
|
+
return pathProxy(children, [...address, property], owner);
|
|
251
|
+
} });
|
|
252
|
+
paths.set(proxy, {
|
|
253
|
+
nodes,
|
|
254
|
+
address,
|
|
255
|
+
owner
|
|
256
|
+
});
|
|
257
|
+
return proxy;
|
|
258
|
+
};
|
|
259
|
+
const compilePath = (owner, kind, pick) => {
|
|
260
|
+
const scope = {};
|
|
261
|
+
const value = pick(pathProxy([owner], [], scope));
|
|
262
|
+
const selected = typeof value === "object" && value !== null ? paths.get(value) : void 0;
|
|
263
|
+
if (!selected || selected.owner !== scope) throw new TypeError("Selector must return a path from its callback.");
|
|
264
|
+
if (kind === "collection" && !selected.nodes.every((node) => node.kind === "table" || node.kind === "map")) throw new TypeError("Collection selectors require a table or map path.");
|
|
265
|
+
return Object.freeze({
|
|
266
|
+
kind,
|
|
267
|
+
schema: owner,
|
|
268
|
+
address: Object.freeze([...selected.address])
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region core/src/mutation/operations/table.ts
|
|
273
|
+
function create(session, container, entries, position) {
|
|
274
|
+
const { at, node, value } = container;
|
|
275
|
+
if (node.kind !== "table") return fail(at, "invalid-collection", "Expected a table.");
|
|
276
|
+
const table = value;
|
|
277
|
+
if (!valid(table.ids, position)) return fail(at, "invalid-anchor", "Unknown table anchor.");
|
|
278
|
+
const ids = /* @__PURE__ */ new Set();
|
|
279
|
+
for (const entry of entries) {
|
|
280
|
+
if (Object.hasOwn(table.byId, entry.id) || ids.has(entry.id)) return fail(at, "duplicate-entity", "Table key already exists.");
|
|
281
|
+
ids.add(entry.id);
|
|
282
|
+
}
|
|
283
|
+
if (!entries.length) return;
|
|
284
|
+
const index$1 = index(table.ids, position);
|
|
285
|
+
for (const entry of entries) session.writeMember(container, entry.id, entry.value, "set");
|
|
286
|
+
const length = table.ids.length;
|
|
287
|
+
table.ids.length += entries.length;
|
|
288
|
+
table.ids.copyWithin(index$1 + entries.length, index$1, length);
|
|
289
|
+
for (let i = 0; i < entries.length; i++) table.ids[index$1 + i] = entries[i].id;
|
|
290
|
+
}
|
|
291
|
+
function remove$2(session, container, ids) {
|
|
292
|
+
const { at, node, value } = container;
|
|
293
|
+
if (node.kind !== "table") return fail(at, "invalid-collection", "Expected a table.");
|
|
294
|
+
const table = value;
|
|
295
|
+
for (const id of ids) if (!Object.hasOwn(table.byId, id)) return fail(at, "missing-entity", "Table key does not exist.");
|
|
296
|
+
if (!ids.length) return;
|
|
297
|
+
const removed = new Set(ids);
|
|
298
|
+
for (const id of removed) session.writeMember(container, id, void 0, "remove");
|
|
299
|
+
table.ids = table.ids.filter((id) => !removed.has(id));
|
|
300
|
+
}
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region core/src/mutation/operations/list.ts
|
|
303
|
+
function insert$1(session, container, value, position) {
|
|
304
|
+
const { at, node, value: current } = container;
|
|
305
|
+
if (node.kind !== "list") return fail(at, "invalid-list-key", "Expected a list.");
|
|
306
|
+
session.validate(node.value, value, at);
|
|
307
|
+
const id = node.keyOf(value);
|
|
308
|
+
if (typeof id !== "string") return fail(at, "invalid-list-key", "List key must be a string.");
|
|
309
|
+
const items = current, keys$2 = keys(items, node.keyOf);
|
|
310
|
+
if (keys$2.index(id) >= 0) return fail(at, "duplicate-list-item", "List key already exists.");
|
|
311
|
+
if (!valid(keys$2, position)) return fail(at, "invalid-anchor", "Unknown list anchor.");
|
|
312
|
+
session.recorder.order(container);
|
|
313
|
+
session.recorder.member(container, id, session.definition(container, id), -1);
|
|
314
|
+
insert$2(items, index(keys$2, position), value);
|
|
315
|
+
session.invalidate();
|
|
316
|
+
}
|
|
317
|
+
function set$1(session, container, id, value) {
|
|
318
|
+
const { at, node, value: items } = container;
|
|
319
|
+
if (node.kind !== "list") return fail(at, "invalid-list-key", "Expected a list.");
|
|
320
|
+
const index = indexedKeys(items, node.keyOf).index(id);
|
|
321
|
+
if (index < 0) return fail(at, "missing-list-item", "List key does not exist.");
|
|
322
|
+
session.writeMember(container, id, value, "set", index);
|
|
323
|
+
}
|
|
324
|
+
function remove$1(session, container, id) {
|
|
325
|
+
const { at, node, value } = container;
|
|
326
|
+
if (node.kind !== "list") return fail(at, "missing-list-item", "List key does not exist.");
|
|
327
|
+
const index = keys(value, node.keyOf).index(id);
|
|
328
|
+
if (index < 0) return fail(at, "missing-list-item", "List key does not exist.");
|
|
329
|
+
session.writeMember(container, id, void 0, "remove", index);
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region core/src/mutation/operations/tree.ts
|
|
333
|
+
const node = (tree, id) => Object.hasOwn(tree.nodes, id) ? tree.nodes[id] : void 0;
|
|
334
|
+
const positionIndex = (index, length, at) => {
|
|
335
|
+
if (index !== void 0 && (!Number.isInteger(index) || index < 0)) return fail(at, "invalid-tree-index", "Tree index must be nonnegative.");
|
|
336
|
+
return Math.min(index ?? length, length);
|
|
337
|
+
};
|
|
338
|
+
const insert = (session, container, id, value, position) => {
|
|
339
|
+
const { at, node: schema, value: tree } = container;
|
|
340
|
+
if (typeof id !== "string") return fail(at, "invalid-key", "Tree keys must be strings.");
|
|
341
|
+
if (contains(tree, id)) return fail(at, "duplicate-tree-node", "Tree node already exists.");
|
|
342
|
+
const parentId = position?.parentId;
|
|
343
|
+
const parent = parentId === void 0 ? void 0 : node(tree, parentId);
|
|
344
|
+
if (tree.rootId !== void 0 && !parent || tree.rootId === void 0 && parentId !== void 0) return fail(at, "missing-tree-parent", "A nonempty tree requires an existing parent.");
|
|
345
|
+
const index = positionIndex(position?.index, parent?.children.length ?? 0, at);
|
|
346
|
+
session.validate(schema.value, value, at.concat(id));
|
|
347
|
+
session.recorder.tree(container, id);
|
|
348
|
+
if (parentId !== void 0) session.recorder.tree(container, parentId);
|
|
349
|
+
Object.defineProperty(tree.nodes, id, {
|
|
350
|
+
value: {
|
|
351
|
+
...parentId === void 0 ? {} : { parentId },
|
|
352
|
+
children: [],
|
|
353
|
+
value
|
|
354
|
+
},
|
|
355
|
+
writable: true,
|
|
356
|
+
configurable: true,
|
|
357
|
+
enumerable: true
|
|
358
|
+
});
|
|
359
|
+
if (parent) parent.children.splice(index, 0, id);
|
|
360
|
+
else tree.rootId = id;
|
|
361
|
+
session.invalidate();
|
|
362
|
+
};
|
|
363
|
+
const set = (session, container, id, value) => {
|
|
364
|
+
const { at, node: schema, value: tree } = container;
|
|
365
|
+
const entry = node(tree, id);
|
|
366
|
+
if (!entry) return fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
367
|
+
if (Object.is(entry.value, value) && Object.hasOwn(entry, "value")) return;
|
|
368
|
+
session.validate(schema.value, value, at.concat(id));
|
|
369
|
+
session.recorder.tree(container, id);
|
|
370
|
+
entry.value = value;
|
|
371
|
+
};
|
|
372
|
+
const remove = (session, container, id) => {
|
|
373
|
+
const { at, value: tree } = container;
|
|
374
|
+
const entry = node(tree, id);
|
|
375
|
+
if (!entry) return fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
376
|
+
const ids = [], stack = [id];
|
|
377
|
+
while (stack.length) {
|
|
378
|
+
const current = stack.pop();
|
|
379
|
+
ids.push(current);
|
|
380
|
+
for (const child of tree.nodes[current].children) stack.push(child);
|
|
381
|
+
}
|
|
382
|
+
for (const key of ids) session.recorder.tree(container, key);
|
|
383
|
+
if (entry.parentId !== void 0) session.recorder.tree(container, entry.parentId);
|
|
384
|
+
if (entry.parentId === void 0) delete tree.rootId;
|
|
385
|
+
else {
|
|
386
|
+
const children = tree.nodes[entry.parentId].children;
|
|
387
|
+
children.splice(children.indexOf(id), 1);
|
|
388
|
+
}
|
|
389
|
+
for (const key of ids) delete tree.nodes[key];
|
|
390
|
+
session.invalidate();
|
|
391
|
+
};
|
|
392
|
+
const move$1 = (session, container, id, position) => {
|
|
393
|
+
const { at, value: tree } = container;
|
|
394
|
+
const entry = node(tree, id), parentId = position?.parentId;
|
|
395
|
+
if (!entry) return fail(at, "missing-tree-node", "Tree node does not exist.");
|
|
396
|
+
if (entry.parentId === void 0 || parentId === void 0 || !contains(tree, parentId)) return fail(at, "missing-tree-parent", "Moving requires a non-root node and an existing parent.");
|
|
397
|
+
for (let ancestor = parentId; ancestor !== void 0; ancestor = tree.nodes[ancestor].parentId) if (ancestor === id) return fail(at, "tree-cycle", "Cannot move a node under its descendant.");
|
|
398
|
+
const old = tree.nodes[entry.parentId], next = tree.nodes[parentId];
|
|
399
|
+
const index = positionIndex(position?.index, next.children.length - (old === next ? 1 : 0), at);
|
|
400
|
+
const previous = old.children.indexOf(id);
|
|
401
|
+
if (old === next && previous === index) return;
|
|
402
|
+
session.recorder.tree(container, id);
|
|
403
|
+
session.recorder.tree(container, entry.parentId);
|
|
404
|
+
session.recorder.tree(container, parentId);
|
|
405
|
+
old.children.splice(previous, 1);
|
|
406
|
+
next.children.splice(index, 0, id);
|
|
407
|
+
entry.parentId = parentId;
|
|
408
|
+
session.invalidate();
|
|
409
|
+
};
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region core/src/mutation/operations/order.ts
|
|
412
|
+
function move(session, container, id, position) {
|
|
413
|
+
const { at, node, value } = container;
|
|
414
|
+
if (node.kind !== "table" && node.kind !== "list") return fail(at, "invalid-collection", "Expected an ordered container.");
|
|
415
|
+
const items = node.kind === "table" ? value.ids : value;
|
|
416
|
+
const keys$1 = node.kind === "table" ? items : keys(items, node.keyOf);
|
|
417
|
+
const index = node.kind === "table" ? items.indexOf(id) : keys$1.index(id);
|
|
418
|
+
if (index < 0) return fail(at, "missing-entity", "Ordered key does not exist.");
|
|
419
|
+
if (!valid(keys$1, position)) return fail(at, "invalid-anchor", "Unknown order anchor.");
|
|
420
|
+
const next = afterRemove(keys$1, index, position);
|
|
421
|
+
if (next === index) return;
|
|
422
|
+
session.recorder.order(container);
|
|
423
|
+
move$2(items, index, next);
|
|
424
|
+
session.invalidate();
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region core/src/access/scope.ts
|
|
428
|
+
const locations = /* @__PURE__ */ new WeakMap();
|
|
429
|
+
const locationOf = (value) => locations.get(value);
|
|
430
|
+
const addressOf = (target) => {
|
|
431
|
+
if (target.at) return target.at;
|
|
432
|
+
profile.access("addresses");
|
|
433
|
+
return target.at = addressOf(target.parent).concat(target.key);
|
|
434
|
+
};
|
|
435
|
+
const assertActive = (context) => {
|
|
436
|
+
if (context.active && !context.active()) throw new Error("Document access scope has expired.");
|
|
437
|
+
};
|
|
438
|
+
const collect = (context, at, kind = "value", id) => {
|
|
439
|
+
context.dependencies?.record(kind === "value" ? {
|
|
440
|
+
kind,
|
|
441
|
+
at
|
|
442
|
+
} : {
|
|
443
|
+
kind,
|
|
444
|
+
at,
|
|
445
|
+
...id === void 0 ? {} : { id }
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
const snapshot = (value) => {
|
|
449
|
+
const location = value && typeof value === "object" ? locationOf(value) : void 0;
|
|
450
|
+
if (!location) return value;
|
|
451
|
+
const { context } = location;
|
|
452
|
+
const at = addressOf(location.target);
|
|
453
|
+
profile.access("snapshots");
|
|
454
|
+
collect(context, at);
|
|
455
|
+
const resolved = resolve(context, location.target);
|
|
456
|
+
if (!resolved.node) throw new Error("The selected schema address no longer exists.");
|
|
457
|
+
return copyValue(resolved.node, resolved.value);
|
|
458
|
+
};
|
|
459
|
+
/** TypeScript cannot express asymmetric index signatures for nested collection tools. */
|
|
460
|
+
const assign = (container, key, value) => {
|
|
461
|
+
if (!locationOf(container)) throw new TypeError("assign requires scoped document access.");
|
|
462
|
+
if (typeof key !== "string") throw new TypeError("Document keys must be strings.");
|
|
463
|
+
Reflect.set(container, key, value);
|
|
464
|
+
};
|
|
465
|
+
const resolve = (context, target) => {
|
|
466
|
+
assertActive(context);
|
|
467
|
+
const generation = context.session?.generation ?? 0;
|
|
468
|
+
if (target.generation !== generation) {
|
|
469
|
+
profile.access("resolutions");
|
|
470
|
+
if (!target.parent && !target.at.length) {
|
|
471
|
+
target.node = context.state.schema;
|
|
472
|
+
target.value = context.state.document;
|
|
473
|
+
} else {
|
|
474
|
+
const parent = target.parent && resolve(context, target.parent);
|
|
475
|
+
const location = parent ? resolveChild(parent.node, parent.value, target.key) : resolveLocated(context.state.schema, context.state.document, target.at);
|
|
476
|
+
target.node = location?.node ?? nodeAt(context.state.schema, addressOf(target), context.state.document);
|
|
477
|
+
target.value = location && Object.hasOwn(location.parent, location.key) ? location.parent[location.key] : void 0;
|
|
478
|
+
}
|
|
479
|
+
target.generation = generation;
|
|
480
|
+
}
|
|
481
|
+
return target;
|
|
482
|
+
};
|
|
483
|
+
const createAccess = (context, initial = []) => {
|
|
484
|
+
profile.access("scopes");
|
|
485
|
+
const mutable = () => {
|
|
486
|
+
assertActive(context);
|
|
487
|
+
if (!context.session) throw new TypeError("Cannot modify read-only document access.");
|
|
488
|
+
return context.session;
|
|
489
|
+
};
|
|
490
|
+
const writableContainer = (target, session) => {
|
|
491
|
+
if (target.container?.generation === session.generation && "layout" in target.container) return target.container;
|
|
492
|
+
return target.container = session.bind(addressOf(target), target.node, target.value);
|
|
493
|
+
};
|
|
494
|
+
const childAt = (target, key) => {
|
|
495
|
+
profile.access("addresses");
|
|
496
|
+
return addressOf(target).concat(key);
|
|
497
|
+
};
|
|
498
|
+
const access = (target) => {
|
|
499
|
+
if (target.proxy) return target.proxy;
|
|
500
|
+
const proxy = new Proxy(target, handler);
|
|
501
|
+
profile.access("proxies");
|
|
502
|
+
locations.set(proxy, {
|
|
503
|
+
context,
|
|
504
|
+
target
|
|
505
|
+
});
|
|
506
|
+
target.proxy = proxy;
|
|
507
|
+
return proxy;
|
|
508
|
+
};
|
|
509
|
+
const createTarget = (at, parent, key) => ({
|
|
510
|
+
at,
|
|
511
|
+
parent,
|
|
512
|
+
key,
|
|
513
|
+
node: void 0,
|
|
514
|
+
value: void 0,
|
|
515
|
+
generation: -1,
|
|
516
|
+
children: void 0,
|
|
517
|
+
proxy: void 0
|
|
518
|
+
});
|
|
519
|
+
const childAccess = (target, member, node, value) => {
|
|
520
|
+
const key = typeof member === "string" ? member : member.key;
|
|
521
|
+
if (node.kind === "field" || value === void 0) {
|
|
522
|
+
if (context.dependencies) collect(context, childAt(target, key));
|
|
523
|
+
return value;
|
|
524
|
+
}
|
|
525
|
+
let children = target.children;
|
|
526
|
+
let child;
|
|
527
|
+
if (typeof member !== "string") {
|
|
528
|
+
if (children?.kind !== "fixed" || children.schema !== target.node) children = {
|
|
529
|
+
kind: "fixed",
|
|
530
|
+
schema: target.node,
|
|
531
|
+
slots: new Array(compiledShape(target.node, target.value).members.size)
|
|
532
|
+
};
|
|
533
|
+
child = children.slots[member.slot] ??= createTarget(void 0, target, key);
|
|
534
|
+
} else {
|
|
535
|
+
if (children?.kind !== "dynamic") children = {
|
|
536
|
+
kind: "dynamic",
|
|
537
|
+
keys: /* @__PURE__ */ new Map()
|
|
538
|
+
};
|
|
539
|
+
const retained = children.keys.get(key);
|
|
540
|
+
child = retained ?? createTarget(void 0, target, key);
|
|
541
|
+
if (!retained) children.keys.set(key, child);
|
|
542
|
+
}
|
|
543
|
+
target.children = children;
|
|
544
|
+
if (child.generation === target.generation) return child.proxy;
|
|
545
|
+
child.node = node;
|
|
546
|
+
child.value = value;
|
|
547
|
+
child.generation = target.generation;
|
|
548
|
+
return access(child);
|
|
549
|
+
};
|
|
550
|
+
const collectionValue = (target, node) => {
|
|
551
|
+
const current = resolve(context, target);
|
|
552
|
+
if (current.node !== node || current.value === void 0) throw new TypeError("The collection method belongs to a replaced schema branch.");
|
|
553
|
+
return current.value;
|
|
554
|
+
};
|
|
555
|
+
const writableCollection = (target, node) => {
|
|
556
|
+
const session = mutable();
|
|
557
|
+
collectionValue(target, node);
|
|
558
|
+
return writableContainer(target, session);
|
|
559
|
+
};
|
|
560
|
+
const writableTree = (target, node) => {
|
|
561
|
+
const session = mutable();
|
|
562
|
+
collectionValue(target, node);
|
|
563
|
+
if (target.container?.generation === session.generation && !("layout" in target.container)) return target.container;
|
|
564
|
+
return target.container = session.bindTree(addressOf(target), target.node, target.value);
|
|
565
|
+
};
|
|
566
|
+
const orderedMethod = (target, property, node) => {
|
|
567
|
+
const at = addressOf(target);
|
|
568
|
+
if (property === "ids") return () => {
|
|
569
|
+
const current = collectionValue(target, node);
|
|
570
|
+
collect(context, at, "collection");
|
|
571
|
+
return node.kind === "table" ? [...current.ids] : current.map(node.keyOf);
|
|
572
|
+
};
|
|
573
|
+
if (property === "has") return (id) => {
|
|
574
|
+
const current = collectionValue(target, node);
|
|
575
|
+
collect(context, at, "collection", id);
|
|
576
|
+
return node.kind === "table" ? Object.hasOwn(current.byId, id) : indexedKeys(current, node.keyOf).index(id) >= 0;
|
|
577
|
+
};
|
|
578
|
+
if (property === "get") return (id) => {
|
|
579
|
+
const current = collectionValue(target, node);
|
|
580
|
+
collect(context, at, "collection", id);
|
|
581
|
+
const location = resolveChild(node, current, id);
|
|
582
|
+
if (!location || !Object.hasOwn(location.parent, location.key)) return void 0;
|
|
583
|
+
return childAccess(target, id, location.node, location.parent[location.key]);
|
|
584
|
+
};
|
|
585
|
+
if (property === "move") return (id, position) => {
|
|
586
|
+
const writable = writableCollection(target, node);
|
|
587
|
+
move(context.session, writable, id, position);
|
|
588
|
+
};
|
|
589
|
+
};
|
|
590
|
+
const tableMethod = (target, property, node) => {
|
|
591
|
+
if (property === "create") return (input, position) => {
|
|
592
|
+
const writable = writableCollection(target, node);
|
|
593
|
+
create(context.session, writable, Array.isArray(input) ? input : [input], position);
|
|
594
|
+
};
|
|
595
|
+
if (property === "remove") return (ids) => {
|
|
596
|
+
const writable = writableCollection(target, node);
|
|
597
|
+
remove$2(context.session, writable, typeof ids === "string" ? [ids] : ids);
|
|
598
|
+
};
|
|
599
|
+
return orderedMethod(target, property, node);
|
|
600
|
+
};
|
|
601
|
+
const listMethod = (target, property, node) => {
|
|
602
|
+
const at = addressOf(target);
|
|
603
|
+
if (property === "insert") return (value, position) => {
|
|
604
|
+
const writable = writableCollection(target, node);
|
|
605
|
+
insert$1(context.session, writable, value, position);
|
|
606
|
+
};
|
|
607
|
+
if (property === "remove") return (id) => {
|
|
608
|
+
const writable = writableCollection(target, node);
|
|
609
|
+
remove$1(context.session, writable, id);
|
|
610
|
+
};
|
|
611
|
+
if (property === "set") return (id, value) => {
|
|
612
|
+
const writable = writableCollection(target, node);
|
|
613
|
+
set$1(context.session, writable, id, value);
|
|
614
|
+
};
|
|
615
|
+
if (property === "replace") return (value) => {
|
|
616
|
+
const session = mutable();
|
|
617
|
+
collectionValue(target, node);
|
|
618
|
+
session.replace(at, value);
|
|
619
|
+
};
|
|
620
|
+
return orderedMethod(target, property, node);
|
|
621
|
+
};
|
|
622
|
+
const treeMethod = (target, property, node) => {
|
|
623
|
+
const at = addressOf(target);
|
|
624
|
+
if (property === "insert") return (id, value, position) => {
|
|
625
|
+
const writable = writableTree(target, node);
|
|
626
|
+
insert(context.session, writable, id, value, position);
|
|
627
|
+
};
|
|
628
|
+
if (property === "set") return (id, value) => {
|
|
629
|
+
const writable = writableTree(target, node);
|
|
630
|
+
set(context.session, writable, id, value);
|
|
631
|
+
};
|
|
632
|
+
if (property === "remove") return (id) => {
|
|
633
|
+
const writable = writableTree(target, node);
|
|
634
|
+
remove(context.session, writable, id);
|
|
635
|
+
};
|
|
636
|
+
if (property === "move") return (id, position) => {
|
|
637
|
+
const writable = writableTree(target, node);
|
|
638
|
+
move$1(context.session, writable, id, position);
|
|
639
|
+
};
|
|
640
|
+
if (property === "replace") return (value) => {
|
|
641
|
+
const session = mutable();
|
|
642
|
+
collectionValue(target, node);
|
|
643
|
+
session.replace(at, value);
|
|
644
|
+
};
|
|
645
|
+
const current = () => {
|
|
646
|
+
collect(context, at);
|
|
647
|
+
return collectionValue(target, node);
|
|
648
|
+
};
|
|
649
|
+
if (property === "rootId") return () => current().rootId;
|
|
650
|
+
if (property === "get") return (id) => {
|
|
651
|
+
const value = current();
|
|
652
|
+
return contains(value, id) ? value.nodes[id].value : void 0;
|
|
653
|
+
};
|
|
654
|
+
if (property === "has") return (id) => contains(current(), id);
|
|
655
|
+
if (property === "parent") return (id) => parent(current(), id);
|
|
656
|
+
if (property === "children") return (id) => {
|
|
657
|
+
const children$1 = children(current(), id);
|
|
658
|
+
return children$1 ? [...children$1] : void 0;
|
|
659
|
+
};
|
|
660
|
+
};
|
|
661
|
+
const handler = {
|
|
662
|
+
get: (target, property) => {
|
|
663
|
+
const { node, value } = resolve(context, target);
|
|
664
|
+
if (!node || value === void 0) return void 0;
|
|
665
|
+
if (typeof property !== "string") return void 0;
|
|
666
|
+
if (node.kind === "map") {
|
|
667
|
+
if (context.dependencies) collect(context, addressOf(target), "collection", property);
|
|
668
|
+
if (!Object.hasOwn(value, property)) return void 0;
|
|
669
|
+
if (node.value.kind === "field") {
|
|
670
|
+
if (context.dependencies) collect(context, childAt(target, property));
|
|
671
|
+
return value[property];
|
|
672
|
+
}
|
|
673
|
+
return childAccess(target, property, node.value, value[property]);
|
|
674
|
+
}
|
|
675
|
+
if (node.kind === "table") return tableMethod(target, property, node);
|
|
676
|
+
if (node.kind === "list") return listMethod(target, property, node);
|
|
677
|
+
if (node.kind === "tree") return treeMethod(target, property, node);
|
|
678
|
+
if (node.kind === "variant" && property === node.tag) {
|
|
679
|
+
collect(context, childAt(target, property));
|
|
680
|
+
return value[property];
|
|
681
|
+
}
|
|
682
|
+
const member = compiledShape(node, value)?.members.get(property);
|
|
683
|
+
if (!member) return void 0;
|
|
684
|
+
if (member.field) {
|
|
685
|
+
if (context.dependencies) collect(context, childAt(target, property));
|
|
686
|
+
return value[property];
|
|
687
|
+
}
|
|
688
|
+
return childAccess(target, node.kind === "object" ? member : property, member.node, value[property]);
|
|
689
|
+
},
|
|
690
|
+
set: (target, property, value) => {
|
|
691
|
+
const session = mutable(), { node } = resolve(context, target);
|
|
692
|
+
if (typeof property !== "string" || !node || node.kind !== "object" && node.kind !== "variant" && node.kind !== "map") throw new TypeError("Invalid structural assignment.");
|
|
693
|
+
if (node.kind === "variant" && node.tag === property) throw new TypeError("Variant discriminants are read-only.");
|
|
694
|
+
session.assignMember(writableContainer(target, session), property, value);
|
|
695
|
+
return true;
|
|
696
|
+
},
|
|
697
|
+
deleteProperty: (target, property) => {
|
|
698
|
+
const session = mutable(), { node } = resolve(context, target);
|
|
699
|
+
if (typeof property !== "string" || node?.kind === "variant" && node.tag === property) throw new TypeError("Invalid structural deletion.");
|
|
700
|
+
session.removeMember(writableContainer(target, session), property);
|
|
701
|
+
return true;
|
|
702
|
+
},
|
|
703
|
+
has: (target, property) => {
|
|
704
|
+
const { node, value } = resolve(context, target);
|
|
705
|
+
if (typeof property !== "string") return false;
|
|
706
|
+
collect(context, addressOf(target), node?.kind === "map" ? "collection" : "value", node?.kind === "map" ? property : void 0);
|
|
707
|
+
return value !== void 0 && Object.hasOwn(value, property);
|
|
708
|
+
},
|
|
709
|
+
ownKeys: (target) => {
|
|
710
|
+
const { value } = resolve(context, target);
|
|
711
|
+
if (context.dependencies) collect(context, addressOf(target), "collection");
|
|
712
|
+
return value && typeof value === "object" ? Object.keys(value) : [];
|
|
713
|
+
},
|
|
714
|
+
getOwnPropertyDescriptor: (target, property) => {
|
|
715
|
+
const { value } = resolve(context, target);
|
|
716
|
+
return value && Object.hasOwn(value, property) ? {
|
|
717
|
+
enumerable: true,
|
|
718
|
+
configurable: true
|
|
719
|
+
} : void 0;
|
|
720
|
+
},
|
|
721
|
+
defineProperty: () => {
|
|
722
|
+
assertActive(context);
|
|
723
|
+
throw new TypeError("Use schema assignments.");
|
|
724
|
+
},
|
|
725
|
+
setPrototypeOf: () => {
|
|
726
|
+
assertActive(context);
|
|
727
|
+
throw new TypeError("Document prototypes are immutable.");
|
|
728
|
+
},
|
|
729
|
+
preventExtensions: () => {
|
|
730
|
+
assertActive(context);
|
|
731
|
+
throw new TypeError("Document access cannot be frozen.");
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
const root = resolve(context, createTarget(initial, void 0, ""));
|
|
735
|
+
if (!root.node || root.node.kind === "field" || root.value === void 0) {
|
|
736
|
+
if (context.dependencies) collect(context, initial);
|
|
737
|
+
return root.value;
|
|
738
|
+
}
|
|
739
|
+
return access(root);
|
|
740
|
+
};
|
|
741
|
+
const collectionAccess = (context, at) => {
|
|
742
|
+
const value = createAccess(context, at);
|
|
743
|
+
if (value === void 0) return {
|
|
744
|
+
get: () => {
|
|
745
|
+
assertActive(context);
|
|
746
|
+
},
|
|
747
|
+
has: () => {
|
|
748
|
+
assertActive(context);
|
|
749
|
+
return false;
|
|
750
|
+
},
|
|
751
|
+
ids: () => {
|
|
752
|
+
assertActive(context);
|
|
753
|
+
return [];
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
if (locations.get(value).target.node?.kind === "table") return value;
|
|
757
|
+
const map = value;
|
|
758
|
+
return {
|
|
759
|
+
get: (id) => map[id],
|
|
760
|
+
has: (id) => id in map,
|
|
761
|
+
ids: () => Object.keys(map)
|
|
762
|
+
};
|
|
763
|
+
};
|
|
764
|
+
//#endregion
|
|
765
|
+
//#region core/src/impact-target.ts
|
|
766
|
+
const address = (target) => "at" in target ? target.at : target.address;
|
|
767
|
+
const id = (target) => target.kind === "collection" && "id" in target ? target.id : void 0;
|
|
768
|
+
const belongs = (target, schema) => !("schema" in target) || target.schema === schema;
|
|
769
|
+
const same = (left, right) => {
|
|
770
|
+
if (left.kind !== right.kind) return false;
|
|
771
|
+
if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
|
|
772
|
+
const leftAddress = address(left);
|
|
773
|
+
const rightAddress = address(right);
|
|
774
|
+
if (leftAddress.length !== rightAddress.length) return false;
|
|
775
|
+
for (let index = 0; index < leftAddress.length; index += 1) if (leftAddress[index] !== rightAddress[index]) return false;
|
|
776
|
+
return left.kind !== "collection" || id(left) === id(right);
|
|
777
|
+
};
|
|
778
|
+
const indexedAddress = (target) => {
|
|
779
|
+
const key = id(target);
|
|
780
|
+
return key === void 0 ? address(target) : [...address(target), key];
|
|
781
|
+
};
|
|
782
|
+
const affected = (target, values, orders) => {
|
|
783
|
+
const at = indexedAddress(target);
|
|
784
|
+
return id(target) !== void 0 ? values.hasAncestor(at) : values.overlaps(at) || orders.hasDescendant(at);
|
|
785
|
+
};
|
|
786
|
+
/** Targets are classified once; commit matching never builds a reverse impact index. */
|
|
787
|
+
var SubscriptionIndex = class {
|
|
788
|
+
ordinary = new AddressIndex();
|
|
789
|
+
membership = new AddressIndex();
|
|
790
|
+
constructor(schema) {
|
|
791
|
+
this.schema = schema;
|
|
792
|
+
}
|
|
793
|
+
add(target, value) {
|
|
794
|
+
if (belongs(target, this.schema)) (id(target) === void 0 ? this.ordinary : this.membership).add(indexedAddress(target), value);
|
|
795
|
+
}
|
|
796
|
+
delete(target, value) {
|
|
797
|
+
if (belongs(target, this.schema)) (id(target) === void 0 ? this.ordinary : this.membership).delete(indexedAddress(target), value);
|
|
798
|
+
}
|
|
799
|
+
collect(changes, visit) {
|
|
800
|
+
const values = this.ordinary.query(visit);
|
|
801
|
+
const members = this.membership.query(visit, "descendants");
|
|
802
|
+
const orders = this.ordinary.query(visit, "ancestors");
|
|
803
|
+
for (const change of changes.changes) if (change.kind === "reset") {
|
|
804
|
+
values([]);
|
|
805
|
+
members([]);
|
|
806
|
+
} else if (change.kind === "members") {
|
|
807
|
+
values(change.at, change.members);
|
|
808
|
+
members(change.at, change.members);
|
|
809
|
+
if (change.order) orders(change.at);
|
|
810
|
+
} else {
|
|
811
|
+
values(change.at);
|
|
812
|
+
members(change.at);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
clear() {
|
|
816
|
+
this.ordinary.clear();
|
|
817
|
+
this.membership.clear();
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
//#endregion
|
|
821
|
+
//#region core/src/impact.ts
|
|
822
|
+
const queries = /* @__PURE__ */ new WeakMap();
|
|
823
|
+
const affectsTarget = (impact, value) => queries.get(impact).affects(value);
|
|
824
|
+
const collectionImpact = (impact, selector) => queries.get(impact).collection(selector);
|
|
825
|
+
const createImpact = (schema, changes) => {
|
|
826
|
+
const reset = changes.changes.some((change) => change.kind === "reset");
|
|
827
|
+
let values;
|
|
828
|
+
let orders;
|
|
829
|
+
const index = () => {
|
|
830
|
+
if (!values) {
|
|
831
|
+
profile.impact.index();
|
|
832
|
+
values = new AddressIndex();
|
|
833
|
+
orders = new AddressIndex();
|
|
834
|
+
for (const change of changes.changes) if (change.kind === "members") {
|
|
835
|
+
for (const member of change.members) values.add(change.at, true, member.key);
|
|
836
|
+
if (change.order) orders.add(change.at, true);
|
|
837
|
+
} else if (change.kind === "tree") values.add(change.at, true);
|
|
838
|
+
}
|
|
839
|
+
return values;
|
|
840
|
+
};
|
|
841
|
+
const cache = /* @__PURE__ */ new Map();
|
|
842
|
+
const implementation = {
|
|
843
|
+
affects(value) {
|
|
844
|
+
profile.impact.affects();
|
|
845
|
+
if (!belongs(value, schema)) return false;
|
|
846
|
+
if (reset) return true;
|
|
847
|
+
return affected(value, index(), orders);
|
|
848
|
+
},
|
|
849
|
+
collection(selector) {
|
|
850
|
+
if (selector.schema !== schema) throw new TypeError("Collection belongs to another root model.");
|
|
851
|
+
const key = JSON.stringify(selector.address), cached = cache.get(key);
|
|
852
|
+
if (cached) return cached;
|
|
853
|
+
if (reset) {
|
|
854
|
+
const result = { kind: "reset" };
|
|
855
|
+
cache.set(key, result);
|
|
856
|
+
return result;
|
|
857
|
+
}
|
|
858
|
+
const at = selector.address;
|
|
859
|
+
const added = /* @__PURE__ */ new Set(), removed = /* @__PURE__ */ new Set(), updated = /* @__PURE__ */ new Set();
|
|
860
|
+
let orderChanged = false;
|
|
861
|
+
for (const change of changes.changes) {
|
|
862
|
+
if (change.kind === "reset") continue;
|
|
863
|
+
if (change.kind === "members") {
|
|
864
|
+
if (change.at.length < at.length && contains$1(change.at, at)) {
|
|
865
|
+
if (change.members.some((member) => member.key === at[change.at.length])) {
|
|
866
|
+
const result = { kind: "reset" };
|
|
867
|
+
cache.set(key, result);
|
|
868
|
+
return result;
|
|
869
|
+
}
|
|
870
|
+
} else if (contains$1(at, change.at)) if (change.at.length === at.length) {
|
|
871
|
+
if (change.order) {
|
|
872
|
+
const positions = new Map(change.order.after.map((id, i) => [id, i]));
|
|
873
|
+
let previous = -1;
|
|
874
|
+
for (const id of change.order.before) {
|
|
875
|
+
const position = positions.get(id);
|
|
876
|
+
if (position === void 0) continue;
|
|
877
|
+
if (position < previous) orderChanged = true;
|
|
878
|
+
previous = position;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
for (const member of change.members) if (member.kind === "added") added.add(member.key);
|
|
882
|
+
else if (member.kind === "removed") removed.add(member.key);
|
|
883
|
+
else updated.add(member.key);
|
|
884
|
+
} else updated.add(change.at[at.length]);
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
if (change.kind === "tree" && contains$1(change.at, at)) {
|
|
888
|
+
const result = { kind: "reset" };
|
|
889
|
+
cache.set(key, result);
|
|
890
|
+
return result;
|
|
891
|
+
}
|
|
892
|
+
if (!contains$1(at, change.at)) continue;
|
|
893
|
+
if (change.at.length === at.length) continue;
|
|
894
|
+
const id = change.at[at.length];
|
|
895
|
+
updated.add(id);
|
|
896
|
+
}
|
|
897
|
+
added.forEach((id) => updated.delete(id));
|
|
898
|
+
removed.forEach((id) => updated.delete(id));
|
|
899
|
+
const result = {
|
|
900
|
+
kind: "incremental",
|
|
901
|
+
added,
|
|
902
|
+
removed,
|
|
903
|
+
updated,
|
|
904
|
+
orderChanged
|
|
905
|
+
};
|
|
906
|
+
cache.set(key, result);
|
|
907
|
+
return result;
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
const impact = Object.freeze({
|
|
911
|
+
kind: reset ? "reset" : "incremental",
|
|
912
|
+
affects: (pick) => implementation.affects(compilePath(schema, "value", pick)),
|
|
913
|
+
collection: (pick) => implementation.collection(compilePath(schema, "collection", pick))
|
|
914
|
+
});
|
|
915
|
+
queries.set(impact, implementation);
|
|
916
|
+
return impact;
|
|
917
|
+
};
|
|
918
|
+
//#endregion
|
|
919
|
+
//#region core/src/runtime/contract.ts
|
|
920
|
+
var DocumentReentrancyError = class extends Error {
|
|
921
|
+
constructor() {
|
|
922
|
+
super("Document writes cannot be re-entered during an update or notification.");
|
|
923
|
+
this.name = "DocumentReentrancyError";
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
var DocumentDisposedError = class extends Error {
|
|
927
|
+
constructor() {
|
|
928
|
+
super("Doxum runtime has been disposed.");
|
|
929
|
+
this.name = "DocumentDisposedError";
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
var TransactionRejected = class extends Error {
|
|
933
|
+
issues;
|
|
934
|
+
constructor(input) {
|
|
935
|
+
super("Document update rejected.");
|
|
936
|
+
this.name = "TransactionRejected";
|
|
937
|
+
const issues = Array.isArray(input) ? input : [input];
|
|
938
|
+
this.issues = Object.freeze(issues.map((issue) => Object.freeze({
|
|
939
|
+
code: issue.code,
|
|
940
|
+
message: issue.message,
|
|
941
|
+
source: "application",
|
|
942
|
+
...issue.address === void 0 ? {} : { address: Object.freeze([...issue.address]) }
|
|
943
|
+
})));
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
//#endregion
|
|
947
|
+
//#region core/src/runtime/access.ts
|
|
948
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
949
|
+
const bindRuntimeAccess = (runtime, state) => {
|
|
950
|
+
states.set(runtime, state);
|
|
951
|
+
};
|
|
952
|
+
const accessOf = (runtime) => {
|
|
953
|
+
const state = states.get(runtime);
|
|
954
|
+
if (!state) throw new Error("Unknown Doxum runtime.");
|
|
955
|
+
return state;
|
|
956
|
+
};
|
|
957
|
+
const readWith = (runtime, run, dependencies) => {
|
|
958
|
+
const state = accessOf(runtime);
|
|
959
|
+
if (state.disposed) throw new DocumentDisposedError();
|
|
960
|
+
state.projectionLocks = (state.projectionLocks ?? 0) + 1;
|
|
961
|
+
try {
|
|
962
|
+
return run(createAccess({
|
|
963
|
+
state,
|
|
964
|
+
dependencies
|
|
965
|
+
}));
|
|
966
|
+
} finally {
|
|
967
|
+
state.projectionLocks--;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region core/src/runtime/notification.ts
|
|
972
|
+
const notifications = /* @__PURE__ */ new WeakMap();
|
|
973
|
+
const readableOwners = /* @__PURE__ */ new WeakMap();
|
|
974
|
+
const bindDocumentReadable = (readable, runtime) => {
|
|
975
|
+
readableOwners.set(readable, runtime);
|
|
976
|
+
};
|
|
977
|
+
const documentReadableOwner = (readable) => readableOwners.get(readable);
|
|
978
|
+
const createNotification = (runtime) => {
|
|
979
|
+
const notification = {
|
|
980
|
+
root: /* @__PURE__ */ new Set(),
|
|
981
|
+
filtered: /* @__PURE__ */ new Set(),
|
|
982
|
+
index: new SubscriptionIndex(runtime.schema),
|
|
983
|
+
cleanups: /* @__PURE__ */ new Set(),
|
|
984
|
+
processors: [],
|
|
985
|
+
candidates: /* @__PURE__ */ new Set(),
|
|
986
|
+
rootSnapshot: [],
|
|
987
|
+
notifying: false
|
|
988
|
+
};
|
|
989
|
+
notifications.set(runtime, notification);
|
|
990
|
+
return notification;
|
|
991
|
+
};
|
|
992
|
+
const notificationOf = (runtime) => {
|
|
993
|
+
const value = notifications.get(runtime);
|
|
994
|
+
if (!value) throw new Error("Unknown Doxum runtime.");
|
|
995
|
+
return value;
|
|
996
|
+
};
|
|
997
|
+
const shareNotification = (source, target) => {
|
|
998
|
+
notifications.set(target, notificationOf(source));
|
|
999
|
+
};
|
|
1000
|
+
const attachProjection = (runtime, processor) => {
|
|
1001
|
+
const notification = notificationOf(runtime);
|
|
1002
|
+
const entry = {
|
|
1003
|
+
processor,
|
|
1004
|
+
active: true
|
|
1005
|
+
};
|
|
1006
|
+
notification.processors.push(entry);
|
|
1007
|
+
return () => {
|
|
1008
|
+
if (!entry.active) return;
|
|
1009
|
+
entry.active = false;
|
|
1010
|
+
if (!notification.notifying) {
|
|
1011
|
+
const index = notification.processors.indexOf(entry);
|
|
1012
|
+
if (index >= 0) notification.processors.splice(index, 1);
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
};
|
|
1016
|
+
const subscribeRoot = (notification, listener) => {
|
|
1017
|
+
const entry = {
|
|
1018
|
+
listener,
|
|
1019
|
+
active: true
|
|
1020
|
+
};
|
|
1021
|
+
notification.root.add(entry);
|
|
1022
|
+
return () => {
|
|
1023
|
+
entry.active = false;
|
|
1024
|
+
if (!notification.notifying) notification.root.delete(entry);
|
|
1025
|
+
else notification.cleanups.add(() => {
|
|
1026
|
+
notification.root.delete(entry);
|
|
1027
|
+
});
|
|
1028
|
+
};
|
|
1029
|
+
};
|
|
1030
|
+
const subscribeTargets = (notification, targets, listener) => {
|
|
1031
|
+
const entry = {
|
|
1032
|
+
targets: Object.freeze(targets.slice()),
|
|
1033
|
+
listener,
|
|
1034
|
+
active: true
|
|
1035
|
+
};
|
|
1036
|
+
notification.filtered.add(entry);
|
|
1037
|
+
entry.targets.forEach((value) => notification.index.add(value, entry));
|
|
1038
|
+
const remove = () => {
|
|
1039
|
+
notification.filtered.delete(entry);
|
|
1040
|
+
entry.targets.forEach((value) => notification.index.delete(value, entry));
|
|
1041
|
+
};
|
|
1042
|
+
return () => {
|
|
1043
|
+
if (!entry.active) return;
|
|
1044
|
+
entry.active = false;
|
|
1045
|
+
if (notification.notifying) notification.cleanups.add(remove);
|
|
1046
|
+
else remove();
|
|
1047
|
+
};
|
|
1048
|
+
};
|
|
1049
|
+
const notify = (notification, commit, afterSettle) => {
|
|
1050
|
+
notification.notifying = true;
|
|
1051
|
+
const errors = [];
|
|
1052
|
+
const call = (listener) => {
|
|
1053
|
+
try {
|
|
1054
|
+
listener(commit);
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
errors.push(Object.freeze({
|
|
1057
|
+
phase: "listener",
|
|
1058
|
+
error
|
|
1059
|
+
}));
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
try {
|
|
1063
|
+
const processorCount = notification.processors.length;
|
|
1064
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1065
|
+
const entry = notification.processors[index];
|
|
1066
|
+
if (!entry?.active) continue;
|
|
1067
|
+
try {
|
|
1068
|
+
entry.processor.capture(commit);
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
errors.push(Object.freeze({
|
|
1071
|
+
phase: "processor",
|
|
1072
|
+
error
|
|
1073
|
+
}));
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1077
|
+
const entry = notification.processors[index];
|
|
1078
|
+
if (!entry?.active) continue;
|
|
1079
|
+
try {
|
|
1080
|
+
entry.processor.settle();
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
errors.push(Object.freeze({
|
|
1083
|
+
phase: "processor",
|
|
1084
|
+
error
|
|
1085
|
+
}));
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
for (let index = 0; index < processorCount; index += 1) {
|
|
1089
|
+
const entry = notification.processors[index];
|
|
1090
|
+
if (!entry?.active) continue;
|
|
1091
|
+
try {
|
|
1092
|
+
errors.push(...entry.processor.flush());
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
errors.push(Object.freeze({
|
|
1095
|
+
phase: "flush",
|
|
1096
|
+
error
|
|
1097
|
+
}));
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (afterSettle) errors.push(...afterSettle());
|
|
1101
|
+
const candidates = notification.candidates;
|
|
1102
|
+
candidates.clear();
|
|
1103
|
+
if (notification.filtered.size) notification.index.collect(commit.changes, (entry) => candidates.add(entry));
|
|
1104
|
+
candidates.forEach((entry) => {
|
|
1105
|
+
if (entry.active) call(entry.listener);
|
|
1106
|
+
});
|
|
1107
|
+
const rootSnapshot = notification.rootSnapshot;
|
|
1108
|
+
rootSnapshot.length = 0;
|
|
1109
|
+
notification.root.forEach((entry) => rootSnapshot.push(entry));
|
|
1110
|
+
for (const entry of rootSnapshot) if (entry.active) call(entry.listener);
|
|
1111
|
+
} finally {
|
|
1112
|
+
notification.notifying = false;
|
|
1113
|
+
let writeIndex = 0;
|
|
1114
|
+
for (const entry of notification.processors) if (entry.active) notification.processors[writeIndex++] = entry;
|
|
1115
|
+
notification.processors.length = writeIndex;
|
|
1116
|
+
notification.cleanups.forEach((cleanup) => cleanup());
|
|
1117
|
+
notification.cleanups.clear();
|
|
1118
|
+
}
|
|
1119
|
+
return Object.freeze(errors);
|
|
1120
|
+
};
|
|
1121
|
+
const subscribeDependencies = (runtime, targets, listener) => subscribeTargets(notificationOf(runtime), targets, listener);
|
|
1122
|
+
const disposeNotification = (notification) => {
|
|
1123
|
+
const attachments = notification.processors.slice();
|
|
1124
|
+
notification.root.clear();
|
|
1125
|
+
notification.filtered.clear();
|
|
1126
|
+
notification.index.clear();
|
|
1127
|
+
notification.cleanups.clear();
|
|
1128
|
+
notification.processors.length = 0;
|
|
1129
|
+
notification.candidates.clear();
|
|
1130
|
+
notification.rootSnapshot.length = 0;
|
|
1131
|
+
const errors = [];
|
|
1132
|
+
attachments.forEach((entry) => {
|
|
1133
|
+
if (!entry.active) return;
|
|
1134
|
+
try {
|
|
1135
|
+
entry.processor.dispose();
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
errors.push(error);
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
if (errors.length) throw new AggregateError(errors, "Projection disposal notification failed.");
|
|
1141
|
+
};
|
|
1142
|
+
//#endregion
|
|
1143
|
+
//#region core/src/projection/definition.ts
|
|
1144
|
+
const definitions = /* @__PURE__ */ new WeakMap();
|
|
1145
|
+
const freezeSources = (sources) => Object.freeze({ ...sources });
|
|
1146
|
+
const define = (definition) => {
|
|
1147
|
+
const handle = Object.freeze({});
|
|
1148
|
+
definitions.set(handle, definition);
|
|
1149
|
+
return handle;
|
|
1150
|
+
};
|
|
1151
|
+
const definitionOf = (projection) => {
|
|
1152
|
+
const definition = definitions.get(projection);
|
|
1153
|
+
if (!definition) throw new TypeError("Unknown projection definition.");
|
|
1154
|
+
return definition;
|
|
1155
|
+
};
|
|
1156
|
+
const input = (initial, options) => define({
|
|
1157
|
+
kind: "input",
|
|
1158
|
+
initial,
|
|
1159
|
+
isEqual: options?.isEqual
|
|
1160
|
+
});
|
|
1161
|
+
function project(first, second, third, fourth) {
|
|
1162
|
+
if (first && typeof first === "object" && "kind" in first && (first.kind === "value" || first.kind === "collection")) {
|
|
1163
|
+
const spec = first;
|
|
1164
|
+
return define({
|
|
1165
|
+
kind: spec.kind === "value" ? "advanced-value" : "advanced-collection",
|
|
1166
|
+
sources: freezeSources(spec.sources),
|
|
1167
|
+
build: spec.build,
|
|
1168
|
+
isEqual: spec.isEqual,
|
|
1169
|
+
name: spec.name
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
if (first && typeof first === "object" && "revision" in first && "subscribe" in first) {
|
|
1173
|
+
if ("address" in first) {
|
|
1174
|
+
const document = first;
|
|
1175
|
+
if (!second) return define({
|
|
1176
|
+
kind: "document",
|
|
1177
|
+
document
|
|
1178
|
+
});
|
|
1179
|
+
if (Array.isArray(second)) return define({
|
|
1180
|
+
kind: "document",
|
|
1181
|
+
document,
|
|
1182
|
+
targets: Object.freeze([...second])
|
|
1183
|
+
});
|
|
1184
|
+
if (typeof second !== "function") throw new TypeError("Invalid document projection.");
|
|
1185
|
+
const source = define({
|
|
1186
|
+
kind: "document-collection",
|
|
1187
|
+
document,
|
|
1188
|
+
pick: second
|
|
1189
|
+
});
|
|
1190
|
+
if (!third || typeof third !== "function") return source;
|
|
1191
|
+
return define({
|
|
1192
|
+
kind: "map",
|
|
1193
|
+
source,
|
|
1194
|
+
mapper: third,
|
|
1195
|
+
isEqual: fourth?.isEqual
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
return define({
|
|
1199
|
+
kind: "readable",
|
|
1200
|
+
readable: first
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
if (second) {
|
|
1204
|
+
if (typeof second !== "function") throw new TypeError("Invalid project declaration.");
|
|
1205
|
+
if (definitions.has(first)) return define({
|
|
1206
|
+
kind: "map",
|
|
1207
|
+
source: first,
|
|
1208
|
+
mapper: second,
|
|
1209
|
+
isEqual: third?.isEqual
|
|
1210
|
+
});
|
|
1211
|
+
return define({
|
|
1212
|
+
kind: "value",
|
|
1213
|
+
sources: freezeSources(first),
|
|
1214
|
+
compute: second,
|
|
1215
|
+
isEqual: third?.isEqual
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
throw new TypeError("Invalid project declaration.");
|
|
1219
|
+
}
|
|
1220
|
+
//#endregion
|
|
1221
|
+
//#region core/src/projection/contract.ts
|
|
1222
|
+
var ProjectionError = class extends Error {
|
|
1223
|
+
constructor(phase, identity, revisions, cause) {
|
|
1224
|
+
super(`Projection ${phase} failed: ${identity}`, { cause });
|
|
1225
|
+
this.phase = phase;
|
|
1226
|
+
this.identity = identity;
|
|
1227
|
+
this.revisions = revisions;
|
|
1228
|
+
this.name = "ProjectionError";
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
var ProjectionDisposedError = class extends Error {
|
|
1232
|
+
constructor() {
|
|
1233
|
+
super("Projection has been disposed.");
|
|
1234
|
+
this.name = "ProjectionDisposedError";
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
//#endregion
|
|
1238
|
+
//#region core/src/projection/scheduler.ts
|
|
1239
|
+
const projectionHandles = /* @__PURE__ */ new WeakSet();
|
|
1240
|
+
const createScheduler = (onError) => {
|
|
1241
|
+
let disposed = false;
|
|
1242
|
+
let depth = 0;
|
|
1243
|
+
let phase = "idle";
|
|
1244
|
+
let sequence = 0;
|
|
1245
|
+
const records = /* @__PURE__ */ new Map();
|
|
1246
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
1247
|
+
const pending = /* @__PURE__ */ new Set();
|
|
1248
|
+
const heap = [];
|
|
1249
|
+
const queued = /* @__PURE__ */ new Set();
|
|
1250
|
+
const completed = [];
|
|
1251
|
+
const emissions = /* @__PURE__ */ new Set();
|
|
1252
|
+
const errors = [];
|
|
1253
|
+
let reportingFailures = [];
|
|
1254
|
+
const guards = /* @__PURE__ */ new Set();
|
|
1255
|
+
const cleanups = /* @__PURE__ */ new Set();
|
|
1256
|
+
const assertActive = () => {
|
|
1257
|
+
if (disposed) throw new ProjectionDisposedError();
|
|
1258
|
+
};
|
|
1259
|
+
const assertIdle = () => {
|
|
1260
|
+
assertActive();
|
|
1261
|
+
if (phase !== "idle") throw new Error("Projection cannot be re-entered during processing or notification.");
|
|
1262
|
+
};
|
|
1263
|
+
const lock = (value) => guards.forEach((guard) => guard(value));
|
|
1264
|
+
const enqueue = (node) => {
|
|
1265
|
+
if (node.disposed || queued.has(node)) return;
|
|
1266
|
+
queued.add(node);
|
|
1267
|
+
profile.projection("scheduledNodes");
|
|
1268
|
+
let i = heap.length;
|
|
1269
|
+
heap.push(node);
|
|
1270
|
+
while (i > 0) {
|
|
1271
|
+
const p = i - 1 >> 1;
|
|
1272
|
+
if (heap[p].order < node.order) break;
|
|
1273
|
+
heap[i] = heap[p];
|
|
1274
|
+
i = p;
|
|
1275
|
+
}
|
|
1276
|
+
heap[i] = node;
|
|
1277
|
+
};
|
|
1278
|
+
const pop = () => {
|
|
1279
|
+
const first = heap[0];
|
|
1280
|
+
const last = heap.pop();
|
|
1281
|
+
if (heap.length) {
|
|
1282
|
+
let i = 0;
|
|
1283
|
+
while (i * 2 + 1 < heap.length) {
|
|
1284
|
+
let c = i * 2 + 1;
|
|
1285
|
+
if (c + 1 < heap.length && heap[c + 1].order < heap[c].order) c++;
|
|
1286
|
+
if (last.order < heap[c].order) break;
|
|
1287
|
+
heap[i] = heap[c];
|
|
1288
|
+
i = c;
|
|
1289
|
+
}
|
|
1290
|
+
heap[i] = last;
|
|
1291
|
+
}
|
|
1292
|
+
return first;
|
|
1293
|
+
};
|
|
1294
|
+
const errorFor = (node, cause, kind = "processor") => new ProjectionError(kind, node.name, node.sources.map((source) => source.revision()), cause);
|
|
1295
|
+
const capture = (source) => {
|
|
1296
|
+
if (disposed) return;
|
|
1297
|
+
if (phase !== "idle") {
|
|
1298
|
+
source.fault = new ProjectionError("source", "reentrant source", [source.revision()], /* @__PURE__ */ new Error("Source changed while projection was running."));
|
|
1299
|
+
errors.push(source.fault);
|
|
1300
|
+
throw source.fault;
|
|
1301
|
+
}
|
|
1302
|
+
pending.add(source);
|
|
1303
|
+
profile.projection("sourceEvents");
|
|
1304
|
+
if (source.fault) errors.push(source.fault);
|
|
1305
|
+
};
|
|
1306
|
+
const settle = () => {
|
|
1307
|
+
if (disposed || depth || phase !== "idle") return;
|
|
1308
|
+
phase = "compute";
|
|
1309
|
+
lock(true);
|
|
1310
|
+
try {
|
|
1311
|
+
pending.forEach((source) => source.consumers.forEach(enqueue));
|
|
1312
|
+
while (heap.length) {
|
|
1313
|
+
const node = pop();
|
|
1314
|
+
if (node.disposed) continue;
|
|
1315
|
+
const wasFaulted = node.fault !== void 0;
|
|
1316
|
+
profile.projection("processedNodes");
|
|
1317
|
+
let changed = false;
|
|
1318
|
+
const blocked = node.sources.find((source) => source.fault || source.disposed);
|
|
1319
|
+
if (blocked) node.fault = errorFor(node, blocked.fault ?? new ProjectionDisposedError(), "blocked");
|
|
1320
|
+
else {
|
|
1321
|
+
const build = node.forced || wasFaulted || node.sources.some((source) => source.reset());
|
|
1322
|
+
try {
|
|
1323
|
+
changed = node.evaluate(build);
|
|
1324
|
+
const failure = node.sources.find((source) => source.fault)?.fault;
|
|
1325
|
+
if (failure) throw failure;
|
|
1326
|
+
node.fault = void 0;
|
|
1327
|
+
} catch (cause) {
|
|
1328
|
+
const error = errorFor(node, cause);
|
|
1329
|
+
errors.push(error);
|
|
1330
|
+
node.fault = error;
|
|
1331
|
+
if (!build) try {
|
|
1332
|
+
changed = node.evaluate(true);
|
|
1333
|
+
const failure = node.sources.find((source) => source.fault)?.fault;
|
|
1334
|
+
if (failure) throw failure;
|
|
1335
|
+
node.fault = void 0;
|
|
1336
|
+
} catch (cause) {
|
|
1337
|
+
node.fault = errorFor(node, cause);
|
|
1338
|
+
errors.push(node.fault);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
node.forced = false;
|
|
1343
|
+
node.statusChanged = wasFaulted !== (node.fault !== void 0);
|
|
1344
|
+
completed.push(node);
|
|
1345
|
+
if (changed || wasFaulted || node.fault) {
|
|
1346
|
+
profile.projection("publishedNodes");
|
|
1347
|
+
emissions.add(node);
|
|
1348
|
+
node.consumers.forEach(enqueue);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
completed.forEach((node) => node.publish());
|
|
1352
|
+
} finally {
|
|
1353
|
+
lock(false);
|
|
1354
|
+
phase = "idle";
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
const flush = () => {
|
|
1358
|
+
if (disposed || depth) return [];
|
|
1359
|
+
if (pending.size || completed.length) profile.projection("flushes");
|
|
1360
|
+
phase = "notify";
|
|
1361
|
+
lock(true);
|
|
1362
|
+
const reportFailures = [];
|
|
1363
|
+
try {
|
|
1364
|
+
emissions.forEach((node) => node.emit((listener) => {
|
|
1365
|
+
try {
|
|
1366
|
+
listener();
|
|
1367
|
+
} catch (cause) {
|
|
1368
|
+
errors.push(errorFor(node, cause, "listener"));
|
|
1369
|
+
}
|
|
1370
|
+
}));
|
|
1371
|
+
for (const error of errors.slice()) try {
|
|
1372
|
+
onError(error);
|
|
1373
|
+
} catch (cause) {
|
|
1374
|
+
reportFailures.push(cause);
|
|
1375
|
+
}
|
|
1376
|
+
return Object.freeze([...errors.map((error) => Object.freeze({
|
|
1377
|
+
phase: error.phase === "listener" ? "listener" : "processor",
|
|
1378
|
+
error
|
|
1379
|
+
})), ...reportFailures.map((error) => Object.freeze({
|
|
1380
|
+
phase: "listener",
|
|
1381
|
+
error
|
|
1382
|
+
}))]);
|
|
1383
|
+
} finally {
|
|
1384
|
+
pending.forEach((source) => source.clear());
|
|
1385
|
+
completed.forEach((node) => node.clear());
|
|
1386
|
+
pending.clear();
|
|
1387
|
+
queued.clear();
|
|
1388
|
+
completed.length = 0;
|
|
1389
|
+
emissions.clear();
|
|
1390
|
+
errors.length = 0;
|
|
1391
|
+
reportingFailures = reportFailures;
|
|
1392
|
+
lock(false);
|
|
1393
|
+
phase = "idle";
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
const run = () => {
|
|
1397
|
+
settle();
|
|
1398
|
+
const result = flush();
|
|
1399
|
+
const failures = reportingFailures;
|
|
1400
|
+
reportingFailures = [];
|
|
1401
|
+
if (failures.length) throw new AggregateError(failures, "Projection error reporter failed.");
|
|
1402
|
+
return result;
|
|
1403
|
+
};
|
|
1404
|
+
return {
|
|
1405
|
+
assertActive,
|
|
1406
|
+
assertIdle,
|
|
1407
|
+
capture,
|
|
1408
|
+
settle,
|
|
1409
|
+
flush,
|
|
1410
|
+
run,
|
|
1411
|
+
records,
|
|
1412
|
+
guards,
|
|
1413
|
+
cleanups,
|
|
1414
|
+
get active() {
|
|
1415
|
+
return !disposed;
|
|
1416
|
+
},
|
|
1417
|
+
register(handle, record) {
|
|
1418
|
+
assertIdle();
|
|
1419
|
+
records.set(handle, record);
|
|
1420
|
+
projectionHandles.add(handle);
|
|
1421
|
+
},
|
|
1422
|
+
unregisterSource(handle) {
|
|
1423
|
+
const record = records.get(handle);
|
|
1424
|
+
if (record) {
|
|
1425
|
+
record.disposed = true;
|
|
1426
|
+
pending.delete(record);
|
|
1427
|
+
record.clear();
|
|
1428
|
+
records.delete(handle);
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
source(handle) {
|
|
1432
|
+
assertActive();
|
|
1433
|
+
const source = records.get(handle);
|
|
1434
|
+
if (!source || source.disposed) throw new Error("Unknown, disposed, or foreign projection source.");
|
|
1435
|
+
return source;
|
|
1436
|
+
},
|
|
1437
|
+
addNode(node) {
|
|
1438
|
+
nodes.add(node);
|
|
1439
|
+
node.sources.forEach((source) => source.consumers.add(node));
|
|
1440
|
+
},
|
|
1441
|
+
order: () => sequence++,
|
|
1442
|
+
initialize(run) {
|
|
1443
|
+
assertIdle();
|
|
1444
|
+
phase = "compute";
|
|
1445
|
+
lock(true);
|
|
1446
|
+
try {
|
|
1447
|
+
return run();
|
|
1448
|
+
} finally {
|
|
1449
|
+
lock(false);
|
|
1450
|
+
phase = "idle";
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
rebuild(node) {
|
|
1454
|
+
assertIdle();
|
|
1455
|
+
if (node.disposed) throw new ProjectionDisposedError();
|
|
1456
|
+
node.forced = true;
|
|
1457
|
+
enqueue(node);
|
|
1458
|
+
run();
|
|
1459
|
+
},
|
|
1460
|
+
disposeNode(node) {
|
|
1461
|
+
if (node.disposed) return;
|
|
1462
|
+
assertIdle();
|
|
1463
|
+
if (node.consumers.size) throw new Error("Cannot dispose a projection node with downstream consumers.");
|
|
1464
|
+
node.disposed = true;
|
|
1465
|
+
node.sources.forEach((source) => source.consumers.delete(node));
|
|
1466
|
+
nodes.delete(node);
|
|
1467
|
+
node.release();
|
|
1468
|
+
for (const [handle, record] of records) if (record === node) records.delete(handle);
|
|
1469
|
+
},
|
|
1470
|
+
batch(callback) {
|
|
1471
|
+
assertIdle();
|
|
1472
|
+
depth++;
|
|
1473
|
+
let failed = false;
|
|
1474
|
+
try {
|
|
1475
|
+
const value = callback();
|
|
1476
|
+
assertSynchronous(value);
|
|
1477
|
+
return value;
|
|
1478
|
+
} catch (error) {
|
|
1479
|
+
failed = true;
|
|
1480
|
+
throw error;
|
|
1481
|
+
} finally {
|
|
1482
|
+
depth--;
|
|
1483
|
+
if (!depth) if (failed) try {
|
|
1484
|
+
run();
|
|
1485
|
+
} catch {}
|
|
1486
|
+
else run();
|
|
1487
|
+
}
|
|
1488
|
+
},
|
|
1489
|
+
dispose() {
|
|
1490
|
+
if (disposed) return;
|
|
1491
|
+
assertIdle();
|
|
1492
|
+
disposed = true;
|
|
1493
|
+
const failures = [];
|
|
1494
|
+
cleanups.forEach((cleanup) => {
|
|
1495
|
+
try {
|
|
1496
|
+
cleanup();
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
failures.push(error);
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
cleanups.clear();
|
|
1502
|
+
nodes.forEach((node) => {
|
|
1503
|
+
node.disposed = true;
|
|
1504
|
+
node.release();
|
|
1505
|
+
node.consumers.clear();
|
|
1506
|
+
});
|
|
1507
|
+
records.forEach((source) => {
|
|
1508
|
+
source.disposed = true;
|
|
1509
|
+
source.consumers.clear();
|
|
1510
|
+
source.clear();
|
|
1511
|
+
});
|
|
1512
|
+
nodes.clear();
|
|
1513
|
+
records.clear();
|
|
1514
|
+
pending.clear();
|
|
1515
|
+
heap.length = 0;
|
|
1516
|
+
completed.length = 0;
|
|
1517
|
+
queued.clear();
|
|
1518
|
+
emissions.clear();
|
|
1519
|
+
guards.clear();
|
|
1520
|
+
errors.length = 0;
|
|
1521
|
+
reportingFailures = [];
|
|
1522
|
+
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
1523
|
+
},
|
|
1524
|
+
debug: () => ({
|
|
1525
|
+
nodes: nodes.size,
|
|
1526
|
+
sources: records.size - nodes.size,
|
|
1527
|
+
subscriptions: cleanups.size,
|
|
1528
|
+
pending: pending.size + heap.length
|
|
1529
|
+
})
|
|
1530
|
+
};
|
|
1531
|
+
};
|
|
1532
|
+
const assertSynchronous = (value) => {
|
|
1533
|
+
if (value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function") throw new TypeError("Projection callbacks must be synchronous.");
|
|
1534
|
+
};
|
|
1535
|
+
const assertScope = (active) => {
|
|
1536
|
+
if (!active()) throw new Error("Projection reader or writer is no longer active.");
|
|
1537
|
+
};
|
|
1538
|
+
//#endregion
|
|
1539
|
+
//#region core/src/projection/node.ts
|
|
1540
|
+
const createNode = (scheduler, spec, behavior) => {
|
|
1541
|
+
scheduler.assertIdle();
|
|
1542
|
+
const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
|
|
1543
|
+
const order = scheduler.order();
|
|
1544
|
+
const node = {
|
|
1545
|
+
...behavior,
|
|
1546
|
+
publish: () => {
|
|
1547
|
+
if (node.fault) behavior.clear();
|
|
1548
|
+
else behavior.publish();
|
|
1549
|
+
},
|
|
1550
|
+
order,
|
|
1551
|
+
name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
|
|
1552
|
+
sources: entries.map(([, source]) => source),
|
|
1553
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1554
|
+
disposed: false,
|
|
1555
|
+
fault: void 0,
|
|
1556
|
+
forced: false,
|
|
1557
|
+
statusChanged: false,
|
|
1558
|
+
evaluate: (build) => {
|
|
1559
|
+
let active = true;
|
|
1560
|
+
try {
|
|
1561
|
+
const scopeActive = () => active;
|
|
1562
|
+
const inputs = Object.fromEntries(entries.map(([key, source]) => [key, source.context(scopeActive)]));
|
|
1563
|
+
return behavior.evaluate(inputs, build, scopeActive);
|
|
1564
|
+
} finally {
|
|
1565
|
+
active = false;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
scheduler.initialize(() => {
|
|
1570
|
+
const invalid = node.sources.find((source) => source.fault || source.disposed);
|
|
1571
|
+
if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
|
|
1572
|
+
node.evaluate(true);
|
|
1573
|
+
node.publish();
|
|
1574
|
+
node.clear();
|
|
1575
|
+
});
|
|
1576
|
+
const check = () => {
|
|
1577
|
+
if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
|
|
1578
|
+
if (node.fault) throw node.fault;
|
|
1579
|
+
};
|
|
1580
|
+
return {
|
|
1581
|
+
node,
|
|
1582
|
+
check,
|
|
1583
|
+
install(handle) {
|
|
1584
|
+
scheduler.register(handle, node);
|
|
1585
|
+
scheduler.addNode(node);
|
|
1586
|
+
},
|
|
1587
|
+
rebuild: () => scheduler.rebuild(node),
|
|
1588
|
+
dispose: () => scheduler.disposeNode(node)
|
|
1589
|
+
};
|
|
1590
|
+
};
|
|
1591
|
+
//#endregion
|
|
1592
|
+
//#region core/src/projection/collection.ts
|
|
1593
|
+
const collectionHandles = /* @__PURE__ */ new WeakSet();
|
|
1594
|
+
const readonlySet = (values) => {
|
|
1595
|
+
const set = new Set(values);
|
|
1596
|
+
const view = Object.freeze({
|
|
1597
|
+
get size() {
|
|
1598
|
+
return set.size;
|
|
1599
|
+
},
|
|
1600
|
+
has: (value) => set.has(value),
|
|
1601
|
+
entries: () => set.entries(),
|
|
1602
|
+
keys: () => set.keys(),
|
|
1603
|
+
values: () => set.values(),
|
|
1604
|
+
[Symbol.iterator]: () => set.values(),
|
|
1605
|
+
forEach: (callback, thisArg) => set.forEach((value) => callback.call(thisArg, value, value, view))
|
|
1606
|
+
});
|
|
1607
|
+
return view;
|
|
1608
|
+
};
|
|
1609
|
+
const createCollection = (scheduler, spec) => {
|
|
1610
|
+
const values = /* @__PURE__ */ new Map();
|
|
1611
|
+
let ids = Object.freeze([]);
|
|
1612
|
+
let staged = /* @__PURE__ */ new Map();
|
|
1613
|
+
let nextIds = ids;
|
|
1614
|
+
let change;
|
|
1615
|
+
let instance;
|
|
1616
|
+
let initialized = false;
|
|
1617
|
+
let reset = false;
|
|
1618
|
+
let revision = 0;
|
|
1619
|
+
let idsRevision = 0;
|
|
1620
|
+
let all;
|
|
1621
|
+
const items = /* @__PURE__ */ new Map();
|
|
1622
|
+
const subscribedItems = /* @__PURE__ */ new Set();
|
|
1623
|
+
const idsListeners = /* @__PURE__ */ new Set();
|
|
1624
|
+
const allListeners = /* @__PURE__ */ new Set();
|
|
1625
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1626
|
+
const changedKeys = /* @__PURE__ */ new Set();
|
|
1627
|
+
let orderChanged = false;
|
|
1628
|
+
const current = Object.freeze({
|
|
1629
|
+
get: (key) => {
|
|
1630
|
+
owner.check();
|
|
1631
|
+
return values.get(key);
|
|
1632
|
+
},
|
|
1633
|
+
has: (key) => {
|
|
1634
|
+
owner.check();
|
|
1635
|
+
return values.has(key);
|
|
1636
|
+
},
|
|
1637
|
+
ids: () => {
|
|
1638
|
+
owner.check();
|
|
1639
|
+
return ids;
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
|
|
1643
|
+
const getNext = (key) => {
|
|
1644
|
+
const entry = staged.get(key);
|
|
1645
|
+
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
1646
|
+
};
|
|
1647
|
+
const owner = createNode(scheduler, spec, {
|
|
1648
|
+
evaluate: (sources, build, active) => {
|
|
1649
|
+
staged = /* @__PURE__ */ new Map();
|
|
1650
|
+
nextIds = ids;
|
|
1651
|
+
change = void 0;
|
|
1652
|
+
changedKeys.clear();
|
|
1653
|
+
let explicitOrder;
|
|
1654
|
+
let cleared = false;
|
|
1655
|
+
const nextHas = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
|
|
1656
|
+
const nextGet = (key) => {
|
|
1657
|
+
const entry = staged.get(key);
|
|
1658
|
+
return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
|
|
1659
|
+
};
|
|
1660
|
+
const deriveIds = () => {
|
|
1661
|
+
const result = cleared ? [] : ids.filter(nextHas);
|
|
1662
|
+
for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
|
|
1663
|
+
return result;
|
|
1664
|
+
};
|
|
1665
|
+
const read = (next) => ({
|
|
1666
|
+
get: (key) => {
|
|
1667
|
+
assertScope(active);
|
|
1668
|
+
return next ? nextGet(key) : values.get(key);
|
|
1669
|
+
},
|
|
1670
|
+
has: (key) => {
|
|
1671
|
+
assertScope(active);
|
|
1672
|
+
return next ? nextHas(key) : values.has(key);
|
|
1673
|
+
},
|
|
1674
|
+
ids: () => {
|
|
1675
|
+
assertScope(active);
|
|
1676
|
+
return next ? Object.freeze(explicitOrder ? explicitOrder.slice() : deriveIds()) : ids;
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
const writer = {
|
|
1680
|
+
set: (key, value) => {
|
|
1681
|
+
assertScope(active);
|
|
1682
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
1683
|
+
staged.set(key, {
|
|
1684
|
+
present: true,
|
|
1685
|
+
value
|
|
1686
|
+
});
|
|
1687
|
+
},
|
|
1688
|
+
remove: (key) => {
|
|
1689
|
+
assertScope(active);
|
|
1690
|
+
staged.set(key, { present: false });
|
|
1691
|
+
},
|
|
1692
|
+
order: (order) => {
|
|
1693
|
+
assertScope(active);
|
|
1694
|
+
explicitOrder = order.slice();
|
|
1695
|
+
},
|
|
1696
|
+
replace: (entries) => {
|
|
1697
|
+
assertScope(active);
|
|
1698
|
+
staged.clear();
|
|
1699
|
+
cleared = true;
|
|
1700
|
+
const order = [];
|
|
1701
|
+
for (const [key, value] of entries) {
|
|
1702
|
+
if (staged.has(key)) throw new TypeError("Duplicate projection key.");
|
|
1703
|
+
writer.set(key, value);
|
|
1704
|
+
order.push(key);
|
|
1705
|
+
}
|
|
1706
|
+
explicitOrder = order;
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
const input = {
|
|
1710
|
+
sources,
|
|
1711
|
+
previous: read(false),
|
|
1712
|
+
next: read(true),
|
|
1713
|
+
writer
|
|
1714
|
+
};
|
|
1715
|
+
if (build || !instance) {
|
|
1716
|
+
profile.materialized.rebuilt();
|
|
1717
|
+
instance = void 0;
|
|
1718
|
+
cleared = true;
|
|
1719
|
+
const built = spec.build(input);
|
|
1720
|
+
assertSynchronous(built);
|
|
1721
|
+
instance = built;
|
|
1722
|
+
} else {
|
|
1723
|
+
profile.materialized.updated();
|
|
1724
|
+
const result = instance.update(input);
|
|
1725
|
+
assertSynchronous(result);
|
|
1726
|
+
if (result?.kind === "rebuild") {
|
|
1727
|
+
staged.clear();
|
|
1728
|
+
cleared = true;
|
|
1729
|
+
explicitOrder = void 0;
|
|
1730
|
+
instance = void 0;
|
|
1731
|
+
const built = spec.build(input);
|
|
1732
|
+
assertSynchronous(built);
|
|
1733
|
+
instance = built;
|
|
1734
|
+
build = true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (cleared) {
|
|
1738
|
+
for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
|
|
1739
|
+
}
|
|
1740
|
+
const added = /* @__PURE__ */ new Set();
|
|
1741
|
+
const removed = /* @__PURE__ */ new Set();
|
|
1742
|
+
const updated = /* @__PURE__ */ new Set();
|
|
1743
|
+
profile.projection("touchedKeys", staged.size);
|
|
1744
|
+
for (const [key, entry] of staged) {
|
|
1745
|
+
const existed = values.has(key);
|
|
1746
|
+
if (entry.present) if (!existed) added.add(key);
|
|
1747
|
+
else if (!(spec.isEqual ?? Object.is)(values.get(key), entry.value)) updated.add(key);
|
|
1748
|
+
else staged.delete(key);
|
|
1749
|
+
else if (existed) removed.add(key);
|
|
1750
|
+
else staged.delete(key);
|
|
1751
|
+
}
|
|
1752
|
+
if (explicitOrder || added.size || removed.size) {
|
|
1753
|
+
const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
|
|
1754
|
+
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.");
|
|
1755
|
+
nextIds = ids.length === order.length && ids.every((key, i) => key === order[i]) ? ids : Object.freeze(order.slice());
|
|
1756
|
+
}
|
|
1757
|
+
orderChanged = nextIds !== ids;
|
|
1758
|
+
if (added.size || removed.size || updated.size || orderChanged) {
|
|
1759
|
+
let commonOrderChanged = false;
|
|
1760
|
+
if (orderChanged) {
|
|
1761
|
+
const before = ids.filter((key) => !removed.has(key));
|
|
1762
|
+
const after = nextIds.filter((key) => !added.has(key));
|
|
1763
|
+
commonOrderChanged = before.some((key, i) => after[i] !== key);
|
|
1764
|
+
}
|
|
1765
|
+
change = Object.freeze({
|
|
1766
|
+
kind: "incremental",
|
|
1767
|
+
added: readonlySet(added),
|
|
1768
|
+
removed: readonlySet(removed),
|
|
1769
|
+
updated: readonlySet(updated),
|
|
1770
|
+
orderChanged: commonOrderChanged
|
|
1771
|
+
});
|
|
1772
|
+
[
|
|
1773
|
+
...added,
|
|
1774
|
+
...removed,
|
|
1775
|
+
...updated
|
|
1776
|
+
].forEach((key) => changedKeys.add(key));
|
|
1777
|
+
profile.projection("changedKeys", changedKeys.size);
|
|
1778
|
+
}
|
|
1779
|
+
reset = build;
|
|
1780
|
+
return change !== void 0;
|
|
1781
|
+
},
|
|
1782
|
+
context: (active) => {
|
|
1783
|
+
const input = {
|
|
1784
|
+
get: (key) => {
|
|
1785
|
+
assertScope(active);
|
|
1786
|
+
return getNext(key);
|
|
1787
|
+
},
|
|
1788
|
+
has: (key) => {
|
|
1789
|
+
assertScope(active);
|
|
1790
|
+
return hasNext(key);
|
|
1791
|
+
},
|
|
1792
|
+
ids: () => {
|
|
1793
|
+
assertScope(active);
|
|
1794
|
+
return nextIds;
|
|
1795
|
+
},
|
|
1796
|
+
change,
|
|
1797
|
+
revision: revision + (change ? 1 : 0),
|
|
1798
|
+
reset
|
|
1799
|
+
};
|
|
1800
|
+
return Object.freeze(input);
|
|
1801
|
+
},
|
|
1802
|
+
revision: () => revision,
|
|
1803
|
+
reset: () => reset,
|
|
1804
|
+
publish: () => {
|
|
1805
|
+
for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
|
|
1806
|
+
else values.delete(key);
|
|
1807
|
+
if (change) {
|
|
1808
|
+
if (initialized) revision++;
|
|
1809
|
+
if (orderChanged && initialized) idsRevision++;
|
|
1810
|
+
all = void 0;
|
|
1811
|
+
changedKeys.forEach((key) => {
|
|
1812
|
+
const item = items.get(key);
|
|
1813
|
+
if (item && initialized) item.revision++;
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
ids = nextIds;
|
|
1817
|
+
initialized = true;
|
|
1818
|
+
},
|
|
1819
|
+
emit: (call) => {
|
|
1820
|
+
profile.materialized.notification();
|
|
1821
|
+
const fault = owner.node.fault !== void 0;
|
|
1822
|
+
const resetEvent = Object.freeze({ kind: "reset" });
|
|
1823
|
+
Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
|
|
1824
|
+
if (orderChanged || !change || fault || owner.node.statusChanged) Array.from(idsListeners).forEach((listener) => call(listener));
|
|
1825
|
+
Array.from(allListeners).forEach((listener) => call(listener));
|
|
1826
|
+
if (!change || fault || owner.node.statusChanged) for (const item of subscribedItems) Array.from(item.listeners ?? []).forEach((listener) => call(listener));
|
|
1827
|
+
else for (const key of changedKeys) Array.from(items.get(key)?.listeners ?? []).forEach((listener) => call(listener));
|
|
1828
|
+
},
|
|
1829
|
+
clear: () => {
|
|
1830
|
+
staged.clear();
|
|
1831
|
+
nextIds = ids;
|
|
1832
|
+
change = void 0;
|
|
1833
|
+
reset = false;
|
|
1834
|
+
changedKeys.clear();
|
|
1835
|
+
orderChanged = false;
|
|
1836
|
+
},
|
|
1837
|
+
release: () => {
|
|
1838
|
+
values.clear();
|
|
1839
|
+
staged.clear();
|
|
1840
|
+
items.clear();
|
|
1841
|
+
subscribedItems.clear();
|
|
1842
|
+
idsListeners.clear();
|
|
1843
|
+
allListeners.clear();
|
|
1844
|
+
listeners.clear();
|
|
1845
|
+
ids = nextIds = Object.freeze([]);
|
|
1846
|
+
all = void 0;
|
|
1847
|
+
instance = void 0;
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
const handle = {
|
|
1851
|
+
current: () => {
|
|
1852
|
+
owner.check();
|
|
1853
|
+
return current;
|
|
1854
|
+
},
|
|
1855
|
+
ids: {
|
|
1856
|
+
current: () => {
|
|
1857
|
+
owner.check();
|
|
1858
|
+
return ids;
|
|
1859
|
+
},
|
|
1860
|
+
revision: () => {
|
|
1861
|
+
owner.check();
|
|
1862
|
+
return idsRevision;
|
|
1863
|
+
},
|
|
1864
|
+
subscribe: (listener) => {
|
|
1865
|
+
owner.check();
|
|
1866
|
+
idsListeners.add(listener);
|
|
1867
|
+
return () => {
|
|
1868
|
+
idsListeners.delete(listener);
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
},
|
|
1872
|
+
all: {
|
|
1873
|
+
current: () => {
|
|
1874
|
+
owner.check();
|
|
1875
|
+
if (!all) {
|
|
1876
|
+
profile.collectionView.arrayCopied();
|
|
1877
|
+
all = Object.freeze(ids.map((key) => values.get(key)));
|
|
1878
|
+
}
|
|
1879
|
+
return all;
|
|
1880
|
+
},
|
|
1881
|
+
revision: () => {
|
|
1882
|
+
owner.check();
|
|
1883
|
+
return revision;
|
|
1884
|
+
},
|
|
1885
|
+
subscribe: (listener) => {
|
|
1886
|
+
owner.check();
|
|
1887
|
+
allListeners.add(listener);
|
|
1888
|
+
return () => {
|
|
1889
|
+
allListeners.delete(listener);
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
},
|
|
1893
|
+
item: (key) => {
|
|
1894
|
+
owner.check();
|
|
1895
|
+
const old = items.get(key);
|
|
1896
|
+
if (old) return old.readable;
|
|
1897
|
+
const readable = Object.freeze({
|
|
1898
|
+
current: () => {
|
|
1899
|
+
owner.check();
|
|
1900
|
+
return values.get(key);
|
|
1901
|
+
},
|
|
1902
|
+
revision: () => {
|
|
1903
|
+
owner.check();
|
|
1904
|
+
return item.revision;
|
|
1905
|
+
},
|
|
1906
|
+
subscribe: (listener) => {
|
|
1907
|
+
owner.check();
|
|
1908
|
+
const set = item.listeners ??= /* @__PURE__ */ new Set();
|
|
1909
|
+
set.add(listener);
|
|
1910
|
+
subscribedItems.add(item);
|
|
1911
|
+
return () => {
|
|
1912
|
+
set.delete(listener);
|
|
1913
|
+
if (!set.size) subscribedItems.delete(item);
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
const item = {
|
|
1918
|
+
readable,
|
|
1919
|
+
revision: 0
|
|
1920
|
+
};
|
|
1921
|
+
projectionHandles.add(readable);
|
|
1922
|
+
items.set(key, item);
|
|
1923
|
+
return readable;
|
|
1924
|
+
},
|
|
1925
|
+
revision: () => {
|
|
1926
|
+
owner.check();
|
|
1927
|
+
return revision;
|
|
1928
|
+
},
|
|
1929
|
+
subscribe: (listener) => {
|
|
1930
|
+
owner.check();
|
|
1931
|
+
listeners.add(listener);
|
|
1932
|
+
return () => {
|
|
1933
|
+
listeners.delete(listener);
|
|
1934
|
+
};
|
|
1935
|
+
},
|
|
1936
|
+
rebuild: owner.rebuild,
|
|
1937
|
+
dispose: owner.dispose
|
|
1938
|
+
};
|
|
1939
|
+
projectionHandles.add(handle.ids);
|
|
1940
|
+
projectionHandles.add(handle.all);
|
|
1941
|
+
collectionHandles.add(handle);
|
|
1942
|
+
owner.install(handle);
|
|
1943
|
+
return Object.freeze(handle);
|
|
1944
|
+
};
|
|
1945
|
+
//#endregion
|
|
1946
|
+
//#region core/src/projection/source.ts
|
|
1947
|
+
const createSources = (scheduler) => {
|
|
1948
|
+
const documents = /* @__PURE__ */ new Map();
|
|
1949
|
+
const readables = /* @__PURE__ */ new Map();
|
|
1950
|
+
const collections = /* @__PURE__ */ new WeakSet();
|
|
1951
|
+
const document = (runtime) => {
|
|
1952
|
+
scheduler.assertIdle();
|
|
1953
|
+
const state = accessOf(runtime);
|
|
1954
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1955
|
+
const existing = documents.get(state);
|
|
1956
|
+
if (existing) return existing;
|
|
1957
|
+
const bindings = [];
|
|
1958
|
+
const make = (input) => {
|
|
1959
|
+
const collection = "collection" in input ? input.collection : void 0;
|
|
1960
|
+
const targets = "targets" in input ? input.targets : [input.collection];
|
|
1961
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1962
|
+
targets.forEach((value) => {
|
|
1963
|
+
if (!belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
|
|
1964
|
+
});
|
|
1965
|
+
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])));
|
|
1966
|
+
if (old) return old.handle;
|
|
1967
|
+
let commits = [];
|
|
1968
|
+
let reset = false;
|
|
1969
|
+
let orderDirty = false;
|
|
1970
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1971
|
+
let candidates;
|
|
1972
|
+
const record = {
|
|
1973
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1974
|
+
disposed: false,
|
|
1975
|
+
fault: void 0,
|
|
1976
|
+
revision: runtime.revision,
|
|
1977
|
+
reset: () => reset,
|
|
1978
|
+
clear: () => {
|
|
1979
|
+
commits = [];
|
|
1980
|
+
reset = orderDirty = false;
|
|
1981
|
+
keys.clear();
|
|
1982
|
+
candidates = void 0;
|
|
1983
|
+
},
|
|
1984
|
+
context: (active) => {
|
|
1985
|
+
assertScope(active);
|
|
1986
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1987
|
+
const context = {
|
|
1988
|
+
state,
|
|
1989
|
+
active
|
|
1990
|
+
};
|
|
1991
|
+
const read = collection ? collectionAccess(context, collection.address) : createAccess(context);
|
|
1992
|
+
return Object.freeze({
|
|
1993
|
+
read,
|
|
1994
|
+
revision: runtime.revision(),
|
|
1995
|
+
commits: Object.freeze(commits.slice()),
|
|
1996
|
+
reset: record.reset(),
|
|
1997
|
+
...collection ? { candidates: candidates ??= Object.freeze({
|
|
1998
|
+
keys: Object.freeze([...keys]),
|
|
1999
|
+
orderDirty
|
|
2000
|
+
}) } : {}
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
const handle = collection ? {} : {
|
|
2005
|
+
collection: (pick) => {
|
|
2006
|
+
scheduler.assertIdle();
|
|
2007
|
+
return make({ collection: compilePath(state.schema, "collection", pick) });
|
|
2008
|
+
},
|
|
2009
|
+
targets: (...selected) => {
|
|
2010
|
+
scheduler.assertIdle();
|
|
2011
|
+
if (!selected.length) throw new TypeError("Expected at least one target.");
|
|
2012
|
+
return make({ targets: selected.map((pick) => compilePath(state.schema, "value", pick)) });
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
2015
|
+
Object.freeze(handle);
|
|
2016
|
+
if (collection) collections.add(handle);
|
|
2017
|
+
scheduler.register(handle, record);
|
|
2018
|
+
const receive = (commit) => {
|
|
2019
|
+
commits.push(commit);
|
|
2020
|
+
reset ||= commit.impact.kind === "reset";
|
|
2021
|
+
if (collection) {
|
|
2022
|
+
const change = collectionImpact(commit.impact, collection);
|
|
2023
|
+
if (change.kind === "reset") reset = true;
|
|
2024
|
+
else {
|
|
2025
|
+
change.added.forEach((key) => keys.add(key));
|
|
2026
|
+
change.removed.forEach((key) => keys.add(key));
|
|
2027
|
+
change.updated.forEach((key) => keys.add(key));
|
|
2028
|
+
orderDirty ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
|
|
2029
|
+
}
|
|
2030
|
+
candidates = void 0;
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
bindings.push({
|
|
2034
|
+
handle,
|
|
2035
|
+
targets,
|
|
2036
|
+
record,
|
|
2037
|
+
receive
|
|
2038
|
+
});
|
|
2039
|
+
return handle;
|
|
2040
|
+
};
|
|
2041
|
+
const handle = make({ targets: [] });
|
|
2042
|
+
const guard = (locked) => {
|
|
2043
|
+
state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
|
|
2044
|
+
};
|
|
2045
|
+
scheduler.guards.add(guard);
|
|
2046
|
+
const unsubscribe = attachProjection(runtime, {
|
|
2047
|
+
capture: (commit) => {
|
|
2048
|
+
for (const binding of bindings) if (!binding.targets.length || binding.targets.some((value) => affectsTarget(commit.impact, value))) {
|
|
2049
|
+
binding.receive(commit);
|
|
2050
|
+
scheduler.capture(binding.record);
|
|
2051
|
+
}
|
|
2052
|
+
},
|
|
2053
|
+
settle: scheduler.settle,
|
|
2054
|
+
flush: scheduler.flush,
|
|
2055
|
+
dispose: () => {
|
|
2056
|
+
bindings.forEach(({ record }) => {
|
|
2057
|
+
record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
2058
|
+
scheduler.capture(record);
|
|
2059
|
+
});
|
|
2060
|
+
scheduler.run();
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
scheduler.cleanups.add(() => {
|
|
2064
|
+
unsubscribe();
|
|
2065
|
+
scheduler.guards.delete(guard);
|
|
2066
|
+
});
|
|
2067
|
+
documents.set(state, handle);
|
|
2068
|
+
return handle;
|
|
2069
|
+
};
|
|
2070
|
+
const valueSource = (initial, equal) => {
|
|
2071
|
+
let value = initial;
|
|
2072
|
+
let previous = initial;
|
|
2073
|
+
let revision = 0;
|
|
2074
|
+
const handle = Object.freeze({});
|
|
2075
|
+
const record = {
|
|
2076
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
2077
|
+
disposed: false,
|
|
2078
|
+
fault: void 0,
|
|
2079
|
+
revision: () => revision,
|
|
2080
|
+
reset: () => false,
|
|
2081
|
+
clear: () => {
|
|
2082
|
+
previous = value;
|
|
2083
|
+
},
|
|
2084
|
+
context: (active) => {
|
|
2085
|
+
assertScope(active);
|
|
2086
|
+
return Object.freeze({
|
|
2087
|
+
value,
|
|
2088
|
+
previous,
|
|
2089
|
+
revision,
|
|
2090
|
+
changed: !equal(previous, value),
|
|
2091
|
+
reset: false
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
};
|
|
2095
|
+
scheduler.register(handle, record);
|
|
2096
|
+
const accept = (next) => {
|
|
2097
|
+
scheduler.assertIdle();
|
|
2098
|
+
const recovering = record.fault !== void 0;
|
|
2099
|
+
if (equal(value, next) && !recovering) return;
|
|
2100
|
+
record.fault = void 0;
|
|
2101
|
+
value = next;
|
|
2102
|
+
revision++;
|
|
2103
|
+
scheduler.capture(record);
|
|
2104
|
+
};
|
|
2105
|
+
return {
|
|
2106
|
+
source: handle,
|
|
2107
|
+
accept,
|
|
2108
|
+
set(next) {
|
|
2109
|
+
accept(next);
|
|
2110
|
+
scheduler.run();
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
};
|
|
2114
|
+
return {
|
|
2115
|
+
dispose: () => {
|
|
2116
|
+
documents.clear();
|
|
2117
|
+
readables.clear();
|
|
2118
|
+
},
|
|
2119
|
+
document,
|
|
2120
|
+
input: (initial, options) => {
|
|
2121
|
+
const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
|
|
2122
|
+
return Object.freeze({
|
|
2123
|
+
source,
|
|
2124
|
+
set
|
|
2125
|
+
});
|
|
2126
|
+
},
|
|
2127
|
+
fromReadable: (readable, options) => {
|
|
2128
|
+
scheduler.assertIdle();
|
|
2129
|
+
if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
|
|
2130
|
+
const equal = options?.isEqual ?? Object.is;
|
|
2131
|
+
let bindings = readables.get(readable);
|
|
2132
|
+
const old = bindings?.get(equal);
|
|
2133
|
+
if (old) return old.source;
|
|
2134
|
+
const input = valueSource(readable.current(), equal);
|
|
2135
|
+
if (bindings) {
|
|
2136
|
+
bindings.set(equal, {
|
|
2137
|
+
source: input.source,
|
|
2138
|
+
accept: (value) => input.accept(value)
|
|
2139
|
+
});
|
|
2140
|
+
return input.source;
|
|
2141
|
+
}
|
|
2142
|
+
bindings = new Map([[equal, {
|
|
2143
|
+
source: input.source,
|
|
2144
|
+
accept: (value) => input.accept(value)
|
|
2145
|
+
}]]);
|
|
2146
|
+
const members = bindings;
|
|
2147
|
+
const receive = (settle = true) => {
|
|
2148
|
+
if (!scheduler.active) return;
|
|
2149
|
+
let value;
|
|
2150
|
+
try {
|
|
2151
|
+
value = readable.current();
|
|
2152
|
+
} catch (cause) {
|
|
2153
|
+
for (const member of members.values()) {
|
|
2154
|
+
const record = scheduler.source(member.source);
|
|
2155
|
+
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
2156
|
+
scheduler.capture(record);
|
|
2157
|
+
}
|
|
2158
|
+
if (settle) scheduler.run();
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
for (const member of members.values()) try {
|
|
2162
|
+
member.accept(value);
|
|
2163
|
+
} catch (cause) {
|
|
2164
|
+
const record = scheduler.source(member.source);
|
|
2165
|
+
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
2166
|
+
scheduler.capture(record);
|
|
2167
|
+
}
|
|
2168
|
+
if (settle) scheduler.run();
|
|
2169
|
+
};
|
|
2170
|
+
let unsubscribe;
|
|
2171
|
+
let detach;
|
|
2172
|
+
try {
|
|
2173
|
+
const owner = documentReadableOwner(readable);
|
|
2174
|
+
if (owner) {
|
|
2175
|
+
document(owner);
|
|
2176
|
+
detach = attachProjection(owner, {
|
|
2177
|
+
capture: () => receive(false),
|
|
2178
|
+
settle: scheduler.settle,
|
|
2179
|
+
flush: scheduler.flush,
|
|
2180
|
+
dispose: () => {
|
|
2181
|
+
for (const member of members.values()) {
|
|
2182
|
+
const record = scheduler.source(member.source);
|
|
2183
|
+
record.fault = new ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
2184
|
+
scheduler.capture(record);
|
|
2185
|
+
}
|
|
2186
|
+
scheduler.run();
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
unsubscribe = readable.subscribe(() => receive());
|
|
2191
|
+
input.accept(readable.current());
|
|
2192
|
+
} catch (error) {
|
|
2193
|
+
try {
|
|
2194
|
+
unsubscribe?.();
|
|
2195
|
+
detach?.();
|
|
2196
|
+
} finally {
|
|
2197
|
+
scheduler.unregisterSource(input.source);
|
|
2198
|
+
}
|
|
2199
|
+
throw error;
|
|
2200
|
+
}
|
|
2201
|
+
scheduler.cleanups.add(unsubscribe);
|
|
2202
|
+
if (detach) scheduler.cleanups.add(detach);
|
|
2203
|
+
readables.set(readable, bindings);
|
|
2204
|
+
scheduler.run();
|
|
2205
|
+
return input.source;
|
|
2206
|
+
},
|
|
2207
|
+
assertCollection: (handle) => {
|
|
2208
|
+
scheduler.source(handle);
|
|
2209
|
+
if (!collections.has(handle) && !collectionHandles.has(handle)) throw new Error("map requires a collection source.");
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
};
|
|
2213
|
+
//#endregion
|
|
2214
|
+
//#region core/src/projection/value.ts
|
|
2215
|
+
const createValue = (scheduler, spec, options) => {
|
|
2216
|
+
let instance;
|
|
2217
|
+
let value;
|
|
2218
|
+
let next;
|
|
2219
|
+
let previous;
|
|
2220
|
+
let initialized = false;
|
|
2221
|
+
let revision = 0;
|
|
2222
|
+
let changed = false;
|
|
2223
|
+
let reset = false;
|
|
2224
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2225
|
+
const owner = createNode(scheduler, spec, {
|
|
2226
|
+
evaluate: (sources, build) => {
|
|
2227
|
+
let candidate;
|
|
2228
|
+
if (build || !instance) {
|
|
2229
|
+
profile.materialized.rebuilt();
|
|
2230
|
+
instance = void 0;
|
|
2231
|
+
const built = spec.build(sources);
|
|
2232
|
+
assertSynchronous(built);
|
|
2233
|
+
instance = built;
|
|
2234
|
+
candidate = built.value;
|
|
2235
|
+
} else {
|
|
2236
|
+
profile.materialized.updated();
|
|
2237
|
+
const result = instance.update(sources);
|
|
2238
|
+
assertSynchronous(result);
|
|
2239
|
+
if (result.kind === "rebuild") {
|
|
2240
|
+
instance = void 0;
|
|
2241
|
+
const built = spec.build(sources);
|
|
2242
|
+
assertSynchronous(built);
|
|
2243
|
+
instance = built;
|
|
2244
|
+
candidate = built.value;
|
|
2245
|
+
build = true;
|
|
2246
|
+
} else candidate = result.kind === "changed" ? result.value : value;
|
|
2247
|
+
}
|
|
2248
|
+
previous = value;
|
|
2249
|
+
changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
|
|
2250
|
+
next = changed ? candidate : value;
|
|
2251
|
+
reset = build;
|
|
2252
|
+
return changed;
|
|
2253
|
+
},
|
|
2254
|
+
context: (active) => {
|
|
2255
|
+
assertScope(active);
|
|
2256
|
+
return Object.freeze({
|
|
2257
|
+
value: next,
|
|
2258
|
+
previous,
|
|
2259
|
+
changed,
|
|
2260
|
+
revision: revision + (changed ? 1 : 0),
|
|
2261
|
+
reset
|
|
2262
|
+
});
|
|
2263
|
+
},
|
|
2264
|
+
revision: () => revision,
|
|
2265
|
+
reset: () => reset,
|
|
2266
|
+
publish: () => {
|
|
2267
|
+
if (changed && initialized) revision++;
|
|
2268
|
+
value = next;
|
|
2269
|
+
initialized = true;
|
|
2270
|
+
},
|
|
2271
|
+
emit: (call) => {
|
|
2272
|
+
profile.materialized.notification();
|
|
2273
|
+
Array.from(listeners).forEach((listener) => call(listener));
|
|
2274
|
+
},
|
|
2275
|
+
clear: () => {
|
|
2276
|
+
next = value;
|
|
2277
|
+
previous = value;
|
|
2278
|
+
changed = false;
|
|
2279
|
+
reset = false;
|
|
2280
|
+
},
|
|
2281
|
+
release: () => {
|
|
2282
|
+
listeners.clear();
|
|
2283
|
+
instance = void 0;
|
|
2284
|
+
value = next = previous = void 0;
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
const handle = Object.freeze({
|
|
2288
|
+
current: () => {
|
|
2289
|
+
owner.check();
|
|
2290
|
+
return value;
|
|
2291
|
+
},
|
|
2292
|
+
revision: () => {
|
|
2293
|
+
owner.check();
|
|
2294
|
+
return revision;
|
|
2295
|
+
},
|
|
2296
|
+
subscribe: (listener) => {
|
|
2297
|
+
owner.check();
|
|
2298
|
+
listeners.add(listener);
|
|
2299
|
+
return () => {
|
|
2300
|
+
listeners.delete(listener);
|
|
2301
|
+
};
|
|
2302
|
+
},
|
|
2303
|
+
rebuild: owner.rebuild,
|
|
2304
|
+
dispose: owner.dispose
|
|
2305
|
+
});
|
|
2306
|
+
owner.install(handle);
|
|
2307
|
+
return handle;
|
|
2308
|
+
};
|
|
2309
|
+
//#endregion
|
|
2310
|
+
//#region core/src/projection/runtime.ts
|
|
2311
|
+
const debug = /* @__PURE__ */ new WeakMap();
|
|
2312
|
+
const projectionEngineDebug = (runtime) => {
|
|
2313
|
+
const read = debug.get(runtime);
|
|
2314
|
+
if (!read) throw new Error("Unknown projection engine.");
|
|
2315
|
+
return Object.freeze(read());
|
|
2316
|
+
};
|
|
2317
|
+
const createProjectionEngine = (options) => {
|
|
2318
|
+
const scheduler = createScheduler(options.onError);
|
|
2319
|
+
const sources = createSources(scheduler);
|
|
2320
|
+
function value(input, computeOrOptions, options) {
|
|
2321
|
+
if (typeof computeOrOptions !== "function") return createValue(scheduler, input, computeOrOptions);
|
|
2322
|
+
const compute = (input) => {
|
|
2323
|
+
const result = computeOrOptions(input);
|
|
2324
|
+
assertSynchronous(result);
|
|
2325
|
+
return result;
|
|
2326
|
+
};
|
|
2327
|
+
return createValue(scheduler, {
|
|
2328
|
+
sources: input,
|
|
2329
|
+
build: (input) => ({
|
|
2330
|
+
value: compute(input),
|
|
2331
|
+
update: (input) => ({
|
|
2332
|
+
kind: "changed",
|
|
2333
|
+
value: compute(input)
|
|
2334
|
+
})
|
|
2335
|
+
})
|
|
2336
|
+
}, options);
|
|
2337
|
+
}
|
|
2338
|
+
const map = (source, mapper, options) => {
|
|
2339
|
+
sources.assertCollection(source);
|
|
2340
|
+
const calculate = (id, entry) => {
|
|
2341
|
+
const value = mapper(id, entry);
|
|
2342
|
+
assertSynchronous(value);
|
|
2343
|
+
return value;
|
|
2344
|
+
};
|
|
2345
|
+
return createCollection(scheduler, {
|
|
2346
|
+
sources: { source },
|
|
2347
|
+
isEqual: options?.isEqual,
|
|
2348
|
+
build: ({ sources, writer }) => {
|
|
2349
|
+
const input = sources.source;
|
|
2350
|
+
const read = "read" in input ? input.read : input;
|
|
2351
|
+
const ids = read.ids();
|
|
2352
|
+
profile.collectionView.idsScanned(ids.length);
|
|
2353
|
+
for (const id of ids) {
|
|
2354
|
+
profile.collectionView.mapped();
|
|
2355
|
+
writer.set(id, calculate(id, read.get(id)));
|
|
2356
|
+
}
|
|
2357
|
+
writer.order(ids);
|
|
2358
|
+
return { update: ({ sources, writer }) => {
|
|
2359
|
+
const input = sources.source;
|
|
2360
|
+
if (input.reset) return { kind: "rebuild" };
|
|
2361
|
+
const read = "read" in input ? input.read : input;
|
|
2362
|
+
let keys;
|
|
2363
|
+
let structural;
|
|
2364
|
+
if ("candidates" in input) {
|
|
2365
|
+
keys = input.candidates.keys;
|
|
2366
|
+
structural = input.candidates.orderDirty;
|
|
2367
|
+
} else {
|
|
2368
|
+
const change = input.change;
|
|
2369
|
+
if (!change) return;
|
|
2370
|
+
if (change.kind === "reset") return { kind: "rebuild" };
|
|
2371
|
+
keys = new Set([
|
|
2372
|
+
...change.added,
|
|
2373
|
+
...change.removed,
|
|
2374
|
+
...change.updated
|
|
2375
|
+
]);
|
|
2376
|
+
structural = Boolean(change.added.size || change.removed.size || change.orderChanged);
|
|
2377
|
+
}
|
|
2378
|
+
for (const key of keys) if (!read.has(key)) writer.remove(key);
|
|
2379
|
+
else {
|
|
2380
|
+
profile.collectionView.mapped();
|
|
2381
|
+
writer.set(key, calculate(key, read.get(key)));
|
|
2382
|
+
}
|
|
2383
|
+
if (structural) {
|
|
2384
|
+
const ids = read.ids();
|
|
2385
|
+
profile.collectionView.idsScanned(ids.length);
|
|
2386
|
+
writer.order(ids);
|
|
2387
|
+
}
|
|
2388
|
+
} };
|
|
2389
|
+
}
|
|
2390
|
+
});
|
|
2391
|
+
};
|
|
2392
|
+
const runtime = {
|
|
2393
|
+
document: sources.document,
|
|
2394
|
+
input: sources.input,
|
|
2395
|
+
fromReadable: sources.fromReadable,
|
|
2396
|
+
value,
|
|
2397
|
+
collection: () => (spec) => createCollection(scheduler, spec),
|
|
2398
|
+
map,
|
|
2399
|
+
batch: scheduler.batch,
|
|
2400
|
+
dispose: () => {
|
|
2401
|
+
try {
|
|
2402
|
+
scheduler.dispose();
|
|
2403
|
+
} finally {
|
|
2404
|
+
if (!scheduler.active) sources.dispose();
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
};
|
|
2408
|
+
debug.set(runtime, scheduler.debug);
|
|
2409
|
+
return Object.freeze(runtime);
|
|
2410
|
+
};
|
|
2411
|
+
//#endregion
|
|
2412
|
+
//#region core/src/projection/store.ts
|
|
2413
|
+
const stores = /* @__PURE__ */ new WeakMap();
|
|
2414
|
+
const projectionStoreDebug = (store) => {
|
|
2415
|
+
const runtime = stores.get(store);
|
|
2416
|
+
if (!runtime) throw new Error("Unknown projection store.");
|
|
2417
|
+
return projectionEngineDebug(runtime);
|
|
2418
|
+
};
|
|
2419
|
+
const createProjectionStore = (options) => {
|
|
2420
|
+
const runtime = createProjectionEngine(options);
|
|
2421
|
+
const instances = /* @__PURE__ */ new WeakMap();
|
|
2422
|
+
const inputs = /* @__PURE__ */ new WeakMap();
|
|
2423
|
+
const materialize = (projection) => {
|
|
2424
|
+
const old = instances.get(projection);
|
|
2425
|
+
if (old) return old;
|
|
2426
|
+
const definition = definitionOf(projection);
|
|
2427
|
+
let instance;
|
|
2428
|
+
if (definition.kind === "input") {
|
|
2429
|
+
const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
|
|
2430
|
+
inputs.set(projection, input);
|
|
2431
|
+
instance = runtime.value({ input: input.source }, ({ input }) => input.value);
|
|
2432
|
+
} else if (definition.kind === "readable") {
|
|
2433
|
+
const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
|
|
2434
|
+
instance = runtime.value({ source }, ({ source }) => source.value);
|
|
2435
|
+
} else if (definition.kind === "document") {
|
|
2436
|
+
const document = runtime.document(definition.document);
|
|
2437
|
+
instance = definition.targets?.length ? document.targets(...definition.targets) : document;
|
|
2438
|
+
} else if (definition.kind === "document-collection") instance = runtime.document(definition.document).collection(definition.pick);
|
|
2439
|
+
else if (definition.kind === "map") instance = runtime.map(materialize(definition.source), definition.mapper, { isEqual: definition.isEqual });
|
|
2440
|
+
else {
|
|
2441
|
+
const sources = Object.fromEntries(Object.entries(definition.sources).map(([key, source]) => [key, materialize(source)]));
|
|
2442
|
+
if (definition.kind === "value") instance = runtime.value(sources, (events) => {
|
|
2443
|
+
const values = Object.fromEntries(Object.entries(events).map(([key, event]) => [key, current(event)]));
|
|
2444
|
+
return definition.compute(values);
|
|
2445
|
+
}, { isEqual: definition.isEqual });
|
|
2446
|
+
else if (definition.kind === "advanced-value") instance = runtime.value({
|
|
2447
|
+
sources,
|
|
2448
|
+
build: definition.build,
|
|
2449
|
+
name: definition.name
|
|
2450
|
+
}, { isEqual: definition.isEqual });
|
|
2451
|
+
else instance = runtime.collection()({
|
|
2452
|
+
sources,
|
|
2453
|
+
build: definition.build,
|
|
2454
|
+
isEqual: definition.isEqual,
|
|
2455
|
+
name: definition.name
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
instances.set(projection, instance);
|
|
2459
|
+
return instance;
|
|
2460
|
+
};
|
|
2461
|
+
const current = (event) => {
|
|
2462
|
+
if (event && typeof event === "object") {
|
|
2463
|
+
if ("value" in event) return event.value;
|
|
2464
|
+
if ("read" in event) return event.read;
|
|
2465
|
+
}
|
|
2466
|
+
return event;
|
|
2467
|
+
};
|
|
2468
|
+
const store = {
|
|
2469
|
+
get: ((projection) => {
|
|
2470
|
+
const instance = materialize(projection);
|
|
2471
|
+
if ("current" in instance && typeof instance.current === "function") return instance.current();
|
|
2472
|
+
throw new TypeError("Projection is a source and has no published value.");
|
|
2473
|
+
}),
|
|
2474
|
+
set: (projection, value) => {
|
|
2475
|
+
materialize(projection);
|
|
2476
|
+
const target = inputs.get(projection);
|
|
2477
|
+
if (!target) throw new TypeError("Projection is not an input.");
|
|
2478
|
+
target.set(value);
|
|
2479
|
+
},
|
|
2480
|
+
subscribe: ((projection, listener) => {
|
|
2481
|
+
const instance = materialize(projection);
|
|
2482
|
+
if (!("subscribe" in instance)) throw new TypeError("Projection is a source and cannot be subscribed.");
|
|
2483
|
+
return instance.subscribe(listener);
|
|
2484
|
+
}),
|
|
2485
|
+
revision: (projection) => {
|
|
2486
|
+
const instance = materialize(projection);
|
|
2487
|
+
if (!("revision" in instance) || typeof instance.revision !== "function") throw new TypeError("Projection has no published revision.");
|
|
2488
|
+
return instance.revision();
|
|
2489
|
+
},
|
|
2490
|
+
rebuild: (projection) => {
|
|
2491
|
+
const instance = materialize(projection);
|
|
2492
|
+
if (!("rebuild" in instance) || typeof instance.rebuild !== "function") throw new TypeError("Projection cannot be rebuilt.");
|
|
2493
|
+
instance.rebuild();
|
|
2494
|
+
},
|
|
2495
|
+
release: (projection) => {
|
|
2496
|
+
const instance = instances.get(projection);
|
|
2497
|
+
if (!instance) return;
|
|
2498
|
+
if ("dispose" in instance && typeof instance.dispose === "function") instance.dispose();
|
|
2499
|
+
instances.delete(projection);
|
|
2500
|
+
inputs.delete(projection);
|
|
2501
|
+
},
|
|
2502
|
+
batch: runtime.batch,
|
|
2503
|
+
dispose: runtime.dispose
|
|
2504
|
+
};
|
|
2505
|
+
stores.set(store, runtime);
|
|
2506
|
+
return Object.freeze(store);
|
|
2507
|
+
};
|
|
2508
|
+
//#endregion
|
|
2509
|
+
export { object as A, parse as B, assign as C, field as D, compilePath as E, ParseError as F, checkKey as I, checkValue as L, table as M, tree as N, list as O, variant as P, copyValue as R, same as S, snapshot as T, readWith as _, input as a, TransactionRejected as b, createNotification as c, shareNotification as d, subscribeDependencies as f, bindRuntimeAccess as g, accessOf as h, ProjectionError as i, optional as j, map as k, disposeNotification as l, subscribeTargets as m, projectionStoreDebug as n, project as o, subscribeRoot as p, ProjectionDisposedError as r, bindDocumentReadable as s, createProjectionStore as t, notify as u, DocumentDisposedError as v, createAccess as w, createImpact as x, DocumentReentrancyError as y, equalValue as z };
|
|
2510
|
+
|
|
2511
|
+
//# sourceMappingURL=store-D7QH6Rzw.js.map
|