@loomwork/replay-vm 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +70 -2
- package/dist/ReplayVM.d.ts +59 -0
- package/dist/ReplayVM.d.ts.map +1 -0
- package/dist/ReplayVM.js +160 -0
- package/dist/ReplayVM.js.map +1 -0
- package/dist/eventHandlers.d.ts +65 -0
- package/dist/eventHandlers.d.ts.map +1 -0
- package/dist/eventHandlers.js +1563 -0
- package/dist/eventHandlers.js.map +1 -0
- package/dist/foldEvents.d.ts +23 -0
- package/dist/foldEvents.d.ts.map +1 -0
- package/dist/foldEvents.js +119 -0
- package/dist/foldEvents.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -3
- package/src/ReplayVM.ts +179 -0
- package/src/eventHandlers.ts +1760 -0
- package/src/foldEvents.ts +128 -0
- package/src/index.ts +18 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { EventEnvelope, ExecutionFrame, ExecutionState } from "@loomwork/core";
|
|
2
|
+
import {
|
|
3
|
+
FAILED_WHILE_PARKED,
|
|
4
|
+
FLOW_EMIT_DIGEST,
|
|
5
|
+
countFlowEmit,
|
|
6
|
+
getHandler,
|
|
7
|
+
resetFoldOwnership,
|
|
8
|
+
useFoldOwnership,
|
|
9
|
+
} from "./eventHandlers";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_STATE: Omit<ExecutionState, "executionId" | "workflowId" | "workflowVersion"> = {
|
|
12
|
+
status: "pending",
|
|
13
|
+
frames: {},
|
|
14
|
+
variables: {},
|
|
15
|
+
sequence: -1,
|
|
16
|
+
startedAt: 0,
|
|
17
|
+
updatedAt: 0,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function foldEvents(
|
|
21
|
+
events: EventEnvelope[],
|
|
22
|
+
initial?: Partial<ExecutionState>,
|
|
23
|
+
/**
|
|
24
|
+
* R1a — the ownership set of a LIVE fold (see {@link useFoldOwnership}). Omit it for a
|
|
25
|
+
* one-shot fold: ownership then starts empty, so nothing the caller passed in is written to.
|
|
26
|
+
*/
|
|
27
|
+
owned?: WeakSet<object>
|
|
28
|
+
): ExecutionState {
|
|
29
|
+
// `state` is always an object this function created (the seed below, or a fresh
|
|
30
|
+
// handler result) — never the caller's `initial` — so setting `sequence` in
|
|
31
|
+
// place is safe and avoids a full-state spread per event.
|
|
32
|
+
let state: ExecutionState = {
|
|
33
|
+
executionId: initial?.executionId ?? "",
|
|
34
|
+
workflowId: initial?.workflowId ?? "",
|
|
35
|
+
workflowVersion: initial?.workflowVersion ?? "",
|
|
36
|
+
...DEFAULT_STATE,
|
|
37
|
+
...initial,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Fresh audit-list ownership for THIS fold pass, so in-place appends never touch the
|
|
41
|
+
// caller's `initial`/snapshot arrays (which we treat as immutable). See appendAudit.
|
|
42
|
+
if (owned === undefined) resetFoldOwnership();
|
|
43
|
+
else useFoldOwnership(owned);
|
|
44
|
+
|
|
45
|
+
for (const event of events) {
|
|
46
|
+
const handler = getHandler(event.eventType);
|
|
47
|
+
if (handler) {
|
|
48
|
+
state = handler(state, event);
|
|
49
|
+
}
|
|
50
|
+
// 1.19.0 (ADR 0102) — the one fold step keyed on an envelope STAMP rather than an
|
|
51
|
+
// event type: a flow-level emit (`ctx.emit`/`ctx.stream`/`ctx.ui`, usually a domain
|
|
52
|
+
// event with no handler) may carry `metadata.flowEmit = <content digest>`, and
|
|
53
|
+
// `_flowEmits` counts the stamped emits per digest, so a re-run from the top can serve
|
|
54
|
+
// the emits the log already holds instead of appending them again. An unstamped log
|
|
55
|
+
// (legacy, or kernel facts only) never gains the key and folds byte-identically.
|
|
56
|
+
const stamp = event.metadata?.flowEmit;
|
|
57
|
+
if (typeof stamp === "string" && FLOW_EMIT_DIGEST.test(stamp)) {
|
|
58
|
+
state.variables = countFlowEmit(state.variables, stamp);
|
|
59
|
+
}
|
|
60
|
+
state.sequence = event.sequence;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return state;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* ADR 0105 (EVENT_SCHEMA 1.21.0) — read a SNAPSHOT's state as the current fold would have
|
|
68
|
+
* written it. Before 1.21.0 `STEP_FAILED` also set the EXECUTION's status to "failed", so a
|
|
69
|
+
* snapshot saved after a step failure the flow caught (or a parallel sibling's failure that
|
|
70
|
+
* landed after the flow parked) records a terminal status for a live execution. A log folds
|
|
71
|
+
* correctly on its own; a snapshot is a stored fold and does not.
|
|
72
|
+
*
|
|
73
|
+
* The artifact is recognisable in O(1) on the common path: the kernel's only
|
|
74
|
+
* `EXECUTION_FAILED` writer always records `error`, so a genuinely failed execution carries
|
|
75
|
+
* `variables._error`. "failed" with no `_error` and a failed step frame is the old fold's
|
|
76
|
+
* doing, and the status it hid is `suspended` when the execution is parked (`suspendedOn`
|
|
77
|
+
* survives only while parked) and `running` otherwise. Every other state is returned as-is
|
|
78
|
+
* (same object).
|
|
79
|
+
*/
|
|
80
|
+
export function normalizeSnapshotState<T extends Partial<ExecutionState>>(state: T): T {
|
|
81
|
+
return normalizeFailed(stripSettledInputs(state));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const SETTLED_FRAME = new Set(["completed", "failed", "cancelled"]);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* ADR 0138 D8 (EVENT_SCHEMA 1.26.0) - the fold no longer keeps a SETTLED frame's `input` (the log, or the
|
|
88
|
+
* compaction archive, holds it). A snapshot written by an older fold still carries them; strip them in
|
|
89
|
+
* one O(frames) pass so `fold(snapshot, tail) == fold(events)` holds. Frames still in flight keep theirs.
|
|
90
|
+
* Returns the same object when there is nothing to strip.
|
|
91
|
+
*/
|
|
92
|
+
function stripSettledInputs<T extends Partial<ExecutionState>>(state: T): T {
|
|
93
|
+
const frames = state.frames;
|
|
94
|
+
if (frames === undefined) return state;
|
|
95
|
+
let stripped: Record<string, ExecutionFrame> | undefined;
|
|
96
|
+
for (const stepId of Object.keys(frames)) {
|
|
97
|
+
const frame = frames[stepId];
|
|
98
|
+
if (frame !== undefined && frame.input !== undefined && SETTLED_FRAME.has(frame.status)) {
|
|
99
|
+
stripped ??= { ...frames };
|
|
100
|
+
const { input: _input, ...rest } = frame;
|
|
101
|
+
stripped[stepId] = rest as ExecutionFrame;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return stripped === undefined ? state : { ...state, frames: stripped };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeFailed<T extends Partial<ExecutionState>>(state: T): T {
|
|
108
|
+
if (state.status !== "failed" || state.variables?._error !== undefined) return state;
|
|
109
|
+
const frames = state.frames ?? {};
|
|
110
|
+
let stepFailed = false;
|
|
111
|
+
for (const stepId of Object.keys(frames)) {
|
|
112
|
+
if (frames[stepId]?.status === "failed") {
|
|
113
|
+
stepFailed = true;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!stepFailed) return state;
|
|
118
|
+
// A parked artifact means the failure landed AFTER the park (a STEP_FAILED before it would
|
|
119
|
+
// have been overwritten by the park's "suspended"; a resume removes `suspendedOn`) — exactly
|
|
120
|
+
// what the current fold records as `_failedWhileParked`, so the repaired snapshot folds its
|
|
121
|
+
// tail as the full log would.
|
|
122
|
+
if (state.suspendedOn === undefined) return { ...state, status: "running" };
|
|
123
|
+
return {
|
|
124
|
+
...state,
|
|
125
|
+
status: "suspended",
|
|
126
|
+
variables: { ...state.variables, [FAILED_WHILE_PARKED]: true },
|
|
127
|
+
};
|
|
128
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { foldEvents, normalizeSnapshotState } from "./foldEvents";
|
|
2
|
+
export { ReplayVM } from "./ReplayVM";
|
|
3
|
+
export {
|
|
4
|
+
EVENT_SCHEMA_VERSION,
|
|
5
|
+
FAILED_WHILE_PARKED,
|
|
6
|
+
FLOW_DIVERGED_GATE,
|
|
7
|
+
FLOW_EMIT_DIGEST,
|
|
8
|
+
INBOX_DEDUPE_CAP,
|
|
9
|
+
QUARANTINE_FOLD_CAP,
|
|
10
|
+
KERNEL_STAMPED_EVENT_TYPES,
|
|
11
|
+
BUILTIN_DIMENSION_NAMES,
|
|
12
|
+
CUSTOM_DIMENSION_CEILING,
|
|
13
|
+
CUSTOM_DIMENSION_MAX,
|
|
14
|
+
CUSTOM_DIMENSION_PATTERN,
|
|
15
|
+
flowEmitCount,
|
|
16
|
+
knownEventTypes,
|
|
17
|
+
} from "./eventHandlers";
|
|
18
|
+
export type { FlowEmitCounts, FlowEmitLeaf } from "./eventHandlers";
|