bunqueue 2.8.49 → 2.8.50

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 (129) hide show
  1. package/README.md +19 -9
  2. package/dist/application/contextFactory.d.ts +4 -2
  3. package/dist/application/contextFactory.js +3 -0
  4. package/dist/application/flowFailureRecovery.d.ts +17 -0
  5. package/dist/application/flowFailureRecovery.js +92 -0
  6. package/dist/application/operations/ack.d.ts +3 -2
  7. package/dist/application/operations/ack.js +34 -8
  8. package/dist/application/operations/flowPush.d.ts +9 -0
  9. package/dist/application/operations/flowPush.js +112 -0
  10. package/dist/application/operations/flowTopologyValidation.d.ts +3 -0
  11. package/dist/application/operations/flowTopologyValidation.js +101 -0
  12. package/dist/application/operations/flowValidation.d.ts +3 -0
  13. package/dist/application/operations/flowValidation.js +166 -0
  14. package/dist/application/operations/push.d.ts +3 -0
  15. package/dist/application/operations/push.js +2 -2
  16. package/dist/application/operations/pushInsert.d.ts +7 -1
  17. package/dist/application/operations/pushInsert.js +18 -8
  18. package/dist/application/operations/pushLocks.d.ts +1 -0
  19. package/dist/application/operations/pushLocks.js +5 -1
  20. package/dist/application/operations/queryOperations.js +7 -6
  21. package/dist/application/queueManager.d.ts +5 -0
  22. package/dist/application/queueManager.js +195 -60
  23. package/dist/cli/commandRouter.d.ts +1 -1
  24. package/dist/client/flow.d.ts +3 -6
  25. package/dist/client/flow.js +71 -210
  26. package/dist/client/flowAtomic.d.ts +9 -0
  27. package/dist/client/flowAtomic.js +24 -0
  28. package/dist/client/flowJobCoreMethods.d.ts +28 -0
  29. package/dist/client/flowJobCoreMethods.js +151 -0
  30. package/dist/client/flowJobDependencies.d.ts +7 -0
  31. package/dist/client/flowJobDependencies.js +64 -0
  32. package/dist/client/flowJobFactory.d.ts +11 -24
  33. package/dist/client/flowJobFactory.js +91 -360
  34. package/dist/client/flowJobMoveMethods.d.ts +16 -0
  35. package/dist/client/flowJobMoveMethods.js +112 -0
  36. package/dist/client/flowJobTypes.d.ts +29 -0
  37. package/dist/client/flowJobTypes.js +6 -0
  38. package/dist/client/flowLegacyPlan.d.ts +16 -0
  39. package/dist/client/flowLegacyPlan.js +118 -0
  40. package/dist/client/flowOptions.d.ts +10 -0
  41. package/dist/client/flowOptions.js +44 -0
  42. package/dist/client/flowPlan.d.ts +16 -0
  43. package/dist/client/flowPlan.js +93 -0
  44. package/dist/client/flowPush.js +4 -1
  45. package/dist/client/flowReader.d.ts +10 -0
  46. package/dist/client/flowReader.js +147 -0
  47. package/dist/client/jobHelpers.js +2 -0
  48. package/dist/client/workflow/clock.d.ts +3 -1
  49. package/dist/client/workflow/clock.js +18 -1
  50. package/dist/client/workflow/compensationChild.d.ts +30 -0
  51. package/dist/client/workflow/compensationChild.js +65 -0
  52. package/dist/client/workflow/compensationPass.d.ts +16 -0
  53. package/dist/client/workflow/compensationPass.js +96 -0
  54. package/dist/client/workflow/compensationSupport.d.ts +24 -0
  55. package/dist/client/workflow/compensationSupport.js +57 -0
  56. package/dist/client/workflow/compensator.d.ts +11 -50
  57. package/dist/client/workflow/compensator.js +22 -374
  58. package/dist/client/workflow/definitionGuard.d.ts +11 -0
  59. package/dist/client/workflow/definitionGuard.js +22 -0
  60. package/dist/client/workflow/engine.d.ts +2 -2
  61. package/dist/client/workflow/engine.js +8 -8
  62. package/dist/client/workflow/eventTypes.d.ts +44 -0
  63. package/dist/client/workflow/eventTypes.js +2 -0
  64. package/dist/client/workflow/executionTypes.d.ts +133 -0
  65. package/dist/client/workflow/executionTypes.js +2 -0
  66. package/dist/client/workflow/executor.d.ts +4 -17
  67. package/dist/client/workflow/executor.js +50 -208
  68. package/dist/client/workflow/executorLifecycle.d.ts +18 -0
  69. package/dist/client/workflow/executorLifecycle.js +69 -0
  70. package/dist/client/workflow/executorNodes.d.ts +17 -0
  71. package/dist/client/workflow/executorNodes.js +138 -0
  72. package/dist/client/workflow/identity.d.ts +2 -0
  73. package/dist/client/workflow/identity.js +8 -0
  74. package/dist/client/workflow/index.d.ts +1 -1
  75. package/dist/client/workflow/loops.d.ts +3 -5
  76. package/dist/client/workflow/loops.js +25 -23
  77. package/dist/client/workflow/mapRunner.d.ts +4 -0
  78. package/dist/client/workflow/mapRunner.js +45 -0
  79. package/dist/client/workflow/recovery.js +10 -3
  80. package/dist/client/workflow/runner.d.ts +2 -21
  81. package/dist/client/workflow/runner.js +39 -104
  82. package/dist/client/workflow/runnerTiming.d.ts +14 -0
  83. package/dist/client/workflow/runnerTiming.js +66 -0
  84. package/dist/client/workflow/stepTypes.d.ts +171 -0
  85. package/dist/client/workflow/stepTypes.js +3 -0
  86. package/dist/client/workflow/store.d.ts +17 -17
  87. package/dist/client/workflow/store.js +65 -100
  88. package/dist/client/workflow/storeExecutionCodec.d.ts +11 -0
  89. package/dist/client/workflow/storeExecutionCodec.js +34 -0
  90. package/dist/client/workflow/storeListing.d.ts +11 -0
  91. package/dist/client/workflow/storeListing.js +45 -0
  92. package/dist/client/workflow/storeMaintenance.d.ts +4 -0
  93. package/dist/client/workflow/storeMaintenance.js +40 -0
  94. package/dist/client/workflow/storeSignals.d.ts +9 -0
  95. package/dist/client/workflow/storeSignals.js +38 -2
  96. package/dist/client/workflow/subWorkflowRunner.d.ts +13 -0
  97. package/dist/client/workflow/subWorkflowRunner.js +40 -0
  98. package/dist/client/workflow/types.d.ts +4 -356
  99. package/dist/client/workflow/types.js +1 -3
  100. package/dist/client/workflow/waitFor.js +39 -26
  101. package/dist/client/workflow/workflow.d.ts +16 -59
  102. package/dist/client/workflow/workflow.js +53 -179
  103. package/dist/client/workflow/workflowDecisions.d.ts +11 -0
  104. package/dist/client/workflow/workflowDecisions.js +27 -0
  105. package/dist/client/workflow/workflowDefinition.d.ts +16 -0
  106. package/dist/client/workflow/workflowDefinition.js +123 -0
  107. package/dist/client/workflow/workflowIntrospection.d.ts +5 -0
  108. package/dist/client/workflow/workflowIntrospection.js +46 -0
  109. package/dist/client/workflow/workflowValidation.d.ts +44 -0
  110. package/dist/client/workflow/workflowValidation.js +143 -0
  111. package/dist/domain/types/command.d.ts +7 -1
  112. package/dist/domain/types/flow.d.ts +25 -0
  113. package/dist/domain/types/flow.js +1 -0
  114. package/dist/infrastructure/persistence/schema.d.ts +2 -2
  115. package/dist/infrastructure/persistence/schema.js +52 -3
  116. package/dist/infrastructure/persistence/sqlite.d.ts +16 -0
  117. package/dist/infrastructure/persistence/sqlite.js +129 -7
  118. package/dist/infrastructure/persistence/sqliteBatch.js +10 -4
  119. package/dist/infrastructure/persistence/sqliteSerializer.d.ts +2 -0
  120. package/dist/infrastructure/persistence/sqliteSerializer.js +18 -5
  121. package/dist/infrastructure/persistence/statements.d.ts +4 -0
  122. package/dist/infrastructure/persistence/statements.js +8 -2
  123. package/dist/infrastructure/server/handlerRoutes.js +3 -0
  124. package/dist/infrastructure/server/handlers/advanced.js +2 -2
  125. package/dist/infrastructure/server/handlers/flow.d.ts +7 -0
  126. package/dist/infrastructure/server/handlers/flow.js +11 -0
  127. package/dist/infrastructure/server/handlers/index.d.ts +1 -0
  128. package/dist/infrastructure/server/handlers/index.js +1 -0
  129. package/package.json +4 -2
@@ -0,0 +1,96 @@
1
+ /** One checkpointed compensation pass. */
2
+ import { compensationContext, settle } from './compensationSupport';
3
+ import { unwindChild } from './compensationChild';
4
+ import { describeError } from './identity';
5
+ import { buildContext, findStepDef, runWithTimeout } from './runner';
6
+ import { decideUnwindAction } from './unwindPlan';
7
+ export async function unwind(pass, runChild) {
8
+ const { exec, wf, store, emitter, eligible, workflows, retryFailed } = pass;
9
+ const baseCtx = buildContext(exec);
10
+ let haltedAt = null;
11
+ let writeFailure;
12
+ exec.state = 'compensating';
13
+ try {
14
+ store.update(exec);
15
+ }
16
+ catch (error) {
17
+ // Nothing ran, so park the owed unwind and retain the original store error.
18
+ writeFailure = error;
19
+ haltedAt = '(the compensating transition)';
20
+ }
21
+ if (writeFailure === undefined) {
22
+ emitter?.emitWorkflow('workflow:compensating', exec.id, exec.workflowName, 'compensating');
23
+ }
24
+ for (const [name, record] of eligible) {
25
+ if (writeFailure !== undefined)
26
+ break;
27
+ const action = decideUnwindAction(wf, name, record, haltedAt !== null, retryFailed);
28
+ if (action.kind === 'stop')
29
+ break;
30
+ if (action.kind === 'skip')
31
+ continue;
32
+ if (action.kind === 'halt-failed') {
33
+ haltedAt = name;
34
+ continue;
35
+ }
36
+ if (action.kind === 'halt-vanished') {
37
+ haltedAt = name;
38
+ settle(record, 'compensation-failed', action.error);
39
+ emitter?.emitStep('compensation:failed', exec.id, exec.workflowName, name, {
40
+ error: 'step no longer declared',
41
+ });
42
+ continue;
43
+ }
44
+ emitter?.emitStep('compensation:started', exec.id, exec.workflowName, name);
45
+ try {
46
+ if (action.kind === 'unwind-child') {
47
+ await unwindChild({ record, store, emitter, workflows, runChild, retryFailed });
48
+ }
49
+ else {
50
+ const def = findStepDef(wf, name);
51
+ const controller = new AbortController();
52
+ const context = {
53
+ ...compensationContext(exec, baseCtx, name, record),
54
+ signal: controller.signal,
55
+ };
56
+ await runWithTimeout(def?.compensate?.(context), action.timeoutMs, controller);
57
+ }
58
+ settle(record, 'compensated');
59
+ emitter?.emitStep('compensation:completed', exec.id, exec.workflowName, name);
60
+ }
61
+ catch (error) {
62
+ const message = describeError(error);
63
+ settle(record, 'compensation-failed', message);
64
+ emitter?.emitStep('compensation:failed', exec.id, exec.workflowName, name, {
65
+ error: message,
66
+ });
67
+ haltedAt = name;
68
+ }
69
+ try {
70
+ store.update(exec);
71
+ }
72
+ catch (error) {
73
+ // An unpersisted reversal could run twice on recovery; stop immediately.
74
+ writeFailure = error;
75
+ haltedAt = name;
76
+ break;
77
+ }
78
+ }
79
+ if (haltedAt !== null) {
80
+ exec.state = 'compensation-stuck';
81
+ exec.rollbackStatus = 'stuck';
82
+ }
83
+ else {
84
+ exec.state = 'failed';
85
+ exec.rollbackStatus = 'completed';
86
+ }
87
+ try {
88
+ store.update(exec);
89
+ }
90
+ catch (error) {
91
+ if (writeFailure === undefined)
92
+ throw error;
93
+ }
94
+ if (writeFailure !== undefined)
95
+ throw writeFailure;
96
+ }
@@ -0,0 +1,24 @@
1
+ /** Pure record selection and context construction for saga compensation. */
2
+ import { buildContext } from './runner';
3
+ import type { CompensationStatus, Execution, StepRecord } from './types';
4
+ import type { Workflow } from './workflow';
5
+ /**
6
+ * Eligible records in reverse start order. Failed records are included because their
7
+ * external effect may have succeeded before its response was lost.
8
+ */
9
+ export declare function unwindSet(exec: Execution, wf: Workflow): [string, StepRecord][];
10
+ export declare function settle(record: StepRecord, status: CompensationStatus, error?: string): void;
11
+ /**
12
+ * Rebind a loop's bare step name to this occurrence and restore forEach item metadata.
13
+ */
14
+ export declare function compensationContext(exec: Execution, baseCtx: ReturnType<typeof buildContext>, name: string, record: StepRecord): {
15
+ steps: {
16
+ [x: string]: unknown;
17
+ };
18
+ idempotencyKey: string;
19
+ forwardIdempotencyKey: string | undefined;
20
+ input: unknown;
21
+ signals: Readonly<Record<string, unknown>>;
22
+ executionId: string;
23
+ signal?: AbortSignal;
24
+ };
@@ -0,0 +1,57 @@
1
+ /** Pure record selection and context construction for saga compensation. */
2
+ import { clock } from './clock';
3
+ import { idempotencyKey, loopBaseName } from './identity';
4
+ import { findStepDef } from './runner';
5
+ /**
6
+ * Eligible records in reverse start order. Failed records are included because their
7
+ * external effect may have succeeded before its response was lost.
8
+ */
9
+ export function unwindSet(exec, wf) {
10
+ if (exec.committedAt !== undefined)
11
+ return [];
12
+ return Object.entries(exec.steps)
13
+ .filter(([name, record]) => {
14
+ if (name.startsWith('__'))
15
+ return false;
16
+ if (record.status !== 'completed' && record.status !== 'failed')
17
+ return false;
18
+ // Indexed loop records are authoritative; the bare name is only the last-result
19
+ // mirror and compensating both would undo the final iteration twice.
20
+ if (isLoopBody(wf, name) && exec.steps[`${name}:0`] !== undefined)
21
+ return false;
22
+ if (name.startsWith('sub:'))
23
+ return record.childExecutionId !== undefined;
24
+ if (findStepDef(wf, name) !== null)
25
+ return true;
26
+ // A removed definition must halt rather than disappear from an unwind it owed.
27
+ return record.compensation !== undefined || record.compensatable === true;
28
+ })
29
+ .reverse();
30
+ }
31
+ export function settle(record, status, error) {
32
+ record.compensation = { status, at: clock().now(), ...(error ? { error } : {}) };
33
+ }
34
+ /**
35
+ * Rebind a loop's bare step name to this occurrence and restore forEach item metadata.
36
+ */
37
+ export function compensationContext(exec, baseCtx, name, record) {
38
+ const occurrence = record.occurrence ?? 0;
39
+ const base = loopBaseName(name);
40
+ const ctx = {
41
+ ...baseCtx,
42
+ steps: { ...baseCtx.steps, [base]: record.result, [name]: record.result },
43
+ idempotencyKey: idempotencyKey(exec.id, name, occurrence, 'compensate'),
44
+ forwardIdempotencyKey: record.idempotencyKey,
45
+ };
46
+ if (record.loopIndex === undefined)
47
+ return ctx;
48
+ return {
49
+ ...ctx,
50
+ steps: { ...ctx.steps, __item: record.loopItem, __index: record.loopIndex },
51
+ };
52
+ }
53
+ function isLoopBody(wf, name) {
54
+ return wf.nodes.some((node) => ((node.type === 'doUntil' || node.type === 'doWhile') &&
55
+ node.def.steps.some((step) => step.name === name)) ||
56
+ (node.type === 'forEach' && node.def.step.name === name));
57
+ }
@@ -1,67 +1,28 @@
1
1
  /**
2
- * Saga rollback runs compensate handlers in reverse start order.
2
+ * Public saga-compensation entry points.
3
3
  *
4
- * Four properties this file is responsible for, each of which used to be violated:
5
- *
6
- * - EXACTLY ONE OUTCOME per eligible step. Success is recorded as loudly as
7
- * failure, and a step the unwind never reached is recorded as skipped rather
8
- * than left blank. "Never zero, never two" is not checkable otherwise.
9
- * - NEVER TWICE. A step that already carries an outcome is not re-run, so an
10
- * unwind interrupted by a crash resumes where it stopped instead of replaying
11
- * reversals from the top.
12
- * - HALT, NOT PLOUGH ON. A compensation that fails definitively stops the chain:
13
- * continuing past it would undo things whose dependencies are still standing.
14
- * The remaining eligible steps are marked skipped so the gap is visible.
15
- * - PIVOT. Past `.pivot()` the saga is committed; those steps are never eligible.
16
- *
17
- * `rollbackStatus` on the execution is a separate axis from the failure reason. The
18
- * rollback is what the engine did *after* the failure, not why it failed, and an
19
- * operator needs to alert on "the refund never went through" independently of "the
20
- * payment failed".
4
+ * A pass is checkpointed per step, halts on an uncertain reversal, and is protected
5
+ * from overlapping in-process drivers. The execution mechanics live in
6
+ * compensationPass.ts; record selection lives in compensationSupport.ts.
21
7
  */
8
+ import type { WorkflowEmitter } from './emitter';
9
+ import type { WorkflowStore } from './store';
22
10
  import type { Execution } from './types';
23
11
  import type { Workflow } from './workflow';
24
- import type { WorkflowStore } from './store';
25
- import type { WorkflowEmitter } from './emitter';
26
- /** Sentinel error thrown when execution must pause for a signal */
12
+ export { AbandonedCompensationError, CommittedChildError } from './compensationChild';
13
+ /** Sentinel thrown when an execution deliberately parks for a signal. */
27
14
  export declare class WaitForSignalError extends Error {
28
15
  readonly event: string;
29
16
  constructor(event: string);
30
17
  }
31
- /**
32
- * Deliberately NOT exported, and deliberately never bulk-cleared.
33
- *
34
- * `Engine.close()` used to call a `releaseUnwindClaim()` that emptied this set. The
35
- * set is process-global, so closing ANY engine dropped the claim protecting an unwind
36
- * still running under a DIFFERENT one, and the next caller re-dispatched compensate
37
- * handlers that were mid-flight: the duplicate refund this claim exists to stop.
38
- *
39
- * Nothing needs to clear it. `runCompensation` releases in a `finally`, so a claim
40
- * outlives its unwind by nothing, bounded by the step's own `timeout` even if the
41
- * handler wedges. If the process dies the module state dies with it.
42
- */
43
- /**
44
- * Outcome of asking for an unwind. `claim-lost` means another unwind of the same
45
- * execution is already in flight, so THIS call did nothing and its caller must not
46
- * treat the rollback as done.
47
- */
18
+ /** `claim-lost` means another local driver owns the unwind and this call did nothing. */
48
19
  export type UnwindOutcome = 'ran' | 'claim-lost';
49
20
  /** Run compensation handlers in reverse start order for every eligible step. */
50
21
  export declare function runCompensation(exec: Execution, wf: Workflow, store: WorkflowStore, emitter: WorkflowEmitter | null, workflows?: Map<string, Workflow>, opts?: {
51
22
  retryFailed?: boolean;
52
23
  }): Promise<UnwindOutcome>;
53
24
  /**
54
- * Give up on a parked unwind: every eligible step still without an outcome is
55
- * recorded as skipped, and the run becomes terminal. This is where "exactly one
56
- * outcome per eligible step" is finally discharged.
25
+ * Give up on a parked unwind and record an explicit skipped outcome for every
26
+ * remaining eligible step.
57
27
  */
58
28
  export declare function abandonCompensation(exec: Execution, wf: Workflow, store: WorkflowStore, emitter: WorkflowEmitter | null): void;
59
- /**
60
- * A sub-workflow that passed its own pivot cannot be rolled back. Distinct from a
61
- * handler failure so the parent's record carries the reason rather than a generic
62
- * error, and so the parent parks (operator decision) instead of silently claiming
63
- * success.
64
- */
65
- export declare class CommittedChildError extends Error {
66
- constructor(workflowName: string, executionId: string);
67
- }