@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
package/dist/tracing/types.d.ts
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle status of a workflow run.
|
|
3
|
+
*
|
|
4
|
+
* - `pending`: created but not yet started.
|
|
5
|
+
* - `running`: currently executing.
|
|
6
|
+
* - `completed`: finished successfully.
|
|
7
|
+
* - `failed`: a step threw or the run aborted with an error.
|
|
8
|
+
* - `cancelled`: stopped externally before completion.
|
|
9
|
+
* - `throttled` (Tier 2 #6): rejected at run-entry because the
|
|
10
|
+
* concurrency limit for the resolved `concurrencyKey` was reached.
|
|
11
|
+
* Distinct from `failed` — no step ran; nothing produced an error.
|
|
12
|
+
* - `delayed` (Tier 2 #5): scheduled to start at a future time. The
|
|
13
|
+
* run record exists; the dispatch is pending. Transitions to
|
|
14
|
+
* `running` when the timer fires.
|
|
15
|
+
* - `expired` (Tier 2 #5): TTL exceeded before dispatch. Auto-cancelled
|
|
16
|
+
* without execution. Distinct from `cancelled` (which is operator-
|
|
17
|
+
* initiated) and `failed` (which implies a step ran).
|
|
18
|
+
* - `debounced` (Tier 2 #7): coalesced into another run via the
|
|
19
|
+
* `debounce.key` mechanism. The "loser" of a leading-mode coalesce;
|
|
20
|
+
* in trailing mode this state is transient (run flips to `running`
|
|
21
|
+
* when the debounce timer fires).
|
|
22
|
+
* - `crashed` (Tier 2 quick-wins): the runner itself crashed (uncaught
|
|
23
|
+
* exception, OOM, signal). Distinct from `failed` (which implies a
|
|
24
|
+
* step's `process()` threw cleanly). Currently MANUAL — call
|
|
25
|
+
* `tracker.markRunCrashed(runId, {error})` from custom triggers /
|
|
26
|
+
* ops harnesses. Auto-flip on uncaught TriggerBase errors is a
|
|
27
|
+
* deferred follow-up.
|
|
28
|
+
* - `timedOut` (Tier 2 quick-wins): a step's final retry attempt
|
|
29
|
+
* exceeded its `maxDuration` cap. Distinct from `failed` so SLA
|
|
30
|
+
* dashboards can separate timeout-driven failures (network /
|
|
31
|
+
* capacity) from logic failures (bugs). Auto-flipped by
|
|
32
|
+
* `RunnerSteps` on final-attempt `StepTimeoutError`.
|
|
33
|
+
*/
|
|
34
|
+
export type WorkflowRunStatus = "pending" | "running" | "completed" | "failed" | "cancelled" | "throttled" | "delayed" | "expired" | "debounced" | "queued" | "crashed" | "timedOut";
|
|
2
35
|
/**
|
|
3
36
|
* Structured failure detail attached to a {@link WorkflowRun} or
|
|
4
37
|
* {@link NodeRun}. When the failure source threw a typed `BlokError`
|
|
@@ -60,6 +93,74 @@ export interface WorkflowRun {
|
|
|
60
93
|
* SQLite databases still work because the column is optional.
|
|
61
94
|
*/
|
|
62
95
|
environment?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Tier 1 · replay lineage. When this run was started via
|
|
98
|
+
* `POST /__blok/runs/:id/replay`, this carries the original run's id.
|
|
99
|
+
* Studio renders a "Replay of #..." breadcrumb that links back to the
|
|
100
|
+
* source run. Absent on first-class triggered runs.
|
|
101
|
+
*
|
|
102
|
+
* Plumbed end-to-end via the `X-Blok-Replay-Of` HTTP header that the
|
|
103
|
+
* replay endpoint sets on the dispatched request. TriggerBase reads
|
|
104
|
+
* the header and threads it into `tracker.startRun({ replayOf })`.
|
|
105
|
+
*/
|
|
106
|
+
replayOf?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Tier 2 · sub-workflow lineage. When this run was started by a
|
|
109
|
+
* `subworkflow:` step in another workflow, this carries the parent
|
|
110
|
+
* run's id. Studio renders a "called from #..." breadcrumb that
|
|
111
|
+
* links back to the parent run. Absent on first-class triggered runs.
|
|
112
|
+
*/
|
|
113
|
+
parentRunId?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Tier 2 · sub-workflow lineage. The specific NodeRun within the
|
|
116
|
+
* parent run that invoked this sub-workflow — lets Studio jump to
|
|
117
|
+
* the exact sub-workflow step on the parent's run-detail page.
|
|
118
|
+
*/
|
|
119
|
+
parentNodeRunId?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Tier 2 #5 · scheduled dispatch time (ms since epoch). Set when the
|
|
122
|
+
* run is created with `delay` on the trigger config, OR when the
|
|
123
|
+
* debounce coordinator parks a coalescing run. Absent on immediate
|
|
124
|
+
* runs.
|
|
125
|
+
*/
|
|
126
|
+
scheduledAt?: number;
|
|
127
|
+
/**
|
|
128
|
+
* Tier 2 #5 · TTL deadline (ms since epoch). When `now > expiresAt`
|
|
129
|
+
* at dispatch time, the run is marked `expired` and skipped. Set
|
|
130
|
+
* from `trigger.ttl` plus the original submission timestamp. Absent
|
|
131
|
+
* when no TTL is configured.
|
|
132
|
+
*/
|
|
133
|
+
expiresAt?: number;
|
|
134
|
+
/**
|
|
135
|
+
* Tier 2 #7 · resolved debounce key (`debounce.key` after
|
|
136
|
+
* `js/...`-expression evaluation). Pings sharing this key + workflow
|
|
137
|
+
* name coalesce into the same `WorkflowRun`. Absent on non-debounced
|
|
138
|
+
* runs.
|
|
139
|
+
*/
|
|
140
|
+
debounceKey?: string;
|
|
141
|
+
/**
|
|
142
|
+
* Tier 2 #7 · debounce mode that produced this run. Surfaces in
|
|
143
|
+
* Studio's run detail so users can tell `leading` (immediate-fire)
|
|
144
|
+
* vs `trailing` (silence-then-fire) at a glance.
|
|
145
|
+
*/
|
|
146
|
+
debounceMode?: "leading" | "trailing";
|
|
147
|
+
/**
|
|
148
|
+
* Tier 2 #7 · number of pings absorbed by this run before dispatch.
|
|
149
|
+
* Starts at 1 (the first ping). Subsequent same-key pings increment
|
|
150
|
+
* this rather than creating new run records. Surfaces on Studio's
|
|
151
|
+
* run row as "Pings: N".
|
|
152
|
+
*/
|
|
153
|
+
pingCount?: number;
|
|
154
|
+
/**
|
|
155
|
+
* PR 4 · `wait.for(duration)` / `wait.until(date)` resume cursor.
|
|
156
|
+
*
|
|
157
|
+
* Set after each non-wait step completes. On
|
|
158
|
+
* `dispatchDeferred` re-entry from a wait step, the runner skips
|
|
159
|
+
* steps with `stepIndex <= lastCompletedStepIndex` to avoid
|
|
160
|
+
* re-running pre-wait steps. Undefined for runs that never
|
|
161
|
+
* encountered a wait step (preserves existing semantics).
|
|
162
|
+
*/
|
|
163
|
+
lastCompletedStepIndex?: number;
|
|
63
164
|
}
|
|
64
165
|
export type NodeRunStatus = "pending" | "running" | "completed" | "failed" | "skipped";
|
|
65
166
|
export interface NodeRun {
|
|
@@ -113,12 +214,199 @@ export interface NodeRun {
|
|
|
113
214
|
/** Bytes received from the SDK in the response. */
|
|
114
215
|
response_bytes?: number;
|
|
115
216
|
};
|
|
217
|
+
/**
|
|
218
|
+
* Tier 1 idempotency cache lineage. Populated by `RunTracker.markNodeCached`
|
|
219
|
+
* when the step short-circuited via a cache hit instead of running. Studio
|
|
220
|
+
* renders a "CACHED" badge with a click-through to the source run/node.
|
|
221
|
+
* Absent on regular completed nodes.
|
|
222
|
+
*/
|
|
223
|
+
cached?: {
|
|
224
|
+
sourceRunId: string;
|
|
225
|
+
sourceNodeRunId: string;
|
|
226
|
+
cachedAt: number;
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Tier 1 retry attempts. One entry per `NODE_ATTEMPT_FAILED` event the
|
|
230
|
+
* step emitted before either succeeding or exhausting `retry.maxAttempts`.
|
|
231
|
+
* Capped at 10 entries (`MAX_STORED_ATTEMPTS`) — extreme retry counts
|
|
232
|
+
* are bounded so a runaway loop can't bloat the run store.
|
|
233
|
+
*
|
|
234
|
+
* Studio renders this as a collapsible "Attempts (N)" disclosure on the
|
|
235
|
+
* step's panel. The final outcome lives on the node's status / error
|
|
236
|
+
* fields; this array carries only the failed attempts that came before.
|
|
237
|
+
*/
|
|
238
|
+
attempts?: Array<{
|
|
239
|
+
attempt: number;
|
|
240
|
+
error: RunErrorDetail;
|
|
241
|
+
timestamp: number;
|
|
242
|
+
}>;
|
|
243
|
+
/**
|
|
244
|
+
* Tier 2 #4 sub-workflow mode — only set for `nodeType === "subworkflow"`.
|
|
245
|
+
* - `true` (default) — synchronous: parent step blocks on child completion.
|
|
246
|
+
* - `false` — fire-and-forget: parent returns dispatch metadata immediately;
|
|
247
|
+
* child runs asynchronously via `setImmediate`. Studio renders a distinct
|
|
248
|
+
* `↳ async` badge in StepRail to differentiate from synchronous siblings.
|
|
249
|
+
*
|
|
250
|
+
* Captured at `tracker.startNode()` time so it survives the trace and is
|
|
251
|
+
* visible to Studio without recomputing from outputs heuristics.
|
|
252
|
+
*/
|
|
253
|
+
wait?: boolean;
|
|
254
|
+
/**
|
|
255
|
+
* PR 5 E3 — sub-workflow nesting depth surfaced for Studio.
|
|
256
|
+
*
|
|
257
|
+
* Set on subworkflow node runs. Top-level workflow's sub-workflow
|
|
258
|
+
* step has `subworkflowDepth = 1`; that child's sub-workflow step
|
|
259
|
+
* has `subworkflowDepth = 2`; etc. Bounded by
|
|
260
|
+
* `BLOK_MAX_SUBWORKFLOW_DEPTH` (default 10).
|
|
261
|
+
*
|
|
262
|
+
* Studio renders `↳ async (N)` / `↳ sub (N)` only when N >= 2 to
|
|
263
|
+
* keep top-level invocations un-cluttered.
|
|
264
|
+
*/
|
|
265
|
+
subworkflowDepth?: number;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Stored result of a previously-successful step execution, keyed by
|
|
269
|
+
* `(workflowName, step.id, idempotencyKey)`. On cache hit, RunnerSteps
|
|
270
|
+
* skips `step.process()` and replays the cached `data` through the same
|
|
271
|
+
* `PersistenceHelper.applyStepOutput` rules — caching layers ABOVE
|
|
272
|
+
* persistence, never within it.
|
|
273
|
+
*/
|
|
274
|
+
export interface CachedStepResult {
|
|
275
|
+
/** The data the step originally returned. */
|
|
276
|
+
data: unknown;
|
|
277
|
+
/** ms since epoch when the entry was written. */
|
|
278
|
+
cachedAt: number;
|
|
279
|
+
/** ms since epoch when the entry expires; null = no expiry. */
|
|
280
|
+
expiresAt: number | null;
|
|
281
|
+
/** Run that originally produced this result. */
|
|
282
|
+
sourceRunId: string;
|
|
283
|
+
/** Node run that originally produced this result. */
|
|
284
|
+
sourceNodeRunId: string;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Outcome of an `acquireConcurrencySlot` attempt (Tier 2 #6).
|
|
288
|
+
*
|
|
289
|
+
* The store's gate decides whether the run is allowed to proceed by
|
|
290
|
+
* comparing the current in-flight count for the (workflow, key) pair
|
|
291
|
+
* against the requested limit.
|
|
292
|
+
*/
|
|
293
|
+
export interface ConcurrencySlotResult {
|
|
294
|
+
/** True when the slot was granted; false when the run should be throttled. */
|
|
295
|
+
acquired: boolean;
|
|
296
|
+
/**
|
|
297
|
+
* Number of in-flight runs (including the just-acquired one when
|
|
298
|
+
* `acquired === true`) sharing the same (workflowName, concurrencyKey).
|
|
299
|
+
* Useful for observability — Studio surfaces this on `RUN_THROTTLED`.
|
|
300
|
+
*/
|
|
301
|
+
currentInFlight: number;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* A persisted scheduled dispatch — one row per pending HTTP-trigger
|
|
305
|
+
* deferral. Written by `DeferredRunScheduler.schedule()` when a
|
|
306
|
+
* `persist` payload is provided; deleted on cancel or fire.
|
|
307
|
+
*
|
|
308
|
+
* Boot recovery (HttpTrigger.recoverDispatches) scans this table,
|
|
309
|
+
* marks past-due+TTL-expired rows as `"expired"`, and re-registers
|
|
310
|
+
* timers for live dispatches.
|
|
311
|
+
*/
|
|
312
|
+
export interface ScheduledDispatchRow {
|
|
313
|
+
runId: string;
|
|
314
|
+
workflowName: string;
|
|
315
|
+
/** `"http"` for v1; future triggers can opt in. */
|
|
316
|
+
triggerType: string;
|
|
317
|
+
/** ms since epoch when to dispatch. */
|
|
318
|
+
scheduledAt: number;
|
|
319
|
+
/** ms since epoch TTL deadline (undefined = no TTL). */
|
|
320
|
+
expiresAt?: number;
|
|
321
|
+
/** Mirrors the run record's status — `"delayed" | "queued" | "debounced"`. */
|
|
322
|
+
dispatchStatus: "delayed" | "queued" | "debounced";
|
|
323
|
+
/**
|
|
324
|
+
* JSON-serialized minimal Context subset, trigger-defined.
|
|
325
|
+
* For HTTP: `{method, path, headers, body, params, query, workflowPath}`
|
|
326
|
+
* with sensitive header keys stripped (authorization, cookie, x-api-key).
|
|
327
|
+
*/
|
|
328
|
+
payload: unknown;
|
|
329
|
+
/** ms since epoch when the row was first written. */
|
|
330
|
+
createdAt: number;
|
|
116
331
|
}
|
|
117
332
|
export type RunEventType = "RUN_STARTED" | "RUN_COMPLETED" | "RUN_FAILED" | "NODE_STARTED" | "NODE_COMPLETED" | "NODE_FAILED" | "NODE_SKIPPED" | "VARS_UPDATED" | "LOG_ENTRY"
|
|
118
333
|
/** §17 Phase 5: streaming `Progress` frame from the SDK. */
|
|
119
334
|
| "NODE_PROGRESS"
|
|
120
335
|
/** §17 Phase 5: streaming `PartialResult` frame from the SDK. */
|
|
121
|
-
| "NODE_PARTIAL_RESULT"
|
|
336
|
+
| "NODE_PARTIAL_RESULT"
|
|
337
|
+
/**
|
|
338
|
+
* Tier 1 idempotency cache hit: the step short-circuited via the
|
|
339
|
+
* idempotency cache instead of running. Payload carries
|
|
340
|
+
* `{ durationMs, source: { sourceRunId, sourceNodeRunId, cachedAt } }`.
|
|
341
|
+
* Studio renders a CACHED badge on the affected node.
|
|
342
|
+
*/
|
|
343
|
+
| "NODE_CACHED"
|
|
344
|
+
/**
|
|
345
|
+
* Tier 1 retry: a step attempt failed and another retry will follow.
|
|
346
|
+
* Payload carries `{ attempt, error }`. Final attempt failure (after
|
|
347
|
+
* exhausting `retry.maxAttempts`) emits `NODE_FAILED` instead.
|
|
348
|
+
*/
|
|
349
|
+
| "NODE_ATTEMPT_FAILED"
|
|
350
|
+
/**
|
|
351
|
+
* Tier 2 #6 concurrency gate denied a run before any step executed.
|
|
352
|
+
* Payload carries `{ concurrencyKey, concurrencyLimit, currentInFlight }`.
|
|
353
|
+
* The run's status flips to `"throttled"` (distinct from `"failed"` —
|
|
354
|
+
* no step ran). Studio surfaces a Throttled badge.
|
|
355
|
+
*/
|
|
356
|
+
| "RUN_THROTTLED"
|
|
357
|
+
/**
|
|
358
|
+
* Tier 2 #5 — run scheduled for later dispatch via `trigger.delay`.
|
|
359
|
+
* Payload `{scheduledAt, delayMs, expiresAt?}`. Status flips to
|
|
360
|
+
* `"delayed"`; transitions to `"running"` when the timer fires.
|
|
361
|
+
*/
|
|
362
|
+
| "RUN_DELAYED"
|
|
363
|
+
/**
|
|
364
|
+
* Tier 2 #5 — TTL exceeded; run auto-cancelled before dispatch.
|
|
365
|
+
* Payload `{expiresAt, expiredAt, lateBy}`. Status flips to `"expired"`.
|
|
366
|
+
*/
|
|
367
|
+
| "RUN_EXPIRED"
|
|
368
|
+
/**
|
|
369
|
+
* Tier 2 #7 — run coalesced into a debounce window. Payload
|
|
370
|
+
* `{debounceKey, mode, intoRunId?, pingCount?}`. Status flips to
|
|
371
|
+
* `"debounced"`. In leading mode this is terminal (the run never
|
|
372
|
+
* executes — execution went to a sibling). In trailing mode this is
|
|
373
|
+
* transient and the same run flips to `"running"` when the window
|
|
374
|
+
* closes.
|
|
375
|
+
*/
|
|
376
|
+
| "RUN_DEBOUNCED"
|
|
377
|
+
/**
|
|
378
|
+
* Tier 2 #6 follow-up — concurrency gate denied a run AND the trigger
|
|
379
|
+
* is configured with `onLimit: "queue"`. Instead of throwing, the run
|
|
380
|
+
* is deferred via `DeferredRunScheduler` and re-attempts acquisition
|
|
381
|
+
* after a 1s delay. Payload `{concurrencyKey, concurrencyLimit,
|
|
382
|
+
* currentInFlight, scheduledAt}`. Status flips to `"queued"`;
|
|
383
|
+
* transitions to `"running"` when the timer fires (and may flip back
|
|
384
|
+
* to `"queued"` on re-denial).
|
|
385
|
+
*/
|
|
386
|
+
| "RUN_QUEUED"
|
|
387
|
+
/**
|
|
388
|
+
* Tier 2 polish — operator cancelled a pending (delayed/debounced/
|
|
389
|
+
* queued) run via `POST /__blok/runs/:runId/cancel`. Payload
|
|
390
|
+
* `{durationMs, previousStatus}`. Status flips to `"cancelled"`.
|
|
391
|
+
* Currently only pre-execution states are cancellable; running runs
|
|
392
|
+
* require cooperative `AbortSignal` (deferred follow-up).
|
|
393
|
+
*/
|
|
394
|
+
| "RUN_CANCELLED"
|
|
395
|
+
/**
|
|
396
|
+
* Tier 2 quick-wins — run crashed (uncaught exception / OOM /
|
|
397
|
+
* signal). Payload `{durationMs, error}`. Distinct from `RUN_FAILED`
|
|
398
|
+
* (which implies a step's `process()` threw cleanly). Currently
|
|
399
|
+
* manual via `tracker.markRunCrashed(runId, {error})`.
|
|
400
|
+
*/
|
|
401
|
+
| "RUN_CRASHED"
|
|
402
|
+
/**
|
|
403
|
+
* Tier 2 quick-wins — a step's final retry attempt exceeded its
|
|
404
|
+
* `maxDuration` cap. Payload `{durationMs, stepId, maxDurationMs,
|
|
405
|
+
* attemptsExhausted}`. Auto-emitted by `RunnerSteps` when the
|
|
406
|
+
* timeout fires on the last attempt. Run status flips to
|
|
407
|
+
* `"timedOut"`.
|
|
408
|
+
*/
|
|
409
|
+
| "RUN_TIMED_OUT";
|
|
122
410
|
export interface RunEvent {
|
|
123
411
|
id: string;
|
|
124
412
|
type: RunEventType;
|
|
@@ -170,6 +458,43 @@ export interface StartRunOptions {
|
|
|
170
458
|
nodeCount: number;
|
|
171
459
|
tags?: string[];
|
|
172
460
|
metadata?: Record<string, unknown>;
|
|
461
|
+
/**
|
|
462
|
+
* Tier 1 · replay lineage. When a run is started via the replay endpoint,
|
|
463
|
+
* this carries the original run's id so Studio can render a "Replay of #..."
|
|
464
|
+
* breadcrumb. Plumbed end-to-end via the `X-Blok-Replay-Of` HTTP header.
|
|
465
|
+
*/
|
|
466
|
+
replayOf?: string;
|
|
467
|
+
/**
|
|
468
|
+
* Tier 2 · sub-workflow lineage. Populated by `SubworkflowNode` when
|
|
469
|
+
* invoking a child workflow inline. Studio uses this to render a
|
|
470
|
+
* "called from #..." breadcrumb on the child's run detail.
|
|
471
|
+
*/
|
|
472
|
+
parentRunId?: string;
|
|
473
|
+
parentNodeRunId?: string;
|
|
474
|
+
/**
|
|
475
|
+
* Tier 2 #5 · scheduled dispatch time (ms since epoch). When set, the
|
|
476
|
+
* run is created with status `"delayed"` instead of `"running"`.
|
|
477
|
+
*/
|
|
478
|
+
scheduledAt?: number;
|
|
479
|
+
/**
|
|
480
|
+
* Tier 2 #5 · TTL deadline (ms since epoch). Persists onto the run
|
|
481
|
+
* for the dispatcher to consult.
|
|
482
|
+
*/
|
|
483
|
+
expiresAt?: number;
|
|
484
|
+
/**
|
|
485
|
+
* Tier 2 #7 · resolved debounce key. When set, pings sharing this
|
|
486
|
+
* key + workflow name coalesce.
|
|
487
|
+
*/
|
|
488
|
+
debounceKey?: string;
|
|
489
|
+
/**
|
|
490
|
+
* Tier 2 #7 · debounce mode that produced this run.
|
|
491
|
+
*/
|
|
492
|
+
debounceMode?: "leading" | "trailing";
|
|
493
|
+
/**
|
|
494
|
+
* Tier 2 #7 · initial ping count for the run (typically 1 — the
|
|
495
|
+
* first ping). Subsequent pings increment via direct store update.
|
|
496
|
+
*/
|
|
497
|
+
pingCount?: number;
|
|
173
498
|
}
|
|
174
499
|
export interface StartNodeOptions {
|
|
175
500
|
nodeName: string;
|
|
@@ -179,6 +504,18 @@ export interface StartNodeOptions {
|
|
|
179
504
|
parentNodeId?: string;
|
|
180
505
|
depth: number;
|
|
181
506
|
stepIndex: number;
|
|
507
|
+
/**
|
|
508
|
+
* Tier 2 #4 sub-workflow mode. Only meaningful when `nodeType === "subworkflow"`.
|
|
509
|
+
* Persisted onto `NodeRun.wait` so Studio can render `↳ async` vs `↳ sub`.
|
|
510
|
+
*/
|
|
511
|
+
wait?: boolean;
|
|
512
|
+
/**
|
|
513
|
+
* PR 5 E3 — sub-workflow nesting depth. Only meaningful when
|
|
514
|
+
* `nodeType === "subworkflow"`. The runner computes
|
|
515
|
+
* `parentDepth + 1` from `ctx._subworkflowDepth` at start-time and
|
|
516
|
+
* passes here so Studio can render `↳ sub (N)` for N >= 2.
|
|
517
|
+
*/
|
|
518
|
+
subworkflowDepth?: number;
|
|
182
519
|
}
|
|
183
520
|
export type WidgetType = "stat-card" | "timeline" | "error-rate" | "duration-distribution" | "workflow-breakdown" | "node-performance" | "recent-runs" | "heatmap";
|
|
184
521
|
export interface DashboardWidget {
|
|
@@ -212,6 +549,15 @@ export interface RunQuery {
|
|
|
212
549
|
workflow?: string;
|
|
213
550
|
status?: WorkflowRunStatus;
|
|
214
551
|
tags?: string[];
|
|
552
|
+
/**
|
|
553
|
+
* Tier 2 quick-wins — filter by metadata key=value pairs. Multiple
|
|
554
|
+
* pairs combine with AND semantics (a run matches only when all
|
|
555
|
+
* declared keys match the requested values, compared via stringified
|
|
556
|
+
* equality). Backed by `json_extract` on SQLite + `Object` lookup
|
|
557
|
+
* on InMemory. Sequential scan (no index); acceptable given the
|
|
558
|
+
* `evictOldRuns` size cap on the runs table.
|
|
559
|
+
*/
|
|
560
|
+
metadata?: Record<string, string>;
|
|
215
561
|
limit?: number;
|
|
216
562
|
offset?: number;
|
|
217
563
|
sort?: "asc" | "desc";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Context } from "@blokjs/shared";
|
|
2
|
+
/**
|
|
3
|
+
* Construct a fresh `Context` for a sub-workflow invocation.
|
|
4
|
+
*
|
|
5
|
+
* **Isolation contract**: the child gets fresh `state`, fresh `response`,
|
|
6
|
+
* fresh `error`, and a fresh `id`. The child cannot read or mutate the
|
|
7
|
+
* parent's state — sub-workflows are referentially transparent at the
|
|
8
|
+
* state-passing boundary. Parent passes data in via `request.body`
|
|
9
|
+
* (mirrors HTTP semantics), child returns data via `ctx.response`.
|
|
10
|
+
*
|
|
11
|
+
* **Shared by reference (intentional)**: the `logger`, `env`, and
|
|
12
|
+
* `eventLogger` are shared with the parent — log routing stays
|
|
13
|
+
* consistent and ENV is process-global anyway. The runner's tracing
|
|
14
|
+
* layer (`_traceRunId`, `_traceNodeId`) is set separately by
|
|
15
|
+
* `SubworkflowNode` after this function returns.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors `TriggerBase.createContext` shape one-for-one (same `req`/
|
|
18
|
+
* `prev` getters, same `state`/`vars` aliasing, same `publish`
|
|
19
|
+
* default). Kept as a standalone helper rather than a TriggerBase
|
|
20
|
+
* method so sub-workflow dispatch doesn't depend on having a
|
|
21
|
+
* TriggerBase instance.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createChildContext(parent: Context, opts: {
|
|
24
|
+
/** The child workflow's `name:` field. */
|
|
25
|
+
workflowName: string;
|
|
26
|
+
/** Filesystem path or `"<inline>"` — used for trace + diagnostics. */
|
|
27
|
+
workflowPath: string;
|
|
28
|
+
/** Parent step's resolved inputs, becomes child's `request.body`. */
|
|
29
|
+
body: unknown;
|
|
30
|
+
/** Child's resolved `nodes` map (from child Configuration). Powers blueprint mapper. */
|
|
31
|
+
config: Context["config"];
|
|
32
|
+
}): Context;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { v4 as uuid } from "uuid";
|
|
2
|
+
/**
|
|
3
|
+
* Construct a fresh `Context` for a sub-workflow invocation.
|
|
4
|
+
*
|
|
5
|
+
* **Isolation contract**: the child gets fresh `state`, fresh `response`,
|
|
6
|
+
* fresh `error`, and a fresh `id`. The child cannot read or mutate the
|
|
7
|
+
* parent's state — sub-workflows are referentially transparent at the
|
|
8
|
+
* state-passing boundary. Parent passes data in via `request.body`
|
|
9
|
+
* (mirrors HTTP semantics), child returns data via `ctx.response`.
|
|
10
|
+
*
|
|
11
|
+
* **Shared by reference (intentional)**: the `logger`, `env`, and
|
|
12
|
+
* `eventLogger` are shared with the parent — log routing stays
|
|
13
|
+
* consistent and ENV is process-global anyway. The runner's tracing
|
|
14
|
+
* layer (`_traceRunId`, `_traceNodeId`) is set separately by
|
|
15
|
+
* `SubworkflowNode` after this function returns.
|
|
16
|
+
*
|
|
17
|
+
* Mirrors `TriggerBase.createContext` shape one-for-one (same `req`/
|
|
18
|
+
* `prev` getters, same `state`/`vars` aliasing, same `publish`
|
|
19
|
+
* default). Kept as a standalone helper rather than a TriggerBase
|
|
20
|
+
* method so sub-workflow dispatch doesn't depend on having a
|
|
21
|
+
* TriggerBase instance.
|
|
22
|
+
*/
|
|
23
|
+
export function createChildContext(parent, opts) {
|
|
24
|
+
const id = uuid();
|
|
25
|
+
const request = {
|
|
26
|
+
body: opts.body ?? {},
|
|
27
|
+
headers: {},
|
|
28
|
+
params: {},
|
|
29
|
+
query: {},
|
|
30
|
+
};
|
|
31
|
+
const response = {
|
|
32
|
+
data: "",
|
|
33
|
+
contentType: "",
|
|
34
|
+
success: true,
|
|
35
|
+
error: null,
|
|
36
|
+
};
|
|
37
|
+
const state = {};
|
|
38
|
+
// Tier 2 follow-up · cooperative cancellation. Child gets its own
|
|
39
|
+
// AbortController so it has independent lifecycle (cancellation of
|
|
40
|
+
// THIS sub-workflow doesn't propagate up to the parent). But we
|
|
41
|
+
// chain off the parent's signal: if the parent gets cancelled, the
|
|
42
|
+
// child should abort too — otherwise long-running sub-workflows
|
|
43
|
+
// would orphan when the parent exits.
|
|
44
|
+
//
|
|
45
|
+
// PR 1 follow-up · A3 fix. `addEventListener({once: true})` only
|
|
46
|
+
// auto-removes the listener when abort fires. If the parent never
|
|
47
|
+
// aborts AND the parent ctx outlives many child invocations, listeners
|
|
48
|
+
// accumulate and Node's MaxListenersExceededWarning fires at the 11th.
|
|
49
|
+
// Pass a child-scoped AbortSignal as the listener's `signal:` option
|
|
50
|
+
// so the listener auto-removes when the child completes (the
|
|
51
|
+
// SubworkflowNode dispatch path calls `listenerCleanup.abort()` in
|
|
52
|
+
// its finally block).
|
|
53
|
+
const childAbortController = new AbortController();
|
|
54
|
+
const listenerCleanup = new AbortController();
|
|
55
|
+
if (parent.signal) {
|
|
56
|
+
if (parent.signal.aborted) {
|
|
57
|
+
childAbortController.abort();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
parent.signal.addEventListener("abort", () => {
|
|
61
|
+
if (!childAbortController.signal.aborted)
|
|
62
|
+
childAbortController.abort();
|
|
63
|
+
}, { once: true, signal: listenerCleanup.signal });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const ctx = {
|
|
67
|
+
id,
|
|
68
|
+
workflow_name: opts.workflowName,
|
|
69
|
+
workflow_path: opts.workflowPath,
|
|
70
|
+
config: opts.config,
|
|
71
|
+
request,
|
|
72
|
+
response,
|
|
73
|
+
error: { message: [] },
|
|
74
|
+
logger: parent.logger,
|
|
75
|
+
eventLogger: parent.eventLogger ?? null,
|
|
76
|
+
// Fresh state map — child runs in isolation. Aliased as `vars`
|
|
77
|
+
// for v1 back-compat, same shape as `TriggerBase.createContext`.
|
|
78
|
+
state,
|
|
79
|
+
vars: state,
|
|
80
|
+
env: parent.env,
|
|
81
|
+
signal: childAbortController.signal,
|
|
82
|
+
// Stash the controller in a child-specific _PRIVATE_ slot so the
|
|
83
|
+
// tracker can fire it via abortRunningRun on direct sub-run cancel.
|
|
84
|
+
// Don't mutate the parent's _PRIVATE_ (intentional isolation).
|
|
85
|
+
// `listenerCleanup` is exposed so SubworkflowNode can abort it on
|
|
86
|
+
// child completion, which removes the parent.signal listener (PR 1
|
|
87
|
+
// A3 fix — prevents listener accumulation on long-lived parents).
|
|
88
|
+
_PRIVATE_: { abortController: childAbortController, listenerCleanup },
|
|
89
|
+
};
|
|
90
|
+
// V2 read-only aliases — same object reference, no copy. Mirrors
|
|
91
|
+
// `TriggerBase.createContext`.
|
|
92
|
+
Object.defineProperty(ctx, "req", {
|
|
93
|
+
get() {
|
|
94
|
+
return ctx.request;
|
|
95
|
+
},
|
|
96
|
+
enumerable: true,
|
|
97
|
+
});
|
|
98
|
+
Object.defineProperty(ctx, "prev", {
|
|
99
|
+
get() {
|
|
100
|
+
return ctx.response;
|
|
101
|
+
},
|
|
102
|
+
enumerable: true,
|
|
103
|
+
});
|
|
104
|
+
// Default `publish` — writes to state, no side-channel event. The
|
|
105
|
+
// triggers' production createContext also wires Studio trace
|
|
106
|
+
// events; for sub-workflows we omit that (the child has its own
|
|
107
|
+
// trace run, events fire there).
|
|
108
|
+
ctx.publish = (name, value) => {
|
|
109
|
+
ctx.state[name] = value;
|
|
110
|
+
};
|
|
111
|
+
return ctx;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=createChildContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createChildContext.js","sourceRoot":"","sources":["../../src/utils/createChildContext.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,IAAI,IAAI,EAAE,MAAM,MAAM,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CACjC,MAAe,EACf,IASC;IAED,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;IAClB,MAAM,OAAO,GAAuB;QACnC,IAAI,EAAG,IAAI,CAAC,IAAmC,IAAI,EAAE;QACrD,OAAO,EAAE,EAAmC;QAC5C,MAAM,EAAE,EAAkC;QAC1C,KAAK,EAAE,EAAiC;KAClB,CAAC;IACxB,MAAM,QAAQ,GAAwB;QACrC,IAAI,EAAE,EAAE;QACR,WAAW,EAAE,EAAE;QACf,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,IAAI;KACY,CAAC;IACzB,MAAM,KAAK,GAA4B,EAAE,CAAC;IAE1C,kEAAkE;IAClE,mEAAmE;IACnE,gEAAgE;IAChE,mEAAmE;IACnE,gEAAgE;IAChE,sCAAsC;IACtC,EAAE;IACF,iEAAiE;IACjE,kEAAkE;IAClE,uEAAuE;IACvE,uEAAuE;IACvE,qEAAqE;IACrE,6DAA6D;IAC7D,mEAAmE;IACnE,sBAAsB;IACtB,MAAM,oBAAoB,GAAG,IAAI,eAAe,EAAE,CAAC;IACnD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IAC9C,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAC3B,oBAAoB,CAAC,KAAK,EAAE,CAAC;QAC9B,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAC7B,OAAO,EACP,GAAG,EAAE;gBACJ,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,OAAO;oBAAE,oBAAoB,CAAC,KAAK,EAAE,CAAC;YACxE,CAAC,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,CAC9C,CAAC;QACH,CAAC;IACF,CAAC;IAED,MAAM,GAAG,GAAY;QACpB,EAAE;QACF,aAAa,EAAE,IAAI,CAAC,YAAY;QAChC,aAAa,EAAE,IAAI,CAAC,YAAY;QAChC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,OAAO;QACP,QAAQ;QACR,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAsB;QAC1C,MAAM,EAAE,MAAM,CAAC,MAAuB;QACtC,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,IAAI;QACvC,+DAA+D;QAC/D,iEAAiE;QACjE,KAAK;QACL,IAAI,EAAE,KAAK;QACX,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,MAAM,EAAE,oBAAoB,CAAC,MAAM;QACnC,iEAAiE;QACjE,oEAAoE;QACpE,+DAA+D;QAC/D,kEAAkE;QAClE,mEAAmE;QACnE,kEAAkE;QAClE,SAAS,EAAE,EAAE,eAAe,EAAE,oBAAoB,EAAE,eAAe,EAAE;KACrE,CAAC;IAEF,iEAAiE;IACjE,+BAA+B;IAC/B,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,KAAK,EAAE;QACjC,GAAG;YACF,OAAO,GAAG,CAAC,OAAO,CAAC;QACpB,CAAC;QACD,UAAU,EAAE,IAAI;KAChB,CAAC,CAAC;IACH,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE;QAClC,GAAG;YACF,OAAO,GAAG,CAAC,QAAQ,CAAC;QACrB,CAAC;QACD,UAAU,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH,kEAAkE;IAClE,6DAA6D;IAC7D,gEAAgE;IAChE,iCAAiC;IACjC,GAAG,CAAC,OAAO,GAAG,CAAC,IAAY,EAAE,KAAc,EAAQ,EAAE;QACnD,GAAG,CAAC,KAAiC,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACtD,CAAC,CAAC;IAEF,OAAO,GAAG,CAAC;AACZ,CAAC"}
|
|
@@ -1,44 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* v1 (legacy):
|
|
9
|
-
* ```
|
|
10
|
-
* {
|
|
11
|
-
* name, version, trigger,
|
|
12
|
-
* steps: [{ name, node, type, active?, stop?, set_var? }],
|
|
13
|
-
* nodes: { [stepName]: { inputs?, conditions? } }
|
|
14
|
-
* }
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* v2 (canonical):
|
|
18
|
-
* ```
|
|
19
|
-
* {
|
|
20
|
-
* name, version, trigger,
|
|
21
|
-
* steps: [
|
|
22
|
-
* { id, use, type?, inputs?, as?, spread?, ephemeral?, active?, stop? }
|
|
23
|
-
* | { id, branch: { when, then, else? } }
|
|
24
|
-
* ]
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
27
|
-
*
|
|
28
|
-
* **Output shape** (always v1-compatible internal):
|
|
29
|
-
* ```
|
|
30
|
-
* {
|
|
31
|
-
* name, version, trigger, // method "*" normalized to "ANY"
|
|
32
|
-
* steps: [{ name, node, type, active, stop, set_var, as, spread, ephemeral, ... }],
|
|
33
|
-
* nodes: { [stepName]: { inputs?, conditions? } }
|
|
34
|
-
* }
|
|
35
|
-
* ```
|
|
36
|
-
*
|
|
37
|
-
* **Why a single internal shape?** The runner core (RunnerSteps,
|
|
38
|
-
* Configuration, Blok.run, etc.) is unchanged. Normalization is purely
|
|
39
|
-
* an authoring-layer concern. Old workflows keep running; new authoring
|
|
40
|
-
* shapes get translated transparently.
|
|
41
|
-
*/
|
|
1
|
+
interface RetryConfig {
|
|
2
|
+
maxAttempts: number;
|
|
3
|
+
minTimeoutInMs?: number;
|
|
4
|
+
maxTimeoutInMs?: number;
|
|
5
|
+
factor?: number;
|
|
6
|
+
}
|
|
42
7
|
interface InternalStep {
|
|
43
8
|
name: string;
|
|
44
9
|
node: string;
|
|
@@ -51,6 +16,29 @@ interface InternalStep {
|
|
|
51
16
|
ephemeral?: boolean;
|
|
52
17
|
stream_logs?: boolean;
|
|
53
18
|
flow?: boolean;
|
|
19
|
+
idempotencyKey?: string;
|
|
20
|
+
idempotencyKeyTTL?: number;
|
|
21
|
+
retry?: RetryConfig;
|
|
22
|
+
subworkflow?: string;
|
|
23
|
+
wait?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Tier 2 quick-wins — per-attempt execution timeout. Number (ms) or
|
|
26
|
+
* duration string (`"30s"`, etc.). Configuration thread-through
|
|
27
|
+
* normalizes to milliseconds via `parseDuration`.
|
|
28
|
+
*/
|
|
29
|
+
maxDuration?: number | string;
|
|
30
|
+
/**
|
|
31
|
+
* PR 4 — `wait.for(duration)` / `wait.until(date)` step.
|
|
32
|
+
*
|
|
33
|
+
* Discriminates by `type === "wait"` and the presence of either
|
|
34
|
+
* `waitForMs` (numeric ms after parseDuration) or `waitUntil` (number
|
|
35
|
+
* ms-since-epoch OR string for $-proxy / ISO).
|
|
36
|
+
*
|
|
37
|
+
* `wait?: boolean` above is the sub-workflow `wait: true|false` flag
|
|
38
|
+
* — separate concern, separate field.
|
|
39
|
+
*/
|
|
40
|
+
waitForMs?: number;
|
|
41
|
+
waitUntil?: number | string;
|
|
54
42
|
[key: string]: unknown;
|
|
55
43
|
}
|
|
56
44
|
interface InternalNodeConfig {
|