@blokjs/runner 0.2.2 → 0.6.0

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 (213) hide show
  1. package/dist/Blok.js +32 -3
  2. package/dist/Blok.js.map +1 -1
  3. package/dist/Configuration.d.ts +59 -5
  4. package/dist/Configuration.js +366 -96
  5. package/dist/Configuration.js.map +1 -1
  6. package/dist/ForEachNode.d.ts +59 -0
  7. package/dist/ForEachNode.js +522 -0
  8. package/dist/ForEachNode.js.map +1 -0
  9. package/dist/LoopMaxIterationsError.d.ts +11 -0
  10. package/dist/LoopMaxIterationsError.js +18 -0
  11. package/dist/LoopMaxIterationsError.js.map +1 -0
  12. package/dist/LoopNode.d.ts +36 -0
  13. package/dist/LoopNode.js +182 -0
  14. package/dist/LoopNode.js.map +1 -0
  15. package/dist/PayloadTooLargeError.d.ts +19 -0
  16. package/dist/PayloadTooLargeError.js +29 -0
  17. package/dist/PayloadTooLargeError.js.map +1 -0
  18. package/dist/RunCancelledError.d.ts +17 -0
  19. package/dist/RunCancelledError.js +25 -0
  20. package/dist/RunCancelledError.js.map +1 -0
  21. package/dist/Runner.d.ts +11 -1
  22. package/dist/Runner.js +9 -2
  23. package/dist/Runner.js.map +1 -1
  24. package/dist/RunnerSteps.js +648 -44
  25. package/dist/RunnerSteps.js.map +1 -1
  26. package/dist/RuntimeAdapterNode.d.ts +2 -1
  27. package/dist/RuntimeAdapterNode.js +2 -2
  28. package/dist/RuntimeAdapterNode.js.map +1 -1
  29. package/dist/RuntimeRegistry.d.ts +23 -2
  30. package/dist/RuntimeRegistry.js +31 -2
  31. package/dist/RuntimeRegistry.js.map +1 -1
  32. package/dist/SubworkflowNode.d.ts +181 -0
  33. package/dist/SubworkflowNode.js +479 -0
  34. package/dist/SubworkflowNode.js.map +1 -0
  35. package/dist/SwitchNode.d.ts +37 -0
  36. package/dist/SwitchNode.js +153 -0
  37. package/dist/SwitchNode.js.map +1 -0
  38. package/dist/TriggerBase.d.ts +178 -0
  39. package/dist/TriggerBase.js +1032 -5
  40. package/dist/TriggerBase.js.map +1 -1
  41. package/dist/TryCatchNode.d.ts +32 -0
  42. package/dist/TryCatchNode.js +207 -0
  43. package/dist/TryCatchNode.js.map +1 -0
  44. package/dist/WaitDispatchRequest.d.ts +38 -0
  45. package/dist/WaitDispatchRequest.js +13 -0
  46. package/dist/WaitDispatchRequest.js.map +1 -0
  47. package/dist/WaitNode.d.ts +23 -0
  48. package/dist/WaitNode.js +26 -0
  49. package/dist/WaitNode.js.map +1 -0
  50. package/dist/adapters/grpc/GrpcCodec.js +2 -2
  51. package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +6 -4
  52. package/dist/adapters/grpc/GrpcRuntimeAdapter.js +6 -4
  53. package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -1
  54. package/dist/adapters/grpc/types.d.ts +7 -5
  55. package/dist/adapters/grpc/types.js.map +1 -1
  56. package/dist/adapters/transport.d.ts +12 -41
  57. package/dist/adapters/transport.js +21 -70
  58. package/dist/adapters/transport.js.map +1 -1
  59. package/dist/cache/NodeResultCache.js +7 -0
  60. package/dist/cache/NodeResultCache.js.map +1 -1
  61. package/dist/concurrency/ConcurrencyBackend.d.ts +61 -0
  62. package/dist/concurrency/ConcurrencyBackend.js +20 -0
  63. package/dist/concurrency/ConcurrencyBackend.js.map +1 -0
  64. package/dist/concurrency/ConcurrencyLimitError.d.ts +37 -0
  65. package/dist/concurrency/ConcurrencyLimitError.js +16 -0
  66. package/dist/concurrency/ConcurrencyLimitError.js.map +1 -0
  67. package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +64 -0
  68. package/dist/concurrency/NatsKvConcurrencyBackend.js +310 -0
  69. package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -0
  70. package/dist/concurrency/QueueExpiredError.d.ts +40 -0
  71. package/dist/concurrency/QueueExpiredError.js +15 -0
  72. package/dist/concurrency/QueueExpiredError.js.map +1 -0
  73. package/dist/concurrency/RedisConcurrencyBackend.d.ts +64 -0
  74. package/dist/concurrency/RedisConcurrencyBackend.js +374 -0
  75. package/dist/concurrency/RedisConcurrencyBackend.js.map +1 -0
  76. package/dist/concurrency/createConcurrencyBackend.d.ts +24 -0
  77. package/dist/concurrency/createConcurrencyBackend.js +38 -0
  78. package/dist/concurrency/createConcurrencyBackend.js.map +1 -0
  79. package/dist/concurrency/readConcurrencyConfig.d.ts +60 -0
  80. package/dist/concurrency/readConcurrencyConfig.js +60 -0
  81. package/dist/concurrency/readConcurrencyConfig.js.map +1 -0
  82. package/dist/defineNode.d.ts +8 -0
  83. package/dist/defineNode.js +25 -5
  84. package/dist/defineNode.js.map +1 -1
  85. package/dist/graphql/GraphQLSchemaGenerator.js +1 -1
  86. package/dist/graphql/GraphQLSchemaGenerator.js.map +1 -1
  87. package/dist/idempotency/resolveIdempotencyKey.d.ts +20 -0
  88. package/dist/idempotency/resolveIdempotencyKey.js +37 -0
  89. package/dist/idempotency/resolveIdempotencyKey.js.map +1 -0
  90. package/dist/index.d.ts +30 -6
  91. package/dist/index.js +55 -6
  92. package/dist/index.js.map +1 -1
  93. package/dist/marketplace/RuntimeCatalog.d.ts +6 -0
  94. package/dist/marketplace/RuntimeCatalog.js.map +1 -1
  95. package/dist/marketplace/RuntimeDiscovery.d.ts +2 -2
  96. package/dist/marketplace/RuntimeDiscovery.js +18 -6
  97. package/dist/marketplace/RuntimeDiscovery.js.map +1 -1
  98. package/dist/monitoring/ConcurrencyMetrics.d.ts +82 -0
  99. package/dist/monitoring/ConcurrencyMetrics.js +139 -0
  100. package/dist/monitoring/ConcurrencyMetrics.js.map +1 -0
  101. package/dist/monitoring/ForEachWaitMetrics.d.ts +22 -0
  102. package/dist/monitoring/ForEachWaitMetrics.js +36 -0
  103. package/dist/monitoring/ForEachWaitMetrics.js.map +1 -0
  104. package/dist/monitoring/JanitorMetrics.d.ts +27 -0
  105. package/dist/monitoring/JanitorMetrics.js +48 -0
  106. package/dist/monitoring/JanitorMetrics.js.map +1 -0
  107. package/dist/openapi/OpenAPIGenerator.js +7 -2
  108. package/dist/openapi/OpenAPIGenerator.js.map +1 -1
  109. package/dist/runtime/PrimitiveStack.d.ts +64 -0
  110. package/dist/runtime/PrimitiveStack.js +92 -0
  111. package/dist/runtime/PrimitiveStack.js.map +1 -0
  112. package/dist/scheduling/DebounceBackend.d.ts +108 -0
  113. package/dist/scheduling/DebounceBackend.js +23 -0
  114. package/dist/scheduling/DebounceBackend.js.map +1 -0
  115. package/dist/scheduling/DebounceCoordinator.d.ts +141 -0
  116. package/dist/scheduling/DebounceCoordinator.js +362 -0
  117. package/dist/scheduling/DebounceCoordinator.js.map +1 -0
  118. package/dist/scheduling/DeferredDispatchSignal.d.ts +50 -0
  119. package/dist/scheduling/DeferredDispatchSignal.js +14 -0
  120. package/dist/scheduling/DeferredDispatchSignal.js.map +1 -0
  121. package/dist/scheduling/DeferredRunScheduler.d.ts +96 -0
  122. package/dist/scheduling/DeferredRunScheduler.js +256 -0
  123. package/dist/scheduling/DeferredRunScheduler.js.map +1 -0
  124. package/dist/scheduling/NatsKvDebounceBackend.d.ts +53 -0
  125. package/dist/scheduling/NatsKvDebounceBackend.js +334 -0
  126. package/dist/scheduling/NatsKvDebounceBackend.js.map +1 -0
  127. package/dist/scheduling/RedisDebounceBackend.d.ts +49 -0
  128. package/dist/scheduling/RedisDebounceBackend.js +356 -0
  129. package/dist/scheduling/RedisDebounceBackend.js.map +1 -0
  130. package/dist/scheduling/createDebounceBackend.d.ts +25 -0
  131. package/dist/scheduling/createDebounceBackend.js +39 -0
  132. package/dist/scheduling/createDebounceBackend.js.map +1 -0
  133. package/dist/scheduling/readSchedulingConfig.d.ts +24 -0
  134. package/dist/scheduling/readSchedulingConfig.js +52 -0
  135. package/dist/scheduling/readSchedulingConfig.js.map +1 -0
  136. package/dist/security/AuditLogger.js +1 -1
  137. package/dist/security/AuditLogger.js.map +1 -1
  138. package/dist/security/AuthMiddleware.d.ts +19 -20
  139. package/dist/security/AuthMiddleware.js +35 -20
  140. package/dist/security/AuthMiddleware.js.map +1 -1
  141. package/dist/security/OAuthProvider.js +2 -2
  142. package/dist/security/OAuthProvider.js.map +1 -1
  143. package/dist/security/SecretManager.js +14 -13
  144. package/dist/security/SecretManager.js.map +1 -1
  145. package/dist/security/index.d.ts +3 -1
  146. package/dist/security/index.js +3 -1
  147. package/dist/security/index.js.map +1 -1
  148. package/dist/testing/TestHarness.d.ts +27 -12
  149. package/dist/testing/TestHarness.js +19 -3
  150. package/dist/testing/TestHarness.js.map +1 -1
  151. package/dist/testing/WorkflowTestRunner.js +0 -7
  152. package/dist/testing/WorkflowTestRunner.js.map +1 -1
  153. package/dist/timeouts/StepTimeoutError.d.ts +22 -0
  154. package/dist/timeouts/StepTimeoutError.js +31 -0
  155. package/dist/timeouts/StepTimeoutError.js.map +1 -0
  156. package/dist/tracing/InMemoryRunStore.d.ts +41 -1
  157. package/dist/tracing/InMemoryRunStore.js +239 -0
  158. package/dist/tracing/InMemoryRunStore.js.map +1 -1
  159. package/dist/tracing/Janitor.d.ts +70 -0
  160. package/dist/tracing/Janitor.js +150 -0
  161. package/dist/tracing/Janitor.js.map +1 -0
  162. package/dist/tracing/PostgresRunStore.d.ts +57 -1
  163. package/dist/tracing/PostgresRunStore.js +711 -6
  164. package/dist/tracing/PostgresRunStore.js.map +1 -1
  165. package/dist/tracing/RoutingDiagnostics.d.ts +55 -0
  166. package/dist/tracing/RoutingDiagnostics.js +50 -0
  167. package/dist/tracing/RoutingDiagnostics.js.map +1 -0
  168. package/dist/tracing/RunStore.d.ts +181 -1
  169. package/dist/tracing/RunTracker.d.ts +244 -9
  170. package/dist/tracing/RunTracker.js +594 -1
  171. package/dist/tracing/RunTracker.js.map +1 -1
  172. package/dist/tracing/SqliteRunStore.d.ts +79 -2
  173. package/dist/tracing/SqliteRunStore.js +775 -16
  174. package/dist/tracing/SqliteRunStore.js.map +1 -1
  175. package/dist/tracing/TraceRouter.d.ts +20 -2
  176. package/dist/tracing/TraceRouter.js +612 -6
  177. package/dist/tracing/TraceRouter.js.map +1 -1
  178. package/dist/tracing/createStore.js +14 -3
  179. package/dist/tracing/createStore.js.map +1 -1
  180. package/dist/tracing/metadataFilter.d.ts +63 -0
  181. package/dist/tracing/metadataFilter.js +224 -0
  182. package/dist/tracing/metadataFilter.js.map +1 -0
  183. package/dist/tracing/sanitize.d.ts +11 -0
  184. package/dist/tracing/sanitize.js +29 -0
  185. package/dist/tracing/sanitize.js.map +1 -1
  186. package/dist/tracing/types.d.ts +672 -2
  187. package/dist/utils/createChildContext.d.ts +32 -0
  188. package/dist/utils/createChildContext.js +113 -0
  189. package/dist/utils/createChildContext.js.map +1 -0
  190. package/dist/utils/envAllowlist.d.ts +35 -0
  191. package/dist/utils/envAllowlist.js +113 -0
  192. package/dist/utils/envAllowlist.js.map +1 -0
  193. package/dist/version/RuntimeVersionValidator.d.ts +38 -0
  194. package/dist/version/RuntimeVersionValidator.js +121 -0
  195. package/dist/version/RuntimeVersionValidator.js.map +1 -0
  196. package/dist/visualization/WorkflowVisualizer.js +4 -4
  197. package/dist/visualization/WorkflowVisualizer.js.map +1 -1
  198. package/dist/workflow/PersistenceHelper.d.ts +18 -10
  199. package/dist/workflow/PersistenceHelper.js +35 -9
  200. package/dist/workflow/PersistenceHelper.js.map +1 -1
  201. package/dist/workflow/WorkflowNormalizer.d.ts +48 -42
  202. package/dist/workflow/WorkflowNormalizer.js +650 -18
  203. package/dist/workflow/WorkflowNormalizer.js.map +1 -1
  204. package/dist/workflow/WorkflowRegistry.d.ts +186 -0
  205. package/dist/workflow/WorkflowRegistry.js +202 -0
  206. package/dist/workflow/WorkflowRegistry.js.map +1 -0
  207. package/dist/workflow/sampleBody.d.ts +54 -0
  208. package/dist/workflow/sampleBody.js +320 -0
  209. package/dist/workflow/sampleBody.js.map +1 -0
  210. package/package.json +3 -8
  211. package/dist/adapters/HttpRuntimeAdapter.d.ts +0 -79
  212. package/dist/adapters/HttpRuntimeAdapter.js +0 -233
  213. package/dist/adapters/HttpRuntimeAdapter.js.map +0 -1
@@ -0,0 +1,153 @@
1
+ /**
2
+ * SwitchNode — v0.5 primitive. N-way branch keyed on a value. First
3
+ * matching case wins; an optional `default` block runs when no case
4
+ * matches.
5
+ *
6
+ * The runtime config is read from `ctx.config[this.name]`:
7
+ * {
8
+ * on: unknown, // resolved by mapper
9
+ * cases: [{ when, steps: NodeBase[] }], // pre-resolved by Configuration
10
+ * default?: NodeBase[], // pre-resolved
11
+ * }
12
+ *
13
+ * Match semantics:
14
+ * - `when` is a literal scalar (string/number/boolean) → match if `on === when`.
15
+ * - `when` is an array → match if `array.includes(on)` (group related cases).
16
+ *
17
+ * Mutations to state inside the matched case's sub-pipeline DO carry
18
+ * forward to subsequent top-level steps — switch is a passthrough flow,
19
+ * NOT an isolation boundary like forEach. The matched case's last step
20
+ * output becomes the switch step's `response.data`.
21
+ *
22
+ * If no case matches and there's no default, the step is a no-op
23
+ * (success, data: null).
24
+ *
25
+ * v0.6 Phase 4 — switch + wait support. The cursor schema
26
+ * (`SwitchIterationContext`) records `caseIndex` (or -1 for `default`)
27
+ * plus `innerStepIndex` within that arm. On re-entry after a wait,
28
+ * SwitchNode reads its cursor from the rehydrated map (keyed by its
29
+ * own NodeRun id), walks back into the matched arm at the right step,
30
+ * and ignores the `on` value entirely — we already committed to a
31
+ * specific arm in the first pass.
32
+ */
33
+ import RunnerNode from "./RunnerNode";
34
+ import { consumeRehydratedCursor, popPrimitiveFrame, pushPrimitiveFrame, readRehydratedCursor, } from "./runtime/PrimitiveStack";
35
+ import { applyStepOutput } from "./workflow/PersistenceHelper";
36
+ /** Sentinel `caseIndex` value meaning "the `default` arm matched". */
37
+ const DEFAULT_ARM = -1;
38
+ function caseMatches(when, on) {
39
+ if (Array.isArray(when)) {
40
+ return when.some((w) => w === on);
41
+ }
42
+ return when === on;
43
+ }
44
+ export class SwitchNode extends RunnerNode {
45
+ async run(ctx) {
46
+ this.contentType = "application/json";
47
+ const response = { success: true, data: null, error: null };
48
+ const opts = (ctx.config?.[this.name] ?? {});
49
+ const on = opts.on;
50
+ const cases = Array.isArray(opts.cases) ? opts.cases : [];
51
+ const defaultSteps = Array.isArray(opts.default) ? opts.default : undefined;
52
+ // v0.6 Phase 4 — resume cursor lookup. On re-entry from a wait
53
+ // fired inside a switch case, the persisted NodeRun's
54
+ // iteration_context carries the matched arm + inner step index.
55
+ const ctxAny = ctx;
56
+ const myNodeRunId = ctxAny._traceNodeId;
57
+ // Phase 4 — lookup by step NAME (stable across re-entries).
58
+ const resumeRaw = readRehydratedCursor(ctx, this.name);
59
+ const resume = resumeRaw && resumeRaw.mode === "switch" ? resumeRaw : undefined;
60
+ if (resume) {
61
+ consumeRehydratedCursor(ctx, this.name);
62
+ }
63
+ // Case selection. On resume, skip re-evaluating the `when`
64
+ // expressions — we've already committed to a specific arm and
65
+ // `on` could have changed mid-flight (different ctx state after
66
+ // pre-wait mutations). Walk straight back into the cached arm.
67
+ let selected;
68
+ let selectedCaseIndex = DEFAULT_ARM;
69
+ if (resume !== undefined) {
70
+ if (resume.caseIndex === DEFAULT_ARM) {
71
+ selected = defaultSteps;
72
+ }
73
+ else if (resume.caseIndex >= 0 && resume.caseIndex < cases.length) {
74
+ selected = cases[resume.caseIndex].steps;
75
+ selectedCaseIndex = resume.caseIndex;
76
+ }
77
+ // Cursor pointed at an arm that no longer exists (case removed
78
+ // between deploys, etc.). Fall through to fresh first-match
79
+ // behaviour — better than crashing the resume.
80
+ }
81
+ if (selected === undefined && resume === undefined) {
82
+ for (let idx = 0; idx < cases.length; idx++) {
83
+ if (caseMatches(cases[idx].when, on)) {
84
+ selected = cases[idx].steps;
85
+ selectedCaseIndex = idx;
86
+ break;
87
+ }
88
+ }
89
+ if (selected === undefined) {
90
+ selected = defaultSteps;
91
+ if (selected !== undefined)
92
+ selectedCaseIndex = DEFAULT_ARM;
93
+ }
94
+ }
95
+ if (selected === undefined || selected.length === 0) {
96
+ // No matching case + no default → no-op success. State
97
+ // entry is `null` so downstream `$.state[<id>]` reads return
98
+ // null (not undefined) — keeps the persistence model uniform.
99
+ applyStepOutput(ctx, this, { data: null });
100
+ // Preserve the previous step's ctx.response so the NEXT
101
+ // top-level step doesn't dereference null when RunnerSteps
102
+ // assigns `ctx.response.contentType`.
103
+ response.data = ctx.response;
104
+ return response;
105
+ }
106
+ // v0.6 Phase 4 — push a primitive frame so a nested wait fired
107
+ // inside this case's sub-pipeline persists the cursor on
108
+ // THIS switch's NodeRun. `innerStepIndex` is updated at each
109
+ // step boundary inside the case body by RunnerSteps; we just
110
+ // keep `caseIndex` pinned for the duration of this run.
111
+ const initialCursor = {
112
+ mode: "switch",
113
+ caseIndex: selectedCaseIndex,
114
+ innerStepIndex: resume?.innerStepIndex ?? 0,
115
+ completedResults: [],
116
+ };
117
+ const frame = myNodeRunId
118
+ ? { nodeRunId: myNodeRunId, cursor: initialCursor }
119
+ : undefined;
120
+ if (frame)
121
+ pushPrimitiveFrame(ctx, frame);
122
+ try {
123
+ // Lazy import — same circular-dep guard ForEach/Loop use.
124
+ const { default: Runner } = await import("./Runner");
125
+ const runner = new Runner(selected);
126
+ // On resume, pass the inner-step resume index — INCLUDING
127
+ // the index-0 case where the wait is the very first step
128
+ // of the matched arm. The deep runSteps' wait re-entry
129
+ // detection uses `innerResumeIndex !== undefined` as the
130
+ // "this primitive resumed here" signal (Phase 4 fix).
131
+ if (resume) {
132
+ ctxAny._blokInnerResumeIndex = resume.innerStepIndex;
133
+ }
134
+ // `deep: true` so the inner runSteps doesn't inherit the
135
+ // outer run's `lastCompletedStepIndex` cursor (PR 4 wait/
136
+ // resume logic).
137
+ await runner.run(ctx, { deep: true, stepName: this.name });
138
+ // Switch is a passthrough — the matched case ran on the
139
+ // parent ctx, so state mutations are already visible to
140
+ // downstream steps. The switch step's own data is the last
141
+ // inner step's response.
142
+ const data = ctx.response;
143
+ response.data = data;
144
+ applyStepOutput(ctx, this, { data });
145
+ return response;
146
+ }
147
+ finally {
148
+ if (frame)
149
+ popPrimitiveFrame(ctx);
150
+ }
151
+ }
152
+ }
153
+ //# sourceMappingURL=SwitchNode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwitchNode.js","sourceRoot":"","sources":["../src/SwitchNode.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,22 @@ import { PrometheusMetricsBridge } from "./monitoring/PrometheusMetricsBridge";
11
11
  import { RateLimiter } from "./monitoring/RateLimiter";
12
12
  import type { RateLimitConfig, RateLimitResult } from "./monitoring/RateLimiter";
13
13
  import { TriggerMetricsCollector } from "./monitoring/TriggerMetricsCollector";
14
+ import type GlobalOptions from "./types/GlobalOptions";
14
15
  import type TriggerResponse from "./types/TriggerResponse";
16
+ /**
17
+ * Tier 2 quick-wins follow-up · structural logger interface used by
18
+ * `installCrashHandlers` and `recoverOrphanedRuns`. Both `error` and
19
+ * `log` are optional so callers can pass a `DefaultLogger`, the Hono
20
+ * `console` shim, a custom logger, or omit it entirely.
21
+ *
22
+ * Single-arg calls (one pre-formatted string) are intentional — Node's
23
+ * `process.on("uncaughtException")` handlers can't await, and the
24
+ * `DefaultLogger` interface only takes `(message: string)`.
25
+ */
26
+ interface CrashAutoflipLogger {
27
+ error?: (message: string) => void;
28
+ log?: (message: string) => void;
29
+ }
15
30
  export default abstract class TriggerBase extends Trigger {
16
31
  configuration: Configuration;
17
32
  /** Health check instance for this trigger */
@@ -32,6 +47,143 @@ export default abstract class TriggerBase extends Trigger {
32
47
  abstract listen(): Promise<number>;
33
48
  getConfiguration(): Configuration;
34
49
  getRunner(): Runner;
50
+ /**
51
+ * Tier 2 #5+#7 follow-up — durable scheduler hook.
52
+ *
53
+ * When a trigger supports re-firing deferred dispatches across process
54
+ * restarts, it overrides this method to extract a JSON-serializable
55
+ * subset of `ctx` sufficient for `restoreDispatch(payload)` (defined
56
+ * by the trigger) to reconstruct an equivalent ctx and re-enter
57
+ * `dispatchDeferred`.
58
+ *
59
+ * Returns `null` (default) when the trigger does NOT support
60
+ * cross-restart durability — the scheduler then runs purely in-memory
61
+ * for that trigger (existing pre-follow-up behaviour).
62
+ *
63
+ * Override in `HttpTrigger` to return `{method, path, headers, body,
64
+ * params, query, workflowPath}` (with sensitive header keys stripped).
65
+ * Worker triggers don't override — broker handles delay durability.
66
+ */
67
+ protected extractDispatchPayload(_ctx: Context): unknown | null;
68
+ /**
69
+ * Returns the trigger type string used to tag persisted scheduled
70
+ * dispatch rows (`scheduled_dispatches.trigger_type`). Mirrors the
71
+ * convention from `tracker.startRun({triggerType})`. Override when
72
+ * the class name doesn't naturally produce the right tag.
73
+ */
74
+ protected getTriggerType(): string;
75
+ /**
76
+ * v0.6 — apply the merged middleware chain (process-global → workflow-level
77
+ * → trigger-level) to `ctx` before the main workflow body runs.
78
+ *
79
+ * Trigger-level names are read from
80
+ * `this.configuration.trigger[<this.getTriggerType()>].middleware`, so
81
+ * HttpTrigger reads `trigger.http.middleware`, WorkerTrigger reads
82
+ * `trigger.worker.middleware`, CronTrigger reads `trigger.cron.middleware`.
83
+ *
84
+ * Pre-v0.6 the merge code lived inline in `HttpTrigger.run` and worker
85
+ * + cron triggers silently skipped middleware. Centralising it on
86
+ * TriggerBase gives all three trigger families uniform semantics.
87
+ *
88
+ * Resolution order outer→inner:
89
+ * process-global → workflow-level → trigger-level → main workflow body.
90
+ *
91
+ * State mutations from earlier middleware (e.g. `ctx.state.identity`
92
+ * from auth-check) carry forward to later middleware and the main
93
+ * workflow because they share the same ctx. Middleware authors
94
+ * short-circuit via `@blokjs/throw` — the throw propagates to the
95
+ * caller's outer catch, and the main workflow does NOT run.
96
+ */
97
+ protected applyMiddlewareChain(ctx: Context, nodeMap: GlobalOptions): Promise<void>;
98
+ /**
99
+ * v0.6 — dispatch a chain of middleware workflows on the same parent
100
+ * ctx. Each entry in `names` is the `name:` of a workflow registered
101
+ * with `middleware: true`. For each:
102
+ *
103
+ * - Materialise a fresh `Configuration` for the middleware (resolves
104
+ * its inner steps + nodes against `nodeMap` so `@blokjs/throw`
105
+ * etc. resolve from `@blokjs/helpers`).
106
+ * - Save the parent ctx.config; swap in the middleware's resolved
107
+ * `mwConfig.nodes` so the blueprint mapper finds the middleware's
108
+ * step inputs.
109
+ * - Run via `new Runner(...).run(ctx, { deep: true })` — `deep: true`
110
+ * prevents the inner runSteps from inheriting the outer run's
111
+ * `lastCompletedStepIndex` cursor (PR 4 wait/resume hazard).
112
+ * - Restore parent ctx.config in `finally`.
113
+ *
114
+ * Missing middleware (name not registered) is a configuration error
115
+ * — throws a clear message naming the unknown middleware. Authors
116
+ * typically use `@blokjs/throw` inside middleware with a `code:` to
117
+ * produce structured HTTP responses (e.g. 401) — those throws
118
+ * propagate to the outer catch in the calling trigger.
119
+ *
120
+ * Pre-v0.6 this lived as a private method on `HttpTrigger`. Lifted
121
+ * here so worker + cron triggers can reuse it without duplication.
122
+ */
123
+ protected runMiddlewareChain(ctx: Context, names: readonly string[], nodeMap: GlobalOptions): Promise<void>;
124
+ /** Flag — set true after `installCrashHandlers` has run once in this process. */
125
+ private static crashHandlersInstalled;
126
+ /**
127
+ * Tier 2 quick-wins follow-up — install process-level handlers for
128
+ * `uncaughtException` and `unhandledRejection`. When fired, flip
129
+ * every in-flight `running` run to `"crashed"` (with the captured
130
+ * error) BEFORE re-throwing / letting Node's default behavior take
131
+ * over. Idempotent — safe to call from every trigger's `listen()`;
132
+ * only the first call installs handlers.
133
+ *
134
+ * Kill-switch: `BLOK_CRASH_AUTOFLIP_DISABLED=1`.
135
+ *
136
+ * Why sync: `process.on("uncaughtException")` handlers can't await.
137
+ * `markAllRunningRunsAsCrashed` is sync (sqlite + in-memory writes
138
+ * complete before the handler returns).
139
+ */
140
+ static installCrashHandlers(logger?: CrashAutoflipLogger): void;
141
+ /** Test-only — reset the install flag so tests can re-install handlers. */
142
+ static resetCrashHandlersInstalled(): void;
143
+ /** Flag — set true after `installShutdownHandlers` has run once in this process. */
144
+ private static shutdownHandlersInstalled;
145
+ /**
146
+ * Install SIGTERM + SIGINT handlers that drain process resources
147
+ * cleanly before exit. Mirrors the `installCrashHandlers` pattern —
148
+ * idempotent + opt-out via `BLOK_GRACEFUL_SHUTDOWN_DISABLED=1`.
149
+ *
150
+ * Drain order:
151
+ * 1. Stop accepting new work — calls `trigger.stop()` if available
152
+ * (HttpTrigger drains in-flight requests + closes the server).
153
+ * 2. Stop the periodic janitor sweep so it doesn't fire mid-drain.
154
+ * 3. Cancel pending deferred dispatches in the in-memory scheduler.
155
+ * (Persisted rows in `scheduled_dispatches` survive — the next
156
+ * boot recovers them.)
157
+ * 4. Disconnect the cross-process concurrency backend (NATS KV)
158
+ * so locks held by this process release on the broker side.
159
+ * 5. `process.exit(0)`.
160
+ *
161
+ * Errors during drain are caught + logged; the process still exits
162
+ * (cleanup is best-effort; the operator wants a clean exit).
163
+ *
164
+ * Why this is a `static` method: shutdown handlers must be installed
165
+ * once per process, regardless of how many trigger subclasses
166
+ * coexist. Subclasses pass `this` so the handler can call their
167
+ * specific `stop()`.
168
+ */
169
+ static installShutdownHandlers(trigger: TriggerBase, logger?: CrashAutoflipLogger): void;
170
+ /** Test-only — reset the install flag so tests can re-install handlers. */
171
+ static resetShutdownHandlersInstalled(): void;
172
+ /**
173
+ * Tier 2 quick-wins follow-up — boot recovery for orphaned `running`
174
+ * runs. Scans the store for runs in `running` status whose
175
+ * `startedAt` is older than `thresholdMs` ago (default 2 minutes,
176
+ * override via `BLOK_ORPHAN_THRESHOLD_MS` env var). Flips each to
177
+ * `"crashed"` with `Error("Orphaned — process restarted before run completed")`.
178
+ *
179
+ * Catches the case where the previous process died via SIGKILL or
180
+ * OOM and the `installCrashHandlers` path never ran. Returns the
181
+ * count flipped for observability + tests.
182
+ *
183
+ * Idempotent — safe to call multiple times; runs are flipped to
184
+ * a terminal status so a second pass finds none.
185
+ */
186
+ static recoverOrphanedRuns(thresholdMs?: number, logger?: CrashAutoflipLogger): number;
35
187
  /**
36
188
  * Enable hot reload for this trigger. Only active in development
37
189
  * (NODE_ENV !== 'production') unless BLOK_HMR=true is explicitly set.
@@ -67,6 +219,31 @@ export default abstract class TriggerBase extends Trigger {
67
219
  */
68
220
  destroyHmr(): Promise<void>;
69
221
  run(ctx: Context): Promise<TriggerResponse>;
222
+ /**
223
+ * Tier 2 #5 + #7 — evaluate the scheduling gates and either return a
224
+ * `DeferredDispatchSignal` (the caller throws it) or null (the caller
225
+ * proceeds with immediate dispatch).
226
+ *
227
+ * Order: debounce → delay. They DON'T compose in a single PR (a
228
+ * trigger may use one or the other; both at once would be unusual).
229
+ * If both are configured, debounce takes precedence — the debounce
230
+ * coordinator handles its own scheduling (the `delay` field is
231
+ * effectively ignored on debounced triggers).
232
+ */
233
+ private maybeDeferRun;
234
+ /**
235
+ * Tier 2 #5 + #7 — re-enter the dispatch pipeline for a deferred run.
236
+ *
237
+ * Called by the `DeferredRunScheduler` timer (delay) or
238
+ * `DebounceCoordinator.onFire` (debounce trailing) when the wait
239
+ * window closes. Checks TTL, transitions the run to `running`, and
240
+ * re-enters `run(ctx)` with the `_blokDispatchReentry` flag so the
241
+ * scheduling gates are skipped on the second pass.
242
+ *
243
+ * The re-entered `run(ctx)` reuses the existing `traceRunId` (already
244
+ * stashed on `ctx._traceRunId` from the first pass).
245
+ */
246
+ protected dispatchDeferred(ctx: Context, traceRunId: string, expiresAt: number | undefined): Promise<void>;
70
247
  /**
71
248
  * Build a human-readable trigger summary for trace display.
72
249
  */
@@ -117,3 +294,4 @@ export default abstract class TriggerBase extends Trigger {
117
294
  */
118
295
  destroyMonitoring(): void;
119
296
  }
297
+ export {};