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 +23 -10
- package/dist/ai/index.d.ts +2 -2
- package/dist/ai/index.js +1 -1
- package/dist/{core-ERYGlYsS.js → core-DNWPwFaE.js} +119 -37
- package/dist/{dispatcher-CnMTwfCc.d.ts → dispatcher-Cbb-KjY_.d.ts} +15 -2
- package/dist/dispatcher-pgboss/index.d.ts +7 -3
- package/dist/dispatcher-pgboss/index.js +11 -4
- package/dist/{hooks-CDRFQZbT.d.ts → hooks-BdxIcc92.d.ts} +1 -1
- package/dist/{index-D_fOYfmf.d.ts → index-BURAYZui.d.ts} +259 -13
- package/dist/index.d.ts +37 -9
- package/dist/index.js +762 -129
- package/dist/{observability-3tcmRwIw.d.ts → observability-B5KgSJfF.d.ts} +77 -6
- package/dist/store-pg/index.d.ts +2 -2
- package/dist/store-pg/index.js +64 -7
- package/package.json +4 -4
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
|
|
69
|
-
|
|
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
|
|
123
|
-
[
|
|
124
|
-
|
|
125
|
-
|
|
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,
|
|
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
|
---
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
220
|
+
message: `[${type}] Invalid dependency output '${depKey}': ${depResult.error.message}`,
|
|
184
221
|
retryable: false
|
|
185
222
|
}
|
|
186
223
|
};
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
|
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`;
|
|
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 {
|
|
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 {
|
|
50
|
+
export { EnqueueOptions as i, DispatchStepPayload as n, Dispatcher as r, DispatchKind as t };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import "../index-
|
|
1
|
+
import "../index-BURAYZui.js";
|
|
2
2
|
import { n as Logger } from "../logger-BCus3AOa.js";
|
|
3
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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;
|