workflow 5.0.0-beta.9 → 5.0.1
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 +68 -23
- package/dist/api-workflow.d.ts +3 -1
- package/dist/api-workflow.d.ts.map +1 -1
- package/dist/api-workflow.js +2 -1
- package/dist/api.d.ts +5 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/internal/builtins.d.ts +4 -4
- package/dist/internal/builtins.js +6 -6
- package/dist/internal/errors.d.ts +1 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +2 -2
- 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 +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +4 -1
- package/docs/advanced/dynamic-workflows.mdx +227 -0
- package/docs/advanced/index.mdx +13 -0
- package/docs/advanced/meta.json +5 -0
- package/docs/ai/chat-session-modeling.mdx +176 -422
- package/docs/ai/defining-tools.mdx +6 -7
- package/docs/ai/human-in-the-loop.mdx +16 -12
- package/docs/ai/index.mdx +67 -72
- package/docs/ai/message-queueing.mdx +71 -110
- package/docs/ai/meta.json +1 -0
- package/docs/ai/resumable-streams.mdx +40 -28
- package/docs/ai/sleep-and-delays.mdx +10 -10
- package/docs/ai/streaming-updates-from-tools.mdx +6 -6
- package/docs/api-reference/index.mdx +25 -1
- package/docs/api-reference/meta.json +8 -0
- package/docs/api-reference/vitest/index.mdx +68 -15
- package/docs/api-reference/workflow/create-hook.mdx +170 -10
- package/docs/api-reference/workflow/create-webhook.mdx +16 -15
- package/docs/api-reference/workflow/define-hook.mdx +41 -33
- package/docs/api-reference/workflow/fatal-error.mdx +30 -8
- package/docs/api-reference/workflow/fetch.mdx +14 -10
- package/docs/api-reference/workflow/get-step-metadata.mdx +2 -2
- package/docs/api-reference/workflow/get-workflow-metadata.mdx +3 -3
- package/docs/api-reference/workflow/get-writable.mdx +7 -7
- package/docs/api-reference/workflow/index.mdx +3 -3
- package/docs/api-reference/workflow/retryable-error.mdx +1 -1
- package/docs/api-reference/workflow/set-attributes.mdx +63 -0
- package/docs/api-reference/workflow/sleep.mdx +4 -4
- package/docs/api-reference/workflow-ai/durable-agent.mdx +63 -101
- package/docs/api-reference/workflow-ai/index.mdx +5 -5
- package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +67 -24
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +37 -15
- package/docs/api-reference/workflow-api/get-run.mdx +43 -8
- package/docs/api-reference/workflow-api/index.mdx +8 -9
- package/docs/api-reference/workflow-api/register-lifecycle-hooks.mdx +87 -0
- package/docs/api-reference/workflow-api/resume-hook.mdx +77 -12
- package/docs/api-reference/workflow-api/resume-webhook.mdx +15 -9
- package/docs/api-reference/workflow-api/start.mdx +107 -12
- package/docs/api-reference/workflow-astro/index.mdx +18 -0
- package/docs/api-reference/workflow-astro/meta.json +4 -0
- package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
- package/docs/api-reference/workflow-errors/entity-conflict-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
- package/docs/api-reference/workflow-errors/hook-force-claimed-error.mdx +70 -0
- package/docs/api-reference/workflow-errors/hook-not-found-error.mdx +8 -8
- package/docs/api-reference/workflow-errors/index.mdx +91 -0
- package/docs/api-reference/workflow-errors/meta.json +7 -0
- package/docs/api-reference/workflow-errors/precondition-failed-error.mdx +68 -0
- package/docs/api-reference/workflow-errors/run-expired-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/step-not-registered-error.mdx +5 -5
- package/docs/api-reference/workflow-errors/throttle-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/too-early-error.mdx +2 -2
- package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
- package/docs/api-reference/workflow-errors/workflow-not-registered-error.mdx +5 -6
- package/docs/api-reference/workflow-errors/workflow-run-cancelled-error.mdx +13 -6
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +12 -5
- package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-run-not-found-error.mdx +4 -4
- package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-world-error.mdx +8 -8
- package/docs/api-reference/workflow-globals.mdx +19 -11
- package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +37 -0
- package/docs/api-reference/workflow-nest/index.mdx +31 -0
- package/docs/api-reference/workflow-nest/meta.json +9 -0
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
- package/docs/api-reference/workflow-nest/workflow-controller.mdx +46 -0
- package/docs/api-reference/workflow-nest/workflow-module.mdx +134 -0
- package/docs/api-reference/workflow-next/with-workflow.mdx +39 -17
- package/docs/api-reference/workflow-nitro/index.mdx +60 -0
- package/docs/api-reference/workflow-nuxt/index.mdx +48 -0
- package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
- package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
- package/docs/api-reference/workflow-observability/index.mdx +62 -0
- package/docs/api-reference/workflow-observability/meta.json +11 -0
- package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
- package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
- package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
- package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
- package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
- package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +11 -14
- package/docs/api-reference/workflow-runtime/health-check.mdx +51 -0
- package/docs/api-reference/workflow-runtime/index.mdx +41 -0
- package/docs/api-reference/workflow-runtime/meta.json +12 -0
- package/docs/api-reference/workflow-runtime/set-world.mdx +51 -0
- package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +43 -0
- package/docs/api-reference/workflow-runtime/world/analytics.mdx +315 -0
- package/docs/api-reference/workflow-runtime/world/index.mdx +60 -0
- package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
- package/docs/api-reference/workflow-runtime/world/queue.mdx +88 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +133 -35
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +8 -8
- package/docs/api-reference/workflow-serde/index.mdx +1 -2
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +3 -4
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +8 -8
- package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
- package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
- package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
- package/docs/api-reference/workflow-vite/index.mdx +18 -0
- package/docs/api-reference/workflow-vite/meta.json +4 -0
- package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
- package/docs/changelog/attributes-mvp.mdx +53 -41
- package/docs/changelog/batched-event-writes.mdx +79 -0
- package/docs/changelog/eager-processing.mdx +110 -436
- package/docs/changelog/index.mdx +4 -2
- package/docs/changelog/lazy-event-creation.md +127 -0
- package/docs/changelog/lazy-hook-resume.mdx +78 -0
- package/docs/changelog/meta.json +11 -1
- package/docs/changelog/resilient-resume.mdx +32 -0
- package/docs/changelog/resilient-start.mdx +33 -285
- package/docs/changelog/step-message-ownership.mdx +360 -0
- package/docs/changelog/turbo-mode.md +87 -0
- 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 +104 -0
- package/docs/configuration/build-and-diagnostics.mdx +89 -0
- package/docs/configuration/cli-and-web-ui.mdx +241 -0
- package/docs/configuration/framework-options.mdx +165 -0
- package/docs/configuration/index.mdx +32 -0
- package/docs/configuration/meta.json +12 -0
- package/docs/configuration/runtime-tuning.mdx +424 -0
- package/docs/configuration/worlds.mdx +341 -0
- package/docs/cookbook/advanced/child-workflows.mdx +33 -25
- package/docs/cookbook/advanced/publishing-libraries.mdx +65 -56
- package/docs/cookbook/advanced/serializable-steps.mdx +48 -68
- package/docs/cookbook/advanced/upgrading-workflows.mdx +35 -31
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +78 -60
- package/docs/cookbook/agent-patterns/durable-agent.mdx +23 -135
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +180 -195
- package/docs/cookbook/common-patterns/batching.mdx +20 -14
- package/docs/cookbook/common-patterns/idempotency.mdx +41 -53
- package/docs/cookbook/common-patterns/rate-limiting.mdx +8 -4
- package/docs/cookbook/common-patterns/saga.mdx +23 -19
- package/docs/cookbook/common-patterns/scheduling.mdx +30 -22
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +29 -25
- package/docs/cookbook/common-patterns/timeouts.mdx +26 -21
- package/docs/cookbook/common-patterns/webhooks.mdx +12 -7
- package/docs/cookbook/common-patterns/workflow-composition.mdx +27 -17
- package/docs/cookbook/index.mdx +22 -22
- package/docs/cookbook/integrations/ai-sdk.mdx +63 -48
- package/docs/cookbook/integrations/chat-sdk.mdx +46 -33
- package/docs/cookbook/integrations/sandbox.mdx +58 -45
- package/docs/deploying.mdx +106 -0
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +16 -12
- package/docs/errors/corrupted-event-log.mdx +39 -18
- package/docs/errors/deployment-mismatch.mdx +71 -0
- package/docs/errors/fetch-in-workflow.mdx +15 -14
- package/docs/errors/hook-conflict.mdx +38 -11
- package/docs/errors/hook-force-claimed.mdx +96 -0
- package/docs/errors/index.mdx +24 -37
- package/docs/errors/node-js-module-in-workflow.mdx +9 -5
- package/docs/errors/replay-divergence.mdx +27 -0
- package/docs/errors/run-expired.mdx +85 -0
- package/docs/errors/runtime-decryption-failed.mdx +77 -0
- package/docs/errors/serialization-failed.mdx +44 -12
- package/docs/errors/start-invalid-workflow-function.mdx +9 -5
- package/docs/errors/step-executed-multiple-times.mdx +23 -0
- package/docs/errors/step-not-registered.mdx +6 -6
- package/docs/errors/timeout-in-workflow.mdx +12 -8
- package/docs/errors/webhook-invalid-respond-with-value.mdx +18 -18
- package/docs/errors/webhook-response-not-sent.mdx +20 -16
- package/docs/errors/workflow-not-registered.mdx +5 -5
- package/docs/foundations/cancellation.mdx +31 -32
- package/docs/foundations/errors-and-retries.mdx +54 -11
- package/docs/foundations/hooks.mdx +187 -36
- package/docs/foundations/idempotency.mdx +267 -12
- package/docs/foundations/index.mdx +1 -26
- package/docs/foundations/serialization.mdx +22 -22
- package/docs/foundations/starting-workflows.mdx +104 -30
- package/docs/foundations/streaming.mdx +108 -60
- package/docs/foundations/versioning.mdx +4 -4
- package/docs/foundations/workflows-and-steps.mdx +10 -10
- package/docs/getting-started/astro.mdx +22 -18
- package/docs/getting-started/express.mdx +15 -11
- package/docs/getting-started/fastify.mdx +15 -11
- package/docs/getting-started/hono.mdx +15 -11
- package/docs/getting-started/index.mdx +10 -3
- package/docs/getting-started/meta.json +3 -1
- package/docs/getting-started/nestjs.mdx +264 -21
- package/docs/getting-started/next.mdx +18 -14
- package/docs/getting-started/nitro.mdx +22 -18
- package/docs/getting-started/nuxt.mdx +15 -11
- package/docs/getting-started/python.mdx +190 -41
- package/docs/getting-started/react-router/index.mdx +33 -0
- package/docs/getting-started/react-router/meta.json +5 -0
- package/docs/getting-started/react-router/v7.mdx +237 -0
- package/docs/getting-started/react-router/v8.mdx +232 -0
- package/docs/getting-started/sveltekit.mdx +20 -16
- package/docs/getting-started/tanstack-start.mdx +17 -13
- package/docs/getting-started/vite.mdx +15 -11
- package/docs/how-it-works/cancellation.mdx +63 -63
- package/docs/how-it-works/code-transform.mdx +82 -66
- package/docs/how-it-works/encryption.mdx +30 -26
- package/docs/how-it-works/event-sourcing.mdx +132 -35
- package/docs/how-it-works/framework-integrations.mdx +96 -337
- package/docs/how-it-works/understanding-directives.mdx +22 -22
- package/docs/internal/index.mdx +6 -4
- package/docs/internal/meta.json +6 -1
- package/docs/internal/nitro-native-build.mdx +38 -0
- package/docs/internal/nitro-web-ui.mdx +24 -0
- package/docs/internal/serializable-abort-controller.mdx +7 -7
- package/docs/meta.json +4 -2
- package/docs/observability/attributes.mdx +91 -21
- package/docs/observability/index.mdx +29 -15
- package/docs/observability/lifecycle-hooks.mdx +95 -0
- package/docs/observability/meta.json +1 -1
- package/docs/observability/retention.mdx +95 -0
- package/docs/observability/tracing.mdx +124 -0
- package/docs/testing/index.mdx +118 -38
- package/docs/testing/server-based.mdx +10 -10
- package/docs/whats-new.mdx +196 -0
- package/docs/worlds/building-a-world.mdx +600 -0
- package/docs/worlds/local.mdx +129 -0
- package/docs/worlds/meta.json +10 -0
- package/docs/worlds/postgres.mdx +428 -0
- package/docs/worlds/upgrading-to-v5.mdx +183 -0
- package/docs/worlds/vercel.mdx +389 -0
- package/package.json +17 -14
- package/docs/api-reference/workflow/experimental-set-attributes.mdx +0 -63
- package/docs/api-reference/workflow-api/world/index.mdx +0 -58
- package/docs/api-reference/workflow-api/world/meta.json +0 -4
- package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
- package/docs/api-reference/workflow-api/world/queue.mdx +0 -86
- package/docs/deploying/building-a-world.mdx +0 -251
- package/docs/deploying/index.mdx +0 -95
- package/docs/deploying/meta.json +0 -4
- package/docs/deploying/world/local-world.mdx +0 -84
- package/docs/deploying/world/meta.json +0 -4
- package/docs/deploying/world/postgres-world.mdx +0 -224
- package/docs/deploying/world/vercel-world.mdx +0 -181
- 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 -358
- package/docs/migration-guides/migrating-from-inngest.mdx +0 -304
- package/docs/migration-guides/migrating-from-temporal.mdx +0 -313
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +0 -328
|
@@ -0,0 +1,129 @@
|
|
|
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-configuration workflow development on your machine.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/deploying
|
|
8
|
+
related:
|
|
9
|
+
- /worlds/postgres
|
|
10
|
+
- /worlds/vercel
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
The Local World is bundled with `workflow` and used automatically during local development. It requires no installation or configuration.
|
|
14
|
+
|
|
15
|
+
To explicitly use the Local World in any environment, set this 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. Run these commands inside your workflow project to view 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 requires no configuration, but you can customize its behavior through environment variables or programmatically through `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 deliver workflow queue messages to the combined flow route. 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_RUN_STATUS_POLL_INTERVAL_MS`
|
|
66
|
+
|
|
67
|
+
How often a wait for a terminal run status re-reads the run file, in milliseconds. Default: `100`.
|
|
68
|
+
|
|
69
|
+
`await run.returnValue` asks the World to wait for the run to finish. When the run and the caller share a process (the usual development server case), an in-process signal resolves the wait as soon as the run ends, and this interval never comes into play. The interval covers activity the signal cannot detect, primarily a second process awaiting a run over the same data directory, so it is set far below Workflow's own polling interval.
|
|
70
|
+
|
|
71
|
+
### `WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS`
|
|
72
|
+
|
|
73
|
+
Maximum time in milliseconds to wait for a local queue handler to begin responding before redelivering the durable message. Default: `0` (no deadline).
|
|
74
|
+
|
|
75
|
+
A delivery executes inline steps before the handler responds, so a deadline shorter than your longest step redelivers a healthy delivery while it is still running and executes the same step again. Set this only if you would rather a hung handler be redelivered, and set it above the longest inline work you expect.
|
|
76
|
+
|
|
77
|
+
### `WORKFLOW_LOCAL_BODY_TIMEOUT_MS`
|
|
78
|
+
|
|
79
|
+
Maximum gap in milliseconds between response body chunks from a local queue handler before redelivering the durable message. Default: `0` (no deadline). The same caution as `WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS` applies.
|
|
80
|
+
|
|
81
|
+
### `WORKFLOW_NODE_HTTP`
|
|
82
|
+
|
|
83
|
+
Whether queue deliveries go out through Node's built-in `node:http` and `node:https` modules instead of the HTTP client library this World normally uses. Default: disabled. Set to `1` to switch to Node's modules. Socket pooling, keep-alive, and both timeouts above apply either way. See [`WORKFLOW_NODE_HTTP`](/docs/configuration/runtime-tuning#workflow_node_http) for what else changes.
|
|
84
|
+
|
|
85
|
+
### `WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS`
|
|
86
|
+
|
|
87
|
+
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`.
|
|
88
|
+
|
|
89
|
+
### `WORKFLOW_LOCAL_HOOK_RETENTION_LIMIT_DAYS`
|
|
90
|
+
|
|
91
|
+
Maximum [`experimental_minRetention`](/docs/api-reference/workflow/create-hook#keep-a-token-unavailable-after-the-run-ends) accepted by the Local World, in days. Default: `30`. Set this to the same limit as your production World so oversized values fail during local development.
|
|
92
|
+
|
|
93
|
+
### `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`
|
|
94
|
+
|
|
95
|
+
Group-commit window, in milliseconds, for the leading chunk of an idle stream. Default: `0` (dispatch immediately). A positive value holds the first chunk up to that long to collect a group, trading first-chunk latency for fewer requests. Chunks arriving while a request is in flight always coalesce into the next group regardless.
|
|
96
|
+
|
|
97
|
+
### Programmatic configuration
|
|
98
|
+
|
|
99
|
+
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:
|
|
100
|
+
|
|
101
|
+
```typescript title="my-world.ts" lineNumbers
|
|
102
|
+
import { createWorld } from "@workflow/world-local";
|
|
103
|
+
|
|
104
|
+
export default createWorld({
|
|
105
|
+
dataDir: "./custom-workflow-data",
|
|
106
|
+
port: 5173,
|
|
107
|
+
// baseUrl overrides port if set
|
|
108
|
+
baseUrl: "https://local.example.com:3000",
|
|
109
|
+
recoverActiveRuns: true, // overrides WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNS
|
|
110
|
+
streamFlushIntervalMs: 10, // WORKFLOW_STREAM_FLUSH_INTERVAL_MS, if set, overrides this
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```bash title=".env"
|
|
115
|
+
WORKFLOW_TARGET_WORLD="./my-world.ts"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`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.
|
|
119
|
+
|
|
120
|
+
## Limitations
|
|
121
|
+
|
|
122
|
+
The Local World is designed for development, not production:
|
|
123
|
+
|
|
124
|
+
- **In-memory queue**: Workflow messages, including queued step invocations, do not persist across server restarts.
|
|
125
|
+
- **Filesystem storage**: Data is stored in local JSON files.
|
|
126
|
+
- **Single instance**: The Local World cannot handle distributed deployments.
|
|
127
|
+
- **No authentication**: The Local World is suitable only for local development.
|
|
128
|
+
|
|
129
|
+
For production deployments, use the [Vercel World](/worlds/vercel), which handles execution, persistence, multi-tenancy, scale, observability, and security for you on Vercel, or check out the [Postgres World](/worlds/postgres) - a tested reference implementation that implements the complete World spec and can be used to deploy workflows anywhere.
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Postgres World
|
|
3
|
+
description: Self-hosted reference 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
|
+
- /worlds/local
|
|
10
|
+
- /worlds/vercel
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
The Postgres World is a self-hosted backend that uses PostgreSQL for durable storage and [graphile-worker](https://github.com/graphile/worker) for reliable job processing.
|
|
14
|
+
|
|
15
|
+
Use the Postgres World to deploy workflows on your own infrastructure outside Vercel, such as a Docker container, Kubernetes cluster, or any cloud that supports long-running servers.
|
|
16
|
+
|
|
17
|
+
<Callout type="warn">
|
|
18
|
+
The Postgres World is a **reference implementation**: it implements the
|
|
19
|
+
complete World spec and is tested, but it is not optimized for scale, speed,
|
|
20
|
+
or security, and it ships with **no authentication** on the workflow HTTP
|
|
21
|
+
routes. For production use-cases we recommend cloning it and adapting it to
|
|
22
|
+
your persistence, network stack, scale, and security requirements. If you
|
|
23
|
+
deploy it as is, you must restrict those routes yourself. Read
|
|
24
|
+
[Security](#security) first.
|
|
25
|
+
</Callout>
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
Install the Postgres World package in your workflow project:
|
|
30
|
+
|
|
31
|
+
```package-install
|
|
32
|
+
@workflow/world-postgres
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
<Callout type="info">
|
|
36
|
+
Keep `workflow` and `@workflow/world-postgres` on the same major version and release
|
|
37
|
+
cycle. If your app uses a prerelease Workflow version, install the matching prerelease Postgres
|
|
38
|
+
World package. Mismatched versions fail before starting a run with an error that names the
|
|
39
|
+
spec versions the runtime supports and the one the World declares.
|
|
40
|
+
</Callout>
|
|
41
|
+
|
|
42
|
+
Configure the required environment variables to use the world and point it to your PostgreSQL database:
|
|
43
|
+
|
|
44
|
+
```bash title=".env"
|
|
45
|
+
WORKFLOW_TARGET_WORLD="@workflow/world-postgres"
|
|
46
|
+
WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
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:
|
|
50
|
+
|
|
51
|
+
<Tabs items={["npm", "pnpm", "Yarn", "Bun"]}>
|
|
52
|
+
|
|
53
|
+
<Tab value="npm">
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx --package=@workflow/world-postgres bootstrap
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
</Tab>
|
|
60
|
+
|
|
61
|
+
<Tab value="pnpm">
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pnpm dlx --package @workflow/world-postgres bootstrap
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
</Tab>
|
|
68
|
+
|
|
69
|
+
<Tab value="Yarn">
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
yarn dlx --package @workflow/world-postgres bootstrap
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
</Tab>
|
|
76
|
+
|
|
77
|
+
<Tab value="Bun">
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bunx --package @workflow/world-postgres bootstrap
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
</Tab>
|
|
84
|
+
|
|
85
|
+
</Tabs>
|
|
86
|
+
|
|
87
|
+
<Callout type="info">
|
|
88
|
+
The migration is idempotent and can safely be run as a post-deployment lifecycle script.
|
|
89
|
+
</Callout>
|
|
90
|
+
|
|
91
|
+
## Starting the World
|
|
92
|
+
|
|
93
|
+
### Experimental invocation delivery
|
|
94
|
+
|
|
95
|
+
Set `WORKFLOW_POSTGRES_INVOKE=1`, or pass `enableInvoke: true` to the Postgres
|
|
96
|
+
World's factory, to send hook inputs to the run's executor and wait for its
|
|
97
|
+
response. Invocation is off by default. Apply the migrations and upgrade
|
|
98
|
+
producers and workers together before enabling it.
|
|
99
|
+
|
|
100
|
+
The executor validates the hook input and writes its event before responding.
|
|
101
|
+
Workflow user code can consume the event later. The World stores inputs and
|
|
102
|
+
responses in `workflow.workflow_invocations`. Inserting an input and enqueueing
|
|
103
|
+
a workflow execution job share one transaction. Retries also enqueue an execution
|
|
104
|
+
job, including when a retained response already exists, so committed events can
|
|
105
|
+
be replayed after a runner failure.
|
|
106
|
+
|
|
107
|
+
With the default job prefix, Graphile uses `workflow_flows_executor` for workflow
|
|
108
|
+
execution and `workflow_flows` for step jobs and health checks. Execution jobs
|
|
109
|
+
use a named queue per run, `workflow_flows:<runId>:executor`. Graphile permits
|
|
110
|
+
one active job on each named queue across workers. Other runs, steps, and health
|
|
111
|
+
checks can run concurrently. `queueConcurrency` limits total active jobs per
|
|
112
|
+
worker process and defaults to 50. A custom job prefix replaces `workflow_` in
|
|
113
|
+
these names.
|
|
114
|
+
|
|
115
|
+
The HTTP receiver checks each execution request against its active Graphile job
|
|
116
|
+
before processing pending inputs. Updated workers and receivers move legacy
|
|
117
|
+
orchestration requests to the run's named queue. Before acknowledging an execution
|
|
118
|
+
job, the World finishes any input already being processed and replays newly
|
|
119
|
+
committed events. These entry checks do not stop an old handler from writing
|
|
120
|
+
after its job is reclaimed.
|
|
121
|
+
|
|
122
|
+
The caller receives a stored return value or a restored Workflow error. Terminal
|
|
123
|
+
errors are retained as outcomes; transient or unrecognized failures leave inputs
|
|
124
|
+
pending for Graphile to retry. Migration 0022 preserves the meaning of responses
|
|
125
|
+
stored by earlier previews. Failure to store or read a response leaves the
|
|
126
|
+
processing outcome unknown. Durable resume identities deduplicate the hook event
|
|
127
|
+
when response storage must be retried.
|
|
128
|
+
|
|
129
|
+
A shared `LISTEN/NOTIFY` connection signals changes to pending inputs and
|
|
130
|
+
responses. Notifications contain identifiers, and readers fetch data from the
|
|
131
|
+
table. Use a session-capable connection for `LISTEN`. Readers fall back to polling
|
|
132
|
+
at 1s intervals if notifications fail, and connection retries use a 1s backoff.
|
|
133
|
+
Notification failure and recovery are logged once per transition without
|
|
134
|
+
payloads or connection details.
|
|
135
|
+
|
|
136
|
+
The response-wait timeout defaults to 30s and can be set with `timeoutMs`. Encoded
|
|
137
|
+
inputs and outcomes are limited to 1 MiB each. A timeout does not cancel
|
|
138
|
+
processing. `$retention: 0` purges invocation data and prevents late writes from
|
|
139
|
+
restoring it. Callers whose result has expired receive a 410 error with code
|
|
140
|
+
`INVOCATION_DATA_EXPIRED`, even if the hook event committed earlier.
|
|
141
|
+
|
|
142
|
+
Automatic cleanup of other results, stopping writes from reclaimed handlers, and
|
|
143
|
+
routing requests across incompatible worker versions remain unimplemented.
|
|
144
|
+
|
|
145
|
+
### Server startup
|
|
146
|
+
|
|
147
|
+
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:
|
|
148
|
+
|
|
149
|
+
<Callout type="warn">
|
|
150
|
+
This step is specific to worlds that run a background worker, such as Postgres
|
|
151
|
+
World. Worlds whose queue delivers work over HTTP, including the Vercel World,
|
|
152
|
+
have no worker to subscribe, so `start()` does nothing there while the import
|
|
153
|
+
still adds overhead. Pulling in `workflow/runtime` from a server-startup hook puts
|
|
154
|
+
the whole runtime into the cold-start path before the first request is served.
|
|
155
|
+
</Callout>
|
|
156
|
+
|
|
157
|
+
<Tabs items={["Next.js", "SvelteKit", "Nitro"]}>
|
|
158
|
+
|
|
159
|
+
<Tab value="Next.js">
|
|
160
|
+
|
|
161
|
+
Create an `instrumentation.ts` file in your project root:
|
|
162
|
+
|
|
163
|
+
```ts title="instrumentation.ts" lineNumbers
|
|
164
|
+
export async function register() {
|
|
165
|
+
if (process.env.NEXT_RUNTIME !== "edge") {
|
|
166
|
+
const { getWorld } = await import("workflow/runtime");
|
|
167
|
+
const world = await getWorld();
|
|
168
|
+
await world.start?.();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
<Callout type="info">
|
|
174
|
+
Learn more about [Next.js Instrumentation](https://nextjs.org/docs/app/guides/instrumentation).
|
|
175
|
+
</Callout>
|
|
176
|
+
|
|
177
|
+
</Tab>
|
|
178
|
+
|
|
179
|
+
<Tab value="SvelteKit">
|
|
180
|
+
|
|
181
|
+
Create a `src/hooks.server.ts` file:
|
|
182
|
+
|
|
183
|
+
```ts title="src/hooks.server.ts" lineNumbers
|
|
184
|
+
import type { ServerInit } from "@sveltejs/kit";
|
|
185
|
+
|
|
186
|
+
export const init: ServerInit = async () => {
|
|
187
|
+
const { getWorld } = await import("workflow/runtime");
|
|
188
|
+
const world = await getWorld();
|
|
189
|
+
await world.start?.();
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
<Callout type="info">
|
|
194
|
+
Learn more about [SvelteKit Hooks](https://svelte.dev/docs/kit/hooks).
|
|
195
|
+
</Callout>
|
|
196
|
+
|
|
197
|
+
</Tab>
|
|
198
|
+
|
|
199
|
+
<Tab value="Nitro">
|
|
200
|
+
|
|
201
|
+
Create a plugin to start the world on server initialization:
|
|
202
|
+
|
|
203
|
+
```ts title="plugins/start-pg-world.ts" lineNumbers
|
|
204
|
+
import { defineNitroPlugin } from "nitro/~internal/runtime/plugin";
|
|
205
|
+
|
|
206
|
+
export default defineNitroPlugin(async () => {
|
|
207
|
+
const { getWorld } = await import("workflow/runtime");
|
|
208
|
+
const world = await getWorld();
|
|
209
|
+
await world.start?.();
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Register the plugin in your config:
|
|
214
|
+
|
|
215
|
+
```ts title="nitro.config.ts"
|
|
216
|
+
import { defineNitroConfig } from "nitropack";
|
|
217
|
+
|
|
218
|
+
export default defineNitroConfig({
|
|
219
|
+
modules: ["workflow/nitro"],
|
|
220
|
+
plugins: ["plugins/start-pg-world.ts"],
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
<Callout type="info">
|
|
225
|
+
Learn more about [Nitro Plugins](https://v3.nitro.build/docs/plugins).
|
|
226
|
+
</Callout>
|
|
227
|
+
|
|
228
|
+
</Tab>
|
|
229
|
+
|
|
230
|
+
</Tabs>
|
|
231
|
+
|
|
232
|
+
<Callout type="info">
|
|
233
|
+
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.
|
|
234
|
+
</Callout>
|
|
235
|
+
|
|
236
|
+
## Observability
|
|
237
|
+
|
|
238
|
+
Use the Workflow CLI to inspect workflows stored in PostgreSQL:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Set your database URL
|
|
242
|
+
export WORKFLOW_POSTGRES_URL="postgres://user:password@host:5432/database"
|
|
243
|
+
|
|
244
|
+
# List workflow runs
|
|
245
|
+
npx workflow inspect runs --backend @workflow/world-postgres
|
|
246
|
+
|
|
247
|
+
# Launch the web UI
|
|
248
|
+
npx workflow web --backend @workflow/world-postgres
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
If `WORKFLOW_POSTGRES_URL` is not set, the CLI defaults to `postgres://world:world@localhost:5432/world`.
|
|
252
|
+
|
|
253
|
+
Learn more in the [Observability](/docs/observability) documentation.
|
|
254
|
+
|
|
255
|
+
## Testing & compatibility
|
|
256
|
+
|
|
257
|
+
<WorldTestingPerformance worldId="postgres" />
|
|
258
|
+
|
|
259
|
+
## Configuration
|
|
260
|
+
|
|
261
|
+
You can set all configuration options through environment variables or programmatically through `createWorld()`.
|
|
262
|
+
|
|
263
|
+
### `WORKFLOW_POSTGRES_URL`
|
|
264
|
+
|
|
265
|
+
PostgreSQL connection string used by the runtime World.
|
|
266
|
+
|
|
267
|
+
Precedence: `WORKFLOW_POSTGRES_URL` > `DATABASE_URL` > `postgres://world:world@localhost:5432/world`
|
|
268
|
+
|
|
269
|
+
The `bootstrap` migration command uses the same precedence.
|
|
270
|
+
|
|
271
|
+
### `WORKFLOW_POSTGRES_JOB_PREFIX`
|
|
272
|
+
|
|
273
|
+
Prefix for graphile-worker queue job names. Useful when sharing a database between multiple applications. Default: `workflow_`
|
|
274
|
+
|
|
275
|
+
### `WORKFLOW_POSTGRES_WORKER_CONCURRENCY`
|
|
276
|
+
|
|
277
|
+
Number of concurrent workers polling for jobs. Default: `50`.
|
|
278
|
+
|
|
279
|
+
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` end-to-end test (`fib(6)`, about 24 concurrent polls at peak) passes; deeper recursion or larger fanouts need a correspondingly larger setting.
|
|
280
|
+
|
|
281
|
+
### `WORKFLOW_POSTGRES_POLL_INTERVAL_MS`
|
|
282
|
+
|
|
283
|
+
Milliseconds between idle job fetches per worker. Each worker polls on its own, so an idle process runs about `queueConcurrency × 1000 / pollInterval` fetches per second. Default: `500`.
|
|
284
|
+
|
|
285
|
+
### `WORKFLOW_POSTGRES_MAX_POOL_SIZE`
|
|
286
|
+
|
|
287
|
+
Maximum size of the internal `pg.Pool` used when `createWorld()` constructs the pool. Default: the `pg` default (`10`).
|
|
288
|
+
|
|
289
|
+
For higher worker concurrency, Graphile Worker recommends setting `maxPoolSize` to `10` or `queueConcurrency + 2`, whichever is larger.
|
|
290
|
+
|
|
291
|
+
### `WORKFLOW_POSTGRES_APPLICATION_MANAGED_SHUTDOWN`
|
|
292
|
+
|
|
293
|
+
Set to `1` when the application or framework coordinates shutdown and awaits `world.close()` before closing its workflow HTTP server and any caller-owned pool. Default: unset (`false`).
|
|
294
|
+
|
|
295
|
+
### `WORKFLOW_POSTGRES_RUN_STATUS_POLL_INTERVAL_MS`
|
|
296
|
+
|
|
297
|
+
How often a wait for a terminal run status re-reads the run, in milliseconds. Default: `1000`.
|
|
298
|
+
|
|
299
|
+
`await run.returnValue` asks the World to wait for the run to finish, and the Postgres World parks that wait on a `NOTIFY` issued by the run-terminal write. This interval is only the backstop: it bounds how long a lost notification can go unnoticed, so lowering it costs a query per waiting run per interval and buys nothing while notifications are arriving.
|
|
300
|
+
|
|
301
|
+
### `WORKFLOW_POSTGRES_HEADERS_TIMEOUT_MS`
|
|
302
|
+
|
|
303
|
+
Maximum time in milliseconds a queue delivery waits for the workflow handler to begin responding before the delivery is failed and Graphile Worker redelivers the message. Default: `0` (no deadline).
|
|
304
|
+
|
|
305
|
+
A delivery executes the workflow body inline, so the handler responds only once that work is done. A deadline shorter than your longest inline step declares a healthy delivery crashed and redelivers it while the original is still running, executing the same steps twice. Set this only if you would rather a hung handler be redelivered than hold its worker slot until the process restarts, and set it above the longest inline work you expect.
|
|
306
|
+
|
|
307
|
+
### `WORKFLOW_POSTGRES_BODY_TIMEOUT_MS`
|
|
308
|
+
|
|
309
|
+
Maximum gap in milliseconds between response body chunks from the workflow handler before the delivery is failed and redelivered. Default: `0` (no deadline). The same caution as `WORKFLOW_POSTGRES_HEADERS_TIMEOUT_MS` applies.
|
|
310
|
+
|
|
311
|
+
### `WORKFLOW_POSTGRES_HOOK_RETENTION_LIMIT_DAYS`
|
|
312
|
+
|
|
313
|
+
Maximum [`experimental_minRetention`](/docs/api-reference/workflow/create-hook#keep-a-token-unavailable-after-the-run-ends) accepted by the Postgres World, in days. Default: `30`. Set this to the same limit as your production World so oversized values fail during development.
|
|
314
|
+
|
|
315
|
+
### `WORKFLOW_QUEUE_NAMESPACE`
|
|
316
|
+
|
|
317
|
+
Queue topic namespace shared by build output and the Postgres World. Default: unset.
|
|
318
|
+
|
|
319
|
+
For example, `custom` changes the queue topic prefix from `__wkf_workflow_` to `__custom_wkf_workflow_`. The value must be lowercase alphanumeric and start with a letter.
|
|
320
|
+
|
|
321
|
+
### `WORKFLOW_STREAM_FLUSH_INTERVAL_MS`
|
|
322
|
+
|
|
323
|
+
Group-commit window, in milliseconds, for the leading chunk of an idle stream. Default: `0` (dispatch immediately). A positive value holds the first chunk up to that long to collect a group, trading first-chunk latency for fewer requests. Chunks arriving while a request is in flight always coalesce into the next group regardless.
|
|
324
|
+
|
|
325
|
+
### Programmatic configuration
|
|
326
|
+
|
|
327
|
+
{/*@skip-typecheck: incomplete code sample*/}
|
|
328
|
+
|
|
329
|
+
```typescript title="my-world.ts" lineNumbers
|
|
330
|
+
import { createWorld } from "@workflow/world-postgres";
|
|
331
|
+
|
|
332
|
+
export default createWorld({
|
|
333
|
+
connectionString:
|
|
334
|
+
process.env.WORKFLOW_POSTGRES_URL ?? process.env.DATABASE_URL!,
|
|
335
|
+
jobPrefix: "myapp_",
|
|
336
|
+
namespace: "myapp",
|
|
337
|
+
queueConcurrency: 50,
|
|
338
|
+
maxPoolSize: 52, // overrides WORKFLOW_POSTGRES_MAX_POOL_SIZE
|
|
339
|
+
streamFlushIntervalMs: 10,
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
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:
|
|
344
|
+
|
|
345
|
+
You can also pass an existing `pg.Pool` as `pool` instead of a connection string.
|
|
346
|
+
|
|
347
|
+
```bash title=".env"
|
|
348
|
+
WORKFLOW_TARGET_WORLD="./my-world.ts"
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Application-managed shutdown
|
|
352
|
+
|
|
353
|
+
Graphile Worker responds automatically when the application is asked to shut down. If your application or framework already coordinates a broader shutdown sequence, set `WORKFLOW_POSTGRES_APPLICATION_MANAGED_SHUTDOWN=1` when selecting the package directly with `WORKFLOW_TARGET_WORLD`, or set `applicationManagedShutdown: true` in a programmatic World. Use the application's normal shutdown hook. This prevents Graphile Worker's default handler from terminating the process as soon as its queue stops. The hook must handle cleanup errors and await resources in this order:
|
|
354
|
+
|
|
355
|
+
1. `world.close()`
|
|
356
|
+
2. The workflow HTTP server
|
|
357
|
+
3. Any caller-owned `pg.Pool`
|
|
358
|
+
|
|
359
|
+
Closing the world stops new queue claims and waits for active jobs. After Graphile Worker's grace period, a pending workflow HTTP request is aborted. Graphile Worker unlocks that same row through its normal failure handling. The already-claimed delivery consumes a Graphile attempt and is retried only if its attempt budget remains; a one-attempt or final-attempt job is not retried. The shutdown handler does not insert a successor row. Because a client abort does not prove the server handler stopped, workflow and step handlers must tolerate at-least-once execution.
|
|
360
|
+
|
|
361
|
+
## How it works
|
|
362
|
+
|
|
363
|
+
The Postgres World uses PostgreSQL as a durable backend:
|
|
364
|
+
|
|
365
|
+
- **Storage**: Workflow runs, events, steps, and hooks are stored in PostgreSQL tables.
|
|
366
|
+
- **Job queue**: [graphile-worker](https://github.com/graphile/worker) handles reliable job processing with retries.
|
|
367
|
+
- **Streaming**: PostgreSQL NOTIFY/LISTEN enables real-time event distribution.
|
|
368
|
+
|
|
369
|
+
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).
|
|
370
|
+
|
|
371
|
+
## Security
|
|
372
|
+
|
|
373
|
+
Unlike the [Vercel World](/worlds/vercel), which authenticates and [encrypts](/docs/how-it-works/encryption) workflow traffic automatically, the Postgres World does **not** automatically authenticate requests that drive workflow execution. Adding that is your responsibility and should be in place before an app using this World is used in production and reachable by untrusted clients. We also recommend [enabling encryption](/docs/how-it-works/encryption#custom-world-implementations).
|
|
374
|
+
|
|
375
|
+
### Protect the queue route
|
|
376
|
+
|
|
377
|
+
`POST /.well-known/workflow/v1/flow` is where the worker delivers workflow orchestration and queued step invocations. It is mounted in your application like any other route, so it is publicly reachable by default, and it accepts any request whose headers and body are well-formed: there is no signature, shared secret, or caller check. Anyone who can reach it can forge or replay a workflow or step invocation, including steps your application would only reach after its own gating. Restrict it before you expose the app.
|
|
378
|
+
|
|
379
|
+
The other routes under `/.well-known/workflow/v1/` differ:
|
|
380
|
+
|
|
381
|
+
- `webhook/:token`, created by [`createWebhook()`](/docs/api-reference/workflow/create-webhook), is authorized by the token in the URL and nothing else. Use [`createHook()`](/docs/api-reference/workflow/create-hook) behind your own authenticated route and [`resumeHook()`](/docs/api-reference/workflow-api/resume-hook) when you need more than that.
|
|
382
|
+
- `manifest.json` responds with `404` unless the app was built with `WORKFLOW_PUBLIC_MANIFEST=1`. That variable is read at build time, so unsetting it in the runtime environment of an already-built deployment does not withdraw the manifest. Leave it unset outside of testing, because the manifest lists your workflow and step names.
|
|
383
|
+
|
|
384
|
+
### Bringing your own auth
|
|
385
|
+
|
|
386
|
+
Workflow does not prescribe an auth mechanism for self-hosted Worlds, so restrict the flow route at the network edge rather than inside the application:
|
|
387
|
+
|
|
388
|
+
- **Keep the flow route unreachable from outside.** By default the worker delivers to a loopback address (`http://localhost:{PORT}`, or `WORKFLOW_LOCAL_BASE_URL` when set), so in the common single-process topology nothing outside the container needs to reach it. Blocking external requests to `/.well-known/workflow/v1/flow` at your ingress, reverse proxy, or firewall costs you nothing, because loopback delivery never traverses that layer. Do not block the whole `/.well-known/workflow/` prefix if you use [`createWebhook()`](/docs/api-reference/workflow/create-webhook): its `webhook/:token` route sits under the same prefix and has to stay reachable by whoever calls it.
|
|
389
|
+
- **Authenticate at the proxy when the routes must cross hosts.** If your web tier and workers are separate deployments, require mTLS or a shared-secret header at the proxy in front of the application, and strip any client-supplied copy of that header at the edge.
|
|
390
|
+
- **Do not gate these paths in framework middleware.** The [Next.js setup guide](/docs/getting-started/next) tells you to exclude `/.well-known/workflow/*` from your proxy matcher, because a handler that consumes the internal request body breaks execution. Adding the paths back in order to gate them reintroduces that failure mode.
|
|
391
|
+
- **Do not expect the World to present a credential.** It does not sign its delivery requests or attach a secret to them, so an in-application check that requires one will reject the World's own deliveries.
|
|
392
|
+
|
|
393
|
+
### Data at rest
|
|
394
|
+
|
|
395
|
+
The Postgres World does not currently implement `getEncryptionKeyForRun()`, so it does not participate in Workflow's [end-to-end encryption](/docs/how-it-works/encryption): workflow and step inputs and return values, hook payloads and metadata, and stream chunks are all stored in your database in readable form. A World derived from this one can opt in by implementing that one method; see [Custom World implementations](/docs/how-it-works/encryption#custom-world-implementations). Until then, protect the database, its credentials, and its backups accordingly.
|
|
396
|
+
|
|
397
|
+
The [observability UI](/docs/observability) has no authentication of its own either. If you self-host `@workflow/web` against your Postgres database, put it behind your own auth layer, because everyone who can reach it can read every run.
|
|
398
|
+
|
|
399
|
+
## Deployment
|
|
400
|
+
|
|
401
|
+
Deploy your application to any cloud that supports long-running servers:
|
|
402
|
+
|
|
403
|
+
- Docker containers
|
|
404
|
+
- Kubernetes clusters
|
|
405
|
+
- Virtual machines
|
|
406
|
+
- Platform-as-a-service (PaaS) providers, such as Railway, Render, and Fly.io
|
|
407
|
+
|
|
408
|
+
Ensure your deployment has:
|
|
409
|
+
|
|
410
|
+
- Network access to your PostgreSQL database
|
|
411
|
+
- Environment variables configured correctly
|
|
412
|
+
- The `start()` function called on server initialization
|
|
413
|
+
- `/.well-known/workflow/v1/flow` restricted so untrusted clients cannot reach it (see [Security](#security))
|
|
414
|
+
|
|
415
|
+
<Callout type="info">
|
|
416
|
+
The Postgres World is not compatible with Vercel deployments. On Vercel, workflows automatically use the [Vercel World](/worlds/vercel) with zero configuration.
|
|
417
|
+
</Callout>
|
|
418
|
+
|
|
419
|
+
## Limitations
|
|
420
|
+
|
|
421
|
+
- **Reference implementation**: Tested and implements the complete World spec, but is not optimized for scale, speed, or security; a production deployment typically clones it and adapts it, or runs workers in separate processes with a more robust queuing system
|
|
422
|
+
- **No built-in authentication**: The workflow HTTP routes accept any request that reaches them; you must restrict them yourself (see [Security](#security))
|
|
423
|
+
- **No encryption**: Workflow and step data is stored unencrypted; the World does not currently implement [end-to-end encryption](/docs/how-it-works/encryption)
|
|
424
|
+
- **Requires long-running process**: Must call `start()` on server initialization; not compatible with serverless platforms
|
|
425
|
+
- **PostgreSQL infrastructure**: Requires a PostgreSQL database (self-hosted or managed)
|
|
426
|
+
- **Not compatible with Vercel**: Use the [Vercel World](/worlds/vercel) for Vercel deployments
|
|
427
|
+
|
|
428
|
+
For local development, use the [Local World](/worlds/local) which requires no external services.
|