@iworkflow/dbos 0.2.0 → 0.3.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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/index.ts +28 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iworkflow/dbos",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "DBOS adapter for @iworkflow/core",
5
5
  "keywords": [
6
6
  "workflow",
@@ -29,7 +29,7 @@
29
29
  "test": "bun test"
30
30
  },
31
31
  "dependencies": {
32
- "@iworkflow/core": "^0.2.0",
32
+ "@iworkflow/core": "^0.3.0",
33
33
  "pg": "^8.0.0"
34
34
  },
35
35
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -167,7 +167,14 @@ async function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
167
167
  }
168
168
  }
169
169
 
170
- async function attempts(fn: Activity, args: unknown[], opts: StepOptions, pool: Semaphore | undefined) {
170
+ /** Повторы внутри одного runStep: процесс прерван (срок, отмена) — новых попыток нет. */
171
+ async function attempts(
172
+ fn: Activity,
173
+ args: unknown[],
174
+ opts: StepOptions,
175
+ pool: Semaphore | undefined,
176
+ alive: () => Promise<boolean>,
177
+ ) {
171
178
  const max = opts.attempts ?? 3;
172
179
  const started = Date.now();
173
180
  let interval = opts.retryIntervalMs ?? 200;
@@ -194,6 +201,9 @@ async function attempts(fn: Activity, args: unknown[], opts: StepOptions, pool:
194
201
  details: e instanceof Failure ? e.details : undefined,
195
202
  });
196
203
  await sleepMs(interval);
204
+ // После паузы: за неё процесс мог выйти по сроку или быть отменён.
205
+ // Сбой чтения статуса — не повод бросать шаг: повторяем дальше.
206
+ if (!(await alive().catch(() => true))) throw new FlowError("процесс прерван — повторы шага остановлены", { reason: "terminated" });
197
207
  interval = Math.min(interval * (opts.backoff ?? 1), opts.maxRetryIntervalMs ?? Infinity);
198
208
  }
199
209
  }
@@ -268,7 +278,23 @@ export function dbosRuntime(): EngineRuntime {
268
278
  const pool = opts.pool ? pools[opts.pool] : undefined;
269
279
  if (opts.pool && !pool) throw new Error(`пул "${opts.pool}" не объявлен в AppDefinition.pools модуля ${def.app}`);
270
280
  // heartbeatTimeoutMs у DBOS не действует: умерший процесс ловится рестартом, шаг повторяется.
271
- return DBOS.runStep(() => attempts(def.activities[step]!, stepArgs, opts, pool), {
281
+ // Срок вышел, а DBOS ещё не отменил процесс — ждём отмены: иначе процесс поймает отказ и завершится успехом.
282
+ const alive = async () => {
283
+ for (;;) {
284
+ const st = (
285
+ await db.query(
286
+ "SELECT status, workflow_deadline_epoch_ms AS deadline FROM dbos.workflow_status WHERE workflow_uuid = $1",
287
+ [workflowId],
288
+ )
289
+ ).rows[0];
290
+ if (!st || !["PENDING", "ENQUEUED"].includes(st.status)) return false;
291
+ if (st.deadline === null || Number(st.deadline) > Date.now()) return true;
292
+ // Отмену ставит DBOS; не поставил за минуту — не ждать вечно.
293
+ if (Date.now() - Number(st.deadline) > 60_000) return false;
294
+ await sleepMs(100);
295
+ }
296
+ };
297
+ return DBOS.runStep(() => attempts(def.activities[step]!, stepArgs, opts, pool, alive), {
272
298
  name: step,
273
299
  retriesAllowed: false,
274
300
  }).catch((e) => {