octaflow 0.17.0 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/octabits-io/octaflow/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/octaflow)
|
|
5
|
-
[](https://octaflow.octabits.io/)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
[](https://www.npmjs.com/package/octaflow)
|
|
8
8
|
|
|
@@ -14,12 +14,12 @@ restart. There is
|
|
|
14
14
|
no workflow server to operate, no control plane, and no vendor — it's a library you import,
|
|
15
15
|
not a platform you adopt.
|
|
16
16
|
|
|
17
|
-
### 📖 [Read the docs →](https://octabits
|
|
17
|
+
### 📖 [Read the docs →](https://octaflow.octabits.io/)
|
|
18
18
|
|
|
19
|
-
[Quick start](https://octabits
|
|
20
|
-
[Concepts](https://octabits
|
|
21
|
-
[Postgres & pg-boss](https://octabits
|
|
22
|
-
[API reference](https://octabits
|
|
19
|
+
[Quick start](https://octaflow.octabits.io/start/quick-start/) ·
|
|
20
|
+
[Concepts](https://octaflow.octabits.io/core/concepts/) ·
|
|
21
|
+
[Postgres & pg-boss](https://octaflow.octabits.io/running/postgres-and-pg-boss/) ·
|
|
22
|
+
[API reference](https://octaflow.octabits.io/reference/api/)
|
|
23
23
|
|
|
24
24
|
```ts
|
|
25
25
|
const wf = buildWorkflow({
|
|
@@ -73,7 +73,7 @@ fails and its completed steps roll back in reverse.
|
|
|
73
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">
|
|
74
74
|
</p>
|
|
75
75
|
|
|
76
|
-
Every line is an engine transition emitted through the [`FlowObserver`](https://octabits
|
|
76
|
+
Every line is an engine transition emitted through the [`FlowObserver`](https://octaflow.octabits.io/running/observability/) seam —
|
|
77
77
|
the same one you'd point at OpenTelemetry or an events table — not a `console.log` in a handler.
|
|
78
78
|
|
|
79
79
|
```bash
|
|
@@ -106,7 +106,7 @@ workflow is expressed**.
|
|
|
106
106
|
| **Model** | declarative — a static DAG value | imperative workflow code | imperative step functions | imperative tasks | job queue; flows are **trees** |
|
|
107
107
|
| **Fan-in / diamond deps** | yes | yes | yes | yes | no — a job can't be shared by two branches |
|
|
108
108
|
| **Inspect before running** | yes — the DAG is a value | no — the graph is the execution trace | no | no | yes — the flow tree is data |
|
|
109
|
-
| **Web dashboard** | **none** — [build one](https://octabits
|
|
109
|
+
| **Web dashboard** | **none** — [build one](https://octaflow.octabits.io/running/live-progress/) | yes | yes | yes | via third-party UIs |
|
|
110
110
|
| **Languages** | TypeScript | polyglot SDKs | TS, Python, Go, Kotlin | TS | Node (+ ports) |
|
|
111
111
|
| **Maturity** | **pre-1.0** | mature | mature | mature | mature, widely deployed |
|
|
112
112
|
|
|
@@ -120,14 +120,14 @@ that the others have earned.
|
|
|
120
120
|
Reach for something else if:
|
|
121
121
|
|
|
122
122
|
- **Your control flow is genuinely dynamic.** A declarative DAG is fixed at definition time.
|
|
123
|
-
Flow softens this a lot — [`when` guards and joins](https://octabits
|
|
124
|
-
[`defineMapStep`](https://octabits
|
|
125
|
-
[sub-workflows](https://octabits
|
|
123
|
+
Flow softens this a lot — [`when` guards and joins](https://octaflow.octabits.io/core/branching/) (if/else over a static graph),
|
|
124
|
+
[`defineMapStep`](https://octaflow.octabits.io/core/fan-out-and-map/) (runtime-sized fan-out),
|
|
125
|
+
[sub-workflows](https://octaflow.octabits.io/core/sub-workflows/), and [`waitForEvent`](https://octaflow.octabits.io/core/signals/) — but the
|
|
126
126
|
set of steps is still fixed up front. If your process is "**loop** until a human approves,
|
|
127
127
|
branching on whatever they typed," an imperative durable function will express it more
|
|
128
128
|
naturally: Flow can pick a branch, not invent a step.
|
|
129
129
|
- **You want a UI out of the box.** Flow ships a wire-safe projection
|
|
130
|
-
([`toPublicWorkflow`](https://octabits
|
|
130
|
+
([`toPublicWorkflow`](https://octaflow.octabits.io/extending/http/)) and lifecycle events, not a
|
|
131
131
|
dashboard. You build it.
|
|
132
132
|
- **You need non-TypeScript workers.** The DAG and its schemas are TypeScript values.
|
|
133
133
|
- **You can't run Postgres**, or you need throughput past what a Postgres-backed queue gives you.
|
|
@@ -239,25 +239,25 @@ pnpm add ai @ai-sdk/provider
|
|
|
239
239
|
## Documentation
|
|
240
240
|
|
|
241
241
|
Full docs — concepts, every feature, production wiring and the API reference — live at
|
|
242
|
-
**[octabits
|
|
242
|
+
**[octaflow.octabits.io](https://octaflow.octabits.io/)**.
|
|
243
243
|
|
|
244
244
|
| | |
|
|
245
245
|
|---|---|
|
|
246
|
-
| [Quick start](https://octabits
|
|
247
|
-
| [Concepts](https://octabits
|
|
248
|
-
| [Defining steps](https://octabits
|
|
249
|
-
| [Retry & timeout](https://octabits
|
|
250
|
-
| [Fan-out & map](https://octabits
|
|
251
|
-
| [Branching](https://octabits
|
|
252
|
-
| [Signals](https://octabits
|
|
253
|
-
| [Deadlines](https://octabits
|
|
254
|
-
| [Heartbeats](https://octabits
|
|
255
|
-
| [Postgres & pg-boss](https://octabits
|
|
256
|
-
| [Cancellation & recovery](https://octabits
|
|
257
|
-
| [Observability](https://octabits
|
|
258
|
-
| [The AI add-on](https://octabits
|
|
259
|
-
| [Extending](https://octabits
|
|
260
|
-
| [API reference](https://octabits
|
|
246
|
+
| [Quick start](https://octaflow.octabits.io/start/quick-start/) | a runnable workflow in one file, no database |
|
|
247
|
+
| [Concepts](https://octaflow.octabits.io/core/concepts/) | step, workflow, registry, store, dispatcher, engine, partition |
|
|
248
|
+
| [Defining steps](https://octaflow.octabits.io/core/defining-steps/) | `defineStep` and its variants |
|
|
249
|
+
| [Retry & timeout](https://octaflow.octabits.io/core/retry-and-timeout/) | attempt budgets, backoff, and how a failure is classified |
|
|
250
|
+
| [Fan-out & map](https://octaflow.octabits.io/core/fan-out-and-map/) | one child step per item of a runtime list |
|
|
251
|
+
| [Branching](https://octaflow.octabits.io/core/branching/) | `when` guards and join rules — if/else over a static DAG |
|
|
252
|
+
| [Signals](https://octaflow.octabits.io/core/signals/) · [Sub-workflows](https://octaflow.octabits.io/core/sub-workflows/) · [Saga](https://octaflow.octabits.io/core/saga-compensation/) | suspend, nest, and roll back |
|
|
253
|
+
| [Deadlines](https://octaflow.octabits.io/core/deadlines/) | budgets for a suspended step and for a whole run |
|
|
254
|
+
| [Heartbeats](https://octaflow.octabits.io/core/heartbeats/) | liveness for long steps, and interrupting one that was cancelled |
|
|
255
|
+
| [Postgres & pg-boss](https://octaflow.octabits.io/running/postgres-and-pg-boss/) | production wiring, workers, DLQ, cron |
|
|
256
|
+
| [Cancellation & recovery](https://octaflow.octabits.io/running/cancellation-and-recovery/) | cancelling a run, sweeping steps a crash left behind, and retrying a failed run |
|
|
257
|
+
| [Observability](https://octaflow.octabits.io/running/observability/) · [Live progress](https://octaflow.octabits.io/running/live-progress/) | lifecycle events, spans, and streaming them to a browser |
|
|
258
|
+
| [The AI add-on](https://octaflow.octabits.io/extending/ai/) | token/cost capture, quota, usage rollups |
|
|
259
|
+
| [Extending](https://octaflow.octabits.io/extending/interfaces/) | custom stores, dispatchers and gates |
|
|
260
|
+
| [API reference](https://octaflow.octabits.io/reference/api/) | every export, by entry point |
|
|
261
261
|
|
|
262
262
|
---
|
|
263
263
|
|
|
@@ -34,6 +34,20 @@ interface EnqueueOptions {
|
|
|
34
34
|
*/
|
|
35
35
|
interface Dispatcher {
|
|
36
36
|
enqueueStep(payload: DispatchStepPayload, options?: EnqueueOptions): Promise<Result<void, FlowErrorShape>>;
|
|
37
|
+
/**
|
|
38
|
+
* **Optional capability.** Whatever the dispatcher must do *outside* a store
|
|
39
|
+
* transaction before it can enqueue inside one — queue DDL, warming a queue
|
|
40
|
+
* cache. The engine calls it once, before its first transactional dispatch,
|
|
41
|
+
* and never while a transaction is open.
|
|
42
|
+
*
|
|
43
|
+
* Why it exists: a dispatcher that lazily creates its queue on first enqueue
|
|
44
|
+
* does so on its own connection. On a multi-connection database that merely
|
|
45
|
+
* costs a second connection; on a single-connection database (an embedded
|
|
46
|
+
* PGlite) it deadlocks — the transaction holds the only connection and waits
|
|
47
|
+
* for a query that waits for the transaction. `prepare` moves that work to
|
|
48
|
+
* where no transaction is open.
|
|
49
|
+
*/
|
|
50
|
+
prepare?(): Promise<Result<void, FlowErrorShape>>;
|
|
37
51
|
/**
|
|
38
52
|
* **Optional capability.** Enqueue inside the caller's store transaction, so
|
|
39
53
|
* the job and the state change that produced it commit together.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../index-BURAYZui.js";
|
|
2
2
|
import { n as Logger } from "../logger-BCus3AOa.js";
|
|
3
|
-
import { r as Dispatcher } from "../dispatcher-
|
|
3
|
+
import { r as Dispatcher } from "../dispatcher-g7Ct0J6w.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { PgBoss } from "pg-boss";
|
|
6
6
|
//#region src/dispatcher-pgboss/payload.d.ts
|
|
@@ -68,8 +68,15 @@ declare function resolveStartIdempotencyKey(payload: WireStartPayload, jobId: st
|
|
|
68
68
|
//#endregion
|
|
69
69
|
//#region src/dispatcher-pgboss/queue.d.ts
|
|
70
70
|
/**
|
|
71
|
-
* Create the step queue and its dead-letter queue
|
|
72
|
-
* startup; the dispatcher also
|
|
71
|
+
* Create the step queue and its dead-letter queue if they do not exist. Call
|
|
72
|
+
* once at startup; the dispatcher also runs it from `prepare` and lazily on a
|
|
73
|
+
* plain first enqueue.
|
|
74
|
+
*
|
|
75
|
+
* Only what is missing is created: pg-boss's `createQueue` evicts the queue's
|
|
76
|
+
* entry from its per-instance cache, and a `send` that misses that cache reads
|
|
77
|
+
* the queue on pg-boss's *own* connection — inside a store transaction on a
|
|
78
|
+
* single-connection database that is a deadlock. An existing queue is left
|
|
79
|
+
* alone, cache entry and all.
|
|
73
80
|
*/
|
|
74
81
|
declare function ensureStepQueue(boss: PgBoss, queueName: string, config?: StepQueueConfig): Promise<void>;
|
|
75
82
|
interface PgBossDispatcherDeps {
|
|
@@ -57,8 +57,15 @@ function dlqName(queueName) {
|
|
|
57
57
|
return `${queueName}-dlq`;
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
|
-
* Create the step queue and its dead-letter queue
|
|
61
|
-
* startup; the dispatcher also
|
|
60
|
+
* Create the step queue and its dead-letter queue if they do not exist. Call
|
|
61
|
+
* once at startup; the dispatcher also runs it from `prepare` and lazily on a
|
|
62
|
+
* plain first enqueue.
|
|
63
|
+
*
|
|
64
|
+
* Only what is missing is created: pg-boss's `createQueue` evicts the queue's
|
|
65
|
+
* entry from its per-instance cache, and a `send` that misses that cache reads
|
|
66
|
+
* the queue on pg-boss's *own* connection — inside a store transaction on a
|
|
67
|
+
* single-connection database that is a deadlock. An existing queue is left
|
|
68
|
+
* alone, cache entry and all.
|
|
62
69
|
*/
|
|
63
70
|
async function ensureStepQueue(boss, queueName, config = {}) {
|
|
64
71
|
const cfg = {
|
|
@@ -70,12 +77,12 @@ async function ensureStepQueue(boss, queueName, config = {}) {
|
|
|
70
77
|
if (error instanceof Error && error.message.includes("already exists")) return;
|
|
71
78
|
throw error;
|
|
72
79
|
};
|
|
73
|
-
try {
|
|
80
|
+
if (!await boss.getQueue(dlq)) try {
|
|
74
81
|
await boss.createQueue(dlq, { retryLimit: 0 });
|
|
75
82
|
} catch (e) {
|
|
76
83
|
swallowExists(e);
|
|
77
84
|
}
|
|
78
|
-
try {
|
|
85
|
+
if (!await boss.getQueue(queueName)) try {
|
|
79
86
|
await boss.createQueue(queueName, {
|
|
80
87
|
retryLimit: cfg.retryLimit,
|
|
81
88
|
retryDelay: cfg.retryDelay,
|
|
@@ -86,6 +93,24 @@ async function ensureStepQueue(boss, queueName, config = {}) {
|
|
|
86
93
|
swallowExists(e);
|
|
87
94
|
}
|
|
88
95
|
}
|
|
96
|
+
/** A job id that cannot exist — see {@link fillQueueCache}. */
|
|
97
|
+
const NIL_JOB_ID = "00000000-0000-0000-0000-000000000000";
|
|
98
|
+
/**
|
|
99
|
+
* pg-boss keeps a per-instance cache of queue rows and consults it on every
|
|
100
|
+
* `send` — on a miss it reads the queue on its *own* connection, whatever `db`
|
|
101
|
+
* the send was handed. `createQueue` evicts the entry rather than filling it,
|
|
102
|
+
* and the `PgBoss` facade keeps the cache private. So after creating our queue
|
|
103
|
+
* we fill the entry here, outside any transaction, through the one public call
|
|
104
|
+
* that reads the queue via the cache and then changes nothing: cancelling a job
|
|
105
|
+
* id that cannot exist. (A boss that does expose `getQueueCache` — a bare
|
|
106
|
+
* manager — is asked directly.) Without this the first transactional send would
|
|
107
|
+
* read on a second connection — a deadlock on a single-connection database.
|
|
108
|
+
*/
|
|
109
|
+
async function fillQueueCache(boss, queueName) {
|
|
110
|
+
const cache = boss.getQueueCache;
|
|
111
|
+
if (typeof cache === "function") await cache.call(boss, queueName);
|
|
112
|
+
else await boss.cancel(queueName, NIL_JOB_ID);
|
|
113
|
+
}
|
|
89
114
|
/** A flow-core `Dispatcher` backed by pg-boss. */
|
|
90
115
|
function createPgBossDispatcher(deps) {
|
|
91
116
|
const { boss, queueName, partitionKey } = deps;
|
|
@@ -93,7 +118,23 @@ function createPgBossDispatcher(deps) {
|
|
|
93
118
|
...DEFAULT_STEP_QUEUE_CONFIG,
|
|
94
119
|
...deps.config
|
|
95
120
|
};
|
|
96
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Queue DDL and cache warm, once. Memoised as a promise so concurrent first
|
|
123
|
+
* sends share it; cleared on failure so the next attempt retries. The engine
|
|
124
|
+
* calls this through `prepare` before its first transaction; a direct
|
|
125
|
+
* `enqueueStep` outside any transaction is free to trigger it lazily.
|
|
126
|
+
*/
|
|
127
|
+
let prepared = null;
|
|
128
|
+
function ensurePrepared() {
|
|
129
|
+
prepared ??= (async () => {
|
|
130
|
+
await ensureStepQueue(boss, queueName, cfg);
|
|
131
|
+
await fillQueueCache(boss, queueName);
|
|
132
|
+
})().catch((error) => {
|
|
133
|
+
prepared = null;
|
|
134
|
+
throw error;
|
|
135
|
+
});
|
|
136
|
+
return prepared;
|
|
137
|
+
}
|
|
97
138
|
/**
|
|
98
139
|
* Adapt flow's `SqlExecutor` to the shape pg-boss wants for a caller-supplied
|
|
99
140
|
* connection. The two are the same idea under different names.
|
|
@@ -118,10 +159,7 @@ function createPgBossDispatcher(deps) {
|
|
|
118
159
|
message: `Invalid step payload: ${parsed.error.message}`
|
|
119
160
|
}
|
|
120
161
|
};
|
|
121
|
-
|
|
122
|
-
await ensureStepQueue(boss, queueName, cfg);
|
|
123
|
-
ensured = true;
|
|
124
|
-
}
|
|
162
|
+
await ensurePrepared();
|
|
125
163
|
const startAfter = options?.startAfterSeconds;
|
|
126
164
|
const db = handle === void 0 ? void 0 : asPgBossDb(handle);
|
|
127
165
|
if (handle !== void 0 && !db) return {
|
|
@@ -159,6 +197,24 @@ function createPgBossDispatcher(deps) {
|
|
|
159
197
|
}
|
|
160
198
|
}
|
|
161
199
|
return {
|
|
200
|
+
/** Queue DDL + cache fill, outside any transaction. See `Dispatcher.prepare`. */
|
|
201
|
+
async prepare() {
|
|
202
|
+
try {
|
|
203
|
+
await ensurePrepared();
|
|
204
|
+
return {
|
|
205
|
+
ok: true,
|
|
206
|
+
value: void 0
|
|
207
|
+
};
|
|
208
|
+
} catch (error) {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: {
|
|
212
|
+
key: "queue_error",
|
|
213
|
+
message: error instanceof Error ? error.message : "Failed to prepare step queue"
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
},
|
|
162
218
|
enqueueStep: (payload, options) => send(payload, options),
|
|
163
219
|
/**
|
|
164
220
|
* Enqueue on the store's open transaction, so the job and the state change
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { $ as computeReadiness, A as StepConditionHandler, B as StepStatus, C as defineWaitStep, D as StartOptions, E as RetryPolicy, F as StepHandlerNotFoundError, G as WorkflowNotFoundError, H as WorkflowCreatedResult, I as StepHandlerRegistry, J as WorkflowStatus, K as WorkflowNotRetryableError, L as StepId, M as StepError, N as StepExecutionContext, O as StepCompensateHandler, P as StepHandler, Q as ReadinessPlan, R as StepRecord, S as defineSubWorkflowStep, T as InvalidWorkflowDefinitionError, U as WorkflowDefinition, V as WaitTimeoutPolicy, W as WorkflowId, X as JoinRule, Y as WorkflowWithSteps, Z as PlannedSkip, _ as WorkflowOutput, a as STEP_DISPLAY_STATUS, b as defineSleepStep, c as WORKFLOW_STATUS_SCHEMA, d as toPublicWorkflow, et as isTerminalStepStatus, f as StepDeps, g as TypedWorkflow, h as TypedStepContext, i as PublicWorkflowStep, it as ok, j as StepDefinition, k as StepCompensationContext, l as toDisplayStepStatus, m as TypedStep, n as PUBLIC_WORKFLOW_STEP_SCHEMA, nt as Result, o as STEP_DISPLAY_STATUS_SCHEMA, p as StepOutput, q as WorkflowRecord, r as PublicWorkflow, rt as err, s as StepDisplayStatus, t as PUBLIC_WORKFLOW_SCHEMA, tt as FlowErrorShape, u as toPublicStep, v as buildWorkflow, w as FlowError, x as defineStep, y as defineMapStep, z as StepRegistration } from "./index-BURAYZui.js";
|
|
2
2
|
import { n as Logger, r as noopLogger, t as LogAttributes } from "./logger-BCus3AOa.js";
|
|
3
3
|
import { A as WorkflowStore, C as CreateWorkflowStep, D as ListWorkflowsFilters, E as FinishWorkflowParams, O as ReopenWorkflowParams, S as CreateWorkflowParams, T as FailStepParams, _ as StepGateDecision, a as FlowTracer, b as AddChildStep, c as RecordingTracer, d as noopObserver, f as noopTracer, g as StepGate, h as RateRule, i as FlowSpan, k as TransactionalScope, l as createRecordingObserver, m as InMemoryStepGateConfig, n as FlowEventType, o as RecordedSpan, p as ConcurrencyRule, r as FlowObserver, s as RecordingObserver, t as FlowEvent, u as createRecordingTracer, v as StepGateRequest, w as CreatedWorkflow, x as CompleteStepParams, y as createInMemoryStepGate } from "./observability-B5KgSJfF.js";
|
|
4
|
-
import { i as EnqueueOptions, n as DispatchStepPayload, r as Dispatcher, t as DispatchKind } from "./dispatcher-
|
|
4
|
+
import { i as EnqueueOptions, n as DispatchStepPayload, r as Dispatcher, t as DispatchKind } from "./dispatcher-g7Ct0J6w.js";
|
|
5
5
|
import { a as WorkflowCompletedArgs, i as BuildStepContextArgs, n as BeforeStartArgs, o as WorkflowHooks, r as BeforeStartResult, t as AfterStepArgs } from "./hooks-BdxIcc92.js";
|
|
6
6
|
//#region src/core/registry.d.ts
|
|
7
7
|
/**
|
package/dist/index.js
CHANGED
|
@@ -318,15 +318,34 @@ function createWorkflowEngine(deps) {
|
|
|
318
318
|
*/
|
|
319
319
|
const atomicDispatch = typeof store.runInTransaction === "function" && typeof dispatcher.enqueueStepIn === "function";
|
|
320
320
|
/**
|
|
321
|
+
* The dispatcher's one-time setup (`Dispatcher.prepare`), run before the first
|
|
322
|
+
* transaction opens and never inside one. Memoised as a promise so concurrent
|
|
323
|
+
* first dispatches share it; cleared on failure so the next attempt retries.
|
|
324
|
+
*/
|
|
325
|
+
let prepared = null;
|
|
326
|
+
function ensurePrepared() {
|
|
327
|
+
if (!dispatcher.prepare) return Promise.resolve();
|
|
328
|
+
prepared ??= dispatcher.prepare().then((result) => {
|
|
329
|
+
if (!result.ok) throw new Error(`Dispatcher failed to prepare: ${result.error.message}`);
|
|
330
|
+
}).catch((error) => {
|
|
331
|
+
prepared = null;
|
|
332
|
+
throw error;
|
|
333
|
+
});
|
|
334
|
+
return prepared;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
321
337
|
* Run `fn` atomically when both halves support it, otherwise run it directly.
|
|
322
338
|
* `fn` receives the scope to write through — pass it to `dispatchReadyStep`.
|
|
323
339
|
*
|
|
324
340
|
* Only writes and enqueues belong in here. Anything that can run user code
|
|
325
341
|
* (saga compensation, hooks) must stay outside, or a rollback handler making a
|
|
326
|
-
* network call would hold a database transaction open while it does.
|
|
342
|
+
* network call would hold a database transaction open while it does. The
|
|
343
|
+
* dispatcher's own setup is the same kind of thing — it runs before the
|
|
344
|
+
* transaction, not in it (see `Dispatcher.prepare`).
|
|
327
345
|
*/
|
|
328
346
|
async function withDispatchScope(fn) {
|
|
329
347
|
if (!atomicDispatch) return fn(void 0);
|
|
348
|
+
await ensurePrepared();
|
|
330
349
|
return store.runInTransaction((scope) => fn(scope));
|
|
331
350
|
}
|
|
332
351
|
const nowIso = () => now().toISOString();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "octaflow",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.2",
|
|
4
4
|
"description": "Durable DAG workflow engine: Zod-typed steps over Postgres + pg-boss, with an optional AI add-on (token/cost/quota). Layered single package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"type": "git",
|
|
45
45
|
"url": "https://github.com/octabits-io/octaflow.git"
|
|
46
46
|
},
|
|
47
|
-
"homepage": "https://octabits
|
|
47
|
+
"homepage": "https://octaflow.octabits.io/",
|
|
48
48
|
"bugs": {
|
|
49
49
|
"url": "https://github.com/octabits-io/octaflow/issues"
|
|
50
50
|
},
|
|
@@ -82,23 +82,23 @@
|
|
|
82
82
|
}
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
|
-
"@ai-sdk/provider": "^4.0.
|
|
85
|
+
"@ai-sdk/provider": "^4.0.9",
|
|
86
86
|
"@changesets/changelog-github": "^1.0.0",
|
|
87
|
-
"@changesets/cli": "^3.0.
|
|
87
|
+
"@changesets/cli": "^3.0.1",
|
|
88
88
|
"@commitlint/cli": "^21.2.2",
|
|
89
89
|
"@commitlint/config-conventional": "^21.2.2",
|
|
90
90
|
"@testcontainers/postgresql": "^12.1.0",
|
|
91
|
-
"@types/node": "^26.
|
|
92
|
-
"@types/pg": "^8.
|
|
93
|
-
"ai": "^7.0.
|
|
91
|
+
"@types/node": "^26.4.0",
|
|
92
|
+
"@types/pg": "^8.23.1",
|
|
93
|
+
"ai": "^7.0.87",
|
|
94
94
|
"pg": "^8.23.0",
|
|
95
|
-
"pg-boss": "^12.
|
|
96
|
-
"simple-git-hooks": "^2.
|
|
95
|
+
"pg-boss": "^12.29.0",
|
|
96
|
+
"simple-git-hooks": "^2.14.0",
|
|
97
97
|
"testcontainers": "^12.1.0",
|
|
98
98
|
"tsdown": "^0.22.14",
|
|
99
99
|
"typescript": "^7.0.2",
|
|
100
|
-
"vitest": "^4.1.
|
|
101
|
-
"zod": "^4.4
|
|
100
|
+
"vitest": "^4.1.11",
|
|
101
|
+
"zod": "^4.5.4"
|
|
102
102
|
},
|
|
103
103
|
"scripts": {
|
|
104
104
|
"build": "tsdown",
|