@iworkflow/dbos 0.5.0 → 0.6.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/dist/index.js +16 -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,7 @@ class Semaphore {
|
|
|
119
127
|
class AttemptTimeout extends Error {
|
|
120
128
|
}
|
|
121
129
|
var sleepMs = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
122
|
-
var
|
|
130
|
+
var scope = stepScope(alsScope);
|
|
123
131
|
async function withTimeout(p, ms) {
|
|
124
132
|
let timer;
|
|
125
133
|
try {
|
|
@@ -131,13 +139,13 @@ async function withTimeout(p, ms) {
|
|
|
131
139
|
clearTimeout(timer);
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
|
-
async function attempts(fn, args, opts, pool, alive, priority) {
|
|
142
|
+
async function attempts(fn, args, opts, pool, alive, priority, runtime) {
|
|
135
143
|
const max = opts.attempts ?? 3;
|
|
136
144
|
const started = Date.now();
|
|
137
145
|
let interval = opts.retryIntervalMs ?? 200;
|
|
138
146
|
for (let n = 1;; n++) {
|
|
139
147
|
try {
|
|
140
|
-
const attempt = () =>
|
|
148
|
+
const attempt = () => scope.run(runtime(n), () => withTimeout(fn(...args), opts.timeoutMs ?? 60000));
|
|
141
149
|
return await (pool ? pool.run(attempt, priority) : attempt());
|
|
142
150
|
} catch (e) {
|
|
143
151
|
const stop = e instanceof Failure && e.nonRetryable || (opts.nonRetryableTypes ?? []).includes(typeOf(e));
|
|
@@ -184,7 +192,7 @@ function dbosRuntime() {
|
|
|
184
192
|
pool = new Pool({ connectionString: sysDb(), max: 2 });
|
|
185
193
|
const db = pool;
|
|
186
194
|
const patches = patchBook(db, def.app, def.patches ?? []);
|
|
187
|
-
|
|
195
|
+
const stepRuntime = (n) => ({
|
|
188
196
|
async heartbeat(details) {
|
|
189
197
|
await db.query(`INSERT INTO iwf.heartbeat (workflow_id, step_id, details) VALUES ($1, $2, $3)
|
|
190
198
|
ON CONFLICT (workflow_id, step_id) DO UPDATE SET details = EXCLUDED.details, at = now()`, [...stepKey(), JSON.stringify(details ?? null)]);
|
|
@@ -193,12 +201,7 @@ function dbosRuntime() {
|
|
|
193
201
|
const r = await db.query("SELECT details FROM iwf.heartbeat WHERE workflow_id = $1 AND step_id = $2", stepKey());
|
|
194
202
|
return r.rows[0]?.details ?? undefined;
|
|
195
203
|
},
|
|
196
|
-
attempt: () =>
|
|
197
|
-
const n = attemptNo.getStore();
|
|
198
|
-
if (n === undefined)
|
|
199
|
-
throw new Error("stepAttempt вне шага");
|
|
200
|
-
return n;
|
|
201
|
-
}
|
|
204
|
+
attempt: () => n
|
|
202
205
|
});
|
|
203
206
|
const pools = Object.fromEntries(Object.entries(def.pools ?? {}).map(([p, n]) => [p, new Semaphore(n)]));
|
|
204
207
|
const enqueue = (target, args, opts, reject, priority) => DBOS.enqueueWorkflowWithOptions({
|
|
@@ -246,7 +249,7 @@ function dbosRuntime() {
|
|
|
246
249
|
await sleepMs(100);
|
|
247
250
|
}
|
|
248
251
|
};
|
|
249
|
-
return DBOS.runStep(() => attempts(def.activities[step], stepArgs, opts, pool, alive, checkPriority(opts.priority) ?? ctx.priority), {
|
|
252
|
+
return DBOS.runStep(() => attempts(def.activities[step], stepArgs, opts, pool, alive, checkPriority(opts.priority) ?? ctx.priority, stepRuntime), {
|
|
250
253
|
name: step,
|
|
251
254
|
retriesAllowed: false
|
|
252
255
|
}).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.0",
|
|
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.0",
|
|
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
|
+
const scope = stepScope(alsScope);
|
|
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 = () => scope.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
|
+
};
|