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.
- package/README.md +19 -9
- package/dist/application/contextFactory.d.ts +4 -2
- package/dist/application/contextFactory.js +3 -0
- package/dist/application/flowFailureRecovery.d.ts +17 -0
- package/dist/application/flowFailureRecovery.js +92 -0
- package/dist/application/operations/ack.d.ts +3 -2
- package/dist/application/operations/ack.js +34 -8
- package/dist/application/operations/flowPush.d.ts +9 -0
- package/dist/application/operations/flowPush.js +112 -0
- package/dist/application/operations/flowTopologyValidation.d.ts +3 -0
- package/dist/application/operations/flowTopologyValidation.js +101 -0
- package/dist/application/operations/flowValidation.d.ts +3 -0
- package/dist/application/operations/flowValidation.js +166 -0
- package/dist/application/operations/push.d.ts +3 -0
- package/dist/application/operations/push.js +2 -2
- package/dist/application/operations/pushInsert.d.ts +7 -1
- package/dist/application/operations/pushInsert.js +18 -8
- package/dist/application/operations/pushLocks.d.ts +1 -0
- package/dist/application/operations/pushLocks.js +5 -1
- package/dist/application/operations/queryOperations.js +7 -6
- package/dist/application/queueManager.d.ts +5 -0
- package/dist/application/queueManager.js +195 -60
- package/dist/cli/commandRouter.d.ts +1 -1
- package/dist/client/flow.d.ts +3 -6
- package/dist/client/flow.js +71 -210
- package/dist/client/flowAtomic.d.ts +9 -0
- package/dist/client/flowAtomic.js +24 -0
- package/dist/client/flowJobCoreMethods.d.ts +28 -0
- package/dist/client/flowJobCoreMethods.js +151 -0
- package/dist/client/flowJobDependencies.d.ts +7 -0
- package/dist/client/flowJobDependencies.js +64 -0
- package/dist/client/flowJobFactory.d.ts +11 -24
- package/dist/client/flowJobFactory.js +91 -360
- package/dist/client/flowJobMoveMethods.d.ts +16 -0
- package/dist/client/flowJobMoveMethods.js +112 -0
- package/dist/client/flowJobTypes.d.ts +29 -0
- package/dist/client/flowJobTypes.js +6 -0
- package/dist/client/flowLegacyPlan.d.ts +16 -0
- package/dist/client/flowLegacyPlan.js +118 -0
- package/dist/client/flowOptions.d.ts +10 -0
- package/dist/client/flowOptions.js +44 -0
- package/dist/client/flowPlan.d.ts +16 -0
- package/dist/client/flowPlan.js +93 -0
- package/dist/client/flowPush.js +4 -1
- package/dist/client/flowReader.d.ts +10 -0
- package/dist/client/flowReader.js +147 -0
- package/dist/client/jobHelpers.js +2 -0
- package/dist/client/workflow/clock.d.ts +3 -1
- package/dist/client/workflow/clock.js +18 -1
- package/dist/client/workflow/compensationChild.d.ts +30 -0
- package/dist/client/workflow/compensationChild.js +65 -0
- package/dist/client/workflow/compensationPass.d.ts +16 -0
- package/dist/client/workflow/compensationPass.js +96 -0
- package/dist/client/workflow/compensationSupport.d.ts +24 -0
- package/dist/client/workflow/compensationSupport.js +57 -0
- package/dist/client/workflow/compensator.d.ts +11 -50
- package/dist/client/workflow/compensator.js +22 -374
- package/dist/client/workflow/definitionGuard.d.ts +11 -0
- package/dist/client/workflow/definitionGuard.js +22 -0
- package/dist/client/workflow/engine.d.ts +2 -2
- package/dist/client/workflow/engine.js +8 -8
- package/dist/client/workflow/eventTypes.d.ts +44 -0
- package/dist/client/workflow/eventTypes.js +2 -0
- package/dist/client/workflow/executionTypes.d.ts +133 -0
- package/dist/client/workflow/executionTypes.js +2 -0
- package/dist/client/workflow/executor.d.ts +4 -17
- package/dist/client/workflow/executor.js +50 -208
- package/dist/client/workflow/executorLifecycle.d.ts +18 -0
- package/dist/client/workflow/executorLifecycle.js +69 -0
- package/dist/client/workflow/executorNodes.d.ts +17 -0
- package/dist/client/workflow/executorNodes.js +138 -0
- package/dist/client/workflow/identity.d.ts +2 -0
- package/dist/client/workflow/identity.js +8 -0
- package/dist/client/workflow/index.d.ts +1 -1
- package/dist/client/workflow/loops.d.ts +3 -5
- package/dist/client/workflow/loops.js +25 -23
- package/dist/client/workflow/mapRunner.d.ts +4 -0
- package/dist/client/workflow/mapRunner.js +45 -0
- package/dist/client/workflow/recovery.js +10 -3
- package/dist/client/workflow/runner.d.ts +2 -21
- package/dist/client/workflow/runner.js +39 -104
- package/dist/client/workflow/runnerTiming.d.ts +14 -0
- package/dist/client/workflow/runnerTiming.js +66 -0
- package/dist/client/workflow/stepTypes.d.ts +171 -0
- package/dist/client/workflow/stepTypes.js +3 -0
- package/dist/client/workflow/store.d.ts +17 -17
- package/dist/client/workflow/store.js +65 -100
- package/dist/client/workflow/storeExecutionCodec.d.ts +11 -0
- package/dist/client/workflow/storeExecutionCodec.js +34 -0
- package/dist/client/workflow/storeListing.d.ts +11 -0
- package/dist/client/workflow/storeListing.js +45 -0
- package/dist/client/workflow/storeMaintenance.d.ts +4 -0
- package/dist/client/workflow/storeMaintenance.js +40 -0
- package/dist/client/workflow/storeSignals.d.ts +9 -0
- package/dist/client/workflow/storeSignals.js +38 -2
- package/dist/client/workflow/subWorkflowRunner.d.ts +13 -0
- package/dist/client/workflow/subWorkflowRunner.js +40 -0
- package/dist/client/workflow/types.d.ts +4 -356
- package/dist/client/workflow/types.js +1 -3
- package/dist/client/workflow/waitFor.js +39 -26
- package/dist/client/workflow/workflow.d.ts +16 -59
- package/dist/client/workflow/workflow.js +53 -179
- package/dist/client/workflow/workflowDecisions.d.ts +11 -0
- package/dist/client/workflow/workflowDecisions.js +27 -0
- package/dist/client/workflow/workflowDefinition.d.ts +16 -0
- package/dist/client/workflow/workflowDefinition.js +123 -0
- package/dist/client/workflow/workflowIntrospection.d.ts +5 -0
- package/dist/client/workflow/workflowIntrospection.js +46 -0
- package/dist/client/workflow/workflowValidation.d.ts +44 -0
- package/dist/client/workflow/workflowValidation.js +143 -0
- package/dist/domain/types/command.d.ts +7 -1
- package/dist/domain/types/flow.d.ts +25 -0
- package/dist/domain/types/flow.js +1 -0
- package/dist/infrastructure/persistence/schema.d.ts +2 -2
- package/dist/infrastructure/persistence/schema.js +52 -3
- package/dist/infrastructure/persistence/sqlite.d.ts +16 -0
- package/dist/infrastructure/persistence/sqlite.js +129 -7
- package/dist/infrastructure/persistence/sqliteBatch.js +10 -4
- package/dist/infrastructure/persistence/sqliteSerializer.d.ts +2 -0
- package/dist/infrastructure/persistence/sqliteSerializer.js +18 -5
- package/dist/infrastructure/persistence/statements.d.ts +4 -0
- package/dist/infrastructure/persistence/statements.js +8 -2
- package/dist/infrastructure/server/handlerRoutes.js +3 -0
- package/dist/infrastructure/server/handlers/advanced.js +2 -2
- package/dist/infrastructure/server/handlers/flow.d.ts +7 -0
- package/dist/infrastructure/server/handlers/flow.js +11 -0
- package/dist/infrastructure/server/handlers/index.d.ts +1 -0
- package/dist/infrastructure/server/handlers/index.js +1 -0
- 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
|
-
*
|
|
2
|
+
* Public saga-compensation entry points.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
25
|
-
|
|
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
|
|
55
|
-
*
|
|
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
|
-
}
|