pg-workflows 0.14.0 → 0.16.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/client.entry.cjs +227 -93
- package/dist/client.entry.d.cts +31 -1
- package/dist/client.entry.d.ts +31 -1
- package/dist/client.entry.js +4 -2
- package/dist/client.entry.js.map +10 -10
- package/dist/index.cjs +249 -269
- package/dist/index.d.cts +44 -11
- package/dist/index.d.ts +44 -11
- package/dist/index.js +29 -182
- package/dist/index.js.map +12 -13
- package/dist/shared/{chunk-w3qcrrwx.js → chunk-d204nxd5.js} +167 -36
- package/dist/shared/chunk-d204nxd5.js.map +17 -0
- package/package.json +14 -25
- package/LICENSE +0 -22
- package/README.md +0 -214
- package/dist/shared/chunk-w3qcrrwx.js.map +0 -17
package/dist/client.entry.cjs
CHANGED
|
@@ -67,6 +67,7 @@ __export(exports_client_entry, {
|
|
|
67
67
|
createWorkflowRef: () => createWorkflowRef,
|
|
68
68
|
WorkflowStatus: () => WorkflowStatus,
|
|
69
69
|
WorkflowRunNotFoundError: () => WorkflowRunNotFoundError,
|
|
70
|
+
WorkflowRunInProgressError: () => WorkflowRunInProgressError,
|
|
70
71
|
WorkflowEngineError: () => WorkflowEngineError,
|
|
71
72
|
WorkflowClient: () => WorkflowClient
|
|
72
73
|
});
|
|
@@ -91,10 +92,11 @@ var WAIT_FOR_TIMELINE_SUFFIX = "wait-for";
|
|
|
91
92
|
var invokeChildWorkflowTimelineKey = (stepId) => `${stepId}-${INVOKE_CHILD_WORKFLOW_TIMELINE_SUFFIX}`;
|
|
92
93
|
var waitForTimelineKey = (stepId) => `${stepId}-${WAIT_FOR_TIMELINE_SUFFIX}`;
|
|
93
94
|
var isInvokeChildWorkflowTimelineEntry = (entry) => !!entry && typeof entry === "object" && ("invokeChildWorkflow" in entry);
|
|
95
|
+
var isStepCached = (timeline, stepId) => timeline[stepId]?.output !== undefined || isInvokeChildWorkflowTimelineEntry(timeline[invokeChildWorkflowTimelineKey(stepId)]);
|
|
94
96
|
|
|
95
97
|
// src/db/migration.ts
|
|
96
98
|
var MIGRATION_LOCK_ID = 738291645;
|
|
97
|
-
var CURRENT_SCHEMA_VERSION =
|
|
99
|
+
var CURRENT_SCHEMA_VERSION = 7;
|
|
98
100
|
async function runMigrations(db) {
|
|
99
101
|
if (await isSchemaUpToDate(db)) {
|
|
100
102
|
return;
|
|
@@ -173,6 +175,14 @@ async function runMigrations(db) {
|
|
|
173
175
|
if (currentVersion < 6) {
|
|
174
176
|
commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS priority integer DEFAULT 0 NOT NULL");
|
|
175
177
|
}
|
|
178
|
+
if (currentVersion < 7) {
|
|
179
|
+
commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS singleton boolean DEFAULT false NOT NULL");
|
|
180
|
+
commands.push(`
|
|
181
|
+
CREATE UNIQUE INDEX IF NOT EXISTS workflow_runs_singleton_active_idx
|
|
182
|
+
ON workflow_runs (workflow_id)
|
|
183
|
+
WHERE singleton AND status IN ('pending', 'running')
|
|
184
|
+
`);
|
|
185
|
+
}
|
|
176
186
|
if (currentVersion === 0) {
|
|
177
187
|
commands.push(`INSERT INTO workflow_schema_version (version) VALUES (${CURRENT_SCHEMA_VERSION})`);
|
|
178
188
|
} else {
|
|
@@ -212,9 +222,95 @@ async function getCurrentVersion(db) {
|
|
|
212
222
|
|
|
213
223
|
// src/db/queries.ts
|
|
214
224
|
var import_ksuid = __toESM(require("ksuid"));
|
|
225
|
+
|
|
226
|
+
// src/error.ts
|
|
227
|
+
function validateWorkflowId(workflowId) {
|
|
228
|
+
if (workflowId.length > MAX_WORKFLOW_ID_LENGTH) {
|
|
229
|
+
throw new WorkflowEngineError(`workflowId exceeds maximum length of ${MAX_WORKFLOW_ID_LENGTH} characters (got ${workflowId.length})`, workflowId);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function validateResourceId(resourceId) {
|
|
233
|
+
if (resourceId != null && resourceId.length > MAX_RESOURCE_ID_LENGTH) {
|
|
234
|
+
throw new WorkflowEngineError(`resourceId exceeds maximum length of ${MAX_RESOURCE_ID_LENGTH} characters (got ${resourceId.length})`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
class WorkflowEngineError extends Error {
|
|
239
|
+
workflowId;
|
|
240
|
+
runId;
|
|
241
|
+
cause;
|
|
242
|
+
issues;
|
|
243
|
+
constructor(message, workflowId, runId, cause = undefined, issues) {
|
|
244
|
+
super(message);
|
|
245
|
+
this.workflowId = workflowId;
|
|
246
|
+
this.runId = runId;
|
|
247
|
+
this.cause = cause;
|
|
248
|
+
this.issues = issues;
|
|
249
|
+
this.name = "WorkflowEngineError";
|
|
250
|
+
if (Error.captureStackTrace) {
|
|
251
|
+
Error.captureStackTrace(this, WorkflowEngineError);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
257
|
+
constructor(runId, workflowId) {
|
|
258
|
+
super("Workflow run not found", workflowId, runId);
|
|
259
|
+
this.name = "WorkflowRunNotFoundError";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
class WorkflowRunInProgressError extends WorkflowEngineError {
|
|
264
|
+
constructor(workflowId, runId) {
|
|
265
|
+
super(`Workflow "${workflowId}" already has a running run; wait for it to pause, complete, fail, or be cancelled`, workflowId, runId);
|
|
266
|
+
this.name = "WorkflowRunInProgressError";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/types.ts
|
|
271
|
+
var WorkflowStatus;
|
|
272
|
+
((WorkflowStatus2) => {
|
|
273
|
+
WorkflowStatus2["PENDING"] = "pending";
|
|
274
|
+
WorkflowStatus2["RUNNING"] = "running";
|
|
275
|
+
WorkflowStatus2["PAUSED"] = "paused";
|
|
276
|
+
WorkflowStatus2["COMPLETED"] = "completed";
|
|
277
|
+
WorkflowStatus2["FAILED"] = "failed";
|
|
278
|
+
WorkflowStatus2["CANCELLED"] = "cancelled";
|
|
279
|
+
})(WorkflowStatus ||= {});
|
|
280
|
+
var _STEP_BASE_METHOD_TO_TYPE = {
|
|
281
|
+
run: "run" /* RUN */,
|
|
282
|
+
waitFor: "waitFor" /* WAIT_FOR */,
|
|
283
|
+
waitUntil: "waitUntil" /* WAIT_UNTIL */,
|
|
284
|
+
delay: "delay" /* DELAY */,
|
|
285
|
+
sleep: "delay" /* DELAY */,
|
|
286
|
+
pause: "pause" /* PAUSE */,
|
|
287
|
+
poll: "poll" /* POLL */,
|
|
288
|
+
invokeChildWorkflow: "invokeChildWorkflow" /* INVOKE_CHILD_WORKFLOW */
|
|
289
|
+
};
|
|
290
|
+
var STEP_BASE_METHOD_TYPES = new Map(Object.entries(_STEP_BASE_METHOD_TO_TYPE));
|
|
291
|
+
|
|
292
|
+
// src/db/queries.ts
|
|
293
|
+
var EMPTY_RUN_STATS = {
|
|
294
|
+
["pending" /* PENDING */]: 0,
|
|
295
|
+
["running" /* RUNNING */]: 0,
|
|
296
|
+
["paused" /* PAUSED */]: 0,
|
|
297
|
+
["completed" /* COMPLETED */]: 0,
|
|
298
|
+
["failed" /* FAILED */]: 0,
|
|
299
|
+
["cancelled" /* CANCELLED */]: 0
|
|
300
|
+
};
|
|
215
301
|
function generateKSUID(prefix) {
|
|
216
302
|
return `${prefix ? `${prefix}_` : ""}${import_ksuid.default.randomSync().string}`;
|
|
217
303
|
}
|
|
304
|
+
function isUniqueViolation(error) {
|
|
305
|
+
if (!error || typeof error !== "object") {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
if ("code" in error && error.code === "23505") {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
const cause = "cause" in error ? error.cause : undefined;
|
|
312
|
+
return Boolean(cause && typeof cause === "object" && "code" in cause && cause.code === "23505");
|
|
313
|
+
}
|
|
218
314
|
function mapRowToWorkflowRun(row) {
|
|
219
315
|
return {
|
|
220
316
|
id: row.id,
|
|
@@ -240,7 +336,8 @@ function mapRowToWorkflowRun(row) {
|
|
|
240
336
|
parentRunId: row.parent_run_id,
|
|
241
337
|
parentStepId: row.parent_step_id,
|
|
242
338
|
parentResourceId: row.parent_resource_id,
|
|
243
|
-
scheduledAt: row.scheduled_at ? new Date(row.scheduled_at) : null
|
|
339
|
+
scheduledAt: row.scheduled_at ? new Date(row.scheduled_at) : null,
|
|
340
|
+
singleton: Boolean(row.singleton)
|
|
244
341
|
};
|
|
245
342
|
}
|
|
246
343
|
async function insertWorkflowRun({
|
|
@@ -256,11 +353,20 @@ async function insertWorkflowRun({
|
|
|
256
353
|
parentRunId,
|
|
257
354
|
parentStepId,
|
|
258
355
|
parentResourceId,
|
|
259
|
-
scheduledAt
|
|
356
|
+
scheduledAt,
|
|
357
|
+
singleton = false
|
|
260
358
|
}, db) {
|
|
261
359
|
const runId = generateKSUID("run");
|
|
262
360
|
const now = new Date;
|
|
263
|
-
|
|
361
|
+
if (singleton) {
|
|
362
|
+
const active = await getActiveSingletonRun(workflowId, db);
|
|
363
|
+
if (active) {
|
|
364
|
+
throw new WorkflowRunInProgressError(workflowId, active.id);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
let result;
|
|
368
|
+
try {
|
|
369
|
+
result = await db.executeSql(`INSERT INTO workflow_runs (
|
|
264
370
|
id,
|
|
265
371
|
resource_id,
|
|
266
372
|
workflow_id,
|
|
@@ -278,30 +384,39 @@ async function insertWorkflowRun({
|
|
|
278
384
|
parent_run_id,
|
|
279
385
|
parent_step_id,
|
|
280
386
|
parent_resource_id,
|
|
281
|
-
scheduled_at
|
|
387
|
+
scheduled_at,
|
|
388
|
+
singleton
|
|
282
389
|
)
|
|
283
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
|
|
390
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
|
|
284
391
|
ON CONFLICT (idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING
|
|
285
392
|
RETURNING *`, [
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
393
|
+
runId,
|
|
394
|
+
resourceId ?? null,
|
|
395
|
+
workflowId,
|
|
396
|
+
currentStepId,
|
|
397
|
+
status,
|
|
398
|
+
JSON.stringify(input),
|
|
399
|
+
maxRetries,
|
|
400
|
+
priority,
|
|
401
|
+
timeoutAt,
|
|
402
|
+
now,
|
|
403
|
+
now,
|
|
404
|
+
"{}",
|
|
405
|
+
0,
|
|
406
|
+
idempotencyKey ?? null,
|
|
407
|
+
parentRunId ?? null,
|
|
408
|
+
parentStepId ?? null,
|
|
409
|
+
parentResourceId ?? null,
|
|
410
|
+
scheduledAt ?? null,
|
|
411
|
+
singleton
|
|
412
|
+
]);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
if (singleton && isUniqueViolation(error)) {
|
|
415
|
+
const active = await getActiveSingletonRun(workflowId, db);
|
|
416
|
+
throw new WorkflowRunInProgressError(workflowId, active?.id);
|
|
417
|
+
}
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
305
420
|
if (result.rows[0]) {
|
|
306
421
|
return { run: mapRowToWorkflowRun(result.rows[0]), created: true };
|
|
307
422
|
}
|
|
@@ -313,6 +428,20 @@ async function insertWorkflowRun({
|
|
|
313
428
|
}
|
|
314
429
|
return { run: mapRowToWorkflowRun(existing.rows[0]), created: false };
|
|
315
430
|
}
|
|
431
|
+
async function getActiveSingletonRun(workflowId, db) {
|
|
432
|
+
const result = await db.executeSql(`SELECT * FROM workflow_runs
|
|
433
|
+
WHERE workflow_id = $1
|
|
434
|
+
AND singleton
|
|
435
|
+
AND status IN ('pending', 'running')
|
|
436
|
+
LIMIT 1`, [workflowId]);
|
|
437
|
+
return result.rows[0] ? mapRowToWorkflowRun(result.rows[0]) : null;
|
|
438
|
+
}
|
|
439
|
+
async function assertSingletonSlotAvailable({ workflowId, exceptRunId }, db) {
|
|
440
|
+
const active = await getActiveSingletonRun(workflowId, db);
|
|
441
|
+
if (active && active.id !== exceptRunId) {
|
|
442
|
+
throw new WorkflowRunInProgressError(workflowId, active.id);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
316
445
|
async function getWorkflowRun({
|
|
317
446
|
runId,
|
|
318
447
|
resourceId
|
|
@@ -427,7 +556,15 @@ async function updateWorkflowRun({
|
|
|
427
556
|
${whereClause}
|
|
428
557
|
RETURNING *
|
|
429
558
|
`;
|
|
430
|
-
|
|
559
|
+
let result;
|
|
560
|
+
try {
|
|
561
|
+
result = await db.executeSql(query, values);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
if (data.status === "running" && isUniqueViolation(error)) {
|
|
564
|
+
throw new WorkflowRunInProgressError("unknown", runId);
|
|
565
|
+
}
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
431
568
|
const run = result.rows[0];
|
|
432
569
|
if (!run) {
|
|
433
570
|
return null;
|
|
@@ -508,6 +645,32 @@ async function getWorkflowRuns({
|
|
|
508
645
|
const prevCursor = hasPrev && items.length > 0 ? items[0]?.id ?? null : null;
|
|
509
646
|
return { items, nextCursor, prevCursor, hasMore, hasPrev };
|
|
510
647
|
}
|
|
648
|
+
async function getWorkflowRunStats({
|
|
649
|
+
resourceId,
|
|
650
|
+
workflowId
|
|
651
|
+
}, db) {
|
|
652
|
+
const conditions = [];
|
|
653
|
+
const values = [];
|
|
654
|
+
let paramIndex = 1;
|
|
655
|
+
if (resourceId) {
|
|
656
|
+
conditions.push(`resource_id = $${paramIndex}`);
|
|
657
|
+
values.push(resourceId);
|
|
658
|
+
paramIndex++;
|
|
659
|
+
}
|
|
660
|
+
if (workflowId) {
|
|
661
|
+
conditions.push(`workflow_id = $${paramIndex}`);
|
|
662
|
+
values.push(workflowId);
|
|
663
|
+
}
|
|
664
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
665
|
+
const result = await db.executeSql(`SELECT status, COUNT(*)::int AS count FROM workflow_runs ${whereClause} GROUP BY status`, values);
|
|
666
|
+
const stats = { ...EMPTY_RUN_STATS };
|
|
667
|
+
for (const row of result.rows) {
|
|
668
|
+
if (row.status in stats) {
|
|
669
|
+
stats[row.status] = Number(row.count);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return stats;
|
|
673
|
+
}
|
|
511
674
|
async function withPostgresTransaction(db, callback, pool) {
|
|
512
675
|
let txDb;
|
|
513
676
|
let release;
|
|
@@ -533,43 +696,6 @@ async function withPostgresTransaction(db, callback, pool) {
|
|
|
533
696
|
}
|
|
534
697
|
}
|
|
535
698
|
|
|
536
|
-
// src/error.ts
|
|
537
|
-
function validateWorkflowId(workflowId) {
|
|
538
|
-
if (workflowId.length > MAX_WORKFLOW_ID_LENGTH) {
|
|
539
|
-
throw new WorkflowEngineError(`workflowId exceeds maximum length of ${MAX_WORKFLOW_ID_LENGTH} characters (got ${workflowId.length})`, workflowId);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
function validateResourceId(resourceId) {
|
|
543
|
-
if (resourceId != null && resourceId.length > MAX_RESOURCE_ID_LENGTH) {
|
|
544
|
-
throw new WorkflowEngineError(`resourceId exceeds maximum length of ${MAX_RESOURCE_ID_LENGTH} characters (got ${resourceId.length})`);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
class WorkflowEngineError extends Error {
|
|
549
|
-
workflowId;
|
|
550
|
-
runId;
|
|
551
|
-
cause;
|
|
552
|
-
issues;
|
|
553
|
-
constructor(message, workflowId, runId, cause = undefined, issues) {
|
|
554
|
-
super(message);
|
|
555
|
-
this.workflowId = workflowId;
|
|
556
|
-
this.runId = runId;
|
|
557
|
-
this.cause = cause;
|
|
558
|
-
this.issues = issues;
|
|
559
|
-
this.name = "WorkflowEngineError";
|
|
560
|
-
if (Error.captureStackTrace) {
|
|
561
|
-
Error.captureStackTrace(this, WorkflowEngineError);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
567
|
-
constructor(runId, workflowId) {
|
|
568
|
-
super("Workflow run not found", workflowId, runId);
|
|
569
|
-
this.name = "WorkflowRunNotFoundError";
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
699
|
// src/priority.ts
|
|
574
700
|
var PRIORITY_LEVELS = { high: 100, normal: 0, low: -100 };
|
|
575
701
|
function priorityToInt(value) {
|
|
@@ -594,28 +720,6 @@ function resolvePriority(...candidates) {
|
|
|
594
720
|
return PRIORITY_LEVELS.normal;
|
|
595
721
|
}
|
|
596
722
|
|
|
597
|
-
// src/types.ts
|
|
598
|
-
var WorkflowStatus;
|
|
599
|
-
((WorkflowStatus2) => {
|
|
600
|
-
WorkflowStatus2["PENDING"] = "pending";
|
|
601
|
-
WorkflowStatus2["RUNNING"] = "running";
|
|
602
|
-
WorkflowStatus2["PAUSED"] = "paused";
|
|
603
|
-
WorkflowStatus2["COMPLETED"] = "completed";
|
|
604
|
-
WorkflowStatus2["FAILED"] = "failed";
|
|
605
|
-
WorkflowStatus2["CANCELLED"] = "cancelled";
|
|
606
|
-
})(WorkflowStatus ||= {});
|
|
607
|
-
var _STEP_BASE_METHOD_TO_TYPE = {
|
|
608
|
-
run: "run" /* RUN */,
|
|
609
|
-
waitFor: "waitFor" /* WAIT_FOR */,
|
|
610
|
-
waitUntil: "waitUntil" /* WAIT_UNTIL */,
|
|
611
|
-
delay: "delay" /* DELAY */,
|
|
612
|
-
sleep: "delay" /* DELAY */,
|
|
613
|
-
pause: "pause" /* PAUSE */,
|
|
614
|
-
poll: "poll" /* POLL */,
|
|
615
|
-
invokeChildWorkflow: "invokeChildWorkflow" /* INVOKE_CHILD_WORKFLOW */
|
|
616
|
-
};
|
|
617
|
-
var STEP_BASE_METHOD_TYPES = new Map(Object.entries(_STEP_BASE_METHOD_TO_TYPE));
|
|
618
|
-
|
|
619
723
|
// src/client.ts
|
|
620
724
|
var LOG_PREFIX = "[WorkflowClient]";
|
|
621
725
|
var defaultLogger = {
|
|
@@ -644,11 +748,10 @@ class WorkflowClient {
|
|
|
644
748
|
const db = {
|
|
645
749
|
executeSql: (text, values) => this.pool.query(text, values)
|
|
646
750
|
};
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
751
|
+
this.boss = boss ?? new import_pg_boss.PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
|
|
752
|
+
this.boss.on("error", (error) => {
|
|
753
|
+
this.logger.error("pg-boss error", error);
|
|
754
|
+
});
|
|
652
755
|
this.db = db;
|
|
653
756
|
}
|
|
654
757
|
async start() {
|
|
@@ -677,6 +780,7 @@ class WorkflowClient {
|
|
|
677
780
|
let resourceId;
|
|
678
781
|
let idempotencyKey;
|
|
679
782
|
let options;
|
|
783
|
+
let singleton = false;
|
|
680
784
|
if (typeof refOrParams === "function" && "id" in refOrParams) {
|
|
681
785
|
const ref = refOrParams;
|
|
682
786
|
workflowId = ref.id;
|
|
@@ -684,6 +788,7 @@ class WorkflowClient {
|
|
|
684
788
|
options = optionsArg;
|
|
685
789
|
resourceId = optionsArg?.resourceId;
|
|
686
790
|
idempotencyKey = optionsArg?.idempotencyKey;
|
|
791
|
+
singleton = optionsArg?.singleton ?? ref.singleton === true;
|
|
687
792
|
if (ref.inputSchema) {
|
|
688
793
|
const result = await ref.inputSchema["~standard"].validate(input);
|
|
689
794
|
if (result.issues) {
|
|
@@ -697,6 +802,7 @@ class WorkflowClient {
|
|
|
697
802
|
resourceId = params.resourceId;
|
|
698
803
|
idempotencyKey = params.idempotencyKey;
|
|
699
804
|
options = params.options;
|
|
805
|
+
singleton = params.options?.singleton === true;
|
|
700
806
|
}
|
|
701
807
|
validateWorkflowId(workflowId);
|
|
702
808
|
validateResourceId(resourceId);
|
|
@@ -711,7 +817,8 @@ class WorkflowClient {
|
|
|
711
817
|
maxRetries: options?.retries ?? 0,
|
|
712
818
|
priority: resolvePriority(options?.priority),
|
|
713
819
|
timeoutAt,
|
|
714
|
-
idempotencyKey
|
|
820
|
+
idempotencyKey,
|
|
821
|
+
singleton
|
|
715
822
|
}, _db);
|
|
716
823
|
if (created) {
|
|
717
824
|
const job = {
|
|
@@ -741,6 +848,9 @@ class WorkflowClient {
|
|
|
741
848
|
}) {
|
|
742
849
|
await this.ensureStarted();
|
|
743
850
|
const run = await this.getRun({ runId, resourceId });
|
|
851
|
+
if (run.singleton) {
|
|
852
|
+
await assertSingletonSlotAvailable({ workflowId: run.workflowId, exceptRunId: run.id }, this.db);
|
|
853
|
+
}
|
|
744
854
|
const job = {
|
|
745
855
|
runId: run.id,
|
|
746
856
|
resourceId: resourceId ?? run.resourceId ?? undefined,
|
|
@@ -918,6 +1028,16 @@ class WorkflowClient {
|
|
|
918
1028
|
workflowId
|
|
919
1029
|
}, this.db);
|
|
920
1030
|
}
|
|
1031
|
+
async getStats({
|
|
1032
|
+
resourceId,
|
|
1033
|
+
workflowId
|
|
1034
|
+
} = {}) {
|
|
1035
|
+
await this.ensureStarted();
|
|
1036
|
+
if (workflowId)
|
|
1037
|
+
validateWorkflowId(workflowId);
|
|
1038
|
+
validateResourceId(resourceId);
|
|
1039
|
+
return getWorkflowRunStats({ resourceId, workflowId }, this.db);
|
|
1040
|
+
}
|
|
921
1041
|
async ensureStarted() {
|
|
922
1042
|
if (!this._started) {
|
|
923
1043
|
await this.start();
|
|
@@ -933,6 +1053,7 @@ function createWorkflowRef(id, options) {
|
|
|
933
1053
|
timeout: defineOptions?.timeout,
|
|
934
1054
|
retries: defineOptions?.retries,
|
|
935
1055
|
priority: defineOptions?.priority,
|
|
1056
|
+
singleton: defineOptions?.singleton ?? options?.singleton,
|
|
936
1057
|
schedule: defineOptions?.schedule,
|
|
937
1058
|
timezone: defineOptions?.timezone
|
|
938
1059
|
});
|
|
@@ -941,16 +1062,29 @@ function createWorkflowRef(id, options) {
|
|
|
941
1062
|
value: options?.inputSchema,
|
|
942
1063
|
enumerable: true
|
|
943
1064
|
});
|
|
1065
|
+
Object.defineProperty(ref, "singleton", {
|
|
1066
|
+
value: options?.singleton,
|
|
1067
|
+
enumerable: true
|
|
1068
|
+
});
|
|
944
1069
|
return ref;
|
|
945
1070
|
}
|
|
946
1071
|
function createWorkflowFactory(plugins = []) {
|
|
947
|
-
const factory = (id, handler, {
|
|
1072
|
+
const factory = (id, handler, {
|
|
1073
|
+
inputSchema,
|
|
1074
|
+
timeout,
|
|
1075
|
+
retries,
|
|
1076
|
+
priority,
|
|
1077
|
+
singleton,
|
|
1078
|
+
schedule,
|
|
1079
|
+
timezone
|
|
1080
|
+
} = {}) => ({
|
|
948
1081
|
id,
|
|
949
1082
|
handler,
|
|
950
1083
|
inputSchema,
|
|
951
1084
|
timeout,
|
|
952
1085
|
retries,
|
|
953
1086
|
priority,
|
|
1087
|
+
singleton,
|
|
954
1088
|
schedule,
|
|
955
1089
|
timezone,
|
|
956
1090
|
plugins: plugins.length > 0 ? plugins : undefined
|
|
@@ -964,5 +1098,5 @@ function createWorkflowFactory(plugins = []) {
|
|
|
964
1098
|
}
|
|
965
1099
|
var workflow = createWorkflowFactory();
|
|
966
1100
|
|
|
967
|
-
//# debugId=
|
|
1101
|
+
//# debugId=19F2D140ECC31EEE64756E2164756E21
|
|
968
1102
|
//# sourceMappingURL=client.entry.js.map
|
package/dist/client.entry.d.cts
CHANGED
|
@@ -20,6 +20,13 @@ type WorkflowRun = {
|
|
|
20
20
|
maxRetries: number;
|
|
21
21
|
/** Resolved scheduling priority (pg-boss integer; higher runs first). */
|
|
22
22
|
priority: number;
|
|
23
|
+
/**
|
|
24
|
+
* When true, this run participates in the per-workflow-ID uniqueness
|
|
25
|
+
* constraint: no other pending or running singleton run of the same
|
|
26
|
+
* workflow ID may exist at the same time. Paused, completed, failed, and
|
|
27
|
+
* cancelled runs release the slot.
|
|
28
|
+
*/
|
|
29
|
+
singleton: boolean;
|
|
23
30
|
jobId: string | null;
|
|
24
31
|
idempotencyKey: string | null;
|
|
25
32
|
parentRunId: string | null;
|
|
@@ -58,6 +65,7 @@ declare enum WorkflowStatus {
|
|
|
58
65
|
FAILED = "failed",
|
|
59
66
|
CANCELLED = "cancelled"
|
|
60
67
|
}
|
|
68
|
+
type WorkflowRunStats = Record<WorkflowStatus, number>;
|
|
61
69
|
type InputParameters = StandardSchemaV1;
|
|
62
70
|
type InferInputParameters<P extends InputParameters> = StandardSchemaV1.InferOutput<P>;
|
|
63
71
|
type StartWorkflowOptions = {
|
|
@@ -67,6 +75,12 @@ type StartWorkflowOptions = {
|
|
|
67
75
|
expireInSeconds?: number;
|
|
68
76
|
idempotencyKey?: string;
|
|
69
77
|
priority?: WorkflowPriority;
|
|
78
|
+
/**
|
|
79
|
+
* Client-side counterpart of `workflow(..., { singleton: true })`.
|
|
80
|
+
* The engine reads this from the registered definition; pass it here when
|
|
81
|
+
* starting from `WorkflowClient` so the uniqueness constraint applies.
|
|
82
|
+
*/
|
|
83
|
+
singleton?: boolean;
|
|
70
84
|
};
|
|
71
85
|
type WorkflowOptions<I extends InputParameters> = {
|
|
72
86
|
timeout?: number;
|
|
@@ -74,6 +88,12 @@ type WorkflowOptions<I extends InputParameters> = {
|
|
|
74
88
|
inputSchema?: I;
|
|
75
89
|
priority?: WorkflowPriority;
|
|
76
90
|
/**
|
|
91
|
+
* When true, at most one pending or running run of this workflow ID may exist.
|
|
92
|
+
* A second `startWorkflow` throws `WorkflowRunInProgressError` until the
|
|
93
|
+
* current run pauses, completes, fails, or is cancelled.
|
|
94
|
+
*/
|
|
95
|
+
singleton?: boolean;
|
|
96
|
+
/**
|
|
77
97
|
* Recurring schedule. Accepts a cron expression (`'0 9 * * 1-5'`),
|
|
78
98
|
* a duration string (`'5m'`, `'1 hour'`), or a `DurationObject`.
|
|
79
99
|
*/
|
|
@@ -181,6 +201,7 @@ type WorkflowDefinition<TInput extends InputParameters = InputParameters> = {
|
|
|
181
201
|
timeout?: number;
|
|
182
202
|
retries?: number;
|
|
183
203
|
priority?: WorkflowPriority;
|
|
204
|
+
singleton?: boolean;
|
|
184
205
|
schedule?: Schedule;
|
|
185
206
|
timezone?: string;
|
|
186
207
|
plugins?: WorkflowPlugin[];
|
|
@@ -199,6 +220,7 @@ interface WorkflowRef<
|
|
|
199
220
|
(handler: (context: WorkflowContext<TInput, StepBaseContext>) => Promise<unknown>, options?: Omit<WorkflowOptions<TInput>, "inputSchema">): WorkflowDefinition<TInput>;
|
|
200
221
|
readonly id: string;
|
|
201
222
|
readonly inputSchema?: TInput;
|
|
223
|
+
readonly singleton?: boolean;
|
|
202
224
|
}
|
|
203
225
|
type WorkflowRunProgress = WorkflowRun & {
|
|
204
226
|
completionPercentage: number;
|
|
@@ -294,6 +316,10 @@ declare class WorkflowClient {
|
|
|
294
316
|
hasMore: boolean;
|
|
295
317
|
hasPrev: boolean;
|
|
296
318
|
}>;
|
|
319
|
+
getStats({ resourceId, workflowId }?: {
|
|
320
|
+
resourceId?: string;
|
|
321
|
+
workflowId?: string;
|
|
322
|
+
}): Promise<WorkflowRunStats>;
|
|
297
323
|
private ensureStarted;
|
|
298
324
|
}
|
|
299
325
|
/**
|
|
@@ -305,6 +331,7 @@ declare function createWorkflowRef<
|
|
|
305
331
|
TInput extends InputParameters = InputParameters
|
|
306
332
|
>(id: string, options?: {
|
|
307
333
|
inputSchema?: TInput;
|
|
334
|
+
singleton?: boolean;
|
|
308
335
|
}): WorkflowRef<TInput, TOutput>;
|
|
309
336
|
import { StandardSchemaV1 as StandardSchemaV12 } from "@standard-schema/spec";
|
|
310
337
|
declare class WorkflowEngineError extends Error {
|
|
@@ -317,4 +344,7 @@ declare class WorkflowEngineError extends Error {
|
|
|
317
344
|
declare class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
318
345
|
constructor(runId?: string, workflowId?: string);
|
|
319
346
|
}
|
|
320
|
-
|
|
347
|
+
declare class WorkflowRunInProgressError extends WorkflowEngineError {
|
|
348
|
+
constructor(workflowId: string, runId?: string);
|
|
349
|
+
}
|
|
350
|
+
export { createWorkflowRef, WorkflowStatus, WorkflowRunStats, WorkflowRunProgress, WorkflowRunNotFoundError, WorkflowRunInProgressError, WorkflowRun, WorkflowRef, WorkflowLogger, WorkflowEngineError, WorkflowClientOptions, WorkflowClient, StartWorkflowOptions, InputParameters, InferInputParameters };
|
package/dist/client.entry.d.ts
CHANGED
|
@@ -20,6 +20,13 @@ type WorkflowRun = {
|
|
|
20
20
|
maxRetries: number;
|
|
21
21
|
/** Resolved scheduling priority (pg-boss integer; higher runs first). */
|
|
22
22
|
priority: number;
|
|
23
|
+
/**
|
|
24
|
+
* When true, this run participates in the per-workflow-ID uniqueness
|
|
25
|
+
* constraint: no other pending or running singleton run of the same
|
|
26
|
+
* workflow ID may exist at the same time. Paused, completed, failed, and
|
|
27
|
+
* cancelled runs release the slot.
|
|
28
|
+
*/
|
|
29
|
+
singleton: boolean;
|
|
23
30
|
jobId: string | null;
|
|
24
31
|
idempotencyKey: string | null;
|
|
25
32
|
parentRunId: string | null;
|
|
@@ -58,6 +65,7 @@ declare enum WorkflowStatus {
|
|
|
58
65
|
FAILED = "failed",
|
|
59
66
|
CANCELLED = "cancelled"
|
|
60
67
|
}
|
|
68
|
+
type WorkflowRunStats = Record<WorkflowStatus, number>;
|
|
61
69
|
type InputParameters = StandardSchemaV1;
|
|
62
70
|
type InferInputParameters<P extends InputParameters> = StandardSchemaV1.InferOutput<P>;
|
|
63
71
|
type StartWorkflowOptions = {
|
|
@@ -67,6 +75,12 @@ type StartWorkflowOptions = {
|
|
|
67
75
|
expireInSeconds?: number;
|
|
68
76
|
idempotencyKey?: string;
|
|
69
77
|
priority?: WorkflowPriority;
|
|
78
|
+
/**
|
|
79
|
+
* Client-side counterpart of `workflow(..., { singleton: true })`.
|
|
80
|
+
* The engine reads this from the registered definition; pass it here when
|
|
81
|
+
* starting from `WorkflowClient` so the uniqueness constraint applies.
|
|
82
|
+
*/
|
|
83
|
+
singleton?: boolean;
|
|
70
84
|
};
|
|
71
85
|
type WorkflowOptions<I extends InputParameters> = {
|
|
72
86
|
timeout?: number;
|
|
@@ -74,6 +88,12 @@ type WorkflowOptions<I extends InputParameters> = {
|
|
|
74
88
|
inputSchema?: I;
|
|
75
89
|
priority?: WorkflowPriority;
|
|
76
90
|
/**
|
|
91
|
+
* When true, at most one pending or running run of this workflow ID may exist.
|
|
92
|
+
* A second `startWorkflow` throws `WorkflowRunInProgressError` until the
|
|
93
|
+
* current run pauses, completes, fails, or is cancelled.
|
|
94
|
+
*/
|
|
95
|
+
singleton?: boolean;
|
|
96
|
+
/**
|
|
77
97
|
* Recurring schedule. Accepts a cron expression (`'0 9 * * 1-5'`),
|
|
78
98
|
* a duration string (`'5m'`, `'1 hour'`), or a `DurationObject`.
|
|
79
99
|
*/
|
|
@@ -181,6 +201,7 @@ type WorkflowDefinition<TInput extends InputParameters = InputParameters> = {
|
|
|
181
201
|
timeout?: number;
|
|
182
202
|
retries?: number;
|
|
183
203
|
priority?: WorkflowPriority;
|
|
204
|
+
singleton?: boolean;
|
|
184
205
|
schedule?: Schedule;
|
|
185
206
|
timezone?: string;
|
|
186
207
|
plugins?: WorkflowPlugin[];
|
|
@@ -199,6 +220,7 @@ interface WorkflowRef<
|
|
|
199
220
|
(handler: (context: WorkflowContext<TInput, StepBaseContext>) => Promise<unknown>, options?: Omit<WorkflowOptions<TInput>, "inputSchema">): WorkflowDefinition<TInput>;
|
|
200
221
|
readonly id: string;
|
|
201
222
|
readonly inputSchema?: TInput;
|
|
223
|
+
readonly singleton?: boolean;
|
|
202
224
|
}
|
|
203
225
|
type WorkflowRunProgress = WorkflowRun & {
|
|
204
226
|
completionPercentage: number;
|
|
@@ -294,6 +316,10 @@ declare class WorkflowClient {
|
|
|
294
316
|
hasMore: boolean;
|
|
295
317
|
hasPrev: boolean;
|
|
296
318
|
}>;
|
|
319
|
+
getStats({ resourceId, workflowId }?: {
|
|
320
|
+
resourceId?: string;
|
|
321
|
+
workflowId?: string;
|
|
322
|
+
}): Promise<WorkflowRunStats>;
|
|
297
323
|
private ensureStarted;
|
|
298
324
|
}
|
|
299
325
|
/**
|
|
@@ -305,6 +331,7 @@ declare function createWorkflowRef<
|
|
|
305
331
|
TInput extends InputParameters = InputParameters
|
|
306
332
|
>(id: string, options?: {
|
|
307
333
|
inputSchema?: TInput;
|
|
334
|
+
singleton?: boolean;
|
|
308
335
|
}): WorkflowRef<TInput, TOutput>;
|
|
309
336
|
import { StandardSchemaV1 as StandardSchemaV12 } from "@standard-schema/spec";
|
|
310
337
|
declare class WorkflowEngineError extends Error {
|
|
@@ -317,4 +344,7 @@ declare class WorkflowEngineError extends Error {
|
|
|
317
344
|
declare class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
318
345
|
constructor(runId?: string, workflowId?: string);
|
|
319
346
|
}
|
|
320
|
-
|
|
347
|
+
declare class WorkflowRunInProgressError extends WorkflowEngineError {
|
|
348
|
+
constructor(workflowId: string, runId?: string);
|
|
349
|
+
}
|
|
350
|
+
export { createWorkflowRef, WorkflowStatus, WorkflowRunStats, WorkflowRunProgress, WorkflowRunNotFoundError, WorkflowRunInProgressError, WorkflowRun, WorkflowRef, WorkflowLogger, WorkflowEngineError, WorkflowClientOptions, WorkflowClient, StartWorkflowOptions, InputParameters, InferInputParameters };
|
package/dist/client.entry.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WorkflowClient,
|
|
3
3
|
WorkflowEngineError,
|
|
4
|
+
WorkflowRunInProgressError,
|
|
4
5
|
WorkflowRunNotFoundError,
|
|
5
6
|
WorkflowStatus,
|
|
6
7
|
createWorkflowRef
|
|
7
|
-
} from "./shared/chunk-
|
|
8
|
+
} from "./shared/chunk-d204nxd5.js";
|
|
8
9
|
export {
|
|
9
10
|
createWorkflowRef,
|
|
10
11
|
WorkflowStatus,
|
|
11
12
|
WorkflowRunNotFoundError,
|
|
13
|
+
WorkflowRunInProgressError,
|
|
12
14
|
WorkflowEngineError,
|
|
13
15
|
WorkflowClient
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
//# debugId=
|
|
18
|
+
//# debugId=2B13F4A442F0A3AD64756E2164756E21
|
|
17
19
|
//# sourceMappingURL=client.entry.js.map
|