@dudousxd/nestjs-durable-dashboard 0.0.0-beta-20260702153047

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Davide Carvalho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,186 @@
1
+ export type RunStatus = 'pending' | 'running' | 'suspended' | 'completed' | 'failed' | 'cancelling' | 'cancelled' | 'dead';
2
+ export type StepKind = 'local' | 'remote' | 'sleep' | 'signal';
3
+ export interface WorkflowRun {
4
+ id: string;
5
+ workflow: string;
6
+ workflowVersion: string;
7
+ status: RunStatus;
8
+ input?: unknown;
9
+ output?: unknown;
10
+ error?: {
11
+ message: string;
12
+ code?: string;
13
+ };
14
+ wakeAt?: number;
15
+ recoveryAttempts?: number;
16
+ tags?: string[];
17
+ searchAttributes?: Record<string, string | number | boolean>;
18
+ createdAt: string;
19
+ updatedAt: string;
20
+ }
21
+ export interface StepEvent {
22
+ at: number;
23
+ level: 'debug' | 'info' | 'warn' | 'error';
24
+ message: string;
25
+ /** Stable run identity for a sub-process; distinct invocations of the same `name` get distinct ids. */
26
+ subId?: string;
27
+ /** For a sub-step/sub-process within the step: its name. */
28
+ name?: string;
29
+ /** Open, consumer-defined grouping label for a sub-process (e.g. a handler/lane). */
30
+ group?: string;
31
+ /** For a sub-step: its terminal outcome. */
32
+ status?: 'ok' | 'failed' | 'skipped';
33
+ /** Open, consumer-defined intermediate phase label for a sub-process transition (no `status`). */
34
+ phase?: string;
35
+ /** @deprecated owning sub-process **name** for a log line — superseded by `subId`. */
36
+ process?: string;
37
+ data?: unknown;
38
+ }
39
+ export interface StepCheckpoint {
40
+ runId: string;
41
+ seq: number;
42
+ name: string;
43
+ kind: StepKind;
44
+ /**
45
+ * `pending` = a remote step dispatched and awaiting its worker result (in-flight).
46
+ * `running` = a local step whose body is executing right now (in-flight).
47
+ */
48
+ status: 'pending' | 'running' | 'completed' | 'failed';
49
+ /** What the step was called with (a remote step's `ctx.call` args). */
50
+ input?: unknown;
51
+ output?: unknown;
52
+ error?: {
53
+ message: string;
54
+ };
55
+ /** Structured events the step (or its worker) emitted — sub-process outcomes + debug/error logs. */
56
+ events?: StepEvent[];
57
+ /**
58
+ * Shared tag across the N siblings of one `ctx.gather`/`ctx.all` fan-out (e.g. `gather:1` /
59
+ * `all:1`). The prefix is cosmetic; every sibling of ONE fan shares the same exact string, so the
60
+ * dashboard groups by exact string to render them as same-level siblings. Absent for non-fan steps.
61
+ */
62
+ parallelGroup?: string;
63
+ attempts: number;
64
+ workerGroup?: string;
65
+ wakeAt?: number;
66
+ /** When the step was dispatched (remote) or began (local). Queue-wait = startedAt − enqueuedAt. */
67
+ enqueuedAt?: string;
68
+ /** When processing actually began: worker pickup for a remote step, execution start for a local one. */
69
+ startedAt: string;
70
+ finishedAt: string;
71
+ }
72
+ export interface RunDetail {
73
+ run: WorkflowRun;
74
+ timeline: StepCheckpoint[];
75
+ /** Ids of runs this run spawned (ctx.child / ctx.startChild) — the parent→children tree. */
76
+ children?: string[];
77
+ }
78
+ /** A run's status as shown to a human. The engine stores one generic `suspended` for any durably
79
+ * parked run, but WHY it's parked reads very differently, so we refine it for display only. */
80
+ export type RunDisplayStatus = RunStatus | 'sleeping' | 'awaiting';
81
+ /**
82
+ * Refine a run's stored status for display. The engine keeps `suspended` for every durably-parked
83
+ * run (it's what drives recovery/timers/queries — we never change that), but to a human a run whose
84
+ * remote step is being executed by a worker right now is `running`, a durable sleep is `sleeping`,
85
+ * and a wait on a signal is `awaiting`. Pass `timeline` (the detail view has it) for full precision;
86
+ * without it (the run list) a non-timer suspend reads as `running` — open and in progress — rather
87
+ * than the catch-all `suspended`.
88
+ */
89
+ export declare function runDisplayStatus(run: WorkflowRun, timeline?: StepCheckpoint[]): RunDisplayStatus;
90
+ export interface EngineEvent {
91
+ type: 'run.started' | 'run.completed' | 'run.failed' | 'run.suspended' | 'step.started' | 'step.completed' | 'step.failed' | 'step.progress';
92
+ runId: string;
93
+ workflow?: string;
94
+ seq?: number;
95
+ name?: string;
96
+ kind?: StepKind;
97
+ durationMs?: number;
98
+ /** The live step event carried by a `step.progress` (a running step's just-emitted log/sub-process). */
99
+ event?: StepEvent;
100
+ at: string;
101
+ }
102
+ /** How a worker decides its concurrency (mirror of core `WorkerConcurrencyStatus`). */
103
+ export interface WorkerConcurrencyStatus {
104
+ mode: 'fixed' | 'adaptive';
105
+ /** The concurrency ceiling in effect now (fixed: configured; adaptive: the live limit). */
106
+ limit: number;
107
+ /** Adaptive only — the floor the controller won't go below. */
108
+ min?: number;
109
+ /** Adaptive only — the ceiling the controller won't exceed. */
110
+ max?: number;
111
+ }
112
+ /** The adaptive controller's most recent limit change (mirror of core `WorkerAdjust`). */
113
+ export interface WorkerAdjust {
114
+ at: number;
115
+ from: number;
116
+ to: number;
117
+ reason: 'ram_ceiling' | 'cpu_ceiling' | 'backpressure' | 'grow' | 'shrink';
118
+ }
119
+ /** A live snapshot of a worker's execution state riding the heartbeat (mirror of core
120
+ * `WorkerStatus`). Every field beyond concurrency/inFlight is best-effort and may be omitted. */
121
+ export interface WorkerStatus {
122
+ runtime?: 'node' | 'python';
123
+ concurrency: WorkerConcurrencyStatus;
124
+ inFlight: number;
125
+ rssBytes?: number;
126
+ rssLimitBytes?: number;
127
+ rssPct?: number;
128
+ cpuPct?: number;
129
+ throughputPerMin?: number;
130
+ p95Ms?: number;
131
+ lastAdjust?: WorkerAdjust;
132
+ }
133
+ /** One worker's liveness record (mirror of the engine's `WorkerHeartbeat`). */
134
+ export interface WorkerHeartbeat {
135
+ group: string;
136
+ instanceId: string;
137
+ lastBeatAt: number;
138
+ /** Live execution snapshot, when the heartbeat carries one (older SDKs leave it undefined). */
139
+ status?: WorkerStatus;
140
+ }
141
+ /** Per-group worker health for the Workers panel: backlog vs. live workers. The alert state a row
142
+ * turns red on is `depth > 0 && liveWorkers.length === 0` (work piling up with no consumer). */
143
+ export interface GroupHealth {
144
+ group: string;
145
+ depth: number;
146
+ liveWorkers: WorkerHeartbeat[];
147
+ }
148
+ declare global {
149
+ interface Window {
150
+ /** UI mount base (e.g. `/durable`) injected by the UI controller; falls back to `/durable`. */
151
+ __DURABLE_BASE__?: string;
152
+ /** JSON API base (e.g. `/api/durable`) injected by the UI controller; falls back to `<base>/api`. */
153
+ __DURABLE_API__?: string;
154
+ }
155
+ }
156
+ export declare const durableClient: {
157
+ runs(status?: RunStatus, tag?: string, attr?: string[]): Promise<WorkflowRun[]>;
158
+ run(id: string): Promise<RunDetail>;
159
+ /** Per-group worker health (queue backlog + live worker heartbeats) for the Workers panel. */
160
+ workers(): Promise<GroupHealth[]>;
161
+ retry(id: string): Promise<WorkflowRun>;
162
+ /** Fix-and-replay: re-run a dead/failed run with a corrected input. Returns the new run's id. */
163
+ retryWithInput(id: string, input: unknown): Promise<{
164
+ runId: string;
165
+ }>;
166
+ /** Bulk retry/cancel every run matching a filter. Returns how many matched + were acted on. */
167
+ bulk(action: "retry" | "cancel", filter: {
168
+ status?: RunStatus | undefined;
169
+ tag?: string | undefined;
170
+ attr?: string[] | undefined;
171
+ }): Promise<{
172
+ matched: number;
173
+ applied: number;
174
+ }>;
175
+ cancel(id: string, opts?: {
176
+ compensate?: boolean;
177
+ }): Promise<WorkflowRun>;
178
+ continue(id: string): Promise<WorkflowRun>;
179
+ /**
180
+ * Live-tail a run's lifecycle events over SSE (replaces polling). Calls `onEvent` per event;
181
+ * returns a function to close the stream. Cross-pod when the server transport has a control plane.
182
+ */
183
+ streamRun(id: string, onEvent: (event: EngineEvent) => void): () => void;
184
+ };
185
+ export { type SubProcess, groupSubProcesses } from './group-subprocesses';
186
+ //# sourceMappingURL=durable-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-client.d.ts","sourceRoot":"","sources":["../../src/client/durable-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Refine a run's stored status for display. The engine keeps `suspended` for every durably-parked
3
+ * run (it's what drives recovery/timers/queries — we never change that), but to a human a run whose
4
+ * remote step is being executed by a worker right now is `running`, a durable sleep is `sleeping`,
5
+ * and a wait on a signal is `awaiting`. Pass `timeline` (the detail view has it) for full precision;
6
+ * without it (the run list) a non-timer suspend reads as `running` — open and in progress — rather
7
+ * than the catch-all `suspended`.
8
+ */
9
+ export function runDisplayStatus(run, timeline) {
10
+ if (run.status !== 'suspended')
11
+ return run.status;
12
+ // a remote step is in flight (`pending`) or a local step body is executing (`running`)
13
+ if (timeline?.some((s) => s.status === 'pending' || s.status === 'running'))
14
+ return 'running';
15
+ if (run.wakeAt != null)
16
+ return 'sleeping'; // parked on a durable timer
17
+ if (timeline)
18
+ return 'awaiting'; // timeline known, nothing pending, no timer → waiting on a signal
19
+ return 'running'; // list view (no timeline): show open runs as in-progress, not the generic suspended
20
+ }
21
+ function apiBase() {
22
+ if (typeof window !== 'undefined' && window.__DURABLE_API__)
23
+ return window.__DURABLE_API__;
24
+ const base = (typeof window !== 'undefined' && window.__DURABLE_BASE__) || '/durable';
25
+ return `${base}/api`;
26
+ }
27
+ async function http(path, init) {
28
+ const res = await fetch(apiBase() + path, init);
29
+ if (!res.ok)
30
+ throw new Error(`${res.status} ${res.statusText}`);
31
+ return (await res.json());
32
+ }
33
+ export const durableClient = {
34
+ runs(status, tag, attr) {
35
+ const q = new URLSearchParams();
36
+ if (status)
37
+ q.set('status', status);
38
+ if (tag)
39
+ q.set('tag', tag);
40
+ // Each `attr` is a `key:op:value` predicate; repeated params are ANDed server-side.
41
+ for (const a of attr ?? [])
42
+ q.append('attr', a);
43
+ const qs = q.toString();
44
+ return http(qs ? `/runs?${qs}` : '/runs');
45
+ },
46
+ run(id) {
47
+ return http(`/runs/${encodeURIComponent(id)}`);
48
+ },
49
+ /** Per-group worker health (queue backlog + live worker heartbeats) for the Workers panel. */
50
+ workers() {
51
+ return http('/workers');
52
+ },
53
+ retry(id) {
54
+ return http(`/runs/${encodeURIComponent(id)}/retry`, { method: 'POST' });
55
+ },
56
+ /** Fix-and-replay: re-run a dead/failed run with a corrected input. Returns the new run's id. */
57
+ retryWithInput(id, input) {
58
+ return http(`/runs/${encodeURIComponent(id)}/retry-with-input`, {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({ input }),
62
+ });
63
+ },
64
+ /** Bulk retry/cancel every run matching a filter. Returns how many matched + were acted on. */
65
+ bulk(action, filter) {
66
+ const q = new URLSearchParams();
67
+ if (filter.status)
68
+ q.set('status', filter.status);
69
+ if (filter.tag)
70
+ q.set('tag', filter.tag);
71
+ for (const a of filter.attr ?? [])
72
+ q.append('attr', a);
73
+ const qs = q.toString();
74
+ return http(`/bulk/${action}${qs ? `?${qs}` : ''}`, {
75
+ method: 'POST',
76
+ });
77
+ },
78
+ cancel(id, opts) {
79
+ const qs = opts?.compensate ? '?compensate=true' : '';
80
+ return http(`/runs/${encodeURIComponent(id)}/cancel${qs}`, { method: 'POST' });
81
+ },
82
+ continue(id) {
83
+ return http(`/runs/${encodeURIComponent(id)}/continue`, { method: 'POST' });
84
+ },
85
+ /**
86
+ * Live-tail a run's lifecycle events over SSE (replaces polling). Calls `onEvent` per event;
87
+ * returns a function to close the stream. Cross-pod when the server transport has a control plane.
88
+ */
89
+ streamRun(id, onEvent) {
90
+ const source = new EventSource(`${apiBase()}/runs/${encodeURIComponent(id)}/stream`);
91
+ source.onmessage = (msg) => {
92
+ try {
93
+ onEvent(JSON.parse(msg.data));
94
+ }
95
+ catch {
96
+ /* ignore malformed event */
97
+ }
98
+ };
99
+ return () => source.close();
100
+ },
101
+ };
102
+ // Re-export the canonical sub-process grouper so external consumers (e.g. flip's embedded
103
+ // pipeline-runs view) reconstruct sub-processes from a step's events the exact same way the
104
+ // dashboard does — by run identity (`subId`/`name`), treating `phase` events as a sub's lifecycle.
105
+ export { groupSubProcesses } from './group-subprocesses';
106
+ //# sourceMappingURL=durable-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-client.js","sourceRoot":"","sources":["../../src/client/durable-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { StepCheckpoint } from './durable-client';
2
+ /**
3
+ * A timeline rendered as a sequence of nodes: a plain `single` step, or a `fan` — N steps that the
4
+ * engine tagged with the same `parallelGroup` (a `ctx.gather`/`ctx.all` fan-out, e.g. processing's 7
5
+ * handlers) and that therefore ran as siblings, to be laid out at the same level rather than stacked.
6
+ */
7
+ export type TimelineNode = {
8
+ kind: 'single';
9
+ step: StepCheckpoint;
10
+ } | {
11
+ kind: 'fan';
12
+ group: string;
13
+ steps: StepCheckpoint[];
14
+ label: string;
15
+ };
16
+ /**
17
+ * Collapse consecutive steps that share a non-empty `parallelGroup` into one `fan` node (the engine
18
+ * tags every sibling of one `ctx.gather`/`ctx.all` fan with the SAME exact group string — the
19
+ * `gather:`/`all:` prefix is cosmetic, grouping is by exact string). A fan keeps its position where
20
+ * its first member sat; input order is preserved. Steps with no `parallelGroup`, or a group of one,
21
+ * stay `single`.
22
+ */
23
+ export declare function groupParallelSpans(timeline: StepCheckpoint[]): TimelineNode[];
24
+ //# sourceMappingURL=group-parallel-spans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-parallel-spans.d.ts","sourceRoot":"","sources":["../../src/client/group-parallel-spans.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAuB3E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,YAAY,EAAE,CAgC7E"}
@@ -0,0 +1,64 @@
1
+ /** Longest shared leading word-ish prefix across names, trimmed of trailing separators. */
2
+ function commonPrefix(names) {
3
+ if (names.length === 0)
4
+ return '';
5
+ let prefix = names[0] ?? '';
6
+ for (const name of names.slice(1)) {
7
+ let i = 0;
8
+ while (i < prefix.length && i < name.length && prefix[i] === name[i])
9
+ i += 1;
10
+ prefix = prefix.slice(0, i);
11
+ }
12
+ // Trim trailing separators (`handle_` → `handle`) so the label reads cleanly.
13
+ return prefix.replace(/[\s._:-]+$/, '');
14
+ }
15
+ /** A concise summary of a fan: a name-derived label like `handle ×7` (NOT the group prefix). */
16
+ function fanLabel(steps) {
17
+ const prefix = commonPrefix(steps.map((s) => s.name));
18
+ // A single-character shared prefix (e.g. `a`/`b` → ``, but `fA`/`fB` → `f`) reads as noise, not a
19
+ // meaningful label — fall back to `parallel` unless the shared prefix is at least two chars.
20
+ return `${prefix.length >= 2 ? prefix : 'parallel'} ×${steps.length}`;
21
+ }
22
+ /**
23
+ * Collapse consecutive steps that share a non-empty `parallelGroup` into one `fan` node (the engine
24
+ * tags every sibling of one `ctx.gather`/`ctx.all` fan with the SAME exact group string — the
25
+ * `gather:`/`all:` prefix is cosmetic, grouping is by exact string). A fan keeps its position where
26
+ * its first member sat; input order is preserved. Steps with no `parallelGroup`, or a group of one,
27
+ * stay `single`.
28
+ */
29
+ export function groupParallelSpans(timeline) {
30
+ const nodes = [];
31
+ let i = 0;
32
+ while (i < timeline.length) {
33
+ const step = timeline[i];
34
+ if (step === undefined) {
35
+ i += 1;
36
+ continue;
37
+ }
38
+ const group = step.parallelGroup;
39
+ if (group === undefined || group === '') {
40
+ nodes.push({ kind: 'single', step });
41
+ i += 1;
42
+ continue;
43
+ }
44
+ // Gather the consecutive run of steps sharing this exact group.
45
+ const steps = [];
46
+ let j = i;
47
+ while (j < timeline.length && timeline[j]?.parallelGroup === group) {
48
+ const s = timeline[j];
49
+ if (s !== undefined)
50
+ steps.push(s);
51
+ j += 1;
52
+ }
53
+ if (steps.length <= 1) {
54
+ // A lone member is not a fan — render it as an ordinary single row.
55
+ nodes.push({ kind: 'single', step: steps[0] ?? step });
56
+ }
57
+ else {
58
+ nodes.push({ kind: 'fan', group, steps, label: fanLabel(steps) });
59
+ }
60
+ i = j;
61
+ }
62
+ return nodes;
63
+ }
64
+ //# sourceMappingURL=group-parallel-spans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-parallel-spans.js","sourceRoot":"","sources":["../../src/client/group-parallel-spans.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { StepEvent } from './durable-client';
2
+ /** A sub-process reconstructed from a step's events, keyed by run identity. */
3
+ export interface SubProcess {
4
+ id: string;
5
+ name: string;
6
+ group?: string | undefined;
7
+ /** Intermediate transitions (events carrying a `phase`), in arrival order. */
8
+ phases: StepEvent[];
9
+ /** Log lines owned by this sub-process (no `phase`, no `status`), in arrival order. */
10
+ logs: StepEvent[];
11
+ /** The terminal event (carries a `status`), if the sub has finished. */
12
+ terminal?: StepEvent | undefined;
13
+ status?: 'ok' | 'failed' | 'skipped' | undefined;
14
+ /** Earliest `at` across this sub's events. */
15
+ startedAt?: number | undefined;
16
+ /** `data.durationMs` when provided, else `terminal.at − startedAt`. */
17
+ durationMs?: number | undefined;
18
+ }
19
+ /**
20
+ * Group a step's events into sub-processes by run identity (`subId`, falling back to `name` then the
21
+ * legacy `process` tag). Events with no owner (step-level logs) are returned separately. `Map`
22
+ * iteration preserves first-seen order, so subs come back in the order they first appeared.
23
+ */
24
+ export declare function groupSubProcesses(events: StepEvent[]): {
25
+ subs: SubProcess[];
26
+ stepLogs: StepEvent[];
27
+ };
28
+ //# sourceMappingURL=group-subprocesses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-subprocesses.d.ts","sourceRoot":"","sources":["../../src/client/group-subprocesses.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,8EAA8E;IAC9E,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,uFAAuF;IACvF,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACjC,MAAM,CAAC,EAAE,IAAI,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IACjD,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAUD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG;IACtD,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB,CA8CA"}
@@ -0,0 +1,62 @@
1
+ function durationFromData(data) {
2
+ if (typeof data === 'object' && data !== null && 'durationMs' in data) {
3
+ const value = data.durationMs;
4
+ if (typeof value === 'number')
5
+ return value;
6
+ }
7
+ return undefined;
8
+ }
9
+ /**
10
+ * Group a step's events into sub-processes by run identity (`subId`, falling back to `name` then the
11
+ * legacy `process` tag). Events with no owner (step-level logs) are returned separately. `Map`
12
+ * iteration preserves first-seen order, so subs come back in the order they first appeared.
13
+ */
14
+ export function groupSubProcesses(events) {
15
+ const byId = new Map();
16
+ const stepLogs = [];
17
+ for (const event of events) {
18
+ // key === undefined iff the event carries none of subId/name/process — a step-level log.
19
+ const key = event.subId ?? event.name ?? event.process;
20
+ if (key === undefined) {
21
+ stepLogs.push(event);
22
+ continue;
23
+ }
24
+ const existing = byId.get(key);
25
+ const sub = existing ?? { id: key, name: event.name ?? key, phases: [], logs: [] };
26
+ if (!existing)
27
+ byId.set(key, sub);
28
+ if (event.name !== undefined)
29
+ sub.name = event.name;
30
+ if (event.group !== undefined)
31
+ sub.group = event.group;
32
+ if (event.status !== undefined) {
33
+ sub.terminal = event;
34
+ sub.status = event.status;
35
+ }
36
+ else if (event.phase !== undefined) {
37
+ sub.phases.push(event);
38
+ }
39
+ else {
40
+ sub.logs.push(event);
41
+ }
42
+ }
43
+ const subs = [...byId.values()].map((sub) => {
44
+ const nonTerminal = [...sub.phases, ...sub.logs];
45
+ const stamps = [...nonTerminal, ...(sub.terminal ? [sub.terminal] : [])].map((e) => e.at);
46
+ // reduce, not Math.min(...spread): a noisy sub can have tens of thousands of log events,
47
+ // which would overflow the call-stack argument limit.
48
+ const startedAt = stamps.length
49
+ ? stamps.reduce((a, b) => (b < a ? b : a), Number.POSITIVE_INFINITY)
50
+ : undefined;
51
+ const fromData = durationFromData(sub.terminal?.data);
52
+ // Duration is known only when there's a non-terminal stamp to measure from; a terminal-only
53
+ // sub (e.g. legacy `sub(name, status)` with no phases/logs) has an UNKNOWN duration, not 0.
54
+ const durationMs = fromData ??
55
+ (sub.terminal && startedAt !== undefined && nonTerminal.length > 0
56
+ ? sub.terminal.at - startedAt
57
+ : undefined);
58
+ return { ...sub, startedAt, durationMs };
59
+ });
60
+ return { subs, stepLogs };
61
+ }
62
+ //# sourceMappingURL=group-subprocesses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-subprocesses.js","sourceRoot":"","sources":["../../src/client/group-subprocesses.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { RunDetail } from './durable-client';
2
+ /**
3
+ * Merge a freshly-fetched run with the previously-cached one, preserving the live event trail the SSE
4
+ * stream accumulated for in-flight steps.
5
+ *
6
+ * The store only persists a step's `events` at COMPLETION, so a poll/refetch returns a still-running
7
+ * step with empty `events`. The live-tail (`step.progress`) appends events into the cache as they
8
+ * arrive; replacing the cache wholesale on every 1.5s refetch would wipe that streamed trail — it
9
+ * then reappears on the next stream event, so sub-processes flicker (appear → vanish → reappear).
10
+ *
11
+ * Rule: a step still `pending`/`running` with no fetched events keeps the streamed copy; a
12
+ * `completed`/`failed` step is authoritative and always uses the fetched events (even if empty).
13
+ */
14
+ export declare function mergeLiveEvents(prev: RunDetail | undefined, fresh: RunDetail): RunDetail;
15
+ //# sourceMappingURL=merge-live-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-live-events.d.ts","sourceRoot":"","sources":["../../src/client/merge-live-events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,CAcxF"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Merge a freshly-fetched run with the previously-cached one, preserving the live event trail the SSE
3
+ * stream accumulated for in-flight steps.
4
+ *
5
+ * The store only persists a step's `events` at COMPLETION, so a poll/refetch returns a still-running
6
+ * step with empty `events`. The live-tail (`step.progress`) appends events into the cache as they
7
+ * arrive; replacing the cache wholesale on every 1.5s refetch would wipe that streamed trail — it
8
+ * then reappears on the next stream event, so sub-processes flicker (appear → vanish → reappear).
9
+ *
10
+ * Rule: a step still `pending`/`running` with no fetched events keeps the streamed copy; a
11
+ * `completed`/`failed` step is authoritative and always uses the fetched events (even if empty).
12
+ */
13
+ export function mergeLiveEvents(prev, fresh) {
14
+ if (!prev)
15
+ return fresh;
16
+ const prevBySeq = new Map(prev.timeline.map((step) => [step.seq, step]));
17
+ return {
18
+ ...fresh,
19
+ timeline: fresh.timeline.map((step) => {
20
+ const inFlight = step.status === 'pending' || step.status === 'running';
21
+ if (inFlight && (step.events?.length ?? 0) === 0) {
22
+ const streamed = prevBySeq.get(step.seq)?.events;
23
+ if (streamed?.length)
24
+ return { ...step, events: streamed };
25
+ }
26
+ return step;
27
+ }),
28
+ };
29
+ }
30
+ //# sourceMappingURL=merge-live-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-live-events.js","sourceRoot":"","sources":["../../src/client/merge-live-events.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,IAA2B,EAAE,KAAgB;IAC3E,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO;QACL,GAAG,KAAK;QACR,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC;YACxE,IAAI,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjD,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;gBACjD,IAAI,QAAQ,EAAE,MAAM;oBAAE,OAAO,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;YAC7D,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;KACH,CAAC;AACJ,CAAC"}