@iworkflow/dbos 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +15 -13
- package/dist/scope.d.ts +2 -0
- package/package.json +2 -2
- package/src/index.ts +10 -12
- package/src/scope.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { DBOS, DBOSClient, Error as DBOSErrors } from "@dbos-inc/dbos-sdk";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
stepScope,
|
|
5
5
|
checkPriority,
|
|
6
6
|
DEFAULT_PRIORITY,
|
|
7
7
|
exitSignal,
|
|
@@ -9,8 +9,16 @@ import {
|
|
|
9
9
|
FlowError,
|
|
10
10
|
isFlowError
|
|
11
11
|
} from "@iworkflow/core";
|
|
12
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
12
|
import { Pool } from "pg";
|
|
13
|
+
|
|
14
|
+
// src/scope.ts
|
|
15
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
16
|
+
var alsScope = () => {
|
|
17
|
+
const als = new AsyncLocalStorage;
|
|
18
|
+
return { run: (rt, fn) => als.run(rt, fn), current: () => als.getStore() };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
14
22
|
var resultPoll = () => ({ pollingIntervalMs: Number(process.env.DBOS_RESULT_POLL_MS) || undefined });
|
|
15
23
|
var sysDb = () => process.env.DBOS_SYSTEM_DATABASE_URL ?? "postgres://iworkflow:iworkflow@localhost:55432/iworkflow";
|
|
16
24
|
var HEARTBEAT_DDL = [
|
|
@@ -119,7 +127,6 @@ class Semaphore {
|
|
|
119
127
|
class AttemptTimeout extends Error {
|
|
120
128
|
}
|
|
121
129
|
var sleepMs = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
122
|
-
var attemptNo = new AsyncLocalStorage;
|
|
123
130
|
async function withTimeout(p, ms) {
|
|
124
131
|
let timer;
|
|
125
132
|
try {
|
|
@@ -131,13 +138,13 @@ async function withTimeout(p, ms) {
|
|
|
131
138
|
clearTimeout(timer);
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
|
-
async function attempts(fn, args, opts, pool, alive, priority) {
|
|
141
|
+
async function attempts(fn, args, opts, pool, alive, priority, runtime) {
|
|
135
142
|
const max = opts.attempts ?? 3;
|
|
136
143
|
const started = Date.now();
|
|
137
144
|
let interval = opts.retryIntervalMs ?? 200;
|
|
138
145
|
for (let n = 1;; n++) {
|
|
139
146
|
try {
|
|
140
|
-
const attempt = () =>
|
|
147
|
+
const attempt = () => stepScope(alsScope).run(runtime(n), () => withTimeout(fn(...args), opts.timeoutMs ?? 60000));
|
|
141
148
|
return await (pool ? pool.run(attempt, priority) : attempt());
|
|
142
149
|
} catch (e) {
|
|
143
150
|
const stop = e instanceof Failure && e.nonRetryable || (opts.nonRetryableTypes ?? []).includes(typeOf(e));
|
|
@@ -184,7 +191,7 @@ function dbosRuntime() {
|
|
|
184
191
|
pool = new Pool({ connectionString: sysDb(), max: 2 });
|
|
185
192
|
const db = pool;
|
|
186
193
|
const patches = patchBook(db, def.app, def.patches ?? []);
|
|
187
|
-
|
|
194
|
+
const stepRuntime = (n) => ({
|
|
188
195
|
async heartbeat(details) {
|
|
189
196
|
await db.query(`INSERT INTO iwf.heartbeat (workflow_id, step_id, details) VALUES ($1, $2, $3)
|
|
190
197
|
ON CONFLICT (workflow_id, step_id) DO UPDATE SET details = EXCLUDED.details, at = now()`, [...stepKey(), JSON.stringify(details ?? null)]);
|
|
@@ -193,12 +200,7 @@ function dbosRuntime() {
|
|
|
193
200
|
const r = await db.query("SELECT details FROM iwf.heartbeat WHERE workflow_id = $1 AND step_id = $2", stepKey());
|
|
194
201
|
return r.rows[0]?.details ?? undefined;
|
|
195
202
|
},
|
|
196
|
-
attempt: () =>
|
|
197
|
-
const n = attemptNo.getStore();
|
|
198
|
-
if (n === undefined)
|
|
199
|
-
throw new Error("stepAttempt вне шага");
|
|
200
|
-
return n;
|
|
201
|
-
}
|
|
203
|
+
attempt: () => n
|
|
202
204
|
});
|
|
203
205
|
const pools = Object.fromEntries(Object.entries(def.pools ?? {}).map(([p, n]) => [p, new Semaphore(n)]));
|
|
204
206
|
const enqueue = (target, args, opts, reject, priority) => DBOS.enqueueWorkflowWithOptions({
|
|
@@ -246,7 +248,7 @@ function dbosRuntime() {
|
|
|
246
248
|
await sleepMs(100);
|
|
247
249
|
}
|
|
248
250
|
};
|
|
249
|
-
return DBOS.runStep(() => attempts(def.activities[step], stepArgs, opts, pool, alive, checkPriority(opts.priority) ?? ctx.priority), {
|
|
251
|
+
return DBOS.runStep(() => attempts(def.activities[step], stepArgs, opts, pool, alive, checkPriority(opts.priority) ?? ctx.priority, stepRuntime), {
|
|
250
252
|
name: step,
|
|
251
253
|
retriesAllowed: false
|
|
252
254
|
}).catch((e) => {
|
package/dist/scope.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iworkflow/dbos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "DBOS adapter for @iworkflow/core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"workflow",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"prepack": "bun run build"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@iworkflow/core": "^0.
|
|
40
|
+
"@iworkflow/core": "^0.6.1",
|
|
41
41
|
"pg": "^8.0.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Дочерний процесс в чужом модуле — enqueueWorkflowWithOptions по имени, без ссылки на функцию.
|
|
3
3
|
import { DBOS, DBOSClient, Error as DBOSErrors, type WorkflowStatus } from "@dbos-inc/dbos-sdk";
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
stepScope,
|
|
6
|
+
type StepRuntime,
|
|
6
7
|
checkPriority,
|
|
7
8
|
DEFAULT_PRIORITY,
|
|
8
9
|
exitSignal,
|
|
@@ -21,8 +22,8 @@ import {
|
|
|
21
22
|
type StartOptions,
|
|
22
23
|
type StepOptions,
|
|
23
24
|
} from "@iworkflow/core";
|
|
24
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
25
25
|
import { Pool } from "pg";
|
|
26
|
+
import { alsScope } from "./scope";
|
|
26
27
|
|
|
27
28
|
// Ожидание результата дочернего процесса — опрос базы, по умолчанию раз в секунду: это и есть
|
|
28
29
|
// задержка каждого перехода между модулями. DBOS_RESULT_POLL_MS уменьшает её ценой запросов к базе.
|
|
@@ -165,8 +166,7 @@ class AttemptTimeout extends Error {}
|
|
|
165
166
|
|
|
166
167
|
const sleepMs = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
167
168
|
|
|
168
|
-
|
|
169
|
-
const attemptNo = new AsyncLocalStorage<number>();
|
|
169
|
+
|
|
170
170
|
|
|
171
171
|
/** Попытка, урезанная по сроку: брошенная продолжает идти в фоне, её итог отбрасывается. */
|
|
172
172
|
async function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
|
|
@@ -189,13 +189,14 @@ async function attempts(
|
|
|
189
189
|
pool: Semaphore | undefined,
|
|
190
190
|
alive: () => Promise<boolean>,
|
|
191
191
|
priority: number | undefined,
|
|
192
|
+
runtime: (n: number) => StepRuntime,
|
|
192
193
|
) {
|
|
193
194
|
const max = opts.attempts ?? 3;
|
|
194
195
|
const started = Date.now();
|
|
195
196
|
let interval = opts.retryIntervalMs ?? 200;
|
|
196
197
|
for (let n = 1; ; n++) {
|
|
197
198
|
try {
|
|
198
|
-
const attempt = () =>
|
|
199
|
+
const attempt = () => stepScope(alsScope).run(runtime(n), () => withTimeout(fn(...args), opts.timeoutMs ?? 60_000));
|
|
199
200
|
return await (pool ? pool.run(attempt, priority) : attempt());
|
|
200
201
|
} catch (e) {
|
|
201
202
|
const stop = (e instanceof Failure && e.nonRetryable) || (opts.nonRetryableTypes ?? []).includes(typeOf(e));
|
|
@@ -251,7 +252,8 @@ export function dbosRuntime(): EngineRuntime {
|
|
|
251
252
|
pool = new Pool({ connectionString: sysDb(), max: 2 });
|
|
252
253
|
const db = pool;
|
|
253
254
|
const patches = patchBook(db, def.app, def.patches ?? []);
|
|
254
|
-
|
|
255
|
+
// Рантайм попытки шага: прогресс — в iwf.heartbeat по (процесс, шаг), номер — из цикла повторов.
|
|
256
|
+
const stepRuntime = (n: number): StepRuntime => ({
|
|
255
257
|
async heartbeat(details) {
|
|
256
258
|
await db.query(
|
|
257
259
|
`INSERT INTO iwf.heartbeat (workflow_id, step_id, details) VALUES ($1, $2, $3)
|
|
@@ -263,11 +265,7 @@ export function dbosRuntime(): EngineRuntime {
|
|
|
263
265
|
const r = await db.query("SELECT details FROM iwf.heartbeat WHERE workflow_id = $1 AND step_id = $2", stepKey());
|
|
264
266
|
return r.rows[0]?.details ?? undefined;
|
|
265
267
|
},
|
|
266
|
-
attempt: () =>
|
|
267
|
-
const n = attemptNo.getStore();
|
|
268
|
-
if (n === undefined) throw new Error("stepAttempt вне шага");
|
|
269
|
-
return n;
|
|
270
|
-
},
|
|
268
|
+
attempt: () => n,
|
|
271
269
|
});
|
|
272
270
|
const pools = Object.fromEntries(Object.entries(def.pools ?? {}).map(([p, n]) => [p, new Semaphore(n)]));
|
|
273
271
|
const enqueue = (
|
|
@@ -333,7 +331,7 @@ export function dbosRuntime(): EngineRuntime {
|
|
|
333
331
|
await sleepMs(100);
|
|
334
332
|
}
|
|
335
333
|
};
|
|
336
|
-
return DBOS.runStep(() => attempts(def.activities[step]!, stepArgs, opts, pool, alive, checkPriority(opts.priority) ?? ctx.priority), {
|
|
334
|
+
return DBOS.runStep(() => attempts(def.activities[step]!, stepArgs, opts, pool, alive, checkPriority(opts.priority) ?? ctx.priority, stepRuntime), {
|
|
337
335
|
name: step,
|
|
338
336
|
retriesAllowed: false,
|
|
339
337
|
}).catch((e) => {
|
package/src/scope.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Область шага на AsyncLocalStorage: stepScope ядра берёт её у первого адаптера процесса.
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import type { StepRuntime, StepScope } from "@iworkflow/core";
|
|
4
|
+
|
|
5
|
+
export const alsScope = (): StepScope => {
|
|
6
|
+
const als = new AsyncLocalStorage<StepRuntime>();
|
|
7
|
+
return { run: (rt, fn) => als.run(rt, fn), current: () => als.getStore() };
|
|
8
|
+
};
|