bunqueue 2.7.0 → 2.7.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.
Files changed (32) hide show
  1. package/README.md +6 -0
  2. package/dist/client/workflow/emitter.d.ts +18 -0
  3. package/dist/client/workflow/emitter.d.ts.map +1 -0
  4. package/dist/client/workflow/emitter.js +75 -0
  5. package/dist/client/workflow/emitter.js.map +1 -0
  6. package/dist/client/workflow/engine.d.ts +16 -1
  7. package/dist/client/workflow/engine.d.ts.map +1 -1
  8. package/dist/client/workflow/engine.js +42 -1
  9. package/dist/client/workflow/engine.js.map +1 -1
  10. package/dist/client/workflow/executor.d.ts +13 -11
  11. package/dist/client/workflow/executor.d.ts.map +1 -1
  12. package/dist/client/workflow/executor.js +117 -123
  13. package/dist/client/workflow/executor.js.map +1 -1
  14. package/dist/client/workflow/index.d.ts +2 -1
  15. package/dist/client/workflow/index.d.ts.map +1 -1
  16. package/dist/client/workflow/index.js +1 -0
  17. package/dist/client/workflow/index.js.map +1 -1
  18. package/dist/client/workflow/runner.d.ts +21 -0
  19. package/dist/client/workflow/runner.d.ts.map +1 -0
  20. package/dist/client/workflow/runner.js +157 -0
  21. package/dist/client/workflow/runner.js.map +1 -0
  22. package/dist/client/workflow/store.d.ts +6 -0
  23. package/dist/client/workflow/store.d.ts.map +1 -1
  24. package/dist/client/workflow/store.js +55 -0
  25. package/dist/client/workflow/store.js.map +1 -1
  26. package/dist/client/workflow/types.d.ts +50 -0
  27. package/dist/client/workflow/types.d.ts.map +1 -1
  28. package/dist/client/workflow/workflow.d.ts +5 -1
  29. package/dist/client/workflow/workflow.d.ts.map +1 -1
  30. package/dist/client/workflow/workflow.js +29 -0
  31. package/dist/client/workflow/workflow.js.map +1 -1
  32. package/package.json +13 -2
package/README.md CHANGED
@@ -519,7 +519,13 @@ await engine.start('order-pipeline', { orderId: 'ORD-1', amount: 99.99 });
519
519
 
520
520
  - **Saga pattern** — Compensation handlers run in reverse when a step fails
521
521
  - **Branching** — Route to different paths based on runtime conditions
522
+ - **Parallel steps** — Run independent steps concurrently with `.parallel()`
522
523
  - **Human-in-the-loop** — `waitFor('event')` pauses execution, `engine.signal()` resumes it
524
+ - **Signal timeout** — `waitFor('event', { timeout })` fails if signal doesn't arrive in time
525
+ - **Step retry** — Automatic retry with exponential backoff and jitter
526
+ - **Nested workflows** — Compose workflows with `.subWorkflow()`, child results passed back
527
+ - **Observability** — Typed event emitter with 11 event types (`engine.on/onAny`)
528
+ - **Cleanup & archival** — `engine.cleanup()` / `engine.archive()` for execution history management
523
529
  - **Step timeouts** — Prevent steps from running indefinitely
524
530
  - **Context passing** — Each step accesses input and all previous step results
525
531
  - **SQLite persistence** — Execution state survives restarts
@@ -0,0 +1,18 @@
1
+ /**
2
+ * WorkflowEmitter - Typed event system for workflow observability
3
+ */
4
+ import type { WorkflowEventType, StepEvent, WorkflowLifecycleEvent, WorkflowEventListener, ExecutionState } from './types';
5
+ export declare class WorkflowEmitter {
6
+ private readonly listeners;
7
+ private readonly globalListeners;
8
+ on(type: WorkflowEventType, listener: WorkflowEventListener): this;
9
+ onAny(listener: WorkflowEventListener): this;
10
+ off(type: WorkflowEventType, listener: WorkflowEventListener): this;
11
+ offAny(listener: WorkflowEventListener): this;
12
+ emitStep(type: 'step:started' | 'step:completed' | 'step:failed' | 'step:retry', executionId: string, workflowName: string, stepName: string, extra?: Partial<Omit<StepEvent, 'type' | 'executionId' | 'workflowName' | 'timestamp' | 'stepName'>>): void;
13
+ emitWorkflow(type: 'workflow:started' | 'workflow:completed' | 'workflow:failed' | 'workflow:compensating' | 'workflow:waiting', executionId: string, workflowName: string, state: ExecutionState, extra?: Partial<Omit<WorkflowLifecycleEvent, 'type' | 'executionId' | 'workflowName' | 'timestamp' | 'state'>>): void;
14
+ emitSignal(type: 'signal:received' | 'signal:timeout', executionId: string, workflowName: string, event: string, payload?: unknown): void;
15
+ removeAllListeners(): void;
16
+ private dispatch;
17
+ }
18
+ //# sourceMappingURL=emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../../../src/client/workflow/emitter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,iBAAiB,EAEjB,SAAS,EACT,sBAAsB,EAEtB,qBAAqB,EACrB,cAAc,EACf,MAAM,SAAS,CAAC;AAEjB,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4D;IACtF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IAEpE,EAAE,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAUlE,KAAK,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAK5C,GAAG,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAKnE,MAAM,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAK7C,QAAQ,CACN,IAAI,EAAE,cAAc,GAAG,gBAAgB,GAAG,aAAa,GAAG,YAAY,EACtE,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,OAAO,CACb,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,WAAW,GAAG,UAAU,CAAC,CACpF,GACA,IAAI;IAYP,YAAY,CACV,IAAI,EACA,kBAAkB,GAClB,oBAAoB,GACpB,iBAAiB,GACjB,uBAAuB,GACvB,kBAAkB,EACtB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,cAAc,EACrB,KAAK,CAAC,EAAE,OAAO,CACb,IAAI,CAAC,sBAAsB,EAAE,MAAM,GAAG,aAAa,GAAG,cAAc,GAAG,WAAW,GAAG,OAAO,CAAC,CAC9F,GACA,IAAI;IAYP,UAAU,CACR,IAAI,EAAE,iBAAiB,GAAG,gBAAgB,EAC1C,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,OAAO,GAChB,IAAI;IAYP,kBAAkB,IAAI,IAAI;IAK1B,OAAO,CAAC,QAAQ;CAOjB"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * WorkflowEmitter - Typed event system for workflow observability
3
+ */
4
+ export class WorkflowEmitter {
5
+ listeners = new Map();
6
+ globalListeners = new Set();
7
+ on(type, listener) {
8
+ let set = this.listeners.get(type);
9
+ if (!set) {
10
+ set = new Set();
11
+ this.listeners.set(type, set);
12
+ }
13
+ set.add(listener);
14
+ return this;
15
+ }
16
+ onAny(listener) {
17
+ this.globalListeners.add(listener);
18
+ return this;
19
+ }
20
+ off(type, listener) {
21
+ this.listeners.get(type)?.delete(listener);
22
+ return this;
23
+ }
24
+ offAny(listener) {
25
+ this.globalListeners.delete(listener);
26
+ return this;
27
+ }
28
+ emitStep(type, executionId, workflowName, stepName, extra) {
29
+ const event = {
30
+ type,
31
+ executionId,
32
+ workflowName,
33
+ timestamp: Date.now(),
34
+ stepName,
35
+ ...extra,
36
+ };
37
+ this.dispatch(type, event);
38
+ }
39
+ emitWorkflow(type, executionId, workflowName, state, extra) {
40
+ const event = {
41
+ type,
42
+ executionId,
43
+ workflowName,
44
+ timestamp: Date.now(),
45
+ state,
46
+ ...extra,
47
+ };
48
+ this.dispatch(type, event);
49
+ }
50
+ emitSignal(type, executionId, workflowName, event, payload) {
51
+ const evt = {
52
+ type,
53
+ executionId,
54
+ workflowName,
55
+ timestamp: Date.now(),
56
+ event,
57
+ payload,
58
+ };
59
+ this.dispatch(type, evt);
60
+ }
61
+ removeAllListeners() {
62
+ this.listeners.clear();
63
+ this.globalListeners.clear();
64
+ }
65
+ dispatch(type, event) {
66
+ const typed = this.listeners.get(type);
67
+ if (typed) {
68
+ for (const fn of typed)
69
+ fn(event);
70
+ }
71
+ for (const fn of this.globalListeners)
72
+ fn(event);
73
+ }
74
+ }
75
+ //# sourceMappingURL=emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.js","sourceRoot":"","sources":["../../../src/client/workflow/emitter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,MAAM,OAAO,eAAe;IACT,SAAS,GAAG,IAAI,GAAG,EAAiD,CAAC;IACrE,eAAe,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEpE,EAAE,CAAC,IAAuB,EAAE,QAA+B;QACzD,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,QAA+B;QACnC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,GAAG,CAAC,IAAuB,EAAE,QAA+B;QAC1D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,CAAC,QAA+B;QACpC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,CACN,IAAsE,EACtE,WAAmB,EACnB,YAAoB,EACpB,QAAgB,EAChB,KAEC;QAED,MAAM,KAAK,GAAc;YACvB,IAAI;YACJ,WAAW;YACX,YAAY;YACZ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,QAAQ;YACR,GAAG,KAAK;SACT,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,YAAY,CACV,IAKsB,EACtB,WAAmB,EACnB,YAAoB,EACpB,KAAqB,EACrB,KAEC;QAED,MAAM,KAAK,GAA2B;YACpC,IAAI;YACJ,WAAW;YACX,YAAY;YACZ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,KAAK;YACL,GAAG,KAAK;SACT,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,UAAU,CACR,IAA0C,EAC1C,WAAmB,EACnB,YAAoB,EACpB,KAAa,EACb,OAAiB;QAEjB,MAAM,GAAG,GAAgB;YACvB,IAAI;YACJ,WAAW;YACX,YAAY;YACZ,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,KAAK;YACL,OAAO;SACR,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,kBAAkB;QAChB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;IAEO,QAAQ,CAAC,IAAuB,EAAE,KAAoB;QAC5D,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,MAAM,EAAE,IAAI,KAAK;gBAAE,EAAE,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,eAAe;YAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC;CACF"}
@@ -3,12 +3,13 @@
3
3
  * Manages lifecycle of internal Queue, Worker, and Store.
4
4
  */
5
5
  import type { Workflow } from './workflow';
6
- import type { EngineOptions, RunHandle, Execution, ExecutionState } from './types';
6
+ import type { EngineOptions, RunHandle, Execution, ExecutionState, WorkflowEventType, WorkflowEventListener } from './types';
7
7
  export declare class Engine {
8
8
  private readonly queue;
9
9
  private readonly worker;
10
10
  private readonly store;
11
11
  private readonly executor;
12
+ private readonly emitter;
12
13
  constructor(opts?: EngineOptions);
13
14
  /** Register a workflow definition */
14
15
  register(workflow: Workflow): this;
@@ -20,6 +21,20 @@ export declare class Engine {
20
21
  listExecutions(workflowName?: string, state?: ExecutionState): Execution[];
21
22
  /** Send a signal to a waiting execution */
22
23
  signal(executionId: string, event: string, payload?: unknown): Promise<void>;
24
+ /** Subscribe to a specific workflow event type */
25
+ on(type: WorkflowEventType, listener: WorkflowEventListener): this;
26
+ /** Subscribe to all workflow events */
27
+ onAny(listener: WorkflowEventListener): this;
28
+ /** Unsubscribe from a specific event type */
29
+ off(type: WorkflowEventType, listener: WorkflowEventListener): this;
30
+ /** Unsubscribe a catch-all listener */
31
+ offAny(listener: WorkflowEventListener): this;
32
+ /** Remove old completed/failed executions */
33
+ cleanup(maxAgeMs: number, states?: ExecutionState[]): number;
34
+ /** Archive old executions to a separate table */
35
+ archive(maxAgeMs: number, states?: ExecutionState[]): number;
36
+ /** Get archived execution count */
37
+ getArchivedCount(): number;
23
38
  /** Shut down the engine */
24
39
  close(force?: boolean): Promise<void>;
25
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/client/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAe,MAAM,SAAS,CAAC;AAIhG,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;gBAEhC,IAAI,GAAE,aAAkB;IA2BpC,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAKlC,qCAAqC;IAC/B,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAItE,gCAAgC;IAChC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAI1C,4CAA4C;IAC5C,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,EAAE;IAI1E,2CAA2C;IACrC,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlF,2BAA2B;IACrB,KAAK,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAK1C"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/client/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,SAAS,EACT,cAAc,EAEd,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAIjB,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;gBAE9B,IAAI,GAAE,aAAkB;IAiCpC,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAKlC,qCAAqC;IAC/B,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAItE,gCAAgC;IAChC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAI1C,4CAA4C;IAC5C,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,cAAc,GAAG,SAAS,EAAE;IAI1E,2CAA2C;IACrC,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlF,kDAAkD;IAClD,EAAE,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAKlE,uCAAuC;IACvC,KAAK,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAK5C,6CAA6C;IAC7C,GAAG,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAKnE,uCAAuC;IACvC,MAAM,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAO7C,6CAA6C;IAC7C,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,EAAE,GAAG,MAAM;IAI5D,iDAAiD;IACjD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,cAAc,EAAE,GAAG,MAAM;IAI5D,mCAAmC;IACnC,gBAAgB,IAAI,MAAM;IAI1B,2BAA2B;IACrB,KAAK,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CAM1C"}
@@ -6,12 +6,14 @@ import { Queue } from '../queue/queue';
6
6
  import { Worker } from '../worker/worker';
7
7
  import { WorkflowStore } from './store';
8
8
  import { WorkflowExecutor } from './executor';
9
+ import { WorkflowEmitter } from './emitter';
9
10
  const DEFAULT_QUEUE_NAME = '__wf:steps';
10
11
  export class Engine {
11
12
  queue;
12
13
  worker;
13
14
  store;
14
15
  executor;
16
+ emitter;
15
17
  constructor(opts = {}) {
16
18
  const queueName = opts.queueName ?? DEFAULT_QUEUE_NAME;
17
19
  this.queue = new Queue(queueName, {
@@ -20,7 +22,11 @@ export class Engine {
20
22
  dataPath: opts.dataPath,
21
23
  });
22
24
  this.store = new WorkflowStore(opts.dataPath);
23
- this.executor = new WorkflowExecutor(this.store, this.queue);
25
+ this.emitter = new WorkflowEmitter();
26
+ if (opts.onEvent) {
27
+ this.emitter.onAny(opts.onEvent);
28
+ }
29
+ this.executor = new WorkflowExecutor(this.store, this.queue, this.emitter);
24
30
  this.worker = new Worker(queueName, async (job) => {
25
31
  const data = job.data;
26
32
  return this.executor.processStep(data);
@@ -52,11 +58,46 @@ export class Engine {
52
58
  async signal(executionId, event, payload) {
53
59
  return this.executor.signal(executionId, event, payload);
54
60
  }
61
+ // ============ Observability ============
62
+ /** Subscribe to a specific workflow event type */
63
+ on(type, listener) {
64
+ this.emitter.on(type, listener);
65
+ return this;
66
+ }
67
+ /** Subscribe to all workflow events */
68
+ onAny(listener) {
69
+ this.emitter.onAny(listener);
70
+ return this;
71
+ }
72
+ /** Unsubscribe from a specific event type */
73
+ off(type, listener) {
74
+ this.emitter.off(type, listener);
75
+ return this;
76
+ }
77
+ /** Unsubscribe a catch-all listener */
78
+ offAny(listener) {
79
+ this.emitter.offAny(listener);
80
+ return this;
81
+ }
82
+ // ============ Cleanup ============
83
+ /** Remove old completed/failed executions */
84
+ cleanup(maxAgeMs, states) {
85
+ return this.store.cleanup(maxAgeMs, states);
86
+ }
87
+ /** Archive old executions to a separate table */
88
+ archive(maxAgeMs, states) {
89
+ return this.store.archive(maxAgeMs, states);
90
+ }
91
+ /** Get archived execution count */
92
+ getArchivedCount() {
93
+ return this.store.getArchivedCount();
94
+ }
55
95
  /** Shut down the engine */
56
96
  async close(force = false) {
57
97
  await this.worker.close(force);
58
98
  this.queue.close();
59
99
  this.store.close();
100
+ this.emitter.removeAllListeners();
60
101
  }
61
102
  }
62
103
  //# sourceMappingURL=engine.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/client/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAI9C,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAExC,MAAM,OAAO,MAAM;IACA,KAAK,CAAQ;IACb,MAAM,CAAS;IACf,KAAK,CAAgB;IACrB,QAAQ,CAAmB;IAE5C,YAAY,OAAsB,EAAE;QAClC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;QAEvD,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE;YAChC,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;QAEH,IAAI,CAAC,KAAK,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,QAAQ,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAE7D,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CACtB,SAAS,EACT,KAAK,EAAE,GAAG,EAAE,EAAE;YACZ,MAAM,IAAI,GAAG,GAAG,CAAC,IAA8B,CAAC;YAChD,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC,EACD;YACE,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC;SACnC,CACF,CAAC;IACJ,CAAC;IAED,qCAAqC;IACrC,QAAQ,CAAC,QAAkB;QACzB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,KAAK,CAAC,YAAoB,EAAE,KAAe;QAC/C,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,gCAAgC;IAChC,YAAY,CAAC,EAAU;QACrB,OAAO,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC;IAED,4CAA4C;IAC5C,cAAc,CAAC,YAAqB,EAAE,KAAsB;QAC1D,OAAO,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAC3D,CAAC;IAED,2CAA2C;IAC3C,KAAK,CAAC,MAAM,CAAC,WAAmB,EAAE,KAAa,EAAE,OAAiB;QAChE,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED,2BAA2B;IAC3B,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK;QACvB,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF"}
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/client/workflow/engine.ts"],"names":[],"mappings":"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"}
@@ -5,28 +5,30 @@
5
5
  import type { Queue } from '../queue/queue';
6
6
  import type { Workflow } from './workflow';
7
7
  import type { WorkflowStore } from './store';
8
+ import type { WorkflowEmitter } from './emitter';
8
9
  import type { Execution, StepJobData, RunHandle } from './types';
9
10
  export declare class WorkflowExecutor {
10
11
  private readonly store;
11
12
  private readonly queue;
13
+ private readonly emitter;
12
14
  private readonly workflows;
13
- constructor(store: WorkflowStore, queue: Queue);
15
+ private readonly timeoutTimers;
16
+ constructor(store: WorkflowStore, queue: Queue, emitter?: WorkflowEmitter | null);
14
17
  register(workflow: Workflow): void;
15
18
  start(workflowName: string, input: unknown): Promise<RunHandle>;
16
- /** Process a step job — this is the Worker processor function */
17
19
  processStep(data: StepJobData): Promise<unknown>;
18
20
  signal(executionId: string, event: string, payload: unknown): Promise<void>;
19
21
  getExecution(id: string): Execution | null;
20
22
  listExecutions(workflowName?: string, state?: Execution['state']): Execution[];
21
23
  private executeNode;
22
- private executeStep;
23
- private executeBranch;
24
- private executeWaitFor;
25
- private advanceToNext;
26
- private enqueueNode;
27
- private runCompensation;
28
- private findStepDef;
29
- private buildContext;
30
- private runWithTimeout;
24
+ private runStep;
25
+ private runBranch;
26
+ private runParallel;
27
+ private runSubWorkflow;
28
+ private runWaitFor;
29
+ private advance;
30
+ private enqueue;
31
+ private scheduleTimeoutCheck;
32
+ private compensate;
31
33
  }
32
34
  //# sourceMappingURL=executor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/client/workflow/executor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,SAAS,EAIV,MAAM,SAAS,CAAC;AASjB,qBAAa,gBAAgB;IAIzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;gBAGtC,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,KAAK;IAG/B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAS5B,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAuBrE,iEAAiE;IAC3D,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA2BhD,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjF,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAI1C,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,EAAE;YAMhE,WAAW;YAeX,WAAW;YAiCX,aAAa;YA2Bb,cAAc;YAkBd,aAAa;YASb,WAAW;YASX,eAAe;IA0B7B,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,cAAc;CAmBvB"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/client/workflow/executor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAgC,MAAM,SAAS,CAAC;AAe/F,qBAAa,gBAAgB;IAKzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoD;gBAG/D,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,KAAK,EACZ,OAAO,GAAE,eAAe,GAAG,IAAW;IAGzD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAS5B,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC;IAwB/D,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BhD,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjF,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAI1C,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,EAAE;YAMhE,WAAW;YAaX,OAAO;YAQP,SAAS;YAkBT,WAAW;YAYX,cAAc;YAiBd,UAAU;YA6CV,OAAO;YAYP,OAAO;IASrB,OAAO,CAAC,oBAAoB;YASd,UAAU;CAwBzB"}
@@ -2,7 +2,7 @@
2
2
  * WorkflowExecutor - Core execution logic
3
3
  * Processes steps as bunqueue jobs, handles transitions, branching, compensation.
4
4
  */
5
- /** Thrown when a step needs to wait for a signal */
5
+ import { executeStepWithRetry, executeParallelSteps, executeSubWorkflow, findStepDef, buildContext, } from './runner';
6
6
  class WaitForSignalError extends Error {
7
7
  event;
8
8
  constructor(event) {
@@ -13,10 +13,13 @@ class WaitForSignalError extends Error {
13
13
  export class WorkflowExecutor {
14
14
  store;
15
15
  queue;
16
+ emitter;
16
17
  workflows = new Map();
17
- constructor(store, queue) {
18
+ timeoutTimers = new Map();
19
+ constructor(store, queue, emitter = null) {
18
20
  this.store = store;
19
21
  this.queue = queue;
22
+ this.emitter = emitter;
20
23
  }
21
24
  register(workflow) {
22
25
  const names = workflow.getStepNames();
@@ -45,14 +48,17 @@ export class WorkflowExecutor {
45
48
  updatedAt: now,
46
49
  };
47
50
  this.store.save(exec);
48
- await this.enqueueNode(exec);
51
+ this.emitter?.emitWorkflow('workflow:started', exec.id, workflowName, 'running', { input });
52
+ await this.enqueue(exec);
49
53
  return { id: exec.id, workflowName };
50
54
  }
51
- /** Process a step job — this is the Worker processor function */
52
55
  async processStep(data) {
53
56
  const exec = this.store.get(data.executionId);
54
- if (exec?.state !== 'running')
57
+ if (!exec || (exec.state !== 'running' && exec.state !== 'waiting'))
55
58
  return null;
59
+ // If waiting, set back to running for timeout re-check
60
+ if (exec.state === 'waiting')
61
+ exec.state = 'running';
56
62
  const wf = this.workflows.get(exec.workflowName);
57
63
  if (!wf)
58
64
  throw new Error(`Workflow "${exec.workflowName}" not registered`);
@@ -60,6 +66,7 @@ export class WorkflowExecutor {
60
66
  if (!node) {
61
67
  exec.state = 'completed';
62
68
  this.store.update(exec);
69
+ this.emitter?.emitWorkflow('workflow:completed', exec.id, exec.workflowName, 'completed');
63
70
  return null;
64
71
  }
65
72
  try {
@@ -70,7 +77,8 @@ export class WorkflowExecutor {
70
77
  return null;
71
78
  exec.state = 'failed';
72
79
  this.store.update(exec);
73
- await this.runCompensation(exec, wf);
80
+ this.emitter?.emitWorkflow('workflow:failed', exec.id, exec.workflowName, 'failed');
81
+ await this.compensate(exec, wf);
74
82
  throw err;
75
83
  }
76
84
  return null;
@@ -79,10 +87,17 @@ export class WorkflowExecutor {
79
87
  const exec = this.store.get(executionId);
80
88
  if (!exec)
81
89
  throw new Error(`Execution "${executionId}" not found`);
90
+ // Cancel any pending timeout timer for this execution
91
+ const timer = this.timeoutTimers.get(executionId);
92
+ if (timer) {
93
+ clearTimeout(timer);
94
+ this.timeoutTimers.delete(executionId);
95
+ }
82
96
  exec.signals[event] = payload;
83
97
  exec.state = 'running';
84
98
  this.store.update(exec);
85
- await this.enqueueNode(exec);
99
+ this.emitter?.emitSignal('signal:received', exec.id, exec.workflowName, event, payload);
100
+ await this.enqueue(exec);
86
101
  }
87
102
  getExecution(id) {
88
103
  return this.store.get(id);
@@ -90,84 +105,99 @@ export class WorkflowExecutor {
90
105
  listExecutions(workflowName, state) {
91
106
  return this.store.list(workflowName, state);
92
107
  }
93
- // ============ Internal ============
94
- async executeNode(exec, node, nodeIndex, wf) {
95
- if (node.type === 'step') {
96
- await this.executeStep(exec, node.def, nodeIndex, wf);
97
- }
98
- else if (node.type === 'branch') {
99
- await this.executeBranch(exec, node, nodeIndex, wf);
100
- }
101
- else {
102
- await this.executeWaitFor(exec, node, nodeIndex);
103
- }
104
- }
105
- async executeStep(exec, def, nodeIndex, wf) {
106
- const ctx = this.buildContext(exec);
107
- exec.steps[def.name] = { status: 'running', startedAt: Date.now() };
108
- this.store.update(exec);
109
- try {
110
- const result = await this.runWithTimeout(def.handler(ctx), def.timeout);
111
- exec.steps[def.name] = {
112
- status: 'completed',
113
- result,
114
- startedAt: exec.steps[def.name].startedAt,
115
- completedAt: Date.now(),
116
- };
117
- exec.currentNodeIndex = nodeIndex + 1;
118
- this.store.update(exec);
119
- await this.advanceToNext(exec, wf);
120
- }
121
- catch (err) {
122
- exec.steps[def.name] = {
123
- status: 'failed',
124
- error: String(err),
125
- startedAt: exec.steps[def.name].startedAt,
126
- completedAt: Date.now(),
127
- };
128
- this.store.update(exec);
129
- throw err;
130
- }
108
+ // ============ Node dispatch ============
109
+ async executeNode(exec, node, idx, wf) {
110
+ if (node.type === 'step')
111
+ await this.runStep(exec, node.def, idx, wf);
112
+ else if (node.type === 'branch')
113
+ await this.runBranch(exec, node, idx, wf);
114
+ else if (node.type === 'parallel')
115
+ await this.runParallel(exec, node, idx, wf);
116
+ else if (node.type === 'subWorkflow')
117
+ await this.runSubWorkflow(exec, node, idx, wf);
118
+ else
119
+ await this.runWaitFor(exec, node, idx, wf);
120
+ }
121
+ async runStep(exec, def, idx, wf) {
122
+ const ctx = buildContext(exec);
123
+ await executeStepWithRetry(def, ctx, exec, this.emitter, (e) => {
124
+ this.store.update(e);
125
+ });
126
+ await this.advance(exec, idx + 1, wf);
131
127
  }
132
- async executeBranch(exec, node, nodeIndex, wf) {
133
- const ctx = this.buildContext(exec);
134
- const pathName = node.def.condition(ctx);
128
+ async runBranch(exec, node, idx, wf) {
129
+ const pathName = node.def.condition(buildContext(exec));
135
130
  const pathSteps = node.def.paths.get(pathName);
136
- if (!pathSteps || pathSteps.length === 0) {
137
- exec.currentNodeIndex = nodeIndex + 1;
138
- this.store.update(exec);
139
- await this.advanceToNext(exec, wf);
140
- return;
141
- }
142
- // Execute branch steps inline
143
- for (const step of pathSteps) {
144
- await this.executeStep(exec, step, nodeIndex, wf);
131
+ if (pathSteps && pathSteps.length > 0) {
132
+ for (const step of pathSteps) {
133
+ await executeStepWithRetry(step, buildContext(exec), exec, this.emitter, (e) => {
134
+ this.store.update(e);
135
+ });
136
+ }
145
137
  }
146
- exec.currentNodeIndex = nodeIndex + 1;
147
- this.store.update(exec);
148
- await this.advanceToNext(exec, wf);
138
+ await this.advance(exec, idx + 1, wf);
139
+ }
140
+ async runParallel(exec, node, idx, wf) {
141
+ await executeParallelSteps(node.def.steps, buildContext(exec), exec, this.emitter, (e) => {
142
+ this.store.update(e);
143
+ });
144
+ await this.advance(exec, idx + 1, wf);
149
145
  }
150
- async executeWaitFor(exec, node, nodeIndex) {
146
+ async runSubWorkflow(exec, node, idx, wf) {
147
+ const subInput = node.inputMapper(buildContext(exec));
148
+ const result = await executeSubWorkflow(node.name, subInput, (name, input) => this.start(name, input), (id) => this.store.get(id));
149
+ exec.steps[`sub:${node.name}`] = { status: 'completed', result, completedAt: Date.now() };
150
+ await this.advance(exec, idx + 1, wf);
151
+ }
152
+ async runWaitFor(exec, node, idx, wf) {
151
153
  if (exec.signals[node.event] !== undefined) {
152
- exec.currentNodeIndex = nodeIndex + 1;
153
- this.store.update(exec);
154
- await this.enqueueNode(exec);
154
+ await this.advance(exec, idx + 1, wf);
155
155
  return;
156
156
  }
157
- // Not yet received — pause execution
157
+ const waitKey = `__waitFor:${node.event}`;
158
+ if (node.timeout !== undefined) {
159
+ const existing = exec.steps[waitKey];
160
+ const waitingSince = existing?.startedAt ?? Date.now();
161
+ if (!existing) {
162
+ exec.steps[waitKey] = { status: 'running', startedAt: waitingSince };
163
+ }
164
+ if (Date.now() - waitingSince >= node.timeout) {
165
+ this.emitter?.emitSignal('signal:timeout', exec.id, exec.workflowName, node.event);
166
+ exec.steps[waitKey] = {
167
+ status: 'failed',
168
+ error: `Signal "${node.event}" timed out after ${node.timeout}ms`,
169
+ startedAt: waitingSince,
170
+ completedAt: Date.now(),
171
+ };
172
+ exec.state = 'failed';
173
+ this.store.update(exec);
174
+ await this.compensate(exec, wf);
175
+ throw new Error(`Signal "${node.event}" timed out`);
176
+ }
177
+ this.store.update(exec);
178
+ // Schedule a timer to re-check after timeout
179
+ const remaining = node.timeout - (Date.now() - waitingSince);
180
+ this.scheduleTimeoutCheck(exec.id, exec.workflowName, exec.currentNodeIndex, remaining);
181
+ }
158
182
  exec.state = 'waiting';
159
183
  this.store.update(exec);
184
+ this.emitter?.emitWorkflow('workflow:waiting', exec.id, exec.workflowName, 'waiting');
160
185
  throw new WaitForSignalError(node.event);
161
186
  }
162
- async advanceToNext(exec, wf) {
163
- if (exec.currentNodeIndex >= wf.nodes.length) {
187
+ // ============ Helpers ============
188
+ async advance(exec, nextIdx, wf) {
189
+ exec.currentNodeIndex = nextIdx;
190
+ this.store.update(exec);
191
+ if (nextIdx >= wf.nodes.length) {
164
192
  exec.state = 'completed';
165
193
  this.store.update(exec);
166
- return;
194
+ this.emitter?.emitWorkflow('workflow:completed', exec.id, exec.workflowName, 'completed');
195
+ }
196
+ else {
197
+ await this.enqueue(exec);
167
198
  }
168
- await this.enqueueNode(exec);
169
199
  }
170
- async enqueueNode(exec) {
200
+ async enqueue(exec) {
171
201
  const jobData = {
172
202
  executionId: exec.id,
173
203
  workflowName: exec.workflowName,
@@ -175,73 +205,37 @@ export class WorkflowExecutor {
175
205
  };
176
206
  await this.queue.add('wf:step', jobData);
177
207
  }
178
- async runCompensation(exec, wf) {
179
- const completedSteps = Object.entries(exec.steps)
180
- .filter(([, s]) => s.status === 'completed')
208
+ scheduleTimeoutCheck(execId, workflowName, nodeIdx, ms) {
209
+ const timer = setTimeout(() => {
210
+ this.timeoutTimers.delete(execId);
211
+ const jobData = { executionId: execId, workflowName, nodeIndex: nodeIdx };
212
+ this.queue.add('wf:step', jobData).catch(() => { }); // Queue may be closed
213
+ }, ms);
214
+ this.timeoutTimers.set(execId, timer);
215
+ }
216
+ async compensate(exec, wf) {
217
+ const completed = Object.entries(exec.steps)
218
+ .filter(([name, s]) => s.status === 'completed' && !name.startsWith('__'))
181
219
  .reverse();
182
- if (completedSteps.length === 0)
220
+ if (completed.length === 0)
183
221
  return;
184
222
  exec.state = 'compensating';
185
223
  this.store.update(exec);
186
- const ctx = this.buildContext(exec);
187
- for (const [name] of completedSteps) {
188
- const def = this.findStepDef(wf, name);
224
+ this.emitter?.emitWorkflow('workflow:compensating', exec.id, exec.workflowName, 'compensating');
225
+ const ctx = buildContext(exec);
226
+ for (const [name] of completed) {
227
+ const def = findStepDef(wf, name);
189
228
  if (def?.compensate) {
190
229
  try {
191
230
  await def.compensate(ctx);
192
231
  }
193
232
  catch {
194
- // Compensation errors are logged but don't stop the chain
233
+ // Compensation errors don't stop the chain
195
234
  }
196
235
  }
197
236
  }
198
237
  exec.state = 'failed';
199
238
  this.store.update(exec);
200
239
  }
201
- findStepDef(wf, name) {
202
- for (const node of wf.nodes) {
203
- if (node.type === 'step' && node.def.name === name)
204
- return node.def;
205
- if (node.type === 'branch') {
206
- for (const steps of node.def.paths.values()) {
207
- const found = steps.find((s) => s.name === name);
208
- if (found)
209
- return found;
210
- }
211
- }
212
- }
213
- return null;
214
- }
215
- buildContext(exec) {
216
- const stepResults = {};
217
- for (const [name, record] of Object.entries(exec.steps)) {
218
- if (record.status === 'completed')
219
- stepResults[name] = record.result;
220
- }
221
- return {
222
- input: exec.input,
223
- steps: stepResults,
224
- signals: exec.signals,
225
- executionId: exec.id,
226
- };
227
- }
228
- runWithTimeout(promise, timeoutMs) {
229
- if (!(promise instanceof Promise))
230
- return Promise.resolve(promise);
231
- if (timeoutMs <= 0)
232
- return promise;
233
- return new Promise((resolve, reject) => {
234
- const timer = setTimeout(() => {
235
- reject(new Error(`Step timed out after ${timeoutMs}ms`));
236
- }, timeoutMs);
237
- promise.then((v) => {
238
- clearTimeout(timer);
239
- resolve(v);
240
- }, (e) => {
241
- clearTimeout(timer);
242
- reject(e instanceof Error ? e : new Error(String(e)));
243
- });
244
- });
245
- }
246
240
  }
247
241
  //# sourceMappingURL=executor.js.map