octaflow 0.16.0 → 0.17.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/README.md CHANGED
@@ -65,11 +65,12 @@ of what it did. Both models are durable — they trade off differently, and
65
65
 
66
66
  A fuller pipeline than the snippet above — dynamic fan-out over two `map` steps at once, a
67
67
  concurrency cap so only 2 of 6 images encode at a time, a flaky step retried, a sub-workflow,
68
- a step suspended on an external event, and a durable sleep. Then a second workflow fails and
69
- its completed steps roll back in reverse.
68
+ a step suspended on an external event under a deadline, a durable sleep, and a branch where
69
+ the arm the review didn't choose is skipped but the join still fires. Then a second workflow
70
+ fails and its completed steps roll back in reverse.
70
71
 
71
72
  <p align="center">
72
- <img src="./docs/demo.svg" alt="Terminal trace of a publishing pipeline: parallel fan-out over locales and images with a concurrency cap, a retried step, a sub-workflow, a suspend-and-resume on an external event, a durable sleep, then a saga rollback in reverse order" width="760">
73
+ <img src="./docs/demo.svg" alt="Terminal trace of a publishing pipeline: parallel fan-out over locales and images with a concurrency cap, a retried step, a sub-workflow, a suspend-and-resume on an external event with a deadline armed, a durable sleep, a skipped branch converging on a join, then a saga rollback in reverse order" width="760">
73
74
  </p>
74
75
 
75
76
  Every line is an engine transition emitted through the [`FlowObserver`](https://octabits-io.github.io/octaflow/running/observability/) seam —
@@ -119,10 +120,12 @@ that the others have earned.
119
120
  Reach for something else if:
120
121
 
121
122
  - **Your control flow is genuinely dynamic.** A declarative DAG is fixed at definition time.
122
- Flow softens this with [`defineMapStep`](https://octabits-io.github.io/octaflow/core/fan-out-and-map/) (runtime-sized fan-out),
123
- [sub-workflows](https://octabits-io.github.io/octaflow/core/sub-workflows/), and [`waitForEvent`](https://octabits-io.github.io/octaflow/core/signals/) — but if your
124
- process is "loop until a human approves, branching on whatever they typed," an imperative
125
- durable function will express it more naturally.
123
+ Flow softens this a lot — [`when` guards and joins](https://octabits-io.github.io/octaflow/core/branching/) (if/else over a static graph),
124
+ [`defineMapStep`](https://octabits-io.github.io/octaflow/core/fan-out-and-map/) (runtime-sized fan-out),
125
+ [sub-workflows](https://octabits-io.github.io/octaflow/core/sub-workflows/), and [`waitForEvent`](https://octabits-io.github.io/octaflow/core/signals/) — but the
126
+ set of steps is still fixed up front. If your process is "**loop** until a human approves,
127
+ branching on whatever they typed," an imperative durable function will express it more
128
+ naturally: Flow can pick a branch, not invent a step.
126
129
  - **You want a UI out of the box.** Flow ships a wire-safe projection
127
130
  ([`toPublicWorkflow`](https://octabits-io.github.io/octaflow/extending/http/)) and lifecycle events, not a
128
131
  dashboard. You build it.
@@ -188,13 +191,18 @@ next to what your steps actually do.
188
191
  | ⚡ | **Auto-parallelism** — dependency-free steps run concurrently; a step starts when all its deps complete |
189
192
  | 🔁 | **Retry & timeout** — per-step `maxAttempts`, fixed/exponential backoff, wall-clock timeout |
190
193
  | 💤 | **Durable sleep** — hold a step in the queue for N ms (survives restarts) |
194
+ | 🔀 | **Conditional branching** — `when` guards skip a step and its branch; `join: 'any'` converges |
195
+ | ⏳ | **Deadlines** — a budget on a suspended step (fail, or continue with a stand-in answer) and on a whole run |
196
+ | ♻️ | **Retry a failed run** — `retryWorkflow` resumes from the failure point; completed steps keep their output |
191
197
  | 🚦 | **Concurrency & rate limiting** — per-step-type caps and token buckets via a pluggable gate |
192
198
  | ⏰ | **Cron / scheduled starts** — fire workflows on a schedule (pg-boss) |
193
199
  | 🔑 | **Start idempotency** — a dedup key collapses double-clicks / overlapping ticks |
194
200
  | 🗺️ | **Dynamic fan-out / map** — spawn one child step per item of a runtime-sized list |
195
- | ⏸️ | **Signals / waitForEvent** — suspend a step until an external event (`resumeStep`) |
201
+ | ⏸️ | **Signals / waitForEvent** — suspend a step until an external event (`resumeStep`), with an optional deadline |
196
202
  | 🪆 | **Sub-workflows** — a step starts a child workflow and awaits its result |
197
203
  | ↩️ | **Saga compensation** — run rollback handlers in reverse order on failure |
204
+ | 🚑 | **Crash recovery** — a step whose worker died is re-queued while its attempt budget lasts |
205
+ | 💓 | **Heartbeats** — a long step proves it is alive, so a dead one is caught in seconds, not minutes |
198
206
  | 🔭 | **Observability** — lifecycle events (run history) + per-step spans, both pluggable |
199
207
  | 🤖 | **AI add-on** — instrumented models, token/cost capture, quota, daily rollups |
200
208
  | 🧱 | **Pluggable everything** — `WorkflowStore`, `Dispatcher`, `StepGate`, `FlowObserver`, hooks |
@@ -240,9 +248,12 @@ Full docs — concepts, every feature, production wiring and the API reference
240
248
  | [Defining steps](https://octabits-io.github.io/octaflow/core/defining-steps/) | `defineStep` and its variants |
241
249
  | [Retry & timeout](https://octabits-io.github.io/octaflow/core/retry-and-timeout/) | attempt budgets, backoff, and how a failure is classified |
242
250
  | [Fan-out & map](https://octabits-io.github.io/octaflow/core/fan-out-and-map/) | one child step per item of a runtime list |
251
+ | [Branching](https://octabits-io.github.io/octaflow/core/branching/) | `when` guards and join rules — if/else over a static DAG |
243
252
  | [Signals](https://octabits-io.github.io/octaflow/core/signals/) · [Sub-workflows](https://octabits-io.github.io/octaflow/core/sub-workflows/) · [Saga](https://octabits-io.github.io/octaflow/core/saga-compensation/) | suspend, nest, and roll back |
253
+ | [Deadlines](https://octabits-io.github.io/octaflow/core/deadlines/) | budgets for a suspended step and for a whole run |
254
+ | [Heartbeats](https://octabits-io.github.io/octaflow/core/heartbeats/) | liveness for long steps, and interrupting one that was cancelled |
244
255
  | [Postgres & pg-boss](https://octabits-io.github.io/octaflow/running/postgres-and-pg-boss/) | production wiring, workers, DLQ, cron |
245
- | [Cancellation & recovery](https://octabits-io.github.io/octaflow/running/cancellation-and-recovery/) | cancelling a run, and sweeping steps a crash left behind |
256
+ | [Cancellation & recovery](https://octabits-io.github.io/octaflow/running/cancellation-and-recovery/) | cancelling a run, sweeping steps a crash left behind, and retrying a failed run |
246
257
  | [Observability](https://octabits-io.github.io/octaflow/running/observability/) · [Live progress](https://octabits-io.github.io/octaflow/running/live-progress/) | lifecycle events, spans, and streaming them to a browser |
247
258
  | [The AI add-on](https://octabits-io.github.io/octaflow/extending/ai/) | token/cost capture, quota, usage rollups |
248
259
  | [Extending](https://octabits-io.github.io/octaflow/extending/interfaces/) | custom stores, dispatchers and gates |
@@ -270,8 +281,10 @@ Runnable, focused examples live in [`examples/`](./examples) — see [`examples/
270
281
  | 12 | `12-postgres-pgboss-production.ts` | full pg store + gate + event sink + pg-boss + cron |
271
282
  | 13 | `13-ai-workflow.ts` | AI add-on (instrumented model + cost) |
272
283
  | 14 | `14-live-progress.ts` | `FlowObserver` → SSE fan-out (build your own dashboard) |
284
+ | 15 | `15-conditional-branching.ts` | `when` guards + a `join: 'any'` convergence |
285
+ | 16 | `16-deadlines-and-retry.ts` | wait deadlines, run deadlines, `retryWorkflow`, heartbeats |
273
286
 
274
- The in-memory examples (01–11) share a small driver, [`examples/runtime.ts`](./examples/runtime.ts),
287
+ The in-memory examples (01–11, 14–16) share a small driver, [`examples/runtime.ts`](./examples/runtime.ts),
275
288
  that builds an engine over the in-memory store and an in-process queue you drain.
276
289
 
277
290
  ---
@@ -1,6 +1,6 @@
1
- import { K as FlowErrorShape, T as RetryPolicy, h as TypedWorkflow, m as TypedStepContext, p as TypedStep, q as Result } from "../index-D_fOYfmf.js";
1
+ import { E as RetryPolicy, g as TypedWorkflow, h as TypedStepContext, m as TypedStep, nt as Result, tt as FlowErrorShape } from "../index-BURAYZui.js";
2
2
  import { n as Logger } from "../logger-BCus3AOa.js";
3
- import { i as BuildStepContextArgs, o as WorkflowHooks } from "../hooks-CDRFQZbT.js";
3
+ import { i as BuildStepContextArgs, o as WorkflowHooks } from "../hooks-BdxIcc92.js";
4
4
  import { z } from "zod";
5
5
  import { EmbeddingModelV4, LanguageModelV4 } from "@ai-sdk/provider";
6
6
  //#region src/ai/instrumented-model.d.ts
package/dist/ai/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { S as ok, b as noopLogger, f as defineStep, l as buildWorkflow, x as err } from "../core-ERYGlYsS.js";
1
+ import { S as ok, b as noopLogger, f as defineStep, l as buildWorkflow, x as err } from "../core-DNWPwFaE.js";
2
2
  import { wrapEmbeddingModel, wrapLanguageModel } from "ai";
3
3
  //#region src/ai/instrumented-model.ts
4
4
  function createUsageAccumulator() {
@@ -171,33 +171,61 @@ const emptyObjectSchema = z.object({});
171
171
  * Validation errors are non-retryable (a schema mismatch is a programming error).
172
172
  */
173
173
  function defineStep(config) {
174
- const { type, workflowInputSchema, outputSchema, dependencies, handler, retry, isRetryable, timeoutMs, delayMs, waitForEvent } = config;
174
+ const { type, workflowInputSchema, outputSchema, dependencies, handler, retry, isRetryable, timeoutMs, delayMs, waitForEvent, join } = config;
175
175
  const deps = dependencies ?? {};
176
- const wrappedHandler = async (ctx) => {
177
- try {
178
- const inputResult = workflowInputSchema.safeParse(ctx.workflowInput);
179
- if (!inputResult.success) return {
176
+ /**
177
+ * Classify a thrown error. Precedence: an explicit marker on the error
178
+ * (following `cause`), then this step's predicate, then the default
179
+ * classifier. `retryableFrom` records which one answered, so the engine's
180
+ * `defaultRetryable` can override a guess without overriding a decision
181
+ * someone actually made.
182
+ */
183
+ const toStepError = (error) => {
184
+ const message = error instanceof Error ? error.message : "Unknown step error";
185
+ const marked = explicitRetryability(error);
186
+ return {
187
+ key: "step_error",
188
+ message,
189
+ retryable: marked ?? (isRetryable ? isRetryable(error) : isRetryableError(error)),
190
+ retryableFrom: marked !== void 0 ? "explicit" : isRetryable ? "predicate" : "heuristic"
191
+ };
192
+ };
193
+ /**
194
+ * Phases 1–3 of the pipeline: validate the workflow input, validate each
195
+ * dependency's output, and assemble the typed context. Shared so the `when`
196
+ * guard decides on exactly the same values the handler would receive.
197
+ */
198
+ const buildTypedContext = (ctx) => {
199
+ const inputResult = workflowInputSchema.safeParse(ctx.workflowInput);
200
+ if (!inputResult.success) return {
201
+ ok: false,
202
+ error: {
203
+ key: "step_error",
204
+ message: `[${type}] Invalid workflow input: ${inputResult.error.message}`,
205
+ retryable: false
206
+ }
207
+ };
208
+ const parsedDeps = {};
209
+ for (const [depKey, depStep] of Object.entries(deps)) {
210
+ const raw = ctx.dependencyOutputs[depKey];
211
+ if (join === "any" && raw === void 0) {
212
+ parsedDeps[depKey] = void 0;
213
+ continue;
214
+ }
215
+ const depResult = depStep.outputSchema.safeParse(raw);
216
+ if (!depResult.success) return {
180
217
  ok: false,
181
218
  error: {
182
219
  key: "step_error",
183
- message: `[${type}] Invalid workflow input: ${inputResult.error.message}`,
220
+ message: `[${type}] Invalid dependency output '${depKey}': ${depResult.error.message}`,
184
221
  retryable: false
185
222
  }
186
223
  };
187
- const parsedDeps = {};
188
- for (const [depKey, depStep] of Object.entries(deps)) {
189
- const depResult = depStep.outputSchema.safeParse(ctx.dependencyOutputs[depKey]);
190
- if (!depResult.success) return {
191
- ok: false,
192
- error: {
193
- key: "step_error",
194
- message: `[${type}] Invalid dependency output '${depKey}': ${depResult.error.message}`,
195
- retryable: false
196
- }
197
- };
198
- parsedDeps[depKey] = depResult.data;
199
- }
200
- const typedCtx = {
224
+ parsedDeps[depKey] = depResult.data;
225
+ }
226
+ return {
227
+ ok: true,
228
+ value: {
201
229
  workflowId: ctx.workflowId,
202
230
  stepId: ctx.stepId,
203
231
  stepKey: ctx.stepKey,
@@ -206,9 +234,16 @@ function defineStep(config) {
206
234
  stepInput: ctx.stepInput,
207
235
  deps: parsedDeps,
208
236
  signal: ctx.signal,
237
+ heartbeat: ctx.heartbeat,
209
238
  context: ctx.context
210
- };
211
- const output = await handler(typedCtx);
239
+ }
240
+ };
241
+ };
242
+ const wrappedHandler = async (ctx) => {
243
+ try {
244
+ const built = buildTypedContext(ctx);
245
+ if (!built.ok) return built;
246
+ const output = await handler(built.value);
212
247
  const outputResult = outputSchema.safeParse(output);
213
248
  if (!outputResult.success) return {
214
249
  ok: false,
@@ -223,19 +258,31 @@ function defineStep(config) {
223
258
  value: outputResult.data
224
259
  };
225
260
  } catch (error) {
226
- const message = error instanceof Error ? error.message : "Unknown step error";
227
- const marked = explicitRetryability(error);
228
261
  return {
229
262
  ok: false,
230
- error: {
231
- key: "step_error",
232
- message,
233
- retryable: marked ?? (isRetryable ? isRetryable(error) : isRetryableError(error)),
234
- retryableFrom: marked !== void 0 ? "explicit" : isRetryable ? "predicate" : "heuristic"
235
- }
263
+ error: toStepError(error)
236
264
  };
237
265
  }
238
266
  };
267
+ let wrappedCondition;
268
+ if (config.when) {
269
+ const when = config.when;
270
+ wrappedCondition = async (ctx) => {
271
+ try {
272
+ const built = buildTypedContext(ctx);
273
+ if (!built.ok) return built;
274
+ return {
275
+ ok: true,
276
+ value: await when(built.value) === true
277
+ };
278
+ } catch (error) {
279
+ return {
280
+ ok: false,
281
+ error: toStepError(error)
282
+ };
283
+ }
284
+ };
285
+ }
239
286
  let wrappedCompensate;
240
287
  if (config.compensate) {
241
288
  const compensate = config.compensate;
@@ -256,6 +303,7 @@ function defineStep(config) {
256
303
  stepInput: ctx.stepInput,
257
304
  deps: parsedDeps,
258
305
  signal: ctx.signal,
306
+ heartbeat: ctx.heartbeat,
259
307
  context: ctx.context,
260
308
  output: out.success ? out.data : ctx.output
261
309
  });
@@ -269,9 +317,14 @@ function defineStep(config) {
269
317
  handler: wrappedHandler,
270
318
  retry,
271
319
  timeoutMs,
320
+ onTimeout: config.onTimeout,
321
+ heartbeatTimeoutMs: config.heartbeatTimeoutMs,
322
+ heartbeat: config.heartbeat,
272
323
  delayMs,
273
324
  waitForEvent,
274
- compensate: wrappedCompensate
325
+ compensate: wrappedCompensate,
326
+ condition: wrappedCondition,
327
+ join
275
328
  };
276
329
  }
277
330
  /**
@@ -281,6 +334,21 @@ function defineStep(config) {
281
334
  * (validated by dependents against `outputSchema`), then the DAG advances.
282
335
  *
283
336
  * Use for human-in-the-loop approvals, webhooks, or any async external dependency.
337
+ *
338
+ * Give it a `timeoutMs` so the wait cannot last forever. What the expiry does is
339
+ * `onTimeout`'s call: `'fail'` (the default) ends the run, while `{ output }`
340
+ * completes the step with a stand-in answer and lets the DAG carry on — which,
341
+ * paired with a `when` guard downstream, is how "approve within 48 hours,
342
+ * otherwise escalate" is expressed:
343
+ *
344
+ * ```ts
345
+ * const approval = defineWaitStep({
346
+ * type: 'await-approval',
347
+ * outputSchema: z.object({ approved: z.boolean() }),
348
+ * timeoutMs: 48 * 60 * 60 * 1000,
349
+ * onTimeout: { output: { approved: false } },
350
+ * });
351
+ * ```
284
352
  */
285
353
  function defineWaitStep(config) {
286
354
  return defineStep({
@@ -289,6 +357,10 @@ function defineWaitStep(config) {
289
357
  outputSchema: config.outputSchema,
290
358
  dependencies: config.dependencies,
291
359
  waitForEvent: true,
360
+ timeoutMs: config.timeoutMs,
361
+ onTimeout: config.onTimeout,
362
+ when: config.when,
363
+ join: config.join,
292
364
  handler: async () => ({})
293
365
  });
294
366
  }
@@ -308,6 +380,7 @@ function defineMapStep(config) {
308
380
  workflowInputSchema: config.workflowInputSchema,
309
381
  outputSchema: z.object({ items: z.array(z.unknown()) }),
310
382
  dependencies: config.dependencies,
383
+ when: config.when,
311
384
  handler: async (ctx) => ({ items: await config.items(ctx) })
312
385
  });
313
386
  const child = defineStep({
@@ -317,6 +390,7 @@ function defineMapStep(config) {
317
390
  retry: config.itemRetry,
318
391
  isRetryable: config.itemIsRetryable,
319
392
  timeoutMs: config.itemTimeoutMs,
393
+ heartbeatTimeoutMs: config.itemHeartbeatTimeoutMs,
320
394
  handler: async (ctx) => config.each(ctx.stepInput.item, {
321
395
  index: Number(ctx.stepInput.index ?? 0),
322
396
  context: ctx.context,
@@ -333,7 +407,8 @@ function defineMapStep(config) {
333
407
  type: childType,
334
408
  handler: child.handler,
335
409
  retry: child.retry,
336
- timeoutMs: child.timeoutMs
410
+ timeoutMs: child.timeoutMs,
411
+ heartbeatTimeoutMs: child.heartbeatTimeoutMs
337
412
  }
338
413
  };
339
414
  }
@@ -355,6 +430,7 @@ function defineSubWorkflowStep(config) {
355
430
  workflowInputSchema: config.workflowInputSchema,
356
431
  outputSchema: recordSchema,
357
432
  dependencies: config.dependencies,
433
+ when: config.when,
358
434
  handler: async (ctx) => config.input(ctx)
359
435
  }),
360
436
  outputSchema: config.outputSchema ?? recordSchema,
@@ -409,18 +485,24 @@ function buildWorkflow(config) {
409
485
  registry.register(step.type, step.handler, {
410
486
  retry: step.retry,
411
487
  timeoutMs: step.timeoutMs,
488
+ onTimeout: step.onTimeout,
489
+ heartbeatTimeoutMs: step.heartbeatTimeoutMs,
490
+ heartbeat: step.heartbeat,
412
491
  delayMs: step.delayMs,
413
492
  waitForEvent: step.waitForEvent,
414
493
  map: step.map,
415
494
  childType: step.childType,
416
495
  subWorkflowDefinition: step.subWorkflowDefinition,
417
- compensate: step.compensate
496
+ compensate: step.compensate,
497
+ condition: step.condition,
498
+ join: step.join
418
499
  });
419
500
  if (step.childRegistration) {
420
501
  const c = step.childRegistration;
421
502
  registry.register(c.type, c.handler, {
422
503
  retry: c.retry,
423
- timeoutMs: c.timeoutMs
504
+ timeoutMs: c.timeoutMs,
505
+ heartbeatTimeoutMs: c.heartbeatTimeoutMs
424
506
  });
425
507
  }
426
508
  if (step.subWorkflowRegister) step.subWorkflowRegister(registry);
@@ -441,7 +523,7 @@ function buildWorkflow(config) {
441
523
  * The engine's records ({@link WorkflowRecord}, {@link StepRecord}) are the
442
524
  * store's data model: they carry fields that must not leak onto a public API
443
525
  * (`partitionKey`, `idempotencyKey`, sub-workflow linkage, `metadata`, retry
444
- * `attempts`) and step statuses that are engine mechanics rather than display
526
+ * `attempts`, heartbeats) and step statuses that are engine mechanics rather than display
445
527
  * states. Serving them verbatim also couples the consumer's API contract to
446
528
  * flow's record shape. This module owns the boundary instead:
447
529
  *
@@ -531,8 +613,8 @@ const PUBLIC_WORKFLOW_SCHEMA = z.object({
531
613
  });
532
614
  /**
533
615
  * Project a step record to its public view. Drops `workflowId` (redundant
534
- * under the parent), `metadata`, `attempts`, and `parentStepId`; folds the
535
- * status.
616
+ * under the parent), `metadata`, `attempts`, `heartbeatAt`, and `parentStepId`;
617
+ * folds the status.
536
618
  */
537
619
  function toPublicStep(step) {
538
620
  return {
@@ -1,11 +1,24 @@
1
- import { F as StepId, K as FlowErrorShape, V as WorkflowId, q as Result } from "./index-D_fOYfmf.js";
1
+ import { L as StepId, W as WorkflowId, nt as Result, tt as FlowErrorShape } from "./index-BURAYZui.js";
2
2
  //#region src/core/dispatcher.d.ts
3
+ /**
4
+ * What a step job is for. A queue carries two kinds of work for a step, and
5
+ * they differ only in this field:
6
+ *
7
+ * - `'execute'` (default) — run the step. Every job before wait deadlines
8
+ * existed was this, which is why it is also what a payload without a `kind`
9
+ * means: an in-flight job enqueued by an older version still routes correctly.
10
+ * - `'timeout'` — the step's wait budget has elapsed; settle it if it is still
11
+ * suspended. Delivered by the same durable delay that powers a sleep step.
12
+ */
13
+ type DispatchKind = 'execute' | 'timeout';
3
14
  /** Payload handed to the dispatcher to schedule a single step for execution. */
4
15
  interface DispatchStepPayload {
5
16
  workflowId: WorkflowId;
6
17
  stepId: StepId;
7
18
  stepKey: string;
8
19
  stepType: string;
20
+ /** Defaults to `'execute'` when absent. */
21
+ kind?: DispatchKind;
9
22
  }
10
23
  interface EnqueueOptions {
11
24
  /**
@@ -34,4 +47,4 @@ interface Dispatcher {
34
47
  enqueueStepIn?(handle: unknown, payload: DispatchStepPayload, options?: EnqueueOptions): Promise<Result<void, FlowErrorShape>>;
35
48
  }
36
49
  //#endregion
37
- export { Dispatcher as n, EnqueueOptions as r, DispatchStepPayload as t };
50
+ export { EnqueueOptions as i, DispatchStepPayload as n, Dispatcher as r, DispatchKind as t };
@@ -1,13 +1,13 @@
1
- import "../index-D_fOYfmf.js";
1
+ import "../index-BURAYZui.js";
2
2
  import { n as Logger } from "../logger-BCus3AOa.js";
3
- import { n as Dispatcher } from "../dispatcher-CnMTwfCc.js";
3
+ import { r as Dispatcher } from "../dispatcher-Cbb-KjY_.js";
4
4
  import { z } from "zod";
5
5
  import { PgBoss } from "pg-boss";
6
6
  //#region src/dispatcher-pgboss/payload.d.ts
7
7
  /**
8
8
  * The wire payload for a step job. It is the flow-core `DispatchStepPayload`
9
9
  * plus the `partitionKey` — the worker needs the partition to reconstruct a
10
- * partition-scoped engine before calling `executeStep`.
10
+ * partition-scoped engine before handing the job to `engine.handleStepJob`.
11
11
  */
12
12
  declare const WIRE_STEP_PAYLOAD_SCHEMA: z.ZodObject<{
13
13
  partitionKey: z.ZodString;
@@ -15,6 +15,10 @@ declare const WIRE_STEP_PAYLOAD_SCHEMA: z.ZodObject<{
15
15
  stepId: z.ZodNumber;
16
16
  stepKey: z.ZodString;
17
17
  stepType: z.ZodString;
18
+ kind: z.ZodDefault<z.ZodEnum<{
19
+ execute: "execute";
20
+ timeout: "timeout";
21
+ }>>;
18
22
  }, z.core.$strip>;
19
23
  type WireStepPayload = z.infer<typeof WIRE_STEP_PAYLOAD_SCHEMA>;
20
24
  interface StepQueueConfig {
@@ -3,14 +3,20 @@ import { z } from "zod";
3
3
  /**
4
4
  * The wire payload for a step job. It is the flow-core `DispatchStepPayload`
5
5
  * plus the `partitionKey` — the worker needs the partition to reconstruct a
6
- * partition-scoped engine before calling `executeStep`.
6
+ * partition-scoped engine before handing the job to `engine.handleStepJob`.
7
7
  */
8
8
  const WIRE_STEP_PAYLOAD_SCHEMA = z.object({
9
9
  partitionKey: z.string().min(1),
10
10
  workflowId: z.number().int().positive(),
11
11
  stepId: z.number().int().positive(),
12
12
  stepKey: z.string().min(1),
13
- stepType: z.string().min(1)
13
+ stepType: z.string().min(1),
14
+ /**
15
+ * What the job asks for: run the step, or settle it because its wait deadline
16
+ * elapsed. Defaulted rather than required, so a job enqueued before wait
17
+ * deadlines existed still parses — and still means "run the step".
18
+ */
19
+ kind: z.enum(["execute", "timeout"]).default("execute")
14
20
  });
15
21
  const DEFAULT_STEP_QUEUE_CONFIG = {
16
22
  retryLimit: 2,
@@ -100,8 +106,9 @@ function createPgBossDispatcher(deps) {
100
106
  async function send(payload, options, handle) {
101
107
  try {
102
108
  const wire = {
103
- partitionKey,
104
- ...payload
109
+ kind: "execute",
110
+ ...payload,
111
+ partitionKey
105
112
  };
106
113
  const parsed = WIRE_STEP_PAYLOAD_SCHEMA.safeParse(wire);
107
114
  if (!parsed.success) return {
@@ -1,4 +1,4 @@
1
- import { B as WorkflowDefinition, E as StartOptions, F as StepId, I as StepRecord, K as FlowErrorShape, U as WorkflowRecord, V as WorkflowId, q as Result } from "./index-D_fOYfmf.js";
1
+ import { D as StartOptions, L as StepId, R as StepRecord, U as WorkflowDefinition, W as WorkflowId, nt as Result, q as WorkflowRecord, tt as FlowErrorShape } from "./index-BURAYZui.js";
2
2
  //#region src/core/hooks.d.ts
3
3
  interface BeforeStartArgs {
4
4
  definition: WorkflowDefinition;