@graphrefly/ts 0.0.0
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/LICENSE +21 -0
- package/dist/adapters/index.cjs +9015 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.cts +678 -0
- package/dist/adapters/index.d.ts +678 -0
- package/dist/adapters/index.js +2894 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/observe-storage.cjs +260 -0
- package/dist/adapters/observe-storage.cjs.map +1 -0
- package/dist/adapters/observe-storage.d.cts +77 -0
- package/dist/adapters/observe-storage.d.ts +77 -0
- package/dist/adapters/observe-storage.js +161 -0
- package/dist/adapters/observe-storage.js.map +1 -0
- package/dist/append-log-DisvEReb.d.cts +189 -0
- package/dist/append-log-DzKMmjxx.d.ts +189 -0
- package/dist/chunk-2ILL7HQI.js +1189 -0
- package/dist/chunk-2ILL7HQI.js.map +1 -0
- package/dist/chunk-4VLAQHZI.js +330 -0
- package/dist/chunk-4VLAQHZI.js.map +1 -0
- package/dist/chunk-6PHB5CJS.js +229 -0
- package/dist/chunk-6PHB5CJS.js.map +1 -0
- package/dist/chunk-COHO25CZ.js +2449 -0
- package/dist/chunk-COHO25CZ.js.map +1 -0
- package/dist/chunk-D6BG7U6A.js +161 -0
- package/dist/chunk-D6BG7U6A.js.map +1 -0
- package/dist/chunk-DJC4CHLQ.js +818 -0
- package/dist/chunk-DJC4CHLQ.js.map +1 -0
- package/dist/chunk-DK554R7P.js +67 -0
- package/dist/chunk-DK554R7P.js.map +1 -0
- package/dist/chunk-DKP56CSW.js +322 -0
- package/dist/chunk-DKP56CSW.js.map +1 -0
- package/dist/chunk-G4M3NHRJ.js +688 -0
- package/dist/chunk-G4M3NHRJ.js.map +1 -0
- package/dist/chunk-GAJW5I5P.js +1 -0
- package/dist/chunk-GAJW5I5P.js.map +1 -0
- package/dist/chunk-GDL26N2T.js +282 -0
- package/dist/chunk-GDL26N2T.js.map +1 -0
- package/dist/chunk-GTPCOEOZ.js +741 -0
- package/dist/chunk-GTPCOEOZ.js.map +1 -0
- package/dist/chunk-H2HTHFVN.js +1 -0
- package/dist/chunk-H2HTHFVN.js.map +1 -0
- package/dist/chunk-HDRL4VQQ.js +218 -0
- package/dist/chunk-HDRL4VQQ.js.map +1 -0
- package/dist/chunk-I3IHOHCJ.js +2267 -0
- package/dist/chunk-I3IHOHCJ.js.map +1 -0
- package/dist/chunk-JIMUDQN3.js +965 -0
- package/dist/chunk-JIMUDQN3.js.map +1 -0
- package/dist/chunk-JR4IKCR2.js +462 -0
- package/dist/chunk-JR4IKCR2.js.map +1 -0
- package/dist/chunk-K3FXTC3I.js +165 -0
- package/dist/chunk-K3FXTC3I.js.map +1 -0
- package/dist/chunk-OOEN33FB.js +65 -0
- package/dist/chunk-OOEN33FB.js.map +1 -0
- package/dist/chunk-PKVBHGBM.js +554 -0
- package/dist/chunk-PKVBHGBM.js.map +1 -0
- package/dist/chunk-RIIOUTGM.js +1857 -0
- package/dist/chunk-RIIOUTGM.js.map +1 -0
- package/dist/chunk-SCRFRCGR.js +33 -0
- package/dist/chunk-SCRFRCGR.js.map +1 -0
- package/dist/chunk-SWTLEQL6.js +53 -0
- package/dist/chunk-SWTLEQL6.js.map +1 -0
- package/dist/chunk-SZAETNNA.js +1 -0
- package/dist/chunk-SZAETNNA.js.map +1 -0
- package/dist/chunk-TEW47IVR.js +49 -0
- package/dist/chunk-TEW47IVR.js.map +1 -0
- package/dist/chunk-Y3ZLRGHC.js +1482 -0
- package/dist/chunk-Y3ZLRGHC.js.map +1 -0
- package/dist/chunk-Z2ANQ6JW.js +149 -0
- package/dist/chunk-Z2ANQ6JW.js.map +1 -0
- package/dist/codec-BB7p8fJc.d.cts +38 -0
- package/dist/codec-BB7p8fJc.d.ts +38 -0
- package/dist/composition/index.cjs +2505 -0
- package/dist/composition/index.cjs.map +1 -0
- package/dist/composition/index.d.cts +95 -0
- package/dist/composition/index.d.ts +95 -0
- package/dist/composition/index.js +19 -0
- package/dist/composition/index.js.map +1 -0
- package/dist/core/index.cjs +2475 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +25 -0
- package/dist/core/index.d.ts +25 -0
- package/dist/core/index.js +74 -0
- package/dist/core/index.js.map +1 -0
- package/dist/cqrs/index.cjs +595 -0
- package/dist/cqrs/index.cjs.map +1 -0
- package/dist/cqrs/index.d.cts +200 -0
- package/dist/cqrs/index.d.ts +200 -0
- package/dist/cqrs/index.js +557 -0
- package/dist/cqrs/index.js.map +1 -0
- package/dist/data-structures/index.cjs +4277 -0
- package/dist/data-structures/index.cjs.map +1 -0
- package/dist/data-structures/index.d.cts +22 -0
- package/dist/data-structures/index.d.ts +22 -0
- package/dist/data-structures/index.js +31 -0
- package/dist/data-structures/index.js.map +1 -0
- package/dist/describe-BZb7AS2y.d.ts +57 -0
- package/dist/describe-Ciu8SUnN.d.cts +57 -0
- package/dist/graph/index.cjs +7832 -0
- package/dist/graph/index.cjs.map +1 -0
- package/dist/graph/index.d.cts +251 -0
- package/dist/graph/index.d.ts +251 -0
- package/dist/graph/index.js +131 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph-DfdF9zy4.d.ts +666 -0
- package/dist/graph-s23RbbFO.d.cts +666 -0
- package/dist/index-CAEmLCYF.d.cts +166 -0
- package/dist/index-SnzZu3x7.d.ts +166 -0
- package/dist/index.cjs +15409 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +582 -0
- package/dist/index.js.map +1 -0
- package/dist/messages-BZqrGGm1.d.cts +55 -0
- package/dist/messages-BZqrGGm1.d.ts +55 -0
- package/dist/messaging/index.cjs +818 -0
- package/dist/messaging/index.cjs.map +1 -0
- package/dist/messaging/index.d.cts +235 -0
- package/dist/messaging/index.d.ts +235 -0
- package/dist/messaging/index.js +767 -0
- package/dist/messaging/index.js.map +1 -0
- package/dist/observe-event-log-0SVsx7JG.d.cts +88 -0
- package/dist/observe-event-log-Dnm76owp.d.ts +88 -0
- package/dist/operators/index.cjs +3774 -0
- package/dist/operators/index.cjs.map +1 -0
- package/dist/operators/index.d.cts +308 -0
- package/dist/operators/index.d.ts +308 -0
- package/dist/operators/index.js +112 -0
- package/dist/operators/index.js.map +1 -0
- package/dist/orchestration/index.cjs +3541 -0
- package/dist/orchestration/index.cjs.map +1 -0
- package/dist/orchestration/index.d.cts +302 -0
- package/dist/orchestration/index.d.ts +302 -0
- package/dist/orchestration/index.js +1004 -0
- package/dist/orchestration/index.js.map +1 -0
- package/dist/patterns/index.cjs +1045 -0
- package/dist/patterns/index.cjs.map +1 -0
- package/dist/patterns/index.d.cts +39 -0
- package/dist/patterns/index.d.ts +39 -0
- package/dist/patterns/index.js +33 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/reactive-collection-BxBqtIOE.d.cts +344 -0
- package/dist/reactive-collection-Cb1II1Fi.d.ts +344 -0
- package/dist/reactive-collection-storage-BNiEc-Gd.d.ts +217 -0
- package/dist/reactive-collection-storage-D-KmjE0J.d.cts +217 -0
- package/dist/reactive-map-BcB4KsQR.d.ts +256 -0
- package/dist/reactive-map-D-g3dv8I.d.cts +256 -0
- package/dist/read-through-B1CYPfq_.d.cts +82 -0
- package/dist/read-through-DwyJhGs1.d.ts +82 -0
- package/dist/render/index.cjs +250 -0
- package/dist/render/index.cjs.map +1 -0
- package/dist/render/index.d.cts +58 -0
- package/dist/render/index.d.ts +58 -0
- package/dist/render/index.js +20 -0
- package/dist/render/index.js.map +1 -0
- package/dist/resilience-Dig3Q7n0.d.cts +45 -0
- package/dist/resilience-Dig3Q7n0.d.ts +45 -0
- package/dist/semantic-memory-graph-BYoIlP35.d.cts +356 -0
- package/dist/semantic-memory-graph-CxyXLms3.d.ts +356 -0
- package/dist/solutions/index.cjs +3249 -0
- package/dist/solutions/index.cjs.map +1 -0
- package/dist/solutions/index.d.cts +520 -0
- package/dist/solutions/index.d.ts +520 -0
- package/dist/solutions/index.js +31 -0
- package/dist/solutions/index.js.map +1 -0
- package/dist/sources/browser.cjs +3095 -0
- package/dist/sources/browser.cjs.map +1 -0
- package/dist/sources/browser.d.cts +39 -0
- package/dist/sources/browser.d.ts +39 -0
- package/dist/sources/browser.js +137 -0
- package/dist/sources/browser.js.map +1 -0
- package/dist/sources/index.cjs +3021 -0
- package/dist/sources/index.cjs.map +1 -0
- package/dist/sources/index.d.cts +7 -0
- package/dist/sources/index.d.ts +7 -0
- package/dist/sources/index.js +63 -0
- package/dist/sources/index.js.map +1 -0
- package/dist/sources/node.cjs +675 -0
- package/dist/sources/node.cjs.map +1 -0
- package/dist/sources/node.d.cts +109 -0
- package/dist/sources/node.d.ts +109 -0
- package/dist/sources/node.js +639 -0
- package/dist/sources/node.js.map +1 -0
- package/dist/storage/browser.cjs +777 -0
- package/dist/storage/browser.cjs.map +1 -0
- package/dist/storage/browser.d.cts +40 -0
- package/dist/storage/browser.d.ts +40 -0
- package/dist/storage/browser.js +275 -0
- package/dist/storage/browser.js.map +1 -0
- package/dist/storage/index.cjs +1989 -0
- package/dist/storage/index.cjs.map +1 -0
- package/dist/storage/index.d.cts +92 -0
- package/dist/storage/index.d.ts +92 -0
- package/dist/storage/index.js +161 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/node.cjs +898 -0
- package/dist/storage/node.cjs.map +1 -0
- package/dist/storage/node.d.cts +66 -0
- package/dist/storage/node.d.ts +66 -0
- package/dist/storage/node.js +402 -0
- package/dist/storage/node.js.map +1 -0
- package/dist/testing/index.cjs +95 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +20 -0
- package/dist/testing/index.d.ts +20 -0
- package/dist/testing/index.js +8 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types-bNMSWKqF.d.cts +732 -0
- package/dist/types-h12mNYbq.d.ts +732 -0
- package/package.json +250 -0
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
import {
|
|
2
|
+
timeout
|
|
3
|
+
} from "../chunk-JR4IKCR2.js";
|
|
4
|
+
import {
|
|
5
|
+
backoffDelayMs,
|
|
6
|
+
nextRetryDelayMs,
|
|
7
|
+
noBackoff,
|
|
8
|
+
retryPolicy,
|
|
9
|
+
shouldRetry
|
|
10
|
+
} from "../chunk-OOEN33FB.js";
|
|
11
|
+
import "../chunk-G4M3NHRJ.js";
|
|
12
|
+
import "../chunk-PKVBHGBM.js";
|
|
13
|
+
import "../chunk-I3IHOHCJ.js";
|
|
14
|
+
import "../chunk-4VLAQHZI.js";
|
|
15
|
+
import {
|
|
16
|
+
depBatch
|
|
17
|
+
} from "../chunk-SWTLEQL6.js";
|
|
18
|
+
import "../chunk-DK554R7P.js";
|
|
19
|
+
|
|
20
|
+
// src/orchestration/index.ts
|
|
21
|
+
var processCommandSources = /* @__PURE__ */ new WeakMap();
|
|
22
|
+
function retryStatusBundle(graph, events, opts = {}) {
|
|
23
|
+
const policy = opts.policy ?? retryPolicy();
|
|
24
|
+
const name = opts.name ?? "retry";
|
|
25
|
+
const status = graph.node(
|
|
26
|
+
[events],
|
|
27
|
+
(ctx) => {
|
|
28
|
+
let next = ctx.state.get() ?? { state: "idle", attempt: 0, maxAttempts: policy.maxAttempts };
|
|
29
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
30
|
+
const event = raw;
|
|
31
|
+
if (event.kind === "attempt") {
|
|
32
|
+
next = { state: "running", attempt: event.attempt, maxAttempts: policy.maxAttempts };
|
|
33
|
+
} else if (event.kind === "retry") {
|
|
34
|
+
next = {
|
|
35
|
+
state: "waiting",
|
|
36
|
+
attempt: event.attempt,
|
|
37
|
+
maxAttempts: policy.maxAttempts,
|
|
38
|
+
delayMs: event.delayMs
|
|
39
|
+
};
|
|
40
|
+
} else if (event.kind === "success") {
|
|
41
|
+
next = {
|
|
42
|
+
state: "succeeded",
|
|
43
|
+
attempt: event.attempt ?? next.attempt,
|
|
44
|
+
maxAttempts: policy.maxAttempts
|
|
45
|
+
};
|
|
46
|
+
} else if (event.kind === "failure") {
|
|
47
|
+
const attempt = event.attempt ?? next.attempt;
|
|
48
|
+
next = {
|
|
49
|
+
state: shouldRetry(policy, attempt) ? "failed" : "exhausted",
|
|
50
|
+
attempt,
|
|
51
|
+
maxAttempts: policy.maxAttempts,
|
|
52
|
+
delayMs: shouldRetry(policy, attempt) ? nextRetryDelayMs(policy, attempt + 1) : void 0
|
|
53
|
+
};
|
|
54
|
+
} else {
|
|
55
|
+
next = {
|
|
56
|
+
state: "exhausted",
|
|
57
|
+
attempt: event.attempt,
|
|
58
|
+
maxAttempts: policy.maxAttempts
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
ctx.state.set(next);
|
|
63
|
+
ctx.down([["DATA", next]]);
|
|
64
|
+
},
|
|
65
|
+
{ name: `${name}/status`, factory: "retryStatus" }
|
|
66
|
+
);
|
|
67
|
+
const errors = graph.node(
|
|
68
|
+
[events],
|
|
69
|
+
(ctx) => {
|
|
70
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
71
|
+
const event = raw;
|
|
72
|
+
if ("error" in event) ctx.down([["DATA", event.error]]);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{ name: `${name}/errors`, factory: "retryErrors" }
|
|
76
|
+
);
|
|
77
|
+
return { status, errors };
|
|
78
|
+
}
|
|
79
|
+
function breakerBundle(graph, events, opts) {
|
|
80
|
+
if (!Number.isInteger(opts.failureThreshold) || opts.failureThreshold <= 0) {
|
|
81
|
+
throw new RangeError("breakerBundle: failureThreshold must be a positive integer");
|
|
82
|
+
}
|
|
83
|
+
const name = opts.name ?? "breaker";
|
|
84
|
+
const now = opts.now ?? Date.now;
|
|
85
|
+
const status = graph.node(
|
|
86
|
+
[events],
|
|
87
|
+
(ctx) => {
|
|
88
|
+
let next = ctx.state.get() ?? { state: "closed", failures: 0 };
|
|
89
|
+
if (next.state === "open" && opts.resetAfterMs !== void 0 && next.openedAtMs !== void 0 && now() - next.openedAtMs >= opts.resetAfterMs) {
|
|
90
|
+
next = { ...next, state: "half-open" };
|
|
91
|
+
}
|
|
92
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
93
|
+
const event = raw;
|
|
94
|
+
if (event.kind === "success") {
|
|
95
|
+
next = { state: "closed", failures: 0 };
|
|
96
|
+
} else if (event.kind === "failure" || event.kind === "exhausted") {
|
|
97
|
+
const failures = next.failures + 1;
|
|
98
|
+
next = failures >= opts.failureThreshold ? { state: "open", failures, openedAtMs: now() } : { ...next, failures };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
ctx.state.set(next);
|
|
102
|
+
ctx.down([["DATA", next]]);
|
|
103
|
+
},
|
|
104
|
+
{ name: `${name}/status`, factory: "breakerStatus" }
|
|
105
|
+
);
|
|
106
|
+
const allowed = graph.node(
|
|
107
|
+
[status],
|
|
108
|
+
(ctx) => {
|
|
109
|
+
const statusBatch = depBatch(ctx, 0) ?? [];
|
|
110
|
+
for (const value of statusBatch) {
|
|
111
|
+
ctx.down([["DATA", value.state !== "open"]]);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{ name: `${name}/allowed`, factory: "breakerAllowed" }
|
|
115
|
+
);
|
|
116
|
+
return { status, allowed };
|
|
117
|
+
}
|
|
118
|
+
function rateLimitBundle(graph, source, opts) {
|
|
119
|
+
if (!Number.isInteger(opts.max) || opts.max <= 0) {
|
|
120
|
+
throw new RangeError("rateLimitBundle: max must be a positive integer");
|
|
121
|
+
}
|
|
122
|
+
if (!Number.isFinite(opts.windowMs) || opts.windowMs <= 0) {
|
|
123
|
+
throw new RangeError("rateLimitBundle: windowMs must be positive");
|
|
124
|
+
}
|
|
125
|
+
const now = opts.now ?? Date.now;
|
|
126
|
+
const name = opts.name ?? "rateLimit";
|
|
127
|
+
const events = graph.node(
|
|
128
|
+
[source],
|
|
129
|
+
(ctx) => {
|
|
130
|
+
const current = now();
|
|
131
|
+
let state = ctx.state.get() ?? { count: 0, resetAtMs: current + opts.windowMs, allowed: 0, dropped: 0 };
|
|
132
|
+
if (current >= state.resetAtMs) {
|
|
133
|
+
state = { ...state, count: 0, resetAtMs: current + opts.windowMs };
|
|
134
|
+
}
|
|
135
|
+
for (const value of depBatch(ctx, 0) ?? []) {
|
|
136
|
+
const allowed2 = state.count < opts.max;
|
|
137
|
+
state = allowed2 ? { ...state, count: state.count + 1, allowed: state.allowed + 1 } : { ...state, dropped: state.dropped + 1 };
|
|
138
|
+
const status2 = {
|
|
139
|
+
allowed: state.allowed,
|
|
140
|
+
dropped: state.dropped,
|
|
141
|
+
remaining: Math.max(0, opts.max - state.count),
|
|
142
|
+
resetAtMs: state.resetAtMs
|
|
143
|
+
};
|
|
144
|
+
ctx.down([
|
|
145
|
+
[
|
|
146
|
+
"DATA",
|
|
147
|
+
allowed2 ? { kind: "allowed", value, status: status2 } : { kind: "dropped", value, status: status2 }
|
|
148
|
+
]
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
ctx.state.set(state);
|
|
152
|
+
},
|
|
153
|
+
{ name: `${name}/events`, factory: "rateLimitEvents" }
|
|
154
|
+
);
|
|
155
|
+
const allowed = graph.node(
|
|
156
|
+
[events],
|
|
157
|
+
(ctx) => {
|
|
158
|
+
for (const event of depBatch(ctx, 0) ?? []) {
|
|
159
|
+
const typed = event;
|
|
160
|
+
if (typed.kind === "allowed") ctx.down([["DATA", typed.value]]);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{ name: `${name}/allowed`, factory: "rateLimitAllowed" }
|
|
164
|
+
);
|
|
165
|
+
const dropped = graph.node(
|
|
166
|
+
[events],
|
|
167
|
+
(ctx) => {
|
|
168
|
+
for (const event of depBatch(ctx, 0) ?? []) {
|
|
169
|
+
const typed = event;
|
|
170
|
+
if (typed.kind === "dropped") ctx.down([["DATA", typed.value]]);
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{ name: `${name}/dropped`, factory: "rateLimitDropped" }
|
|
174
|
+
);
|
|
175
|
+
const status = graph.node(
|
|
176
|
+
[events],
|
|
177
|
+
(ctx) => {
|
|
178
|
+
for (const event of depBatch(ctx, 0) ?? []) {
|
|
179
|
+
ctx.down([["DATA", event.status]]);
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{ name: `${name}/status`, factory: "rateLimitStatus" }
|
|
183
|
+
);
|
|
184
|
+
return { allowed, dropped, status };
|
|
185
|
+
}
|
|
186
|
+
function timeoutBundle(graph, source, ms, opts = {}) {
|
|
187
|
+
const name = opts.name ?? "timeout";
|
|
188
|
+
const node = timeout(source, ms);
|
|
189
|
+
const status = graph.node(
|
|
190
|
+
[node],
|
|
191
|
+
(ctx) => {
|
|
192
|
+
const terminal = ctx.terminal[0];
|
|
193
|
+
if (terminal === true) ctx.down([["DATA", "completed"]]);
|
|
194
|
+
else if (terminal !== false && terminal !== void 0) ctx.down([["DATA", "errored"]]);
|
|
195
|
+
else if ((depBatch(ctx, 0) ?? []).length > 0) ctx.down([["DATA", "running"]]);
|
|
196
|
+
},
|
|
197
|
+
{ name: `${name}/status`, factory: "timeoutStatus" }
|
|
198
|
+
);
|
|
199
|
+
const errors = graph.node(
|
|
200
|
+
[node],
|
|
201
|
+
(ctx) => {
|
|
202
|
+
const terminal = ctx.terminal[0];
|
|
203
|
+
if (terminal !== true && terminal !== false && terminal !== void 0) {
|
|
204
|
+
ctx.down([["DATA", terminal]]);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{ name: `${name}/errors`, factory: "timeoutErrors" }
|
|
208
|
+
);
|
|
209
|
+
return { node, status, errors };
|
|
210
|
+
}
|
|
211
|
+
function processBundle(graph, opts) {
|
|
212
|
+
if (typeof opts.reduce !== "function") {
|
|
213
|
+
throw new TypeError("processBundle: reduce must be a function");
|
|
214
|
+
}
|
|
215
|
+
if (opts.initialState === void 0) {
|
|
216
|
+
throw new TypeError("processBundle: initialState must not be undefined");
|
|
217
|
+
}
|
|
218
|
+
const name = opts.name ?? "process";
|
|
219
|
+
const now = opts.now ?? Date.now;
|
|
220
|
+
const topology = graph.topologyGroup({ name: `${name}.process` });
|
|
221
|
+
const command = topology.node([], null, {
|
|
222
|
+
name: `${name}/command`,
|
|
223
|
+
factory: "processCommand",
|
|
224
|
+
completeWhenDepsComplete: false,
|
|
225
|
+
errorWhenDepsError: false
|
|
226
|
+
});
|
|
227
|
+
const runtime = topology.node(
|
|
228
|
+
[command],
|
|
229
|
+
(ctx) => {
|
|
230
|
+
let state2 = ctx.state.get() ?? {
|
|
231
|
+
eventSeq: 0,
|
|
232
|
+
effectSeq: 0,
|
|
233
|
+
commandCount: 0,
|
|
234
|
+
errorCount: 0,
|
|
235
|
+
auditSeq: 0,
|
|
236
|
+
seenEventIds: [],
|
|
237
|
+
seenEffectIds: [],
|
|
238
|
+
state: opts.initialState
|
|
239
|
+
};
|
|
240
|
+
ctx.state.persist(true);
|
|
241
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
242
|
+
state2 = reduceProcessCommandFact(
|
|
243
|
+
raw,
|
|
244
|
+
state2,
|
|
245
|
+
opts.reduce,
|
|
246
|
+
now,
|
|
247
|
+
(fact) => ctx.down([["DATA", fact]])
|
|
248
|
+
);
|
|
249
|
+
ctx.state.set(state2);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: `${name}/runtime`,
|
|
254
|
+
factory: "processRuntime",
|
|
255
|
+
meta: { process: "facts-plus-reducer", d: "D136" },
|
|
256
|
+
completeWhenDepsComplete: false,
|
|
257
|
+
errorWhenDepsError: false
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
const events = processProjection(
|
|
261
|
+
topology,
|
|
262
|
+
runtime,
|
|
263
|
+
`${name}/events`,
|
|
264
|
+
"processEvents",
|
|
265
|
+
(fact) => fact.kind === "event" ? fact.event : void 0
|
|
266
|
+
);
|
|
267
|
+
const state = processProjection(
|
|
268
|
+
topology,
|
|
269
|
+
runtime,
|
|
270
|
+
`${name}/state`,
|
|
271
|
+
"processState",
|
|
272
|
+
(fact) => fact.kind === "state" ? fact.state : void 0
|
|
273
|
+
);
|
|
274
|
+
const audit = processProjection(
|
|
275
|
+
topology,
|
|
276
|
+
runtime,
|
|
277
|
+
`${name}/audit`,
|
|
278
|
+
"processAudit",
|
|
279
|
+
(fact) => fact.kind === "audit" ? fact.audit : void 0
|
|
280
|
+
);
|
|
281
|
+
const effectRequests = processProjection(
|
|
282
|
+
topology,
|
|
283
|
+
runtime,
|
|
284
|
+
`${name}/effectRequests`,
|
|
285
|
+
"processEffectRequests",
|
|
286
|
+
(fact) => fact.kind === "effect-request" ? fact.effect : void 0
|
|
287
|
+
);
|
|
288
|
+
const status = processProjection(
|
|
289
|
+
topology,
|
|
290
|
+
runtime,
|
|
291
|
+
`${name}/status`,
|
|
292
|
+
"processStatus",
|
|
293
|
+
(fact) => fact.kind === "status" ? fact.status : void 0
|
|
294
|
+
);
|
|
295
|
+
const errors = processProjection(
|
|
296
|
+
topology,
|
|
297
|
+
runtime,
|
|
298
|
+
`${name}/errors`,
|
|
299
|
+
"processErrors",
|
|
300
|
+
(fact) => fact.kind === "error" ? fact.error : void 0
|
|
301
|
+
);
|
|
302
|
+
const cursor = processProjection(
|
|
303
|
+
topology,
|
|
304
|
+
runtime,
|
|
305
|
+
`${name}/cursor`,
|
|
306
|
+
"processCursor",
|
|
307
|
+
(fact) => fact.kind === "cursor" ? fact.cursor : void 0
|
|
308
|
+
);
|
|
309
|
+
const retainProcessNodes = () => [
|
|
310
|
+
graph.retain(runtime, { reason: `${name}.process.runtime` }),
|
|
311
|
+
graph.retain(events, { reason: `${name}.process.events` }),
|
|
312
|
+
graph.retain(state, { reason: `${name}.process.state` }),
|
|
313
|
+
graph.retain(audit, { reason: `${name}.process.audit` }),
|
|
314
|
+
graph.retain(effectRequests, { reason: `${name}.process.effectRequests` }),
|
|
315
|
+
graph.retain(status, { reason: `${name}.process.status` }),
|
|
316
|
+
graph.retain(errors, { reason: `${name}.process.errors` }),
|
|
317
|
+
graph.retain(cursor, { reason: `${name}.process.cursor` })
|
|
318
|
+
];
|
|
319
|
+
let releaseRetains = retainProcessNodes();
|
|
320
|
+
let released = false;
|
|
321
|
+
const bundle = {
|
|
322
|
+
command,
|
|
323
|
+
events,
|
|
324
|
+
state,
|
|
325
|
+
audit,
|
|
326
|
+
effectRequests,
|
|
327
|
+
status,
|
|
328
|
+
errors,
|
|
329
|
+
cursor,
|
|
330
|
+
dispatch(next) {
|
|
331
|
+
command.down([["DATA", next]]);
|
|
332
|
+
return next;
|
|
333
|
+
},
|
|
334
|
+
release() {
|
|
335
|
+
if (released) return;
|
|
336
|
+
const activeRetains = releaseRetains;
|
|
337
|
+
releaseRetains = [];
|
|
338
|
+
for (const releaseRetain of activeRetains) releaseRetain();
|
|
339
|
+
try {
|
|
340
|
+
topology.release({ reason: `${name}.process.release` });
|
|
341
|
+
released = true;
|
|
342
|
+
processCommandSources.delete(
|
|
343
|
+
bundle
|
|
344
|
+
);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
releaseRetains = retainProcessNodes();
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
processCommandSources.set(
|
|
352
|
+
bundle,
|
|
353
|
+
{ sources: [] }
|
|
354
|
+
);
|
|
355
|
+
return bundle;
|
|
356
|
+
}
|
|
357
|
+
function processEffectRunner(graph, process, opts) {
|
|
358
|
+
if (opts.outcomes.length === 0) {
|
|
359
|
+
throw new RangeError("processEffectRunner: outcomes must contain at least one node");
|
|
360
|
+
}
|
|
361
|
+
const name = opts.name ?? "processEffectRunner";
|
|
362
|
+
const topology = graph.topologyGroup({ name: `${name}.effectRunner` });
|
|
363
|
+
const requests = topology.node(
|
|
364
|
+
[process.effectRequests],
|
|
365
|
+
(ctx) => {
|
|
366
|
+
for (const request of depBatch(ctx, 0) ?? []) {
|
|
367
|
+
ctx.down([["DATA", request]]);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: `${name}/requests`,
|
|
372
|
+
factory: "processEffectRunnerRequests",
|
|
373
|
+
partial: true,
|
|
374
|
+
completeWhenDepsComplete: false,
|
|
375
|
+
errorWhenDepsError: false
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
const runtime = topology.node(
|
|
379
|
+
[...opts.outcomes],
|
|
380
|
+
(ctx) => {
|
|
381
|
+
for (let i = 0; i < opts.outcomes.length; i += 1) {
|
|
382
|
+
for (const raw of depBatch(ctx, i) ?? []) {
|
|
383
|
+
const parsed = parseProcessEffectOutcome(raw);
|
|
384
|
+
if (typeof parsed === "string") {
|
|
385
|
+
const error = {
|
|
386
|
+
code: "malformed-outcome",
|
|
387
|
+
message: parsed,
|
|
388
|
+
outcome: raw
|
|
389
|
+
};
|
|
390
|
+
ctx.down([["DATA", { kind: "error", error }]]);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
const outcome = parsed;
|
|
394
|
+
const command = processEffectOutcomeCommand(outcome);
|
|
395
|
+
ctx.down([["DATA", { kind: "outcome", outcome }]]);
|
|
396
|
+
ctx.down([["DATA", { kind: "command", command }]]);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: `${name}/runtime`,
|
|
402
|
+
factory: "processEffectRunner",
|
|
403
|
+
meta: { process: "effect-runner", d: "D156" },
|
|
404
|
+
partial: true,
|
|
405
|
+
completeWhenDepsComplete: false,
|
|
406
|
+
errorWhenDepsError: false
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
const outcomes = processEffectRunnerProjection(
|
|
410
|
+
topology,
|
|
411
|
+
runtime,
|
|
412
|
+
`${name}/outcomes`,
|
|
413
|
+
"processEffectRunnerOutcomes",
|
|
414
|
+
(fact) => fact.kind === "outcome" ? fact.outcome : void 0
|
|
415
|
+
);
|
|
416
|
+
const commands = processEffectRunnerProjection(
|
|
417
|
+
topology,
|
|
418
|
+
runtime,
|
|
419
|
+
`${name}/commands`,
|
|
420
|
+
"processEffectRunnerCommands",
|
|
421
|
+
(fact) => fact.kind === "command" ? fact.command : void 0
|
|
422
|
+
);
|
|
423
|
+
const errors = processEffectRunnerProjection(
|
|
424
|
+
topology,
|
|
425
|
+
runtime,
|
|
426
|
+
`${name}/errors`,
|
|
427
|
+
"processEffectRunnerErrors",
|
|
428
|
+
(fact) => fact.kind === "error" ? fact.error : void 0
|
|
429
|
+
);
|
|
430
|
+
const status = topology.node(
|
|
431
|
+
[requests, runtime],
|
|
432
|
+
(ctx) => {
|
|
433
|
+
let state = ctx.state.get() ?? { requested: 0, commanded: 0, rejected: 0 };
|
|
434
|
+
for (const request of depBatch(ctx, 0) ?? []) {
|
|
435
|
+
state = { ...state, requested: state.requested + 1 };
|
|
436
|
+
const typedRequest = request;
|
|
437
|
+
ctx.down([
|
|
438
|
+
[
|
|
439
|
+
"DATA",
|
|
440
|
+
{
|
|
441
|
+
state: "requested",
|
|
442
|
+
effectId: typedRequest.id,
|
|
443
|
+
effectType: typedRequest.type,
|
|
444
|
+
requested: state.requested,
|
|
445
|
+
commanded: state.commanded,
|
|
446
|
+
rejected: state.rejected
|
|
447
|
+
}
|
|
448
|
+
]
|
|
449
|
+
]);
|
|
450
|
+
}
|
|
451
|
+
for (const fact of depBatch(ctx, 1) ?? []) {
|
|
452
|
+
const typedFact = fact;
|
|
453
|
+
if (typedFact.kind === "command") {
|
|
454
|
+
state = { ...state, commanded: state.commanded + 1 };
|
|
455
|
+
ctx.down([
|
|
456
|
+
[
|
|
457
|
+
"DATA",
|
|
458
|
+
{
|
|
459
|
+
state: "commanded",
|
|
460
|
+
effectId: typedFact.command.payload.effectId,
|
|
461
|
+
effectType: typedFact.command.payload.effectType,
|
|
462
|
+
commandId: typedFact.command.id,
|
|
463
|
+
commandType: typedFact.command.type,
|
|
464
|
+
requested: state.requested,
|
|
465
|
+
commanded: state.commanded,
|
|
466
|
+
rejected: state.rejected
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
]);
|
|
470
|
+
}
|
|
471
|
+
if (typedFact.kind === "error") {
|
|
472
|
+
state = { ...state, rejected: state.rejected + 1 };
|
|
473
|
+
ctx.down([
|
|
474
|
+
[
|
|
475
|
+
"DATA",
|
|
476
|
+
{
|
|
477
|
+
state: "rejected",
|
|
478
|
+
effectId: typedFact.error.effectId,
|
|
479
|
+
effectType: typedFact.error.effectType,
|
|
480
|
+
requested: state.requested,
|
|
481
|
+
commanded: state.commanded,
|
|
482
|
+
rejected: state.rejected
|
|
483
|
+
}
|
|
484
|
+
]
|
|
485
|
+
]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
ctx.state.set(state);
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: `${name}/status`,
|
|
492
|
+
factory: "processEffectRunnerStatus",
|
|
493
|
+
partial: true,
|
|
494
|
+
completeWhenDepsComplete: false,
|
|
495
|
+
errorWhenDepsError: false
|
|
496
|
+
}
|
|
497
|
+
);
|
|
498
|
+
const retainRunnerNodes = () => [
|
|
499
|
+
graph.retain(requests, { reason: `${name}.effectRunner.requests` }),
|
|
500
|
+
graph.retain(outcomes, { reason: `${name}.effectRunner.outcomes` }),
|
|
501
|
+
graph.retain(commands, { reason: `${name}.effectRunner.commands` }),
|
|
502
|
+
graph.retain(status, { reason: `${name}.effectRunner.status` }),
|
|
503
|
+
graph.retain(errors, { reason: `${name}.effectRunner.errors` })
|
|
504
|
+
];
|
|
505
|
+
let releaseRetains = retainRunnerNodes();
|
|
506
|
+
try {
|
|
507
|
+
attachProcessCommandSource(process, commands);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
for (const releaseRetain of releaseRetains) releaseRetain();
|
|
510
|
+
releaseRetains = [];
|
|
511
|
+
topology.release({ reason: `${name}.effectRunner.failedAttach` });
|
|
512
|
+
throw error;
|
|
513
|
+
}
|
|
514
|
+
let released = false;
|
|
515
|
+
return {
|
|
516
|
+
requests,
|
|
517
|
+
outcomes,
|
|
518
|
+
commands,
|
|
519
|
+
status,
|
|
520
|
+
errors,
|
|
521
|
+
release() {
|
|
522
|
+
if (released) return;
|
|
523
|
+
const activeRetains = releaseRetains;
|
|
524
|
+
releaseRetains = [];
|
|
525
|
+
for (const releaseRetain of activeRetains) releaseRetain();
|
|
526
|
+
detachProcessCommandSource(process, commands);
|
|
527
|
+
try {
|
|
528
|
+
topology.release({ reason: `${name}.effectRunner.release` });
|
|
529
|
+
released = true;
|
|
530
|
+
} catch (error) {
|
|
531
|
+
attachProcessCommandSource(process, commands);
|
|
532
|
+
releaseRetains = retainRunnerNodes();
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function constantBackoff(delayMs) {
|
|
539
|
+
return { kind: "constant", delayMs };
|
|
540
|
+
}
|
|
541
|
+
function processEffectRunnerProjection(topology, runtime, name, factory, pick) {
|
|
542
|
+
return topology.node(
|
|
543
|
+
[runtime],
|
|
544
|
+
(ctx) => {
|
|
545
|
+
for (const fact of depBatch(ctx, 0) ?? []) {
|
|
546
|
+
const value = pick(fact);
|
|
547
|
+
if (value !== void 0) ctx.down([["DATA", value]]);
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name,
|
|
552
|
+
factory,
|
|
553
|
+
partial: true,
|
|
554
|
+
completeWhenDepsComplete: false,
|
|
555
|
+
errorWhenDepsError: false
|
|
556
|
+
}
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
function parseProcessEffectOutcome(raw) {
|
|
560
|
+
if (!isObjectRecord(raw)) return "processEffectRunner: outcome must be an object";
|
|
561
|
+
if (typeof raw.kind !== "string") {
|
|
562
|
+
return "processEffectRunner: outcome kind must be a string";
|
|
563
|
+
}
|
|
564
|
+
if (raw.kind !== "result" && raw.kind !== "failure" && raw.kind !== "cancel" && raw.kind !== "timeout") {
|
|
565
|
+
return "processEffectRunner: outcome kind must be result, failure, cancel, or timeout";
|
|
566
|
+
}
|
|
567
|
+
if (typeof raw.effectId !== "string" || raw.effectId.length === 0) {
|
|
568
|
+
return "processEffectRunner: outcome effectId must be a non-empty string";
|
|
569
|
+
}
|
|
570
|
+
if (typeof raw.effectType !== "string" || raw.effectType.length === 0) {
|
|
571
|
+
return "processEffectRunner: outcome effectType must be a non-empty string";
|
|
572
|
+
}
|
|
573
|
+
if ("commandId" in raw && raw.commandId !== void 0) {
|
|
574
|
+
if (typeof raw.commandId !== "string" || raw.commandId.length === 0) {
|
|
575
|
+
return "processEffectRunner: outcome commandId must be a non-empty string";
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
for (const key of ["processId", "correlationId", "causationId"]) {
|
|
579
|
+
if (key in raw && raw[key] !== void 0 && typeof raw[key] !== "string") {
|
|
580
|
+
return `processEffectRunner: outcome ${key} must be a string`;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if ("metadata" in raw && raw.metadata !== void 0 && !isObjectRecord(raw.metadata)) {
|
|
584
|
+
return "processEffectRunner: outcome metadata must be a plain object";
|
|
585
|
+
}
|
|
586
|
+
if (raw.kind === "result" && !("value" in raw)) {
|
|
587
|
+
return "processEffectRunner: result outcome must carry value";
|
|
588
|
+
}
|
|
589
|
+
if ((raw.kind === "failure" || raw.kind === "timeout") && !("error" in raw)) {
|
|
590
|
+
return `processEffectRunner: ${raw.kind} outcome must carry error`;
|
|
591
|
+
}
|
|
592
|
+
if (raw.kind === "cancel" && "reason" in raw && raw.reason !== void 0 && typeof raw.reason !== "string") {
|
|
593
|
+
return "processEffectRunner: cancel outcome reason must be a string";
|
|
594
|
+
}
|
|
595
|
+
return raw;
|
|
596
|
+
}
|
|
597
|
+
function processEffectOutcomeCommand(outcome) {
|
|
598
|
+
const commandType = processEffectCommandType(outcome.kind);
|
|
599
|
+
return {
|
|
600
|
+
id: outcome.commandId ?? `${outcome.effectId}:${commandType}`,
|
|
601
|
+
type: commandType,
|
|
602
|
+
payload: processEffectCommandPayload(outcome),
|
|
603
|
+
processId: outcome.processId,
|
|
604
|
+
correlationId: outcome.correlationId,
|
|
605
|
+
causationId: outcome.causationId,
|
|
606
|
+
metadata: outcome.metadata
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function processEffectCommandType(kind) {
|
|
610
|
+
switch (kind) {
|
|
611
|
+
case "result":
|
|
612
|
+
return "effect.result";
|
|
613
|
+
case "failure":
|
|
614
|
+
return "effect.failure";
|
|
615
|
+
case "cancel":
|
|
616
|
+
return "effect.cancel";
|
|
617
|
+
case "timeout":
|
|
618
|
+
return "effect.timeout";
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function processEffectCommandPayload(outcome) {
|
|
622
|
+
const base = {
|
|
623
|
+
effectId: outcome.effectId,
|
|
624
|
+
effectType: outcome.effectType,
|
|
625
|
+
processId: outcome.processId,
|
|
626
|
+
correlationId: outcome.correlationId,
|
|
627
|
+
causationId: outcome.causationId,
|
|
628
|
+
metadata: outcome.metadata
|
|
629
|
+
};
|
|
630
|
+
switch (outcome.kind) {
|
|
631
|
+
case "result":
|
|
632
|
+
return { ...base, kind: "result", value: outcome.value };
|
|
633
|
+
case "failure":
|
|
634
|
+
return { ...base, kind: "failure", error: outcome.error };
|
|
635
|
+
case "cancel":
|
|
636
|
+
return { ...base, kind: "cancel", reason: outcome.reason };
|
|
637
|
+
case "timeout":
|
|
638
|
+
return { ...base, kind: "timeout", error: outcome.error };
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
function attachProcessCommandSource(process, source) {
|
|
642
|
+
const attached = processCommandSources.get(process);
|
|
643
|
+
if (attached === void 0) {
|
|
644
|
+
throw new Error("processEffectRunner: process command source registry missing");
|
|
645
|
+
}
|
|
646
|
+
const sources = attached.sources;
|
|
647
|
+
const previousSources = [...sources];
|
|
648
|
+
if (!sources.includes(source)) sources.push(source);
|
|
649
|
+
try {
|
|
650
|
+
process.command.replaceDeps([...sources], processCommandSourceFn(sources.length));
|
|
651
|
+
} catch (error) {
|
|
652
|
+
sources.splice(0, sources.length, ...previousSources);
|
|
653
|
+
process.command.replaceDeps(
|
|
654
|
+
[...previousSources],
|
|
655
|
+
processCommandSourceFn(previousSources.length)
|
|
656
|
+
);
|
|
657
|
+
throw error;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function detachProcessCommandSource(process, source) {
|
|
661
|
+
const attached = processCommandSources.get(process);
|
|
662
|
+
if (attached === void 0) {
|
|
663
|
+
throw new Error("processEffectRunner: process command source registry missing");
|
|
664
|
+
}
|
|
665
|
+
const sources = attached.sources;
|
|
666
|
+
if (!sources.includes(source)) return;
|
|
667
|
+
const previousSources = [...sources];
|
|
668
|
+
const nextSources = sources.filter((candidate) => candidate !== source);
|
|
669
|
+
sources.splice(0, sources.length, ...nextSources);
|
|
670
|
+
try {
|
|
671
|
+
process.command.replaceDeps([...nextSources], processCommandSourceFn(nextSources.length));
|
|
672
|
+
} catch (error) {
|
|
673
|
+
sources.splice(0, sources.length, ...previousSources);
|
|
674
|
+
process.command.replaceDeps(
|
|
675
|
+
[...previousSources],
|
|
676
|
+
processCommandSourceFn(previousSources.length)
|
|
677
|
+
);
|
|
678
|
+
throw error;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function processCommandSourceFn(sourceCount) {
|
|
682
|
+
return (ctx) => {
|
|
683
|
+
for (let i = 0; i < sourceCount; i += 1) {
|
|
684
|
+
for (const command of depBatch(ctx, i) ?? []) {
|
|
685
|
+
ctx.down([["DATA", command]]);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function processRuntimeDraft(state) {
|
|
691
|
+
return {
|
|
692
|
+
eventSeq: state.eventSeq,
|
|
693
|
+
effectSeq: state.effectSeq,
|
|
694
|
+
commandCount: state.commandCount,
|
|
695
|
+
errorCount: state.errorCount,
|
|
696
|
+
auditSeq: state.auditSeq,
|
|
697
|
+
seenEventIds: [...state.seenEventIds],
|
|
698
|
+
seenEffectIds: [...state.seenEffectIds],
|
|
699
|
+
state: state.state
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function reduceProcessCommandFact(raw, state, reduce, now, emit) {
|
|
703
|
+
const draft = processRuntimeDraft(state);
|
|
704
|
+
draft.commandCount += 1;
|
|
705
|
+
const parsed = parseProcessCommand(raw);
|
|
706
|
+
if (typeof parsed === "string") {
|
|
707
|
+
emitProcessFailure(draft, emit, raw, void 0, "malformed-command", parsed);
|
|
708
|
+
return draft;
|
|
709
|
+
}
|
|
710
|
+
const command = parsed;
|
|
711
|
+
let reduction;
|
|
712
|
+
const reducerState = cloneProcessState(state.state);
|
|
713
|
+
if (!reducerState.ok) {
|
|
714
|
+
emitProcessFailure(draft, emit, command, command, "malformed-state", reducerState.message);
|
|
715
|
+
return draft;
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
reduction = reduce(command, reducerState.value);
|
|
719
|
+
} catch (error) {
|
|
720
|
+
rethrowGraphRuntimeInvariant(error);
|
|
721
|
+
emitProcessFailure(draft, emit, command, command, "reducer-threw", errorMessage(error));
|
|
722
|
+
return draft;
|
|
723
|
+
}
|
|
724
|
+
if (!isObjectRecord(reduction) || !("state" in reduction)) {
|
|
725
|
+
emitProcessFailure(
|
|
726
|
+
draft,
|
|
727
|
+
emit,
|
|
728
|
+
command,
|
|
729
|
+
command,
|
|
730
|
+
"malformed-state",
|
|
731
|
+
"processBundle: reducer must return { state, events?, effects? }"
|
|
732
|
+
);
|
|
733
|
+
return draft;
|
|
734
|
+
}
|
|
735
|
+
if (reduction.state === void 0) {
|
|
736
|
+
emitProcessFailure(
|
|
737
|
+
draft,
|
|
738
|
+
emit,
|
|
739
|
+
command,
|
|
740
|
+
command,
|
|
741
|
+
"malformed-state",
|
|
742
|
+
"processBundle: reducer state must not be undefined"
|
|
743
|
+
);
|
|
744
|
+
return draft;
|
|
745
|
+
}
|
|
746
|
+
const privateState = cloneProcessState(reduction.state);
|
|
747
|
+
if (!privateState.ok) {
|
|
748
|
+
emitProcessFailure(draft, emit, command, command, "malformed-state", privateState.message);
|
|
749
|
+
return draft;
|
|
750
|
+
}
|
|
751
|
+
const visibleState = cloneProcessState(privateState.value);
|
|
752
|
+
if (!visibleState.ok) {
|
|
753
|
+
emitProcessFailure(draft, emit, command, command, "malformed-state", visibleState.message);
|
|
754
|
+
return draft;
|
|
755
|
+
}
|
|
756
|
+
const preparedEvents = prepareProcessEvents(command, draft, reduction.events ?? []);
|
|
757
|
+
if (typeof preparedEvents === "string") {
|
|
758
|
+
emitProcessFailure(draft, emit, command, command, "malformed-event", preparedEvents);
|
|
759
|
+
return draft;
|
|
760
|
+
}
|
|
761
|
+
const preparedEffects = prepareProcessEffects(command, draft, reduction.effects ?? []);
|
|
762
|
+
if (typeof preparedEffects === "string") {
|
|
763
|
+
emitProcessFailure(draft, emit, command, command, "malformed-effect", preparedEffects);
|
|
764
|
+
return draft;
|
|
765
|
+
}
|
|
766
|
+
const timestampMs = readTimestampMs(now);
|
|
767
|
+
if (typeof timestampMs === "string") {
|
|
768
|
+
emitProcessFailure(draft, emit, command, command, "clock-threw", timestampMs);
|
|
769
|
+
return draft;
|
|
770
|
+
}
|
|
771
|
+
draft.state = privateState.value;
|
|
772
|
+
emit({ kind: "state", state: visibleState.value, cursor: processCursorOf(draft) });
|
|
773
|
+
const events = [];
|
|
774
|
+
for (const eventDraft of preparedEvents) {
|
|
775
|
+
draft.eventSeq += 1;
|
|
776
|
+
draft.seenEventIds.push(eventDraft.id);
|
|
777
|
+
const event = {
|
|
778
|
+
id: eventDraft.id,
|
|
779
|
+
type: eventDraft.type,
|
|
780
|
+
seq: draft.eventSeq,
|
|
781
|
+
cursor: draft.eventSeq,
|
|
782
|
+
commandId: command.id,
|
|
783
|
+
commandType: command.type,
|
|
784
|
+
payload: eventDraft.payload,
|
|
785
|
+
timestampMs,
|
|
786
|
+
...eventDraft.processId === void 0 ? {} : { processId: eventDraft.processId },
|
|
787
|
+
...eventDraft.correlationId === void 0 ? {} : { correlationId: eventDraft.correlationId },
|
|
788
|
+
...eventDraft.causationId === void 0 ? {} : { causationId: eventDraft.causationId },
|
|
789
|
+
...eventDraft.metadata === void 0 ? {} : { metadata: eventDraft.metadata }
|
|
790
|
+
};
|
|
791
|
+
events.push(event);
|
|
792
|
+
emit({ kind: "event", event });
|
|
793
|
+
}
|
|
794
|
+
const effects = [];
|
|
795
|
+
for (const effectDraft of preparedEffects) {
|
|
796
|
+
draft.effectSeq += 1;
|
|
797
|
+
draft.seenEffectIds.push(effectDraft.id);
|
|
798
|
+
const effect = {
|
|
799
|
+
id: effectDraft.id,
|
|
800
|
+
type: effectDraft.type,
|
|
801
|
+
seq: draft.effectSeq,
|
|
802
|
+
cursor: draft.effectSeq,
|
|
803
|
+
commandId: command.id,
|
|
804
|
+
commandType: command.type,
|
|
805
|
+
payload: effectDraft.payload,
|
|
806
|
+
timestampMs,
|
|
807
|
+
...effectDraft.processId === void 0 ? {} : { processId: effectDraft.processId },
|
|
808
|
+
...effectDraft.correlationId === void 0 ? {} : { correlationId: effectDraft.correlationId },
|
|
809
|
+
...effectDraft.causationId === void 0 ? {} : { causationId: effectDraft.causationId },
|
|
810
|
+
...effectDraft.metadata === void 0 ? {} : { metadata: effectDraft.metadata }
|
|
811
|
+
};
|
|
812
|
+
effects.push(effect);
|
|
813
|
+
emit({ kind: "effect-request", effect });
|
|
814
|
+
}
|
|
815
|
+
emitProcessStatus(draft, emit, command, "accepted", events.length, effects.length);
|
|
816
|
+
emitProcessAudit(draft, emit, command, "success", events, effects, void 0, void 0);
|
|
817
|
+
emit({ kind: "cursor", cursor: processCursorOf(draft) });
|
|
818
|
+
return draft;
|
|
819
|
+
}
|
|
820
|
+
function processProjection(graph, runtime, name, factory, select) {
|
|
821
|
+
return graph.node(
|
|
822
|
+
[runtime],
|
|
823
|
+
(ctx) => {
|
|
824
|
+
for (const raw of depBatch(ctx, 0) ?? []) {
|
|
825
|
+
const selected = select(raw);
|
|
826
|
+
if (selected !== void 0) ctx.down([["DATA", selected]]);
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
{ name, factory, completeWhenDepsComplete: false, errorWhenDepsError: false }
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
function parseProcessCommand(value) {
|
|
833
|
+
if (!isObjectRecord(value)) return "processBundle: command fact must be an object";
|
|
834
|
+
if (typeof value.id !== "string" || value.id.length === 0) {
|
|
835
|
+
return "processBundle: command id must be a non-empty string";
|
|
836
|
+
}
|
|
837
|
+
if (typeof value.type !== "string" || value.type.length === 0) {
|
|
838
|
+
return "processBundle: command type must be a non-empty string";
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
id: value.id,
|
|
842
|
+
type: value.type,
|
|
843
|
+
payload: value.payload,
|
|
844
|
+
...typeof value.processId === "string" ? { processId: value.processId } : {},
|
|
845
|
+
...typeof value.correlationId === "string" ? { correlationId: value.correlationId } : {},
|
|
846
|
+
...typeof value.causationId === "string" ? { causationId: value.causationId } : {},
|
|
847
|
+
...isObjectRecord(value.metadata) ? { metadata: value.metadata } : {}
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function prepareProcessEvents(command, state, drafts) {
|
|
851
|
+
if (!Array.isArray(drafts)) return "processBundle: events must be an array";
|
|
852
|
+
const seen = /* @__PURE__ */ new Set();
|
|
853
|
+
const out = [];
|
|
854
|
+
for (let i = 0; i < drafts.length; i += 1) {
|
|
855
|
+
const draft = drafts[i];
|
|
856
|
+
if (!isObjectRecord(draft) || typeof draft.type !== "string" || draft.type.length === 0) {
|
|
857
|
+
return "processBundle: event draft must have a non-empty type";
|
|
858
|
+
}
|
|
859
|
+
const id = typeof draft.id === "string" && draft.id.length > 0 ? draft.id : `${command.id}:event:${state.eventSeq + i + 1}`;
|
|
860
|
+
if (seen.has(id)) return `processBundle: duplicate event '${id}'`;
|
|
861
|
+
if (state.seenEventIds.includes(id)) return `processBundle: duplicate event '${id}'`;
|
|
862
|
+
seen.add(id);
|
|
863
|
+
out.push({
|
|
864
|
+
id,
|
|
865
|
+
type: draft.type,
|
|
866
|
+
payload: draft.payload,
|
|
867
|
+
...typeof draft.processId === "string" ? { processId: draft.processId } : {},
|
|
868
|
+
...typeof draft.correlationId === "string" ? { correlationId: draft.correlationId } : {},
|
|
869
|
+
...typeof draft.causationId === "string" ? { causationId: draft.causationId } : {},
|
|
870
|
+
...isObjectRecord(draft.metadata) ? { metadata: draft.metadata } : {}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return out;
|
|
874
|
+
}
|
|
875
|
+
function prepareProcessEffects(command, state, drafts) {
|
|
876
|
+
if (!Array.isArray(drafts)) return "processBundle: effects must be an array";
|
|
877
|
+
const seen = /* @__PURE__ */ new Set();
|
|
878
|
+
const out = [];
|
|
879
|
+
for (let i = 0; i < drafts.length; i += 1) {
|
|
880
|
+
const draft = drafts[i];
|
|
881
|
+
if (!isObjectRecord(draft) || typeof draft.type !== "string" || draft.type.length === 0) {
|
|
882
|
+
return "processBundle: effect draft must have a non-empty type";
|
|
883
|
+
}
|
|
884
|
+
const id = typeof draft.id === "string" && draft.id.length > 0 ? draft.id : `${command.id}:effect:${state.effectSeq + i + 1}`;
|
|
885
|
+
if (seen.has(id)) return `processBundle: duplicate effect '${id}'`;
|
|
886
|
+
if (state.seenEffectIds.includes(id)) return `processBundle: duplicate effect '${id}'`;
|
|
887
|
+
seen.add(id);
|
|
888
|
+
out.push({
|
|
889
|
+
id,
|
|
890
|
+
type: draft.type,
|
|
891
|
+
payload: draft.payload,
|
|
892
|
+
...typeof draft.processId === "string" ? { processId: draft.processId } : {},
|
|
893
|
+
...typeof draft.correlationId === "string" ? { correlationId: draft.correlationId } : {},
|
|
894
|
+
...typeof draft.causationId === "string" ? { causationId: draft.causationId } : {},
|
|
895
|
+
...isObjectRecord(draft.metadata) ? { metadata: draft.metadata } : {}
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
function emitProcessFailure(state, emit, raw, command, code, message) {
|
|
901
|
+
state.errorCount += 1;
|
|
902
|
+
const cursor = processCursorOf(state);
|
|
903
|
+
const error = {
|
|
904
|
+
code,
|
|
905
|
+
message,
|
|
906
|
+
command: raw,
|
|
907
|
+
...command === void 0 ? {} : { commandId: command.id, commandType: command.type },
|
|
908
|
+
cursor
|
|
909
|
+
};
|
|
910
|
+
emit({ kind: "error", error });
|
|
911
|
+
emitProcessStatus(state, emit, command, "rejected", 0, 0, code);
|
|
912
|
+
emitProcessAudit(state, emit, command, "failure", [], [], code, message);
|
|
913
|
+
emit({ kind: "cursor", cursor: processCursorOf(state) });
|
|
914
|
+
}
|
|
915
|
+
function emitProcessStatus(state, emit, command, statusState, eventCount, effectCount, errorCode) {
|
|
916
|
+
emit({
|
|
917
|
+
kind: "status",
|
|
918
|
+
status: {
|
|
919
|
+
state: statusState,
|
|
920
|
+
...command === void 0 ? {} : { commandId: command.id, commandType: command.type },
|
|
921
|
+
eventCount,
|
|
922
|
+
effectCount,
|
|
923
|
+
...errorCode === void 0 ? {} : { errorCode },
|
|
924
|
+
cursor: processCursorOf(state)
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
function emitProcessAudit(state, emit, command, outcome, events, effects, errorCode, errorMessage2) {
|
|
929
|
+
state.auditSeq += 1;
|
|
930
|
+
emit({
|
|
931
|
+
kind: "audit",
|
|
932
|
+
audit: {
|
|
933
|
+
seq: state.auditSeq,
|
|
934
|
+
...command === void 0 ? {} : { commandId: command.id, commandType: command.type },
|
|
935
|
+
outcome,
|
|
936
|
+
eventIds: events.map((event) => event.id),
|
|
937
|
+
eventTypes: events.map((event) => event.type),
|
|
938
|
+
effectIds: effects.map((effect) => effect.id),
|
|
939
|
+
effectTypes: effects.map((effect) => effect.type),
|
|
940
|
+
...errorCode === void 0 ? {} : { errorCode },
|
|
941
|
+
...errorMessage2 === void 0 ? {} : { errorMessage: errorMessage2 },
|
|
942
|
+
cursor: processCursorOf(state)
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
function processCursorOf(state) {
|
|
947
|
+
return {
|
|
948
|
+
eventSeq: state.eventSeq,
|
|
949
|
+
effectSeq: state.effectSeq,
|
|
950
|
+
commandCount: state.commandCount,
|
|
951
|
+
errorCount: state.errorCount,
|
|
952
|
+
auditSeq: state.auditSeq
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function cloneProcessState(state) {
|
|
956
|
+
if (typeof state !== "object" || state === null) return { ok: true, value: state };
|
|
957
|
+
try {
|
|
958
|
+
if (typeof globalThis.structuredClone === "function") {
|
|
959
|
+
return { ok: true, value: globalThis.structuredClone(state) };
|
|
960
|
+
}
|
|
961
|
+
return { ok: true, value: JSON.parse(JSON.stringify(state)) };
|
|
962
|
+
} catch (error) {
|
|
963
|
+
return {
|
|
964
|
+
ok: false,
|
|
965
|
+
message: `processBundle: state must be cloneable before reducer execution (${errorMessage(error)})`
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
function readTimestampMs(now) {
|
|
970
|
+
try {
|
|
971
|
+
const timestampMs = now();
|
|
972
|
+
if (!Number.isFinite(timestampMs)) return "processBundle: now() must return a finite number";
|
|
973
|
+
return timestampMs;
|
|
974
|
+
} catch (error) {
|
|
975
|
+
return errorMessage(error);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function rethrowGraphRuntimeInvariant(error) {
|
|
979
|
+
const message = errorMessage(error);
|
|
980
|
+
if (message.includes("R-reentrancy") || message.includes("R-rewire") || message.includes("R-graph-domain") || message.includes("D37") || message.includes("D22") || message.includes("different graph") || message.includes("cross-graph") || message.includes("wire bridge") || message.includes("mid-fn topology mutation") || message.includes("reentrant dep mutation") || message.includes("feedback cycle")) {
|
|
981
|
+
throw error;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function isObjectRecord(value) {
|
|
985
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
986
|
+
}
|
|
987
|
+
function errorMessage(error) {
|
|
988
|
+
return error instanceof Error ? error.message : String(error);
|
|
989
|
+
}
|
|
990
|
+
export {
|
|
991
|
+
backoffDelayMs,
|
|
992
|
+
breakerBundle,
|
|
993
|
+
constantBackoff,
|
|
994
|
+
nextRetryDelayMs,
|
|
995
|
+
noBackoff,
|
|
996
|
+
processBundle,
|
|
997
|
+
processEffectRunner,
|
|
998
|
+
rateLimitBundle,
|
|
999
|
+
retryPolicy,
|
|
1000
|
+
retryStatusBundle,
|
|
1001
|
+
shouldRetry,
|
|
1002
|
+
timeoutBundle
|
|
1003
|
+
};
|
|
1004
|
+
//# sourceMappingURL=index.js.map
|