doxum 0.1.23 → 0.1.26
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 +208 -102
- package/dist/advanced.cjs +237 -2
- package/dist/advanced.cjs.map +1 -0
- package/dist/advanced.d.cts +90 -2
- package/dist/advanced.d.ts +90 -2
- package/dist/advanced.js +236 -1
- package/dist/advanced.js.map +1 -0
- package/dist/{contract-ULPQmiEs.d.ts → contract-CNR6BS75.d.cts} +216 -30
- package/dist/{contract-SYJFP0lM.d.cts → contract-COgxO5sv.d.ts} +216 -30
- package/dist/definition-CDOuvb5C.d.cts +111 -0
- package/dist/definition-xgk3YNBp.d.ts +111 -0
- package/dist/{driver-yFfvDMX5.js → driver-B5vdPohL.js} +59 -3
- package/dist/driver-B5vdPohL.js.map +1 -0
- package/dist/{driver-BD3DQOW6.cjs → driver-BL91gn7_.cjs} +107 -9
- package/dist/driver-BL91gn7_.cjs.map +1 -0
- package/dist/index.cjs +1637 -703
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -33
- package/dist/index.d.ts +65 -33
- package/dist/index.js +1432 -497
- package/dist/index.js.map +1 -1
- package/dist/local-sync.cjs +69 -103
- package/dist/local-sync.cjs.map +1 -1
- package/dist/local-sync.d.cts +14 -26
- package/dist/local-sync.d.ts +14 -26
- package/dist/local-sync.js +68 -96
- package/dist/local-sync.js.map +1 -1
- package/dist/react.cjs +4 -5
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -8
- package/dist/react.d.ts +6 -8
- package/dist/react.js +4 -5
- package/dist/react.js.map +1 -1
- package/dist/record-B2yRhYsc.js +28 -0
- package/dist/record-B2yRhYsc.js.map +1 -0
- package/dist/record-ChYUi8PP.cjs +45 -0
- package/dist/record-ChYUi8PP.cjs.map +1 -0
- package/dist/scheduler--137L3j-.js +500 -0
- package/dist/scheduler--137L3j-.js.map +1 -0
- package/dist/scheduler-D4VthaaK.cjs +589 -0
- package/dist/scheduler-D4VthaaK.cjs.map +1 -0
- package/package.json +6 -3
- package/skills/doxum-runtime/SKILL.md +32 -7
- package/skills/doxum-runtime/agents/openai.yaml +1 -1
- package/skills/doxum-runtime/references/api.en.md +249 -0
- package/skills/doxum-runtime/references/api.zh-CN.md +242 -0
- package/skills/doxum-runtime/references/guide.en.md +18 -8
- package/skills/doxum-runtime/references/guide.zh-CN.md +14 -6
- package/skills/doxum-runtime/references/invariants.en.md +29 -5
- package/skills/doxum-runtime/references/invariants.zh-CN.md +24 -6
- package/skills/doxum-runtime/references/patterns.en.md +78 -18
- package/skills/doxum-runtime/references/patterns.zh-CN.md +76 -18
- package/skills/doxum-runtime/references/projections.en.md +209 -119
- package/skills/doxum-runtime/references/projections.zh-CN.md +200 -101
- package/dist/advanced-CLJCWkHa.d.cts +0 -176
- package/dist/advanced-CRNHfJBu.js +0 -1224
- package/dist/advanced-CRNHfJBu.js.map +0 -1
- package/dist/advanced-Cle5g3B4.cjs +0 -1517
- package/dist/advanced-Cle5g3B4.cjs.map +0 -1
- package/dist/advanced-d6J61er9.d.ts +0 -176
- package/dist/context-BcLKGfq8.cjs +0 -149
- package/dist/context-BcLKGfq8.cjs.map +0 -1
- package/dist/context-DFEaDUv5.js +0 -84
- package/dist/context-DFEaDUv5.js.map +0 -1
- package/dist/driver-BD3DQOW6.cjs.map +0 -1
- package/dist/driver-yFfvDMX5.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,431 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
//#region core/src/
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { a as ProjectionError, c as snapshotCollectionView, d as isProjection, f as ownProjections, h as profile, i as ProjectionDisposedError, l as defineProcessor, m as projectionRef, n as assertSynchronous, o as collectionView, p as producerOf, r as createScheduler, s as mapRead, t as assertScope, u as defineSource } from "./scheduler--137L3j-.js";
|
|
2
|
+
import { n as isPlainObject, r as isRecord, t as installOwn } from "./record-B2yRhYsc.js";
|
|
3
|
+
import { c as AddressIndex, d as invalidValue, f as children, g as validate, h as parent, i as contextOf, l as MutationRejected, m as is, o as decodeChanges, p as contains$1, r as bindContext, s as sealChanges, t as assertRuntimeWritable, u as fail } from "./driver-B5vdPohL.js";
|
|
4
|
+
//#region core/src/schema/model.ts
|
|
5
|
+
const schemaNodeOf = (schema) => schema;
|
|
6
|
+
const collectionEntryNode = (node) => node.kind === "map" || node.kind === "table" || node.kind === "list" ? node.value : void 0;
|
|
7
|
+
const node$2 = (value) => Object.freeze(value);
|
|
8
|
+
const field = (validator) => node$2({
|
|
9
|
+
kind: "field",
|
|
10
|
+
...validator ? { validator } : {}
|
|
11
|
+
});
|
|
12
|
+
function optional(value) {
|
|
13
|
+
const concrete = schemaNodeOf(value);
|
|
14
|
+
if (![
|
|
15
|
+
"field",
|
|
16
|
+
"variant",
|
|
17
|
+
"map",
|
|
18
|
+
"list",
|
|
19
|
+
"tree"
|
|
20
|
+
].includes(concrete.kind)) throw new TypeError("Only field, variant, map, list and tree nodes support optional presence.");
|
|
21
|
+
return node$2({
|
|
22
|
+
...concrete,
|
|
23
|
+
optional: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const object = (shape) => node$2({
|
|
27
|
+
kind: "object",
|
|
28
|
+
shape: Object.freeze(Object.fromEntries(Object.entries(shape).map(([key, value]) => [key, schemaNodeOf(value)])))
|
|
29
|
+
});
|
|
30
|
+
const variant = (tag, variants) => node$2({
|
|
31
|
+
kind: "variant",
|
|
32
|
+
tag,
|
|
33
|
+
variants: Object.freeze(Object.fromEntries(Object.entries(variants).map(([key, value]) => [key, schemaNodeOf(value)])))
|
|
34
|
+
});
|
|
35
|
+
const table = (value, options) => node$2({
|
|
36
|
+
kind: "table",
|
|
37
|
+
value: schemaNodeOf(value),
|
|
38
|
+
...options
|
|
39
|
+
});
|
|
40
|
+
const map = (value, options) => node$2({
|
|
41
|
+
kind: "map",
|
|
42
|
+
value: schemaNodeOf(value),
|
|
43
|
+
...options
|
|
44
|
+
});
|
|
45
|
+
const list = (value, config) => node$2({
|
|
46
|
+
kind: "list",
|
|
47
|
+
keyOf: config.keyOf,
|
|
48
|
+
value: schemaNodeOf(value)
|
|
49
|
+
});
|
|
50
|
+
function tree(value) {
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
kind: "tree",
|
|
53
|
+
value: schemaNodeOf(value)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region core/src/order/sequence.ts
|
|
58
|
+
const length$1 = (keys) => keys.length;
|
|
59
|
+
const keyAt = (keys, index) => Array.isArray(keys) ? keys[index] : keys.at(index);
|
|
60
|
+
const equal = (left, right) => {
|
|
61
|
+
profile.equality.call();
|
|
62
|
+
if (left === right) return true;
|
|
63
|
+
profile.equality.container();
|
|
64
|
+
if (left.length !== right.length) return false;
|
|
65
|
+
for (let index = 0; index < left.length; index++) if (left[index] !== keyAt(right, index)) return false;
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
68
|
+
const keys = (values, keyOf) => ({
|
|
69
|
+
length: values.length,
|
|
70
|
+
at: (index) => index >= 0 && index < values.length ? keyOf(values[index]) : void 0,
|
|
71
|
+
index: (key) => {
|
|
72
|
+
for (let index = 0; index < values.length; index += 1) if (keyOf(values[index]) === key) return index;
|
|
73
|
+
return -1;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
const listIndexes = /* @__PURE__ */ new WeakMap();
|
|
77
|
+
const knownKeys = (values) => {
|
|
78
|
+
let positions;
|
|
79
|
+
return {
|
|
80
|
+
length: values.length,
|
|
81
|
+
at: (index) => values[index],
|
|
82
|
+
index: (key) => {
|
|
83
|
+
if (!positions) {
|
|
84
|
+
positions = /* @__PURE__ */ new Map();
|
|
85
|
+
for (let index = 0; index < values.length; index++) positions.set(values[index], index);
|
|
86
|
+
profile.address.listIndex(values.length);
|
|
87
|
+
}
|
|
88
|
+
return positions.get(key) ?? -1;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
const cacheKnownKeys = (values, keyOf, order) => {
|
|
93
|
+
listIndexes.set(values, {
|
|
94
|
+
keyOf,
|
|
95
|
+
order: knownKeys(order)
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
/** Canonical lists keep their key positions until a structural write invalidates them. */
|
|
99
|
+
const indexedKeys = (values, keyOf) => {
|
|
100
|
+
const cached = listIndexes.get(values);
|
|
101
|
+
if (cached?.keyOf === keyOf) return cached.order;
|
|
102
|
+
let positions;
|
|
103
|
+
const order = {
|
|
104
|
+
length: values.length,
|
|
105
|
+
at: (index) => index >= 0 && index < values.length ? keyOf(values[index]) : void 0,
|
|
106
|
+
index: (key) => {
|
|
107
|
+
if (!positions) {
|
|
108
|
+
positions = /* @__PURE__ */ new Map();
|
|
109
|
+
for (let index = 0; index < values.length; index++) positions.set(keyOf(values[index]), index);
|
|
110
|
+
profile.address.listIndex(values.length);
|
|
111
|
+
}
|
|
112
|
+
return positions.get(key) ?? -1;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
listIndexes.set(values, {
|
|
116
|
+
keyOf,
|
|
117
|
+
order
|
|
118
|
+
});
|
|
119
|
+
return order;
|
|
120
|
+
};
|
|
121
|
+
const toArray = (keys) => {
|
|
122
|
+
const result = new Array(length$1(keys));
|
|
123
|
+
for (let index = 0; index < result.length; index++) result[index] = keyAt(keys, index);
|
|
124
|
+
return result;
|
|
125
|
+
};
|
|
126
|
+
/** One operation-local list scan provides both stable keys and item association. */
|
|
127
|
+
const listSequence = (values, keyOf) => {
|
|
128
|
+
const current = indexedKeys(values, keyOf);
|
|
129
|
+
const order = new Array(values.length);
|
|
130
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
131
|
+
for (let index = 0; index < values.length; index++) {
|
|
132
|
+
const key = current.at(index);
|
|
133
|
+
order[index] = key;
|
|
134
|
+
byKey.set(key, values[index]);
|
|
135
|
+
}
|
|
136
|
+
cacheKnownKeys(values, keyOf, order);
|
|
137
|
+
return {
|
|
138
|
+
order,
|
|
139
|
+
values: byKey
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
/** Structural sequence writes own invalidation, including rollback writes. */
|
|
143
|
+
const insert$2 = (values, index, value) => {
|
|
144
|
+
values.splice(index, 0, value);
|
|
145
|
+
listIndexes.delete(values);
|
|
146
|
+
};
|
|
147
|
+
const remove$3 = (values, index) => {
|
|
148
|
+
values.splice(index, 1);
|
|
149
|
+
listIndexes.delete(values);
|
|
150
|
+
};
|
|
151
|
+
/** Installs an exact string-key sequence such as table ids in one structural write. */
|
|
152
|
+
const installKeys = (values, order) => {
|
|
153
|
+
values.length = order.length;
|
|
154
|
+
for (let index = 0; index < order.length; index++) values[index] = order[index];
|
|
155
|
+
};
|
|
156
|
+
/** Installs a pre-resolved list sequence without running keyOf again. */
|
|
157
|
+
const installList = (values, keyOf, sequence, order) => {
|
|
158
|
+
values.length = order.length;
|
|
159
|
+
for (let index = 0; index < order.length; index++) values[index] = sequence.values.get(order[index]);
|
|
160
|
+
cacheKnownKeys(values, keyOf, order);
|
|
161
|
+
};
|
|
162
|
+
const install = (values, keyOf, order) => {
|
|
163
|
+
installList(values, keyOf, listSequence(values, keyOf), order);
|
|
164
|
+
};
|
|
165
|
+
const matches = (order, members) => {
|
|
166
|
+
if (order.length !== members.length) return false;
|
|
167
|
+
const ids = new Set(order);
|
|
168
|
+
return ids.size === order.length && members.every((id) => ids.has(id));
|
|
169
|
+
};
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region core/src/schema/layout.ts
|
|
172
|
+
const compiledObjects = /* @__PURE__ */ new WeakMap();
|
|
173
|
+
const compiledEntries = /* @__PURE__ */ new WeakMap();
|
|
174
|
+
const variantBranch = (node, discriminator) => node.variants[discriminator ?? Object.keys(node.variants)[0] ?? ""];
|
|
175
|
+
const objectBranch = (node, value) => {
|
|
176
|
+
if (node.kind === "object") return node;
|
|
177
|
+
if (node.kind !== "variant" || typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
178
|
+
const rawTag = value[node.tag];
|
|
179
|
+
return variantBranch(node, typeof rawTag === "string" ? rawTag : void 0);
|
|
180
|
+
};
|
|
181
|
+
const compiledShape = (node, value) => {
|
|
182
|
+
const branch = objectBranch(node, value);
|
|
183
|
+
if (!branch) return void 0;
|
|
184
|
+
const shape = branch.shape;
|
|
185
|
+
let layout = compiledObjects.get(shape);
|
|
186
|
+
if (!layout) {
|
|
187
|
+
const members = /* @__PURE__ */ new Map();
|
|
188
|
+
for (const key of Object.keys(shape).sort()) {
|
|
189
|
+
profile.address.schemaStep();
|
|
190
|
+
members.set(key, {
|
|
191
|
+
kind: "fixed",
|
|
192
|
+
key,
|
|
193
|
+
node: shape[key],
|
|
194
|
+
field: shape[key].kind === "field",
|
|
195
|
+
slot: members.size
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
layout = {
|
|
199
|
+
kind: "fixed",
|
|
200
|
+
members
|
|
201
|
+
};
|
|
202
|
+
compiledObjects.set(shape, layout);
|
|
203
|
+
}
|
|
204
|
+
return layout;
|
|
205
|
+
};
|
|
206
|
+
const compiledLayout = (node, value) => {
|
|
207
|
+
if (node.kind !== "map" && node.kind !== "table" && node.kind !== "list") return compiledShape(node, value);
|
|
208
|
+
let layout = compiledEntries.get(node);
|
|
209
|
+
if (!layout) {
|
|
210
|
+
layout = {
|
|
211
|
+
kind: "dynamic",
|
|
212
|
+
entry: {
|
|
213
|
+
kind: "entry",
|
|
214
|
+
node: node.value,
|
|
215
|
+
field: node.value.kind === "field"
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
compiledEntries.set(node, layout);
|
|
219
|
+
}
|
|
220
|
+
return layout;
|
|
221
|
+
};
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region core/src/schema/value.ts
|
|
224
|
+
var ParseError = class extends TypeError {
|
|
225
|
+
issues;
|
|
226
|
+
constructor(issue) {
|
|
227
|
+
super(`${issue.message} at ${JSON.stringify(issue.address)}`);
|
|
228
|
+
this.name = "ParseError";
|
|
229
|
+
this.issues = Object.freeze([Object.freeze({
|
|
230
|
+
...issue,
|
|
231
|
+
address: Object.freeze([...issue.address])
|
|
232
|
+
})]);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
const issue = (address, message, code = "invalid-value") => ({
|
|
236
|
+
address,
|
|
237
|
+
message,
|
|
238
|
+
code
|
|
239
|
+
});
|
|
240
|
+
const checkScalar = (validator, value, address, strict) => {
|
|
241
|
+
if (!validator) return strict ? issue(address, "A validator is required to parse an opaque value.", "missing-validator") : void 0;
|
|
242
|
+
try {
|
|
243
|
+
const result = typeof validator === "function" ? validator(value) : validator["~standard"].validate(value);
|
|
244
|
+
if (result && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function") return issue(address, "Validators must be synchronous.");
|
|
245
|
+
if (typeof validator === "function") {
|
|
246
|
+
if (result === false) return issue(address, "Value validation failed.");
|
|
247
|
+
if (result !== void 0 && result !== true) return issue(address, "Function validators must return boolean or undefined.");
|
|
248
|
+
} else {
|
|
249
|
+
if (!isRecord(result)) return issue(address, "Malformed validator result.");
|
|
250
|
+
if (Array.isArray(result.issues)) {
|
|
251
|
+
const first = result.issues[0];
|
|
252
|
+
return issue([...address, ...(first?.path ?? []).map((part) => String(typeof part === "object" ? part.key : part))], first?.message ?? "Value validation failed.");
|
|
253
|
+
}
|
|
254
|
+
if (!Object.hasOwn(result, "value")) return issue(address, "Malformed validator result.");
|
|
255
|
+
if (!Object.is(result.value, value)) return issue(address, "Validators must not transform values.");
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
return issue(address, error instanceof Error ? error.message : "Value validation failed.");
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const checkKey = (validator, value, address) => {
|
|
263
|
+
if (typeof value !== "string") return issue(address, "Collection keys must be strings.", "invalid-key");
|
|
264
|
+
const failure = checkScalar(validator, value, address, false);
|
|
265
|
+
return failure ? {
|
|
266
|
+
...failure,
|
|
267
|
+
code: "invalid-key"
|
|
268
|
+
} : void 0;
|
|
269
|
+
};
|
|
270
|
+
const checkValue = (node, value, address = [], strict = false) => {
|
|
271
|
+
if (node.optional && value === void 0) return void 0;
|
|
272
|
+
if (node.kind === "field") return checkScalar(node.validator, value, address, strict);
|
|
273
|
+
if (node.kind === "object" || node.kind === "variant") {
|
|
274
|
+
if (!isPlainObject(value)) return issue(address, "Expected an object.");
|
|
275
|
+
if (node.kind === "variant") {
|
|
276
|
+
const tag = value[node.tag];
|
|
277
|
+
if (!Object.hasOwn(value, node.tag) || typeof tag !== "string" || !Object.hasOwn(node.variants, tag)) return issue([...address, node.tag], "Unknown variant tag.");
|
|
278
|
+
}
|
|
279
|
+
const { members } = compiledShape(node, value);
|
|
280
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
281
|
+
if (node.kind === "variant" && key === node.tag) continue;
|
|
282
|
+
if (typeof key !== "string" || !members.has(key)) return issue([...address, String(key)], "Property is not declared in the object schema.");
|
|
283
|
+
}
|
|
284
|
+
for (const [key, member] of members) {
|
|
285
|
+
const child = member.node;
|
|
286
|
+
if (!Object.prototype.hasOwnProperty.call(value, key) && !child.optional) return issue([...address, key], "Required property is missing.");
|
|
287
|
+
const failure = checkValue(child, value[key], [...address, key], strict);
|
|
288
|
+
if (failure) return failure;
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (node.kind === "table" || node.kind === "map") {
|
|
293
|
+
if (!isPlainObject(value)) return issue(address, "Expected a collection object.");
|
|
294
|
+
const entries = node.kind === "table" ? value.byId : value;
|
|
295
|
+
if (!isPlainObject(entries)) return issue(address, "Expected a byId object.");
|
|
296
|
+
if (node.kind === "table") {
|
|
297
|
+
if (!Array.isArray(value.ids) || value.ids.length !== Object.keys(entries).length) return issue(address, "Table ids and entries must agree.");
|
|
298
|
+
const seen = /* @__PURE__ */ new Set();
|
|
299
|
+
for (const id of value.ids) {
|
|
300
|
+
const failure = checkKey(node.key, id, address);
|
|
301
|
+
if (failure) return failure;
|
|
302
|
+
if (seen.has(id) || !Object.prototype.hasOwnProperty.call(entries, id)) return issue(address, "Table ids must be unique and reference existing entries.");
|
|
303
|
+
seen.add(id);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const id of Object.keys(entries)) {
|
|
307
|
+
const failure = checkKey(node.key, id, [...address, id]) ?? checkValue(node.value, entries[id], [...address, id], strict);
|
|
308
|
+
if (failure) return failure;
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (node.kind === "list") {
|
|
313
|
+
if (!Array.isArray(value)) return issue(address, "Expected a list.");
|
|
314
|
+
const seen = /* @__PURE__ */ new Set();
|
|
315
|
+
const order = keys(value, node.keyOf);
|
|
316
|
+
for (let i = 0; i < value.length; i++) {
|
|
317
|
+
const failure = checkValue(node.value, value[i], [...address, String(i)], strict);
|
|
318
|
+
if (failure) return failure;
|
|
319
|
+
let key;
|
|
320
|
+
try {
|
|
321
|
+
key = order.at(i);
|
|
322
|
+
} catch {
|
|
323
|
+
return issue(address, "List keyOf failed.");
|
|
324
|
+
}
|
|
325
|
+
if (typeof key !== "string" || seen.has(key)) return issue(address, "List keys must be unique strings.", "invalid-key");
|
|
326
|
+
seen.add(key);
|
|
327
|
+
}
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (!validate(value)) return issue(address, "Invalid tree structure.", "invalid-tree");
|
|
331
|
+
for (const id of Object.keys(value.nodes)) {
|
|
332
|
+
const failure = checkTreePayload(node.value, value.nodes[id], [...address, id], strict);
|
|
333
|
+
if (failure) return failure;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
const checkTreePayload = (node, value, address, strict = false) => {
|
|
337
|
+
if (!isRecord(value)) return issue(address, "Invalid tree node.", "invalid-tree");
|
|
338
|
+
const present = Object.prototype.hasOwnProperty.call(value, "value");
|
|
339
|
+
if (!present && !node.optional) return issue(address, "Tree node value is required.");
|
|
340
|
+
return present ? checkValue(node, value.value, address, strict) : void 0;
|
|
341
|
+
};
|
|
342
|
+
const copyTreeNode = (value) => {
|
|
343
|
+
profile.copy.treeNode();
|
|
344
|
+
return {
|
|
345
|
+
...value,
|
|
346
|
+
children: [...value.children]
|
|
347
|
+
};
|
|
348
|
+
};
|
|
349
|
+
const equalTreeNode = (left, right) => {
|
|
350
|
+
if (left === right) return true;
|
|
351
|
+
if (!left || !right) return false;
|
|
352
|
+
return left.parentId === right.parentId && equal(left.children, right.children) && Object.hasOwn(left, "value") === Object.hasOwn(right, "value") && Object.is(left.value, right.value);
|
|
353
|
+
};
|
|
354
|
+
/** Copy editable structure; atomic payloads retain their immutable ownership contract. */
|
|
355
|
+
const copyValue = (node, value) => {
|
|
356
|
+
if (value === void 0 || node.kind === "field") return value;
|
|
357
|
+
profile.copy.structure();
|
|
358
|
+
if ((node.kind === "object" || node.kind === "variant") && isRecord(value)) {
|
|
359
|
+
const result = Object.create(Object.getPrototypeOf(value));
|
|
360
|
+
const { members } = compiledShape(node, value);
|
|
361
|
+
for (const key of Object.getOwnPropertyNames(value)) Object.defineProperty(result, key, {
|
|
362
|
+
value: node.kind === "variant" && key === node.tag ? value[key] : copyValue(members.get(key).node, value[key]),
|
|
363
|
+
writable: true,
|
|
364
|
+
enumerable: Object.getOwnPropertyDescriptor(value, key)?.enumerable,
|
|
365
|
+
configurable: true
|
|
366
|
+
});
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
if ((node.kind === "table" || node.kind === "map") && isRecord(value)) {
|
|
370
|
+
const entries = node.kind === "table" ? value.byId : value;
|
|
371
|
+
const result = Object.fromEntries(Object.keys(entries).map((id) => [id, copyValue(node.value, entries[id])]));
|
|
372
|
+
return node.kind === "table" ? {
|
|
373
|
+
ids: [...value.ids],
|
|
374
|
+
byId: result
|
|
375
|
+
} : result;
|
|
376
|
+
}
|
|
377
|
+
if (node.kind === "list") return [...value];
|
|
378
|
+
if (node.kind === "tree" && isRecord(value)) {
|
|
379
|
+
const entries = value.nodes;
|
|
380
|
+
return {
|
|
381
|
+
...value,
|
|
382
|
+
nodes: Object.fromEntries(Object.entries(entries).map(([id, n]) => [id, copyTreeNode(n)]))
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
return value;
|
|
386
|
+
};
|
|
387
|
+
const equalValue = (node, left, right) => {
|
|
388
|
+
if (Object.is(left, right)) return true;
|
|
389
|
+
if (node.kind === "field" || left === void 0 || right === void 0) return false;
|
|
390
|
+
if ((node.kind === "object" || node.kind === "variant") && isRecord(left) && isRecord(right)) {
|
|
391
|
+
if (node.kind === "variant" && left[node.tag] !== right[node.tag]) return false;
|
|
392
|
+
for (const [key, member] of compiledShape(node, left).members) {
|
|
393
|
+
if (Object.hasOwn(left, key) !== Object.hasOwn(right, key)) return false;
|
|
394
|
+
if (!equalValue(member.node, left[key], right[key])) return false;
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
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]));
|
|
399
|
+
if (node.kind === "table" && isRecord(left) && isRecord(right)) return equal(left.ids, right.ids) && equalValue({
|
|
400
|
+
kind: "map",
|
|
401
|
+
value: node.value
|
|
402
|
+
}, left.byId, right.byId);
|
|
403
|
+
if (node.kind === "tree" && isRecord(left) && isRecord(right)) {
|
|
404
|
+
if (left.rootId !== right.rootId) return false;
|
|
405
|
+
const a = left.nodes, b = right.nodes;
|
|
406
|
+
return Object.keys(a).length === Object.keys(b).length && Object.keys(a).every((id) => Object.hasOwn(b, id) && equalTreeNode(a[id], b[id]));
|
|
407
|
+
}
|
|
408
|
+
if (node.kind === "map" && isRecord(left) && isRecord(right)) {
|
|
409
|
+
const keys = Object.keys(left);
|
|
410
|
+
return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && equalValue(node.value, left[key], right[key]));
|
|
411
|
+
}
|
|
412
|
+
return false;
|
|
8
413
|
};
|
|
414
|
+
const parse = (schema, input) => {
|
|
415
|
+
const valueNode = schemaNodeOf(schema);
|
|
416
|
+
const failure = checkValue(valueNode, input, [], true);
|
|
417
|
+
if (failure) throw new ParseError(failure);
|
|
418
|
+
return copyValue(valueNode, input);
|
|
419
|
+
};
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region core/src/address/resolve.ts
|
|
9
422
|
const step = (nodeInput, value, segment) => {
|
|
10
423
|
let node = nodeInput;
|
|
11
424
|
if (!node) return void 0;
|
|
12
|
-
if (node.kind === "variant")
|
|
425
|
+
if (node.kind === "variant") {
|
|
426
|
+
const rawTag = isRecord(value) ? value[node.tag] : void 0;
|
|
427
|
+
node = variantBranch(node, typeof rawTag === "string" ? rawTag : void 0);
|
|
428
|
+
}
|
|
13
429
|
if (!node) return void 0;
|
|
14
430
|
if (node.kind === "object") return Object.hasOwn(node.shape, segment) ? node.shape[segment] : void 0;
|
|
15
431
|
if (node.kind === "table" || node.kind === "map") return node.value;
|
|
@@ -137,16 +553,6 @@ const resolveChild = (node, current, last) => {
|
|
|
137
553
|
};
|
|
138
554
|
};
|
|
139
555
|
//#endregion
|
|
140
|
-
//#region core/src/mutation/operations/map.ts
|
|
141
|
-
function put(session, container, id, value) {
|
|
142
|
-
if (container.node.kind !== "map") return fail(container.at, "invalid-collection", "Expected a map.");
|
|
143
|
-
session.writeMember(container, id, value, "set");
|
|
144
|
-
}
|
|
145
|
-
function remove$3(session, container, id) {
|
|
146
|
-
if (container.node.kind !== "map") return fail(container.at, "invalid-collection", "Expected a map.");
|
|
147
|
-
session.writeMember(container, id, void 0, "remove");
|
|
148
|
-
}
|
|
149
|
-
//#endregion
|
|
150
556
|
//#region core/src/order/anchor.ts
|
|
151
557
|
const length = (keys) => keys.length;
|
|
152
558
|
const keyIndex = (keys, key) => Array.isArray(keys) ? keys.indexOf(key) : keys.index(key);
|
|
@@ -410,13 +816,17 @@ const sameTreeTarget = (left, right) => {
|
|
|
410
816
|
return left.kind !== "node" || right.kind === "node" && left.id === right.id;
|
|
411
817
|
};
|
|
412
818
|
const same = (left, right) => {
|
|
819
|
+
profile.dependency.comparison();
|
|
413
820
|
if ("schema" in left && "schema" in right && left.schema !== right.schema) return false;
|
|
414
821
|
if (!sameTreeTarget(treeTarget(left), treeTarget(right))) return false;
|
|
415
822
|
if (left.kind !== right.kind) return false;
|
|
416
823
|
const leftAddress = address(left);
|
|
417
824
|
const rightAddress = address(right);
|
|
418
825
|
if (leftAddress.length !== rightAddress.length) return false;
|
|
419
|
-
for (let index = 0; index < leftAddress.length; index += 1)
|
|
826
|
+
for (let index = 0; index < leftAddress.length; index += 1) {
|
|
827
|
+
profile.dependency.segmentCompared();
|
|
828
|
+
if (leftAddress[index] !== rightAddress[index]) return false;
|
|
829
|
+
}
|
|
420
830
|
return left.kind !== "collection" || id(left) === id(right);
|
|
421
831
|
};
|
|
422
832
|
const indexedAddress = (target) => {
|
|
@@ -444,6 +854,12 @@ const indexedAddress = (target) => {
|
|
|
444
854
|
key
|
|
445
855
|
];
|
|
446
856
|
};
|
|
857
|
+
/**
|
|
858
|
+
* Coarse identity for dependency de-duplication. Schema is intentionally excluded because
|
|
859
|
+
* schema-bound and schema-free targets may compare equal; callers must still use same()
|
|
860
|
+
* inside a bucket for the final equality decision.
|
|
861
|
+
*/
|
|
862
|
+
const dependencyBucket = (target) => JSON.stringify([target.kind, indexedAddress(target)]);
|
|
447
863
|
const visitChangedLocations = (changes, value, order) => {
|
|
448
864
|
for (const change of changes.changes) {
|
|
449
865
|
if (change.kind === "reset") continue;
|
|
@@ -682,12 +1098,14 @@ const createAccess = (context, initial = []) => {
|
|
|
682
1098
|
return childAccess(target, id, node.value, current[id]);
|
|
683
1099
|
};
|
|
684
1100
|
if (property === "put") return (id, value) => {
|
|
1101
|
+
const session = mutable();
|
|
685
1102
|
const writable = writableCollection(target, node);
|
|
686
|
-
|
|
1103
|
+
session.writeMember(writable, id, value, "set");
|
|
687
1104
|
};
|
|
688
1105
|
if (property === "remove") return (id) => {
|
|
1106
|
+
const session = mutable();
|
|
689
1107
|
const writable = writableCollection(target, node);
|
|
690
|
-
|
|
1108
|
+
session.writeMember(writable, id, void 0, "remove");
|
|
691
1109
|
};
|
|
692
1110
|
if (property === "replace") return (value) => replaceCollection(target, node, value);
|
|
693
1111
|
};
|
|
@@ -888,6 +1306,62 @@ const createAccess = (context, initial = []) => {
|
|
|
888
1306
|
return access(root);
|
|
889
1307
|
};
|
|
890
1308
|
//#endregion
|
|
1309
|
+
//#region core/src/schema/path.ts
|
|
1310
|
+
const paths = /* @__PURE__ */ new WeakMap();
|
|
1311
|
+
const pathProxy = (nodes, address, owner, treeSelection) => {
|
|
1312
|
+
const proxy = new Proxy({}, { get: (_target, property) => {
|
|
1313
|
+
if (typeof property !== "string") return void 0;
|
|
1314
|
+
const trees = nodes.every((node) => node.kind === "tree");
|
|
1315
|
+
if (!treeSelection && trees && property === "rootId") return pathProxy(nodes, address, owner, { kind: "root" });
|
|
1316
|
+
if (!treeSelection && trees && property === "nodes") return pathProxy(nodes, address, owner, { kind: "nodes" });
|
|
1317
|
+
if (treeSelection?.kind === "nodes" && property === "item") return (id) => {
|
|
1318
|
+
if (typeof id !== "string") throw new TypeError("Tree node keys must be strings.");
|
|
1319
|
+
return pathProxy(nodes, address, owner, {
|
|
1320
|
+
kind: "node",
|
|
1321
|
+
id
|
|
1322
|
+
});
|
|
1323
|
+
};
|
|
1324
|
+
if (treeSelection) throw new TypeError(`Invalid tree path segment: ${property}`);
|
|
1325
|
+
if (nodes.every((node) => collectionEntryNode(node) !== void 0) && property === "item") return (id) => {
|
|
1326
|
+
if (typeof id !== "string") throw new TypeError("Collection keys must be strings.");
|
|
1327
|
+
for (const node of nodes) if (node.kind === "table" || node.kind === "map") {
|
|
1328
|
+
const invalid = checkKey(node.key, id, [...address, id]);
|
|
1329
|
+
if (invalid) throw new TypeError(invalid.message);
|
|
1330
|
+
}
|
|
1331
|
+
return pathProxy(nodes.map((node) => collectionEntryNode(node)), [...address, id], owner);
|
|
1332
|
+
};
|
|
1333
|
+
const children = nodes.flatMap((node) => {
|
|
1334
|
+
if (node.kind === "object") return Object.hasOwn(node.shape, property) ? [node.shape[property]] : [];
|
|
1335
|
+
if (node.kind !== "variant") return [];
|
|
1336
|
+
if (property === node.tag) return [Object.freeze({ kind: "field" })];
|
|
1337
|
+
return Object.values(node.variants).flatMap((branch) => Object.hasOwn(branch.shape, property) ? [branch.shape[property]] : []);
|
|
1338
|
+
});
|
|
1339
|
+
if (!children.length) throw new TypeError(`Invalid schema path segment: ${property}`);
|
|
1340
|
+
return pathProxy(children, [...address, property], owner);
|
|
1341
|
+
} });
|
|
1342
|
+
paths.set(proxy, {
|
|
1343
|
+
nodes,
|
|
1344
|
+
address,
|
|
1345
|
+
owner,
|
|
1346
|
+
...treeSelection ? { tree: treeSelection } : {}
|
|
1347
|
+
});
|
|
1348
|
+
return proxy;
|
|
1349
|
+
};
|
|
1350
|
+
const compilePath = (owner, kind, pick) => {
|
|
1351
|
+
const scope = {};
|
|
1352
|
+
const value = pick(pathProxy([owner], [], scope));
|
|
1353
|
+
const selected = typeof value === "object" && value !== null ? paths.get(value) : void 0;
|
|
1354
|
+
if (!selected || selected.owner !== scope) throw new TypeError("Selector must return a path from its callback.");
|
|
1355
|
+
const collection = selected.tree?.kind === "nodes" || selected.nodes.every((node) => collectionEntryNode(node) !== void 0);
|
|
1356
|
+
if (kind === "collection" && !collection) throw new TypeError("Collection selectors require a map, table, list or tree nodes path.");
|
|
1357
|
+
return Object.freeze({
|
|
1358
|
+
kind: kind === "auto" ? collection ? "collection" : "value" : kind,
|
|
1359
|
+
schema: owner,
|
|
1360
|
+
address: Object.freeze([...selected.address]),
|
|
1361
|
+
...selected.tree ? { tree: Object.freeze({ ...selected.tree }) } : {}
|
|
1362
|
+
});
|
|
1363
|
+
};
|
|
1364
|
+
//#endregion
|
|
891
1365
|
//#region core/src/address/relation.ts
|
|
892
1366
|
const contains = (parent, child) => {
|
|
893
1367
|
profile.address.prefixComparison();
|
|
@@ -1194,7 +1668,7 @@ const installMember = (parent, key, present, value) => {
|
|
|
1194
1668
|
const index = Number(key);
|
|
1195
1669
|
if (present) if (index < 0) insert$2(parent, parent.length, value);
|
|
1196
1670
|
else parent[index] = value;
|
|
1197
|
-
else if (index >= 0) remove$
|
|
1671
|
+
else if (index >= 0) remove$3(parent, index);
|
|
1198
1672
|
} else installOwn(parent, key, present, value);
|
|
1199
1673
|
};
|
|
1200
1674
|
const orderOf = (node, value) => {
|
|
@@ -1749,6 +2223,16 @@ function apply(session, changes, direction) {
|
|
|
1749
2223
|
}
|
|
1750
2224
|
}
|
|
1751
2225
|
//#endregion
|
|
2226
|
+
//#region core/src/runtime/readable.ts
|
|
2227
|
+
const readonlyDocument = (runtime) => {
|
|
2228
|
+
const readable = {
|
|
2229
|
+
revision: runtime.revision,
|
|
2230
|
+
subscribe: runtime.subscribe
|
|
2231
|
+
};
|
|
2232
|
+
bindContext(readable, contextOf(runtime));
|
|
2233
|
+
return Object.freeze(readable);
|
|
2234
|
+
};
|
|
2235
|
+
//#endregion
|
|
1752
2236
|
//#region core/src/runtime/notification.ts
|
|
1753
2237
|
const attachProjection = (notification, processor) => {
|
|
1754
2238
|
const entry = {
|
|
@@ -1912,18 +2396,19 @@ const createNotificationCenter = (schema) => {
|
|
|
1912
2396
|
//#endregion
|
|
1913
2397
|
//#region core/src/runtime.ts
|
|
1914
2398
|
const createDocument = (input) => {
|
|
1915
|
-
|
|
1916
|
-
const invalid = checkValue(
|
|
2399
|
+
const schema = schemaNodeOf(input.schema);
|
|
2400
|
+
const invalid = checkValue(schema, input.initial);
|
|
1917
2401
|
if (invalid) throw new ParseError(invalid);
|
|
1918
2402
|
const state = {
|
|
1919
|
-
schema
|
|
1920
|
-
document: copyValue(
|
|
2403
|
+
schema,
|
|
2404
|
+
document: copyValue(schema, input.initial),
|
|
1921
2405
|
disposed: false,
|
|
1922
2406
|
projectionLocks: 0
|
|
1923
2407
|
};
|
|
1924
2408
|
let revision = 0, busy = false;
|
|
1925
|
-
const notifications = createNotificationCenter(
|
|
2409
|
+
const notifications = createNotificationCenter(schema);
|
|
1926
2410
|
let runtime, context;
|
|
2411
|
+
let readonlyAlias;
|
|
1927
2412
|
const idle = () => {
|
|
1928
2413
|
if (state.disposed) throw new DocumentDisposedError();
|
|
1929
2414
|
if (busy || state.projectionLocks) throw new DocumentReentrancyError();
|
|
@@ -1956,7 +2441,7 @@ const createDocument = (input) => {
|
|
|
1956
2441
|
revision: ++revision,
|
|
1957
2442
|
source,
|
|
1958
2443
|
changes,
|
|
1959
|
-
impact: createImpact(
|
|
2444
|
+
impact: createImpact(schema, changes)
|
|
1960
2445
|
};
|
|
1961
2446
|
if (source === "remote") history.invalidate();
|
|
1962
2447
|
else if (recordHistory && (source === "local" || source === "system")) history.record(changes);
|
|
@@ -2003,6 +2488,7 @@ const createDocument = (input) => {
|
|
|
2003
2488
|
runtime = {
|
|
2004
2489
|
schema: input.schema,
|
|
2005
2490
|
revision: () => revision,
|
|
2491
|
+
readonly: () => readonlyAlias ??= readonlyDocument(runtime),
|
|
2006
2492
|
update: (run, options) => {
|
|
2007
2493
|
const source = options?.source ?? "local";
|
|
2008
2494
|
let value;
|
|
@@ -2021,13 +2507,16 @@ const createDocument = (input) => {
|
|
|
2021
2507
|
value
|
|
2022
2508
|
};
|
|
2023
2509
|
},
|
|
2024
|
-
apply: (inputChanges, options) =>
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2510
|
+
apply: (inputChanges, options) => {
|
|
2511
|
+
const source = options?.source ?? "local";
|
|
2512
|
+
return mutate({
|
|
2513
|
+
kind: "apply",
|
|
2514
|
+
source
|
|
2515
|
+
}, source === "remote" ? false : options?.history ?? true, (session) => {
|
|
2516
|
+
if (!options || options.expectedRevision !== revision) fail([], "baseline-mismatch", "apply requires the current expectedRevision.");
|
|
2517
|
+
apply(session, decodeChanges(inputChanges), "forward");
|
|
2518
|
+
});
|
|
2519
|
+
},
|
|
2031
2520
|
replace: (value, options) => {
|
|
2032
2521
|
const source = options?.source ?? "system";
|
|
2033
2522
|
return mutate({
|
|
@@ -2037,14 +2526,14 @@ const createDocument = (input) => {
|
|
|
2037
2526
|
},
|
|
2038
2527
|
snapshot: () => {
|
|
2039
2528
|
if (state.disposed) throw new DocumentDisposedError();
|
|
2040
|
-
return copyValue(
|
|
2529
|
+
return copyValue(schema, state.document);
|
|
2041
2530
|
},
|
|
2042
2531
|
subscribe: ((pick, listener) => {
|
|
2043
2532
|
if (state.disposed) throw new DocumentDisposedError();
|
|
2044
2533
|
if (!listener) return notifications.subscribe(pick);
|
|
2045
2534
|
const picks = Array.isArray(pick) ? pick : [pick];
|
|
2046
2535
|
if (!picks.length) throw new TypeError("Expected at least one subscription path.");
|
|
2047
|
-
return notifications.subscribeTargets(picks.map((p) => compilePath(
|
|
2536
|
+
return notifications.subscribeTargets(picks.map((p) => compilePath(schema, "value", p)), listener);
|
|
2048
2537
|
}),
|
|
2049
2538
|
history: history.api,
|
|
2050
2539
|
dispose: () => {
|
|
@@ -2071,22 +2560,20 @@ const createDocument = (input) => {
|
|
|
2071
2560
|
return runtime;
|
|
2072
2561
|
};
|
|
2073
2562
|
//#endregion
|
|
2074
|
-
//#region core/src/runtime/readable.ts
|
|
2075
|
-
const asReadable = (runtime) => {
|
|
2076
|
-
const readable = {
|
|
2077
|
-
revision: runtime.revision,
|
|
2078
|
-
subscribe: runtime.subscribe
|
|
2079
|
-
};
|
|
2080
|
-
bindContext(readable, contextOf(runtime));
|
|
2081
|
-
return Object.freeze(readable);
|
|
2082
|
-
};
|
|
2083
|
-
//#endregion
|
|
2084
2563
|
//#region core/src/access/dependency.ts
|
|
2085
2564
|
const createDependencyTracker = () => {
|
|
2086
2565
|
const targets = [];
|
|
2566
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
2087
2567
|
return {
|
|
2088
2568
|
record: (value) => {
|
|
2089
|
-
|
|
2569
|
+
const key = dependencyBucket(value);
|
|
2570
|
+
let entries = buckets.get(key);
|
|
2571
|
+
if (!entries) {
|
|
2572
|
+
entries = [];
|
|
2573
|
+
buckets.set(key, entries);
|
|
2574
|
+
} else if (entries.some((entry) => same(entry, value))) return;
|
|
2575
|
+
entries.push(value);
|
|
2576
|
+
targets.push(value);
|
|
2090
2577
|
},
|
|
2091
2578
|
snapshot: () => Object.freeze(targets.slice())
|
|
2092
2579
|
};
|
|
@@ -2175,6 +2662,281 @@ const select = (document, selector, equality = Object.is) => {
|
|
|
2175
2662
|
});
|
|
2176
2663
|
};
|
|
2177
2664
|
//#endregion
|
|
2665
|
+
//#region core/src/projection/input.ts
|
|
2666
|
+
const valueInput = (initial, equality = Object.is) => defineSource({
|
|
2667
|
+
kind: "value-input",
|
|
2668
|
+
initial,
|
|
2669
|
+
equality
|
|
2670
|
+
}, {
|
|
2671
|
+
kind: "value",
|
|
2672
|
+
equality
|
|
2673
|
+
});
|
|
2674
|
+
const collectionInput = (initial = /* @__PURE__ */ new Map(), equality = Object.is) => {
|
|
2675
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2676
|
+
for (const [key, value] of initial) {
|
|
2677
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
2678
|
+
entries.set(key, value);
|
|
2679
|
+
}
|
|
2680
|
+
return defineSource({
|
|
2681
|
+
kind: "collection-input",
|
|
2682
|
+
initial: entries,
|
|
2683
|
+
equality
|
|
2684
|
+
}, {
|
|
2685
|
+
kind: "collection",
|
|
2686
|
+
equality
|
|
2687
|
+
});
|
|
2688
|
+
};
|
|
2689
|
+
const input = Object.assign(valueInput, { collection: collectionInput });
|
|
2690
|
+
//#endregion
|
|
2691
|
+
//#region core/src/projection/observe.ts
|
|
2692
|
+
function observe(source, selector) {
|
|
2693
|
+
if (isExternalSource(source)) return source.kind === "collection" ? defineSource({
|
|
2694
|
+
kind: "external-collection",
|
|
2695
|
+
source
|
|
2696
|
+
}, {
|
|
2697
|
+
kind: "collection",
|
|
2698
|
+
equality: Object.is
|
|
2699
|
+
}) : defineSource({
|
|
2700
|
+
kind: "external-value",
|
|
2701
|
+
source,
|
|
2702
|
+
equality: Object.is
|
|
2703
|
+
}, {
|
|
2704
|
+
kind: "value",
|
|
2705
|
+
equality: Object.is
|
|
2706
|
+
});
|
|
2707
|
+
if ("current" in source && typeof source.current === "function") return defineSource({
|
|
2708
|
+
kind: "readable",
|
|
2709
|
+
readable: source,
|
|
2710
|
+
equality: Object.is
|
|
2711
|
+
}, {
|
|
2712
|
+
kind: "value",
|
|
2713
|
+
equality: Object.is
|
|
2714
|
+
});
|
|
2715
|
+
const document = source;
|
|
2716
|
+
const state = contextOf(document).state;
|
|
2717
|
+
const selected = selector ? compilePath(state.schema, "auto", selector) : Object.freeze({
|
|
2718
|
+
kind: "value",
|
|
2719
|
+
schema: state.schema,
|
|
2720
|
+
address: Object.freeze([])
|
|
2721
|
+
});
|
|
2722
|
+
return defineSource({
|
|
2723
|
+
kind: "document",
|
|
2724
|
+
document,
|
|
2725
|
+
selector: selected
|
|
2726
|
+
}, {
|
|
2727
|
+
kind: selected.kind,
|
|
2728
|
+
equality: Object.is
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
const isExternalSource = (source) => {
|
|
2732
|
+
if (!source || typeof source !== "object") return false;
|
|
2733
|
+
const candidate = source;
|
|
2734
|
+
return (candidate.kind === "value" || candidate.kind === "collection") && typeof candidate.current === "function" && typeof candidate.revision === "function" && typeof candidate.subscribe === "function";
|
|
2735
|
+
};
|
|
2736
|
+
//#endregion
|
|
2737
|
+
//#region core/src/projection/derive.ts
|
|
2738
|
+
const sourceValue = (source) => source.kind === "value" ? source.value : snapshotCollectionView(source.read);
|
|
2739
|
+
const outputKind = (projection) => {
|
|
2740
|
+
const ref = projectionRef(projection);
|
|
2741
|
+
const definition = ref.producer;
|
|
2742
|
+
const output = definition.kind === "source" ? definition.output : definition.outputs[ref.output];
|
|
2743
|
+
if (!output) throw new TypeError("Projection output does not exist.");
|
|
2744
|
+
return output.kind;
|
|
2745
|
+
};
|
|
2746
|
+
const assertKeyedProjection = (projection, role) => {
|
|
2747
|
+
if (outputKind(projection) !== "collection") throw new TypeError(`${role} must be a keyed collection projection.`);
|
|
2748
|
+
};
|
|
2749
|
+
const isKeyedDependency = (value) => {
|
|
2750
|
+
if (!value || typeof value !== "object" || isProjection(value)) return false;
|
|
2751
|
+
const candidate = value;
|
|
2752
|
+
return isProjection(candidate.source) && typeof candidate.key === "function";
|
|
2753
|
+
};
|
|
2754
|
+
const bindKey = (index, outputKey, sourceKey) => {
|
|
2755
|
+
const previous = index.forward.get(outputKey);
|
|
2756
|
+
if (previous === sourceKey) return;
|
|
2757
|
+
if (previous !== void 0) {
|
|
2758
|
+
const dependents = index.reverse.get(previous);
|
|
2759
|
+
dependents?.delete(outputKey);
|
|
2760
|
+
if (dependents?.size === 0) index.reverse.delete(previous);
|
|
2761
|
+
index.forward.delete(outputKey);
|
|
2762
|
+
}
|
|
2763
|
+
if (sourceKey === void 0) return;
|
|
2764
|
+
index.forward.set(outputKey, sourceKey);
|
|
2765
|
+
const dependents = index.reverse.get(sourceKey);
|
|
2766
|
+
if (dependents) dependents.add(outputKey);
|
|
2767
|
+
else index.reverse.set(sourceKey, new Set([outputKey]));
|
|
2768
|
+
};
|
|
2769
|
+
const unbindKey = (indexes, outputKey) => {
|
|
2770
|
+
for (const index of indexes) bindKey(index, outputKey, void 0);
|
|
2771
|
+
};
|
|
2772
|
+
const createBindingIndex = () => ({
|
|
2773
|
+
forward: /* @__PURE__ */ new Map(),
|
|
2774
|
+
reverse: /* @__PURE__ */ new Map()
|
|
2775
|
+
});
|
|
2776
|
+
function createValueDerive(dependencies, compute, equality = Object.is) {
|
|
2777
|
+
if (!isPlainObject(dependencies) || isProjection(dependencies)) throw new TypeError("Derive dependencies must be a plain object.");
|
|
2778
|
+
const names = Object.keys(dependencies);
|
|
2779
|
+
const ordered = names.map((name) => dependencies[name]);
|
|
2780
|
+
ordered.forEach(projectionRef);
|
|
2781
|
+
const [projection] = defineProcessor({
|
|
2782
|
+
dependencies: ordered,
|
|
2783
|
+
outputs: [{
|
|
2784
|
+
kind: "value",
|
|
2785
|
+
equality
|
|
2786
|
+
}],
|
|
2787
|
+
create: () => ({ evaluate: (evaluation) => {
|
|
2788
|
+
const output = evaluation.outputs[0];
|
|
2789
|
+
if (output.kind !== "value") throw new Error("derive requires a value output.");
|
|
2790
|
+
const values = Object.create(null);
|
|
2791
|
+
for (let index = 0; index < names.length; index++) values[names[index]] = sourceValue(evaluation.sources[index]);
|
|
2792
|
+
const next = compute(Object.freeze(values));
|
|
2793
|
+
assertSynchronous(next);
|
|
2794
|
+
output.output.set(next);
|
|
2795
|
+
} })
|
|
2796
|
+
});
|
|
2797
|
+
return projection;
|
|
2798
|
+
}
|
|
2799
|
+
function createKeyedDerive(source, dependenciesOrSelect, selectOrEquality, maybeEquality) {
|
|
2800
|
+
assertKeyedProjection(source, "Keyed derive source");
|
|
2801
|
+
const dependencySpecs = typeof dependenciesOrSelect === "function" ? void 0 : dependenciesOrSelect;
|
|
2802
|
+
const select = dependencySpecs === void 0 ? dependenciesOrSelect : selectOrEquality;
|
|
2803
|
+
const equality = dependencySpecs === void 0 ? selectOrEquality : maybeEquality;
|
|
2804
|
+
if (typeof select !== "function") throw new TypeError("Keyed derive requires a selector.");
|
|
2805
|
+
const dependencies = [source];
|
|
2806
|
+
const dependencyNames = [];
|
|
2807
|
+
const keyed = [];
|
|
2808
|
+
if (dependencySpecs !== void 0) {
|
|
2809
|
+
if (!isPlainObject(dependencySpecs) || isProjection(dependencySpecs)) throw new TypeError("Keyed derive dependencies must be a plain object.");
|
|
2810
|
+
for (const name of Reflect.ownKeys(dependencySpecs)) {
|
|
2811
|
+
if (typeof name !== "string") throw new TypeError("Keyed derive dependency names must be strings.");
|
|
2812
|
+
const descriptor = Object.getOwnPropertyDescriptor(dependencySpecs, name);
|
|
2813
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Keyed derive dependencies must be enumerable data properties.");
|
|
2814
|
+
const dependency = descriptor.value;
|
|
2815
|
+
dependencyNames.push(name);
|
|
2816
|
+
if (isProjection(dependency)) {
|
|
2817
|
+
dependencies.push(dependency);
|
|
2818
|
+
keyed.push(void 0);
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2821
|
+
if (!isKeyedDependency(dependency)) throw new TypeError("Keyed derive dependencies must be projections or keyed lookups.");
|
|
2822
|
+
assertKeyedProjection(dependency.source, "Dynamic keyed dependency source");
|
|
2823
|
+
dependencies.push(dependency.source);
|
|
2824
|
+
keyed.push(dependency);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
const [projection] = defineProcessor({
|
|
2828
|
+
dependencies,
|
|
2829
|
+
outputs: [{
|
|
2830
|
+
kind: "collection",
|
|
2831
|
+
equality: equality ?? Object.is
|
|
2832
|
+
}],
|
|
2833
|
+
create: () => {
|
|
2834
|
+
const bindings = keyed.map((dependency) => dependency ? createBindingIndex() : void 0);
|
|
2835
|
+
const bindingIndexes = bindings.filter((index) => index !== void 0);
|
|
2836
|
+
const globalValues = keyed.map(() => void 0);
|
|
2837
|
+
let revisions = Object.freeze([]);
|
|
2838
|
+
const clearBindings = () => {
|
|
2839
|
+
for (const index of bindings) {
|
|
2840
|
+
index?.forward.clear();
|
|
2841
|
+
index?.reverse.clear();
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
return {
|
|
2845
|
+
evaluate: (evaluation) => {
|
|
2846
|
+
const driver = evaluation.sources[0];
|
|
2847
|
+
const output = evaluation.outputs[0];
|
|
2848
|
+
if (driver.kind !== "collection" || output.kind !== "collection") throw new Error("Keyed derive requires collection input and output.");
|
|
2849
|
+
const currentRevisions = evaluation.sources.map((current) => current.revision);
|
|
2850
|
+
for (let index = 0; index < keyed.length; index++) {
|
|
2851
|
+
if (keyed[index]) continue;
|
|
2852
|
+
if (evaluation.reset || revisions[index + 1] !== currentRevisions[index + 1]) globalValues[index] = sourceValue(evaluation.sources[index + 1]);
|
|
2853
|
+
}
|
|
2854
|
+
const project = (key) => {
|
|
2855
|
+
if (!driver.read.has(key)) return;
|
|
2856
|
+
const value = driver.read.get(key);
|
|
2857
|
+
const dependencyValues = Object.create(null);
|
|
2858
|
+
for (let index = 0; index < keyed.length; index++) {
|
|
2859
|
+
const dependency = keyed[index];
|
|
2860
|
+
if (!dependency) {
|
|
2861
|
+
dependencyValues[dependencyNames[index]] = globalValues[index];
|
|
2862
|
+
continue;
|
|
2863
|
+
}
|
|
2864
|
+
const sourceContext = evaluation.sources[index + 1];
|
|
2865
|
+
if (sourceContext.kind !== "collection") throw new TypeError("Dynamic keyed dependency resolved to a non-collection source.");
|
|
2866
|
+
const selectedKey = dependency.key(value, key);
|
|
2867
|
+
assertSynchronous(selectedKey);
|
|
2868
|
+
if (selectedKey !== void 0 && typeof selectedKey !== "string") throw new TypeError("Dynamic keyed dependency keys must be strings or undefined.");
|
|
2869
|
+
bindKey(bindings[index], key, selectedKey);
|
|
2870
|
+
dependencyValues[dependencyNames[index]] = selectedKey === void 0 ? void 0 : sourceContext.read.get(selectedKey);
|
|
2871
|
+
}
|
|
2872
|
+
const next = dependencySpecs === void 0 ? select(value, key) : select(value, key, Object.freeze(dependencyValues));
|
|
2873
|
+
assertSynchronous(next);
|
|
2874
|
+
output.output.set(key, next);
|
|
2875
|
+
};
|
|
2876
|
+
if (evaluation.reset) {
|
|
2877
|
+
clearBindings();
|
|
2878
|
+
const ids = driver.read.ids();
|
|
2879
|
+
for (const key of ids) project(key);
|
|
2880
|
+
output.output.order(ids);
|
|
2881
|
+
revisions = Object.freeze(currentRevisions);
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
2885
|
+
let all = false;
|
|
2886
|
+
let updateOrder = false;
|
|
2887
|
+
if (revisions[0] !== currentRevisions[0]) {
|
|
2888
|
+
const change = driver.change;
|
|
2889
|
+
if (!change || change.kind === "reset") {
|
|
2890
|
+
all = true;
|
|
2891
|
+
updateOrder = true;
|
|
2892
|
+
} else {
|
|
2893
|
+
for (const entry of change.removed) {
|
|
2894
|
+
output.output.remove(entry.key);
|
|
2895
|
+
unbindKey(bindingIndexes, entry.key);
|
|
2896
|
+
dirty.delete(entry.key);
|
|
2897
|
+
}
|
|
2898
|
+
for (const entry of change.added) dirty.add(entry.key);
|
|
2899
|
+
for (const entry of change.updated) dirty.add(entry.key);
|
|
2900
|
+
updateOrder = Boolean(change.added.length || change.removed.length || change.order);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
for (let index = 0; index < keyed.length; index++) {
|
|
2904
|
+
if (revisions[index + 1] === currentRevisions[index + 1]) continue;
|
|
2905
|
+
if (!keyed[index]) {
|
|
2906
|
+
all = true;
|
|
2907
|
+
continue;
|
|
2908
|
+
}
|
|
2909
|
+
const sourceContext = evaluation.sources[index + 1];
|
|
2910
|
+
if (sourceContext.kind !== "collection" || !sourceContext.change) {
|
|
2911
|
+
all = true;
|
|
2912
|
+
continue;
|
|
2913
|
+
}
|
|
2914
|
+
if (sourceContext.change.kind === "reset") {
|
|
2915
|
+
all = true;
|
|
2916
|
+
continue;
|
|
2917
|
+
}
|
|
2918
|
+
const reverse = bindings[index].reverse;
|
|
2919
|
+
for (const transition of sourceContext.change.added) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
|
|
2920
|
+
for (const transition of sourceContext.change.updated) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
|
|
2921
|
+
for (const transition of sourceContext.change.removed) for (const outputKey of reverse.get(transition.key) ?? []) dirty.add(outputKey);
|
|
2922
|
+
}
|
|
2923
|
+
if (all) for (const key of driver.read.ids()) dirty.add(key);
|
|
2924
|
+
for (const key of dirty) if (driver.read.has(key)) project(key);
|
|
2925
|
+
if (updateOrder) output.output.order(driver.read.ids());
|
|
2926
|
+
revisions = Object.freeze(currentRevisions);
|
|
2927
|
+
},
|
|
2928
|
+
release: () => {
|
|
2929
|
+
clearBindings();
|
|
2930
|
+
revisions = Object.freeze([]);
|
|
2931
|
+
}
|
|
2932
|
+
};
|
|
2933
|
+
},
|
|
2934
|
+
name: "derive.keyed"
|
|
2935
|
+
});
|
|
2936
|
+
return projection;
|
|
2937
|
+
}
|
|
2938
|
+
const derive = Object.assign(createValueDerive, { keyed: createKeyedDerive });
|
|
2939
|
+
//#endregion
|
|
2178
2940
|
//#region core/src/projection/collection/change.ts
|
|
2179
2941
|
const commonOrderChanged = (before, after, added, removed) => {
|
|
2180
2942
|
const beforeCommon = before.filter((key) => !removed.has(key));
|
|
@@ -2373,7 +3135,7 @@ const createPublishedCollection = () => {
|
|
|
2373
3135
|
};
|
|
2374
3136
|
};
|
|
2375
3137
|
/** Owns staged keyed intent, exact transitions and published collection state. */
|
|
2376
|
-
const createCollectionOutput = () => {
|
|
3138
|
+
const createCollectionOutput = (binding) => {
|
|
2377
3139
|
const published = createPublishedCollection();
|
|
2378
3140
|
let staged = /* @__PURE__ */ new Map();
|
|
2379
3141
|
let nextIds = published.ids();
|
|
@@ -2384,6 +3146,7 @@ const createCollectionOutput = () => {
|
|
|
2384
3146
|
let explicitOrder;
|
|
2385
3147
|
let cleared = false;
|
|
2386
3148
|
const listeners = /* @__PURE__ */ new Set();
|
|
3149
|
+
const consumers = /* @__PURE__ */ new Set();
|
|
2387
3150
|
const hasNext = (key) => staged.has(key) ? staged.get(key).present : !cleared && published.has(key);
|
|
2388
3151
|
const getNext = (key) => {
|
|
2389
3152
|
const entry = staged.get(key);
|
|
@@ -2461,11 +3224,10 @@ const createCollectionOutput = () => {
|
|
|
2461
3224
|
if (new Set(order).size !== order.length || order.length !== published.size() + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
|
|
2462
3225
|
nextIds = ids.length === order.length && ids.every((key, index) => key === order[index]) ? ids : Object.freeze([...order]);
|
|
2463
3226
|
} else nextIds = ids;
|
|
2464
|
-
const changedKeys = new Set(
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
]);
|
|
3227
|
+
const changedKeys = /* @__PURE__ */ new Set();
|
|
3228
|
+
added.forEach((key) => changedKeys.add(key));
|
|
3229
|
+
removed.forEach((key) => changedKeys.add(key));
|
|
3230
|
+
updated.forEach((key) => changedKeys.add(key));
|
|
2469
3231
|
profile.projection("changedKeys", changedKeys.size);
|
|
2470
3232
|
const addedEntries = [];
|
|
2471
3233
|
const updatedEntries = [];
|
|
@@ -2474,7 +3236,8 @@ const createCollectionOutput = () => {
|
|
|
2474
3236
|
const beforePresent = published.has(key);
|
|
2475
3237
|
const afterPresent = hasNext(key);
|
|
2476
3238
|
if (beforePresent === afterPresent) {
|
|
2477
|
-
if (!beforePresent
|
|
3239
|
+
if (!beforePresent) continue;
|
|
3240
|
+
if (isEqual !== Object.is && isEqual(published.get(key), getNext(key))) continue;
|
|
2478
3241
|
}
|
|
2479
3242
|
if (!beforePresent) addedEntries.push({
|
|
2480
3243
|
key,
|
|
@@ -2522,10 +3285,14 @@ const createCollectionOutput = () => {
|
|
|
2522
3285
|
cause
|
|
2523
3286
|
});
|
|
2524
3287
|
return {
|
|
3288
|
+
kind: "collection",
|
|
3289
|
+
get owner() {
|
|
3290
|
+
return binding.owner();
|
|
3291
|
+
},
|
|
2525
3292
|
begin,
|
|
2526
3293
|
seal,
|
|
2527
|
-
context,
|
|
2528
|
-
current: (
|
|
3294
|
+
context: (active) => context(active, binding.cause()),
|
|
3295
|
+
current: () => published.read(binding.check),
|
|
2529
3296
|
revision: () => revision,
|
|
2530
3297
|
reset: () => reset,
|
|
2531
3298
|
publish: () => {
|
|
@@ -2534,9 +3301,12 @@ const createCollectionOutput = () => {
|
|
|
2534
3301
|
initialized = true;
|
|
2535
3302
|
},
|
|
2536
3303
|
emit: (call) => {
|
|
2537
|
-
|
|
2538
|
-
Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
|
|
3304
|
+
Array.from(listeners).forEach((listener) => call(listener));
|
|
2539
3305
|
},
|
|
3306
|
+
hasConsumers: () => consumers.size > 0,
|
|
3307
|
+
forEachConsumer: (run) => consumers.forEach(run),
|
|
3308
|
+
attachConsumer: (consumer) => consumers.add(consumer),
|
|
3309
|
+
detachConsumer: (consumer) => consumers.delete(consumer),
|
|
2540
3310
|
clear: () => {
|
|
2541
3311
|
staged.clear();
|
|
2542
3312
|
nextIds = published.ids();
|
|
@@ -2549,6 +3319,7 @@ const createCollectionOutput = () => {
|
|
|
2549
3319
|
published.release();
|
|
2550
3320
|
staged.clear();
|
|
2551
3321
|
listeners.clear();
|
|
3322
|
+
consumers.clear();
|
|
2552
3323
|
nextIds = Object.freeze([]);
|
|
2553
3324
|
change = void 0;
|
|
2554
3325
|
initialized = false;
|
|
@@ -2557,15 +3328,18 @@ const createCollectionOutput = () => {
|
|
|
2557
3328
|
explicitOrder = void 0;
|
|
2558
3329
|
cleared = false;
|
|
2559
3330
|
},
|
|
2560
|
-
subscribe: (listener) =>
|
|
2561
|
-
|
|
3331
|
+
subscribe: (listener) => {
|
|
3332
|
+
binding.check();
|
|
3333
|
+
listeners.add(listener);
|
|
3334
|
+
return () => listeners.delete(listener);
|
|
3335
|
+
}
|
|
2562
3336
|
};
|
|
2563
3337
|
};
|
|
2564
3338
|
//#endregion
|
|
2565
3339
|
//#region core/src/projection/output/value.ts
|
|
2566
3340
|
const unset = Symbol("projection-value-unset");
|
|
2567
3341
|
/** Owns staged and published state for one scalar Projection output. */
|
|
2568
|
-
const createValueOutput = () => {
|
|
3342
|
+
const createValueOutput = (binding) => {
|
|
2569
3343
|
let value;
|
|
2570
3344
|
let staged = unset;
|
|
2571
3345
|
let next;
|
|
@@ -2574,6 +3348,7 @@ const createValueOutput = () => {
|
|
|
2574
3348
|
let changed = false;
|
|
2575
3349
|
let reset = false;
|
|
2576
3350
|
const listeners = /* @__PURE__ */ new Set();
|
|
3351
|
+
const consumers = /* @__PURE__ */ new Set();
|
|
2577
3352
|
const begin = (active, initialize) => {
|
|
2578
3353
|
staged = unset;
|
|
2579
3354
|
next = value;
|
|
@@ -2607,20 +3382,24 @@ const createValueOutput = () => {
|
|
|
2607
3382
|
return changed;
|
|
2608
3383
|
};
|
|
2609
3384
|
return {
|
|
3385
|
+
kind: "value",
|
|
3386
|
+
get owner() {
|
|
3387
|
+
return binding.owner();
|
|
3388
|
+
},
|
|
2610
3389
|
begin,
|
|
2611
3390
|
seal,
|
|
2612
|
-
context: (active
|
|
3391
|
+
context: (active) => {
|
|
2613
3392
|
assertScope(active);
|
|
2614
3393
|
return Object.freeze({
|
|
2615
3394
|
kind: "value",
|
|
2616
3395
|
value: next,
|
|
2617
3396
|
revision,
|
|
2618
3397
|
reset,
|
|
2619
|
-
cause
|
|
3398
|
+
cause: binding.cause()
|
|
2620
3399
|
});
|
|
2621
3400
|
},
|
|
2622
|
-
current: (
|
|
2623
|
-
check();
|
|
3401
|
+
current: () => {
|
|
3402
|
+
binding.check();
|
|
2624
3403
|
return value;
|
|
2625
3404
|
},
|
|
2626
3405
|
revision: () => revision,
|
|
@@ -2634,6 +3413,10 @@ const createValueOutput = () => {
|
|
|
2634
3413
|
profile.materialized.notification();
|
|
2635
3414
|
Array.from(listeners).forEach((listener) => call(listener));
|
|
2636
3415
|
},
|
|
3416
|
+
hasConsumers: () => consumers.size > 0,
|
|
3417
|
+
forEachConsumer: (run) => consumers.forEach(run),
|
|
3418
|
+
attachConsumer: (consumer) => consumers.add(consumer),
|
|
3419
|
+
detachConsumer: (consumer) => consumers.delete(consumer),
|
|
2637
3420
|
clear: () => {
|
|
2638
3421
|
staged = unset;
|
|
2639
3422
|
next = value;
|
|
@@ -2642,6 +3425,7 @@ const createValueOutput = () => {
|
|
|
2642
3425
|
},
|
|
2643
3426
|
release: () => {
|
|
2644
3427
|
listeners.clear();
|
|
3428
|
+
consumers.clear();
|
|
2645
3429
|
staged = unset;
|
|
2646
3430
|
value = next = void 0;
|
|
2647
3431
|
initialized = false;
|
|
@@ -2649,8 +3433,11 @@ const createValueOutput = () => {
|
|
|
2649
3433
|
changed = false;
|
|
2650
3434
|
reset = false;
|
|
2651
3435
|
},
|
|
2652
|
-
subscribe: (listener) =>
|
|
2653
|
-
|
|
3436
|
+
subscribe: (listener) => {
|
|
3437
|
+
binding.check();
|
|
3438
|
+
listeners.add(listener);
|
|
3439
|
+
return () => listeners.delete(listener);
|
|
3440
|
+
}
|
|
2654
3441
|
};
|
|
2655
3442
|
};
|
|
2656
3443
|
//#endregion
|
|
@@ -2666,59 +3453,23 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
2666
3453
|
if (processor.fault) throw processor.fault;
|
|
2667
3454
|
};
|
|
2668
3455
|
const bound = definition.outputs.map((outputDefinition) => {
|
|
2669
|
-
if (outputDefinition.kind === "value") {
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
return processor;
|
|
2679
|
-
},
|
|
2680
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
2681
|
-
context: (active) => state.context(active, cause),
|
|
2682
|
-
current: () => state.current(check),
|
|
2683
|
-
revision: state.revision,
|
|
2684
|
-
reset: state.reset,
|
|
2685
|
-
subscribe: (listener) => {
|
|
2686
|
-
check();
|
|
2687
|
-
const wrapped = () => listener();
|
|
2688
|
-
state.subscribe(wrapped);
|
|
2689
|
-
return () => state.unsubscribe(wrapped);
|
|
2690
|
-
},
|
|
2691
|
-
emit: state.emit,
|
|
2692
|
-
clear: state.clear,
|
|
2693
|
-
release: state.release
|
|
2694
|
-
}
|
|
2695
|
-
};
|
|
2696
|
-
}
|
|
2697
|
-
const state = createCollectionOutput();
|
|
3456
|
+
if (outputDefinition.kind === "value") return {
|
|
3457
|
+
kind: "value",
|
|
3458
|
+
definition: outputDefinition,
|
|
3459
|
+
state: createValueOutput({
|
|
3460
|
+
owner: () => processor,
|
|
3461
|
+
check,
|
|
3462
|
+
cause: () => cause
|
|
3463
|
+
})
|
|
3464
|
+
};
|
|
2698
3465
|
return {
|
|
2699
3466
|
kind: "collection",
|
|
2700
3467
|
definition: outputDefinition,
|
|
2701
|
-
state
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
},
|
|
2707
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
2708
|
-
context: (active) => state.context(active, cause),
|
|
2709
|
-
current: () => state.current(check),
|
|
2710
|
-
revision: state.revision,
|
|
2711
|
-
reset: state.reset,
|
|
2712
|
-
subscribe: (listener) => {
|
|
2713
|
-
check();
|
|
2714
|
-
const wrapped = (change) => listener(change);
|
|
2715
|
-
state.subscribe(wrapped);
|
|
2716
|
-
return () => state.unsubscribe(wrapped);
|
|
2717
|
-
},
|
|
2718
|
-
emit: state.emit,
|
|
2719
|
-
clear: state.clear,
|
|
2720
|
-
release: state.release
|
|
2721
|
-
}
|
|
3468
|
+
state: createCollectionOutput({
|
|
3469
|
+
owner: () => processor,
|
|
3470
|
+
check,
|
|
3471
|
+
cause: () => cause
|
|
3472
|
+
})
|
|
2722
3473
|
};
|
|
2723
3474
|
});
|
|
2724
3475
|
const begin = (active, initialize) => Object.freeze(bound.map((entry) => {
|
|
@@ -2737,40 +3488,28 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
2737
3488
|
}));
|
|
2738
3489
|
const seal = (reset) => {
|
|
2739
3490
|
const changed = [];
|
|
2740
|
-
for (const entry of bound) if (entry.state.seal(reset, entry.definition.equality)) changed.push(entry.
|
|
3491
|
+
for (const entry of bound) if (entry.state.seal(reset, entry.definition.equality)) changed.push(entry.state);
|
|
2741
3492
|
return Object.freeze(changed);
|
|
2742
3493
|
};
|
|
2743
|
-
const run = (reset, runCause) => {
|
|
3494
|
+
const run = (reset, runCause, recreate = false) => {
|
|
2744
3495
|
cause = runCause;
|
|
2745
3496
|
let active = true;
|
|
2746
3497
|
try {
|
|
2747
3498
|
const scopeActive = () => active;
|
|
2748
3499
|
const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive)));
|
|
2749
|
-
|
|
3500
|
+
const outputs = begin(scopeActive, reset);
|
|
3501
|
+
if (recreate && instance) {
|
|
3502
|
+
instance.release?.();
|
|
3503
|
+
instance = void 0;
|
|
3504
|
+
}
|
|
2750
3505
|
if (!instance) instance = definition.create();
|
|
2751
3506
|
profile.materialized[reset ? "rebuilt" : "updated"]();
|
|
2752
|
-
|
|
3507
|
+
assertSynchronous(instance.evaluate({
|
|
2753
3508
|
reset,
|
|
2754
3509
|
cause: runCause,
|
|
2755
3510
|
sources,
|
|
2756
3511
|
outputs
|
|
2757
|
-
});
|
|
2758
|
-
assertSynchronous(result);
|
|
2759
|
-
if (result?.kind === "rebuild") {
|
|
2760
|
-
if (reset) throw new TypeError("Initial or reset projection processor cannot rebuild.");
|
|
2761
|
-
instance.release?.();
|
|
2762
|
-
instance = definition.create();
|
|
2763
|
-
outputs = begin(scopeActive, true);
|
|
2764
|
-
result = instance.evaluate({
|
|
2765
|
-
reset: true,
|
|
2766
|
-
cause: runCause,
|
|
2767
|
-
sources,
|
|
2768
|
-
outputs
|
|
2769
|
-
});
|
|
2770
|
-
assertSynchronous(result);
|
|
2771
|
-
if (result?.kind === "rebuild") throw new TypeError("Reset projection processor cannot rebuild again.");
|
|
2772
|
-
return seal(true);
|
|
2773
|
-
}
|
|
3512
|
+
}));
|
|
2774
3513
|
return seal(reset);
|
|
2775
3514
|
} finally {
|
|
2776
3515
|
active = false;
|
|
@@ -2782,7 +3521,7 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
2782
3521
|
order,
|
|
2783
3522
|
name: definition.name ? `${definition.name} (${order})` : `projection-${order}`,
|
|
2784
3523
|
dependencies: Object.freeze([...dependencies]),
|
|
2785
|
-
outputs: Object.freeze(bound.map((entry) => entry.
|
|
3524
|
+
outputs: Object.freeze(bound.map((entry) => entry.state)),
|
|
2786
3525
|
fault: void 0,
|
|
2787
3526
|
disposed: false,
|
|
2788
3527
|
evaluate: run,
|
|
@@ -2792,13 +3531,21 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
2792
3531
|
cause = void 0;
|
|
2793
3532
|
},
|
|
2794
3533
|
release: () => {
|
|
2795
|
-
instance
|
|
3534
|
+
const current = instance;
|
|
2796
3535
|
instance = void 0;
|
|
2797
|
-
|
|
2798
|
-
|
|
3536
|
+
const failures = [];
|
|
3537
|
+
try {
|
|
3538
|
+
current?.release?.();
|
|
3539
|
+
} catch (error) {
|
|
3540
|
+
failures.push(error);
|
|
3541
|
+
}
|
|
3542
|
+
for (const entry of bound) try {
|
|
2799
3543
|
entry.state.release();
|
|
2800
|
-
})
|
|
3544
|
+
} catch (error) {
|
|
3545
|
+
failures.push(error);
|
|
3546
|
+
}
|
|
2801
3547
|
cause = void 0;
|
|
3548
|
+
if (failures.length) throw failures[0];
|
|
2802
3549
|
}
|
|
2803
3550
|
};
|
|
2804
3551
|
try {
|
|
@@ -2808,11 +3555,14 @@ const createProcessor = (scheduler, definition, dependencies) => {
|
|
|
2808
3555
|
if (processor.evaluate(true, void 0).length) processor.publish();
|
|
2809
3556
|
processor.clear();
|
|
2810
3557
|
});
|
|
3558
|
+
scheduler.addProcessor(processor);
|
|
2811
3559
|
} catch (error) {
|
|
2812
|
-
processor.
|
|
3560
|
+
processor.disposed = true;
|
|
3561
|
+
try {
|
|
3562
|
+
processor.release();
|
|
3563
|
+
} catch {}
|
|
2813
3564
|
throw error;
|
|
2814
3565
|
}
|
|
2815
|
-
scheduler.addProcessor(processor);
|
|
2816
3566
|
return processor;
|
|
2817
3567
|
};
|
|
2818
3568
|
//#endregion
|
|
@@ -2978,53 +3728,7 @@ const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
|
2978
3728
|
};
|
|
2979
3729
|
//#endregion
|
|
2980
3730
|
//#region core/src/projection/source/materialization.ts
|
|
2981
|
-
const createDocumentDirty = () => ({
|
|
2982
|
-
replace: false,
|
|
2983
|
-
order: false,
|
|
2984
|
-
treeRoot: false,
|
|
2985
|
-
treeNodes: /* @__PURE__ */ new Set(),
|
|
2986
|
-
children: /* @__PURE__ */ new Map()
|
|
2987
|
-
});
|
|
2988
|
-
const clearDocumentDirty = (dirty) => {
|
|
2989
|
-
dirty.replace = false;
|
|
2990
|
-
dirty.order = false;
|
|
2991
|
-
dirty.treeRoot = false;
|
|
2992
|
-
dirty.treeNodes.clear();
|
|
2993
|
-
dirty.children.clear();
|
|
2994
|
-
};
|
|
2995
3731
|
const hasDocumentDirty = (dirty) => dirty.replace || dirty.order || dirty.treeRoot || dirty.treeNodes.size > 0 || dirty.children.size > 0;
|
|
2996
|
-
const locate = (dirty, path) => {
|
|
2997
|
-
let current = dirty;
|
|
2998
|
-
for (const key of path) {
|
|
2999
|
-
if (current.replace) return void 0;
|
|
3000
|
-
let child = current.children.get(key);
|
|
3001
|
-
if (!child) {
|
|
3002
|
-
child = createDocumentDirty();
|
|
3003
|
-
current.children.set(key, child);
|
|
3004
|
-
}
|
|
3005
|
-
current = child;
|
|
3006
|
-
}
|
|
3007
|
-
return current;
|
|
3008
|
-
};
|
|
3009
|
-
const markDocumentReplace = (dirty, path) => {
|
|
3010
|
-
const current = locate(dirty, path);
|
|
3011
|
-
if (!current) return;
|
|
3012
|
-
current.replace = true;
|
|
3013
|
-
current.order = false;
|
|
3014
|
-
current.treeRoot = false;
|
|
3015
|
-
current.treeNodes.clear();
|
|
3016
|
-
current.children.clear();
|
|
3017
|
-
};
|
|
3018
|
-
const markDocumentOrder = (dirty, path) => {
|
|
3019
|
-
const current = locate(dirty, path);
|
|
3020
|
-
if (current && !current.replace) current.order = true;
|
|
3021
|
-
};
|
|
3022
|
-
const markDocumentTree = (dirty, path, root, nodes) => {
|
|
3023
|
-
const current = locate(dirty, path);
|
|
3024
|
-
if (!current || current.replace) return;
|
|
3025
|
-
current.treeRoot ||= root;
|
|
3026
|
-
for (const id of nodes) current.treeNodes.add(id);
|
|
3027
|
-
};
|
|
3028
3732
|
const equivalent = (node, left, right) => node.kind === "field" ? Object.is(left, right) : equalValue(node, left, right);
|
|
3029
3733
|
const replaceValue = (node, previous, current) => equivalent(node, previous, current) ? previous : copyValue(node, current);
|
|
3030
3734
|
const cloneRecord = (value) => {
|
|
@@ -3187,6 +3891,52 @@ const materializeDocumentValue = (node, previous, current, dirty) => {
|
|
|
3187
3891
|
};
|
|
3188
3892
|
//#endregion
|
|
3189
3893
|
//#region core/src/projection/source/dirty.ts
|
|
3894
|
+
const createDocumentDirty = () => ({
|
|
3895
|
+
replace: false,
|
|
3896
|
+
order: false,
|
|
3897
|
+
treeRoot: false,
|
|
3898
|
+
treeNodes: /* @__PURE__ */ new Set(),
|
|
3899
|
+
children: /* @__PURE__ */ new Map()
|
|
3900
|
+
});
|
|
3901
|
+
const clearDocumentDirty = (dirty) => {
|
|
3902
|
+
dirty.replace = false;
|
|
3903
|
+
dirty.order = false;
|
|
3904
|
+
dirty.treeRoot = false;
|
|
3905
|
+
dirty.treeNodes.clear();
|
|
3906
|
+
dirty.children.clear();
|
|
3907
|
+
};
|
|
3908
|
+
const locate = (dirty, path) => {
|
|
3909
|
+
let current = dirty;
|
|
3910
|
+
for (const key of path) {
|
|
3911
|
+
if (current.replace) return void 0;
|
|
3912
|
+
let child = current.children.get(key);
|
|
3913
|
+
if (!child) {
|
|
3914
|
+
child = createDocumentDirty();
|
|
3915
|
+
current.children.set(key, child);
|
|
3916
|
+
}
|
|
3917
|
+
current = child;
|
|
3918
|
+
}
|
|
3919
|
+
return current;
|
|
3920
|
+
};
|
|
3921
|
+
const markDocumentReplace = (dirty, path) => {
|
|
3922
|
+
const current = locate(dirty, path);
|
|
3923
|
+
if (!current) return;
|
|
3924
|
+
current.replace = true;
|
|
3925
|
+
current.order = false;
|
|
3926
|
+
current.treeRoot = false;
|
|
3927
|
+
current.treeNodes.clear();
|
|
3928
|
+
current.children.clear();
|
|
3929
|
+
};
|
|
3930
|
+
const markDocumentOrder = (dirty, path) => {
|
|
3931
|
+
const current = locate(dirty, path);
|
|
3932
|
+
if (current && !current.replace) current.order = true;
|
|
3933
|
+
};
|
|
3934
|
+
const markDocumentTree = (dirty, path, root, nodes) => {
|
|
3935
|
+
const current = locate(dirty, path);
|
|
3936
|
+
if (!current || current.replace) return;
|
|
3937
|
+
current.treeRoot ||= root;
|
|
3938
|
+
for (const id of nodes) current.treeNodes.add(id);
|
|
3939
|
+
};
|
|
3190
3940
|
const createCollectionPending = () => ({
|
|
3191
3941
|
reset: false,
|
|
3192
3942
|
order: false,
|
|
@@ -3288,9 +4038,8 @@ const mergeCollectionChanges = (selector, pending, changes) => {
|
|
|
3288
4038
|
};
|
|
3289
4039
|
//#endregion
|
|
3290
4040
|
//#region core/src/projection/source/boundary.ts
|
|
3291
|
-
const sourceError = (
|
|
4041
|
+
const sourceError = (cause) => new ProjectionError("source", cause);
|
|
3292
4042
|
const createValueBoundary = (scheduler, name, initial, equality, prepare, clearSource) => {
|
|
3293
|
-
const state = createValueOutput();
|
|
3294
4043
|
let boundary;
|
|
3295
4044
|
let pendingReset = false;
|
|
3296
4045
|
let pendingCause;
|
|
@@ -3301,26 +4050,12 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
|
|
|
3301
4050
|
if (!scheduler.active || boundary.disposed) throw new ProjectionDisposedError();
|
|
3302
4051
|
if (boundary.fault) throw boundary.fault;
|
|
3303
4052
|
};
|
|
3304
|
-
const
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
context: (active) => state.context(active, contextCause),
|
|
3311
|
-
current: () => state.current(check),
|
|
3312
|
-
revision: state.revision,
|
|
3313
|
-
reset: state.reset,
|
|
3314
|
-
subscribe: (listener) => {
|
|
3315
|
-
check();
|
|
3316
|
-
const wrapped = () => listener();
|
|
3317
|
-
state.subscribe(wrapped);
|
|
3318
|
-
return () => state.unsubscribe(wrapped);
|
|
3319
|
-
},
|
|
3320
|
-
emit: state.emit,
|
|
3321
|
-
clear: state.clear,
|
|
3322
|
-
release: state.release
|
|
3323
|
-
};
|
|
4053
|
+
const state = createValueOutput({
|
|
4054
|
+
owner: () => boundary,
|
|
4055
|
+
check,
|
|
4056
|
+
cause: () => contextCause
|
|
4057
|
+
});
|
|
4058
|
+
const output = state;
|
|
3324
4059
|
boundary = {
|
|
3325
4060
|
kind: "source",
|
|
3326
4061
|
name,
|
|
@@ -3356,24 +4091,42 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
|
|
|
3356
4091
|
clearSource(failed);
|
|
3357
4092
|
},
|
|
3358
4093
|
release: () => {
|
|
3359
|
-
cleanup
|
|
4094
|
+
const detach = cleanup;
|
|
3360
4095
|
cleanup = void 0;
|
|
3361
|
-
|
|
3362
|
-
|
|
4096
|
+
const failures = [];
|
|
4097
|
+
try {
|
|
4098
|
+
detach?.();
|
|
4099
|
+
} catch (error) {
|
|
4100
|
+
failures.push(error);
|
|
4101
|
+
}
|
|
4102
|
+
try {
|
|
4103
|
+
state.release();
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
failures.push(error);
|
|
4106
|
+
}
|
|
4107
|
+
if (failures.length) throw failures[0];
|
|
3363
4108
|
}
|
|
3364
4109
|
};
|
|
3365
|
-
|
|
3366
|
-
|
|
4110
|
+
try {
|
|
4111
|
+
scheduler.initialize(() => {
|
|
4112
|
+
let active = true;
|
|
4113
|
+
try {
|
|
4114
|
+
state.begin(() => active, true).output.set(initial);
|
|
4115
|
+
state.seal(true, equality);
|
|
4116
|
+
state.publish();
|
|
4117
|
+
state.clear();
|
|
4118
|
+
} finally {
|
|
4119
|
+
active = false;
|
|
4120
|
+
}
|
|
4121
|
+
});
|
|
4122
|
+
scheduler.addSource(boundary);
|
|
4123
|
+
} catch (error) {
|
|
4124
|
+
boundary.disposed = true;
|
|
3367
4125
|
try {
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
} finally {
|
|
3373
|
-
active = false;
|
|
3374
|
-
}
|
|
3375
|
-
});
|
|
3376
|
-
scheduler.addSource(boundary);
|
|
4126
|
+
boundary.release();
|
|
4127
|
+
} catch {}
|
|
4128
|
+
throw error;
|
|
4129
|
+
}
|
|
3377
4130
|
const mark = (metadata = {}) => {
|
|
3378
4131
|
scheduler.assertIdle();
|
|
3379
4132
|
pendingReset ||= metadata.reset ?? false;
|
|
@@ -3382,7 +4135,7 @@ const createValueBoundary = (scheduler, name, initial, equality, prepare, clearS
|
|
|
3382
4135
|
scheduler.capture(boundary);
|
|
3383
4136
|
};
|
|
3384
4137
|
const fail = (cause) => {
|
|
3385
|
-
boundary.fault = sourceError(
|
|
4138
|
+
boundary.fault = sourceError(cause);
|
|
3386
4139
|
recovering = false;
|
|
3387
4140
|
scheduler.capture(boundary);
|
|
3388
4141
|
};
|
|
@@ -3408,7 +4161,7 @@ const stageCollectionRead = (state, read, active, options) => {
|
|
|
3408
4161
|
}
|
|
3409
4162
|
evaluation.output.order(ids);
|
|
3410
4163
|
} else {
|
|
3411
|
-
const previous = state.current(
|
|
4164
|
+
const previous = state.current();
|
|
3412
4165
|
const keys = options.candidates ? [...new Set(options.candidates)] : [...new Set([...previous.ids(), ...read.ids()])];
|
|
3413
4166
|
if (!options.candidates) profile.collectionView.idsScanned(keys.length);
|
|
3414
4167
|
for (const key of keys) if (read.has(key)) {
|
|
@@ -3424,7 +4177,6 @@ const stageCollectionRead = (state, read, active, options) => {
|
|
|
3424
4177
|
return state.seal(options.reset, options.isEqual ?? Object.is);
|
|
3425
4178
|
};
|
|
3426
4179
|
const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSource) => {
|
|
3427
|
-
const state = createCollectionOutput();
|
|
3428
4180
|
let boundary;
|
|
3429
4181
|
let pendingReset = false;
|
|
3430
4182
|
let pendingCause;
|
|
@@ -3438,26 +4190,12 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
3438
4190
|
if (!scheduler.active || boundary.disposed) throw new ProjectionDisposedError();
|
|
3439
4191
|
if (boundary.fault) throw boundary.fault;
|
|
3440
4192
|
};
|
|
3441
|
-
const
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
context: (active) => state.context(active, contextCause),
|
|
3448
|
-
current: () => state.current(check),
|
|
3449
|
-
revision: state.revision,
|
|
3450
|
-
reset: state.reset,
|
|
3451
|
-
subscribe: (listener) => {
|
|
3452
|
-
check();
|
|
3453
|
-
const wrapped = (change) => listener(change);
|
|
3454
|
-
state.subscribe(wrapped);
|
|
3455
|
-
return () => state.unsubscribe(wrapped);
|
|
3456
|
-
},
|
|
3457
|
-
emit: state.emit,
|
|
3458
|
-
clear: state.clear,
|
|
3459
|
-
release: state.release
|
|
3460
|
-
};
|
|
4193
|
+
const state = createCollectionOutput({
|
|
4194
|
+
owner: () => boundary,
|
|
4195
|
+
check,
|
|
4196
|
+
cause: () => contextCause
|
|
4197
|
+
});
|
|
4198
|
+
const output = state;
|
|
3461
4199
|
boundary = {
|
|
3462
4200
|
kind: "source",
|
|
3463
4201
|
name,
|
|
@@ -3470,7 +4208,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
3470
4208
|
contextCause = cause;
|
|
3471
4209
|
let active = true;
|
|
3472
4210
|
try {
|
|
3473
|
-
const changed = stageCollectionRead(state, readLatest(() => active, state.current(
|
|
4211
|
+
const changed = stageCollectionRead(state, readLatest(() => active, state.current()), () => active, {
|
|
3474
4212
|
reset: pendingReset,
|
|
3475
4213
|
...fullScan ? {} : { candidates },
|
|
3476
4214
|
orderMayChange,
|
|
@@ -3499,26 +4237,44 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
3499
4237
|
clearSource(failed);
|
|
3500
4238
|
},
|
|
3501
4239
|
release: () => {
|
|
3502
|
-
cleanup
|
|
4240
|
+
const detach = cleanup;
|
|
3503
4241
|
cleanup = void 0;
|
|
3504
|
-
|
|
3505
|
-
|
|
4242
|
+
const failures = [];
|
|
4243
|
+
try {
|
|
4244
|
+
detach?.();
|
|
4245
|
+
} catch (error) {
|
|
4246
|
+
failures.push(error);
|
|
4247
|
+
}
|
|
4248
|
+
try {
|
|
4249
|
+
state.release();
|
|
4250
|
+
} catch (error) {
|
|
4251
|
+
failures.push(error);
|
|
4252
|
+
}
|
|
4253
|
+
if (failures.length) throw failures[0];
|
|
3506
4254
|
}
|
|
3507
4255
|
};
|
|
3508
|
-
|
|
3509
|
-
|
|
4256
|
+
try {
|
|
4257
|
+
scheduler.initialize(() => {
|
|
4258
|
+
let active = true;
|
|
4259
|
+
try {
|
|
4260
|
+
stageCollectionRead(state, readLatest(() => active, state.current()), () => active, {
|
|
4261
|
+
reset: true,
|
|
4262
|
+
isEqual: equality
|
|
4263
|
+
});
|
|
4264
|
+
state.publish();
|
|
4265
|
+
state.clear();
|
|
4266
|
+
} finally {
|
|
4267
|
+
active = false;
|
|
4268
|
+
}
|
|
4269
|
+
});
|
|
4270
|
+
scheduler.addSource(boundary);
|
|
4271
|
+
} catch (error) {
|
|
4272
|
+
boundary.disposed = true;
|
|
3510
4273
|
try {
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
state.publish();
|
|
3516
|
-
state.clear();
|
|
3517
|
-
} finally {
|
|
3518
|
-
active = false;
|
|
3519
|
-
}
|
|
3520
|
-
});
|
|
3521
|
-
scheduler.addSource(boundary);
|
|
4274
|
+
boundary.release();
|
|
4275
|
+
} catch {}
|
|
4276
|
+
throw error;
|
|
4277
|
+
}
|
|
3522
4278
|
const mark = (metadata = {}) => {
|
|
3523
4279
|
scheduler.assertIdle();
|
|
3524
4280
|
pendingReset ||= metadata.reset ?? false;
|
|
@@ -3530,7 +4286,7 @@ const createCollectionBoundary = (scheduler, name, readLatest, equality, clearSo
|
|
|
3530
4286
|
scheduler.capture(boundary);
|
|
3531
4287
|
};
|
|
3532
4288
|
const fail = (cause) => {
|
|
3533
|
-
boundary.fault = sourceError(
|
|
4289
|
+
boundary.fault = sourceError(cause);
|
|
3534
4290
|
recovering = false;
|
|
3535
4291
|
scheduler.capture(boundary);
|
|
3536
4292
|
};
|
|
@@ -3564,14 +4320,19 @@ const createDocumentSourceRegistry = (scheduler) => {
|
|
|
3564
4320
|
const guard = (locked) => {
|
|
3565
4321
|
state.projectionLocks += locked ? 1 : -1;
|
|
3566
4322
|
};
|
|
3567
|
-
scheduler.
|
|
4323
|
+
let unregisterGuard = scheduler.registerGuard(guard);
|
|
3568
4324
|
let closed = false;
|
|
3569
|
-
let detach
|
|
4325
|
+
let detach;
|
|
3570
4326
|
const documentConnection = {
|
|
3571
4327
|
add(binding) {
|
|
3572
4328
|
if (closed) throw new Error("Document projection connection is closed.");
|
|
3573
4329
|
bindings.add(binding);
|
|
3574
|
-
|
|
4330
|
+
try {
|
|
4331
|
+
index.add(binding.target, binding);
|
|
4332
|
+
} catch (error) {
|
|
4333
|
+
bindings.delete(binding);
|
|
4334
|
+
throw error;
|
|
4335
|
+
}
|
|
3575
4336
|
let active = true;
|
|
3576
4337
|
return () => {
|
|
3577
4338
|
if (!active) return;
|
|
@@ -3584,27 +4345,58 @@ const createDocumentSourceRegistry = (scheduler) => {
|
|
|
3584
4345
|
close() {
|
|
3585
4346
|
if (closed) return;
|
|
3586
4347
|
closed = true;
|
|
3587
|
-
detach
|
|
4348
|
+
const stopNotifications = detach;
|
|
4349
|
+
detach = void 0;
|
|
4350
|
+
const stopGuard = unregisterGuard;
|
|
4351
|
+
unregisterGuard = () => void 0;
|
|
3588
4352
|
bindings.clear();
|
|
3589
4353
|
candidates.clear();
|
|
3590
4354
|
index.clear();
|
|
3591
|
-
scheduler.guards.delete(guard);
|
|
3592
4355
|
documents.delete(state);
|
|
4356
|
+
const failures = [];
|
|
4357
|
+
try {
|
|
4358
|
+
stopNotifications?.();
|
|
4359
|
+
} catch (error) {
|
|
4360
|
+
failures.push(error);
|
|
4361
|
+
}
|
|
4362
|
+
try {
|
|
4363
|
+
stopGuard();
|
|
4364
|
+
} catch (error) {
|
|
4365
|
+
failures.push(error);
|
|
4366
|
+
}
|
|
4367
|
+
if (failures.length) throw failures[0];
|
|
3593
4368
|
}
|
|
3594
4369
|
};
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
4370
|
+
try {
|
|
4371
|
+
detach = context.notifications.attachProjection({
|
|
4372
|
+
capture: (commit) => {
|
|
4373
|
+
candidates.clear();
|
|
4374
|
+
index.collect(commit.changes, (binding) => candidates.add(binding));
|
|
4375
|
+
candidates.forEach((binding) => binding.capture(commit));
|
|
4376
|
+
},
|
|
4377
|
+
settle: scheduler.settle,
|
|
4378
|
+
flush: scheduler.flush,
|
|
4379
|
+
dispose: () => {
|
|
4380
|
+
const failures = [];
|
|
4381
|
+
for (const binding of [...bindings]) try {
|
|
4382
|
+
binding.dispose();
|
|
4383
|
+
} catch (error) {
|
|
4384
|
+
failures.push(error);
|
|
4385
|
+
}
|
|
4386
|
+
try {
|
|
4387
|
+
scheduler.run();
|
|
4388
|
+
} catch (error) {
|
|
4389
|
+
failures.push(error);
|
|
4390
|
+
}
|
|
4391
|
+
if (failures.length) throw failures[0];
|
|
4392
|
+
}
|
|
4393
|
+
});
|
|
4394
|
+
} catch (error) {
|
|
4395
|
+
try {
|
|
4396
|
+
documentConnection.close();
|
|
4397
|
+
} catch {}
|
|
4398
|
+
throw error;
|
|
4399
|
+
}
|
|
3608
4400
|
documents.set(state, documentConnection);
|
|
3609
4401
|
return documentConnection;
|
|
3610
4402
|
};
|
|
@@ -3639,18 +4431,26 @@ const createDocumentSourceRegistry = (scheduler) => {
|
|
|
3639
4431
|
if (failed) markDocumentReplace(dirty, []);
|
|
3640
4432
|
else clearDocumentDirty(dirty);
|
|
3641
4433
|
});
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
4434
|
+
let remove;
|
|
4435
|
+
try {
|
|
4436
|
+
remove = connection(document).add({
|
|
4437
|
+
target: selector,
|
|
4438
|
+
capture: (commit) => {
|
|
4439
|
+
try {
|
|
4440
|
+
mergeValueChanges(selector, dirty, commit.changes);
|
|
4441
|
+
boundary.mark({ reset: commit.changes.changes.some((change) => change.kind === "reset") });
|
|
4442
|
+
} catch (cause) {
|
|
4443
|
+
boundary.fail(cause);
|
|
4444
|
+
}
|
|
4445
|
+
},
|
|
4446
|
+
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
4447
|
+
});
|
|
4448
|
+
} catch (error) {
|
|
4449
|
+
try {
|
|
4450
|
+
scheduler.releaseProducer(boundary.producer);
|
|
4451
|
+
} catch {}
|
|
4452
|
+
throw error;
|
|
4453
|
+
}
|
|
3654
4454
|
boundary.detach(remove);
|
|
3655
4455
|
return boundary;
|
|
3656
4456
|
}
|
|
@@ -3721,22 +4521,30 @@ const createDocumentSourceRegistry = (scheduler) => {
|
|
|
3721
4521
|
if (failed) resetCollectionPending(pending);
|
|
3722
4522
|
else clearCollectionPending(pending);
|
|
3723
4523
|
});
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
4524
|
+
let remove;
|
|
4525
|
+
try {
|
|
4526
|
+
remove = connection(document).add({
|
|
4527
|
+
target: selector,
|
|
4528
|
+
capture: (commit) => {
|
|
4529
|
+
try {
|
|
4530
|
+
mergeCollectionChanges(selector, pending, commit.changes);
|
|
4531
|
+
boundary.mark({
|
|
4532
|
+
reset: pending.reset,
|
|
4533
|
+
...pending.reset ? {} : { candidates: pending.entries.keys() },
|
|
4534
|
+
orderMayChange: pending.order
|
|
4535
|
+
});
|
|
4536
|
+
} catch (cause) {
|
|
4537
|
+
boundary.fail(cause);
|
|
4538
|
+
}
|
|
4539
|
+
},
|
|
4540
|
+
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
4541
|
+
});
|
|
4542
|
+
} catch (error) {
|
|
4543
|
+
try {
|
|
4544
|
+
scheduler.releaseProducer(boundary.producer);
|
|
4545
|
+
} catch {}
|
|
4546
|
+
throw error;
|
|
4547
|
+
}
|
|
3740
4548
|
boundary.detach(remove);
|
|
3741
4549
|
return boundary;
|
|
3742
4550
|
};
|
|
@@ -3750,115 +4558,23 @@ const createDocumentSourceRegistry = (scheduler) => {
|
|
|
3750
4558
|
});
|
|
3751
4559
|
},
|
|
3752
4560
|
dispose() {
|
|
3753
|
-
|
|
4561
|
+
const failures = [];
|
|
4562
|
+
for (const document of [...documents.values()]) try {
|
|
4563
|
+
document.close();
|
|
4564
|
+
} catch (error) {
|
|
4565
|
+
failures.push(error);
|
|
4566
|
+
}
|
|
3754
4567
|
documents.clear();
|
|
4568
|
+
if (failures.length) throw failures[0];
|
|
3755
4569
|
}
|
|
3756
4570
|
};
|
|
3757
4571
|
};
|
|
3758
4572
|
//#endregion
|
|
3759
|
-
//#region core/src/projection/source/
|
|
3760
|
-
const
|
|
3761
|
-
const documents = createDocumentSourceRegistry(scheduler);
|
|
4573
|
+
//#region core/src/projection/source/external.ts
|
|
4574
|
+
const createExternalSourceRegistry = (scheduler, documents) => {
|
|
3762
4575
|
const readables = /* @__PURE__ */ new Map();
|
|
3763
|
-
const externalValues = /* @__PURE__ */ new
|
|
3764
|
-
const externalCollections = /* @__PURE__ */ new
|
|
3765
|
-
const valueInput = (definition) => {
|
|
3766
|
-
if (definition.source.kind !== "value-input") throw new Error("Invalid value input source.");
|
|
3767
|
-
const source = definition.source;
|
|
3768
|
-
let latest = source.initial;
|
|
3769
|
-
const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality, () => latest, () => void 0);
|
|
3770
|
-
return {
|
|
3771
|
-
...boundary,
|
|
3772
|
-
write: {
|
|
3773
|
-
kind: "value",
|
|
3774
|
-
set(value) {
|
|
3775
|
-
scheduler.assertIdle();
|
|
3776
|
-
latest = value;
|
|
3777
|
-
boundary.mark();
|
|
3778
|
-
scheduler.run();
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
|
-
};
|
|
3782
|
-
};
|
|
3783
|
-
const collectionInput = (definition) => {
|
|
3784
|
-
if (definition.source.kind !== "collection-input") throw new Error("Invalid collection input source.");
|
|
3785
|
-
const values = new Map(definition.source.initial);
|
|
3786
|
-
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => mapRead(values), definition.output.equality, () => void 0);
|
|
3787
|
-
return {
|
|
3788
|
-
...boundary,
|
|
3789
|
-
write: {
|
|
3790
|
-
kind: "collection",
|
|
3791
|
-
update(run) {
|
|
3792
|
-
scheduler.assertIdle();
|
|
3793
|
-
const staged = /* @__PURE__ */ new Map();
|
|
3794
|
-
const operations = [];
|
|
3795
|
-
let active = true;
|
|
3796
|
-
const draft = Object.freeze({
|
|
3797
|
-
get: (key) => {
|
|
3798
|
-
assertScope(() => active);
|
|
3799
|
-
const entry = staged.get(key);
|
|
3800
|
-
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
3801
|
-
},
|
|
3802
|
-
has: (key) => {
|
|
3803
|
-
assertScope(() => active);
|
|
3804
|
-
const entry = staged.get(key);
|
|
3805
|
-
return entry ? entry.present : values.has(key);
|
|
3806
|
-
},
|
|
3807
|
-
set: (key, value) => {
|
|
3808
|
-
assertScope(() => active);
|
|
3809
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
3810
|
-
staged.set(key, {
|
|
3811
|
-
present: true,
|
|
3812
|
-
value
|
|
3813
|
-
});
|
|
3814
|
-
operations.push({
|
|
3815
|
-
kind: "set",
|
|
3816
|
-
key,
|
|
3817
|
-
value
|
|
3818
|
-
});
|
|
3819
|
-
},
|
|
3820
|
-
remove: (key) => {
|
|
3821
|
-
assertScope(() => active);
|
|
3822
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
3823
|
-
staged.set(key, { present: false });
|
|
3824
|
-
operations.push({
|
|
3825
|
-
kind: "remove",
|
|
3826
|
-
key
|
|
3827
|
-
});
|
|
3828
|
-
}
|
|
3829
|
-
});
|
|
3830
|
-
try {
|
|
3831
|
-
assertSynchronous(run(draft));
|
|
3832
|
-
} finally {
|
|
3833
|
-
active = false;
|
|
3834
|
-
}
|
|
3835
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
3836
|
-
let structural = false;
|
|
3837
|
-
let changed = false;
|
|
3838
|
-
for (const operation of operations) {
|
|
3839
|
-
const present = values.has(operation.key);
|
|
3840
|
-
if (operation.kind === "set") {
|
|
3841
|
-
if (present && Object.is(values.get(operation.key), operation.value)) continue;
|
|
3842
|
-
structural ||= !present;
|
|
3843
|
-
values.set(operation.key, operation.value);
|
|
3844
|
-
} else {
|
|
3845
|
-
if (!present) continue;
|
|
3846
|
-
structural = true;
|
|
3847
|
-
values.delete(operation.key);
|
|
3848
|
-
}
|
|
3849
|
-
candidates.add(operation.key);
|
|
3850
|
-
changed = true;
|
|
3851
|
-
}
|
|
3852
|
-
if (!changed) return;
|
|
3853
|
-
boundary.mark({
|
|
3854
|
-
candidates,
|
|
3855
|
-
orderMayChange: structural
|
|
3856
|
-
});
|
|
3857
|
-
scheduler.run();
|
|
3858
|
-
}
|
|
3859
|
-
}
|
|
3860
|
-
};
|
|
3861
|
-
};
|
|
4576
|
+
const externalValues = /* @__PURE__ */ new Map();
|
|
4577
|
+
const externalCollections = /* @__PURE__ */ new Map();
|
|
3862
4578
|
const readable = (definition) => {
|
|
3863
4579
|
if (definition.source.kind !== "readable") throw new Error("Invalid Readable source.");
|
|
3864
4580
|
const { readable, equality } = definition.source;
|
|
@@ -3892,19 +4608,44 @@ const createSourceRegistry = (scheduler) => {
|
|
|
3892
4608
|
close() {
|
|
3893
4609
|
if (closed) return;
|
|
3894
4610
|
closed = true;
|
|
3895
|
-
unsubscribe
|
|
3896
|
-
|
|
4611
|
+
const stopReadable = unsubscribe;
|
|
4612
|
+
unsubscribe = void 0;
|
|
4613
|
+
const stopDocument = detachDocument;
|
|
4614
|
+
detachDocument = void 0;
|
|
3897
4615
|
readables.delete(readable);
|
|
4616
|
+
members.clear();
|
|
4617
|
+
const failures = [];
|
|
4618
|
+
try {
|
|
4619
|
+
stopReadable?.();
|
|
4620
|
+
} catch (error) {
|
|
4621
|
+
failures.push(error);
|
|
4622
|
+
}
|
|
4623
|
+
try {
|
|
4624
|
+
stopDocument?.();
|
|
4625
|
+
} catch (error) {
|
|
4626
|
+
failures.push(error);
|
|
4627
|
+
}
|
|
4628
|
+
if (failures.length) throw failures[0];
|
|
3898
4629
|
}
|
|
3899
4630
|
};
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
4631
|
+
try {
|
|
4632
|
+
const owner = contextOf(readable, false)?.owner;
|
|
4633
|
+
if (owner) detachDocument = documents.attachRoot(owner, {
|
|
4634
|
+
capture: () => receive(false),
|
|
4635
|
+
dispose: () => {
|
|
4636
|
+
members.forEach((next) => next.boundary.fail(/* @__PURE__ */ new Error("Document has been disposed.")));
|
|
4637
|
+
}
|
|
4638
|
+
});
|
|
4639
|
+
unsubscribe = readable.subscribe(() => receive(true));
|
|
4640
|
+
} catch (error) {
|
|
4641
|
+
try {
|
|
4642
|
+
created.close();
|
|
4643
|
+
} catch {}
|
|
4644
|
+
try {
|
|
4645
|
+
scheduler.releaseProducer(boundary.producer);
|
|
4646
|
+
} catch {}
|
|
4647
|
+
throw error;
|
|
4648
|
+
}
|
|
3908
4649
|
connection = created;
|
|
3909
4650
|
readables.set(readable, connection);
|
|
3910
4651
|
}
|
|
@@ -3937,8 +4678,11 @@ const createSourceRegistry = (scheduler) => {
|
|
|
3937
4678
|
close() {
|
|
3938
4679
|
if (closed) return;
|
|
3939
4680
|
closed = true;
|
|
3940
|
-
unsubscribe
|
|
4681
|
+
const stop = unsubscribe;
|
|
4682
|
+
unsubscribe = void 0;
|
|
3941
4683
|
externalValues.delete(source);
|
|
4684
|
+
members.clear();
|
|
4685
|
+
stop?.();
|
|
3942
4686
|
}
|
|
3943
4687
|
};
|
|
3944
4688
|
try {
|
|
@@ -3953,9 +4697,14 @@ const createSourceRegistry = (scheduler) => {
|
|
|
3953
4697
|
}
|
|
3954
4698
|
scheduler.run();
|
|
3955
4699
|
});
|
|
3956
|
-
} catch (
|
|
3957
|
-
|
|
3958
|
-
|
|
4700
|
+
} catch (error) {
|
|
4701
|
+
try {
|
|
4702
|
+
created.close();
|
|
4703
|
+
} catch {}
|
|
4704
|
+
try {
|
|
4705
|
+
scheduler.releaseProducer(boundary.producer);
|
|
4706
|
+
} catch {}
|
|
4707
|
+
throw error;
|
|
3959
4708
|
}
|
|
3960
4709
|
connection = created;
|
|
3961
4710
|
externalValues.set(source, connection);
|
|
@@ -3988,8 +4737,11 @@ const createSourceRegistry = (scheduler) => {
|
|
|
3988
4737
|
close() {
|
|
3989
4738
|
if (closed) return;
|
|
3990
4739
|
closed = true;
|
|
3991
|
-
unsubscribe
|
|
4740
|
+
const stop = unsubscribe;
|
|
4741
|
+
unsubscribe = void 0;
|
|
3992
4742
|
externalCollections.delete(source);
|
|
4743
|
+
members.clear();
|
|
4744
|
+
stop?.();
|
|
3993
4745
|
}
|
|
3994
4746
|
};
|
|
3995
4747
|
try {
|
|
@@ -4021,9 +4773,14 @@ const createSourceRegistry = (scheduler) => {
|
|
|
4021
4773
|
}
|
|
4022
4774
|
scheduler.run();
|
|
4023
4775
|
});
|
|
4024
|
-
} catch (
|
|
4025
|
-
|
|
4026
|
-
|
|
4776
|
+
} catch (error) {
|
|
4777
|
+
try {
|
|
4778
|
+
created.close();
|
|
4779
|
+
} catch {}
|
|
4780
|
+
try {
|
|
4781
|
+
scheduler.releaseProducer(boundary.producer);
|
|
4782
|
+
} catch {}
|
|
4783
|
+
throw error;
|
|
4027
4784
|
}
|
|
4028
4785
|
connection = created;
|
|
4029
4786
|
externalCollections.set(source, connection);
|
|
@@ -4038,18 +4795,166 @@ const createSourceRegistry = (scheduler) => {
|
|
|
4038
4795
|
return {
|
|
4039
4796
|
materialize(definition) {
|
|
4040
4797
|
switch (definition.source.kind) {
|
|
4041
|
-
case "value-input": return valueInput(definition);
|
|
4042
|
-
case "collection-input": return collectionInput(definition);
|
|
4043
|
-
case "document": return documents.materialize(definition);
|
|
4044
4798
|
case "readable": return readable(definition);
|
|
4045
4799
|
case "external-value": return externalValue(definition);
|
|
4046
4800
|
case "external-collection": return externalCollection(definition);
|
|
4801
|
+
default: throw new Error("Invalid external projection source.");
|
|
4047
4802
|
}
|
|
4048
4803
|
},
|
|
4049
4804
|
dispose() {
|
|
4050
|
-
|
|
4051
|
-
|
|
4805
|
+
const failures = [];
|
|
4806
|
+
const connections = new Set([
|
|
4807
|
+
...readables.values(),
|
|
4808
|
+
...externalValues.values(),
|
|
4809
|
+
...externalCollections.values()
|
|
4810
|
+
]);
|
|
4811
|
+
for (const connection of connections) try {
|
|
4812
|
+
connection.close();
|
|
4813
|
+
} catch (error) {
|
|
4814
|
+
failures.push(error);
|
|
4815
|
+
}
|
|
4052
4816
|
readables.clear();
|
|
4817
|
+
externalValues.clear();
|
|
4818
|
+
externalCollections.clear();
|
|
4819
|
+
if (failures.length) throw failures[0];
|
|
4820
|
+
}
|
|
4821
|
+
};
|
|
4822
|
+
};
|
|
4823
|
+
//#endregion
|
|
4824
|
+
//#region core/src/projection/source/input.ts
|
|
4825
|
+
const materializeInputSource = (scheduler, definition) => {
|
|
4826
|
+
const source = definition.source;
|
|
4827
|
+
if (source.kind === "value-input") {
|
|
4828
|
+
let latest = source.initial;
|
|
4829
|
+
const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality, () => latest, () => void 0);
|
|
4830
|
+
return {
|
|
4831
|
+
...boundary,
|
|
4832
|
+
write: {
|
|
4833
|
+
kind: "value",
|
|
4834
|
+
set(value) {
|
|
4835
|
+
scheduler.assertIdle();
|
|
4836
|
+
latest = value;
|
|
4837
|
+
boundary.mark();
|
|
4838
|
+
scheduler.run();
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
if (source.kind !== "collection-input") throw new Error("Invalid projection input source.");
|
|
4844
|
+
const values = new Map(source.initial);
|
|
4845
|
+
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => mapRead(values), definition.output.equality, () => void 0);
|
|
4846
|
+
return {
|
|
4847
|
+
...boundary,
|
|
4848
|
+
write: {
|
|
4849
|
+
kind: "collection",
|
|
4850
|
+
update(run) {
|
|
4851
|
+
scheduler.assertIdle();
|
|
4852
|
+
const staged = /* @__PURE__ */ new Map();
|
|
4853
|
+
const operations = [];
|
|
4854
|
+
let active = true;
|
|
4855
|
+
const draft = Object.freeze({
|
|
4856
|
+
get: (key) => {
|
|
4857
|
+
assertScope(() => active);
|
|
4858
|
+
const entry = staged.get(key);
|
|
4859
|
+
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
4860
|
+
},
|
|
4861
|
+
has: (key) => {
|
|
4862
|
+
assertScope(() => active);
|
|
4863
|
+
const entry = staged.get(key);
|
|
4864
|
+
return entry ? entry.present : values.has(key);
|
|
4865
|
+
},
|
|
4866
|
+
set: (key, value) => {
|
|
4867
|
+
assertScope(() => active);
|
|
4868
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
4869
|
+
staged.set(key, {
|
|
4870
|
+
present: true,
|
|
4871
|
+
value
|
|
4872
|
+
});
|
|
4873
|
+
operations.push({
|
|
4874
|
+
kind: "set",
|
|
4875
|
+
key,
|
|
4876
|
+
value
|
|
4877
|
+
});
|
|
4878
|
+
},
|
|
4879
|
+
remove: (key) => {
|
|
4880
|
+
assertScope(() => active);
|
|
4881
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
4882
|
+
staged.set(key, { present: false });
|
|
4883
|
+
operations.push({
|
|
4884
|
+
kind: "remove",
|
|
4885
|
+
key
|
|
4886
|
+
});
|
|
4887
|
+
}
|
|
4888
|
+
});
|
|
4889
|
+
try {
|
|
4890
|
+
assertSynchronous(run(draft));
|
|
4891
|
+
} finally {
|
|
4892
|
+
active = false;
|
|
4893
|
+
}
|
|
4894
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
4895
|
+
let structural = false;
|
|
4896
|
+
const accepted = [];
|
|
4897
|
+
const overlay = /* @__PURE__ */ new Map();
|
|
4898
|
+
for (const operation of operations) {
|
|
4899
|
+
const overlayValue = overlay.get(operation.key);
|
|
4900
|
+
const present = overlayValue ? overlayValue.present : values.has(operation.key);
|
|
4901
|
+
if (operation.kind === "set") {
|
|
4902
|
+
const previous = overlayValue ? overlayValue.present ? overlayValue.value : void 0 : values.get(operation.key);
|
|
4903
|
+
if (present && source.equality(previous, operation.value)) continue;
|
|
4904
|
+
structural ||= !present;
|
|
4905
|
+
overlay.set(operation.key, {
|
|
4906
|
+
present: true,
|
|
4907
|
+
value: operation.value
|
|
4908
|
+
});
|
|
4909
|
+
} else {
|
|
4910
|
+
if (!present) continue;
|
|
4911
|
+
structural = true;
|
|
4912
|
+
overlay.set(operation.key, { present: false });
|
|
4913
|
+
}
|
|
4914
|
+
accepted.push(operation);
|
|
4915
|
+
candidates.add(operation.key);
|
|
4916
|
+
}
|
|
4917
|
+
if (!accepted.length) return;
|
|
4918
|
+
for (const operation of accepted) if (operation.kind === "set") values.set(operation.key, operation.value);
|
|
4919
|
+
else values.delete(operation.key);
|
|
4920
|
+
boundary.mark({
|
|
4921
|
+
candidates,
|
|
4922
|
+
orderMayChange: structural
|
|
4923
|
+
});
|
|
4924
|
+
scheduler.run();
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
};
|
|
4928
|
+
};
|
|
4929
|
+
//#endregion
|
|
4930
|
+
//#region core/src/projection/source/registry.ts
|
|
4931
|
+
const createSourceRegistry = (scheduler) => {
|
|
4932
|
+
const documents = createDocumentSourceRegistry(scheduler);
|
|
4933
|
+
const external = createExternalSourceRegistry(scheduler, documents);
|
|
4934
|
+
return {
|
|
4935
|
+
materialize(definition) {
|
|
4936
|
+
switch (definition.source.kind) {
|
|
4937
|
+
case "value-input":
|
|
4938
|
+
case "collection-input": return materializeInputSource(scheduler, definition);
|
|
4939
|
+
case "document": return documents.materialize(definition);
|
|
4940
|
+
case "readable":
|
|
4941
|
+
case "external-value":
|
|
4942
|
+
case "external-collection": return external.materialize(definition);
|
|
4943
|
+
}
|
|
4944
|
+
},
|
|
4945
|
+
dispose() {
|
|
4946
|
+
const failures = [];
|
|
4947
|
+
try {
|
|
4948
|
+
external.dispose();
|
|
4949
|
+
} catch (error) {
|
|
4950
|
+
failures.push(error);
|
|
4951
|
+
}
|
|
4952
|
+
try {
|
|
4953
|
+
documents.dispose();
|
|
4954
|
+
} catch (error) {
|
|
4955
|
+
failures.push(error);
|
|
4956
|
+
}
|
|
4957
|
+
if (failures.length) throw failures[0];
|
|
4053
4958
|
}
|
|
4054
4959
|
};
|
|
4055
4960
|
};
|
|
@@ -4092,6 +4997,7 @@ const createProjectionRuntime = (options) => {
|
|
|
4092
4997
|
outputs: processor.outputs
|
|
4093
4998
|
});
|
|
4094
4999
|
}
|
|
5000
|
+
definition.materialized = true;
|
|
4095
5001
|
materialized.set(definition, result);
|
|
4096
5002
|
rememberScoped(definition, requester);
|
|
4097
5003
|
return result;
|
|
@@ -4132,12 +5038,12 @@ const createProjectionRuntime = (options) => {
|
|
|
4132
5038
|
publicViews.delete(output);
|
|
4133
5039
|
return current;
|
|
4134
5040
|
};
|
|
4135
|
-
const
|
|
5041
|
+
const readProjection = (projection, requester) => publicCurrent(resolveOutput(projection, requester));
|
|
4136
5042
|
const readableSource = (projection, requester) => {
|
|
4137
5043
|
const output = resolveOutput(projection, requester);
|
|
4138
5044
|
return Object.freeze({
|
|
4139
5045
|
kind: output.kind,
|
|
4140
|
-
current: () =>
|
|
5046
|
+
current: () => readProjection(projection, requester),
|
|
4141
5047
|
revision: () => {
|
|
4142
5048
|
assertOutput(output);
|
|
4143
5049
|
return output.revision();
|
|
@@ -4149,7 +5055,7 @@ const createProjectionRuntime = (options) => {
|
|
|
4149
5055
|
const source = readableSource(projection, requester);
|
|
4150
5056
|
return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
|
|
4151
5057
|
};
|
|
4152
|
-
function
|
|
5058
|
+
function selectProjection(projection, selector, equality) {
|
|
4153
5059
|
return makeReadable(projection, selector, equality);
|
|
4154
5060
|
}
|
|
4155
5061
|
const inputWrite = (target, requester) => {
|
|
@@ -4160,34 +5066,46 @@ const createProjectionRuntime = (options) => {
|
|
|
4160
5066
|
if (!write) throw new TypeError("Projection input is not writable.");
|
|
4161
5067
|
return write;
|
|
4162
5068
|
};
|
|
4163
|
-
const
|
|
4164
|
-
const write = inputWrite(target, requester);
|
|
4165
|
-
if (write.kind !== "value") throw new TypeError("Projection is not a value input.");
|
|
4166
|
-
write.set(value);
|
|
4167
|
-
};
|
|
4168
|
-
const updateInput = (target, run, requester) => {
|
|
5069
|
+
const updateInput = (target, valueOrRun, requester) => {
|
|
4169
5070
|
const write = inputWrite(target, requester);
|
|
4170
|
-
if (write.kind
|
|
4171
|
-
|
|
5071
|
+
if (write.kind === "value") {
|
|
5072
|
+
write.set(valueOrRun);
|
|
5073
|
+
return;
|
|
5074
|
+
}
|
|
5075
|
+
if (typeof valueOrRun !== "function") throw new TypeError("Collection input update requires a draft callback.");
|
|
5076
|
+
write.update(valueOrRun);
|
|
4172
5077
|
};
|
|
4173
|
-
function
|
|
4174
|
-
|
|
5078
|
+
function update(target, valueOrRun) {
|
|
5079
|
+
updateInput(target, valueOrRun);
|
|
4175
5080
|
}
|
|
5081
|
+
const batch = (run, options) => scheduler.batch(run, options);
|
|
4176
5082
|
const disposeScope = (state) => {
|
|
4177
5083
|
if (!state.active) return;
|
|
4178
5084
|
scheduler.assertIdle();
|
|
4179
5085
|
state.active = false;
|
|
4180
|
-
|
|
5086
|
+
const failures = [];
|
|
5087
|
+
const subscriptions = [...state.subscriptions];
|
|
4181
5088
|
state.subscriptions.clear();
|
|
5089
|
+
for (const unsubscribe of subscriptions) try {
|
|
5090
|
+
unsubscribe();
|
|
5091
|
+
} catch (error) {
|
|
5092
|
+
failures.push(error);
|
|
5093
|
+
}
|
|
4182
5094
|
for (let index = state.materialized.length - 1; index >= 0; index--) {
|
|
4183
5095
|
const definition = state.materialized[index];
|
|
4184
5096
|
const current = materialized.get(definition);
|
|
4185
5097
|
if (!current) continue;
|
|
4186
|
-
|
|
4187
|
-
|
|
5098
|
+
try {
|
|
5099
|
+
scheduler.releaseProducer(current.record);
|
|
5100
|
+
} catch (error) {
|
|
5101
|
+
failures.push(error);
|
|
5102
|
+
} finally {
|
|
5103
|
+
materialized.delete(definition);
|
|
5104
|
+
}
|
|
4188
5105
|
}
|
|
4189
5106
|
state.materialized.length = 0;
|
|
4190
5107
|
scopes.delete(state);
|
|
5108
|
+
if (failures.length) throw failures[0];
|
|
4191
5109
|
};
|
|
4192
5110
|
const createScope = () => {
|
|
4193
5111
|
scheduler.assertIdle();
|
|
@@ -4201,23 +5119,30 @@ const createProjectionRuntime = (options) => {
|
|
|
4201
5119
|
if (!state.active) throw new ProjectionDisposedError();
|
|
4202
5120
|
scheduler.assertActive();
|
|
4203
5121
|
};
|
|
4204
|
-
|
|
5122
|
+
function own(target) {
|
|
4205
5123
|
assertActive();
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
5124
|
+
const projections = [];
|
|
5125
|
+
const collect = (value) => {
|
|
5126
|
+
if (isProjection(value)) {
|
|
5127
|
+
projections.push(value);
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
5130
|
+
if (!isPlainObject(value)) throw new TypeError("Projection ownership tree must be a plain object.");
|
|
5131
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
5132
|
+
if (typeof key !== "string") throw new TypeError("Projection ownership tree keys must be strings.");
|
|
5133
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
5134
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) throw new TypeError("Projection ownership tree must use enumerable data properties.");
|
|
5135
|
+
const child = descriptor.value;
|
|
5136
|
+
if (!isProjection(child) && !isPlainObject(child)) throw new TypeError("Projection ownership tree leaves must be projections.");
|
|
5137
|
+
collect(child);
|
|
5138
|
+
}
|
|
5139
|
+
};
|
|
5140
|
+
collect(target);
|
|
5141
|
+
if (!projections.length) throw new TypeError("Projection ownership tree must contain a projection.");
|
|
5142
|
+
ownProjections(projections, state);
|
|
5143
|
+
return target;
|
|
5144
|
+
}
|
|
5145
|
+
function scopeSelect(projection, selector, equality) {
|
|
4221
5146
|
assertActive();
|
|
4222
5147
|
const source = makeReadable(projection, selector, equality, state);
|
|
4223
5148
|
return Object.freeze({
|
|
@@ -4244,54 +5169,64 @@ const createProjectionRuntime = (options) => {
|
|
|
4244
5169
|
}
|
|
4245
5170
|
});
|
|
4246
5171
|
}
|
|
4247
|
-
function
|
|
5172
|
+
function scopeUpdate(target, valueOrRun) {
|
|
5173
|
+
assertActive();
|
|
5174
|
+
updateInput(target, valueOrRun, state);
|
|
5175
|
+
}
|
|
5176
|
+
function scopeBatch(run, options) {
|
|
4248
5177
|
assertActive();
|
|
4249
|
-
return
|
|
5178
|
+
return batch(run, options);
|
|
4250
5179
|
}
|
|
4251
5180
|
return Object.freeze({
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
incremental: scopedIncremental,
|
|
4255
|
-
get: (projection) => {
|
|
5181
|
+
own,
|
|
5182
|
+
read: (projection) => {
|
|
4256
5183
|
assertActive();
|
|
4257
|
-
return
|
|
4258
|
-
},
|
|
4259
|
-
readable: scopeReadable,
|
|
4260
|
-
set: (target, value) => {
|
|
4261
|
-
assertActive();
|
|
4262
|
-
setInput(target, value, state);
|
|
4263
|
-
},
|
|
4264
|
-
update: (target, run) => {
|
|
4265
|
-
assertActive();
|
|
4266
|
-
updateInput(target, run, state);
|
|
5184
|
+
return readProjection(projection, state);
|
|
4267
5185
|
},
|
|
5186
|
+
select: scopeSelect,
|
|
5187
|
+
update: scopeUpdate,
|
|
4268
5188
|
batch: scopeBatch,
|
|
4269
5189
|
dispose: () => disposeScope(state)
|
|
4270
5190
|
});
|
|
4271
5191
|
};
|
|
4272
5192
|
return Object.freeze({
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
update: updateInput,
|
|
5193
|
+
read: readProjection,
|
|
5194
|
+
select: selectProjection,
|
|
5195
|
+
update,
|
|
4277
5196
|
batch,
|
|
4278
5197
|
scope: createScope,
|
|
4279
5198
|
dispose: () => {
|
|
4280
5199
|
if (!scheduler.active) return;
|
|
4281
5200
|
scheduler.assertIdle();
|
|
5201
|
+
const failures = [];
|
|
4282
5202
|
for (const scope of scopes) {
|
|
4283
5203
|
scope.active = false;
|
|
4284
|
-
scope.subscriptions
|
|
5204
|
+
const subscriptions = [...scope.subscriptions];
|
|
4285
5205
|
scope.subscriptions.clear();
|
|
5206
|
+
for (const unsubscribe of subscriptions) try {
|
|
5207
|
+
unsubscribe();
|
|
5208
|
+
} catch (error) {
|
|
5209
|
+
failures.push(error);
|
|
5210
|
+
}
|
|
4286
5211
|
scope.materialized.length = 0;
|
|
4287
5212
|
}
|
|
4288
5213
|
scopes.clear();
|
|
4289
|
-
|
|
4290
|
-
|
|
5214
|
+
try {
|
|
5215
|
+
sources.dispose();
|
|
5216
|
+
} catch (error) {
|
|
5217
|
+
failures.push(error);
|
|
5218
|
+
}
|
|
5219
|
+
try {
|
|
5220
|
+
scheduler.dispose();
|
|
5221
|
+
} catch (error) {
|
|
5222
|
+
failures.push(error);
|
|
5223
|
+
}
|
|
5224
|
+
if (failures.length === 1) throw failures[0];
|
|
5225
|
+
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
4291
5226
|
}
|
|
4292
5227
|
});
|
|
4293
5228
|
};
|
|
4294
5229
|
//#endregion
|
|
4295
|
-
export { DocumentDisposedError, DocumentReentrancyError, ParseError, ProjectionDisposedError, ProjectionError, TransactionRejected,
|
|
5230
|
+
export { DocumentDisposedError, DocumentReentrancyError, ParseError, ProjectionDisposedError, ProjectionError, TransactionRejected, createDocument, createProjectionRuntime, derive, field, input, list, map, object, observe, optional, parse, read, replace, select, snapshot, table, tree, variant };
|
|
4296
5231
|
|
|
4297
5232
|
//# sourceMappingURL=index.js.map
|