alchemy 0.5.1 → 0.6.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.
- package/lib/cloudflare/account-api-token.d.ts +6 -1
- package/lib/cloudflare/account-api-token.js +5 -1
- package/lib/cloudflare/account-id.d.ts +5 -1
- package/lib/cloudflare/account-id.js +5 -0
- package/lib/cloudflare/api-error.d.ts +32 -0
- package/lib/cloudflare/api-error.js +47 -0
- package/lib/cloudflare/api.d.ts +16 -49
- package/lib/cloudflare/api.js +50 -151
- package/lib/cloudflare/auth.d.ts +55 -38
- package/lib/cloudflare/auth.js +148 -77
- package/lib/cloudflare/bucket.d.ts +30 -5
- package/lib/cloudflare/bucket.js +12 -11
- package/lib/cloudflare/custom-domain.d.ts +2 -1
- package/lib/cloudflare/custom-domain.js +3 -2
- package/lib/cloudflare/dns-records.d.ts +2 -1
- package/lib/cloudflare/dns-records.js +2 -2
- package/lib/cloudflare/kv-namespace.d.ts +7 -1
- package/lib/cloudflare/kv-namespace.js +72 -71
- package/lib/cloudflare/permission-groups.d.ts +4 -0
- package/lib/cloudflare/permission-groups.js +5 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts +3 -17
- package/lib/cloudflare/r2-rest-state-store.js +4 -12
- package/lib/cloudflare/worker.d.ts +2 -6
- package/lib/cloudflare/worker.js +4 -62
- package/lib/cloudflare/zone.d.ts +2 -6
- package/lib/cloudflare/zone.js +1 -3
- package/lib/index.d.ts +1 -0
- package/lib/internal/docs/providers.js +0 -7
- package/package.json +5 -1
- package/src/cloudflare/account-api-token.ts +7 -3
- package/src/cloudflare/account-id.ts +12 -0
- package/src/cloudflare/api-error.ts +58 -0
- package/src/cloudflare/api.ts +70 -219
- package/src/cloudflare/auth.ts +262 -119
- package/src/cloudflare/bucket.ts +52 -29
- package/src/cloudflare/custom-domain.ts +8 -3
- package/src/cloudflare/dns-records.ts +7 -3
- package/src/cloudflare/kv-namespace.ts +117 -105
- package/src/cloudflare/permission-groups.ts +5 -1
- package/src/cloudflare/r2-rest-state-store.ts +8 -31
- package/src/cloudflare/worker.ts +11 -103
- package/src/cloudflare/zone.ts +17 -25
- package/src/index.ts +1 -0
- package/src/internal/docs/providers.ts +0 -7
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Scope } from "../scope";
|
|
2
|
-
import type { Secret } from "../secret";
|
|
3
2
|
import type { State, StateStore } from "../state";
|
|
3
|
+
import { type CloudflareApiOptions } from "./api";
|
|
4
4
|
/**
|
|
5
5
|
* Options for CloudflareR2StateStore
|
|
6
6
|
*/
|
|
7
|
-
export interface CloudflareR2StateStoreOptions {
|
|
7
|
+
export interface CloudflareR2StateStoreOptions extends CloudflareApiOptions {
|
|
8
8
|
/**
|
|
9
9
|
* The prefix to use for object keys in the R2 bucket
|
|
10
10
|
* This allows multiple state stores to use the same R2 bucket
|
|
@@ -15,18 +15,6 @@ export interface CloudflareR2StateStoreOptions {
|
|
|
15
15
|
* Required - the bucket must already exist
|
|
16
16
|
*/
|
|
17
17
|
bucketName: string;
|
|
18
|
-
/**
|
|
19
|
-
* API key to use (overrides CLOUDFLARE_API_KEY env var)
|
|
20
|
-
*/
|
|
21
|
-
apiKey?: Secret;
|
|
22
|
-
/**
|
|
23
|
-
* Account ID to use (overrides CLOUDFLARE_ACCOUNT_ID env var)
|
|
24
|
-
*/
|
|
25
|
-
accountId?: string;
|
|
26
|
-
/**
|
|
27
|
-
* Email to use with API Key authentication (overrides CLOUDFLARE_EMAIL env var)
|
|
28
|
-
*/
|
|
29
|
-
email?: string;
|
|
30
18
|
}
|
|
31
19
|
/**
|
|
32
20
|
* State store implementation using Cloudflare R2 API
|
|
@@ -34,12 +22,10 @@ export interface CloudflareR2StateStoreOptions {
|
|
|
34
22
|
*/
|
|
35
23
|
export declare class R2RestStateStore implements StateStore {
|
|
36
24
|
readonly scope: Scope;
|
|
25
|
+
private readonly options;
|
|
37
26
|
private api;
|
|
38
27
|
private prefix;
|
|
39
28
|
private bucketName;
|
|
40
|
-
private apiKey;
|
|
41
|
-
private accountId;
|
|
42
|
-
private email;
|
|
43
29
|
private initialized;
|
|
44
30
|
/**
|
|
45
31
|
* Create a new CloudflareR2StateStore
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { deserialize, serialize } from "../serde";
|
|
2
2
|
import { withExponentialBackoff } from "../util/retry";
|
|
3
|
-
import { createCloudflareApi } from "./api";
|
|
3
|
+
import { createCloudflareApi, } from "./api";
|
|
4
4
|
/**
|
|
5
5
|
* State store implementation using Cloudflare R2 API
|
|
6
6
|
* Uses R2 for immediate consistency compared to KV's eventual consistency
|
|
7
7
|
*/
|
|
8
8
|
export class R2RestStateStore {
|
|
9
9
|
scope;
|
|
10
|
+
options;
|
|
10
11
|
api;
|
|
11
12
|
prefix;
|
|
12
13
|
bucketName;
|
|
13
|
-
apiKey;
|
|
14
|
-
accountId;
|
|
15
|
-
email;
|
|
16
14
|
initialized = false;
|
|
17
15
|
/**
|
|
18
16
|
* Create a new CloudflareR2StateStore
|
|
@@ -22,6 +20,7 @@ export class R2RestStateStore {
|
|
|
22
20
|
*/
|
|
23
21
|
constructor(scope, options) {
|
|
24
22
|
this.scope = scope;
|
|
23
|
+
this.options = options;
|
|
25
24
|
// Use the scope's chain to build the prefix, similar to how FileSystemStateStore builds its directory
|
|
26
25
|
const scopePath = scope.chain.join("/");
|
|
27
26
|
this.prefix = options.prefix
|
|
@@ -31,9 +30,6 @@ export class R2RestStateStore {
|
|
|
31
30
|
throw new Error("bucketName is required for CloudflareR2StateStore");
|
|
32
31
|
}
|
|
33
32
|
this.bucketName = options.bucketName;
|
|
34
|
-
this.apiKey = options.apiKey;
|
|
35
|
-
this.accountId = options.accountId;
|
|
36
|
-
this.email = options.email;
|
|
37
33
|
// We'll initialize the API in init() to allow for async creation
|
|
38
34
|
this.api = null;
|
|
39
35
|
}
|
|
@@ -44,11 +40,7 @@ export class R2RestStateStore {
|
|
|
44
40
|
if (this.initialized)
|
|
45
41
|
return;
|
|
46
42
|
// Create Cloudflare API client with automatic account discovery
|
|
47
|
-
this.api = await createCloudflareApi(
|
|
48
|
-
apiKey: this.apiKey,
|
|
49
|
-
accountId: this.accountId,
|
|
50
|
-
email: this.email,
|
|
51
|
-
});
|
|
43
|
+
this.api = await createCloudflareApi(this.options);
|
|
52
44
|
this.initialized = true;
|
|
53
45
|
}
|
|
54
46
|
/**
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Context } from "../context";
|
|
2
2
|
import { type BundleProps } from "../esbuild/bundle";
|
|
3
3
|
import { Resource } from "../resource";
|
|
4
|
+
import { type CloudflareApiOptions } from "./api";
|
|
4
5
|
import { type Bindings } from "./bindings";
|
|
5
6
|
import type { Bound } from "./bound";
|
|
6
7
|
import type { SingleStepMigration } from "./worker-migration";
|
|
7
8
|
/**
|
|
8
9
|
* Properties for creating or updating a Worker
|
|
9
10
|
*/
|
|
10
|
-
export interface WorkerProps<B extends Bindings = Bindings> {
|
|
11
|
+
export interface WorkerProps<B extends Bindings = Bindings> extends CloudflareApiOptions {
|
|
11
12
|
/**
|
|
12
13
|
* The worker script content (JavaScript or WASM)
|
|
13
14
|
* One of script, entryPoint, or bundle must be provided
|
|
@@ -36,11 +37,6 @@ export interface WorkerProps<B extends Bindings = Bindings> {
|
|
|
36
37
|
* This is mandatory - must be explicitly specified
|
|
37
38
|
*/
|
|
38
39
|
name: string;
|
|
39
|
-
/**
|
|
40
|
-
* Routes to associate with this worker
|
|
41
|
-
* Format: example.com/* or *.example.com/*
|
|
42
|
-
*/
|
|
43
|
-
routes?: string[];
|
|
44
40
|
/**
|
|
45
41
|
* Bindings to attach to the worker
|
|
46
42
|
*/
|
package/lib/cloudflare/worker.js
CHANGED
|
@@ -6,7 +6,7 @@ import { isSecret } from "../secret";
|
|
|
6
6
|
import { getContentType } from "../util/content-type";
|
|
7
7
|
import { withExponentialBackoff } from "../util/retry";
|
|
8
8
|
import { slugify } from "../util/slugify";
|
|
9
|
-
import { createCloudflareApi } from "./api";
|
|
9
|
+
import { createCloudflareApi, } from "./api";
|
|
10
10
|
import { isAssets, isDurableObjectNamespace, } from "./bindings";
|
|
11
11
|
import { isKVNamespace } from "./kv-namespace";
|
|
12
12
|
/**
|
|
@@ -90,7 +90,7 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
90
90
|
alwaysUpdate: true,
|
|
91
91
|
}, async function (id, props) {
|
|
92
92
|
// Create Cloudflare API client with automatic account discovery
|
|
93
|
-
const api = await createCloudflareApi();
|
|
93
|
+
const api = await createCloudflareApi(props);
|
|
94
94
|
// Use the provided name
|
|
95
95
|
const workerName = props.name;
|
|
96
96
|
// Validate input - we need either script, entryPoint, or bundle
|
|
@@ -133,8 +133,6 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
133
133
|
await putWorker(api, workerName, scriptContent, scriptMetadata);
|
|
134
134
|
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
135
135
|
await this.set("bindings", props.bindings);
|
|
136
|
-
// Handle routes if requested
|
|
137
|
-
await setupRoutes(api, workerName, props.routes || []);
|
|
138
136
|
// Handle worker URL if requested
|
|
139
137
|
const workerUrl = await configureURL(this, api, workerName, props.url ?? false);
|
|
140
138
|
// Get current timestamp
|
|
@@ -146,7 +144,6 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
146
144
|
name: workerName,
|
|
147
145
|
script: scriptContent,
|
|
148
146
|
format: props.format || "esm", // Include format in the output
|
|
149
|
-
routes: props.routes || [],
|
|
150
147
|
bindings: props.bindings ?? {},
|
|
151
148
|
env: props.env,
|
|
152
149
|
observability: scriptMetadata.observability,
|
|
@@ -167,25 +164,6 @@ async function deleteWorker(ctx, api, workerName) {
|
|
|
167
164
|
.catch(() => ({ errors: [{ message: deleteResponse.statusText }] }));
|
|
168
165
|
console.error("Error deleting worker:", errorData.errors?.[0]?.message || deleteResponse.statusText);
|
|
169
166
|
}
|
|
170
|
-
// Also remove any associated routes if they exist
|
|
171
|
-
if (ctx.output?.routes && ctx.output.routes.length > 0 && api.zoneId) {
|
|
172
|
-
// First get existing routes to find their IDs
|
|
173
|
-
const routesResponse = await api.get(`/zones/${api.zoneId}/workers/routes`);
|
|
174
|
-
if (!routesResponse.ok) {
|
|
175
|
-
throw new Error(`Could not fetch routes for cleanup: ${routesResponse.status} ${routesResponse.statusText}`);
|
|
176
|
-
}
|
|
177
|
-
const routesData = await routesResponse.json();
|
|
178
|
-
const existingRoutes = routesData.result || [];
|
|
179
|
-
for (const route of existingRoutes) {
|
|
180
|
-
if (ctx.output.routes.includes(route.pattern)) {
|
|
181
|
-
// Delete the route
|
|
182
|
-
const routeDeleteResponse = await api.delete(`/zones/${api.zoneId}/workers/routes/${route.id}`);
|
|
183
|
-
if (!routeDeleteResponse.ok) {
|
|
184
|
-
console.warn(`Failed to delete route ${route.pattern}: ${routeDeleteResponse.status} ${routeDeleteResponse.statusText}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
167
|
// Disable the URL if it was enabled
|
|
190
168
|
if (ctx.output?.url) {
|
|
191
169
|
try {
|
|
@@ -421,44 +399,6 @@ async function bundleWorkerScript(props) {
|
|
|
421
399
|
throw new Error("Error reading bundle");
|
|
422
400
|
}
|
|
423
401
|
}
|
|
424
|
-
async function setupRoutes(api, workerName, routes) {
|
|
425
|
-
// Set up routes if provided
|
|
426
|
-
if (routes && routes.length > 0 && api.zoneId) {
|
|
427
|
-
// First get existing routes
|
|
428
|
-
const routesResponse = await api.get(`/zones/${api.zoneId}/workers/routes`);
|
|
429
|
-
if (!routesResponse.ok) {
|
|
430
|
-
throw new Error(`Could not fetch routes: ${routesResponse.status} ${routesResponse.statusText}`);
|
|
431
|
-
}
|
|
432
|
-
const routesData = await routesResponse.json();
|
|
433
|
-
const existingRoutes = routesData.result || [];
|
|
434
|
-
// For each desired route
|
|
435
|
-
for (const pattern of routes) {
|
|
436
|
-
const existingRoute = existingRoutes.find((r) => r.pattern === pattern);
|
|
437
|
-
if (existingRoute) {
|
|
438
|
-
// Update if script name doesn't match
|
|
439
|
-
if (existingRoute.script !== workerName) {
|
|
440
|
-
const updateRouteResponse = await api.put(`/zones/${api.zoneId}/workers/routes/${existingRoute.id}`, {
|
|
441
|
-
pattern,
|
|
442
|
-
script: workerName,
|
|
443
|
-
});
|
|
444
|
-
if (!updateRouteResponse.ok) {
|
|
445
|
-
console.warn(`Failed to update route ${pattern}: ${updateRouteResponse.status} ${updateRouteResponse.statusText}`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
// Create new route
|
|
451
|
-
const createRouteResponse = await api.post(`/zones/${api.zoneId}/workers/routes`, {
|
|
452
|
-
pattern,
|
|
453
|
-
script: workerName,
|
|
454
|
-
});
|
|
455
|
-
if (!createRouteResponse.ok) {
|
|
456
|
-
throw new Error(`Failed to create route ${pattern}: ${createRouteResponse.status} ${createRouteResponse.statusText}`);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
402
|
async function configureURL(ctx, api, workerName, url) {
|
|
463
403
|
let workerUrl;
|
|
464
404
|
if (url) {
|
|
@@ -557,6 +497,7 @@ async function uploadAssets(api, workerName, assets) {
|
|
|
557
497
|
// Process each bucket of files
|
|
558
498
|
for (const bucket of buckets) {
|
|
559
499
|
const formData = new FormData();
|
|
500
|
+
let totalBytes = 0;
|
|
560
501
|
// Add each file in the bucket to the form
|
|
561
502
|
for (const fileHash of bucket) {
|
|
562
503
|
// Find the file with this hash
|
|
@@ -575,6 +516,7 @@ async function uploadAssets(api, workerName, assets) {
|
|
|
575
516
|
const blob = new Blob([base64Content], {
|
|
576
517
|
type: getContentType(file.filePath),
|
|
577
518
|
});
|
|
519
|
+
totalBytes += blob.size;
|
|
578
520
|
formData.append(fileHash, blob, fileHash);
|
|
579
521
|
}
|
|
580
522
|
// Upload this batch of files
|
package/lib/cloudflare/zone.d.ts
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import type { Context } from "../context";
|
|
2
2
|
import { Resource } from "../resource";
|
|
3
|
+
import { type CloudflareApiOptions } from "./api";
|
|
3
4
|
import type { AlwaysUseHTTPSValue, AutomaticHTTPSRewritesValue, BrotliValue, DevelopmentModeValue, EarlyHintsValue, EmailObfuscationValue, HTTP2Value, HTTP3Value, HotlinkProtectionValue, IPv6Value, MinTLSVersionValue, SSLValue, TLS13Value, WebSocketsValue, ZeroRTTValue } from "./zone-settings";
|
|
4
5
|
/**
|
|
5
6
|
* Properties for creating or updating a Zone
|
|
6
7
|
*/
|
|
7
|
-
export interface ZoneProps {
|
|
8
|
+
export interface ZoneProps extends CloudflareApiOptions {
|
|
8
9
|
/**
|
|
9
10
|
* The domain name for the zone
|
|
10
11
|
*/
|
|
11
12
|
name: string;
|
|
12
|
-
/**
|
|
13
|
-
* Account ID to use for the zone
|
|
14
|
-
* If not provided, will use the default account
|
|
15
|
-
*/
|
|
16
|
-
accountId?: string;
|
|
17
13
|
/**
|
|
18
14
|
* The type of zone to create
|
|
19
15
|
* "full" - Full zone implies that DNS is hosted with Cloudflare
|
package/lib/cloudflare/zone.js
CHANGED
|
@@ -57,9 +57,7 @@ import { createCloudflareApi } from "./api";
|
|
|
57
57
|
*/
|
|
58
58
|
export const Zone = Resource("cloudflare::Zone", async function (id, props) {
|
|
59
59
|
// Create Cloudflare API client with automatic account discovery
|
|
60
|
-
const api = await createCloudflareApi(
|
|
61
|
-
accountId: props.accountId,
|
|
62
|
-
});
|
|
60
|
+
const api = await createCloudflareApi(props);
|
|
63
61
|
if (this.phase === "delete") {
|
|
64
62
|
if (this.output?.id) {
|
|
65
63
|
// Delete zone
|
package/lib/index.d.ts
CHANGED
|
@@ -21,13 +21,6 @@ export async function Providers({ srcDir, outDir, filter, parallel = true, }) {
|
|
|
21
21
|
}))
|
|
22
22
|
.filter((dirent) => dirent.isDirectory() && !exclude.includes(dirent.name))
|
|
23
23
|
.map((dirent) => path.join(dirent.parentPath, dirent.name));
|
|
24
|
-
// For each provider, list all files
|
|
25
|
-
if (filter === false) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
else if (typeof filter === "number") {
|
|
29
|
-
providers = providers.slice(0, filter);
|
|
30
|
-
}
|
|
31
24
|
if (parallel) {
|
|
32
25
|
return await Promise.all(providers.map((provider) => generateProviderDocs({ provider, outDir, parallel })));
|
|
33
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "./lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@aws-sdk/client-sesv2": "^3.0.0",
|
|
40
40
|
"@aws-sdk/client-sqs": "^3.0.0",
|
|
41
41
|
"@aws-sdk/client-sts": "^3.0.0",
|
|
42
|
+
"@iarna/toml": "^2.2.5",
|
|
42
43
|
"@octokit/rest": "^21.1.1",
|
|
43
44
|
"ai": "^4.0.0",
|
|
44
45
|
"arktype": "^2.0.0",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"prettier": "^3.0.0",
|
|
54
55
|
"stripe": "^17.0.0",
|
|
55
56
|
"turndown": "^7.0.0",
|
|
57
|
+
"xdg-app-paths": "^8.0.0",
|
|
56
58
|
"yaml": "^2.0.0",
|
|
57
59
|
"zod": "^3.0.0"
|
|
58
60
|
},
|
|
@@ -62,6 +64,7 @@
|
|
|
62
64
|
"@aws-sdk/client-s3": "3.726.1",
|
|
63
65
|
"@biomejs/biome": "^1.9.4",
|
|
64
66
|
"@cloudflare/workers-types": "^4.20250303.0",
|
|
67
|
+
"@iarna/toml": "^2.2.5",
|
|
65
68
|
"@octokit/rest": "^21.1.1",
|
|
66
69
|
"@types/bun": "latest",
|
|
67
70
|
"@types/diff": "^5.0.0",
|
|
@@ -82,6 +85,7 @@
|
|
|
82
85
|
"vite": "^6.0.7",
|
|
83
86
|
"vitepress": "^1.6.3",
|
|
84
87
|
"wrangler": "^3.114.0",
|
|
88
|
+
"xdg-app-paths": "^8.3.0",
|
|
85
89
|
"yaml": "^2.7.1"
|
|
86
90
|
}
|
|
87
91
|
}
|
|
@@ -3,7 +3,7 @@ import type { Context } from "../context";
|
|
|
3
3
|
import { Resource } from "../resource";
|
|
4
4
|
import { Secret } from "../secret";
|
|
5
5
|
import { sha256 } from "../util/sha256";
|
|
6
|
-
import { createCloudflareApi } from "./api";
|
|
6
|
+
import { createCloudflareApi, type CloudflareApiOptions } from "./api";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Permission group for a token policy
|
|
@@ -63,7 +63,7 @@ export interface TokenCondition {
|
|
|
63
63
|
/**
|
|
64
64
|
* Properties for creating or updating an Account API Token
|
|
65
65
|
*/
|
|
66
|
-
export interface AccountApiTokenProps {
|
|
66
|
+
export interface AccountApiTokenProps extends CloudflareApiOptions {
|
|
67
67
|
/**
|
|
68
68
|
* Name of the token
|
|
69
69
|
*/
|
|
@@ -162,6 +162,10 @@ export interface AccountApiToken
|
|
|
162
162
|
/**
|
|
163
163
|
* Creates a Cloudflare Account API Token with specified permissions.
|
|
164
164
|
*
|
|
165
|
+
* Note: Requires a Cloudflare API Key or Token with admin-level account access.
|
|
166
|
+
* The OAuth token from `wrangler login` is NOT sufficient for this operation.
|
|
167
|
+
* You must use an API token with permission to manage account API tokens.
|
|
168
|
+
*
|
|
165
169
|
* @see https://developers.cloudflare.com/api/resources/accounts/subresources/tokens/methods/create/
|
|
166
170
|
*
|
|
167
171
|
* @example
|
|
@@ -222,7 +226,7 @@ export const AccountApiToken = Resource(
|
|
|
222
226
|
props: AccountApiTokenProps
|
|
223
227
|
): Promise<AccountApiToken> {
|
|
224
228
|
// Create Cloudflare API client with automatic account discovery
|
|
225
|
-
const api = await createCloudflareApi();
|
|
229
|
+
const api = await createCloudflareApi(props);
|
|
226
230
|
|
|
227
231
|
if (this.phase === "delete") {
|
|
228
232
|
// Delete token if we have an ID
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getCloudflareUserInfo, type CloudflareAuthOptions } from "./auth";
|
|
2
|
+
|
|
3
|
+
export type CloudflareAccountId = string & {
|
|
4
|
+
readonly __brand: "CloudflareAccountId";
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export async function CloudflareAccountId(
|
|
8
|
+
options: CloudflareAuthOptions
|
|
9
|
+
): Promise<CloudflareAccountId> {
|
|
10
|
+
const userInfo = await getCloudflareUserInfo(options);
|
|
11
|
+
return userInfo.accounts[0].id as CloudflareAccountId;
|
|
12
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class for Cloudflare API errors
|
|
3
|
+
* Includes HTTP status information from the Response
|
|
4
|
+
*/
|
|
5
|
+
export class CloudflareApiError extends Error {
|
|
6
|
+
/**
|
|
7
|
+
* HTTP status code
|
|
8
|
+
*/
|
|
9
|
+
status: number;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* HTTP status text
|
|
13
|
+
*/
|
|
14
|
+
statusText: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Raw error data from the API
|
|
18
|
+
*/
|
|
19
|
+
errorData?: any;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a new CloudflareApiError
|
|
23
|
+
*/
|
|
24
|
+
constructor(message: string, response: Response, errorData?: any) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "CloudflareApiError";
|
|
27
|
+
this.status = response.status;
|
|
28
|
+
this.statusText = response.statusText;
|
|
29
|
+
this.errorData = errorData;
|
|
30
|
+
|
|
31
|
+
// Ensure instanceof works correctly
|
|
32
|
+
Object.setPrototypeOf(this, CloudflareApiError.prototype);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Helper function to handle API errors
|
|
38
|
+
*
|
|
39
|
+
* @param response The fetch Response object
|
|
40
|
+
* @param action The action being performed (e.g., "creating", "deleting")
|
|
41
|
+
* @param resourceType The type of resource being acted upon (e.g., "R2 bucket", "Worker")
|
|
42
|
+
* @param resourceName The name/identifier of the specific resource
|
|
43
|
+
* @returns Never returns - always throws an error
|
|
44
|
+
*/
|
|
45
|
+
export async function handleApiError(
|
|
46
|
+
response: Response,
|
|
47
|
+
action: string,
|
|
48
|
+
resourceType: string,
|
|
49
|
+
resourceName: string
|
|
50
|
+
): Promise<never> {
|
|
51
|
+
const json: any = await response.json();
|
|
52
|
+
const errors: { message: string }[] = json.errors || [
|
|
53
|
+
{ message: response.statusText },
|
|
54
|
+
];
|
|
55
|
+
const errorMessage = `Error ${action} ${resourceType} '${resourceName}': ${errors[0]?.message || response.statusText}`;
|
|
56
|
+
|
|
57
|
+
throw new CloudflareApiError(errorMessage, response, errors);
|
|
58
|
+
}
|