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.
- package/bin/alchemy.js +109 -29
- package/bin/services/execute-alchemy.ts +10 -1
- package/lib/aws/ec2/index.d.ts +1 -0
- package/lib/aws/ec2/index.d.ts.map +1 -1
- package/lib/aws/ec2/index.js +1 -0
- package/lib/aws/ec2/index.js.map +1 -1
- package/lib/aws/index.d.ts +1 -7
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -8
- package/lib/aws/index.js.map +1 -1
- package/lib/cloudflare/bucket-custom-domain.d.ts +90 -0
- package/lib/cloudflare/bucket-custom-domain.d.ts.map +1 -0
- package/lib/cloudflare/bucket-custom-domain.js +81 -0
- package/lib/cloudflare/bucket-custom-domain.js.map +1 -0
- package/lib/cloudflare/bucket.d.ts +28 -4
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +41 -14
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/cloudflare-env-proxy.d.ts.map +1 -1
- package/lib/cloudflare/cloudflare-env-proxy.js +0 -3
- package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +8 -5
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/d1-database.d.ts +13 -3
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +31 -15
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +73 -9
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/react-router/plugin.d.ts +2 -1
- package/lib/cloudflare/react-router/plugin.d.ts.map +1 -1
- package/lib/cloudflare/react-router/plugin.js +6 -14
- package/lib/cloudflare/react-router/plugin.js.map +1 -1
- package/lib/cloudflare/vite/plugin.d.ts.map +1 -1
- package/lib/cloudflare/vite/plugin.js +0 -3
- package/lib/cloudflare/vite/plugin.js.map +1 -1
- package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
- package/lib/cloudflare/worker-bundle.js +17 -0
- package/lib/cloudflare/worker-bundle.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +6 -4
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
- package/lib/cloudflare/worker-subdomain.js +0 -1
- package/lib/cloudflare/worker-subdomain.js.map +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +28 -38
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +2 -281
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +23 -11
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/docker/api.d.ts +4 -0
- package/lib/docker/api.d.ts.map +1 -1
- package/lib/docker/api.js +3 -0
- package/lib/docker/api.js.map +1 -1
- package/lib/docker/image.d.ts +50 -12
- package/lib/docker/image.d.ts.map +1 -1
- package/lib/docker/image.js +86 -58
- package/lib/docker/image.js.map +1 -1
- package/lib/docker/remote-image.d.ts +1 -0
- package/lib/docker/remote-image.d.ts.map +1 -1
- package/lib/docker/remote-image.js +2 -0
- package/lib/docker/remote-image.js.map +1 -1
- package/lib/encrypt.d.ts +28 -3
- package/lib/encrypt.d.ts.map +1 -1
- package/lib/encrypt.js +77 -3
- package/lib/encrypt.js.map +1 -1
- package/lib/planetscale/role.d.ts +9 -0
- package/lib/planetscale/role.d.ts.map +1 -1
- package/lib/planetscale/role.js +18 -3
- package/lib/planetscale/role.js.map +1 -1
- package/lib/prisma-postgres/api.d.ts.map +1 -1
- package/lib/prisma-postgres/api.js +1 -0
- package/lib/prisma-postgres/api.js.map +1 -1
- package/lib/scope.d.ts +5 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +23 -0
- package/lib/scope.js.map +1 -1
- package/lib/serde.js +1 -1
- package/lib/serde.js.map +1 -1
- package/lib/util/telemetry.js +4 -0
- package/lib/util/telemetry.js.map +1 -1
- package/package.json +12 -10
- package/src/aws/ec2/index.ts +1 -0
- package/src/aws/index.ts +1 -9
- package/src/cloudflare/bucket-custom-domain.ts +281 -0
- package/src/cloudflare/bucket.ts +85 -18
- package/src/cloudflare/cloudflare-env-proxy.ts +0 -3
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/container.ts +10 -7
- package/src/cloudflare/d1-database.ts +44 -6
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +80 -9
- package/src/cloudflare/react-router/plugin.ts +9 -21
- package/src/cloudflare/vite/plugin.ts +0 -3
- package/src/cloudflare/worker-bundle.ts +28 -1
- package/src/cloudflare/worker-metadata.ts +8 -4
- package/src/cloudflare/worker-subdomain.ts +0 -1
- package/src/cloudflare/worker.ts +32 -45
- package/src/cloudflare/wrangler.json.ts +62 -397
- package/src/docker/api.ts +7 -0
- package/src/docker/image.ts +164 -89
- package/src/docker/remote-image.ts +3 -0
- package/src/encrypt.ts +115 -3
- package/src/planetscale/role.ts +30 -3
- package/src/prisma-postgres/api.ts +1 -0
- package/src/scope.ts +27 -0
- package/src/serde.ts +1 -1
- package/src/util/telemetry.ts +5 -0
- package/workers/cloudflare-state-store.js +4802 -8
- package/workers/dofs-state-store.js +632 -2
- package/workers/remote-binding-proxy.js +1929 -3
- package/workers/tunnel-proxy.js +58 -6
- package/workers/tunnel-proxy.ts +1 -1
package/src/cloudflare/bucket.ts
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
612
|
-
|
|
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
|
|
717
|
+
const devDomain = await putManagedDomain(
|
|
652
718
|
api,
|
|
653
719
|
bucketName,
|
|
654
|
-
|
|
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
|
-
|
|
682
|
-
|
|
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
|
|
705
|
-
if (!!
|
|
706
|
-
|
|
771
|
+
let devDomain = this.output.devDomain ?? this.output.domain;
|
|
772
|
+
if (!!devDomain !== isDevDomainEnabled) {
|
|
773
|
+
devDomain = await putManagedDomain(
|
|
707
774
|
api,
|
|
708
775
|
bucketName,
|
|
709
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
1210
|
+
props: { jurisdiction?: R2BucketJurisdiction },
|
|
1144
1211
|
): Promise<R2BucketLockRule[]> {
|
|
1145
1212
|
const res = await api.get(
|
|
1146
1213
|
`/accounts/${api.accountId}/r2/buckets/${bucketName}/lock`,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
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) {
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|