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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "2.0.0-beta.35",
3
+ "version": "2.0.0-beta.37",
4
4
  "homepage": "https://alchemy.run",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Sam Goodwin <sam@alchemy.run>",
@@ -275,14 +275,14 @@
275
275
  "@ai-sdk/provider": "^3.0.8",
276
276
  "@aws-sdk/credential-providers": "^3.0.0",
277
277
  "@clack/prompts": "^0.11.0",
278
- "@distilled.cloud/aws": "^0.19.0",
279
- "@distilled.cloud/axiom": "^0.19.0",
280
- "@distilled.cloud/cloudflare": "^0.19.0",
278
+ "@distilled.cloud/aws": "^0.19.1",
279
+ "@distilled.cloud/axiom": "^0.19.1",
280
+ "@distilled.cloud/cloudflare": "^0.19.1",
281
281
  "@distilled.cloud/cloudflare-rolldown-plugin": "0.3.0",
282
282
  "@distilled.cloud/cloudflare-runtime": "0.3.2",
283
283
  "@distilled.cloud/cloudflare-vite-plugin": "0.2.0",
284
- "@distilled.cloud/core": "^0.19.0",
285
- "@distilled.cloud/neon": "^0.19.0",
284
+ "@distilled.cloud/core": "^0.19.1",
285
+ "@distilled.cloud/neon": "^0.19.1",
286
286
  "pg": "^8.13.0",
287
287
  "@effect/vitest": ">=4.0.0-beta.60 || >=4.0.0",
288
288
  "@libsql/client": "^0.17.0",
@@ -291,7 +291,6 @@
291
291
  "@smithy/shared-ini-file-loader": "^4.3.4",
292
292
  "@smithy/types": "^4.8.1",
293
293
  "@types/aws-lambda": "^8.10.152",
294
- "@types/libsodium-wrappers": "^0.8.2",
295
294
  "ai": "^6.0.62",
296
295
  "aws4fetch": "^1.0.20",
297
296
  "capnweb": "^0.6.1",
@@ -309,6 +308,7 @@
309
308
  "react": "^19.2.0",
310
309
  "rolldown": "1.0.0-rc.17",
311
310
  "sonda": "^0.11.1",
311
+ "undici": "^7.16.0",
312
312
  "web-tree-sitter": "0.25.10",
313
313
  "yaml": "^2.0.0"
314
314
  },
@@ -10,6 +10,7 @@ import * as Context from "effect/Context";
10
10
  import * as Effect from "effect/Effect";
11
11
  import * as FileSystem from "effect/FileSystem";
12
12
  import * as Option from "effect/Option";
13
+ import * as Redacted from "effect/Redacted";
13
14
  import * as Schedule from "effect/Schedule";
14
15
  import * as Stream from "effect/Stream";
15
16
  import type * as rolldown from "rolldown";
@@ -368,7 +369,21 @@ export const Function: Platform<
368
369
  set: (id: string, output: Output.Output) =>
369
370
  Effect.sync(() => {
370
371
  const key = id.replaceAll(/[^a-zA-Z0-9]/g, "_");
371
- env[key] = output.pipe(Output.map((value) => JSON.stringify(value)));
372
+ // Preserve `Redacted`-ness across the Output → Lambda env var
373
+ // round-trip. `JSON.stringify(Redacted)` would emit the literal
374
+ // string `"<redacted>"` and lose the value, so secrets are
375
+ // serialized with a `{_tag: "Redacted", value: ...}` marker
376
+ // that the runtime `get` path detects and rebuilds.
377
+ env[key] = output.pipe(
378
+ Output.map((value) =>
379
+ Redacted.isRedacted(value)
380
+ ? JSON.stringify({
381
+ _tag: "Redacted",
382
+ value: Redacted.value(value),
383
+ })
384
+ : JSON.stringify(value),
385
+ ),
386
+ );
372
387
  return key;
373
388
  }),
374
389
  get: <T>(key: string) =>
@@ -377,7 +392,20 @@ export const Function: Platform<
377
392
  .pipe(
378
393
  Effect.flatMap((val) =>
379
394
  Effect.try({
380
- try: () => JSON.parse(val) as T,
395
+ try: () => {
396
+ const value = JSON.parse(val);
397
+ if (
398
+ value !== null &&
399
+ typeof value === "object" &&
400
+ (value as { _tag?: unknown })._tag === "Redacted" &&
401
+ "value" in (value as object)
402
+ ) {
403
+ return Redacted.make(
404
+ (value as { value: unknown }).value,
405
+ ) as unknown as T;
406
+ }
407
+ return value as T;
408
+ },
381
409
  catch: () => val, // assume it's just a string
382
410
  }),
383
411
  ),
package/src/Apply.ts CHANGED
@@ -185,7 +185,14 @@ export const apply = <P extends Plan>(
185
185
  const outputs = Object.fromEntries(
186
186
  Object.entries(tracker).map(([fqn, t]) => [fqn, t.output]),
187
187
  );
188
- return yield* Output.evaluate(plan.output, outputs);
188
+ const resolved = yield* Output.evaluate(plan.output, outputs);
189
+
190
+ // Persist the stack's evaluated outputs so cross-stack references
191
+ // (`yield* OtherStack` / `OtherStack.stage.<name>` / `Output.stackRef`)
192
+ // can read them back out of the state store.
193
+ yield* state.setOutput({ stack: stackName, stage, value: resolved });
194
+
195
+ return resolved;
189
196
  }).pipe(
190
197
  ensureArtifactStore,
191
198
  Effect.withSpan("apply", {
package/src/Cli/main.ts CHANGED
@@ -8,9 +8,12 @@ import { AlchemyContextLive } from "alchemy/AlchemyContext";
8
8
  import { CredentialsStoreLive } from "alchemy/Auth/Credentials";
9
9
  import { ProfileLive } from "alchemy/Auth/Profile";
10
10
  import { TelemetryLive } from "alchemy/Telemetry/Layer";
11
+ import { installLocalhostDns } from "alchemy/Util/LocalhostDns";
11
12
  import { PlatformServices } from "alchemy/Util/PlatformServices";
12
13
  import packageJson from "../../package.json" with { type: "json" };
13
14
 
15
+ installLocalhostDns();
16
+
14
17
  import { checkLatestVersion } from "./checkVersion.ts";
15
18
  import { handleCancellation } from "./commands/_shared.ts";
16
19
  import { awsCommand } from "./commands/aws.ts";
@@ -0,0 +1,82 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { AnalyticsEngineDatasetBinding } from "./AnalyticsEngineDatasetBinding.ts";
3
+
4
+ type AnalyticsEngineDatasetTypeId = typeof AnalyticsEngineDatasetTypeId;
5
+ const AnalyticsEngineDatasetTypeId =
6
+ "Cloudflare.AnalyticsEngineDataset" as const;
7
+
8
+ export type AnalyticsEngineDatasetProps = {
9
+ /**
10
+ * Dataset name. If omitted, the logical ID is used.
11
+ */
12
+ dataset?: string;
13
+ };
14
+
15
+ /**
16
+ * A Cloudflare Workers Analytics Engine dataset binding.
17
+ *
18
+ * Analytics Engine datasets are configured as Worker bindings. The binding
19
+ * exposes `writeDataPoint()` at runtime and does not require separate
20
+ * provisioning through the Cloudflare API.
21
+ *
22
+ * @resource
23
+ *
24
+ * @section Binding to a Worker
25
+ * @example Basic Analytics Engine binding
26
+ * ```typescript
27
+ * const Analytics = yield* Cloudflare.AnalyticsEngineDataset("Analytics", {
28
+ * dataset: "app-events",
29
+ * });
30
+ *
31
+ * export const Worker = Cloudflare.Worker("Worker", {
32
+ * main: "./src/worker.ts",
33
+ * bindings: { Analytics },
34
+ * });
35
+ * ```
36
+ *
37
+ * @example Effect-style worker
38
+ * ```typescript
39
+ * const analytics = yield* Cloudflare.AnalyticsEngineDataset.bind(Analytics);
40
+ * yield* analytics.writeDataPoint({ blobs: ["signup"] });
41
+ * ```
42
+ */
43
+ export type AnalyticsEngineDataset = {
44
+ kind: AnalyticsEngineDatasetTypeId;
45
+ name: string;
46
+ dataset: string;
47
+ };
48
+
49
+ export const isAnalyticsEngineDataset = (
50
+ value: unknown,
51
+ ): value is AnalyticsEngineDataset =>
52
+ typeof value === "object" &&
53
+ value !== null &&
54
+ "kind" in value &&
55
+ (value as AnalyticsEngineDataset).kind === AnalyticsEngineDatasetTypeId;
56
+
57
+ export const AnalyticsEngineDataset: {
58
+ (
59
+ name: string,
60
+ props?: AnalyticsEngineDatasetProps,
61
+ ): Effect.Effect<AnalyticsEngineDataset>;
62
+ /**
63
+ * Bind Analytics Engine to the surrounding Worker, returning an
64
+ * Effect-native client with access to the native Workers runtime binding.
65
+ */
66
+ bind: typeof AnalyticsEngineDatasetBinding.bind;
67
+ } = Object.assign(
68
+ Effect.fnUntraced(function* (
69
+ name: string,
70
+ props?: AnalyticsEngineDatasetProps,
71
+ ) {
72
+ return {
73
+ kind: AnalyticsEngineDatasetTypeId,
74
+ name,
75
+ dataset: props?.dataset ?? name,
76
+ } satisfies AnalyticsEngineDataset;
77
+ }),
78
+ {
79
+ bind: (...args: Parameters<typeof AnalyticsEngineDatasetBinding.bind>) =>
80
+ AnalyticsEngineDatasetBinding.bind(...args),
81
+ },
82
+ );
@@ -0,0 +1,106 @@
1
+ import * as Data from "effect/Data";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Binding from "../../Binding.ts";
5
+ import type { ResourceLike } from "../../Resource.ts";
6
+ import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
7
+ import type { AnalyticsEngineDataset as AnalyticsEngineDatasetLike } from "./AnalyticsEngineDataset.ts";
8
+
9
+ export interface AnalyticsEngineDataPoint {
10
+ indexes?: string[];
11
+ blobs?: string[];
12
+ doubles?: number[];
13
+ }
14
+
15
+ export interface RuntimeAnalyticsEngineDataset {
16
+ writeDataPoint(dataPoint: AnalyticsEngineDataPoint): void;
17
+ }
18
+
19
+ export class AnalyticsEngineDatasetError extends Data.TaggedError(
20
+ "AnalyticsEngineDatasetError",
21
+ )<{
22
+ message: string;
23
+ cause: Error;
24
+ }> {}
25
+
26
+ export interface AnalyticsEngineDatasetClient {
27
+ raw: Effect.Effect<RuntimeAnalyticsEngineDataset, never, WorkerEnvironment>;
28
+ writeDataPoint(
29
+ dataPoint: AnalyticsEngineDataPoint,
30
+ ): Effect.Effect<void, AnalyticsEngineDatasetError, WorkerEnvironment>;
31
+ }
32
+
33
+ export class AnalyticsEngineDatasetBinding extends Binding.Service<
34
+ AnalyticsEngineDatasetBinding,
35
+ (
36
+ dataset: AnalyticsEngineDatasetLike,
37
+ ) => Effect.Effect<AnalyticsEngineDatasetClient>
38
+ >()("Cloudflare.AnalyticsEngineDataset.Binding") {}
39
+
40
+ export const AnalyticsEngineDatasetBindingLive = Layer.effect(
41
+ AnalyticsEngineDatasetBinding,
42
+ Effect.gen(function* () {
43
+ const bind = yield* AnalyticsEngineDatasetBindingPolicy;
44
+
45
+ return Effect.fnUntraced(function* (dataset: AnalyticsEngineDatasetLike) {
46
+ yield* bind(dataset);
47
+
48
+ const raw = WorkerEnvironment.asEffect().pipe(
49
+ Effect.map(
50
+ (env) =>
51
+ (env as Record<string, RuntimeAnalyticsEngineDataset>)[
52
+ dataset.name
53
+ ]!,
54
+ ),
55
+ );
56
+
57
+ return {
58
+ raw,
59
+ writeDataPoint: (dataPoint) =>
60
+ raw.pipe(
61
+ Effect.flatMap((raw) =>
62
+ Effect.try({
63
+ try: () => raw.writeDataPoint(dataPoint),
64
+ catch: (error: any) =>
65
+ new AnalyticsEngineDatasetError({
66
+ message: error?.message ?? "Unknown error",
67
+ cause: error,
68
+ }),
69
+ }),
70
+ ),
71
+ ),
72
+ } satisfies AnalyticsEngineDatasetClient;
73
+ });
74
+ }),
75
+ );
76
+
77
+ export class AnalyticsEngineDatasetBindingPolicy extends Binding.Policy<
78
+ AnalyticsEngineDatasetBindingPolicy,
79
+ (dataset: AnalyticsEngineDatasetLike) => Effect.Effect<void>
80
+ >()("Cloudflare.AnalyticsEngineDataset.Binding") {}
81
+
82
+ export const AnalyticsEngineDatasetBindingPolicyLive =
83
+ AnalyticsEngineDatasetBindingPolicy.layer.succeed(
84
+ Effect.fnUntraced(function* (
85
+ host: ResourceLike,
86
+ dataset: AnalyticsEngineDatasetLike,
87
+ ) {
88
+ if (isWorker(host)) {
89
+ yield* host.bind(dataset.name, {
90
+ bindings: [
91
+ {
92
+ type: "analytics_engine",
93
+ name: dataset.name,
94
+ dataset: dataset.dataset,
95
+ },
96
+ ],
97
+ });
98
+ } else {
99
+ return yield* Effect.die(
100
+ new Error(
101
+ `AnalyticsEngineDatasetBinding does not support runtime '${host.Type}'`,
102
+ ),
103
+ );
104
+ }
105
+ }),
106
+ );
@@ -0,0 +1,2 @@
1
+ export * from "./AnalyticsEngineDataset.ts";
2
+ export * from "./AnalyticsEngineDatasetBinding.ts";
@@ -7,6 +7,86 @@ import { WorkerEnvironment } from "../Workers/Worker.ts";
7
7
  import type { D1Database } from "./D1Database.ts";
8
8
  import { DatabaseBinding } from "./D1DatabaseBinding.ts";
9
9
 
10
+ /**
11
+ * Effect-native wrapper around a Cloudflare D1 prepared statement.
12
+ *
13
+ * Construction (`prepare`) and binding (`bind`) are synchronous —
14
+ * they're plan builders and don't touch D1. Only the executors
15
+ * (`all`, `first`, `run`, `raw`) round-trip to the database, and
16
+ * those return Effects so they participate in the Effect runtime.
17
+ */
18
+ export class D1PreparedStatement {
19
+ /** @internal */
20
+ constructor(
21
+ private readonly query: string,
22
+ private readonly binds: ReadonlyArray<unknown>,
23
+ private readonly rawEff: Effect.Effect<runtime.D1Database>,
24
+ ) {}
25
+
26
+ /**
27
+ * Return a new prepared statement bound to `values`. Subsequent
28
+ * calls replace the previous binding, matching the underlying
29
+ * Cloudflare runtime semantics.
30
+ */
31
+ bind(...values: unknown[]): D1PreparedStatement {
32
+ return new D1PreparedStatement(this.query, values, this.rawEff);
33
+ }
34
+
35
+ /** Run the query and return all matching rows. */
36
+ all<T = unknown>(): Effect.Effect<runtime.D1Result<T>> {
37
+ return this.withRuntime((stmt) => stmt.all<T>());
38
+ }
39
+
40
+ /** Run the query and return the first row, or `null` if no rows. */
41
+ first<T = unknown>(): Effect.Effect<T | null>;
42
+ first<T = unknown>(column: string): Effect.Effect<T | null>;
43
+ first<T = unknown>(column?: string): Effect.Effect<T | null> {
44
+ return this.withRuntime((stmt) =>
45
+ column !== undefined ? stmt.first<T>(column) : stmt.first<T>(),
46
+ );
47
+ }
48
+
49
+ /** Run the query as a mutation; returns row metadata. */
50
+ run<T = unknown>(): Effect.Effect<runtime.D1Result<T>> {
51
+ return this.withRuntime((stmt) => stmt.run<T>());
52
+ }
53
+
54
+ /** Run the query and return rows as flat arrays. */
55
+ raw<T = unknown[]>(): Effect.Effect<T[]>;
56
+ raw<T = unknown[]>(options: {
57
+ columnNames: true;
58
+ }): Effect.Effect<[string[], ...T[]]>;
59
+ raw<T = unknown[]>(options?: {
60
+ columnNames: true;
61
+ }): Effect.Effect<T[] | [string[], ...T[]]> {
62
+ return this.withRuntime((stmt) =>
63
+ options
64
+ ? stmt.raw<T>(options)
65
+ : (stmt.raw<T>() as Promise<T[] | [string[], ...T[]]>),
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Materialize the underlying Cloudflare prepared statement against
71
+ * a concrete D1 binding. Used by `D1ConnectionClient.batch` to
72
+ * collect statements for a single transactional call.
73
+ *
74
+ * @internal
75
+ */
76
+ _build(raw: runtime.D1Database): runtime.D1PreparedStatement {
77
+ const stmt = raw.prepare(this.query);
78
+ return this.binds.length > 0 ? stmt.bind(...this.binds) : stmt;
79
+ }
80
+
81
+ private withRuntime<A>(
82
+ fn: (stmt: runtime.D1PreparedStatement) => Promise<A>,
83
+ ): Effect.Effect<A> {
84
+ return Effect.flatMap(this.rawEff, (raw) =>
85
+ Effect.promise(() => fn(this._build(raw))),
86
+ );
87
+ }
88
+ }
89
+
10
90
  export interface D1ConnectionClient {
11
91
  /**
12
92
  * An Effect that resolves to the raw underlying Cloudflare D1Database binding.
@@ -14,9 +94,11 @@ export interface D1ConnectionClient {
14
94
  */
15
95
  raw: Effect.Effect<runtime.D1Database>;
16
96
  /**
17
- * Prepare a SQL query statement for later execution.
97
+ * Prepare a SQL statement. Returns synchronously — the network
98
+ * round-trip happens when you yield one of the statement's
99
+ * executors (`all`, `first`, `run`, `raw`).
18
100
  */
19
- prepare: (query: string) => Effect.Effect<runtime.D1PreparedStatement>;
101
+ prepare: (query: string) => D1PreparedStatement;
20
102
  /**
21
103
  * Execute raw SQL without prepared statements.
22
104
  */
@@ -26,7 +108,7 @@ export interface D1ConnectionClient {
26
108
  * Statements execute sequentially and are rolled back on failure.
27
109
  */
28
110
  batch: <T = unknown>(
29
- statements: runtime.D1PreparedStatement[],
111
+ statements: D1PreparedStatement[],
30
112
  ) => Effect.Effect<runtime.D1Result<T>[]>;
31
113
  }
32
114
 
@@ -42,22 +124,23 @@ export const D1ConnectionLive = Layer.effect(
42
124
 
43
125
  return Effect.fn(function* (database: D1Database) {
44
126
  yield* Policy(database);
45
- const d1 = yield* Effect.serviceOption(WorkerEnvironment).pipe(
127
+ const rawEff = yield* Effect.serviceOption(WorkerEnvironment).pipe(
46
128
  Effect.map(Option.getOrUndefined),
47
129
  Effect.map((env) => env?.[database.LogicalId]! as runtime.D1Database),
48
130
  Effect.cached,
49
131
  );
50
132
 
51
133
  return {
52
- raw: d1,
53
- prepare: (query: string) =>
54
- d1.pipe(Effect.map((d1) => d1.prepare(query))),
134
+ raw: rawEff,
135
+ prepare: (query: string) => new D1PreparedStatement(query, [], rawEff),
55
136
  exec: (query: string) =>
56
- d1.pipe(Effect.flatMap((d1) => Effect.promise(() => d1.exec(query)))),
57
- batch: <T = unknown>(statements: runtime.D1PreparedStatement[]) =>
58
- d1.pipe(
59
- Effect.flatMap((d1) =>
60
- Effect.promise(() => d1.batch<T>(statements)),
137
+ Effect.flatMap(rawEff, (raw) =>
138
+ Effect.promise(() => raw.exec(query)),
139
+ ),
140
+ batch: <T = unknown>(statements: D1PreparedStatement[]) =>
141
+ Effect.flatMap(rawEff, (raw) =>
142
+ Effect.promise(() =>
143
+ raw.batch<T>(statements.map((s) => s._build(raw))),
61
144
  ),
62
145
  ),
63
146
  } satisfies D1ConnectionClient;
@@ -0,0 +1,121 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { ImagesBinding } from "./ImagesBinding.ts";
3
+
4
+ type ImagesTypeId = typeof ImagesTypeId;
5
+ const ImagesTypeId = "Cloudflare.Images" as const;
6
+
7
+ export type ImagesProps = {
8
+ /**
9
+ * Binding name used when `Cloudflare.Images.bind(images)` attaches Images
10
+ * from inside a Worker init phase. When Images is passed through
11
+ * `Worker({ bindings: { ... } })`, the object key remains the binding name.
12
+ *
13
+ * @default "IMAGES"
14
+ */
15
+ name?: string;
16
+ };
17
+
18
+ /**
19
+ * Marker for a Cloudflare Images binding.
20
+ *
21
+ * Images bindings are configured directly on Workers and do not have a
22
+ * standalone provisioning API. The Worker provider sees this object in
23
+ * `bindings: { ... }` and emits the corresponding `{ type: "images" }`
24
+ * metadata binding to the script.
25
+ */
26
+ export type Images = {
27
+ kind: ImagesTypeId;
28
+ name: string;
29
+ };
30
+
31
+ export const isImages = (value: unknown): value is Images =>
32
+ typeof value === "object" &&
33
+ value !== null &&
34
+ "kind" in value &&
35
+ (value as Images).kind === ImagesTypeId;
36
+
37
+ /**
38
+ * A Cloudflare Images binding for image transformation and manipulation inside
39
+ * Workers.
40
+ *
41
+ * The Effect-native interface (`Cloudflare.Images.bind(...)`) returns an
42
+ * `ImagesClient` whose methods take Effect `Stream.Stream<Uint8Array>`
43
+ * inputs and return `Effect`s — `info`, `input(...).transform(...)
44
+ * .draw(...).output(...)`. The runtime conversion to Cloudflare's
45
+ * `ReadableStream` is handled internally.
46
+ *
47
+ * @section Declaring Images
48
+ * @example
49
+ * ```typescript
50
+ * const Pipeline = yield* Cloudflare.Images({ name: "PIPELINE" });
51
+ * ```
52
+ *
53
+ * @section Effect-style Worker (recommended)
54
+ * @example Read image format and dimensions from the request body
55
+ * ```typescript
56
+ * import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest";
57
+ *
58
+ * Cloudflare.Worker("ImageWorker", { main: import.meta.filename },
59
+ * Effect.gen(function* () {
60
+ * const pipeline = yield* Pipeline;
61
+ * const images = yield* Cloudflare.Images.bind(pipeline);
62
+ * return {
63
+ * fetch: Effect.gen(function* () {
64
+ * const request = yield* HttpServerRequest;
65
+ * // request.stream is Stream.Stream<Uint8Array>
66
+ * const info = yield* images.info(request.stream);
67
+ * return yield* HttpServerResponse.json(info);
68
+ * }),
69
+ * };
70
+ * }).pipe(Effect.provide(Cloudflare.ImagesBindingLive)),
71
+ * );
72
+ * ```
73
+ *
74
+ * @example Transform an image — chainable pipeline, single Effect at the end
75
+ * ```typescript
76
+ * const result = yield* (yield* images.input(request.stream))
77
+ * .transform({ width: 128 })
78
+ * .output({ format: "image/jpeg" });
79
+ *
80
+ * const response = yield* result.response;
81
+ * ```
82
+ *
83
+ * @section Binding to a Worker (declarative)
84
+ * @example
85
+ * ```typescript
86
+ * export const Worker = Cloudflare.Worker("Worker", {
87
+ * main: "./src/worker.ts",
88
+ * bindings: { MEDIA: Pipeline },
89
+ * });
90
+ *
91
+ * export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
92
+ * // { MEDIA: ImagesBinding }
93
+ * ```
94
+ *
95
+ * Inside the Worker, the raw Cloudflare runtime binding is reachable via
96
+ * `client.raw` if you need to call `info()` / `input()` directly with
97
+ * `async`/`await`. The Effect-native interface is preferred — it returns
98
+ * tagged `ImagesError`s, threads `WorkerEnvironment`, and lets you stream
99
+ * Effect `Stream<Uint8Array>` sources without manual conversion.
100
+ *
101
+ * @see https://developers.cloudflare.com/images/transform-images/bindings/
102
+ */
103
+ export const Images: {
104
+ (props?: ImagesProps): Effect.Effect<Images>;
105
+ /**
106
+ * Bind Cloudflare Images to the surrounding Worker, returning an
107
+ * Effect-native client with access to the native Workers runtime binding.
108
+ */
109
+ bind: typeof ImagesBinding.bind;
110
+ } = Object.assign(
111
+ Effect.fn(function* (props?: ImagesProps) {
112
+ return {
113
+ kind: ImagesTypeId,
114
+ name: props?.name ?? "IMAGES",
115
+ } satisfies Images;
116
+ }),
117
+ {
118
+ bind: (...args: Parameters<typeof ImagesBinding.bind>) =>
119
+ ImagesBinding.bind(...args),
120
+ },
121
+ );