pg-workflows 0.0.1-claimed → 0.1.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sokratis Vidros
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,430 @@
1
+ # pg-workflows
2
+
3
+ **The simplest Postgres workflow engine for TypeScript.** Durable execution, event-driven orchestration, and automatic retries — powered entirely by PostgreSQL. No extra infrastructure. No vendor lock-in.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/pg-workflows.svg)](https://www.npmjs.com/package/pg-workflows)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org/)
8
+ [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-%3E%3D10-336791.svg)](https://www.postgresql.org/)
9
+
10
+ ```bash
11
+ npm install pg-workflows
12
+ ```
13
+
14
+ ---
15
+
16
+ ## Why pg-workflows?
17
+
18
+ Most workflow engines ask you to adopt an entirely new platform — a new runtime, a new deployment target, a new bill. **pg-workflows takes a different approach**: if you already have PostgreSQL, you already have everything you need.
19
+
20
+ | | pg-workflows | Temporal | Inngest | DBOS | pgflow |
21
+ |---|:---:|:---:|:---:|:---:|:---:|
22
+ | **Runs on your existing Postgres** | Yes | No | No | Partial | Supabase only |
23
+ | **Zero extra infrastructure** | Yes | No | No | No | No |
24
+ | **Framework-agnostic** | Yes | Yes | No | Yes | No |
25
+ | **Event-driven pause/resume** | Yes | Yes | Yes | No | No |
26
+ | **Open source** | MIT | MIT | ELv2 | MIT | Apache-2.0 |
27
+ | **TypeScript-first** | Yes | Via SDK | Yes | Via SDK | Yes |
28
+
29
+ ### When to use pg-workflows
30
+
31
+ - You already run **PostgreSQL** and want to add durable workflows without new services
32
+ - You need a **lightweight, self-hosted** workflow engine with zero operational overhead
33
+ - You want **event-driven orchestration** (pause, resume, wait for external signals)
34
+ - You're building with **TypeScript/Node.js** and want a native developer experience
35
+
36
+ ### When to consider alternatives
37
+
38
+ If you need enterprise-grade features like distributed tracing, complex DAG scheduling, or plan to scale to millions of concurrent workflows, consider [Temporal](https://temporal.io/), [Inngest](https://www.inngest.com/), [Trigger.dev](https://trigger.dev/), or [DBOS](https://www.dbos.dev/).
39
+
40
+ ---
41
+
42
+ ## Features
43
+
44
+ - **Durable Execution on Postgres** — Workflow state is persisted in PostgreSQL. Workflows survive process crashes, restarts, and deployments.
45
+ - **Step-by-Step Execution** — Break complex processes into discrete, resumable steps. Each step runs exactly once, even across retries.
46
+ - **Event-Driven Orchestration** — Pause workflows and wait for external events with `step.waitFor()`. Resume automatically when signals arrive.
47
+ - **Pause and Resume** — Manually pause long-running workflows and resume them later via API.
48
+ - **Built-in Retries** — Automatic retries with exponential backoff at the workflow level.
49
+ - **Configurable Timeouts** — Set workflow-level and step-level timeouts to prevent runaway executions.
50
+ - **Progress Tracking** — Monitor workflow completion percentage, completed steps, and total steps in real-time.
51
+ - **Input Validation** — Define schemas with Zod for type-safe, validated workflow inputs.
52
+ - **Built on pg-boss** — Leverages the battle-tested [pg-boss](https://github.com/timgit/pg-boss) job queue for reliable task scheduling.
53
+
54
+ ---
55
+
56
+ ## How It Works
57
+
58
+ pg-workflows uses PostgreSQL as both the **job queue** and the **state store**. Under the hood:
59
+
60
+ 1. **Define** workflows as TypeScript functions with discrete steps
61
+ 2. **Start** a workflow run — the engine creates a database record and enqueues the first execution
62
+ 3. **Execute** steps one by one — each step's result is persisted before moving to the next
63
+ 4. **Pause** on `waitFor()` or `pause()` — the workflow sleeps with zero resource consumption
64
+ 5. **Resume** when an external event arrives or `resumeWorkflow()` is called
65
+ 6. **Complete** — the final result is stored and the workflow is marked as done
66
+
67
+ All state lives in PostgreSQL. No Redis. No message broker. No external scheduler. Just Postgres.
68
+
69
+ ---
70
+
71
+ ## Quick Start
72
+
73
+ ### 1. Install
74
+
75
+ ```bash
76
+ npm install pg-workflows pg-boss
77
+ # or
78
+ yarn add pg-workflows pg-boss
79
+ # or
80
+ bun add pg-workflows pg-boss
81
+ ```
82
+
83
+ ### 2. Define a Workflow
84
+
85
+ ```typescript
86
+ import { WorkflowEngine, workflow } from 'pg-workflows';
87
+ import PgBoss from 'pg-boss';
88
+ import { z } from 'zod';
89
+
90
+ // Define a durable workflow
91
+ const sendWelcomeEmail = workflow(
92
+ 'send-welcome-email',
93
+ async ({ step, input }) => {
94
+ // Step 1: Create user record (runs exactly once)
95
+ const user = await step.run('create-user', async () => {
96
+ return { id: '123', email: input.email };
97
+ });
98
+
99
+ // Step 2: Send email (runs exactly once)
100
+ await step.run('send-email', async () => {
101
+ await sendEmail(user.email, 'Welcome!');
102
+ });
103
+
104
+ // Step 3: Wait for user confirmation (pauses the workflow)
105
+ const confirmation = await step.waitFor('wait-confirmation', {
106
+ eventName: 'user-confirmed',
107
+ timeout: 24 * 60 * 60 * 1000, // 24 hours
108
+ });
109
+
110
+ return { success: true, user, confirmation };
111
+ },
112
+ {
113
+ inputSchema: z.object({
114
+ email: z.string().email(),
115
+ }),
116
+ timeout: 48 * 60 * 60 * 1000, // 48 hours
117
+ retries: 3,
118
+ }
119
+ );
120
+ ```
121
+
122
+ ### 3. Start the Engine
123
+
124
+ ```typescript
125
+ const boss = new PgBoss({
126
+ connectionString: process.env.DATABASE_URL,
127
+ });
128
+
129
+ const engine = new WorkflowEngine({
130
+ boss,
131
+ workflows: [sendWelcomeEmail],
132
+ });
133
+
134
+ await engine.start();
135
+ ```
136
+
137
+ ### 4. Run Workflows
138
+
139
+ ```typescript
140
+ // Start a workflow run
141
+ const run = await engine.startWorkflow({
142
+ workflowId: 'send-welcome-email',
143
+ resourceId: 'user-123',
144
+ input: { email: 'user@example.com' },
145
+ });
146
+
147
+ // Send an event to resume the waiting workflow
148
+ await engine.triggerEvent({
149
+ runId: run.id,
150
+ resourceId: 'user-123',
151
+ eventName: 'user-confirmed',
152
+ data: { confirmedAt: new Date() },
153
+ });
154
+
155
+ // Check progress
156
+ const progress = await engine.checkProgress({
157
+ runId: run.id,
158
+ resourceId: 'user-123',
159
+ });
160
+
161
+ console.log(`Progress: ${progress.completionPercentage}%`);
162
+ ```
163
+
164
+ ---
165
+
166
+ ## What Can You Build?
167
+
168
+ - **User Onboarding Flows** — Multi-step signup sequences with email verification, waiting for user actions, and conditional paths.
169
+ - **Payment & Checkout Pipelines** — Durable payment processing that survives failures, with automatic retries and event-driven confirmations.
170
+ - **AI & LLM Pipelines** — Chain LLM calls with built-in retries for flaky APIs. Persist intermediate results across steps.
171
+ - **Background Job Orchestration** — Replace fragile cron jobs with durable, observable workflows that track progress.
172
+ - **Approval Workflows** — Pause execution and wait for human approval events before proceeding.
173
+ - **Data Processing Pipelines** — ETL workflows with step-by-step execution, error handling, and progress monitoring.
174
+
175
+ ---
176
+
177
+ ## Core Concepts
178
+
179
+ ### Workflows
180
+
181
+ A workflow is a durable function that breaks complex operations into discrete, resumable steps. Define workflows using the `workflow()` function:
182
+
183
+ ```typescript
184
+ const myWorkflow = workflow(
185
+ 'workflow-id',
186
+ async ({ step, input }) => {
187
+ // Your workflow logic here
188
+ },
189
+ {
190
+ inputSchema: z.object({ /* ... */ }),
191
+ timeout: 60000, // milliseconds
192
+ retries: 3,
193
+ }
194
+ );
195
+ ```
196
+
197
+ ### Steps
198
+
199
+ Steps are the building blocks of durable workflows. Each step is executed **exactly once**, even if the workflow is retried:
200
+
201
+ ```typescript
202
+ await step.run('step-id', async () => {
203
+ // This will only execute once — the result is persisted in Postgres
204
+ return { result: 'data' };
205
+ });
206
+ ```
207
+
208
+ ### Event-Driven Workflows
209
+
210
+ Wait for external events to pause and resume workflows without consuming resources:
211
+
212
+ ```typescript
213
+ const eventData = await step.waitFor('wait-step', {
214
+ eventName: 'payment-completed',
215
+ timeout: 5 * 60 * 1000, // 5 minutes
216
+ });
217
+ ```
218
+
219
+ ### Pause and Resume
220
+
221
+ Manually pause a workflow and resume it later:
222
+
223
+ ```typescript
224
+ // Pause inside a workflow
225
+ await step.pause('pause-step');
226
+
227
+ // Resume from outside the workflow
228
+ await engine.resumeWorkflow({
229
+ runId: run.id,
230
+ resourceId: 'resource-123',
231
+ });
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Examples
237
+
238
+ ### Conditional Steps
239
+
240
+ ```typescript
241
+ const conditionalWorkflow = workflow('conditional', async ({ step }) => {
242
+ const data = await step.run('fetch-data', async () => {
243
+ return { isPremium: true };
244
+ });
245
+
246
+ if (data.isPremium) {
247
+ await step.run('premium-action', async () => {
248
+ // Only runs for premium users
249
+ });
250
+ }
251
+ });
252
+ ```
253
+
254
+ ### Batch Processing with Loops
255
+
256
+ ```typescript
257
+ const batchWorkflow = workflow('batch-process', async ({ step }) => {
258
+ const items = await step.run('get-items', async () => {
259
+ return [1, 2, 3, 4, 5];
260
+ });
261
+
262
+ for (const item of items) {
263
+ await step.run(`process-${item}`, async () => {
264
+ // Each item is processed durably
265
+ return processItem(item);
266
+ });
267
+ }
268
+ });
269
+ ```
270
+
271
+ ### Error Handling with Retries
272
+
273
+ ```typescript
274
+ const resilientWorkflow = workflow('resilient', async ({ step }) => {
275
+ await step.run('risky-operation', async () => {
276
+ // Retries up to 3 times with exponential backoff
277
+ return await riskyApiCall();
278
+ });
279
+ }, {
280
+ retries: 3,
281
+ timeout: 60000,
282
+ });
283
+ ```
284
+
285
+ ### Monitoring Workflow Progress
286
+
287
+ ```typescript
288
+ const progress = await engine.checkProgress({
289
+ runId: run.id,
290
+ resourceId: 'resource-123',
291
+ });
292
+
293
+ console.log({
294
+ status: progress.status,
295
+ completionPercentage: progress.completionPercentage,
296
+ completedSteps: progress.completedSteps,
297
+ totalSteps: progress.totalSteps,
298
+ });
299
+ ```
300
+
301
+ ---
302
+
303
+ ## API Reference
304
+
305
+ ### WorkflowEngine
306
+
307
+ #### Constructor
308
+
309
+ ```typescript
310
+ const engine = new WorkflowEngine({
311
+ boss: PgBoss, // Required: pg-boss instance
312
+ workflows: WorkflowDefinition[], // Optional: register workflows on init
313
+ logger: WorkflowLogger, // Optional: custom logger
314
+ });
315
+ ```
316
+
317
+ #### Methods
318
+
319
+ | Method | Description |
320
+ |--------|-------------|
321
+ | `start(asEngine?, options?)` | Start the engine and workers |
322
+ | `stop()` | Stop the engine gracefully |
323
+ | `registerWorkflow(definition)` | Register a workflow definition |
324
+ | `startWorkflow({ workflowId, resourceId?, input, options? })` | Start a new workflow run |
325
+ | `pauseWorkflow({ runId, resourceId? })` | Pause a running workflow |
326
+ | `resumeWorkflow({ runId, resourceId?, options? })` | Resume a paused workflow |
327
+ | `cancelWorkflow({ runId, resourceId? })` | Cancel a workflow |
328
+ | `triggerEvent({ runId, resourceId?, eventName, data?, options? })` | Send an event to a workflow |
329
+ | `getRun({ runId, resourceId? })` | Get workflow run details |
330
+ | `checkProgress({ runId, resourceId? })` | Get workflow progress |
331
+ | `getRuns(filters)` | List workflow runs with pagination |
332
+
333
+ ### workflow()
334
+
335
+ ```typescript
336
+ workflow<I extends Parameters>(
337
+ id: string,
338
+ handler: (context: WorkflowContext) => Promise<unknown>,
339
+ options?: {
340
+ inputSchema?: I,
341
+ timeout?: number,
342
+ retries?: number,
343
+ }
344
+ ): WorkflowDefinition<I>
345
+ ```
346
+
347
+ ### WorkflowContext
348
+
349
+ The context object passed to workflow handlers:
350
+
351
+ ```typescript
352
+ {
353
+ input: T, // Validated input data
354
+ workflowId: string, // Workflow ID
355
+ runId: string, // Unique run ID
356
+ timeline: Record<string, unknown>, // Step execution history
357
+ logger: WorkflowLogger, // Logger instance
358
+ step: {
359
+ run: <T>(stepId, handler) => Promise<T>,
360
+ waitFor: <T>(stepId, { eventName, timeout?, schema? }) => Promise<T>,
361
+ waitUntil: (stepId, { date }) => Promise<void>,
362
+ pause: (stepId) => Promise<void>,
363
+ }
364
+ }
365
+ ```
366
+
367
+ ### WorkflowStatus
368
+
369
+ ```typescript
370
+ enum WorkflowStatus {
371
+ PENDING = 'pending',
372
+ RUNNING = 'running',
373
+ PAUSED = 'paused',
374
+ COMPLETED = 'completed',
375
+ FAILED = 'failed',
376
+ CANCELLED = 'cancelled',
377
+ }
378
+ ```
379
+
380
+ ---
381
+
382
+ ## Configuration
383
+
384
+ ### Environment Variables
385
+
386
+ | Variable | Description | Default |
387
+ |----------|-------------|---------|
388
+ | `DATABASE_URL` | PostgreSQL connection string | *required* |
389
+ | `WORKFLOW_RUN_WORKERS` | Number of worker processes | `3` |
390
+ | `WORKFLOW_RUN_EXPIRE_IN_SECONDS` | Job expiration time in seconds | `300` |
391
+
392
+ ### Database Setup
393
+
394
+ The engine automatically runs migrations on startup to create the required tables:
395
+
396
+ - `workflow_runs` — Stores workflow execution state, step results, and timeline
397
+ - `pgboss.*` — pg-boss job queue tables for reliable task scheduling
398
+
399
+ ---
400
+
401
+ ## The PostgreSQL-for-Everything Philosophy
402
+
403
+ As championed by [postgresforeverything.com](https://postgresforeverything.com/), PostgreSQL is one of the most reliable, feature-rich, and cost-effective databases ever built. pg-workflows embraces this philosophy:
404
+
405
+ - **One database to rule them all** — Your application data and workflow state live in the same PostgreSQL instance. No distributed systems headaches.
406
+ - **Battle-tested reliability** — PostgreSQL's ACID transactions guarantee your workflow state is always consistent.
407
+ - **Zero operational overhead** — No Redis cluster to manage. No message broker to monitor. No external service to pay for.
408
+ - **Full queryability** — Inspect, debug, and analyze workflow runs with plain SQL.
409
+
410
+ If you're already running Postgres (and you probably should be), adding durable workflows is as simple as:
411
+
412
+ ```bash
413
+ npm install pg-workflows
414
+ ```
415
+
416
+ ---
417
+
418
+ ## Requirements
419
+
420
+ - Node.js >= 18.0.0
421
+ - PostgreSQL >= 10
422
+ - pg-boss >= 10.0.0
423
+
424
+ ## Acknowledgments
425
+
426
+ Special thanks to the teams behind [Temporal](https://temporal.io/), [Inngest](https://www.inngest.com/), [Trigger.dev](https://trigger.dev/), and [DBOS](https://www.dbos.dev/) for pioneering durable execution patterns and inspiring this project.
427
+
428
+ ## License
429
+
430
+ MIT