alchemy 0.76.0 → 0.77.0

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 (107) hide show
  1. package/bin/alchemy.js +111 -30
  2. package/bin/services/execute-alchemy.ts +10 -1
  3. package/lib/aws/ec2/index.d.ts +1 -0
  4. package/lib/aws/ec2/index.d.ts.map +1 -1
  5. package/lib/aws/ec2/index.js +1 -0
  6. package/lib/aws/ec2/index.js.map +1 -1
  7. package/lib/aws/index.d.ts +1 -7
  8. package/lib/aws/index.d.ts.map +1 -1
  9. package/lib/aws/index.js +1 -8
  10. package/lib/aws/index.js.map +1 -1
  11. package/lib/cloudflare/auth.d.ts.map +1 -1
  12. package/lib/cloudflare/auth.js +1 -0
  13. package/lib/cloudflare/auth.js.map +1 -1
  14. package/lib/cloudflare/bucket-custom-domain.d.ts +90 -0
  15. package/lib/cloudflare/bucket-custom-domain.d.ts.map +1 -0
  16. package/lib/cloudflare/bucket-custom-domain.js +81 -0
  17. package/lib/cloudflare/bucket-custom-domain.js.map +1 -0
  18. package/lib/cloudflare/bucket.d.ts +28 -4
  19. package/lib/cloudflare/bucket.d.ts.map +1 -1
  20. package/lib/cloudflare/bucket.js +41 -14
  21. package/lib/cloudflare/bucket.js.map +1 -1
  22. package/lib/cloudflare/cloudflare-env-proxy.d.ts.map +1 -1
  23. package/lib/cloudflare/cloudflare-env-proxy.js +0 -3
  24. package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -1
  25. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  26. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  27. package/lib/cloudflare/index.d.ts +1 -0
  28. package/lib/cloudflare/index.d.ts.map +1 -1
  29. package/lib/cloudflare/index.js +1 -0
  30. package/lib/cloudflare/index.js.map +1 -1
  31. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  32. package/lib/cloudflare/miniflare/build-worker-options.js +3 -1
  33. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  34. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  35. package/lib/cloudflare/miniflare/remote-binding-proxy.js +73 -9
  36. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  37. package/lib/cloudflare/react-router/plugin.d.ts +2 -1
  38. package/lib/cloudflare/react-router/plugin.d.ts.map +1 -1
  39. package/lib/cloudflare/react-router/plugin.js +6 -14
  40. package/lib/cloudflare/react-router/plugin.js.map +1 -1
  41. package/lib/cloudflare/vite/plugin.d.ts.map +1 -1
  42. package/lib/cloudflare/vite/plugin.js +0 -3
  43. package/lib/cloudflare/vite/plugin.js.map +1 -1
  44. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  45. package/lib/cloudflare/worker-bundle.js +17 -0
  46. package/lib/cloudflare/worker-bundle.js.map +1 -1
  47. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  48. package/lib/cloudflare/worker-metadata.js +6 -4
  49. package/lib/cloudflare/worker-metadata.js.map +1 -1
  50. package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
  51. package/lib/cloudflare/worker-subdomain.js +0 -1
  52. package/lib/cloudflare/worker-subdomain.js.map +1 -1
  53. package/lib/cloudflare/worker.d.ts.map +1 -1
  54. package/lib/cloudflare/worker.js +28 -38
  55. package/lib/cloudflare/worker.js.map +1 -1
  56. package/lib/cloudflare/wrangler.json.d.ts +2 -281
  57. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  58. package/lib/cloudflare/wrangler.json.js +23 -11
  59. package/lib/cloudflare/wrangler.json.js.map +1 -1
  60. package/lib/encrypt.d.ts +28 -3
  61. package/lib/encrypt.d.ts.map +1 -1
  62. package/lib/encrypt.js +77 -3
  63. package/lib/encrypt.js.map +1 -1
  64. package/lib/planetscale/role.d.ts +9 -0
  65. package/lib/planetscale/role.d.ts.map +1 -1
  66. package/lib/planetscale/role.js +18 -3
  67. package/lib/planetscale/role.js.map +1 -1
  68. package/lib/prisma-postgres/api.d.ts.map +1 -1
  69. package/lib/prisma-postgres/api.js +1 -0
  70. package/lib/prisma-postgres/api.js.map +1 -1
  71. package/lib/scope.d.ts +5 -0
  72. package/lib/scope.d.ts.map +1 -1
  73. package/lib/scope.js +23 -0
  74. package/lib/scope.js.map +1 -1
  75. package/lib/serde.js +1 -1
  76. package/lib/serde.js.map +1 -1
  77. package/lib/util/telemetry.js +4 -0
  78. package/lib/util/telemetry.js.map +1 -1
  79. package/package.json +12 -10
  80. package/src/aws/ec2/index.ts +1 -0
  81. package/src/aws/index.ts +1 -9
  82. package/src/cloudflare/auth.ts +1 -0
  83. package/src/cloudflare/bucket-custom-domain.ts +281 -0
  84. package/src/cloudflare/bucket.ts +85 -18
  85. package/src/cloudflare/cloudflare-env-proxy.ts +0 -3
  86. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  87. package/src/cloudflare/index.ts +1 -0
  88. package/src/cloudflare/miniflare/build-worker-options.ts +3 -1
  89. package/src/cloudflare/miniflare/remote-binding-proxy.ts +80 -9
  90. package/src/cloudflare/react-router/plugin.ts +9 -21
  91. package/src/cloudflare/vite/plugin.ts +0 -3
  92. package/src/cloudflare/worker-bundle.ts +28 -1
  93. package/src/cloudflare/worker-metadata.ts +8 -4
  94. package/src/cloudflare/worker-subdomain.ts +0 -1
  95. package/src/cloudflare/worker.ts +32 -45
  96. package/src/cloudflare/wrangler.json.ts +62 -397
  97. package/src/encrypt.ts +115 -3
  98. package/src/planetscale/role.ts +30 -3
  99. package/src/prisma-postgres/api.ts +1 -0
  100. package/src/scope.ts +27 -0
  101. package/src/serde.ts +1 -1
  102. package/src/util/telemetry.ts +5 -0
  103. package/workers/cloudflare-state-store.js +4802 -8
  104. package/workers/dofs-state-store.js +632 -2
  105. package/workers/remote-binding-proxy.js +1929 -3
  106. package/workers/tunnel-proxy.js +58 -6
  107. package/workers/tunnel-proxy.ts +1 -1
@@ -0,0 +1,281 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ import { diff } from "../util/diff";
4
+ import {
5
+ CloudflareApi,
6
+ createCloudflareApi,
7
+ type CloudflareApiOptions,
8
+ } from "./api";
9
+ import { CloudflareApiError } from "./api-error";
10
+ import { extractCloudflareResult } from "./api-response";
11
+ import { withJurisdiction, type R2BucketJurisdiction } from "./bucket";
12
+ import { inferZoneIdFromPattern } from "./route";
13
+ import type { Zone } from "./zone";
14
+
15
+ export interface R2BucketCustomDomainProps
16
+ extends CloudflareApiOptions,
17
+ R2BucketCustomDomainOptions {
18
+ /**
19
+ * The bucket to attach the domain to.
20
+ */
21
+ bucketName: string;
22
+
23
+ /**
24
+ * The jurisdiction of the bucket.
25
+ * @default "default"
26
+ */
27
+ jurisdiction?: R2BucketJurisdiction;
28
+
29
+ /**
30
+ * Development mode properties
31
+ * @internal
32
+ */
33
+ dev?: {
34
+ remote?: boolean;
35
+ };
36
+ }
37
+
38
+ export interface R2BucketCustomDomainOptions {
39
+ /**
40
+ * The domain name to attach to the bucket.
41
+ */
42
+ domain: string;
43
+
44
+ /**
45
+ * Whether the domain is enabled.
46
+ * @default true
47
+ */
48
+ enabled?: boolean;
49
+
50
+ /**
51
+ * The zone to attach the domain to.
52
+ */
53
+ zone?: string | Zone;
54
+
55
+ /**
56
+ * An allowlist of ciphers for TLS termination. These ciphers must be in the BoringSSL format.
57
+ */
58
+ ciphers?: string[];
59
+
60
+ /**
61
+ * The minimum TLS version to support.
62
+ */
63
+ minTLS?: "1.0" | "1.1" | "1.2" | "1.3" | (string & {});
64
+
65
+ /**
66
+ * Whether to adopt an existing custom domain binding during creation.
67
+ * If false and the domain already exists, creation will fail.
68
+ * This only applies during the create phase.
69
+ * @default false
70
+ */
71
+ adopt?: boolean;
72
+
73
+ /**
74
+ * Whether to delete the custom domain binding during deletion.
75
+ * If false, the custom domain binding will remain but the resource will be removed from state
76
+ * @default true
77
+ */
78
+ delete?: boolean;
79
+ }
80
+
81
+ export type R2BucketCustomDomain = {
82
+ /**
83
+ * The domain name.
84
+ */
85
+ domain: string;
86
+ /**
87
+ * Whether the domain is enabled.
88
+ */
89
+ enabled: boolean;
90
+ /**
91
+ * The zone ID.
92
+ */
93
+ zoneId: string;
94
+ /**
95
+ * The ciphers to support.
96
+ */
97
+ ciphers?: string[];
98
+ /**
99
+ * The minimum TLS version to support.
100
+ */
101
+ minTLS?: "1.0" | "1.1" | "1.2" | "1.3" | (string & {});
102
+ };
103
+
104
+ export const R2BucketCustomDomain = Resource(
105
+ "cloudflare::R2BucketCustomDomain",
106
+ async function (
107
+ this: Context<R2BucketCustomDomain>,
108
+ _id: string,
109
+ props: R2BucketCustomDomainProps,
110
+ ): Promise<R2BucketCustomDomain> {
111
+ if (this.scope.local && !props.dev?.remote && this.phase !== "delete") {
112
+ return {
113
+ domain: props.domain,
114
+ enabled: props.enabled ?? false,
115
+ zoneId:
116
+ typeof props.zone === "string"
117
+ ? props.zone
118
+ : (props.zone?.id ?? "noop-zone"),
119
+ ciphers: props.ciphers,
120
+ minTLS: props.minTLS,
121
+ };
122
+ }
123
+ const adopt = props.adopt ?? this.scope.adopt;
124
+ const api = await createCloudflareApi(props);
125
+ const payload: R2BucketCustomDomain = {
126
+ domain: props.domain,
127
+ enabled: props.enabled ?? true,
128
+ zoneId:
129
+ typeof props.zone === "string"
130
+ ? props.zone
131
+ : (props.zone?.id ??
132
+ (await inferZoneIdFromPattern(api, props.domain))),
133
+ ciphers: props.ciphers,
134
+ minTLS: props.minTLS,
135
+ };
136
+ switch (this.phase) {
137
+ case "create": {
138
+ await createBucketCustomDomain(
139
+ api,
140
+ props.bucketName,
141
+ props.jurisdiction,
142
+ payload,
143
+ ).catch(async (error) => {
144
+ if (
145
+ error instanceof CloudflareApiError &&
146
+ error.status === 409 &&
147
+ adopt
148
+ ) {
149
+ return await updateBucketCustomDomain(
150
+ api,
151
+ props.bucketName,
152
+ props.jurisdiction,
153
+ payload,
154
+ );
155
+ }
156
+ throw error;
157
+ });
158
+ return payload;
159
+ }
160
+ case "update": {
161
+ if (
162
+ diff(this.output, payload).some(
163
+ (property) => property === "domain" || property === "zoneId",
164
+ )
165
+ ) {
166
+ return this.replace();
167
+ }
168
+ await updateBucketCustomDomain(
169
+ api,
170
+ props.bucketName,
171
+ props.jurisdiction,
172
+ payload,
173
+ );
174
+ return payload;
175
+ }
176
+ case "delete": {
177
+ if (props.delete !== false) {
178
+ await deleteBucketCustomDomain(
179
+ api,
180
+ props.bucketName,
181
+ props.domain,
182
+ props.jurisdiction,
183
+ );
184
+ }
185
+ return this.destroy();
186
+ }
187
+ }
188
+ },
189
+ );
190
+
191
+ async function createBucketCustomDomain(
192
+ api: CloudflareApi,
193
+ bucket: string,
194
+ jurisdiction: R2BucketJurisdiction | undefined,
195
+ payload: R2BucketCustomDomain,
196
+ ) {
197
+ return await extractCloudflareResult(
198
+ `create custom domain for bucket "${bucket}"`,
199
+ api.post(
200
+ `/accounts/${api.accountId}/r2/buckets/${bucket}/domains/custom`,
201
+ payload,
202
+ { headers: withJurisdiction({ jurisdiction }) },
203
+ ),
204
+ );
205
+ }
206
+
207
+ async function updateBucketCustomDomain(
208
+ api: CloudflareApi,
209
+ bucket: string,
210
+ jurisdiction: R2BucketJurisdiction | undefined,
211
+ payload: R2BucketCustomDomain,
212
+ ) {
213
+ return await extractCloudflareResult(
214
+ `update custom domain for bucket "${bucket}"`,
215
+ api.put(
216
+ `/accounts/${api.accountId}/r2/buckets/${bucket}/domains/custom/${encodeURIComponent(payload.domain)}`,
217
+ payload,
218
+ { headers: withJurisdiction({ jurisdiction }) },
219
+ ),
220
+ );
221
+ }
222
+
223
+ export async function deleteBucketCustomDomain(
224
+ api: CloudflareApi,
225
+ bucket: string,
226
+ domain: string,
227
+ jurisdiction: R2BucketJurisdiction | undefined,
228
+ ) {
229
+ return await extractCloudflareResult(
230
+ `delete custom domain for bucket "${bucket}"`,
231
+ api.delete(
232
+ `/accounts/${api.accountId}/r2/buckets/${bucket}/domains/custom/${encodeURIComponent(domain)}`,
233
+ { headers: withJurisdiction({ jurisdiction }) },
234
+ ),
235
+ ).catch((error) => {
236
+ if (!(error instanceof CloudflareApiError && error.status === 404)) {
237
+ throw error;
238
+ }
239
+ });
240
+ }
241
+
242
+ export async function getBucketCustomDomain(
243
+ api: CloudflareApi,
244
+ bucket: string,
245
+ domain: string,
246
+ jurisdiction: R2BucketJurisdiction | undefined,
247
+ ) {
248
+ return await extractCloudflareResult<
249
+ R2BucketCustomDomain & {
250
+ zoneName?: string;
251
+ status: {
252
+ ownership:
253
+ | "pending"
254
+ | "active"
255
+ | "deactivated"
256
+ | "blocked"
257
+ | "error"
258
+ | "unknown";
259
+ ssl:
260
+ | "initializing"
261
+ | "pending"
262
+ | "active"
263
+ | "deactivated"
264
+ | "error"
265
+ | "unknown";
266
+ };
267
+ }
268
+ >(
269
+ `get custom domain for bucket "${bucket}"`,
270
+ api.get(
271
+ `/accounts/${api.accountId}/r2/buckets/${bucket}/domains/custom/${encodeURIComponent(domain)}`,
272
+ {
273
+ headers: withJurisdiction({ jurisdiction }),
274
+ },
275
+ ),
276
+ ).catch((error) => {
277
+ if (!(error instanceof CloudflareApiError && error.status === 404)) {
278
+ throw error;
279
+ }
280
+ });
281
+ }
@@ -17,6 +17,10 @@ import {
17
17
  type CloudflareApi,
18
18
  type CloudflareApiOptions,
19
19
  } from "./api.ts";
20
+ import {
21
+ R2BucketCustomDomain,
22
+ type R2BucketCustomDomainOptions,
23
+ } from "./bucket-custom-domain.ts";
20
24
  import { deleteMiniflareBinding } from "./miniflare/delete.ts";
21
25
  import { getDefaultPersistPath } from "./miniflare/paths.ts";
22
26
 
@@ -56,9 +60,25 @@ export interface BucketProps extends CloudflareApiOptions {
56
60
  /**
57
61
  * Whether to allow public access through the r2.dev subdomain
58
62
  * Only for development purposes - use custom domains for production
63
+ * @deprecated Use `devDomain` instead
59
64
  */
60
65
  allowPublicAccess?: boolean;
61
66
 
67
+ /**
68
+ * Whether to allow public access through the r2.dev subdomain
69
+ * Only for development purposes - use custom domains for production
70
+ */
71
+ devDomain?: boolean;
72
+
73
+ /**
74
+ * The custom domain(s) to attach to the bucket.
75
+ */
76
+ domains?:
77
+ | string
78
+ | R2BucketCustomDomainOptions
79
+ | string[]
80
+ | R2BucketCustomDomainOptions[];
81
+
62
82
  /**
63
83
  * Whether to delete the bucket.
64
84
  * If set to false, the bucket will remain but the resource will be removed from state
@@ -307,7 +327,10 @@ export type R2Bucket = _R2Bucket & {
307
327
  /**
308
328
  * Output returned after R2 Bucket creation/update
309
329
  */
310
- type _R2Bucket = Omit<BucketProps, "delete" | "dev"> & {
330
+ type _R2Bucket = Omit<
331
+ BucketProps,
332
+ "delete" | "dev" | "domains" | "devDomain"
333
+ > & {
311
334
  /**
312
335
  * Resource type identifier
313
336
  */
@@ -330,8 +353,19 @@ type _R2Bucket = Omit<BucketProps, "delete" | "dev"> & {
330
353
 
331
354
  /**
332
355
  * The `r2.dev` subdomain for the bucket, if `allowPublicAccess` is true
356
+ * @deprecated Use `devDomain` instead
357
+ */
358
+ domain?: string;
359
+
360
+ /**
361
+ * The `r2.dev` subdomain for the bucket, if `devDomain` is true
333
362
  */
334
- domain: string | undefined;
363
+ devDomain: string | undefined;
364
+
365
+ /**
366
+ * The custom domains for the bucket, if applicable
367
+ */
368
+ domains: string[] | undefined;
335
369
 
336
370
  /**
337
371
  * Development mode properties
@@ -590,10 +624,14 @@ const _R2Bucket = Resource(
590
624
  props.name ?? (this.output?.name || this.scope.createPhysicalName(id));
591
625
 
592
626
  if (this.phase === "update" && this.output?.name !== bucketName) {
593
- this.replace();
627
+ this.replace(
628
+ // force replacement if custom domain is enabled; otherwise, replace will fail because the bucket has child resources
629
+ !!this.output.domains?.length,
630
+ );
594
631
  }
595
632
 
596
- const allowPublicAccess = props.allowPublicAccess === true;
633
+ const isDevDomainEnabled =
634
+ props.devDomain === true || props.allowPublicAccess === true;
597
635
  const isLocal = this.scope.local && !props.dev?.remote;
598
636
  const dev = {
599
637
  id: this.output?.dev?.id ?? bucketName,
@@ -602,14 +640,42 @@ const _R2Bucket = Resource(
602
640
  } satisfies _R2Bucket["dev"];
603
641
  const adopt = props.adopt ?? this.scope.adopt;
604
642
 
643
+ async function createDomains(domain: NonNullable<BucketProps["domains"]>) {
644
+ return Promise.all(
645
+ (Array.isArray(domain) ? domain : [domain]).map(async (domain) => {
646
+ const options: R2BucketCustomDomainOptions =
647
+ typeof domain === "string"
648
+ ? { domain, adopt, delete: props.delete }
649
+ : {
650
+ ...domain,
651
+ adopt: domain.adopt ?? adopt,
652
+ delete: domain.delete ?? props.delete,
653
+ };
654
+ const output = await R2BucketCustomDomain(options.domain, {
655
+ ...options,
656
+ accountId: props.accountId,
657
+ apiKey: props.apiKey,
658
+ apiToken: props.apiToken,
659
+ email: props.email,
660
+ baseUrl: props.baseUrl,
661
+ profile: props.profile,
662
+ bucketName: bucketName,
663
+ jurisdiction: props.jurisdiction,
664
+ dev,
665
+ });
666
+ return output.domain;
667
+ }),
668
+ );
669
+ }
670
+
605
671
  if (isLocal) {
606
672
  return {
607
673
  name: bucketName,
608
674
  location: this.output?.location ?? "",
609
675
  creationDate: this.output?.creationDate ?? new Date(),
610
676
  jurisdiction: this.output?.jurisdiction ?? "default",
611
- allowPublicAccess,
612
- domain: this.output?.domain,
677
+ devDomain: this.output?.devDomain ?? this.output?.domain,
678
+ domains: props.domains ? await createDomains(props.domains) : undefined,
613
679
  type: "r2_bucket",
614
680
  accountId: this.output?.accountId ?? "",
615
681
  cors: props.cors,
@@ -648,10 +714,10 @@ const _R2Bucket = Resource(
648
714
  throw err;
649
715
  },
650
716
  );
651
- const domain = await putManagedDomain(
717
+ const devDomain = await putManagedDomain(
652
718
  api,
653
719
  bucketName,
654
- allowPublicAccess,
720
+ isDevDomainEnabled,
655
721
  props.jurisdiction,
656
722
  );
657
723
  if (props.cors?.length) {
@@ -673,13 +739,14 @@ const _R2Bucket = Resource(
673
739
  if (props.dataCatalog) {
674
740
  dataCatalog = await enableDataCatalog(api, bucketName);
675
741
  }
742
+
676
743
  return {
677
744
  name: bucketName,
678
745
  location: bucket.location,
679
746
  creationDate: new Date(bucket.creation_date),
680
747
  jurisdiction: bucket.jurisdiction,
681
- allowPublicAccess,
682
- domain,
748
+ devDomain,
749
+ domains: props.domains ? await createDomains(props.domains) : undefined,
683
750
  type: "r2_bucket",
684
751
  accountId: api.accountId,
685
752
  lifecycle: props.lifecycle,
@@ -701,12 +768,12 @@ const _R2Bucket = Resource(
701
768
  if (!this.output.catalog && props.dataCatalog) {
702
769
  this.output.catalog = await enableDataCatalog(api, bucketName);
703
770
  }
704
- let domain = this.output.domain;
705
- if (!!domain !== allowPublicAccess) {
706
- domain = await putManagedDomain(
771
+ let devDomain = this.output.devDomain ?? this.output.domain;
772
+ if (!!devDomain !== isDevDomainEnabled) {
773
+ devDomain = await putManagedDomain(
707
774
  api,
708
775
  bucketName,
709
- allowPublicAccess,
776
+ isDevDomainEnabled,
710
777
  props.jurisdiction,
711
778
  );
712
779
  }
@@ -723,12 +790,12 @@ const _R2Bucket = Resource(
723
790
  }
724
791
  return {
725
792
  ...this.output,
726
- allowPublicAccess,
793
+ devDomain,
794
+ domains: props.domains ? await createDomains(props.domains) : undefined,
727
795
  dev,
728
796
  cors: props.cors,
729
797
  lifecycle: props.lifecycle,
730
798
  lock: props.lock,
731
- domain,
732
799
  };
733
800
  }
734
801
  },
@@ -1087,7 +1154,7 @@ export async function putBucketLifecycleRules(
1087
1154
  export async function getBucketLifecycleRules(
1088
1155
  api: CloudflareApi,
1089
1156
  bucketName: string,
1090
- props: BucketProps = {},
1157
+ props: { jurisdiction?: string },
1091
1158
  ): Promise<R2BucketLifecycleRule[]> {
1092
1159
  const res = await api.get(
1093
1160
  `/accounts/${api.accountId}/r2/buckets/${bucketName}/lifecycle`,
@@ -1140,7 +1207,7 @@ export async function putBucketLockRules(
1140
1207
  export async function getBucketLockRules(
1141
1208
  api: CloudflareApi,
1142
1209
  bucketName: string,
1143
- props: BucketProps = {},
1210
+ props: { jurisdiction?: R2BucketJurisdiction },
1144
1211
  ): Promise<R2BucketLockRule[]> {
1145
1212
  const res = await api.get(
1146
1213
  `/accounts/${api.accountId}/r2/buckets/${bucketName}/lock`,
@@ -39,9 +39,6 @@ export const getPlatformProxyOptions = (
39
39
  ...input,
40
40
  configPath: validateConfigPath(input.configPath ?? getDefaultConfigPath()),
41
41
  persist,
42
- experimental: (input as any).experimental ?? {
43
- remoteBindings: true,
44
- },
45
42
  };
46
43
  };
47
44
 
@@ -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-10-14";
7
+ export const DEFAULT_COMPATIBILITY_DATE = "2025-10-31";
@@ -15,6 +15,7 @@ export * from "./astro/astro.ts";
15
15
  export * from "./bindings.ts";
16
16
  export * from "./bound.ts";
17
17
  export * from "./browser-rendering.ts";
18
+ export * from "./bucket-custom-domain.ts";
18
19
  export * from "./bucket-object.ts";
19
20
  export * from "./bucket.ts";
20
21
  export * from "./bun-spa/bun-spa.ts";
@@ -342,7 +342,9 @@ export const buildWorkerOptions = async (
342
342
  const dlq = eventSource.settings?.deadLetterQueue;
343
343
  (options.queueConsumers ??= {})[queue.name] = {
344
344
  maxBatchSize: eventSource.settings?.batchSize,
345
- maxBatchTimeout: eventSource.settings?.maxWaitTimeMs,
345
+ maxBatchTimeout: eventSource.settings?.maxWaitTimeMs
346
+ ? eventSource.settings?.maxWaitTimeMs / 1000
347
+ : undefined,
346
348
  maxRetries: eventSource.settings?.maxRetries,
347
349
  retryDelay: eventSource.settings?.retryDelay,
348
350
  deadLetterQueue: typeof dlq === "string" ? dlq : dlq?.name,
@@ -1,6 +1,8 @@
1
1
  import type { RemoteProxyConnectionString } from "miniflare";
2
2
  import { WebSocket } from "ws";
3
+ import { Scope } from "../../scope.ts";
3
4
  import { HTTPServer } from "../../util/http.ts";
5
+ import { memoize } from "../../util/memoize.ts";
4
6
  import { extractCloudflareResult } from "../api-response.ts";
5
7
  import type { CloudflareApi } from "../api.ts";
6
8
  import type { WorkerBindingSpec } from "../bindings.ts";
@@ -37,7 +39,7 @@ export async function createRemoteProxyWorker(input: {
37
39
  bindings: WorkerBindingSpec[];
38
40
  }): Promise<RemoteBindingProxy> {
39
41
  const script = await getInternalWorkerBundle("remote-binding-proxy");
40
- const [token, subdomain] = await Promise.all([
42
+ const [token, { host, accessToken }] = await Promise.all([
41
43
  createWorkersPreviewToken(input.api, {
42
44
  name: input.name,
43
45
  metadata: {
@@ -52,18 +54,27 @@ export async function createRemoteProxyWorker(input: {
52
54
  minimal_mode: true,
53
55
  },
54
56
  }),
55
- getAccountSubdomain(input.api),
57
+ getAccountSubdomain(input.api).then(async (subdomain) => {
58
+ const host = `${input.name}.${subdomain}.workers.dev`;
59
+ return {
60
+ host,
61
+ accessToken: await getAccessToken(host),
62
+ };
63
+ }),
56
64
  ]);
57
65
 
58
- const host = `${input.name}.${subdomain}.workers.dev`;
66
+ const baseHeaders: Record<string, string> = {
67
+ "cf-workers-preview-token": token,
68
+ host,
69
+ ...(accessToken ? { cookie: `CF_Authorization=${accessToken}` } : {}),
70
+ };
59
71
 
60
72
  const server = new HTTPServer({
61
73
  websocket: async (req) => {
62
74
  const input = new URL(req.url);
63
75
  const proxied = new URL(input.pathname + input.search, `https://${host}`);
64
76
  const headers: Record<string, string> = {
65
- "cf-workers-preview-token": token,
66
- host,
77
+ ...baseHeaders,
67
78
  };
68
79
  // We don't want to include all headers because some can mess with websocket connections.
69
80
  // However, it's important to include `mf-` prefixed headers to configure bindings for miniflare to work.
@@ -81,8 +92,9 @@ export async function createRemoteProxyWorker(input: {
81
92
  const proxied = new URL(url.pathname + url.search, `https://${host}`);
82
93
 
83
94
  const headers = new Headers(req.headers);
84
- headers.set("cf-workers-preview-token", token);
85
- headers.set("host", host);
95
+ for (const [key, value] of Object.entries(baseHeaders)) {
96
+ headers.set(key, value);
97
+ }
86
98
  headers.delete("cf-connecting-ip");
87
99
 
88
100
  const res = await fetch(proxied, {
@@ -146,8 +158,13 @@ async function createWorkersPreviewToken(
146
158
 
147
159
  async function prewarm(url: string, previewToken: string) {
148
160
  try {
161
+ const accessToken = await getAccessToken(new URL(url).hostname);
149
162
  await fetch(url, {
150
- headers: { "cf-workers-preview-token": previewToken },
163
+ method: "POST",
164
+ headers: {
165
+ "cf-workers-preview-token": previewToken,
166
+ ...(accessToken ? { cookie: `CF_Authorization=${accessToken}` } : {}),
167
+ },
151
168
  });
152
169
  } catch {
153
170
  // Ignore prewarm errors
@@ -162,7 +179,12 @@ async function createWorkersPreviewSession(api: CloudflareApi) {
162
179
  "create workers preview session",
163
180
  api.get(`/accounts/${api.accountId}/workers/subdomain/edge-preview`),
164
181
  );
165
- const res = await fetch(exchange_url);
182
+ const accessToken = await getAccessToken(new URL(exchange_url).hostname);
183
+ const res = await fetch(exchange_url, {
184
+ headers: {
185
+ ...(accessToken ? { cookie: `CF_Authorization=${accessToken}` } : {}),
186
+ },
187
+ });
166
188
  if (!res.ok) {
167
189
  throw new Error(
168
190
  `Failed to create workers preview session: ${res.status} ${res.statusText}`,
@@ -171,3 +193,52 @@ async function createWorkersPreviewSession(api: CloudflareApi) {
171
193
  const json: WorkersPreviewSession = await res.json();
172
194
  return json;
173
195
  }
196
+
197
+ /**
198
+ * If the given domain uses Cloudflare Access, fetches the access token for the domain.
199
+ * Otherwise, returns undefined.
200
+ * @see https://github.com/cloudflare/workers-sdk/blob/a5eb5134f73d3983655d325a4de71c6370c57faa/packages/wrangler/src/user/access.ts#L10
201
+ */
202
+ const getAccessToken = memoize(async (hostname: string) => {
203
+ if (!(await domainUsesAccess(hostname))) {
204
+ return undefined;
205
+ }
206
+ const result = await Scope.current
207
+ .exec(`access-${hostname}`, `cloudflared access login ${hostname}`)
208
+ .catch(() => {
209
+ throw new Error(
210
+ [
211
+ `The \`cloudflared\` CLI is not installed, but is required to access the domain "${hostname}".`,
212
+ `Please install it from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation and run \`cloudflare access login ${hostname}\`.`,
213
+ ].join("\n"),
214
+ );
215
+ });
216
+ const matches = result.stdout.match(/fetched your token:\n\n(.*)/m);
217
+ if (matches?.[1]) {
218
+ return matches[1];
219
+ }
220
+ const error = new Error(
221
+ `Failed to get access token for domain "${hostname}".`,
222
+ );
223
+ Object.assign(error, result);
224
+ throw error;
225
+ });
226
+
227
+ /**
228
+ * Returns true if the domain uses Cloudflare Access.
229
+ */
230
+ async function domainUsesAccess(hostname: string) {
231
+ try {
232
+ const response = await fetch(`https://${hostname}`, {
233
+ method: "HEAD",
234
+ redirect: "manual",
235
+ signal: AbortSignal.timeout(1000),
236
+ });
237
+ return !!(
238
+ response.status === 302 &&
239
+ response.headers.get("location")?.includes("cloudflareaccess.com")
240
+ );
241
+ } catch {
242
+ return false;
243
+ }
244
+ }