doxum 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -3
- package/dist/{scope-d-L87NeC.js → access-B9XDWjHy.js} +54 -2
- package/dist/access-B9XDWjHy.js.map +1 -0
- package/dist/{scope-Ezs-Y3TY.cjs → access-EQ-XiFyl.cjs} +89 -1
- package/dist/access-EQ-XiFyl.cjs.map +1 -0
- package/dist/advanced-BIGP86NL.cjs +1361 -0
- package/dist/advanced-BIGP86NL.cjs.map +1 -0
- package/dist/advanced-Bcle5C2M.js +1188 -0
- package/dist/advanced-Bcle5C2M.js.map +1 -0
- package/dist/{advanced-c0F3gpLG.d.ts → advanced-CSjF-u_k.d.cts} +50 -12
- package/dist/{advanced-GTwIWcn6.d.cts → advanced-Du3O8O1A.d.ts} +50 -12
- package/dist/advanced.cjs +1 -1
- package/dist/advanced.d.cts +2 -2
- package/dist/advanced.d.ts +2 -2
- package/dist/advanced.js +1 -1
- package/dist/{contract-XmKnroJq.d.cts → contract-BDQth1pe.d.cts} +4 -4
- package/dist/{contract-CATCCVu5.d.ts → contract-BlEl8Ibo.d.ts} +4 -4
- package/dist/{driver-BCyfqxsl.cjs → driver-DRoWiadc.cjs} +1 -1
- package/dist/{driver-BCyfqxsl.cjs.map → driver-DRoWiadc.cjs.map} +1 -1
- package/dist/{driver-CSnhMjbP.js → driver-DiN-0Mnc.js} +1 -1
- package/dist/{driver-CSnhMjbP.js.map → driver-DiN-0Mnc.js.map} +1 -1
- package/dist/index.cjs +1181 -2114
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1133 -2066
- package/dist/index.js.map +1 -1
- package/dist/{integration-CxVhq-KD.js → integration-DHxVpbxc.js} +3 -2
- package/dist/{integration-CxVhq-KD.js.map → integration-DHxVpbxc.js.map} +1 -1
- package/dist/{integration-B7VDgAOJ.cjs → integration-DL6dLuu1.cjs} +4 -3
- package/dist/{integration-B7VDgAOJ.cjs.map → integration-DL6dLuu1.cjs.map} +1 -1
- package/dist/integration.cjs +2 -2
- package/dist/integration.d.cts +1 -1
- package/dist/integration.d.ts +1 -1
- package/dist/integration.js +2 -2
- package/dist/local-sync.cjs +1 -1
- package/dist/local-sync.d.cts +1 -1
- package/dist/local-sync.d.ts +1 -1
- package/dist/local-sync.js +1 -1
- package/dist/{notification-IQIJyH3Q.js → notification-AXT94XYC.js} +2 -55
- package/dist/notification-AXT94XYC.js.map +1 -0
- package/dist/{notification-CDSUfiip.cjs → notification-DGv-_G9t.cjs} +1 -90
- package/dist/notification-DGv-_G9t.cjs.map +1 -0
- package/dist/react.cjs +2 -2
- package/dist/react.js +2 -2
- package/package.json +1 -1
- package/skills/doxum-runtime/SKILL.md +2 -1
- package/skills/doxum-runtime/references/projections.en.md +41 -3
- package/skills/doxum-runtime/references/projections.zh-CN.md +36 -2
- package/dist/advanced-DgRnyRAc.js +0 -233
- package/dist/advanced-DgRnyRAc.js.map +0 -1
- package/dist/advanced-Dgu8-bPS.cjs +0 -274
- package/dist/advanced-Dgu8-bPS.cjs.map +0 -1
- package/dist/notification-CDSUfiip.cjs.map +0 -1
- package/dist/notification-IQIJyH3Q.js.map +0 -1
- package/dist/scope-Ezs-Y3TY.cjs.map +0 -1
- package/dist/scope-d-L87NeC.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,97 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_issue = require("./issue-DhrNdQNg.cjs");
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
//#region core/src/schema.ts
|
|
8
|
-
const node = (value) => Object.freeze(value);
|
|
9
|
-
const field = (validator) => node({
|
|
10
|
-
kind: "field",
|
|
11
|
-
...validator ? { validator } : {}
|
|
12
|
-
});
|
|
13
|
-
const optional = (value) => {
|
|
14
|
-
if (![
|
|
15
|
-
"field",
|
|
16
|
-
"variant",
|
|
17
|
-
"map",
|
|
18
|
-
"list",
|
|
19
|
-
"tree"
|
|
20
|
-
].includes(value.kind)) throw new TypeError("Only field, variant, map, list and tree nodes support optional presence.");
|
|
21
|
-
return node({
|
|
22
|
-
...value,
|
|
23
|
-
optional: true
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
const object = (shape) => node({
|
|
27
|
-
kind: "object",
|
|
28
|
-
shape: Object.freeze({ ...shape })
|
|
29
|
-
});
|
|
30
|
-
const variant = (tag, variants) => node({
|
|
31
|
-
kind: "variant",
|
|
32
|
-
tag,
|
|
33
|
-
variants: Object.freeze({ ...variants })
|
|
34
|
-
});
|
|
35
|
-
const table = (value, options) => node({
|
|
36
|
-
kind: "table",
|
|
37
|
-
value,
|
|
38
|
-
...options
|
|
39
|
-
});
|
|
40
|
-
const map = (value, options) => node({
|
|
41
|
-
kind: "map",
|
|
42
|
-
value,
|
|
43
|
-
...options
|
|
44
|
-
});
|
|
45
|
-
const list = (value, config) => node({
|
|
46
|
-
kind: "list",
|
|
47
|
-
keyOf: config.keyOf,
|
|
48
|
-
value
|
|
49
|
-
});
|
|
50
|
-
const tree = (value) => node({
|
|
51
|
-
kind: "tree",
|
|
52
|
-
value
|
|
53
|
-
});
|
|
54
|
-
const paths = /* @__PURE__ */ new WeakMap();
|
|
55
|
-
const pathProxy = (nodes, address, owner) => {
|
|
56
|
-
const proxy = new Proxy({}, { get: (_target, property) => {
|
|
57
|
-
if (typeof property !== "string") return void 0;
|
|
58
|
-
if (nodes.every((node) => node.kind === "table" || node.kind === "map") && property === "item") return (id) => {
|
|
59
|
-
if (typeof id !== "string") throw new TypeError("Collection keys must be strings.");
|
|
60
|
-
for (const node of nodes) if (node.kind === "table" || node.kind === "map") {
|
|
61
|
-
const invalid = require_scope.checkKey(node.key, id, [...address, id]);
|
|
62
|
-
if (invalid) throw new TypeError(invalid.message);
|
|
63
|
-
}
|
|
64
|
-
return pathProxy(nodes.map((node) => node.value), [...address, id], owner);
|
|
65
|
-
};
|
|
66
|
-
const children = nodes.flatMap((node) => {
|
|
67
|
-
if (node.kind === "object") return Object.hasOwn(node.shape, property) ? [node.shape[property]] : [];
|
|
68
|
-
if (node.kind !== "variant") return [];
|
|
69
|
-
if (property === node.tag) return [field()];
|
|
70
|
-
return Object.values(node.variants).flatMap((branch) => Object.hasOwn(branch.shape, property) ? [branch.shape[property]] : []);
|
|
71
|
-
});
|
|
72
|
-
if (!children.length) throw new TypeError(`Invalid schema path segment: ${property}`);
|
|
73
|
-
return pathProxy(children, [...address, property], owner);
|
|
74
|
-
} });
|
|
75
|
-
paths.set(proxy, {
|
|
76
|
-
nodes,
|
|
77
|
-
address,
|
|
78
|
-
owner
|
|
79
|
-
});
|
|
80
|
-
return proxy;
|
|
81
|
-
};
|
|
82
|
-
const compilePath = (owner, kind, pick) => {
|
|
83
|
-
const scope = {};
|
|
84
|
-
const value = pick(pathProxy([owner], [], scope));
|
|
85
|
-
const selected = typeof value === "object" && value !== null ? paths.get(value) : void 0;
|
|
86
|
-
if (!selected || selected.owner !== scope) throw new TypeError("Selector must return a path from its callback.");
|
|
87
|
-
if (kind === "collection" && !selected.nodes.every((node) => node.kind === "table" || node.kind === "map")) throw new TypeError("Collection selectors require a table or map path.");
|
|
88
|
-
return Object.freeze({
|
|
89
|
-
kind,
|
|
90
|
-
schema: owner,
|
|
91
|
-
address: Object.freeze([...selected.address])
|
|
92
|
-
});
|
|
93
|
-
};
|
|
94
|
-
//#endregion
|
|
3
|
+
const require_access = require("./access-EQ-XiFyl.cjs");
|
|
4
|
+
const require_advanced = require("./advanced-BIGP86NL.cjs");
|
|
5
|
+
const require_notification = require("./notification-DGv-_G9t.cjs");
|
|
6
|
+
const require_driver = require("./driver-DRoWiadc.cjs");
|
|
95
7
|
//#region core/src/impact.ts
|
|
96
8
|
const queries = /* @__PURE__ */ new WeakMap();
|
|
97
9
|
const affectsTarget = (impact, value) => queries.get(impact).affects(value);
|
|
@@ -183,8 +95,8 @@ const createImpact = (schema, changes) => {
|
|
|
183
95
|
};
|
|
184
96
|
const impact = Object.freeze({
|
|
185
97
|
kind: reset ? "reset" : "incremental",
|
|
186
|
-
affects: (pick) => implementation.affects(compilePath(schema, "value", pick)),
|
|
187
|
-
collection: (pick) => implementation.collection(compilePath(schema, "collection", pick))
|
|
98
|
+
affects: (pick) => implementation.affects(require_advanced.compilePath(schema, "value", pick)),
|
|
99
|
+
collection: (pick) => implementation.collection(require_advanced.compilePath(schema, "collection", pick))
|
|
188
100
|
});
|
|
189
101
|
queries.set(impact, implementation);
|
|
190
102
|
return impact;
|
|
@@ -278,7 +190,7 @@ const createHistory = (input) => {
|
|
|
278
190
|
current: () => state,
|
|
279
191
|
revision: () => revision,
|
|
280
192
|
subscribe: (listener) => {
|
|
281
|
-
if (disposed) throw new
|
|
193
|
+
if (disposed) throw new require_access.DocumentDisposedError();
|
|
282
194
|
listeners.add(listener);
|
|
283
195
|
return () => {
|
|
284
196
|
listeners.delete(listener);
|
|
@@ -403,8 +315,8 @@ const copyNode = (node) => ({
|
|
|
403
315
|
});
|
|
404
316
|
const transition = (node, key, beforePresent, before, afterPresent, after) => {
|
|
405
317
|
if (beforePresent === afterPresent && (!beforePresent || Object.is(before, after))) return;
|
|
406
|
-
if (node.kind !== "field" && beforePresent === afterPresent &&
|
|
407
|
-
const value = afterPresent ? node.kind === "field" ? after :
|
|
318
|
+
if (node.kind !== "field" && beforePresent === afterPresent && require_access.equalValue(node, before, after)) return;
|
|
319
|
+
const value = afterPresent ? node.kind === "field" ? after : require_access.copyValue(node, after) : void 0;
|
|
408
320
|
require_issue.profile.recorder("transitions");
|
|
409
321
|
return !beforePresent ? {
|
|
410
322
|
key,
|
|
@@ -477,7 +389,7 @@ const restore = (fact, schema, root, skip = 0) => {
|
|
|
477
389
|
const at = skip ? fact.at.slice(skip) : fact.at;
|
|
478
390
|
if (fact.kind === "member") {
|
|
479
391
|
const member = fact.member;
|
|
480
|
-
const value = member.present ?
|
|
392
|
+
const value = member.present ? require_access.copyValue(member.node, member.value) : void 0;
|
|
481
393
|
if (!at.length) return value;
|
|
482
394
|
const location = require_issue.resolveLocated(schema, root, at);
|
|
483
395
|
if (!location) throw new Error("Cannot restore an unresolved member.");
|
|
@@ -496,7 +408,7 @@ const restore = (fact, schema, root, skip = 0) => {
|
|
|
496
408
|
return root;
|
|
497
409
|
};
|
|
498
410
|
const reconstructBefore = (node, current, records, offset) => {
|
|
499
|
-
let before =
|
|
411
|
+
let before = require_access.copyValue(node, current);
|
|
500
412
|
for (let i = records.length - 1; i >= 0; i--) if (records[i].kind !== "order") before = restore(records[i], node, before, offset);
|
|
501
413
|
for (const record of records) if (record.kind === "order") before = restore(record, node, before, offset);
|
|
502
414
|
return before;
|
|
@@ -536,7 +448,7 @@ const sealTree = (fact, state, changes) => {
|
|
|
536
448
|
for (const [id, before] of fact.nodes) {
|
|
537
449
|
const after = Object.hasOwn(tree.nodes, id) ? tree.nodes[id] : void 0;
|
|
538
450
|
if (!before && !after) continue;
|
|
539
|
-
if (before && after && before.parentId === after.parentId && require_issue.equal(before.children, after.children) && Object.hasOwn(before, "value") === Object.hasOwn(after, "value") &&
|
|
451
|
+
if (before && after && before.parentId === after.parentId && require_issue.equal(before.children, after.children) && Object.hasOwn(before, "value") === Object.hasOwn(after, "value") && require_access.equalValue(node.value, before.value, after.value)) continue;
|
|
540
452
|
nodes.push(!before ? {
|
|
541
453
|
id,
|
|
542
454
|
kind: "added",
|
|
@@ -744,7 +656,7 @@ var ChangeRecorder = class {
|
|
|
744
656
|
const fact = facts[i];
|
|
745
657
|
if (fact.kind === "members") for (const member of fact.members?.values() ?? []) {
|
|
746
658
|
if (!member) continue;
|
|
747
|
-
installMember(fact.parent, require_issue.memberKey(fact, member.key), member.present, member.present ?
|
|
659
|
+
installMember(fact.parent, require_issue.memberKey(fact, member.key), member.present, member.present ? require_access.copyValue(member.node, member.value) : void 0);
|
|
748
660
|
}
|
|
749
661
|
else if (fact.kind === "tree") restore(fact, this.state.schema, this.state.document);
|
|
750
662
|
}
|
|
@@ -752,12 +664,12 @@ var ChangeRecorder = class {
|
|
|
752
664
|
}
|
|
753
665
|
seal() {
|
|
754
666
|
if (this.resetBefore) {
|
|
755
|
-
if (
|
|
667
|
+
if (require_access.equalValue(this.state.schema, this.resetBefore.value, this.state.document)) return require_driver.sealChanges([]);
|
|
756
668
|
require_issue.profile.recorder("sealed");
|
|
757
669
|
return require_driver.sealChanges([{
|
|
758
670
|
kind: "reset",
|
|
759
671
|
before: this.resetBefore.value,
|
|
760
|
-
after:
|
|
672
|
+
after: require_access.copyValue(this.state.schema, this.state.document)
|
|
761
673
|
}]);
|
|
762
674
|
}
|
|
763
675
|
const changes = [];
|
|
@@ -784,7 +696,7 @@ var MutationSession = class {
|
|
|
784
696
|
this.resolvedRoot = state.document;
|
|
785
697
|
}
|
|
786
698
|
validate(node, value, at) {
|
|
787
|
-
const issue =
|
|
699
|
+
const issue = require_access.checkValue(node, value, at);
|
|
788
700
|
if (issue) require_issue.invalidValue(issue);
|
|
789
701
|
}
|
|
790
702
|
resolveContainer(at) {
|
|
@@ -806,7 +718,7 @@ var MutationSession = class {
|
|
|
806
718
|
this.validate(this.state.schema, value, at);
|
|
807
719
|
if (Object.is(this.state.document, value)) return;
|
|
808
720
|
this.recorder.reset();
|
|
809
|
-
this.state.document =
|
|
721
|
+
this.state.document = require_access.copyValue(this.state.schema, value);
|
|
810
722
|
this.invalidate();
|
|
811
723
|
return;
|
|
812
724
|
}
|
|
@@ -848,12 +760,12 @@ var MutationSession = class {
|
|
|
848
760
|
const previous = container.parent[physicalKey];
|
|
849
761
|
if (existed === present && (!present || Object.is(previous, value))) return false;
|
|
850
762
|
if (parentNode.kind === "map" || parentNode.kind === "table") {
|
|
851
|
-
const issue =
|
|
763
|
+
const issue = require_access.checkKey(parentNode.key, key, []);
|
|
852
764
|
if (issue) return require_issue.fail(container.at.concat(key, ...issue.address), "invalid-key", issue.message);
|
|
853
765
|
}
|
|
854
766
|
if (present) {
|
|
855
767
|
if (node.kind === "field") {
|
|
856
|
-
const issue =
|
|
768
|
+
const issue = require_access.checkValue(node, value, []);
|
|
857
769
|
if (issue) return require_issue.invalidValue(issue, container.at.concat(key, ...issue.address));
|
|
858
770
|
} else this.validate(node, value, container.at.concat(key));
|
|
859
771
|
if (parentNode.kind === "list" && parentNode.keyOf(value) !== key) return require_issue.fail(container.at.concat(key), "invalid-list-key", "Replacing an item must retain its addressed key.");
|
|
@@ -861,7 +773,7 @@ var MutationSession = class {
|
|
|
861
773
|
const membershipChanged = entry && existed !== present;
|
|
862
774
|
if (membershipChanged && (parentNode.kind === "list" || parentNode.kind === "table")) this.recorder.order(container);
|
|
863
775
|
this.recorder.member(container, key, member, physicalKey);
|
|
864
|
-
const next = present ? node.kind === "field" ? value :
|
|
776
|
+
const next = present ? node.kind === "field" ? value : require_access.copyValue(node, value) : void 0;
|
|
865
777
|
installMember(container.parent, physicalKey, present, next);
|
|
866
778
|
if (node.kind !== "field" || membershipChanged) this.invalidate();
|
|
867
779
|
return membershipChanged;
|
|
@@ -931,18 +843,18 @@ function apply(session, changes, direction) {
|
|
|
931
843
|
//#region core/src/runtime.ts
|
|
932
844
|
const createDocument = (input) => {
|
|
933
845
|
if (input.schema.kind !== "object") throw new TypeError("Document schema must be a root object node.");
|
|
934
|
-
const invalid =
|
|
935
|
-
if (invalid) throw new
|
|
846
|
+
const invalid = require_access.checkValue(input.schema, input.initial);
|
|
847
|
+
if (invalid) throw new require_access.ParseError(invalid);
|
|
936
848
|
const state = {
|
|
937
849
|
schema: input.schema,
|
|
938
|
-
document:
|
|
850
|
+
document: require_access.copyValue(input.schema, input.initial),
|
|
939
851
|
disposed: false
|
|
940
852
|
};
|
|
941
853
|
let revision = 0, busy = false;
|
|
942
854
|
let runtime, notification;
|
|
943
855
|
const idle = () => {
|
|
944
|
-
if (state.disposed) throw new
|
|
945
|
-
if (busy ||
|
|
856
|
+
if (state.disposed) throw new require_access.DocumentDisposedError();
|
|
857
|
+
if (busy || require_access.accessOf(runtime).projectionLocks) throw new require_access.DocumentReentrancyError();
|
|
946
858
|
};
|
|
947
859
|
const writable = (intent) => {
|
|
948
860
|
idle();
|
|
@@ -1000,7 +912,7 @@ const createDocument = (input) => {
|
|
|
1000
912
|
issues: [error.issue],
|
|
1001
913
|
revision
|
|
1002
914
|
};
|
|
1003
|
-
if (intent.kind === "update" && error instanceof
|
|
915
|
+
if (intent.kind === "update" && error instanceof require_access.TransactionRejected) return {
|
|
1004
916
|
status: "rejected",
|
|
1005
917
|
issues: error.issues,
|
|
1006
918
|
revision
|
|
@@ -1033,7 +945,7 @@ const createDocument = (input) => {
|
|
|
1033
945
|
kind: "update",
|
|
1034
946
|
source
|
|
1035
947
|
}, options?.history ?? true, (session) => {
|
|
1036
|
-
value = run(
|
|
948
|
+
value = run(require_access.createAccess({
|
|
1037
949
|
state,
|
|
1038
950
|
session
|
|
1039
951
|
}));
|
|
@@ -1059,15 +971,15 @@ const createDocument = (input) => {
|
|
|
1059
971
|
}, source !== "remote", (session) => session.replace([], value));
|
|
1060
972
|
},
|
|
1061
973
|
snapshot: () => {
|
|
1062
|
-
if (state.disposed) throw new
|
|
1063
|
-
return
|
|
974
|
+
if (state.disposed) throw new require_access.DocumentDisposedError();
|
|
975
|
+
return require_access.copyValue(input.schema, state.document);
|
|
1064
976
|
},
|
|
1065
977
|
subscribe: ((pick, listener) => {
|
|
1066
|
-
if (state.disposed) throw new
|
|
978
|
+
if (state.disposed) throw new require_access.DocumentDisposedError();
|
|
1067
979
|
if (!listener) return require_notification.subscribeRoot(notification, pick);
|
|
1068
980
|
const picks = Array.isArray(pick) ? pick : [pick];
|
|
1069
981
|
if (!picks.length) throw new TypeError("Expected at least one subscription path.");
|
|
1070
|
-
return require_notification.subscribeTargets(notification, picks.map((p) => compilePath(input.schema, "value", p)), listener);
|
|
982
|
+
return require_notification.subscribeTargets(notification, picks.map((p) => require_advanced.compilePath(input.schema, "value", p)), listener);
|
|
1071
983
|
}),
|
|
1072
984
|
history: history.api,
|
|
1073
985
|
dispose: () => {
|
|
@@ -1082,7 +994,7 @@ const createDocument = (input) => {
|
|
|
1082
994
|
}
|
|
1083
995
|
}
|
|
1084
996
|
};
|
|
1085
|
-
|
|
997
|
+
require_access.bindRuntimeAccess(runtime, state);
|
|
1086
998
|
require_driver.bindRuntimeDriver(runtime);
|
|
1087
999
|
notification = require_notification.createNotification(runtime);
|
|
1088
1000
|
require_notification.bindDocumentReadable(history.api, runtime);
|
|
@@ -1096,2059 +1008,1227 @@ const asReadable = (runtime) => {
|
|
|
1096
1008
|
revision: runtime.revision,
|
|
1097
1009
|
subscribe: runtime.subscribe
|
|
1098
1010
|
};
|
|
1099
|
-
|
|
1011
|
+
require_access.bindRuntimeAccess(readable, require_access.accessOf(runtime));
|
|
1100
1012
|
require_notification.shareNotification(runtime, readable);
|
|
1101
1013
|
return Object.freeze(readable);
|
|
1102
1014
|
};
|
|
1103
1015
|
//#endregion
|
|
1104
1016
|
//#region core/src/projection/select.ts
|
|
1105
|
-
const read = (runtime, selector) =>
|
|
1106
|
-
//#endregion
|
|
1107
|
-
//#region core/src/projection/contract.ts
|
|
1108
|
-
var ProjectionError = class extends Error {
|
|
1109
|
-
constructor(phase, identity, revisions, cause) {
|
|
1110
|
-
super(`Projection ${phase} failed: ${identity}`, { cause });
|
|
1111
|
-
this.phase = phase;
|
|
1112
|
-
this.identity = identity;
|
|
1113
|
-
this.revisions = revisions;
|
|
1114
|
-
this.name = "ProjectionError";
|
|
1115
|
-
}
|
|
1116
|
-
};
|
|
1117
|
-
var ProjectionDisposedError = class extends Error {
|
|
1118
|
-
constructor() {
|
|
1119
|
-
super("Projection has been disposed.");
|
|
1120
|
-
this.name = "ProjectionDisposedError";
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1017
|
+
const read = (runtime, selector) => require_access.readWith(runtime, selector);
|
|
1123
1018
|
//#endregion
|
|
1124
|
-
//#region core/src/projection/
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
let
|
|
1129
|
-
let
|
|
1130
|
-
let
|
|
1131
|
-
let
|
|
1132
|
-
let
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
const
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1019
|
+
//#region core/src/projection/value-output.ts
|
|
1020
|
+
const unset = Symbol("projection-value-unset");
|
|
1021
|
+
/** Owns staged and published state for one scalar Projection output. */
|
|
1022
|
+
const createValueOutput = () => {
|
|
1023
|
+
let value;
|
|
1024
|
+
let staged = unset;
|
|
1025
|
+
let next;
|
|
1026
|
+
let initialized = false;
|
|
1027
|
+
let revision = 0;
|
|
1028
|
+
let changed = false;
|
|
1029
|
+
let reset = false;
|
|
1030
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1031
|
+
const begin = (active, initialize) => {
|
|
1032
|
+
staged = unset;
|
|
1033
|
+
next = value;
|
|
1034
|
+
changed = false;
|
|
1035
|
+
reset = initialize;
|
|
1036
|
+
return {
|
|
1037
|
+
previous: initialized ? value : void 0,
|
|
1038
|
+
next: () => {
|
|
1039
|
+
require_advanced.assertScope(active);
|
|
1040
|
+
if (staged !== unset) return staged;
|
|
1041
|
+
return initialize ? void 0 : initialized ? value : void 0;
|
|
1042
|
+
},
|
|
1043
|
+
output: Object.freeze({ set: (candidate) => {
|
|
1044
|
+
require_advanced.assertScope(active);
|
|
1045
|
+
staged = candidate;
|
|
1046
|
+
} })
|
|
1047
|
+
};
|
|
1150
1048
|
};
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
if (
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
while (i > 0) {
|
|
1159
|
-
const p = i - 1 >> 1;
|
|
1160
|
-
if (heap[p].order < node.order) break;
|
|
1161
|
-
heap[i] = heap[p];
|
|
1162
|
-
i = p;
|
|
1049
|
+
const seal = (nextReset, isEqual) => {
|
|
1050
|
+
if (reset && staged === unset) throw new TypeError("Value output must be initialized on reset.");
|
|
1051
|
+
if (staged === unset) {
|
|
1052
|
+
reset = nextReset;
|
|
1053
|
+
changed = false;
|
|
1054
|
+
next = value;
|
|
1055
|
+
return false;
|
|
1163
1056
|
}
|
|
1164
|
-
|
|
1057
|
+
const candidate = staged;
|
|
1058
|
+
changed = !initialized || !isEqual(value, candidate);
|
|
1059
|
+
next = changed ? candidate : value;
|
|
1060
|
+
reset = nextReset;
|
|
1061
|
+
return changed;
|
|
1165
1062
|
};
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1063
|
+
return {
|
|
1064
|
+
begin,
|
|
1065
|
+
seal,
|
|
1066
|
+
context: (active, cause) => {
|
|
1067
|
+
require_advanced.assertScope(active);
|
|
1068
|
+
return Object.freeze({
|
|
1069
|
+
kind: "value",
|
|
1070
|
+
value: next,
|
|
1071
|
+
revision,
|
|
1072
|
+
reset,
|
|
1073
|
+
cause
|
|
1074
|
+
});
|
|
1075
|
+
},
|
|
1076
|
+
current: (check) => {
|
|
1077
|
+
check();
|
|
1078
|
+
return value;
|
|
1079
|
+
},
|
|
1080
|
+
revision: () => revision,
|
|
1081
|
+
reset: () => reset,
|
|
1082
|
+
publish: () => {
|
|
1083
|
+
if (changed && initialized) revision++;
|
|
1084
|
+
if (changed) value = next;
|
|
1085
|
+
initialized = true;
|
|
1086
|
+
},
|
|
1087
|
+
emit: (call) => {
|
|
1088
|
+
require_issue.profile.materialized.notification();
|
|
1089
|
+
Array.from(listeners).forEach((listener) => call(listener));
|
|
1090
|
+
},
|
|
1091
|
+
clear: () => {
|
|
1092
|
+
staged = unset;
|
|
1093
|
+
next = value;
|
|
1094
|
+
changed = false;
|
|
1095
|
+
reset = false;
|
|
1096
|
+
},
|
|
1097
|
+
release: () => {
|
|
1098
|
+
listeners.clear();
|
|
1099
|
+
staged = unset;
|
|
1100
|
+
value = next = void 0;
|
|
1101
|
+
initialized = false;
|
|
1102
|
+
revision = 0;
|
|
1103
|
+
changed = false;
|
|
1104
|
+
reset = false;
|
|
1105
|
+
},
|
|
1106
|
+
subscribe: (listener) => listeners.add(listener),
|
|
1107
|
+
unsubscribe: (listener) => listeners.delete(listener)
|
|
1181
1108
|
};
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
if (
|
|
1109
|
+
};
|
|
1110
|
+
//#endregion
|
|
1111
|
+
//#region core/src/projection/processor.ts
|
|
1112
|
+
/** Materializes every derive/incremental/group definition through one processor lifecycle. */
|
|
1113
|
+
const createProcessor = (scheduler, definition, dependencies) => {
|
|
1114
|
+
scheduler.assertIdle();
|
|
1115
|
+
let processor;
|
|
1116
|
+
let instance;
|
|
1117
|
+
let cause;
|
|
1118
|
+
const check = () => {
|
|
1119
|
+
if (!scheduler.active || processor.disposed) throw new require_advanced.ProjectionDisposedError();
|
|
1120
|
+
if (processor.fault) throw processor.fault;
|
|
1193
1121
|
};
|
|
1194
|
-
const
|
|
1195
|
-
if (
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
node.fault = error;
|
|
1221
|
-
if (!build) try {
|
|
1222
|
-
changed = node.evaluate(true);
|
|
1223
|
-
const failure = node.sources.find((source) => source.fault)?.fault;
|
|
1224
|
-
if (failure) throw failure;
|
|
1225
|
-
node.fault = void 0;
|
|
1226
|
-
} catch (cause) {
|
|
1227
|
-
node.fault = errorFor(node, cause);
|
|
1228
|
-
errors.push(node.fault);
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
completed.push(node);
|
|
1233
|
-
if (changed || wasFaulted || node.fault) {
|
|
1234
|
-
require_issue.profile.projection("publishedNodes");
|
|
1235
|
-
emissions.add(node);
|
|
1236
|
-
node.consumers.forEach(enqueue);
|
|
1122
|
+
const bound = definition.outputs.map((outputDefinition) => {
|
|
1123
|
+
if (outputDefinition.kind === "value") {
|
|
1124
|
+
const state = createValueOutput();
|
|
1125
|
+
return {
|
|
1126
|
+
kind: "value",
|
|
1127
|
+
definition: outputDefinition,
|
|
1128
|
+
state,
|
|
1129
|
+
output: {
|
|
1130
|
+
kind: "value",
|
|
1131
|
+
get owner() {
|
|
1132
|
+
return processor;
|
|
1133
|
+
},
|
|
1134
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1135
|
+
context: (active) => state.context(active, cause),
|
|
1136
|
+
current: () => state.current(check),
|
|
1137
|
+
revision: state.revision,
|
|
1138
|
+
reset: state.reset,
|
|
1139
|
+
subscribe: (listener) => {
|
|
1140
|
+
check();
|
|
1141
|
+
const wrapped = () => listener();
|
|
1142
|
+
state.subscribe(wrapped);
|
|
1143
|
+
return () => state.unsubscribe(wrapped);
|
|
1144
|
+
},
|
|
1145
|
+
emit: state.emit,
|
|
1146
|
+
clear: state.clear,
|
|
1147
|
+
release: state.release
|
|
1237
1148
|
}
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
const state = require_advanced.createCollectionOutput();
|
|
1152
|
+
return {
|
|
1153
|
+
kind: "collection",
|
|
1154
|
+
definition: outputDefinition,
|
|
1155
|
+
state,
|
|
1156
|
+
output: {
|
|
1157
|
+
kind: "collection",
|
|
1158
|
+
get owner() {
|
|
1159
|
+
return processor;
|
|
1160
|
+
},
|
|
1161
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1162
|
+
context: (active) => state.context(active, cause),
|
|
1163
|
+
current: () => state.current(check),
|
|
1164
|
+
revision: state.revision,
|
|
1165
|
+
reset: state.reset,
|
|
1166
|
+
subscribe: (listener) => {
|
|
1167
|
+
check();
|
|
1168
|
+
const wrapped = (change) => listener(change);
|
|
1169
|
+
state.subscribe(wrapped);
|
|
1170
|
+
return () => state.unsubscribe(wrapped);
|
|
1171
|
+
},
|
|
1172
|
+
emit: state.emit,
|
|
1173
|
+
clear: state.clear,
|
|
1174
|
+
release: state.release
|
|
1238
1175
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1176
|
+
};
|
|
1177
|
+
});
|
|
1178
|
+
const begin = (active, initialize) => Object.freeze(bound.map((entry) => {
|
|
1179
|
+
if (entry.kind === "value") {
|
|
1180
|
+
const evaluation = entry.state.begin(active, initialize);
|
|
1181
|
+
return Object.freeze({
|
|
1182
|
+
kind: "value",
|
|
1183
|
+
...evaluation
|
|
1184
|
+
});
|
|
1243
1185
|
}
|
|
1186
|
+
const evaluation = entry.state.begin(active, initialize);
|
|
1187
|
+
return Object.freeze({
|
|
1188
|
+
kind: "collection",
|
|
1189
|
+
...evaluation
|
|
1190
|
+
});
|
|
1191
|
+
}));
|
|
1192
|
+
const seal = (reset) => {
|
|
1193
|
+
const changed = [];
|
|
1194
|
+
for (const entry of bound) if (entry.state.seal(reset, entry.definition.equality)) changed.push(entry.output);
|
|
1195
|
+
return Object.freeze(changed);
|
|
1244
1196
|
};
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
phase = "notify";
|
|
1249
|
-
lock(true);
|
|
1250
|
-
const reportFailures = [];
|
|
1197
|
+
const run = (reset, runCause) => {
|
|
1198
|
+
cause = runCause;
|
|
1199
|
+
let active = true;
|
|
1251
1200
|
try {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1201
|
+
const scopeActive = () => active;
|
|
1202
|
+
const sources = Object.freeze(dependencies.map((output) => output.context(scopeActive)));
|
|
1203
|
+
let outputs = begin(scopeActive, reset);
|
|
1204
|
+
if (!instance) instance = definition.create();
|
|
1205
|
+
require_issue.profile.materialized[reset ? "rebuilt" : "updated"]();
|
|
1206
|
+
let result = instance.evaluate({
|
|
1207
|
+
reset,
|
|
1208
|
+
cause: runCause,
|
|
1209
|
+
sources,
|
|
1210
|
+
outputs
|
|
1211
|
+
});
|
|
1212
|
+
require_advanced.assertSynchronous(result);
|
|
1213
|
+
if (result?.kind === "rebuild") {
|
|
1214
|
+
if (reset) throw new TypeError("Initial or reset projection processor cannot rebuild.");
|
|
1215
|
+
instance.release?.();
|
|
1216
|
+
instance = definition.create();
|
|
1217
|
+
outputs = begin(scopeActive, true);
|
|
1218
|
+
result = instance.evaluate({
|
|
1219
|
+
reset: true,
|
|
1220
|
+
cause: runCause,
|
|
1221
|
+
sources,
|
|
1222
|
+
outputs
|
|
1223
|
+
});
|
|
1224
|
+
require_advanced.assertSynchronous(result);
|
|
1225
|
+
if (result?.kind === "rebuild") throw new TypeError("Reset projection processor cannot rebuild again.");
|
|
1226
|
+
return seal(true);
|
|
1263
1227
|
}
|
|
1264
|
-
return
|
|
1265
|
-
phase: error.phase === "listener" ? "listener" : "processor",
|
|
1266
|
-
error
|
|
1267
|
-
})), ...reportFailures.map((error) => Object.freeze({
|
|
1268
|
-
phase: "listener",
|
|
1269
|
-
error
|
|
1270
|
-
}))]);
|
|
1228
|
+
return seal(reset);
|
|
1271
1229
|
} finally {
|
|
1272
|
-
|
|
1273
|
-
completed.forEach((node) => node.clear());
|
|
1274
|
-
pending.clear();
|
|
1275
|
-
queued.clear();
|
|
1276
|
-
completed.length = 0;
|
|
1277
|
-
emissions.clear();
|
|
1278
|
-
errors.length = 0;
|
|
1279
|
-
reportingFailures = reportFailures;
|
|
1280
|
-
lock(false);
|
|
1281
|
-
phase = "idle";
|
|
1230
|
+
active = false;
|
|
1282
1231
|
}
|
|
1283
1232
|
};
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
} catch (error) {
|
|
1308
|
-
failed = true;
|
|
1309
|
-
throw error;
|
|
1310
|
-
} finally {
|
|
1311
|
-
depth--;
|
|
1312
|
-
if (!depth) try {
|
|
1313
|
-
if (failed) try {
|
|
1314
|
-
run();
|
|
1315
|
-
} catch {}
|
|
1316
|
-
else run();
|
|
1317
|
-
} finally {
|
|
1318
|
-
activeBatch = void 0;
|
|
1319
|
-
}
|
|
1233
|
+
const order = scheduler.order();
|
|
1234
|
+
processor = {
|
|
1235
|
+
kind: "processor",
|
|
1236
|
+
order,
|
|
1237
|
+
name: definition.name ? `${definition.name} (${order})` : `projection-${order}`,
|
|
1238
|
+
dependencies: Object.freeze([...dependencies]),
|
|
1239
|
+
outputs: Object.freeze(bound.map((entry) => entry.output)),
|
|
1240
|
+
fault: void 0,
|
|
1241
|
+
disposed: false,
|
|
1242
|
+
evaluate: run,
|
|
1243
|
+
publish: () => bound.forEach((entry) => entry.state.publish()),
|
|
1244
|
+
clear: () => {
|
|
1245
|
+
bound.forEach((entry) => entry.state.clear());
|
|
1246
|
+
cause = void 0;
|
|
1247
|
+
},
|
|
1248
|
+
release: () => {
|
|
1249
|
+
instance?.release?.();
|
|
1250
|
+
instance = void 0;
|
|
1251
|
+
bound.forEach((entry) => {
|
|
1252
|
+
entry.output.consumers.clear();
|
|
1253
|
+
entry.state.release();
|
|
1254
|
+
});
|
|
1255
|
+
cause = void 0;
|
|
1320
1256
|
}
|
|
1321
1257
|
};
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1258
|
+
try {
|
|
1259
|
+
scheduler.initialize(() => {
|
|
1260
|
+
const invalid = dependencies.find((output) => output.owner.fault || output.owner.disposed);
|
|
1261
|
+
if (invalid) throw invalid.owner.fault ?? new require_advanced.ProjectionDisposedError();
|
|
1262
|
+
if (processor.evaluate(true, void 0).length) processor.publish();
|
|
1263
|
+
processor.clear();
|
|
1264
|
+
});
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
processor.release();
|
|
1267
|
+
throw error;
|
|
1268
|
+
}
|
|
1269
|
+
scheduler.addProcessor(processor);
|
|
1270
|
+
return processor;
|
|
1271
|
+
};
|
|
1272
|
+
//#endregion
|
|
1273
|
+
//#region core/src/projection/projection-readable.ts
|
|
1274
|
+
let activeTracker;
|
|
1275
|
+
const trackKey = (key) => {
|
|
1276
|
+
activeTracker?.keys.add(key);
|
|
1277
|
+
};
|
|
1278
|
+
const trackStructure = () => {
|
|
1279
|
+
if (activeTracker) activeTracker.structure = true;
|
|
1280
|
+
};
|
|
1281
|
+
const trackAll = () => {
|
|
1282
|
+
if (activeTracker) activeTracker.all = true;
|
|
1283
|
+
};
|
|
1284
|
+
const isMapLike = (value) => value instanceof Map || value !== null && typeof value === "object" && typeof value.get === "function" && typeof value.keys === "function" && typeof value.entries === "function";
|
|
1285
|
+
const trackedMap = (source) => {
|
|
1286
|
+
const view = {
|
|
1287
|
+
get: (key) => {
|
|
1288
|
+
trackKey(key);
|
|
1289
|
+
return source.get(key);
|
|
1334
1290
|
},
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
projectionHandles.add(handle);
|
|
1291
|
+
has: (key) => {
|
|
1292
|
+
trackKey(key);
|
|
1293
|
+
return source.has(key);
|
|
1339
1294
|
},
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
assertIdle();
|
|
1344
|
-
if (record.consumers.size) throw new Error("Projection source is still used by a node.");
|
|
1345
|
-
record.disposed = true;
|
|
1346
|
-
pending.delete(record);
|
|
1347
|
-
record.clear();
|
|
1348
|
-
records.delete(handle);
|
|
1349
|
-
}
|
|
1295
|
+
get size() {
|
|
1296
|
+
trackStructure();
|
|
1297
|
+
return source.size;
|
|
1350
1298
|
},
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
if (!node || !nodes.has(node)) throw new Error("Unknown projection node.");
|
|
1355
|
-
if (node.consumers.size) throw new Error("Projection node is still used by another node.");
|
|
1356
|
-
node.disposed = true;
|
|
1357
|
-
node.sources.forEach((source) => source.consumers.delete(node));
|
|
1358
|
-
node.release();
|
|
1359
|
-
node.clear();
|
|
1360
|
-
nodes.delete(node);
|
|
1361
|
-
records.delete(handle);
|
|
1362
|
-
pending.delete(node);
|
|
1363
|
-
queued.delete(node);
|
|
1364
|
-
emissions.delete(node);
|
|
1299
|
+
keys: () => {
|
|
1300
|
+
trackStructure();
|
|
1301
|
+
return source.keys();
|
|
1365
1302
|
},
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
if (!source || source.disposed) throw new Error("Unknown, disposed, or foreign projection source.");
|
|
1370
|
-
return source;
|
|
1303
|
+
values: () => {
|
|
1304
|
+
trackAll();
|
|
1305
|
+
return source.values();
|
|
1371
1306
|
},
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1307
|
+
entries: () => {
|
|
1308
|
+
trackAll();
|
|
1309
|
+
return source.entries();
|
|
1375
1310
|
},
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
phase = "compute";
|
|
1380
|
-
lock(true);
|
|
1381
|
-
try {
|
|
1382
|
-
return run();
|
|
1383
|
-
} finally {
|
|
1384
|
-
lock(false);
|
|
1385
|
-
phase = "idle";
|
|
1386
|
-
}
|
|
1311
|
+
forEach: (callback, thisArg) => {
|
|
1312
|
+
trackAll();
|
|
1313
|
+
source.forEach((value, key) => callback.call(thisArg, value, key, view));
|
|
1387
1314
|
},
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
if (disposed) return;
|
|
1392
|
-
assertIdle();
|
|
1393
|
-
disposed = true;
|
|
1394
|
-
const failures = [];
|
|
1395
|
-
cleanups.forEach((cleanup) => {
|
|
1396
|
-
try {
|
|
1397
|
-
cleanup();
|
|
1398
|
-
} catch (error) {
|
|
1399
|
-
failures.push(error);
|
|
1400
|
-
}
|
|
1401
|
-
});
|
|
1402
|
-
cleanups.clear();
|
|
1403
|
-
nodes.forEach((node) => {
|
|
1404
|
-
node.disposed = true;
|
|
1405
|
-
node.release();
|
|
1406
|
-
node.consumers.clear();
|
|
1407
|
-
});
|
|
1408
|
-
records.forEach((source) => {
|
|
1409
|
-
source.disposed = true;
|
|
1410
|
-
source.consumers.clear();
|
|
1411
|
-
source.clear();
|
|
1412
|
-
});
|
|
1413
|
-
nodes.clear();
|
|
1414
|
-
records.clear();
|
|
1415
|
-
pending.clear();
|
|
1416
|
-
heap.length = 0;
|
|
1417
|
-
completed.length = 0;
|
|
1418
|
-
queued.clear();
|
|
1419
|
-
emissions.clear();
|
|
1420
|
-
guards.clear();
|
|
1421
|
-
errors.length = 0;
|
|
1422
|
-
reportingFailures = [];
|
|
1423
|
-
if (failures.length) throw new AggregateError(failures, "Projection cleanup failed.");
|
|
1315
|
+
[Symbol.iterator]: () => {
|
|
1316
|
+
trackAll();
|
|
1317
|
+
return source[Symbol.iterator]();
|
|
1424
1318
|
}
|
|
1425
1319
|
};
|
|
1320
|
+
return Object.freeze(view);
|
|
1426
1321
|
};
|
|
1427
|
-
const
|
|
1428
|
-
if (
|
|
1322
|
+
const selectionAffects = (selection, change) => {
|
|
1323
|
+
if (!change || change.kind === "reset") return true;
|
|
1324
|
+
if (selection.all && (change.added.length || change.removed.length || change.updated.length || change.order)) return true;
|
|
1325
|
+
if (selection.structure && (change.added.length || change.removed.length || change.order)) return true;
|
|
1326
|
+
for (const key of selection.keys) if (change.added.some((entry) => entry.key === key) || change.removed.some((entry) => entry.key === key) || change.updated.some((entry) => entry.key === key)) return true;
|
|
1327
|
+
return false;
|
|
1429
1328
|
};
|
|
1430
|
-
const
|
|
1431
|
-
|
|
1329
|
+
const createDirectReadable = (source) => Object.freeze({
|
|
1330
|
+
current: source.current,
|
|
1331
|
+
revision: source.revision,
|
|
1332
|
+
subscribe: (listener) => source.subscribe(() => listener())
|
|
1333
|
+
});
|
|
1334
|
+
/** Consumer-side selector tracking; it never mutates processor dependencies. */
|
|
1335
|
+
const createSelectorReadable = (source, selector, equality = Object.is) => {
|
|
1336
|
+
let initialized = false;
|
|
1337
|
+
let value;
|
|
1338
|
+
let selectedRevision = 0;
|
|
1339
|
+
let sourceRevision = -1;
|
|
1340
|
+
let selection = Object.freeze({
|
|
1341
|
+
keys: /* @__PURE__ */ new Set(),
|
|
1342
|
+
all: true,
|
|
1343
|
+
structure: false
|
|
1344
|
+
});
|
|
1345
|
+
let previousCollection;
|
|
1346
|
+
let unsubscribeSource;
|
|
1347
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1348
|
+
const updateCollectionSnapshot = (current) => {
|
|
1349
|
+
const candidate = current ?? source.current();
|
|
1350
|
+
previousCollection = isMapLike(candidate) ? candidate : void 0;
|
|
1351
|
+
};
|
|
1352
|
+
const evaluate = () => {
|
|
1353
|
+
const tracker = {
|
|
1354
|
+
keys: /* @__PURE__ */ new Set(),
|
|
1355
|
+
all: false,
|
|
1356
|
+
structure: false
|
|
1357
|
+
};
|
|
1358
|
+
const previousTracker = activeTracker;
|
|
1359
|
+
activeTracker = tracker;
|
|
1360
|
+
let current;
|
|
1361
|
+
let next;
|
|
1362
|
+
try {
|
|
1363
|
+
current = source.current();
|
|
1364
|
+
next = selector(isMapLike(current) ? trackedMap(current) : current);
|
|
1365
|
+
} finally {
|
|
1366
|
+
activeTracker = previousTracker;
|
|
1367
|
+
}
|
|
1368
|
+
const nextSelection = Object.freeze({
|
|
1369
|
+
keys: new Set(tracker.keys),
|
|
1370
|
+
all: tracker.all || isMapLike(current) && tracker.keys.size === 0 && !tracker.structure,
|
|
1371
|
+
structure: tracker.structure
|
|
1372
|
+
});
|
|
1373
|
+
const changed = !initialized || !equality(value, next);
|
|
1374
|
+
if (changed) {
|
|
1375
|
+
if (initialized) selectedRevision++;
|
|
1376
|
+
value = next;
|
|
1377
|
+
}
|
|
1378
|
+
selection = nextSelection;
|
|
1379
|
+
initialized = true;
|
|
1380
|
+
sourceRevision = source.revision();
|
|
1381
|
+
updateCollectionSnapshot(current);
|
|
1382
|
+
return changed;
|
|
1383
|
+
};
|
|
1384
|
+
const ensureCurrent = () => {
|
|
1385
|
+
const revision = source.revision();
|
|
1386
|
+
if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
|
|
1387
|
+
return value;
|
|
1388
|
+
};
|
|
1389
|
+
const onProjectionChange = (incoming) => {
|
|
1390
|
+
let change = incoming;
|
|
1391
|
+
if (!change && previousCollection) {
|
|
1392
|
+
const current = source.current();
|
|
1393
|
+
if (isMapLike(current)) {
|
|
1394
|
+
change = require_advanced.collectionChangeBetween(previousCollection, current);
|
|
1395
|
+
previousCollection = current;
|
|
1396
|
+
if (!change) {
|
|
1397
|
+
sourceRevision = source.revision();
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
} else previousCollection = void 0;
|
|
1401
|
+
}
|
|
1402
|
+
if (!selectionAffects(selection, change)) {
|
|
1403
|
+
sourceRevision = source.revision();
|
|
1404
|
+
updateCollectionSnapshot();
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
if (evaluate()) Array.from(listeners).forEach((listener) => listener());
|
|
1408
|
+
};
|
|
1409
|
+
const installSource = () => {
|
|
1410
|
+
if (!unsubscribeSource) unsubscribeSource = source.subscribe(onProjectionChange);
|
|
1411
|
+
};
|
|
1412
|
+
return Object.freeze({
|
|
1413
|
+
current: ensureCurrent,
|
|
1414
|
+
revision: () => {
|
|
1415
|
+
ensureCurrent();
|
|
1416
|
+
return selectedRevision;
|
|
1417
|
+
},
|
|
1418
|
+
subscribe: (listener) => {
|
|
1419
|
+
ensureCurrent();
|
|
1420
|
+
listeners.add(listener);
|
|
1421
|
+
installSource();
|
|
1422
|
+
return () => {
|
|
1423
|
+
listeners.delete(listener);
|
|
1424
|
+
if (!listeners.size && unsubscribeSource) {
|
|
1425
|
+
unsubscribeSource();
|
|
1426
|
+
unsubscribeSource = void 0;
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1432
1431
|
};
|
|
1433
1432
|
//#endregion
|
|
1434
1433
|
//#region core/src/projection/source.ts
|
|
1435
|
-
const
|
|
1436
|
-
|
|
1437
|
-
const
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
const make = (input) => {
|
|
1449
|
-
const collection = "collection" in input ? input.collection : void 0;
|
|
1450
|
-
const targets = "targets" in input ? input.targets : [input.collection];
|
|
1451
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1452
|
-
targets.forEach((value) => {
|
|
1453
|
-
if (!require_notification.belongs(value, state.schema)) throw new Error("Projection target belongs to another schema.");
|
|
1454
|
-
});
|
|
1455
|
-
const old = bindings.find((entry) => Boolean(collection) === isCollectionSource(entry.handle) && entry.targets.length === targets.length && entry.targets.every((value, i) => require_notification.same(value, targets[i])));
|
|
1456
|
-
if (old) return old.handle;
|
|
1457
|
-
let reset = false;
|
|
1458
|
-
let orderChanged = false;
|
|
1459
|
-
const keys = /* @__PURE__ */ new Set();
|
|
1460
|
-
let baseline;
|
|
1461
|
-
let baselineIds = Object.freeze([]);
|
|
1462
|
-
let nextBaseline;
|
|
1463
|
-
let nextBaselineIds = Object.freeze([]);
|
|
1464
|
-
const collectionNode = collection ? require_issue.nodeAt(state.schema, collection.address, state.document) : void 0;
|
|
1465
|
-
const entryNode = collectionNode && (collectionNode.kind === "map" || collectionNode.kind === "table") ? collectionNode.value : void 0;
|
|
1466
|
-
const sameEntry = (left, right) => entryNode ? require_scope.equalValue(entryNode, left, right) : Object.is(left, right);
|
|
1467
|
-
const record = {
|
|
1468
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1469
|
-
disposed: false,
|
|
1470
|
-
fault: void 0,
|
|
1471
|
-
revision: runtime.revision,
|
|
1472
|
-
reset: () => reset,
|
|
1473
|
-
clear: () => {
|
|
1474
|
-
if (collection && nextBaseline) {
|
|
1475
|
-
baseline = nextBaseline;
|
|
1476
|
-
baselineIds = nextBaselineIds;
|
|
1477
|
-
}
|
|
1478
|
-
reset = false;
|
|
1479
|
-
orderChanged = false;
|
|
1480
|
-
batch = void 0;
|
|
1481
|
-
keys.clear();
|
|
1482
|
-
nextBaseline = void 0;
|
|
1483
|
-
nextBaselineIds = Object.freeze([]);
|
|
1484
|
-
},
|
|
1485
|
-
context: (active) => {
|
|
1486
|
-
assertScope(active);
|
|
1487
|
-
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1488
|
-
const context = {
|
|
1489
|
-
state,
|
|
1490
|
-
active
|
|
1491
|
-
};
|
|
1492
|
-
const rawRead = collection ? require_scope.collectionAccess(context, collection.address) : void 0;
|
|
1493
|
-
const snapshots = collection ? /* @__PURE__ */ new Map() : void 0;
|
|
1494
|
-
const read = collection ? Object.freeze({
|
|
1495
|
-
get: (key) => {
|
|
1496
|
-
assertScope(active);
|
|
1497
|
-
if (!snapshots.has(key)) snapshots.set(key, require_scope.snapshot(rawRead.get(key)));
|
|
1498
|
-
return snapshots.get(key);
|
|
1499
|
-
},
|
|
1500
|
-
has: (key) => {
|
|
1501
|
-
assertScope(active);
|
|
1502
|
-
return rawRead.has(key);
|
|
1503
|
-
},
|
|
1504
|
-
ids: () => {
|
|
1505
|
-
assertScope(active);
|
|
1506
|
-
return rawRead.ids();
|
|
1507
|
-
}
|
|
1508
|
-
}) : require_scope.createAccess(context);
|
|
1509
|
-
if (!collection) return Object.freeze({
|
|
1510
|
-
kind: "document",
|
|
1511
|
-
read,
|
|
1512
|
-
revision: runtime.revision(),
|
|
1513
|
-
reset: record.reset(),
|
|
1514
|
-
cause: batch?.cause
|
|
1515
|
-
});
|
|
1516
|
-
if (!baseline) {
|
|
1517
|
-
const collectionRead = read;
|
|
1518
|
-
baseline = /* @__PURE__ */ new Map();
|
|
1519
|
-
baselineIds = Object.freeze(require_scope.collectionAccess(context, collection.address).ids().slice());
|
|
1520
|
-
for (const key of baselineIds) {
|
|
1521
|
-
const value = collectionRead.get(key);
|
|
1522
|
-
baseline.set(key, value);
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
const changed = [...keys];
|
|
1526
|
-
const added = [];
|
|
1527
|
-
const updated = [];
|
|
1528
|
-
const removed = [];
|
|
1529
|
-
const next = new Map(baseline);
|
|
1530
|
-
const collectionRead = read;
|
|
1531
|
-
for (const key of changed) {
|
|
1532
|
-
const beforePresent = baseline.has(key);
|
|
1533
|
-
const afterPresent = collectionRead.has(key);
|
|
1534
|
-
const afterValue = afterPresent ? collectionRead.get(key) : void 0;
|
|
1535
|
-
if (!beforePresent && afterPresent) {
|
|
1536
|
-
added.push({
|
|
1537
|
-
key,
|
|
1538
|
-
after: afterValue
|
|
1539
|
-
});
|
|
1540
|
-
next.set(key, afterValue);
|
|
1541
|
-
} else if (beforePresent && !afterPresent) {
|
|
1542
|
-
removed.push({
|
|
1543
|
-
key,
|
|
1544
|
-
before: baseline.get(key)
|
|
1545
|
-
});
|
|
1546
|
-
next.delete(key);
|
|
1547
|
-
} else if (beforePresent && afterPresent) {
|
|
1548
|
-
const beforeValue = baseline.get(key);
|
|
1549
|
-
if (!sameEntry(beforeValue, afterValue)) updated.push({
|
|
1550
|
-
key,
|
|
1551
|
-
before: beforeValue,
|
|
1552
|
-
after: afterValue
|
|
1553
|
-
});
|
|
1554
|
-
next.set(key, afterValue);
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
const ids = record.reset() || orderChanged ? Object.freeze([...collectionRead.ids()]) : baselineIds;
|
|
1558
|
-
if (record.reset()) {
|
|
1559
|
-
next.clear();
|
|
1560
|
-
for (const key of ids) {
|
|
1561
|
-
const value = collectionRead.get(key);
|
|
1562
|
-
next.set(key, value);
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
let order;
|
|
1566
|
-
if (orderChanged) {
|
|
1567
|
-
const beforeKeys = new Set(baselineIds);
|
|
1568
|
-
const afterKeys = new Set(ids);
|
|
1569
|
-
const beforeCommon = baselineIds.filter((key) => afterKeys.has(key));
|
|
1570
|
-
const afterCommon = ids.filter((key) => beforeKeys.has(key));
|
|
1571
|
-
if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = {
|
|
1572
|
-
before: Object.freeze([...baselineIds]),
|
|
1573
|
-
after: ids
|
|
1574
|
-
};
|
|
1575
|
-
}
|
|
1576
|
-
const change = record.reset() ? Object.freeze({ kind: "reset" }) : added.length || updated.length || removed.length || order ? Object.freeze({
|
|
1577
|
-
kind: "incremental",
|
|
1578
|
-
added: Object.freeze(added),
|
|
1579
|
-
updated: Object.freeze(updated),
|
|
1580
|
-
removed: Object.freeze(removed),
|
|
1581
|
-
...order ? { order: Object.freeze(order) } : {}
|
|
1582
|
-
}) : void 0;
|
|
1583
|
-
nextBaseline = next;
|
|
1584
|
-
nextBaselineIds = ids;
|
|
1585
|
-
return Object.freeze({
|
|
1586
|
-
kind: "collection",
|
|
1587
|
-
read,
|
|
1588
|
-
change,
|
|
1589
|
-
revision: runtime.revision(),
|
|
1590
|
-
cause: batch?.cause
|
|
1591
|
-
});
|
|
1592
|
-
}
|
|
1593
|
-
};
|
|
1594
|
-
const handle = collection ? { kind: "collection" } : {
|
|
1595
|
-
collection: (pick) => {
|
|
1596
|
-
scheduler.assertIdle();
|
|
1597
|
-
return make({ collection: compilePath(state.schema, "collection", pick) });
|
|
1598
|
-
},
|
|
1599
|
-
targets: (...selected) => {
|
|
1600
|
-
scheduler.assertIdle();
|
|
1601
|
-
if (!selected.length) throw new TypeError("Expected at least one target.");
|
|
1602
|
-
return make({ targets: selected.map((pick) => compilePath(state.schema, "value", pick)) });
|
|
1603
|
-
}
|
|
1604
|
-
};
|
|
1605
|
-
Object.freeze(handle);
|
|
1606
|
-
scheduler.register(handle, record);
|
|
1607
|
-
const receive = (commit) => {
|
|
1608
|
-
reset ||= commit.impact.kind === "reset";
|
|
1609
|
-
if (collection) {
|
|
1610
|
-
const change = collectionImpact(commit.impact, collection);
|
|
1611
|
-
if (change.kind === "reset") reset = true;
|
|
1612
|
-
else {
|
|
1613
|
-
change.added.forEach((key) => keys.add(key));
|
|
1614
|
-
change.removed.forEach((key) => keys.add(key));
|
|
1615
|
-
change.updated.forEach((key) => keys.add(key));
|
|
1616
|
-
orderChanged ||= Boolean(change.added.size || change.removed.size || change.orderChanged);
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
};
|
|
1620
|
-
bindings.push({
|
|
1621
|
-
handle,
|
|
1622
|
-
targets,
|
|
1623
|
-
record,
|
|
1624
|
-
receive
|
|
1625
|
-
});
|
|
1626
|
-
return handle;
|
|
1627
|
-
};
|
|
1628
|
-
const handle = make({ targets: [] });
|
|
1629
|
-
const guard = (locked) => {
|
|
1630
|
-
state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
|
|
1631
|
-
};
|
|
1632
|
-
scheduler.guards.add(guard);
|
|
1633
|
-
const unsubscribe = require_notification.attachProjection(runtime, {
|
|
1634
|
-
capture: (commit) => {
|
|
1635
|
-
for (const binding of bindings) if (!binding.targets.length || binding.targets.some((value) => affectsTarget(commit.impact, value))) {
|
|
1636
|
-
binding.receive(commit);
|
|
1637
|
-
batch = scheduler.batchContext();
|
|
1638
|
-
scheduler.capture(binding.record);
|
|
1639
|
-
}
|
|
1640
|
-
},
|
|
1641
|
-
settle: scheduler.settle,
|
|
1642
|
-
flush: scheduler.flush,
|
|
1643
|
-
dispose: () => {
|
|
1644
|
-
bindings.forEach(({ record }) => {
|
|
1645
|
-
record.fault = new ProjectionError("source", "document disposed", [runtime.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
1646
|
-
scheduler.capture(record);
|
|
1647
|
-
});
|
|
1648
|
-
scheduler.run();
|
|
1649
|
-
}
|
|
1650
|
-
});
|
|
1651
|
-
scheduler.cleanups.add(() => {
|
|
1652
|
-
unsubscribe();
|
|
1653
|
-
scheduler.guards.delete(guard);
|
|
1654
|
-
});
|
|
1655
|
-
documents.set(state, handle);
|
|
1656
|
-
return handle;
|
|
1657
|
-
};
|
|
1658
|
-
const valueSource = (initial, equal) => {
|
|
1659
|
-
let value = initial;
|
|
1660
|
-
let revision = 0;
|
|
1661
|
-
let cause;
|
|
1662
|
-
let reset = false;
|
|
1663
|
-
const handle = Object.freeze({});
|
|
1664
|
-
const record = {
|
|
1665
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1666
|
-
disposed: false,
|
|
1667
|
-
fault: void 0,
|
|
1668
|
-
revision: () => revision,
|
|
1669
|
-
reset: () => reset,
|
|
1670
|
-
clear: () => {
|
|
1671
|
-
cause = void 0;
|
|
1672
|
-
reset = false;
|
|
1673
|
-
},
|
|
1674
|
-
context: (active) => {
|
|
1675
|
-
assertScope(active);
|
|
1676
|
-
return Object.freeze({
|
|
1677
|
-
kind: "value",
|
|
1678
|
-
value,
|
|
1679
|
-
revision,
|
|
1680
|
-
reset,
|
|
1681
|
-
cause
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
|
-
};
|
|
1685
|
-
scheduler.register(handle, record);
|
|
1686
|
-
const accept = (next, metadata) => {
|
|
1687
|
-
scheduler.assertIdle();
|
|
1688
|
-
const recovering = record.fault !== void 0;
|
|
1689
|
-
const eventful = metadata !== void 0 && (metadata.reset === true || metadata.revision !== void 0 && metadata.revision !== revision || metadata.cause !== void 0);
|
|
1690
|
-
if (equal(value, next) && !recovering && !eventful) return;
|
|
1691
|
-
record.fault = void 0;
|
|
1692
|
-
value = next;
|
|
1693
|
-
revision = metadata?.revision ?? revision + 1;
|
|
1694
|
-
cause = metadata?.cause ?? scheduler.batchContext()?.cause ?? cause;
|
|
1695
|
-
reset ||= metadata?.reset ?? false;
|
|
1696
|
-
scheduler.capture(record);
|
|
1697
|
-
};
|
|
1698
|
-
return {
|
|
1699
|
-
source: handle,
|
|
1700
|
-
accept,
|
|
1701
|
-
set(next) {
|
|
1702
|
-
accept(next);
|
|
1703
|
-
scheduler.run();
|
|
1704
|
-
}
|
|
1705
|
-
};
|
|
1706
|
-
};
|
|
1707
|
-
const collectionInput = (initial) => {
|
|
1708
|
-
const values = new Map(initial);
|
|
1709
|
-
const touched = /* @__PURE__ */ new Map();
|
|
1710
|
-
let beforeIds;
|
|
1711
|
-
let revision = 0;
|
|
1712
|
-
let cause;
|
|
1713
|
-
const handle = Object.freeze({ kind: "collection" });
|
|
1714
|
-
const change = () => {
|
|
1715
|
-
const added = [];
|
|
1716
|
-
const updated = [];
|
|
1717
|
-
const removed = [];
|
|
1718
|
-
for (const [key, before] of touched) {
|
|
1719
|
-
const afterPresent = values.has(key);
|
|
1720
|
-
if (!before.present && afterPresent) added.push({
|
|
1721
|
-
key,
|
|
1722
|
-
after: values.get(key)
|
|
1723
|
-
});
|
|
1724
|
-
else if (before.present && !afterPresent) removed.push({
|
|
1725
|
-
key,
|
|
1726
|
-
before: before.value
|
|
1727
|
-
});
|
|
1728
|
-
else if (before.present && afterPresent && !Object.is(before.value, values.get(key))) updated.push({
|
|
1729
|
-
key,
|
|
1730
|
-
before: before.value,
|
|
1731
|
-
after: values.get(key)
|
|
1732
|
-
});
|
|
1733
|
-
}
|
|
1734
|
-
let order;
|
|
1735
|
-
if (beforeIds) {
|
|
1736
|
-
const afterIds = [...values.keys()];
|
|
1737
|
-
const beforeKeys = new Set(beforeIds);
|
|
1738
|
-
const afterKeys = new Set(afterIds);
|
|
1739
|
-
const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
|
|
1740
|
-
const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
|
|
1741
|
-
if (beforeCommon.some((key, index) => key !== afterCommon[index])) order = Object.freeze({
|
|
1742
|
-
before: beforeIds,
|
|
1743
|
-
after: Object.freeze(afterIds)
|
|
1744
|
-
});
|
|
1745
|
-
}
|
|
1746
|
-
if (!added.length && !updated.length && !removed.length && !order) return void 0;
|
|
1747
|
-
return Object.freeze({
|
|
1748
|
-
kind: "incremental",
|
|
1749
|
-
added: Object.freeze(added),
|
|
1750
|
-
updated: Object.freeze(updated),
|
|
1751
|
-
removed: Object.freeze(removed),
|
|
1752
|
-
...order ? { order } : {}
|
|
1753
|
-
});
|
|
1754
|
-
};
|
|
1755
|
-
const record = {
|
|
1756
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1757
|
-
disposed: false,
|
|
1758
|
-
fault: void 0,
|
|
1759
|
-
revision: () => revision,
|
|
1760
|
-
reset: () => false,
|
|
1761
|
-
shouldSettle: () => change() !== void 0,
|
|
1762
|
-
clear: () => {
|
|
1763
|
-
touched.clear();
|
|
1764
|
-
beforeIds = void 0;
|
|
1765
|
-
cause = void 0;
|
|
1766
|
-
},
|
|
1767
|
-
context: (active) => {
|
|
1768
|
-
assertScope(active);
|
|
1769
|
-
const read = Object.freeze({
|
|
1770
|
-
get: (key) => {
|
|
1771
|
-
assertScope(active);
|
|
1772
|
-
return values.get(key);
|
|
1773
|
-
},
|
|
1774
|
-
has: (key) => {
|
|
1775
|
-
assertScope(active);
|
|
1776
|
-
return values.has(key);
|
|
1777
|
-
},
|
|
1778
|
-
ids: () => {
|
|
1779
|
-
assertScope(active);
|
|
1780
|
-
return Object.freeze([...values.keys()]);
|
|
1781
|
-
}
|
|
1782
|
-
});
|
|
1783
|
-
return Object.freeze({
|
|
1784
|
-
kind: "collection",
|
|
1785
|
-
read,
|
|
1786
|
-
change: change(),
|
|
1787
|
-
revision,
|
|
1788
|
-
cause
|
|
1789
|
-
});
|
|
1790
|
-
}
|
|
1791
|
-
};
|
|
1792
|
-
scheduler.register(handle, record);
|
|
1793
|
-
return {
|
|
1794
|
-
source: handle,
|
|
1795
|
-
update(run) {
|
|
1796
|
-
scheduler.assertIdle();
|
|
1797
|
-
const staged = /* @__PURE__ */ new Map();
|
|
1798
|
-
let active = true;
|
|
1799
|
-
const draft = Object.freeze({
|
|
1800
|
-
get: (key) => {
|
|
1801
|
-
assertScope(() => active);
|
|
1802
|
-
const entry = staged.get(key);
|
|
1803
|
-
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
1804
|
-
},
|
|
1805
|
-
has: (key) => {
|
|
1806
|
-
assertScope(() => active);
|
|
1807
|
-
const entry = staged.get(key);
|
|
1808
|
-
return entry ? entry.present : values.has(key);
|
|
1809
|
-
},
|
|
1810
|
-
set: (key, value) => {
|
|
1811
|
-
assertScope(() => active);
|
|
1812
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
1813
|
-
staged.set(key, {
|
|
1814
|
-
present: true,
|
|
1815
|
-
value
|
|
1816
|
-
});
|
|
1817
|
-
},
|
|
1818
|
-
remove: (key) => {
|
|
1819
|
-
assertScope(() => active);
|
|
1820
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
1821
|
-
staged.set(key, { present: false });
|
|
1822
|
-
}
|
|
1823
|
-
});
|
|
1824
|
-
try {
|
|
1825
|
-
assertSynchronous(run(draft));
|
|
1826
|
-
} finally {
|
|
1827
|
-
active = false;
|
|
1828
|
-
}
|
|
1829
|
-
let changed = false;
|
|
1830
|
-
for (const [key, entry] of staged) {
|
|
1831
|
-
const present = values.has(key);
|
|
1832
|
-
if (entry.present ? present && Object.is(values.get(key), entry.value) : !present) continue;
|
|
1833
|
-
if (!touched.has(key)) touched.set(key, {
|
|
1834
|
-
present,
|
|
1835
|
-
value: values.get(key)
|
|
1836
|
-
});
|
|
1837
|
-
if (!beforeIds && present !== entry.present) beforeIds = Object.freeze([...values.keys()]);
|
|
1838
|
-
if (entry.present) values.set(key, entry.value);
|
|
1839
|
-
else values.delete(key);
|
|
1840
|
-
changed = true;
|
|
1841
|
-
}
|
|
1842
|
-
if (!changed) return;
|
|
1843
|
-
revision++;
|
|
1844
|
-
cause = scheduler.batchContext()?.cause ?? cause;
|
|
1845
|
-
scheduler.capture(record);
|
|
1846
|
-
scheduler.run();
|
|
1847
|
-
}
|
|
1848
|
-
};
|
|
1849
|
-
};
|
|
1850
|
-
const externalValueSource = (port, equal) => {
|
|
1851
|
-
scheduler.assertIdle();
|
|
1852
|
-
const old = externalSources.get(port);
|
|
1853
|
-
if (old) return old;
|
|
1854
|
-
const input = valueSource(port.current(), equal);
|
|
1855
|
-
const fail = (cause) => {
|
|
1856
|
-
const record = scheduler.source(input.source);
|
|
1857
|
-
record.fault = new ProjectionError("source", "external source", [record.revision()], cause);
|
|
1858
|
-
scheduler.capture(record);
|
|
1859
|
-
};
|
|
1860
|
-
let unsubscribe;
|
|
1861
|
-
try {
|
|
1862
|
-
unsubscribe = port.subscribe((event) => {
|
|
1863
|
-
try {
|
|
1864
|
-
input.accept(event.value, {
|
|
1865
|
-
revision: event.revision,
|
|
1866
|
-
reset: event.reset ?? false,
|
|
1867
|
-
cause: event.cause
|
|
1868
|
-
});
|
|
1869
|
-
} catch (cause) {
|
|
1870
|
-
fail(cause);
|
|
1871
|
-
}
|
|
1872
|
-
scheduler.run();
|
|
1873
|
-
});
|
|
1874
|
-
} catch (cause) {
|
|
1875
|
-
unsubscribe?.();
|
|
1876
|
-
scheduler.unregisterSource(input.source);
|
|
1877
|
-
throw cause;
|
|
1878
|
-
}
|
|
1879
|
-
scheduler.cleanups.add(() => unsubscribe?.());
|
|
1880
|
-
externalSources.set(port, input.source);
|
|
1881
|
-
return input.source;
|
|
1882
|
-
};
|
|
1883
|
-
const externalCollectionSource = (port) => {
|
|
1884
|
-
scheduler.assertIdle();
|
|
1885
|
-
const old = externalSources.get(port);
|
|
1886
|
-
if (old) return old;
|
|
1887
|
-
let read = port.current();
|
|
1888
|
-
let previous = read;
|
|
1889
|
-
let revision = port.revision();
|
|
1890
|
-
let change;
|
|
1891
|
-
let reset = false;
|
|
1892
|
-
let orderChanged = false;
|
|
1893
|
-
let cause;
|
|
1894
|
-
let transitionKeys = /* @__PURE__ */ new Set();
|
|
1895
|
-
let pending = false;
|
|
1896
|
-
const handle = Object.freeze({ kind: "collection" });
|
|
1897
|
-
const record = {
|
|
1898
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
1899
|
-
disposed: false,
|
|
1900
|
-
fault: void 0,
|
|
1901
|
-
revision: () => revision,
|
|
1902
|
-
reset: () => reset,
|
|
1903
|
-
clear: () => {
|
|
1904
|
-
previous = read;
|
|
1905
|
-
pending = false;
|
|
1906
|
-
change = void 0;
|
|
1907
|
-
reset = false;
|
|
1908
|
-
orderChanged = false;
|
|
1909
|
-
cause = void 0;
|
|
1910
|
-
transitionKeys.clear();
|
|
1911
|
-
},
|
|
1912
|
-
context: (active) => {
|
|
1913
|
-
assertScope(active);
|
|
1914
|
-
const currentRead = Object.freeze({
|
|
1915
|
-
get: (key) => {
|
|
1916
|
-
assertScope(active);
|
|
1917
|
-
return read.get(key);
|
|
1918
|
-
},
|
|
1919
|
-
has: (key) => {
|
|
1920
|
-
assertScope(active);
|
|
1921
|
-
return read.has(key);
|
|
1922
|
-
},
|
|
1923
|
-
ids: () => {
|
|
1924
|
-
assertScope(active);
|
|
1925
|
-
return read.ids();
|
|
1926
|
-
}
|
|
1927
|
-
});
|
|
1928
|
-
return Object.freeze({
|
|
1929
|
-
kind: "collection",
|
|
1930
|
-
read: currentRead,
|
|
1931
|
-
change,
|
|
1932
|
-
revision,
|
|
1933
|
-
cause
|
|
1934
|
-
});
|
|
1935
|
-
}
|
|
1936
|
-
};
|
|
1937
|
-
scheduler.register(handle, record);
|
|
1938
|
-
const fail = (error) => {
|
|
1939
|
-
record.fault = new ProjectionError("source", "external collection source", [revision], error);
|
|
1940
|
-
scheduler.capture(record);
|
|
1941
|
-
};
|
|
1942
|
-
const deriveChange = () => {
|
|
1943
|
-
if (reset) return Object.freeze({ kind: "reset" });
|
|
1944
|
-
const added = [];
|
|
1945
|
-
const updated = [];
|
|
1946
|
-
const removed = [];
|
|
1947
|
-
for (const key of transitionKeys) {
|
|
1948
|
-
const beforePresent = previous.has(key);
|
|
1949
|
-
const afterPresent = read.has(key);
|
|
1950
|
-
const before = beforePresent ? previous.get(key) : void 0;
|
|
1951
|
-
const after = afterPresent ? read.get(key) : void 0;
|
|
1952
|
-
if (beforePresent === afterPresent && Object.is(before, after)) continue;
|
|
1953
|
-
if (!beforePresent && afterPresent) added.push({
|
|
1954
|
-
key,
|
|
1955
|
-
after
|
|
1956
|
-
});
|
|
1957
|
-
else if (beforePresent && !afterPresent) removed.push({
|
|
1958
|
-
key,
|
|
1959
|
-
before
|
|
1960
|
-
});
|
|
1961
|
-
else if (beforePresent && afterPresent) updated.push({
|
|
1962
|
-
key,
|
|
1963
|
-
before,
|
|
1964
|
-
after
|
|
1965
|
-
});
|
|
1966
|
-
}
|
|
1967
|
-
let order;
|
|
1968
|
-
if (orderChanged) {
|
|
1969
|
-
const beforeIds = previous.ids();
|
|
1970
|
-
const afterIds = read.ids();
|
|
1971
|
-
const beforeKeys = new Set(beforeIds);
|
|
1972
|
-
const afterKeys = new Set(afterIds);
|
|
1973
|
-
const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
|
|
1974
|
-
const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
|
|
1975
|
-
if (beforeCommon.some((key, index) => afterCommon[index] !== key)) order = Object.freeze({
|
|
1976
|
-
before: Object.freeze([...beforeIds]),
|
|
1977
|
-
after: Object.freeze([...afterIds])
|
|
1978
|
-
});
|
|
1979
|
-
}
|
|
1980
|
-
if (!added.length && !removed.length && !updated.length && !order) return void 0;
|
|
1981
|
-
return Object.freeze({
|
|
1982
|
-
kind: "incremental",
|
|
1983
|
-
added: Object.freeze(added),
|
|
1984
|
-
updated: Object.freeze(updated),
|
|
1985
|
-
removed: Object.freeze(removed),
|
|
1986
|
-
...order ? { order } : {}
|
|
1987
|
-
});
|
|
1988
|
-
};
|
|
1989
|
-
let unsubscribe;
|
|
1990
|
-
try {
|
|
1991
|
-
unsubscribe = port.subscribe((event) => {
|
|
1992
|
-
try {
|
|
1993
|
-
scheduler.assertIdle();
|
|
1994
|
-
if (!pending) previous = event.previous;
|
|
1995
|
-
read = port.current();
|
|
1996
|
-
revision = event.revision;
|
|
1997
|
-
if (event.impact?.kind === "incremental") {
|
|
1998
|
-
event.impact.added.forEach((key) => transitionKeys.add(key));
|
|
1999
|
-
event.impact.removed.forEach((key) => transitionKeys.add(key));
|
|
2000
|
-
event.impact.updated.forEach((key) => transitionKeys.add(key));
|
|
2001
|
-
orderChanged ||= Boolean(event.impact.added.size || event.impact.removed.size || event.impact.orderChanged);
|
|
2002
|
-
} else if (!event.impact) {
|
|
2003
|
-
previous.ids().forEach((key) => transitionKeys.add(key));
|
|
2004
|
-
read.ids().forEach((key) => transitionKeys.add(key));
|
|
2005
|
-
orderChanged = true;
|
|
2006
|
-
}
|
|
2007
|
-
reset ||= event.impact?.kind === "reset";
|
|
2008
|
-
cause = event.cause ?? scheduler.batchContext()?.cause ?? cause;
|
|
2009
|
-
change = deriveChange();
|
|
2010
|
-
record.fault = void 0;
|
|
2011
|
-
scheduler.capture(record);
|
|
2012
|
-
pending = true;
|
|
2013
|
-
} catch (error) {
|
|
2014
|
-
fail(error);
|
|
2015
|
-
}
|
|
2016
|
-
scheduler.run();
|
|
2017
|
-
});
|
|
2018
|
-
} catch (error) {
|
|
2019
|
-
unsubscribe?.();
|
|
2020
|
-
scheduler.unregisterSource(handle);
|
|
2021
|
-
throw error;
|
|
2022
|
-
}
|
|
2023
|
-
scheduler.cleanups.add(() => unsubscribe?.());
|
|
2024
|
-
externalSources.set(port, handle);
|
|
2025
|
-
return handle;
|
|
1434
|
+
const sourceError = (source, cause) => new require_advanced.ProjectionError("source", source.name, [source.outputs[0].revision()], cause);
|
|
1435
|
+
const createValueBoundary = (scheduler, name, initial, equality) => {
|
|
1436
|
+
const state = createValueOutput();
|
|
1437
|
+
let boundary;
|
|
1438
|
+
let latest = initial;
|
|
1439
|
+
let pendingReset = false;
|
|
1440
|
+
let pendingCause;
|
|
1441
|
+
let contextCause;
|
|
1442
|
+
let recovering = false;
|
|
1443
|
+
let cleanup;
|
|
1444
|
+
const check = () => {
|
|
1445
|
+
if (!scheduler.active || boundary.disposed) throw new require_advanced.ProjectionDisposedError();
|
|
1446
|
+
if (boundary.fault) throw boundary.fault;
|
|
2026
1447
|
};
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
},
|
|
2032
|
-
document,
|
|
2033
|
-
fromSource: (source, options) => externalValueSource(source, options?.isEqual ?? Object.is),
|
|
2034
|
-
fromCollectionSource: (source) => externalCollectionSource(source),
|
|
2035
|
-
input: (initial, options) => {
|
|
2036
|
-
const { source, set } = valueSource(initial, options?.isEqual ?? Object.is);
|
|
2037
|
-
return Object.freeze({
|
|
2038
|
-
source,
|
|
2039
|
-
set
|
|
2040
|
-
});
|
|
1448
|
+
const output = {
|
|
1449
|
+
kind: "value",
|
|
1450
|
+
get owner() {
|
|
1451
|
+
return boundary;
|
|
2041
1452
|
},
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
bindings.set(equal, {
|
|
2053
|
-
source: input.source,
|
|
2054
|
-
accept: (value) => input.accept(value)
|
|
2055
|
-
});
|
|
2056
|
-
return input.source;
|
|
2057
|
-
}
|
|
2058
|
-
bindings = new Map([[equal, {
|
|
2059
|
-
source: input.source,
|
|
2060
|
-
accept: (value) => input.accept(value)
|
|
2061
|
-
}]]);
|
|
2062
|
-
const members = bindings;
|
|
2063
|
-
const receive = (settle = true) => {
|
|
2064
|
-
if (!scheduler.active) return;
|
|
2065
|
-
let value;
|
|
2066
|
-
try {
|
|
2067
|
-
value = readable.current();
|
|
2068
|
-
} catch (cause) {
|
|
2069
|
-
for (const member of members.values()) {
|
|
2070
|
-
const record = scheduler.source(member.source);
|
|
2071
|
-
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
2072
|
-
scheduler.capture(record);
|
|
2073
|
-
}
|
|
2074
|
-
if (settle) scheduler.run();
|
|
2075
|
-
return;
|
|
2076
|
-
}
|
|
2077
|
-
for (const member of members.values()) try {
|
|
2078
|
-
member.accept(value);
|
|
2079
|
-
} catch (cause) {
|
|
2080
|
-
const record = scheduler.source(member.source);
|
|
2081
|
-
record.fault = new ProjectionError("source", "external readable", [record.revision()], cause);
|
|
2082
|
-
scheduler.capture(record);
|
|
2083
|
-
}
|
|
2084
|
-
if (settle) scheduler.run();
|
|
2085
|
-
};
|
|
2086
|
-
let unsubscribe;
|
|
2087
|
-
let detach;
|
|
2088
|
-
try {
|
|
2089
|
-
const owner = require_notification.documentReadableOwner(readable);
|
|
2090
|
-
if (owner) {
|
|
2091
|
-
document(owner);
|
|
2092
|
-
detach = require_notification.attachProjection(owner, {
|
|
2093
|
-
capture: () => receive(false),
|
|
2094
|
-
settle: scheduler.settle,
|
|
2095
|
-
flush: scheduler.flush,
|
|
2096
|
-
dispose: () => {
|
|
2097
|
-
for (const member of members.values()) {
|
|
2098
|
-
const record = scheduler.source(member.source);
|
|
2099
|
-
record.fault = new ProjectionError("source", "document readable disposed", [record.revision()], /* @__PURE__ */ new Error("Document has been disposed."));
|
|
2100
|
-
scheduler.capture(record);
|
|
2101
|
-
}
|
|
2102
|
-
scheduler.run();
|
|
2103
|
-
}
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
unsubscribe = readable.subscribe(() => receive());
|
|
2107
|
-
input.accept(readable.current());
|
|
2108
|
-
} catch (error) {
|
|
2109
|
-
try {
|
|
2110
|
-
unsubscribe?.();
|
|
2111
|
-
detach?.();
|
|
2112
|
-
} finally {
|
|
2113
|
-
scheduler.unregisterSource(input.source);
|
|
2114
|
-
}
|
|
2115
|
-
throw error;
|
|
2116
|
-
}
|
|
2117
|
-
scheduler.cleanups.add(unsubscribe);
|
|
2118
|
-
if (detach) scheduler.cleanups.add(detach);
|
|
2119
|
-
readables.set(readable, bindings);
|
|
2120
|
-
scheduler.run();
|
|
2121
|
-
return input.source;
|
|
1453
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1454
|
+
context: (active) => state.context(active, contextCause),
|
|
1455
|
+
current: () => state.current(check),
|
|
1456
|
+
revision: state.revision,
|
|
1457
|
+
reset: state.reset,
|
|
1458
|
+
subscribe: (listener) => {
|
|
1459
|
+
check();
|
|
1460
|
+
const wrapped = () => listener();
|
|
1461
|
+
state.subscribe(wrapped);
|
|
1462
|
+
return () => state.unsubscribe(wrapped);
|
|
2122
1463
|
},
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
}
|
|
1464
|
+
emit: state.emit,
|
|
1465
|
+
clear: state.clear,
|
|
1466
|
+
release: state.release
|
|
2127
1467
|
};
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
scheduler.assertIdle();
|
|
2133
|
-
const entries = Object.entries(spec.sources).map(([key, handle]) => [key, scheduler.source(handle)]);
|
|
2134
|
-
const order = scheduler.order();
|
|
2135
|
-
const node = {
|
|
2136
|
-
...behavior,
|
|
2137
|
-
publish: () => {
|
|
2138
|
-
if (node.fault) behavior.clear();
|
|
2139
|
-
else behavior.publish();
|
|
2140
|
-
},
|
|
2141
|
-
order,
|
|
2142
|
-
name: spec.name ? `${spec.name} (${order})` : `projection-${order}`,
|
|
2143
|
-
sources: entries.map(([, source]) => source),
|
|
2144
|
-
consumers: /* @__PURE__ */ new Set(),
|
|
2145
|
-
disposed: false,
|
|
1468
|
+
boundary = {
|
|
1469
|
+
kind: "source",
|
|
1470
|
+
name,
|
|
1471
|
+
outputs: Object.freeze([output]),
|
|
2146
1472
|
fault: void 0,
|
|
2147
|
-
|
|
1473
|
+
disposed: false,
|
|
1474
|
+
pendingCause: () => pendingCause,
|
|
1475
|
+
recovering: () => recovering,
|
|
1476
|
+
prepare: (cause) => {
|
|
1477
|
+
contextCause = cause;
|
|
2148
1478
|
let active = true;
|
|
2149
1479
|
try {
|
|
2150
|
-
|
|
2151
|
-
const
|
|
2152
|
-
|
|
1480
|
+
state.begin(() => active, pendingReset).output.set(latest);
|
|
1481
|
+
const changed = state.seal(pendingReset, equality);
|
|
1482
|
+
recovering = false;
|
|
1483
|
+
return {
|
|
1484
|
+
changed,
|
|
1485
|
+
force: pendingReset
|
|
1486
|
+
};
|
|
2153
1487
|
} finally {
|
|
2154
1488
|
active = false;
|
|
2155
1489
|
}
|
|
2156
|
-
}
|
|
2157
|
-
};
|
|
2158
|
-
scheduler.initialize(() => {
|
|
2159
|
-
const invalid = node.sources.find((source) => source.fault || source.disposed);
|
|
2160
|
-
if (invalid) throw invalid.fault ?? new ProjectionDisposedError();
|
|
2161
|
-
node.evaluate(true);
|
|
2162
|
-
node.publish();
|
|
2163
|
-
node.clear();
|
|
2164
|
-
});
|
|
2165
|
-
const check = () => {
|
|
2166
|
-
if (node.disposed || !scheduler.active) throw new ProjectionDisposedError();
|
|
2167
|
-
if (node.fault) throw node.fault;
|
|
2168
|
-
};
|
|
2169
|
-
return {
|
|
2170
|
-
node,
|
|
2171
|
-
check,
|
|
2172
|
-
install(handle) {
|
|
2173
|
-
scheduler.register(handle, node);
|
|
2174
|
-
scheduler.addNode(node);
|
|
2175
|
-
}
|
|
2176
|
-
};
|
|
2177
|
-
};
|
|
2178
|
-
//#endregion
|
|
2179
|
-
//#region core/src/projection/value.ts
|
|
2180
|
-
const createValue = (scheduler, spec, options) => {
|
|
2181
|
-
let instance;
|
|
2182
|
-
let value;
|
|
2183
|
-
let next;
|
|
2184
|
-
let initialized = false;
|
|
2185
|
-
let revision = 0;
|
|
2186
|
-
let changed = false;
|
|
2187
|
-
let reset = false;
|
|
2188
|
-
let cause;
|
|
2189
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
2190
|
-
const owner = createNode(scheduler, spec, {
|
|
2191
|
-
evaluate: (sources, build) => {
|
|
2192
|
-
cause = Object.values(sources).find((value) => value && typeof value === "object" && "cause" in value && value.cause !== void 0)?.cause ?? scheduler.batchContext()?.cause;
|
|
2193
|
-
let candidate;
|
|
2194
|
-
if (build || !instance) {
|
|
2195
|
-
require_issue.profile.materialized.rebuilt();
|
|
2196
|
-
instance = void 0;
|
|
2197
|
-
const built = spec.build(sources);
|
|
2198
|
-
assertSynchronous(built);
|
|
2199
|
-
instance = built;
|
|
2200
|
-
candidate = built.value;
|
|
2201
|
-
} else {
|
|
2202
|
-
require_issue.profile.materialized.updated();
|
|
2203
|
-
const result = instance.update(sources);
|
|
2204
|
-
assertSynchronous(result);
|
|
2205
|
-
if (result.kind === "rebuild") {
|
|
2206
|
-
instance = void 0;
|
|
2207
|
-
const built = spec.build(sources);
|
|
2208
|
-
assertSynchronous(built);
|
|
2209
|
-
instance = built;
|
|
2210
|
-
candidate = built.value;
|
|
2211
|
-
build = true;
|
|
2212
|
-
} else candidate = result.kind === "changed" ? result.value : value;
|
|
2213
|
-
}
|
|
2214
|
-
changed = !initialized || !(options?.isEqual ?? Object.is)(value, candidate);
|
|
2215
|
-
next = changed ? candidate : value;
|
|
2216
|
-
reset = build;
|
|
2217
|
-
return changed;
|
|
2218
|
-
},
|
|
2219
|
-
context: (active) => {
|
|
2220
|
-
assertScope(active);
|
|
2221
|
-
return Object.freeze({
|
|
2222
|
-
kind: "value",
|
|
2223
|
-
value: next,
|
|
2224
|
-
revision: revision + (changed ? 1 : 0),
|
|
2225
|
-
reset,
|
|
2226
|
-
cause
|
|
2227
|
-
});
|
|
2228
|
-
},
|
|
2229
|
-
revision: () => revision,
|
|
2230
|
-
reset: () => reset,
|
|
2231
|
-
publish: () => {
|
|
2232
|
-
if (changed && initialized) revision++;
|
|
2233
|
-
value = next;
|
|
2234
|
-
initialized = true;
|
|
2235
|
-
},
|
|
2236
|
-
emit: (call) => {
|
|
2237
|
-
require_issue.profile.materialized.notification();
|
|
2238
|
-
Array.from(listeners).forEach((listener) => call(listener));
|
|
2239
1490
|
},
|
|
1491
|
+
publish: state.publish,
|
|
2240
1492
|
clear: () => {
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
1493
|
+
state.clear();
|
|
1494
|
+
pendingReset = false;
|
|
1495
|
+
pendingCause = void 0;
|
|
1496
|
+
contextCause = void 0;
|
|
1497
|
+
recovering = false;
|
|
2245
1498
|
},
|
|
2246
1499
|
release: () => {
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
1500
|
+
cleanup?.();
|
|
1501
|
+
cleanup = void 0;
|
|
1502
|
+
state.release();
|
|
1503
|
+
output.consumers.clear();
|
|
2250
1504
|
}
|
|
2251
|
-
});
|
|
2252
|
-
const handle = Object.freeze({
|
|
2253
|
-
kind: "value",
|
|
2254
|
-
current: () => {
|
|
2255
|
-
owner.check();
|
|
2256
|
-
return value;
|
|
2257
|
-
},
|
|
2258
|
-
revision: () => {
|
|
2259
|
-
owner.check();
|
|
2260
|
-
return revision;
|
|
2261
|
-
},
|
|
2262
|
-
subscribe: (listener) => {
|
|
2263
|
-
owner.check();
|
|
2264
|
-
listeners.add(listener);
|
|
2265
|
-
return () => {
|
|
2266
|
-
listeners.delete(listener);
|
|
2267
|
-
};
|
|
2268
|
-
}
|
|
2269
|
-
});
|
|
2270
|
-
owner.install(handle);
|
|
2271
|
-
return handle;
|
|
2272
|
-
};
|
|
2273
|
-
//#endregion
|
|
2274
|
-
//#region core/src/projection/collection.ts
|
|
2275
|
-
const height = (node) => node?.height ?? 0;
|
|
2276
|
-
const makeTreeNode = (key, value, left, right) => Object.freeze({
|
|
2277
|
-
key,
|
|
2278
|
-
value,
|
|
2279
|
-
left,
|
|
2280
|
-
right,
|
|
2281
|
-
height: Math.max(height(left), height(right)) + 1
|
|
2282
|
-
});
|
|
2283
|
-
const rotateLeft = (node) => {
|
|
2284
|
-
const right = node.right;
|
|
2285
|
-
return makeTreeNode(right.key, right.value, makeTreeNode(node.key, node.value, node.left, right.left), right.right);
|
|
2286
|
-
};
|
|
2287
|
-
const rotateRight = (node) => {
|
|
2288
|
-
const left = node.left;
|
|
2289
|
-
return makeTreeNode(left.key, left.value, left.left, makeTreeNode(node.key, node.value, left.right, node.right));
|
|
2290
|
-
};
|
|
2291
|
-
const rebalance = (node) => {
|
|
2292
|
-
const balance = height(node.left) - height(node.right);
|
|
2293
|
-
if (balance > 1) {
|
|
2294
|
-
if (height(node.left.left) < height(node.left.right)) return rotateRight(makeTreeNode(node.key, node.value, rotateLeft(node.left), node.right));
|
|
2295
|
-
return rotateRight(node);
|
|
2296
|
-
}
|
|
2297
|
-
if (balance < -1) {
|
|
2298
|
-
if (height(node.right.right) < height(node.right.left)) return rotateLeft(makeTreeNode(node.key, node.value, node.left, rotateRight(node.right)));
|
|
2299
|
-
return rotateLeft(node);
|
|
2300
|
-
}
|
|
2301
|
-
return node;
|
|
2302
|
-
};
|
|
2303
|
-
const treeSet = (node, key, value) => {
|
|
2304
|
-
if (!node) return makeTreeNode(key, value);
|
|
2305
|
-
if (key === node.key) return makeTreeNode(key, value, node.left, node.right);
|
|
2306
|
-
if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeSet(node.left, key, value), node.right));
|
|
2307
|
-
return rebalance(makeTreeNode(node.key, node.value, node.left, treeSet(node.right, key, value)));
|
|
2308
|
-
};
|
|
2309
|
-
const treeMin = (node) => node.left ? treeMin(node.left) : node;
|
|
2310
|
-
const treeRemove = (node, key) => {
|
|
2311
|
-
if (!node) return void 0;
|
|
2312
|
-
if (key < node.key) return rebalance(makeTreeNode(node.key, node.value, treeRemove(node.left, key), node.right));
|
|
2313
|
-
if (key > node.key) return rebalance(makeTreeNode(node.key, node.value, node.left, treeRemove(node.right, key)));
|
|
2314
|
-
if (!node.left) return node.right;
|
|
2315
|
-
if (!node.right) return node.left;
|
|
2316
|
-
const next = treeMin(node.right);
|
|
2317
|
-
return rebalance(makeTreeNode(next.key, next.value, node.left, treeRemove(node.right, next.key)));
|
|
2318
|
-
};
|
|
2319
|
-
const treeGet = (node, key) => {
|
|
2320
|
-
let current = node;
|
|
2321
|
-
while (current) {
|
|
2322
|
-
if (key === current.key) return current.value;
|
|
2323
|
-
current = key < current.key ? current.left : current.right;
|
|
2324
|
-
}
|
|
2325
|
-
};
|
|
2326
|
-
const treeHas = (node, key) => {
|
|
2327
|
-
let current = node;
|
|
2328
|
-
while (current) {
|
|
2329
|
-
if (key === current.key) return true;
|
|
2330
|
-
current = key < current.key ? current.left : current.right;
|
|
2331
|
-
}
|
|
2332
|
-
return false;
|
|
2333
|
-
};
|
|
2334
|
-
const treeFromSorted = (entries, start = 0, end = entries.length) => {
|
|
2335
|
-
if (start >= end) return void 0;
|
|
2336
|
-
const middle = start + end >> 1;
|
|
2337
|
-
const [key, value] = entries[middle];
|
|
2338
|
-
return makeTreeNode(key, value, treeFromSorted(entries, start, middle), treeFromSorted(entries, middle + 1, end));
|
|
2339
|
-
};
|
|
2340
|
-
const createCollection = (scheduler, spec) => {
|
|
2341
|
-
const values = /* @__PURE__ */ new Map();
|
|
2342
|
-
let ids = Object.freeze([]);
|
|
2343
|
-
let staged = /* @__PURE__ */ new Map();
|
|
2344
|
-
let nextIds = ids;
|
|
2345
|
-
let change;
|
|
2346
|
-
let instance;
|
|
2347
|
-
let initialized = false;
|
|
2348
|
-
let reset = false;
|
|
2349
|
-
let revision = 0;
|
|
2350
|
-
let publishedRoot;
|
|
2351
|
-
let cause;
|
|
2352
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
2353
|
-
const changedKeys = /* @__PURE__ */ new Set();
|
|
2354
|
-
let orderChanged = false;
|
|
2355
|
-
const readPublished = (root, publishedIds) => Object.freeze({
|
|
2356
|
-
get: (key) => {
|
|
2357
|
-
owner.check();
|
|
2358
|
-
return treeGet(root, key);
|
|
2359
|
-
},
|
|
2360
|
-
has: (key) => {
|
|
2361
|
-
owner.check();
|
|
2362
|
-
return treeHas(root, key);
|
|
2363
|
-
},
|
|
2364
|
-
ids: () => {
|
|
2365
|
-
owner.check();
|
|
2366
|
-
return publishedIds;
|
|
2367
|
-
}
|
|
2368
|
-
});
|
|
2369
|
-
const hasNext = (key) => staged.has(key) ? staged.get(key).present : values.has(key);
|
|
2370
|
-
const getNext = (key) => {
|
|
2371
|
-
const entry = staged.get(key);
|
|
2372
|
-
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
2373
1505
|
};
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
const nextHas = (key) => staged.has(key) ? staged.get(key).present : !cleared && values.has(key);
|
|
2384
|
-
const nextGet = (key) => {
|
|
2385
|
-
const entry = staged.get(key);
|
|
2386
|
-
return entry ? entry.present ? entry.value : void 0 : cleared ? void 0 : values.get(key);
|
|
2387
|
-
};
|
|
2388
|
-
const deriveIds = () => {
|
|
2389
|
-
const result = cleared ? [] : ids.filter(nextHas);
|
|
2390
|
-
for (const [key, entry] of staged) if (entry.present && (cleared || !values.has(key))) result.push(key);
|
|
2391
|
-
return result;
|
|
2392
|
-
};
|
|
2393
|
-
const read = (next) => ({
|
|
2394
|
-
get: (key) => {
|
|
2395
|
-
assertScope(active);
|
|
2396
|
-
return next ? nextGet(key) : values.get(key);
|
|
2397
|
-
},
|
|
2398
|
-
has: (key) => {
|
|
2399
|
-
assertScope(active);
|
|
2400
|
-
return next ? nextHas(key) : values.has(key);
|
|
2401
|
-
},
|
|
2402
|
-
ids: () => {
|
|
2403
|
-
assertScope(active);
|
|
2404
|
-
return next ? Object.freeze(explicitOrder ? explicitOrder.slice() : deriveIds()) : ids;
|
|
2405
|
-
}
|
|
2406
|
-
});
|
|
2407
|
-
const input = {
|
|
2408
|
-
sources,
|
|
2409
|
-
previous: read(false),
|
|
2410
|
-
next: read(true),
|
|
2411
|
-
output: {
|
|
2412
|
-
set: (key, value) => {
|
|
2413
|
-
assertScope(active);
|
|
2414
|
-
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
2415
|
-
staged.set(key, {
|
|
2416
|
-
present: true,
|
|
2417
|
-
value
|
|
2418
|
-
});
|
|
2419
|
-
},
|
|
2420
|
-
remove: (key) => {
|
|
2421
|
-
assertScope(active);
|
|
2422
|
-
staged.set(key, { present: false });
|
|
2423
|
-
},
|
|
2424
|
-
order: (order) => {
|
|
2425
|
-
assertScope(active);
|
|
2426
|
-
explicitOrder = order.slice();
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2429
|
-
};
|
|
2430
|
-
if (build || !instance) {
|
|
2431
|
-
require_issue.profile.materialized.rebuilt();
|
|
2432
|
-
instance = void 0;
|
|
2433
|
-
cleared = true;
|
|
2434
|
-
const built = spec.build(input);
|
|
2435
|
-
assertSynchronous(built);
|
|
2436
|
-
instance = built;
|
|
2437
|
-
} else {
|
|
2438
|
-
require_issue.profile.materialized.updated();
|
|
2439
|
-
const result = instance.update(input);
|
|
2440
|
-
assertSynchronous(result);
|
|
2441
|
-
if (result?.kind === "rebuild") {
|
|
2442
|
-
staged.clear();
|
|
2443
|
-
cleared = true;
|
|
2444
|
-
explicitOrder = void 0;
|
|
2445
|
-
instance = void 0;
|
|
2446
|
-
const built = spec.build(input);
|
|
2447
|
-
assertSynchronous(built);
|
|
2448
|
-
instance = built;
|
|
2449
|
-
build = true;
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
if (cleared) {
|
|
2453
|
-
for (const key of values.keys()) if (!staged.has(key)) staged.set(key, { present: false });
|
|
2454
|
-
}
|
|
2455
|
-
const added = /* @__PURE__ */ new Set();
|
|
2456
|
-
const removed = /* @__PURE__ */ new Set();
|
|
2457
|
-
const updated = /* @__PURE__ */ new Set();
|
|
2458
|
-
require_issue.profile.projection("touchedKeys", staged.size);
|
|
2459
|
-
for (const [key, entry] of staged) {
|
|
2460
|
-
const existed = values.has(key);
|
|
2461
|
-
if (entry.present) if (!existed) added.add(key);
|
|
2462
|
-
else if (!(spec.isEqual ?? Object.is)(values.get(key), entry.value)) updated.add(key);
|
|
2463
|
-
else staged.delete(key);
|
|
2464
|
-
else if (existed) removed.add(key);
|
|
2465
|
-
else staged.delete(key);
|
|
2466
|
-
}
|
|
2467
|
-
if (explicitOrder || added.size || removed.size) {
|
|
2468
|
-
const order = explicitOrder ?? [...ids.filter((key) => !removed.has(key)), ...added];
|
|
2469
|
-
if (new Set(order).size !== order.length || order.length !== values.size + added.size - removed.size || order.some((key) => !hasNext(key))) throw new TypeError("Projection order must contain every key exactly once.");
|
|
2470
|
-
nextIds = ids.length === order.length && ids.every((key, i) => key === order[i]) ? ids : Object.freeze(order.slice());
|
|
2471
|
-
}
|
|
2472
|
-
orderChanged = nextIds !== ids;
|
|
2473
|
-
[
|
|
2474
|
-
...added,
|
|
2475
|
-
...removed,
|
|
2476
|
-
...updated
|
|
2477
|
-
].forEach((key) => changedKeys.add(key));
|
|
2478
|
-
require_issue.profile.projection("changedKeys", changedKeys.size);
|
|
2479
|
-
const addedEntries = [];
|
|
2480
|
-
const updatedEntries = [];
|
|
2481
|
-
const removedEntries = [];
|
|
2482
|
-
for (const key of changedKeys) {
|
|
2483
|
-
const beforePresent = values.has(key);
|
|
2484
|
-
const afterPresent = hasNext(key);
|
|
2485
|
-
if (beforePresent === afterPresent) {
|
|
2486
|
-
if (!beforePresent) continue;
|
|
2487
|
-
if ((spec.isEqual ?? Object.is)(values.get(key), getNext(key))) continue;
|
|
2488
|
-
}
|
|
2489
|
-
if (!beforePresent) addedEntries.push({
|
|
2490
|
-
key,
|
|
2491
|
-
after: getNext(key)
|
|
2492
|
-
});
|
|
2493
|
-
else if (!afterPresent) removedEntries.push({
|
|
2494
|
-
key,
|
|
2495
|
-
before: values.get(key)
|
|
2496
|
-
});
|
|
2497
|
-
else updatedEntries.push({
|
|
2498
|
-
key,
|
|
2499
|
-
before: values.get(key),
|
|
2500
|
-
after: getNext(key)
|
|
2501
|
-
});
|
|
2502
|
-
}
|
|
2503
|
-
if (build) change = Object.freeze({ kind: "reset" });
|
|
2504
|
-
else if (addedEntries.length || updatedEntries.length || removedEntries.length || orderChanged) {
|
|
2505
|
-
let commonOrderChanged = false;
|
|
2506
|
-
if (orderChanged) {
|
|
2507
|
-
const before = ids.filter((key) => !removed.has(key));
|
|
2508
|
-
const after = nextIds.filter((key) => !added.has(key));
|
|
2509
|
-
commonOrderChanged = before.some((key, i) => after[i] !== key);
|
|
2510
|
-
}
|
|
2511
|
-
change = Object.freeze({
|
|
2512
|
-
kind: "incremental",
|
|
2513
|
-
added: Object.freeze(addedEntries),
|
|
2514
|
-
updated: Object.freeze(updatedEntries),
|
|
2515
|
-
removed: Object.freeze(removedEntries),
|
|
2516
|
-
...commonOrderChanged ? { order: Object.freeze({
|
|
2517
|
-
before: Object.freeze([...ids]),
|
|
2518
|
-
after: Object.freeze([...nextIds])
|
|
2519
|
-
}) } : {}
|
|
2520
|
-
});
|
|
2521
|
-
}
|
|
2522
|
-
reset = build;
|
|
2523
|
-
return change !== void 0;
|
|
2524
|
-
},
|
|
2525
|
-
context: (active) => {
|
|
2526
|
-
const input = {
|
|
2527
|
-
kind: "collection",
|
|
2528
|
-
read: {
|
|
2529
|
-
get: (key) => {
|
|
2530
|
-
assertScope(active);
|
|
2531
|
-
return getNext(key);
|
|
2532
|
-
},
|
|
2533
|
-
has: (key) => {
|
|
2534
|
-
assertScope(active);
|
|
2535
|
-
return hasNext(key);
|
|
2536
|
-
},
|
|
2537
|
-
ids: () => {
|
|
2538
|
-
assertScope(active);
|
|
2539
|
-
return nextIds;
|
|
2540
|
-
}
|
|
2541
|
-
},
|
|
2542
|
-
change,
|
|
2543
|
-
revision: revision + (change ? 1 : 0),
|
|
2544
|
-
cause
|
|
2545
|
-
};
|
|
2546
|
-
return Object.freeze(input);
|
|
2547
|
-
},
|
|
2548
|
-
revision: () => revision,
|
|
2549
|
-
reset: () => reset,
|
|
2550
|
-
publish: () => {
|
|
2551
|
-
if (change) if (change.kind === "reset") {
|
|
2552
|
-
const entries = [];
|
|
2553
|
-
for (const [key, entry] of staged) if (entry.present) entries.push([key, entry.value]);
|
|
2554
|
-
entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
2555
|
-
publishedRoot = treeFromSorted(entries);
|
|
2556
|
-
} else for (const [key, entry] of staged) publishedRoot = entry.present ? treeSet(publishedRoot, key, entry.value) : treeRemove(publishedRoot, key);
|
|
2557
|
-
for (const [key, entry] of staged) if (entry.present) values.set(key, entry.value);
|
|
2558
|
-
else values.delete(key);
|
|
2559
|
-
if (change) {
|
|
2560
|
-
if (initialized) revision++;
|
|
2561
|
-
}
|
|
2562
|
-
ids = nextIds;
|
|
2563
|
-
initialized = true;
|
|
2564
|
-
},
|
|
2565
|
-
emit: (call) => {
|
|
2566
|
-
require_issue.profile.materialized.notification();
|
|
2567
|
-
const resetEvent = Object.freeze({ kind: "reset" });
|
|
2568
|
-
Array.from(listeners).forEach((listener) => call(() => listener(change ?? resetEvent)));
|
|
2569
|
-
},
|
|
2570
|
-
clear: () => {
|
|
2571
|
-
staged.clear();
|
|
2572
|
-
nextIds = ids;
|
|
2573
|
-
change = void 0;
|
|
2574
|
-
reset = false;
|
|
2575
|
-
changedKeys.clear();
|
|
2576
|
-
cause = void 0;
|
|
2577
|
-
orderChanged = false;
|
|
2578
|
-
},
|
|
2579
|
-
release: () => {
|
|
2580
|
-
values.clear();
|
|
2581
|
-
staged.clear();
|
|
2582
|
-
publishedRoot = void 0;
|
|
2583
|
-
listeners.clear();
|
|
2584
|
-
ids = nextIds = Object.freeze([]);
|
|
2585
|
-
instance = void 0;
|
|
1506
|
+
scheduler.initialize(() => {
|
|
1507
|
+
let active = true;
|
|
1508
|
+
try {
|
|
1509
|
+
state.begin(() => active, true).output.set(initial);
|
|
1510
|
+
state.seal(true, equality);
|
|
1511
|
+
state.publish();
|
|
1512
|
+
state.clear();
|
|
1513
|
+
} finally {
|
|
1514
|
+
active = false;
|
|
2586
1515
|
}
|
|
2587
1516
|
});
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
owner.check();
|
|
2596
|
-
return revision;
|
|
2597
|
-
},
|
|
2598
|
-
subscribe: (listener) => {
|
|
2599
|
-
owner.check();
|
|
2600
|
-
listeners.add(listener);
|
|
2601
|
-
return () => {
|
|
2602
|
-
listeners.delete(listener);
|
|
2603
|
-
};
|
|
2604
|
-
}
|
|
2605
|
-
};
|
|
2606
|
-
owner.install(handle);
|
|
2607
|
-
return Object.freeze(handle);
|
|
2608
|
-
};
|
|
2609
|
-
//#endregion
|
|
2610
|
-
//#region core/src/projection/graph.ts
|
|
2611
|
-
const createProjectionGraph = (options) => {
|
|
2612
|
-
const scheduler = createScheduler(options.onError);
|
|
2613
|
-
const sources = createSources(scheduler);
|
|
2614
|
-
const value = (spec, options) => createValue(scheduler, spec, options);
|
|
2615
|
-
const map = (source, mapper, options) => {
|
|
2616
|
-
sources.assertCollection(source);
|
|
2617
|
-
const calculate = (id, entry) => {
|
|
2618
|
-
const value = mapper(id, entry);
|
|
2619
|
-
assertSynchronous(value);
|
|
2620
|
-
return value;
|
|
2621
|
-
};
|
|
2622
|
-
return createCollection(scheduler, {
|
|
2623
|
-
sources: { source },
|
|
2624
|
-
isEqual: options?.isEqual,
|
|
2625
|
-
build: ({ sources, output }) => {
|
|
2626
|
-
const read = sources.source.read;
|
|
2627
|
-
const ids = read.ids();
|
|
2628
|
-
require_issue.profile.collectionView.idsScanned(ids.length);
|
|
2629
|
-
for (const id of ids) {
|
|
2630
|
-
require_issue.profile.collectionView.mapped();
|
|
2631
|
-
output.set(id, calculate(id, read.get(id)));
|
|
2632
|
-
}
|
|
2633
|
-
output.order(ids);
|
|
2634
|
-
return { update: ({ sources, output }) => {
|
|
2635
|
-
const input = sources.source;
|
|
2636
|
-
const read = input.read;
|
|
2637
|
-
if (input.change?.kind === "reset") return { kind: "rebuild" };
|
|
2638
|
-
const change = input.change;
|
|
2639
|
-
if (!change) return;
|
|
2640
|
-
for (const entry of change.added) {
|
|
2641
|
-
require_issue.profile.collectionView.mapped();
|
|
2642
|
-
output.set(entry.key, calculate(entry.key, entry.after));
|
|
2643
|
-
}
|
|
2644
|
-
for (const entry of change.updated) {
|
|
2645
|
-
require_issue.profile.collectionView.mapped();
|
|
2646
|
-
output.set(entry.key, calculate(entry.key, entry.after));
|
|
2647
|
-
}
|
|
2648
|
-
for (const entry of change.removed) output.remove(entry.key);
|
|
2649
|
-
if (change.added.length || change.removed.length || change.order) {
|
|
2650
|
-
const ids = read.ids();
|
|
2651
|
-
require_issue.profile.collectionView.idsScanned(ids.length);
|
|
2652
|
-
output.order(ids);
|
|
2653
|
-
}
|
|
2654
|
-
} };
|
|
2655
|
-
}
|
|
2656
|
-
});
|
|
1517
|
+
scheduler.addSource(boundary);
|
|
1518
|
+
const mark = (metadata = {}) => {
|
|
1519
|
+
scheduler.assertIdle();
|
|
1520
|
+
pendingReset ||= metadata.reset ?? false;
|
|
1521
|
+
if (metadata.cause !== void 0) pendingCause ??= metadata.cause;
|
|
1522
|
+
if (boundary.fault) recovering = true;
|
|
1523
|
+
scheduler.capture(boundary);
|
|
2657
1524
|
};
|
|
2658
|
-
const
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
input: sources.input,
|
|
2663
|
-
collectionInput: sources.collectionInput,
|
|
2664
|
-
fromReadable: sources.fromReadable,
|
|
2665
|
-
value,
|
|
2666
|
-
collection: (spec) => createCollection(scheduler, spec),
|
|
2667
|
-
map,
|
|
2668
|
-
batch: scheduler.batch,
|
|
2669
|
-
assertIdle: scheduler.assertIdle,
|
|
2670
|
-
releaseNode: scheduler.releaseNode,
|
|
2671
|
-
releaseInput: scheduler.unregisterSource,
|
|
2672
|
-
dispose: () => {
|
|
2673
|
-
try {
|
|
2674
|
-
scheduler.dispose();
|
|
2675
|
-
} finally {
|
|
2676
|
-
if (!scheduler.active) sources.dispose();
|
|
2677
|
-
}
|
|
2678
|
-
}
|
|
1525
|
+
const fail = (cause) => {
|
|
1526
|
+
boundary.fault = sourceError(boundary, cause);
|
|
1527
|
+
recovering = false;
|
|
1528
|
+
scheduler.capture(boundary);
|
|
2679
1529
|
};
|
|
2680
|
-
return
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
const trackKey = (key) => {
|
|
2686
|
-
activeTracker?.keys.add(key);
|
|
2687
|
-
};
|
|
2688
|
-
const trackStructure = () => {
|
|
2689
|
-
if (!activeTracker) return;
|
|
2690
|
-
activeTracker.structure = true;
|
|
2691
|
-
};
|
|
2692
|
-
const trackAll = () => {
|
|
2693
|
-
if (!activeTracker) return;
|
|
2694
|
-
activeTracker.all = true;
|
|
2695
|
-
};
|
|
2696
|
-
const mapView = (read) => {
|
|
2697
|
-
const keys = read.ids();
|
|
2698
|
-
const source = /* @__PURE__ */ new Map();
|
|
2699
|
-
for (const key of keys) source.set(key, read.get(key));
|
|
2700
|
-
const view = {
|
|
2701
|
-
get: (key) => {
|
|
2702
|
-
trackKey(key);
|
|
2703
|
-
return source.get(key);
|
|
2704
|
-
},
|
|
2705
|
-
has: (key) => {
|
|
2706
|
-
trackKey(key);
|
|
2707
|
-
return source.has(key);
|
|
2708
|
-
},
|
|
2709
|
-
get size() {
|
|
2710
|
-
trackStructure();
|
|
2711
|
-
return source.size;
|
|
2712
|
-
},
|
|
2713
|
-
keys: () => {
|
|
2714
|
-
trackStructure();
|
|
2715
|
-
return source.keys();
|
|
2716
|
-
},
|
|
2717
|
-
values: () => {
|
|
2718
|
-
trackAll();
|
|
2719
|
-
return source.values();
|
|
2720
|
-
},
|
|
2721
|
-
entries: () => {
|
|
2722
|
-
trackAll();
|
|
2723
|
-
return source.entries();
|
|
2724
|
-
},
|
|
2725
|
-
forEach: (callback, thisArg) => {
|
|
2726
|
-
trackAll();
|
|
2727
|
-
source.forEach((value, key) => callback.call(thisArg, value, key, view));
|
|
1530
|
+
return {
|
|
1531
|
+
producer: boundary,
|
|
1532
|
+
output,
|
|
1533
|
+
setLatest: (value) => {
|
|
1534
|
+
latest = value;
|
|
2728
1535
|
},
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
1536
|
+
mark,
|
|
1537
|
+
fail,
|
|
1538
|
+
detach: (next) => {
|
|
1539
|
+
cleanup = next;
|
|
2732
1540
|
}
|
|
2733
1541
|
};
|
|
2734
|
-
return Object.freeze(view);
|
|
2735
1542
|
};
|
|
2736
|
-
const
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
1543
|
+
const createCollectionBoundary = (scheduler, name, readLatest, equality) => {
|
|
1544
|
+
const state = require_advanced.createCollectionOutput();
|
|
1545
|
+
let boundary;
|
|
1546
|
+
let pendingReset = false;
|
|
1547
|
+
let pendingCause;
|
|
1548
|
+
let contextCause;
|
|
1549
|
+
let recovering = false;
|
|
1550
|
+
let fullScan = false;
|
|
1551
|
+
let orderMayChange = false;
|
|
1552
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1553
|
+
let cleanup;
|
|
1554
|
+
const check = () => {
|
|
1555
|
+
if (!scheduler.active || boundary.disposed) throw new require_advanced.ProjectionDisposedError();
|
|
1556
|
+
if (boundary.fault) throw boundary.fault;
|
|
2743
1557
|
};
|
|
2744
|
-
const
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
return
|
|
2748
|
-
},
|
|
2749
|
-
has: (key) => {
|
|
2750
|
-
trackKey(key);
|
|
2751
|
-
return read.has(key);
|
|
2752
|
-
},
|
|
2753
|
-
get size() {
|
|
2754
|
-
trackStructure();
|
|
2755
|
-
return ids.length;
|
|
2756
|
-
},
|
|
2757
|
-
keys: () => {
|
|
2758
|
-
trackStructure();
|
|
2759
|
-
return ids[Symbol.iterator]();
|
|
1558
|
+
const output = {
|
|
1559
|
+
kind: "collection",
|
|
1560
|
+
get owner() {
|
|
1561
|
+
return boundary;
|
|
2760
1562
|
},
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
1563
|
+
consumers: /* @__PURE__ */ new Set(),
|
|
1564
|
+
context: (active) => state.context(active, contextCause),
|
|
1565
|
+
current: () => state.current(check),
|
|
1566
|
+
revision: state.revision,
|
|
1567
|
+
reset: state.reset,
|
|
1568
|
+
subscribe: (listener) => {
|
|
1569
|
+
check();
|
|
1570
|
+
const wrapped = (change) => listener(change);
|
|
1571
|
+
state.subscribe(wrapped);
|
|
1572
|
+
return () => state.unsubscribe(wrapped);
|
|
2764
1573
|
},
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
1574
|
+
emit: state.emit,
|
|
1575
|
+
clear: state.clear,
|
|
1576
|
+
release: state.release
|
|
1577
|
+
};
|
|
1578
|
+
boundary = {
|
|
1579
|
+
kind: "source",
|
|
1580
|
+
name,
|
|
1581
|
+
outputs: Object.freeze([output]),
|
|
1582
|
+
fault: void 0,
|
|
1583
|
+
disposed: false,
|
|
1584
|
+
pendingCause: () => pendingCause,
|
|
1585
|
+
recovering: () => recovering,
|
|
1586
|
+
prepare: (cause) => {
|
|
1587
|
+
contextCause = cause;
|
|
1588
|
+
let active = true;
|
|
1589
|
+
try {
|
|
1590
|
+
const changed = require_advanced.stageCollectionRead(state, readLatest(() => active), () => active, {
|
|
1591
|
+
reset: pendingReset,
|
|
1592
|
+
...fullScan ? {} : { candidates },
|
|
1593
|
+
orderMayChange,
|
|
1594
|
+
isEqual: equality
|
|
1595
|
+
});
|
|
1596
|
+
recovering = false;
|
|
1597
|
+
return {
|
|
1598
|
+
changed,
|
|
1599
|
+
force: pendingReset
|
|
1600
|
+
};
|
|
1601
|
+
} finally {
|
|
1602
|
+
active = false;
|
|
1603
|
+
}
|
|
2768
1604
|
},
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
1605
|
+
publish: state.publish,
|
|
1606
|
+
clear: () => {
|
|
1607
|
+
state.clear();
|
|
1608
|
+
pendingReset = false;
|
|
1609
|
+
pendingCause = void 0;
|
|
1610
|
+
contextCause = void 0;
|
|
1611
|
+
recovering = false;
|
|
1612
|
+
fullScan = false;
|
|
1613
|
+
orderMayChange = false;
|
|
1614
|
+
candidates.clear();
|
|
2772
1615
|
},
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
1616
|
+
release: () => {
|
|
1617
|
+
cleanup?.();
|
|
1618
|
+
cleanup = void 0;
|
|
1619
|
+
state.release();
|
|
1620
|
+
output.consumers.clear();
|
|
2776
1621
|
}
|
|
2777
1622
|
};
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
for (const key of previous.keys()) if (!current.has(key)) removed.push({
|
|
2791
|
-
key,
|
|
2792
|
-
before: previous.get(key)
|
|
2793
|
-
});
|
|
2794
|
-
else if (!Object.is(previous.get(key), current.get(key))) updated.push({
|
|
2795
|
-
key,
|
|
2796
|
-
before: previous.get(key),
|
|
2797
|
-
after: current.get(key)
|
|
2798
|
-
});
|
|
2799
|
-
for (const key of current.keys()) if (!previous.has(key)) added.push({
|
|
2800
|
-
key,
|
|
2801
|
-
after: current.get(key)
|
|
1623
|
+
scheduler.initialize(() => {
|
|
1624
|
+
let active = true;
|
|
1625
|
+
try {
|
|
1626
|
+
require_advanced.stageCollectionRead(state, readLatest(() => active), () => active, {
|
|
1627
|
+
reset: true,
|
|
1628
|
+
isEqual: equality
|
|
1629
|
+
});
|
|
1630
|
+
state.publish();
|
|
1631
|
+
state.clear();
|
|
1632
|
+
} finally {
|
|
1633
|
+
active = false;
|
|
1634
|
+
}
|
|
2802
1635
|
});
|
|
2803
|
-
|
|
2804
|
-
const
|
|
2805
|
-
|
|
2806
|
-
|
|
1636
|
+
scheduler.addSource(boundary);
|
|
1637
|
+
const mark = (metadata = {}) => {
|
|
1638
|
+
scheduler.assertIdle();
|
|
1639
|
+
pendingReset ||= metadata.reset ?? false;
|
|
1640
|
+
if (metadata.cause !== void 0) pendingCause ??= metadata.cause;
|
|
1641
|
+
fullScan ||= metadata.fullScan ?? false;
|
|
1642
|
+
orderMayChange ||= metadata.orderMayChange ?? false;
|
|
1643
|
+
if (metadata.candidates) for (const key of metadata.candidates) candidates.add(key);
|
|
1644
|
+
if (boundary.fault) recovering = true;
|
|
1645
|
+
scheduler.capture(boundary);
|
|
1646
|
+
};
|
|
1647
|
+
const fail = (cause) => {
|
|
1648
|
+
boundary.fault = sourceError(boundary, cause);
|
|
1649
|
+
recovering = false;
|
|
1650
|
+
scheduler.capture(boundary);
|
|
1651
|
+
};
|
|
2807
1652
|
return {
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
}) } : {}
|
|
1653
|
+
producer: boundary,
|
|
1654
|
+
output,
|
|
1655
|
+
mark,
|
|
1656
|
+
fail,
|
|
1657
|
+
detach: (next) => {
|
|
1658
|
+
cleanup = next;
|
|
1659
|
+
}
|
|
2816
1660
|
};
|
|
2817
1661
|
};
|
|
2818
|
-
const
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
const
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
if (owner) {
|
|
2846
|
-
if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
|
|
2847
|
-
if (!requester.active) throw new ProjectionDisposedError();
|
|
2848
|
-
}
|
|
2849
|
-
const old = materialized.get(projection);
|
|
2850
|
-
if (old) return old.node;
|
|
2851
|
-
const definition = require_advanced.definitionOf(projection);
|
|
2852
|
-
let instance;
|
|
2853
|
-
let write;
|
|
2854
|
-
switch (definition.kind) {
|
|
2855
|
-
case "input": {
|
|
2856
|
-
const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
|
|
2857
|
-
write = {
|
|
2858
|
-
kind: "value-input",
|
|
2859
|
-
source: input.source,
|
|
2860
|
-
set: input.set
|
|
2861
|
-
};
|
|
2862
|
-
instance = runtime.value({
|
|
2863
|
-
sources: { input: input.source },
|
|
2864
|
-
build: (values) => ({
|
|
2865
|
-
value: values.input.value,
|
|
2866
|
-
update: (next) => ({
|
|
2867
|
-
kind: "changed",
|
|
2868
|
-
value: next.input.value
|
|
2869
|
-
})
|
|
2870
|
-
})
|
|
2871
|
-
});
|
|
2872
|
-
break;
|
|
2873
|
-
}
|
|
2874
|
-
case "collection-input": {
|
|
2875
|
-
const source = runtime.collectionInput(definition.initial);
|
|
2876
|
-
write = {
|
|
2877
|
-
kind: "collection-input",
|
|
2878
|
-
source: source.source,
|
|
2879
|
-
update: source.update
|
|
1662
|
+
const createSourceRegistry = (scheduler) => {
|
|
1663
|
+
const documents = /* @__PURE__ */ new Map();
|
|
1664
|
+
const readables = /* @__PURE__ */ new Map();
|
|
1665
|
+
const externalValues = /* @__PURE__ */ new WeakMap();
|
|
1666
|
+
const externalCollections = /* @__PURE__ */ new WeakMap();
|
|
1667
|
+
const documentConnection = (runtime) => {
|
|
1668
|
+
const state = require_access.accessOf(runtime);
|
|
1669
|
+
const cached = documents.get(state);
|
|
1670
|
+
if (cached) return cached;
|
|
1671
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1672
|
+
const bindings = /* @__PURE__ */ new Set();
|
|
1673
|
+
const guard = (locked) => {
|
|
1674
|
+
state.projectionLocks = (state.projectionLocks ?? 0) + (locked ? 1 : -1);
|
|
1675
|
+
};
|
|
1676
|
+
scheduler.guards.add(guard);
|
|
1677
|
+
let closed = false;
|
|
1678
|
+
let detach = () => void 0;
|
|
1679
|
+
const connection = {
|
|
1680
|
+
add(binding) {
|
|
1681
|
+
if (closed) throw new Error("Document projection connection is closed.");
|
|
1682
|
+
bindings.add(binding);
|
|
1683
|
+
let active = true;
|
|
1684
|
+
return () => {
|
|
1685
|
+
if (!active) return;
|
|
1686
|
+
active = false;
|
|
1687
|
+
bindings.delete(binding);
|
|
1688
|
+
if (!bindings.size) connection.close();
|
|
2880
1689
|
};
|
|
2881
|
-
|
|
2882
|
-
|
|
1690
|
+
},
|
|
1691
|
+
close() {
|
|
1692
|
+
if (closed) return;
|
|
1693
|
+
closed = true;
|
|
1694
|
+
detach();
|
|
1695
|
+
bindings.clear();
|
|
1696
|
+
scheduler.guards.delete(guard);
|
|
1697
|
+
documents.delete(state);
|
|
2883
1698
|
}
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
})
|
|
2895
|
-
});
|
|
2896
|
-
break;
|
|
1699
|
+
};
|
|
1700
|
+
detach = require_notification.attachProjection(runtime, {
|
|
1701
|
+
capture: (commit) => {
|
|
1702
|
+
for (const binding of bindings) if (binding.matches(commit)) binding.capture(commit);
|
|
1703
|
+
},
|
|
1704
|
+
settle: scheduler.settle,
|
|
1705
|
+
flush: scheduler.flush,
|
|
1706
|
+
dispose: () => {
|
|
1707
|
+
for (const binding of bindings) binding.dispose();
|
|
1708
|
+
scheduler.run();
|
|
2897
1709
|
}
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
1710
|
+
});
|
|
1711
|
+
documents.set(state, connection);
|
|
1712
|
+
return connection;
|
|
1713
|
+
};
|
|
1714
|
+
const valueInput = (definition) => {
|
|
1715
|
+
if (definition.source.kind !== "value-input") throw new Error("Invalid value input source.");
|
|
1716
|
+
const source = definition.source;
|
|
1717
|
+
const boundary = createValueBoundary(scheduler, "projection input", source.initial, source.equality);
|
|
1718
|
+
return {
|
|
1719
|
+
...boundary,
|
|
1720
|
+
write: {
|
|
1721
|
+
kind: "value",
|
|
1722
|
+
set(value) {
|
|
1723
|
+
scheduler.assertIdle();
|
|
1724
|
+
boundary.setLatest(value);
|
|
1725
|
+
boundary.mark();
|
|
1726
|
+
scheduler.run();
|
|
2911
1727
|
}
|
|
2912
|
-
break;
|
|
2913
|
-
case "source-collection": {
|
|
2914
|
-
const source = runtime.fromCollectionSource(definition.source);
|
|
2915
|
-
instance = runtime.map(source, (_key, value) => value);
|
|
2916
|
-
break;
|
|
2917
1728
|
}
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
1729
|
+
};
|
|
1730
|
+
};
|
|
1731
|
+
const collectionInput = (definition) => {
|
|
1732
|
+
if (definition.source.kind !== "collection-input") throw new Error("Invalid collection input source.");
|
|
1733
|
+
const values = new Map(definition.source.initial);
|
|
1734
|
+
const boundary = createCollectionBoundary(scheduler, "projection collection input", () => require_advanced.mapRead(values), definition.output.equality);
|
|
1735
|
+
return {
|
|
1736
|
+
...boundary,
|
|
1737
|
+
write: {
|
|
1738
|
+
kind: "collection",
|
|
1739
|
+
update(run) {
|
|
1740
|
+
scheduler.assertIdle();
|
|
1741
|
+
const staged = /* @__PURE__ */ new Map();
|
|
1742
|
+
const operations = [];
|
|
1743
|
+
let active = true;
|
|
1744
|
+
const draft = Object.freeze({
|
|
1745
|
+
get: (key) => {
|
|
1746
|
+
require_advanced.assertScope(() => active);
|
|
1747
|
+
const entry = staged.get(key);
|
|
1748
|
+
return entry ? entry.present ? entry.value : void 0 : values.get(key);
|
|
1749
|
+
},
|
|
1750
|
+
has: (key) => {
|
|
1751
|
+
require_advanced.assertScope(() => active);
|
|
1752
|
+
const entry = staged.get(key);
|
|
1753
|
+
return entry ? entry.present : values.has(key);
|
|
1754
|
+
},
|
|
1755
|
+
set: (key, value) => {
|
|
1756
|
+
require_advanced.assertScope(() => active);
|
|
1757
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
1758
|
+
staged.set(key, {
|
|
1759
|
+
present: true,
|
|
1760
|
+
value
|
|
1761
|
+
});
|
|
1762
|
+
operations.push({
|
|
1763
|
+
kind: "set",
|
|
1764
|
+
key,
|
|
1765
|
+
value
|
|
1766
|
+
});
|
|
1767
|
+
},
|
|
1768
|
+
remove: (key) => {
|
|
1769
|
+
require_advanced.assertScope(() => active);
|
|
1770
|
+
if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
|
|
1771
|
+
staged.set(key, { present: false });
|
|
1772
|
+
operations.push({
|
|
1773
|
+
kind: "remove",
|
|
1774
|
+
key
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
try {
|
|
1779
|
+
require_advanced.assertSynchronous(run(draft));
|
|
1780
|
+
} finally {
|
|
1781
|
+
active = false;
|
|
1782
|
+
}
|
|
1783
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1784
|
+
let structural = false;
|
|
1785
|
+
let changed = false;
|
|
1786
|
+
for (const operation of operations) {
|
|
1787
|
+
const present = values.has(operation.key);
|
|
1788
|
+
if (operation.kind === "set") {
|
|
1789
|
+
if (present && Object.is(values.get(operation.key), operation.value)) continue;
|
|
1790
|
+
structural ||= !present;
|
|
1791
|
+
values.set(operation.key, operation.value);
|
|
1792
|
+
} else {
|
|
1793
|
+
if (!present) continue;
|
|
1794
|
+
structural = true;
|
|
1795
|
+
values.delete(operation.key);
|
|
1796
|
+
}
|
|
1797
|
+
candidates.add(operation.key);
|
|
1798
|
+
changed = true;
|
|
1799
|
+
}
|
|
1800
|
+
if (!changed) return;
|
|
1801
|
+
boundary.mark({
|
|
1802
|
+
candidates,
|
|
1803
|
+
orderMayChange: structural
|
|
2930
1804
|
});
|
|
2931
|
-
|
|
1805
|
+
scheduler.run();
|
|
2932
1806
|
}
|
|
2933
|
-
|
|
1807
|
+
}
|
|
1808
|
+
};
|
|
1809
|
+
};
|
|
1810
|
+
const documentValue = (definition) => {
|
|
1811
|
+
if (definition.source.kind !== "document-value") throw new Error("Invalid document value source.");
|
|
1812
|
+
const { document, selector } = definition.source;
|
|
1813
|
+
const state = require_access.accessOf(document);
|
|
1814
|
+
const address = selector?.address ?? Object.freeze([]);
|
|
1815
|
+
const schemaNode = () => require_issue.nodeAt(state.schema, address, state.document);
|
|
1816
|
+
const read = () => {
|
|
1817
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1818
|
+
const node = schemaNode();
|
|
1819
|
+
if (!node) return void 0;
|
|
1820
|
+
return require_access.copyValue(node, selector ? require_issue.read(state.document, address, state.schema) : state.document);
|
|
1821
|
+
};
|
|
1822
|
+
const boundary = createValueBoundary(scheduler, "document source", read(), (left, right) => {
|
|
1823
|
+
const node = schemaNode();
|
|
1824
|
+
return node ? require_access.equalValue(node, left, right) : Object.is(left, right);
|
|
1825
|
+
});
|
|
1826
|
+
const remove = documentConnection(document).add({
|
|
1827
|
+
matches: (commit) => !selector || affectsTarget(commit.impact, selector),
|
|
1828
|
+
capture: (commit) => {
|
|
2934
1829
|
try {
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
1830
|
+
boundary.setLatest(read());
|
|
1831
|
+
boundary.mark({ reset: commit.impact.kind === "reset" });
|
|
1832
|
+
} catch (cause) {
|
|
1833
|
+
boundary.fail(cause);
|
|
1834
|
+
}
|
|
1835
|
+
},
|
|
1836
|
+
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
1837
|
+
});
|
|
1838
|
+
boundary.detach(remove);
|
|
1839
|
+
return boundary;
|
|
1840
|
+
};
|
|
1841
|
+
const documentCollection = (definition) => {
|
|
1842
|
+
if (definition.source.kind !== "document-collection") throw new Error("Invalid document collection source.");
|
|
1843
|
+
const { document, selector } = definition.source;
|
|
1844
|
+
const state = require_access.accessOf(document);
|
|
1845
|
+
const read = (active) => {
|
|
1846
|
+
if (state.disposed) throw new Error("Document has been disposed.");
|
|
1847
|
+
const raw = require_access.collectionAccess({
|
|
1848
|
+
state,
|
|
1849
|
+
active
|
|
1850
|
+
}, selector.address);
|
|
1851
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
1852
|
+
return Object.freeze({
|
|
1853
|
+
get: (key) => {
|
|
1854
|
+
require_advanced.assertScope(active);
|
|
1855
|
+
if (!snapshots.has(key)) snapshots.set(key, require_access.snapshot(raw.get(key)));
|
|
1856
|
+
return snapshots.get(key);
|
|
1857
|
+
},
|
|
1858
|
+
has: (key) => {
|
|
1859
|
+
require_advanced.assertScope(active);
|
|
1860
|
+
return raw.has(key);
|
|
1861
|
+
},
|
|
1862
|
+
ids: () => {
|
|
1863
|
+
require_advanced.assertScope(active);
|
|
1864
|
+
return raw.ids();
|
|
2951
1865
|
}
|
|
2952
|
-
|
|
1866
|
+
});
|
|
1867
|
+
};
|
|
1868
|
+
const collectionNode = () => require_issue.nodeAt(state.schema, selector.address, state.document);
|
|
1869
|
+
const boundary = createCollectionBoundary(scheduler, "document collection source", read, (left, right) => {
|
|
1870
|
+
const node = collectionNode();
|
|
1871
|
+
const entry = node && require_advanced.collectionEntryNode(node);
|
|
1872
|
+
return entry ? require_access.equalValue(entry, left, right) : Object.is(left, right);
|
|
1873
|
+
});
|
|
1874
|
+
const remove = documentConnection(document).add({
|
|
1875
|
+
matches: (commit) => affectsTarget(commit.impact, selector),
|
|
1876
|
+
capture: (commit) => {
|
|
1877
|
+
const impact = collectionImpact(commit.impact, selector);
|
|
1878
|
+
if (impact.kind === "reset") {
|
|
1879
|
+
boundary.mark({ reset: true });
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
const candidates = new Set([
|
|
1883
|
+
...impact.added,
|
|
1884
|
+
...impact.updated,
|
|
1885
|
+
...impact.removed
|
|
1886
|
+
]);
|
|
1887
|
+
boundary.mark({
|
|
1888
|
+
candidates,
|
|
1889
|
+
orderMayChange: Boolean(impact.added.size || impact.removed.size || impact.orderChanged)
|
|
1890
|
+
});
|
|
1891
|
+
},
|
|
1892
|
+
dispose: () => boundary.fail(/* @__PURE__ */ new Error("Document has been disposed."))
|
|
1893
|
+
});
|
|
1894
|
+
boundary.detach(remove);
|
|
1895
|
+
return boundary;
|
|
1896
|
+
};
|
|
1897
|
+
const readable = (definition) => {
|
|
1898
|
+
if (definition.source.kind !== "readable") throw new Error("Invalid Readable source.");
|
|
1899
|
+
const { readable, equality } = definition.source;
|
|
1900
|
+
let initial = readable.current();
|
|
1901
|
+
const boundary = createValueBoundary(scheduler, "external readable", initial, equality);
|
|
1902
|
+
const member = {
|
|
1903
|
+
boundary,
|
|
1904
|
+
receive(value, metadata) {
|
|
1905
|
+
initial = value;
|
|
1906
|
+
boundary.setLatest(value);
|
|
1907
|
+
boundary.mark(metadata);
|
|
2953
1908
|
}
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
1909
|
+
};
|
|
1910
|
+
let connection = readables.get(readable);
|
|
1911
|
+
if (!connection) {
|
|
1912
|
+
const members = /* @__PURE__ */ new Set();
|
|
1913
|
+
let closed = false;
|
|
1914
|
+
let unsubscribe;
|
|
1915
|
+
let detachDocument;
|
|
1916
|
+
const receive = (settle) => {
|
|
1917
|
+
if (closed || !scheduler.active) return;
|
|
1918
|
+
try {
|
|
1919
|
+
const value = readable.current();
|
|
1920
|
+
members.forEach((next) => next.receive(value));
|
|
1921
|
+
} catch (cause) {
|
|
1922
|
+
members.forEach((next) => next.boundary.fail(cause));
|
|
1923
|
+
}
|
|
1924
|
+
if (settle) scheduler.run();
|
|
1925
|
+
};
|
|
1926
|
+
const created = {
|
|
1927
|
+
members,
|
|
1928
|
+
close() {
|
|
1929
|
+
if (closed) return;
|
|
1930
|
+
closed = true;
|
|
1931
|
+
unsubscribe?.();
|
|
1932
|
+
detachDocument?.();
|
|
1933
|
+
readables.delete(readable);
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
const owner = require_notification.documentReadableOwner(readable);
|
|
1937
|
+
if (owner) detachDocument = documentConnection(owner).add({
|
|
1938
|
+
matches: () => true,
|
|
1939
|
+
capture: () => receive(false),
|
|
1940
|
+
dispose: () => {
|
|
1941
|
+
members.forEach((next) => next.boundary.fail(/* @__PURE__ */ new Error("Document has been disposed.")));
|
|
1942
|
+
}
|
|
1943
|
+
});
|
|
1944
|
+
unsubscribe = readable.subscribe(() => receive(true));
|
|
1945
|
+
connection = created;
|
|
1946
|
+
readables.set(readable, connection);
|
|
1947
|
+
}
|
|
1948
|
+
connection.members.add(member);
|
|
1949
|
+
boundary.detach(() => {
|
|
1950
|
+
connection.members.delete(member);
|
|
1951
|
+
if (!connection.members.size) connection.close();
|
|
1952
|
+
});
|
|
1953
|
+
return boundary;
|
|
1954
|
+
};
|
|
1955
|
+
const externalValue = (definition) => {
|
|
1956
|
+
if (definition.source.kind !== "external-value") throw new Error("Invalid external value source.");
|
|
1957
|
+
const { source, equality } = definition.source;
|
|
1958
|
+
const boundary = createValueBoundary(scheduler, "external source", source.current(), equality);
|
|
1959
|
+
const member = {
|
|
1960
|
+
boundary,
|
|
1961
|
+
receive(value, metadata) {
|
|
1962
|
+
boundary.setLatest(value);
|
|
1963
|
+
boundary.mark(metadata);
|
|
2967
1964
|
}
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
1965
|
+
};
|
|
1966
|
+
let connection = externalValues.get(source);
|
|
1967
|
+
if (!connection) {
|
|
1968
|
+
const members = /* @__PURE__ */ new Set();
|
|
1969
|
+
let closed = false;
|
|
1970
|
+
let unsubscribe;
|
|
1971
|
+
const created = {
|
|
1972
|
+
members,
|
|
1973
|
+
close() {
|
|
1974
|
+
if (closed) return;
|
|
1975
|
+
closed = true;
|
|
1976
|
+
unsubscribe?.();
|
|
1977
|
+
externalValues.delete(source);
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
try {
|
|
1981
|
+
unsubscribe = source.subscribe((event) => {
|
|
1982
|
+
try {
|
|
1983
|
+
members.forEach((next) => next.receive(event.value, {
|
|
1984
|
+
reset: event.reset,
|
|
1985
|
+
cause: event.cause
|
|
1986
|
+
}));
|
|
1987
|
+
} catch (cause) {
|
|
1988
|
+
members.forEach((next) => next.boundary.fail(cause));
|
|
1989
|
+
}
|
|
1990
|
+
scheduler.run();
|
|
1991
|
+
});
|
|
1992
|
+
} catch (cause) {
|
|
1993
|
+
created.close();
|
|
1994
|
+
throw cause;
|
|
2975
1995
|
}
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
1996
|
+
connection = created;
|
|
1997
|
+
externalValues.set(source, connection);
|
|
1998
|
+
}
|
|
1999
|
+
connection.members.add(member);
|
|
2000
|
+
boundary.detach(() => {
|
|
2001
|
+
connection.members.delete(member);
|
|
2002
|
+
if (!connection.members.size) connection.close();
|
|
2003
|
+
});
|
|
2004
|
+
return boundary;
|
|
2005
|
+
};
|
|
2006
|
+
const externalCollection = (definition) => {
|
|
2007
|
+
if (definition.source.kind !== "external-collection") throw new Error("Invalid external collection source.");
|
|
2008
|
+
const source = definition.source.source;
|
|
2009
|
+
const boundary = createCollectionBoundary(scheduler, "external collection source", () => {
|
|
2010
|
+
const current = source.current();
|
|
2011
|
+
return Object.freeze({
|
|
2012
|
+
get: (key) => current.get(key),
|
|
2013
|
+
has: (key) => current.has(key),
|
|
2014
|
+
ids: () => current.ids()
|
|
2015
|
+
});
|
|
2016
|
+
}, definition.output.equality);
|
|
2017
|
+
let connection = externalCollections.get(source);
|
|
2018
|
+
if (!connection) {
|
|
2019
|
+
const members = /* @__PURE__ */ new Set();
|
|
2020
|
+
let closed = false;
|
|
2021
|
+
let unsubscribe;
|
|
2022
|
+
const created = {
|
|
2023
|
+
members,
|
|
2024
|
+
close() {
|
|
2025
|
+
if (closed) return;
|
|
2026
|
+
closed = true;
|
|
2027
|
+
unsubscribe?.();
|
|
2028
|
+
externalCollections.delete(source);
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
try {
|
|
2032
|
+
unsubscribe = source.subscribe((event) => {
|
|
2033
|
+
try {
|
|
2034
|
+
const impact = event.impact;
|
|
2035
|
+
if (impact?.kind === "reset") members.forEach((next) => next.mark({
|
|
2036
|
+
reset: true,
|
|
2037
|
+
cause: event.cause
|
|
2038
|
+
}));
|
|
2039
|
+
else if (impact?.kind === "incremental") {
|
|
2040
|
+
const candidates = new Set([
|
|
2041
|
+
...impact.added,
|
|
2042
|
+
...impact.updated,
|
|
2043
|
+
...impact.removed
|
|
2044
|
+
]);
|
|
2045
|
+
members.forEach((next) => next.mark({
|
|
2046
|
+
candidates,
|
|
2047
|
+
orderMayChange: Boolean(impact.added.size || impact.removed.size || impact.orderChanged),
|
|
2048
|
+
cause: event.cause
|
|
2049
|
+
}));
|
|
2050
|
+
} else members.forEach((next) => next.mark({
|
|
2051
|
+
fullScan: true,
|
|
2052
|
+
orderMayChange: true,
|
|
2053
|
+
cause: event.cause
|
|
2054
|
+
}));
|
|
2055
|
+
} catch (cause) {
|
|
2056
|
+
members.forEach((next) => next.fail(cause));
|
|
2057
|
+
}
|
|
2058
|
+
scheduler.run();
|
|
2983
2059
|
});
|
|
2984
|
-
|
|
2060
|
+
} catch (cause) {
|
|
2061
|
+
created.close();
|
|
2062
|
+
throw cause;
|
|
2985
2063
|
}
|
|
2064
|
+
connection = created;
|
|
2065
|
+
externalCollections.set(source, connection);
|
|
2986
2066
|
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2067
|
+
connection.members.add(boundary);
|
|
2068
|
+
boundary.detach(() => {
|
|
2069
|
+
connection.members.delete(boundary);
|
|
2070
|
+
if (!connection.members.size) connection.close();
|
|
2071
|
+
});
|
|
2072
|
+
return boundary;
|
|
2073
|
+
};
|
|
2074
|
+
return {
|
|
2075
|
+
materialize(definition) {
|
|
2076
|
+
switch (definition.source.kind) {
|
|
2077
|
+
case "value-input": return valueInput(definition);
|
|
2078
|
+
case "collection-input": return collectionInput(definition);
|
|
2079
|
+
case "document-value": return documentValue(definition);
|
|
2080
|
+
case "document-collection": return documentCollection(definition);
|
|
2081
|
+
case "readable": return readable(definition);
|
|
2082
|
+
case "external-value": return externalValue(definition);
|
|
2083
|
+
case "external-collection": return externalCollection(definition);
|
|
2084
|
+
}
|
|
2085
|
+
},
|
|
2086
|
+
dispose() {
|
|
2087
|
+
documents.forEach((connection) => connection.close());
|
|
2088
|
+
readables.forEach((connection) => connection.close());
|
|
2089
|
+
documents.clear();
|
|
2090
|
+
readables.clear();
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
};
|
|
2094
|
+
//#endregion
|
|
2095
|
+
//#region core/src/projection/runtime.ts
|
|
2096
|
+
const createProjectionRuntime = (options) => {
|
|
2097
|
+
const scheduler = require_advanced.createScheduler(options?.onError ?? (() => void 0));
|
|
2098
|
+
const sources = createSourceRegistry(scheduler);
|
|
2099
|
+
const materialized = /* @__PURE__ */ new WeakMap();
|
|
2100
|
+
const publicViews = /* @__PURE__ */ new WeakMap();
|
|
2101
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
2102
|
+
const assertAccess = (definition, requester) => {
|
|
2103
|
+
scheduler.assertActive();
|
|
2104
|
+
const owner = definition.owner;
|
|
2105
|
+
if (owner === void 0) return;
|
|
2106
|
+
if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
|
|
2107
|
+
if (!requester?.active) throw new require_advanced.ProjectionDisposedError();
|
|
2108
|
+
};
|
|
2109
|
+
const rememberScoped = (definition, requester) => {
|
|
2110
|
+
if (requester && definition.owner === requester) requester.materialized.push(definition);
|
|
2111
|
+
};
|
|
2112
|
+
const materializeProducer = (definition, requester) => {
|
|
2113
|
+
assertAccess(definition, requester);
|
|
2114
|
+
const existing = materialized.get(definition);
|
|
2115
|
+
if (existing) return existing;
|
|
2116
|
+
let result;
|
|
2117
|
+
if (definition.kind === "source") {
|
|
2118
|
+
const source = sources.materialize(definition);
|
|
2119
|
+
result = Object.freeze({
|
|
2120
|
+
record: source.producer,
|
|
2121
|
+
outputs: Object.freeze([source.output]),
|
|
2122
|
+
...source.write ? { write: source.write } : {}
|
|
2123
|
+
});
|
|
2124
|
+
} else {
|
|
2125
|
+
if (definition.owner === void 0 && definition.dependencies.some((dependency) => require_advanced.producerOf(dependency).owner !== void 0)) throw new TypeError("A root projection cannot depend on a scoped projection.");
|
|
2126
|
+
const dependencies = definition.dependencies.map((dependency) => resolveOutput(dependency, requester));
|
|
2127
|
+
const processor = createProcessor(scheduler, definition, Object.freeze(dependencies));
|
|
2128
|
+
result = Object.freeze({
|
|
2129
|
+
record: processor,
|
|
2130
|
+
outputs: processor.outputs
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
materialized.set(definition, result);
|
|
2134
|
+
rememberScoped(definition, requester);
|
|
2135
|
+
return result;
|
|
2997
2136
|
};
|
|
2998
|
-
const
|
|
2999
|
-
const
|
|
3000
|
-
const
|
|
3001
|
-
if (
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
2137
|
+
const resolveOutput = (projection, requester) => {
|
|
2138
|
+
const ref = require_advanced.projectionRef(projection);
|
|
2139
|
+
const output = materializeProducer(ref.producer, requester).outputs[ref.output];
|
|
2140
|
+
if (!output) throw new TypeError("Projection output does not exist.");
|
|
2141
|
+
return output;
|
|
2142
|
+
};
|
|
2143
|
+
const assertOutput = (output) => {
|
|
2144
|
+
scheduler.assertActive();
|
|
2145
|
+
if (output.owner.disposed) throw new require_advanced.ProjectionDisposedError();
|
|
2146
|
+
if (output.owner.fault) throw output.owner.fault;
|
|
2147
|
+
};
|
|
2148
|
+
const publicCurrent = (output) => {
|
|
2149
|
+
assertOutput(output);
|
|
2150
|
+
const current = output.current();
|
|
2151
|
+
const revision = output.revision();
|
|
2152
|
+
const cached = publicViews.get(output);
|
|
2153
|
+
if (cached?.revision === revision) return cached.value;
|
|
2154
|
+
if (output.kind === "collection") {
|
|
2155
|
+
const value = require_advanced.collectionView(current);
|
|
2156
|
+
publicViews.set(output, {
|
|
3008
2157
|
revision,
|
|
3009
2158
|
value
|
|
3010
|
-
};
|
|
2159
|
+
});
|
|
3011
2160
|
return value;
|
|
3012
2161
|
}
|
|
3013
2162
|
if (isMapLike(current)) {
|
|
3014
|
-
const
|
|
3015
|
-
|
|
3016
|
-
const revision = instance.revision();
|
|
3017
|
-
if (existing?.revision === revision) return existing.value;
|
|
3018
|
-
const value = mapView(mapRead(current));
|
|
3019
|
-
if (record) record.collection = {
|
|
2163
|
+
const value = require_advanced.snapshotCollectionView(require_advanced.mapRead(current));
|
|
2164
|
+
publicViews.set(output, {
|
|
3020
2165
|
revision,
|
|
3021
2166
|
value
|
|
3022
|
-
};
|
|
2167
|
+
});
|
|
3023
2168
|
return value;
|
|
3024
2169
|
}
|
|
2170
|
+
publicViews.delete(output);
|
|
3025
2171
|
return current;
|
|
3026
2172
|
};
|
|
3027
|
-
const
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
revision: () => materialize(projection, requester).revision(),
|
|
3031
|
-
subscribe: (listener) => materialize(projection, requester).subscribe(listener)
|
|
3032
|
-
});
|
|
3033
|
-
let initialized = false;
|
|
3034
|
-
let value;
|
|
3035
|
-
let selectedRevision = 0;
|
|
3036
|
-
let sourceRevision = -1;
|
|
3037
|
-
let selection = Object.freeze({
|
|
3038
|
-
keys: /* @__PURE__ */ new Set(),
|
|
3039
|
-
all: true,
|
|
3040
|
-
structure: false
|
|
3041
|
-
});
|
|
3042
|
-
let previousCollection;
|
|
3043
|
-
let unsubscribeSource;
|
|
3044
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
3045
|
-
const updateCollectionSnapshot = () => {
|
|
3046
|
-
const current = get(projection, requester);
|
|
3047
|
-
previousCollection = isMapLike(current) ? current : void 0;
|
|
3048
|
-
};
|
|
3049
|
-
const evaluate = () => {
|
|
3050
|
-
const tracker = {
|
|
3051
|
-
keys: /* @__PURE__ */ new Set(),
|
|
3052
|
-
all: false,
|
|
3053
|
-
structure: false
|
|
3054
|
-
};
|
|
3055
|
-
const previousTracker = activeTracker;
|
|
3056
|
-
activeTracker = tracker;
|
|
3057
|
-
let source;
|
|
3058
|
-
let next;
|
|
3059
|
-
try {
|
|
3060
|
-
source = get(projection, requester);
|
|
3061
|
-
next = selector(source);
|
|
3062
|
-
} finally {
|
|
3063
|
-
activeTracker = previousTracker;
|
|
3064
|
-
}
|
|
3065
|
-
const nextSelection = Object.freeze({
|
|
3066
|
-
keys: new Set(tracker.keys),
|
|
3067
|
-
all: tracker.all || isMapLike(source) && tracker.keys.size === 0,
|
|
3068
|
-
structure: tracker.structure
|
|
3069
|
-
});
|
|
3070
|
-
const changed = !initialized || !equality(value, next);
|
|
3071
|
-
if (changed) {
|
|
3072
|
-
if (initialized) selectedRevision++;
|
|
3073
|
-
value = next;
|
|
3074
|
-
}
|
|
3075
|
-
selection = nextSelection;
|
|
3076
|
-
initialized = true;
|
|
3077
|
-
sourceRevision = materialize(projection, requester).revision();
|
|
3078
|
-
updateCollectionSnapshot();
|
|
3079
|
-
return changed;
|
|
3080
|
-
};
|
|
3081
|
-
const ensureCurrent = () => {
|
|
3082
|
-
const revision = materialize(projection, requester).revision();
|
|
3083
|
-
if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
|
|
3084
|
-
return value;
|
|
3085
|
-
};
|
|
3086
|
-
const onProjectionChange = (incoming) => {
|
|
3087
|
-
let change = incoming;
|
|
3088
|
-
if (!change && previousCollection) {
|
|
3089
|
-
const current = get(projection, requester);
|
|
3090
|
-
if (isMapLike(current)) {
|
|
3091
|
-
change = mapChange(previousCollection, current);
|
|
3092
|
-
previousCollection = current;
|
|
3093
|
-
if (!change) {
|
|
3094
|
-
sourceRevision = materialize(projection, requester).revision();
|
|
3095
|
-
return;
|
|
3096
|
-
}
|
|
3097
|
-
} else previousCollection = void 0;
|
|
3098
|
-
}
|
|
3099
|
-
if (!selectionAffects(selection, change)) {
|
|
3100
|
-
sourceRevision = materialize(projection, requester).revision();
|
|
3101
|
-
updateCollectionSnapshot();
|
|
3102
|
-
return;
|
|
3103
|
-
}
|
|
3104
|
-
const previousSelection = selection;
|
|
3105
|
-
const changed = evaluate();
|
|
3106
|
-
if (unsubscribeSource && !sameSelection(previousSelection, selection)) {
|
|
3107
|
-
unsubscribeSource();
|
|
3108
|
-
unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
|
|
3109
|
-
}
|
|
3110
|
-
if (changed) Array.from(listeners).forEach((listener) => listener());
|
|
3111
|
-
};
|
|
3112
|
-
const installSource = () => {
|
|
3113
|
-
if (!unsubscribeSource) unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
|
|
3114
|
-
};
|
|
2173
|
+
const get = (projection, requester) => publicCurrent(resolveOutput(projection, requester));
|
|
2174
|
+
const readableSource = (projection, requester) => {
|
|
2175
|
+
const output = resolveOutput(projection, requester);
|
|
3115
2176
|
return Object.freeze({
|
|
3116
|
-
|
|
2177
|
+
kind: output.kind,
|
|
2178
|
+
current: () => get(projection, requester),
|
|
3117
2179
|
revision: () => {
|
|
3118
|
-
|
|
3119
|
-
return
|
|
2180
|
+
assertOutput(output);
|
|
2181
|
+
return output.revision();
|
|
3120
2182
|
},
|
|
3121
|
-
subscribe:
|
|
3122
|
-
ensureCurrent();
|
|
3123
|
-
listeners.add(listener);
|
|
3124
|
-
installSource();
|
|
3125
|
-
return () => {
|
|
3126
|
-
listeners.delete(listener);
|
|
3127
|
-
if (!listeners.size && unsubscribeSource) {
|
|
3128
|
-
unsubscribeSource();
|
|
3129
|
-
unsubscribeSource = void 0;
|
|
3130
|
-
}
|
|
3131
|
-
};
|
|
3132
|
-
}
|
|
2183
|
+
subscribe: output.subscribe
|
|
3133
2184
|
});
|
|
3134
2185
|
};
|
|
2186
|
+
const makeReadable = (projection, selector, equality, requester) => {
|
|
2187
|
+
const source = readableSource(projection, requester);
|
|
2188
|
+
return selector ? createSelectorReadable(source, selector, equality) : createDirectReadable(source);
|
|
2189
|
+
};
|
|
3135
2190
|
function readable(projection, selector, equality) {
|
|
3136
2191
|
return makeReadable(projection, selector, equality);
|
|
3137
2192
|
}
|
|
2193
|
+
const inputWrite = (target, requester) => {
|
|
2194
|
+
const ref = require_advanced.projectionRef(target);
|
|
2195
|
+
if (ref.output !== 0 || ref.producer.kind !== "source") throw new TypeError("Projection is not an input.");
|
|
2196
|
+
if (ref.producer.source.kind !== "value-input" && ref.producer.source.kind !== "collection-input") throw new TypeError("Projection is not an input.");
|
|
2197
|
+
const write = materializeProducer(ref.producer, requester).write;
|
|
2198
|
+
if (!write) throw new TypeError("Projection input is not writable.");
|
|
2199
|
+
return write;
|
|
2200
|
+
};
|
|
3138
2201
|
const setInput = (target, value, requester) => {
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
record.set(value);
|
|
2202
|
+
const write = inputWrite(target, requester);
|
|
2203
|
+
if (write.kind !== "value") throw new TypeError("Projection is not a value input.");
|
|
2204
|
+
write.set(value);
|
|
3143
2205
|
};
|
|
3144
2206
|
const updateInput = (target, run, requester) => {
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
2207
|
+
const write = inputWrite(target, requester);
|
|
2208
|
+
if (write.kind !== "collection") throw new TypeError("Projection is not a collection input.");
|
|
2209
|
+
write.update(run);
|
|
2210
|
+
};
|
|
2211
|
+
function batch(optionsOrCallback, maybeCallback) {
|
|
2212
|
+
return typeof optionsOrCallback === "function" ? scheduler.batch(optionsOrCallback) : scheduler.batch(optionsOrCallback, maybeCallback);
|
|
2213
|
+
}
|
|
2214
|
+
const disposeScope = (state) => {
|
|
2215
|
+
if (!state.active) return;
|
|
2216
|
+
scheduler.assertIdle();
|
|
2217
|
+
state.active = false;
|
|
2218
|
+
state.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
2219
|
+
state.subscriptions.clear();
|
|
2220
|
+
for (let index = state.materialized.length - 1; index >= 0; index--) {
|
|
2221
|
+
const definition = state.materialized[index];
|
|
2222
|
+
const current = materialized.get(definition);
|
|
2223
|
+
if (!current) continue;
|
|
2224
|
+
scheduler.releaseProducer(current.record);
|
|
2225
|
+
materialized.delete(definition);
|
|
2226
|
+
}
|
|
2227
|
+
state.materialized.length = 0;
|
|
2228
|
+
scopes.delete(state);
|
|
3149
2229
|
};
|
|
3150
2230
|
const createScope = () => {
|
|
3151
|
-
|
|
2231
|
+
scheduler.assertIdle();
|
|
3152
2232
|
const state = {
|
|
3153
2233
|
active: true,
|
|
3154
2234
|
materialized: [],
|
|
@@ -3156,25 +2236,27 @@ const createProjectionRuntime = (options) => {
|
|
|
3156
2236
|
};
|
|
3157
2237
|
scopes.add(state);
|
|
3158
2238
|
const assertActive = () => {
|
|
3159
|
-
if (!state.active) throw new ProjectionDisposedError();
|
|
2239
|
+
if (!state.active) throw new require_advanced.ProjectionDisposedError();
|
|
2240
|
+
scheduler.assertActive();
|
|
3160
2241
|
};
|
|
3161
2242
|
const own = (projection) => {
|
|
3162
2243
|
assertActive();
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
return projection;
|
|
2244
|
+
return require_advanced.ownProjection(projection, state);
|
|
2245
|
+
};
|
|
2246
|
+
const ownOutputTree = (tree) => {
|
|
2247
|
+
if (require_advanced.isProjection(tree)) return own(tree);
|
|
2248
|
+
if (!tree || typeof tree !== "object") throw new TypeError("Incremental group output tree must be an object.");
|
|
2249
|
+
for (const value of Object.values(tree)) ownOutputTree(value);
|
|
2250
|
+
return tree;
|
|
3171
2251
|
};
|
|
3172
2252
|
const scopedInput = Object.assign((initial, equality) => own(require_advanced.input(initial, equality)), { collection: (initial) => own(require_advanced.input.collection(initial)) });
|
|
3173
2253
|
const scopedDerive = (dependencies, compute, equality) => own(require_advanced.derive(dependencies, compute, equality));
|
|
3174
|
-
const scopedIncremental = Object.assign((dependencies, processor) => own(require_advanced.incremental(dependencies, processor)), {
|
|
2254
|
+
const scopedIncremental = Object.assign((dependencies, processor) => own(require_advanced.incremental(dependencies, processor)), {
|
|
2255
|
+
collection: (dependencies, processor) => own(require_advanced.incremental.collection(dependencies, processor)),
|
|
2256
|
+
group: (dependencies, defineOutputs, processor) => ownOutputTree(require_advanced.incremental.group(dependencies, defineOutputs, processor))
|
|
2257
|
+
});
|
|
3175
2258
|
function scopeReadable(projection, selector, equality) {
|
|
3176
2259
|
assertActive();
|
|
3177
|
-
materialize(projection, state);
|
|
3178
2260
|
const source = makeReadable(projection, selector, equality, state);
|
|
3179
2261
|
return Object.freeze({
|
|
3180
2262
|
current: () => {
|
|
@@ -3202,7 +2284,7 @@ const createProjectionRuntime = (options) => {
|
|
|
3202
2284
|
}
|
|
3203
2285
|
function scopeBatch(optionsOrCallback, maybeCallback) {
|
|
3204
2286
|
assertActive();
|
|
3205
|
-
return typeof optionsOrCallback === "function" ?
|
|
2287
|
+
return typeof optionsOrCallback === "function" ? batch(optionsOrCallback) : batch(optionsOrCallback, maybeCallback);
|
|
3206
2288
|
}
|
|
3207
2289
|
return Object.freeze({
|
|
3208
2290
|
input: scopedInput,
|
|
@@ -3222,34 +2304,19 @@ const createProjectionRuntime = (options) => {
|
|
|
3222
2304
|
updateInput(target, run, state);
|
|
3223
2305
|
},
|
|
3224
2306
|
batch: scopeBatch,
|
|
3225
|
-
dispose: () =>
|
|
3226
|
-
if (!state.active) return;
|
|
3227
|
-
runtime.assertIdle();
|
|
3228
|
-
state.active = false;
|
|
3229
|
-
state.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
3230
|
-
for (const definition of state.materialized.reverse()) {
|
|
3231
|
-
const record = materialized.get(definition);
|
|
3232
|
-
if (!record) continue;
|
|
3233
|
-
runtime.releaseNode(record.node);
|
|
3234
|
-
if (record.kind !== "projection") runtime.releaseInput(record.source);
|
|
3235
|
-
materialized.delete(definition);
|
|
3236
|
-
}
|
|
3237
|
-
state.materialized.length = 0;
|
|
3238
|
-
scopes.delete(state);
|
|
3239
|
-
}
|
|
2307
|
+
dispose: () => disposeScope(state)
|
|
3240
2308
|
});
|
|
3241
2309
|
};
|
|
3242
|
-
|
|
2310
|
+
return Object.freeze({
|
|
3243
2311
|
get,
|
|
3244
2312
|
readable,
|
|
3245
2313
|
set: setInput,
|
|
3246
2314
|
update: updateInput,
|
|
3247
|
-
batch
|
|
2315
|
+
batch,
|
|
3248
2316
|
scope: createScope,
|
|
3249
2317
|
dispose: () => {
|
|
3250
|
-
if (
|
|
3251
|
-
|
|
3252
|
-
disposed = true;
|
|
2318
|
+
if (!scheduler.active) return;
|
|
2319
|
+
scheduler.assertIdle();
|
|
3253
2320
|
for (const scope of scopes) {
|
|
3254
2321
|
scope.active = false;
|
|
3255
2322
|
scope.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
@@ -3257,35 +2324,35 @@ const createProjectionRuntime = (options) => {
|
|
|
3257
2324
|
scope.materialized.length = 0;
|
|
3258
2325
|
}
|
|
3259
2326
|
scopes.clear();
|
|
3260
|
-
|
|
2327
|
+
sources.dispose();
|
|
2328
|
+
scheduler.dispose();
|
|
3261
2329
|
}
|
|
3262
|
-
};
|
|
3263
|
-
return Object.freeze(store);
|
|
2330
|
+
});
|
|
3264
2331
|
};
|
|
3265
2332
|
//#endregion
|
|
3266
|
-
exports.DocumentDisposedError =
|
|
3267
|
-
exports.DocumentReentrancyError =
|
|
3268
|
-
exports.ParseError =
|
|
3269
|
-
exports.ProjectionDisposedError = ProjectionDisposedError;
|
|
3270
|
-
exports.ProjectionError = ProjectionError;
|
|
3271
|
-
exports.TransactionRejected =
|
|
2333
|
+
exports.DocumentDisposedError = require_access.DocumentDisposedError;
|
|
2334
|
+
exports.DocumentReentrancyError = require_access.DocumentReentrancyError;
|
|
2335
|
+
exports.ParseError = require_access.ParseError;
|
|
2336
|
+
exports.ProjectionDisposedError = require_advanced.ProjectionDisposedError;
|
|
2337
|
+
exports.ProjectionError = require_advanced.ProjectionError;
|
|
2338
|
+
exports.TransactionRejected = require_access.TransactionRejected;
|
|
3272
2339
|
exports.asReadable = asReadable;
|
|
3273
2340
|
exports.createDocument = createDocument;
|
|
3274
2341
|
exports.createProjectionRuntime = createProjectionRuntime;
|
|
3275
2342
|
exports.derive = require_advanced.derive;
|
|
3276
|
-
exports.field = field;
|
|
2343
|
+
exports.field = require_advanced.field;
|
|
3277
2344
|
exports.input = require_advanced.input;
|
|
3278
|
-
exports.list = list;
|
|
3279
|
-
exports.map = map;
|
|
3280
|
-
exports.object = object;
|
|
2345
|
+
exports.list = require_advanced.list;
|
|
2346
|
+
exports.map = require_advanced.map;
|
|
2347
|
+
exports.object = require_advanced.object;
|
|
3281
2348
|
exports.observe = require_advanced.observe;
|
|
3282
|
-
exports.optional = optional;
|
|
3283
|
-
exports.parse =
|
|
2349
|
+
exports.optional = require_advanced.optional;
|
|
2350
|
+
exports.parse = require_access.parse;
|
|
3284
2351
|
exports.read = read;
|
|
3285
|
-
exports.replace =
|
|
3286
|
-
exports.snapshot =
|
|
3287
|
-
exports.table = table;
|
|
3288
|
-
exports.tree = tree;
|
|
3289
|
-
exports.variant = variant;
|
|
2352
|
+
exports.replace = require_access.replace;
|
|
2353
|
+
exports.snapshot = require_access.snapshot;
|
|
2354
|
+
exports.table = require_advanced.table;
|
|
2355
|
+
exports.tree = require_advanced.tree;
|
|
2356
|
+
exports.variant = require_advanced.variant;
|
|
3290
2357
|
|
|
3291
2358
|
//# sourceMappingURL=index.cjs.map
|