pg-workflows 0.2.0 → 0.3.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
@@ -45,6 +45,8 @@ If you need enterprise-grade features like distributed tracing, complex DAG sche
45
45
  - **Durable Execution on Postgres** - Workflow state is persisted in PostgreSQL. Workflows survive process crashes, restarts, and deployments.
46
46
  - **Step-by-Step Execution** - Break complex processes into discrete, resumable steps. Each step runs exactly once, even across retries.
47
47
  - **Event-Driven Orchestration** - Pause workflows and wait for external events with `step.waitFor()`. Resume automatically when signals arrive.
48
+ - **Polling Steps** - Repeatedly check a condition with `step.poll()` at a configurable interval (minimum 30s) until it returns a truthy value or a timeout expires.
49
+ - **Scheduled & Delay Steps** - Wait until a specific date with `step.waitUntil()`, or use `step.delay()` / `step.sleep()` with human-readable durations (`'3 days'`, `{ hours: 2 }`). Past dates run immediately.
48
50
  - **Pause and Resume** - Manually pause long-running workflows and resume them later via API.
49
51
  - **Built-in Retries** - Automatic retries with exponential backoff at the workflow level.
50
52
  - **Configurable Timeouts** - Set workflow-level and step-level timeouts to prevent runaway executions.
@@ -378,6 +380,23 @@ const eventData = await step.waitFor('wait-step', {
378
380
  });
379
381
  ```
380
382
 
383
+ ### Scheduled and Delay Steps
384
+
385
+ Wait until a specific time, or delay for a duration (sugar over `waitUntil`). If the date is in the past, the step runs immediately.
386
+
387
+ ```typescript
388
+ // Wait until a specific date (Date, ISO string, or { date })
389
+ await step.waitUntil('scheduled-step', new Date('2025-06-01'));
390
+ await step.waitUntil('scheduled-step', '2025-06-01T12:00:00.000Z');
391
+ await step.waitUntil('scheduled-step', { date: new Date('2025-06-01') });
392
+
393
+ // Delay for a duration (string or object). sleep is an alias of delay.
394
+ await step.delay('cool-off', '3 days');
395
+ await step.delay('cool-off', { days: 3 });
396
+ await step.delay('ramp-up', '2 days 12 hours');
397
+ await step.sleep('backoff', '1 hour');
398
+ ```
399
+
381
400
  ### Resource ID
382
401
 
383
402
  The optional `resourceId` associates a workflow run with an external entity in your application - a user, an order, a subscription, or any domain object the workflow operates on. It serves two purposes:
@@ -453,6 +472,44 @@ const batchWorkflow = workflow('batch-process', async ({ step }) => {
453
472
  });
454
473
  ```
455
474
 
475
+ ### Scheduled Reminder with Delay
476
+
477
+ ```typescript
478
+ const reminderWorkflow = workflow('send-reminder', async ({ step, input }) => {
479
+ await step.run('send-initial', async () => {
480
+ return await sendEmail(input.email, 'Welcome!');
481
+ });
482
+ // Pause for 3 days, then send follow-up (durable - survives restarts)
483
+ await step.delay('cool-off', '3 days');
484
+ await step.run('send-follow-up', async () => {
485
+ return await sendEmail(input.email, 'Here’s a reminder…');
486
+ });
487
+ }, { inputSchema: z.object({ email: z.string().email() }) });
488
+ ```
489
+
490
+ ### Polling Until a Condition Is Met
491
+
492
+ ```typescript
493
+ const paymentWorkflow = workflow('await-payment', async ({ step, input }) => {
494
+ const result = await step.poll(
495
+ 'wait-for-payment',
496
+ async () => {
497
+ const payment = await getPaymentStatus(input.paymentId);
498
+ return payment.completed ? payment : false;
499
+ },
500
+ { interval: '1 minute', timeout: '24 hours' },
501
+ );
502
+
503
+ if (result.timedOut) {
504
+ return { status: 'expired' };
505
+ }
506
+
507
+ return { status: 'paid', payment: result.data };
508
+ });
509
+ ```
510
+
511
+ `conditionFn` returns `false` to keep polling, or a truthy value to resolve the step. The minimum interval is 30s (default). If `timeout` is omitted the step polls indefinitely.
512
+
456
513
  ### Error Handling with Retries
457
514
 
458
515
  ```typescript
@@ -542,13 +599,21 @@ The context object passed to workflow handlers:
542
599
  logger: WorkflowLogger, // Logger instance
543
600
  step: {
544
601
  run: <T>(stepId, handler) => Promise<T>,
545
- waitFor: <T>(stepId, { eventName, timeout?, schema? }) => Promise<T>,
546
- waitUntil: (stepId, { date }) => Promise<void>,
602
+ // without timeout: always returns event data T
603
+ waitFor: <T>(stepId, { eventName, schema? }) => Promise<T>,
604
+ // with timeout: returns event data T or undefined if timeout fires first
605
+ waitFor: <T>(stepId, { eventName, timeout, schema? }) => Promise<T | undefined>,
606
+ waitUntil: (stepId, date | dateString | { date }) => Promise<void>,
607
+ delay: (stepId, duration) => Promise<void>,
608
+ sleep: (stepId, duration) => Promise<void>,
547
609
  pause: (stepId) => Promise<void>,
610
+ poll: <T>(stepId, conditionFn, { interval?, timeout? }) => Promise<{ timedOut: false; data: T } | { timedOut: true }>,
548
611
  }
549
612
  }
550
613
  ```
551
614
 
615
+ `duration` is a string (e.g. `'3 days'`, `'2h'`) or an object (`{ weeks?, days?, hours?, minutes?, seconds? }`). See the `Duration` type and `parseDuration` from the package.
616
+
552
617
  ### WorkflowStatus
553
618
 
554
619
  ```typescript