pg-workflows 0.13.2 → 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 +189 -64
- package/dist/client.entry.d.cts +43 -1
- package/dist/client.entry.d.ts +43 -1
- package/dist/client.entry.js +4 -2
- package/dist/client.entry.js.map +10 -9
- package/dist/index.cjs +209 -70
- package/dist/index.d.cts +44 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +26 -8
- package/dist/index.js.map +11 -10
- package/dist/shared/{chunk-hjycwrsq.js → chunk-n0f7hd40.js} +158 -36
- package/dist/shared/chunk-n0f7hd40.js.map +17 -0
- package/package.json +9 -3
- package/dist/shared/chunk-hjycwrsq.js.map +0 -16
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;
|
|
@@ -170,6 +171,17 @@ async function runMigrations(db) {
|
|
|
170
171
|
if (currentVersion < 5) {
|
|
171
172
|
commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS scheduled_at timestamp with time zone");
|
|
172
173
|
}
|
|
174
|
+
if (currentVersion < 6) {
|
|
175
|
+
commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS priority integer DEFAULT 0 NOT NULL");
|
|
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
|
+
}
|
|
173
185
|
if (currentVersion === 0) {
|
|
174
186
|
commands.push(`INSERT INTO workflow_schema_version (version) VALUES (${CURRENT_SCHEMA_VERSION})`);
|
|
175
187
|
} else {
|
|
@@ -209,9 +221,65 @@ async function getCurrentVersion(db) {
|
|
|
209
221
|
|
|
210
222
|
// src/db/queries.ts
|
|
211
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
|
|
212
270
|
function generateKSUID(prefix) {
|
|
213
271
|
return `${prefix ? `${prefix}_` : ""}${import_ksuid.default.randomSync().string}`;
|
|
214
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
|
+
}
|
|
215
283
|
function mapRowToWorkflowRun(row) {
|
|
216
284
|
return {
|
|
217
285
|
id: row.id,
|
|
@@ -231,12 +299,14 @@ function mapRowToWorkflowRun(row) {
|
|
|
231
299
|
timeoutAt: row.timeout_at ? new Date(row.timeout_at) : null,
|
|
232
300
|
retryCount: row.retry_count,
|
|
233
301
|
maxRetries: row.max_retries,
|
|
302
|
+
priority: row.priority,
|
|
234
303
|
jobId: row.job_id,
|
|
235
304
|
idempotencyKey: row.idempotency_key,
|
|
236
305
|
parentRunId: row.parent_run_id,
|
|
237
306
|
parentStepId: row.parent_step_id,
|
|
238
307
|
parentResourceId: row.parent_resource_id,
|
|
239
|
-
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)
|
|
240
310
|
};
|
|
241
311
|
}
|
|
242
312
|
async function insertWorkflowRun({
|
|
@@ -246,16 +316,26 @@ async function insertWorkflowRun({
|
|
|
246
316
|
status,
|
|
247
317
|
input,
|
|
248
318
|
maxRetries,
|
|
319
|
+
priority,
|
|
249
320
|
timeoutAt,
|
|
250
321
|
idempotencyKey,
|
|
251
322
|
parentRunId,
|
|
252
323
|
parentStepId,
|
|
253
324
|
parentResourceId,
|
|
254
|
-
scheduledAt
|
|
325
|
+
scheduledAt,
|
|
326
|
+
singleton = false
|
|
255
327
|
}, db) {
|
|
256
328
|
const runId = generateKSUID("run");
|
|
257
329
|
const now = new Date;
|
|
258
|
-
|
|
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 (
|
|
259
339
|
id,
|
|
260
340
|
resource_id,
|
|
261
341
|
workflow_id,
|
|
@@ -263,6 +343,7 @@ async function insertWorkflowRun({
|
|
|
263
343
|
status,
|
|
264
344
|
input,
|
|
265
345
|
max_retries,
|
|
346
|
+
priority,
|
|
266
347
|
timeout_at,
|
|
267
348
|
created_at,
|
|
268
349
|
updated_at,
|
|
@@ -272,29 +353,39 @@ async function insertWorkflowRun({
|
|
|
272
353
|
parent_run_id,
|
|
273
354
|
parent_step_id,
|
|
274
355
|
parent_resource_id,
|
|
275
|
-
scheduled_at
|
|
356
|
+
scheduled_at,
|
|
357
|
+
singleton
|
|
276
358
|
)
|
|
277
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
|
359
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
|
|
278
360
|
ON CONFLICT (idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING
|
|
279
361
|
RETURNING *`, [
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
+
}
|
|
298
389
|
if (result.rows[0]) {
|
|
299
390
|
return { run: mapRowToWorkflowRun(result.rows[0]), created: true };
|
|
300
391
|
}
|
|
@@ -306,6 +397,20 @@ async function insertWorkflowRun({
|
|
|
306
397
|
}
|
|
307
398
|
return { run: mapRowToWorkflowRun(existing.rows[0]), created: false };
|
|
308
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
|
+
}
|
|
309
414
|
async function getWorkflowRun({
|
|
310
415
|
runId,
|
|
311
416
|
resourceId
|
|
@@ -420,7 +525,15 @@ async function updateWorkflowRun({
|
|
|
420
525
|
${whereClause}
|
|
421
526
|
RETURNING *
|
|
422
527
|
`;
|
|
423
|
-
|
|
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
|
+
}
|
|
424
537
|
const run = result.rows[0];
|
|
425
538
|
if (!run) {
|
|
426
539
|
return null;
|
|
@@ -526,41 +639,28 @@ async function withPostgresTransaction(db, callback, pool) {
|
|
|
526
639
|
}
|
|
527
640
|
}
|
|
528
641
|
|
|
529
|
-
// src/
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
642
|
+
// src/priority.ts
|
|
643
|
+
var PRIORITY_LEVELS = { high: 100, normal: 0, low: -100 };
|
|
644
|
+
function priorityToInt(value) {
|
|
645
|
+
if (typeof value === "number") {
|
|
646
|
+
if (!Number.isInteger(value)) {
|
|
647
|
+
throw new WorkflowEngineError(`Invalid priority ${value}: numeric priority must be an integer.`);
|
|
648
|
+
}
|
|
649
|
+
return value;
|
|
533
650
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
throw new WorkflowEngineError(`resourceId exceeds maximum length of ${MAX_RESOURCE_ID_LENGTH} characters (got ${resourceId.length})`);
|
|
651
|
+
const mapped = PRIORITY_LEVELS[value];
|
|
652
|
+
if (mapped === undefined) {
|
|
653
|
+
throw new WorkflowEngineError(`Invalid priority "${value}": expected one of ${Object.keys(PRIORITY_LEVELS).map((k) => `"${k}"`).join(", ")} or an integer.`);
|
|
538
654
|
}
|
|
655
|
+
return mapped;
|
|
539
656
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
cause;
|
|
545
|
-
issues;
|
|
546
|
-
constructor(message, workflowId, runId, cause = undefined, issues) {
|
|
547
|
-
super(message);
|
|
548
|
-
this.workflowId = workflowId;
|
|
549
|
-
this.runId = runId;
|
|
550
|
-
this.cause = cause;
|
|
551
|
-
this.issues = issues;
|
|
552
|
-
this.name = "WorkflowEngineError";
|
|
553
|
-
if (Error.captureStackTrace) {
|
|
554
|
-
Error.captureStackTrace(this, WorkflowEngineError);
|
|
657
|
+
function resolvePriority(...candidates) {
|
|
658
|
+
for (const candidate of candidates) {
|
|
659
|
+
if (candidate !== undefined) {
|
|
660
|
+
return priorityToInt(candidate);
|
|
555
661
|
}
|
|
556
662
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
560
|
-
constructor(runId, workflowId) {
|
|
561
|
-
super("Workflow run not found", workflowId, runId);
|
|
562
|
-
this.name = "WorkflowRunNotFoundError";
|
|
563
|
-
}
|
|
663
|
+
return PRIORITY_LEVELS.normal;
|
|
564
664
|
}
|
|
565
665
|
|
|
566
666
|
// src/types.ts
|
|
@@ -613,11 +713,10 @@ class WorkflowClient {
|
|
|
613
713
|
const db = {
|
|
614
714
|
executeSql: (text, values) => this.pool.query(text, values)
|
|
615
715
|
};
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
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
|
+
});
|
|
621
720
|
this.db = db;
|
|
622
721
|
}
|
|
623
722
|
async start() {
|
|
@@ -646,6 +745,7 @@ class WorkflowClient {
|
|
|
646
745
|
let resourceId;
|
|
647
746
|
let idempotencyKey;
|
|
648
747
|
let options;
|
|
748
|
+
let singleton = false;
|
|
649
749
|
if (typeof refOrParams === "function" && "id" in refOrParams) {
|
|
650
750
|
const ref = refOrParams;
|
|
651
751
|
workflowId = ref.id;
|
|
@@ -653,6 +753,7 @@ class WorkflowClient {
|
|
|
653
753
|
options = optionsArg;
|
|
654
754
|
resourceId = optionsArg?.resourceId;
|
|
655
755
|
idempotencyKey = optionsArg?.idempotencyKey;
|
|
756
|
+
singleton = optionsArg?.singleton ?? ref.singleton === true;
|
|
656
757
|
if (ref.inputSchema) {
|
|
657
758
|
const result = await ref.inputSchema["~standard"].validate(input);
|
|
658
759
|
if (result.issues) {
|
|
@@ -666,6 +767,7 @@ class WorkflowClient {
|
|
|
666
767
|
resourceId = params.resourceId;
|
|
667
768
|
idempotencyKey = params.idempotencyKey;
|
|
668
769
|
options = params.options;
|
|
770
|
+
singleton = params.options?.singleton === true;
|
|
669
771
|
}
|
|
670
772
|
validateWorkflowId(workflowId);
|
|
671
773
|
validateResourceId(resourceId);
|
|
@@ -678,8 +780,10 @@ class WorkflowClient {
|
|
|
678
780
|
status: "running" /* RUNNING */,
|
|
679
781
|
input,
|
|
680
782
|
maxRetries: options?.retries ?? 0,
|
|
783
|
+
priority: resolvePriority(options?.priority),
|
|
681
784
|
timeoutAt,
|
|
682
|
-
idempotencyKey
|
|
785
|
+
idempotencyKey,
|
|
786
|
+
singleton
|
|
683
787
|
}, _db);
|
|
684
788
|
if (created) {
|
|
685
789
|
const job = {
|
|
@@ -691,6 +795,7 @@ class WorkflowClient {
|
|
|
691
795
|
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
692
796
|
startAfter: new Date,
|
|
693
797
|
expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
|
|
798
|
+
priority: insertedRun.priority,
|
|
694
799
|
db: _db
|
|
695
800
|
});
|
|
696
801
|
}
|
|
@@ -708,6 +813,9 @@ class WorkflowClient {
|
|
|
708
813
|
}) {
|
|
709
814
|
await this.ensureStarted();
|
|
710
815
|
const run = await this.getRun({ runId, resourceId });
|
|
816
|
+
if (run.singleton) {
|
|
817
|
+
await assertSingletonSlotAvailable({ workflowId: run.workflowId, exceptRunId: run.id }, this.db);
|
|
818
|
+
}
|
|
711
819
|
const job = {
|
|
712
820
|
runId: run.id,
|
|
713
821
|
resourceId: resourceId ?? run.resourceId ?? undefined,
|
|
@@ -719,7 +827,8 @@ class WorkflowClient {
|
|
|
719
827
|
}
|
|
720
828
|
};
|
|
721
829
|
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
722
|
-
expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds
|
|
830
|
+
expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
|
|
831
|
+
priority: run.priority
|
|
723
832
|
});
|
|
724
833
|
this.logger.log(`${LOG_PREFIX} Event ${eventName} sent for workflow run ${runId}`);
|
|
725
834
|
return run;
|
|
@@ -898,6 +1007,8 @@ function createWorkflowRef(id, options) {
|
|
|
898
1007
|
inputSchema: options?.inputSchema,
|
|
899
1008
|
timeout: defineOptions?.timeout,
|
|
900
1009
|
retries: defineOptions?.retries,
|
|
1010
|
+
priority: defineOptions?.priority,
|
|
1011
|
+
singleton: defineOptions?.singleton ?? options?.singleton,
|
|
901
1012
|
schedule: defineOptions?.schedule,
|
|
902
1013
|
timezone: defineOptions?.timezone
|
|
903
1014
|
});
|
|
@@ -906,15 +1017,29 @@ function createWorkflowRef(id, options) {
|
|
|
906
1017
|
value: options?.inputSchema,
|
|
907
1018
|
enumerable: true
|
|
908
1019
|
});
|
|
1020
|
+
Object.defineProperty(ref, "singleton", {
|
|
1021
|
+
value: options?.singleton,
|
|
1022
|
+
enumerable: true
|
|
1023
|
+
});
|
|
909
1024
|
return ref;
|
|
910
1025
|
}
|
|
911
1026
|
function createWorkflowFactory(plugins = []) {
|
|
912
|
-
const factory = (id, handler, {
|
|
1027
|
+
const factory = (id, handler, {
|
|
1028
|
+
inputSchema,
|
|
1029
|
+
timeout,
|
|
1030
|
+
retries,
|
|
1031
|
+
priority,
|
|
1032
|
+
singleton,
|
|
1033
|
+
schedule,
|
|
1034
|
+
timezone
|
|
1035
|
+
} = {}) => ({
|
|
913
1036
|
id,
|
|
914
1037
|
handler,
|
|
915
1038
|
inputSchema,
|
|
916
1039
|
timeout,
|
|
917
1040
|
retries,
|
|
1041
|
+
priority,
|
|
1042
|
+
singleton,
|
|
918
1043
|
schedule,
|
|
919
1044
|
timezone,
|
|
920
1045
|
plugins: plugins.length > 0 ? plugins : undefined
|
|
@@ -928,5 +1053,5 @@ function createWorkflowFactory(plugins = []) {
|
|
|
928
1053
|
}
|
|
929
1054
|
var workflow = createWorkflowFactory();
|
|
930
1055
|
|
|
931
|
-
//# debugId=
|
|
1056
|
+
//# debugId=9B37033329AD266364756E2164756E21
|
|
932
1057
|
//# sourceMappingURL=client.entry.js.map
|
package/dist/client.entry.d.cts
CHANGED
|
@@ -18,6 +18,15 @@ type WorkflowRun = {
|
|
|
18
18
|
timeoutAt: Date | null;
|
|
19
19
|
retryCount: number;
|
|
20
20
|
maxRetries: number;
|
|
21
|
+
/** Resolved scheduling priority (pg-boss integer; higher runs first). */
|
|
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;
|
|
21
30
|
jobId: string | null;
|
|
22
31
|
idempotencyKey: string | null;
|
|
23
32
|
parentRunId: string | null;
|
|
@@ -35,6 +44,18 @@ type DurationObject = {
|
|
|
35
44
|
seconds?: number;
|
|
36
45
|
};
|
|
37
46
|
type Duration = string | DurationObject;
|
|
47
|
+
/**
|
|
48
|
+
* Named priority levels mapped to pg-boss integer priorities. Higher numbers
|
|
49
|
+
* are fetched first (pg-boss orders by `priority DESC`), and `normal = 0`
|
|
50
|
+
* matches pg-boss's own default. The ±100 spacing leaves room for numeric
|
|
51
|
+
* tuning between tiers via the escape hatch.
|
|
52
|
+
*/
|
|
53
|
+
declare const PRIORITY_LEVELS: {
|
|
54
|
+
readonly high: 100;
|
|
55
|
+
readonly normal: 0;
|
|
56
|
+
readonly low: -100;
|
|
57
|
+
};
|
|
58
|
+
type WorkflowPriority = keyof typeof PRIORITY_LEVELS | number;
|
|
38
59
|
type Schedule = string | Exclude<Duration, string>;
|
|
39
60
|
declare enum WorkflowStatus {
|
|
40
61
|
PENDING = "pending",
|
|
@@ -52,11 +73,25 @@ type StartWorkflowOptions = {
|
|
|
52
73
|
retries?: number;
|
|
53
74
|
expireInSeconds?: number;
|
|
54
75
|
idempotencyKey?: string;
|
|
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;
|
|
55
83
|
};
|
|
56
84
|
type WorkflowOptions<I extends InputParameters> = {
|
|
57
85
|
timeout?: number;
|
|
58
86
|
retries?: number;
|
|
59
87
|
inputSchema?: I;
|
|
88
|
+
priority?: WorkflowPriority;
|
|
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;
|
|
60
95
|
/**
|
|
61
96
|
* Recurring schedule. Accepts a cron expression (`'0 9 * * 1-5'`),
|
|
62
97
|
* a duration string (`'5m'`, `'1 hour'`), or a `DurationObject`.
|
|
@@ -164,6 +199,8 @@ type WorkflowDefinition<TInput extends InputParameters = InputParameters> = {
|
|
|
164
199
|
inputSchema?: TInput;
|
|
165
200
|
timeout?: number;
|
|
166
201
|
retries?: number;
|
|
202
|
+
priority?: WorkflowPriority;
|
|
203
|
+
singleton?: boolean;
|
|
167
204
|
schedule?: Schedule;
|
|
168
205
|
timezone?: string;
|
|
169
206
|
plugins?: WorkflowPlugin[];
|
|
@@ -182,6 +219,7 @@ interface WorkflowRef<
|
|
|
182
219
|
(handler: (context: WorkflowContext<TInput, StepBaseContext>) => Promise<unknown>, options?: Omit<WorkflowOptions<TInput>, "inputSchema">): WorkflowDefinition<TInput>;
|
|
183
220
|
readonly id: string;
|
|
184
221
|
readonly inputSchema?: TInput;
|
|
222
|
+
readonly singleton?: boolean;
|
|
185
223
|
}
|
|
186
224
|
type WorkflowRunProgress = WorkflowRun & {
|
|
187
225
|
completionPercentage: number;
|
|
@@ -288,6 +326,7 @@ declare function createWorkflowRef<
|
|
|
288
326
|
TInput extends InputParameters = InputParameters
|
|
289
327
|
>(id: string, options?: {
|
|
290
328
|
inputSchema?: TInput;
|
|
329
|
+
singleton?: boolean;
|
|
291
330
|
}): WorkflowRef<TInput, TOutput>;
|
|
292
331
|
import { StandardSchemaV1 as StandardSchemaV12 } from "@standard-schema/spec";
|
|
293
332
|
declare class WorkflowEngineError extends Error {
|
|
@@ -300,4 +339,7 @@ declare class WorkflowEngineError extends Error {
|
|
|
300
339
|
declare class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
301
340
|
constructor(runId?: string, workflowId?: string);
|
|
302
341
|
}
|
|
303
|
-
|
|
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
|
@@ -18,6 +18,15 @@ type WorkflowRun = {
|
|
|
18
18
|
timeoutAt: Date | null;
|
|
19
19
|
retryCount: number;
|
|
20
20
|
maxRetries: number;
|
|
21
|
+
/** Resolved scheduling priority (pg-boss integer; higher runs first). */
|
|
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;
|
|
21
30
|
jobId: string | null;
|
|
22
31
|
idempotencyKey: string | null;
|
|
23
32
|
parentRunId: string | null;
|
|
@@ -35,6 +44,18 @@ type DurationObject = {
|
|
|
35
44
|
seconds?: number;
|
|
36
45
|
};
|
|
37
46
|
type Duration = string | DurationObject;
|
|
47
|
+
/**
|
|
48
|
+
* Named priority levels mapped to pg-boss integer priorities. Higher numbers
|
|
49
|
+
* are fetched first (pg-boss orders by `priority DESC`), and `normal = 0`
|
|
50
|
+
* matches pg-boss's own default. The ±100 spacing leaves room for numeric
|
|
51
|
+
* tuning between tiers via the escape hatch.
|
|
52
|
+
*/
|
|
53
|
+
declare const PRIORITY_LEVELS: {
|
|
54
|
+
readonly high: 100;
|
|
55
|
+
readonly normal: 0;
|
|
56
|
+
readonly low: -100;
|
|
57
|
+
};
|
|
58
|
+
type WorkflowPriority = keyof typeof PRIORITY_LEVELS | number;
|
|
38
59
|
type Schedule = string | Exclude<Duration, string>;
|
|
39
60
|
declare enum WorkflowStatus {
|
|
40
61
|
PENDING = "pending",
|
|
@@ -52,11 +73,25 @@ type StartWorkflowOptions = {
|
|
|
52
73
|
retries?: number;
|
|
53
74
|
expireInSeconds?: number;
|
|
54
75
|
idempotencyKey?: string;
|
|
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;
|
|
55
83
|
};
|
|
56
84
|
type WorkflowOptions<I extends InputParameters> = {
|
|
57
85
|
timeout?: number;
|
|
58
86
|
retries?: number;
|
|
59
87
|
inputSchema?: I;
|
|
88
|
+
priority?: WorkflowPriority;
|
|
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;
|
|
60
95
|
/**
|
|
61
96
|
* Recurring schedule. Accepts a cron expression (`'0 9 * * 1-5'`),
|
|
62
97
|
* a duration string (`'5m'`, `'1 hour'`), or a `DurationObject`.
|
|
@@ -164,6 +199,8 @@ type WorkflowDefinition<TInput extends InputParameters = InputParameters> = {
|
|
|
164
199
|
inputSchema?: TInput;
|
|
165
200
|
timeout?: number;
|
|
166
201
|
retries?: number;
|
|
202
|
+
priority?: WorkflowPriority;
|
|
203
|
+
singleton?: boolean;
|
|
167
204
|
schedule?: Schedule;
|
|
168
205
|
timezone?: string;
|
|
169
206
|
plugins?: WorkflowPlugin[];
|
|
@@ -182,6 +219,7 @@ interface WorkflowRef<
|
|
|
182
219
|
(handler: (context: WorkflowContext<TInput, StepBaseContext>) => Promise<unknown>, options?: Omit<WorkflowOptions<TInput>, "inputSchema">): WorkflowDefinition<TInput>;
|
|
183
220
|
readonly id: string;
|
|
184
221
|
readonly inputSchema?: TInput;
|
|
222
|
+
readonly singleton?: boolean;
|
|
185
223
|
}
|
|
186
224
|
type WorkflowRunProgress = WorkflowRun & {
|
|
187
225
|
completionPercentage: number;
|
|
@@ -288,6 +326,7 @@ declare function createWorkflowRef<
|
|
|
288
326
|
TInput extends InputParameters = InputParameters
|
|
289
327
|
>(id: string, options?: {
|
|
290
328
|
inputSchema?: TInput;
|
|
329
|
+
singleton?: boolean;
|
|
291
330
|
}): WorkflowRef<TInput, TOutput>;
|
|
292
331
|
import { StandardSchemaV1 as StandardSchemaV12 } from "@standard-schema/spec";
|
|
293
332
|
declare class WorkflowEngineError extends Error {
|
|
@@ -300,4 +339,7 @@ declare class WorkflowEngineError extends Error {
|
|
|
300
339
|
declare class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
301
340
|
constructor(runId?: string, workflowId?: string);
|
|
302
341
|
}
|
|
303
|
-
|
|
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
|