@workflow/core 5.0.0-beta.4 → 5.0.0-beta.40
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/dist/attribute-changes.d.ts +7 -0
- package/dist/attribute-changes.d.ts.map +1 -0
- package/dist/attribute-changes.js +25 -0
- package/dist/capabilities.d.ts +33 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +71 -4
- package/dist/capture-stack.d.ts +16 -0
- package/dist/capture-stack.d.ts.map +1 -0
- package/dist/capture-stack.js +21 -0
- package/dist/class-serialization.d.ts +32 -0
- package/dist/class-serialization.d.ts.map +1 -1
- package/dist/class-serialization.js +37 -1
- package/dist/classify-error.d.ts +26 -3
- package/dist/classify-error.d.ts.map +1 -1
- package/dist/classify-error.js +109 -6
- package/dist/context-errors.d.ts +27 -0
- package/dist/context-errors.d.ts.map +1 -0
- package/dist/context-errors.js +101 -0
- package/dist/context-violation-error.d.ts +97 -0
- package/dist/context-violation-error.d.ts.map +1 -0
- package/dist/context-violation-error.js +149 -0
- package/dist/correlation-id.d.ts +109 -0
- package/dist/correlation-id.d.ts.map +1 -0
- package/dist/correlation-id.js +139 -0
- package/dist/create-hook.d.ts +62 -4
- package/dist/create-hook.d.ts.map +1 -1
- package/dist/create-hook.js +4 -3
- package/dist/define-hook.d.ts.map +1 -1
- package/dist/define-hook.js +20 -5
- package/dist/describe-error.d.ts +70 -0
- package/dist/describe-error.d.ts.map +1 -0
- package/dist/describe-error.js +189 -0
- package/dist/encryption.d.ts +37 -3
- package/dist/encryption.d.ts.map +1 -1
- package/dist/encryption.js +97 -31
- package/dist/events-consumer.d.ts +26 -1
- package/dist/events-consumer.d.ts.map +1 -1
- package/dist/events-consumer.js +105 -18
- package/dist/flushable-stream.d.ts +65 -10
- package/dist/flushable-stream.d.ts.map +1 -1
- package/dist/flushable-stream.js +131 -18
- package/dist/global.d.ts +37 -1
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js +21 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/log-format.d.ts +25 -0
- package/dist/log-format.d.ts.map +1 -0
- package/dist/log-format.js +250 -0
- package/dist/logger.d.ts +29 -30
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +82 -32
- package/dist/private.d.ts +178 -9
- package/dist/private.d.ts.map +1 -1
- package/dist/private.js +273 -9
- package/dist/replay-payload-cache.d.ts +68 -0
- package/dist/replay-payload-cache.d.ts.map +1 -0
- package/dist/replay-payload-cache.js +160 -0
- package/dist/runtime/compute-instance.d.ts +12 -0
- package/dist/runtime/compute-instance.d.ts.map +1 -0
- package/dist/runtime/compute-instance.js +13 -0
- package/dist/runtime/constants.d.ts +237 -0
- package/dist/runtime/constants.d.ts.map +1 -1
- package/dist/runtime/constants.js +405 -15
- package/dist/runtime/count-step-started-events.d.ts +52 -0
- package/dist/runtime/count-step-started-events.d.ts.map +1 -0
- package/dist/runtime/count-step-started-events.js +72 -0
- package/dist/runtime/deployment-guard.d.ts +99 -0
- package/dist/runtime/deployment-guard.d.ts.map +1 -0
- package/dist/runtime/deployment-guard.js +154 -0
- package/dist/runtime/get-port-lazy.d.ts +25 -0
- package/dist/runtime/get-port-lazy.d.ts.map +1 -0
- package/dist/runtime/get-port-lazy.js +92 -0
- package/dist/runtime/get-world-lazy.d.ts +23 -0
- package/dist/runtime/get-world-lazy.d.ts.map +1 -0
- package/dist/runtime/get-world-lazy.js +46 -0
- package/dist/runtime/helpers.d.ts +219 -10
- package/dist/runtime/helpers.d.ts.map +1 -1
- package/dist/runtime/helpers.js +414 -40
- package/dist/runtime/quickjs-assets.generated.d.ts +14 -0
- package/dist/runtime/quickjs-assets.generated.d.ts.map +1 -0
- package/dist/runtime/quickjs-assets.generated.js +30 -0
- package/dist/runtime/quickjs-entrypoint.d.ts +124 -0
- package/dist/runtime/quickjs-entrypoint.d.ts.map +1 -0
- package/dist/runtime/quickjs-entrypoint.js +1429 -0
- package/dist/runtime/quickjs-runtime.d.ts +193 -0
- package/dist/runtime/quickjs-runtime.d.ts.map +1 -0
- package/dist/runtime/quickjs-runtime.js +1944 -0
- package/dist/runtime/replay-budget.d.ts +98 -0
- package/dist/runtime/replay-budget.d.ts.map +1 -0
- package/dist/runtime/replay-budget.js +192 -0
- package/dist/runtime/replay-recovery-reporter.d.ts +36 -0
- package/dist/runtime/replay-recovery-reporter.d.ts.map +1 -0
- package/dist/runtime/replay-recovery-reporter.js +64 -0
- package/dist/runtime/resume-hook.d.ts +27 -4
- package/dist/runtime/resume-hook.d.ts.map +1 -1
- package/dist/runtime/resume-hook.js +448 -76
- package/dist/runtime/run-id-time.d.ts +19 -0
- package/dist/runtime/run-id-time.d.ts.map +1 -0
- package/dist/runtime/run-id-time.js +42 -0
- package/dist/runtime/run.d.ts +8 -2
- package/dist/runtime/run.d.ts.map +1 -1
- package/dist/runtime/run.js +62 -15
- package/dist/runtime/runs.d.ts +54 -3
- package/dist/runtime/runs.d.ts.map +1 -1
- package/dist/runtime/runs.js +117 -10
- package/dist/runtime/start.d.ts +70 -1
- package/dist/runtime/start.d.ts.map +1 -1
- package/dist/runtime/start.js +312 -54
- package/dist/runtime/step-executor.d.ts +177 -0
- package/dist/runtime/step-executor.d.ts.map +1 -0
- package/dist/runtime/step-executor.js +949 -0
- package/dist/runtime/step-latency.d.ts +197 -0
- package/dist/runtime/step-latency.d.ts.map +1 -0
- package/dist/runtime/step-latency.js +207 -0
- package/dist/runtime/step-ownership.d.ts +72 -0
- package/dist/runtime/step-ownership.d.ts.map +1 -0
- package/dist/runtime/step-ownership.js +114 -0
- package/dist/runtime/step-single-flight.d.ts +12 -0
- package/dist/runtime/step-single-flight.d.ts.map +1 -0
- package/dist/runtime/step-single-flight.js +69 -0
- package/dist/runtime/suspension-handler.d.ts +102 -7
- package/dist/runtime/suspension-handler.d.ts.map +1 -1
- package/dist/runtime/suspension-handler.js +439 -140
- package/dist/runtime/vm-mode.d.ts +44 -0
- package/dist/runtime/vm-mode.d.ts.map +1 -0
- package/dist/runtime/vm-mode.js +62 -0
- package/dist/runtime/vm-serde-bundle.generated.d.ts +14 -0
- package/dist/runtime/vm-serde-bundle.generated.d.ts.map +1 -0
- package/dist/runtime/vm-serde-bundle.generated.js +16 -0
- package/dist/runtime/wait-continuation.d.ts +84 -0
- package/dist/runtime/wait-continuation.d.ts.map +1 -0
- package/dist/runtime/wait-continuation.js +105 -0
- package/dist/runtime/wait-until.d.ts +18 -0
- package/dist/runtime/wait-until.d.ts.map +1 -0
- package/dist/runtime/wait-until.js +42 -0
- package/dist/runtime/world-compatibility.d.ts +5 -0
- package/dist/runtime/world-compatibility.d.ts.map +1 -0
- package/dist/runtime/world-compatibility.js +12 -0
- package/dist/runtime/world-init.d.ts +50 -0
- package/dist/runtime/world-init.d.ts.map +1 -0
- package/dist/runtime/world-init.js +50 -0
- package/dist/runtime/world.d.ts +14 -2
- package/dist/runtime/world.d.ts.map +1 -1
- package/dist/runtime/world.js +101 -26
- package/dist/runtime.d.ts +17 -12
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +3078 -327
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +1 -1
- package/dist/sealed-box.d.ts +167 -0
- package/dist/sealed-box.d.ts.map +1 -0
- package/dist/sealed-box.js +571 -0
- package/dist/serialization/client.d.ts +17 -0
- package/dist/serialization/client.d.ts.map +1 -0
- package/dist/serialization/client.js +48 -0
- package/dist/serialization/codec-devalue-vm.d.ts +9 -0
- package/dist/serialization/codec-devalue-vm.d.ts.map +1 -0
- package/dist/serialization/codec-devalue-vm.js +137 -0
- package/dist/serialization/codec-devalue.d.ts +14 -0
- package/dist/serialization/codec-devalue.d.ts.map +1 -0
- package/dist/serialization/codec-devalue.js +105 -0
- package/dist/serialization/codec.d.ts +125 -0
- package/dist/serialization/codec.d.ts.map +1 -0
- package/dist/serialization/codec.js +17 -0
- package/dist/serialization/compression.d.ts +104 -0
- package/dist/serialization/compression.d.ts.map +1 -0
- package/dist/serialization/compression.js +260 -0
- package/dist/serialization/encryption.d.ts +134 -0
- package/dist/serialization/encryption.d.ts.map +1 -0
- package/dist/serialization/encryption.js +186 -0
- package/dist/serialization/errors.d.ts +34 -0
- package/dist/serialization/errors.d.ts.map +1 -0
- package/dist/serialization/errors.js +59 -0
- package/dist/serialization/format.d.ts +60 -0
- package/dist/serialization/format.d.ts.map +1 -0
- package/dist/serialization/format.js +97 -0
- package/dist/serialization/hardened.d.ts +155 -0
- package/dist/serialization/hardened.d.ts.map +1 -0
- package/dist/serialization/hardened.js +523 -0
- package/dist/serialization/index.d.ts +20 -0
- package/dist/serialization/index.d.ts.map +1 -0
- package/dist/serialization/index.js +22 -0
- package/dist/serialization/reducers/class-vm.d.ts +20 -0
- package/dist/serialization/reducers/class-vm.d.ts.map +1 -0
- package/dist/serialization/reducers/class-vm.js +77 -0
- package/dist/serialization/reducers/class.d.ts +11 -0
- package/dist/serialization/reducers/class.d.ts.map +1 -0
- package/dist/serialization/reducers/class.js +73 -0
- package/dist/serialization/reducers/common-vm.d.ts +15 -0
- package/dist/serialization/reducers/common-vm.d.ts.map +1 -0
- package/dist/serialization/reducers/common-vm.js +579 -0
- package/dist/serialization/reducers/common.d.ts +16 -0
- package/dist/serialization/reducers/common.d.ts.map +1 -0
- package/dist/serialization/reducers/common.js +478 -0
- package/dist/serialization/reducers/step-function-vm.d.ts +44 -0
- package/dist/serialization/reducers/step-function-vm.d.ts.map +1 -0
- package/dist/serialization/reducers/step-function-vm.js +97 -0
- package/dist/serialization/reducers/step-function.d.ts +35 -0
- package/dist/serialization/reducers/step-function.d.ts.map +1 -0
- package/dist/serialization/reducers/step-function.js +104 -0
- package/dist/serialization/step.d.ts +17 -0
- package/dist/serialization/step.d.ts.map +1 -0
- package/dist/serialization/step.js +48 -0
- package/dist/serialization/types.d.ts +273 -0
- package/dist/serialization/types.d.ts.map +1 -0
- package/dist/serialization/types.js +35 -0
- package/dist/serialization/vm-bundle-entry.d.ts +12 -0
- package/dist/serialization/vm-bundle-entry.d.ts.map +1 -0
- package/dist/serialization/vm-bundle-entry.js +58 -0
- package/dist/serialization/workflow-vm.d.ts +24 -0
- package/dist/serialization/workflow-vm.d.ts.map +1 -0
- package/dist/serialization/workflow-vm.js +66 -0
- package/dist/serialization/workflow.d.ts +29 -0
- package/dist/serialization/workflow.d.ts.map +1 -0
- package/dist/serialization/workflow.js +54 -0
- package/dist/serialization-format.d.ts +60 -4
- package/dist/serialization-format.d.ts.map +1 -1
- package/dist/serialization-format.js +276 -56
- package/dist/serialization.d.ts +374 -221
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +2300 -783
- package/dist/set-attributes.d.ts +13 -0
- package/dist/set-attributes.d.ts.map +1 -0
- package/dist/set-attributes.js +60 -0
- package/dist/sleep.d.ts.map +1 -1
- package/dist/sleep.js +3 -2
- package/dist/source-map.d.ts +25 -0
- package/dist/source-map.d.ts.map +1 -1
- package/dist/source-map.js +148 -10
- package/dist/step/context-storage.d.ts +61 -2
- package/dist/step/context-storage.d.ts.map +1 -1
- package/dist/step/context-storage.js +7 -5
- package/dist/step/get-closure-vars.d.ts.map +1 -1
- package/dist/step/get-closure-vars.js +3 -2
- package/dist/step/get-step-metadata.d.ts.map +1 -1
- package/dist/step/get-step-metadata.js +3 -2
- package/dist/step/get-workflow-metadata.d.ts.map +1 -1
- package/dist/step/get-workflow-metadata.js +3 -2
- package/dist/step/writable-stream.d.ts.map +1 -1
- package/dist/step/writable-stream.js +71 -7
- package/dist/step.d.ts.map +1 -1
- package/dist/step.js +226 -30
- package/dist/symbols.d.ts +54 -0
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +55 -1
- package/dist/telemetry/semantic-conventions.d.ts +242 -2
- package/dist/telemetry/semantic-conventions.d.ts.map +1 -1
- package/dist/telemetry/semantic-conventions.js +173 -1
- package/dist/telemetry.d.ts +73 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +153 -16
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +23 -1
- package/dist/util.d.ts +16 -6
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +25 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +2 -2
- package/dist/vm/index.d.ts.map +1 -1
- package/dist/vm/index.js +77 -9
- package/dist/vm/script-cache.d.ts +28 -0
- package/dist/vm/script-cache.d.ts.map +1 -0
- package/dist/vm/script-cache.js +140 -0
- package/dist/workflow/abort-controller.d.ts +65 -0
- package/dist/workflow/abort-controller.d.ts.map +1 -0
- package/dist/workflow/abort-controller.js +314 -0
- package/dist/workflow/attribute-dispatcher.d.ts +6 -0
- package/dist/workflow/attribute-dispatcher.d.ts.map +1 -0
- package/dist/workflow/attribute-dispatcher.js +48 -0
- package/dist/workflow/create-hook.d.ts.map +1 -1
- package/dist/workflow/create-hook.js +25 -3
- package/dist/workflow/define-hook.d.ts +1 -1
- package/dist/workflow/define-hook.d.ts.map +1 -1
- package/dist/workflow/define-hook.js +8 -4
- package/dist/workflow/get-workflow-metadata.d.ts.map +1 -1
- package/dist/workflow/get-workflow-metadata.js +14 -3
- package/dist/workflow/hook.d.ts.map +1 -1
- package/dist/workflow/hook.js +303 -37
- package/dist/workflow/index.d.ts +1 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +5 -3
- package/dist/workflow/set-attributes.d.ts +68 -0
- package/dist/workflow/set-attributes.d.ts.map +1 -0
- package/dist/workflow/set-attributes.js +60 -0
- package/dist/workflow/sleep.d.ts.map +1 -1
- package/dist/workflow/sleep.js +58 -9
- package/dist/workflow/world-init-stub.d.ts +15 -0
- package/dist/workflow/world-init-stub.d.ts.map +1 -0
- package/dist/workflow/world-init-stub.js +15 -0
- package/dist/workflow.d.ts +69 -3
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +791 -554
- package/docs/api-reference/create-hook.mdx +79 -0
- package/docs/api-reference/create-webhook.mdx +1 -0
- package/docs/api-reference/define-hook.mdx +26 -24
- package/docs/api-reference/fatal-error.mdx +29 -7
- package/docs/api-reference/fetch.mdx +8 -4
- package/docs/api-reference/index.mdx +3 -0
- package/docs/api-reference/set-attributes.mdx +61 -0
- package/docs/api-reference/sleep.mdx +1 -1
- package/docs/foundations/cancellation.mdx +459 -0
- package/docs/foundations/errors-and-retries.mdx +7 -3
- package/docs/foundations/hooks.mdx +29 -0
- package/docs/foundations/idempotency.mdx +243 -11
- package/docs/foundations/index.mdx +1 -23
- package/docs/foundations/meta.json +3 -1
- package/docs/foundations/serialization.mdx +77 -41
- package/docs/foundations/starting-workflows.mdx +79 -2
- package/docs/foundations/streaming.mdx +14 -23
- package/docs/foundations/versioning.mdx +263 -0
- package/docs/how-it-works/cancellation.mdx +287 -0
- package/docs/how-it-works/code-transform.mdx +21 -17
- package/docs/how-it-works/encryption.mdx +5 -5
- package/docs/how-it-works/event-sourcing.mdx +6 -6
- package/docs/how-it-works/framework-integrations.mdx +96 -337
- package/docs/how-it-works/meta.json +2 -1
- package/package.json +31 -13
- package/dist/runtime/step-handler.d.ts +0 -2
- package/dist/runtime/step-handler.d.ts.map +0 -1
- package/dist/runtime/step-handler.js +0 -678
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-invocation accounting of the *non-step* portion of a workflow
|
|
3
|
+
* handler run: deterministic event-log replay, workflow-VM execution
|
|
4
|
+
* between step boundaries, suspension handling, queue round-trips, etc.
|
|
5
|
+
* Inline step bodies (`"use step"` functions invoked via `executeStep`)
|
|
6
|
+
* are intentionally excluded — they are bounded by the platform's
|
|
7
|
+
* function `maxDuration` and the `NO_INLINE_REPLAY_AFTER_MS` early-return
|
|
8
|
+
* guard.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const budget = new ReplayBudget();
|
|
14
|
+
* // …non-step work happens here, accumulates against the budget…
|
|
15
|
+
* budget.pause();
|
|
16
|
+
* try {
|
|
17
|
+
* await executeStep(...); // not charged
|
|
18
|
+
* } finally {
|
|
19
|
+
* budget.resume();
|
|
20
|
+
* }
|
|
21
|
+
* // back to charging
|
|
22
|
+
* if (budget.isExhausted()) { ... }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Implementation notes:
|
|
26
|
+
*
|
|
27
|
+
* - `pause()` and `resume()` are idempotent: calling `pause()` while
|
|
28
|
+
* already paused (or `resume()` while already resumed) is a no-op.
|
|
29
|
+
* This protects against double-counting in future refactors that nest
|
|
30
|
+
* step execution or take an early-return path between a `pause()` and
|
|
31
|
+
* the matching `resume()`.
|
|
32
|
+
* - `isExhausted()` is checked at loop boundaries by the caller — the
|
|
33
|
+
* budget itself does not arm any timers. This means an in-flight
|
|
34
|
+
* pathological `runWorkflow` call (e.g. a huge event-log replay) can
|
|
35
|
+
* overshoot the budget by up to one iteration's worth of work before
|
|
36
|
+
* the next check fires. In practice the 20s headroom built into
|
|
37
|
+
* `MAX_REPLAY_TIMEOUT_MS` (and the function `maxDuration` ceiling)
|
|
38
|
+
* gives us slack; the old `setTimeout`-based approach also ultimately
|
|
39
|
+
* relied on the platform SIGTERM as the hard backstop.
|
|
40
|
+
*/
|
|
41
|
+
export declare class ReplayBudget {
|
|
42
|
+
private readonly limitMs;
|
|
43
|
+
private elapsedMs;
|
|
44
|
+
private intervalStart;
|
|
45
|
+
constructor(limitMs?: number);
|
|
46
|
+
/**
|
|
47
|
+
* The configured replay-timeout limit, in ms. Useful for log messages.
|
|
48
|
+
*/
|
|
49
|
+
get configuredLimitMs(): number;
|
|
50
|
+
/**
|
|
51
|
+
* Total non-step time accumulated so far, in ms. Includes the
|
|
52
|
+
* currently-active interval if the budget is not paused.
|
|
53
|
+
*/
|
|
54
|
+
elapsed(): number;
|
|
55
|
+
/**
|
|
56
|
+
* Stop counting elapsed time toward the budget. Idempotent — safe to
|
|
57
|
+
* call multiple times in a row; subsequent calls are no-ops until
|
|
58
|
+
* `resume()` reopens an interval.
|
|
59
|
+
*/
|
|
60
|
+
pause(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Resume counting elapsed time toward the budget. Idempotent — safe to
|
|
63
|
+
* call multiple times in a row; subsequent calls re-anchor the
|
|
64
|
+
* interval start to `now()`, which is fine because no time accrues
|
|
65
|
+
* between back-to-back `resume()` calls.
|
|
66
|
+
*/
|
|
67
|
+
resume(): void;
|
|
68
|
+
/**
|
|
69
|
+
* True if the budget has been exhausted (`elapsed() >= limitMs`).
|
|
70
|
+
* Callers should invoke `handleExhausted(...)` afterward and return
|
|
71
|
+
* from the handler.
|
|
72
|
+
*/
|
|
73
|
+
isExhausted(): boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fail the run (or retry, on early attempts) when the replay budget is
|
|
77
|
+
* exhausted. The handling depends on whether the underlying World
|
|
78
|
+
* supports `process.exit(1)` as a queue redelivery signal (see
|
|
79
|
+
* `World.processExitTriggersQueueRedelivery`):
|
|
80
|
+
*
|
|
81
|
+
* - **Managed-platform Worlds** (`world-vercel`): on attempts <=
|
|
82
|
+
* `REPLAY_TIMEOUT_MAX_RETRIES` exit the process so the platform fails
|
|
83
|
+
* the invocation and the queue redelivers; on the next attempt write
|
|
84
|
+
* `run_failed` with `RUN_ERROR_CODES.REPLAY_TIMEOUT` and exit.
|
|
85
|
+
*
|
|
86
|
+
* - **In-process Worlds** (`world-local`, dev servers): calling
|
|
87
|
+
* `process.exit()` would terminate the host (e.g. `pnpm dev`), so
|
|
88
|
+
* instead log a warning, write `run_failed` best-effort, and return.
|
|
89
|
+
* The framework completes the request normally.
|
|
90
|
+
*/
|
|
91
|
+
export declare function handleReplayBudgetExhausted(args: {
|
|
92
|
+
runId: string;
|
|
93
|
+
workflowName: string;
|
|
94
|
+
requestId: string | undefined;
|
|
95
|
+
attempt: number;
|
|
96
|
+
limitMs: number;
|
|
97
|
+
}): Promise<void>;
|
|
98
|
+
//# sourceMappingURL=replay-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-budget.d.ts","sourceRoot":"","sources":["../../src/runtime/replay-budget.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,aAAa,CAAgB;gBAEzB,OAAO,GAAE,MAA6B;IAKlD;;OAEG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED;;;OAGG;IACH,OAAO,IAAI,MAAM;IAMjB;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAMb;;;;;OAKG;IACH,MAAM,IAAI,IAAI;IAId;;;;OAIG;IACH,WAAW,IAAI,OAAO;CAGvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkHhB"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { FatalError, RUN_ERROR_CODES } from '@workflow/errors';
|
|
2
|
+
import { SPEC_VERSION_CURRENT } from '@workflow/world';
|
|
3
|
+
import { describeError } from '../describe-error.js';
|
|
4
|
+
import { runtimeLogger } from '../logger.js';
|
|
5
|
+
import { dehydrateRunError } from '../serialization.js';
|
|
6
|
+
import { getReplayTimeoutMaxRetries, getReplayTimeoutMs } from './constants.js';
|
|
7
|
+
import { memoizeEncryptionKey } from './helpers.js';
|
|
8
|
+
import { getWorld } from './world.js';
|
|
9
|
+
/**
|
|
10
|
+
* Per-invocation accounting of the *non-step* portion of a workflow
|
|
11
|
+
* handler run: deterministic event-log replay, workflow-VM execution
|
|
12
|
+
* between step boundaries, suspension handling, queue round-trips, etc.
|
|
13
|
+
* Inline step bodies (`"use step"` functions invoked via `executeStep`)
|
|
14
|
+
* are intentionally excluded — they are bounded by the platform's
|
|
15
|
+
* function `maxDuration` and the `NO_INLINE_REPLAY_AFTER_MS` early-return
|
|
16
|
+
* guard.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const budget = new ReplayBudget();
|
|
22
|
+
* // …non-step work happens here, accumulates against the budget…
|
|
23
|
+
* budget.pause();
|
|
24
|
+
* try {
|
|
25
|
+
* await executeStep(...); // not charged
|
|
26
|
+
* } finally {
|
|
27
|
+
* budget.resume();
|
|
28
|
+
* }
|
|
29
|
+
* // back to charging
|
|
30
|
+
* if (budget.isExhausted()) { ... }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* Implementation notes:
|
|
34
|
+
*
|
|
35
|
+
* - `pause()` and `resume()` are idempotent: calling `pause()` while
|
|
36
|
+
* already paused (or `resume()` while already resumed) is a no-op.
|
|
37
|
+
* This protects against double-counting in future refactors that nest
|
|
38
|
+
* step execution or take an early-return path between a `pause()` and
|
|
39
|
+
* the matching `resume()`.
|
|
40
|
+
* - `isExhausted()` is checked at loop boundaries by the caller — the
|
|
41
|
+
* budget itself does not arm any timers. This means an in-flight
|
|
42
|
+
* pathological `runWorkflow` call (e.g. a huge event-log replay) can
|
|
43
|
+
* overshoot the budget by up to one iteration's worth of work before
|
|
44
|
+
* the next check fires. In practice the 20s headroom built into
|
|
45
|
+
* `MAX_REPLAY_TIMEOUT_MS` (and the function `maxDuration` ceiling)
|
|
46
|
+
* gives us slack; the old `setTimeout`-based approach also ultimately
|
|
47
|
+
* relied on the platform SIGTERM as the hard backstop.
|
|
48
|
+
*/
|
|
49
|
+
export class ReplayBudget {
|
|
50
|
+
limitMs;
|
|
51
|
+
elapsedMs = 0;
|
|
52
|
+
intervalStart;
|
|
53
|
+
constructor(limitMs = getReplayTimeoutMs()) {
|
|
54
|
+
this.limitMs = limitMs;
|
|
55
|
+
this.intervalStart = Date.now();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The configured replay-timeout limit, in ms. Useful for log messages.
|
|
59
|
+
*/
|
|
60
|
+
get configuredLimitMs() {
|
|
61
|
+
return this.limitMs;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Total non-step time accumulated so far, in ms. Includes the
|
|
65
|
+
* currently-active interval if the budget is not paused.
|
|
66
|
+
*/
|
|
67
|
+
elapsed() {
|
|
68
|
+
const open = this.intervalStart === null ? 0 : Date.now() - this.intervalStart;
|
|
69
|
+
return this.elapsedMs + open;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Stop counting elapsed time toward the budget. Idempotent — safe to
|
|
73
|
+
* call multiple times in a row; subsequent calls are no-ops until
|
|
74
|
+
* `resume()` reopens an interval.
|
|
75
|
+
*/
|
|
76
|
+
pause() {
|
|
77
|
+
if (this.intervalStart === null)
|
|
78
|
+
return;
|
|
79
|
+
this.elapsedMs += Date.now() - this.intervalStart;
|
|
80
|
+
this.intervalStart = null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resume counting elapsed time toward the budget. Idempotent — safe to
|
|
84
|
+
* call multiple times in a row; subsequent calls re-anchor the
|
|
85
|
+
* interval start to `now()`, which is fine because no time accrues
|
|
86
|
+
* between back-to-back `resume()` calls.
|
|
87
|
+
*/
|
|
88
|
+
resume() {
|
|
89
|
+
this.intervalStart = Date.now();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* True if the budget has been exhausted (`elapsed() >= limitMs`).
|
|
93
|
+
* Callers should invoke `handleExhausted(...)` afterward and return
|
|
94
|
+
* from the handler.
|
|
95
|
+
*/
|
|
96
|
+
isExhausted() {
|
|
97
|
+
return this.elapsed() >= this.limitMs;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fail the run (or retry, on early attempts) when the replay budget is
|
|
102
|
+
* exhausted. The handling depends on whether the underlying World
|
|
103
|
+
* supports `process.exit(1)` as a queue redelivery signal (see
|
|
104
|
+
* `World.processExitTriggersQueueRedelivery`):
|
|
105
|
+
*
|
|
106
|
+
* - **Managed-platform Worlds** (`world-vercel`): on attempts <=
|
|
107
|
+
* `REPLAY_TIMEOUT_MAX_RETRIES` exit the process so the platform fails
|
|
108
|
+
* the invocation and the queue redelivers; on the next attempt write
|
|
109
|
+
* `run_failed` with `RUN_ERROR_CODES.REPLAY_TIMEOUT` and exit.
|
|
110
|
+
*
|
|
111
|
+
* - **In-process Worlds** (`world-local`, dev servers): calling
|
|
112
|
+
* `process.exit()` would terminate the host (e.g. `pnpm dev`), so
|
|
113
|
+
* instead log a warning, write `run_failed` best-effort, and return.
|
|
114
|
+
* The framework completes the request normally.
|
|
115
|
+
*/
|
|
116
|
+
export async function handleReplayBudgetExhausted(args) {
|
|
117
|
+
const { runId, workflowName, requestId, attempt, limitMs } = args;
|
|
118
|
+
const runLogger = runtimeLogger.forRun(runId, workflowName);
|
|
119
|
+
const world = await getWorld();
|
|
120
|
+
const canExitForRedelivery = world.processExitTriggersQueueRedelivery === true;
|
|
121
|
+
// Worlds without managed-platform redelivery (e.g. world-local, custom
|
|
122
|
+
// in-process worlds) must not have us exit the process — that would
|
|
123
|
+
// kill the user's host (`pnpm dev`, CLI, etc.) without producing a
|
|
124
|
+
// retry. Surface the failure via the event log if we can, then return.
|
|
125
|
+
if (!canExitForRedelivery) {
|
|
126
|
+
runLogger.warn('Workflow replay exceeded timeout; current World does not support process exit for redelivery — failing the run and returning', { timeoutMs: limitMs, attempt });
|
|
127
|
+
try {
|
|
128
|
+
const getEncryptionKey = memoizeEncryptionKey(world, runId);
|
|
129
|
+
const timeoutErr = new FatalError(`Workflow replay exceeded maximum duration (${limitMs / 1000}s)`);
|
|
130
|
+
await world.events.create(runId, {
|
|
131
|
+
eventType: 'run_failed',
|
|
132
|
+
specVersion: SPEC_VERSION_CURRENT,
|
|
133
|
+
eventData: {
|
|
134
|
+
error: await dehydrateRunError(timeoutErr, runId, await getEncryptionKey()),
|
|
135
|
+
errorCode: RUN_ERROR_CODES.REPLAY_TIMEOUT,
|
|
136
|
+
},
|
|
137
|
+
}, { requestId });
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
runLogger.warn('Unable to mark run as failed', {
|
|
141
|
+
attempt,
|
|
142
|
+
errorName: err instanceof Error ? err.name : 'UnknownError',
|
|
143
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const maxRetries = getReplayTimeoutMaxRetries();
|
|
149
|
+
if (attempt <= maxRetries) {
|
|
150
|
+
runLogger.warn('Workflow replay exceeded timeout but will be re-attempted (attempt < maxRetries)', {
|
|
151
|
+
timeoutMs: limitMs,
|
|
152
|
+
attempt,
|
|
153
|
+
maxRetries,
|
|
154
|
+
});
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
const replayTimeoutDescription = describeError(undefined, RUN_ERROR_CODES.REPLAY_TIMEOUT);
|
|
158
|
+
runLogger.error('Workflow replay exceeded timeout and max retries exceeded. Failing the run', {
|
|
159
|
+
timeoutMs: limitMs,
|
|
160
|
+
attempt,
|
|
161
|
+
maxRetries,
|
|
162
|
+
errorCode: replayTimeoutDescription.errorCode,
|
|
163
|
+
errorAttribution: replayTimeoutDescription.attribution,
|
|
164
|
+
});
|
|
165
|
+
try {
|
|
166
|
+
const getEncryptionKey = memoizeEncryptionKey(world, runId);
|
|
167
|
+
const timeoutErr = new FatalError(`Workflow replay exceeded maximum duration (${limitMs / 1000}s) after ${attempt} attempts`);
|
|
168
|
+
await world.events.create(runId, {
|
|
169
|
+
eventType: 'run_failed',
|
|
170
|
+
specVersion: SPEC_VERSION_CURRENT,
|
|
171
|
+
eventData: {
|
|
172
|
+
error: await dehydrateRunError(timeoutErr, runId, await getEncryptionKey()),
|
|
173
|
+
errorCode: RUN_ERROR_CODES.REPLAY_TIMEOUT,
|
|
174
|
+
},
|
|
175
|
+
}, { requestId });
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
// Best effort — process exits regardless. Surface why so operators
|
|
179
|
+
// can diagnose repeat timeouts against the backend.
|
|
180
|
+
runLogger.warn('Unable to mark run as failed. The queue will continue to retry', {
|
|
181
|
+
attempt,
|
|
182
|
+
errorName: err instanceof Error ? err.name : 'UnknownError',
|
|
183
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
184
|
+
errorStack: err instanceof Error ? err.stack : undefined,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// Note that this also prevents the runtime from acking the queue
|
|
188
|
+
// message, so the queue will call back once, after which a 410 will
|
|
189
|
+
// get it to exit early.
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwbGF5LWJ1ZGdldC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ydW50aW1lL3JlcGxheS1idWRnZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxlQUFlLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUMvRCxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDckQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUM3QyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN4RCxPQUFPLEVBQUUsMEJBQTBCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNoRixPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDcEQsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUV0Qzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBdUNHO0FBQ0gsTUFBTSxPQUFPLFlBQVk7SUFDTixPQUFPLENBQVM7SUFDekIsU0FBUyxHQUFHLENBQUMsQ0FBQztJQUNkLGFBQWEsQ0FBZ0I7SUFFckMsWUFBWSxVQUFrQixrQkFBa0IsRUFBRTtRQUNoRCxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsYUFBYSxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUNsQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJLGlCQUFpQjtRQUNuQixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDdEIsQ0FBQztJQUVEOzs7T0FHRztJQUNILE9BQU87UUFDTCxNQUFNLElBQUksR0FDUixJQUFJLENBQUMsYUFBYSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMs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QUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLGNBQWM7WUFDM0QsWUFBWSxFQUFFLEdBQUcsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUM7WUFDOUQsVUFBVSxFQUFFLEdBQUcsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDekQsQ0FDRixDQUFDO0lBQ0osQ0FBQztJQUNELGlFQUFpRTtJQUNqRSxvRUFBb0U7SUFDcEUsd0JBQXdCO0lBQ3hCLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDbEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEZhdGFsRXJyb3IsIFJVTl9FUlJPUl9DT0RFUyB9IGZyb20gJ0B3b3JrZmxvdy9lcnJvcnMnO1xuaW1wb3J0IHsgU1BFQ19WRVJTSU9OX0NVUlJFTlQgfSBmcm9tICdAd29ya2Zsb3cvd29ybGQnO1xuaW1wb3J0IHsgZGVzY3JpYmVFcnJvciB9IGZyb20gJy4uL2Rlc2NyaWJlLWVycm9yLmpzJztcbmltcG9ydCB7IHJ1bnRpbWVMb2dnZXIgfSBmcm9tICcuLi9sb2dnZXIuanMnO1xuaW1wb3J0IHsgZGVoeWRyYXRlUnVuRXJyb3IgfSBmcm9tICcuLi9zZXJpYWxpemF0aW9uLmpzJztcbmltcG9ydCB7IGdldFJlcGxheVRpbWVvdXRNYXhSZXRyaWVzLCBnZXRSZXBsYXlUaW1lb3V0TXMgfSBmcm9tICcuL2NvbnN0YW50cy5qcyc7XG5pbXBvcnQgeyBtZW1vaXplRW5jcnlwdGlvbktleSB9IGZyb20gJy4vaGVscGVycy5qcyc7XG5pbXBvcnQgeyBnZXRXb3JsZCB9IGZyb20gJy4vd29ybGQuanMnO1xuXG4vKipcbiAqIFBlci1pbnZvY2F0aW9uIGFjY291bnRpbmcgb2YgdGhlICpub24tc3RlcCogcG9ydGlvbiBvZiBhIHdvcmtmbG93XG4gKiBoYW5kbGVyIHJ1bjogZGV0ZXJtaW5pc3RpYyBldmVudC1sb2cgcmVwbGF5LCB3b3JrZmxvdy1WTSBleGVjdXRpb25cbiAqIGJldHdlZW4gc3RlcCBib3VuZGFyaWVzLCBzdXNwZW5zaW9uIGhhbmRsaW5nLCBxdWV1ZSByb3VuZC10cmlwcywgZXRjLlxuICogSW5saW5lIHN0ZXAgYm9kaWVzIChgXCJ1c2Ugc3RlcFwiYCBmdW5jdGlvbnMgaW52b2tlZCB2aWEgYGV4ZWN1dGVTdGVwYClcbiAqIGFyZSBpbnRlbnRpb25hbGx5IGV4Y2x1ZGVkIOKAlCB0aGV5IGFyZSBib3VuZGVkIGJ5IHRoZSBwbGF0Zm9ybSdzXG4gKiBmdW5jdGlvbiBgbWF4RHVyYXRpb25gIGFuZCB0aGUgYE5PX0lOTElORV9SRVBMQVlfQUZURVJfTVNgIGVhcmx5LXJldHVyblxuICogZ3VhcmQuXG4gKlxuICogVXNhZ2U6XG4gKlxuICogYGBgdHNcbiAqIGNvbnN0IGJ1ZGdldCA9IG5ldyBSZXBsYXlCdWRnZXQoKTtcbiAqIC8vIOKApm5vbi1zdGVwIHdvcmsgaGFwcGVucyBoZXJlLCBhY2N1bXVsYXRlcyBhZ2FpbnN0IHRoZSBidWRnZXTigKZcbiAqIGJ1ZGdldC5wYXVzZSgpO1xuICogdHJ5IHtcbiAqICAgYXdhaXQgZXhlY3V0ZVN0ZXAoLi4uKTsgLy8gbm90IGNoYXJnZWRcbiAqIH0gZmluYWxseSB7XG4gKiAgIGJ1ZGdldC5yZXN1bWUoKTtcbiAqIH1cbiAqIC8vIGJhY2sgdG8gY2hhcmdpbmdcbiAqIGlmIChidWRnZXQuaXNFeGhhdXN0ZWQoKSkgeyAuLi4gfVxuICogYGBgXG4gKlxuICogSW1wbGVtZW50YXRpb24gbm90ZXM6XG4gKlxuICogLSBgcGF1c2UoKWAgYW5kIGByZXN1bWUoKWAgYXJlIGlkZW1wb3RlbnQ6IGNhbGxpbmcgYHBhdXNlKClgIHdoaWxlXG4gKiAgIGFscmVhZHkgcGF1c2VkIChvciBgcmVzdW1lKClgIHdoaWxlIGFscmVhZHkgcmVzdW1lZCkgaXMgYSBuby1vcC5cbiAqICAgVGhpcyBwcm90ZWN0cyBhZ2FpbnN0IGRvdWJsZS1jb3VudGluZyBpbiBmdXR1cmUgcmVmYWN0b3JzIHRoYXQgbmVzdFxuICogICBzdGVwIGV4ZWN1dGlvbiBvciB0YWtlIGFuIGVhcmx5LXJldHVybiBwYXRoIGJldHdlZW4gYSBgcGF1c2UoKWAgYW5kXG4gKiAgIHRoZSBtYXRjaGluZyBgcmVzdW1lKClgLlxuICogLSBgaXNFeGhhdXN0ZWQoKWAgaXMgY2hlY2tlZCBhdCBsb29wIGJvdW5kYXJpZXMgYnkgdGhlIGNhbGxlciDigJQgdGhlXG4gKiAgIGJ1ZGdldCBpdHNlbGYgZG9lcyBub3QgYXJtIGFueSB0aW1lcnMuIFRoaXMgbWVhbnMgYW4gaW4tZmxpZ2h0XG4gKiAgIHBhdGhvbG9naWNhbCBgcnVuV29ya2Zsb3dgIGNhbGwgKGUuZy4gYSBodWdlIGV2ZW50LWxvZyByZXBsYXkpIGNhblxuICogICBvdmVyc2hvb3QgdGhlIGJ1ZGdldCBieSB1cCB0byBvbmUgaXRlcmF0aW9uJ3Mgd29ydGggb2Ygd29yayBiZWZvcmVcbiAqICAgdGhlIG5leHQgY2hlY2sgZmlyZXMuIEluIHByYWN0aWNlIHRoZSAyMHMgaGVhZHJvb20gYnVpbHQgaW50b1xuICogICBgTUFYX1JFUExBWV9USU1FT1VUX01TYCAoYW5kIHRoZSBmdW5jdGlvbiBgbWF4RHVyYXRpb25gIGNlaWxpbmcpXG4gKiAgIGdpdmVzIHVzIHNsYWNrOyB0aGUgb2xkIGBzZXRUaW1lb3V0YC1iYXNlZCBhcHByb2FjaCBhbHNvIHVsdGltYXRlbHlcbiAqICAgcmVsaWVkIG9uIHRoZSBwbGF0Zm9ybSBTSUdURVJNIGFzIHRoZSBoYXJkIGJhY2tzdG9wLlxuICovXG5leHBvcnQgY2xhc3MgUmVwbGF5QnVkZ2V0IHtcbiAgcHJpdmF0ZSByZWFkb25seSBsaW1pdE1zOiBudW1iZXI7XG4gIHByaXZhdGUgZWxhcHNlZE1zID0gMDtcbiAgcHJpdmF0ZSBpbnRlcnZhbFN0YXJ0OiBudW1iZXIgfCBudWxsO1xuXG4gIGNvbnN0cnVjdG9yKGxpbWl0TXM6IG51bWJlciA9IGdldFJlcGxheVRpbWVvdXRNcygpKSB7XG4gICAgdGhpcy5saW1pdE1zID0gbGltaXRNcztcbiAgICB0aGlzLmludGVydmFsU3RhcnQgPSBEYXRlLm5vdygpO1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSBjb25maWd1cmVkIHJlcGxheS10aW1lb3V0IGxpbWl0LCBpbiBtcy4gVXNlZnVsIGZvciBsb2cgbWVzc2FnZXMuXG4gICAqL1xuICBnZXQgY29uZmlndXJlZExpbWl0TXMoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gdGhpcy5saW1pdE1zO1xuICB9XG5cbiAgLyoqXG4gICAqIFRvdGFsIG5vbi1zdGVwIHRpbWUgYWNjdW11bGF0ZWQgc28gZmFyLCBpbiBtcy4gSW5jbHVkZXMgdGhlXG4gICAqIGN1cnJlbnRseS1hY3RpdmUgaW50ZXJ2YWwgaWYgdGhlIGJ1ZGdldCBpcyBub3QgcGF1c2VkLlxuICAgKi9cbiAgZWxhcHNlZCgpOiBudW1iZXIge1xuICAgIGNvbnN0IG9wZW4gPVxuICAgICAgdGhpcy5pbnRlcnZhbFN0YXJ0ID09PSBudWxsID8gMCA6IERhdGUubm93KCkgLSB0aGlzLmludGVydmFsU3RhcnQ7XG4gICAgcmV0dXJuIHRoaXMuZWxhcHNlZE1zICsgb3BlbjtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdG9wIGNvdW50aW5nIGVsYXBzZWQgdGltZSB0b3dhcmQgdGhlIGJ1ZGdldC4gSWRlbXBvdGVudCDigJQgc2FmZSB0b1xuICAgKiBjYWxsIG11bHRpcGxlIHRpbWVzIGluIGEgcm93OyBzdWJzZXF1ZW50IGNhbGxzIGFyZSBuby1vcHMgdW50aWxcbiAgICogYHJlc3VtZSgpYCByZW9wZW5zIGFuIGludGVydmFsLlxuICAgKi9cbiAgcGF1c2UoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuaW50ZXJ2YWxTdGFydCA9PT0gbnVsbCkgcmV0dXJuO1xuICAgIHRoaXMuZWxhcHNlZE1zICs9IERhdGUubm93KCkgLSB0aGlzLmludGVydmFsU3RhcnQ7XG4gICAgdGhpcy5pbnRlcnZhbFN0YXJ0ID0gbnVsbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXN1bWUgY291bnRpbmcgZWxhcHNlZCB0aW1lIHRvd2FyZCB0aGUgYnVkZ2V0LiBJZGVtcG90ZW50IOKAlCBzYWZlIHRvXG4gICAqIGNhbGwgbXVsdGlwbGUgdGltZXMgaW4gYSByb3c7IHN1YnNlcXVlbnQgY2FsbHMgcmUtYW5jaG9yIHRoZVxuICAgKiBpbnRlcnZhbCBzdGFydCB0byBgbm93KClgLCB3aGljaCBpcyBmaW5lIGJlY2F1c2Ugbm8gdGltZSBhY2NydWVzXG4gICAqIGJldHdlZW4gYmFjay10by1iYWNrIGByZXN1bWUoKWAgY2FsbHMuXG4gICAqL1xuICByZXN1bWUoKTogdm9pZCB7XG4gICAgdGhpcy5pbnRlcnZhbFN0YXJ0ID0gRGF0ZS5ub3coKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUcnVlIGlmIHRoZSBidWRnZXQgaGFzIGJlZW4gZXhoYXVzdGVkIChgZWxhcHNlZCgpID49IGxpbWl0TXNgKS5cbiAgICogQ2FsbGVycyBzaG91bGQgaW52b2tlIGBoYW5kbGVFeGhhdXN0ZWQoLi4uKWAgYWZ0ZXJ3YXJkIGFuZCByZXR1cm5cbiAgICogZnJvbSB0aGUgaGFuZGxlci5cbiAgICovXG4gIGlzRXhoYXVzdGVkKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmVsYXBzZWQoKSA+PSB0aGlzLmxpbWl0TXM7XG4gIH1cbn1cblxuLyoqXG4gKiBGYWlsIHRoZSBydW4gKG9yIHJldHJ5LCBvbiBlYXJseSBhdHRlbXB0cykgd2hlbiB0aGUgcmVwbGF5IGJ1ZGdldCBpc1xuICogZXhoYXVzdGVkLiBUaGUgaGFuZGxpbmcgZGVwZW5kcyBvbiB3aGV0aGVyIHRoZSB1bmRlcmx5aW5nIFdvcmxkXG4gKiBzdXBwb3J0cyBgcHJvY2Vzcy5leGl0KDEpYCBhcyBhIHF1ZXVlIHJlZGVsaXZlcnkgc2lnbmFsIChzZWVcbiAqIGBXb3JsZC5wcm9jZXNzRXhpdFRyaWdnZXJzUXVldWVSZWRlbGl2ZXJ5YCk6XG4gKlxuICogLSAqKk1hbmFnZWQtcGxhdGZvcm0gV29ybGRzKiogKGB3b3JsZC12ZXJjZWxgKTogb24gYXR0ZW1wdHMgPD1cbiAqICAgYFJFUExBWV9USU1FT1VUX01BWF9SRVRSSUVTYCBleGl0IHRoZSBwcm9jZXNzIHNvIHRoZSBwbGF0Zm9ybSBmYWlsc1xuICogICB0aGUgaW52b2NhdGlvbiBhbmQgdGhlIHF1ZXVlIHJlZGVsaXZlcnM7IG9uIHRoZSBuZXh0IGF0dGVtcHQgd3JpdGVcbiAqICAgYHJ1bl9mYWlsZWRgIHdpdGggYFJVTl9FUlJPUl9DT0RFUy5SRVBMQVlfVElNRU9VVGAgYW5kIGV4aXQuXG4gKlxuICogLSAqKkluLXByb2Nlc3MgV29ybGRzKiogKGB3b3JsZC1sb2NhbGAsIGRldiBzZXJ2ZXJzKTogY2FsbGluZ1xuICogICBgcHJvY2Vzcy5leGl0KClgIHdvdWxkIHRlcm1pbmF0ZSB0aGUgaG9zdCAoZS5nLiBgcG5wbSBkZXZgKSwgc29cbiAqICAgaW5zdGVhZCBsb2cgYSB3YXJuaW5nLCB3cml0ZSBgcnVuX2ZhaWxlZGAgYmVzdC1lZmZvcnQsIGFuZCByZXR1cm4uXG4gKiAgIFRoZSBmcmFtZXdvcmsgY29tcGxldGVzIHRoZSByZXF1ZXN0IG5vcm1hbGx5LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaGFuZGxlUmVwbGF5QnVkZ2V0RXhoYXVzdGVkKGFyZ3M6IHtcbiAgcnVuSWQ6IHN0cmluZztcbiAgd29ya2Zsb3dOYW1lOiBzdHJpbmc7XG4gIHJlcXVlc3RJZDogc3RyaW5nIHwgdW5kZWZpbmVkO1xuICBhdHRlbXB0OiBudW1iZXI7XG4gIGxpbWl0TXM6IG51bWJlcjtcbn0pOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3QgeyBydW5JZCwgd29ya2Zsb3dOYW1lLCByZXF1ZXN0SWQsIGF0dGVtcHQsIGxpbWl0TXMgfSA9IGFyZ3M7XG4gIGNvbnN0IHJ1bkxvZ2dlciA9IHJ1bnRpbWVMb2dnZXIuZm9yUnVuKHJ1bklkLCB3b3JrZmxvd05hbWUpO1xuXG4gIGNvbnN0IHdvcmxkID0gYXdhaXQgZ2V0V29ybGQoKTtcbiAgY29uc3QgY2FuRXhpdEZvclJlZGVsaXZlcnkgPVxuICAgIHdvcmxkLnByb2Nlc3NFeGl0VHJpZ2dlcnNRdWV1ZVJlZGVsaXZlcnkgPT09IHRydWU7XG5cbiAgLy8gV29ybGRzIHdpdGhvdXQgbWFuYWdlZC1wbGF0Zm9ybSByZWRlbGl2ZXJ5IChlLmcuIHdvcmxkLWxvY2FsLCBjdXN0b21cbiAgLy8gaW4tcHJvY2VzcyB3b3JsZHMpIG11c3Qgbm90IGhhdmUgdXMgZXhpdCB0aGUgcHJvY2VzcyDigJQgdGhhdCB3b3VsZFxuICAvLyBraWxsIHRoZSB1c2VyJ3MgaG9zdCAoYHBucG0gZGV2YCwgQ0xJLCBldGMuKSB3aXRob3V0IHByb2R1Y2luZyBhXG4gIC8vIHJldHJ5LiBTdXJmYWNlIHRoZSBmYWlsdXJlIHZpYSB0aGUgZXZlbnQgbG9nIGlmIHdlIGNhbiwgdGhlbiByZXR1cm4uXG4gIGlmICghY2FuRXhpdEZvclJlZGVsaXZlcnkpIHtcbiAgICBydW5Mb2dnZXIud2FybihcbiAgICAgICdXb3JrZmxvdyByZXBsYXkgZXhjZWVkZWQgdGltZW91dDsgY3VycmVudCBXb3JsZCBkb2VzIG5vdCBzdXBwb3J0IHByb2Nlc3MgZXhpdCBmb3IgcmVkZWxpdmVyeSDigJQgZmFpbGluZyB0aGUgcnVuIGFuZCByZXR1cm5pbmcnLFxuICAgICAgeyB0aW1lb3V0TXM6IGxpbWl0TXMsIGF0dGVtcHQgfVxuICAgICk7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGdldEVuY3J5cHRpb25LZXkgPSBtZW1vaXplRW5jcnlwdGlvbktleSh3b3JsZCwgcnVuSWQpO1xuICAgICAgY29uc3QgdGltZW91dEVyciA9IG5ldyBGYXRhbEVycm9yKFxuICAgICAgICBgV29ya2Zsb3cgcmVwbGF5IGV4Y2VlZGVkIG1heGltdW0gZHVyYXRpb24gKCR7bGltaXRNcyAvIDEwMDB9cylgXG4gICAgICApO1xuICAgICAgYXdhaXQgd29ybGQuZXZlbnRzLmNyZWF0ZShcbiAgICAgICAgcnVuSWQsXG4gICAgICAgIHtcbiAgICAgICAgICBldmVudFR5cGU6ICdydW5fZmFpbGVkJyxcbiAgICAgICAgICBzcGVjVmVyc2lvbjogU1BFQ19WRVJTSU9OX0NVUlJFTlQsXG4gICAgICAgICAgZXZlbnREYXRhOiB7XG4gICAgICAgICAgICBlcnJvcjogYXdhaXQgZGVoeWRyYXRlUnVuRXJyb3IoXG4gICAgICAgICAgICAgIHRpbWVvdXRFcnIsXG4gICAgICAgICAgICAgIHJ1bklkLFxuICAgICAgICAgICAgICBhd2FpdCBnZXRFbmNyeXB0aW9uS2V5KClcbiAgICAgICAgICAgICksXG4gICAgICAgICAgICBlcnJvckNvZGU6IFJVTl9FUlJPUl9DT0RFUy5SRVBMQVlfVElNRU9VVCxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgICB7IHJlcXVlc3RJZCB9XG4gICAgICApO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgcnVuTG9nZ2VyLndhcm4oJ1VuYWJsZSB0byBtYXJrIHJ1biBhcyBmYWlsZWQnLCB7XG4gICAgICAgIGF0dGVtcHQsXG4gICAgICAgIGVycm9yTmFtZTogZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubmFtZSA6ICdVbmtub3duRXJyb3InLFxuICAgICAgICBlcnJvck1lc3NhZ2U6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSxcbiAgICAgIH0pO1xuICAgIH1cbiAgICByZXR1cm47XG4gIH1cblxuICBjb25zdCBtYXhSZXRyaWVzID0gZ2V0UmVwbGF5VGltZW91dE1heFJldHJpZXMoKTtcbiAgaWYgKGF0dGVtcHQgPD0gbWF4UmV0cmllcykge1xuICAgIHJ1bkxvZ2dlci53YXJuKFxuICAgICAgJ1dvcmtmbG93IHJlcGxheSBleGNlZWRlZCB0aW1lb3V0IGJ1dCB3aWxsIGJlIHJlLWF0dGVtcHRlZCAoYXR0ZW1wdCA8IG1heFJldHJpZXMpJyxcbiAgICAgIHtcbiAgICAgICAgdGltZW91dE1zOiBsaW1pdE1zLFxuICAgICAgICBhdHRlbXB0LFxuICAgICAgICBtYXhSZXRyaWVzLFxuICAgICAgfVxuICAgICk7XG4gICAgcHJvY2Vzcy5leGl0KDEpO1xuICB9XG5cbiAgY29uc3QgcmVwbGF5VGltZW91dERlc2NyaXB0aW9uID0gZGVzY3JpYmVFcnJvcihcbiAgICB1bmRlZmluZWQsXG4gICAgUlVOX0VSUk9SX0NPREVTLlJFUExBWV9USU1FT1VUXG4gICk7XG4gIHJ1bkxvZ2dlci5lcnJvcihcbiAgICAnV29ya2Zsb3cgcmVwbGF5IGV4Y2VlZGVkIHRpbWVvdXQgYW5kIG1heCByZXRyaWVzIGV4Y2VlZGVkLiBGYWlsaW5nIHRoZSBydW4nLFxuICAgIHtcbiAgICAgIHRpbWVvdXRNczogbGltaXRNcyxcbiAgICAgIGF0dGVtcHQsXG4gICAgICBtYXhSZXRyaWVzLFxuICAgICAgZXJyb3JDb2RlOiByZXBsYXlUaW1lb3V0RGVzY3JpcHRpb24uZXJyb3JDb2RlLFxuICAgICAgZXJyb3JBdHRyaWJ1dGlvbjogcmVwbGF5VGltZW91dERlc2NyaXB0aW9uLmF0dHJpYnV0aW9uLFxuICAgIH1cbiAgKTtcblxuICB0cnkge1xuICAgIGNvbnN0IGdldEVuY3J5cHRpb25LZXkgPSBtZW1vaXplRW5jcnlwdGlvbktleSh3b3JsZCwgcnVuSWQpO1xuICAgIGNvbnN0IHRpbWVvdXRFcnIgPSBuZXcgRmF0YWxFcnJvcihcbiAgICAgIGBXb3JrZmxvdyByZXBsYXkgZXhjZWVkZWQgbWF4aW11bSBkdXJhdGlvbiAoJHtsaW1pdE1zIC8gMTAwMH1zKSBhZnRlciAke2F0dGVtcHR9IGF0dGVtcHRzYFxuICAgICk7XG4gICAgYXdhaXQgd29ybGQuZXZlbnRzLmNyZWF0ZShcbiAgICAgIHJ1bklkLFxuICAgICAge1xuICAgICAgICBldmVudFR5cGU6ICdydW5fZmFpbGVkJyxcbiAgICAgICAgc3BlY1ZlcnNpb246IFNQRUNfVkVSU0lPTl9DVVJSRU5ULFxuICAgICAgICBldmVudERhdGE6IHtcbiAgICAgICAgICBlcnJvcjogYXdhaXQgZGVoeWRyYXRlUnVuRXJyb3IoXG4gICAgICAgICAgICB0aW1lb3V0RXJyLFxuICAgICAgICAgICAgcnVuSWQsXG4gICAgICAgICAgICBhd2FpdCBnZXRFbmNyeXB0aW9uS2V5KClcbiAgICAgICAgICApLFxuICAgICAgICAgIGVycm9yQ29kZTogUlVOX0VSUk9SX0NPREVTLlJFUExBWV9USU1FT1VULFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICAgIHsgcmVxdWVzdElkIH1cbiAgICApO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICAvLyBCZXN0IGVmZm9ydCDigJQgcHJvY2VzcyBleGl0cyByZWdhcmRsZXNzLiBTdXJmYWNlIHdoeSBzbyBvcGVyYXRvcnNcbiAgICAvLyBjYW4gZGlhZ25vc2UgcmVwZWF0IHRpbWVvdXRzIGFnYWluc3QgdGhlIGJhY2tlbmQuXG4gICAgcnVuTG9nZ2VyLndhcm4oXG4gICAgICAnVW5hYmxlIHRvIG1hcmsgcnVuIGFzIGZhaWxlZC4gVGhlIHF1ZXVlIHdpbGwgY29udGludWUgdG8gcmV0cnknLFxuICAgICAge1xuICAgICAgICBhdHRlbXB0LFxuICAgICAgICBlcnJvck5hbWU6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm5hbWUgOiAnVW5rbm93bkVycm9yJyxcbiAgICAgICAgZXJyb3JNZXNzYWdlOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVyciksXG4gICAgICAgIGVycm9yU3RhY2s6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLnN0YWNrIDogdW5kZWZpbmVkLFxuICAgICAgfVxuICAgICk7XG4gIH1cbiAgLy8gTm90ZSB0aGF0IHRoaXMgYWxzbyBwcmV2ZW50cyB0aGUgcnVudGltZSBmcm9tIGFja2luZyB0aGUgcXVldWVcbiAgLy8gbWVzc2FnZSwgc28gdGhlIHF1ZXVlIHdpbGwgY2FsbCBiYWNrIG9uY2UsIGFmdGVyIHdoaWNoIGEgNDEwIHdpbGxcbiAgLy8gZ2V0IGl0IHRvIGV4aXQgZWFybHkuXG4gIHByb2Nlc3MuZXhpdCgxKTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CreateEventParams } from '@workflow/world';
|
|
2
|
+
/**
|
|
3
|
+
* Invocation-scoped, one-shot reporter for a replay-divergence recovery.
|
|
4
|
+
*
|
|
5
|
+
* The reporter stays dormant until replay reaches a valid deterministic
|
|
6
|
+
* boundary. The first subsequent event write carries the telemetry for free.
|
|
7
|
+
* A recovered invocation with no natural event write intentionally reports
|
|
8
|
+
* nothing.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ReplayRecoveryReporter {
|
|
11
|
+
private readonly divergenceCount;
|
|
12
|
+
private active;
|
|
13
|
+
private reported;
|
|
14
|
+
constructor(divergenceCount: number);
|
|
15
|
+
/**
|
|
16
|
+
* A reporter for an invocation that never diverged: `activate()` cannot arm
|
|
17
|
+
* it, so `withEventCreate` always passes writes through untouched. Lets every
|
|
18
|
+
* call site hold a non-optional reporter instead of branching on one.
|
|
19
|
+
*
|
|
20
|
+
* Structurally inert rather than a zero-count reporter on purpose — the
|
|
21
|
+
* server rejects a count below 1 as out-of-range and emits a
|
|
22
|
+
* `telemetry_rejected` metric, so a reporter that *can* report zero is a
|
|
23
|
+
* live footgun.
|
|
24
|
+
*/
|
|
25
|
+
static inert(): ReplayRecoveryReporter;
|
|
26
|
+
activate(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Decorate one natural event write with recovery telemetry.
|
|
29
|
+
*
|
|
30
|
+
* The claim is taken synchronously, so concurrent suspension writes cannot
|
|
31
|
+
* all carry the same count. A failed request releases it for a later natural
|
|
32
|
+
* write in the same invocation.
|
|
33
|
+
*/
|
|
34
|
+
withEventCreate<T>(params: CreateEventParams | undefined, create: (params: CreateEventParams | undefined) => Promise<T>): Promise<T>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=replay-recovery-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-recovery-reporter.d.ts","sourceRoot":"","sources":["../../src/runtime/replay-recovery-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;;;;;;GAOG;AACH,qBAAa,sBAAsB;IAIrB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAH5C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAS;gBAEI,eAAe,EAAE,MAAM;IAEpD;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,IAAI,sBAAsB;IAItC,QAAQ,IAAI,IAAI;IAIhB;;;;;;OAMG;IACG,eAAe,CAAC,CAAC,EACrB,MAAM,EAAE,iBAAiB,GAAG,SAAS,EACrC,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5D,OAAO,CAAC,CAAC,CAAC;CAcd"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invocation-scoped, one-shot reporter for a replay-divergence recovery.
|
|
3
|
+
*
|
|
4
|
+
* The reporter stays dormant until replay reaches a valid deterministic
|
|
5
|
+
* boundary. The first subsequent event write carries the telemetry for free.
|
|
6
|
+
* A recovered invocation with no natural event write intentionally reports
|
|
7
|
+
* nothing.
|
|
8
|
+
*/
|
|
9
|
+
export class ReplayRecoveryReporter {
|
|
10
|
+
divergenceCount;
|
|
11
|
+
active = false;
|
|
12
|
+
reported = false;
|
|
13
|
+
constructor(divergenceCount) {
|
|
14
|
+
this.divergenceCount = divergenceCount;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A reporter for an invocation that never diverged: `activate()` cannot arm
|
|
18
|
+
* it, so `withEventCreate` always passes writes through untouched. Lets every
|
|
19
|
+
* call site hold a non-optional reporter instead of branching on one.
|
|
20
|
+
*
|
|
21
|
+
* Structurally inert rather than a zero-count reporter on purpose — the
|
|
22
|
+
* server rejects a count below 1 as out-of-range and emits a
|
|
23
|
+
* `telemetry_rejected` metric, so a reporter that *can* report zero is a
|
|
24
|
+
* live footgun.
|
|
25
|
+
*/
|
|
26
|
+
static inert() {
|
|
27
|
+
return new InertReplayRecoveryReporter();
|
|
28
|
+
}
|
|
29
|
+
activate() {
|
|
30
|
+
this.active = true;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Decorate one natural event write with recovery telemetry.
|
|
34
|
+
*
|
|
35
|
+
* The claim is taken synchronously, so concurrent suspension writes cannot
|
|
36
|
+
* all carry the same count. A failed request releases it for a later natural
|
|
37
|
+
* write in the same invocation.
|
|
38
|
+
*/
|
|
39
|
+
async withEventCreate(params, create) {
|
|
40
|
+
if (!this.active || this.reported)
|
|
41
|
+
return create(params);
|
|
42
|
+
this.reported = true;
|
|
43
|
+
try {
|
|
44
|
+
return await create({
|
|
45
|
+
...params,
|
|
46
|
+
replayDivergenceCount: this.divergenceCount,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
this.reported = false;
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** See {@link ReplayRecoveryReporter.inert}. */
|
|
56
|
+
class InertReplayRecoveryReporter extends ReplayRecoveryReporter {
|
|
57
|
+
constructor() {
|
|
58
|
+
super(0);
|
|
59
|
+
}
|
|
60
|
+
activate() {
|
|
61
|
+
// Deliberately empty: nothing to report, so nothing can arm.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwbGF5LXJlY292ZXJ5LXJlcG9ydGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3J1bnRpbWUvcmVwbGF5LXJlY292ZXJ5LXJlcG9ydGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLE9BQU8sc0JBQXNCO0lBSUo7SUFIckIsTUFBTSxHQUFHLEtBQUssQ0FBQztJQUNmLFFBQVEsR0FBRyxLQUFLLENBQUM7SUFFekIsWUFBNkIsZUFBdUI7UUFBdkIsb0JBQWUsR0FBZixlQUFlLENBQVE7SUFBRyxDQUFDO0lBRXhEOzs7Ozs7Ozs7T0FTRztJQUNILE1BQU0sQ0FBQyxLQUFLO1FBQ1YsT0FBTyxJQUFJLDJCQUEyQixFQUFFLENBQUM7SUFDM0MsQ0FBQztJQUVELFFBQVE7UUFDTixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztJQUNyQixDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsS0FBSyxDQUFDLGVBQWUsQ0FDbkIsTUFBcUMsRUFDckMsTUFBNkQ7UUFFN0QsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLFFBQVE7WUFBRSxPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUV6RCxJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQztRQUNyQixJQUFJLENBQUM7WUFDSCxPQUFPLE1BQU0sTUFBTSxDQUFDO2dCQUNsQixHQUFHLE1BQU07Z0JBQ1QscUJBQXFCLEVBQUUsSUFBSSxDQUFDLGVBQWU7YUFDNUMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixJQUFJLENBQUMsUUFBUSxHQUFHLEtBQUssQ0FBQztZQUN0QixNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7SUFDSCxDQUFDO0NBQ0Y7QUFFRCxnREFBZ0Q7QUFDaEQsTUFBTSwyQkFBNEIsU0FBUSxzQkFBc0I7SUFDOUQ7UUFDRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDWCxDQUFDO0lBRVEsUUFBUTtRQUNmLDZEQUE2RDtJQUMvRCxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IENyZWF0ZUV2ZW50UGFyYW1zIH0gZnJvbSAnQHdvcmtmbG93L3dvcmxkJztcblxuLyoqXG4gKiBJbnZvY2F0aW9uLXNjb3BlZCwgb25lLXNob3QgcmVwb3J0ZXIgZm9yIGEgcmVwbGF5LWRpdmVyZ2VuY2UgcmVjb3ZlcnkuXG4gKlxuICogVGhlIHJlcG9ydGVyIHN0YXlzIGRvcm1hbnQgdW50aWwgcmVwbGF5IHJlYWNoZXMgYSB2YWxpZCBkZXRlcm1pbmlzdGljXG4gKiBib3VuZGFyeS4gVGhlIGZpcnN0IHN1YnNlcXVlbnQgZXZlbnQgd3JpdGUgY2FycmllcyB0aGUgdGVsZW1ldHJ5IGZvciBmcmVlLlxuICogQSByZWNvdmVyZWQgaW52b2NhdGlvbiB3aXRoIG5vIG5hdHVyYWwgZXZlbnQgd3JpdGUgaW50ZW50aW9uYWxseSByZXBvcnRzXG4gKiBub3RoaW5nLlxuICovXG5leHBvcnQgY2xhc3MgUmVwbGF5UmVjb3ZlcnlSZXBvcnRlciB7XG4gIHByaXZhdGUgYWN0aXZlID0gZmFsc2U7XG4gIHByaXZhdGUgcmVwb3J0ZWQgPSBmYWxzZTtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IGRpdmVyZ2VuY2VDb3VudDogbnVtYmVyKSB7fVxuXG4gIC8qKlxuICAgKiBBIHJlcG9ydGVyIGZvciBhbiBpbnZvY2F0aW9uIHRoYXQgbmV2ZXIgZGl2ZXJnZWQ6IGBhY3RpdmF0ZSgpYCBjYW5ub3QgYXJtXG4gICAqIGl0LCBzbyBgd2l0aEV2ZW50Q3JlYXRlYCBhbHdheXMgcGFzc2VzIHdyaXRlcyB0aHJvdWdoIHVudG91Y2hlZC4gTGV0cyBldmVyeVxuICAgKiBjYWxsIHNpdGUgaG9sZCBhIG5vbi1vcHRpb25hbCByZXBvcnRlciBpbnN0ZWFkIG9mIGJyYW5jaGluZyBvbiBvbmUuXG4gICAqXG4gICAqIFN0cnVjdHVyYWxseSBpbmVydCByYXRoZXIgdGhhbiBhIHplcm8tY291bnQgcmVwb3J0ZXIgb24gcHVycG9zZSDigJQgdGhlXG4gICAqIHNlcnZlciByZWplY3RzIGEgY291bnQgYmVsb3cgMSBhcyBvdXQtb2YtcmFuZ2UgYW5kIGVtaXRzIGFcbiAgICogYHRlbGVtZXRyeV9yZWplY3RlZGAgbWV0cmljLCBzbyBhIHJlcG9ydGVyIHRoYXQgKmNhbiogcmVwb3J0IHplcm8gaXMgYVxuICAgKiBsaXZlIGZvb3RndW4uXG4gICAqL1xuICBzdGF0aWMgaW5lcnQoKTogUmVwbGF5UmVjb3ZlcnlSZXBvcnRlciB7XG4gICAgcmV0dXJuIG5ldyBJbmVydFJlcGxheVJlY292ZXJ5UmVwb3J0ZXIoKTtcbiAgfVxuXG4gIGFjdGl2YXRlKCk6IHZvaWQge1xuICAgIHRoaXMuYWN0aXZlID0gdHJ1ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBEZWNvcmF0ZSBvbmUgbmF0dXJhbCBldmVudCB3cml0ZSB3aXRoIHJlY292ZXJ5IHRlbGVtZXRyeS5cbiAgICpcbiAgICogVGhlIGNsYWltIGlzIHRha2VuIHN5bmNocm9ub3VzbHksIHNvIGNvbmN1cnJlbnQgc3VzcGVuc2lvbiB3cml0ZXMgY2Fubm90XG4gICAqIGFsbCBjYXJyeSB0aGUgc2FtZSBjb3VudC4gQSBmYWlsZWQgcmVxdWVzdCByZWxlYXNlcyBpdCBmb3IgYSBsYXRlciBuYXR1cmFsXG4gICAqIHdyaXRlIGluIHRoZSBzYW1lIGludm9jYXRpb24uXG4gICAqL1xuICBhc3luYyB3aXRoRXZlbnRDcmVhdGU8VD4oXG4gICAgcGFyYW1zOiBDcmVhdGVFdmVudFBhcmFtcyB8IHVuZGVmaW5lZCxcbiAgICBjcmVhdGU6IChwYXJhbXM6IENyZWF0ZUV2ZW50UGFyYW1zIHwgdW5kZWZpbmVkKSA9PiBQcm9taXNlPFQ+XG4gICk6IFByb21pc2U8VD4ge1xuICAgIGlmICghdGhpcy5hY3RpdmUgfHwgdGhpcy5yZXBvcnRlZCkgcmV0dXJuIGNyZWF0ZShwYXJhbXMpO1xuXG4gICAgdGhpcy5yZXBvcnRlZCA9IHRydWU7XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBhd2FpdCBjcmVhdGUoe1xuICAgICAgICAuLi5wYXJhbXMsXG4gICAgICAgIHJlcGxheURpdmVyZ2VuY2VDb3VudDogdGhpcy5kaXZlcmdlbmNlQ291bnQsXG4gICAgICB9KTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgdGhpcy5yZXBvcnRlZCA9IGZhbHNlO1xuICAgICAgdGhyb3cgZXJyb3I7XG4gICAgfVxuICB9XG59XG5cbi8qKiBTZWUge0BsaW5rIFJlcGxheVJlY292ZXJ5UmVwb3J0ZXIuaW5lcnR9LiAqL1xuY2xhc3MgSW5lcnRSZXBsYXlSZWNvdmVyeVJlcG9ydGVyIGV4dGVuZHMgUmVwbGF5UmVjb3ZlcnlSZXBvcnRlciB7XG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIHN1cGVyKDApO1xuICB9XG5cbiAgb3ZlcnJpZGUgYWN0aXZhdGUoKTogdm9pZCB7XG4gICAgLy8gRGVsaWJlcmF0ZWx5IGVtcHR5OiBub3RoaW5nIHRvIHJlcG9ydCwgc28gbm90aGluZyBjYW4gYXJtLlxuICB9XG59XG4iXX0=
|
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import { type Hook } from '@workflow/world';
|
|
2
|
-
import { type
|
|
2
|
+
import { type PayloadKey } from '../serialization.js';
|
|
3
3
|
/**
|
|
4
4
|
* Get the hook by token to find the associated workflow run,
|
|
5
5
|
* and hydrate the `metadata` property if it was set from within
|
|
6
6
|
* the workflow run.
|
|
7
7
|
*
|
|
8
|
+
* A Hook kept by minimum retention remains available here after its run ends,
|
|
9
|
+
* but cannot be resumed.
|
|
10
|
+
*
|
|
8
11
|
* @param token - The unique token identifying the hook
|
|
9
12
|
*/
|
|
10
13
|
export declare function getHookByToken(token: string): Promise<Hook>;
|
|
14
|
+
/**
|
|
15
|
+
* The result of {@link resumeHook}: a {@link Hook} augmented with an optional
|
|
16
|
+
* resilience signal.
|
|
17
|
+
*
|
|
18
|
+
* On the parallel fast path, `resumeHook()` writes the `hook_received` event and
|
|
19
|
+
* dispatches the workflow queue message concurrently. When the direct event
|
|
20
|
+
* write fails *transiently* — a 429/5xx, a transport error, or an expected
|
|
21
|
+
* `(runId, resumeId)` conflict with its own re-ensuring consumer — but the queue
|
|
22
|
+
* dispatch succeeds, the resume is still guaranteed: the queue consumer
|
|
23
|
+
* idempotently materializes the `hook_received` event from the payload carried
|
|
24
|
+
* on the message before replay. In that recovered case the returned hook carries
|
|
25
|
+
* `resilientResume: true`.
|
|
26
|
+
*
|
|
27
|
+
* On the happy path (the direct write landed) and on the sequential fallback
|
|
28
|
+
* path, the flag is absent (`undefined`). Callers that don't care about the
|
|
29
|
+
* distinction can treat the result as a plain {@link Hook}.
|
|
30
|
+
*/
|
|
31
|
+
export type ResumedHook = Hook & {
|
|
32
|
+
resilientResume?: boolean;
|
|
33
|
+
};
|
|
11
34
|
/**
|
|
12
35
|
* Resumes a workflow run by sending a payload to a hook identified by its token.
|
|
13
36
|
*
|
|
@@ -16,8 +39,8 @@ export declare function getHookByToken(token: string): Promise<Hook>;
|
|
|
16
39
|
*
|
|
17
40
|
* @param tokenOrHook - The unique token identifying the hook, or the hook object itself
|
|
18
41
|
* @param payload - The data payload to send to the hook
|
|
19
|
-
* @returns Promise resolving to the
|
|
20
|
-
* @throws
|
|
42
|
+
* @returns Promise resolving to the {@link ResumedHook}
|
|
43
|
+
* @throws {HookNotFoundError} If the Hook does not exist or its run has ended
|
|
21
44
|
*
|
|
22
45
|
* @example
|
|
23
46
|
*
|
|
@@ -37,7 +60,7 @@ export declare function getHookByToken(token: string): Promise<Hook>;
|
|
|
37
60
|
* }
|
|
38
61
|
* ```
|
|
39
62
|
*/
|
|
40
|
-
export declare function resumeHook<T = any>(tokenOrHook: string | Hook, payload: T, encryptionKeyOverride?:
|
|
63
|
+
export declare function resumeHook<T = any>(tokenOrHook: string | Hook, payload: T, encryptionKeyOverride?: PayloadKey): Promise<ResumedHook>;
|
|
41
64
|
/**
|
|
42
65
|
* Resumes a webhook by sending a {@link https://developer.mozilla.org/en-US/docs/Web/API/Request | Request}
|
|
43
66
|
* object to a hook identified by its token.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume-hook.d.ts","sourceRoot":"","sources":["../../src/runtime/resume-hook.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"resume-hook.d.ts","sourceRoot":"","sources":["../../src/runtime/resume-hook.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,IAAI,EAUV,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,qBAAqB,CAAC;AAwJ7B;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,UAAU,CAAC,CAAC,GAAG,GAAG,EACtC,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,OAAO,EAAE,CAAC,EACV,qBAAqB,CAAC,EAAE,UAAU,GACjC,OAAO,CAAC,WAAW,CAAC,CAUtB;AA8ZD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,QAAQ,CAAC,CA+DnB"}
|