alchemy 2.0.0-beta.35 → 2.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.
Files changed (222) hide show
  1. package/bin/exec.js +176 -26
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/Lambda/Function.d.ts.map +1 -1
  4. package/lib/AWS/Lambda/Function.js +22 -2
  5. package/lib/AWS/Lambda/Function.js.map +1 -1
  6. package/lib/Apply.d.ts.map +1 -1
  7. package/lib/Apply.js +6 -1
  8. package/lib/Apply.js.map +1 -1
  9. package/lib/Cli/main.d.ts.map +1 -1
  10. package/lib/Cli/main.js +2 -0
  11. package/lib/Cli/main.js.map +1 -1
  12. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts +54 -0
  13. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts.map +1 -0
  14. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js +17 -0
  15. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js.map +1 -0
  16. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts +37 -0
  17. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts.map +1 -0
  18. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js +45 -0
  19. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js.map +1 -0
  20. package/lib/Cloudflare/AnalyticsEngine/index.d.ts +3 -0
  21. package/lib/Cloudflare/AnalyticsEngine/index.d.ts.map +1 -0
  22. package/lib/Cloudflare/AnalyticsEngine/index.js +3 -0
  23. package/lib/Cloudflare/AnalyticsEngine/index.js.map +1 -0
  24. package/lib/Cloudflare/D1/D1Connection.d.ts +47 -3
  25. package/lib/Cloudflare/D1/D1Connection.d.ts.map +1 -1
  26. package/lib/Cloudflare/D1/D1Connection.js +62 -5
  27. package/lib/Cloudflare/D1/D1Connection.js.map +1 -1
  28. package/lib/Cloudflare/Images/Images.d.ts +103 -0
  29. package/lib/Cloudflare/Images/Images.d.ts.map +1 -0
  30. package/lib/Cloudflare/Images/Images.js +82 -0
  31. package/lib/Cloudflare/Images/Images.js.map +1 -0
  32. package/lib/Cloudflare/Images/ImagesBinding.d.ts +73 -0
  33. package/lib/Cloudflare/Images/ImagesBinding.d.ts.map +1 -0
  34. package/lib/Cloudflare/Images/ImagesBinding.js +83 -0
  35. package/lib/Cloudflare/Images/ImagesBinding.js.map +1 -0
  36. package/lib/Cloudflare/Images/index.d.ts +3 -0
  37. package/lib/Cloudflare/Images/index.d.ts.map +1 -0
  38. package/lib/Cloudflare/Images/index.js +3 -0
  39. package/lib/Cloudflare/Images/index.js.map +1 -0
  40. package/lib/Cloudflare/Local/SidecarHandlers.js +9 -1
  41. package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
  42. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  43. package/lib/Cloudflare/Providers.js +6 -1
  44. package/lib/Cloudflare/Providers.js.map +1 -1
  45. package/lib/Cloudflare/R2/R2Bucket.d.ts +135 -0
  46. package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
  47. package/lib/Cloudflare/R2/R2Bucket.js +146 -7
  48. package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
  49. package/lib/Cloudflare/StateStore/Api.d.ts +6 -1
  50. package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
  51. package/lib/Cloudflare/StateStore/Api.js +23 -1
  52. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  53. package/lib/Cloudflare/StateStore/Store.d.ts +16 -1
  54. package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -1
  55. package/lib/Cloudflare/StateStore/Store.js +22 -5
  56. package/lib/Cloudflare/StateStore/Store.js.map +1 -1
  57. package/lib/Cloudflare/Workers/CronEventSource.d.ts +35 -0
  58. package/lib/Cloudflare/Workers/CronEventSource.d.ts.map +1 -0
  59. package/lib/Cloudflare/Workers/CronEventSource.js +55 -0
  60. package/lib/Cloudflare/Workers/CronEventSource.js.map +1 -0
  61. package/lib/Cloudflare/Workers/InferEnv.d.ts +37 -2
  62. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  63. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  64. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +2 -1
  65. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  66. package/lib/Cloudflare/Workers/Rpc.d.ts +76 -3
  67. package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
  68. package/lib/Cloudflare/Workers/Rpc.js +197 -13
  69. package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
  70. package/lib/Cloudflare/Workers/Worker.d.ts +26 -6
  71. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  72. package/lib/Cloudflare/Workers/Worker.js +183 -41
  73. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  74. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  75. package/lib/Cloudflare/Workers/WorkerBundle.js +15 -5
  76. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  77. package/lib/Cloudflare/Workers/Workflow.d.ts +50 -47
  78. package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
  79. package/lib/Cloudflare/Workers/Workflow.js +41 -35
  80. package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
  81. package/lib/Cloudflare/Workers/index.d.ts +1 -0
  82. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  83. package/lib/Cloudflare/Workers/index.js +1 -0
  84. package/lib/Cloudflare/Workers/index.js.map +1 -1
  85. package/lib/Cloudflare/index.d.ts +2 -0
  86. package/lib/Cloudflare/index.d.ts.map +1 -1
  87. package/lib/Cloudflare/index.js +2 -0
  88. package/lib/Cloudflare/index.js.map +1 -1
  89. package/lib/Deploy.d.ts +4 -1
  90. package/lib/Deploy.d.ts.map +1 -1
  91. package/lib/Deploy.js +2 -2
  92. package/lib/Deploy.js.map +1 -1
  93. package/lib/Destroy.d.ts +4 -1
  94. package/lib/Destroy.d.ts.map +1 -1
  95. package/lib/Destroy.js +2 -2
  96. package/lib/Destroy.js.map +1 -1
  97. package/lib/Output.d.ts +45 -5
  98. package/lib/Output.d.ts.map +1 -1
  99. package/lib/Output.js +100 -4
  100. package/lib/Output.js.map +1 -1
  101. package/lib/Plan.d.ts.map +1 -1
  102. package/lib/Plan.js +93 -12
  103. package/lib/Plan.js.map +1 -1
  104. package/lib/Platform.d.ts.map +1 -1
  105. package/lib/Platform.js +16 -3
  106. package/lib/Platform.js.map +1 -1
  107. package/lib/Ref.d.ts +4 -1
  108. package/lib/Ref.d.ts.map +1 -1
  109. package/lib/Ref.js +1 -1
  110. package/lib/Ref.js.map +1 -1
  111. package/lib/Resource.d.ts +8 -0
  112. package/lib/Resource.d.ts.map +1 -1
  113. package/lib/Resource.js +10 -0
  114. package/lib/Resource.js.map +1 -1
  115. package/lib/RuntimeContext.d.ts +3 -1
  116. package/lib/RuntimeContext.d.ts.map +1 -1
  117. package/lib/RuntimeContext.js.map +1 -1
  118. package/lib/Secret.d.ts +50 -0
  119. package/lib/Secret.d.ts.map +1 -0
  120. package/lib/Secret.js +23 -0
  121. package/lib/Secret.js.map +1 -0
  122. package/lib/Serverless/Function.d.ts +9 -1
  123. package/lib/Serverless/Function.d.ts.map +1 -1
  124. package/lib/Stack.d.ts +14 -5
  125. package/lib/Stack.d.ts.map +1 -1
  126. package/lib/Stack.js +31 -19
  127. package/lib/Stack.js.map +1 -1
  128. package/lib/State/HttpStateApi.d.ts +15 -1
  129. package/lib/State/HttpStateApi.d.ts.map +1 -1
  130. package/lib/State/HttpStateApi.js +11 -0
  131. package/lib/State/HttpStateApi.js.map +1 -1
  132. package/lib/State/HttpStateStore.d.ts.map +1 -1
  133. package/lib/State/HttpStateStore.js +11 -0
  134. package/lib/State/HttpStateStore.js.map +1 -1
  135. package/lib/State/InMemoryState.d.ts +2 -2
  136. package/lib/State/InMemoryState.d.ts.map +1 -1
  137. package/lib/State/InMemoryState.js +12 -6
  138. package/lib/State/InMemoryState.js.map +1 -1
  139. package/lib/State/LocalState.d.ts.map +1 -1
  140. package/lib/State/LocalState.js +11 -2
  141. package/lib/State/LocalState.js.map +1 -1
  142. package/lib/State/State.d.ts +21 -0
  143. package/lib/State/State.d.ts.map +1 -1
  144. package/lib/Test/Bun.d.ts.map +1 -1
  145. package/lib/Test/Bun.js +30 -7
  146. package/lib/Test/Bun.js.map +1 -1
  147. package/lib/Test/Core.d.ts +17 -2
  148. package/lib/Test/Core.d.ts.map +1 -1
  149. package/lib/Test/Core.js +29 -11
  150. package/lib/Test/Core.js.map +1 -1
  151. package/lib/Test/Vitest.d.ts.map +1 -1
  152. package/lib/Test/Vitest.js +26 -5
  153. package/lib/Test/Vitest.js.map +1 -1
  154. package/lib/Util/LocalhostDns.d.ts +23 -0
  155. package/lib/Util/LocalhostDns.d.ts.map +1 -0
  156. package/lib/Util/LocalhostDns.js +46 -0
  157. package/lib/Util/LocalhostDns.js.map +1 -0
  158. package/lib/Util/PlatformServices.d.ts.map +1 -1
  159. package/lib/Util/PlatformServices.js +2 -0
  160. package/lib/Util/PlatformServices.js.map +1 -1
  161. package/lib/Util/effect.d.ts +7 -0
  162. package/lib/Util/effect.d.ts.map +1 -1
  163. package/lib/Util/effect.js +6 -0
  164. package/lib/Util/effect.js.map +1 -1
  165. package/lib/Variable.d.ts +29 -0
  166. package/lib/Variable.d.ts.map +1 -0
  167. package/lib/Variable.js +9 -0
  168. package/lib/Variable.js.map +1 -0
  169. package/lib/index.d.ts +3 -0
  170. package/lib/index.d.ts.map +1 -1
  171. package/lib/index.js +3 -0
  172. package/lib/index.js.map +1 -1
  173. package/lib/tsconfig.test.tsbuildinfo +1 -1
  174. package/package.json +7 -7
  175. package/src/AWS/Lambda/Function.ts +30 -2
  176. package/src/Apply.ts +8 -1
  177. package/src/Cli/main.ts +3 -0
  178. package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.ts +82 -0
  179. package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.ts +106 -0
  180. package/src/Cloudflare/AnalyticsEngine/index.ts +2 -0
  181. package/src/Cloudflare/D1/D1Connection.ts +95 -12
  182. package/src/Cloudflare/Images/Images.ts +121 -0
  183. package/src/Cloudflare/Images/ImagesBinding.ts +192 -0
  184. package/src/Cloudflare/Images/index.ts +2 -0
  185. package/src/Cloudflare/Local/SidecarHandlers.ts +9 -1
  186. package/src/Cloudflare/Providers.ts +8 -0
  187. package/src/Cloudflare/R2/R2Bucket.ts +271 -7
  188. package/src/Cloudflare/StateStore/Api.ts +34 -1
  189. package/src/Cloudflare/StateStore/Store.ts +40 -10
  190. package/src/Cloudflare/Workers/CronEventSource.ts +89 -0
  191. package/src/Cloudflare/Workers/InferEnv.ts +94 -16
  192. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +9 -1
  193. package/src/Cloudflare/Workers/Rpc.ts +227 -18
  194. package/src/Cloudflare/Workers/Worker.ts +295 -62
  195. package/src/Cloudflare/Workers/WorkerBundle.ts +15 -5
  196. package/src/Cloudflare/Workers/Workflow.ts +74 -70
  197. package/src/Cloudflare/Workers/index.ts +1 -0
  198. package/src/Cloudflare/index.ts +2 -0
  199. package/src/Deploy.ts +5 -1
  200. package/src/Destroy.ts +5 -1
  201. package/src/Output.ts +121 -12
  202. package/src/Plan.ts +101 -12
  203. package/src/Platform.ts +19 -4
  204. package/src/Ref.ts +10 -5
  205. package/src/Resource.ts +22 -0
  206. package/src/RuntimeContext.ts +1 -0
  207. package/src/Secret.ts +87 -0
  208. package/src/Serverless/Function.ts +10 -1
  209. package/src/Stack.ts +52 -29
  210. package/src/State/HttpStateApi.ts +21 -0
  211. package/src/State/HttpStateStore.ts +21 -0
  212. package/src/State/InMemoryState.ts +29 -8
  213. package/src/State/LocalState.ts +28 -4
  214. package/src/State/State.ts +21 -0
  215. package/src/Test/Bun.ts +43 -7
  216. package/src/Test/Core.ts +29 -8
  217. package/src/Test/Vitest.ts +36 -4
  218. package/src/Util/LocalhostDns.ts +52 -0
  219. package/src/Util/PlatformServices.ts +2 -0
  220. package/src/Util/effect.ts +25 -0
  221. package/src/Variable.ts +44 -0
  222. package/src/index.ts +3 -0
@@ -50,12 +50,21 @@ export class WorkflowStep extends Context.Service<
50
50
  * Execute a named, durable workflow step. The effect is run inside the
51
51
  * Cloudflare step transaction so its result is automatically persisted
52
52
  * and replayed on retries.
53
+ *
54
+ * Any services the inner effect requires (e.g. `WorkerEnvironment` from a
55
+ * binding like `kv.put` / `kv.get`) are threaded through automatically by
56
+ * capturing the surrounding workflow body's context and providing it to
57
+ * the inner effect before it runs inside `step.do`.
53
58
  */
54
- export const task = <T>(
59
+ export const task = <T, R = never>(
55
60
  name: string,
56
- effect: Effect.Effect<T>,
57
- ): Effect.Effect<T, never, WorkflowStep> =>
58
- WorkflowStep.asEffect().pipe(Effect.flatMap((step) => step.do(name, effect)));
61
+ effect: Effect.Effect<T, never, R>,
62
+ ): Effect.Effect<T, never, WorkflowStep | R> =>
63
+ Effect.gen(function* () {
64
+ const step = yield* WorkflowStep;
65
+ const context = yield* Effect.context<R>();
66
+ return yield* step.do(name, effect.pipe(Effect.provide(context)));
67
+ });
59
68
 
60
69
  /**
61
70
  * Pause the workflow for the given duration.
@@ -103,20 +112,17 @@ export type WorkflowServices = WorkerServices | PlatformServices;
103
112
  */
104
113
  export interface WorkflowExport {
105
114
  readonly kind: "workflow";
106
- readonly make: (
107
- env: unknown,
108
- ) => Effect.Effect<Effect.Effect<unknown, never, WorkflowRunServices>>;
115
+ readonly make: (env: unknown) => Effect.Effect<WorkflowImpl<any, any>>;
109
116
  }
110
117
 
111
118
  /**
112
- * A workflow body is an Effect that requires WorkflowRunServices
113
- * (event + step) to execute.
119
+ * A workflow implementation is a function from a typed `Input` payload to
120
+ * an Effect that produces the workflow's `Result`. The Effect requires
121
+ * `WorkflowRunServices` (event + step + env) to execute.
114
122
  */
115
- export type WorkflowBody<Result = unknown> = Effect.Effect<
116
- Result,
117
- never,
118
- WorkflowRunServices
119
- >;
123
+ export type WorkflowImpl<Input = unknown, Result = unknown> = (
124
+ input: Input,
125
+ ) => Effect.Effect<Result, never, WorkflowRunServices>;
120
126
 
121
127
  export const isWorkflowExport = (value: unknown): value is WorkflowExport =>
122
128
  typeof value === "object" &&
@@ -141,24 +147,24 @@ export const isWorkflowBinding = (binding: {
141
147
  * Handle returned to the caller at deploy/bind time. Allows starting
142
148
  * workflow instances and checking their status from the Api layer.
143
149
  */
144
- export interface WorkflowHandle<Params = unknown> {
150
+ export interface WorkflowHandle<Input = unknown, Result = unknown> {
145
151
  Type: WorkflowTypeId;
146
152
  name: string;
147
- create(params?: Params): Effect.Effect<WorkflowInstance>;
148
- get(instanceId: string): Effect.Effect<WorkflowInstance>;
153
+ create(input: Input): Effect.Effect<WorkflowInstance<Result>>;
154
+ get(instanceId: string): Effect.Effect<WorkflowInstance<Result>>;
149
155
  }
150
156
 
151
- export interface WorkflowInstance {
157
+ export interface WorkflowInstance<Result = unknown> {
152
158
  id: string;
153
- status(): Effect.Effect<WorkflowInstanceStatus>;
159
+ status(): Effect.Effect<WorkflowInstanceStatus<Result>>;
154
160
  pause(): Effect.Effect<void>;
155
161
  resume(): Effect.Effect<void>;
156
162
  terminate(): Effect.Effect<void>;
157
163
  }
158
164
 
159
- export interface WorkflowInstanceStatus {
165
+ export interface WorkflowInstanceStatus<Result = unknown> {
160
166
  status: string;
161
- output?: unknown;
167
+ output?: Result;
162
168
  error?: { name: string; message: string } | null;
163
169
  }
164
170
 
@@ -168,22 +174,22 @@ export interface WorkflowClass extends Effect.Effect<
168
174
  WorkflowHandle
169
175
  > {
170
176
  <_Self>(): {
171
- <Result = unknown, InitReq = never>(
177
+ <Input = unknown, Result = unknown, InitReq = never>(
172
178
  name: string,
173
- impl: Effect.Effect<WorkflowBody<Result>, never, InitReq>,
179
+ impl: Effect.Effect<WorkflowImpl<Input, Result>, never, InitReq>,
174
180
  ): Effect.Effect<
175
- WorkflowHandle,
181
+ WorkflowHandle<Input, Result>,
176
182
  never,
177
183
  Worker | Exclude<InitReq, WorkflowServices>
178
184
  > & {
179
- new (_: never): WorkflowBody<Result>;
185
+ new (_: never): WorkflowImpl<Input, Result>;
180
186
  };
181
187
  };
182
- <Result = unknown, InitReq = never>(
188
+ <Input = unknown, Result = unknown, InitReq = never>(
183
189
  name: string,
184
- impl: Effect.Effect<WorkflowBody<Result>, never, InitReq>,
190
+ impl: Effect.Effect<WorkflowImpl<Input, Result>, never, InitReq>,
185
191
  ): Effect.Effect<
186
- WorkflowHandle,
192
+ WorkflowHandle<Input, Result>,
187
193
  never,
188
194
  Worker | Exclude<InitReq, WorkflowServices>
189
195
  >;
@@ -200,18 +206,18 @@ export class WorkflowScope extends Context.Service<
200
206
  *
201
207
  * A Workflow follows the same two-phase pattern as Workers and Durable
202
208
  * Objects. The outer `Effect.gen` resolves shared dependencies. The inner
203
- * `Effect.gen` is the workflow body — it reads the triggering event and
204
- * runs steps using `task`, `sleep`, and `sleepUntil`.
209
+ * `Effect.fn` is the workflow body — a function from a typed `input`
210
+ * payload to an Effect that runs steps using `task`, `sleep`, and
211
+ * `sleepUntil`.
205
212
  *
206
213
  * ```typescript
207
214
  * Effect.gen(function* () {
208
215
  * // Phase 1: resolve dependencies
209
216
  * const notifier = yield* NotificationService;
210
217
  *
211
- * return Effect.gen(function* () {
218
+ * return Effect.fn(function* (input: { orderId: string }) {
212
219
  * // Phase 2: workflow body (durable steps)
213
- * const event = yield* Cloudflare.WorkflowEvent;
214
- * const result = yield* Cloudflare.task("process", doWork(event.payload));
220
+ * const result = yield* Cloudflare.task("process", doWork(input.orderId));
215
221
  * yield* Cloudflare.sleep("cooldown", "10 seconds");
216
222
  * return result;
217
223
  * });
@@ -226,9 +232,8 @@ export class WorkflowScope extends Context.Service<
226
232
  * export default class MyWorkflow extends Cloudflare.Workflow<MyWorkflow>()(
227
233
  * "MyWorkflow",
228
234
  * Effect.gen(function* () {
229
- * return Effect.gen(function* () {
230
- * const event = yield* Cloudflare.WorkflowEvent;
231
- * return { received: event.payload };
235
+ * return Effect.fn(function* (input: { name: string }) {
236
+ * return { received: input.name };
232
237
  * });
233
238
  * }),
234
239
  * ) {}
@@ -248,33 +253,31 @@ export class WorkflowScope extends Context.Service<
248
253
  * yield* Cloudflare.sleep("cooldown", "30 seconds");
249
254
  * ```
250
255
  *
251
- * @example Accessing env bindings inside a step
252
- * `Cloudflare.WorkerEnvironment` is the same service available
253
- * inside a Worker — it gives you typed access to env bindings (KV,
254
- * R2, etc.) from inside a workflow body. Wrap any side-effecting
255
- * call in `task` so the result is persisted across replays.
256
+ * @example Accessing env bindings inside a task
257
+ * Bind a resource (e.g. `KVNamespace`, `R2Bucket`) in the workflow's
258
+ * outer init phase to get a typed Effect-native client, then use it
259
+ * directly inside `task`. `task` threads the binding's service
260
+ * requirement (`WorkerEnvironment`) through automatically so the inner
261
+ * Effect needs no extra plumbing.
256
262
  *
257
263
  * ```typescript
258
264
  * Effect.gen(function* () {
259
- * const env = yield* Cloudflare.WorkerEnvironment;
260
- * const event = yield* Cloudflare.WorkflowEvent;
261
- * const { roomId, message } = event.payload as {
262
- * roomId: string;
263
- * message: string;
264
- * };
265
+ * const kv = yield* Cloudflare.KVNamespace.bind(KV);
266
+ *
267
+ * return Effect.fn(function* (input: { roomId: string; message: string }) {
268
+ * const { roomId, message } = input;
265
269
  *
266
- * const stored = yield* Cloudflare.task(
267
- * "kv-roundtrip",
268
- * Effect.tryPromise({
269
- * try: async () => {
270
- * await env.KV.put(`workflow:${roomId}`, message);
271
- * return await env.KV.get(`workflow:${roomId}`);
272
- * },
273
- * catch: (cause) =>
274
- * cause instanceof Error ? cause : new Error(String(cause)),
275
- * }).pipe(Effect.orDie),
276
- * );
277
- * return stored;
270
+ * const stored = yield* Cloudflare.task(
271
+ * "kv-roundtrip",
272
+ * Effect.gen(function* () {
273
+ * const key = `workflow:${roomId}`;
274
+ * yield* kv.put(key, message);
275
+ * return yield* kv.get(key);
276
+ * }).pipe(Effect.orDie),
277
+ * );
278
+ *
279
+ * return stored;
280
+ * });
278
281
  * });
279
282
  * ```
280
283
  *
@@ -357,7 +360,7 @@ export class WorkflowScope extends Context.Service<
357
360
  * ```
358
361
  */
359
362
  export const Workflow: WorkflowClass = taggedFunction(WorkflowScope, ((
360
- ...args: [] | [name: string, impl: Effect.Effect<WorkflowBody>]
363
+ ...args: [] | [name: string, impl: Effect.Effect<WorkflowImpl<any, any>>]
361
364
  ) =>
362
365
  args.length === 0
363
366
  ? Workflow
@@ -404,11 +407,11 @@ export const Workflow: WorkflowClass = taggedFunction(WorkflowScope, ((
404
407
  }),
405
408
  );
406
409
 
407
- const self: WorkflowHandle = {
410
+ const self: WorkflowHandle<any, any> = {
408
411
  Type: WorkflowTypeId,
409
412
  name,
410
- create: (params?: unknown) =>
411
- Effect.tryPromise(() => binding.create({ params })).pipe(
413
+ create: (input: unknown) =>
414
+ Effect.tryPromise(() => binding.create({ params: input })).pipe(
412
415
  Effect.map(wrapInstance),
413
416
  Effect.orDie,
414
417
  ),
@@ -419,21 +422,22 @@ export const Workflow: WorkflowClass = taggedFunction(WorkflowScope, ((
419
422
  ),
420
423
  };
421
424
 
422
- const body = yield* impl.pipe(
425
+ const fn = yield* impl.pipe(
423
426
  Effect.provideService(WorkflowScope, self as any),
424
427
  );
425
428
 
426
429
  yield* worker.export(name, {
427
430
  kind: "workflow",
428
431
  make: (env: unknown) =>
429
- Effect.succeed(
430
- body.pipe(
432
+ Effect.succeed(((input: unknown) =>
433
+ fn(input).pipe(
431
434
  Effect.provideService(
432
435
  WorkerEnvironment,
433
436
  env as Record<string, any>,
434
437
  ),
435
- ),
436
- ).pipe(Effect.provideContext(services)),
438
+ )) as WorkflowImpl<any, any>).pipe(
439
+ Effect.provideContext(services),
440
+ ),
437
441
  } satisfies WorkflowExport);
438
442
 
439
443
  return self;
@@ -520,13 +524,13 @@ export const WorkflowProvider = () =>
520
524
  // Helpers
521
525
  // ---------------------------------------------------------------------------
522
526
 
523
- const wrapInstance = (raw: any): WorkflowInstance => ({
527
+ const wrapInstance = <Result>(raw: any): WorkflowInstance<Result> => ({
524
528
  id: raw.id,
525
529
  status: () =>
526
530
  Effect.tryPromise(() => raw.status()).pipe(
527
531
  Effect.map((s: any) => ({
528
532
  status: s.status as string,
529
- output: s.output,
533
+ output: s.output as Result,
530
534
  error: s.error,
531
535
  })),
532
536
  Effect.orDie,
@@ -1,5 +1,6 @@
1
1
  export * from "./Assets.ts";
2
2
  export * from "./ConfigProvider.ts";
3
+ export * from "./CronEventSource.ts";
3
4
  export * from "./DurableObjectNamespace.ts";
4
5
  export * from "./DurableObjectState.ts";
5
6
  export * from "./DurableObjectStorage.ts";
@@ -1,11 +1,13 @@
1
1
  export * from "./ApiToken/index.ts";
2
2
  export * from "./Artifacts/index.ts";
3
3
  export * from "./AiGateway/index.ts";
4
+ export * from "./AnalyticsEngine/index.ts";
4
5
  export * from "./CloudflareEnvironment.ts";
5
6
  export * from "./Container/index.ts";
6
7
  export * from "./D1/index.ts";
7
8
  export * from "./EdgeSession.ts";
8
9
  export * from "./Hyperdrive/index.ts";
10
+ export * from "./Images/index.ts";
9
11
  export * from "./KV/index.ts";
10
12
  export * from "./Providers.ts";
11
13
  export * from "./Queue/index.ts";
package/src/Deploy.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as Effect from "effect/Effect";
2
+ import type * as Scope from "effect/Scope";
2
3
  import { AlchemyContext } from "./AlchemyContext.ts";
3
4
  import * as Apply from "./Apply.ts";
4
5
  import type { Input } from "./Input.ts";
@@ -10,10 +11,13 @@ export const deploy = <A>({
10
11
  stack,
11
12
  stage,
12
13
  dev,
14
+ scope,
13
15
  }: {
14
16
  stack: StackEffect<CompiledStack<A>, Stage | AlchemyContext>;
15
17
  stage: string;
16
18
  dev?: boolean;
19
+ /** See {@link evalStack} — when set, scoped resources outlive `deploy`. */
20
+ scope?: Scope.Scope;
17
21
  }) =>
18
22
  evalStack(
19
23
  stack,
@@ -23,5 +27,5 @@ export const deploy = <A>({
23
27
  const output = yield* Apply.apply(plan);
24
28
  return output as Input.Resolve<A>;
25
29
  }),
26
- { stage, dev },
30
+ { stage, dev, scope },
27
31
  );
package/src/Destroy.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Effect } from "effect";
2
+ import type * as Scope from "effect/Scope";
2
3
  import type { AlchemyContext } from "./AlchemyContext.ts";
3
4
  import * as Apply from "./Apply.ts";
4
5
  import * as Plan from "./Plan.ts";
@@ -10,10 +11,13 @@ export const destroy = ({
10
11
  stack,
11
12
  stage,
12
13
  dev,
14
+ scope,
13
15
  }: {
14
16
  stack: StackEffect<CompiledStack, Stage | AlchemyContext>;
15
17
  stage: string;
16
18
  dev?: boolean;
19
+ /** See {@link evalStack} — when set, scoped resources outlive `destroy`. */
20
+ scope?: Scope.Scope;
17
21
  }) =>
18
22
  evalStack(
19
23
  stack,
@@ -26,5 +30,5 @@ export const destroy = ({
26
30
  bindings: {},
27
31
  output: {},
28
32
  }).pipe(Effect.flatMap(Apply.apply)),
29
- { stage, dev },
33
+ { stage, dev, scope },
30
34
  );
package/src/Output.ts CHANGED
@@ -5,7 +5,7 @@ import { pipe } from "effect/Function";
5
5
  import type { Pipeable } from "effect/Pipeable";
6
6
  import * as Redacted from "effect/Redacted";
7
7
  import { SingleShotGen } from "effect/Utils";
8
- import { getRefMetadata, isRef, ref as stageRef, type Ref } from "./Ref.ts";
8
+ import { getRefMetadata, isRef, type Ref } from "./Ref.ts";
9
9
  import { isResource, type Resource, type ResourceLike } from "./Resource.ts";
10
10
  import { RuntimeContext } from "./RuntimeContext.ts";
11
11
  import { Stack } from "./Stack.ts";
@@ -82,9 +82,11 @@ export type Expr<A = any, Req = any> =
82
82
  | ApplyExpr<any, A, Req>
83
83
  | EffectExpr<any, A, Req>
84
84
  | LiteralExpr<A>
85
+ | NamedExpr<A, Req>
85
86
  | PropExpr<A, keyof A, Req>
86
87
  | ResourceExpr<A, Req>
87
- | RefExpr<A>;
88
+ | RefExpr<A>
89
+ | StackRefExpr<A>;
88
90
 
89
91
  export abstract class BaseExpr<A = any, Req = any> implements Output<A, Req> {
90
92
  declare readonly kind: any;
@@ -255,6 +257,36 @@ export class EffectExpr<A, B, Req = never, Req2 = never> extends BaseExpr<
255
257
  }
256
258
  }
257
259
 
260
+ export const isNamedExpr = <A = any, Req = any>(
261
+ node: any,
262
+ ): node is NamedExpr<A, Req> => node?.kind === "NamedExpr";
263
+
264
+ /**
265
+ * Wraps another `Expr` and overrides its `toString()` / inspect output.
266
+ *
267
+ * `BaseExpr.asEffect()` derives the binding id from `this.toString()`, so
268
+ * wrapping an expression in `NamedExpr` makes that derived id stable and
269
+ * caller-controlled (e.g. an env var name like `"API_KEY"`).
270
+ */
271
+ export class NamedExpr<A, Req = never> extends BaseExpr<A, Req> {
272
+ readonly kind = "NamedExpr";
273
+ constructor(
274
+ public readonly expr: Expr<A, Req>,
275
+ public readonly bindingName: string,
276
+ ) {
277
+ super();
278
+ return proxy(this);
279
+ }
280
+ [inspect](): string {
281
+ return this.bindingName;
282
+ }
283
+ }
284
+
285
+ export const named = <A, Req>(
286
+ expr: Output<A, Req>,
287
+ name: string,
288
+ ): Output<A, Req> => new NamedExpr(expr as Expr<A, Req>, name) as any;
289
+
258
290
  export const all = <Outs extends (Output | Expr)[]>(...outs: Outs) =>
259
291
  new AllExpr(outs as any) as unknown as All<Outs>;
260
292
 
@@ -309,6 +341,48 @@ export class RefExpr<A> extends BaseExpr<A, never> {
309
341
  }
310
342
  }
311
343
 
344
+ export const isStackRefExpr = <A = any>(node: any): node is StackRefExpr<A> =>
345
+ node?.kind === "StackRefExpr";
346
+
347
+ /**
348
+ * A reference to the persisted output of a Stack at `(stack, stage)`.
349
+ *
350
+ * Resolved at evaluation time by reading `state.getOutput({ stack,
351
+ * stage })`. Distinct from {@link RefExpr}, which references a
352
+ * single resource's attributes within a stack/stage. `stage` may be
353
+ * `undefined`, in which case it falls back to the current stage.
354
+ */
355
+ export class StackRefExpr<A> extends BaseExpr<A, never> {
356
+ readonly kind = "StackRefExpr";
357
+ constructor(
358
+ public readonly stack: string,
359
+ public readonly stage: string | undefined,
360
+ ) {
361
+ super();
362
+ return proxy(this);
363
+ }
364
+ [inspect](): string {
365
+ return `stackRef(${this.stack}${
366
+ this.stage ? `, { stage: ${this.stage} }` : ""
367
+ })`;
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Build an `Output<A>` referencing the persisted output of another
373
+ * Stack. The returned Effect resolves to a lazy `Output<A>` whose
374
+ * value is read from the state store at plan/apply time.
375
+ *
376
+ * Returns `Effect<Output<A>>` (not `Output<A>` directly) so that
377
+ * `yield* Output.stackRef(...)` reads ergonomically inside an Effect
378
+ * generator and lines up with `Resource.ref` and `Stack.stage.<name>`.
379
+ */
380
+ export const stackRef = <A>(
381
+ stack: string,
382
+ options: { stage?: string } = {},
383
+ ): Effect.Effect<Output<A, never>> =>
384
+ Effect.succeed(new StackRefExpr<A>(stack, options.stage) as any);
385
+
312
386
  export const filter = <Outs extends any[]>(...outs: Outs) =>
313
387
  outs.filter(isOutput) as unknown as Filter<Outs>;
314
388
 
@@ -353,7 +427,32 @@ function proxy(self: any): any {
353
427
  prop === ExprSymbol || prop === inspect ? true : prop in self,
354
428
  get: (target, prop) =>
355
429
  prop === Symbol.toPrimitive
356
- ? (hint: string) => (hint === "number" ? Number.NaN : self.toString())
430
+ ? (hint: string) => {
431
+ // Any JS-level coercion of an unresolved Output produces a
432
+ // placeholder that *looks* like a real value but isn't:
433
+ //
434
+ // - `string` / `default` hints (`${output}`, `output + ""`,
435
+ // `==` against a primitive) previously fell through to
436
+ // `self.toString()` and returned the inspect form
437
+ // (e.g. "tunnel.tunnelId"). The bogus string flowed
438
+ // into resource props and into the cloud — only
439
+ // surfacing as an opaque downstream error (see PR
440
+ // description for a real Cloudflare DNS landing).
441
+ //
442
+ // - `number` hint (`+output`, `output * 2`,
443
+ // `Math.max(0, output)`) previously returned NaN, which
444
+ // propagates silently through arithmetic and lands as
445
+ // "the API rejected a NaN field" much later.
446
+ //
447
+ // All three hints fail loud at the coercion site with a
448
+ // pointer to the right composition API.
449
+ throw new Error(
450
+ `Cannot coerce Output<${self[inspect]()}> to a ` +
451
+ `${hint === "number" ? "number" : "string"} via JS coercion. ` +
452
+ `Use Output.interpolate\`...\` or Output.map(output, fn) ` +
453
+ `to compose Outputs — the value isn't known until deploy time.`,
454
+ );
455
+ }
357
456
  : prop === ExprSymbol
358
457
  ? self
359
458
  : prop === inspect
@@ -445,6 +544,8 @@ export const evaluate: <A, Req = never>(
445
544
  );
446
545
  } else if (isPropExpr(expr)) {
447
546
  return (yield* evaluate(expr.expr, upstream))?.[expr.identifier];
547
+ } else if (isNamedExpr(expr)) {
548
+ return yield* evaluate(expr.expr, upstream);
448
549
  } else if (isRefExpr(expr)) {
449
550
  const state = yield* State.State;
450
551
  const stack = expr.stack ?? (yield* Stack).name;
@@ -465,6 +566,22 @@ export const evaluate: <A, Req = never>(
465
566
  );
466
567
  }
467
568
  return resource.attr;
569
+ } else if (isStackRefExpr(expr)) {
570
+ const state = yield* State.State;
571
+ const stack = expr.stack;
572
+ const stage = expr.stage ?? (yield* Stage);
573
+ const output = yield* state.getOutput({ stack, stage });
574
+ if (output == null) {
575
+ return yield* Effect.fail(
576
+ new InvalidReferenceError({
577
+ message: `Reference to stack '${stack}' at stage '${stage}' not found. Have you deployed stage '${stage}' of '${stack}'?`,
578
+ stack,
579
+ stage,
580
+ resourceId: stack,
581
+ }),
582
+ );
583
+ }
584
+ return output;
468
585
  }
469
586
  }
470
587
  if (Array.isArray(expr)) {
@@ -483,14 +600,6 @@ export const evaluate: <A, Req = never>(
483
600
  return expr;
484
601
  }) as Effect.Effect<any>;
485
602
 
486
- export const ref = <R extends ResourceLike>(
487
- resourceId: R["LogicalId"],
488
- options?: {
489
- stage?: string;
490
- stack?: string;
491
- },
492
- ) => of(stageRef({ id: resourceId, ...options }));
493
-
494
603
  export const hasOutputs = (value: any): value is Output<any, any> =>
495
604
  Object.keys(upstreamAny(value)).length > 0;
496
605
 
@@ -531,7 +640,7 @@ export const upstream = <E extends Output<any, any>>(expr: E): any => {
531
640
  return upstream(expr.expr);
532
641
  } else if (isAllExpr(expr)) {
533
642
  return Object.assign({}, ...expr.outs.map((out) => upstream(out)));
534
- } else if (isEffectExpr(expr) || isApplyExpr(expr)) {
643
+ } else if (isEffectExpr(expr) || isApplyExpr(expr) || isNamedExpr(expr)) {
535
644
  return upstream(expr.expr);
536
645
  } else if (Array.isArray(expr)) {
537
646
  return expr.map(upstream).reduce(toObject, {});