alchemy 0.76.1 → 0.77.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 (123) hide show
  1. package/bin/alchemy.js +109 -29
  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/bucket-custom-domain.d.ts +90 -0
  12. package/lib/cloudflare/bucket-custom-domain.d.ts.map +1 -0
  13. package/lib/cloudflare/bucket-custom-domain.js +81 -0
  14. package/lib/cloudflare/bucket-custom-domain.js.map +1 -0
  15. package/lib/cloudflare/bucket.d.ts +28 -4
  16. package/lib/cloudflare/bucket.d.ts.map +1 -1
  17. package/lib/cloudflare/bucket.js +41 -14
  18. package/lib/cloudflare/bucket.js.map +1 -1
  19. package/lib/cloudflare/cloudflare-env-proxy.d.ts.map +1 -1
  20. package/lib/cloudflare/cloudflare-env-proxy.js +0 -3
  21. package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -1
  22. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  23. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  24. package/lib/cloudflare/container.d.ts.map +1 -1
  25. package/lib/cloudflare/container.js +8 -5
  26. package/lib/cloudflare/container.js.map +1 -1
  27. package/lib/cloudflare/d1-database.d.ts +13 -3
  28. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  29. package/lib/cloudflare/d1-database.js +31 -15
  30. package/lib/cloudflare/d1-database.js.map +1 -1
  31. package/lib/cloudflare/index.d.ts +1 -0
  32. package/lib/cloudflare/index.d.ts.map +1 -1
  33. package/lib/cloudflare/index.js +1 -0
  34. package/lib/cloudflare/index.js.map +1 -1
  35. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  36. package/lib/cloudflare/miniflare/remote-binding-proxy.js +73 -9
  37. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  38. package/lib/cloudflare/react-router/plugin.d.ts +2 -1
  39. package/lib/cloudflare/react-router/plugin.d.ts.map +1 -1
  40. package/lib/cloudflare/react-router/plugin.js +6 -14
  41. package/lib/cloudflare/react-router/plugin.js.map +1 -1
  42. package/lib/cloudflare/vite/plugin.d.ts.map +1 -1
  43. package/lib/cloudflare/vite/plugin.js +0 -3
  44. package/lib/cloudflare/vite/plugin.js.map +1 -1
  45. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  46. package/lib/cloudflare/worker-bundle.js +17 -0
  47. package/lib/cloudflare/worker-bundle.js.map +1 -1
  48. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  49. package/lib/cloudflare/worker-metadata.js +6 -4
  50. package/lib/cloudflare/worker-metadata.js.map +1 -1
  51. package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
  52. package/lib/cloudflare/worker-subdomain.js +0 -1
  53. package/lib/cloudflare/worker-subdomain.js.map +1 -1
  54. package/lib/cloudflare/worker.d.ts.map +1 -1
  55. package/lib/cloudflare/worker.js +28 -38
  56. package/lib/cloudflare/worker.js.map +1 -1
  57. package/lib/cloudflare/wrangler.json.d.ts +2 -281
  58. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  59. package/lib/cloudflare/wrangler.json.js +23 -11
  60. package/lib/cloudflare/wrangler.json.js.map +1 -1
  61. package/lib/docker/api.d.ts +4 -0
  62. package/lib/docker/api.d.ts.map +1 -1
  63. package/lib/docker/api.js +3 -0
  64. package/lib/docker/api.js.map +1 -1
  65. package/lib/docker/image.d.ts +50 -12
  66. package/lib/docker/image.d.ts.map +1 -1
  67. package/lib/docker/image.js +86 -58
  68. package/lib/docker/image.js.map +1 -1
  69. package/lib/docker/remote-image.d.ts +1 -0
  70. package/lib/docker/remote-image.d.ts.map +1 -1
  71. package/lib/docker/remote-image.js +2 -0
  72. package/lib/docker/remote-image.js.map +1 -1
  73. package/lib/encrypt.d.ts +28 -3
  74. package/lib/encrypt.d.ts.map +1 -1
  75. package/lib/encrypt.js +77 -3
  76. package/lib/encrypt.js.map +1 -1
  77. package/lib/planetscale/role.d.ts +9 -0
  78. package/lib/planetscale/role.d.ts.map +1 -1
  79. package/lib/planetscale/role.js +18 -3
  80. package/lib/planetscale/role.js.map +1 -1
  81. package/lib/prisma-postgres/api.d.ts.map +1 -1
  82. package/lib/prisma-postgres/api.js +1 -0
  83. package/lib/prisma-postgres/api.js.map +1 -1
  84. package/lib/scope.d.ts +5 -0
  85. package/lib/scope.d.ts.map +1 -1
  86. package/lib/scope.js +23 -0
  87. package/lib/scope.js.map +1 -1
  88. package/lib/serde.js +1 -1
  89. package/lib/serde.js.map +1 -1
  90. package/lib/util/telemetry.js +4 -0
  91. package/lib/util/telemetry.js.map +1 -1
  92. package/package.json +12 -10
  93. package/src/aws/ec2/index.ts +1 -0
  94. package/src/aws/index.ts +1 -9
  95. package/src/cloudflare/bucket-custom-domain.ts +281 -0
  96. package/src/cloudflare/bucket.ts +85 -18
  97. package/src/cloudflare/cloudflare-env-proxy.ts +0 -3
  98. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  99. package/src/cloudflare/container.ts +10 -7
  100. package/src/cloudflare/d1-database.ts +44 -6
  101. package/src/cloudflare/index.ts +1 -0
  102. package/src/cloudflare/miniflare/remote-binding-proxy.ts +80 -9
  103. package/src/cloudflare/react-router/plugin.ts +9 -21
  104. package/src/cloudflare/vite/plugin.ts +0 -3
  105. package/src/cloudflare/worker-bundle.ts +28 -1
  106. package/src/cloudflare/worker-metadata.ts +8 -4
  107. package/src/cloudflare/worker-subdomain.ts +0 -1
  108. package/src/cloudflare/worker.ts +32 -45
  109. package/src/cloudflare/wrangler.json.ts +62 -397
  110. package/src/docker/api.ts +7 -0
  111. package/src/docker/image.ts +164 -89
  112. package/src/docker/remote-image.ts +3 -0
  113. package/src/encrypt.ts +115 -3
  114. package/src/planetscale/role.ts +30 -3
  115. package/src/prisma-postgres/api.ts +1 -0
  116. package/src/scope.ts +27 -0
  117. package/src/serde.ts +1 -1
  118. package/src/util/telemetry.ts +5 -0
  119. package/workers/cloudflare-state-store.js +4802 -8
  120. package/workers/dofs-state-store.js +632 -2
  121. package/workers/remote-binding-proxy.js +1929 -3
  122. package/workers/tunnel-proxy.js +58 -6
  123. package/workers/tunnel-proxy.ts +1 -1
@@ -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-11-09";
@@ -218,10 +218,10 @@ export async function Container<T>(
218
218
  const isDev = scope.local && !props.dev?.remote;
219
219
  if (isDev) {
220
220
  const image = await Image(id, {
221
+ ...props,
221
222
  name: `cloudflare-dev/${name}`, // prefix used by Miniflare
222
223
  tag,
223
- build: props.build,
224
- });
224
+ } as ImageProps);
225
225
 
226
226
  return {
227
227
  ...output,
@@ -235,16 +235,19 @@ export async function Container<T>(
235
235
  const image = await Image(id, {
236
236
  name: `${api.accountId}/${name}`,
237
237
  tag,
238
- build: {
239
- platform: props.build?.platform ?? "linux/amd64",
240
- ...props.build,
241
- },
238
+ build: props.build
239
+ ? {
240
+ platform: props.build?.platform ?? "linux/amd64",
241
+ ...props.build,
242
+ }
243
+ : undefined,
244
+ image: props.image,
242
245
  registry: {
243
246
  server: "registry.cloudflare.com",
244
247
  username: credentials.username || credentials.user!,
245
248
  password: secret(credentials.password),
246
249
  },
247
- });
250
+ } as ImageProps);
248
251
 
249
252
  return {
250
253
  ...output,
@@ -8,6 +8,7 @@ import {
8
8
  type CloudflareApi,
9
9
  type CloudflareApiOptions,
10
10
  } from "./api.ts";
11
+ import { withJurisdiction } from "./bucket.ts";
11
12
  import { cloneD1Database } from "./d1-clone.ts";
12
13
  import { applyLocalD1Migrations } from "./d1-local-migrations.ts";
13
14
  import { applyMigrations, listMigrationsFiles } from "./d1-migrations.ts";
@@ -15,6 +16,8 @@ import { deleteMiniflareBinding } from "./miniflare/delete.ts";
15
16
 
16
17
  const DEFAULT_MIGRATIONS_TABLE = "d1_migrations";
17
18
 
19
+ export type D1DatabaseJurisdiction = "default" | "eu" | "fedramp";
20
+
18
21
  type PrimaryLocationHint =
19
22
  | "wnam"
20
23
  | "enam"
@@ -115,6 +118,12 @@ export interface D1DatabaseProps extends CloudflareApiOptions {
115
118
  */
116
119
  force?: boolean;
117
120
  };
121
+
122
+ /**
123
+ * Optional jurisdiction for the bucket
124
+ * Determines the regulatory jurisdiction the bucket data falls under
125
+ */
126
+ jurisdiction?: D1DatabaseJurisdiction;
118
127
  }
119
128
 
120
129
  export function isD1Database(resource: any): resource is D1Database {
@@ -157,6 +166,11 @@ export type D1Database = Pick<
157
166
  */
158
167
  remote: boolean;
159
168
  };
169
+
170
+ /**
171
+ * The jurisdiction of the database
172
+ */
173
+ jurisdiction: D1DatabaseJurisdiction;
160
174
  };
161
175
 
162
176
  /**
@@ -260,10 +274,11 @@ const _D1Database = Resource(
260
274
  async function (
261
275
  this: Context<D1Database>,
262
276
  id: string,
263
- props: D1DatabaseProps = {},
277
+ props: D1DatabaseProps,
264
278
  ): Promise<D1Database> {
265
279
  const databaseName =
266
280
  props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
281
+ const jurisdiction = props.jurisdiction ?? "default";
267
282
 
268
283
  if (this.phase === "update" && this.output?.name !== databaseName) {
269
284
  this.replace();
@@ -294,6 +309,7 @@ const _D1Database = Resource(
294
309
  migrationsDir: props.migrationsDir,
295
310
  migrationsTable: props.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE,
296
311
  dev,
312
+ jurisdiction,
297
313
  };
298
314
  }
299
315
 
@@ -304,7 +320,7 @@ const _D1Database = Resource(
304
320
  await deleteMiniflareBinding(this.scope, "d1", this.output.dev.id);
305
321
  }
306
322
  if (props.delete !== false && this.output?.id) {
307
- await deleteDatabase(api, this.output.id);
323
+ await deleteDatabase(api, this.output.id, props);
308
324
  }
309
325
  // Return void (a deleted database has no content)
310
326
  return this.destroy();
@@ -328,7 +344,7 @@ const _D1Database = Resource(
328
344
 
329
345
  // If clone property is provided, perform cloning after database creation
330
346
  if (props.clone && dbData.result.uuid) {
331
- await cloneDb(api, props.clone, dbData.result.uuid);
347
+ await cloneDb(api, props.clone, dbData.result.uuid, jurisdiction);
332
348
  }
333
349
  } catch (error) {
334
350
  // Check if this is a "database already exists" error and adopt is enabled
@@ -339,7 +355,7 @@ const _D1Database = Resource(
339
355
  ) {
340
356
  logger.log(`Database ${databaseName} already exists, adopting it`);
341
357
  // Find the existing database by name
342
- const databases = await listDatabases(api, databaseName);
358
+ const databases = await listDatabases(api, databaseName, props);
343
359
  const existingDb = databases.find((db) => db.name === databaseName);
344
360
 
345
361
  if (!existingDb) {
@@ -349,7 +365,7 @@ const _D1Database = Resource(
349
365
  }
350
366
 
351
367
  // Get the database details using its ID
352
- dbData = await getDatabase(api, existingDb.id);
368
+ dbData = await getDatabase(api, existingDb.id, props);
353
369
 
354
370
  // Update the database with the provided properties
355
371
  if (props.readReplication) {
@@ -413,6 +429,7 @@ const _D1Database = Resource(
413
429
  dev,
414
430
  migrationsDir: props.migrationsDir,
415
431
  migrationsTable: props.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE,
432
+ jurisdiction,
416
433
  };
417
434
  },
418
435
  );
@@ -454,6 +471,9 @@ export async function createDatabase(
454
471
  const createResponse = await api.post(
455
472
  `/accounts/${api.accountId}/d1/database`,
456
473
  createPayload,
474
+ {
475
+ headers: withJurisdiction(props),
476
+ },
457
477
  );
458
478
 
459
479
  if (!createResponse.ok) {
@@ -474,6 +494,7 @@ export async function createDatabase(
474
494
  export async function getDatabase(
475
495
  api: CloudflareApi,
476
496
  databaseId?: string,
497
+ props: D1DatabaseProps = {},
477
498
  ): Promise<CloudflareD1Response> {
478
499
  if (!databaseId) {
479
500
  throw new Error("Database ID is required");
@@ -481,6 +502,9 @@ export async function getDatabase(
481
502
 
482
503
  const response = await api.get(
483
504
  `/accounts/${api.accountId}/d1/database/${databaseId}`,
505
+ {
506
+ headers: withJurisdiction(props),
507
+ },
484
508
  );
485
509
 
486
510
  if (!response.ok) {
@@ -496,6 +520,7 @@ export async function getDatabase(
496
520
  export async function deleteDatabase(
497
521
  api: CloudflareApi,
498
522
  databaseId?: string,
523
+ props: D1DatabaseProps = {},
499
524
  ): Promise<void> {
500
525
  if (!databaseId) {
501
526
  logger.log("No database ID provided, skipping delete");
@@ -505,6 +530,9 @@ export async function deleteDatabase(
505
530
  // Delete D1 database
506
531
  const deleteResponse = await api.delete(
507
532
  `/accounts/${api.accountId}/d1/database/${databaseId}`,
533
+ {
534
+ headers: withJurisdiction(props),
535
+ },
508
536
  );
509
537
 
510
538
  if (!deleteResponse.ok && deleteResponse.status !== 404) {
@@ -524,12 +552,16 @@ export async function deleteDatabase(
524
552
  export async function listDatabases(
525
553
  api: CloudflareApi,
526
554
  name?: string,
555
+ props: D1DatabaseProps = {},
527
556
  ): Promise<{ name: string; id: string }[]> {
528
557
  // Construct query string if name is provided
529
558
  const queryParams = name ? `?name=${encodeURIComponent(name)}` : "";
530
559
 
531
560
  const response = await api.get(
532
561
  `/accounts/${api.accountId}/d1/database${queryParams}`,
562
+ {
563
+ headers: withJurisdiction(props),
564
+ },
533
565
  );
534
566
 
535
567
  if (!response.ok) {
@@ -582,6 +614,9 @@ export async function updateDatabase(
582
614
  const updateResponse = await api.patch(
583
615
  `/accounts/${api.accountId}/d1/database/${databaseId}`,
584
616
  updatePayload,
617
+ {
618
+ headers: withJurisdiction(props),
619
+ },
585
620
  );
586
621
 
587
622
  if (!updateResponse.ok) {
@@ -608,6 +643,7 @@ async function cloneDb(
608
643
  api: CloudflareApi,
609
644
  sourceDb: D1Database | { id: string } | { name: string },
610
645
  targetDbId: string,
646
+ jurisdiction: D1DatabaseJurisdiction,
611
647
  ): Promise<void> {
612
648
  let sourceId: string;
613
649
 
@@ -617,7 +653,9 @@ async function cloneDb(
617
653
  sourceId = sourceDb.id;
618
654
  } else if ("name" in sourceDb && sourceDb.name) {
619
655
  // Look up ID by name
620
- const databases = await listDatabases(api, sourceDb.name);
656
+ const databases = await listDatabases(api, sourceDb.name, {
657
+ jurisdiction,
658
+ });
621
659
  const foundDb = databases.find((db) => db.name === sourceDb.name);
622
660
 
623
661
  if (!foundDb) {
@@ -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";
@@ -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
+ }
@@ -1,32 +1,20 @@
1
1
  import type { PluginConfig } from "@cloudflare/vite-plugin";
2
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
- import { dirname } from "pathe";
4
- import { getDefaultConfigPath } from "../miniflare/paths.ts";
2
+ import type { PluginOption } from "vite";
5
3
  import alchemyVite from "../vite/plugin.ts";
6
4
 
7
- const alchemy = (config?: PluginConfig) => {
8
- const configPath = config?.configPath ?? getDefaultConfigPath();
9
- // react-router type generation requries a wrangler.json and creates a chicken and egg problem
10
- // we create a dummy wrangler.json file so that clean builds can be performed
11
- if (!existsSync(configPath)) {
12
- mkdirSync(dirname(configPath), { recursive: true });
13
- writeFileSync(
14
- configPath,
15
- JSON.stringify(
16
- {
17
- name: "dummy",
18
- compatibility_date: "2025-09-23",
19
- },
20
- null,
21
- 2,
22
- ),
23
- );
5
+ const alchemy = (config: PluginConfig = {}): PluginOption => {
6
+ // plugin is not required for react-router typegen, so return a no-op to avoid errors
7
+ if (
8
+ process.argv.some((arg) => arg.includes("react-router")) &&
9
+ process.argv.includes("typegen")
10
+ ) {
11
+ return null;
24
12
  }
25
13
  return alchemyVite({
26
- ...config,
27
14
  viteEnvironment: {
28
15
  name: "ssr",
29
16
  },
17
+ ...config,
30
18
  });
31
19
  };
32
20
 
@@ -26,9 +26,6 @@ const alchemy = (config?: PluginConfig): PluginOption => {
26
26
  config?.configPath ?? getDefaultConfigPath(),
27
27
  ),
28
28
  persistState,
29
- experimental: config?.experimental ?? {
30
- remoteBindings: true,
31
- },
32
29
  }),
33
30
  {
34
31
  name: "alchemy-supress-watch",