pg-workflows 0.14.0 → 0.15.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/README.md +2 -0
- package/dist/client.entry.cjs +160 -71
- package/dist/client.entry.d.cts +26 -1
- package/dist/client.entry.d.ts +26 -1
- package/dist/client.entry.js +4 -2
- package/dist/client.entry.js.map +8 -8
- package/dist/index.cjs +173 -77
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +18 -8
- package/dist/index.js.map +9 -9
- package/dist/shared/{chunk-w3qcrrwx.js → chunk-n0f7hd40.js} +122 -36
- package/dist/shared/chunk-n0f7hd40.js.map +17 -0
- package/package.json +9 -3
- package/dist/shared/chunk-w3qcrrwx.js.map +0 -17
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ That's it. Each step runs **exactly once**. Crash, redeploy, or retry - the work
|
|
|
54
54
|
- **Schedules & polling built in** - `step.delay('3 days')`, `step.waitUntil('2025-01-01')`, `step.poll(...)` - no cron, no external scheduler.
|
|
55
55
|
- **Built for AI agents** - cache expensive LLM calls, retry on 429s, pause for human review. [See AI patterns →](docs/ai-agents.md)
|
|
56
56
|
- **Client/worker separation** - keep your API service light; run handlers in a worker. [See architecture →](docs/architecture.md)
|
|
57
|
+
- **Optional React UI** - browse runs and step timelines with [`@pg-workflows/ui`](packages/ui/README.md). Separate package, so workers never take a React peer.
|
|
57
58
|
- **Idempotent starts** - pass an `idempotencyKey` and duplicate calls safely return the same run.
|
|
58
59
|
|
|
59
60
|
---
|
|
@@ -155,6 +156,7 @@ See [runnable examples](https://github.com/SokratisVidros/pg-workflows/tree/main
|
|
|
155
156
|
- **[API Reference](docs/api-reference.md)** - `WorkflowEngine`, `WorkflowClient`, `WorkflowRef`, types
|
|
156
157
|
- **[Configuration](docs/configuration.md)** - env vars, database setup, requirements
|
|
157
158
|
- **[Observability](docs/observability.md)** - OpenTelemetry tracing via `otelPlugin`
|
|
159
|
+
- **[UI](packages/ui/README.md)** - `@pg-workflows/ui`: hooks and components for inspecting runs
|
|
158
160
|
|
|
159
161
|
---
|
|
160
162
|
|
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
|
});
|
|
@@ -94,7 +95,7 @@ var isInvokeChildWorkflowTimelineEntry = (entry) => !!entry && typeof entry ===
|
|
|
94
95
|
|
|
95
96
|
// src/db/migration.ts
|
|
96
97
|
var MIGRATION_LOCK_ID = 738291645;
|
|
97
|
-
var CURRENT_SCHEMA_VERSION =
|
|
98
|
+
var CURRENT_SCHEMA_VERSION = 7;
|
|
98
99
|
async function runMigrations(db) {
|
|
99
100
|
if (await isSchemaUpToDate(db)) {
|
|
100
101
|
return;
|
|
@@ -173,6 +174,14 @@ async function runMigrations(db) {
|
|
|
173
174
|
if (currentVersion < 6) {
|
|
174
175
|
commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS priority integer DEFAULT 0 NOT NULL");
|
|
175
176
|
}
|
|
177
|
+
if (currentVersion < 7) {
|
|
178
|
+
commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS singleton boolean DEFAULT false NOT NULL");
|
|
179
|
+
commands.push(`
|
|
180
|
+
CREATE UNIQUE INDEX IF NOT EXISTS workflow_runs_singleton_active_idx
|
|
181
|
+
ON workflow_runs (workflow_id)
|
|
182
|
+
WHERE singleton AND status IN ('pending', 'running')
|
|
183
|
+
`);
|
|
184
|
+
}
|
|
176
185
|
if (currentVersion === 0) {
|
|
177
186
|
commands.push(`INSERT INTO workflow_schema_version (version) VALUES (${CURRENT_SCHEMA_VERSION})`);
|
|
178
187
|
} else {
|
|
@@ -212,9 +221,65 @@ async function getCurrentVersion(db) {
|
|
|
212
221
|
|
|
213
222
|
// src/db/queries.ts
|
|
214
223
|
var import_ksuid = __toESM(require("ksuid"));
|
|
224
|
+
|
|
225
|
+
// src/error.ts
|
|
226
|
+
function validateWorkflowId(workflowId) {
|
|
227
|
+
if (workflowId.length > MAX_WORKFLOW_ID_LENGTH) {
|
|
228
|
+
throw new WorkflowEngineError(`workflowId exceeds maximum length of ${MAX_WORKFLOW_ID_LENGTH} characters (got ${workflowId.length})`, workflowId);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function validateResourceId(resourceId) {
|
|
232
|
+
if (resourceId != null && resourceId.length > MAX_RESOURCE_ID_LENGTH) {
|
|
233
|
+
throw new WorkflowEngineError(`resourceId exceeds maximum length of ${MAX_RESOURCE_ID_LENGTH} characters (got ${resourceId.length})`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
class WorkflowEngineError extends Error {
|
|
238
|
+
workflowId;
|
|
239
|
+
runId;
|
|
240
|
+
cause;
|
|
241
|
+
issues;
|
|
242
|
+
constructor(message, workflowId, runId, cause = undefined, issues) {
|
|
243
|
+
super(message);
|
|
244
|
+
this.workflowId = workflowId;
|
|
245
|
+
this.runId = runId;
|
|
246
|
+
this.cause = cause;
|
|
247
|
+
this.issues = issues;
|
|
248
|
+
this.name = "WorkflowEngineError";
|
|
249
|
+
if (Error.captureStackTrace) {
|
|
250
|
+
Error.captureStackTrace(this, WorkflowEngineError);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
256
|
+
constructor(runId, workflowId) {
|
|
257
|
+
super("Workflow run not found", workflowId, runId);
|
|
258
|
+
this.name = "WorkflowRunNotFoundError";
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
class WorkflowRunInProgressError extends WorkflowEngineError {
|
|
263
|
+
constructor(workflowId, runId) {
|
|
264
|
+
super(`Workflow "${workflowId}" already has a running run; wait for it to pause, complete, fail, or be cancelled`, workflowId, runId);
|
|
265
|
+
this.name = "WorkflowRunInProgressError";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/db/queries.ts
|
|
215
270
|
function generateKSUID(prefix) {
|
|
216
271
|
return `${prefix ? `${prefix}_` : ""}${import_ksuid.default.randomSync().string}`;
|
|
217
272
|
}
|
|
273
|
+
function isUniqueViolation(error) {
|
|
274
|
+
if (!error || typeof error !== "object") {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
if ("code" in error && error.code === "23505") {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
const cause = "cause" in error ? error.cause : undefined;
|
|
281
|
+
return Boolean(cause && typeof cause === "object" && "code" in cause && cause.code === "23505");
|
|
282
|
+
}
|
|
218
283
|
function mapRowToWorkflowRun(row) {
|
|
219
284
|
return {
|
|
220
285
|
id: row.id,
|
|
@@ -240,7 +305,8 @@ function mapRowToWorkflowRun(row) {
|
|
|
240
305
|
parentRunId: row.parent_run_id,
|
|
241
306
|
parentStepId: row.parent_step_id,
|
|
242
307
|
parentResourceId: row.parent_resource_id,
|
|
243
|
-
scheduledAt: row.scheduled_at ? new Date(row.scheduled_at) : null
|
|
308
|
+
scheduledAt: row.scheduled_at ? new Date(row.scheduled_at) : null,
|
|
309
|
+
singleton: Boolean(row.singleton)
|
|
244
310
|
};
|
|
245
311
|
}
|
|
246
312
|
async function insertWorkflowRun({
|
|
@@ -256,11 +322,20 @@ async function insertWorkflowRun({
|
|
|
256
322
|
parentRunId,
|
|
257
323
|
parentStepId,
|
|
258
324
|
parentResourceId,
|
|
259
|
-
scheduledAt
|
|
325
|
+
scheduledAt,
|
|
326
|
+
singleton = false
|
|
260
327
|
}, db) {
|
|
261
328
|
const runId = generateKSUID("run");
|
|
262
329
|
const now = new Date;
|
|
263
|
-
|
|
330
|
+
if (singleton) {
|
|
331
|
+
const active = await getActiveSingletonRun(workflowId, db);
|
|
332
|
+
if (active) {
|
|
333
|
+
throw new WorkflowRunInProgressError(workflowId, active.id);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
let result;
|
|
337
|
+
try {
|
|
338
|
+
result = await db.executeSql(`INSERT INTO workflow_runs (
|
|
264
339
|
id,
|
|
265
340
|
resource_id,
|
|
266
341
|
workflow_id,
|
|
@@ -278,30 +353,39 @@ async function insertWorkflowRun({
|
|
|
278
353
|
parent_run_id,
|
|
279
354
|
parent_step_id,
|
|
280
355
|
parent_resource_id,
|
|
281
|
-
scheduled_at
|
|
356
|
+
scheduled_at,
|
|
357
|
+
singleton
|
|
282
358
|
)
|
|
283
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
|
|
359
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
|
|
284
360
|
ON CONFLICT (idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING
|
|
285
361
|
RETURNING *`, [
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
362
|
+
runId,
|
|
363
|
+
resourceId ?? null,
|
|
364
|
+
workflowId,
|
|
365
|
+
currentStepId,
|
|
366
|
+
status,
|
|
367
|
+
JSON.stringify(input),
|
|
368
|
+
maxRetries,
|
|
369
|
+
priority,
|
|
370
|
+
timeoutAt,
|
|
371
|
+
now,
|
|
372
|
+
now,
|
|
373
|
+
"{}",
|
|
374
|
+
0,
|
|
375
|
+
idempotencyKey ?? null,
|
|
376
|
+
parentRunId ?? null,
|
|
377
|
+
parentStepId ?? null,
|
|
378
|
+
parentResourceId ?? null,
|
|
379
|
+
scheduledAt ?? null,
|
|
380
|
+
singleton
|
|
381
|
+
]);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (singleton && isUniqueViolation(error)) {
|
|
384
|
+
const active = await getActiveSingletonRun(workflowId, db);
|
|
385
|
+
throw new WorkflowRunInProgressError(workflowId, active?.id);
|
|
386
|
+
}
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
305
389
|
if (result.rows[0]) {
|
|
306
390
|
return { run: mapRowToWorkflowRun(result.rows[0]), created: true };
|
|
307
391
|
}
|
|
@@ -313,6 +397,20 @@ async function insertWorkflowRun({
|
|
|
313
397
|
}
|
|
314
398
|
return { run: mapRowToWorkflowRun(existing.rows[0]), created: false };
|
|
315
399
|
}
|
|
400
|
+
async function getActiveSingletonRun(workflowId, db) {
|
|
401
|
+
const result = await db.executeSql(`SELECT * FROM workflow_runs
|
|
402
|
+
WHERE workflow_id = $1
|
|
403
|
+
AND singleton
|
|
404
|
+
AND status IN ('pending', 'running')
|
|
405
|
+
LIMIT 1`, [workflowId]);
|
|
406
|
+
return result.rows[0] ? mapRowToWorkflowRun(result.rows[0]) : null;
|
|
407
|
+
}
|
|
408
|
+
async function assertSingletonSlotAvailable({ workflowId, exceptRunId }, db) {
|
|
409
|
+
const active = await getActiveSingletonRun(workflowId, db);
|
|
410
|
+
if (active && active.id !== exceptRunId) {
|
|
411
|
+
throw new WorkflowRunInProgressError(workflowId, active.id);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
316
414
|
async function getWorkflowRun({
|
|
317
415
|
runId,
|
|
318
416
|
resourceId
|
|
@@ -427,7 +525,15 @@ async function updateWorkflowRun({
|
|
|
427
525
|
${whereClause}
|
|
428
526
|
RETURNING *
|
|
429
527
|
`;
|
|
430
|
-
|
|
528
|
+
let result;
|
|
529
|
+
try {
|
|
530
|
+
result = await db.executeSql(query, values);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (data.status === "running" && isUniqueViolation(error)) {
|
|
533
|
+
throw new WorkflowRunInProgressError("unknown", runId);
|
|
534
|
+
}
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
431
537
|
const run = result.rows[0];
|
|
432
538
|
if (!run) {
|
|
433
539
|
return null;
|
|
@@ -533,43 +639,6 @@ async function withPostgresTransaction(db, callback, pool) {
|
|
|
533
639
|
}
|
|
534
640
|
}
|
|
535
641
|
|
|
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
642
|
// src/priority.ts
|
|
574
643
|
var PRIORITY_LEVELS = { high: 100, normal: 0, low: -100 };
|
|
575
644
|
function priorityToInt(value) {
|
|
@@ -644,11 +713,10 @@ class WorkflowClient {
|
|
|
644
713
|
const db = {
|
|
645
714
|
executeSql: (text, values) => this.pool.query(text, values)
|
|
646
715
|
};
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
716
|
+
this.boss = boss ?? new import_pg_boss.PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
|
|
717
|
+
this.boss.on("error", (error) => {
|
|
718
|
+
this.logger.error("pg-boss error", error);
|
|
719
|
+
});
|
|
652
720
|
this.db = db;
|
|
653
721
|
}
|
|
654
722
|
async start() {
|
|
@@ -677,6 +745,7 @@ class WorkflowClient {
|
|
|
677
745
|
let resourceId;
|
|
678
746
|
let idempotencyKey;
|
|
679
747
|
let options;
|
|
748
|
+
let singleton = false;
|
|
680
749
|
if (typeof refOrParams === "function" && "id" in refOrParams) {
|
|
681
750
|
const ref = refOrParams;
|
|
682
751
|
workflowId = ref.id;
|
|
@@ -684,6 +753,7 @@ class WorkflowClient {
|
|
|
684
753
|
options = optionsArg;
|
|
685
754
|
resourceId = optionsArg?.resourceId;
|
|
686
755
|
idempotencyKey = optionsArg?.idempotencyKey;
|
|
756
|
+
singleton = optionsArg?.singleton ?? ref.singleton === true;
|
|
687
757
|
if (ref.inputSchema) {
|
|
688
758
|
const result = await ref.inputSchema["~standard"].validate(input);
|
|
689
759
|
if (result.issues) {
|
|
@@ -697,6 +767,7 @@ class WorkflowClient {
|
|
|
697
767
|
resourceId = params.resourceId;
|
|
698
768
|
idempotencyKey = params.idempotencyKey;
|
|
699
769
|
options = params.options;
|
|
770
|
+
singleton = params.options?.singleton === true;
|
|
700
771
|
}
|
|
701
772
|
validateWorkflowId(workflowId);
|
|
702
773
|
validateResourceId(resourceId);
|
|
@@ -711,7 +782,8 @@ class WorkflowClient {
|
|
|
711
782
|
maxRetries: options?.retries ?? 0,
|
|
712
783
|
priority: resolvePriority(options?.priority),
|
|
713
784
|
timeoutAt,
|
|
714
|
-
idempotencyKey
|
|
785
|
+
idempotencyKey,
|
|
786
|
+
singleton
|
|
715
787
|
}, _db);
|
|
716
788
|
if (created) {
|
|
717
789
|
const job = {
|
|
@@ -741,6 +813,9 @@ class WorkflowClient {
|
|
|
741
813
|
}) {
|
|
742
814
|
await this.ensureStarted();
|
|
743
815
|
const run = await this.getRun({ runId, resourceId });
|
|
816
|
+
if (run.singleton) {
|
|
817
|
+
await assertSingletonSlotAvailable({ workflowId: run.workflowId, exceptRunId: run.id }, this.db);
|
|
818
|
+
}
|
|
744
819
|
const job = {
|
|
745
820
|
runId: run.id,
|
|
746
821
|
resourceId: resourceId ?? run.resourceId ?? undefined,
|
|
@@ -933,6 +1008,7 @@ function createWorkflowRef(id, options) {
|
|
|
933
1008
|
timeout: defineOptions?.timeout,
|
|
934
1009
|
retries: defineOptions?.retries,
|
|
935
1010
|
priority: defineOptions?.priority,
|
|
1011
|
+
singleton: defineOptions?.singleton ?? options?.singleton,
|
|
936
1012
|
schedule: defineOptions?.schedule,
|
|
937
1013
|
timezone: defineOptions?.timezone
|
|
938
1014
|
});
|
|
@@ -941,16 +1017,29 @@ function createWorkflowRef(id, options) {
|
|
|
941
1017
|
value: options?.inputSchema,
|
|
942
1018
|
enumerable: true
|
|
943
1019
|
});
|
|
1020
|
+
Object.defineProperty(ref, "singleton", {
|
|
1021
|
+
value: options?.singleton,
|
|
1022
|
+
enumerable: true
|
|
1023
|
+
});
|
|
944
1024
|
return ref;
|
|
945
1025
|
}
|
|
946
1026
|
function createWorkflowFactory(plugins = []) {
|
|
947
|
-
const factory = (id, handler, {
|
|
1027
|
+
const factory = (id, handler, {
|
|
1028
|
+
inputSchema,
|
|
1029
|
+
timeout,
|
|
1030
|
+
retries,
|
|
1031
|
+
priority,
|
|
1032
|
+
singleton,
|
|
1033
|
+
schedule,
|
|
1034
|
+
timezone
|
|
1035
|
+
} = {}) => ({
|
|
948
1036
|
id,
|
|
949
1037
|
handler,
|
|
950
1038
|
inputSchema,
|
|
951
1039
|
timeout,
|
|
952
1040
|
retries,
|
|
953
1041
|
priority,
|
|
1042
|
+
singleton,
|
|
954
1043
|
schedule,
|
|
955
1044
|
timezone,
|
|
956
1045
|
plugins: plugins.length > 0 ? plugins : undefined
|
|
@@ -964,5 +1053,5 @@ function createWorkflowFactory(plugins = []) {
|
|
|
964
1053
|
}
|
|
965
1054
|
var workflow = createWorkflowFactory();
|
|
966
1055
|
|
|
967
|
-
//# debugId=
|
|
1056
|
+
//# debugId=9B37033329AD266364756E2164756E21
|
|
968
1057
|
//# 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;
|
|
@@ -67,6 +74,12 @@ type StartWorkflowOptions = {
|
|
|
67
74
|
expireInSeconds?: number;
|
|
68
75
|
idempotencyKey?: string;
|
|
69
76
|
priority?: WorkflowPriority;
|
|
77
|
+
/**
|
|
78
|
+
* Client-side counterpart of `workflow(..., { singleton: true })`.
|
|
79
|
+
* The engine reads this from the registered definition; pass it here when
|
|
80
|
+
* starting from `WorkflowClient` so the uniqueness constraint applies.
|
|
81
|
+
*/
|
|
82
|
+
singleton?: boolean;
|
|
70
83
|
};
|
|
71
84
|
type WorkflowOptions<I extends InputParameters> = {
|
|
72
85
|
timeout?: number;
|
|
@@ -74,6 +87,12 @@ type WorkflowOptions<I extends InputParameters> = {
|
|
|
74
87
|
inputSchema?: I;
|
|
75
88
|
priority?: WorkflowPriority;
|
|
76
89
|
/**
|
|
90
|
+
* When true, at most one pending or running run of this workflow ID may exist.
|
|
91
|
+
* A second `startWorkflow` throws `WorkflowRunInProgressError` until the
|
|
92
|
+
* current run pauses, completes, fails, or is cancelled.
|
|
93
|
+
*/
|
|
94
|
+
singleton?: boolean;
|
|
95
|
+
/**
|
|
77
96
|
* Recurring schedule. Accepts a cron expression (`'0 9 * * 1-5'`),
|
|
78
97
|
* a duration string (`'5m'`, `'1 hour'`), or a `DurationObject`.
|
|
79
98
|
*/
|
|
@@ -181,6 +200,7 @@ type WorkflowDefinition<TInput extends InputParameters = InputParameters> = {
|
|
|
181
200
|
timeout?: number;
|
|
182
201
|
retries?: number;
|
|
183
202
|
priority?: WorkflowPriority;
|
|
203
|
+
singleton?: boolean;
|
|
184
204
|
schedule?: Schedule;
|
|
185
205
|
timezone?: string;
|
|
186
206
|
plugins?: WorkflowPlugin[];
|
|
@@ -199,6 +219,7 @@ interface WorkflowRef<
|
|
|
199
219
|
(handler: (context: WorkflowContext<TInput, StepBaseContext>) => Promise<unknown>, options?: Omit<WorkflowOptions<TInput>, "inputSchema">): WorkflowDefinition<TInput>;
|
|
200
220
|
readonly id: string;
|
|
201
221
|
readonly inputSchema?: TInput;
|
|
222
|
+
readonly singleton?: boolean;
|
|
202
223
|
}
|
|
203
224
|
type WorkflowRunProgress = WorkflowRun & {
|
|
204
225
|
completionPercentage: number;
|
|
@@ -305,6 +326,7 @@ declare function createWorkflowRef<
|
|
|
305
326
|
TInput extends InputParameters = InputParameters
|
|
306
327
|
>(id: string, options?: {
|
|
307
328
|
inputSchema?: TInput;
|
|
329
|
+
singleton?: boolean;
|
|
308
330
|
}): WorkflowRef<TInput, TOutput>;
|
|
309
331
|
import { StandardSchemaV1 as StandardSchemaV12 } from "@standard-schema/spec";
|
|
310
332
|
declare class WorkflowEngineError extends Error {
|
|
@@ -317,4 +339,7 @@ declare class WorkflowEngineError extends Error {
|
|
|
317
339
|
declare class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
318
340
|
constructor(runId?: string, workflowId?: string);
|
|
319
341
|
}
|
|
320
|
-
|
|
342
|
+
declare class WorkflowRunInProgressError extends WorkflowEngineError {
|
|
343
|
+
constructor(workflowId: string, runId?: string);
|
|
344
|
+
}
|
|
345
|
+
export { createWorkflowRef, WorkflowStatus, 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;
|
|
@@ -67,6 +74,12 @@ type StartWorkflowOptions = {
|
|
|
67
74
|
expireInSeconds?: number;
|
|
68
75
|
idempotencyKey?: string;
|
|
69
76
|
priority?: WorkflowPriority;
|
|
77
|
+
/**
|
|
78
|
+
* Client-side counterpart of `workflow(..., { singleton: true })`.
|
|
79
|
+
* The engine reads this from the registered definition; pass it here when
|
|
80
|
+
* starting from `WorkflowClient` so the uniqueness constraint applies.
|
|
81
|
+
*/
|
|
82
|
+
singleton?: boolean;
|
|
70
83
|
};
|
|
71
84
|
type WorkflowOptions<I extends InputParameters> = {
|
|
72
85
|
timeout?: number;
|
|
@@ -74,6 +87,12 @@ type WorkflowOptions<I extends InputParameters> = {
|
|
|
74
87
|
inputSchema?: I;
|
|
75
88
|
priority?: WorkflowPriority;
|
|
76
89
|
/**
|
|
90
|
+
* When true, at most one pending or running run of this workflow ID may exist.
|
|
91
|
+
* A second `startWorkflow` throws `WorkflowRunInProgressError` until the
|
|
92
|
+
* current run pauses, completes, fails, or is cancelled.
|
|
93
|
+
*/
|
|
94
|
+
singleton?: boolean;
|
|
95
|
+
/**
|
|
77
96
|
* Recurring schedule. Accepts a cron expression (`'0 9 * * 1-5'`),
|
|
78
97
|
* a duration string (`'5m'`, `'1 hour'`), or a `DurationObject`.
|
|
79
98
|
*/
|
|
@@ -181,6 +200,7 @@ type WorkflowDefinition<TInput extends InputParameters = InputParameters> = {
|
|
|
181
200
|
timeout?: number;
|
|
182
201
|
retries?: number;
|
|
183
202
|
priority?: WorkflowPriority;
|
|
203
|
+
singleton?: boolean;
|
|
184
204
|
schedule?: Schedule;
|
|
185
205
|
timezone?: string;
|
|
186
206
|
plugins?: WorkflowPlugin[];
|
|
@@ -199,6 +219,7 @@ interface WorkflowRef<
|
|
|
199
219
|
(handler: (context: WorkflowContext<TInput, StepBaseContext>) => Promise<unknown>, options?: Omit<WorkflowOptions<TInput>, "inputSchema">): WorkflowDefinition<TInput>;
|
|
200
220
|
readonly id: string;
|
|
201
221
|
readonly inputSchema?: TInput;
|
|
222
|
+
readonly singleton?: boolean;
|
|
202
223
|
}
|
|
203
224
|
type WorkflowRunProgress = WorkflowRun & {
|
|
204
225
|
completionPercentage: number;
|
|
@@ -305,6 +326,7 @@ declare function createWorkflowRef<
|
|
|
305
326
|
TInput extends InputParameters = InputParameters
|
|
306
327
|
>(id: string, options?: {
|
|
307
328
|
inputSchema?: TInput;
|
|
329
|
+
singleton?: boolean;
|
|
308
330
|
}): WorkflowRef<TInput, TOutput>;
|
|
309
331
|
import { StandardSchemaV1 as StandardSchemaV12 } from "@standard-schema/spec";
|
|
310
332
|
declare class WorkflowEngineError extends Error {
|
|
@@ -317,4 +339,7 @@ declare class WorkflowEngineError extends Error {
|
|
|
317
339
|
declare class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
318
340
|
constructor(runId?: string, workflowId?: string);
|
|
319
341
|
}
|
|
320
|
-
|
|
342
|
+
declare class WorkflowRunInProgressError extends WorkflowEngineError {
|
|
343
|
+
constructor(workflowId: string, runId?: string);
|
|
344
|
+
}
|
|
345
|
+
export { createWorkflowRef, WorkflowStatus, 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-n0f7hd40.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
|