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.
Files changed (66) hide show
  1. package/README.md +208 -102
  2. package/dist/advanced.cjs +237 -2
  3. package/dist/advanced.cjs.map +1 -0
  4. package/dist/advanced.d.cts +90 -2
  5. package/dist/advanced.d.ts +90 -2
  6. package/dist/advanced.js +236 -1
  7. package/dist/advanced.js.map +1 -0
  8. package/dist/{contract-ULPQmiEs.d.ts → contract-CNR6BS75.d.cts} +216 -30
  9. package/dist/{contract-SYJFP0lM.d.cts → contract-COgxO5sv.d.ts} +216 -30
  10. package/dist/definition-CDOuvb5C.d.cts +111 -0
  11. package/dist/definition-xgk3YNBp.d.ts +111 -0
  12. package/dist/{driver-yFfvDMX5.js → driver-B5vdPohL.js} +59 -3
  13. package/dist/driver-B5vdPohL.js.map +1 -0
  14. package/dist/{driver-BD3DQOW6.cjs → driver-BL91gn7_.cjs} +107 -9
  15. package/dist/driver-BL91gn7_.cjs.map +1 -0
  16. package/dist/index.cjs +1637 -703
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +65 -33
  19. package/dist/index.d.ts +65 -33
  20. package/dist/index.js +1432 -497
  21. package/dist/index.js.map +1 -1
  22. package/dist/local-sync.cjs +69 -103
  23. package/dist/local-sync.cjs.map +1 -1
  24. package/dist/local-sync.d.cts +14 -26
  25. package/dist/local-sync.d.ts +14 -26
  26. package/dist/local-sync.js +68 -96
  27. package/dist/local-sync.js.map +1 -1
  28. package/dist/react.cjs +4 -5
  29. package/dist/react.cjs.map +1 -1
  30. package/dist/react.d.cts +6 -8
  31. package/dist/react.d.ts +6 -8
  32. package/dist/react.js +4 -5
  33. package/dist/react.js.map +1 -1
  34. package/dist/record-B2yRhYsc.js +28 -0
  35. package/dist/record-B2yRhYsc.js.map +1 -0
  36. package/dist/record-ChYUi8PP.cjs +45 -0
  37. package/dist/record-ChYUi8PP.cjs.map +1 -0
  38. package/dist/scheduler--137L3j-.js +500 -0
  39. package/dist/scheduler--137L3j-.js.map +1 -0
  40. package/dist/scheduler-D4VthaaK.cjs +589 -0
  41. package/dist/scheduler-D4VthaaK.cjs.map +1 -0
  42. package/package.json +6 -3
  43. package/skills/doxum-runtime/SKILL.md +32 -7
  44. package/skills/doxum-runtime/agents/openai.yaml +1 -1
  45. package/skills/doxum-runtime/references/api.en.md +249 -0
  46. package/skills/doxum-runtime/references/api.zh-CN.md +242 -0
  47. package/skills/doxum-runtime/references/guide.en.md +18 -8
  48. package/skills/doxum-runtime/references/guide.zh-CN.md +14 -6
  49. package/skills/doxum-runtime/references/invariants.en.md +29 -5
  50. package/skills/doxum-runtime/references/invariants.zh-CN.md +24 -6
  51. package/skills/doxum-runtime/references/patterns.en.md +78 -18
  52. package/skills/doxum-runtime/references/patterns.zh-CN.md +76 -18
  53. package/skills/doxum-runtime/references/projections.en.md +209 -119
  54. package/skills/doxum-runtime/references/projections.zh-CN.md +200 -101
  55. package/dist/advanced-CLJCWkHa.d.cts +0 -176
  56. package/dist/advanced-CRNHfJBu.js +0 -1224
  57. package/dist/advanced-CRNHfJBu.js.map +0 -1
  58. package/dist/advanced-Cle5g3B4.cjs +0 -1517
  59. package/dist/advanced-Cle5g3B4.cjs.map +0 -1
  60. package/dist/advanced-d6J61er9.d.ts +0 -176
  61. package/dist/context-BcLKGfq8.cjs +0 -149
  62. package/dist/context-BcLKGfq8.cjs.map +0 -1
  63. package/dist/context-DFEaDUv5.js +0 -84
  64. package/dist/context-DFEaDUv5.js.map +0 -1
  65. package/dist/driver-BD3DQOW6.cjs.map +0 -1
  66. package/dist/driver-yFfvDMX5.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,15 +1,431 @@
1
- import { A as checkValue, B as insert$2, C as optional, D as ParseError, E as variant, F as parse, G as listSequence, H as installKeys, I as compiledLayout, J as toArray, K as matches, L as compiledShape, M as copyValue, N as equalTreeNode, O as checkKey, P as equalValue, R as equal, S as object, T as tree, U as installList, V as install, W as keys, Y as profile, _ as collectionEntryNode, a as observe, b as list, c as projectionRef, d as createScheduler, f as ProjectionDisposedError, g as snapshotCollectionView, h as mapRead, i as isProjection, j as copyTreeNode, k as checkTreePayload, l as assertScope, m as collectionView, n as derive, o as ownProjection, p as ProjectionError, q as remove$4, r as input, s as producerOf, t as incremental, u as assertSynchronous, v as compilePath, w as table, x as map, y as field, z as indexedKeys } from "./advanced-CRNHfJBu.js";
2
- import { a as is, c as validate, d as isRecord, i as contains$1, l as installOwn, n as contextOf, o as parent, r as children, t as bindContext } from "./context-DFEaDUv5.js";
3
- import { a as sealChanges, c as fail, i as decodeChanges, l as invalidValue, o as AddressIndex, s as MutationRejected, t as assertRuntimeWritable } from "./driver-yFfvDMX5.js";
4
- //#region core/src/address/resolve.ts
5
- const variantNode = (node, value) => {
6
- const tag = isRecord(value) && typeof value[node.tag] === "string" ? value[node.tag] : void 0;
7
- return node.variants[String(tag ?? Object.keys(node.variants)[0] ?? "")];
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") node = variantNode(node, value);
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) if (leftAddress[index] !== rightAddress[index]) return false;
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
- put(context.session, writable, id, value);
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
- remove$3(context.session, writable, id);
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$4(parent, index);
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
- if (input.schema.kind !== "object") throw new TypeError("Document schema must be a root object node.");
1916
- const invalid = checkValue(input.schema, input.initial);
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: input.schema,
1920
- document: copyValue(input.schema, input.initial),
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(input.schema);
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(input.schema, changes)
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) => mutate({
2025
- kind: "apply",
2026
- source: options?.source ?? "local"
2027
- }, options?.history ?? true, (session) => {
2028
- if (!options || options.expectedRevision !== revision) fail([], "baseline-mismatch", "apply requires the current expectedRevision.");
2029
- apply(session, decodeChanges(inputChanges), "forward");
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(input.schema, state.document);
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(input.schema, "value", p)), listener);
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
- if (!targets.some((entry) => same(entry, value))) targets.push(value);
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
- ...added,
2466
- ...removed,
2467
- ...updated
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 || isEqual(published.get(key), getNext(key))) continue;
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: (check) => published.read(check),
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
- const resetEvent = Object.freeze({ kind: "reset" });
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) => listeners.add(listener),
2561
- unsubscribe: (listener) => listeners.delete(listener)
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, cause) => {
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: (check) => {
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) => listeners.add(listener),
2653
- unsubscribe: (listener) => listeners.delete(listener)
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
- const state = createValueOutput();
2671
- return {
2672
- kind: "value",
2673
- definition: outputDefinition,
2674
- state,
2675
- output: {
2676
- kind: "value",
2677
- get owner() {
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
- output: {
2703
- kind: "collection",
2704
- get owner() {
2705
- return processor;
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.output);
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
- let outputs = begin(scopeActive, reset);
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
- let result = instance.evaluate({
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.output)),
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?.release?.();
3534
+ const current = instance;
2796
3535
  instance = void 0;
2797
- bound.forEach((entry) => {
2798
- entry.output.consumers.clear();
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.release();
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 = (source, cause) => new ProjectionError("source", source.name, [source.outputs[0].revision()], cause);
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 output = {
3305
- kind: "value",
3306
- get owner() {
3307
- return boundary;
3308
- },
3309
- consumers: /* @__PURE__ */ new Set(),
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
- state.release();
3362
- output.consumers.clear();
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
- scheduler.initialize(() => {
3366
- let active = true;
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
- state.begin(() => active, true).output.set(initial);
3369
- state.seal(true, equality);
3370
- state.publish();
3371
- state.clear();
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(boundary, cause);
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(() => void 0);
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 output = {
3442
- kind: "collection",
3443
- get owner() {
3444
- return boundary;
3445
- },
3446
- consumers: /* @__PURE__ */ new Set(),
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(() => void 0)), () => active, {
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
- state.release();
3505
- output.consumers.clear();
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
- scheduler.initialize(() => {
3509
- let active = true;
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
- stageCollectionRead(state, readLatest(() => active, state.current(() => void 0)), () => active, {
3512
- reset: true,
3513
- isEqual: equality
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(boundary, cause);
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.guards.add(guard);
4323
+ let unregisterGuard = scheduler.registerGuard(guard);
3568
4324
  let closed = false;
3569
- let detach = () => void 0;
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
- index.add(binding.target, binding);
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
- detach = context.notifications.attachProjection({
3596
- capture: (commit) => {
3597
- candidates.clear();
3598
- index.collect(commit.changes, (binding) => candidates.add(binding));
3599
- candidates.forEach((binding) => binding.capture(commit));
3600
- },
3601
- settle: scheduler.settle,
3602
- flush: scheduler.flush,
3603
- dispose: () => {
3604
- for (const binding of bindings) binding.dispose();
3605
- scheduler.run();
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
- const remove = connection(document).add({
3643
- target: selector,
3644
- capture: (commit) => {
3645
- try {
3646
- mergeValueChanges(selector, dirty, commit.changes);
3647
- boundary.mark({ reset: commit.changes.changes.some((change) => change.kind === "reset") });
3648
- } catch (cause) {
3649
- boundary.fail(cause);
3650
- }
3651
- },
3652
- dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
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
- const remove = connection(document).add({
3725
- target: selector,
3726
- capture: (commit) => {
3727
- try {
3728
- mergeCollectionChanges(selector, pending, commit.changes);
3729
- boundary.mark({
3730
- reset: pending.reset,
3731
- ...pending.reset ? {} : { candidates: pending.entries.keys() },
3732
- orderMayChange: pending.order
3733
- });
3734
- } catch (cause) {
3735
- boundary.fail(cause);
3736
- }
3737
- },
3738
- dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
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
- documents.forEach((document) => document.close());
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/registry.ts
3760
- const createSourceRegistry = (scheduler) => {
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 WeakMap();
3764
- const externalCollections = /* @__PURE__ */ new WeakMap();
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
- detachDocument?.();
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
- const owner = contextOf(readable, false)?.owner;
3901
- if (owner) detachDocument = documents.attachRoot(owner, {
3902
- capture: () => receive(false),
3903
- dispose: () => {
3904
- members.forEach((next) => next.boundary.fail(/* @__PURE__ */ new Error("Document has been disposed.")));
3905
- }
3906
- });
3907
- unsubscribe = readable.subscribe(() => receive(true));
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 (cause) {
3957
- created.close();
3958
- throw cause;
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 (cause) {
4025
- created.close();
4026
- throw cause;
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
- documents.dispose();
4051
- readables.forEach((connection) => connection.close());
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 get = (projection, requester) => publicCurrent(resolveOutput(projection, requester));
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: () => get(projection, requester),
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 readable(projection, selector, equality) {
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 setInput = (target, value, requester) => {
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 !== "collection") throw new TypeError("Projection is not a collection input.");
4171
- write.update(run);
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 batch(optionsOrCallback, maybeCallback) {
4174
- return typeof optionsOrCallback === "function" ? scheduler.batch(optionsOrCallback) : scheduler.batch(optionsOrCallback, maybeCallback);
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
- state.subscriptions.forEach((unsubscribe) => unsubscribe());
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
- scheduler.releaseProducer(current.record);
4187
- materialized.delete(definition);
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
- const own = (projection) => {
5122
+ function own(target) {
4205
5123
  assertActive();
4206
- return ownProjection(projection, state);
4207
- };
4208
- const ownOutputTree = (tree) => {
4209
- if (isProjection(tree)) return own(tree);
4210
- if (!tree || typeof tree !== "object") throw new TypeError("Incremental group output tree must be an object.");
4211
- for (const value of Object.values(tree)) ownOutputTree(value);
4212
- return tree;
4213
- };
4214
- const scopedInput = Object.assign((initial, equality) => own(input(initial, equality)), { collection: (initial) => own(input.collection(initial)) });
4215
- const scopedDerive = (dependencies, compute, equality) => own(derive(dependencies, compute, equality));
4216
- const scopedIncremental = Object.assign((dependencies, processor) => own(incremental(dependencies, processor)), {
4217
- collection: (dependencies, processor) => own(incremental.collection(dependencies, processor)),
4218
- group: (dependencies, defineOutputs, processor) => ownOutputTree(incremental.group(dependencies, defineOutputs, processor))
4219
- });
4220
- function scopeReadable(projection, selector, equality) {
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 scopeBatch(optionsOrCallback, maybeCallback) {
5172
+ function scopeUpdate(target, valueOrRun) {
5173
+ assertActive();
5174
+ updateInput(target, valueOrRun, state);
5175
+ }
5176
+ function scopeBatch(run, options) {
4248
5177
  assertActive();
4249
- return typeof optionsOrCallback === "function" ? batch(optionsOrCallback) : batch(optionsOrCallback, maybeCallback);
5178
+ return batch(run, options);
4250
5179
  }
4251
5180
  return Object.freeze({
4252
- input: scopedInput,
4253
- derive: scopedDerive,
4254
- incremental: scopedIncremental,
4255
- get: (projection) => {
5181
+ own,
5182
+ read: (projection) => {
4256
5183
  assertActive();
4257
- return get(projection, state);
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
- get,
4274
- readable,
4275
- set: setInput,
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.forEach((unsubscribe) => unsubscribe());
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
- sources.dispose();
4290
- scheduler.dispose();
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, asReadable, createDocument, createProjectionRuntime, derive, field, input, list, map, object, observe, optional, parse, read, replace, select, snapshot, table, tree, variant };
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