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.
@@ -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 = 6;
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
- const result = await db.executeSql(`INSERT INTO workflow_runs (
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
- runId,
287
- resourceId ?? null,
288
- workflowId,
289
- currentStepId,
290
- status,
291
- JSON.stringify(input),
292
- maxRetries,
293
- priority,
294
- timeoutAt,
295
- now,
296
- now,
297
- "{}",
298
- 0,
299
- idempotencyKey ?? null,
300
- parentRunId ?? null,
301
- parentStepId ?? null,
302
- parentResourceId ?? null,
303
- scheduledAt ?? null
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
- const result = await db.executeSql(query, values);
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
- if (boss) {
648
- this.boss = boss;
649
- } else {
650
- this.boss = new import_pg_boss.PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
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, { inputSchema, timeout, retries, priority, schedule, timezone } = {}) => ({
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=65B5E13A5C10F50064756E2164756E21
1101
+ //# debugId=19F2D140ECC31EEE64756E2164756E21
968
1102
  //# sourceMappingURL=client.entry.js.map
@@ -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
- export { createWorkflowRef, WorkflowStatus, WorkflowRunProgress, WorkflowRunNotFoundError, WorkflowRun, WorkflowRef, WorkflowLogger, WorkflowEngineError, WorkflowClientOptions, WorkflowClient, StartWorkflowOptions, InputParameters, InferInputParameters };
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 };
@@ -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
- export { createWorkflowRef, WorkflowStatus, WorkflowRunProgress, WorkflowRunNotFoundError, WorkflowRun, WorkflowRef, WorkflowLogger, WorkflowEngineError, WorkflowClientOptions, WorkflowClient, StartWorkflowOptions, InputParameters, InferInputParameters };
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 };
@@ -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-w3qcrrwx.js";
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=64DD8F1F4DBCBD8C64756E2164756E21
18
+ //# debugId=2B13F4A442F0A3AD64756E2164756E21
17
19
  //# sourceMappingURL=client.entry.js.map