@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 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,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 = () => attemptNo.run(n, () => withTimeout(fn(...args), opts.timeoutMs ?? 60000));
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
- bindStepRuntime({
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) => {
@@ -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.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.5.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
- 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
+
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 = () => 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
- 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
+ };