alchemy 0.68.0 → 0.69.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/bin/alchemy.js +32 -33
  2. package/bin/commands/init.ts +41 -55
  3. package/lib/aws/control/properties.d.ts +64 -0
  4. package/lib/aws/control/properties.d.ts.map +1 -1
  5. package/lib/aws/control/properties.js +554 -39
  6. package/lib/aws/control/properties.js.map +1 -1
  7. package/lib/aws/control/resource.d.ts.map +1 -1
  8. package/lib/aws/control/resource.js +33 -0
  9. package/lib/aws/control/resource.js.map +1 -1
  10. package/lib/aws/control/types.d.ts +5451 -1587
  11. package/lib/aws/control/types.d.ts.map +1 -1
  12. package/lib/aws/control/types.js.map +1 -1
  13. package/lib/aws/control/update-types.d.ts +12913 -0
  14. package/lib/aws/control/update-types.d.ts.map +1 -0
  15. package/lib/aws/control/update-types.js +12916 -0
  16. package/lib/aws/control/update-types.js.map +1 -0
  17. package/lib/cloudflare/api.d.ts +1 -1
  18. package/lib/cloudflare/api.d.ts.map +1 -1
  19. package/lib/cloudflare/api.js +9 -10
  20. package/lib/cloudflare/api.js.map +1 -1
  21. package/lib/cloudflare/astro/astro.d.ts.map +1 -1
  22. package/lib/cloudflare/astro/astro.js +3 -3
  23. package/lib/cloudflare/astro/astro.js.map +1 -1
  24. package/lib/cloudflare/bucket-object.d.ts +86 -0
  25. package/lib/cloudflare/bucket-object.d.ts.map +1 -0
  26. package/lib/cloudflare/bucket-object.js +67 -0
  27. package/lib/cloudflare/bucket-object.js.map +1 -0
  28. package/lib/cloudflare/bucket.d.ts +36 -6
  29. package/lib/cloudflare/bucket.d.ts.map +1 -1
  30. package/lib/cloudflare/bucket.js +154 -18
  31. package/lib/cloudflare/bucket.js.map +1 -1
  32. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  33. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  34. package/lib/cloudflare/container.d.ts.map +1 -1
  35. package/lib/cloudflare/container.js +2 -2
  36. package/lib/cloudflare/container.js.map +1 -1
  37. package/lib/cloudflare/index.d.ts +1 -0
  38. package/lib/cloudflare/index.d.ts.map +1 -1
  39. package/lib/cloudflare/index.js +1 -0
  40. package/lib/cloudflare/index.js.map +1 -1
  41. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  42. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +44 -12
  43. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  44. package/lib/cloudflare/next.js.map +1 -1
  45. package/lib/cloudflare/nuxt/nuxt.d.ts.map +1 -1
  46. package/lib/cloudflare/nuxt/nuxt.js +3 -3
  47. package/lib/cloudflare/nuxt/nuxt.js.map +1 -1
  48. package/lib/cloudflare/tanstack-start/plugin.d.ts +2 -15
  49. package/lib/cloudflare/tanstack-start/plugin.d.ts.map +1 -1
  50. package/lib/cloudflare/tanstack-start/plugin.js +5 -25
  51. package/lib/cloudflare/tanstack-start/plugin.js.map +1 -1
  52. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -1
  53. package/lib/cloudflare/tanstack-start/tanstack-start.js +15 -4
  54. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -1
  55. package/lib/cloudflare/vite/vite.d.ts.map +1 -1
  56. package/lib/cloudflare/vite/vite.js +3 -3
  57. package/lib/cloudflare/vite/vite.js.map +1 -1
  58. package/lib/cloudflare/website.d.ts +7 -1
  59. package/lib/cloudflare/website.d.ts.map +1 -1
  60. package/lib/cloudflare/website.js +20 -1
  61. package/lib/cloudflare/website.js.map +1 -1
  62. package/lib/cloudflare/worker-metadata.d.ts +4 -1
  63. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  64. package/lib/cloudflare/worker-metadata.js +2 -0
  65. package/lib/cloudflare/worker-metadata.js.map +1 -1
  66. package/lib/cloudflare/worker.d.ts +6 -0
  67. package/lib/cloudflare/worker.d.ts.map +1 -1
  68. package/lib/cloudflare/worker.js +1 -0
  69. package/lib/cloudflare/worker.js.map +1 -1
  70. package/lib/cloudflare/wrangler.json.d.ts +34 -9
  71. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  72. package/lib/cloudflare/wrangler.json.js +12 -1
  73. package/lib/cloudflare/wrangler.json.js.map +1 -1
  74. package/lib/docker/api.d.ts +22 -1
  75. package/lib/docker/api.d.ts.map +1 -1
  76. package/lib/docker/api.js +64 -2
  77. package/lib/docker/api.js.map +1 -1
  78. package/lib/docker/container.d.ts +88 -0
  79. package/lib/docker/container.d.ts.map +1 -1
  80. package/lib/docker/container.js +36 -0
  81. package/lib/docker/container.js.map +1 -1
  82. package/lib/llms.cloudflare.txt +2 -2
  83. package/lib/neon/branch.d.ts +19 -3
  84. package/lib/neon/branch.d.ts.map +1 -1
  85. package/lib/neon/branch.js +4 -4
  86. package/lib/neon/branch.js.map +1 -1
  87. package/lib/neon/project.d.ts +17 -1
  88. package/lib/neon/project.d.ts.map +1 -1
  89. package/lib/neon/project.js +4 -4
  90. package/lib/neon/project.js.map +1 -1
  91. package/lib/serde.d.ts +3 -0
  92. package/lib/serde.d.ts.map +1 -1
  93. package/lib/serde.js +40 -0
  94. package/lib/serde.js.map +1 -1
  95. package/lib/test/bun.js.map +1 -1
  96. package/package.json +1 -1
  97. package/src/aws/control/properties.ts +554 -39
  98. package/src/aws/control/resource.ts +58 -0
  99. package/src/aws/control/types.ts +85281 -84914
  100. package/src/aws/control/update-types.ts +12917 -0
  101. package/src/cloudflare/api.ts +9 -10
  102. package/src/cloudflare/astro/astro.ts +8 -3
  103. package/src/cloudflare/bucket-object.ts +127 -0
  104. package/src/cloudflare/bucket.ts +218 -18
  105. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  106. package/src/cloudflare/container.ts +4 -5
  107. package/src/cloudflare/index.ts +1 -0
  108. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +57 -13
  109. package/src/cloudflare/next.ts +2 -2
  110. package/src/cloudflare/nuxt/nuxt.ts +8 -3
  111. package/src/cloudflare/tanstack-start/plugin.ts +7 -28
  112. package/src/cloudflare/tanstack-start/tanstack-start.ts +15 -4
  113. package/src/cloudflare/vite/vite.ts +8 -3
  114. package/src/cloudflare/website.ts +29 -2
  115. package/src/cloudflare/worker-metadata.ts +6 -1
  116. package/src/cloudflare/worker.ts +7 -0
  117. package/src/cloudflare/wrangler.json.ts +47 -18
  118. package/src/docker/api.ts +86 -2
  119. package/src/docker/container.ts +97 -0
  120. package/src/llms.cloudflare.txt +2 -2
  121. package/src/neon/branch.ts +6 -5
  122. package/src/neon/project.ts +4 -4
  123. package/src/serde.ts +50 -0
  124. package/src/test/bun.ts +1 -1
  125. package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +2 -2
  126. package/templates/tanstack-start/.cta.json +13 -0
  127. package/templates/tanstack-start/README.md +267 -49
  128. package/templates/tanstack-start/_gitignore +9 -18
  129. package/templates/tanstack-start/alchemy.run.ts +2 -2
  130. package/templates/tanstack-start/package.json +24 -19
  131. package/templates/tanstack-start/public/favicon.ico +0 -0
  132. package/templates/tanstack-start/public/logo192.png +0 -0
  133. package/templates/tanstack-start/public/logo512.png +0 -0
  134. package/templates/tanstack-start/public/manifest.json +25 -0
  135. package/templates/tanstack-start/public/robots.txt +3 -0
  136. package/templates/tanstack-start/src/components/Header.tsx +21 -0
  137. package/templates/tanstack-start/src/logo.svg +12 -0
  138. package/templates/tanstack-start/src/router.tsx +7 -16
  139. package/templates/tanstack-start/src/routes/__root.tsx +13 -114
  140. package/templates/tanstack-start/src/routes/api.demo-names.ts +15 -0
  141. package/templates/tanstack-start/src/routes/demo.start.api-request.tsx +44 -0
  142. package/templates/tanstack-start/src/routes/demo.start.server-funcs.tsx +97 -0
  143. package/templates/tanstack-start/src/routes/index.tsx +31 -4
  144. package/templates/tanstack-start/src/styles.css +15 -0
  145. package/templates/tanstack-start/tsconfig.json +19 -22
  146. package/templates/tanstack-start/types/env.d.ts +2 -2
  147. package/templates/tanstack-start/vite.config.ts +16 -25
  148. package/workers/tunnel-proxy.js +1 -1
  149. package/templates/tanstack-start/_prettierignore +0 -4
  150. package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
  151. package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
  152. package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
  153. package/templates/tanstack-start/public/favicon-16x16.png +0 -0
  154. package/templates/tanstack-start/public/favicon-32x32.png +0 -0
  155. package/templates/tanstack-start/public/favicon.png +0 -0
  156. package/templates/tanstack-start/public/site.webmanifest +0 -19
  157. package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +0 -53
  158. package/templates/tanstack-start/src/components/NotFound.tsx +0 -25
  159. package/templates/tanstack-start/src/components/PostError.tsx +0 -5
  160. package/templates/tanstack-start/src/components/UserError.tsx +0 -5
  161. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +0 -10
  162. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +0 -10
  163. package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +0 -34
  164. package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +0 -16
  165. package/templates/tanstack-start/src/routes/api/users.$userId.ts +0 -28
  166. package/templates/tanstack-start/src/routes/api/users.ts +0 -59
  167. package/templates/tanstack-start/src/routes/customScript[.]js.ts +0 -10
  168. package/templates/tanstack-start/src/routes/deferred.tsx +0 -62
  169. package/templates/tanstack-start/src/routes/posts.$postId.tsx +0 -34
  170. package/templates/tanstack-start/src/routes/posts.index.tsx +0 -8
  171. package/templates/tanstack-start/src/routes/posts.tsx +0 -38
  172. package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +0 -29
  173. package/templates/tanstack-start/src/routes/redirect.tsx +0 -9
  174. package/templates/tanstack-start/src/routes/users.$userId.tsx +0 -44
  175. package/templates/tanstack-start/src/routes/users.index.tsx +0 -18
  176. package/templates/tanstack-start/src/routes/users.tsx +0 -49
  177. package/templates/tanstack-start/src/styles/app.css +0 -12
  178. package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +0 -41
  179. package/templates/tanstack-start/src/utils/posts.tsx +0 -40
  180. package/templates/tanstack-start/src/utils/seo.ts +0 -33
  181. package/templates/tanstack-start/src/utils/users.tsx +0 -5
@@ -1,5 +1,6 @@
1
1
  import { Provider, type Credentials } from "../auth.ts";
2
2
  import type { Secret } from "../secret.ts";
3
+ import { isBinary } from "../serde.ts";
3
4
  import { memoize } from "../util/memoize.ts";
4
5
  import { withExponentialBackoff } from "../util/retry.ts";
5
6
  import { safeFetch } from "../util/safe-fetch.ts";
@@ -257,7 +258,7 @@ export class CloudflareApi {
257
258
  return this.fetch(path, {
258
259
  ...init,
259
260
  method: "POST",
260
- body: this.toBody(body),
261
+ body: await this.toBody(body),
261
262
  });
262
263
  }
263
264
 
@@ -272,18 +273,16 @@ export class CloudflareApi {
272
273
  return this.fetch(path, {
273
274
  ...init,
274
275
  method: "PUT",
275
- body: this.toBody(body),
276
+ body: await this.toBody(body),
276
277
  });
277
278
  }
278
279
 
279
- toBody(body: BodyInit): BodyInit {
280
- return body instanceof FormData
280
+ async toBody(body: BodyInit): Promise<BodyInit> {
281
+ return body instanceof FormData ||
282
+ typeof body === "string" ||
283
+ isBinary(body)
281
284
  ? body
282
- : typeof body === "string"
283
- ? body
284
- : body instanceof ReadableStream
285
- ? body
286
- : JSON.stringify(body);
285
+ : JSON.stringify(body);
287
286
  }
288
287
 
289
288
  /**
@@ -297,7 +296,7 @@ export class CloudflareApi {
297
296
  return this.fetch(path, {
298
297
  ...init,
299
298
  method: "PATCH",
300
- body: this.toBody(body),
299
+ body: await this.toBody(body),
301
300
  });
302
301
  }
303
302
 
@@ -3,7 +3,12 @@ import { getPackageManagerRunner } from "../../util/detect-package-manager.ts";
3
3
  import type { Assets } from "../assets.ts";
4
4
  import type { Bindings } from "../bindings.ts";
5
5
  import { withSkipPathValidation } from "../miniflare/paths.ts";
6
- import { Website, type WebsiteProps } from "../website.ts";
6
+ import {
7
+ spreadBuildProps,
8
+ spreadDevProps,
9
+ Website,
10
+ type WebsiteProps,
11
+ } from "../website.ts";
7
12
  import type { Worker } from "../worker.ts";
8
13
 
9
14
  /**
@@ -75,8 +80,8 @@ export async function Astro<B extends Bindings>(
75
80
  return await Website(id, {
76
81
  ...props,
77
82
  noBundle: props.noBundle ?? true,
78
- build: props.build ?? `${runner} astro build`,
79
- dev: props.dev ?? `${runner} astro dev`,
83
+ build: spreadBuildProps(props, `${runner} astro build`),
84
+ dev: spreadDevProps(props, `${runner} astro dev`),
80
85
  entrypoint:
81
86
  props.entrypoint ??
82
87
  (output === "server" ? "dist/_worker.js/index.js" : undefined),
@@ -0,0 +1,127 @@
1
+ import type { R2PutOptions } from "@cloudflare/workers-types/experimental/index.ts";
2
+ import type { Context } from "../context.ts";
3
+ import { Resource } from "../resource.ts";
4
+ import { createCloudflareApi, type CloudflareApiOptions } from "./api.ts";
5
+ import { deleteObject, type PutObjectObject, type R2Bucket } from "./bucket.ts";
6
+
7
+ /**
8
+ * Properties for creating or updating an R2 Object
9
+ */
10
+ export interface R2ObjectProps extends CloudflareApiOptions {
11
+ /**
12
+ * The R2 bucket where the object will be stored
13
+ */
14
+ bucket: R2Bucket;
15
+
16
+ /**
17
+ * The key (path/name) of the object within the bucket
18
+ * Must be a valid object key as defined by R2 storage requirements
19
+ */
20
+ key: string;
21
+
22
+ /**
23
+ * The content to store in the object
24
+ * Supports various data types including streams, buffers, strings, and blobs
25
+ */
26
+ content: PutObjectObject;
27
+
28
+ /**
29
+ * The options to use for the object
30
+ */
31
+ options?: Pick<R2PutOptions, "httpMetadata">;
32
+ }
33
+
34
+ /**
35
+ * Output returned after R2 Object creation/update
36
+ */
37
+ export interface R2Object {
38
+ /**
39
+ * The resource identifier
40
+ */
41
+ id: string;
42
+
43
+ /**
44
+ * The key (path/name) of the object within the bucket
45
+ */
46
+ key: string;
47
+
48
+ /**
49
+ * The etag of the object
50
+ */
51
+ etag: string;
52
+ }
53
+
54
+ /**
55
+ * Creates and manages objects within Cloudflare R2 Buckets.
56
+ *
57
+ * R2 Objects represent individual files or data stored within an R2 bucket.
58
+ * This resource provides lifecycle management for object creation, updates, and deletion.
59
+ *
60
+ * @example
61
+ * // Create a simple text object in a bucket
62
+ * const textObject = await R2Object("readme", {
63
+ * bucket: myBucket,
64
+ * key: "docs/README.txt",
65
+ * content: "Welcome to our application!"
66
+ * });
67
+ *
68
+ * @example
69
+ * // Store JSON data as an object
70
+ * const configObject = await R2Object("app-config", {
71
+ * bucket: configBucket,
72
+ * key: "config/app.json",
73
+ * content: JSON.stringify({ version: "1.0.0", debug: false })
74
+ * });
75
+ *
76
+ * @example
77
+ * // Store binary data from a buffer
78
+ * const imageObject = await R2Object("user-avatar", {
79
+ * bucket: assetsBucket,
80
+ * key: "avatars/user-123.png",
81
+ * content: imageBuffer
82
+ * });
83
+ *
84
+ * @example
85
+ * // Store data from a readable stream
86
+ * const streamObject = await R2Object("upload", {
87
+ * bucket: uploadsBucket,
88
+ * key: "uploads/file.dat",
89
+ * content: readableStream
90
+ * });
91
+ *
92
+ * @see https://developers.cloudflare.com/r2/api/workers/workers-api-reference/
93
+ */
94
+ export const R2Object = Resource(
95
+ "cloudflare::R2Object",
96
+ async function (
97
+ this: Context<R2Object>,
98
+ id: string,
99
+ props: R2ObjectProps,
100
+ ): Promise<R2Object> {
101
+ if (this.phase === "delete") {
102
+ const api = await createCloudflareApi(props);
103
+ // Delete the object from the bucket
104
+ await deleteObject(api, {
105
+ bucketName: props.bucket.name,
106
+ key: props.key,
107
+ });
108
+ return this.destroy();
109
+ } else {
110
+ if (this.phase === "update" && this.output?.key !== props.key) {
111
+ this.replace();
112
+ }
113
+ // Create or update the object in the bucket
114
+ const response = await props.bucket.put(
115
+ props.key,
116
+ props.content,
117
+ props.options,
118
+ );
119
+
120
+ return {
121
+ id,
122
+ key: props.key,
123
+ etag: response.etag,
124
+ };
125
+ }
126
+ },
127
+ );
@@ -1,7 +1,10 @@
1
+ import type { R2PutOptions } from "@cloudflare/workers-types/experimental/index.ts";
2
+ import * as mf from "miniflare";
1
3
  import { isDeepStrictEqual } from "node:util";
2
4
  import type { Context } from "../context.ts";
3
5
  import { Resource, ResourceKind } from "../resource.ts";
4
6
  import { Scope } from "../scope.ts";
7
+ import { streamToBuffer } from "../serde.ts";
5
8
  import { isRetryableError } from "../state/r2-rest-state-store.ts";
6
9
  import { withExponentialBackoff } from "../util/retry.ts";
7
10
  import { CloudflareApiError, handleApiError } from "./api-error.ts";
@@ -15,6 +18,7 @@ import {
15
18
  type CloudflareApiOptions,
16
19
  } from "./api.ts";
17
20
  import { deleteMiniflareBinding } from "./miniflare/delete.ts";
21
+ import { getDefaultPersistPath } from "./miniflare/paths.ts";
18
22
 
19
23
  export type R2BucketJurisdiction = "default" | "eu" | "fedramp";
20
24
 
@@ -89,6 +93,11 @@ export interface BucketProps extends CloudflareApiOptions {
89
93
  */
90
94
  lock?: R2BucketLockRule[];
91
95
 
96
+ /**
97
+ * Enable data catalog for bucket
98
+ */
99
+ dataCatalog?: boolean;
100
+
92
101
  /**
93
102
  * Whether to emulate the bucket locally when Alchemy is running in watch mode.
94
103
  */
@@ -245,9 +254,10 @@ export type R2ObjectMetadata = {
245
254
  etag: string;
246
255
  uploaded: Date;
247
256
  size: number;
257
+ httpMetadata?: R2HTTPMetadata;
248
258
  };
249
259
 
250
- export type R2Object = R2ObjectMetadata & {
260
+ export type R2ObjectContent = R2ObjectMetadata & {
251
261
  arrayBuffer(): Promise<ArrayBuffer>;
252
262
  bytes(): Promise<Uint8Array>;
253
263
  text(): Promise<string>;
@@ -264,7 +274,7 @@ export type PutR2ObjectResponse = {
264
274
  };
265
275
 
266
276
  export type R2Objects = {
267
- objects: R2ObjectMetadata[];
277
+ objects: Omit<R2ObjectMetadata, "httpMetadata">[];
268
278
  } & (
269
279
  | {
270
280
  truncated: true;
@@ -278,7 +288,7 @@ export type R2Objects = {
278
288
 
279
289
  export type R2Bucket = _R2Bucket & {
280
290
  head(key: string): Promise<R2ObjectMetadata | null>;
281
- get(key: string): Promise<R2Object | null>;
291
+ get(key: string): Promise<R2ObjectContent | null>;
282
292
  put(
283
293
  key: string,
284
294
  value:
@@ -288,6 +298,7 @@ export type R2Bucket = _R2Bucket & {
288
298
  | string
289
299
  | null
290
300
  | Blob,
301
+ options?: Pick<R2PutOptions, "httpMetadata">,
291
302
  ): Promise<PutR2ObjectResponse>;
292
303
  delete(key: string): Promise<Response>;
293
304
  list(options?: R2ListOptions): Promise<R2Objects>;
@@ -337,6 +348,24 @@ type _R2Bucket = Omit<BucketProps, "delete" | "dev"> & {
337
348
  */
338
349
  remote: boolean;
339
350
  };
351
+
352
+ /**
353
+ * Data catalog for the bucket
354
+ */
355
+ catalog?: {
356
+ /**
357
+ * ID of the data catalog
358
+ */
359
+ id: string;
360
+ /**
361
+ * Name of the data catalog
362
+ */
363
+ name: string;
364
+ /**
365
+ * Host of the data catalog
366
+ */
367
+ host: string;
368
+ };
340
369
  };
341
370
 
342
371
  export function isBucket(resource: any): resource is R2Bucket {
@@ -391,6 +420,8 @@ export async function R2Bucket(
391
420
  id: string,
392
421
  props: BucketProps = {},
393
422
  ): Promise<R2Bucket> {
423
+ const scope = Scope.current;
424
+ const isLocal = scope.local && props.dev?.remote !== true;
394
425
  const api = await createCloudflareApi(props);
395
426
  const bucket = await _R2Bucket(id, {
396
427
  ...props,
@@ -399,14 +430,54 @@ export async function R2Bucket(
399
430
  force: Scope.current.local,
400
431
  },
401
432
  });
433
+
434
+ let _miniflare: mf.Miniflare | undefined;
435
+ const miniflare = () => {
436
+ if (_miniflare) {
437
+ return _miniflare;
438
+ }
439
+ _miniflare = new mf.Miniflare({
440
+ script: "",
441
+ modules: true,
442
+ defaultPersistRoot: getDefaultPersistPath(scope.rootDir),
443
+ r2Buckets: [bucket.dev.id],
444
+ log: process.env.DEBUG ? new mf.Log(mf.LogLevel.DEBUG) : undefined,
445
+ });
446
+ scope.onCleanup(async () => _miniflare?.dispose());
447
+ return _miniflare;
448
+ };
449
+ const localBucket = () => miniflare().getR2Bucket(bucket.dev.id);
450
+
402
451
  return {
403
452
  ...bucket,
404
- head: async (key: string) =>
405
- headObject(api, {
453
+ head: async (key: string) => {
454
+ if (isLocal) {
455
+ const result = await (await localBucket()).head(key);
456
+ if (result) {
457
+ return {
458
+ key: result.key,
459
+ etag: result.etag,
460
+ uploaded: result.uploaded,
461
+ size: result.size,
462
+ httpMetadata: result.httpMetadata,
463
+ } as R2ObjectMetadata;
464
+ }
465
+ return null;
466
+ }
467
+ return headObject(api, {
406
468
  bucketName: bucket.name,
407
469
  key,
408
- }),
470
+ });
471
+ },
409
472
  get: async (key: string) => {
473
+ if (isLocal) {
474
+ const result = await (await localBucket()).get(key);
475
+ if (result) {
476
+ // cast because workers vs node built-ins
477
+ return result as unknown as R2ObjectContent;
478
+ }
479
+ return null;
480
+ }
410
481
  const response = await getObject(api, {
411
482
  bucketName: bucket.name,
412
483
  key,
@@ -419,19 +490,42 @@ export async function R2Bucket(
419
490
  throw await handleApiError(response, "get", "object", key);
420
491
  }
421
492
  },
422
- list: async (options?: R2ListOptions): Promise<R2Objects> =>
423
- listObjects(api, bucket.name, {
493
+ list: async (options?: R2ListOptions): Promise<R2Objects> => {
494
+ if (isLocal) {
495
+ return (await localBucket()).list(options);
496
+ }
497
+ return listObjects(api, bucket.name, {
424
498
  ...options,
425
499
  jurisdiction: bucket.jurisdiction,
426
- }),
500
+ });
501
+ },
427
502
  put: async (
428
503
  key: string,
429
504
  value: PutObjectObject,
505
+ options?: Pick<R2PutOptions, "httpMetadata">,
430
506
  ): Promise<PutR2ObjectResponse> => {
507
+ if (isLocal) {
508
+ return await (await localBucket()).put(
509
+ key,
510
+ typeof value === "string"
511
+ ? value
512
+ : Buffer.isBuffer(value) ||
513
+ value instanceof Uint8Array ||
514
+ value instanceof ArrayBuffer
515
+ ? new Uint8Array(value)
516
+ : value instanceof Blob
517
+ ? new Uint8Array(await value.arrayBuffer())
518
+ : value instanceof ReadableStream
519
+ ? new Uint8Array(await streamToBuffer(value))
520
+ : value,
521
+ options,
522
+ );
523
+ }
431
524
  const response = await putObject(api, {
432
525
  bucketName: bucket.name,
433
526
  key: key,
434
527
  object: value,
528
+ options: options,
435
529
  });
436
530
  const body = (await response.json()) as {
437
531
  result: {
@@ -450,19 +544,24 @@ export async function R2Bucket(
450
544
  size: Number(body.result.size),
451
545
  };
452
546
  },
453
- delete: async (key: string) =>
454
- deleteObject(api, {
547
+ delete: async (key: string) => {
548
+ if (isLocal) {
549
+ await (await localBucket()).delete(key);
550
+ }
551
+ return deleteObject(api, {
455
552
  bucketName: bucket.name,
456
553
  key: key,
457
- }),
554
+ });
555
+ },
458
556
  };
459
557
  }
460
558
 
461
- const parseR2Object = (key: string, response: Response): R2Object => ({
559
+ const parseR2Object = (key: string, response: Response): R2ObjectContent => ({
462
560
  etag: response.headers.get("ETag")!,
463
561
  uploaded: parseDate(response.headers),
464
562
  key,
465
563
  size: Number(response.headers.get("Content-Length")),
564
+ httpMetadata: mapHeadersToHttpMetadata(response.headers),
466
565
  arrayBuffer: () => response.arrayBuffer(),
467
566
  bytes: () => response.bytes(),
468
567
  text: () => response.text(),
@@ -481,7 +580,7 @@ const _R2Bucket = Resource(
481
580
  props: BucketProps = {},
482
581
  ): Promise<_R2Bucket> {
483
582
  const bucketName =
484
- props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
583
+ props.name ?? (this.output?.name || this.scope.createPhysicalName(id));
485
584
 
486
585
  if (this.phase === "update" && this.output?.name !== bucketName) {
487
586
  this.replace();
@@ -513,6 +612,9 @@ const _R2Bucket = Resource(
513
612
 
514
613
  if (this.phase === "delete") {
515
614
  if (props.delete !== false) {
615
+ if (this.output?.catalog) {
616
+ await disableDataCatalog(api, bucketName);
617
+ }
516
618
  if (this.output.dev?.id) {
517
619
  await deleteMiniflareBinding(this.scope, "r2", this.output.dev.id);
518
620
  }
@@ -552,6 +654,16 @@ const _R2Bucket = Resource(
552
654
  if (props.lock?.length) {
553
655
  await putBucketLockRules(api, bucketName, props);
554
656
  }
657
+ let dataCatalog:
658
+ | {
659
+ id: string;
660
+ name: string;
661
+ host: string;
662
+ }
663
+ | undefined;
664
+ if (props.dataCatalog) {
665
+ dataCatalog = await enableDataCatalog(api, bucketName);
666
+ }
555
667
  return {
556
668
  name: bucketName,
557
669
  location: bucket.location,
@@ -565,6 +677,7 @@ const _R2Bucket = Resource(
565
677
  lock: props.lock,
566
678
  cors: props.cors,
567
679
  dev,
680
+ catalog: dataCatalog,
568
681
  };
569
682
  } else {
570
683
  if (bucketName !== this.output.name) {
@@ -572,6 +685,13 @@ const _R2Bucket = Resource(
572
685
  `Cannot update R2Bucket name after creation. Bucket name is immutable. Before: ${this.output.name}, After: ${bucketName}`,
573
686
  );
574
687
  }
688
+ if (this.output?.catalog && !props.dataCatalog) {
689
+ await disableDataCatalog(api, bucketName);
690
+ this.output.catalog = undefined;
691
+ }
692
+ if (!this.output.catalog && props.dataCatalog) {
693
+ this.output.catalog = await enableDataCatalog(api, bucketName);
694
+ }
575
695
  let domain = this.output.domain;
576
696
  if (!!domain !== allowPublicAccess) {
577
697
  domain = await putManagedDomain(
@@ -1051,6 +1171,7 @@ export async function headObject(
1051
1171
  etag: response.headers.get("ETag")?.replace(/"/g, "")!,
1052
1172
  uploaded: parseDate(response.headers),
1053
1173
  size: Number(response.headers.get("Content-Length")),
1174
+ httpMetadata: mapHeadersToHttpMetadata(response.headers),
1054
1175
  };
1055
1176
  }
1056
1177
 
@@ -1079,34 +1200,85 @@ export async function getObject(
1079
1200
  });
1080
1201
  }
1081
1202
 
1082
- type PutObjectObject =
1203
+ export type PutObjectObject =
1083
1204
  | ReadableStream
1084
1205
  | ArrayBuffer
1085
1206
  | ArrayBufferView
1207
+ | Uint8Array
1086
1208
  | string
1209
+ | Buffer
1087
1210
  | Blob;
1088
1211
 
1212
+ function mapHttpMetadataToHeaders(
1213
+ httpMetadata: R2PutOptions["httpMetadata"],
1214
+ ): Record<string, string> {
1215
+ const headers: Record<string, string> = {};
1216
+
1217
+ if (httpMetadata instanceof Headers) {
1218
+ httpMetadata.forEach((value, key) => {
1219
+ headers[key] = value;
1220
+ });
1221
+ } else {
1222
+ const {
1223
+ contentType,
1224
+ contentLanguage,
1225
+ contentDisposition,
1226
+ contentEncoding,
1227
+ cacheControl,
1228
+ cacheExpiry,
1229
+ } = httpMetadata as R2HTTPMetadata;
1230
+ if (contentType) headers["Content-Type"] = contentType;
1231
+ if (contentLanguage) headers["Content-Language"] = contentLanguage;
1232
+ if (contentDisposition) headers["Content-Disposition"] = contentDisposition;
1233
+ if (contentEncoding) headers["Content-Encoding"] = contentEncoding;
1234
+ if (cacheControl) headers["Cache-Control"] = cacheControl;
1235
+ if (cacheExpiry) headers.Expires = cacheExpiry.toUTCString();
1236
+ }
1237
+
1238
+ return headers;
1239
+ }
1240
+
1241
+ function mapHeadersToHttpMetadata(headers: Headers): R2HTTPMetadata {
1242
+ return {
1243
+ contentType: headers.get("Content-Type") ?? undefined,
1244
+ contentLanguage: headers.get("Content-Language") ?? undefined,
1245
+ contentDisposition: headers.get("Content-Disposition") ?? undefined,
1246
+ contentEncoding: headers.get("Content-Encoding") ?? undefined,
1247
+ cacheControl: headers.get("Cache-Control") ?? undefined,
1248
+ cacheExpiry: headers.get("Expires")
1249
+ ? new Date(headers.get("Expires")!)
1250
+ : undefined,
1251
+ };
1252
+ }
1253
+
1089
1254
  export async function putObject(
1090
1255
  api: CloudflareApi,
1091
1256
  {
1092
1257
  bucketName,
1093
1258
  key,
1094
1259
  object,
1260
+ options,
1095
1261
  }: {
1096
1262
  bucketName: string;
1097
1263
  key: string;
1098
1264
  object: PutObjectObject;
1265
+ options?: Pick<R2PutOptions, "httpMetadata">;
1099
1266
  },
1100
1267
  ): Promise<Response> {
1101
1268
  // Using withExponentialBackoff for reliability
1102
1269
  return await withRetries(async () => {
1270
+ const headers: Record<string, string> = {
1271
+ "Content-Type": "application/octet-stream",
1272
+ ...(options?.httpMetadata
1273
+ ? mapHttpMetadataToHeaders(options?.httpMetadata)
1274
+ : {}),
1275
+ };
1276
+
1103
1277
  const response = await api.put(
1104
1278
  `/accounts/${api.accountId}/r2/buckets/${bucketName}/objects/${key}`,
1105
1279
  object,
1106
1280
  {
1107
- headers: {
1108
- "Content-Type": "application/octet-stream",
1109
- },
1281
+ headers,
1110
1282
  },
1111
1283
  );
1112
1284
  if (!response.ok) {
@@ -1132,3 +1304,31 @@ export async function deleteObject(
1132
1304
  return response;
1133
1305
  });
1134
1306
  }
1307
+
1308
+ export async function enableDataCatalog(
1309
+ api: CloudflareApi,
1310
+ bucketName: string,
1311
+ ) {
1312
+ const response = await extractCloudflareResult<{
1313
+ id: string;
1314
+ name: string;
1315
+ }>(
1316
+ `enable data catalog for bucket "${bucketName}"`,
1317
+ api.post(`/accounts/${api.accountId}/r2-catalog/${bucketName}/enable`, {}),
1318
+ );
1319
+ return {
1320
+ id: response.id,
1321
+ name: response.name,
1322
+ host: `https://catalog.cloudflarestorage.com/${response.name.replace("_", "/")}`,
1323
+ };
1324
+ }
1325
+
1326
+ export async function disableDataCatalog(
1327
+ api: CloudflareApi,
1328
+ bucketName: string,
1329
+ ) {
1330
+ await api.post(
1331
+ `/accounts/${api.accountId}/r2-catalog/${bucketName}/disable`,
1332
+ {},
1333
+ );
1334
+ }
@@ -4,4 +4,4 @@
4
4
  /**
5
5
  * The default Cloudflare Workers compatibility date, set based on the latest workerd release at the time of build.
6
6
  */
7
- export const DEFAULT_COMPATIBILITY_DATE = "2025-09-24";
7
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-09-27";
@@ -950,14 +950,13 @@ export async function updateContainerApplication(
950
950
  );
951
951
  const result = (await response.json()) as {
952
952
  result: ContainerApplicationData;
953
- errors: { message: string }[];
953
+ errors: { message: string; code: number }[];
954
954
  };
955
955
  if (response.ok) {
956
956
  return result.result;
957
957
  }
958
-
959
958
  throw Error(
960
- `Failed to create container application: ${result.errors?.map((e: { message: string }) => e.message).join(", ") ?? "Unknown error"}`,
959
+ `Failed to create container application: ${result.errors?.map((e) => `[${e.code}] ${e.message}`).join(", ") ?? "Unknown error"}`,
961
960
  );
962
961
  }
963
962
 
@@ -1045,13 +1044,13 @@ export async function createContainerApplicationRollout(
1045
1044
  );
1046
1045
  const result = (await response.json()) as {
1047
1046
  result: CreateRolloutApplicationResponse;
1048
- errors: { message: string }[];
1047
+ errors: { message: string; code: number }[];
1049
1048
  };
1050
1049
  if (response.ok) {
1051
1050
  return result.result;
1052
1051
  }
1053
1052
  throw Error(
1054
- `Failed to create container application rollout: ${result.errors.map((e: { message: string }) => e.message).join(", ")}`,
1053
+ `Failed to create container application rollout: ${result.errors.map((e) => `[${e.code}] ${e.message}`).join(", ")}`,
1055
1054
  );
1056
1055
  }
1057
1056
 
@@ -13,6 +13,7 @@ export * from "./astro/astro.ts";
13
13
  export * from "./bindings.ts";
14
14
  export * from "./bound.ts";
15
15
  export * from "./browser-rendering.ts";
16
+ export * from "./bucket-object.ts";
16
17
  export * from "./bucket.ts";
17
18
  export * from "./certificate-pack.ts";
18
19
  export * from "./cloudflare-env-proxy.ts";