pg-workflows 0.10.0 → 0.12.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.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  DEFAULT_PGBOSS_SCHEMA,
3
3
  PAUSE_EVENT_NAME,
4
- StepType,
5
4
  WORKFLOW_RUN_DLQ_QUEUE_NAME,
6
5
  WORKFLOW_RUN_QUEUE_NAME,
7
6
  WorkflowClient,
@@ -21,32 +20,7 @@ import {
21
20
  waitForTimelineKey,
22
21
  withPostgresTransaction,
23
22
  workflow
24
- } from "./shared/chunk-nygamc7b.js";
25
- // src/duration.ts
26
- import parse from "parse-duration";
27
- var MS_PER_SECOND = 1000;
28
- var MS_PER_MINUTE = 60 * MS_PER_SECOND;
29
- var MS_PER_HOUR = 60 * MS_PER_MINUTE;
30
- var MS_PER_DAY = 24 * MS_PER_HOUR;
31
- var MS_PER_WEEK = 7 * MS_PER_DAY;
32
- function parseDuration(duration) {
33
- if (typeof duration === "string") {
34
- if (duration.trim() === "") {
35
- throw new WorkflowEngineError("Invalid duration: empty string");
36
- }
37
- const ms2 = parse(duration);
38
- if (ms2 == null || ms2 <= 0) {
39
- throw new WorkflowEngineError(`Invalid duration: "${duration}"`);
40
- }
41
- return ms2;
42
- }
43
- const { weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0 } = duration;
44
- const ms = weeks * MS_PER_WEEK + days * MS_PER_DAY + hours * MS_PER_HOUR + minutes * MS_PER_MINUTE + seconds * MS_PER_SECOND;
45
- if (ms <= 0) {
46
- throw new WorkflowEngineError("Invalid duration: must be a positive value");
47
- }
48
- return ms;
49
- }
23
+ } from "./shared/chunk-ahxqsytt.js";
50
24
  // src/engine.ts
51
25
  import { merge } from "es-toolkit";
52
26
  import pg from "pg";
@@ -122,6 +96,32 @@ function parseWorkflowHandler(handler) {
122
96
  return { steps: Array.from(steps.values()) };
123
97
  }
124
98
 
99
+ // src/duration.ts
100
+ import parse from "parse-duration";
101
+ var MS_PER_SECOND = 1000;
102
+ var MS_PER_MINUTE = 60 * MS_PER_SECOND;
103
+ var MS_PER_HOUR = 60 * MS_PER_MINUTE;
104
+ var MS_PER_DAY = 24 * MS_PER_HOUR;
105
+ var MS_PER_WEEK = 7 * MS_PER_DAY;
106
+ function parseDuration(duration) {
107
+ if (typeof duration === "string") {
108
+ if (duration.trim() === "") {
109
+ throw new WorkflowEngineError("Invalid duration: empty string");
110
+ }
111
+ const ms2 = parse(duration);
112
+ if (ms2 == null || ms2 <= 0) {
113
+ throw new WorkflowEngineError(`Invalid duration: "${duration}"`);
114
+ }
115
+ return ms2;
116
+ }
117
+ const { weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0 } = duration;
118
+ const ms = weeks * MS_PER_WEEK + days * MS_PER_DAY + hours * MS_PER_HOUR + minutes * MS_PER_MINUTE + seconds * MS_PER_SECOND;
119
+ if (ms <= 0) {
120
+ throw new WorkflowEngineError("Invalid duration: must be a positive value");
121
+ }
122
+ return ms;
123
+ }
124
+
125
125
  // src/engine.ts
126
126
  var LOG_PREFIX = "[WorkflowEngine]";
127
127
  var StepTypeToIcon = {
@@ -271,7 +271,7 @@ class WorkflowEngine {
271
271
  async startWorkflow(refOrParams, inputArg, optionsArg) {
272
272
  const { workflowId, input, resourceId, idempotencyKey, options } = this.resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg);
273
273
  if (!this._started) {
274
- await this.start(false, { batchSize: options?.batchSize ?? 1 });
274
+ await this.start(false);
275
275
  }
276
276
  const { run } = await this.createWorkflowRun({
277
277
  workflowId,
@@ -732,19 +732,32 @@ class WorkflowEngine {
732
732
  };
733
733
  let step = { ...baseStep };
734
734
  const plugins = workflow2.plugins ?? [];
735
- for (const plugin of plugins) {
736
- const extra = plugin.methods(step);
737
- step = { ...step, ...extra };
738
- }
739
735
  const context = {
740
736
  input: run.input,
741
737
  workflowId: run.workflowId,
742
738
  runId: run.id,
743
- timeline: run.timeline,
739
+ resourceId: run.resourceId ?? undefined,
740
+ attempt: run.retryCount,
741
+ get timeline() {
742
+ return run?.timeline ?? {};
743
+ },
744
744
  logger: this.logger,
745
745
  step
746
746
  };
747
- const result = await workflow2.handler(context);
747
+ for (const plugin of plugins) {
748
+ const extra = plugin.methods(step, context);
749
+ step = { ...step, ...extra };
750
+ context.step = step;
751
+ }
752
+ let next = () => workflow2.handler(context);
753
+ for (const plugin of [...plugins].reverse()) {
754
+ if (plugin.wrap) {
755
+ const inner = next;
756
+ const wrap = plugin.wrap;
757
+ next = () => wrap(context, inner);
758
+ }
759
+ }
760
+ const result = await next();
748
761
  run = await this.getRun({ runId, resourceId: scopedResourceId });
749
762
  const isLastParsedStep = run.currentStepId === workflow2.steps[workflow2.steps.length - 1]?.id;
750
763
  const hasPluginSteps = (workflow2.plugins?.length ?? 0) > 0;
@@ -1037,7 +1050,7 @@ class WorkflowEngine {
1037
1050
  if (output === undefined) {
1038
1051
  output = {};
1039
1052
  }
1040
- run = await this.updateRun({
1053
+ const updated = await this.updateRun({
1041
1054
  runId: run.id,
1042
1055
  resourceId: run.resourceId ?? undefined,
1043
1056
  data: {
@@ -1049,6 +1062,7 @@ class WorkflowEngine {
1049
1062
  })
1050
1063
  }
1051
1064
  }, { db });
1065
+ Object.assign(run, updated);
1052
1066
  return output;
1053
1067
  } catch (error) {
1054
1068
  this.logger.error(`Step ${stepId} failed:`, error, {
@@ -1276,19 +1290,190 @@ ${error.stack}` : String(error)
1276
1290
  }, this.db);
1277
1291
  }
1278
1292
  }
1293
+ // src/plugins/otel.ts
1294
+ import {
1295
+ context as otelContext,
1296
+ SpanStatusCode,
1297
+ trace
1298
+ } from "@opentelemetry/api";
1299
+ var DEFAULT_PREFIX = "pg_workflows";
1300
+ function isCachedHit(timeline, stepId, kind) {
1301
+ const entry = timeline[stepId];
1302
+ if (entry && typeof entry === "object" && "output" in entry && entry.output !== undefined) {
1303
+ return true;
1304
+ }
1305
+ if (kind === "invokeChildWorkflow" && timeline[invokeChildWorkflowTimelineKey(stepId)]) {
1306
+ return true;
1307
+ }
1308
+ return false;
1309
+ }
1310
+ function otelPlugin(options = {}) {
1311
+ const tracer = options.tracer ?? trace.getTracer("pg-workflows");
1312
+ const prefix = options.spanNamePrefix ?? DEFAULT_PREFIX;
1313
+ const extraAttrs = options.attributes;
1314
+ return {
1315
+ name: "opentelemetry",
1316
+ methods: (step, context) => {
1317
+ const wrapVoidish = (kind, base) => {
1318
+ return async (stepId, ...args) => {
1319
+ if (isCachedHit(context.timeline, stepId, kind)) {
1320
+ return base(stepId, ...args);
1321
+ }
1322
+ const capturedCtx = otelContext.active();
1323
+ const startTime = new Date;
1324
+ let result;
1325
+ let originalErr;
1326
+ let thrownError;
1327
+ try {
1328
+ result = await base(stepId, ...args);
1329
+ } catch (err) {
1330
+ originalErr = err;
1331
+ thrownError = err instanceof Error ? err : new Error(String(err));
1332
+ }
1333
+ const span = tracer.startSpan(`${prefix}.step.${kind}`, {
1334
+ startTime,
1335
+ attributes: { "step.id": stepId, "step.type": kind }
1336
+ }, capturedCtx);
1337
+ if (thrownError) {
1338
+ span.recordException(thrownError);
1339
+ span.setStatus({ code: SpanStatusCode.ERROR, message: thrownError.message });
1340
+ span.end();
1341
+ throw originalErr;
1342
+ }
1343
+ span.setStatus({ code: SpanStatusCode.OK });
1344
+ span.end();
1345
+ return result;
1346
+ };
1347
+ };
1348
+ return {
1349
+ run: async (stepId, handler) => {
1350
+ if (isCachedHit(context.timeline, stepId, "run")) {
1351
+ return step.run(stepId, handler);
1352
+ }
1353
+ const capturedCtx = otelContext.active();
1354
+ const startTime = new Date;
1355
+ let result;
1356
+ let originalErr;
1357
+ let thrownError;
1358
+ try {
1359
+ result = await step.run(stepId, handler);
1360
+ } catch (err) {
1361
+ originalErr = err;
1362
+ thrownError = err instanceof Error ? err : new Error(String(err));
1363
+ }
1364
+ if (result === undefined && !thrownError) {
1365
+ return;
1366
+ }
1367
+ const span = tracer.startSpan(`${prefix}.step.run`, {
1368
+ startTime,
1369
+ attributes: { "step.id": stepId, "step.type": "run" }
1370
+ }, capturedCtx);
1371
+ if (thrownError) {
1372
+ span.recordException(thrownError);
1373
+ span.setStatus({ code: SpanStatusCode.ERROR, message: thrownError.message });
1374
+ span.end();
1375
+ throw originalErr;
1376
+ }
1377
+ span.setStatus({ code: SpanStatusCode.OK });
1378
+ span.end();
1379
+ return result;
1380
+ },
1381
+ waitFor: wrapVoidish("waitFor", step.waitFor),
1382
+ delay: wrapVoidish("delay", step.delay),
1383
+ sleep: wrapVoidish("delay", step.delay),
1384
+ waitUntil: wrapVoidish("waitUntil", step.waitUntil),
1385
+ pause: wrapVoidish("pause", step.pause),
1386
+ poll: async (stepId, conditionFn, pollOptions) => {
1387
+ const capturedCtx = otelContext.active();
1388
+ const startTime = new Date;
1389
+ let result;
1390
+ let originalErr;
1391
+ let thrownError;
1392
+ try {
1393
+ result = await step.poll(stepId, conditionFn, pollOptions);
1394
+ } catch (err) {
1395
+ originalErr = err;
1396
+ thrownError = err instanceof Error ? err : new Error(String(err));
1397
+ }
1398
+ const span = tracer.startSpan(`${prefix}.step.poll`, {
1399
+ startTime,
1400
+ attributes: { "step.id": stepId, "step.type": "poll" }
1401
+ }, capturedCtx);
1402
+ if (thrownError) {
1403
+ span.recordException(thrownError);
1404
+ span.setStatus({ code: SpanStatusCode.ERROR, message: thrownError.message });
1405
+ span.end();
1406
+ throw originalErr;
1407
+ }
1408
+ span.setStatus({ code: SpanStatusCode.OK });
1409
+ span.end();
1410
+ return result;
1411
+ },
1412
+ invokeChildWorkflow: async (stepId, refOrParams, inputArg, optionsArg) => {
1413
+ if (isCachedHit(context.timeline, stepId, "invokeChildWorkflow")) {
1414
+ return step.invokeChildWorkflow(stepId, refOrParams, inputArg, optionsArg);
1415
+ }
1416
+ const capturedCtx = otelContext.active();
1417
+ const startTime = new Date;
1418
+ let result;
1419
+ let originalErr;
1420
+ let thrownError;
1421
+ try {
1422
+ result = await step.invokeChildWorkflow(stepId, refOrParams, inputArg, optionsArg);
1423
+ } catch (err) {
1424
+ originalErr = err;
1425
+ thrownError = err instanceof Error ? err : new Error(String(err));
1426
+ }
1427
+ const span = tracer.startSpan(`${prefix}.step.invokeChildWorkflow`, {
1428
+ startTime,
1429
+ attributes: { "step.id": stepId, "step.type": "invokeChildWorkflow" }
1430
+ }, capturedCtx);
1431
+ if (thrownError) {
1432
+ span.recordException(thrownError);
1433
+ span.setStatus({ code: SpanStatusCode.ERROR, message: thrownError.message });
1434
+ span.end();
1435
+ throw originalErr;
1436
+ }
1437
+ span.setStatus({ code: SpanStatusCode.OK });
1438
+ span.end();
1439
+ return result;
1440
+ }
1441
+ };
1442
+ },
1443
+ wrap: (context, next) => tracer.startActiveSpan(`${prefix}.workflow.run`, {
1444
+ attributes: {
1445
+ "workflow.id": context.workflowId,
1446
+ "workflow.run_id": context.runId,
1447
+ "workflow.attempt": context.attempt,
1448
+ ...context.resourceId ? { "workflow.resource_id": context.resourceId } : {},
1449
+ ...extraAttrs ? extraAttrs(context) : {}
1450
+ }
1451
+ }, async (span) => {
1452
+ try {
1453
+ const result = await next();
1454
+ span.setStatus({ code: SpanStatusCode.OK });
1455
+ return result;
1456
+ } catch (err) {
1457
+ const error = err instanceof Error ? err : new Error(String(err));
1458
+ span.recordException(error);
1459
+ span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
1460
+ throw err;
1461
+ } finally {
1462
+ span.end();
1463
+ }
1464
+ })
1465
+ };
1466
+ }
1279
1467
  export {
1280
1468
  workflow,
1281
- validateWorkflowId,
1282
- validateResourceId,
1283
- parseDuration,
1469
+ otelPlugin,
1284
1470
  createWorkflowRef,
1285
1471
  WorkflowStatus,
1286
1472
  WorkflowRunNotFoundError,
1287
1473
  WorkflowEngineError,
1288
1474
  WorkflowEngine,
1289
- WorkflowClient,
1290
- StepType
1475
+ WorkflowClient
1291
1476
  };
1292
1477
 
1293
- //# debugId=F3273B34BE68C60E64756E2164756E21
1478
+ //# debugId=3131CBB2B482181264756E2164756E21
1294
1479
  //# sourceMappingURL=index.js.map