workflow 5.0.0-beta.2 → 5.0.0-beta.20

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.
Files changed (169) hide show
  1. package/dist/api-workflow.d.ts +1 -1
  2. package/dist/api-workflow.d.ts.map +1 -1
  3. package/dist/api-workflow.js +2 -2
  4. package/dist/api.d.ts +5 -1
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/api.js +14 -2
  7. package/dist/internal/builtins.d.ts +17 -0
  8. package/dist/internal/builtins.d.ts.map +1 -1
  9. package/dist/internal/builtins.js +65 -1
  10. package/dist/observability.d.ts +1 -1
  11. package/dist/observability.js +2 -2
  12. package/dist/runtime.d.ts +1 -1
  13. package/dist/runtime.d.ts.map +1 -1
  14. package/dist/runtime.js +2 -2
  15. package/docs/ai/index.mdx +27 -23
  16. package/docs/api-reference/index.mdx +24 -0
  17. package/docs/api-reference/meta.json +8 -0
  18. package/docs/api-reference/vitest/index.mdx +28 -7
  19. package/docs/api-reference/workflow/create-hook.mdx +38 -0
  20. package/docs/api-reference/workflow/create-webhook.mdx +1 -0
  21. package/docs/api-reference/workflow/experimental-set-attributes.mdx +65 -0
  22. package/docs/api-reference/workflow/fetch.mdx +5 -0
  23. package/docs/api-reference/workflow/index.mdx +3 -0
  24. package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -45
  25. package/docs/api-reference/workflow-api/get-hook-by-token.mdx +7 -0
  26. package/docs/api-reference/workflow-api/get-run.mdx +6 -0
  27. package/docs/api-reference/workflow-api/index.mdx +6 -8
  28. package/docs/api-reference/workflow-api/resume-hook.mdx +57 -0
  29. package/docs/api-reference/workflow-api/start.mdx +13 -5
  30. package/docs/api-reference/workflow-astro/index.mdx +18 -0
  31. package/docs/api-reference/workflow-astro/meta.json +4 -0
  32. package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
  33. package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
  34. package/docs/api-reference/workflow-errors/index.mdx +85 -0
  35. package/docs/api-reference/workflow-errors/meta.json +5 -0
  36. package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
  37. package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
  38. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
  39. package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
  40. package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
  41. package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
  42. package/docs/api-reference/workflow-nest/index.mdx +31 -0
  43. package/docs/api-reference/workflow-nest/meta.json +9 -0
  44. package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
  45. package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
  46. package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
  47. package/docs/api-reference/workflow-next/with-workflow.mdx +34 -2
  48. package/docs/api-reference/workflow-nitro/index.mdx +59 -0
  49. package/docs/api-reference/workflow-nuxt/index.mdx +47 -0
  50. package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
  51. package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
  52. package/docs/api-reference/workflow-observability/index.mdx +64 -0
  53. package/docs/api-reference/workflow-observability/meta.json +11 -0
  54. package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
  55. package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
  56. package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
  57. package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
  58. package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
  59. package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
  60. package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
  61. package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
  62. package/docs/api-reference/workflow-runtime/index.mdx +43 -0
  63. package/docs/api-reference/workflow-runtime/meta.json +12 -0
  64. package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
  65. package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
  66. package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +5 -8
  67. package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
  68. package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +2 -2
  69. package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +11 -4
  70. package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
  71. package/docs/api-reference/workflow-serde/index.mdx +0 -1
  72. package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
  73. package/docs/api-reference/workflow-serde/workflow-serialize.mdx +1 -2
  74. package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
  75. package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
  76. package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
  77. package/docs/api-reference/workflow-vite/index.mdx +18 -0
  78. package/docs/api-reference/workflow-vite/meta.json +4 -0
  79. package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
  80. package/docs/changelog/attributes-mvp.mdx +380 -0
  81. package/docs/changelog/eager-processing.mdx +269 -0
  82. package/docs/changelog/index.mdx +2 -1
  83. package/docs/changelog/lazy-event-creation.md +127 -0
  84. package/docs/changelog/meta.json +6 -1
  85. package/docs/changelog/resilient-start.mdx +31 -283
  86. package/docs/cookbook/advanced/child-workflows.mdx +315 -0
  87. package/docs/cookbook/advanced/meta.json +2 -3
  88. package/docs/cookbook/advanced/publishing-libraries.mdx +87 -29
  89. package/docs/cookbook/advanced/serializable-steps.mdx +17 -5
  90. package/docs/cookbook/advanced/upgrading-workflows.mdx +195 -0
  91. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +156 -0
  92. package/docs/cookbook/agent-patterns/durable-agent.mdx +11 -184
  93. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +150 -173
  94. package/docs/cookbook/agent-patterns/meta.json +1 -7
  95. package/docs/cookbook/common-patterns/batching.mdx +44 -118
  96. package/docs/cookbook/common-patterns/idempotency.mdx +36 -52
  97. package/docs/cookbook/common-patterns/meta.json +4 -4
  98. package/docs/cookbook/common-patterns/rate-limiting.mdx +1 -1
  99. package/docs/cookbook/common-patterns/saga.mdx +128 -33
  100. package/docs/cookbook/common-patterns/scheduling.mdx +77 -193
  101. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +155 -0
  102. package/docs/cookbook/common-patterns/timeouts.mdx +100 -0
  103. package/docs/cookbook/common-patterns/workflow-composition.mdx +117 -0
  104. package/docs/cookbook/index.mdx +14 -17
  105. package/docs/cookbook/integrations/ai-sdk.mdx +330 -142
  106. package/docs/cookbook/integrations/chat-sdk.mdx +264 -151
  107. package/docs/cookbook/integrations/sandbox.mdx +482 -81
  108. package/docs/cookbook/meta.json +1 -1
  109. package/docs/deploying/building-a-world.mdx +1 -1
  110. package/docs/deploying/world/postgres-world.mdx +5 -3
  111. package/docs/deploying/world/vercel-world.mdx +2 -0
  112. package/docs/errors/abort-signal-timeout-in-workflow.mdx +80 -0
  113. package/docs/errors/corrupted-event-log.mdx +5 -5
  114. package/docs/errors/hook-conflict.mdx +56 -4
  115. package/docs/errors/index.mdx +9 -0
  116. package/docs/errors/replay-divergence.mdx +27 -0
  117. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  118. package/docs/errors/step-executed-multiple-times.mdx +23 -0
  119. package/docs/errors/step-not-registered.mdx +1 -1
  120. package/docs/foundations/cancellation.mdx +459 -0
  121. package/docs/foundations/errors-and-retries.mdx +7 -3
  122. package/docs/foundations/hooks.mdx +29 -0
  123. package/docs/foundations/idempotency.mdx +236 -11
  124. package/docs/foundations/index.mdx +3 -3
  125. package/docs/foundations/meta.json +3 -2
  126. package/docs/foundations/serialization.mdx +78 -42
  127. package/docs/foundations/starting-workflows.mdx +6 -2
  128. package/docs/foundations/streaming.mdx +14 -23
  129. package/docs/foundations/versioning.mdx +263 -0
  130. package/docs/getting-started/astro.mdx +6 -0
  131. package/docs/getting-started/index.mdx +6 -7
  132. package/docs/getting-started/meta.json +1 -0
  133. package/docs/getting-started/nestjs.mdx +9 -0
  134. package/docs/getting-started/next.mdx +5 -3
  135. package/docs/getting-started/nitro.mdx +22 -0
  136. package/docs/getting-started/sveltekit.mdx +6 -0
  137. package/docs/getting-started/tanstack-start.mdx +241 -0
  138. package/docs/how-it-works/cancellation.mdx +287 -0
  139. package/docs/how-it-works/code-transform.mdx +2 -2
  140. package/docs/how-it-works/encryption.mdx +2 -2
  141. package/docs/how-it-works/event-sourcing.mdx +2 -2
  142. package/docs/how-it-works/meta.json +2 -1
  143. package/docs/internal/index.mdx +21 -0
  144. package/docs/internal/meta.json +10 -0
  145. package/docs/internal/nitro-native-build.mdx +38 -0
  146. package/docs/internal/nitro-web-ui.mdx +24 -0
  147. package/docs/internal/serializable-abort-controller.mdx +148 -0
  148. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +63 -16
  149. package/docs/migration-guides/migrating-from-inngest.mdx +44 -22
  150. package/docs/migration-guides/migrating-from-temporal.mdx +43 -14
  151. package/docs/migration-guides/migrating-from-trigger-dev.mdx +59 -27
  152. package/docs/observability/attributes.mdx +87 -0
  153. package/docs/observability/index.mdx +25 -1
  154. package/docs/observability/meta.json +1 -1
  155. package/docs/observability/tracing.mdx +106 -0
  156. package/docs/testing/index.mdx +2 -2
  157. package/package.json +14 -13
  158. package/docs/api-reference/workflow-api/world/meta.json +0 -4
  159. package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
  160. package/docs/cookbook/advanced/custom-serialization.mdx +0 -168
  161. package/docs/cookbook/advanced/durable-objects.mdx +0 -148
  162. package/docs/cookbook/advanced/isomorphic-packages.mdx +0 -145
  163. package/docs/cookbook/agent-patterns/stop-workflow.mdx +0 -216
  164. package/docs/cookbook/agent-patterns/tool-orchestration.mdx +0 -255
  165. package/docs/cookbook/agent-patterns/tool-streaming.mdx +0 -181
  166. package/docs/cookbook/common-patterns/child-workflows.mdx +0 -372
  167. package/docs/cookbook/common-patterns/content-router.mdx +0 -207
  168. package/docs/cookbook/common-patterns/fan-out.mdx +0 -208
  169. package/docs/foundations/common-patterns.mdx +0 -265
@@ -1,164 +0,0 @@
1
- ---
2
- title: Observability Utilities
3
- description: Hydrate step I/O, parse display names, and decrypt workflow data using workflow/observability.
4
- type: reference
5
- summary: "Functions: hydrateResourceIO(), parseStepName(), parseWorkflowName(), parseClassName(), getEncryptionKeyForRun(), hydrateResourceIOWithKey()."
6
- prerequisites:
7
- - /docs/api-reference/workflow-api/get-world
8
- related:
9
- - /docs/api-reference/workflow-api/world/storage
10
- keywords:
11
- - workflow/observability
12
- - hydrateResourceIO
13
- - observabilityRevivers
14
- - parseStepName
15
- - parseWorkflowName
16
- - parseClassName
17
- - getEncryptionKeyForRun
18
- - hydrateResourceIOWithKey
19
- - data hydration
20
- - devalue deserialization
21
- - encryption decryption
22
- - display name parsing
23
- ---
24
-
25
- The `workflow/observability` module provides utilities for working with workflow data in observability and debugging tools. It includes functions to hydrate serialized step I/O, parse machine-readable names into display-friendly formats, and decrypt encrypted workflow data.
26
-
27
- ## Import
28
-
29
- ```typescript lineNumbers
30
- import { // [!code highlight]
31
- hydrateResourceIO, // [!code highlight]
32
- observabilityRevivers, // [!code highlight]
33
- parseStepName, // [!code highlight]
34
- parseWorkflowName, // [!code highlight]
35
- parseClassName, // [!code highlight]
36
- } from "workflow/observability"; // [!code highlight]
37
- ```
38
-
39
- ## Data Hydration
40
-
41
- ### hydrateResourceIO()
42
-
43
- Deserialize step or run data that was serialized using the [devalue](https://github.com/Rich-Harris/devalue) format. Required to display step input/output in your UI.
44
-
45
- ```typescript lineNumbers
46
- import { hydrateResourceIO, observabilityRevivers } from "workflow/observability"; // [!code highlight]
47
-
48
- const step = await world.steps.get(runId, stepId);
49
- const hydrated = hydrateResourceIO(step, observabilityRevivers); // [!code highlight]
50
- console.log(hydrated.input, hydrated.output);
51
- ```
52
-
53
- **Parameters:**
54
-
55
- | Parameter | Type | Description |
56
- |-----------|------|-------------|
57
- | `resource` | `Step \| WorkflowRun` | The step or run with serialized data |
58
- | `revivers` | `Revivers` | Reviver functions for deserialization. Use `observabilityRevivers` for standard use. |
59
-
60
- **Returns:** The resource with hydrated `input` and `output` fields.
61
-
62
- ### observabilityRevivers
63
-
64
- A set of reviver functions that handle standard workflow serialization types (Date, Map, Set, Error, etc.).
65
-
66
- ## Name Parsing
67
-
68
- Workflow and step names are stored as machine-readable identifiers. These utilities extract display-friendly names. All return `{ shortName: string, moduleSpecifier: string } | null`.
69
-
70
- ### parseStepName()
71
-
72
- ```typescript lineNumbers
73
- import { parseStepName } from "workflow/observability"; // [!code highlight]
74
-
75
- const parsed = parseStepName("step//./src/workflows/order//processPayment"); // [!code highlight]
76
- // parsed?.shortName → "processPayment"
77
- // parsed?.moduleSpecifier → "./src/workflows/order"
78
- ```
79
-
80
- ### parseWorkflowName()
81
-
82
- ```typescript lineNumbers
83
- import { parseWorkflowName } from "workflow/observability"; // [!code highlight]
84
-
85
- const parsed = parseWorkflowName("workflow//./src/workflows/order//processOrder"); // [!code highlight]
86
- // parsed?.shortName → "processOrder"
87
- ```
88
-
89
- ### parseClassName()
90
-
91
- ```typescript lineNumbers
92
- import { parseClassName } from "workflow/observability"; // [!code highlight]
93
-
94
- const parsed = parseClassName("class//./src/models//User"); // [!code highlight]
95
- // parsed?.shortName → "User"
96
- ```
97
-
98
- ## Encryption
99
-
100
- For workflows with encrypted step data, decrypt before hydrating.
101
-
102
- ### getEncryptionKeyForRun()
103
-
104
- Retrieve the encryption key used for a specific workflow run.
105
-
106
- {/* @expect-error:2305 */}
107
- ```typescript lineNumbers
108
- import { getEncryptionKeyForRun } from "workflow/observability"; // [!code highlight]
109
-
110
- const key = await getEncryptionKeyForRun(runId); // [!code highlight]
111
- ```
112
-
113
- **Parameters:**
114
-
115
- | Parameter | Type | Description |
116
- |-----------|------|-------------|
117
- | `runId` | `string` | The workflow run ID |
118
-
119
- **Returns:** Encryption key for the run
120
-
121
- ### hydrateResourceIOWithKey()
122
-
123
- Hydrate step or run data using a decryption key. Use this instead of `hydrateResourceIO()` when data is encrypted.
124
-
125
- {/* @expect-error:2305,2724 */}
126
- ```typescript lineNumbers
127
- import { getEncryptionKeyForRun, hydrateResourceIOWithKey } from "workflow/observability"; // [!code highlight]
128
-
129
- const key = await getEncryptionKeyForRun(runId); // [!code highlight]
130
- const hydrated = hydrateResourceIOWithKey(step, key); // [!code highlight]
131
- ```
132
-
133
- **Parameters:**
134
-
135
- | Parameter | Type | Description |
136
- |-----------|------|-------------|
137
- | `resource` | `Step \| WorkflowRun` | The step or run with encrypted serialized data |
138
- | `key` | `EncryptionKey` | The encryption key from `getEncryptionKeyForRun()` |
139
-
140
- **Returns:** The resource with decrypted and hydrated `input` and `output` fields.
141
-
142
- ## Examples
143
-
144
- ### Parse Display Names for a Run's Steps
145
-
146
- ```typescript lineNumbers
147
- import { getWorld } from "workflow/runtime";
148
- import { parseStepName, parseWorkflowName } from "workflow/observability"; // [!code highlight]
149
-
150
- const world = await getWorld();
151
- const run = await world.runs.get(runId, { resolveData: "none" });
152
- console.log("Workflow:", parseWorkflowName(run.workflowName)?.shortName); // [!code highlight]
153
-
154
- const steps = await world.steps.list({ runId, resolveData: "none" });
155
- for (const step of steps.data) {
156
- const parsed = parseStepName(step.stepName); // [!code highlight]
157
- console.log(` ${parsed?.shortName}: ${step.status}`); // [!code highlight]
158
- }
159
- ```
160
-
161
- ## Related
162
-
163
- - [Storage](/docs/api-reference/workflow-api/world/storage) — Query runs, steps, hooks, and events
164
- - [Serialization](/docs/foundations/serialization) — How workflow data is serialized
@@ -1,168 +0,0 @@
1
- ---
2
- title: Custom Serialization
3
- description: Make class instances serializable across workflow boundaries using the WORKFLOW_SERIALIZE and WORKFLOW_DESERIALIZE symbol protocol.
4
- type: guide
5
- summary: Implement the WORKFLOW_SERIALIZE and WORKFLOW_DESERIALIZE symbol protocol on classes so instances survive serialization when passed between workflow and step functions.
6
- ---
7
-
8
- <Callout>
9
- This is an advanced guide. It dives into workflow internals and is not required reading to use workflow.
10
- </Callout>
11
-
12
- ## The Problem
13
-
14
- Workflow functions run inside a sandboxed VM. Every value that crosses a function boundary — step arguments, step return values, workflow inputs — must be [serializable](/docs/foundations/serialization). Plain objects, strings, numbers, and many built-in types (`Date`, `Map`, `Set`, `RegExp`, etc.) work automatically, but **class instances** that don't implement the custom class serialization protocol will throw a serialization error.
15
-
16
- ```typescript lineNumbers
17
- class StorageClient {
18
- constructor(private region: string) {}
19
-
20
- async upload(key: string, body: Uint8Array) {
21
- // ... uses this.region internally
22
- }
23
- }
24
-
25
- export async function processFile(client: StorageClient) {
26
- "use workflow";
27
-
28
- // client fails to serialize — StorageClient doesn't implement custom class serialization
29
- // The runtime throws a serialization error
30
- await uploadStep(client, "output.json", data);
31
- }
32
- ```
33
-
34
- Custom class serialization solves this by teaching the runtime how to convert your class instances to plain data and back.
35
-
36
- ## The WORKFLOW_SERIALIZE / WORKFLOW_DESERIALIZE Protocol
37
-
38
- The `@workflow/serde` package exports two symbols that act as a custom class serialization protocol. When the workflow runtime encounters a class instance with these symbols, it knows how to convert it to plain data and back.
39
-
40
- {/* @skip-typecheck - @workflow/serde is not mapped in the type-checker */}
41
- ```typescript lineNumbers
42
- import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
43
-
44
- class Point {
45
- constructor(public x: number, public y: number) {}
46
-
47
- distanceTo(other: Point): number {
48
- return Math.sqrt((this.x - other.x) ** 2 + (this.y - other.y) ** 2);
49
- }
50
-
51
- static [WORKFLOW_SERIALIZE](instance: Point) { // [!code highlight]
52
- return { x: instance.x, y: instance.y };
53
- }
54
-
55
- static [WORKFLOW_DESERIALIZE](data: { x: number; y: number }) { // [!code highlight]
56
- return new Point(data.x, data.y);
57
- }
58
- }
59
- ```
60
-
61
- Both methods must be **static**. `WORKFLOW_SERIALIZE` receives an instance and returns plain serializable data. `WORKFLOW_DESERIALIZE` receives that same data and reconstructs a new instance.
62
-
63
- <Callout type="warn">
64
- Both serialization methods run inside the workflow VM. They must not use Node.js APIs, non-deterministic operations, or network calls. Keep them focused on extracting and reconstructing data.
65
- </Callout>
66
-
67
- ## Automatic Class Registration
68
-
69
- For the runtime to deserialize a class, the class must be registered in a global registry with a stable `classId`. The SWC compiler plugin handles this automatically — when it detects a class with both `WORKFLOW_SERIALIZE` and `WORKFLOW_DESERIALIZE` static methods, it generates registration code at build time.
70
-
71
- This means you only need to implement the two symbol methods. The compiler assigns a deterministic `classId` based on the file path and class name, and registers it in the global `Symbol.for("workflow-class-registry")` registry.
72
-
73
- <Callout type="info">
74
- No manual registration is required for classes defined in your workflow files. The SWC plugin detects the serialization symbols and generates the registration automatically at build time.
75
- </Callout>
76
-
77
- ## Full Example: A Workflow-Safe Storage Client
78
-
79
- Here's a complete example of a storage client class that survives serialization across workflow boundaries. This pattern is useful when you need an object with methods to be passed as a workflow input or returned from a step.
80
-
81
- ```typescript lineNumbers
82
- import { WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from "@workflow/serde";
83
-
84
- interface StorageClientOptions {
85
- region: string;
86
- bucket: string;
87
- accessKeyId?: string;
88
- secretAccessKey?: string;
89
- }
90
-
91
- export class WorkflowStorageClient {
92
- private readonly region: string;
93
- private readonly bucket: string;
94
- private readonly accessKeyId?: string;
95
- private readonly secretAccessKey?: string;
96
-
97
- constructor(options: StorageClientOptions) {
98
- this.region = options.region;
99
- this.bucket = options.bucket;
100
- this.accessKeyId = options.accessKeyId;
101
- this.secretAccessKey = options.secretAccessKey;
102
- }
103
-
104
- async upload(key: string, body: Uint8Array) {
105
- "use step";
106
- const { S3Client, PutObjectCommand } = await import("@aws-sdk/client-s3");
107
- const client = new S3Client({
108
- region: this.region,
109
- credentials: this.accessKeyId
110
- ? { accessKeyId: this.accessKeyId, secretAccessKey: this.secretAccessKey! }
111
- : undefined,
112
- });
113
- await client.send(
114
- new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: body })
115
- );
116
- }
117
-
118
- async getSignedUrl(key: string): Promise<string> {
119
- "use step";
120
- const { S3Client, GetObjectCommand } = await import("@aws-sdk/client-s3");
121
- const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
122
- const client = new S3Client({ region: this.region });
123
- return getSignedUrl(client, new GetObjectCommand({ Bucket: this.bucket, Key: key }));
124
- }
125
-
126
- // --- Serialization protocol ---
127
-
128
- static [WORKFLOW_SERIALIZE](instance: WorkflowStorageClient): StorageClientOptions { // [!code highlight]
129
- return {
130
- region: instance.region,
131
- bucket: instance.bucket,
132
- accessKeyId: instance.accessKeyId,
133
- secretAccessKey: instance.secretAccessKey,
134
- };
135
- }
136
-
137
- static [WORKFLOW_DESERIALIZE]( // [!code highlight]
138
- data: StorageClientOptions
139
- ): WorkflowStorageClient {
140
- return new WorkflowStorageClient(data);
141
- }
142
- }
143
- ```
144
-
145
- Now this client can be passed into a workflow and used directly:
146
-
147
- ```typescript lineNumbers
148
- import { WorkflowStorageClient } from "./storage-client";
149
-
150
- export async function processUpload(
151
- client: WorkflowStorageClient,
152
- data: Uint8Array
153
- ) {
154
- "use workflow";
155
-
156
- // client is a real WorkflowStorageClient with working methods
157
- await client.upload("output/result.json", data); // [!code highlight]
158
- const url = await client.getSignedUrl("output/result.json"); // [!code highlight]
159
- return { url };
160
- }
161
- ```
162
-
163
- ## Key APIs
164
-
165
- - [`WORKFLOW_SERIALIZE`](/docs/api-reference/workflow-serde/workflow-serialize) — symbol for the static serialization method
166
- - [`WORKFLOW_DESERIALIZE`](/docs/api-reference/workflow-serde/workflow-deserialize) — symbol for the static deserialization method
167
- - [`"use step"`](/docs/api-reference/workflow/use-step) — marks a function for extraction and serialization
168
- - [`"use workflow"`](/docs/api-reference/workflow/use-workflow) — declares the orchestrator function
@@ -1,148 +0,0 @@
1
- ---
2
- title: Durable Objects
3
- description: Model long-lived stateful entities as workflows that persist state across requests.
4
- type: guide
5
- summary: Build a durable counter or session object whose state survives restarts by using a workflow's event log as the persistence layer.
6
- ---
7
-
8
- <Callout>
9
- This is an advanced guide. It dives into workflow internals and is not required reading to use workflow.
10
- </Callout>
11
-
12
- ## The Idea
13
-
14
- A workflow's event log already records every step result and replays them to reconstruct state. This is the same property that makes an "object" durable — its fields survive cold starts, crashes, and redeployments. Instead of using a workflow to model a *process*, you can use one to model an *entity* with methods.
15
-
16
- Each "method call" is a hook that the object's workflow loop awaits. External callers resume the hook with a payload describing the operation. The workflow applies the operation, updates its internal state, and waits for the next call.
17
-
18
- ## Pattern: Durable Counter
19
-
20
- A counter that persists its value without a database. Each increment/decrement is recorded in the event log.
21
-
22
- ```typescript lineNumbers
23
- import { defineHook, getWorkflowMetadata } from "workflow";
24
- import { z } from "zod";
25
-
26
- const counterAction = defineHook({ // [!code highlight]
27
- schema: z.object({
28
- type: z.enum(["increment", "decrement", "get"]),
29
- amount: z.number().default(1),
30
- }),
31
- });
32
-
33
- export async function durableCounter() {
34
- "use workflow";
35
-
36
- let count = 0;
37
- const { workflowRunId } = getWorkflowMetadata();
38
-
39
- while (true) {
40
- const hook = counterAction.create({ token: `counter:${workflowRunId}` });
41
- const action = await hook; // [!code highlight]
42
-
43
- switch (action.type) {
44
- case "increment":
45
- count += action.amount;
46
- await recordState(count);
47
- break;
48
- case "decrement":
49
- count -= action.amount;
50
- await recordState(count);
51
- break;
52
- case "get":
53
- await emitValue(count);
54
- break;
55
- }
56
- }
57
- }
58
-
59
- async function recordState(count: number) {
60
- "use step";
61
- // Step records the state transition in the event log.
62
- // On replay, the step result restores `count` without re-executing.
63
- return count;
64
- }
65
-
66
- async function emitValue(count: number) {
67
- "use step";
68
- return { count };
69
- }
70
- ```
71
-
72
- ### Calling the Object
73
-
74
- From an API route, resume the hook to "invoke a method" on the durable object:
75
-
76
- ```typescript lineNumbers
77
- import { resumeHook } from "workflow/api";
78
-
79
- export async function POST(request: Request) {
80
- const { runId, type, amount } = await request.json();
81
- await resumeHook(`counter:${runId}`, { type, amount }); // [!code highlight]
82
- return Response.json({ ok: true });
83
- }
84
- ```
85
-
86
- ## Pattern: Durable Session
87
-
88
- A chat session where conversation history is the durable state. Each user message is a hook event; the workflow accumulates messages and generates responses.
89
-
90
- ```typescript lineNumbers
91
- import { defineHook, getWritable, getWorkflowMetadata } from "workflow";
92
- import { DurableAgent } from "@workflow/ai/agent";
93
- import { anthropic } from "@workflow/ai/anthropic";
94
- import { z } from "zod";
95
- import type { UIMessageChunk, ModelMessage } from "ai";
96
-
97
- const messageHook = defineHook({ // [!code highlight]
98
- schema: z.object({
99
- role: z.literal("user"),
100
- content: z.string(),
101
- }),
102
- });
103
-
104
- export async function durableSession() {
105
- "use workflow";
106
-
107
- const writable = getWritable<UIMessageChunk>();
108
- const { workflowRunId: runId } = getWorkflowMetadata();
109
- const messages: ModelMessage[] = [];
110
-
111
- const agent = new DurableAgent({
112
- model: anthropic("claude-sonnet-4-20250514"),
113
- instructions: "You are a helpful assistant.",
114
- });
115
-
116
- while (true) {
117
- const hook = messageHook.create({ token: `session:${runId}` });
118
- const userMessage = await hook; // [!code highlight]
119
-
120
- messages.push({
121
- role: userMessage.role,
122
- content: userMessage.content,
123
- });
124
-
125
- await agent.stream({ messages, writable });
126
- }
127
- }
128
- ```
129
-
130
- ## When to Use This
131
-
132
- - **Entity-per-workflow**: Each user, document, or device gets its own workflow run. The run ID is the entity ID.
133
- - **No external database needed**: State lives in the event log. Reads replay from the log; writes append to it.
134
- - **Automatic consistency**: Only one execution runs at a time per workflow run, so there are no race conditions on the entity's state.
135
-
136
- ## Trade-offs
137
-
138
- - **Read latency**: Accessing current state requires replaying the event log (or caching the last known state in a step result).
139
- - **Not a replacement for databases**: If you need to query across entities (e.g., "all counters above 100"), you still need a database. Durable objects are for single-entity state.
140
- - **Log growth**: Long-lived objects accumulate large event logs. Consider periodic "snapshot" steps that checkpoint the full state.
141
-
142
- ## Key APIs
143
-
144
- - [`"use workflow"`](/docs/api-reference/workflow/use-workflow) — declares the orchestrator function
145
- - [`"use step"`](/docs/api-reference/workflow/use-step) — marks functions for durable execution
146
- - [`defineHook`](/docs/api-reference/workflow/define-hook) — type-safe hook for receiving external method calls
147
- - [`getWorkflowMetadata`](/docs/api-reference/workflow/get-workflow-metadata) — access the run ID for deterministic hook tokens
148
- - [`resumeHook`](/docs/api-reference/workflow-api/resume-hook) — invoke a method on the durable object from an API route
@@ -1,145 +0,0 @@
1
- ---
2
- title: Isomorphic Packages
3
- description: Publish reusable workflow packages that work both inside and outside the workflow runtime.
4
- type: guide
5
- summary: Use try/catch around getWorkflowMetadata, dynamic imports, and optional peer dependencies to build libraries that run in workflows and in plain Node.js.
6
- ---
7
-
8
- <Callout>
9
- This is an advanced guide. It dives into workflow internals and is not required reading to use workflow.
10
- </Callout>
11
-
12
- ## The Challenge
13
-
14
- If you're a library author publishing a package that integrates with workflow, your code needs to handle two environments:
15
-
16
- 1. **Inside a workflow run** — `getWorkflowMetadata()` works, `"use step"` directives are transformed, and the full workflow runtime is available.
17
- 2. **Outside a workflow** — your package is imported in a regular Node.js process, a test suite, or a project that doesn't use workflow at all.
18
-
19
- A hard dependency on `workflow` will crash at import time for users who don't have it installed.
20
-
21
- ## Pattern 1: Feature-Detect with `getWorkflowMetadata`
22
-
23
- Use a try/catch to detect whether you're running inside a workflow. This lets you add durable behavior when available and fall back to standard execution otherwise.
24
-
25
- ```typescript lineNumbers
26
- import { getWorkflowMetadata } from "workflow";
27
-
28
- export async function processPayment(amount: number, currency: string) {
29
- "use workflow";
30
-
31
- let runId: string | undefined;
32
- try {
33
- const metadata = getWorkflowMetadata(); // [!code highlight]
34
- runId = metadata.workflowRunId;
35
- } catch {
36
- // Not running inside a workflow — proceed without durability
37
- runId = undefined;
38
- }
39
-
40
- if (runId) {
41
- // Inside a workflow: use the run ID as an idempotency key
42
- return await chargeWithIdempotency(amount, currency, runId); // [!code highlight]
43
- } else {
44
- // Outside a workflow: standard charge
45
- return await chargeStandard(amount, currency);
46
- }
47
- }
48
-
49
- async function chargeWithIdempotency(amount: number, currency: string, idempotencyKey: string) {
50
- "use step";
51
- // Stripe charge with idempotency key from workflow run ID
52
- return { charged: true, amount, currency, idempotencyKey };
53
- }
54
-
55
- async function chargeStandard(amount: number, currency: string) {
56
- "use step";
57
- return { charged: true, amount, currency };
58
- }
59
- ```
60
-
61
- ## Pattern 2: Dynamic Imports
62
-
63
- Avoid importing `workflow` at the top level. Use dynamic `import()` so the module is only loaded when actually needed.
64
-
65
- ```typescript lineNumbers
66
- export async function createDurableTask(name: string, payload: unknown) {
67
- "use workflow";
68
-
69
- let sleep: ((duration: string) => Promise<void>) | undefined;
70
-
71
- try {
72
- const wf = await import("workflow"); // [!code highlight]
73
- sleep = wf.sleep;
74
- } catch {
75
- // workflow not installed — use setTimeout fallback
76
- sleep = undefined;
77
- }
78
-
79
- await executeTask(name, payload);
80
-
81
- if (sleep) {
82
- // Inside workflow: durable sleep that survives restarts
83
- await sleep("5m"); // [!code highlight]
84
- } else {
85
- // Outside workflow: plain timer (not durable)
86
- await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000));
87
- }
88
-
89
- await sendNotification(name);
90
- }
91
-
92
- async function executeTask(name: string, payload: unknown) {
93
- "use step";
94
- return { executed: true, name, payload };
95
- }
96
-
97
- async function sendNotification(name: string) {
98
- "use step";
99
- return { notified: true, name };
100
- }
101
- ```
102
-
103
- ## Pattern 3: Optional Peer Dependencies
104
-
105
- In your `package.json`, declare `workflow` as an optional peer dependency. This signals to package managers that your library *can* use workflow but doesn't require it.
106
-
107
- ```json
108
- {
109
- "name": "@acme/payments",
110
- "peerDependencies": {
111
- "workflow": ">=1.0.0"
112
- },
113
- "peerDependenciesMeta": {
114
- "workflow": {
115
- "optional": true
116
- }
117
- }
118
- }
119
- ```
120
-
121
- Then guard all workflow imports with dynamic `import()` and try/catch as shown above.
122
-
123
- ## Real-World Examples
124
-
125
- ### Mux AI
126
-
127
- The Mux team published a reusable workflow package for video processing. Their library detects the workflow runtime and falls back to standard async processing when workflow isn't available.
128
-
129
- ### World ID
130
-
131
- World ID's identity verification library uses `getWorkflowMetadata()` to attach run IDs to their human-in-the-loop verification hooks, but the same library works in non-workflow environments for simple verification flows.
132
-
133
- ## Guidelines for Library Authors
134
-
135
- 1. **Never hard-import `workflow` at the top level** if your package should work without it.
136
- 2. **Use `getWorkflowMetadata()` in a try/catch** as the canonical runtime detection pattern.
137
- 3. **Mark `workflow` as an optional peer dependency** in `package.json`.
138
- 4. **Test both paths**: run your test suite with and without the workflow runtime to catch import errors.
139
- 5. **Document the dual behavior**: make it clear in your README which features require workflow and which work standalone.
140
-
141
- ## Key APIs
142
-
143
- - [`"use workflow"`](/docs/api-reference/workflow/use-workflow) — declares the orchestrator function
144
- - [`"use step"`](/docs/api-reference/workflow/use-step) — marks functions for durable execution
145
- - [`getWorkflowMetadata`](/docs/api-reference/workflow/get-workflow-metadata) — runtime detection and run ID access