@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 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
- bindStepRuntime,
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 attemptNo = new AsyncLocalStorage;
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 = () => attemptNo.run(n, () => withTimeout(fn(...args), opts.timeoutMs ?? 60000));
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
- bindStepRuntime({
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) => {
@@ -0,0 +1,2 @@
1
+ import type { StepScope } from "@iworkflow/core";
2
+ export declare const alsScope: () => StepScope;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iworkflow/dbos",
3
- "version": "0.5.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.5.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
- bindStepRuntime,
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
- /** Номер идущей попытки шага — для stepAttempt(). */
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 = () => attemptNo.run(n, () => withTimeout(fn(...args), opts.timeoutMs ?? 60_000));
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
- bindStepRuntime({
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
+ };