workflow 5.0.0-beta.35 → 5.0.0-beta.37
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/nest-builder.d.ts +2 -0
- package/dist/nest-builder.d.ts.map +1 -0
- package/dist/nest-builder.js +2 -0
- package/dist/nest-vercel-builder.d.ts +2 -0
- package/dist/nest-vercel-builder.d.ts.map +1 -0
- package/dist/nest-vercel-builder.js +2 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1 -1
- package/docs/ai/index.mdx +1 -1
- package/docs/api-reference/workflow/create-hook.mdx +43 -2
- package/docs/api-reference/workflow/define-hook.mdx +26 -24
- package/docs/api-reference/workflow/fatal-error.mdx +29 -7
- package/docs/api-reference/workflow/fetch.mdx +3 -4
- package/docs/api-reference/workflow/set-attributes.mdx +0 -4
- package/docs/api-reference/workflow/sleep.mdx +1 -1
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +2 -0
- package/docs/api-reference/workflow-api/resume-hook.mdx +2 -0
- package/docs/api-reference/workflow-api/resume-webhook.mdx +6 -4
- package/docs/api-reference/workflow-api/start.mdx +1 -1
- package/docs/api-reference/workflow-globals.mdx +4 -1
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +1 -1
- package/docs/api-reference/workflow-runtime/health-check.mdx +4 -4
- package/docs/api-reference/workflow-runtime/world/queue.mdx +4 -4
- package/docs/comparisons/index.mdx +66 -0
- package/docs/comparisons/meta.json +11 -0
- package/docs/comparisons/workflow-sdk-vs-aws-agentcore.mdx +55 -0
- package/docs/comparisons/workflow-sdk-vs-aws-step-functions.mdx +111 -0
- package/docs/comparisons/workflow-sdk-vs-cloudflare-workflows.mdx +71 -0
- package/docs/comparisons/workflow-sdk-vs-inngest.mdx +102 -0
- package/docs/comparisons/workflow-sdk-vs-temporal.mdx +123 -0
- package/docs/comparisons/workflow-sdk-vs-trigger-dev.mdx +103 -0
- package/docs/configuration/build-and-diagnostics.mdx +19 -0
- package/docs/configuration/runtime-tuning.mdx +30 -6
- package/docs/configuration/worlds.mdx +22 -8
- package/docs/cookbook/advanced/publishing-libraries.mdx +17 -13
- package/docs/cookbook/common-patterns/idempotency.mdx +1 -1
- package/docs/{deploying/index.mdx → deploying.mdx} +6 -8
- package/docs/foundations/hooks.mdx +1 -1
- package/docs/foundations/idempotency.mdx +16 -9
- package/docs/getting-started/astro.mdx +2 -2
- package/docs/getting-started/express.mdx +2 -2
- package/docs/getting-started/fastify.mdx +2 -2
- package/docs/getting-started/hono.mdx +2 -2
- package/docs/getting-started/index.mdx +3 -3
- package/docs/getting-started/meta.json +2 -1
- package/docs/getting-started/nestjs.mdx +64 -3
- package/docs/getting-started/next.mdx +3 -3
- package/docs/getting-started/nitro.mdx +2 -2
- package/docs/getting-started/nuxt.mdx +2 -2
- package/docs/getting-started/sveltekit.mdx +2 -2
- package/docs/getting-started/tanstack-start.mdx +1 -1
- package/docs/getting-started/vite.mdx +2 -2
- package/docs/how-it-works/cancellation.mdx +2 -2
- package/docs/how-it-works/code-transform.mdx +19 -15
- package/docs/how-it-works/encryption.mdx +3 -3
- package/docs/how-it-works/event-sourcing.mdx +6 -6
- package/docs/how-it-works/framework-integrations.mdx +96 -342
- package/docs/meta.json +1 -1
- package/package.json +14 -12
- package/docs/deploying/building-a-world.mdx +0 -251
- package/docs/deploying/meta.json +0 -4
- package/docs/deploying/world/local-world.mdx +0 -105
- package/docs/deploying/world/meta.json +0 -4
- package/docs/deploying/world/postgres-world.mdx +0 -288
- package/docs/deploying/world/vercel-world.mdx +0 -270
- package/docs/migration-guides/index.mdx +0 -34
- package/docs/migration-guides/meta.json +0 -9
- package/docs/migration-guides/migrating-from-aws-step-functions.mdx +0 -362
- package/docs/migration-guides/migrating-from-inngest.mdx +0 -308
- package/docs/migration-guides/migrating-from-temporal.mdx +0 -317
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -332
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Building a World
|
|
3
|
-
description: Implement the World interface to run workflows on any custom infrastructure.
|
|
4
|
-
type: guide
|
|
5
|
-
summary: Build a custom World adapter to run workflows on your own infrastructure.
|
|
6
|
-
prerequisites:
|
|
7
|
-
- /docs/deploying
|
|
8
|
-
- /docs/foundations/workflows-and-steps
|
|
9
|
-
related:
|
|
10
|
-
- /docs/deploying/world/local-world
|
|
11
|
-
- /docs/deploying/world/postgres-world
|
|
12
|
-
- /docs/deploying/world/vercel-world
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
A **World** is the abstraction that allows workflows to run on any infrastructure. It handles workflow storage, step execution queuing, and data streaming. This guide explains the World interface and how to implement your own.
|
|
16
|
-
|
|
17
|
-
<Callout>
|
|
18
|
-
Before building a custom World, check the [Worlds Ecosystem](/worlds) page — there may already be a community implementation for your infrastructure.
|
|
19
|
-
</Callout>
|
|
20
|
-
|
|
21
|
-
<Callout type="info">
|
|
22
|
-
**Reference Implementation:** The [Postgres World source code](https://github.com/vercel/workflow/tree/main/packages/world-postgres) is a production-ready example of how to implement the World interface with a database backend and graphile-worker for queuing.
|
|
23
|
-
</Callout>
|
|
24
|
-
|
|
25
|
-
## What is a World?
|
|
26
|
-
|
|
27
|
-
A World connects workflows to the infrastructure that powers them. The World interface abstracts three core responsibilities:
|
|
28
|
-
|
|
29
|
-
1. **Storage** — Persisting workflow runs, steps, hooks, and the event log
|
|
30
|
-
2. **Queue** — Enqueuing and processing workflow and step invocations
|
|
31
|
-
3. **Streamer** — Managing real-time data streams between workflows and clients
|
|
32
|
-
|
|
33
|
-
{/* @skip-typecheck - interface definition, not runnable code */}
|
|
34
|
-
```typescript
|
|
35
|
-
interface World extends Storage, Queue, Streamer {
|
|
36
|
-
start?(): Promise<void>;
|
|
37
|
-
close?(): Promise<void>;
|
|
38
|
-
getEncryptionKeyForRun?(run: WorkflowRun): Promise<Uint8Array | undefined>;
|
|
39
|
-
getEncryptionKeyForRun?(runId: string, context?: Record<string, unknown>): Promise<Uint8Array | undefined>;
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
The optional `start()` method initializes background tasks (for example, queue polling). The optional `close()` method releases resources like connection pools and listeners. The optional `getEncryptionKeyForRun()` method returns the AES-256 key used to encrypt data for a run; if it is not implemented, encryption is disabled.
|
|
44
|
-
|
|
45
|
-
## The Event Log Model
|
|
46
|
-
|
|
47
|
-
Workflow storage is built on an **append-only event log**. All state changes happen through events — you never modify runs, steps, or hooks directly. Instead, you create events that update the materialized state.
|
|
48
|
-
|
|
49
|
-
Events fall into three categories: run lifecycle events, step lifecycle events, and hook lifecycle events. See the [Event Sourcing](/docs/how-it-works/event-sourcing) documentation for a complete list of event types and their semantics.
|
|
50
|
-
|
|
51
|
-
## Storage Interface
|
|
52
|
-
|
|
53
|
-
The Storage interface provides read access to materialized entities and write access through events:
|
|
54
|
-
|
|
55
|
-
{/* @skip-typecheck - interface definition, not runnable code */}
|
|
56
|
-
```typescript
|
|
57
|
-
interface Storage {
|
|
58
|
-
runs: {
|
|
59
|
-
get(id: string, params?: GetWorkflowRunParams): Promise<WorkflowRun>;
|
|
60
|
-
list(params?: ListWorkflowRunsParams): Promise<PaginatedResponse<WorkflowRun>>;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
steps: {
|
|
64
|
-
get(runId: string | undefined, stepId: string, params?: GetStepParams): Promise<Step>;
|
|
65
|
-
list(params: ListWorkflowRunStepsParams): Promise<PaginatedResponse<Step>>;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
events: {
|
|
69
|
-
// Create a new workflow run (runId may be client-provided or null for server generation)
|
|
70
|
-
create(runId: string | null, data: RunCreatedEventRequest, params?: CreateEventParams): Promise<EventResult>;
|
|
71
|
-
|
|
72
|
-
// Create an event for an existing run
|
|
73
|
-
create(runId: string, data: CreateEventRequest, params?: CreateEventParams): Promise<EventResult>;
|
|
74
|
-
|
|
75
|
-
list(params: ListEventsParams): Promise<PaginatedResponse<Event>>;
|
|
76
|
-
listByCorrelationId(params: ListEventsByCorrelationIdParams): Promise<PaginatedResponse<Event>>;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
hooks: {
|
|
80
|
-
get(hookId: string, params?: GetHookParams): Promise<Hook>;
|
|
81
|
-
getByToken(token: string, params?: GetHookParams): Promise<Hook>;
|
|
82
|
-
list(params: ListHooksParams): Promise<PaginatedResponse<Hook>>;
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Key Implementation Details
|
|
88
|
-
|
|
89
|
-
**Event Creation:** When `events.create()` is called, your implementation must:
|
|
90
|
-
1. Persist the event to the event log
|
|
91
|
-
2. Atomically update the affected entity (run, step, or hook)
|
|
92
|
-
3. Return both the created event and the updated entity
|
|
93
|
-
|
|
94
|
-
**Run Creation:** For `run_created` events, the `runId` parameter may be a client-provided string or `null`. When `null`, your World generates and returns a new `runId`.
|
|
95
|
-
|
|
96
|
-
**Hook Tokens:** Hook tokens must be unique. If a `hook_created` event conflicts with an existing token, return a `hook_conflict` event instead and include the active hook owner's run ID as `eventData.conflictingRunId`.
|
|
97
|
-
|
|
98
|
-
**Automatic Hook Disposal:** When a workflow reaches a terminal state (`completed`, `failed`, or `cancelled`), automatically dispose of all associated hooks to release tokens for reuse.
|
|
99
|
-
|
|
100
|
-
## Queue Interface
|
|
101
|
-
|
|
102
|
-
The Queue interface handles asynchronous execution of workflows and steps:
|
|
103
|
-
|
|
104
|
-
{/* @skip-typecheck - interface definition, not runnable code */}
|
|
105
|
-
```typescript
|
|
106
|
-
interface Queue {
|
|
107
|
-
getDeploymentId(): Promise<string>;
|
|
108
|
-
|
|
109
|
-
queue(
|
|
110
|
-
queueName: ValidQueueName,
|
|
111
|
-
message: QueuePayload,
|
|
112
|
-
opts?: QueueOptions
|
|
113
|
-
): Promise<{ messageId: MessageId }>;
|
|
114
|
-
|
|
115
|
-
createQueueHandler(
|
|
116
|
-
queueNamePrefix: QueuePrefix,
|
|
117
|
-
handler: (message: unknown, meta: { attempt: number; queueName: ValidQueueName; messageId: MessageId }) => Promise<void | { timeoutSeconds: number }>
|
|
118
|
-
): (req: Request) => Promise<Response>;
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Queue Names
|
|
123
|
-
|
|
124
|
-
Queue names follow a specific pattern:
|
|
125
|
-
- `__wkf_workflow_<name>` — For workflow invocations
|
|
126
|
-
- `__wkf_step_<name>` — For step invocations
|
|
127
|
-
|
|
128
|
-
### Message Payloads
|
|
129
|
-
|
|
130
|
-
Two types of messages flow through queues:
|
|
131
|
-
|
|
132
|
-
**Workflow Invocations:**
|
|
133
|
-
{/* @skip-typecheck - interface definition, not runnable code */}
|
|
134
|
-
```typescript
|
|
135
|
-
interface WorkflowInvokePayload {
|
|
136
|
-
runId: string;
|
|
137
|
-
traceCarrier?: Record<string, string>; // OpenTelemetry context
|
|
138
|
-
requestedAt?: Date;
|
|
139
|
-
}
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**Step Invocations:**
|
|
143
|
-
{/* @skip-typecheck - interface definition, not runnable code */}
|
|
144
|
-
```typescript
|
|
145
|
-
interface StepInvokePayload {
|
|
146
|
-
workflowName: string;
|
|
147
|
-
workflowRunId: string;
|
|
148
|
-
workflowStartedAt: number;
|
|
149
|
-
stepId: string;
|
|
150
|
-
traceCarrier?: Record<string, string>;
|
|
151
|
-
requestedAt?: Date;
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### Implementation Considerations
|
|
156
|
-
|
|
157
|
-
- Messages must be delivered at-least-once
|
|
158
|
-
- Support configurable retry policies
|
|
159
|
-
- Track attempt counts for observability
|
|
160
|
-
- Implement idempotency using the `idempotencyKey` option when provided
|
|
161
|
-
|
|
162
|
-
## Streamer Interface
|
|
163
|
-
|
|
164
|
-
The Streamer interface enables real-time data streaming:
|
|
165
|
-
|
|
166
|
-
{/* @skip-typecheck - interface definition, not runnable code */}
|
|
167
|
-
```typescript
|
|
168
|
-
interface Streamer {
|
|
169
|
-
streamFlushIntervalMs?: number;
|
|
170
|
-
|
|
171
|
-
streams: {
|
|
172
|
-
write(
|
|
173
|
-
runId: string,
|
|
174
|
-
name: string,
|
|
175
|
-
chunk: string | Uint8Array
|
|
176
|
-
): Promise<void>;
|
|
177
|
-
|
|
178
|
-
writeMulti?(
|
|
179
|
-
runId: string,
|
|
180
|
-
name: string,
|
|
181
|
-
chunks: (string | Uint8Array)[]
|
|
182
|
-
): Promise<void>;
|
|
183
|
-
|
|
184
|
-
close(runId: string, name: string): Promise<void>;
|
|
185
|
-
|
|
186
|
-
get(
|
|
187
|
-
runId: string,
|
|
188
|
-
name: string,
|
|
189
|
-
startIndex?: number
|
|
190
|
-
): Promise<ReadableStream<Uint8Array>>;
|
|
191
|
-
|
|
192
|
-
list(runId: string): Promise<string[]>;
|
|
193
|
-
|
|
194
|
-
/** Paginated snapshot of stream chunks. */
|
|
195
|
-
getChunks(
|
|
196
|
-
runId: string,
|
|
197
|
-
name: string,
|
|
198
|
-
options?: { limit?: number; cursor?: string }
|
|
199
|
-
): Promise<{
|
|
200
|
-
data: { index: number; data: Uint8Array }[];
|
|
201
|
-
cursor: string | null;
|
|
202
|
-
hasMore: boolean;
|
|
203
|
-
done: boolean;
|
|
204
|
-
}>;
|
|
205
|
-
|
|
206
|
-
/** Lightweight metadata: tail index and completion flag. */
|
|
207
|
-
getInfo(
|
|
208
|
-
runId: string,
|
|
209
|
-
name: string
|
|
210
|
-
): Promise<{ tailIndex: number; done: boolean }>;
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Streams are identified by a combination of `runId` and `name`. Each workflow run can have multiple named streams.
|
|
216
|
-
`writeMulti()` is an optional optimization for batching multiple writes.
|
|
217
|
-
|
|
218
|
-
`getChunks` returns a paginated snapshot of currently available chunks (unlike `get` which returns a live `ReadableStream` that waits for new chunks). `getInfo` returns the tail index (last chunk index, 0-based, or `-1` when empty) and whether the stream is complete — useful for resolving negative `startIndex` values into absolute positions.
|
|
219
|
-
|
|
220
|
-
## Reference Implementations
|
|
221
|
-
|
|
222
|
-
Study these implementations for guidance:
|
|
223
|
-
|
|
224
|
-
- **[Local World](https://github.com/vercel/workflow/tree/main/packages/world-local)** — Filesystem-based, great for understanding the basics
|
|
225
|
-
- **[Postgres World](https://github.com/vercel/workflow/tree/main/packages/world-postgres)** — Database-backed with graphile-worker for queuing
|
|
226
|
-
|
|
227
|
-
## Testing Your World
|
|
228
|
-
|
|
229
|
-
Workflow SDK includes an E2E test suite that validates World implementations. Once your World is published to npm:
|
|
230
|
-
|
|
231
|
-
1. Add your world to [`worlds-manifest.json`](https://github.com/vercel/workflow/blob/main/worlds-manifest.json)
|
|
232
|
-
2. Open a PR to the Workflow repository
|
|
233
|
-
3. CI will automatically run the E2E test suite against your implementation
|
|
234
|
-
|
|
235
|
-
Your world will then appear on the [Worlds Ecosystem](/worlds) page with its compatibility status and performance benchmarks.
|
|
236
|
-
|
|
237
|
-
## Publishing Your World
|
|
238
|
-
|
|
239
|
-
1. **Package your World** — Export a default World instance from your package
|
|
240
|
-
2. **Publish to npm** — Publish your package to npm
|
|
241
|
-
3. **Add to the manifest** — Submit a PR adding your world to [`worlds-manifest.json`](https://github.com/vercel/workflow/blob/main/worlds-manifest.json)
|
|
242
|
-
4. **Document configuration** — Clearly document any required environment variables
|
|
243
|
-
|
|
244
|
-
```json
|
|
245
|
-
// worlds-manifest.json entry
|
|
246
|
-
{
|
|
247
|
-
"package": "your-world-package",
|
|
248
|
-
"repository": "https://github.com/you/your-world",
|
|
249
|
-
"docs": "https://github.com/you/your-world#readme"
|
|
250
|
-
}
|
|
251
|
-
```
|
package/docs/deploying/meta.json
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Local World
|
|
3
|
-
description: Zero-config world bundled with Workflow for local development. No external services required.
|
|
4
|
-
type: integration
|
|
5
|
-
summary: Set up the Local World for zero-config workflow development on your machine.
|
|
6
|
-
prerequisites:
|
|
7
|
-
- /docs/deploying
|
|
8
|
-
related:
|
|
9
|
-
- /docs/deploying/world/postgres-world
|
|
10
|
-
- /docs/deploying/world/vercel-world
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
The Local World is bundled with `workflow` and used automatically during local development. No installation or configuration required.
|
|
14
|
-
|
|
15
|
-
To explicitly use the local world in any environment, set the environment variable:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
WORKFLOW_TARGET_WORLD=local
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Observability
|
|
22
|
-
|
|
23
|
-
The `workflow` CLI uses the local world by default. Running these commands inside your workflow project will show your local development workflows:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
# List recent workflow runs
|
|
27
|
-
npx workflow inspect runs
|
|
28
|
-
|
|
29
|
-
# Launch the web UI
|
|
30
|
-
npx workflow web
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Learn more in the [Observability](/docs/observability) documentation.
|
|
34
|
-
|
|
35
|
-
## Testing & Compatibility
|
|
36
|
-
|
|
37
|
-
<WorldTestingPerformance worldId="local" />
|
|
38
|
-
|
|
39
|
-
## Configuration
|
|
40
|
-
|
|
41
|
-
The local world works with zero configuration, but you can customize behavior through environment variables or programmatically via `createWorld()`.
|
|
42
|
-
|
|
43
|
-
### `WORKFLOW_LOCAL_DATA_DIR`
|
|
44
|
-
|
|
45
|
-
Directory for storing workflow data as JSON files. Default: `.workflow-data/`
|
|
46
|
-
|
|
47
|
-
### `PORT`
|
|
48
|
-
|
|
49
|
-
The application dev server port. Used to enqueue steps and workflows. Default: auto-detected
|
|
50
|
-
|
|
51
|
-
### `WORKFLOW_LOCAL_BASE_URL`
|
|
52
|
-
|
|
53
|
-
Full base URL override for HTTPS or custom hostnames. Default: `http://localhost:{port}`
|
|
54
|
-
|
|
55
|
-
Port resolution priority: `baseUrl` > `port` > `PORT` > auto-detected
|
|
56
|
-
|
|
57
|
-
### `WORKFLOW_LOCAL_QUEUE_CONCURRENCY`
|
|
58
|
-
|
|
59
|
-
Maximum number of concurrent queue message handlers. Default: `1000`
|
|
60
|
-
|
|
61
|
-
### `WORKFLOW_LOCAL_QUEUE_MAX_VISIBILITY`
|
|
62
|
-
|
|
63
|
-
Maximum number of seconds a local queue message can stay hidden before the handler rechecks the run. Default: unlimited.
|
|
64
|
-
|
|
65
|
-
### `WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS`
|
|
66
|
-
|
|
67
|
-
Whether pending and running runs found in the data directory are re-enqueued when the world starts. Set to `0` or `false` to skip recovery and leave stale runs untouched. Default: `true`
|
|
68
|
-
|
|
69
|
-
### `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`
|
|
70
|
-
|
|
71
|
-
Flush interval, in milliseconds, for buffered stream writes. Default: `10`.
|
|
72
|
-
|
|
73
|
-
### Programmatic configuration
|
|
74
|
-
|
|
75
|
-
Options passed to `createWorld()` take precedence over the environment variables above. Export the configured World from a module and point `WORKFLOW_TARGET_WORLD` at that module:
|
|
76
|
-
|
|
77
|
-
```typescript title="my-world.ts" lineNumbers
|
|
78
|
-
import { createWorld } from "@workflow/world-local";
|
|
79
|
-
|
|
80
|
-
export default createWorld({
|
|
81
|
-
dataDir: "./custom-workflow-data",
|
|
82
|
-
port: 5173,
|
|
83
|
-
// baseUrl overrides port if set
|
|
84
|
-
baseUrl: "https://local.example.com:3000",
|
|
85
|
-
recoverActiveRuns: true, // overrides WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS
|
|
86
|
-
streamFlushIntervalMs: 10, // overrides WORKFLOW_STREAM_FLUSH_INTERVAL_MS
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
```bash title=".env"
|
|
91
|
-
WORKFLOW_TARGET_WORLD="./my-world.ts"
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
`createWorld()` also accepts `tag`, which scopes local storage files to a suffix. It is mainly used by test harnesses that share one `.workflow-data` directory.
|
|
95
|
-
|
|
96
|
-
## Limitations
|
|
97
|
-
|
|
98
|
-
The local world is designed for development, not production:
|
|
99
|
-
|
|
100
|
-
- **In-memory queue** - Steps are queued in memory and do not persist across server restarts
|
|
101
|
-
- **Filesystem storage** - Data is stored in local JSON files
|
|
102
|
-
- **Single instance** - Cannot handle distributed deployments
|
|
103
|
-
- **No authentication** - Suitable only for local development
|
|
104
|
-
|
|
105
|
-
For production deployments, use the [Vercel World](/worlds/vercel) or [Postgres World](/worlds/postgres).
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Postgres World
|
|
3
|
-
description: Production-ready, self-hosted world using PostgreSQL for storage and graphile-worker for job processing.
|
|
4
|
-
type: integration
|
|
5
|
-
summary: Deploy workflows to your own infrastructure using PostgreSQL and graphile-worker.
|
|
6
|
-
prerequisites:
|
|
7
|
-
- /docs/deploying
|
|
8
|
-
related:
|
|
9
|
-
- /docs/deploying/world/local-world
|
|
10
|
-
- /docs/deploying/world/vercel-world
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
The Postgres World is a production-ready backend for self-hosted deployments. It uses PostgreSQL for durable storage and [graphile-worker](https://github.com/graphile/worker) for reliable job processing.
|
|
14
|
-
|
|
15
|
-
Use the Postgres World when you need to deploy workflows on your own infrastructure outside of Vercel - such as a Docker container, Kubernetes cluster, or any cloud that supports long-running servers.
|
|
16
|
-
|
|
17
|
-
## Installation
|
|
18
|
-
|
|
19
|
-
Install the Postgres World package in your workflow project:
|
|
20
|
-
|
|
21
|
-
```package-install
|
|
22
|
-
@workflow/world-postgres
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
<Callout type="info">
|
|
26
|
-
Use the same release channel for `workflow` and `@workflow/world-postgres`. If
|
|
27
|
-
your app uses a beta or other prerelease Workflow version, install the matching
|
|
28
|
-
prerelease Postgres World package, such as
|
|
29
|
-
`npm install @workflow/world-postgres@beta`. Mismatched versions fail before
|
|
30
|
-
starting a run with an error that says the runtime requires a World with a
|
|
31
|
-
matching spec version.
|
|
32
|
-
</Callout>
|
|
33
|
-
|
|
34
|
-
Configure the required environment variables to use the world and point it to your PostgreSQL database:
|
|
35
|
-
|
|
36
|
-
```bash title=".env"
|
|
37
|
-
WORKFLOW_TARGET_WORLD="@workflow/world-postgres"
|
|
38
|
-
WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Run the migration script to create the necessary tables in your database. Ensure `WORKFLOW_POSTGRES_URL` or `DATABASE_URL` is set when running this command:
|
|
42
|
-
|
|
43
|
-
<Tabs items={["npm", "pnpm", "Yarn", "Bun"]}>
|
|
44
|
-
|
|
45
|
-
<Tab value="npm">
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npx --package=@workflow/world-postgres bootstrap
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
</Tab>
|
|
52
|
-
|
|
53
|
-
<Tab value="pnpm">
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
pnpm dlx --package @workflow/world-postgres bootstrap
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
</Tab>
|
|
60
|
-
|
|
61
|
-
<Tab value="Yarn">
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
yarn dlx --package @workflow/world-postgres bootstrap
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
</Tab>
|
|
68
|
-
|
|
69
|
-
<Tab value="Bun">
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
bunx --package @workflow/world-postgres bootstrap
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
</Tab>
|
|
76
|
-
|
|
77
|
-
</Tabs>
|
|
78
|
-
|
|
79
|
-
<Callout type="info">
|
|
80
|
-
The migration is idempotent and can safely be run as a post-deployment lifecycle script.
|
|
81
|
-
</Callout>
|
|
82
|
-
|
|
83
|
-
## Starting the World
|
|
84
|
-
|
|
85
|
-
To subscribe to the graphile-worker queue, your workflow app needs to start the world on server start. Here are examples for a few frameworks:
|
|
86
|
-
|
|
87
|
-
<Tabs items={["Next.js", "SvelteKit", "Nitro"]}>
|
|
88
|
-
|
|
89
|
-
<Tab value="Next.js">
|
|
90
|
-
|
|
91
|
-
Create an `instrumentation.ts` file in your project root:
|
|
92
|
-
|
|
93
|
-
```ts title="instrumentation.ts" lineNumbers
|
|
94
|
-
export async function register() {
|
|
95
|
-
if (process.env.NEXT_RUNTIME !== "edge") {
|
|
96
|
-
const { getWorld } = await import("workflow/runtime");
|
|
97
|
-
const world = await getWorld();
|
|
98
|
-
await world.start?.();
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
<Callout type="info">
|
|
104
|
-
Learn more about [Next.js Instrumentation](https://nextjs.org/docs/app/guides/instrumentation).
|
|
105
|
-
</Callout>
|
|
106
|
-
|
|
107
|
-
</Tab>
|
|
108
|
-
|
|
109
|
-
<Tab value="SvelteKit">
|
|
110
|
-
|
|
111
|
-
Create a `src/hooks.server.ts` file:
|
|
112
|
-
|
|
113
|
-
```ts title="src/hooks.server.ts" lineNumbers
|
|
114
|
-
import type { ServerInit } from "@sveltejs/kit";
|
|
115
|
-
|
|
116
|
-
export const init: ServerInit = async () => {
|
|
117
|
-
const { getWorld } = await import("workflow/runtime");
|
|
118
|
-
const world = await getWorld();
|
|
119
|
-
await world.start?.();
|
|
120
|
-
};
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
<Callout type="info">
|
|
124
|
-
Learn more about [SvelteKit Hooks](https://svelte.dev/docs/kit/hooks).
|
|
125
|
-
</Callout>
|
|
126
|
-
|
|
127
|
-
</Tab>
|
|
128
|
-
|
|
129
|
-
<Tab value="Nitro">
|
|
130
|
-
|
|
131
|
-
Create a plugin to start the world on server initialization:
|
|
132
|
-
|
|
133
|
-
```ts title="plugins/start-pg-world.ts" lineNumbers
|
|
134
|
-
import { defineNitroPlugin } from "nitro/~internal/runtime/plugin";
|
|
135
|
-
|
|
136
|
-
export default defineNitroPlugin(async () => {
|
|
137
|
-
const { getWorld } = await import("workflow/runtime");
|
|
138
|
-
const world = await getWorld();
|
|
139
|
-
await world.start?.();
|
|
140
|
-
});
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Register the plugin in your config:
|
|
144
|
-
|
|
145
|
-
```ts title="nitro.config.ts"
|
|
146
|
-
import { defineNitroConfig } from "nitropack";
|
|
147
|
-
|
|
148
|
-
export default defineNitroConfig({
|
|
149
|
-
modules: ["workflow/nitro"],
|
|
150
|
-
plugins: ["plugins/start-pg-world.ts"],
|
|
151
|
-
});
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
<Callout type="info">
|
|
155
|
-
Learn more about [Nitro Plugins](https://v3.nitro.build/docs/plugins).
|
|
156
|
-
</Callout>
|
|
157
|
-
|
|
158
|
-
</Tab>
|
|
159
|
-
|
|
160
|
-
</Tabs>
|
|
161
|
-
|
|
162
|
-
<Callout type="info">
|
|
163
|
-
The Postgres World requires a long-lived worker process that polls the database for jobs. This does not work on serverless environments. For Vercel deployments, use the [Vercel World](/worlds/vercel) instead.
|
|
164
|
-
</Callout>
|
|
165
|
-
|
|
166
|
-
## Observability
|
|
167
|
-
|
|
168
|
-
Use the `workflow` CLI to inspect workflows stored in PostgreSQL:
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
# Set your database URL
|
|
172
|
-
export WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
|
|
173
|
-
|
|
174
|
-
# List workflow runs
|
|
175
|
-
npx workflow inspect runs --backend @workflow/world-postgres
|
|
176
|
-
|
|
177
|
-
# Launch the web UI
|
|
178
|
-
npx workflow web --backend @workflow/world-postgres
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
If `WORKFLOW_POSTGRES_URL` is not set, the CLI defaults to `postgres://world:world@localhost:5432/world`.
|
|
182
|
-
|
|
183
|
-
Learn more in the [Observability](/docs/observability) documentation.
|
|
184
|
-
|
|
185
|
-
## Testing & Compatibility
|
|
186
|
-
|
|
187
|
-
<WorldTestingPerformance worldId="postgres" />
|
|
188
|
-
|
|
189
|
-
## Configuration
|
|
190
|
-
|
|
191
|
-
All configuration options can be set via environment variables or programmatically via `createWorld()`.
|
|
192
|
-
|
|
193
|
-
### `WORKFLOW_POSTGRES_URL`
|
|
194
|
-
|
|
195
|
-
PostgreSQL connection string used by the runtime World.
|
|
196
|
-
|
|
197
|
-
Precedence: `WORKFLOW_POSTGRES_URL` > `DATABASE_URL` > `postgres://world:world@localhost:5432/world`
|
|
198
|
-
|
|
199
|
-
The `bootstrap` migration command uses the same precedence.
|
|
200
|
-
|
|
201
|
-
### `WORKFLOW_POSTGRES_JOB_PREFIX`
|
|
202
|
-
|
|
203
|
-
Prefix for graphile-worker queue job names. Useful when sharing a database between multiple applications. Default: `workflow_`
|
|
204
|
-
|
|
205
|
-
### `WORKFLOW_POSTGRES_WORKER_CONCURRENCY`
|
|
206
|
-
|
|
207
|
-
Number of concurrent workers polling for jobs. Default: `50`.
|
|
208
|
-
|
|
209
|
-
This value also bounds how many parent→child workflow polls can be in flight simultaneously. Every `await childRun.returnValue` inside a workflow holds a worker slot until the child run terminates — if you expect recursive or highly-fanned-out parent/child workflows, raise this ceiling above the peak number of concurrent polls. With the default of 50, the included `fibonacciWorkflow` e2e test (fib(6), ~24 concurrent polls at peak) passes; deeper recursion or larger fanouts need a correspondingly larger setting.
|
|
210
|
-
|
|
211
|
-
### `WORKFLOW_POSTGRES_MAX_POOL_SIZE`
|
|
212
|
-
|
|
213
|
-
Maximum size of the internal `pg.Pool` used when `createWorld()` constructs the pool. Default: the `pg` default (`10`).
|
|
214
|
-
|
|
215
|
-
For higher worker concurrency, Graphile Worker recommends setting `maxPoolSize` to `10` or `queueConcurrency + 2`, whichever is larger.
|
|
216
|
-
|
|
217
|
-
### `WORKFLOW_QUEUE_NAMESPACE`
|
|
218
|
-
|
|
219
|
-
Queue topic namespace shared by build output and the Postgres World. Default: unset.
|
|
220
|
-
|
|
221
|
-
For example, `custom` changes the queue topic prefixes from `__wkf_workflow_` and `__wkf_step_` to `__custom_wkf_workflow_` and `__custom_wkf_step_`. The value must be lowercase alphanumeric and start with a letter.
|
|
222
|
-
|
|
223
|
-
### `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`
|
|
224
|
-
|
|
225
|
-
Flush interval, in milliseconds, for buffered stream writes. Default: `10`.
|
|
226
|
-
|
|
227
|
-
### Programmatic configuration
|
|
228
|
-
|
|
229
|
-
{/*@skip-typecheck: incomplete code sample*/}
|
|
230
|
-
|
|
231
|
-
```typescript title="my-world.ts" lineNumbers
|
|
232
|
-
import { createWorld } from "@workflow/world-postgres";
|
|
233
|
-
|
|
234
|
-
export default createWorld({
|
|
235
|
-
connectionString:
|
|
236
|
-
process.env.WORKFLOW_POSTGRES_URL ?? process.env.DATABASE_URL!,
|
|
237
|
-
jobPrefix: "myapp_",
|
|
238
|
-
namespace: "myapp",
|
|
239
|
-
queueConcurrency: 50,
|
|
240
|
-
maxPoolSize: 52, // overrides WORKFLOW_POSTGRES_MAX_POOL_SIZE
|
|
241
|
-
streamFlushIntervalMs: 10,
|
|
242
|
-
});
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Options passed to `createWorld()` take precedence over the environment variables above. Export the World from a module and point `WORKFLOW_TARGET_WORLD` at that module:
|
|
246
|
-
|
|
247
|
-
You can also pass an existing `pg.Pool` as `pool` instead of a connection string.
|
|
248
|
-
|
|
249
|
-
```bash title=".env"
|
|
250
|
-
WORKFLOW_TARGET_WORLD="./my-world.ts"
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## How It Works
|
|
254
|
-
|
|
255
|
-
The Postgres World uses PostgreSQL as a durable backend:
|
|
256
|
-
|
|
257
|
-
- **Storage** - Workflow runs, events, steps, and hooks are stored in PostgreSQL tables
|
|
258
|
-
- **Job Queue** - [graphile-worker](https://github.com/graphile/worker) handles reliable job processing with retries
|
|
259
|
-
- **Streaming** - PostgreSQL NOTIFY/LISTEN enables real-time event distribution
|
|
260
|
-
|
|
261
|
-
This architecture ensures workflows survive application restarts with all state reliably persisted. For implementation details, see the [source code](https://github.com/vercel/workflow/tree/main/packages/world-postgres).
|
|
262
|
-
|
|
263
|
-
## Deployment
|
|
264
|
-
|
|
265
|
-
Deploy your application to any cloud that supports long-running servers:
|
|
266
|
-
|
|
267
|
-
- Docker containers
|
|
268
|
-
- Kubernetes clusters
|
|
269
|
-
- Virtual machines
|
|
270
|
-
- Platform-as-a-Service providers (Railway, Render, Fly.io, etc.)
|
|
271
|
-
|
|
272
|
-
Ensure your deployment has:
|
|
273
|
-
|
|
274
|
-
1. Network access to your PostgreSQL database
|
|
275
|
-
2. Environment variables configured correctly
|
|
276
|
-
3. The `start()` function called on server initialization
|
|
277
|
-
|
|
278
|
-
<Callout type="info">
|
|
279
|
-
The Postgres World is not compatible with Vercel deployments. On Vercel, workflows automatically use the [Vercel World](/worlds/vercel) with zero configuration.
|
|
280
|
-
</Callout>
|
|
281
|
-
|
|
282
|
-
## Limitations
|
|
283
|
-
|
|
284
|
-
- **Requires long-running process** - Must call `start()` on server initialization; not compatible with serverless platforms
|
|
285
|
-
- **PostgreSQL infrastructure** - Requires a PostgreSQL database (self-hosted or managed)
|
|
286
|
-
- **Not compatible with Vercel** - Use the [Vercel World](/worlds/vercel) for Vercel deployments
|
|
287
|
-
|
|
288
|
-
For local development, use the [Local World](/worlds/local) which requires no external services.
|