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.
package/dist/index.cjs CHANGED
@@ -65,10 +65,11 @@ var __export = (target, all) => {
65
65
  var exports_src = {};
66
66
  __export(exports_src, {
67
67
  workflow: () => workflow,
68
- otelPlugin: () => otelPlugin,
68
+ isStepCached: () => isStepCached,
69
69
  createWorkflowRef: () => createWorkflowRef,
70
70
  WorkflowStatus: () => WorkflowStatus,
71
71
  WorkflowRunNotFoundError: () => WorkflowRunNotFoundError,
72
+ WorkflowRunInProgressError: () => WorkflowRunInProgressError,
72
73
  WorkflowEngineError: () => WorkflowEngineError,
73
74
  WorkflowEngine: () => WorkflowEngine,
74
75
  WorkflowClient: () => WorkflowClient
@@ -94,10 +95,11 @@ var WAIT_FOR_TIMELINE_SUFFIX = "wait-for";
94
95
  var invokeChildWorkflowTimelineKey = (stepId) => `${stepId}-${INVOKE_CHILD_WORKFLOW_TIMELINE_SUFFIX}`;
95
96
  var waitForTimelineKey = (stepId) => `${stepId}-${WAIT_FOR_TIMELINE_SUFFIX}`;
96
97
  var isInvokeChildWorkflowTimelineEntry = (entry) => !!entry && typeof entry === "object" && ("invokeChildWorkflow" in entry);
98
+ var isStepCached = (timeline, stepId) => timeline[stepId]?.output !== undefined || isInvokeChildWorkflowTimelineEntry(timeline[invokeChildWorkflowTimelineKey(stepId)]);
97
99
 
98
100
  // src/db/migration.ts
99
101
  var MIGRATION_LOCK_ID = 738291645;
100
- var CURRENT_SCHEMA_VERSION = 6;
102
+ var CURRENT_SCHEMA_VERSION = 7;
101
103
  async function runMigrations(db) {
102
104
  if (await isSchemaUpToDate(db)) {
103
105
  return;
@@ -176,6 +178,14 @@ async function runMigrations(db) {
176
178
  if (currentVersion < 6) {
177
179
  commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS priority integer DEFAULT 0 NOT NULL");
178
180
  }
181
+ if (currentVersion < 7) {
182
+ commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS singleton boolean DEFAULT false NOT NULL");
183
+ commands.push(`
184
+ CREATE UNIQUE INDEX IF NOT EXISTS workflow_runs_singleton_active_idx
185
+ ON workflow_runs (workflow_id)
186
+ WHERE singleton AND status IN ('pending', 'running')
187
+ `);
188
+ }
179
189
  if (currentVersion === 0) {
180
190
  commands.push(`INSERT INTO workflow_schema_version (version) VALUES (${CURRENT_SCHEMA_VERSION})`);
181
191
  } else {
@@ -215,9 +225,95 @@ async function getCurrentVersion(db) {
215
225
 
216
226
  // src/db/queries.ts
217
227
  var import_ksuid = __toESM(require("ksuid"));
228
+
229
+ // src/error.ts
230
+ function validateWorkflowId(workflowId) {
231
+ if (workflowId.length > MAX_WORKFLOW_ID_LENGTH) {
232
+ throw new WorkflowEngineError(`workflowId exceeds maximum length of ${MAX_WORKFLOW_ID_LENGTH} characters (got ${workflowId.length})`, workflowId);
233
+ }
234
+ }
235
+ function validateResourceId(resourceId) {
236
+ if (resourceId != null && resourceId.length > MAX_RESOURCE_ID_LENGTH) {
237
+ throw new WorkflowEngineError(`resourceId exceeds maximum length of ${MAX_RESOURCE_ID_LENGTH} characters (got ${resourceId.length})`);
238
+ }
239
+ }
240
+
241
+ class WorkflowEngineError extends Error {
242
+ workflowId;
243
+ runId;
244
+ cause;
245
+ issues;
246
+ constructor(message, workflowId, runId, cause = undefined, issues) {
247
+ super(message);
248
+ this.workflowId = workflowId;
249
+ this.runId = runId;
250
+ this.cause = cause;
251
+ this.issues = issues;
252
+ this.name = "WorkflowEngineError";
253
+ if (Error.captureStackTrace) {
254
+ Error.captureStackTrace(this, WorkflowEngineError);
255
+ }
256
+ }
257
+ }
258
+
259
+ class WorkflowRunNotFoundError extends WorkflowEngineError {
260
+ constructor(runId, workflowId) {
261
+ super("Workflow run not found", workflowId, runId);
262
+ this.name = "WorkflowRunNotFoundError";
263
+ }
264
+ }
265
+
266
+ class WorkflowRunInProgressError extends WorkflowEngineError {
267
+ constructor(workflowId, runId) {
268
+ super(`Workflow "${workflowId}" already has a running run; wait for it to pause, complete, fail, or be cancelled`, workflowId, runId);
269
+ this.name = "WorkflowRunInProgressError";
270
+ }
271
+ }
272
+
273
+ // src/types.ts
274
+ var WorkflowStatus;
275
+ ((WorkflowStatus2) => {
276
+ WorkflowStatus2["PENDING"] = "pending";
277
+ WorkflowStatus2["RUNNING"] = "running";
278
+ WorkflowStatus2["PAUSED"] = "paused";
279
+ WorkflowStatus2["COMPLETED"] = "completed";
280
+ WorkflowStatus2["FAILED"] = "failed";
281
+ WorkflowStatus2["CANCELLED"] = "cancelled";
282
+ })(WorkflowStatus ||= {});
283
+ var _STEP_BASE_METHOD_TO_TYPE = {
284
+ run: "run" /* RUN */,
285
+ waitFor: "waitFor" /* WAIT_FOR */,
286
+ waitUntil: "waitUntil" /* WAIT_UNTIL */,
287
+ delay: "delay" /* DELAY */,
288
+ sleep: "delay" /* DELAY */,
289
+ pause: "pause" /* PAUSE */,
290
+ poll: "poll" /* POLL */,
291
+ invokeChildWorkflow: "invokeChildWorkflow" /* INVOKE_CHILD_WORKFLOW */
292
+ };
293
+ var STEP_BASE_METHOD_TYPES = new Map(Object.entries(_STEP_BASE_METHOD_TO_TYPE));
294
+
295
+ // src/db/queries.ts
296
+ var EMPTY_RUN_STATS = {
297
+ ["pending" /* PENDING */]: 0,
298
+ ["running" /* RUNNING */]: 0,
299
+ ["paused" /* PAUSED */]: 0,
300
+ ["completed" /* COMPLETED */]: 0,
301
+ ["failed" /* FAILED */]: 0,
302
+ ["cancelled" /* CANCELLED */]: 0
303
+ };
218
304
  function generateKSUID(prefix) {
219
305
  return `${prefix ? `${prefix}_` : ""}${import_ksuid.default.randomSync().string}`;
220
306
  }
307
+ function isUniqueViolation(error) {
308
+ if (!error || typeof error !== "object") {
309
+ return false;
310
+ }
311
+ if ("code" in error && error.code === "23505") {
312
+ return true;
313
+ }
314
+ const cause = "cause" in error ? error.cause : undefined;
315
+ return Boolean(cause && typeof cause === "object" && "code" in cause && cause.code === "23505");
316
+ }
221
317
  function mapRowToWorkflowRun(row) {
222
318
  return {
223
319
  id: row.id,
@@ -243,7 +339,8 @@ function mapRowToWorkflowRun(row) {
243
339
  parentRunId: row.parent_run_id,
244
340
  parentStepId: row.parent_step_id,
245
341
  parentResourceId: row.parent_resource_id,
246
- scheduledAt: row.scheduled_at ? new Date(row.scheduled_at) : null
342
+ scheduledAt: row.scheduled_at ? new Date(row.scheduled_at) : null,
343
+ singleton: Boolean(row.singleton)
247
344
  };
248
345
  }
249
346
  async function insertWorkflowRun({
@@ -259,11 +356,20 @@ async function insertWorkflowRun({
259
356
  parentRunId,
260
357
  parentStepId,
261
358
  parentResourceId,
262
- scheduledAt
359
+ scheduledAt,
360
+ singleton = false
263
361
  }, db) {
264
362
  const runId = generateKSUID("run");
265
363
  const now = new Date;
266
- const result = await db.executeSql(`INSERT INTO workflow_runs (
364
+ if (singleton) {
365
+ const active = await getActiveSingletonRun(workflowId, db);
366
+ if (active) {
367
+ throw new WorkflowRunInProgressError(workflowId, active.id);
368
+ }
369
+ }
370
+ let result;
371
+ try {
372
+ result = await db.executeSql(`INSERT INTO workflow_runs (
267
373
  id,
268
374
  resource_id,
269
375
  workflow_id,
@@ -281,30 +387,39 @@ async function insertWorkflowRun({
281
387
  parent_run_id,
282
388
  parent_step_id,
283
389
  parent_resource_id,
284
- scheduled_at
390
+ scheduled_at,
391
+ singleton
285
392
  )
286
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
393
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
287
394
  ON CONFLICT (idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING
288
395
  RETURNING *`, [
289
- runId,
290
- resourceId ?? null,
291
- workflowId,
292
- currentStepId,
293
- status,
294
- JSON.stringify(input),
295
- maxRetries,
296
- priority,
297
- timeoutAt,
298
- now,
299
- now,
300
- "{}",
301
- 0,
302
- idempotencyKey ?? null,
303
- parentRunId ?? null,
304
- parentStepId ?? null,
305
- parentResourceId ?? null,
306
- scheduledAt ?? null
307
- ]);
396
+ runId,
397
+ resourceId ?? null,
398
+ workflowId,
399
+ currentStepId,
400
+ status,
401
+ JSON.stringify(input),
402
+ maxRetries,
403
+ priority,
404
+ timeoutAt,
405
+ now,
406
+ now,
407
+ "{}",
408
+ 0,
409
+ idempotencyKey ?? null,
410
+ parentRunId ?? null,
411
+ parentStepId ?? null,
412
+ parentResourceId ?? null,
413
+ scheduledAt ?? null,
414
+ singleton
415
+ ]);
416
+ } catch (error) {
417
+ if (singleton && isUniqueViolation(error)) {
418
+ const active = await getActiveSingletonRun(workflowId, db);
419
+ throw new WorkflowRunInProgressError(workflowId, active?.id);
420
+ }
421
+ throw error;
422
+ }
308
423
  if (result.rows[0]) {
309
424
  return { run: mapRowToWorkflowRun(result.rows[0]), created: true };
310
425
  }
@@ -316,6 +431,20 @@ async function insertWorkflowRun({
316
431
  }
317
432
  return { run: mapRowToWorkflowRun(existing.rows[0]), created: false };
318
433
  }
434
+ async function getActiveSingletonRun(workflowId, db) {
435
+ const result = await db.executeSql(`SELECT * FROM workflow_runs
436
+ WHERE workflow_id = $1
437
+ AND singleton
438
+ AND status IN ('pending', 'running')
439
+ LIMIT 1`, [workflowId]);
440
+ return result.rows[0] ? mapRowToWorkflowRun(result.rows[0]) : null;
441
+ }
442
+ async function assertSingletonSlotAvailable({ workflowId, exceptRunId }, db) {
443
+ const active = await getActiveSingletonRun(workflowId, db);
444
+ if (active && active.id !== exceptRunId) {
445
+ throw new WorkflowRunInProgressError(workflowId, active.id);
446
+ }
447
+ }
319
448
  async function getWorkflowRun({
320
449
  runId,
321
450
  resourceId
@@ -430,7 +559,15 @@ async function updateWorkflowRun({
430
559
  ${whereClause}
431
560
  RETURNING *
432
561
  `;
433
- const result = await db.executeSql(query, values);
562
+ let result;
563
+ try {
564
+ result = await db.executeSql(query, values);
565
+ } catch (error) {
566
+ if (data.status === "running" && isUniqueViolation(error)) {
567
+ throw new WorkflowRunInProgressError("unknown", runId);
568
+ }
569
+ throw error;
570
+ }
434
571
  const run = result.rows[0];
435
572
  if (!run) {
436
573
  return null;
@@ -511,6 +648,32 @@ async function getWorkflowRuns({
511
648
  const prevCursor = hasPrev && items.length > 0 ? items[0]?.id ?? null : null;
512
649
  return { items, nextCursor, prevCursor, hasMore, hasPrev };
513
650
  }
651
+ async function getWorkflowRunStats({
652
+ resourceId,
653
+ workflowId
654
+ }, db) {
655
+ const conditions = [];
656
+ const values = [];
657
+ let paramIndex = 1;
658
+ if (resourceId) {
659
+ conditions.push(`resource_id = $${paramIndex}`);
660
+ values.push(resourceId);
661
+ paramIndex++;
662
+ }
663
+ if (workflowId) {
664
+ conditions.push(`workflow_id = $${paramIndex}`);
665
+ values.push(workflowId);
666
+ }
667
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
668
+ const result = await db.executeSql(`SELECT status, COUNT(*)::int AS count FROM workflow_runs ${whereClause} GROUP BY status`, values);
669
+ const stats = { ...EMPTY_RUN_STATS };
670
+ for (const row of result.rows) {
671
+ if (row.status in stats) {
672
+ stats[row.status] = Number(row.count);
673
+ }
674
+ }
675
+ return stats;
676
+ }
514
677
  async function withPostgresTransaction(db, callback, pool) {
515
678
  let txDb;
516
679
  let release;
@@ -536,43 +699,6 @@ async function withPostgresTransaction(db, callback, pool) {
536
699
  }
537
700
  }
538
701
 
539
- // src/error.ts
540
- function validateWorkflowId(workflowId) {
541
- if (workflowId.length > MAX_WORKFLOW_ID_LENGTH) {
542
- throw new WorkflowEngineError(`workflowId exceeds maximum length of ${MAX_WORKFLOW_ID_LENGTH} characters (got ${workflowId.length})`, workflowId);
543
- }
544
- }
545
- function validateResourceId(resourceId) {
546
- if (resourceId != null && resourceId.length > MAX_RESOURCE_ID_LENGTH) {
547
- throw new WorkflowEngineError(`resourceId exceeds maximum length of ${MAX_RESOURCE_ID_LENGTH} characters (got ${resourceId.length})`);
548
- }
549
- }
550
-
551
- class WorkflowEngineError extends Error {
552
- workflowId;
553
- runId;
554
- cause;
555
- issues;
556
- constructor(message, workflowId, runId, cause = undefined, issues) {
557
- super(message);
558
- this.workflowId = workflowId;
559
- this.runId = runId;
560
- this.cause = cause;
561
- this.issues = issues;
562
- this.name = "WorkflowEngineError";
563
- if (Error.captureStackTrace) {
564
- Error.captureStackTrace(this, WorkflowEngineError);
565
- }
566
- }
567
- }
568
-
569
- class WorkflowRunNotFoundError extends WorkflowEngineError {
570
- constructor(runId, workflowId) {
571
- super("Workflow run not found", workflowId, runId);
572
- this.name = "WorkflowRunNotFoundError";
573
- }
574
- }
575
-
576
702
  // src/priority.ts
577
703
  var PRIORITY_LEVELS = { high: 100, normal: 0, low: -100 };
578
704
  function priorityToInt(value) {
@@ -597,28 +723,6 @@ function resolvePriority(...candidates) {
597
723
  return PRIORITY_LEVELS.normal;
598
724
  }
599
725
 
600
- // src/types.ts
601
- var WorkflowStatus;
602
- ((WorkflowStatus2) => {
603
- WorkflowStatus2["PENDING"] = "pending";
604
- WorkflowStatus2["RUNNING"] = "running";
605
- WorkflowStatus2["PAUSED"] = "paused";
606
- WorkflowStatus2["COMPLETED"] = "completed";
607
- WorkflowStatus2["FAILED"] = "failed";
608
- WorkflowStatus2["CANCELLED"] = "cancelled";
609
- })(WorkflowStatus ||= {});
610
- var _STEP_BASE_METHOD_TO_TYPE = {
611
- run: "run" /* RUN */,
612
- waitFor: "waitFor" /* WAIT_FOR */,
613
- waitUntil: "waitUntil" /* WAIT_UNTIL */,
614
- delay: "delay" /* DELAY */,
615
- sleep: "delay" /* DELAY */,
616
- pause: "pause" /* PAUSE */,
617
- poll: "poll" /* POLL */,
618
- invokeChildWorkflow: "invokeChildWorkflow" /* INVOKE_CHILD_WORKFLOW */
619
- };
620
- var STEP_BASE_METHOD_TYPES = new Map(Object.entries(_STEP_BASE_METHOD_TO_TYPE));
621
-
622
726
  // src/client.ts
623
727
  var LOG_PREFIX = "[WorkflowClient]";
624
728
  var defaultLogger = {
@@ -647,11 +751,10 @@ class WorkflowClient {
647
751
  const db = {
648
752
  executeSql: (text, values) => this.pool.query(text, values)
649
753
  };
650
- if (boss) {
651
- this.boss = boss;
652
- } else {
653
- this.boss = new import_pg_boss.PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
654
- }
754
+ this.boss = boss ?? new import_pg_boss.PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
755
+ this.boss.on("error", (error) => {
756
+ this.logger.error("pg-boss error", error);
757
+ });
655
758
  this.db = db;
656
759
  }
657
760
  async start() {
@@ -680,6 +783,7 @@ class WorkflowClient {
680
783
  let resourceId;
681
784
  let idempotencyKey;
682
785
  let options;
786
+ let singleton = false;
683
787
  if (typeof refOrParams === "function" && "id" in refOrParams) {
684
788
  const ref = refOrParams;
685
789
  workflowId = ref.id;
@@ -687,6 +791,7 @@ class WorkflowClient {
687
791
  options = optionsArg;
688
792
  resourceId = optionsArg?.resourceId;
689
793
  idempotencyKey = optionsArg?.idempotencyKey;
794
+ singleton = optionsArg?.singleton ?? ref.singleton === true;
690
795
  if (ref.inputSchema) {
691
796
  const result = await ref.inputSchema["~standard"].validate(input);
692
797
  if (result.issues) {
@@ -700,6 +805,7 @@ class WorkflowClient {
700
805
  resourceId = params.resourceId;
701
806
  idempotencyKey = params.idempotencyKey;
702
807
  options = params.options;
808
+ singleton = params.options?.singleton === true;
703
809
  }
704
810
  validateWorkflowId(workflowId);
705
811
  validateResourceId(resourceId);
@@ -714,7 +820,8 @@ class WorkflowClient {
714
820
  maxRetries: options?.retries ?? 0,
715
821
  priority: resolvePriority(options?.priority),
716
822
  timeoutAt,
717
- idempotencyKey
823
+ idempotencyKey,
824
+ singleton
718
825
  }, _db);
719
826
  if (created) {
720
827
  const job = {
@@ -744,6 +851,9 @@ class WorkflowClient {
744
851
  }) {
745
852
  await this.ensureStarted();
746
853
  const run = await this.getRun({ runId, resourceId });
854
+ if (run.singleton) {
855
+ await assertSingletonSlotAvailable({ workflowId: run.workflowId, exceptRunId: run.id }, this.db);
856
+ }
747
857
  const job = {
748
858
  runId: run.id,
749
859
  resourceId: resourceId ?? run.resourceId ?? undefined,
@@ -921,6 +1031,16 @@ class WorkflowClient {
921
1031
  workflowId
922
1032
  }, this.db);
923
1033
  }
1034
+ async getStats({
1035
+ resourceId,
1036
+ workflowId
1037
+ } = {}) {
1038
+ await this.ensureStarted();
1039
+ if (workflowId)
1040
+ validateWorkflowId(workflowId);
1041
+ validateResourceId(resourceId);
1042
+ return getWorkflowRunStats({ resourceId, workflowId }, this.db);
1043
+ }
924
1044
  async ensureStarted() {
925
1045
  if (!this._started) {
926
1046
  await this.start();
@@ -936,6 +1056,7 @@ function createWorkflowRef(id, options) {
936
1056
  timeout: defineOptions?.timeout,
937
1057
  retries: defineOptions?.retries,
938
1058
  priority: defineOptions?.priority,
1059
+ singleton: defineOptions?.singleton ?? options?.singleton,
939
1060
  schedule: defineOptions?.schedule,
940
1061
  timezone: defineOptions?.timezone
941
1062
  });
@@ -944,16 +1065,29 @@ function createWorkflowRef(id, options) {
944
1065
  value: options?.inputSchema,
945
1066
  enumerable: true
946
1067
  });
1068
+ Object.defineProperty(ref, "singleton", {
1069
+ value: options?.singleton,
1070
+ enumerable: true
1071
+ });
947
1072
  return ref;
948
1073
  }
949
1074
  function createWorkflowFactory(plugins = []) {
950
- const factory = (id, handler, { inputSchema, timeout, retries, priority, schedule, timezone } = {}) => ({
1075
+ const factory = (id, handler, {
1076
+ inputSchema,
1077
+ timeout,
1078
+ retries,
1079
+ priority,
1080
+ singleton,
1081
+ schedule,
1082
+ timezone
1083
+ } = {}) => ({
951
1084
  id,
952
1085
  handler,
953
1086
  inputSchema,
954
1087
  timeout,
955
1088
  retries,
956
1089
  priority,
1090
+ singleton,
957
1091
  schedule,
958
1092
  timezone,
959
1093
  plugins: plugins.length > 0 ? plugins : undefined
@@ -1166,11 +1300,10 @@ class WorkflowEngine {
1166
1300
  const db = {
1167
1301
  executeSql: (text, values) => this.pool.query(text, values)
1168
1302
  };
1169
- if (boss) {
1170
- this.boss = boss;
1171
- } else {
1172
- this.boss = new import_pg_boss2.PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
1173
- }
1303
+ this.boss = boss ?? new import_pg_boss2.PgBoss({ db, schema: DEFAULT_PGBOSS_SCHEMA });
1304
+ this.boss.on("error", (error) => {
1305
+ this.logger.error("pg-boss error", error);
1306
+ });
1174
1307
  this.db = this.boss.getDb();
1175
1308
  }
1176
1309
  async start(asEngine = true, {
@@ -1224,6 +1357,10 @@ class WorkflowEngine {
1224
1357
  try {
1225
1358
  await this.createWorkflowRun({ workflowId, input: {}, scheduledAt });
1226
1359
  } catch (error) {
1360
+ if (error instanceof WorkflowRunInProgressError) {
1361
+ this.logger.log(`Skipping schedule fire for "${workflowId}": a run is already in progress`, { workflowId, runId: error.runId });
1362
+ return;
1363
+ }
1227
1364
  this.logger.error(`Schedule fire failed to start a run for workflow "${workflowId}"`, error instanceof Error ? error : new Error(String(error)), { workflowId });
1228
1365
  throw error;
1229
1366
  }
@@ -1373,7 +1510,8 @@ class WorkflowEngine {
1373
1510
  parentRunId,
1374
1511
  parentStepId,
1375
1512
  parentResourceId,
1376
- scheduledAt
1513
+ scheduledAt,
1514
+ singleton: workflow2.singleton === true || options?.singleton === true
1377
1515
  }, targetDb);
1378
1516
  const insertAndEnqueue = async (targetDb) => {
1379
1517
  const result = await insertRun(targetDb);
@@ -1532,6 +1670,9 @@ class WorkflowEngine {
1532
1670
  }) {
1533
1671
  await this.checkIfHasStarted();
1534
1672
  const run = await this.getRun({ runId, resourceId });
1673
+ if (run.singleton) {
1674
+ await assertSingletonSlotAvailable({ workflowId: run.workflowId, exceptRunId: run.id }, this.db);
1675
+ }
1535
1676
  const jobResourceId = resourceId ?? run.resourceId ?? undefined;
1536
1677
  const job = {
1537
1678
  runId: run.id,
@@ -2329,177 +2470,16 @@ ${error.stack}` : String(error)
2329
2470
  workflowId
2330
2471
  }, this.db);
2331
2472
  }
2332
- }
2333
- // src/plugins/otel.ts
2334
- var import_api = require("@opentelemetry/api");
2335
- var DEFAULT_PREFIX = "pg_workflows";
2336
- function isCachedHit(timeline, stepId, kind) {
2337
- const entry = timeline[stepId];
2338
- if (entry && typeof entry === "object" && "output" in entry && entry.output !== undefined) {
2339
- return true;
2340
- }
2341
- if (kind === "invokeChildWorkflow" && timeline[invokeChildWorkflowTimelineKey(stepId)]) {
2342
- return true;
2473
+ async getStats({
2474
+ resourceId,
2475
+ workflowId
2476
+ } = {}) {
2477
+ if (workflowId)
2478
+ validateWorkflowId(workflowId);
2479
+ validateResourceId(resourceId);
2480
+ return getWorkflowRunStats({ resourceId, workflowId }, this.db);
2343
2481
  }
2344
- return false;
2345
- }
2346
- function otelPlugin(options = {}) {
2347
- const tracer = options.tracer ?? import_api.trace.getTracer("pg-workflows");
2348
- const prefix = options.spanNamePrefix ?? DEFAULT_PREFIX;
2349
- const extraAttrs = options.attributes;
2350
- return {
2351
- name: "opentelemetry",
2352
- methods: (step, context) => {
2353
- const wrapVoidish = (kind, base) => {
2354
- return async (stepId, ...args) => {
2355
- if (isCachedHit(context.timeline, stepId, kind)) {
2356
- return base(stepId, ...args);
2357
- }
2358
- const capturedCtx = import_api.context.active();
2359
- const startTime = new Date;
2360
- let result;
2361
- let originalErr;
2362
- let thrownError;
2363
- try {
2364
- result = await base(stepId, ...args);
2365
- } catch (err) {
2366
- originalErr = err;
2367
- thrownError = err instanceof Error ? err : new Error(String(err));
2368
- }
2369
- const span = tracer.startSpan(`${prefix}.step.${kind}`, {
2370
- startTime,
2371
- attributes: { "step.id": stepId, "step.type": kind }
2372
- }, capturedCtx);
2373
- if (thrownError) {
2374
- span.recordException(thrownError);
2375
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: thrownError.message });
2376
- span.end();
2377
- throw originalErr;
2378
- }
2379
- span.setStatus({ code: import_api.SpanStatusCode.OK });
2380
- span.end();
2381
- return result;
2382
- };
2383
- };
2384
- return {
2385
- run: async (stepId, handler) => {
2386
- if (isCachedHit(context.timeline, stepId, "run")) {
2387
- return step.run(stepId, handler);
2388
- }
2389
- const capturedCtx = import_api.context.active();
2390
- const startTime = new Date;
2391
- let result;
2392
- let originalErr;
2393
- let thrownError;
2394
- try {
2395
- result = await step.run(stepId, handler);
2396
- } catch (err) {
2397
- originalErr = err;
2398
- thrownError = err instanceof Error ? err : new Error(String(err));
2399
- }
2400
- if (result === undefined && !thrownError) {
2401
- return;
2402
- }
2403
- const span = tracer.startSpan(`${prefix}.step.run`, {
2404
- startTime,
2405
- attributes: { "step.id": stepId, "step.type": "run" }
2406
- }, capturedCtx);
2407
- if (thrownError) {
2408
- span.recordException(thrownError);
2409
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: thrownError.message });
2410
- span.end();
2411
- throw originalErr;
2412
- }
2413
- span.setStatus({ code: import_api.SpanStatusCode.OK });
2414
- span.end();
2415
- return result;
2416
- },
2417
- waitFor: wrapVoidish("waitFor", step.waitFor),
2418
- delay: wrapVoidish("delay", step.delay),
2419
- sleep: wrapVoidish("delay", step.delay),
2420
- waitUntil: wrapVoidish("waitUntil", step.waitUntil),
2421
- pause: wrapVoidish("pause", step.pause),
2422
- poll: async (stepId, conditionFn, pollOptions) => {
2423
- const capturedCtx = import_api.context.active();
2424
- const startTime = new Date;
2425
- let result;
2426
- let originalErr;
2427
- let thrownError;
2428
- try {
2429
- result = await step.poll(stepId, conditionFn, pollOptions);
2430
- } catch (err) {
2431
- originalErr = err;
2432
- thrownError = err instanceof Error ? err : new Error(String(err));
2433
- }
2434
- const span = tracer.startSpan(`${prefix}.step.poll`, {
2435
- startTime,
2436
- attributes: { "step.id": stepId, "step.type": "poll" }
2437
- }, capturedCtx);
2438
- if (thrownError) {
2439
- span.recordException(thrownError);
2440
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: thrownError.message });
2441
- span.end();
2442
- throw originalErr;
2443
- }
2444
- span.setStatus({ code: import_api.SpanStatusCode.OK });
2445
- span.end();
2446
- return result;
2447
- },
2448
- invokeChildWorkflow: async (stepId, refOrParams, inputArg, optionsArg) => {
2449
- if (isCachedHit(context.timeline, stepId, "invokeChildWorkflow")) {
2450
- return step.invokeChildWorkflow(stepId, refOrParams, inputArg, optionsArg);
2451
- }
2452
- const capturedCtx = import_api.context.active();
2453
- const startTime = new Date;
2454
- let result;
2455
- let originalErr;
2456
- let thrownError;
2457
- try {
2458
- result = await step.invokeChildWorkflow(stepId, refOrParams, inputArg, optionsArg);
2459
- } catch (err) {
2460
- originalErr = err;
2461
- thrownError = err instanceof Error ? err : new Error(String(err));
2462
- }
2463
- const span = tracer.startSpan(`${prefix}.step.invokeChildWorkflow`, {
2464
- startTime,
2465
- attributes: { "step.id": stepId, "step.type": "invokeChildWorkflow" }
2466
- }, capturedCtx);
2467
- if (thrownError) {
2468
- span.recordException(thrownError);
2469
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: thrownError.message });
2470
- span.end();
2471
- throw originalErr;
2472
- }
2473
- span.setStatus({ code: import_api.SpanStatusCode.OK });
2474
- span.end();
2475
- return result;
2476
- }
2477
- };
2478
- },
2479
- wrap: (context, next) => tracer.startActiveSpan(`${prefix}.workflow.run`, {
2480
- attributes: {
2481
- "workflow.id": context.workflowId,
2482
- "workflow.run_id": context.runId,
2483
- "workflow.attempt": context.attempt,
2484
- ...context.resourceId ? { "workflow.resource_id": context.resourceId } : {},
2485
- ...extraAttrs ? extraAttrs(context) : {}
2486
- }
2487
- }, async (span) => {
2488
- try {
2489
- const result = await next();
2490
- span.setStatus({ code: import_api.SpanStatusCode.OK });
2491
- return result;
2492
- } catch (err) {
2493
- const error = err instanceof Error ? err : new Error(String(err));
2494
- span.recordException(error);
2495
- span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: error.message });
2496
- throw err;
2497
- } finally {
2498
- span.end();
2499
- }
2500
- })
2501
- };
2502
2482
  }
2503
2483
 
2504
- //# debugId=5FA47CDFF028114064756E2164756E21
2484
+ //# debugId=B101AE54CEFCC2C064756E2164756E21
2505
2485
  //# sourceMappingURL=index.js.map