@fabricorg/platform 1.9.0 → 1.10.1
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/CHANGELOG.md +56 -0
- package/README.md +48 -2
- package/dist/actions/index.cjs +45 -26
- package/dist/actions/index.cjs.map +1 -1
- package/dist/actions/index.d.cts +225 -226
- package/dist/actions/index.d.ts +225 -226
- package/dist/actions/index.js +41 -2
- package/dist/actions/index.js.map +1 -1
- package/dist/adapters/index.cjs +696 -66
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +367 -365
- package/dist/adapters/index.d.ts +367 -365
- package/dist/adapters/index.js +682 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/agent-hitl/index.cjs +21 -10
- package/dist/agent-hitl/index.cjs.map +1 -1
- package/dist/agent-hitl/index.d.cts +30 -30
- package/dist/agent-hitl/index.d.ts +30 -30
- package/dist/agent-hitl/index.js +21 -2
- package/dist/agent-hitl/index.js.map +1 -1
- package/dist/certification/index.cjs +109 -24
- package/dist/certification/index.cjs.map +1 -1
- package/dist/certification/index.d.cts +33 -39
- package/dist/certification/index.d.ts +33 -39
- package/dist/certification/index.js +107 -8
- package/dist/certification/index.js.map +1 -1
- package/dist/discovery/index.cjs +12 -10
- package/dist/discovery/index.cjs.map +1 -1
- package/dist/discovery/index.d.cts +2 -10
- package/dist/discovery/index.d.ts +2 -10
- package/dist/discovery/index.js +12 -2
- package/dist/discovery/index.js.map +1 -1
- package/dist/engine-B4csEOEc.js +223 -0
- package/dist/engine-B4csEOEc.js.map +1 -0
- package/dist/engine-BqScuhrT.cjs +246 -0
- package/dist/engine-BqScuhrT.cjs.map +1 -0
- package/dist/events/index.cjs +39 -10
- package/dist/events/index.cjs.map +1 -1
- package/dist/events/index.d.cts +43 -43
- package/dist/events/index.d.ts +43 -43
- package/dist/events/index.js +39 -2
- package/dist/events/index.js.map +1 -1
- package/dist/evidence/index.cjs +12 -50
- package/dist/evidence/index.d.cts +2 -229
- package/dist/evidence/index.d.ts +2 -229
- package/dist/evidence/index.js +1 -5
- package/dist/evidence-BuCICb0c.js +516 -0
- package/dist/evidence-BuCICb0c.js.map +1 -0
- package/dist/evidence-Cq48eDvA.cjs +575 -0
- package/dist/evidence-Cq48eDvA.cjs.map +1 -0
- package/dist/governance/index.cjs +24 -18
- package/dist/governance/index.cjs.map +1 -1
- package/dist/governance/index.d.cts +80 -83
- package/dist/governance/index.d.ts +80 -83
- package/dist/governance/index.js +22 -2
- package/dist/governance/index.js.map +1 -1
- package/dist/ids/index.cjs +52 -22
- package/dist/ids/index.cjs.map +1 -1
- package/dist/ids/index.d.cts +17 -17
- package/dist/ids/index.d.ts +17 -17
- package/dist/ids/index.js +49 -2
- package/dist/ids/index.js.map +1 -1
- package/dist/index-9jasmw76.d.cts +124 -0
- package/dist/index-BXpeFdk2.d.cts +190 -0
- package/dist/index-BXpeFdk2.d.ts +190 -0
- package/dist/index-BnbsKZsT.d.cts +231 -0
- package/dist/index-CIl1rYRv.d.cts +11 -0
- package/dist/index-CIl1rYRv.d.ts +11 -0
- package/dist/index-CVHA1ypT.d.cts +262 -0
- package/dist/index-DZ4mnNb7.d.cts +78 -0
- package/dist/index-DZ4mnNb7.d.ts +78 -0
- package/dist/index-IqcDvl69.d.ts +124 -0
- package/dist/index-fmUiRBXu.d.cts +57 -0
- package/dist/index-fmUiRBXu.d.ts +57 -0
- package/dist/index-sPUPs8pr.d.ts +262 -0
- package/dist/index-z9-CYOIo.d.ts +231 -0
- package/dist/index.cjs +111 -385
- package/dist/index.d.cts +18 -17
- package/dist/index.d.ts +18 -17
- package/dist/index.js +19 -20
- package/dist/manifest/index.cjs +224 -35
- package/dist/manifest/index.cjs.map +1 -1
- package/dist/manifest/index.d.cts +130 -131
- package/dist/manifest/index.d.ts +130 -131
- package/dist/manifest/index.js +219 -7
- package/dist/manifest/index.js.map +1 -1
- package/dist/modules/index.cjs +281 -42
- package/dist/modules/index.cjs.map +1 -1
- package/dist/modules/index.d.cts +157 -159
- package/dist/modules/index.d.ts +157 -159
- package/dist/modules/index.js +274 -6
- package/dist/modules/index.js.map +1 -1
- package/dist/objects/index.cjs +355 -22
- package/dist/objects/index.cjs.map +1 -1
- package/dist/objects/index.d.cts +2 -77
- package/dist/objects/index.d.ts +2 -77
- package/dist/objects/index.js +352 -2
- package/dist/objects/index.js.map +1 -1
- package/dist/policies/index.cjs +14 -48
- package/dist/policies/index.d.cts +2 -247
- package/dist/policies/index.d.ts +2 -247
- package/dist/policies/index.js +1 -3
- package/dist/policies-DH_GCXQ-.js +627 -0
- package/dist/policies-DH_GCXQ-.js.map +1 -0
- package/dist/policies-Nxbp1CH8.cjs +698 -0
- package/dist/policies-Nxbp1CH8.cjs.map +1 -0
- package/dist/privacy/index.cjs +26 -43
- package/dist/privacy/index.cjs.map +1 -1
- package/dist/privacy/index.d.cts +2 -189
- package/dist/privacy/index.d.ts +2 -189
- package/dist/privacy/index.js +18 -3
- package/dist/privacy/index.js.map +1 -1
- package/dist/projections/index.cjs +241 -23
- package/dist/projections/index.cjs.map +1 -1
- package/dist/projections/index.d.cts +2 -125
- package/dist/projections/index.d.ts +2 -125
- package/dist/projections/index.js +238 -3
- package/dist/projections/index.js.map +1 -1
- package/dist/state-machines/index.cjs +76 -18
- package/dist/state-machines/index.cjs.map +1 -1
- package/dist/state-machines/index.d.cts +2 -55
- package/dist/state-machines/index.d.ts +2 -55
- package/dist/state-machines/index.js +74 -2
- package/dist/state-machines/index.js.map +1 -1
- package/dist/testing/index.cjs +27 -19
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +17 -19
- package/dist/testing/index.d.ts +17 -19
- package/dist/testing/index.js +25 -3
- package/dist/testing/index.js.map +1 -1
- package/package.json +6 -5
- package/dist/chunk-2AE6TMAX.cjs +0 -44
- package/dist/chunk-2AE6TMAX.cjs.map +0 -1
- package/dist/chunk-6HY35TKB.cjs +0 -491
- package/dist/chunk-6HY35TKB.cjs.map +0 -1
- package/dist/chunk-6UGEIDLC.cjs +0 -26
- package/dist/chunk-6UGEIDLC.cjs.map +0 -1
- package/dist/chunk-7GAWSUOG.js +0 -64
- package/dist/chunk-7GAWSUOG.js.map +0 -1
- package/dist/chunk-AFXFAEWA.js +0 -673
- package/dist/chunk-AFXFAEWA.js.map +0 -1
- package/dist/chunk-AFXKSIVI.cjs +0 -540
- package/dist/chunk-AFXKSIVI.cjs.map +0 -1
- package/dist/chunk-B5DOGDPG.js +0 -42
- package/dist/chunk-B5DOGDPG.js.map +0 -1
- package/dist/chunk-BJGZE3GE.js +0 -20
- package/dist/chunk-BJGZE3GE.js.map +0 -1
- package/dist/chunk-CRZXTZ36.cjs +0 -258
- package/dist/chunk-CRZXTZ36.cjs.map +0 -1
- package/dist/chunk-D4SWRBUH.cjs +0 -22
- package/dist/chunk-D4SWRBUH.cjs.map +0 -1
- package/dist/chunk-DBTAUI6C.cjs +0 -395
- package/dist/chunk-DBTAUI6C.cjs.map +0 -1
- package/dist/chunk-EMSAFNPX.js +0 -98
- package/dist/chunk-EMSAFNPX.js.map +0 -1
- package/dist/chunk-F3E5SXPN.cjs +0 -28
- package/dist/chunk-F3E5SXPN.cjs.map +0 -1
- package/dist/chunk-HEYMMVDP.js +0 -480
- package/dist/chunk-HEYMMVDP.js.map +0 -1
- package/dist/chunk-IFW43ELT.cjs +0 -331
- package/dist/chunk-IFW43ELT.cjs.map +0 -1
- package/dist/chunk-LFE74IJX.js +0 -20
- package/dist/chunk-LFE74IJX.js.map +0 -1
- package/dist/chunk-LZSE6QGQ.cjs +0 -60
- package/dist/chunk-LZSE6QGQ.cjs.map +0 -1
- package/dist/chunk-MCWSUGB3.cjs +0 -102
- package/dist/chunk-MCWSUGB3.cjs.map +0 -1
- package/dist/chunk-MLBALEUR.js +0 -290
- package/dist/chunk-MLBALEUR.js.map +0 -1
- package/dist/chunk-NNXQZPB3.cjs +0 -31
- package/dist/chunk-NNXQZPB3.cjs.map +0 -1
- package/dist/chunk-OFCCZ27Z.js +0 -26
- package/dist/chunk-OFCCZ27Z.js.map +0 -1
- package/dist/chunk-PDN5DOLQ.js +0 -242
- package/dist/chunk-PDN5DOLQ.js.map +0 -1
- package/dist/chunk-PIDZQAEL.js +0 -50
- package/dist/chunk-PIDZQAEL.js.map +0 -1
- package/dist/chunk-Q4Q4JPR3.js +0 -524
- package/dist/chunk-Q4Q4JPR3.js.map +0 -1
- package/dist/chunk-RALMUSOX.js +0 -55
- package/dist/chunk-RALMUSOX.js.map +0 -1
- package/dist/chunk-SNTV45LH.cjs +0 -295
- package/dist/chunk-SNTV45LH.cjs.map +0 -1
- package/dist/chunk-TBS2S5AX.cjs +0 -40
- package/dist/chunk-TBS2S5AX.cjs.map +0 -1
- package/dist/chunk-TYNIFC7J.js +0 -253
- package/dist/chunk-TYNIFC7J.js.map +0 -1
- package/dist/chunk-UGWGHTNC.cjs +0 -56
- package/dist/chunk-UGWGHTNC.cjs.map +0 -1
- package/dist/chunk-WI7CWNYI.cjs +0 -68
- package/dist/chunk-WI7CWNYI.cjs.map +0 -1
- package/dist/chunk-WNZP53GI.js +0 -386
- package/dist/chunk-WNZP53GI.js.map +0 -1
- package/dist/chunk-WQ3BYI7T.cjs +0 -249
- package/dist/chunk-WQ3BYI7T.cjs.map +0 -1
- package/dist/chunk-Y2PJLRIY.js +0 -36
- package/dist/chunk-Y2PJLRIY.js.map +0 -1
- package/dist/chunk-YGIPN3TU.cjs +0 -684
- package/dist/chunk-YGIPN3TU.cjs.map +0 -1
- package/dist/chunk-ZAEVQSB5.js +0 -326
- package/dist/chunk-ZAEVQSB5.js.map +0 -1
- package/dist/chunk-ZMQTV5OJ.js +0 -27
- package/dist/chunk-ZMQTV5OJ.js.map +0 -1
- package/dist/evidence/index.cjs.map +0 -1
- package/dist/evidence/index.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/policies/index.cjs.map +0 -1
- package/dist/policies/index.js.map +0 -1
|
@@ -1,4 +1,239 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { assertSurfaceDeclaration } from "../discovery/index.js";
|
|
2
|
+
//#region projections/index.ts
|
|
3
|
+
function viewContractKey(view) {
|
|
4
|
+
return `${view.name}@${view.version}`;
|
|
5
|
+
}
|
|
6
|
+
function createViewRegistry(views = []) {
|
|
7
|
+
const registered = /* @__PURE__ */ new Map();
|
|
8
|
+
const register = (view) => {
|
|
9
|
+
assertSurfaceDeclaration(view, `View "${view?.name ?? "unknown"}"`);
|
|
10
|
+
const key = viewContractKey(view);
|
|
11
|
+
if (registered.has(key)) throw new Error(`View contract "${key}" is already registered.`);
|
|
12
|
+
registered.set(key, view);
|
|
13
|
+
};
|
|
14
|
+
for (const view of views) register(view);
|
|
15
|
+
return {
|
|
16
|
+
register,
|
|
17
|
+
registerIfMissing(view) {
|
|
18
|
+
if (!registered.has(viewContractKey(view))) register(view);
|
|
19
|
+
},
|
|
20
|
+
resolve: (name, version) => registered.get(`${name}@${version}`),
|
|
21
|
+
keys: () => Array.from(registered.keys()).sort()
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function replayView({ view, events, scope, snapshot, eventsStartAfterSnapshot = false }) {
|
|
25
|
+
if (snapshot && snapshot.snapshotVersion !== view.snapshotVersion && !view.compatibleSnapshotVersions?.includes(snapshot.snapshotVersion)) throw new Error(`View "${viewContractKey(view)}" cannot resume snapshot version ${snapshot.snapshotVersion}.`);
|
|
26
|
+
if (snapshot && snapshot.snapshotVersion !== view.snapshotVersion && !view.upgradeSnapshot) throw new Error(`View "${viewContractKey(view)}" must provide upgradeSnapshot for snapshot version ${snapshot.snapshotVersion}.`);
|
|
27
|
+
const upgradedSnapshot = snapshot && snapshot.snapshotVersion !== view.snapshotVersion ? {
|
|
28
|
+
...snapshot,
|
|
29
|
+
snapshotVersion: view.snapshotVersion,
|
|
30
|
+
snapshotData: await view.upgradeSnapshot(snapshot.snapshotData, snapshot.snapshotVersion)
|
|
31
|
+
} : snapshot;
|
|
32
|
+
const accepted = new Map(view.consumes.map((consumption) => [consumption.eventType, new Set(consumption.schemaVersions)]));
|
|
33
|
+
const sourceEvents = [];
|
|
34
|
+
for await (const event of events) sourceEvents.push(event);
|
|
35
|
+
const preprocessingWarnings = [];
|
|
36
|
+
const orderedScopedEvents = sortEvents(filterEventsByScope(sourceEvents, scope, preprocessingWarnings), Boolean(scope.subjectType && scope.subjectId));
|
|
37
|
+
const versionWarnings = [];
|
|
38
|
+
const replayable = async function* () {
|
|
39
|
+
for (const event of orderedScopedEvents) {
|
|
40
|
+
const versions = accepted.get(event.eventType);
|
|
41
|
+
if (versions && !versions.has(event.eventSchemaVersion ?? 1)) {
|
|
42
|
+
versionWarnings.push({
|
|
43
|
+
code: "unsupported_event_version",
|
|
44
|
+
eventId: event.id,
|
|
45
|
+
message: `View "${viewContractKey(view)}" does not consume ${event.eventType} schema version ${event.eventSchemaVersion ?? 1}; replay stopped before this event so a compatible view can resume it later.`
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
yield event;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const result = await replayEvents({
|
|
53
|
+
events: replayable(),
|
|
54
|
+
scope,
|
|
55
|
+
initialState: view.initialState,
|
|
56
|
+
snapshot: upgradedSnapshot,
|
|
57
|
+
applyEvent: (state, event) => accepted.has(event.eventType) ? view.applyEvent(state, event) : state,
|
|
58
|
+
eventsStartAfterSnapshot
|
|
59
|
+
});
|
|
60
|
+
const preprocessingWarningKeys = new Set(preprocessingWarnings.map((warning) => JSON.stringify(warning)));
|
|
61
|
+
const resultWarnings = result.warnings.filter((warning) => !preprocessingWarningKeys.has(JSON.stringify(warning)));
|
|
62
|
+
return {
|
|
63
|
+
...result,
|
|
64
|
+
eventCursor: result.eventCursor ?? upgradedSnapshot?.eventCursor ?? null,
|
|
65
|
+
eventSequence: result.appliedEvents.length ? result.eventSequence : upgradedSnapshot?.eventSequence ?? result.eventSequence,
|
|
66
|
+
subjectSequences: result.appliedEvents.length ? result.subjectSequences : upgradedSnapshot?.subjectSequences ?? result.subjectSequences,
|
|
67
|
+
warnings: [
|
|
68
|
+
...preprocessingWarnings,
|
|
69
|
+
...versionWarnings,
|
|
70
|
+
...resultWarnings
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function replayEvents({ events, scope, initialState, snapshot, applyEvent, eventsStartAfterSnapshot = false }) {
|
|
75
|
+
if (eventsStartAfterSnapshot && !snapshot?.eventCursor) throw new Error("eventsStartAfterSnapshot requires a snapshot with an eventCursor checkpoint.");
|
|
76
|
+
const warnings = [];
|
|
77
|
+
const uniqueEvents = removeDuplicateEvents(sortEvents(filterEventsByScope(await collectEvents(events), scope, warnings), isSubjectScoped(scope)), warnings);
|
|
78
|
+
const initialSubjectSequences = eventsStartAfterSnapshot ? snapshotSubjectSequences(snapshot, scope) : void 0;
|
|
79
|
+
const subjectSequences = recordMissingSequenceWarnings(uniqueEvents, warnings, initialSubjectSequences);
|
|
80
|
+
const resumableEvents = applyCursor(uniqueEvents, snapshot, isSubjectScoped(scope), warnings, initialSubjectSequences, eventsStartAfterSnapshot);
|
|
81
|
+
let state = snapshot?.snapshotData ?? initialState;
|
|
82
|
+
const appliedEvents = [];
|
|
83
|
+
let eventCursor = snapshot?.eventCursor ?? null;
|
|
84
|
+
let eventSequence = snapshot?.eventSequence ?? 0;
|
|
85
|
+
for (const event of resumableEvents) {
|
|
86
|
+
state = await applyEvent(state, event);
|
|
87
|
+
appliedEvents.push(event);
|
|
88
|
+
eventCursor = event.id;
|
|
89
|
+
eventSequence = event.sequence;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
state,
|
|
93
|
+
appliedEvents,
|
|
94
|
+
warnings,
|
|
95
|
+
eventCursor,
|
|
96
|
+
eventSequence,
|
|
97
|
+
subjectSequences: Object.fromEntries([...subjectSequences.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0))
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function collectEvents(events) {
|
|
101
|
+
const collected = [];
|
|
102
|
+
if (Symbol.asyncIterator in events) {
|
|
103
|
+
for await (const event of events) collected.push(event);
|
|
104
|
+
return collected;
|
|
105
|
+
}
|
|
106
|
+
for (const event of events) collected.push(event);
|
|
107
|
+
return collected;
|
|
108
|
+
}
|
|
109
|
+
function filterEventsByScope(events, scope, warnings) {
|
|
110
|
+
if (scope.subjectType && !scope.subjectId || !scope.subjectType && scope.subjectId) warnings.push({
|
|
111
|
+
code: "subject_scope_incomplete",
|
|
112
|
+
message: "Both subjectType and subjectId are required for subject-scoped replay.",
|
|
113
|
+
subjectType: scope.subjectType,
|
|
114
|
+
subjectId: scope.subjectId
|
|
115
|
+
});
|
|
116
|
+
return events.filter((event) => {
|
|
117
|
+
if (event.tenantId !== scope.tenantId || event.spaceId !== scope.spaceId) return false;
|
|
118
|
+
if (scope.subjectType && event.subjectType !== scope.subjectType) return false;
|
|
119
|
+
if (scope.subjectId && event.subjectId !== scope.subjectId) return false;
|
|
120
|
+
return true;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function removeDuplicateEvents(events, warnings) {
|
|
124
|
+
const seenEventIds = /* @__PURE__ */ new Set();
|
|
125
|
+
const uniqueEvents = [];
|
|
126
|
+
for (const event of events) {
|
|
127
|
+
if (seenEventIds.has(event.id)) {
|
|
128
|
+
warnings.push({
|
|
129
|
+
code: "duplicate_event",
|
|
130
|
+
message: `Duplicate event ${event.id} ignored during replay.`,
|
|
131
|
+
eventId: event.id,
|
|
132
|
+
subjectType: event.subjectType,
|
|
133
|
+
subjectId: event.subjectId
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
seenEventIds.add(event.id);
|
|
138
|
+
uniqueEvents.push(event);
|
|
139
|
+
}
|
|
140
|
+
return uniqueEvents;
|
|
141
|
+
}
|
|
142
|
+
function recordMissingSequenceWarnings(events, warnings, initialSequences) {
|
|
143
|
+
const lastSequenceBySubject = new Map(initialSequences);
|
|
144
|
+
for (const event of events) {
|
|
145
|
+
const subjectKey = subjectSequenceKey(event.subjectType, event.subjectId);
|
|
146
|
+
const previousSequence = lastSequenceBySubject.get(subjectKey);
|
|
147
|
+
const sequence = event.subjectSequence ?? event.sequence;
|
|
148
|
+
const expectedSequence = previousSequence === void 0 ? sequence : previousSequence + 1;
|
|
149
|
+
if (event.subjectSequence !== void 0 && previousSequence !== void 0 && sequence > expectedSequence) warnings.push({
|
|
150
|
+
code: "missing_sequence",
|
|
151
|
+
message: `Missing event sequence for ${subjectKey}: expected ${expectedSequence}, received ${sequence}.`,
|
|
152
|
+
eventId: event.id,
|
|
153
|
+
subjectType: event.subjectType,
|
|
154
|
+
subjectId: event.subjectId,
|
|
155
|
+
expectedSequence,
|
|
156
|
+
actualSequence: sequence
|
|
157
|
+
});
|
|
158
|
+
lastSequenceBySubject.set(subjectKey, Math.max(previousSequence ?? sequence, sequence));
|
|
159
|
+
}
|
|
160
|
+
return lastSequenceBySubject;
|
|
161
|
+
}
|
|
162
|
+
function applyCursor(events, snapshot, subjectScoped, warnings, initialSubjectSequences, eventsStartAfterSnapshot = false) {
|
|
163
|
+
const eventCursor = snapshot?.eventCursor;
|
|
164
|
+
if (eventCursor && !eventsStartAfterSnapshot) {
|
|
165
|
+
const cursorIndex = events.findIndex((event) => event.id === eventCursor);
|
|
166
|
+
if (cursorIndex >= 0) return events.slice(cursorIndex + 1);
|
|
167
|
+
warnings.push({
|
|
168
|
+
code: "cursor_not_found",
|
|
169
|
+
message: `Snapshot cursor ${eventCursor} was not found in the scoped event stream.`,
|
|
170
|
+
eventId: eventCursor
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (initialSubjectSequences?.size) return events.filter((event) => (event.subjectSequence ?? event.sequence) > (initialSubjectSequences.get(subjectSequenceKey(event.subjectType, event.subjectId)) ?? -1));
|
|
174
|
+
if (subjectScoped && snapshot?.eventSequence !== void 0 && snapshot.eventSequence !== null) return events.filter((event) => event.sequence > snapshot.eventSequence);
|
|
175
|
+
return events;
|
|
176
|
+
}
|
|
177
|
+
function snapshotSubjectSequences(snapshot, scope) {
|
|
178
|
+
const sequences = new Map(Object.entries(snapshot?.subjectSequences ?? {}));
|
|
179
|
+
if (sequences.size === 0 && scope.subjectType && scope.subjectId && snapshot?.eventSequence !== void 0 && snapshot.eventSequence !== null) sequences.set(subjectSequenceKey(scope.subjectType, scope.subjectId), snapshot.eventSequence);
|
|
180
|
+
return sequences;
|
|
181
|
+
}
|
|
182
|
+
function subjectSequenceKey(subjectType, subjectId) {
|
|
183
|
+
return `${subjectType}:${subjectId}`;
|
|
184
|
+
}
|
|
185
|
+
function sortEvents(events, subjectScoped) {
|
|
186
|
+
return [...events].sort((first, second) => subjectScoped ? compareSubjectEvents(first, second) : compareGlobalEvents(first, second));
|
|
187
|
+
}
|
|
188
|
+
function compareSubjectEvents(first, second) {
|
|
189
|
+
return compareValues([
|
|
190
|
+
first.subjectSequence ?? first.sequence,
|
|
191
|
+
toTimestamp(first.recordedAt),
|
|
192
|
+
toTimestamp(first.occurredAt),
|
|
193
|
+
first.id
|
|
194
|
+
], [
|
|
195
|
+
second.subjectSequence ?? second.sequence,
|
|
196
|
+
toTimestamp(second.recordedAt),
|
|
197
|
+
toTimestamp(second.occurredAt),
|
|
198
|
+
second.id
|
|
199
|
+
]);
|
|
200
|
+
}
|
|
201
|
+
function compareGlobalEvents(first, second) {
|
|
202
|
+
return compareValues([
|
|
203
|
+
toTimestamp(first.recordedAt),
|
|
204
|
+
toTimestamp(first.occurredAt),
|
|
205
|
+
first.actionInvocationId,
|
|
206
|
+
first.correlationId,
|
|
207
|
+
first.subjectType,
|
|
208
|
+
first.subjectId,
|
|
209
|
+
first.sequence,
|
|
210
|
+
first.id
|
|
211
|
+
], [
|
|
212
|
+
toTimestamp(second.recordedAt),
|
|
213
|
+
toTimestamp(second.occurredAt),
|
|
214
|
+
second.actionInvocationId,
|
|
215
|
+
second.correlationId,
|
|
216
|
+
second.subjectType,
|
|
217
|
+
second.subjectId,
|
|
218
|
+
second.sequence,
|
|
219
|
+
second.id
|
|
220
|
+
]);
|
|
221
|
+
}
|
|
222
|
+
function compareValues(firstValues, secondValues) {
|
|
223
|
+
for (const [index, firstValue] of firstValues.entries()) {
|
|
224
|
+
const secondValue = secondValues[index];
|
|
225
|
+
if (firstValue < secondValue) return -1;
|
|
226
|
+
if (firstValue > secondValue) return 1;
|
|
227
|
+
}
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
function isSubjectScoped(scope) {
|
|
231
|
+
return Boolean(scope.subjectType && scope.subjectId);
|
|
232
|
+
}
|
|
233
|
+
function toTimestamp(value) {
|
|
234
|
+
return value instanceof Date ? value.getTime() : new Date(value).getTime();
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
export { createViewRegistry, replayEvents, replayView, viewContractKey };
|
|
238
|
+
|
|
4
239
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../projections/index.ts"],"sourcesContent":["import { assertSurfaceDeclaration, type SurfaceDeclaration } from \"../discovery\";\nexport interface ReplayableAssetEvent<Payload = unknown> {\n\tid: string;\n\ttenantId: string;\n\tspaceId: string;\n\teventType: string;\n\teventSchemaVersion?: number;\n\tsubjectType: string;\n\tsubjectId: string;\n\tpayload: Payload;\n\t/**\n\t * Monotonic ordering cursor for the tenant/space event ledger. It is not\n\t * contiguous after a view filters event types or narrows to one subject.\n\t */\n\tsequence: number;\n\t/** Optional contiguous sequence local to this subject. */\n\tsubjectSequence?: number;\n\toccurredAt: Date | string;\n\trecordedAt: Date | string;\n\tactionInvocationId: string;\n\tcorrelationId: string;\n}\n\nimport type { PortableJsonSchema } from \"../objects\";\n\nexport interface ViewEventConsumption {\n\teventType: string;\n\tschemaVersions: number[];\n}\n\nexport interface ViewContract<State = unknown, Event extends ReplayableAssetEvent = ReplayableAssetEvent> extends SurfaceDeclaration {\n\t/** Stable module-qualified logical identity, for example `orders/summary`. */\n\tname: `${string}/${string}`;\n\tversion: string;\n\tshape: PortableJsonSchema;\n\t/** Optional JSON Schema for query parameters. Parameterized variants get isolated snapshots. */\n\tparameterSchema?: PortableJsonSchema;\n\tconsumes: ViewEventConsumption[];\n\tscope: \"tenant-space\";\n\tsnapshotVersion: number;\n\tcompatibleSnapshotVersions?: number[];\n\tusage?: ViewUsageMetadata;\n\t/** Required to transform any older compatible snapshot into the current state shape. */\n\tupgradeSnapshot?: (data: unknown, fromVersion: number) => State | Promise<State>;\n\tinitialState: State;\n\tapplyEvent: (state: State, event: Event) => State | Promise<State>;\n}\n\nexport interface ViewUsageMetadata {\n\tsemanticRole?: string;\n\tcollection?: {\n\t\tpagination: \"cursor\" | \"offset\" | \"none\";\n\t\tfilterSchema?: PortableJsonSchema;\n\t\tsortFields?: string[];\n\t};\n\tfreshness?: { maximumAgeSeconds?: number; offlineUsable?: boolean };\n\tclassification?: \"public\" | \"internal\" | \"confidential\" | \"restricted\";\n}\n\nexport function viewContractKey(view: Pick<ViewContract, \"name\" | \"version\">): string {\n\treturn `${view.name}@${view.version}`;\n}\n\nexport interface ViewRegistry {\n\tregister(view: ViewContract): void;\n\tregisterIfMissing(view: ViewContract): void;\n\tresolve(name: string, version: string): ViewContract | undefined;\n\tkeys(): string[];\n}\n\nexport function createViewRegistry(views: readonly ViewContract[] = []): ViewRegistry {\n\tconst registered = new Map<string, ViewContract>();\n\tconst register = (view: ViewContract): void => {\n\t\tassertSurfaceDeclaration(view, `View \"${view?.name ?? \"unknown\"}\"`);\n\t\tconst key = viewContractKey(view);\n\t\tif (registered.has(key)) throw new Error(`View contract \"${key}\" is already registered.`);\n\t\tregistered.set(key, view);\n\t};\n\tfor (const view of views) register(view);\n\treturn {\n\t\tregister,\n\t\tregisterIfMissing(view) {\n\t\t\tif (!registered.has(viewContractKey(view))) register(view);\n\t\t},\n\t\tresolve: (name, version) => registered.get(`${name}@${version}`),\n\t\tkeys: () => Array.from(registered.keys()).sort(),\n\t};\n}\n\nexport interface ReplayViewOptions<State, Event extends ReplayableAssetEvent = ReplayableAssetEvent> {\n\tview: ViewContract<State, Event>;\n\tevents: Iterable<Event> | AsyncIterable<Event>;\n\tscope: ReplayScope;\n\tsnapshot?: (ReplaySnapshot<State> & { snapshotVersion: number }) | null;\n\t/**\n\t * Set when the event source already applied the snapshot cursor and returns\n\t * only later events. The snapshot still seeds state, but replay must not try\n\t * to find its cursor in the supplied incremental stream.\n\t */\n\teventsStartAfterSnapshot?: boolean;\n}\n\nexport async function replayView<State, Event extends ReplayableAssetEvent = ReplayableAssetEvent>({\n\tview,\n\tevents,\n\tscope,\n\tsnapshot,\n\teventsStartAfterSnapshot = false,\n}: ReplayViewOptions<State, Event>): Promise<ReplayEventsResult<State, Event>> {\n\tif (\n\t\tsnapshot &&\n\t\tsnapshot.snapshotVersion !== view.snapshotVersion &&\n\t\t!view.compatibleSnapshotVersions?.includes(snapshot.snapshotVersion)\n\t) {\n\t\tthrow new Error(\n\t\t\t`View \"${viewContractKey(view)}\" cannot resume snapshot version ${snapshot.snapshotVersion}.`,\n\t\t);\n\t}\n\tif (snapshot && snapshot.snapshotVersion !== view.snapshotVersion && !view.upgradeSnapshot) {\n\t\tthrow new Error(\n\t\t\t`View \"${viewContractKey(view)}\" must provide upgradeSnapshot for snapshot version ${snapshot.snapshotVersion}.`,\n\t\t);\n\t}\n\tconst upgradedSnapshot = snapshot && snapshot.snapshotVersion !== view.snapshotVersion\n\t\t? { ...snapshot, snapshotVersion: view.snapshotVersion, snapshotData: await view.upgradeSnapshot!(snapshot.snapshotData, snapshot.snapshotVersion) }\n\t\t: snapshot;\n\tconst accepted = new Map(\n\t\tview.consumes.map((consumption) => [consumption.eventType, new Set(consumption.schemaVersions)]),\n\t);\n\tconst sourceEvents: Event[] = [];\n\tfor await (const event of events) sourceEvents.push(event);\n\tconst preprocessingWarnings: ReplayWarning[] = [];\n\tconst orderedScopedEvents = sortEvents(filterEventsByScope(sourceEvents, scope, preprocessingWarnings), Boolean(scope.subjectType && scope.subjectId));\n\tconst versionWarnings: ReplayWarning[] = [];\n\tconst replayable = async function* (): AsyncIterable<Event> {\n\t\tfor (const event of orderedScopedEvents) {\n\t\t\tconst versions = accepted.get(event.eventType);\n\t\t\tif (versions && !versions.has(event.eventSchemaVersion ?? 1)) {\n\t\t\t\tversionWarnings.push({ code: \"unsupported_event_version\", eventId: event.id, message: `View \"${viewContractKey(view)}\" does not consume ${event.eventType} schema version ${event.eventSchemaVersion ?? 1}; replay stopped before this event so a compatible view can resume it later.` });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tyield event;\n\t\t}\n\t};\n\tconst result = await replayEvents({\n\t\tevents: replayable(),\n\t\tscope,\n\t\tinitialState: view.initialState,\n\t\tsnapshot: upgradedSnapshot,\n\t\tapplyEvent: (state, event) => accepted.has(event.eventType) ? view.applyEvent(state, event) : state,\n\t\teventsStartAfterSnapshot,\n\t});\n\tconst preprocessingWarningKeys = new Set(preprocessingWarnings.map((warning) => JSON.stringify(warning)));\n\tconst resultWarnings = result.warnings.filter((warning) => !preprocessingWarningKeys.has(JSON.stringify(warning)));\n\treturn {\n\t\t...result,\n\t\teventCursor: result.eventCursor ?? upgradedSnapshot?.eventCursor ?? null,\n\t\teventSequence: result.appliedEvents.length\n\t\t\t? result.eventSequence\n\t\t\t: upgradedSnapshot?.eventSequence ?? result.eventSequence,\n\t\tsubjectSequences: result.appliedEvents.length\n\t\t\t? result.subjectSequences\n\t\t\t: upgradedSnapshot?.subjectSequences ?? result.subjectSequences,\n\t\twarnings: [...preprocessingWarnings, ...versionWarnings, ...resultWarnings],\n\t};\n}\n\nexport interface ReplayScope {\n\ttenantId: string;\n\tspaceId: string;\n\tsubjectType?: string;\n\tsubjectId?: string;\n}\n\nexport interface ReplaySnapshot<State = unknown> {\n\tsnapshotData: State;\n\teventCursor?: string | null;\n\teventSequence?: number | null;\n\t/** Per-subject high-water marks. Values prefer subjectSequence when supplied. */\n\tsubjectSequences?: Record<string, number>;\n}\n\nexport type ReplayWarningCode =\n\t| \"cursor_not_found\"\n\t| \"duplicate_event\"\n\t| \"missing_sequence\"\n\t| \"subject_scope_incomplete\"\n\t| \"unsupported_event_version\";\n\nexport interface ReplayWarning {\n\tcode: ReplayWarningCode;\n\tmessage: string;\n\teventId?: string;\n\tsubjectType?: string;\n\tsubjectId?: string;\n\texpectedSequence?: number;\n\tactualSequence?: number;\n}\n\nexport interface ReplayEventsOptions<State, Event extends ReplayableAssetEvent = ReplayableAssetEvent> {\n\tevents: Iterable<Event> | AsyncIterable<Event>;\n\tscope: ReplayScope;\n\tinitialState: State;\n\tsnapshot?: ReplaySnapshot<State> | null;\n\tapplyEvent: (state: State, event: Event) => State | Promise<State>;\n\t/** The supplied stream begins after the snapshot checkpoint. */\n\teventsStartAfterSnapshot?: boolean;\n}\n\nexport interface ReplayEventsResult<State, Event extends ReplayableAssetEvent = ReplayableAssetEvent> {\n\tstate: State;\n\tappliedEvents: Event[];\n\twarnings: ReplayWarning[];\n\teventCursor: string | null;\n\teventSequence: number;\n\tsubjectSequences: Record<string, number>;\n}\n\nexport async function replayEvents<State, Event extends ReplayableAssetEvent = ReplayableAssetEvent>({\n\tevents,\n\tscope,\n\tinitialState,\n\tsnapshot,\n\tapplyEvent,\n\teventsStartAfterSnapshot = false,\n}: ReplayEventsOptions<State, Event>): Promise<ReplayEventsResult<State, Event>> {\n\tif (eventsStartAfterSnapshot && !snapshot?.eventCursor) {\n\t\tthrow new Error(\"eventsStartAfterSnapshot requires a snapshot with an eventCursor checkpoint.\");\n\t}\n\n\tconst warnings: ReplayWarning[] = [];\n\tconst scopedEvents = filterEventsByScope(await collectEvents(events), scope, warnings);\n\tconst orderedEvents = sortEvents(scopedEvents, isSubjectScoped(scope));\n\tconst uniqueEvents = removeDuplicateEvents(orderedEvents, warnings);\n\tconst initialSubjectSequences = eventsStartAfterSnapshot\n\t\t? snapshotSubjectSequences(snapshot, scope)\n\t\t: undefined;\n\tconst subjectSequences = recordMissingSequenceWarnings(uniqueEvents, warnings, initialSubjectSequences);\n\tconst resumableEvents = applyCursor(\n\t\tuniqueEvents,\n\t\tsnapshot,\n\t\tisSubjectScoped(scope),\n\t\twarnings,\n\t\tinitialSubjectSequences,\n\t\teventsStartAfterSnapshot,\n\t);\n\n\tlet state = snapshot?.snapshotData ?? initialState;\n\tconst appliedEvents: Event[] = [];\n\tlet eventCursor = snapshot?.eventCursor ?? null;\n\tlet eventSequence = snapshot?.eventSequence ?? 0;\n\n\tfor (const event of resumableEvents) {\n\t\tstate = await applyEvent(state, event);\n\t\tappliedEvents.push(event);\n\t\teventCursor = event.id;\n\t\teventSequence = event.sequence;\n\t}\n\n\treturn {\n\t\tstate,\n\t\tappliedEvents,\n\t\twarnings,\n\t\teventCursor,\n\t\teventSequence,\n\t\tsubjectSequences: Object.fromEntries(\n\t\t\t[...subjectSequences.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0),\n\t\t),\n\t};\n}\n\nasync function collectEvents<Event extends ReplayableAssetEvent>(\n\tevents: Iterable<Event> | AsyncIterable<Event>,\n): Promise<Event[]> {\n\tconst collected: Event[] = [];\n\n\tif (Symbol.asyncIterator in events) {\n\t\tfor await (const event of events) {\n\t\t\tcollected.push(event);\n\t\t}\n\n\t\treturn collected;\n\t}\n\n\tfor (const event of events) {\n\t\tcollected.push(event);\n\t}\n\n\treturn collected;\n}\n\nfunction filterEventsByScope<Event extends ReplayableAssetEvent>(\n\tevents: Event[],\n\tscope: ReplayScope,\n\twarnings: ReplayWarning[],\n): Event[] {\n\tif ((scope.subjectType && !scope.subjectId) || (!scope.subjectType && scope.subjectId)) {\n\t\twarnings.push({\n\t\t\tcode: \"subject_scope_incomplete\",\n\t\t\tmessage: \"Both subjectType and subjectId are required for subject-scoped replay.\",\n\t\t\tsubjectType: scope.subjectType,\n\t\t\tsubjectId: scope.subjectId,\n\t\t});\n\t}\n\n\treturn events.filter((event) => {\n\t\tif (event.tenantId !== scope.tenantId || event.spaceId !== scope.spaceId) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (scope.subjectType && event.subjectType !== scope.subjectType) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (scope.subjectId && event.subjectId !== scope.subjectId) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t});\n}\n\nfunction removeDuplicateEvents<Event extends ReplayableAssetEvent>(\n\tevents: Event[],\n\twarnings: ReplayWarning[],\n): Event[] {\n\tconst seenEventIds = new Set<string>();\n\tconst uniqueEvents: Event[] = [];\n\n\tfor (const event of events) {\n\t\tif (seenEventIds.has(event.id)) {\n\t\t\twarnings.push({\n\t\t\t\tcode: \"duplicate_event\",\n\t\t\t\tmessage: `Duplicate event ${event.id} ignored during replay.`,\n\t\t\t\teventId: event.id,\n\t\t\t\tsubjectType: event.subjectType,\n\t\t\t\tsubjectId: event.subjectId,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tseenEventIds.add(event.id);\n\t\tuniqueEvents.push(event);\n\t}\n\n\treturn uniqueEvents;\n}\n\nfunction recordMissingSequenceWarnings<Event extends ReplayableAssetEvent>(\n\tevents: Event[],\n\twarnings: ReplayWarning[],\n\tinitialSequences?: ReadonlyMap<string, number>,\n): Map<string, number> {\n\tconst lastSequenceBySubject = new Map(initialSequences);\n\n\tfor (const event of events) {\n\t\tconst subjectKey = subjectSequenceKey(event.subjectType, event.subjectId);\n\t\tconst previousSequence = lastSequenceBySubject.get(subjectKey);\n\t\tconst sequence = event.subjectSequence ?? event.sequence;\n\t\tconst expectedSequence = previousSequence === undefined ? sequence : previousSequence + 1;\n\n\t\t// A ledger sequence orders the complete tenant/space stream. A projection\n\t\t// filters that stream by event type and often by subject, so gaps in it do\n\t\t// not prove event loss. Only an explicit subject-local sequence can make\n\t\t// that guarantee.\n\t\tif (\n\t\t\tevent.subjectSequence !== undefined &&\n\t\t\tpreviousSequence !== undefined &&\n\t\t\tsequence > expectedSequence\n\t\t) {\n\t\t\twarnings.push({\n\t\t\t\tcode: \"missing_sequence\",\n\t\t\t\tmessage: `Missing event sequence for ${subjectKey}: expected ${expectedSequence}, received ${sequence}.`,\n\t\t\t\teventId: event.id,\n\t\t\t\tsubjectType: event.subjectType,\n\t\t\t\tsubjectId: event.subjectId,\n\t\t\t\texpectedSequence,\n\t\t\t\tactualSequence: sequence,\n\t\t\t});\n\t\t}\n\n\t\tlastSequenceBySubject.set(subjectKey, Math.max(previousSequence ?? sequence, sequence));\n\t}\n\treturn lastSequenceBySubject;\n}\n\nfunction applyCursor<Event extends ReplayableAssetEvent>(\n\tevents: Event[],\n\tsnapshot: ReplaySnapshot | null | undefined,\n\tsubjectScoped: boolean,\n\twarnings: ReplayWarning[],\n\tinitialSubjectSequences?: ReadonlyMap<string, number>,\n\teventsStartAfterSnapshot = false,\n): Event[] {\n\tconst eventCursor = snapshot?.eventCursor;\n\n\tif (eventCursor && !eventsStartAfterSnapshot) {\n\t\tconst cursorIndex = events.findIndex((event) => event.id === eventCursor);\n\n\t\tif (cursorIndex >= 0) {\n\t\t\treturn events.slice(cursorIndex + 1);\n\t\t}\n\n\t\twarnings.push({\n\t\t\tcode: \"cursor_not_found\",\n\t\t\tmessage: `Snapshot cursor ${eventCursor} was not found in the scoped event stream.`,\n\t\t\teventId: eventCursor,\n\t\t});\n\t}\n\n\tif (initialSubjectSequences?.size) {\n\t\treturn events.filter((event) =>\n\t\t\t(event.subjectSequence ?? event.sequence) >\n\t\t\t(initialSubjectSequences.get(subjectSequenceKey(event.subjectType, event.subjectId)) ?? -1),\n\t\t);\n\t}\n\n\tif (subjectScoped && snapshot?.eventSequence !== undefined && snapshot.eventSequence !== null) {\n\t\treturn events.filter((event) => event.sequence > snapshot.eventSequence!);\n\t}\n\n\treturn events;\n}\n\nfunction snapshotSubjectSequences(\n\tsnapshot: ReplaySnapshot | null | undefined,\n\tscope: ReplayScope,\n): Map<string, number> {\n\tconst sequences = new Map(Object.entries(snapshot?.subjectSequences ?? {}));\n\tif (\n\t\tsequences.size === 0 &&\n\t\tscope.subjectType &&\n\t\tscope.subjectId &&\n\t\tsnapshot?.eventSequence !== undefined &&\n\t\tsnapshot.eventSequence !== null\n\t) {\n\t\tsequences.set(subjectSequenceKey(scope.subjectType, scope.subjectId), snapshot.eventSequence);\n\t}\n\treturn sequences;\n}\n\nfunction subjectSequenceKey(subjectType: string, subjectId: string): string {\n\treturn `${subjectType}:${subjectId}`;\n}\n\nfunction sortEvents<Event extends ReplayableAssetEvent>(events: Event[], subjectScoped: boolean): Event[] {\n\treturn [...events].sort((first, second) =>\n\t\tsubjectScoped ? compareSubjectEvents(first, second) : compareGlobalEvents(first, second),\n\t);\n}\n\nfunction compareSubjectEvents(first: ReplayableAssetEvent, second: ReplayableAssetEvent): number {\n\treturn compareValues(\n\t\t[first.subjectSequence ?? first.sequence, toTimestamp(first.recordedAt), toTimestamp(first.occurredAt), first.id],\n\t\t[second.subjectSequence ?? second.sequence, toTimestamp(second.recordedAt), toTimestamp(second.occurredAt), second.id],\n\t);\n}\n\nfunction compareGlobalEvents(first: ReplayableAssetEvent, second: ReplayableAssetEvent): number {\n\treturn compareValues(\n\t\t[\n\t\t\ttoTimestamp(first.recordedAt),\n\t\t\ttoTimestamp(first.occurredAt),\n\t\t\tfirst.actionInvocationId,\n\t\t\tfirst.correlationId,\n\t\t\tfirst.subjectType,\n\t\t\tfirst.subjectId,\n\t\t\tfirst.sequence,\n\t\t\tfirst.id,\n\t\t],\n\t\t[\n\t\t\ttoTimestamp(second.recordedAt),\n\t\t\ttoTimestamp(second.occurredAt),\n\t\t\tsecond.actionInvocationId,\n\t\t\tsecond.correlationId,\n\t\t\tsecond.subjectType,\n\t\t\tsecond.subjectId,\n\t\t\tsecond.sequence,\n\t\t\tsecond.id,\n\t\t],\n\t);\n}\n\nfunction compareValues(firstValues: Array<number | string>, secondValues: Array<number | string>): number {\n\tfor (const [index, firstValue] of firstValues.entries()) {\n\t\tconst secondValue = secondValues[index];\n\n\t\tif (firstValue < secondValue) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (firstValue > secondValue) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nfunction isSubjectScoped(scope: ReplayScope): boolean {\n\treturn Boolean(scope.subjectType && scope.subjectId);\n}\n\nfunction toTimestamp(value: Date | string): number {\n\treturn value instanceof Date ? value.getTime() : new Date(value).getTime();\n}\n"],"mappings":";;AA2DA,SAAgB,gBAAgB,MAAsD;CACrF,OAAO,GAAG,KAAK,KAAK,GAAG,KAAK;AAC7B;AASA,SAAgB,mBAAmB,QAAiC,CAAC,GAAiB;CACrF,MAAM,6BAAa,IAAI,IAA0B;CACjD,MAAM,YAAY,SAA6B;EAC9C,yBAAyB,MAAM,SAAS,MAAM,QAAQ,UAAU,EAAE;EAClE,MAAM,MAAM,gBAAgB,IAAI;EAChC,IAAI,WAAW,IAAI,GAAG,GAAG,MAAM,IAAI,MAAM,kBAAkB,IAAI,yBAAyB;EACxF,WAAW,IAAI,KAAK,IAAI;CACzB;CACA,KAAK,MAAM,QAAQ,OAAO,SAAS,IAAI;CACvC,OAAO;EACN;EACA,kBAAkB,MAAM;GACvB,IAAI,CAAC,WAAW,IAAI,gBAAgB,IAAI,CAAC,GAAG,SAAS,IAAI;EAC1D;EACA,UAAU,MAAM,YAAY,WAAW,IAAI,GAAG,KAAK,GAAG,SAAS;EAC/D,YAAY,MAAM,KAAK,WAAW,KAAK,CAAC,CAAC,CAAC,KAAK;CAChD;AACD;AAeA,eAAsB,WAA6E,EAClG,MACA,QACA,OACA,UACA,2BAA2B,SACmD;CAC9E,IACC,YACA,SAAS,oBAAoB,KAAK,mBAClC,CAAC,KAAK,4BAA4B,SAAS,SAAS,eAAe,GAEnE,MAAM,IAAI,MACT,SAAS,gBAAgB,IAAI,EAAE,mCAAmC,SAAS,gBAAgB,EAC5F;CAED,IAAI,YAAY,SAAS,oBAAoB,KAAK,mBAAmB,CAAC,KAAK,iBAC1E,MAAM,IAAI,MACT,SAAS,gBAAgB,IAAI,EAAE,sDAAsD,SAAS,gBAAgB,EAC/G;CAED,MAAM,mBAAmB,YAAY,SAAS,oBAAoB,KAAK,kBACpE;EAAE,GAAG;EAAU,iBAAiB,KAAK;EAAiB,cAAc,MAAM,KAAK,gBAAiB,SAAS,cAAc,SAAS,eAAe;CAAE,IACjJ;CACH,MAAM,WAAW,IAAI,IACpB,KAAK,SAAS,KAAK,gBAAgB,CAAC,YAAY,WAAW,IAAI,IAAI,YAAY,cAAc,CAAC,CAAC,CAChG;CACA,MAAM,eAAwB,CAAC;CAC/B,WAAW,MAAM,SAAS,QAAQ,aAAa,KAAK,KAAK;CACzD,MAAM,wBAAyC,CAAC;CAChD,MAAM,sBAAsB,WAAW,oBAAoB,cAAc,OAAO,qBAAqB,GAAG,QAAQ,MAAM,eAAe,MAAM,SAAS,CAAC;CACrJ,MAAM,kBAAmC,CAAC;CAC1C,MAAM,aAAa,mBAAyC;EAC3D,KAAK,MAAM,SAAS,qBAAqB;GACxC,MAAM,WAAW,SAAS,IAAI,MAAM,SAAS;GAC7C,IAAI,YAAY,CAAC,SAAS,IAAI,MAAM,sBAAsB,CAAC,GAAG;IAC7D,gBAAgB,KAAK;KAAE,MAAM;KAA6B,SAAS,MAAM;KAAI,SAAS,SAAS,gBAAgB,IAAI,EAAE,qBAAqB,MAAM,UAAU,kBAAkB,MAAM,sBAAsB,EAAE;IAA8E,CAAC;IACzR;GACD;GACA,MAAM;EACP;CACD;CACA,MAAM,SAAS,MAAM,aAAa;EACjC,QAAQ,WAAW;EACnB;EACA,cAAc,KAAK;EACnB,UAAU;EACV,aAAa,OAAO,UAAU,SAAS,IAAI,MAAM,SAAS,IAAI,KAAK,WAAW,OAAO,KAAK,IAAI;EAC9F;CACD,CAAC;CACD,MAAM,2BAA2B,IAAI,IAAI,sBAAsB,KAAK,YAAY,KAAK,UAAU,OAAO,CAAC,CAAC;CACxG,MAAM,iBAAiB,OAAO,SAAS,QAAQ,YAAY,CAAC,yBAAyB,IAAI,KAAK,UAAU,OAAO,CAAC,CAAC;CACjH,OAAO;EACN,GAAG;EACH,aAAa,OAAO,eAAe,kBAAkB,eAAe;EACpE,eAAe,OAAO,cAAc,SACjC,OAAO,gBACP,kBAAkB,iBAAiB,OAAO;EAC7C,kBAAkB,OAAO,cAAc,SACpC,OAAO,mBACP,kBAAkB,oBAAoB,OAAO;EAChD,UAAU;GAAC,GAAG;GAAuB,GAAG;GAAiB,GAAG;EAAc;CAC3E;AACD;AAqDA,eAAsB,aAA+E,EACpG,QACA,OACA,cACA,UACA,YACA,2BAA2B,SACqD;CAChF,IAAI,4BAA4B,CAAC,UAAU,aAC1C,MAAM,IAAI,MAAM,8EAA8E;CAG/F,MAAM,WAA4B,CAAC;CAGnC,MAAM,eAAe,sBADC,WADD,oBAAoB,MAAM,cAAc,MAAM,GAAG,OAAO,QACjC,GAAG,gBAAgB,KAAK,CACb,GAAG,QAAQ;CAClE,MAAM,0BAA0B,2BAC7B,yBAAyB,UAAU,KAAK,IACxC,KAAA;CACH,MAAM,mBAAmB,8BAA8B,cAAc,UAAU,uBAAuB;CACtG,MAAM,kBAAkB,YACvB,cACA,UACA,gBAAgB,KAAK,GACrB,UACA,yBACA,wBACD;CAEA,IAAI,QAAQ,UAAU,gBAAgB;CACtC,MAAM,gBAAyB,CAAC;CAChC,IAAI,cAAc,UAAU,eAAe;CAC3C,IAAI,gBAAgB,UAAU,iBAAiB;CAE/C,KAAK,MAAM,SAAS,iBAAiB;EACpC,QAAQ,MAAM,WAAW,OAAO,KAAK;EACrC,cAAc,KAAK,KAAK;EACxB,cAAc,MAAM;EACpB,gBAAgB,MAAM;CACvB;CAEA,OAAO;EACN;EACA;EACA;EACA;EACA;EACA,kBAAkB,OAAO,YACxB,CAAC,GAAG,iBAAiB,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,OAAO,QAAQ,KAAK,OAAO,QAAQ,IAAI,CAAC,CACnG;CACD;AACD;AAEA,eAAe,cACd,QACmB;CACnB,MAAM,YAAqB,CAAC;CAE5B,IAAI,OAAO,iBAAiB,QAAQ;EACnC,WAAW,MAAM,SAAS,QACzB,UAAU,KAAK,KAAK;EAGrB,OAAO;CACR;CAEA,KAAK,MAAM,SAAS,QACnB,UAAU,KAAK,KAAK;CAGrB,OAAO;AACR;AAEA,SAAS,oBACR,QACA,OACA,UACU;CACV,IAAK,MAAM,eAAe,CAAC,MAAM,aAAe,CAAC,MAAM,eAAe,MAAM,WAC3E,SAAS,KAAK;EACb,MAAM;EACN,SAAS;EACT,aAAa,MAAM;EACnB,WAAW,MAAM;CAClB,CAAC;CAGF,OAAO,OAAO,QAAQ,UAAU;EAC/B,IAAI,MAAM,aAAa,MAAM,YAAY,MAAM,YAAY,MAAM,SAChE,OAAO;EAGR,IAAI,MAAM,eAAe,MAAM,gBAAgB,MAAM,aACpD,OAAO;EAGR,IAAI,MAAM,aAAa,MAAM,cAAc,MAAM,WAChD,OAAO;EAGR,OAAO;CACR,CAAC;AACF;AAEA,SAAS,sBACR,QACA,UACU;CACV,MAAM,+BAAe,IAAI,IAAY;CACrC,MAAM,eAAwB,CAAC;CAE/B,KAAK,MAAM,SAAS,QAAQ;EAC3B,IAAI,aAAa,IAAI,MAAM,EAAE,GAAG;GAC/B,SAAS,KAAK;IACb,MAAM;IACN,SAAS,mBAAmB,MAAM,GAAG;IACrC,SAAS,MAAM;IACf,aAAa,MAAM;IACnB,WAAW,MAAM;GAClB,CAAC;GACD;EACD;EAEA,aAAa,IAAI,MAAM,EAAE;EACzB,aAAa,KAAK,KAAK;CACxB;CAEA,OAAO;AACR;AAEA,SAAS,8BACR,QACA,UACA,kBACsB;CACtB,MAAM,wBAAwB,IAAI,IAAI,gBAAgB;CAEtD,KAAK,MAAM,SAAS,QAAQ;EAC3B,MAAM,aAAa,mBAAmB,MAAM,aAAa,MAAM,SAAS;EACxE,MAAM,mBAAmB,sBAAsB,IAAI,UAAU;EAC7D,MAAM,WAAW,MAAM,mBAAmB,MAAM;EAChD,MAAM,mBAAmB,qBAAqB,KAAA,IAAY,WAAW,mBAAmB;EAMxF,IACC,MAAM,oBAAoB,KAAA,KAC1B,qBAAqB,KAAA,KACrB,WAAW,kBAEX,SAAS,KAAK;GACb,MAAM;GACN,SAAS,8BAA8B,WAAW,aAAa,iBAAiB,aAAa,SAAS;GACtG,SAAS,MAAM;GACf,aAAa,MAAM;GACnB,WAAW,MAAM;GACjB;GACA,gBAAgB;EACjB,CAAC;EAGF,sBAAsB,IAAI,YAAY,KAAK,IAAI,oBAAoB,UAAU,QAAQ,CAAC;CACvF;CACA,OAAO;AACR;AAEA,SAAS,YACR,QACA,UACA,eACA,UACA,yBACA,2BAA2B,OACjB;CACV,MAAM,cAAc,UAAU;CAE9B,IAAI,eAAe,CAAC,0BAA0B;EAC7C,MAAM,cAAc,OAAO,WAAW,UAAU,MAAM,OAAO,WAAW;EAExE,IAAI,eAAe,GAClB,OAAO,OAAO,MAAM,cAAc,CAAC;EAGpC,SAAS,KAAK;GACb,MAAM;GACN,SAAS,mBAAmB,YAAY;GACxC,SAAS;EACV,CAAC;CACF;CAEA,IAAI,yBAAyB,MAC5B,OAAO,OAAO,QAAQ,WACpB,MAAM,mBAAmB,MAAM,aAC/B,wBAAwB,IAAI,mBAAmB,MAAM,aAAa,MAAM,SAAS,CAAC,KAAK,GACzF;CAGD,IAAI,iBAAiB,UAAU,kBAAkB,KAAA,KAAa,SAAS,kBAAkB,MACxF,OAAO,OAAO,QAAQ,UAAU,MAAM,WAAW,SAAS,aAAc;CAGzE,OAAO;AACR;AAEA,SAAS,yBACR,UACA,OACsB;CACtB,MAAM,YAAY,IAAI,IAAI,OAAO,QAAQ,UAAU,oBAAoB,CAAC,CAAC,CAAC;CAC1E,IACC,UAAU,SAAS,KACnB,MAAM,eACN,MAAM,aACN,UAAU,kBAAkB,KAAA,KAC5B,SAAS,kBAAkB,MAE3B,UAAU,IAAI,mBAAmB,MAAM,aAAa,MAAM,SAAS,GAAG,SAAS,aAAa;CAE7F,OAAO;AACR;AAEA,SAAS,mBAAmB,aAAqB,WAA2B;CAC3E,OAAO,GAAG,YAAY,GAAG;AAC1B;AAEA,SAAS,WAA+C,QAAiB,eAAiC;CACzG,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,OAAO,WAC/B,gBAAgB,qBAAqB,OAAO,MAAM,IAAI,oBAAoB,OAAO,MAAM,CACxF;AACD;AAEA,SAAS,qBAAqB,OAA6B,QAAsC;CAChG,OAAO,cACN;EAAC,MAAM,mBAAmB,MAAM;EAAU,YAAY,MAAM,UAAU;EAAG,YAAY,MAAM,UAAU;EAAG,MAAM;CAAE,GAChH;EAAC,OAAO,mBAAmB,OAAO;EAAU,YAAY,OAAO,UAAU;EAAG,YAAY,OAAO,UAAU;EAAG,OAAO;CAAE,CACtH;AACD;AAEA,SAAS,oBAAoB,OAA6B,QAAsC;CAC/F,OAAO,cACN;EACC,YAAY,MAAM,UAAU;EAC5B,YAAY,MAAM,UAAU;EAC5B,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;CACP,GACA;EACC,YAAY,OAAO,UAAU;EAC7B,YAAY,OAAO,UAAU;EAC7B,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO;CACR,CACD;AACD;AAEA,SAAS,cAAc,aAAqC,cAA8C;CACzG,KAAK,MAAM,CAAC,OAAO,eAAe,YAAY,QAAQ,GAAG;EACxD,MAAM,cAAc,aAAa;EAEjC,IAAI,aAAa,aAChB,OAAO;EAGR,IAAI,aAAa,aAChB,OAAO;CAET;CAEA,OAAO;AACR;AAEA,SAAS,gBAAgB,OAA6B;CACrD,OAAO,QAAQ,MAAM,eAAe,MAAM,SAAS;AACpD;AAEA,SAAS,YAAY,OAA8B;CAClD,OAAO,iBAAiB,OAAO,MAAM,QAAQ,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,QAAQ;AAC1E"}
|
|
@@ -1,20 +1,78 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region state-machines/engine.ts
|
|
3
|
+
function validateStateMachineTransition(sm, entityType, from, to, actionId) {
|
|
4
|
+
if (!sm) return {
|
|
5
|
+
valid: false,
|
|
6
|
+
error: `No state machine registered for entity type "${entityType}".`
|
|
7
|
+
};
|
|
8
|
+
if (!sm.states[from]) return {
|
|
9
|
+
valid: false,
|
|
10
|
+
error: `State "${from}" does not exist in state machine for entity type "${entityType}".`
|
|
11
|
+
};
|
|
12
|
+
if (!sm.states[to]) return {
|
|
13
|
+
valid: false,
|
|
14
|
+
error: `State "${to}" does not exist in state machine for entity type "${entityType}".`
|
|
15
|
+
};
|
|
16
|
+
return sm.transitions.some((transition) => transition.from === from && transition.to === to && transition.causedByAction === actionId) ? { valid: true } : {
|
|
17
|
+
valid: false,
|
|
18
|
+
error: `Transition from "${from}" to "${to}" via action "${actionId}" is not defined for entity type "${entityType}".`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validates a transition and evaluates its declared guard against trusted Host
|
|
23
|
+
* context. Callers must construct `context.entity` from authoritative state,
|
|
24
|
+
* never from action parameters.
|
|
25
|
+
*/
|
|
26
|
+
async function evaluateStateMachineTransition(sm, entityType, from, to, actionId, context) {
|
|
27
|
+
const structural = validateStateMachineTransition(sm, entityType, from, to, actionId);
|
|
28
|
+
if (!structural.valid) return structural;
|
|
29
|
+
const transition = sm.transitions.find((candidate) => candidate.from === from && candidate.to === to && candidate.causedByAction === actionId);
|
|
30
|
+
if (!transition?.guard) return structural;
|
|
31
|
+
try {
|
|
32
|
+
return await transition.guard(context) ? structural : {
|
|
33
|
+
valid: false,
|
|
34
|
+
error: `Guard rejected transition from "${from}" to "${to}" via action "${actionId}" for entity type "${entityType}".`
|
|
35
|
+
};
|
|
36
|
+
} catch {
|
|
37
|
+
return {
|
|
38
|
+
valid: false,
|
|
39
|
+
error: `Guard failed while evaluating transition from "${from}" to "${to}" via action "${actionId}" for entity type "${entityType}".`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function createStateMachineRegistry(definitions = []) {
|
|
44
|
+
const machines = /* @__PURE__ */ new Map();
|
|
45
|
+
const register = (definition) => {
|
|
46
|
+
if (machines.has(definition.entityType)) throw new Error(`State machine for entity type "${definition.entityType}" is already registered.`);
|
|
47
|
+
machines.set(definition.entityType, definition);
|
|
48
|
+
};
|
|
49
|
+
const validateTransition = (entityType, from, to, actionId) => validateStateMachineTransition(machines.get(entityType), entityType, from, to, actionId);
|
|
50
|
+
for (const definition of definitions) register(definition);
|
|
51
|
+
return {
|
|
52
|
+
register,
|
|
53
|
+
registerIfMissing(definition) {
|
|
54
|
+
if (!machines.has(definition.entityType)) register(definition);
|
|
55
|
+
},
|
|
56
|
+
resolve: (entityType) => machines.get(entityType),
|
|
57
|
+
validateTransition,
|
|
58
|
+
allowedTransitions(entityType, from) {
|
|
59
|
+
const sm = machines.get(entityType);
|
|
60
|
+
if (!sm) return [];
|
|
61
|
+
return sm.transitions.filter((t) => t.from === from).map((t) => ({
|
|
62
|
+
to: t.to,
|
|
63
|
+
causedByAction: t.causedByAction
|
|
64
|
+
}));
|
|
65
|
+
},
|
|
66
|
+
stateClass(entityType, state) {
|
|
67
|
+
const sm = machines.get(entityType);
|
|
68
|
+
if (!sm) return;
|
|
69
|
+
return sm.states[state]?.stateClass;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
exports.createStateMachineRegistry = createStateMachineRegistry;
|
|
75
|
+
exports.evaluateStateMachineTransition = evaluateStateMachineTransition;
|
|
76
|
+
exports.validateStateMachineTransition = validateStateMachineTransition;
|
|
2
77
|
|
|
3
|
-
var chunkWI7CWNYI_cjs = require('../chunk-WI7CWNYI.cjs');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Object.defineProperty(exports, "createStateMachineRegistry", {
|
|
8
|
-
enumerable: true,
|
|
9
|
-
get: function () { return chunkWI7CWNYI_cjs.createStateMachineRegistry; }
|
|
10
|
-
});
|
|
11
|
-
Object.defineProperty(exports, "evaluateStateMachineTransition", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
get: function () { return chunkWI7CWNYI_cjs.evaluateStateMachineTransition; }
|
|
14
|
-
});
|
|
15
|
-
Object.defineProperty(exports, "validateStateMachineTransition", {
|
|
16
|
-
enumerable: true,
|
|
17
|
-
get: function () { return chunkWI7CWNYI_cjs.validateStateMachineTransition; }
|
|
18
|
-
});
|
|
19
|
-
//# sourceMappingURL=index.cjs.map
|
|
20
78
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../../state-machines/engine.ts"],"sourcesContent":["import type { StateClass, StateDefinition } from \"./index\";\n\nexport interface StateMachineDefinition {\n\tentityType: string;\n\tstates: Record<string, StateDefinition>;\n\ttransitions: Array<{\n\t\tfrom: string;\n\t\tto: string;\n\t\tcausedByAction: string;\n\t\tguard?: (ctx: TransitionContext) => boolean | Promise<boolean>;\n\t}>;\n}\n\nexport type StateMachineDatabaseClient = unknown;\n\nexport interface TransitionContext<TDb = StateMachineDatabaseClient> {\n\tentity: Record<string, unknown>;\n\tactionInvocationId: string;\n\tactorId: string;\n\tparameters: unknown;\n\tdb: TDb;\n}\n\nexport interface StateMachineRegistry {\n\tregister(definition: StateMachineDefinition): void;\n\tregisterIfMissing(definition: StateMachineDefinition): void;\n\tresolve(entityType: string): StateMachineDefinition | undefined;\n\tvalidateTransition(entityType: string, from: string, to: string, actionId: string): { valid: boolean; error?: string };\n\tallowedTransitions(entityType: string, from: string): Array<{ to: string; causedByAction: string }>;\n\tstateClass(entityType: string, state: string): StateClass | undefined;\n}\n\nexport function validateStateMachineTransition(\n\tsm: StateMachineDefinition | undefined,\n\tentityType: string,\n\tfrom: string,\n\tto: string,\n\tactionId: string,\n): { valid: boolean; error?: string } {\n\tif (!sm) return { valid: false, error: `No state machine registered for entity type \"${entityType}\".` };\n\tconst fromDef = sm.states[from];\n\tif (!fromDef) return { valid: false, error: `State \"${from}\" does not exist in state machine for entity type \"${entityType}\".` };\n\tconst toDef = sm.states[to];\n\tif (!toDef) return { valid: false, error: `State \"${to}\" does not exist in state machine for entity type \"${entityType}\".` };\n\tconst transitionExists = sm.transitions.some((transition) => transition.from === from && transition.to === to && transition.causedByAction === actionId);\n\treturn transitionExists\n\t\t? { valid: true }\n\t\t: { valid: false, error: `Transition from \"${from}\" to \"${to}\" via action \"${actionId}\" is not defined for entity type \"${entityType}\".` };\n}\n\n/**\n * Validates a transition and evaluates its declared guard against trusted Host\n * context. Callers must construct `context.entity` from authoritative state,\n * never from action parameters.\n */\nexport async function evaluateStateMachineTransition<TDb = StateMachineDatabaseClient>(\n\tsm: StateMachineDefinition | undefined,\n\tentityType: string,\n\tfrom: string,\n\tto: string,\n\tactionId: string,\n\tcontext: TransitionContext<TDb>,\n): Promise<{ valid: boolean; error?: string }> {\n\tconst structural = validateStateMachineTransition(sm, entityType, from, to, actionId);\n\tif (!structural.valid) return structural;\n\tconst transition = sm!.transitions.find(\n\t\t(candidate) =>\n\t\t\tcandidate.from === from &&\n\t\t\tcandidate.to === to &&\n\t\t\tcandidate.causedByAction === actionId,\n\t);\n\tif (!transition?.guard) return structural;\n\ttry {\n\t\treturn await transition.guard(context)\n\t\t\t? structural\n\t\t\t: {\n\t\t\t\t\tvalid: false,\n\t\t\t\t\terror: `Guard rejected transition from \"${from}\" to \"${to}\" via action \"${actionId}\" for entity type \"${entityType}\".`,\n\t\t\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `Guard failed while evaluating transition from \"${from}\" to \"${to}\" via action \"${actionId}\" for entity type \"${entityType}\".`,\n\t\t};\n\t}\n}\n\nexport function createStateMachineRegistry(definitions: readonly StateMachineDefinition[] = []): StateMachineRegistry {\n\tconst machines = new Map<string, StateMachineDefinition>();\n\tconst register = (definition: StateMachineDefinition): void => {\n\t\tif (machines.has(definition.entityType)) throw new Error(`State machine for entity type \"${definition.entityType}\" is already registered.`);\n\t\tmachines.set(definition.entityType, definition);\n\t};\n\tconst validateTransition = (entityType: string, from: string, to: string, actionId: string) =>\n\t\tvalidateStateMachineTransition(machines.get(entityType), entityType, from, to, actionId);\n\tfor (const definition of definitions) register(definition);\n\treturn {\n\t\tregister,\n\t\tregisterIfMissing(definition) {\n\t\t\tif (!machines.has(definition.entityType)) register(definition);\n\t\t},\n\t\tresolve: (entityType) => machines.get(entityType),\n\t\tvalidateTransition,\n\t\tallowedTransitions(entityType, from) {\n\tconst sm = machines.get(entityType);\n\tif (!sm) {\n\t\treturn [];\n\t}\n\treturn sm.transitions\n\t\t.filter((t) => t.from === from)\n\t\t.map((t) => ({ to: t.to, causedByAction: t.causedByAction }));\n\t\t},\n\t\tstateClass(entityType, state) {\n\tconst sm = machines.get(entityType);\n\tif (!sm) {\n\t\treturn undefined;\n\t}\n\treturn sm.states[state]?.stateClass;\n\t\t},\n\t};\n}\n"],"mappings":";;AAgCA,SAAgB,+BACf,IACA,YACA,MACA,IACA,UACqC;CACrC,IAAI,CAAC,IAAI,OAAO;EAAE,OAAO;EAAO,OAAO,gDAAgD,WAAW;CAAI;CAEtG,IAAI,CADY,GAAG,OAAO,OACZ,OAAO;EAAE,OAAO;EAAO,OAAO,UAAU,KAAK,qDAAqD,WAAW;CAAI;CAE/H,IAAI,CADU,GAAG,OAAO,KACZ,OAAO;EAAE,OAAO;EAAO,OAAO,UAAU,GAAG,qDAAqD,WAAW;CAAI;CAE3H,OADyB,GAAG,YAAY,MAAM,eAAe,WAAW,SAAS,QAAQ,WAAW,OAAO,MAAM,WAAW,mBAAmB,QACzH,IACnB,EAAE,OAAO,KAAK,IACd;EAAE,OAAO;EAAO,OAAO,oBAAoB,KAAK,QAAQ,GAAG,gBAAgB,SAAS,oCAAoC,WAAW;CAAI;AAC3I;;;;;;AAOA,eAAsB,+BACrB,IACA,YACA,MACA,IACA,UACA,SAC8C;CAC9C,MAAM,aAAa,+BAA+B,IAAI,YAAY,MAAM,IAAI,QAAQ;CACpF,IAAI,CAAC,WAAW,OAAO,OAAO;CAC9B,MAAM,aAAa,GAAI,YAAY,MACjC,cACA,UAAU,SAAS,QACnB,UAAU,OAAO,MACjB,UAAU,mBAAmB,QAC/B;CACA,IAAI,CAAC,YAAY,OAAO,OAAO;CAC/B,IAAI;EACH,OAAO,MAAM,WAAW,MAAM,OAAO,IAClC,aACA;GACA,OAAO;GACP,OAAO,mCAAmC,KAAK,QAAQ,GAAG,gBAAgB,SAAS,qBAAqB,WAAW;EACpH;CACH,QAAQ;EACP,OAAO;GACN,OAAO;GACP,OAAO,kDAAkD,KAAK,QAAQ,GAAG,gBAAgB,SAAS,qBAAqB,WAAW;EACnI;CACD;AACD;AAEA,SAAgB,2BAA2B,cAAiD,CAAC,GAAyB;CACrH,MAAM,2BAAW,IAAI,IAAoC;CACzD,MAAM,YAAY,eAA6C;EAC9D,IAAI,SAAS,IAAI,WAAW,UAAU,GAAG,MAAM,IAAI,MAAM,kCAAkC,WAAW,WAAW,yBAAyB;EAC1I,SAAS,IAAI,WAAW,YAAY,UAAU;CAC/C;CACA,MAAM,sBAAsB,YAAoB,MAAc,IAAY,aACzE,+BAA+B,SAAS,IAAI,UAAU,GAAG,YAAY,MAAM,IAAI,QAAQ;CACxF,KAAK,MAAM,cAAc,aAAa,SAAS,UAAU;CACzD,OAAO;EACN;EACA,kBAAkB,YAAY;GAC7B,IAAI,CAAC,SAAS,IAAI,WAAW,UAAU,GAAG,SAAS,UAAU;EAC9D;EACA,UAAU,eAAe,SAAS,IAAI,UAAU;EAChD;EACA,mBAAmB,YAAY,MAAM;GACtC,MAAM,KAAK,SAAS,IAAI,UAAU;GAClC,IAAI,CAAC,IACJ,OAAO,CAAC;GAET,OAAO,GAAG,YACR,QAAQ,MAAM,EAAE,SAAS,IAAI,CAAC,CAC9B,KAAK,OAAO;IAAE,IAAI,EAAE;IAAI,gBAAgB,EAAE;GAAe,EAAE;EAC5D;EACA,WAAW,YAAY,OAAO;GAC/B,MAAM,KAAK,SAAS,IAAI,UAAU;GAClC,IAAI,CAAC,IACJ;GAED,OAAO,GAAG,OAAO,MAAM,EAAE;EACxB;CACD;AACD"}
|
|
@@ -1,55 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
states: Record<string, StateDefinition>;
|
|
4
|
-
transitions: Array<{
|
|
5
|
-
from: string;
|
|
6
|
-
to: string;
|
|
7
|
-
causedByAction: string;
|
|
8
|
-
guard?: (ctx: TransitionContext) => boolean | Promise<boolean>;
|
|
9
|
-
}>;
|
|
10
|
-
}
|
|
11
|
-
type StateMachineDatabaseClient = unknown;
|
|
12
|
-
interface TransitionContext<TDb = StateMachineDatabaseClient> {
|
|
13
|
-
entity: Record<string, unknown>;
|
|
14
|
-
actionInvocationId: string;
|
|
15
|
-
actorId: string;
|
|
16
|
-
parameters: unknown;
|
|
17
|
-
db: TDb;
|
|
18
|
-
}
|
|
19
|
-
interface StateMachineRegistry {
|
|
20
|
-
register(definition: StateMachineDefinition): void;
|
|
21
|
-
registerIfMissing(definition: StateMachineDefinition): void;
|
|
22
|
-
resolve(entityType: string): StateMachineDefinition | undefined;
|
|
23
|
-
validateTransition(entityType: string, from: string, to: string, actionId: string): {
|
|
24
|
-
valid: boolean;
|
|
25
|
-
error?: string;
|
|
26
|
-
};
|
|
27
|
-
allowedTransitions(entityType: string, from: string): Array<{
|
|
28
|
-
to: string;
|
|
29
|
-
causedByAction: string;
|
|
30
|
-
}>;
|
|
31
|
-
stateClass(entityType: string, state: string): StateClass | undefined;
|
|
32
|
-
}
|
|
33
|
-
declare function validateStateMachineTransition(sm: StateMachineDefinition | undefined, entityType: string, from: string, to: string, actionId: string): {
|
|
34
|
-
valid: boolean;
|
|
35
|
-
error?: string;
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Validates a transition and evaluates its declared guard against trusted Host
|
|
39
|
-
* context. Callers must construct `context.entity` from authoritative state,
|
|
40
|
-
* never from action parameters.
|
|
41
|
-
*/
|
|
42
|
-
declare function evaluateStateMachineTransition<TDb = StateMachineDatabaseClient>(sm: StateMachineDefinition | undefined, entityType: string, from: string, to: string, actionId: string, context: TransitionContext<TDb>): Promise<{
|
|
43
|
-
valid: boolean;
|
|
44
|
-
error?: string;
|
|
45
|
-
}>;
|
|
46
|
-
declare function createStateMachineRegistry(definitions?: readonly StateMachineDefinition[]): StateMachineRegistry;
|
|
47
|
-
|
|
48
|
-
type StateClass = "initial" | "active" | "action_required" | "waiting" | "terminal" | "exception";
|
|
49
|
-
interface StateDefinition {
|
|
50
|
-
id: string;
|
|
51
|
-
label: string;
|
|
52
|
-
stateClass: StateClass;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export { type StateClass, type StateDefinition, type StateMachineDatabaseClient, type StateMachineDefinition, type StateMachineRegistry, type TransitionContext, createStateMachineRegistry, evaluateStateMachineTransition, validateStateMachineTransition };
|
|
1
|
+
import { a as StateMachineRegistry, c as evaluateStateMachineTransition, i as StateMachineDefinition, l as validateStateMachineTransition, n as StateDefinition, o as TransitionContext, r as StateMachineDatabaseClient, s as createStateMachineRegistry, t as StateClass } from "../index-fmUiRBXu.cjs";
|
|
2
|
+
export { StateClass, StateDefinition, StateMachineDatabaseClient, StateMachineDefinition, StateMachineRegistry, TransitionContext, createStateMachineRegistry, evaluateStateMachineTransition, validateStateMachineTransition };
|
|
@@ -1,55 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
states: Record<string, StateDefinition>;
|
|
4
|
-
transitions: Array<{
|
|
5
|
-
from: string;
|
|
6
|
-
to: string;
|
|
7
|
-
causedByAction: string;
|
|
8
|
-
guard?: (ctx: TransitionContext) => boolean | Promise<boolean>;
|
|
9
|
-
}>;
|
|
10
|
-
}
|
|
11
|
-
type StateMachineDatabaseClient = unknown;
|
|
12
|
-
interface TransitionContext<TDb = StateMachineDatabaseClient> {
|
|
13
|
-
entity: Record<string, unknown>;
|
|
14
|
-
actionInvocationId: string;
|
|
15
|
-
actorId: string;
|
|
16
|
-
parameters: unknown;
|
|
17
|
-
db: TDb;
|
|
18
|
-
}
|
|
19
|
-
interface StateMachineRegistry {
|
|
20
|
-
register(definition: StateMachineDefinition): void;
|
|
21
|
-
registerIfMissing(definition: StateMachineDefinition): void;
|
|
22
|
-
resolve(entityType: string): StateMachineDefinition | undefined;
|
|
23
|
-
validateTransition(entityType: string, from: string, to: string, actionId: string): {
|
|
24
|
-
valid: boolean;
|
|
25
|
-
error?: string;
|
|
26
|
-
};
|
|
27
|
-
allowedTransitions(entityType: string, from: string): Array<{
|
|
28
|
-
to: string;
|
|
29
|
-
causedByAction: string;
|
|
30
|
-
}>;
|
|
31
|
-
stateClass(entityType: string, state: string): StateClass | undefined;
|
|
32
|
-
}
|
|
33
|
-
declare function validateStateMachineTransition(sm: StateMachineDefinition | undefined, entityType: string, from: string, to: string, actionId: string): {
|
|
34
|
-
valid: boolean;
|
|
35
|
-
error?: string;
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Validates a transition and evaluates its declared guard against trusted Host
|
|
39
|
-
* context. Callers must construct `context.entity` from authoritative state,
|
|
40
|
-
* never from action parameters.
|
|
41
|
-
*/
|
|
42
|
-
declare function evaluateStateMachineTransition<TDb = StateMachineDatabaseClient>(sm: StateMachineDefinition | undefined, entityType: string, from: string, to: string, actionId: string, context: TransitionContext<TDb>): Promise<{
|
|
43
|
-
valid: boolean;
|
|
44
|
-
error?: string;
|
|
45
|
-
}>;
|
|
46
|
-
declare function createStateMachineRegistry(definitions?: readonly StateMachineDefinition[]): StateMachineRegistry;
|
|
47
|
-
|
|
48
|
-
type StateClass = "initial" | "active" | "action_required" | "waiting" | "terminal" | "exception";
|
|
49
|
-
interface StateDefinition {
|
|
50
|
-
id: string;
|
|
51
|
-
label: string;
|
|
52
|
-
stateClass: StateClass;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export { type StateClass, type StateDefinition, type StateMachineDatabaseClient, type StateMachineDefinition, type StateMachineRegistry, type TransitionContext, createStateMachineRegistry, evaluateStateMachineTransition, validateStateMachineTransition };
|
|
1
|
+
import { a as StateMachineRegistry, c as evaluateStateMachineTransition, i as StateMachineDefinition, l as validateStateMachineTransition, n as StateDefinition, o as TransitionContext, r as StateMachineDatabaseClient, s as createStateMachineRegistry, t as StateClass } from "../index-fmUiRBXu.js";
|
|
2
|
+
export { StateClass, StateDefinition, StateMachineDatabaseClient, StateMachineDefinition, StateMachineRegistry, TransitionContext, createStateMachineRegistry, evaluateStateMachineTransition, validateStateMachineTransition };
|
|
@@ -1,3 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region state-machines/engine.ts
|
|
2
|
+
function validateStateMachineTransition(sm, entityType, from, to, actionId) {
|
|
3
|
+
if (!sm) return {
|
|
4
|
+
valid: false,
|
|
5
|
+
error: `No state machine registered for entity type "${entityType}".`
|
|
6
|
+
};
|
|
7
|
+
if (!sm.states[from]) return {
|
|
8
|
+
valid: false,
|
|
9
|
+
error: `State "${from}" does not exist in state machine for entity type "${entityType}".`
|
|
10
|
+
};
|
|
11
|
+
if (!sm.states[to]) return {
|
|
12
|
+
valid: false,
|
|
13
|
+
error: `State "${to}" does not exist in state machine for entity type "${entityType}".`
|
|
14
|
+
};
|
|
15
|
+
return sm.transitions.some((transition) => transition.from === from && transition.to === to && transition.causedByAction === actionId) ? { valid: true } : {
|
|
16
|
+
valid: false,
|
|
17
|
+
error: `Transition from "${from}" to "${to}" via action "${actionId}" is not defined for entity type "${entityType}".`
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validates a transition and evaluates its declared guard against trusted Host
|
|
22
|
+
* context. Callers must construct `context.entity` from authoritative state,
|
|
23
|
+
* never from action parameters.
|
|
24
|
+
*/
|
|
25
|
+
async function evaluateStateMachineTransition(sm, entityType, from, to, actionId, context) {
|
|
26
|
+
const structural = validateStateMachineTransition(sm, entityType, from, to, actionId);
|
|
27
|
+
if (!structural.valid) return structural;
|
|
28
|
+
const transition = sm.transitions.find((candidate) => candidate.from === from && candidate.to === to && candidate.causedByAction === actionId);
|
|
29
|
+
if (!transition?.guard) return structural;
|
|
30
|
+
try {
|
|
31
|
+
return await transition.guard(context) ? structural : {
|
|
32
|
+
valid: false,
|
|
33
|
+
error: `Guard rejected transition from "${from}" to "${to}" via action "${actionId}" for entity type "${entityType}".`
|
|
34
|
+
};
|
|
35
|
+
} catch {
|
|
36
|
+
return {
|
|
37
|
+
valid: false,
|
|
38
|
+
error: `Guard failed while evaluating transition from "${from}" to "${to}" via action "${actionId}" for entity type "${entityType}".`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function createStateMachineRegistry(definitions = []) {
|
|
43
|
+
const machines = /* @__PURE__ */ new Map();
|
|
44
|
+
const register = (definition) => {
|
|
45
|
+
if (machines.has(definition.entityType)) throw new Error(`State machine for entity type "${definition.entityType}" is already registered.`);
|
|
46
|
+
machines.set(definition.entityType, definition);
|
|
47
|
+
};
|
|
48
|
+
const validateTransition = (entityType, from, to, actionId) => validateStateMachineTransition(machines.get(entityType), entityType, from, to, actionId);
|
|
49
|
+
for (const definition of definitions) register(definition);
|
|
50
|
+
return {
|
|
51
|
+
register,
|
|
52
|
+
registerIfMissing(definition) {
|
|
53
|
+
if (!machines.has(definition.entityType)) register(definition);
|
|
54
|
+
},
|
|
55
|
+
resolve: (entityType) => machines.get(entityType),
|
|
56
|
+
validateTransition,
|
|
57
|
+
allowedTransitions(entityType, from) {
|
|
58
|
+
const sm = machines.get(entityType);
|
|
59
|
+
if (!sm) return [];
|
|
60
|
+
return sm.transitions.filter((t) => t.from === from).map((t) => ({
|
|
61
|
+
to: t.to,
|
|
62
|
+
causedByAction: t.causedByAction
|
|
63
|
+
}));
|
|
64
|
+
},
|
|
65
|
+
stateClass(entityType, state) {
|
|
66
|
+
const sm = machines.get(entityType);
|
|
67
|
+
if (!sm) return;
|
|
68
|
+
return sm.states[state]?.stateClass;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { createStateMachineRegistry, evaluateStateMachineTransition, validateStateMachineTransition };
|
|
74
|
+
|
|
3
75
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../state-machines/engine.ts"],"sourcesContent":["import type { StateClass, StateDefinition } from \"./index\";\n\nexport interface StateMachineDefinition {\n\tentityType: string;\n\tstates: Record<string, StateDefinition>;\n\ttransitions: Array<{\n\t\tfrom: string;\n\t\tto: string;\n\t\tcausedByAction: string;\n\t\tguard?: (ctx: TransitionContext) => boolean | Promise<boolean>;\n\t}>;\n}\n\nexport type StateMachineDatabaseClient = unknown;\n\nexport interface TransitionContext<TDb = StateMachineDatabaseClient> {\n\tentity: Record<string, unknown>;\n\tactionInvocationId: string;\n\tactorId: string;\n\tparameters: unknown;\n\tdb: TDb;\n}\n\nexport interface StateMachineRegistry {\n\tregister(definition: StateMachineDefinition): void;\n\tregisterIfMissing(definition: StateMachineDefinition): void;\n\tresolve(entityType: string): StateMachineDefinition | undefined;\n\tvalidateTransition(entityType: string, from: string, to: string, actionId: string): { valid: boolean; error?: string };\n\tallowedTransitions(entityType: string, from: string): Array<{ to: string; causedByAction: string }>;\n\tstateClass(entityType: string, state: string): StateClass | undefined;\n}\n\nexport function validateStateMachineTransition(\n\tsm: StateMachineDefinition | undefined,\n\tentityType: string,\n\tfrom: string,\n\tto: string,\n\tactionId: string,\n): { valid: boolean; error?: string } {\n\tif (!sm) return { valid: false, error: `No state machine registered for entity type \"${entityType}\".` };\n\tconst fromDef = sm.states[from];\n\tif (!fromDef) return { valid: false, error: `State \"${from}\" does not exist in state machine for entity type \"${entityType}\".` };\n\tconst toDef = sm.states[to];\n\tif (!toDef) return { valid: false, error: `State \"${to}\" does not exist in state machine for entity type \"${entityType}\".` };\n\tconst transitionExists = sm.transitions.some((transition) => transition.from === from && transition.to === to && transition.causedByAction === actionId);\n\treturn transitionExists\n\t\t? { valid: true }\n\t\t: { valid: false, error: `Transition from \"${from}\" to \"${to}\" via action \"${actionId}\" is not defined for entity type \"${entityType}\".` };\n}\n\n/**\n * Validates a transition and evaluates its declared guard against trusted Host\n * context. Callers must construct `context.entity` from authoritative state,\n * never from action parameters.\n */\nexport async function evaluateStateMachineTransition<TDb = StateMachineDatabaseClient>(\n\tsm: StateMachineDefinition | undefined,\n\tentityType: string,\n\tfrom: string,\n\tto: string,\n\tactionId: string,\n\tcontext: TransitionContext<TDb>,\n): Promise<{ valid: boolean; error?: string }> {\n\tconst structural = validateStateMachineTransition(sm, entityType, from, to, actionId);\n\tif (!structural.valid) return structural;\n\tconst transition = sm!.transitions.find(\n\t\t(candidate) =>\n\t\t\tcandidate.from === from &&\n\t\t\tcandidate.to === to &&\n\t\t\tcandidate.causedByAction === actionId,\n\t);\n\tif (!transition?.guard) return structural;\n\ttry {\n\t\treturn await transition.guard(context)\n\t\t\t? structural\n\t\t\t: {\n\t\t\t\t\tvalid: false,\n\t\t\t\t\terror: `Guard rejected transition from \"${from}\" to \"${to}\" via action \"${actionId}\" for entity type \"${entityType}\".`,\n\t\t\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `Guard failed while evaluating transition from \"${from}\" to \"${to}\" via action \"${actionId}\" for entity type \"${entityType}\".`,\n\t\t};\n\t}\n}\n\nexport function createStateMachineRegistry(definitions: readonly StateMachineDefinition[] = []): StateMachineRegistry {\n\tconst machines = new Map<string, StateMachineDefinition>();\n\tconst register = (definition: StateMachineDefinition): void => {\n\t\tif (machines.has(definition.entityType)) throw new Error(`State machine for entity type \"${definition.entityType}\" is already registered.`);\n\t\tmachines.set(definition.entityType, definition);\n\t};\n\tconst validateTransition = (entityType: string, from: string, to: string, actionId: string) =>\n\t\tvalidateStateMachineTransition(machines.get(entityType), entityType, from, to, actionId);\n\tfor (const definition of definitions) register(definition);\n\treturn {\n\t\tregister,\n\t\tregisterIfMissing(definition) {\n\t\t\tif (!machines.has(definition.entityType)) register(definition);\n\t\t},\n\t\tresolve: (entityType) => machines.get(entityType),\n\t\tvalidateTransition,\n\t\tallowedTransitions(entityType, from) {\n\tconst sm = machines.get(entityType);\n\tif (!sm) {\n\t\treturn [];\n\t}\n\treturn sm.transitions\n\t\t.filter((t) => t.from === from)\n\t\t.map((t) => ({ to: t.to, causedByAction: t.causedByAction }));\n\t\t},\n\t\tstateClass(entityType, state) {\n\tconst sm = machines.get(entityType);\n\tif (!sm) {\n\t\treturn undefined;\n\t}\n\treturn sm.states[state]?.stateClass;\n\t\t},\n\t};\n}\n"],"mappings":";AAgCA,SAAgB,+BACf,IACA,YACA,MACA,IACA,UACqC;CACrC,IAAI,CAAC,IAAI,OAAO;EAAE,OAAO;EAAO,OAAO,gDAAgD,WAAW;CAAI;CAEtG,IAAI,CADY,GAAG,OAAO,OACZ,OAAO;EAAE,OAAO;EAAO,OAAO,UAAU,KAAK,qDAAqD,WAAW;CAAI;CAE/H,IAAI,CADU,GAAG,OAAO,KACZ,OAAO;EAAE,OAAO;EAAO,OAAO,UAAU,GAAG,qDAAqD,WAAW;CAAI;CAE3H,OADyB,GAAG,YAAY,MAAM,eAAe,WAAW,SAAS,QAAQ,WAAW,OAAO,MAAM,WAAW,mBAAmB,QACzH,IACnB,EAAE,OAAO,KAAK,IACd;EAAE,OAAO;EAAO,OAAO,oBAAoB,KAAK,QAAQ,GAAG,gBAAgB,SAAS,oCAAoC,WAAW;CAAI;AAC3I;;;;;;AAOA,eAAsB,+BACrB,IACA,YACA,MACA,IACA,UACA,SAC8C;CAC9C,MAAM,aAAa,+BAA+B,IAAI,YAAY,MAAM,IAAI,QAAQ;CACpF,IAAI,CAAC,WAAW,OAAO,OAAO;CAC9B,MAAM,aAAa,GAAI,YAAY,MACjC,cACA,UAAU,SAAS,QACnB,UAAU,OAAO,MACjB,UAAU,mBAAmB,QAC/B;CACA,IAAI,CAAC,YAAY,OAAO,OAAO;CAC/B,IAAI;EACH,OAAO,MAAM,WAAW,MAAM,OAAO,IAClC,aACA;GACA,OAAO;GACP,OAAO,mCAAmC,KAAK,QAAQ,GAAG,gBAAgB,SAAS,qBAAqB,WAAW;EACpH;CACH,QAAQ;EACP,OAAO;GACN,OAAO;GACP,OAAO,kDAAkD,KAAK,QAAQ,GAAG,gBAAgB,SAAS,qBAAqB,WAAW;EACnI;CACD;AACD;AAEA,SAAgB,2BAA2B,cAAiD,CAAC,GAAyB;CACrH,MAAM,2BAAW,IAAI,IAAoC;CACzD,MAAM,YAAY,eAA6C;EAC9D,IAAI,SAAS,IAAI,WAAW,UAAU,GAAG,MAAM,IAAI,MAAM,kCAAkC,WAAW,WAAW,yBAAyB;EAC1I,SAAS,IAAI,WAAW,YAAY,UAAU;CAC/C;CACA,MAAM,sBAAsB,YAAoB,MAAc,IAAY,aACzE,+BAA+B,SAAS,IAAI,UAAU,GAAG,YAAY,MAAM,IAAI,QAAQ;CACxF,KAAK,MAAM,cAAc,aAAa,SAAS,UAAU;CACzD,OAAO;EACN;EACA,kBAAkB,YAAY;GAC7B,IAAI,CAAC,SAAS,IAAI,WAAW,UAAU,GAAG,SAAS,UAAU;EAC9D;EACA,UAAU,eAAe,SAAS,IAAI,UAAU;EAChD;EACA,mBAAmB,YAAY,MAAM;GACtC,MAAM,KAAK,SAAS,IAAI,UAAU;GAClC,IAAI,CAAC,IACJ,OAAO,CAAC;GAET,OAAO,GAAG,YACR,QAAQ,MAAM,EAAE,SAAS,IAAI,CAAC,CAC9B,KAAK,OAAO;IAAE,IAAI,EAAE;IAAI,gBAAgB,EAAE;GAAe,EAAE;EAC5D;EACA,WAAW,YAAY,OAAO;GAC/B,MAAM,KAAK,SAAS,IAAI,UAAU;GAClC,IAAI,CAAC,IACJ;GAED,OAAO,GAAG,OAAO,MAAM,EAAE;EACxB;CACD;AACD"}
|