@executablemd/durable-streams 0.3.1 → 0.4.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/esm/combinators.js +0 -12
- package/esm/divergence.js +0 -6
- package/esm/each.js +2 -16
- package/esm/effect.js +6 -9
- package/esm/replay-guard.js +0 -6
- package/esm/replay-index.js +16 -8
- package/esm/serialize.js +4 -8
- package/package.json +1 -1
package/esm/combinators.js
CHANGED
|
@@ -21,9 +21,6 @@ import { all as effectionAll, ensure, race as effectionRace, spawn, suspend, use
|
|
|
21
21
|
import { DurableCtx } from "./context.js";
|
|
22
22
|
import { ephemeral } from "./ephemeral.js";
|
|
23
23
|
import { deserializeError, serializeError } from "./serialize.js";
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// Internal: wrap a child workflow with DurableContext + Close emission
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
24
|
/**
|
|
28
25
|
* Run a child workflow within a spawned scope, setting up its own
|
|
29
26
|
* DurableContext and emitting a Close event when it terminates.
|
|
@@ -121,9 +118,6 @@ function* runDurableChild(childWorkflow, childId, parentCtx) {
|
|
|
121
118
|
throw error;
|
|
122
119
|
}
|
|
123
120
|
}
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// durableSpawn — spawn a single durable child, returns Task<T>
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
121
|
/**
|
|
128
122
|
* Spawn a durable child workflow.
|
|
129
123
|
*
|
|
@@ -147,9 +141,6 @@ export function durableSpawn(childWorkflow) {
|
|
|
147
141
|
return yield* spawn(() => runDurableChild(childWorkflow, childId, ctx));
|
|
148
142
|
})());
|
|
149
143
|
}
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
// durableAll — fork/join, wait for all children
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
144
|
/**
|
|
154
145
|
* Run multiple durable workflows concurrently and wait for all to complete.
|
|
155
146
|
*
|
|
@@ -187,9 +178,6 @@ export function durableAll(workflows) {
|
|
|
187
178
|
return yield* effectionAll(childOps);
|
|
188
179
|
})());
|
|
189
180
|
}
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
// durableRace — first child to complete wins, others cancelled
|
|
192
|
-
// ---------------------------------------------------------------------------
|
|
193
181
|
/**
|
|
194
182
|
* Race multiple durable workflows. The first to complete wins;
|
|
195
183
|
* remaining children are cancelled.
|
package/esm/divergence.js
CHANGED
|
@@ -21,9 +21,6 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { createApi } from "effection/experimental";
|
|
23
23
|
import { ContinuePastCloseDivergenceError, DivergenceError } from "./errors.js";
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// Default policy (strict — all divergences are fatal)
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
24
|
/** The default (strict) decide function. */
|
|
28
25
|
function defaultDecide(info) {
|
|
29
26
|
if (info.kind === "description-mismatch") {
|
|
@@ -39,9 +36,6 @@ function defaultDecide(info) {
|
|
|
39
36
|
};
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// The Divergence API instance
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
39
|
/**
|
|
46
40
|
* The Divergence API.
|
|
47
41
|
*
|
package/esm/each.js
CHANGED
|
@@ -16,9 +16,6 @@
|
|
|
16
16
|
import { ensure } from "effection";
|
|
17
17
|
import { createDurableOperation } from "./effect.js";
|
|
18
18
|
import { ephemeral } from "./ephemeral.js";
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Internal state
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
19
|
/** Sentinel for source exhaustion. Not exported — cannot collide with JSON. */
|
|
23
20
|
const DONE = Symbol("durableEach.done");
|
|
24
21
|
/** Type guard for the DONE sentinel. */
|
|
@@ -46,9 +43,6 @@ function isDone(value) {
|
|
|
46
43
|
* support for context that spans across ephemeral() boundaries.
|
|
47
44
|
*/
|
|
48
45
|
let activeState = null;
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// durableEachFetch — shared helper for fetching one item
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
46
|
/**
|
|
53
47
|
* Fetch a single item from the source (or replay it from the journal).
|
|
54
48
|
*
|
|
@@ -66,14 +60,12 @@ let activeState = null;
|
|
|
66
60
|
function durableEachFetch(name, source) {
|
|
67
61
|
return (function* () {
|
|
68
62
|
const result = (yield createDurableOperation({ type: "each", name }, () => source.next()));
|
|
69
|
-
if ("done" in result)
|
|
63
|
+
if ("done" in result) {
|
|
70
64
|
return DONE;
|
|
65
|
+
}
|
|
71
66
|
return result.value;
|
|
72
67
|
})();
|
|
73
68
|
}
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// durableEach — initial fetch + returns synchronous iterable
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
69
|
/**
|
|
78
70
|
* Durable iteration over a DurableSource (internal implementation).
|
|
79
71
|
*
|
|
@@ -148,9 +140,6 @@ function* _durableEachOp(name, source) {
|
|
|
148
140
|
function _durableEach(name, source) {
|
|
149
141
|
return ephemeral(_durableEachOp(name, source));
|
|
150
142
|
}
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
// durableEach.next — static method to advance iteration
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
143
|
/**
|
|
155
144
|
* Advance the current durable iteration.
|
|
156
145
|
*
|
|
@@ -171,9 +160,6 @@ function* _durableEachNext() {
|
|
|
171
160
|
state.current = yield* durableEachFetch(state.name, state.source);
|
|
172
161
|
state.advanced = true;
|
|
173
162
|
}
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// Public API — durableEach with static .next() method
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
163
|
/**
|
|
178
164
|
* Durable iteration over a DurableSource.
|
|
179
165
|
*
|
package/esm/effect.js
CHANGED
|
@@ -117,9 +117,6 @@ function checkReplay(desc, resolve, routine, ctx) {
|
|
|
117
117
|
} // end replay block
|
|
118
118
|
return { path: "live" };
|
|
119
119
|
}
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
// createDurableEffect — callback-based (Executor pattern)
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
120
|
/**
|
|
124
121
|
* Creates a DurableEffect using a callback-based executor.
|
|
125
122
|
*
|
|
@@ -138,16 +135,18 @@ export function createDurableEffect(desc, execute) {
|
|
|
138
135
|
enter(resolve, routine) {
|
|
139
136
|
const ctx = routine.scope.expect(DurableCtx);
|
|
140
137
|
const replay = checkReplay(desc, resolve, routine, ctx);
|
|
141
|
-
if (replay.path === "replayed")
|
|
138
|
+
if (replay.path === "replayed") {
|
|
142
139
|
return replay.teardown;
|
|
140
|
+
}
|
|
143
141
|
// ── LIVE PATH ──
|
|
144
142
|
let settled = false;
|
|
145
143
|
let tornDown = false;
|
|
146
144
|
let teardown = () => { };
|
|
147
145
|
/** Persist a Yield event then resume the generator. */
|
|
148
146
|
function persistAndResolve(result) {
|
|
149
|
-
if (settled)
|
|
147
|
+
if (settled) {
|
|
150
148
|
return;
|
|
149
|
+
}
|
|
151
150
|
settled = true;
|
|
152
151
|
const event = {
|
|
153
152
|
type: "yield",
|
|
@@ -209,9 +208,6 @@ export function createDurableEffect(desc, execute) {
|
|
|
209
208
|
},
|
|
210
209
|
};
|
|
211
210
|
}
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
// createDurableOperation — Operation-based (structured concurrency)
|
|
214
|
-
// ---------------------------------------------------------------------------
|
|
215
211
|
/**
|
|
216
212
|
* Creates a DurableEffect from an Operation-returning function.
|
|
217
213
|
*
|
|
@@ -233,8 +229,9 @@ export function createDurableOperation(desc, execute) {
|
|
|
233
229
|
enter(resolve, routine) {
|
|
234
230
|
const ctx = routine.scope.expect(DurableCtx);
|
|
235
231
|
const replay = checkReplay(desc, resolve, routine, ctx);
|
|
236
|
-
if (replay.path === "replayed")
|
|
232
|
+
if (replay.path === "replayed") {
|
|
237
233
|
return replay.teardown;
|
|
234
|
+
}
|
|
238
235
|
// ── LIVE PATH ──
|
|
239
236
|
// Run the entire execute → capture → persist → resolve sequence
|
|
240
237
|
// as a structured operation in the routine's scope.
|
package/esm/replay-guard.js
CHANGED
|
@@ -31,9 +31,6 @@
|
|
|
31
31
|
* See replay-guard-spec.md for the full design.
|
|
32
32
|
*/
|
|
33
33
|
import { createApi } from "effection/experimental";
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Default implementation (pass-through)
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
34
|
/**
|
|
38
35
|
* Default check — no-op. Events pass through without observation.
|
|
39
36
|
*/
|
|
@@ -48,9 +45,6 @@ function* defaultCheck(_event) {
|
|
|
48
45
|
function defaultDecide(_event) {
|
|
49
46
|
return { outcome: "replay" };
|
|
50
47
|
}
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// The ReplayGuard API instance
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
48
|
/**
|
|
55
49
|
* The ReplayGuard API.
|
|
56
50
|
*
|
package/esm/replay-index.js
CHANGED
|
@@ -47,8 +47,9 @@ export class ReplayIndex {
|
|
|
47
47
|
* or undefined if the cursor is past the end or replay is disabled.
|
|
48
48
|
*/
|
|
49
49
|
peekYield(coroutineId) {
|
|
50
|
-
if (this.disabled.has(coroutineId))
|
|
50
|
+
if (this.disabled.has(coroutineId)) {
|
|
51
51
|
return undefined;
|
|
52
|
+
}
|
|
52
53
|
const list = this.yields.get(coroutineId);
|
|
53
54
|
const cursor = this.cursors.get(coroutineId) ?? 0;
|
|
54
55
|
return list?.[cursor];
|
|
@@ -64,14 +65,16 @@ export class ReplayIndex {
|
|
|
64
65
|
}
|
|
65
66
|
/** Returns true if a Close event exists for this coroutine (and replay is not disabled). */
|
|
66
67
|
hasClose(coroutineId) {
|
|
67
|
-
if (this.disabled.has(coroutineId))
|
|
68
|
+
if (this.disabled.has(coroutineId)) {
|
|
68
69
|
return false;
|
|
70
|
+
}
|
|
69
71
|
return this.closes.has(coroutineId);
|
|
70
72
|
}
|
|
71
73
|
/** Returns the Close event for this coroutine, or undefined. */
|
|
72
74
|
getClose(coroutineId) {
|
|
73
|
-
if (this.disabled.has(coroutineId))
|
|
75
|
+
if (this.disabled.has(coroutineId)) {
|
|
74
76
|
return undefined;
|
|
77
|
+
}
|
|
75
78
|
return this.closes.get(coroutineId);
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
@@ -82,11 +85,13 @@ export class ReplayIndex {
|
|
|
82
85
|
*/
|
|
83
86
|
hasAnyUnconsumedYields() {
|
|
84
87
|
for (const [coroutineId, entries] of this.yields.entries()) {
|
|
85
|
-
if (this.disabled.has(coroutineId))
|
|
88
|
+
if (this.disabled.has(coroutineId)) {
|
|
86
89
|
continue;
|
|
90
|
+
}
|
|
87
91
|
const cursor = this.cursors.get(coroutineId) ?? 0;
|
|
88
|
-
if (cursor < entries.length)
|
|
92
|
+
if (cursor < entries.length) {
|
|
89
93
|
return true;
|
|
94
|
+
}
|
|
90
95
|
}
|
|
91
96
|
return false;
|
|
92
97
|
}
|
|
@@ -102,10 +107,12 @@ export class ReplayIndex {
|
|
|
102
107
|
*/
|
|
103
108
|
firstUnconsumed() {
|
|
104
109
|
for (const [coroutineId, entries] of this.yields.entries()) {
|
|
105
|
-
if (this.disabled.has(coroutineId))
|
|
110
|
+
if (this.disabled.has(coroutineId)) {
|
|
106
111
|
continue;
|
|
107
|
-
|
|
112
|
+
}
|
|
113
|
+
if (this.closes.has(coroutineId)) {
|
|
108
114
|
continue;
|
|
115
|
+
}
|
|
109
116
|
const cursor = this.cursors.get(coroutineId) ?? 0;
|
|
110
117
|
if (cursor < entries.length) {
|
|
111
118
|
return { coroutineId, cursor, totalYields: entries.length };
|
|
@@ -121,8 +128,9 @@ export class ReplayIndex {
|
|
|
121
128
|
* Returns false if replay is disabled (run-live mode).
|
|
122
129
|
*/
|
|
123
130
|
isFullyReplayed(coroutineId) {
|
|
124
|
-
if (this.disabled.has(coroutineId))
|
|
131
|
+
if (this.disabled.has(coroutineId)) {
|
|
125
132
|
return false;
|
|
133
|
+
}
|
|
126
134
|
return this.peekYield(coroutineId) === undefined && this.hasClose(coroutineId);
|
|
127
135
|
}
|
|
128
136
|
/** Returns the total number of yield entries for this coroutine. */
|
package/esm/serialize.js
CHANGED
|
@@ -6,9 +6,6 @@
|
|
|
6
6
|
* - Effection Result ({ ok: true, value } | { ok: false, error })
|
|
7
7
|
* - Error ↔ SerializedError
|
|
8
8
|
*/
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Error serialization
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
9
|
/** Serialize an Error to a JSON-safe SerializedError. */
|
|
13
10
|
export function serializeError(error) {
|
|
14
11
|
return {
|
|
@@ -20,15 +17,14 @@ export function serializeError(error) {
|
|
|
20
17
|
/** Deserialize a SerializedError back to an Error. */
|
|
21
18
|
export function deserializeError(se) {
|
|
22
19
|
const error = new Error(se.message);
|
|
23
|
-
if (se.name)
|
|
20
|
+
if (se.name) {
|
|
24
21
|
error.name = se.name;
|
|
25
|
-
|
|
22
|
+
}
|
|
23
|
+
if (se.stack) {
|
|
26
24
|
error.stack = se.stack;
|
|
25
|
+
}
|
|
27
26
|
return error;
|
|
28
27
|
}
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Result conversion: Protocol ↔ Effection
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
28
|
/**
|
|
33
29
|
* Convert a protocol Result to an Effection Result.
|
|
34
30
|
*
|