@iworkflow/dbos 0.6.1 → 0.7.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 +15 -6
- package/package.json +2 -2
- package/src/index.ts +17 -6
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { DBOS, DBOSClient, Error as DBOSErrors } from "@dbos-inc/dbos-sdk";
|
|
3
3
|
import {
|
|
4
4
|
stepScope,
|
|
5
|
+
checkLimits,
|
|
5
6
|
checkPriority,
|
|
6
7
|
DEFAULT_PRIORITY,
|
|
7
8
|
exitSignal,
|
|
@@ -188,6 +189,7 @@ function dbosRuntime() {
|
|
|
188
189
|
let pool;
|
|
189
190
|
const exit = exitSignal();
|
|
190
191
|
async function launch(def) {
|
|
192
|
+
checkLimits(def);
|
|
191
193
|
pool = new Pool({ connectionString: sysDb(), max: 2 });
|
|
192
194
|
const db = pool;
|
|
193
195
|
const patches = patchBook(db, def.app, def.patches ?? []);
|
|
@@ -203,6 +205,7 @@ function dbosRuntime() {
|
|
|
203
205
|
attempt: () => n
|
|
204
206
|
});
|
|
205
207
|
const pools = Object.fromEntries(Object.entries(def.pools ?? {}).map(([p, n]) => [p, new Semaphore(n)]));
|
|
208
|
+
const main = def.maxConcurrentSteps ? new Semaphore(def.maxConcurrentSteps) : undefined;
|
|
206
209
|
const enqueue = (target, args, opts, reject, priority) => DBOS.enqueueWorkflowWithOptions({
|
|
207
210
|
queueName: target.app,
|
|
208
211
|
workflowName: target.flow,
|
|
@@ -233,7 +236,7 @@ function dbosRuntime() {
|
|
|
233
236
|
priority: row?.priority || DEFAULT_PRIORITY,
|
|
234
237
|
now: () => DBOS.now(),
|
|
235
238
|
step: (step, stepArgs, opts = {}) => {
|
|
236
|
-
const pool = opts.pool ? pools[opts.pool] :
|
|
239
|
+
const pool = opts.pool ? pools[opts.pool] : main;
|
|
237
240
|
if (opts.pool && !pool)
|
|
238
241
|
throw new Error(`пул "${opts.pool}" не объявлен в AppDefinition.pools модуля ${def.app}`);
|
|
239
242
|
const alive = async () => {
|
|
@@ -286,15 +289,20 @@ function dbosRuntime() {
|
|
|
286
289
|
const poll = Number(process.env.DBOS_QUEUE_POLL_MS) || undefined;
|
|
287
290
|
await DBOS.registerQueue(def.app, { minPollingIntervalMs: poll, onConflict: "always_update" });
|
|
288
291
|
}
|
|
292
|
+
const closePool = async () => {
|
|
293
|
+
await pool?.end().catch(() => {});
|
|
294
|
+
pool = undefined;
|
|
295
|
+
};
|
|
289
296
|
return {
|
|
290
297
|
done: () => exit.promise(),
|
|
291
298
|
async start(def) {
|
|
299
|
+
if (pool)
|
|
300
|
+
throw new Error("рантайм уже запущен");
|
|
292
301
|
exit.arm();
|
|
293
302
|
try {
|
|
294
303
|
await launch(def);
|
|
295
304
|
} catch (e) {
|
|
296
|
-
await
|
|
297
|
-
pool = undefined;
|
|
305
|
+
await closePool();
|
|
298
306
|
exit.reject(e);
|
|
299
307
|
throw e;
|
|
300
308
|
}
|
|
@@ -302,8 +310,8 @@ function dbosRuntime() {
|
|
|
302
310
|
async stop() {
|
|
303
311
|
try {
|
|
304
312
|
await DBOS.shutdown();
|
|
305
|
-
await pool?.end();
|
|
306
313
|
} finally {
|
|
314
|
+
await closePool();
|
|
307
315
|
exit.resolve();
|
|
308
316
|
}
|
|
309
317
|
}
|
|
@@ -342,8 +350,9 @@ async function dbosClient() {
|
|
|
342
350
|
workflowID: opts.id,
|
|
343
351
|
applicationName: target.app,
|
|
344
352
|
...opts.timeoutMs ? { workflowTimeoutMS: opts.timeoutMs } : {},
|
|
345
|
-
priority: checkPriority(opts.priority) ?? DEFAULT_PRIORITY
|
|
346
|
-
|
|
353
|
+
priority: checkPriority(opts.priority) ?? DEFAULT_PRIORITY,
|
|
354
|
+
...opts.onExisting === "reject" ? { workflowIDReusePolicy: "reject" } : {}
|
|
355
|
+
}, ...args).catch(alreadyStarted(opts.id));
|
|
347
356
|
return wrap(h.workflowID);
|
|
348
357
|
},
|
|
349
358
|
async signalWithStart(target, args, opts, topic, value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iworkflow/dbos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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.7.0",
|
|
41
41
|
"pg": "^8.0.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { DBOS, DBOSClient, Error as DBOSErrors, type WorkflowStatus } from "@dbo
|
|
|
4
4
|
import {
|
|
5
5
|
stepScope,
|
|
6
6
|
type StepRuntime,
|
|
7
|
+
checkLimits,
|
|
7
8
|
checkPriority,
|
|
8
9
|
DEFAULT_PRIORITY,
|
|
9
10
|
exitSignal,
|
|
@@ -249,6 +250,7 @@ export function dbosRuntime(): EngineRuntime {
|
|
|
249
250
|
// Воркеров у DBOS нет: done() — до stop или сбоя start; падения исполнителя DBOS не видно.
|
|
250
251
|
const exit = exitSignal();
|
|
251
252
|
async function launch(def: AppDefinition) {
|
|
253
|
+
checkLimits(def);
|
|
252
254
|
pool = new Pool({ connectionString: sysDb(), max: 2 });
|
|
253
255
|
const db = pool;
|
|
254
256
|
const patches = patchBook(db, def.app, def.patches ?? []);
|
|
@@ -268,6 +270,8 @@ export function dbosRuntime(): EngineRuntime {
|
|
|
268
270
|
attempt: () => n,
|
|
269
271
|
});
|
|
270
272
|
const pools = Object.fromEntries(Object.entries(def.pools ?? {}).map(([p, n]) => [p, new Semaphore(n)]));
|
|
273
|
+
// Шаги вне пулов — под общим пределом модуля, как maxConcurrentActivityTaskExecutions у Temporal.
|
|
274
|
+
const main = def.maxConcurrentSteps ? new Semaphore(def.maxConcurrentSteps) : undefined;
|
|
271
275
|
const enqueue = (
|
|
272
276
|
target: { app: string; flow: string },
|
|
273
277
|
args: unknown[],
|
|
@@ -312,7 +316,7 @@ export function dbosRuntime(): EngineRuntime {
|
|
|
312
316
|
priority: row?.priority || DEFAULT_PRIORITY,
|
|
313
317
|
now: () => DBOS.now(),
|
|
314
318
|
step: (step, stepArgs, opts = {}) => {
|
|
315
|
-
const pool = opts.pool ? pools[opts.pool] :
|
|
319
|
+
const pool = opts.pool ? pools[opts.pool] : main;
|
|
316
320
|
if (opts.pool && !pool) throw new Error(`пул "${opts.pool}" не объявлен в AppDefinition.pools модуля ${def.app}`);
|
|
317
321
|
// heartbeatTimeoutMs у DBOS не действует: умерший процесс ловится рестартом, шаг повторяется.
|
|
318
322
|
// Срок вышел, а DBOS ещё не отменил процесс — ждём отмены: иначе процесс поймает отказ и завершится успехом.
|
|
@@ -382,16 +386,21 @@ export function dbosRuntime(): EngineRuntime {
|
|
|
382
386
|
const poll = Number(process.env.DBOS_QUEUE_POLL_MS) || undefined;
|
|
383
387
|
await DBOS.registerQueue(def.app, { minPollingIntervalMs: poll, onConflict: "always_update" });
|
|
384
388
|
}
|
|
389
|
+
const closePool = async () => {
|
|
390
|
+
await pool?.end().catch(() => {});
|
|
391
|
+
pool = undefined;
|
|
392
|
+
};
|
|
385
393
|
return {
|
|
386
394
|
done: () => exit.promise(),
|
|
387
395
|
async start(def: AppDefinition) {
|
|
396
|
+
// Второй start не трогает пул и done() идущего: его сбой закрыл бы живое соединение.
|
|
397
|
+
if (pool) throw new Error("рантайм уже запущен");
|
|
388
398
|
exit.arm();
|
|
389
399
|
// Не запустился — done() отклоняется: для наблюдателя это сбой, а не штатный выход.
|
|
390
400
|
try {
|
|
391
401
|
await launch(def);
|
|
392
402
|
} catch (e) {
|
|
393
|
-
await
|
|
394
|
-
pool = undefined;
|
|
403
|
+
await closePool();
|
|
395
404
|
exit.reject(e);
|
|
396
405
|
throw e;
|
|
397
406
|
}
|
|
@@ -399,8 +408,9 @@ export function dbosRuntime(): EngineRuntime {
|
|
|
399
408
|
async stop() {
|
|
400
409
|
try {
|
|
401
410
|
await DBOS.shutdown();
|
|
402
|
-
await pool?.end();
|
|
403
411
|
} finally {
|
|
412
|
+
// Пул закрывается и при сбое shutdown: иначе соединения копятся от цикла к циклу.
|
|
413
|
+
await closePool();
|
|
404
414
|
exit.resolve();
|
|
405
415
|
}
|
|
406
416
|
},
|
|
@@ -444,12 +454,13 @@ export async function dbosClient(): Promise<EngineClient> {
|
|
|
444
454
|
...(opts.timeoutMs ? { workflowTimeoutMS: opts.timeoutMs } : {}),
|
|
445
455
|
// Без приоритета DBOS ставит 0 — впереди всех; порт по умолчанию 3, как Temporal.
|
|
446
456
|
priority: checkPriority(opts.priority) ?? DEFAULT_PRIORITY,
|
|
457
|
+
...(opts.onExisting === "reject" ? { workflowIDReusePolicy: "reject" as const } : {}),
|
|
447
458
|
},
|
|
448
459
|
...args,
|
|
449
|
-
);
|
|
460
|
+
).catch(alreadyStarted(opts.id));
|
|
450
461
|
return wrap(h.workflowID);
|
|
451
462
|
},
|
|
452
|
-
async signalWithStart<T>(target: FlowRef, args: unknown[], opts: StartOptions, topic: string, value: unknown) {
|
|
463
|
+
async signalWithStart<T>(target: FlowRef, args: unknown[], opts: Omit<StartOptions, "onExisting">, topic: string, value: unknown) {
|
|
453
464
|
// Не атомарно, но без потерь: сигнал ждёт в очереди темы, пока процесс до него дойдёт.
|
|
454
465
|
const h = await self.start<T>(target, args, opts);
|
|
455
466
|
// id завершённого DBOS не переиспользует: сигнал ушёл бы в никуда.
|