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
@@ -0,0 +1,192 @@
1
+ import type * as cf from "@cloudflare/workers-types";
2
+ import * as Data from "effect/Data";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Stream from "effect/Stream";
6
+ import * as Binding from "../../Binding.ts";
7
+ import type { ResourceLike } from "../../Resource.ts";
8
+ import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
9
+ import { type Images as ImagesLike } from "./Images.ts";
10
+
11
+ export class ImagesError extends Data.TaggedError("ImagesError")<{
12
+ message: string;
13
+ code?: number;
14
+ cause: unknown;
15
+ }> {}
16
+
17
+ /**
18
+ * Effect-native handle to the result of `input(...).output(...)`.
19
+ * Mirrors the runtime `ImageTransformationResult` but exposes side
20
+ * effects (response/image/contentType reads) as plain sync effects.
21
+ */
22
+ export interface ImageTransformationResultClient {
23
+ raw: cf.ImageTransformationResult;
24
+ response: Effect.Effect<cf.Response>;
25
+ contentType: Effect.Effect<string>;
26
+ image(
27
+ options?: cf.ImageTransformationOutputOptions,
28
+ ): Effect.Effect<cf.ReadableStream<Uint8Array>>;
29
+ }
30
+
31
+ /**
32
+ * Effect-native chainable transformer. `transform`/`draw` are pure
33
+ * (return a new client wrapping the next runtime transformer);
34
+ * `output` is the only step that crosses into Cloudflare's runtime
35
+ * and therefore returns an Effect.
36
+ */
37
+ export interface ImageTransformerClient {
38
+ raw: cf.ImageTransformer;
39
+ transform(transform: cf.ImageTransform): ImageTransformerClient;
40
+ draw<E = never, R = never>(
41
+ image: Stream.Stream<Uint8Array, E, R> | ImageTransformerClient,
42
+ options?: cf.ImageDrawOptions,
43
+ ): Effect.Effect<ImageTransformerClient, never, R>;
44
+ output(
45
+ options: cf.ImageOutputOptions,
46
+ ): Effect.Effect<ImageTransformationResultClient, ImagesError>;
47
+ }
48
+
49
+ /**
50
+ * Effect-native client for a Cloudflare Images binding.
51
+ *
52
+ * Wraps the runtime {@link cf.ImagesBinding} so each method returns
53
+ * an Effect tagged with {@link ImagesError}. Use
54
+ * `Cloudflare.Images.bind(images)` inside a Worker's init phase.
55
+ */
56
+ export interface ImagesClient {
57
+ /** Effect resolving to the raw Cloudflare runtime binding. */
58
+ raw: Effect.Effect<cf.ImagesBinding, never, WorkerEnvironment>;
59
+ /**
60
+ * Read image format and dimensions from a stream of bytes.
61
+ * Fails with {@link ImagesError} (code 9412) if the input is not
62
+ * a recognized image.
63
+ */
64
+ info<E = never, R = never>(
65
+ stream: Stream.Stream<Uint8Array, E, R>,
66
+ options?: cf.ImageInputOptions,
67
+ ): Effect.Effect<cf.ImageInfoResponse, ImagesError, WorkerEnvironment | R>;
68
+ /**
69
+ * Begin a transformation pipeline. Subsequent `.transform()` /
70
+ * `.draw()` calls are pure; `.output(opts)` runs the pipeline.
71
+ */
72
+ input<E = never, R = never>(
73
+ stream: Stream.Stream<Uint8Array, E, R>,
74
+ options?: cf.ImageInputOptions,
75
+ ): Effect.Effect<ImageTransformerClient, never, WorkerEnvironment | R>;
76
+ }
77
+
78
+ export class ImagesBinding extends Binding.Service<
79
+ ImagesBinding,
80
+ (images: ImagesLike) => Effect.Effect<ImagesClient>
81
+ >()("Cloudflare.Images.Binding") {}
82
+
83
+ export const ImagesBindingLive = Layer.effect(
84
+ ImagesBinding,
85
+ Effect.gen(function* () {
86
+ const Policy = yield* ImagesBindingPolicy;
87
+
88
+ return Effect.fn(function* (images: ImagesLike) {
89
+ yield* Policy(images);
90
+ const env = WorkerEnvironment.asEffect();
91
+ const raw = env.pipe(
92
+ Effect.map(
93
+ (env) => (env as Record<string, cf.ImagesBinding>)[images.name]!,
94
+ ),
95
+ );
96
+
97
+ return {
98
+ raw,
99
+ info: (stream, options) =>
100
+ Effect.gen(function* () {
101
+ const binding = yield* raw;
102
+ const readable = yield* toCfReadable(stream);
103
+ return yield* tryPromise(() => binding.info(readable, options));
104
+ }),
105
+ input: (stream, options) =>
106
+ Effect.gen(function* () {
107
+ const binding = yield* raw;
108
+ const readable = yield* toCfReadable(stream);
109
+ return wrapTransformer(binding.input(readable, options));
110
+ }),
111
+ } satisfies ImagesClient;
112
+ });
113
+ }),
114
+ );
115
+
116
+ export class ImagesBindingPolicy extends Binding.Policy<
117
+ ImagesBindingPolicy,
118
+ (images: ImagesLike) => Effect.Effect<void>
119
+ >()("Cloudflare.Images.Binding") {}
120
+
121
+ export const ImagesBindingPolicyLive = ImagesBindingPolicy.layer.succeed(
122
+ Effect.fn(function* (host: ResourceLike, images: ImagesLike) {
123
+ if (isWorker(host)) {
124
+ yield* host.bind(images.name, {
125
+ bindings: [
126
+ {
127
+ type: "images",
128
+ name: images.name,
129
+ },
130
+ ],
131
+ });
132
+ } else {
133
+ return yield* Effect.die(
134
+ new Error(`ImagesBinding does not support runtime '${host.Type}'`),
135
+ );
136
+ }
137
+ }),
138
+ );
139
+
140
+ const tryPromise = <T>(fn: () => Promise<T>): Effect.Effect<T, ImagesError> =>
141
+ Effect.tryPromise({
142
+ try: fn,
143
+ catch: (error: any) =>
144
+ new ImagesError({
145
+ message: error?.message ?? "Unknown error",
146
+ code: typeof error?.code === "number" ? error.code : undefined,
147
+ cause: error,
148
+ }),
149
+ });
150
+
151
+ /**
152
+ * Convert an Effect `Stream<Uint8Array>` into the `cf.ReadableStream<Uint8Array>`
153
+ * shape that the Cloudflare Images runtime binding expects. The two
154
+ * `ReadableStream` types only differ at the type level; at runtime they
155
+ * are the same Web Streams API.
156
+ */
157
+ const toCfReadable = <E, R>(
158
+ stream: Stream.Stream<Uint8Array, E, R>,
159
+ ): Effect.Effect<cf.ReadableStream<Uint8Array>, never, R> =>
160
+ Stream.toReadableStreamEffect(stream).pipe(
161
+ Effect.map((s) => s as unknown as cf.ReadableStream<Uint8Array>),
162
+ );
163
+
164
+ const isTransformerClient = (image: unknown): image is ImageTransformerClient =>
165
+ typeof image === "object" && image !== null && "raw" in image;
166
+
167
+ const wrapTransformer = (raw: cf.ImageTransformer): ImageTransformerClient => ({
168
+ raw,
169
+ transform: (transform) => wrapTransformer(raw.transform(transform)),
170
+ draw: <E, R>(
171
+ image: Stream.Stream<Uint8Array, E, R> | ImageTransformerClient,
172
+ options?: cf.ImageDrawOptions,
173
+ ): Effect.Effect<ImageTransformerClient, never, R> => {
174
+ if (isTransformerClient(image)) {
175
+ return Effect.succeed(wrapTransformer(raw.draw(image.raw, options)));
176
+ }
177
+ return toCfReadable(image).pipe(
178
+ Effect.map((readable) => wrapTransformer(raw.draw(readable, options))),
179
+ );
180
+ },
181
+ output: (options) =>
182
+ tryPromise(() => raw.output(options)).pipe(Effect.map(wrapResult)),
183
+ });
184
+
185
+ const wrapResult = (
186
+ raw: cf.ImageTransformationResult,
187
+ ): ImageTransformationResultClient => ({
188
+ raw,
189
+ response: Effect.sync(() => raw.response()),
190
+ contentType: Effect.sync(() => raw.contentType()),
191
+ image: (options) => Effect.sync(() => raw.image(options)),
192
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./Images.ts";
2
+ export * from "./ImagesBinding.ts";
@@ -169,7 +169,15 @@ function bundleOutputToWorkerModules(
169
169
  ): WorkerModule[] {
170
170
  const modules: WorkerModule[] = [];
171
171
  for (const file of bundle.files) {
172
- if (file.path.endsWith(".map") || file.content instanceof Uint8Array) {
172
+ if (file.path.endsWith(".map")) {
173
+ continue;
174
+ }
175
+ if (file.content instanceof Uint8Array) {
176
+ modules.push({
177
+ name: file.path,
178
+ type: file.path.endsWith(".wasm") ? "Wasm" : "Data",
179
+ content: file.content,
180
+ });
173
181
  continue;
174
182
  }
175
183
  modules.push({
@@ -13,6 +13,7 @@ import { ProfileLive } from "../Auth/Profile.ts";
13
13
  import { Random, RandomProvider } from "../Random.ts";
14
14
  import * as Access from "./Access.ts";
15
15
  import * as AiGateway from "./AiGateway/index.ts";
16
+ import * as AnalyticsEngine from "./AnalyticsEngine/index.ts";
16
17
  import * as ApiToken from "./ApiToken/index.ts";
17
18
  import * as Artifacts from "./Artifacts/index.ts";
18
19
  import { CloudflareAuth } from "./Auth/AuthProvider.ts";
@@ -21,6 +22,7 @@ import * as Containers from "./Container/index.ts";
21
22
  import * as Credentials from "./Credentials.ts";
22
23
  import * as D1 from "./D1/index.ts";
23
24
  import * as Hyperdrive from "./Hyperdrive/index.ts";
25
+ import * as Images from "./Images/index.ts";
24
26
  import * as KV from "./KV/index.ts";
25
27
  import * as Queue from "./Queue/index.ts";
26
28
  import * as R2 from "./R2/index.ts";
@@ -49,6 +51,7 @@ export const providers = () =>
49
51
  ApiToken.UserApiToken,
50
52
  AiGateway.AiGateway,
51
53
  AiGateway.AiGatewayBindingPolicy,
54
+ AnalyticsEngine.AnalyticsEngineDatasetBindingPolicy,
52
55
  Artifacts.ArtifactsBindingPolicy,
53
56
  Command,
54
57
  Containers.Container,
@@ -56,6 +59,7 @@ export const providers = () =>
56
59
  D1.D1Database,
57
60
  Hyperdrive.Hyperdrive,
58
61
  Hyperdrive.HyperdriveBindingPolicy,
62
+ Images.ImagesBindingPolicy,
59
63
  KV.KVNamespace,
60
64
  KV.KVNamespaceBindingPolicy,
61
65
  Queue.Queue,
@@ -71,6 +75,7 @@ export const providers = () =>
71
75
  VpcService.VpcService,
72
76
  Random,
73
77
  Workers.BindWorkerPolicy,
78
+ Workers.CronEventSourcePolicy,
74
79
  Workers.FetchPolicy,
75
80
  Workers.Worker,
76
81
  Workflows.WorkflowResource,
@@ -82,12 +87,14 @@ export const providers = () =>
82
87
  ApiToken.UserApiTokenProvider(),
83
88
  AiGateway.AiGatewayProvider(),
84
89
  AiGateway.AiGatewayBindingPolicyLive,
90
+ AnalyticsEngine.AnalyticsEngineDatasetBindingPolicyLive,
85
91
  Artifacts.ArtifactsBindingPolicyLive,
86
92
  Containers.ContainerProvider(),
87
93
  D1.D1ConnectionPolicyLive,
88
94
  D1.DatabaseProvider(),
89
95
  Hyperdrive.HyperdriveBindingPolicyLive,
90
96
  Hyperdrive.HyperdriveProvider(),
97
+ Images.ImagesBindingPolicyLive,
91
98
  KV.KVNamespaceBindingPolicyLive,
92
99
  KV.KVNamespaceProvider(),
93
100
  Queue.QueueBindingPolicyLive,
@@ -102,6 +109,7 @@ export const providers = () =>
102
109
  Tunnel.TunnelProvider(),
103
110
  VpcService.VpcServiceProvider(),
104
111
  Workers.BindWorkerPolicyLive,
112
+ Workers.CronEventSourcePolicyLive,
105
113
  Workers.FetchPolicyLive,
106
114
  Workers.WorkerProvider(),
107
115
  Workflows.WorkflowProvider(),
@@ -1,6 +1,7 @@
1
1
  import * as r2 from "@distilled.cloud/cloudflare/r2";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Schedule from "effect/Schedule";
4
+ import * as Stream from "effect/Stream";
4
5
 
5
6
  import { deepEqual, isResolved } from "../../Diff.ts";
6
7
  import { createPhysicalName } from "../../PhysicalName.ts";
@@ -42,6 +43,60 @@ export type R2BucketCustomDomain = {
42
43
  minTLS?: "1.0" | "1.1" | "1.2" | "1.3";
43
44
  };
44
45
 
46
+ export type R2BucketLifecycleCondition =
47
+ | {
48
+ type: "Age";
49
+ /**
50
+ * Maximum age of an object, in seconds, before the rule's action applies.
51
+ */
52
+ maxAge: number;
53
+ }
54
+ | {
55
+ type: "Date";
56
+ /**
57
+ * Absolute date (ISO 8601) at which the rule's action applies.
58
+ */
59
+ date: string;
60
+ };
61
+
62
+ export type R2BucketLifecycleRule = {
63
+ /**
64
+ * Unique identifier for the rule within the bucket.
65
+ */
66
+ id: string;
67
+ /**
68
+ * Whether the rule is enabled.
69
+ * @default true
70
+ */
71
+ enabled?: boolean;
72
+ /**
73
+ * Object key prefix the rule applies to. Use `""` (or omit) to match all
74
+ * objects in the bucket.
75
+ * @default ""
76
+ */
77
+ prefix?: string;
78
+ /**
79
+ * Abort incomplete multipart uploads after the configured age.
80
+ */
81
+ abortMultipartUploadsTransition?: {
82
+ condition?: { type: "Age"; maxAge: number };
83
+ };
84
+ /**
85
+ * Delete matching objects after the configured age or on a specific date.
86
+ */
87
+ deleteObjectsTransition?: {
88
+ condition?: R2BucketLifecycleCondition;
89
+ };
90
+ /**
91
+ * Transition matching objects to a different storage class. Cloudflare R2
92
+ * only supports transitioning to `InfrequentAccess` today.
93
+ */
94
+ storageClassTransitions?: {
95
+ condition: R2BucketLifecycleCondition;
96
+ storageClass: "InfrequentAccess";
97
+ }[];
98
+ };
99
+
45
100
  export type R2BucketProps = {
46
101
  /**
47
102
  * Name of the bucket. If omitted, a unique name will be generated.
@@ -67,6 +122,12 @@ export type R2BucketProps = {
67
122
  * to remove all custom domains.
68
123
  */
69
124
  domains?: R2BucketCustomDomain[];
125
+ /**
126
+ * Object lifecycle rules applied to the bucket. Pass an empty array (or
127
+ * omit) to clear all lifecycle rules. See the Cloudflare R2 docs for
128
+ * supported transitions.
129
+ */
130
+ lifecycleRules?: R2BucketLifecycleRule[];
70
131
  };
71
132
 
72
133
  export type R2Bucket = Resource<
@@ -79,6 +140,7 @@ export type R2Bucket = Resource<
79
140
  location: R2Bucket.Location | undefined;
80
141
  accountId: string;
81
142
  domains: R2Bucket.CustomDomain[];
143
+ lifecycleRules: R2Bucket.LifecycleRule[];
82
144
  },
83
145
  never,
84
146
  Cloudflare.Providers
@@ -175,6 +237,63 @@ export type R2Bucket = Resource<
175
237
  * ],
176
238
  * });
177
239
  * ```
240
+ *
241
+ * @section Object Lifecycle Rules
242
+ *
243
+ * Configure lifecycle rules to automatically delete objects, abort
244
+ * incomplete multipart uploads, or transition objects to InfrequentAccess
245
+ * storage. Pass an empty array (or omit) to clear all rules. See the
246
+ * [Cloudflare R2 docs](https://developers.cloudflare.com/r2/buckets/object-lifecycles/)
247
+ * for details and limits (max 1000 rules per bucket).
248
+ *
249
+ * @example Delete objects 30 days after upload
250
+ * ```typescript
251
+ * const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
252
+ * lifecycleRules: [
253
+ * {
254
+ * id: "expire-old-objects",
255
+ * deleteObjectsTransition: {
256
+ * condition: { type: "Age", maxAge: 60 * 60 * 24 * 30 },
257
+ * },
258
+ * },
259
+ * ],
260
+ * });
261
+ * ```
262
+ *
263
+ * @example Transition to InfrequentAccess after 60 days, delete after 365
264
+ * ```typescript
265
+ * const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
266
+ * lifecycleRules: [
267
+ * {
268
+ * id: "archive-then-delete",
269
+ * prefix: "logs/",
270
+ * storageClassTransitions: [
271
+ * {
272
+ * condition: { type: "Age", maxAge: 60 * 60 * 24 * 60 },
273
+ * storageClass: "InfrequentAccess",
274
+ * },
275
+ * ],
276
+ * deleteObjectsTransition: {
277
+ * condition: { type: "Age", maxAge: 60 * 60 * 24 * 365 },
278
+ * },
279
+ * },
280
+ * ],
281
+ * });
282
+ * ```
283
+ *
284
+ * @example Abort incomplete multipart uploads after 7 days
285
+ * ```typescript
286
+ * const bucket = yield* Cloudflare.R2Bucket("MyBucket", {
287
+ * lifecycleRules: [
288
+ * {
289
+ * id: "abort-stale-uploads",
290
+ * abortMultipartUploadsTransition: {
291
+ * condition: { type: "Age", maxAge: 60 * 60 * 24 * 7 },
292
+ * },
293
+ * },
294
+ * ],
295
+ * });
296
+ * ```
178
297
  */
179
298
  export const R2Bucket = Resource<R2Bucket>("Cloudflare.R2Bucket")({
180
299
  bind: R2BucketBinding.bind,
@@ -184,6 +303,23 @@ export declare namespace R2Bucket {
184
303
  export type StorageClass = "Standard" | "InfrequentAccess";
185
304
  export type Jurisdiction = "default" | "eu" | "fedramp";
186
305
  export type Location = "apac" | "eeur" | "enam" | "weur" | "wnam" | "oc";
306
+ export type LifecycleRule = {
307
+ id: string;
308
+ enabled: boolean;
309
+ prefix: string;
310
+ abortMultipartUploadsTransition:
311
+ | { condition: { type: "Age"; maxAge: number } | undefined }
312
+ | undefined;
313
+ deleteObjectsTransition:
314
+ | { condition: R2BucketLifecycleCondition | undefined }
315
+ | undefined;
316
+ storageClassTransitions:
317
+ | {
318
+ condition: R2BucketLifecycleCondition;
319
+ storageClass: "InfrequentAccess";
320
+ }[]
321
+ | undefined;
322
+ };
187
323
  export type CustomDomain = {
188
324
  domain: string;
189
325
  zoneId: string | undefined;
@@ -220,10 +356,41 @@ export const R2BucketProvider = () =>
220
356
  const patchBucket = yield* r2.patchBucket;
221
357
  const deleteBucket = yield* r2.deleteBucket;
222
358
  const getBucket = yield* r2.getBucket;
359
+ const deleteObjects = yield* r2.deleteObjects;
223
360
  const listBucketDomainCustoms = yield* r2.listBucketDomainCustoms;
224
361
  const createBucketDomainCustom = yield* r2.createBucketDomainCustom;
225
362
  const updateBucketDomainCustom = yield* r2.updateBucketDomainCustom;
226
363
  const deleteBucketDomainCustom = yield* r2.deleteBucketDomainCustom;
364
+ const getBucketLifecycle = yield* r2.getBucketLifecycle;
365
+ const putBucketLifecycle = yield* r2.putBucketLifecycle;
366
+
367
+ const emptyBucket = (
368
+ bucketName: string,
369
+ jurisdiction: R2Bucket.Jurisdiction,
370
+ ) =>
371
+ r2.listObjects
372
+ .items({
373
+ accountId,
374
+ bucketName,
375
+ cfR2Jurisdiction: jurisdiction,
376
+ perPage: 1000,
377
+ })
378
+ .pipe(
379
+ Stream.filter(
380
+ (o): o is typeof o & { key: string } =>
381
+ typeof o.key === "string" && o.key !== "",
382
+ ),
383
+ Stream.map((o) => o.key),
384
+ Stream.runForEachArray((chunk) =>
385
+ deleteObjects({
386
+ accountId,
387
+ bucketName,
388
+ cfR2Jurisdiction: jurisdiction,
389
+ body: [...chunk],
390
+ }),
391
+ ),
392
+ Effect.catchTag("NoSuchBucket", () => Effect.void),
393
+ );
227
394
 
228
395
  const createBucketName = (id: string, name: string | undefined) =>
229
396
  Effect.gen(function* () {
@@ -252,7 +419,7 @@ export const R2BucketProvider = () =>
252
419
  }).pipe(
253
420
  Effect.retry({
254
421
  while: isNoSuchBucket,
255
- schedule: r2CustomDomainConsistencySchedule,
422
+ schedule: r2BucketEndpointConsistencySchedule,
256
423
  }),
257
424
  Effect.map((response) =>
258
425
  response.domains.map(toCustomDomainAttributes),
@@ -351,7 +518,7 @@ export const R2BucketProvider = () =>
351
518
  }).pipe(
352
519
  Effect.retry({
353
520
  while: isNoSuchBucket,
354
- schedule: r2CustomDomainConsistencySchedule,
521
+ schedule: r2BucketEndpointConsistencySchedule,
355
522
  }),
356
523
  );
357
524
  return toCustomDomainAttributes({ ...created, zoneId });
@@ -368,7 +535,7 @@ export const R2BucketProvider = () =>
368
535
  }).pipe(
369
536
  Effect.retry({
370
537
  while: isNoSuchBucket,
371
- schedule: r2CustomDomainConsistencySchedule,
538
+ schedule: r2BucketEndpointConsistencySchedule,
372
539
  }),
373
540
  );
374
541
  return toCustomDomainAttributes({
@@ -383,6 +550,45 @@ export const R2BucketProvider = () =>
383
550
  return applied.sort((a, b) => a.domain.localeCompare(b.domain));
384
551
  });
385
552
 
553
+ const reconcileLifecycleRules = (
554
+ bucketName: string,
555
+ jurisdiction: R2Bucket.Jurisdiction,
556
+ desired: R2BucketLifecycleRule[],
557
+ ) =>
558
+ Effect.gen(function* () {
559
+ const observed = yield* getBucketLifecycle({
560
+ accountId,
561
+ bucketName,
562
+ jurisdiction,
563
+ }).pipe(
564
+ Effect.retry({
565
+ while: isNoSuchBucket,
566
+ schedule: r2BucketEndpointConsistencySchedule,
567
+ }),
568
+ );
569
+
570
+ const observedRules = (observed.rules ?? []).map(toLifecycleRule);
571
+ const desiredRules = desired.map(normalizeLifecycleRule);
572
+
573
+ if (deepEqual(observedRules, desiredRules)) {
574
+ return desiredRules;
575
+ }
576
+
577
+ yield* putBucketLifecycle({
578
+ accountId,
579
+ bucketName,
580
+ jurisdiction,
581
+ rules: desired.map(toLifecyclePutPayload),
582
+ }).pipe(
583
+ Effect.retry({
584
+ while: isNoSuchBucket,
585
+ schedule: r2BucketEndpointConsistencySchedule,
586
+ }),
587
+ );
588
+
589
+ return desiredRules;
590
+ });
591
+
386
592
  return {
387
593
  stables: ["bucketName", "accountId"],
388
594
  diff: Effect.fn(function* ({ id, olds = {}, news = {}, output }) {
@@ -412,6 +618,9 @@ export const R2BucketProvider = () =>
412
618
  if (!deepEqual(olds.domains, news.domains)) {
413
619
  return { action: "update" } as const;
414
620
  }
621
+ if (!deepEqual(olds.lifecycleRules, news.lifecycleRules)) {
622
+ return { action: "update" } as const;
623
+ }
415
624
  }),
416
625
  reconcile: Effect.fn(function* ({ id, news = {}, output }) {
417
626
  const name = yield* createBucketName(id, news.name);
@@ -481,9 +690,16 @@ export const R2BucketProvider = () =>
481
690
  output?.domains ?? [],
482
691
  );
483
692
 
693
+ const lifecycleRules = yield* reconcileLifecycleRules(
694
+ attrs.bucketName,
695
+ attrs.jurisdiction,
696
+ news.lifecycleRules ?? [],
697
+ );
698
+
484
699
  return {
485
700
  ...attrs,
486
701
  domains,
702
+ lifecycleRules,
487
703
  };
488
704
  }),
489
705
  delete: Effect.fn(function* ({ output }) {
@@ -497,6 +713,7 @@ export const R2BucketProvider = () =>
497
713
  Effect.catchIf(isMissingCustomDomainOrBucket, () => Effect.void),
498
714
  );
499
715
  }
716
+ yield* emptyBucket(output.bucketName, output.jurisdiction);
500
717
  yield* deleteBucket({
501
718
  accountId: output.accountId,
502
719
  bucketName: output.bucketName,
@@ -519,6 +736,7 @@ export const R2BucketProvider = () =>
519
736
  location: normalizeLocation(bucket.location),
520
737
  accountId: acct,
521
738
  domains: output?.domains ?? [],
739
+ lifecycleRules: output?.lifecycleRules ?? [],
522
740
  })),
523
741
  Effect.catchTag("NoSuchBucket", () => Effect.succeed(undefined)),
524
742
  );
@@ -527,10 +745,11 @@ export const R2BucketProvider = () =>
527
745
  }),
528
746
  );
529
747
 
530
- // R2 can make a newly-created bucket visible to `getBucket` before the custom
531
- // domain endpoints accept it. Retry only that narrow `NoSuchBucket` lag here;
532
- // not-found domains are still treated as terminal for idempotent deletes.
533
- const r2CustomDomainConsistencySchedule = Schedule.exponential(100).pipe(
748
+ // R2 can make a newly-created bucket visible to `getBucket` before its
749
+ // sub-resource endpoints (custom domains, lifecycle) accept it. Retry only
750
+ // that narrow `NoSuchBucket` lag here; not-found sub-resources are still
751
+ // treated as terminal for idempotent deletes.
752
+ const r2BucketEndpointConsistencySchedule = Schedule.exponential(100).pipe(
534
753
  Schedule.both(Schedule.recurs(5)),
535
754
  );
536
755
 
@@ -573,6 +792,51 @@ const isMissingCustomDomainOrBucket = (error: unknown): boolean =>
573
792
  ((error as { _tag: unknown })._tag === "DomainNotFound" ||
574
793
  (error as { _tag: unknown })._tag === "NoSuchBucket")));
575
794
 
795
+ type LifecycleRuleResponse = NonNullable<
796
+ r2.GetBucketLifecycleResponse["rules"]
797
+ >[number];
798
+
799
+ const toLifecycleRule = (
800
+ rule: LifecycleRuleResponse,
801
+ ): R2Bucket.LifecycleRule => ({
802
+ id: rule.id,
803
+ enabled: rule.enabled,
804
+ prefix: rule.conditions.prefix ?? "",
805
+ abortMultipartUploadsTransition: rule.abortMultipartUploadsTransition
806
+ ? { condition: rule.abortMultipartUploadsTransition.condition ?? undefined }
807
+ : undefined,
808
+ deleteObjectsTransition: rule.deleteObjectsTransition
809
+ ? { condition: rule.deleteObjectsTransition.condition ?? undefined }
810
+ : undefined,
811
+ storageClassTransitions: rule.storageClassTransitions ?? undefined,
812
+ });
813
+
814
+ const normalizeLifecycleRule = (
815
+ rule: R2BucketLifecycleRule,
816
+ ): R2Bucket.LifecycleRule => ({
817
+ id: rule.id,
818
+ enabled: rule.enabled ?? true,
819
+ prefix: rule.prefix ?? "",
820
+ abortMultipartUploadsTransition: rule.abortMultipartUploadsTransition
821
+ ? { condition: rule.abortMultipartUploadsTransition.condition }
822
+ : undefined,
823
+ deleteObjectsTransition: rule.deleteObjectsTransition
824
+ ? { condition: rule.deleteObjectsTransition.condition }
825
+ : undefined,
826
+ storageClassTransitions: rule.storageClassTransitions,
827
+ });
828
+
829
+ const toLifecyclePutPayload = (
830
+ rule: R2BucketLifecycleRule,
831
+ ): NonNullable<r2.PutBucketLifecycleRequest["rules"]>[number] => ({
832
+ id: rule.id,
833
+ enabled: rule.enabled ?? true,
834
+ conditions: { prefix: rule.prefix ?? "" },
835
+ abortMultipartUploadsTransition: rule.abortMultipartUploadsTransition,
836
+ deleteObjectsTransition: rule.deleteObjectsTransition,
837
+ storageClassTransitions: rule.storageClassTransitions,
838
+ });
839
+
576
840
  const isNoSuchBucket = (error: unknown): boolean =>
577
841
  typeof error === "object" &&
578
842
  error !== null &&