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 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
 
@@ -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 = 5;
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
- const result = await db.executeSql(`INSERT INTO workflow_runs (
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
- runId,
281
- resourceId ?? null,
282
- workflowId,
283
- currentStepId,
284
- status,
285
- JSON.stringify(input),
286
- maxRetries,
287
- timeoutAt,
288
- now,
289
- now,
290
- "{}",
291
- 0,
292
- idempotencyKey ?? null,
293
- parentRunId ?? null,
294
- parentStepId ?? null,
295
- parentResourceId ?? null,
296
- scheduledAt ?? null
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
- const result = await db.executeSql(query, values);
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/error.ts
530
- function validateWorkflowId(workflowId) {
531
- if (workflowId.length > MAX_WORKFLOW_ID_LENGTH) {
532
- throw new WorkflowEngineError(`workflowId exceeds maximum length of ${MAX_WORKFLOW_ID_LENGTH} characters (got ${workflowId.length})`, workflowId);
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
- function validateResourceId(resourceId) {
536
- if (resourceId != null && resourceId.length > MAX_RESOURCE_ID_LENGTH) {
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
- class WorkflowEngineError extends Error {
542
- workflowId;
543
- runId;
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
- if (boss) {
617
- this.boss = boss;
618
- } else {
619
- this.boss = new import_pg_boss.PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
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, { inputSchema, timeout, retries, schedule, timezone } = {}) => ({
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=5D57F91D3C5BBBBC64756E2164756E21
1056
+ //# debugId=9B37033329AD266364756E2164756E21
932
1057
  //# sourceMappingURL=client.entry.js.map
@@ -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
- export { createWorkflowRef, WorkflowStatus, WorkflowRunProgress, WorkflowRunNotFoundError, WorkflowRun, WorkflowRef, WorkflowLogger, WorkflowEngineError, WorkflowClientOptions, WorkflowClient, StartWorkflowOptions, InputParameters, InferInputParameters };
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 };
@@ -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
- export { createWorkflowRef, WorkflowStatus, WorkflowRunProgress, WorkflowRunNotFoundError, WorkflowRun, WorkflowRef, WorkflowLogger, WorkflowEngineError, WorkflowClientOptions, WorkflowClient, StartWorkflowOptions, InputParameters, InferInputParameters };
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 };
@@ -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-hjycwrsq.js";
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=64DD8F1F4DBCBD8C64756E2164756E21
18
+ //# debugId=2B13F4A442F0A3AD64756E2164756E21
17
19
  //# sourceMappingURL=client.entry.js.map