octaflow 0.38.0 → 0.40.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 +5 -2
- package/dist/{core-COMWflJl.js → core-ut8VZ0sU.js} +35 -11
- package/dist/{dispatcher-jbFVHfdm.d.ts → dispatcher-BnlAJuu3.d.ts} +1 -5
- package/dist/document/index.d.ts +12 -20
- package/dist/document/index.js +30 -25
- package/dist/drizzle/index.d.ts +1 -3
- package/dist/drizzle/index.js +1 -2
- package/dist/{gate-BBZK5OgY.js → gate-BcjK7FoL.js} +2 -4
- package/dist/{index-BlwtmRJP.d.ts → index-BBO5BGS8.d.ts} +28 -13
- package/dist/index.d.ts +16 -15
- package/dist/index.js +101 -16
- package/dist/{observability-BgUtESlj.d.ts → observability-C-wtcDDo.d.ts} +1 -5
- package/dist/pg-boss/index.d.ts +3 -9
- package/dist/pg-boss/index.js +3 -6
- package/dist/postgres/index.d.ts +4 -14
- package/dist/postgres/index.js +26 -12
- package/dist/{result-CG_l7xQm.js → result-CxvNy9B6.js} +1 -2
- package/dist/{store-BPHVI_a3.d.ts → store-oCpqAuTT.d.ts} +23 -3
- package/package.json +2 -2
- package/skills/add-a-case-loop/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -214,6 +214,8 @@ next to what your steps actually do.
|
|
|
214
214
|
| ↩️ | **Saga compensation** — run rollback handlers in reverse order on failure |
|
|
215
215
|
| 🚑 | **Crash recovery** — a step whose worker died is re-queued while its attempt budget lasts |
|
|
216
216
|
| 💓 | **Heartbeats** — a long step proves it is alive, so a dead one is caught in seconds, not minutes |
|
|
217
|
+
| 📌 | **Step checkpoints** — `ctx.checkpoint`: a step keeps what it has done so far (`save`), and the attempt after a crash reads it (`value`) and goes on from there; written only by the attempt that owns the step |
|
|
218
|
+
| ⏭️ | **Continue later** — a handler throws `continueStep(afterMs)` to stop now and run again in a new invocation, without spending an attempt: work in slices on a host with a hard request ceiling, going on from `ctx.checkpoint` |
|
|
217
219
|
| 🔭 | **Observability** — lifecycle events (run history), per-step spans, and `createMeterObserver` turning the events into OpenTelemetry-style metrics — all pluggable, all structural |
|
|
218
220
|
| 📄 | **Workflow documents** (`octaflow/document`) — a workflow as JSON: a template a tenant saves or a model drafts, checked by a compiler (types, expressions, bounded waits, roles, no outward step without a person's approval) and run on one registration per kind, with no deploy per template. See [Workflow documents](#workflow-documents) |
|
|
219
221
|
| 🤖 | **The model layer** — AI steps with token and cost capture, quotas, the model decider, agents that end by choosing an exit, case loops, managed providers and model runners: [`@octabits-io/models`](../models/README.md) (`./octaflow` is its binding to this engine; it was `octaflow/ai` until 0.36) |
|
|
@@ -263,8 +265,9 @@ decimals (`decimal(input.amount)`) as types of their own.
|
|
|
263
265
|
| `repeat` | its steps pass after pass while a condition holds, up to a cap — one step of one run |
|
|
264
266
|
| `flow` | groups steps, or runs another document (`ref`, pinned to a revision) as a sub-workflow |
|
|
265
267
|
|
|
266
|
-
A host adds its own kinds; a kind can check its own params
|
|
267
|
-
|
|
268
|
+
A host adds its own kinds; a kind can check its own params (`check`, whose issues are `params`
|
|
269
|
+
unless one says `approval_before_impact` — an agent kind given an outward tool set, say), name
|
|
270
|
+
the expressions it keeps (`reads`), or stand for several steps (`expand`). Policy is reviewed between revisions:
|
|
268
271
|
`reviewWorkflowRevision` lists what changed in it, and `checkPublication` wants someone other
|
|
269
272
|
than the author to approve a change. The format and its decisions are in
|
|
270
273
|
[docs/workflow-documents.md](./docs/workflow-documents.md).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
2
|
+
// src/core/retry.ts
|
|
3
3
|
/**
|
|
4
4
|
* Realm-safe marker for an explicit retryability decision. `Symbol.for` so a
|
|
5
5
|
* duplicated copy of this module still reads markers set by the other copy.
|
|
@@ -133,8 +133,30 @@ function isRetryableError(error) {
|
|
|
133
133
|
if (message.includes("503") || message.includes("service unavailable")) return true;
|
|
134
134
|
return false;
|
|
135
135
|
}
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
const CONTINUE = Symbol.for("octaflow.continue");
|
|
137
|
+
/**
|
|
138
|
+
* Throw this from a handler to stop now and run again later, without spending
|
|
139
|
+
* an attempt: the engine puts the step back to `pending` and re-queues it
|
|
140
|
+
* after `afterMs`. For a handler that works in slices of wall-clock time — a
|
|
141
|
+
* host with a hard request ceiling — and keeps its progress in
|
|
142
|
+
* `ctx.checkpoint`, so the next run goes on from there. Nothing caps how
|
|
143
|
+
* often a step continues; the handler's own limits must.
|
|
144
|
+
*/
|
|
145
|
+
function continueStep(afterMs = 0, message = "the step continues in its next slice") {
|
|
146
|
+
const error = new Error(message);
|
|
147
|
+
Object.defineProperty(error, CONTINUE, {
|
|
148
|
+
value: Math.max(0, afterMs),
|
|
149
|
+
enumerable: false
|
|
150
|
+
});
|
|
151
|
+
return error;
|
|
152
|
+
}
|
|
153
|
+
/** The delay a {@link continueStep} error asks for, or `undefined` for any other error. */
|
|
154
|
+
function continuationOf(error) {
|
|
155
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
156
|
+
const afterMs = error[CONTINUE];
|
|
157
|
+
return typeof afterMs === "number" ? afterMs : void 0;
|
|
158
|
+
}
|
|
159
|
+
// src/core/defineStep.ts
|
|
138
160
|
/**
|
|
139
161
|
* `z.object({})` typed as the empty-record schema. Zod infers `{}` for an empty object schema,
|
|
140
162
|
* but we want `Record<string, never>` for input/output-less steps — hence the bridge cast.
|
|
@@ -172,11 +194,15 @@ function defineStep(config) {
|
|
|
172
194
|
const toStepError = (error) => {
|
|
173
195
|
const message = error instanceof Error ? error.message : "Unknown step error";
|
|
174
196
|
const marked = explicitRetryability(error);
|
|
197
|
+
const retryable = marked ?? (isRetryable ? isRetryable(error) : isRetryableError(error));
|
|
198
|
+
const retryableFrom = marked !== void 0 ? "explicit" : isRetryable ? "predicate" : "heuristic";
|
|
199
|
+
const continueAfterMs = continuationOf(error);
|
|
175
200
|
return {
|
|
176
201
|
key: "step_error",
|
|
177
202
|
message,
|
|
178
|
-
retryable
|
|
179
|
-
retryableFrom
|
|
203
|
+
retryable,
|
|
204
|
+
retryableFrom,
|
|
205
|
+
...continueAfterMs !== void 0 && { continueAfterMs }
|
|
180
206
|
};
|
|
181
207
|
};
|
|
182
208
|
/**
|
|
@@ -224,6 +250,7 @@ function defineStep(config) {
|
|
|
224
250
|
deps: parsedDeps,
|
|
225
251
|
signal: ctx.signal,
|
|
226
252
|
heartbeat: ctx.heartbeat,
|
|
253
|
+
checkpoint: ctx.checkpoint,
|
|
227
254
|
context: ctx.context
|
|
228
255
|
}
|
|
229
256
|
};
|
|
@@ -525,8 +552,7 @@ function buildWorkflow(config) {
|
|
|
525
552
|
}
|
|
526
553
|
};
|
|
527
554
|
}
|
|
528
|
-
|
|
529
|
-
//#region src/core/decision.ts
|
|
555
|
+
// src/core/decision.ts
|
|
530
556
|
/**
|
|
531
557
|
* A decider over a plain function: every answer has confidence 1, so it never
|
|
532
558
|
* asks for review. For rules you can state, and for tests.
|
|
@@ -743,8 +769,7 @@ function defineDecisionOutcomeStep(config) {
|
|
|
743
769
|
}
|
|
744
770
|
});
|
|
745
771
|
}
|
|
746
|
-
|
|
747
|
-
//#region src/core/public-view.ts
|
|
772
|
+
// src/core/public-view.ts
|
|
748
773
|
/**
|
|
749
774
|
* Public wire view of a workflow — the projection every HTTP consumer was
|
|
750
775
|
* hand-writing.
|
|
@@ -881,5 +906,4 @@ function toPublicWorkflow(workflow) {
|
|
|
881
906
|
completedAt: workflow.completedAt
|
|
882
907
|
};
|
|
883
908
|
}
|
|
884
|
-
|
|
885
|
-
export { nonRetryableError as C, markRetryable as S, defineStep as _, WORKFLOW_STATUS_SCHEMA as a, explicitRetryability as b, toPublicWorkflow as c, defineDecisionStep as d, functionDecider as f, defineSleepStep as g, defineMapStep as h, STEP_DISPLAY_STATUS_SCHEMA as i, defineDecisionOutcomeStep as l, buildWorkflow as m, PUBLIC_WORKFLOW_STEP_SCHEMA as n, toDisplayStepStatus as o, resolveDecision as p, STEP_DISPLAY_STATUS as r, toPublicStep as s, PUBLIC_WORKFLOW_SCHEMA as t, defineDecisionReviewStep as u, defineSubWorkflowStep as v, retryableError as w, isRetryableError as x, defineWaitStep as y };
|
|
909
|
+
export { isRetryableError as C, retryableError as E, explicitRetryability as S, nonRetryableError as T, defineStep as _, WORKFLOW_STATUS_SCHEMA as a, continuationOf as b, toPublicWorkflow as c, defineDecisionStep as d, functionDecider as f, defineSleepStep as g, defineMapStep as h, STEP_DISPLAY_STATUS_SCHEMA as i, defineDecisionOutcomeStep as l, buildWorkflow as m, PUBLIC_WORKFLOW_STEP_SCHEMA as n, toDisplayStepStatus as o, resolveDecision as p, STEP_DISPLAY_STATUS as r, toPublicStep as s, PUBLIC_WORKFLOW_SCHEMA as t, defineDecisionReviewStep as u, defineSubWorkflowStep as v, markRetryable as w, continueStep as x, defineWaitStep as y };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/core/logger.d.ts
|
|
1
|
+
import { Et as Result, Tt as FlowErrorShape, it as StepId, pt as WorkflowId } from "./index-BBO5BGS8.js";
|
|
3
2
|
type LogAttributes = Record<string, unknown>;
|
|
4
3
|
interface Logger {
|
|
5
4
|
info(message: string, attributes?: LogAttributes): void;
|
|
@@ -7,8 +6,6 @@ interface Logger {
|
|
|
7
6
|
error(message: string, error?: Error, attributes?: LogAttributes): void;
|
|
8
7
|
}
|
|
9
8
|
declare const noopLogger: Logger;
|
|
10
|
-
//#endregion
|
|
11
|
-
//#region src/core/dispatcher.d.ts
|
|
12
9
|
/**
|
|
13
10
|
* What a step job is for. A queue carries two kinds of work for a step, and
|
|
14
11
|
* they differ only in this field:
|
|
@@ -69,5 +66,4 @@ interface Dispatcher {
|
|
|
69
66
|
*/
|
|
70
67
|
enqueueStepIn?(handle: unknown, payload: DispatchStepPayload, options?: EnqueueOptions): Promise<Result<void, FlowErrorShape>>;
|
|
71
68
|
}
|
|
72
|
-
//#endregion
|
|
73
69
|
export { LogAttributes as a, EnqueueOptions as i, DispatchStepPayload as n, Logger as o, Dispatcher as r, noopLogger as s, DispatchKind as t };
|
package/dist/document/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { f as WorkflowStore } from "../store-
|
|
1
|
+
import { $ as StepExecutionContext, Et as Result, H as FlowError, W as RetryPolicy, at as StepPolicy, ft as WorkflowDefinition, pt as WorkflowId, rt as StepHandlerRegistry } from "../index-BBO5BGS8.js";
|
|
2
|
+
import { f as WorkflowStore } from "../store-oCpqAuTT.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
//#region src/document/schema.d.ts
|
|
5
4
|
/** A schema of the subset, or one the checker derived. `{}` is "anything". */
|
|
6
5
|
type JsonSchema = {
|
|
7
6
|
type?: 'object' | 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'null' | 'duration' | 'decimal';
|
|
@@ -29,8 +28,6 @@ export declare function checkSchemaSubset(schema: unknown, path: string): Pathed
|
|
|
29
28
|
* Cached by the schema's text, so a document's steps share one validator each.
|
|
30
29
|
*/
|
|
31
30
|
export declare function zodFor(schema: JsonSchema): z.ZodType;
|
|
32
|
-
//#endregion
|
|
33
|
-
//#region src/document/format.d.ts
|
|
34
31
|
export declare const WORKFLOW_DOCUMENT_FORMAT: 'octaflow.workflow';
|
|
35
32
|
export declare const WORKFLOW_DOCUMENT_VERSION: 1;
|
|
36
33
|
interface DocumentStep {
|
|
@@ -48,6 +45,14 @@ interface DocumentStep {
|
|
|
48
45
|
onFailure?: 'fail' | {
|
|
49
46
|
output: Record<string, unknown>;
|
|
50
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* For `map` only: what an item whose run is cancelled counts as — an item withdrawn on purpose
|
|
50
|
+
* (`cancelWorkflow` on its run). The stand-in takes its slot of `items` and the map goes on with
|
|
51
|
+
* the others. Without it a cancelled item fails the map; an item whose run *fails* fails it either way.
|
|
52
|
+
*/
|
|
53
|
+
itemOnCancel?: {
|
|
54
|
+
output: Record<string, unknown>;
|
|
55
|
+
};
|
|
51
56
|
retry?: {
|
|
52
57
|
maxAttempts: number;
|
|
53
58
|
backoff?: 'fixed' | 'exponential';
|
|
@@ -92,8 +97,6 @@ export declare const workflowDocumentSchema: z.ZodObject<{
|
|
|
92
97
|
}, z.core.$strict>;
|
|
93
98
|
type WorkflowDocument = z.infer<typeof workflowDocumentSchema>;
|
|
94
99
|
type DocumentRole = z.infer<typeof roleSchema>;
|
|
95
|
-
//#endregion
|
|
96
|
-
//#region src/document/kinds.d.ts
|
|
97
100
|
/** Who acts in a step of this kind; a step's `role` must allow it. */
|
|
98
101
|
type KindActor = 'agent' | 'person' | 'system';
|
|
99
102
|
/**
|
|
@@ -132,6 +135,8 @@ type KindCheck = (args: {
|
|
|
132
135
|
}) => Array<{
|
|
133
136
|
path: string;
|
|
134
137
|
message: string;
|
|
138
|
+
/** Default `params`; `approval_before_impact` for a step that would act outward with no person's approval. */
|
|
139
|
+
code?: 'params' | 'approval_before_impact';
|
|
135
140
|
}>;
|
|
136
141
|
interface KindHandlerArgs<TParams, TContext> {
|
|
137
142
|
/** The step's params, every binding resolved, parsed by the kind's schema. */
|
|
@@ -229,8 +234,6 @@ export declare const subflowKind: WorkflowKind<unknown>;
|
|
|
229
234
|
*/
|
|
230
235
|
export declare const decideKind: WorkflowKind<unknown>;
|
|
231
236
|
export declare const coreKinds: readonly WorkflowKind[];
|
|
232
|
-
//#endregion
|
|
233
|
-
//#region src/document/compile.d.ts
|
|
234
237
|
interface CompileIssue extends PathedIssue {
|
|
235
238
|
code: CompileIssueCode;
|
|
236
239
|
}
|
|
@@ -287,8 +290,6 @@ interface InvalidWorkflowDocumentError {
|
|
|
287
290
|
issues: CompileIssue[];
|
|
288
291
|
}
|
|
289
292
|
export declare function compileWorkflowDocument(raw: unknown, kinds: readonly WorkflowKind<any>[], options?: CompileOptions): Result<CompiledWorkflow, InvalidWorkflowDocumentError>;
|
|
290
|
-
//#endregion
|
|
291
|
-
//#region src/document/runtime.d.ts
|
|
292
293
|
/**
|
|
293
294
|
* The spec of a document step, from its record's input — what a model resolver
|
|
294
295
|
* reads to pick the model for a step's `role` (`spec.params.role`). Null for a
|
|
@@ -340,8 +341,6 @@ export declare function resumeDocumentStep(engine: ResumableEngine, compiled: Pi
|
|
|
340
341
|
export declare function resumeWaitingStep(engine: ResumableEngine, store: WorkflowStore, workflowId: WorkflowId, stepKey: string, answer: Record<string, unknown>): Promise<Result<void, ResumeDocumentStepError>>;
|
|
341
342
|
/** The run's result, from its document's `result` expressions over the steps' outputs. */
|
|
342
343
|
export declare function documentResult(store: WorkflowStore, compiled: Pick<CompiledWorkflow, 'result'>, workflowId: WorkflowId): Promise<Record<string, unknown> | null>;
|
|
343
|
-
//#endregion
|
|
344
|
-
//#region src/document/expr.d.ts
|
|
345
344
|
type Expr = {
|
|
346
345
|
kind: 'literal';
|
|
347
346
|
value: string | number | boolean | null;
|
|
@@ -384,8 +383,6 @@ interface CheckResult {
|
|
|
384
383
|
* that never fit. "Anything" passes, since it might.
|
|
385
384
|
*/
|
|
386
385
|
export declare function checkExpr(e: Expr, scope: Record<string, JsonSchema>): CheckResult;
|
|
387
|
-
//#endregion
|
|
388
|
-
//#region src/document/duration.d.ts
|
|
389
386
|
/** A duration as an expression value: tagged, so it never passes for a number. */
|
|
390
387
|
interface Duration {
|
|
391
388
|
readonly $duration: number;
|
|
@@ -394,8 +391,6 @@ export declare function duration(ms: number): Duration;
|
|
|
394
391
|
export declare function isDuration(value: unknown): value is Duration;
|
|
395
392
|
/** An ISO 8601 duration (`P2D`, `PT4H30M`) in ms, or null when it isn't one this format accepts. */
|
|
396
393
|
export declare function parseIsoDuration(text: string): number | null;
|
|
397
|
-
//#endregion
|
|
398
|
-
//#region src/document/decimal.d.ts
|
|
399
394
|
/** A decimal as an expression value: tagged, so it never passes for a number or a string. */
|
|
400
395
|
interface Decimal {
|
|
401
396
|
readonly $decimal: string;
|
|
@@ -404,8 +399,6 @@ export declare const DIVISION_SCALE = 9;
|
|
|
404
399
|
export declare function isDecimal(value: unknown): value is Decimal;
|
|
405
400
|
/** A decimal from a decimal string or a finite number; null for anything else. */
|
|
406
401
|
export declare function toDecimal(value: unknown): Decimal | null;
|
|
407
|
-
//#endregion
|
|
408
|
-
//#region src/document/policy.d.ts
|
|
409
402
|
/** One piece of policy, by where it sits in the document. */
|
|
410
403
|
interface PolicyItem {
|
|
411
404
|
path: string;
|
|
@@ -467,5 +460,4 @@ export declare function checkPublication(review: RevisionReview, people: {
|
|
|
467
460
|
author: string;
|
|
468
461
|
approvedBy: readonly string[];
|
|
469
462
|
}): Result<void, PublicationRefused>;
|
|
470
|
-
//#endregion
|
|
471
463
|
export type { CheckResult, CompileIssue, CompileIssueCode, CompileOptions, CompiledStepSpec, CompiledWorkflow, Decimal, DocumentRole, DocumentStep, Duration, Expr, InvalidWorkflowDocumentError, JsonSchema, KindActor, KindCheck, KindConfig, KindEffect, KindHandlerArgs, KindOutput, PathedIssue, PolicyChange, PolicyItem, PublicationRefused, ResumeDocumentStepError, RevisionReview, WaitingDocumentStep, WorkflowDocument, WorkflowKind };
|
package/dist/document/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as ok, t as err } from "../result-
|
|
2
|
-
import {
|
|
1
|
+
import { n as ok, t as err } from "../result-CxvNy9B6.js";
|
|
2
|
+
import { C as isRetryableError, S as explicitRetryability, b as continuationOf } from "../core-ut8VZ0sU.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
|
|
4
|
+
// src/document/duration.ts
|
|
5
5
|
function duration(ms) {
|
|
6
6
|
return { $duration: ms };
|
|
7
7
|
}
|
|
@@ -25,8 +25,7 @@ const DURATION_UNITS = {
|
|
|
25
25
|
hours: HOUR,
|
|
26
26
|
minutes: MINUTE
|
|
27
27
|
};
|
|
28
|
-
|
|
29
|
-
//#region src/document/decimal.ts
|
|
28
|
+
// src/document/decimal.ts
|
|
30
29
|
const DIVISION_SCALE = 9;
|
|
31
30
|
const PATTERN = /^(-)?(\d+)(?:\.(\d+))?$/;
|
|
32
31
|
function scaled(d) {
|
|
@@ -109,8 +108,7 @@ function decimalCompare(a, b) {
|
|
|
109
108
|
const [x, y] = align(scaled(a), scaled(b));
|
|
110
109
|
return x < y ? -1 : x > y ? 1 : 0;
|
|
111
110
|
}
|
|
112
|
-
|
|
113
|
-
//#region src/document/schema.ts
|
|
111
|
+
// src/document/schema.ts
|
|
114
112
|
const TYPES = /* @__PURE__ */ new Set([
|
|
115
113
|
"object",
|
|
116
114
|
"string",
|
|
@@ -290,8 +288,7 @@ function assignable(actual, expected) {
|
|
|
290
288
|
if (expected.type === "array" && actual.items && expected.items) return assignable(actual.items, expected.items);
|
|
291
289
|
return true;
|
|
292
290
|
}
|
|
293
|
-
|
|
294
|
-
//#region src/document/expr.ts
|
|
291
|
+
// src/document/expr.ts
|
|
295
292
|
var ExprSyntaxError = class extends Error {};
|
|
296
293
|
const KEYWORDS = /* @__PURE__ */ new Set([
|
|
297
294
|
"if",
|
|
@@ -853,8 +850,7 @@ function check(e, scope, errors) {
|
|
|
853
850
|
}
|
|
854
851
|
}
|
|
855
852
|
}
|
|
856
|
-
|
|
857
|
-
//#region src/document/format.ts
|
|
853
|
+
// src/document/format.ts
|
|
858
854
|
const WORKFLOW_DOCUMENT_FORMAT = "octaflow.workflow";
|
|
859
855
|
const WORKFLOW_DOCUMENT_VERSION = 1;
|
|
860
856
|
const keySchema = z.string().regex(/^[A-Za-z][A-Za-z0-9_-]*$/, "a key starts with a letter and holds letters, digits, \"_\" and \"-\"");
|
|
@@ -871,6 +867,7 @@ const documentStepSchema = z.lazy(() => z.strictObject({
|
|
|
871
867
|
timeout: z.string().optional(),
|
|
872
868
|
onTimeout: standIn.optional(),
|
|
873
869
|
onFailure: standIn.optional(),
|
|
870
|
+
itemOnCancel: z.strictObject({ output: z.record(z.string(), z.unknown()) }).optional(),
|
|
874
871
|
retry: z.strictObject({
|
|
875
872
|
maxAttempts: z.number().int().min(1).max(20),
|
|
876
873
|
backoff: z.enum(["fixed", "exponential"]).optional(),
|
|
@@ -903,8 +900,7 @@ const workflowDocumentSchema = z.strictObject({
|
|
|
903
900
|
/** Positions and other presentation; the compiler never reads it. */
|
|
904
901
|
ui: z.record(z.string(), z.unknown()).optional()
|
|
905
902
|
});
|
|
906
|
-
|
|
907
|
-
//#region src/document/compile.ts
|
|
903
|
+
// src/document/compile.ts
|
|
908
904
|
/** The key under which a compiled step's spec rides in its static input. */
|
|
909
905
|
const SPEC_KEY = "$workflowDocument";
|
|
910
906
|
/** The step type a kind runs under; a merge (`join: "any"`) is its own type, since the join is the type's. */
|
|
@@ -995,6 +991,7 @@ function compileWorkflowDocument(raw, kinds, options = {}) {
|
|
|
995
991
|
"timeout",
|
|
996
992
|
"onTimeout",
|
|
997
993
|
"onFailure",
|
|
994
|
+
"itemOnCancel",
|
|
998
995
|
"retry",
|
|
999
996
|
"join"
|
|
1000
997
|
]) if (step[field] !== void 0) issue("shape", `${path}.${field}`, `a flow has no ${field}; set it on the steps inside`);
|
|
@@ -1011,6 +1008,7 @@ function compileWorkflowDocument(raw, kinds, options = {}) {
|
|
|
1011
1008
|
}
|
|
1012
1009
|
if (step.steps !== void 0 && step.kind !== "map" && step.kind !== "repeat") issue("shape", `${path}.steps`, "only a flow, a map and a repeat have steps");
|
|
1013
1010
|
if (step.ref !== void 0) issue("shape", `${path}.ref`, "only a flow refers to another document");
|
|
1011
|
+
if (step.itemOnCancel !== void 0 && step.kind !== "map") issue("shape", `${path}.itemOnCancel`, "only a map has items to withdraw");
|
|
1014
1012
|
flat.set(key, {
|
|
1015
1013
|
key,
|
|
1016
1014
|
path,
|
|
@@ -1182,18 +1180,24 @@ function compileWorkflowDocument(raw, kinds, options = {}) {
|
|
|
1182
1180
|
refChain: options.refChain
|
|
1183
1181
|
});
|
|
1184
1182
|
if (!body) return null;
|
|
1183
|
+
const itemOutput = bodyOutput(body);
|
|
1184
|
+
const itemOnCancel = f.step.itemOnCancel;
|
|
1185
|
+
if (itemOnCancel !== void 0) checkStandIn(itemOnCancel.output, itemOutput, `${f.path}.itemOnCancel.output`, issue);
|
|
1185
1186
|
return {
|
|
1186
1187
|
output: {
|
|
1187
1188
|
type: "object",
|
|
1188
1189
|
required: ["items"],
|
|
1189
1190
|
properties: { items: {
|
|
1190
1191
|
type: "array",
|
|
1191
|
-
items:
|
|
1192
|
+
items: itemOutput
|
|
1192
1193
|
} }
|
|
1193
1194
|
},
|
|
1194
1195
|
policy: {
|
|
1195
1196
|
childType: stepTypeOf("map.item"),
|
|
1196
|
-
childInput: {
|
|
1197
|
+
childInput: {
|
|
1198
|
+
$body: body.definition,
|
|
1199
|
+
...itemOnCancel !== void 0 ? { $itemOnCancel: itemOnCancel } : {}
|
|
1200
|
+
}
|
|
1197
1201
|
}
|
|
1198
1202
|
};
|
|
1199
1203
|
}
|
|
@@ -1339,7 +1343,7 @@ function compileWorkflowDocument(raw, kinds, options = {}) {
|
|
|
1339
1343
|
}
|
|
1340
1344
|
}
|
|
1341
1345
|
});
|
|
1342
|
-
for (const i of found) issue("params", `${f.path}.params${i.path ? `.${i.path}` : ""}`, i.message);
|
|
1346
|
+
for (const i of found) issue(i.code ?? "params", `${f.path}.params${i.path ? `.${i.path}` : ""}`, i.message);
|
|
1343
1347
|
}
|
|
1344
1348
|
for (const g of guardOf.get(key)) {
|
|
1345
1349
|
const r = checkExpr(g.expr, scope);
|
|
@@ -1517,8 +1521,7 @@ function checkStandIn(output, schema, path, issue) {
|
|
|
1517
1521
|
const r = zodFor(schema).safeParse(output);
|
|
1518
1522
|
if (!r.success) for (const i of r.error.issues) issue("schema", `${path}${i.path.length ? `.${i.path.join(".")}` : ""}`, `the stand-in doesn't fit the step's output: ${i.message}`);
|
|
1519
1523
|
}
|
|
1520
|
-
|
|
1521
|
-
//#region src/document/kinds.ts
|
|
1524
|
+
// src/document/kinds.ts
|
|
1522
1525
|
const KIND = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
|
|
1523
1526
|
function defineKind(config) {
|
|
1524
1527
|
if (!KIND.test(config.kind)) throw new Error(`Kind "${config.kind}": a kind name is lower case, dotted (mail.send)`);
|
|
@@ -1749,8 +1752,7 @@ const coreKinds = [
|
|
|
1749
1752
|
subflowKind,
|
|
1750
1753
|
decideKind
|
|
1751
1754
|
];
|
|
1752
|
-
|
|
1753
|
-
//#region src/document/runtime.ts
|
|
1755
|
+
// src/document/runtime.ts
|
|
1754
1756
|
const parsedExprs = /* @__PURE__ */ new Map();
|
|
1755
1757
|
function expr(src) {
|
|
1756
1758
|
let e = parsedExprs.get(src);
|
|
@@ -1821,11 +1823,13 @@ const nonRetryable = (message) => err({
|
|
|
1821
1823
|
*/
|
|
1822
1824
|
function thrown(error) {
|
|
1823
1825
|
const marked = explicitRetryability(error);
|
|
1826
|
+
const continueAfterMs = continuationOf(error);
|
|
1824
1827
|
return err({
|
|
1825
1828
|
key: "step_error",
|
|
1826
1829
|
message: error instanceof Error ? error.message : String(error),
|
|
1827
1830
|
retryable: marked ?? isRetryableError(error),
|
|
1828
|
-
retryableFrom: marked !== void 0 ? "explicit" : "heuristic"
|
|
1831
|
+
retryableFrom: marked !== void 0 ? "explicit" : "heuristic",
|
|
1832
|
+
...continueAfterMs !== void 0 && { continueAfterMs }
|
|
1829
1833
|
});
|
|
1830
1834
|
}
|
|
1831
1835
|
/**
|
|
@@ -1906,7 +1910,10 @@ function registerWorkflowKinds(registry, kinds) {
|
|
|
1906
1910
|
index: ctx.stepInput.index,
|
|
1907
1911
|
with: given
|
|
1908
1912
|
});
|
|
1909
|
-
}, { stepPolicy: (stepInput) => ({
|
|
1913
|
+
}, { stepPolicy: (stepInput) => ({
|
|
1914
|
+
subWorkflowDefinition: stepInput.$body,
|
|
1915
|
+
...stepInput.$itemOnCancel !== void 0 ? { onCancelled: stepInput.$itemOnCancel } : {}
|
|
1916
|
+
}) });
|
|
1910
1917
|
for (const join of ["all", "any"]) registry.register(stepTypeOf(kind.kind, join), handler, {
|
|
1911
1918
|
condition,
|
|
1912
1919
|
join,
|
|
@@ -2019,8 +2026,7 @@ async function documentResult(store, compiled, workflowId) {
|
|
|
2019
2026
|
};
|
|
2020
2027
|
return Object.fromEntries(Object.entries(compiled.result).map(([name, src]) => [name, evaluate(expr(src), scope)]));
|
|
2021
2028
|
}
|
|
2022
|
-
|
|
2023
|
-
//#region src/document/policy.ts
|
|
2029
|
+
// src/document/policy.ts
|
|
2024
2030
|
/**
|
|
2025
2031
|
* The policy in a set of steps (a document's, a body's, a case move's):
|
|
2026
2032
|
*
|
|
@@ -2165,5 +2171,4 @@ function checkPublication(review, people) {
|
|
|
2165
2171
|
}
|
|
2166
2172
|
};
|
|
2167
2173
|
}
|
|
2168
|
-
//#endregion
|
|
2169
2174
|
export { DIVISION_SCALE, ExprSyntaxError, SPEC_KEY, WORKFLOW_DOCUMENT_FORMAT, WORKFLOW_DOCUMENT_VERSION, checkExpr, checkPublication, checkSchemaSubset, compileWorkflowDocument, coreKinds, decideKind, defineKind, describeWaitingStep, diffPolicy, documentResult, documentStepSchema, documentStepSpec, duration, evaluate, eventWaitKind, humanApproveKind, humanTaskKind, isDecimal, isDuration, mapKind, parseExpr, parseIsoDuration, registerWorkflowKinds, repeatKind, resolveParams, resumeDocumentStep, resumeWaitingStep, reviewRevision, reviewWorkflowRevision, roleSchema, sleepKind, stepTypeOf, stepsPolicy, subflowKind, toDecimal, workflowDocumentSchema, workflowPolicy, zodFor };
|
package/dist/drizzle/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/drizzle/index.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* `flow_workflow` — one row per run. `partition_key` is the engine's scoping
|
|
4
3
|
* column: the store is bound to one partition and every query filters by it.
|
|
@@ -169,5 +168,4 @@ export declare const flowStepLeaseColumns: {
|
|
|
169
168
|
acquiredAt: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>>;
|
|
170
169
|
/** A lease past this instant is reaped on the next acquire. */
|
|
171
170
|
expiresAt: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>;
|
|
172
|
-
};
|
|
173
|
-
//#endregion
|
|
171
|
+
};
|
package/dist/drizzle/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { bigint, bigserial, doublePrecision, integer, jsonb, text, timestamp } from "drizzle-orm/pg-core";
|
|
2
|
-
|
|
2
|
+
// src/drizzle/index.ts
|
|
3
3
|
/**
|
|
4
4
|
* `octaflow/drizzle` — the engine's tables as Drizzle column sets.
|
|
5
5
|
*
|
|
@@ -195,5 +195,4 @@ const flowStepLeaseColumns = {
|
|
|
195
195
|
/** A lease past this instant is reaped on the next acquire. */
|
|
196
196
|
expiresAt: timestamptz("expires_at").notNull()
|
|
197
197
|
};
|
|
198
|
-
//#endregion
|
|
199
198
|
export { flowRateBucketColumns, flowStepEventColumns, flowStepLeaseColumns, flowWorkflowColumns, flowWorkflowStepColumns };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/core/store.ts
|
|
2
2
|
/**
|
|
3
3
|
* The step statuses a step can still be settled from — by `completeStep` or
|
|
4
4
|
* `failStep`. Every other status is final for those two writes: a step that
|
|
@@ -10,8 +10,7 @@ const UNSETTLED_STEP_STATUSES = [
|
|
|
10
10
|
"waiting",
|
|
11
11
|
"mapping"
|
|
12
12
|
];
|
|
13
|
-
|
|
14
|
-
//#region src/core/gate.ts
|
|
13
|
+
// src/core/gate.ts
|
|
15
14
|
/**
|
|
16
15
|
* The rule keys a request is held to, in the order they are checked: the
|
|
17
16
|
* step's type, then each resource it declares, without repeats. The shipped
|
|
@@ -118,5 +117,4 @@ function createInMemoryStepGate(config = {}) {
|
|
|
118
117
|
};
|
|
119
118
|
} };
|
|
120
119
|
}
|
|
121
|
-
//#endregion
|
|
122
120
|
export { gateRuleKeys as n, UNSETTLED_STEP_STATUSES as r, createInMemoryStepGate as t };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
//#region src/core/result.d.ts
|
|
3
2
|
interface FlowErrorShape {
|
|
4
3
|
key: string;
|
|
5
4
|
message: string;
|
|
@@ -13,8 +12,6 @@ type Result<T = void, E extends FlowErrorShape = FlowErrorShape> = {
|
|
|
13
12
|
};
|
|
14
13
|
declare const ok: <T>(value: T) => Result<T, never>;
|
|
15
14
|
declare const err: <E extends FlowErrorShape>(error: E) => Result<never, E>;
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/core/readiness.d.ts
|
|
18
15
|
/**
|
|
19
16
|
* How a step's dependencies gate it.
|
|
20
17
|
*
|
|
@@ -55,8 +52,6 @@ interface ReadinessPlan {
|
|
|
55
52
|
* @param joinOf the join rule for a step type, from its registration
|
|
56
53
|
*/
|
|
57
54
|
declare function computeReadiness(steps: StepRecord[], joinOf: (stepType: string) => JoinRule): ReadinessPlan;
|
|
58
|
-
//#endregion
|
|
59
|
-
//#region src/core/types.d.ts
|
|
60
55
|
/** Workflow / step identifiers. Numeric to match common bigserial-backed stores. */
|
|
61
56
|
type WorkflowId = number;
|
|
62
57
|
type StepId = number;
|
|
@@ -163,9 +158,23 @@ interface StepExecutionContext<TContext = unknown> {
|
|
|
163
158
|
* `true`.
|
|
164
159
|
*/
|
|
165
160
|
heartbeat: () => Promise<boolean>;
|
|
161
|
+
/**
|
|
162
|
+
* What an earlier attempt of this step kept (`value`, null on the first), and
|
|
163
|
+
* how this attempt keeps its own (`save`, which replaces it). For a handler
|
|
164
|
+
* that does several things that cost or change something — an agent's tool
|
|
165
|
+
* calls — so a retry after a crash picks up where the last attempt got to.
|
|
166
|
+
* `save` resolves `false` when nothing was written: the step is no longer this
|
|
167
|
+
* attempt's, or the store keeps no checkpoints.
|
|
168
|
+
*/
|
|
169
|
+
checkpoint?: StepCheckpoint;
|
|
166
170
|
/** Host-provided per-step context. */
|
|
167
171
|
context: TContext;
|
|
168
172
|
}
|
|
173
|
+
/** See {@link StepExecutionContext.checkpoint}. */
|
|
174
|
+
interface StepCheckpoint {
|
|
175
|
+
readonly value: unknown;
|
|
176
|
+
save(value: unknown): Promise<boolean>;
|
|
177
|
+
}
|
|
169
178
|
interface StepError extends FlowErrorShape {
|
|
170
179
|
key: 'step_error';
|
|
171
180
|
message: string;
|
|
@@ -177,6 +186,8 @@ interface StepError extends FlowErrorShape {
|
|
|
177
186
|
* the engine's `defaultRetryable` may override those, and only those.
|
|
178
187
|
*/
|
|
179
188
|
retryableFrom?: 'explicit' | 'predicate' | 'heuristic';
|
|
189
|
+
/** The handler threw `continueStep`: not a failure — run the step again after this many ms, without spending an attempt. */
|
|
190
|
+
continueAfterMs?: number;
|
|
180
191
|
}
|
|
181
192
|
type StepHandler<TContext = unknown> = (ctx: StepExecutionContext<TContext>) => Promise<Result<Record<string, unknown>, StepError>>;
|
|
182
193
|
/** Context for a step's compensation handler — the execution context plus the step's own output. */
|
|
@@ -350,6 +361,15 @@ interface StepPolicy {
|
|
|
350
361
|
waitTimeoutMs?: number;
|
|
351
362
|
onTimeout?: WaitTimeoutPolicy;
|
|
352
363
|
onFailure?: StepFailurePolicy;
|
|
364
|
+
/**
|
|
365
|
+
* For a step that runs a sub-workflow (a map item among them): what it completes with when that run is
|
|
366
|
+
* cancelled — `cancelWorkflow` on the child — instead of failing. The step keeps "Sub-workflow cancelled"
|
|
367
|
+
* on its `error`, and a map counts the stand-in in the item's slot. A child that *fails* still fails the
|
|
368
|
+
* step as `onFailure` says.
|
|
369
|
+
*/
|
|
370
|
+
onCancelled?: {
|
|
371
|
+
output: Record<string, unknown>;
|
|
372
|
+
};
|
|
353
373
|
retry?: RetryPolicy;
|
|
354
374
|
/** This step starts a child workflow from this definition: the handler returns its input. */
|
|
355
375
|
subWorkflowDefinition?: WorkflowDefinition;
|
|
@@ -478,8 +498,6 @@ interface WorkflowStepRunningError extends FlowErrorShape {
|
|
|
478
498
|
* reject a start with a domain-specific error key the engine just passes through.
|
|
479
499
|
*/
|
|
480
500
|
type FlowError = WorkflowNotFoundError | InvalidWorkflowDefinitionError | StepHandlerNotFoundError | WorkflowNotRetryableError | WorkflowStepRunningError | StepError | FlowErrorShape;
|
|
481
|
-
//#endregion
|
|
482
|
-
//#region src/core/defineStep.d.ts
|
|
483
501
|
/**
|
|
484
502
|
* A type-safe step definition with Zod validation schemas, parameterized by the
|
|
485
503
|
* host `TContext`. Created by `defineStep()`, consumed by `buildWorkflow()`.
|
|
@@ -565,6 +583,8 @@ interface TypedStepContext<TInput extends Record<string, unknown>, TDeps extends
|
|
|
565
583
|
* re-queued as dead). See `heartbeatTimeoutMs`.
|
|
566
584
|
*/
|
|
567
585
|
heartbeat: () => Promise<boolean>;
|
|
586
|
+
/** What an earlier attempt kept, and how this one keeps its own — see `StepExecutionContext.checkpoint`. */
|
|
587
|
+
checkpoint?: StepCheckpoint;
|
|
568
588
|
context: TContext;
|
|
569
589
|
}
|
|
570
590
|
interface DefineStepConfig<TInput extends Record<string, unknown>, TOutput extends Record<string, unknown>, TDeps extends Record<string, TypedStep<any, any, any, TContext>>, TContext, TJoin extends JoinRule = 'all'> {
|
|
@@ -861,8 +881,6 @@ interface BuildWorkflowConfig<TInput extends Record<string, unknown>, TSteps ext
|
|
|
861
881
|
* that every dependency key references a real step.
|
|
862
882
|
*/
|
|
863
883
|
declare function buildWorkflow<TInput extends Record<string, unknown>, TContext = unknown, TSteps extends Record<string, TypedStep<any, any, any, TContext>> = {}>(config: BuildWorkflowConfig<TInput, TSteps, TContext>): TypedWorkflow<TInput, TContext>;
|
|
864
|
-
//#endregion
|
|
865
|
-
//#region src/core/decision.d.ts
|
|
866
884
|
/** The questions a decision answers: each key names a question, each value its options. */
|
|
867
885
|
type DecisionQuestions = Readonly<Record<string, readonly [string, ...string[]]>>;
|
|
868
886
|
/** One answer: the chosen option, how sure the decider is, and optionally why. */
|
|
@@ -1037,8 +1055,6 @@ declare function defineDecisionOutcomeStep<Q extends DecisionQuestions, TDecisio
|
|
|
1037
1055
|
/** Its review, keyed by its step key: `{ triageReview }`. */
|
|
1038
1056
|
review: { [K in TReviewKey]: TypedStep<any, DecisionReview<Q>, any, TContext>; };
|
|
1039
1057
|
}): TypedStep<Record<string, never>, DecisionOutcome<Q>, { [K in TDecisionKey]: DecisionStep<Q, any, any, TContext>; } & { [K in TReviewKey]: TypedStep<any, DecisionReview<Q>, any, TContext>; }, TContext>;
|
|
1040
|
-
//#endregion
|
|
1041
|
-
//#region src/core/public-view.d.ts
|
|
1042
1058
|
/** The five states a UI renders. Suspensions display as in-flight. */
|
|
1043
1059
|
type StepDisplayStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
1044
1060
|
declare const STEP_DISPLAY_STATUS_SCHEMA: z.ZodEnum<{
|
|
@@ -1165,5 +1181,4 @@ declare function toPublicStep(step: StepRecord): PublicWorkflowStep;
|
|
|
1165
1181
|
* `idempotencyKey`, `parentWorkflowId`/`parentStepId`, and `metadata`.
|
|
1166
1182
|
*/
|
|
1167
1183
|
declare function toPublicWorkflow(workflow: WorkflowWithSteps): PublicWorkflow;
|
|
1168
|
-
|
|
1169
|
-
export { StepFailurePolicy as $, StepOutput as A, defineWaitStep as B, defineDecisionOutcomeStep as C, isTerminalStepStatus as Ct, resolveDecision as D, ok as Dt, functionDecider as E, err as Et, buildWorkflow as F, StartOptions as G, FlowError as H, defineMapStep as I, StepConditionHandler as J, StepCompensateHandler as K, defineSleepStep as L, TypedStepContext as M, TypedWorkflow as N, DefineStepConfig as O, WorkflowOutput as P, StepExecutionContext as Q, defineStep as R, DecisionStep as S, computeReadiness as St, defineDecisionStep as T, Result as Tt, InvalidWorkflowDefinitionError as U, DelayedStep as V, RetryPolicy as W, StepDelay as X, StepDefinition as Y, StepError as Z, DecisionOutput as _, WorkflowStepRunningError as _t, STEP_DISPLAY_STATUS as a, StepRecord as at, DecisionResponse as b, PlannedSkip as bt, WORKFLOW_STATUS_SCHEMA as c, StepStatus as ct, toPublicWorkflow as d, WorkflowDefinition as dt, StepHandler as et, Decider as f, WorkflowId as ft, DecisionOutcome as g, WorkflowStatus as gt, DecisionChoices as h, WorkflowRecord as ht, PublicWorkflowStep as i, StepPolicy as it, TypedStep as j, StepDeps as k, toDisplayStepStatus as l, WaitTimeoutPolicy as lt, DecisionAnswers as m, WorkflowNotRetryableError as mt, PUBLIC_WORKFLOW_STEP_SCHEMA as n, StepHandlerRegistry as nt, STEP_DISPLAY_STATUS_SCHEMA as o, StepRegistration as ot, DecisionAnswer as p, WorkflowNotFoundError as pt, StepCompensationContext as q, PublicWorkflow as r, StepId as rt, StepDisplayStatus as s, StepRepeat as st, PUBLIC_WORKFLOW_SCHEMA as t, StepHandlerNotFoundError as tt, toPublicStep as u, WorkflowCreatedResult as ut, DecisionQuestions as v, WorkflowWithSteps as vt, defineDecisionReviewStep as w, FlowErrorShape as wt, DecisionReview as x, ReadinessPlan as xt, DecisionRequest as y, JoinRule as yt, defineSubWorkflowStep as z };
|
|
1184
|
+
export { StepExecutionContext as $, StepOutput as A, defineWaitStep as B, defineDecisionOutcomeStep as C, computeReadiness as Ct, resolveDecision as D, err as Dt, functionDecider as E, Result as Et, buildWorkflow as F, StartOptions as G, FlowError as H, defineMapStep as I, StepCompensationContext as J, StepCheckpoint as K, defineSleepStep as L, TypedStepContext as M, TypedWorkflow as N, DefineStepConfig as O, ok as Ot, WorkflowOutput as P, StepError as Q, defineStep as R, DecisionStep as S, ReadinessPlan as St, defineDecisionStep as T, FlowErrorShape as Tt, InvalidWorkflowDefinitionError as U, DelayedStep as V, RetryPolicy as W, StepDefinition as X, StepConditionHandler as Y, StepDelay as Z, DecisionOutput as _, WorkflowStatus as _t, STEP_DISPLAY_STATUS as a, StepPolicy as at, DecisionResponse as b, JoinRule as bt, WORKFLOW_STATUS_SCHEMA as c, StepRepeat as ct, toPublicWorkflow as d, WorkflowCreatedResult as dt, StepFailurePolicy as et, Decider as f, WorkflowDefinition as ft, DecisionOutcome as g, WorkflowRecord as gt, DecisionChoices as h, WorkflowNotRetryableError as ht, PublicWorkflowStep as i, StepId as it, TypedStep as j, StepDeps as k, toDisplayStepStatus as l, StepStatus as lt, DecisionAnswers as m, WorkflowNotFoundError as mt, PUBLIC_WORKFLOW_STEP_SCHEMA as n, StepHandlerNotFoundError as nt, STEP_DISPLAY_STATUS_SCHEMA as o, StepRecord as ot, DecisionAnswer as p, WorkflowId as pt, StepCompensateHandler as q, PublicWorkflow as r, StepHandlerRegistry as rt, StepDisplayStatus as s, StepRegistration as st, PUBLIC_WORKFLOW_SCHEMA as t, StepHandler as tt, toPublicStep as u, WaitTimeoutPolicy as ut, DecisionQuestions as v, WorkflowStepRunningError as vt, defineDecisionReviewStep as w, isTerminalStepStatus as wt, DecisionReview as x, PlannedSkip as xt, DecisionRequest as y, WorkflowWithSteps as yt, defineSubWorkflowStep as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { a as LogAttributes, i as EnqueueOptions, n as DispatchStepPayload, o as Logger, r as Dispatcher, s as noopLogger, t as DispatchKind } from "./dispatcher-
|
|
3
|
-
import { a as CreatedWorkflow, c as ListWorkflowsFilters, d as UNSETTLED_STEP_STATUSES, f as WorkflowStore, i as CreateWorkflowStep, l as ReopenWorkflowParams, n as CompleteStepParams, o as FailStepParams, p as WorkflowTreeFilter, r as CreateWorkflowParams, s as FinishWorkflowParams, t as AddChildStep, u as TransactionalScope } from "./store-
|
|
4
|
-
import { C as gateRuleKeys, S as createInMemoryStepGate, _ as InMemoryStepGateConfig, a as FlowSpan, b as StepGateDecision, c as RecordingObserver, d as createMeterObserver, f as createRecordingObserver, g as ConcurrencyRule, h as noopTracer, i as FlowObserver, l as RecordingTracer, m as noopObserver, n as FlowEventType, o as FlowTracer, p as createRecordingTracer, r as FlowMeter, s as RecordedSpan, t as FlowEvent, u as combineObservers, v as RateRule, x as StepGateRequest, y as StepGate } from "./observability-
|
|
5
|
-
//#region src/core/hooks.d.ts
|
|
1
|
+
import { $ as StepExecutionContext, A as StepOutput, B as defineWaitStep, C as defineDecisionOutcomeStep, Ct as computeReadiness, D as resolveDecision, Dt as err, E as functionDecider, Et as Result, F as buildWorkflow, G as StartOptions, H as FlowError, I as defineMapStep, J as StepCompensationContext, K as StepCheckpoint, L as defineSleepStep, M as TypedStepContext, N as TypedWorkflow, O as DefineStepConfig, Ot as ok, P as WorkflowOutput, Q as StepError, R as defineStep, S as DecisionStep, St as ReadinessPlan, T as defineDecisionStep, Tt as FlowErrorShape, U as InvalidWorkflowDefinitionError, V as DelayedStep, W as RetryPolicy, X as StepDefinition, Y as StepConditionHandler, Z as StepDelay, _ as DecisionOutput, _t as WorkflowStatus, a as STEP_DISPLAY_STATUS, at as StepPolicy, b as DecisionResponse, bt as JoinRule, c as WORKFLOW_STATUS_SCHEMA, ct as StepRepeat, d as toPublicWorkflow, dt as WorkflowCreatedResult, et as StepFailurePolicy, f as Decider, ft as WorkflowDefinition, g as DecisionOutcome, gt as WorkflowRecord, h as DecisionChoices, ht as WorkflowNotRetryableError, i as PublicWorkflowStep, it as StepId, j as TypedStep, k as StepDeps, l as toDisplayStepStatus, lt as StepStatus, m as DecisionAnswers, mt as WorkflowNotFoundError, n as PUBLIC_WORKFLOW_STEP_SCHEMA, nt as StepHandlerNotFoundError, o as STEP_DISPLAY_STATUS_SCHEMA, ot as StepRecord, p as DecisionAnswer, pt as WorkflowId, q as StepCompensateHandler, r as PublicWorkflow, rt as StepHandlerRegistry, s as StepDisplayStatus, st as StepRegistration, t as PUBLIC_WORKFLOW_SCHEMA, tt as StepHandler, u as toPublicStep, ut as WaitTimeoutPolicy, v as DecisionQuestions, vt as WorkflowStepRunningError, w as defineDecisionReviewStep, wt as isTerminalStepStatus, x as DecisionReview, xt as PlannedSkip, y as DecisionRequest, yt as WorkflowWithSteps, z as defineSubWorkflowStep } from "./index-BBO5BGS8.js";
|
|
2
|
+
import { a as LogAttributes, i as EnqueueOptions, n as DispatchStepPayload, o as Logger, r as Dispatcher, s as noopLogger, t as DispatchKind } from "./dispatcher-BnlAJuu3.js";
|
|
3
|
+
import { a as CreatedWorkflow, c as ListWorkflowsFilters, d as UNSETTLED_STEP_STATUSES, f as WorkflowStore, i as CreateWorkflowStep, l as ReopenWorkflowParams, n as CompleteStepParams, o as FailStepParams, p as WorkflowTreeFilter, r as CreateWorkflowParams, s as FinishWorkflowParams, t as AddChildStep, u as TransactionalScope } from "./store-oCpqAuTT.js";
|
|
4
|
+
import { C as gateRuleKeys, S as createInMemoryStepGate, _ as InMemoryStepGateConfig, a as FlowSpan, b as StepGateDecision, c as RecordingObserver, d as createMeterObserver, f as createRecordingObserver, g as ConcurrencyRule, h as noopTracer, i as FlowObserver, l as RecordingTracer, m as noopObserver, n as FlowEventType, o as FlowTracer, p as createRecordingTracer, r as FlowMeter, s as RecordedSpan, t as FlowEvent, u as combineObservers, v as RateRule, x as StepGateRequest, y as StepGate } from "./observability-C-wtcDDo.js";
|
|
6
5
|
export interface BeforeStartArgs {
|
|
7
6
|
definition: WorkflowDefinition;
|
|
8
7
|
input: Record<string, unknown>;
|
|
@@ -55,16 +54,12 @@ export interface WorkflowHooks<TContext = unknown> {
|
|
|
55
54
|
/** Runs once when a workflow reaches `completed` (e.g. daily usage rollup). */
|
|
56
55
|
onWorkflowCompleted?(args: WorkflowCompletedArgs): Promise<void>;
|
|
57
56
|
}
|
|
58
|
-
//#endregion
|
|
59
|
-
//#region src/core/registry.d.ts
|
|
60
57
|
/**
|
|
61
58
|
* Creates an in-memory step handler registry.
|
|
62
59
|
* Populate it at application startup (e.g. by registering every workflow's steps).
|
|
63
60
|
* Each registration carries the handler plus its optional retry/timeout policy.
|
|
64
61
|
*/
|
|
65
62
|
export declare function createStepHandlerRegistry<TContext = unknown>(): StepHandlerRegistry<TContext>;
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/core/retry.d.ts
|
|
68
63
|
/**
|
|
69
64
|
* Tag an error with an explicit retryability decision, overriding both the
|
|
70
65
|
* step predicate and the message heuristic. Returns the same error, so it can
|
|
@@ -105,8 +100,17 @@ export declare function explicitRetryability(error: unknown): boolean | undefine
|
|
|
105
100
|
* `defaultRetryable` when the answer matters.
|
|
106
101
|
*/
|
|
107
102
|
export declare function isRetryableError(error: unknown): boolean;
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Throw this from a handler to stop now and run again later, without spending
|
|
105
|
+
* an attempt: the engine puts the step back to `pending` and re-queues it
|
|
106
|
+
* after `afterMs`. For a handler that works in slices of wall-clock time — a
|
|
107
|
+
* host with a hard request ceiling — and keeps its progress in
|
|
108
|
+
* `ctx.checkpoint`, so the next run goes on from there. Nothing caps how
|
|
109
|
+
* often a step continues; the handler's own limits must.
|
|
110
|
+
*/
|
|
111
|
+
export declare function continueStep(afterMs?: number, message?: string): Error;
|
|
112
|
+
/** The delay a {@link continueStep} error asks for, or `undefined` for any other error. */
|
|
113
|
+
export declare function continuationOf(error: unknown): number | undefined;
|
|
110
114
|
export interface WorkflowEngineConfig {
|
|
111
115
|
/** The dispatcher's per-step expiry, in seconds. Used by the stuck-step sweeper. Default 600. */
|
|
112
116
|
stepExpirySeconds?: number;
|
|
@@ -205,8 +209,6 @@ export declare function createWorkflowEngine<TContext = unknown>(deps: WorkflowE
|
|
|
205
209
|
}) => Promise<Result<WorkflowWithSteps[], FlowError>>;
|
|
206
210
|
};
|
|
207
211
|
export type WorkflowEngine<TContext = unknown> = ReturnType<typeof createWorkflowEngine<TContext>>;
|
|
208
|
-
//#endregion
|
|
209
|
-
//#region src/core/in-memory-store.d.ts
|
|
210
212
|
/**
|
|
211
213
|
* Reference `WorkflowStore` backed by plain Maps. Intended for tests and single-
|
|
212
214
|
* process experimentation — NOT durable. Partition-scoped at construction to
|
|
@@ -220,5 +222,4 @@ export declare function createInMemoryWorkflowStore(partitionKey?: string): Work
|
|
|
220
222
|
steps: StepRecord[];
|
|
221
223
|
};
|
|
222
224
|
};
|
|
223
|
-
|
|
224
|
-
export { AddChildStep, CompleteStepParams, ConcurrencyRule, CreateWorkflowParams, CreateWorkflowStep, CreatedWorkflow, Decider, DecisionAnswer, DecisionAnswers, DecisionChoices, DecisionOutcome, DecisionOutput, DecisionQuestions, DecisionRequest, DecisionResponse, DecisionReview, DecisionStep, DefineStepConfig, DelayedStep, DispatchKind, DispatchStepPayload, Dispatcher, EnqueueOptions, FailStepParams, FinishWorkflowParams, FlowError, FlowErrorShape, FlowEvent, FlowEventType, FlowMeter, FlowObserver, FlowSpan, FlowTracer, InMemoryStepGateConfig, InvalidWorkflowDefinitionError, JoinRule, ListWorkflowsFilters, LogAttributes, Logger, PUBLIC_WORKFLOW_SCHEMA, PUBLIC_WORKFLOW_STEP_SCHEMA, PlannedSkip, PublicWorkflow, PublicWorkflowStep, RateRule, ReadinessPlan, RecordedSpan, RecordingObserver, RecordingTracer, ReopenWorkflowParams, Result, RetryPolicy, STEP_DISPLAY_STATUS, STEP_DISPLAY_STATUS_SCHEMA, StartOptions, StepCompensateHandler, StepCompensationContext, StepConditionHandler, StepDefinition, StepDelay, StepDeps, StepDisplayStatus, StepError, StepExecutionContext, StepFailurePolicy, StepGate, StepGateDecision, StepGateRequest, StepHandler, StepHandlerNotFoundError, StepHandlerRegistry, StepId, StepOutput, StepPolicy, StepRecord, StepRegistration, StepRepeat, StepStatus, TransactionalScope, TypedStep, TypedStepContext, TypedWorkflow, UNSETTLED_STEP_STATUSES, WORKFLOW_STATUS_SCHEMA, WaitTimeoutPolicy, WorkflowCreatedResult, WorkflowDefinition, WorkflowId, WorkflowNotFoundError, WorkflowNotRetryableError, WorkflowOutput, WorkflowRecord, WorkflowStatus, WorkflowStepRunningError, WorkflowStore, WorkflowTreeFilter, WorkflowWithSteps, buildWorkflow, combineObservers, computeReadiness, createInMemoryStepGate, createMeterObserver, createRecordingObserver, createRecordingTracer, defineDecisionOutcomeStep, defineDecisionReviewStep, defineDecisionStep, defineMapStep, defineSleepStep, defineStep, defineSubWorkflowStep, defineWaitStep, err, functionDecider, gateRuleKeys, isTerminalStepStatus, noopLogger, noopObserver, noopTracer, ok, resolveDecision, toDisplayStepStatus, toPublicStep, toPublicWorkflow };
|
|
225
|
+
export { AddChildStep, CompleteStepParams, ConcurrencyRule, CreateWorkflowParams, CreateWorkflowStep, CreatedWorkflow, Decider, DecisionAnswer, DecisionAnswers, DecisionChoices, DecisionOutcome, DecisionOutput, DecisionQuestions, DecisionRequest, DecisionResponse, DecisionReview, DecisionStep, DefineStepConfig, DelayedStep, DispatchKind, DispatchStepPayload, Dispatcher, EnqueueOptions, FailStepParams, FinishWorkflowParams, FlowError, FlowErrorShape, FlowEvent, FlowEventType, FlowMeter, FlowObserver, FlowSpan, FlowTracer, InMemoryStepGateConfig, InvalidWorkflowDefinitionError, JoinRule, ListWorkflowsFilters, LogAttributes, Logger, PUBLIC_WORKFLOW_SCHEMA, PUBLIC_WORKFLOW_STEP_SCHEMA, PlannedSkip, PublicWorkflow, PublicWorkflowStep, RateRule, ReadinessPlan, RecordedSpan, RecordingObserver, RecordingTracer, ReopenWorkflowParams, Result, RetryPolicy, STEP_DISPLAY_STATUS, STEP_DISPLAY_STATUS_SCHEMA, StartOptions, StepCheckpoint, StepCompensateHandler, StepCompensationContext, StepConditionHandler, StepDefinition, StepDelay, StepDeps, StepDisplayStatus, StepError, StepExecutionContext, StepFailurePolicy, StepGate, StepGateDecision, StepGateRequest, StepHandler, StepHandlerNotFoundError, StepHandlerRegistry, StepId, StepOutput, StepPolicy, StepRecord, StepRegistration, StepRepeat, StepStatus, TransactionalScope, TypedStep, TypedStepContext, TypedWorkflow, UNSETTLED_STEP_STATUSES, WORKFLOW_STATUS_SCHEMA, WaitTimeoutPolicy, WorkflowCreatedResult, WorkflowDefinition, WorkflowId, WorkflowNotFoundError, WorkflowNotRetryableError, WorkflowOutput, WorkflowRecord, WorkflowStatus, WorkflowStepRunningError, WorkflowStore, WorkflowTreeFilter, WorkflowWithSteps, buildWorkflow, combineObservers, computeReadiness, createInMemoryStepGate, createMeterObserver, createRecordingObserver, createRecordingTracer, defineDecisionOutcomeStep, defineDecisionReviewStep, defineDecisionStep, defineMapStep, defineSleepStep, defineStep, defineSubWorkflowStep, defineWaitStep, err, functionDecider, gateRuleKeys, isTerminalStepStatus, noopLogger, noopObserver, noopTracer, ok, resolveDecision, toDisplayStepStatus, toPublicStep, toPublicWorkflow };
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { n as ok, t as err } from "./result-
|
|
2
|
-
import { n as gateRuleKeys, r as UNSETTLED_STEP_STATUSES, t as createInMemoryStepGate } from "./gate-
|
|
3
|
-
import { C as
|
|
4
|
-
|
|
1
|
+
import { n as ok, t as err } from "./result-CxvNy9B6.js";
|
|
2
|
+
import { n as gateRuleKeys, r as UNSETTLED_STEP_STATUSES, t as createInMemoryStepGate } from "./gate-BcjK7FoL.js";
|
|
3
|
+
import { C as isRetryableError, E as retryableError, S as explicitRetryability, T as nonRetryableError, _ as defineStep, a as WORKFLOW_STATUS_SCHEMA, b as continuationOf, c as toPublicWorkflow, d as defineDecisionStep, f as functionDecider, g as defineSleepStep, h as defineMapStep, i as STEP_DISPLAY_STATUS_SCHEMA, l as defineDecisionOutcomeStep, m as buildWorkflow, n as PUBLIC_WORKFLOW_STEP_SCHEMA, o as toDisplayStepStatus, p as resolveDecision, r as STEP_DISPLAY_STATUS, s as toPublicStep, t as PUBLIC_WORKFLOW_SCHEMA, u as defineDecisionReviewStep, v as defineSubWorkflowStep, w as markRetryable, x as continueStep, y as defineWaitStep } from "./core-ut8VZ0sU.js";
|
|
4
|
+
// src/core/logger.ts
|
|
5
5
|
const noopLogger = {
|
|
6
6
|
info() {},
|
|
7
7
|
warn() {},
|
|
8
8
|
error() {}
|
|
9
9
|
};
|
|
10
|
-
|
|
11
|
-
//#region src/core/readiness.ts
|
|
10
|
+
// src/core/readiness.ts
|
|
12
11
|
/** Statuses a step can no longer move out of on its own. */
|
|
13
12
|
const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
14
13
|
"completed",
|
|
@@ -74,8 +73,7 @@ function computeReadiness(steps, joinOf) {
|
|
|
74
73
|
skip
|
|
75
74
|
};
|
|
76
75
|
}
|
|
77
|
-
|
|
78
|
-
//#region src/core/observability.ts
|
|
76
|
+
// src/core/observability.ts
|
|
79
77
|
const noopObserver = { record() {} };
|
|
80
78
|
function createRecordingObserver() {
|
|
81
79
|
const events = [];
|
|
@@ -199,8 +197,7 @@ function createRecordingTracer() {
|
|
|
199
197
|
}
|
|
200
198
|
};
|
|
201
199
|
}
|
|
202
|
-
|
|
203
|
-
//#region src/core/registry.ts
|
|
200
|
+
// src/core/registry.ts
|
|
204
201
|
/**
|
|
205
202
|
* Creates an in-memory step handler registry.
|
|
206
203
|
* Populate it at application startup (e.g. by registering every workflow's steps).
|
|
@@ -244,8 +241,7 @@ function createStepHandlerRegistry() {
|
|
|
244
241
|
}
|
|
245
242
|
};
|
|
246
243
|
}
|
|
247
|
-
|
|
248
|
-
//#region src/core/engine.ts
|
|
244
|
+
// src/core/engine.ts
|
|
249
245
|
/**
|
|
250
246
|
* Run a handler under an optional wall-clock timeout. On expiry the abort signal
|
|
251
247
|
* is fired (cooperative handlers stop) and a retryable timeout error is returned —
|
|
@@ -476,6 +472,7 @@ function createWorkflowEngine(deps) {
|
|
|
476
472
|
waitTimeoutMs: own.waitTimeoutMs ?? registration?.timeoutMs,
|
|
477
473
|
onTimeout: own.onTimeout ?? registration?.onTimeout ?? "fail",
|
|
478
474
|
onFailure: own.onFailure ?? registration?.onFailure ?? "fail",
|
|
475
|
+
onCancelled: own.onCancelled,
|
|
479
476
|
retry: own.retry ?? registration?.retry,
|
|
480
477
|
subWorkflowDefinition: own.subWorkflowDefinition ?? registration?.subWorkflowDefinition,
|
|
481
478
|
childType: own.childType ?? registration?.childType,
|
|
@@ -765,6 +762,39 @@ function createWorkflowEngine(deps) {
|
|
|
765
762
|
function start(workflow, input, options) {
|
|
766
763
|
return startWorkflow(workflow.definition, workflow.inputSchema.parse(input), options);
|
|
767
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* A handler yielded with `continueStep`: hand the step back without spending
|
|
767
|
+
* the attempt and run it again after `afterMs`. The release is fenced by the
|
|
768
|
+
* claim, so a superseded invocation re-queues nothing.
|
|
769
|
+
*/
|
|
770
|
+
async function continueLater(workflow, step, attemptNo, afterMs) {
|
|
771
|
+
const workflowId = workflow.id;
|
|
772
|
+
if (!await store.releaseStep(step.id, attemptNo)) {
|
|
773
|
+
logger.warn("Discarding the continuation of a superseded step", {
|
|
774
|
+
workflowId,
|
|
775
|
+
stepId: step.id,
|
|
776
|
+
stepKey: step.key,
|
|
777
|
+
attempt: attemptNo
|
|
778
|
+
});
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
logger.info("Step continues later", {
|
|
782
|
+
workflowId,
|
|
783
|
+
stepId: step.id,
|
|
784
|
+
stepKey: step.key,
|
|
785
|
+
afterMs
|
|
786
|
+
});
|
|
787
|
+
const re = await dispatcher.enqueueStep({
|
|
788
|
+
workflowId,
|
|
789
|
+
stepId: step.id,
|
|
790
|
+
stepKey: step.key,
|
|
791
|
+
stepType: step.type
|
|
792
|
+
}, { startAfterSeconds: Math.ceil(afterMs / 1e3) });
|
|
793
|
+
if (!re.ok) await failOrDefault(workflowId, step, `the step could not be queued to continue: ${re.error.message}`, {
|
|
794
|
+
workflowType: workflow.type,
|
|
795
|
+
attempt: attemptNo
|
|
796
|
+
});
|
|
797
|
+
}
|
|
768
798
|
async function executeStep(workflowId, stepId) {
|
|
769
799
|
const workflow = await store.getWorkflow(workflowId);
|
|
770
800
|
if (!workflow) return {
|
|
@@ -956,6 +986,10 @@ function createWorkflowEngine(deps) {
|
|
|
956
986
|
dependencyOutputs,
|
|
957
987
|
signal: abort.signal,
|
|
958
988
|
heartbeat: heartbeat.beat,
|
|
989
|
+
checkpoint: {
|
|
990
|
+
value: step.metadata?.checkpoint ?? null,
|
|
991
|
+
save: (value) => store.saveStepCheckpoint ? store.saveStepCheckpoint(stepId, attemptNo, value) : Promise.resolve(false)
|
|
992
|
+
},
|
|
959
993
|
context
|
|
960
994
|
};
|
|
961
995
|
const guard = registration.condition ? await registration.condition(ctx) : void 0;
|
|
@@ -1063,6 +1097,13 @@ function createWorkflowEngine(deps) {
|
|
|
1063
1097
|
value: void 0
|
|
1064
1098
|
};
|
|
1065
1099
|
}
|
|
1100
|
+
if (handlerResult.error.continueAfterMs !== void 0) {
|
|
1101
|
+
await continueLater(workflow, step, attemptNo, handlerResult.error.continueAfterMs);
|
|
1102
|
+
return {
|
|
1103
|
+
ok: true,
|
|
1104
|
+
value: void 0
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1066
1107
|
if ((handlerResult.error.retryableFrom === "heuristic" && defaultRetryable !== void 0 ? defaultRetryable : handlerResult.error.retryable) && attemptNo < maxAttempts) {
|
|
1067
1108
|
const delaySeconds = backoffDelaySeconds(retry, attemptNo);
|
|
1068
1109
|
logger.info("Retrying step", {
|
|
@@ -1115,6 +1156,14 @@ function createWorkflowEngine(deps) {
|
|
|
1115
1156
|
value: void 0
|
|
1116
1157
|
};
|
|
1117
1158
|
} catch (error) {
|
|
1159
|
+
const afterMs = continuationOf(error);
|
|
1160
|
+
if (afterMs !== void 0 && !heartbeat.lost) {
|
|
1161
|
+
await continueLater(workflow, step, attemptNo, afterMs);
|
|
1162
|
+
return {
|
|
1163
|
+
ok: true,
|
|
1164
|
+
value: void 0
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1118
1167
|
const message = error instanceof Error ? error.message : "Unknown step execution error";
|
|
1119
1168
|
logger.error("Step execution threw", error instanceof Error ? error : new Error(message), {
|
|
1120
1169
|
workflowId,
|
|
@@ -1531,6 +1580,25 @@ function createWorkflowEngine(deps) {
|
|
|
1531
1580
|
else await onStepCompleted(child.parentWorkflowId, child.parentStepId, done);
|
|
1532
1581
|
} else if (parentStep.parentStepId != null) await onChildCompleted(child.parentWorkflowId, parentStep, output);
|
|
1533
1582
|
else await onStepCompleted(child.parentWorkflowId, child.parentStepId, output);
|
|
1583
|
+
} else if (child.status === "cancelled" && policyOf(parentStep).onCancelled) {
|
|
1584
|
+
const message = "Sub-workflow cancelled";
|
|
1585
|
+
const standIn = policyOf(parentStep).onCancelled.output;
|
|
1586
|
+
const announce = () => emit({
|
|
1587
|
+
type: "step.defaulted",
|
|
1588
|
+
workflowId: child.parentWorkflowId,
|
|
1589
|
+
stepId: parentStep.id,
|
|
1590
|
+
stepKey: parentStep.key,
|
|
1591
|
+
stepType: parentStep.type,
|
|
1592
|
+
error: message
|
|
1593
|
+
});
|
|
1594
|
+
if (parentStep.parentStepId != null) await onChildCompleted(child.parentWorkflowId, parentStep, standIn, {
|
|
1595
|
+
error: message,
|
|
1596
|
+
announce
|
|
1597
|
+
});
|
|
1598
|
+
else await onStepCompleted(child.parentWorkflowId, child.parentStepId, standIn, {
|
|
1599
|
+
error: message,
|
|
1600
|
+
announce
|
|
1601
|
+
});
|
|
1534
1602
|
} else await failOrDefault(child.parentWorkflowId, parentStep, `Sub-workflow ${child.status}${child.error ? `: ${child.error}` : ""}`);
|
|
1535
1603
|
logger.info("Bridged sub-workflow to parent step", {
|
|
1536
1604
|
childWorkflowId,
|
|
@@ -2092,8 +2160,7 @@ function createWorkflowEngine(deps) {
|
|
|
2092
2160
|
listWorkflows
|
|
2093
2161
|
};
|
|
2094
2162
|
}
|
|
2095
|
-
|
|
2096
|
-
//#region src/core/in-memory-store.ts
|
|
2163
|
+
// src/core/in-memory-store.ts
|
|
2097
2164
|
/**
|
|
2098
2165
|
* Reference `WorkflowStore` backed by plain Maps. Intended for tests and single-
|
|
2099
2166
|
* process experimentation — NOT durable. Partition-scoped at construction to
|
|
@@ -2190,6 +2257,15 @@ function createInMemoryWorkflowStore(partitionKey = "default") {
|
|
|
2190
2257
|
s.attempts += 1;
|
|
2191
2258
|
return true;
|
|
2192
2259
|
}
|
|
2260
|
+
async function saveStepCheckpoint(stepId, attempt, value) {
|
|
2261
|
+
const s = steps.get(stepId);
|
|
2262
|
+
if (!s || s.status !== "running" || s.attempts !== attempt) return false;
|
|
2263
|
+
s.metadata = {
|
|
2264
|
+
...s.metadata ?? {},
|
|
2265
|
+
checkpoint: clone(value)
|
|
2266
|
+
};
|
|
2267
|
+
return true;
|
|
2268
|
+
}
|
|
2193
2269
|
async function heartbeatStep(stepId, at) {
|
|
2194
2270
|
const s = steps.get(stepId);
|
|
2195
2271
|
if (!s || s.status !== "running") return false;
|
|
@@ -2198,6 +2274,14 @@ function createInMemoryWorkflowStore(partitionKey = "default") {
|
|
|
2198
2274
|
s.heartbeatAt = at;
|
|
2199
2275
|
return true;
|
|
2200
2276
|
}
|
|
2277
|
+
async function releaseStep(stepId, attempt) {
|
|
2278
|
+
const s = steps.get(stepId);
|
|
2279
|
+
if (!s || s.status !== "running" || s.attempts !== attempt) return false;
|
|
2280
|
+
s.status = "pending";
|
|
2281
|
+
s.attempts -= 1;
|
|
2282
|
+
s.heartbeatAt = null;
|
|
2283
|
+
return true;
|
|
2284
|
+
}
|
|
2201
2285
|
async function markStepPending(stepId) {
|
|
2202
2286
|
const s = steps.get(stepId);
|
|
2203
2287
|
if (s) s.status = "pending";
|
|
@@ -2386,7 +2470,9 @@ function createInMemoryWorkflowStore(partitionKey = "default") {
|
|
|
2386
2470
|
listSteps,
|
|
2387
2471
|
markStepRunning,
|
|
2388
2472
|
heartbeatStep,
|
|
2473
|
+
saveStepCheckpoint,
|
|
2389
2474
|
markStepPending,
|
|
2475
|
+
releaseStep,
|
|
2390
2476
|
markStepWaiting,
|
|
2391
2477
|
markStepMapping,
|
|
2392
2478
|
markStepCompensating,
|
|
@@ -2414,5 +2500,4 @@ function createInMemoryWorkflowStore(partitionKey = "default") {
|
|
|
2414
2500
|
}
|
|
2415
2501
|
};
|
|
2416
2502
|
}
|
|
2417
|
-
|
|
2418
|
-
export { PUBLIC_WORKFLOW_SCHEMA, PUBLIC_WORKFLOW_STEP_SCHEMA, STEP_DISPLAY_STATUS, STEP_DISPLAY_STATUS_SCHEMA, UNSETTLED_STEP_STATUSES, WORKFLOW_STATUS_SCHEMA, buildWorkflow, combineObservers, computeReadiness, createInMemoryStepGate, createInMemoryWorkflowStore, createMeterObserver, createRecordingObserver, createRecordingTracer, createStepHandlerRegistry, createWorkflowEngine, defineDecisionOutcomeStep, defineDecisionReviewStep, defineDecisionStep, defineMapStep, defineSleepStep, defineStep, defineSubWorkflowStep, defineWaitStep, err, explicitRetryability, functionDecider, gateRuleKeys, isRetryableError, isTerminalStepStatus, markRetryable, nonRetryableError, noopLogger, noopObserver, noopTracer, ok, resolveDecision, retryableError, toDisplayStepStatus, toPublicStep, toPublicWorkflow };
|
|
2503
|
+
export { PUBLIC_WORKFLOW_SCHEMA, PUBLIC_WORKFLOW_STEP_SCHEMA, STEP_DISPLAY_STATUS, STEP_DISPLAY_STATUS_SCHEMA, UNSETTLED_STEP_STATUSES, WORKFLOW_STATUS_SCHEMA, buildWorkflow, combineObservers, computeReadiness, continuationOf, continueStep, createInMemoryStepGate, createInMemoryWorkflowStore, createMeterObserver, createRecordingObserver, createRecordingTracer, createStepHandlerRegistry, createWorkflowEngine, defineDecisionOutcomeStep, defineDecisionReviewStep, defineDecisionStep, defineMapStep, defineSleepStep, defineStep, defineSubWorkflowStep, defineWaitStep, err, explicitRetryability, functionDecider, gateRuleKeys, isRetryableError, isTerminalStepStatus, markRetryable, nonRetryableError, noopLogger, noopObserver, noopTracer, ok, resolveDecision, retryableError, toDisplayStepStatus, toPublicStep, toPublicWorkflow };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/core/gate.d.ts
|
|
1
|
+
import { it as StepId, pt as WorkflowId } from "./index-BBO5BGS8.js";
|
|
3
2
|
interface StepGateRequest {
|
|
4
3
|
partitionKey: string;
|
|
5
4
|
workflowId: WorkflowId;
|
|
@@ -77,8 +76,6 @@ interface InMemoryStepGateConfig {
|
|
|
77
76
|
* for multi-worker concurrency control.
|
|
78
77
|
*/
|
|
79
78
|
declare function createInMemoryStepGate(config?: InMemoryStepGateConfig): StepGate;
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region src/core/observability.d.ts
|
|
82
79
|
/** Lifecycle event types the engine emits at every workflow/step transition. */
|
|
83
80
|
type FlowEventType = 'workflow.started' | 'workflow.completed' | 'workflow.failed' | 'workflow.cancelled' |
|
|
84
81
|
/** A `failed` workflow was put back in flight by `retryWorkflow`. */
|
|
@@ -199,5 +196,4 @@ interface RecordingTracer extends FlowTracer {
|
|
|
199
196
|
readonly spans: RecordedSpan[];
|
|
200
197
|
}
|
|
201
198
|
declare function createRecordingTracer(): RecordingTracer;
|
|
202
|
-
//#endregion
|
|
203
199
|
export { gateRuleKeys as C, createInMemoryStepGate as S, InMemoryStepGateConfig as _, FlowSpan as a, StepGateDecision as b, RecordingObserver as c, createMeterObserver as d, createRecordingObserver as f, ConcurrencyRule as g, noopTracer as h, FlowObserver as i, RecordingTracer as l, noopObserver as m, FlowEventType as n, FlowTracer as o, createRecordingTracer as p, FlowMeter as r, RecordedSpan as s, FlowEvent as t, combineObservers as u, RateRule as v, StepGateRequest as x, StepGate as y };
|
package/dist/pg-boss/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import "../index-
|
|
2
|
-
import { o as Logger, r as Dispatcher } from "../dispatcher-
|
|
1
|
+
import "../index-BBO5BGS8.js";
|
|
2
|
+
import { o as Logger, r as Dispatcher } from "../dispatcher-BnlAJuu3.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { PgBoss, Schedule } from "pg-boss";
|
|
5
|
-
//#region src/pg-boss/payload.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* The wire payload for a step job. It is the flow-core `DispatchStepPayload`
|
|
8
7
|
* plus the `partitionKey` — the worker needs the partition to reconstruct a
|
|
@@ -64,8 +63,6 @@ export interface StartJobContext extends WireStartPayload {
|
|
|
64
63
|
}
|
|
65
64
|
/** Resolve the per-delivery idempotency key. Explicit key wins over a prefix. */
|
|
66
65
|
export declare function resolveStartIdempotencyKey(payload: WireStartPayload, jobId: string): string | undefined;
|
|
67
|
-
//#endregion
|
|
68
|
-
//#region src/pg-boss/queue.d.ts
|
|
69
66
|
/**
|
|
70
67
|
* Create the step queue and its dead-letter queue if they do not exist. Call
|
|
71
68
|
* once at startup; the dispatcher also runs it from `prepare` and lazily on a
|
|
@@ -146,8 +143,6 @@ export declare function createPgBossDlqWorker(deps: PgBossDlqWorkerDeps): {
|
|
|
146
143
|
start: (onDlq: DlqProcessor) => Promise<void>;
|
|
147
144
|
stop: () => Promise<void>;
|
|
148
145
|
};
|
|
149
|
-
//#endregion
|
|
150
|
-
//#region src/pg-boss/scheduler.d.ts
|
|
151
146
|
/** Create the start queue (idempotent). */
|
|
152
147
|
export declare function ensureStartQueue(boss: PgBoss, queueName: string): Promise<void>;
|
|
153
148
|
export interface PgBossSchedulerDeps {
|
|
@@ -221,5 +216,4 @@ export interface PgBossStartWorkerDeps {
|
|
|
221
216
|
export declare function createPgBossStartWorker(deps: PgBossStartWorkerDeps): {
|
|
222
217
|
start: (process: StartJobProcessor) => Promise<void>;
|
|
223
218
|
stop: () => Promise<void>;
|
|
224
|
-
};
|
|
225
|
-
//#endregion
|
|
219
|
+
};
|
package/dist/pg-boss/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
2
|
+
// src/pg-boss/payload.ts
|
|
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
|
|
@@ -51,8 +51,7 @@ function resolveStartIdempotencyKey(payload, jobId) {
|
|
|
51
51
|
if (payload.idempotencyKey !== void 0) return payload.idempotencyKey;
|
|
52
52
|
if (payload.idempotencyKeyPrefix !== void 0) return `${payload.idempotencyKeyPrefix}:${jobId}`;
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
//#region src/pg-boss/queue.ts
|
|
54
|
+
// src/pg-boss/queue.ts
|
|
56
55
|
function dlqName(queueName) {
|
|
57
56
|
return `${queueName}-dlq`;
|
|
58
57
|
}
|
|
@@ -323,8 +322,7 @@ function createPgBossDlqWorker(deps) {
|
|
|
323
322
|
stop
|
|
324
323
|
};
|
|
325
324
|
}
|
|
326
|
-
|
|
327
|
-
//#region src/pg-boss/scheduler.ts
|
|
325
|
+
// src/pg-boss/scheduler.ts
|
|
328
326
|
/** Create the start queue (idempotent). */
|
|
329
327
|
async function ensureStartQueue(boss, queueName) {
|
|
330
328
|
try {
|
|
@@ -432,5 +430,4 @@ function createPgBossStartWorker(deps) {
|
|
|
432
430
|
stop
|
|
433
431
|
};
|
|
434
432
|
}
|
|
435
|
-
//#endregion
|
|
436
433
|
export { DEFAULT_STEP_QUEUE_CONFIG, WIRE_START_PAYLOAD_SCHEMA, WIRE_STEP_PAYLOAD_SCHEMA, createPgBossDispatcher, createPgBossDlqWorker, createPgBossScheduler, createPgBossStartWorker, createPgBossStepWorker, ensureStartQueue, ensureStepQueue, resolveStartIdempotencyKey };
|
package/dist/postgres/index.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { f as WorkflowStore } from "../store-
|
|
3
|
-
import { g as ConcurrencyRule, i as FlowObserver, t as FlowEvent, v as RateRule, y as StepGate } from "../observability-
|
|
1
|
+
import { pt as WorkflowId } from "../index-BBO5BGS8.js";
|
|
2
|
+
import { f as WorkflowStore } from "../store-oCpqAuTT.js";
|
|
3
|
+
import { g as ConcurrencyRule, i as FlowObserver, t as FlowEvent, v as RateRule, y as StepGate } from "../observability-C-wtcDDo.js";
|
|
4
4
|
import { Pool } from "pg";
|
|
5
|
-
//#region src/postgres/ddl.d.ts
|
|
6
5
|
/**
|
|
7
6
|
* Schema for the Postgres WorkflowStore adapter. Two tables mirror flow-core's
|
|
8
7
|
* record shapes. Apply once at deploy time (or call `applySchema`).
|
|
@@ -15,8 +14,6 @@ export declare function flowStoreDdl(schema?: string): string;
|
|
|
15
14
|
export declare const FLOW_STORE_DDL: string;
|
|
16
15
|
/** `CREATE SCHEMA IF NOT EXISTS` line for non-default schemas; empty for 'public'. */
|
|
17
16
|
export declare function createSchemaDdl(schema: string): string;
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/postgres/executor.d.ts
|
|
20
17
|
/** Minimal query result shape the adapters consume (a structural subset of `pg`'s `QueryResult`). */
|
|
21
18
|
export interface SqlResult<R> {
|
|
22
19
|
rows: R[];
|
|
@@ -103,8 +100,6 @@ export declare function pgliteExecutor(db: PgliteDatabaseLike): SqlExecutor;
|
|
|
103
100
|
* its `transaction` method (a `pg` `Pool` has none).
|
|
104
101
|
*/
|
|
105
102
|
export declare function toExecutor(db: Pool | SqlExecutor): SqlExecutor;
|
|
106
|
-
//#endregion
|
|
107
|
-
//#region src/postgres/store.d.ts
|
|
108
103
|
export interface PgWorkflowStoreDeps {
|
|
109
104
|
pool: Pool;
|
|
110
105
|
/** Partition this store instance is bound to (e.g. a tenant id). */
|
|
@@ -141,8 +136,6 @@ export declare function createWorkflowStore(deps: WorkflowStoreDeps): WorkflowSt
|
|
|
141
136
|
export declare function createPgWorkflowStore(deps: PgWorkflowStoreDeps): WorkflowStore;
|
|
142
137
|
/** Convenience: apply the flow-store schema to a database. */
|
|
143
138
|
export declare function applySchema(pool: Pool, ddl: string): Promise<void>;
|
|
144
|
-
//#endregion
|
|
145
|
-
//#region src/postgres/gate.d.ts
|
|
146
139
|
/** DDL for the gate's two tables. Apply once at deploy time (or via `applySchema`). */
|
|
147
140
|
export declare function flowGateDdl(opts?: {
|
|
148
141
|
schema?: string;
|
|
@@ -189,8 +182,6 @@ export declare function createStepGate(config: StepGateConfig): StepGate;
|
|
|
189
182
|
* Level Security should build an executor and call {@link createStepGate}.
|
|
190
183
|
*/
|
|
191
184
|
export declare function createPgStepGate(config: PgStepGateConfig): StepGate;
|
|
192
|
-
//#endregion
|
|
193
|
-
//#region src/postgres/event-sink.d.ts
|
|
194
185
|
/**
|
|
195
186
|
* DDL for the append-only run-history table. Apply once at deploy time (alongside
|
|
196
187
|
* `flowStoreDdl`), or call `applySchema(pool, flowEventDdl())`.
|
|
@@ -244,5 +235,4 @@ export declare function readFlowEvents(db: Pool | SqlExecutor, params: {
|
|
|
244
235
|
workflowId: WorkflowId;
|
|
245
236
|
partitionKey: string;
|
|
246
237
|
schema?: string;
|
|
247
|
-
}): Promise<FlowEvent[]>;
|
|
248
|
-
//#endregion
|
|
238
|
+
}): Promise<FlowEvent[]>;
|
package/dist/postgres/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as gateRuleKeys, r as UNSETTLED_STEP_STATUSES } from "../gate-
|
|
2
|
-
import "../core-
|
|
3
|
-
|
|
1
|
+
import { n as gateRuleKeys, r as UNSETTLED_STEP_STATUSES } from "../gate-BcjK7FoL.js";
|
|
2
|
+
import "../core-ut8VZ0sU.js";
|
|
3
|
+
// src/postgres/ddl.ts
|
|
4
4
|
/**
|
|
5
5
|
* Schema for the Postgres WorkflowStore adapter. Two tables mirror flow-core's
|
|
6
6
|
* record shapes. Apply once at deploy time (or call `applySchema`).
|
|
@@ -80,8 +80,7 @@ const FLOW_STORE_DDL = flowStoreDdl();
|
|
|
80
80
|
function createSchemaDdl(schema) {
|
|
81
81
|
return schema === "public" ? "" : `CREATE SCHEMA IF NOT EXISTS ${schema};\n\n`;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
//#region src/postgres/executor.ts
|
|
83
|
+
// src/postgres/executor.ts
|
|
85
84
|
/**
|
|
86
85
|
* Build a {@link SqlExecutor} over a `pg` {@link Pool}: top-level queries run on
|
|
87
86
|
* the pool (autocommit); `transaction` checks out a client and wraps `fn` in
|
|
@@ -195,8 +194,7 @@ function pgliteExecutor(db) {
|
|
|
195
194
|
function toExecutor(db) {
|
|
196
195
|
return "transaction" in db ? db : poolExecutor(db);
|
|
197
196
|
}
|
|
198
|
-
|
|
199
|
-
//#region src/postgres/store.ts
|
|
197
|
+
// src/postgres/store.ts
|
|
200
198
|
const iso = (v) => v == null ? null : v instanceof Date ? v.toISOString() : v;
|
|
201
199
|
function mapWorkflow(r) {
|
|
202
200
|
return {
|
|
@@ -322,6 +320,15 @@ function createWorkflowStore(deps) {
|
|
|
322
320
|
partitionKey
|
|
323
321
|
])).rowCount ?? 0) > 0;
|
|
324
322
|
}
|
|
323
|
+
async function saveStepCheckpoint(stepId, attempt, value) {
|
|
324
|
+
return ((await exec.query(`UPDATE ${STEP} SET metadata = jsonb_set(COALESCE(metadata, '{}'::jsonb), '{checkpoint}', $2::text::jsonb)
|
|
325
|
+
WHERE id = $1 AND partition_key = $3 AND status = 'running' AND attempts = $4`, [
|
|
326
|
+
stepId,
|
|
327
|
+
JSON.stringify(value ?? null, (_k, v) => typeof v === "string" ? v.replaceAll("\0", "") : v),
|
|
328
|
+
partitionKey,
|
|
329
|
+
attempt
|
|
330
|
+
])).rowCount ?? 0) > 0;
|
|
331
|
+
}
|
|
325
332
|
async function heartbeatStep(stepId, at) {
|
|
326
333
|
return ((await exec.query(`UPDATE ${STEP} s SET heartbeat_at = $2
|
|
327
334
|
FROM ${WF} w
|
|
@@ -334,6 +341,14 @@ function createWorkflowStore(deps) {
|
|
|
334
341
|
partitionKey
|
|
335
342
|
])).rowCount ?? 0) > 0;
|
|
336
343
|
}
|
|
344
|
+
async function releaseStep(stepId, attempt) {
|
|
345
|
+
return ((await exec.query(`UPDATE ${STEP} SET status = 'pending', attempts = attempts - 1, heartbeat_at = NULL
|
|
346
|
+
WHERE id = $1 AND partition_key = $2 AND status = 'running' AND attempts = $3`, [
|
|
347
|
+
stepId,
|
|
348
|
+
partitionKey,
|
|
349
|
+
attempt
|
|
350
|
+
])).rowCount ?? 0) > 0;
|
|
351
|
+
}
|
|
337
352
|
async function markStepPending(stepId) {
|
|
338
353
|
await exec.query(`UPDATE ${STEP} SET status = 'pending' WHERE id = $1 AND partition_key = $2`, [stepId, partitionKey]);
|
|
339
354
|
}
|
|
@@ -569,7 +584,9 @@ function createWorkflowStore(deps) {
|
|
|
569
584
|
listSteps,
|
|
570
585
|
markStepRunning,
|
|
571
586
|
heartbeatStep,
|
|
587
|
+
saveStepCheckpoint,
|
|
572
588
|
markStepPending,
|
|
589
|
+
releaseStep,
|
|
573
590
|
markStepWaiting,
|
|
574
591
|
markStepMapping,
|
|
575
592
|
markStepCompensating,
|
|
@@ -609,8 +626,7 @@ function createPgWorkflowStore(deps) {
|
|
|
609
626
|
async function applySchema(pool, ddl) {
|
|
610
627
|
await pool.query(ddl);
|
|
611
628
|
}
|
|
612
|
-
|
|
613
|
-
//#region src/postgres/gate.ts
|
|
629
|
+
// src/postgres/gate.ts
|
|
614
630
|
const DEFAULT_RATE_TABLE = "flow_rate_bucket";
|
|
615
631
|
const DEFAULT_LEASE_TABLE = "flow_step_lease";
|
|
616
632
|
/** DDL for the gate's two tables. Apply once at deploy time (or via `applySchema`). */
|
|
@@ -768,8 +784,7 @@ function createPgStepGate(config) {
|
|
|
768
784
|
...rest
|
|
769
785
|
});
|
|
770
786
|
}
|
|
771
|
-
|
|
772
|
-
//#region src/postgres/event-sink.ts
|
|
787
|
+
// src/postgres/event-sink.ts
|
|
773
788
|
/**
|
|
774
789
|
* DDL for the append-only run-history table. Apply once at deploy time (alongside
|
|
775
790
|
* `flowStoreDdl`), or call `applySchema(pool, flowEventDdl())`.
|
|
@@ -868,5 +883,4 @@ async function readFlowEvents(db, params) {
|
|
|
868
883
|
error: r.error ?? void 0
|
|
869
884
|
}));
|
|
870
885
|
}
|
|
871
|
-
//#endregion
|
|
872
886
|
export { FLOW_EVENT_DDL, FLOW_GATE_DDL, FLOW_STORE_DDL, applySchema, createEventSink, createPgEventSink, createPgStepGate, createPgWorkflowStore, createSchemaDdl, createStepGate, createWorkflowStore, flowEventDdl, flowGateDdl, flowStoreDdl, pgliteExecutor, poolExecutor, postgresJsExecutor, readFlowEvents, toExecutor };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/core/store.d.ts
|
|
1
|
+
import { _t as WorkflowStatus, gt as WorkflowRecord, it as StepId, ot as StepRecord, pt as WorkflowId, yt as WorkflowWithSteps } from "./index-BBO5BGS8.js";
|
|
3
2
|
interface CreateWorkflowStep {
|
|
4
3
|
key: string;
|
|
5
4
|
type: string;
|
|
@@ -171,6 +170,16 @@ interface WorkflowStore {
|
|
|
171
170
|
markStepRunning(stepId: StepId, startedAt: string): Promise<boolean>;
|
|
172
171
|
/** Reset a step to `pending` for a scheduled retry (leaves `attempts` as-is). */
|
|
173
172
|
markStepPending(stepId: StepId): Promise<void>;
|
|
173
|
+
/**
|
|
174
|
+
* Hand a running step back to `pending` **without spending the attempt** — a
|
|
175
|
+
* handler that continues later (`continueStep`): `attempts` goes back by one,
|
|
176
|
+
* and the heartbeat is cleared. Returns whether it wrote.
|
|
177
|
+
*
|
|
178
|
+
* Implementations MUST write only while the step is `running` on `attempt`
|
|
179
|
+
* (one conditional statement), like `saveStepCheckpoint`: a worker the sweeper
|
|
180
|
+
* gave up on must not hand back a step another attempt now runs.
|
|
181
|
+
*/
|
|
182
|
+
releaseStep(stepId: StepId, attempt: number): Promise<boolean>;
|
|
174
183
|
/**
|
|
175
184
|
* Stamp `heartbeatAt` on a step whose handler is still working, and report
|
|
176
185
|
* whether that step is still the caller's to run.
|
|
@@ -188,6 +197,18 @@ interface WorkflowStore {
|
|
|
188
197
|
* they own it. One statement, not a read-then-write: the sweeper races it.
|
|
189
198
|
*/
|
|
190
199
|
heartbeatStep(stepId: StepId, at: string): Promise<boolean>;
|
|
200
|
+
/**
|
|
201
|
+
* Keep what a running step has done so far, for the next attempt of the same
|
|
202
|
+
* step: `value` (JSON) under `metadata.checkpoint`, replacing the last one.
|
|
203
|
+
* Returns whether it was written.
|
|
204
|
+
*
|
|
205
|
+
* Implementations MUST write only while the step is `running` **and** still on
|
|
206
|
+
* `attempt` (its `attempts` counter) — one conditional statement — so a worker
|
|
207
|
+
* the sweeper has given up on cannot overwrite what the attempt that replaced
|
|
208
|
+
* it keeps. Optional: a store without it gives steps a checkpoint that never
|
|
209
|
+
* saves, and they start over on a retry as before.
|
|
210
|
+
*/
|
|
211
|
+
saveStepCheckpoint?(stepId: StepId, attempt: number, value: unknown): Promise<boolean>;
|
|
191
212
|
/**
|
|
192
213
|
* Flip a ready step to `waiting` — it suspends until `resumeStep` (waitForEvent)
|
|
193
214
|
* or a sub-workflow child settles. `waitingAt` is stamped on `startedAt`: it is
|
|
@@ -276,5 +297,4 @@ interface WorkflowStore {
|
|
|
276
297
|
*/
|
|
277
298
|
findStuckSteps(workflowId: WorkflowId, cutoff: string): Promise<StepRecord[]>;
|
|
278
299
|
}
|
|
279
|
-
//#endregion
|
|
280
300
|
export { CreatedWorkflow as a, ListWorkflowsFilters as c, UNSETTLED_STEP_STATUSES as d, WorkflowStore as f, CreateWorkflowStep as i, ReopenWorkflowParams as l, CompleteStepParams as n, FailStepParams as o, WorkflowTreeFilter as p, CreateWorkflowParams as r, FinishWorkflowParams as s, AddChildStep as t, TransactionalScope as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octaflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "Durable DAG workflow engine: Zod-typed steps over Postgres + pg-boss. The model layer is @octabits-io/models.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@electric-sql/pglite": "0.5.
|
|
83
|
+
"@electric-sql/pglite": "0.5.12",
|
|
84
84
|
"@testcontainers/postgresql": "^12.2.1",
|
|
85
85
|
"@types/node": "^26.6.5",
|
|
86
86
|
"@types/pg": "^8.23.1",
|
|
@@ -61,7 +61,7 @@ export const caseLoop = defineCaseLoop({
|
|
|
61
61
|
manager: {
|
|
62
62
|
system: 'You decide the next move, one at a time, by calling exactly one decision tool. …',
|
|
63
63
|
prompt: (ctx, result, facts) => caseFile(ctx.workflowInput, result, facts),
|
|
64
|
-
tools: (ctx) => readOnlyTools(ctx), //
|
|
64
|
+
tools: (ctx) => readOnlyTools(ctx), // agentTool('reads', tool({ … })) each — moves do the work
|
|
65
65
|
resources: ['llm'],
|
|
66
66
|
retry: { maxAttempts: 3 },
|
|
67
67
|
},
|
|
@@ -129,7 +129,7 @@ and "closes the case as stuck when the manager cannot decide".
|
|
|
129
129
|
|
|
130
130
|
- [ ] Every exit has a `why`; every non-move exit has a closer
|
|
131
131
|
- [ ] The policy is in `refuse` and reads facts from the store, not from the model's words
|
|
132
|
-
- [ ] The manager's tools only read
|
|
132
|
+
- [ ] The manager's tools only read (`agentTool('reads', …)`); an `outward` one is refused without `propose`
|
|
133
133
|
- [ ] Every outward step waits for a person and sends exactly what they released
|
|
134
134
|
- [ ] `startMove` passes the `caseId:moveNo` idempotency key
|
|
135
135
|
- [ ] The manager's model goes through the disclosure gate
|