@blokjs/runner 0.2.2 → 0.4.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.
- package/dist/Configuration.d.ts +18 -0
- package/dist/Configuration.js +151 -4
- package/dist/Configuration.js.map +1 -1
- package/dist/PayloadTooLargeError.d.ts +19 -0
- package/dist/PayloadTooLargeError.js +29 -0
- package/dist/PayloadTooLargeError.js.map +1 -0
- package/dist/RunCancelledError.d.ts +17 -0
- package/dist/RunCancelledError.js +25 -0
- package/dist/RunCancelledError.js.map +1 -0
- package/dist/RunnerSteps.js +330 -33
- package/dist/RunnerSteps.js.map +1 -1
- package/dist/SubworkflowNode.d.ts +75 -0
- package/dist/SubworkflowNode.js +221 -0
- package/dist/SubworkflowNode.js.map +1 -0
- package/dist/TriggerBase.d.ts +128 -0
- package/dist/TriggerBase.js +773 -4
- package/dist/TriggerBase.js.map +1 -1
- package/dist/WaitDispatchRequest.d.ts +38 -0
- package/dist/WaitDispatchRequest.js +13 -0
- package/dist/WaitDispatchRequest.js.map +1 -0
- package/dist/WaitNode.d.ts +23 -0
- package/dist/WaitNode.js +26 -0
- package/dist/WaitNode.js.map +1 -0
- package/dist/concurrency/ConcurrencyBackend.d.ts +61 -0
- package/dist/concurrency/ConcurrencyBackend.js +20 -0
- package/dist/concurrency/ConcurrencyBackend.js.map +1 -0
- package/dist/concurrency/ConcurrencyLimitError.d.ts +37 -0
- package/dist/concurrency/ConcurrencyLimitError.js +16 -0
- package/dist/concurrency/ConcurrencyLimitError.js.map +1 -0
- package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +64 -0
- package/dist/concurrency/NatsKvConcurrencyBackend.js +297 -0
- package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -0
- package/dist/concurrency/QueueExpiredError.d.ts +40 -0
- package/dist/concurrency/QueueExpiredError.js +15 -0
- package/dist/concurrency/QueueExpiredError.js.map +1 -0
- package/dist/concurrency/createConcurrencyBackend.d.ts +23 -0
- package/dist/concurrency/createConcurrencyBackend.js +34 -0
- package/dist/concurrency/createConcurrencyBackend.js.map +1 -0
- package/dist/concurrency/readConcurrencyConfig.d.ts +60 -0
- package/dist/concurrency/readConcurrencyConfig.js +60 -0
- package/dist/concurrency/readConcurrencyConfig.js.map +1 -0
- package/dist/idempotency/resolveIdempotencyKey.d.ts +20 -0
- package/dist/idempotency/resolveIdempotencyKey.js +37 -0
- package/dist/idempotency/resolveIdempotencyKey.js.map +1 -0
- package/dist/index.d.ts +23 -3
- package/dist/index.js +47 -2
- package/dist/index.js.map +1 -1
- package/dist/monitoring/ConcurrencyMetrics.d.ts +56 -0
- package/dist/monitoring/ConcurrencyMetrics.js +107 -0
- package/dist/monitoring/ConcurrencyMetrics.js.map +1 -0
- package/dist/monitoring/JanitorMetrics.d.ts +27 -0
- package/dist/monitoring/JanitorMetrics.js +48 -0
- package/dist/monitoring/JanitorMetrics.js.map +1 -0
- package/dist/scheduling/DebounceCoordinator.d.ts +88 -0
- package/dist/scheduling/DebounceCoordinator.js +141 -0
- package/dist/scheduling/DebounceCoordinator.js.map +1 -0
- package/dist/scheduling/DeferredDispatchSignal.d.ts +50 -0
- package/dist/scheduling/DeferredDispatchSignal.js +14 -0
- package/dist/scheduling/DeferredDispatchSignal.js.map +1 -0
- package/dist/scheduling/DeferredRunScheduler.d.ts +68 -0
- package/dist/scheduling/DeferredRunScheduler.js +154 -0
- package/dist/scheduling/DeferredRunScheduler.js.map +1 -0
- package/dist/scheduling/readSchedulingConfig.d.ts +24 -0
- package/dist/scheduling/readSchedulingConfig.js +52 -0
- package/dist/scheduling/readSchedulingConfig.js.map +1 -0
- package/dist/timeouts/StepTimeoutError.d.ts +22 -0
- package/dist/timeouts/StepTimeoutError.js +31 -0
- package/dist/timeouts/StepTimeoutError.js.map +1 -0
- package/dist/tracing/InMemoryRunStore.d.ts +28 -1
- package/dist/tracing/InMemoryRunStore.js +150 -0
- package/dist/tracing/InMemoryRunStore.js.map +1 -1
- package/dist/tracing/Janitor.d.ts +70 -0
- package/dist/tracing/Janitor.js +150 -0
- package/dist/tracing/Janitor.js.map +1 -0
- package/dist/tracing/PostgresRunStore.d.ts +30 -0
- package/dist/tracing/PostgresRunStore.js +435 -3
- package/dist/tracing/PostgresRunStore.js.map +1 -1
- package/dist/tracing/RunStore.d.ts +100 -1
- package/dist/tracing/RunTracker.d.ts +238 -9
- package/dist/tracing/RunTracker.js +571 -1
- package/dist/tracing/RunTracker.js.map +1 -1
- package/dist/tracing/SqliteRunStore.d.ts +23 -1
- package/dist/tracing/SqliteRunStore.js +405 -6
- package/dist/tracing/SqliteRunStore.js.map +1 -1
- package/dist/tracing/TraceRouter.d.ts +20 -2
- package/dist/tracing/TraceRouter.js +249 -5
- package/dist/tracing/TraceRouter.js.map +1 -1
- package/dist/tracing/sanitize.d.ts +11 -0
- package/dist/tracing/sanitize.js +29 -0
- package/dist/tracing/sanitize.js.map +1 -1
- package/dist/tracing/types.d.ts +348 -2
- package/dist/utils/createChildContext.d.ts +32 -0
- package/dist/utils/createChildContext.js +113 -0
- package/dist/utils/createChildContext.js.map +1 -0
- package/dist/workflow/WorkflowNormalizer.d.ts +29 -41
- package/dist/workflow/WorkflowNormalizer.js +182 -0
- package/dist/workflow/WorkflowNormalizer.js.map +1 -1
- package/dist/workflow/WorkflowRegistry.d.ts +64 -0
- package/dist/workflow/WorkflowRegistry.js +81 -0
- package/dist/workflow/WorkflowRegistry.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import Configuration from "./Configuration";
|
|
2
|
+
import RunnerNode from "./RunnerNode";
|
|
3
|
+
import { RunTracker } from "./tracing/RunTracker";
|
|
4
|
+
import { createChildContext } from "./utils/createChildContext";
|
|
5
|
+
import { applyStepOutput } from "./workflow/PersistenceHelper";
|
|
6
|
+
import { WorkflowRegistry } from "./workflow/WorkflowRegistry";
|
|
7
|
+
/**
|
|
8
|
+
* Hard cap on `parent → child → grandchild → …` recursion. Bounds the
|
|
9
|
+
* blast radius of an accidental cycle (workflow A calls B calls A) or
|
|
10
|
+
* a legitimate-but-pathological deep nesting. Tunable via
|
|
11
|
+
* `BLOK_MAX_SUBWORKFLOW_DEPTH` env var; falls back to 10.
|
|
12
|
+
*/
|
|
13
|
+
function getMaxDepth() {
|
|
14
|
+
const raw = process.env.BLOK_MAX_SUBWORKFLOW_DEPTH;
|
|
15
|
+
if (typeof raw === "string" && raw.length > 0) {
|
|
16
|
+
const parsed = Number.parseInt(raw, 10);
|
|
17
|
+
if (Number.isInteger(parsed) && parsed > 0)
|
|
18
|
+
return parsed;
|
|
19
|
+
}
|
|
20
|
+
return 10;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Internal ctx field that carries the current sub-workflow depth.
|
|
24
|
+
* Incremented by `SubworkflowNode.run` before invoking the child;
|
|
25
|
+
* read on entry to enforce the cap.
|
|
26
|
+
*/
|
|
27
|
+
const SUBWORKFLOW_DEPTH_KEY = "_subworkflowDepth";
|
|
28
|
+
/**
|
|
29
|
+
* `SubworkflowNode` — the runner-side dispatch primitive that powers
|
|
30
|
+
* the v2 `subworkflow:` step shape. Looks up the named child workflow
|
|
31
|
+
* in the `WorkflowRegistry`, materializes a child `Configuration` +
|
|
32
|
+
* `Runner`, runs the child to completion in its own isolated `Context`,
|
|
33
|
+
* and returns the child's `ctx.response` as this step's `model.data`.
|
|
34
|
+
*
|
|
35
|
+
* **Composition with Tier 1**:
|
|
36
|
+
* - Parent step's `idempotencyKey` is consulted by `RunnerSteps` BEFORE
|
|
37
|
+
* `SubworkflowNode.run` is even called — cache hit short-circuits the
|
|
38
|
+
* entire sub-workflow (no child invocation, no side effects fire).
|
|
39
|
+
* This is the headline pattern AND the documented footgun.
|
|
40
|
+
* - Parent step's `retry` retries the whole sub-workflow on failure;
|
|
41
|
+
* each retry creates a fresh child run record under the same parent.
|
|
42
|
+
* - Replay re-creates fresh sub-run lineage automatically — the new
|
|
43
|
+
* parent run invokes the sub-workflow fresh.
|
|
44
|
+
*
|
|
45
|
+
* **Lineage**: child's `WorkflowRun.parentRunId` and
|
|
46
|
+
* `WorkflowRun.parentNodeRunId` carry the parent run + step that
|
|
47
|
+
* invoked it. Studio renders a "called from #..." breadcrumb on the
|
|
48
|
+
* child and a "Sub-runs" list on the parent.
|
|
49
|
+
*
|
|
50
|
+
* **Recursion guard**: `BLOK_MAX_SUBWORKFLOW_DEPTH` (default 10) bounds
|
|
51
|
+
* cycle / deep-nesting blast radius. Throws a clear error past the cap.
|
|
52
|
+
*/
|
|
53
|
+
export class SubworkflowNode extends RunnerNode {
|
|
54
|
+
/**
|
|
55
|
+
* Runner-wide options (carries the `nodes` registry that the child
|
|
56
|
+
* Configuration needs for `module` step resolution). Set by
|
|
57
|
+
* `Configuration.subworkflowResolver` before this node runs.
|
|
58
|
+
*/
|
|
59
|
+
globalOptions;
|
|
60
|
+
async run(ctx) {
|
|
61
|
+
// === 1. Recursion guard ===
|
|
62
|
+
const depth = (ctx[SUBWORKFLOW_DEPTH_KEY] ?? 0) + 1;
|
|
63
|
+
const maxDepth = getMaxDepth();
|
|
64
|
+
if (depth > maxDepth) {
|
|
65
|
+
throw new Error(`[blok] Sub-workflow recursion limit exceeded (depth ${depth} > ${maxDepth}). Likely a cycle: workflow "${ctx.workflow_name}" called sub-workflow "${this.subworkflow}" too deep. Bump via BLOK_MAX_SUBWORKFLOW_DEPTH if intentional.`);
|
|
66
|
+
}
|
|
67
|
+
// === 2. Look up the child workflow ===
|
|
68
|
+
const registry = WorkflowRegistry.getInstance();
|
|
69
|
+
const entry = registry.get(this.subworkflow);
|
|
70
|
+
if (!entry) {
|
|
71
|
+
const known = registry.list().map((w) => w.name);
|
|
72
|
+
const knownStr = known.length > 0 ? known.join(", ") : "(none registered yet)";
|
|
73
|
+
throw new Error(`[blok] Sub-workflow "${this.subworkflow}" not found in WorkflowRegistry. Available: ${knownStr}. Workflows are registered automatically by the HTTP trigger at boot — make sure the child workflow file is in the scanned directory and has \`name: "${this.subworkflow}"\`.`);
|
|
74
|
+
}
|
|
75
|
+
// === 3. Materialize child Configuration + Runner ===
|
|
76
|
+
// `preloaded` = entry.workflow skips the disk re-read; the
|
|
77
|
+
// normalizer still runs so v1→v2 conversion happens for legacy
|
|
78
|
+
// child workflows.
|
|
79
|
+
const childConfig = new Configuration();
|
|
80
|
+
await childConfig.init(entry.name, this.globalOptions, entry.workflow);
|
|
81
|
+
// Lazy import of Runner to avoid a circular dep
|
|
82
|
+
// (Configuration → RunnerNode → ... — Runner has its own chain).
|
|
83
|
+
const { default: Runner } = await import("./Runner");
|
|
84
|
+
const childRunner = new Runner(childConfig.steps);
|
|
85
|
+
// === 4. Build the child Context ===
|
|
86
|
+
// Parent step's resolved inputs (from blueprint mapper) live on
|
|
87
|
+
// `ctx.config[this.name].inputs` — the blueprint mapper has
|
|
88
|
+
// mutated the wrapper in place, so `js/...` and `$.<path>`
|
|
89
|
+
// expressions are now concrete values. These become the child's
|
|
90
|
+
// `request.body` so the child reads them via `$.req.body.<key>`
|
|
91
|
+
// exactly as if HTTP-triggered (function-call semantics).
|
|
92
|
+
const parentNodeConfig = ctx.config?.[this.name];
|
|
93
|
+
const parentInputs = parentNodeConfig?.inputs ?? {};
|
|
94
|
+
const childCtx = createChildContext(ctx, {
|
|
95
|
+
workflowName: entry.name,
|
|
96
|
+
workflowPath: entry.source,
|
|
97
|
+
body: parentInputs,
|
|
98
|
+
config: childConfig.nodes,
|
|
99
|
+
});
|
|
100
|
+
// Carry the depth counter forward so nested sub-workflows hit the cap.
|
|
101
|
+
childCtx[SUBWORKFLOW_DEPTH_KEY] = depth;
|
|
102
|
+
// === 5. Tracing — child gets its own run record + lineage ===
|
|
103
|
+
const tracker = RunTracker.getInstance();
|
|
104
|
+
const parentRunId = ctx._traceRunId;
|
|
105
|
+
const parentNodeRunId = ctx._traceNodeId;
|
|
106
|
+
const childTriggerSummary = `${ctx.workflow_name ?? "?"} → ${entry.name}`;
|
|
107
|
+
let childRunId;
|
|
108
|
+
if (tracker.active) {
|
|
109
|
+
const childRun = tracker.startRun({
|
|
110
|
+
workflowName: entry.name,
|
|
111
|
+
workflowPath: entry.source,
|
|
112
|
+
triggerType: "subworkflow",
|
|
113
|
+
triggerSummary: childTriggerSummary,
|
|
114
|
+
nodeCount: childConfig.steps.length,
|
|
115
|
+
parentRunId,
|
|
116
|
+
parentNodeRunId,
|
|
117
|
+
});
|
|
118
|
+
childRunId = childRun.id;
|
|
119
|
+
childCtx._traceRunId = childRun.id;
|
|
120
|
+
}
|
|
121
|
+
// === 6. Dispatch — sync or fire-and-forget based on `this.wait` ===
|
|
122
|
+
if (this.wait === false) {
|
|
123
|
+
return this.dispatchAsync(ctx, childRunner, childCtx, childRunId, entry.name);
|
|
124
|
+
}
|
|
125
|
+
// === 6a. Synchronous dispatch (wait: true / default) ===
|
|
126
|
+
try {
|
|
127
|
+
await childRunner.run(childCtx);
|
|
128
|
+
if (childRunId)
|
|
129
|
+
tracker.completeRun(childRunId, childCtx.response);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
if (childRunId)
|
|
133
|
+
tracker.failRun(childRunId, err);
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
// PR 1 follow-up · A3 fix. Abort the listener-cleanup signal so
|
|
138
|
+
// the parent.signal listener (registered in createChildContext)
|
|
139
|
+
// auto-removes. Without this, listeners accumulate on long-lived
|
|
140
|
+
// parents that fire many sub-workflows.
|
|
141
|
+
const childPrivate = childCtx._PRIVATE_;
|
|
142
|
+
if (childPrivate?.listenerCleanup && !childPrivate.listenerCleanup.signal.aborted) {
|
|
143
|
+
childPrivate.listenerCleanup.abort();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// === 7. Apply parent persistence + return child's response ===
|
|
147
|
+
// Mirrors HTTP function-call semantics: parent reads child output
|
|
148
|
+
// at `$.state[<this.name>]`. Child author controls the shape via
|
|
149
|
+
// `@blokjs/respond` (or the last step's natural output).
|
|
150
|
+
//
|
|
151
|
+
// Persistence-helper call mirrors the RuntimeAdapterNode pattern
|
|
152
|
+
// (RuntimeAdapterNode.ts:100). The parent step's `as` / `spread`
|
|
153
|
+
// / `ephemeral` knobs apply identically here — sub-workflow
|
|
154
|
+
// output is just data, persistence rules are uniform.
|
|
155
|
+
const result = { success: !childCtx.response?.error, data: childCtx.response };
|
|
156
|
+
applyStepOutput(ctx, this, result);
|
|
157
|
+
return {
|
|
158
|
+
success: result.success,
|
|
159
|
+
data: childCtx.response,
|
|
160
|
+
error: childCtx.response?.error ?? null,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Fire-and-forget dispatch (Tier 2 #4 follow-up — `wait: false`).
|
|
165
|
+
*
|
|
166
|
+
* Schedules the child runner via `setImmediate` so the parent step
|
|
167
|
+
* can return immediately. Child errors are caught and routed to
|
|
168
|
+
* `tracker.failRun(childRunId, err)` — visible in Studio, NOT
|
|
169
|
+
* propagated to the parent step (which has already returned). Also
|
|
170
|
+
* logged via `console.error` for ops visibility.
|
|
171
|
+
*
|
|
172
|
+
* Parent step's output is the dispatch metadata `{runId,
|
|
173
|
+
* workflowName, scheduledAt}` — NOT the child's response (which
|
|
174
|
+
* doesn't exist yet). Caller polls `GET /__blok/runs/<runId>` for
|
|
175
|
+
* the actual outcome.
|
|
176
|
+
*/
|
|
177
|
+
dispatchAsync(parentCtx, childRunner, childCtx, childRunId, childWorkflowName) {
|
|
178
|
+
const scheduledAt = Date.now();
|
|
179
|
+
const tracker = RunTracker.getInstance();
|
|
180
|
+
setImmediate(() => {
|
|
181
|
+
void (async () => {
|
|
182
|
+
try {
|
|
183
|
+
await childRunner.run(childCtx);
|
|
184
|
+
if (childRunId)
|
|
185
|
+
tracker.completeRun(childRunId, childCtx.response);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
if (childRunId) {
|
|
189
|
+
tracker.failRun(childRunId, err instanceof Error ? err : new Error(String(err)));
|
|
190
|
+
}
|
|
191
|
+
console.error(`[blok][subworkflow] async child '${childWorkflowName}' (run ${childRunId ?? "?"}) failed:`, err instanceof Error ? err.stack || err.message : err);
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
// PR 1 follow-up · A3 fix. Same listener-cleanup hook as the
|
|
195
|
+
// sync path so async sub-workflows also auto-remove the
|
|
196
|
+
// parent.signal listener on completion.
|
|
197
|
+
const childPrivate = childCtx._PRIVATE_;
|
|
198
|
+
if (childPrivate?.listenerCleanup && !childPrivate.listenerCleanup.signal.aborted) {
|
|
199
|
+
childPrivate.listenerCleanup.abort();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
})();
|
|
203
|
+
});
|
|
204
|
+
// Parent step's output: dispatch metadata (the runId is the
|
|
205
|
+
// canonical handle for at-most-once dispatch deduplication when
|
|
206
|
+
// combined with `idempotencyKey`).
|
|
207
|
+
const dispatchData = {
|
|
208
|
+
runId: childRunId ?? null,
|
|
209
|
+
workflowName: childWorkflowName,
|
|
210
|
+
scheduledAt,
|
|
211
|
+
};
|
|
212
|
+
const result = { success: true, data: dispatchData };
|
|
213
|
+
applyStepOutput(parentCtx, this, result);
|
|
214
|
+
return {
|
|
215
|
+
success: true,
|
|
216
|
+
data: dispatchData,
|
|
217
|
+
error: null,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=SubworkflowNode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubworkflowNode.js","sourceRoot":"","sources":["../src/SubworkflowNode.ts"],"names":[],"mappings":"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"}
|
package/dist/TriggerBase.d.ts
CHANGED
|
@@ -12,6 +12,20 @@ import { RateLimiter } from "./monitoring/RateLimiter";
|
|
|
12
12
|
import type { RateLimitConfig, RateLimitResult } from "./monitoring/RateLimiter";
|
|
13
13
|
import { TriggerMetricsCollector } from "./monitoring/TriggerMetricsCollector";
|
|
14
14
|
import type TriggerResponse from "./types/TriggerResponse";
|
|
15
|
+
/**
|
|
16
|
+
* Tier 2 quick-wins follow-up · structural logger interface used by
|
|
17
|
+
* `installCrashHandlers` and `recoverOrphanedRuns`. Both `error` and
|
|
18
|
+
* `log` are optional so callers can pass a `DefaultLogger`, the Hono
|
|
19
|
+
* `console` shim, a custom logger, or omit it entirely.
|
|
20
|
+
*
|
|
21
|
+
* Single-arg calls (one pre-formatted string) are intentional — Node's
|
|
22
|
+
* `process.on("uncaughtException")` handlers can't await, and the
|
|
23
|
+
* `DefaultLogger` interface only takes `(message: string)`.
|
|
24
|
+
*/
|
|
25
|
+
interface CrashAutoflipLogger {
|
|
26
|
+
error?: (message: string) => void;
|
|
27
|
+
log?: (message: string) => void;
|
|
28
|
+
}
|
|
15
29
|
export default abstract class TriggerBase extends Trigger {
|
|
16
30
|
configuration: Configuration;
|
|
17
31
|
/** Health check instance for this trigger */
|
|
@@ -32,6 +46,94 @@ export default abstract class TriggerBase extends Trigger {
|
|
|
32
46
|
abstract listen(): Promise<number>;
|
|
33
47
|
getConfiguration(): Configuration;
|
|
34
48
|
getRunner(): Runner;
|
|
49
|
+
/**
|
|
50
|
+
* Tier 2 #5+#7 follow-up — durable scheduler hook.
|
|
51
|
+
*
|
|
52
|
+
* When a trigger supports re-firing deferred dispatches across process
|
|
53
|
+
* restarts, it overrides this method to extract a JSON-serializable
|
|
54
|
+
* subset of `ctx` sufficient for `restoreDispatch(payload)` (defined
|
|
55
|
+
* by the trigger) to reconstruct an equivalent ctx and re-enter
|
|
56
|
+
* `dispatchDeferred`.
|
|
57
|
+
*
|
|
58
|
+
* Returns `null` (default) when the trigger does NOT support
|
|
59
|
+
* cross-restart durability — the scheduler then runs purely in-memory
|
|
60
|
+
* for that trigger (existing pre-follow-up behaviour).
|
|
61
|
+
*
|
|
62
|
+
* Override in `HttpTrigger` to return `{method, path, headers, body,
|
|
63
|
+
* params, query, workflowPath}` (with sensitive header keys stripped).
|
|
64
|
+
* Worker triggers don't override — broker handles delay durability.
|
|
65
|
+
*/
|
|
66
|
+
protected extractDispatchPayload(_ctx: Context): unknown | null;
|
|
67
|
+
/**
|
|
68
|
+
* Returns the trigger type string used to tag persisted scheduled
|
|
69
|
+
* dispatch rows (`scheduled_dispatches.trigger_type`). Mirrors the
|
|
70
|
+
* convention from `tracker.startRun({triggerType})`. Override when
|
|
71
|
+
* the class name doesn't naturally produce the right tag.
|
|
72
|
+
*/
|
|
73
|
+
protected getTriggerType(): string;
|
|
74
|
+
/** Flag — set true after `installCrashHandlers` has run once in this process. */
|
|
75
|
+
private static crashHandlersInstalled;
|
|
76
|
+
/**
|
|
77
|
+
* Tier 2 quick-wins follow-up — install process-level handlers for
|
|
78
|
+
* `uncaughtException` and `unhandledRejection`. When fired, flip
|
|
79
|
+
* every in-flight `running` run to `"crashed"` (with the captured
|
|
80
|
+
* error) BEFORE re-throwing / letting Node's default behavior take
|
|
81
|
+
* over. Idempotent — safe to call from every trigger's `listen()`;
|
|
82
|
+
* only the first call installs handlers.
|
|
83
|
+
*
|
|
84
|
+
* Kill-switch: `BLOK_CRASH_AUTOFLIP_DISABLED=1`.
|
|
85
|
+
*
|
|
86
|
+
* Why sync: `process.on("uncaughtException")` handlers can't await.
|
|
87
|
+
* `markAllRunningRunsAsCrashed` is sync (sqlite + in-memory writes
|
|
88
|
+
* complete before the handler returns).
|
|
89
|
+
*/
|
|
90
|
+
static installCrashHandlers(logger?: CrashAutoflipLogger): void;
|
|
91
|
+
/** Test-only — reset the install flag so tests can re-install handlers. */
|
|
92
|
+
static resetCrashHandlersInstalled(): void;
|
|
93
|
+
/** Flag — set true after `installShutdownHandlers` has run once in this process. */
|
|
94
|
+
private static shutdownHandlersInstalled;
|
|
95
|
+
/**
|
|
96
|
+
* Install SIGTERM + SIGINT handlers that drain process resources
|
|
97
|
+
* cleanly before exit. Mirrors the `installCrashHandlers` pattern —
|
|
98
|
+
* idempotent + opt-out via `BLOK_GRACEFUL_SHUTDOWN_DISABLED=1`.
|
|
99
|
+
*
|
|
100
|
+
* Drain order:
|
|
101
|
+
* 1. Stop accepting new work — calls `trigger.stop()` if available
|
|
102
|
+
* (HttpTrigger drains in-flight requests + closes the server).
|
|
103
|
+
* 2. Stop the periodic janitor sweep so it doesn't fire mid-drain.
|
|
104
|
+
* 3. Cancel pending deferred dispatches in the in-memory scheduler.
|
|
105
|
+
* (Persisted rows in `scheduled_dispatches` survive — the next
|
|
106
|
+
* boot recovers them.)
|
|
107
|
+
* 4. Disconnect the cross-process concurrency backend (NATS KV)
|
|
108
|
+
* so locks held by this process release on the broker side.
|
|
109
|
+
* 5. `process.exit(0)`.
|
|
110
|
+
*
|
|
111
|
+
* Errors during drain are caught + logged; the process still exits
|
|
112
|
+
* (cleanup is best-effort; the operator wants a clean exit).
|
|
113
|
+
*
|
|
114
|
+
* Why this is a `static` method: shutdown handlers must be installed
|
|
115
|
+
* once per process, regardless of how many trigger subclasses
|
|
116
|
+
* coexist. Subclasses pass `this` so the handler can call their
|
|
117
|
+
* specific `stop()`.
|
|
118
|
+
*/
|
|
119
|
+
static installShutdownHandlers(trigger: TriggerBase, logger?: CrashAutoflipLogger): void;
|
|
120
|
+
/** Test-only — reset the install flag so tests can re-install handlers. */
|
|
121
|
+
static resetShutdownHandlersInstalled(): void;
|
|
122
|
+
/**
|
|
123
|
+
* Tier 2 quick-wins follow-up — boot recovery for orphaned `running`
|
|
124
|
+
* runs. Scans the store for runs in `running` status whose
|
|
125
|
+
* `startedAt` is older than `thresholdMs` ago (default 2 minutes,
|
|
126
|
+
* override via `BLOK_ORPHAN_THRESHOLD_MS` env var). Flips each to
|
|
127
|
+
* `"crashed"` with `Error("Orphaned — process restarted before run completed")`.
|
|
128
|
+
*
|
|
129
|
+
* Catches the case where the previous process died via SIGKILL or
|
|
130
|
+
* OOM and the `installCrashHandlers` path never ran. Returns the
|
|
131
|
+
* count flipped for observability + tests.
|
|
132
|
+
*
|
|
133
|
+
* Idempotent — safe to call multiple times; runs are flipped to
|
|
134
|
+
* a terminal status so a second pass finds none.
|
|
135
|
+
*/
|
|
136
|
+
static recoverOrphanedRuns(thresholdMs?: number, logger?: CrashAutoflipLogger): number;
|
|
35
137
|
/**
|
|
36
138
|
* Enable hot reload for this trigger. Only active in development
|
|
37
139
|
* (NODE_ENV !== 'production') unless BLOK_HMR=true is explicitly set.
|
|
@@ -67,6 +169,31 @@ export default abstract class TriggerBase extends Trigger {
|
|
|
67
169
|
*/
|
|
68
170
|
destroyHmr(): Promise<void>;
|
|
69
171
|
run(ctx: Context): Promise<TriggerResponse>;
|
|
172
|
+
/**
|
|
173
|
+
* Tier 2 #5 + #7 — evaluate the scheduling gates and either return a
|
|
174
|
+
* `DeferredDispatchSignal` (the caller throws it) or null (the caller
|
|
175
|
+
* proceeds with immediate dispatch).
|
|
176
|
+
*
|
|
177
|
+
* Order: debounce → delay. They DON'T compose in a single PR (a
|
|
178
|
+
* trigger may use one or the other; both at once would be unusual).
|
|
179
|
+
* If both are configured, debounce takes precedence — the debounce
|
|
180
|
+
* coordinator handles its own scheduling (the `delay` field is
|
|
181
|
+
* effectively ignored on debounced triggers).
|
|
182
|
+
*/
|
|
183
|
+
private maybeDeferRun;
|
|
184
|
+
/**
|
|
185
|
+
* Tier 2 #5 + #7 — re-enter the dispatch pipeline for a deferred run.
|
|
186
|
+
*
|
|
187
|
+
* Called by the `DeferredRunScheduler` timer (delay) or
|
|
188
|
+
* `DebounceCoordinator.onFire` (debounce trailing) when the wait
|
|
189
|
+
* window closes. Checks TTL, transitions the run to `running`, and
|
|
190
|
+
* re-enters `run(ctx)` with the `_blokDispatchReentry` flag so the
|
|
191
|
+
* scheduling gates are skipped on the second pass.
|
|
192
|
+
*
|
|
193
|
+
* The re-entered `run(ctx)` reuses the existing `traceRunId` (already
|
|
194
|
+
* stashed on `ctx._traceRunId` from the first pass).
|
|
195
|
+
*/
|
|
196
|
+
protected dispatchDeferred(ctx: Context, traceRunId: string, expiresAt: number | undefined): Promise<void>;
|
|
70
197
|
/**
|
|
71
198
|
* Build a human-readable trigger summary for trace display.
|
|
72
199
|
*/
|
|
@@ -117,3 +244,4 @@ export default abstract class TriggerBase extends Trigger {
|
|
|
117
244
|
*/
|
|
118
245
|
destroyMonitoring(): void;
|
|
119
246
|
}
|
|
247
|
+
export {};
|