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/dist/index.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",
@@ -61,11 +82,25 @@ type StartWorkflowOptions = {
61
82
  retries?: number;
62
83
  expireInSeconds?: number;
63
84
  idempotencyKey?: string;
85
+ priority?: WorkflowPriority;
86
+ /**
87
+ * Client-side counterpart of `workflow(..., { singleton: true })`.
88
+ * The engine reads this from the registered definition; pass it here when
89
+ * starting from `WorkflowClient` so the uniqueness constraint applies.
90
+ */
91
+ singleton?: boolean;
64
92
  };
65
93
  type WorkflowOptions<I extends InputParameters> = {
66
94
  timeout?: number;
67
95
  retries?: number;
68
96
  inputSchema?: I;
97
+ priority?: WorkflowPriority;
98
+ /**
99
+ * When true, at most one pending or running run of this workflow ID may exist.
100
+ * A second `startWorkflow` throws `WorkflowRunInProgressError` until the
101
+ * current run pauses, completes, fails, or is cancelled.
102
+ */
103
+ singleton?: boolean;
69
104
  /**
70
105
  * Recurring schedule. Accepts a cron expression (`'0 9 * * 1-5'`),
71
106
  * a duration string (`'5m'`, `'1 hour'`), or a `DurationObject`.
@@ -173,6 +208,8 @@ type WorkflowDefinition<TInput extends InputParameters = InputParameters> = {
173
208
  inputSchema?: TInput;
174
209
  timeout?: number;
175
210
  retries?: number;
211
+ priority?: WorkflowPriority;
212
+ singleton?: boolean;
176
213
  schedule?: Schedule;
177
214
  timezone?: string;
178
215
  plugins?: WorkflowPlugin[];
@@ -199,6 +236,7 @@ interface WorkflowFactory<TStepExt = object> {
199
236
  TOutput = unknown
200
237
  >(id: string, options?: {
201
238
  inputSchema?: TInput;
239
+ singleton?: boolean;
202
240
  }): WorkflowRef<TInput, TOutput>;
203
241
  }
204
242
  /**
@@ -215,6 +253,7 @@ interface WorkflowRef<
215
253
  (handler: (context: WorkflowContext<TInput, StepBaseContext>) => Promise<unknown>, options?: Omit<WorkflowOptions<TInput>, "inputSchema">): WorkflowDefinition<TInput>;
216
254
  readonly id: string;
217
255
  readonly inputSchema?: TInput;
256
+ readonly singleton?: boolean;
218
257
  }
219
258
  type WorkflowRunProgress = WorkflowRun & {
220
259
  completionPercentage: number;
@@ -321,6 +360,7 @@ declare function createWorkflowRef<
321
360
  TInput extends InputParameters = InputParameters
322
361
  >(id: string, options?: {
323
362
  inputSchema?: TInput;
363
+ singleton?: boolean;
324
364
  }): WorkflowRef<TInput, TOutput>;
325
365
  declare const workflow: WorkflowFactory;
326
366
  import pg2 from "pg";
@@ -494,6 +534,9 @@ declare class WorkflowEngineError extends Error {
494
534
  declare class WorkflowRunNotFoundError extends WorkflowEngineError {
495
535
  constructor(runId?: string, workflowId?: string);
496
536
  }
537
+ declare class WorkflowRunInProgressError extends WorkflowEngineError {
538
+ constructor(workflowId: string, runId?: string);
539
+ }
497
540
  import { AttributeValue, Tracer } from "@opentelemetry/api";
498
541
  type OtelPluginOptions = {
499
542
  /** Tracer to use. Defaults to `trace.getTracer('pg-workflows')`. */
@@ -504,4 +547,4 @@ type OtelPluginOptions = {
504
547
  attributes?: (context: WorkflowContext) => Record<string, AttributeValue>;
505
548
  };
506
549
  declare function otelPlugin(options?: OtelPluginOptions): WorkflowPlugin<StepBaseContext, object>;
507
- export { workflow, otelPlugin, createWorkflowRef, WorkflowStatus, WorkflowRunProgress, WorkflowRunNotFoundError, WorkflowRun, WorkflowRef, WorkflowPlugin, WorkflowOptions, WorkflowLogger, WorkflowEngineOptions, WorkflowEngineError, WorkflowEngine, WorkflowDefinition, WorkflowContext, WorkflowClientOptions, WorkflowClient, StepBaseContext, StartWorkflowOptions, ScheduleContext, Schedule, OtelPluginOptions, InputParameters, InferInputParameters, Duration };
550
+ export { workflow, otelPlugin, createWorkflowRef, WorkflowStatus, WorkflowRunProgress, WorkflowRunNotFoundError, WorkflowRunInProgressError, WorkflowRun, WorkflowRef, WorkflowPriority, WorkflowPlugin, WorkflowOptions, WorkflowLogger, WorkflowEngineOptions, WorkflowEngineError, WorkflowEngine, WorkflowDefinition, WorkflowContext, WorkflowClientOptions, WorkflowClient, StepBaseContext, StartWorkflowOptions, ScheduleContext, Schedule, OtelPluginOptions, InputParameters, InferInputParameters, Duration };
package/dist/index.js CHANGED
@@ -6,8 +6,10 @@ import {
6
6
  WORKFLOW_RUN_QUEUE_NAME,
7
7
  WorkflowClient,
8
8
  WorkflowEngineError,
9
+ WorkflowRunInProgressError,
9
10
  WorkflowRunNotFoundError,
10
11
  WorkflowStatus,
12
+ assertSingletonSlotAvailable,
11
13
  createWorkflowRef,
12
14
  getWorkflowLastRun,
13
15
  getWorkflowRun,
@@ -15,6 +17,7 @@ import {
15
17
  insertWorkflowRun,
16
18
  invokeChildWorkflowTimelineKey,
17
19
  isInvokeChildWorkflowTimelineEntry,
20
+ resolvePriority,
18
21
  runMigrations,
19
22
  scheduleQueueNameFor,
20
23
  updateWorkflowRun,
@@ -23,7 +26,7 @@ import {
23
26
  waitForTimelineKey,
24
27
  withPostgresTransaction,
25
28
  workflow
26
- } from "./shared/chunk-hjycwrsq.js";
29
+ } from "./shared/chunk-n0f7hd40.js";
27
30
  // src/engine.ts
28
31
  import { merge } from "es-toolkit";
29
32
  import pg from "pg";
@@ -224,11 +227,10 @@ class WorkflowEngine {
224
227
  const db = {
225
228
  executeSql: (text, values) => this.pool.query(text, values)
226
229
  };
227
- if (boss) {
228
- this.boss = boss;
229
- } else {
230
- this.boss = new PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
231
- }
230
+ this.boss = boss ?? new PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
231
+ this.boss.on("error", (error) => {
232
+ this.logger.error("pg-boss error", error);
233
+ });
232
234
  this.db = this.boss.getDb();
233
235
  }
234
236
  async start(asEngine = true, {
@@ -282,6 +284,10 @@ class WorkflowEngine {
282
284
  try {
283
285
  await this.createWorkflowRun({ workflowId, input: {}, scheduledAt });
284
286
  } catch (error) {
287
+ if (error instanceof WorkflowRunInProgressError) {
288
+ this.logger.log(`Skipping schedule fire for "${workflowId}": a run is already in progress`, { workflowId, runId: error.runId });
289
+ return;
290
+ }
285
291
  this.logger.error(`Schedule fire failed to start a run for workflow "${workflowId}"`, error instanceof Error ? error : new Error(String(error)), { workflowId });
286
292
  throw error;
287
293
  }
@@ -394,6 +400,7 @@ class WorkflowEngine {
394
400
  parentRunId,
395
401
  parentStepId,
396
402
  parentResourceId,
403
+ parentPriority,
397
404
  scheduledAt,
398
405
  enqueue = true,
399
406
  db
@@ -424,12 +431,14 @@ class WorkflowEngine {
424
431
  status: "running" /* RUNNING */,
425
432
  input,
426
433
  maxRetries: options?.retries ?? workflow2.retries ?? 0,
434
+ priority: resolvePriority(options?.priority, workflow2.priority, parentPriority),
427
435
  timeoutAt,
428
436
  idempotencyKey,
429
437
  parentRunId,
430
438
  parentStepId,
431
439
  parentResourceId,
432
- scheduledAt
440
+ scheduledAt,
441
+ singleton: workflow2.singleton === true || options?.singleton === true
433
442
  }, targetDb);
434
443
  const insertAndEnqueue = async (targetDb) => {
435
444
  const result = await insertRun(targetDb);
@@ -451,6 +460,7 @@ class WorkflowEngine {
451
460
  await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
452
461
  startAfter: new Date,
453
462
  expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
463
+ priority: run.priority,
454
464
  ...retrySendOptions(run.maxRetries),
455
465
  ...db ? { db } : {}
456
466
  });
@@ -587,6 +597,9 @@ class WorkflowEngine {
587
597
  }) {
588
598
  await this.checkIfHasStarted();
589
599
  const run = await this.getRun({ runId, resourceId });
600
+ if (run.singleton) {
601
+ await assertSingletonSlotAvailable({ workflowId: run.workflowId, exceptRunId: run.id }, this.db);
602
+ }
590
603
  const jobResourceId = resourceId ?? run.resourceId ?? undefined;
591
604
  const job = {
592
605
  runId: run.id,
@@ -600,6 +613,7 @@ class WorkflowEngine {
600
613
  };
601
614
  await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
602
615
  expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
616
+ priority: run.priority,
603
617
  ...retrySendOptions(run.maxRetries)
604
618
  });
605
619
  this.logger.log(`event ${eventName} sent for workflow run with id ${runId}`);
@@ -1009,6 +1023,7 @@ class WorkflowEngine {
1009
1023
  parentRunId: run.id,
1010
1024
  parentStepId: stepId,
1011
1025
  parentResourceId: run.resourceId ?? undefined,
1026
+ parentPriority: run.priority,
1012
1027
  enqueue: true,
1013
1028
  db
1014
1029
  });
@@ -1207,6 +1222,7 @@ ${error.stack}` : String(error)
1207
1222
  await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
1208
1223
  startAfter: timeoutDate.getTime() <= Date.now() ? new Date : timeoutDate,
1209
1224
  expireInSeconds: defaultExpireInSeconds,
1225
+ priority: run.priority,
1210
1226
  ...retrySendOptions(run.maxRetries)
1211
1227
  });
1212
1228
  } catch (error) {
@@ -1325,6 +1341,7 @@ ${error.stack}` : String(error)
1325
1341
  }, {
1326
1342
  startAfter: new Date(Date.now() + intervalMs),
1327
1343
  expireInSeconds: defaultExpireInSeconds,
1344
+ priority: run.priority,
1328
1345
  ...retrySendOptions(run.maxRetries)
1329
1346
  });
1330
1347
  } catch (error) {
@@ -1561,10 +1578,11 @@ export {
1561
1578
  createWorkflowRef,
1562
1579
  WorkflowStatus,
1563
1580
  WorkflowRunNotFoundError,
1581
+ WorkflowRunInProgressError,
1564
1582
  WorkflowEngineError,
1565
1583
  WorkflowEngine,
1566
1584
  WorkflowClient
1567
1585
  };
1568
1586
 
1569
- //# debugId=EE11F9509955D59264756E2164756E21
1587
+ //# debugId=4EA8624FFA9819BE64756E2164756E21
1570
1588
  //# sourceMappingURL=index.js.map