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,7 +1,12 @@
|
|
|
1
1
|
import type { Context } from "../context";
|
|
2
2
|
import { Resource } from "../resource";
|
|
3
3
|
import { withExponentialBackoff } from "../util/retry";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CloudflareApi,
|
|
6
|
+
createCloudflareApi,
|
|
7
|
+
type CloudflareApiOptions,
|
|
8
|
+
} from "./api";
|
|
9
|
+
import { handleApiError } from "./api-error";
|
|
5
10
|
|
|
6
11
|
export function isKVNamespace(resource: any): resource is KVNamespace {
|
|
7
12
|
return (
|
|
@@ -12,7 +17,7 @@ export function isKVNamespace(resource: any): resource is KVNamespace {
|
|
|
12
17
|
/**
|
|
13
18
|
* Properties for creating or updating a KV Namespace
|
|
14
19
|
*/
|
|
15
|
-
export interface KVNamespaceProps {
|
|
20
|
+
export interface KVNamespaceProps extends CloudflareApiOptions {
|
|
16
21
|
/**
|
|
17
22
|
* Title of the namespace
|
|
18
23
|
*/
|
|
@@ -119,28 +124,16 @@ export const KVNamespace = Resource(
|
|
|
119
124
|
async function (
|
|
120
125
|
this: Context<KVNamespace>,
|
|
121
126
|
id: string,
|
|
122
|
-
props: KVNamespaceProps
|
|
127
|
+
props: KVNamespaceProps
|
|
123
128
|
) {
|
|
124
129
|
// Create Cloudflare API client with automatic account discovery
|
|
125
|
-
const api = await createCloudflareApi();
|
|
130
|
+
const api = await createCloudflareApi(props);
|
|
126
131
|
|
|
127
132
|
if (this.phase === "delete") {
|
|
128
133
|
// For delete operations, we need to check if the namespace ID exists in the output
|
|
129
134
|
const namespaceId = this.output?.namespaceId;
|
|
130
135
|
if (namespaceId) {
|
|
131
|
-
|
|
132
|
-
const deleteResponse = await api.delete(
|
|
133
|
-
`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}`,
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
137
|
-
const errorData: any = await deleteResponse.json().catch(() => ({
|
|
138
|
-
errors: [{ message: deleteResponse.statusText }],
|
|
139
|
-
}));
|
|
140
|
-
throw new Error(
|
|
141
|
-
`Error deleting KV namespace '${props.title}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
136
|
+
await deleteKVNamespace(api, namespaceId);
|
|
144
137
|
}
|
|
145
138
|
|
|
146
139
|
// Return minimal output for deleted state
|
|
@@ -159,96 +152,12 @@ export const KVNamespace = Resource(
|
|
|
159
152
|
// Can't update a KV namespace title directly, just work with existing ID
|
|
160
153
|
} else {
|
|
161
154
|
// TODO: if it already exists, then check the tags to see if we own it and continue
|
|
162
|
-
|
|
163
|
-
// Create new KV namespace
|
|
164
|
-
const createResponse = await api.post(
|
|
165
|
-
`/accounts/${api.accountId}/storage/kv/namespaces`,
|
|
166
|
-
{
|
|
167
|
-
title: props.title,
|
|
168
|
-
},
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
if (!createResponse.ok) {
|
|
172
|
-
const errorData: any = await createResponse.json().catch(() => ({
|
|
173
|
-
errors: [{ message: createResponse.statusText }],
|
|
174
|
-
}));
|
|
175
|
-
throw new Error(
|
|
176
|
-
`Error creating KV namespace '${props.title}': ${errorData.errors?.[0]?.message || createResponse.statusText}`,
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const createResult: any = await createResponse.json();
|
|
181
|
-
namespaceId = createResult.result.id;
|
|
155
|
+
const { id } = await createKVNamespace(api, props);
|
|
182
156
|
createdAt = Date.now();
|
|
157
|
+
namespaceId = id;
|
|
183
158
|
}
|
|
184
159
|
|
|
185
|
-
|
|
186
|
-
if (props.values && props.values.length > 0 && namespaceId) {
|
|
187
|
-
// Process KV pairs in batches of 10000 (API limit)
|
|
188
|
-
const BATCH_SIZE = 10000;
|
|
189
|
-
|
|
190
|
-
for (let i = 0; i < props.values.length; i += BATCH_SIZE) {
|
|
191
|
-
const batch = props.values.slice(i, i + BATCH_SIZE);
|
|
192
|
-
|
|
193
|
-
const bulkPayload = batch.map((entry) => {
|
|
194
|
-
const item: any = {
|
|
195
|
-
key: entry.key,
|
|
196
|
-
value:
|
|
197
|
-
typeof entry.value === "string"
|
|
198
|
-
? entry.value
|
|
199
|
-
: JSON.stringify(entry.value),
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
if (entry.expiration) {
|
|
203
|
-
item.expiration = entry.expiration;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (entry.expirationTtl) {
|
|
207
|
-
item.expiration_ttl = entry.expirationTtl;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (entry.metadata) {
|
|
211
|
-
item.metadata = entry.metadata;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return item;
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
await withExponentialBackoff(
|
|
219
|
-
async () => {
|
|
220
|
-
const bulkResponse = await api.put(
|
|
221
|
-
`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
|
|
222
|
-
bulkPayload,
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
if (!bulkResponse.ok) {
|
|
226
|
-
const errorData: any = await bulkResponse
|
|
227
|
-
.json()
|
|
228
|
-
.catch(() => ({
|
|
229
|
-
errors: [{ message: bulkResponse.statusText }],
|
|
230
|
-
}));
|
|
231
|
-
const errorMessage =
|
|
232
|
-
errorData.errors?.[0]?.message || bulkResponse.statusText;
|
|
233
|
-
|
|
234
|
-
// Throw error to trigger retry
|
|
235
|
-
throw new Error(`Error writing KV batch: ${errorMessage}`);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return bulkResponse;
|
|
239
|
-
},
|
|
240
|
-
(error) => {
|
|
241
|
-
// Retry on "namespace not found" errors as they're likely propagation delays
|
|
242
|
-
return error.message?.includes("not found");
|
|
243
|
-
},
|
|
244
|
-
5, // 5 retry attempts
|
|
245
|
-
1000, // Start with 1 second delay
|
|
246
|
-
);
|
|
247
|
-
} catch (error: any) {
|
|
248
|
-
console.warn(error.message);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
160
|
+
await insertKVRecords(api, namespaceId, props);
|
|
252
161
|
|
|
253
162
|
return this({
|
|
254
163
|
type: "kv_namespace",
|
|
@@ -259,5 +168,108 @@ export const KVNamespace = Resource(
|
|
|
259
168
|
modifiedAt: Date.now(),
|
|
260
169
|
});
|
|
261
170
|
}
|
|
262
|
-
}
|
|
171
|
+
}
|
|
263
172
|
);
|
|
173
|
+
|
|
174
|
+
export async function createKVNamespace(
|
|
175
|
+
api: CloudflareApi,
|
|
176
|
+
props: KVNamespaceProps
|
|
177
|
+
): Promise<{ id: string }> {
|
|
178
|
+
const createResponse = await api.post(
|
|
179
|
+
`/accounts/${api.accountId}/storage/kv/namespaces`,
|
|
180
|
+
{
|
|
181
|
+
title: props.title,
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (!createResponse.ok) {
|
|
186
|
+
await handleApiError(createResponse, "create", "kv_namespace", props.title);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { id: ((await createResponse.json()) as any).result.id };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function deleteKVNamespace(
|
|
193
|
+
api: CloudflareApi,
|
|
194
|
+
namespaceId: string
|
|
195
|
+
) {
|
|
196
|
+
// Delete KV namespace
|
|
197
|
+
const deleteResponse = await api.delete(
|
|
198
|
+
`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}`
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
202
|
+
await handleApiError(deleteResponse, "delete", "kv_namespace", namespaceId);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function insertKVRecords(
|
|
207
|
+
api: CloudflareApi,
|
|
208
|
+
namespaceId: string,
|
|
209
|
+
props: KVNamespaceProps
|
|
210
|
+
) {
|
|
211
|
+
if (props.values && props.values.length > 0) {
|
|
212
|
+
// Process KV pairs in batches of 10000 (API limit)
|
|
213
|
+
const BATCH_SIZE = 10000;
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < props.values.length; i += BATCH_SIZE) {
|
|
216
|
+
const batch = props.values.slice(i, i + BATCH_SIZE);
|
|
217
|
+
|
|
218
|
+
const bulkPayload = batch.map((entry) => {
|
|
219
|
+
const item: any = {
|
|
220
|
+
key: entry.key,
|
|
221
|
+
value:
|
|
222
|
+
typeof entry.value === "string"
|
|
223
|
+
? entry.value
|
|
224
|
+
: JSON.stringify(entry.value),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
if (entry.expiration) {
|
|
228
|
+
item.expiration = entry.expiration;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (entry.expirationTtl) {
|
|
232
|
+
item.expiration_ttl = entry.expirationTtl;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (entry.metadata) {
|
|
236
|
+
item.metadata = entry.metadata;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return item;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
await withExponentialBackoff(
|
|
244
|
+
async () => {
|
|
245
|
+
const bulkResponse = await api.put(
|
|
246
|
+
`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
|
|
247
|
+
bulkPayload
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (!bulkResponse.ok) {
|
|
251
|
+
const errorData: any = await bulkResponse.json().catch(() => ({
|
|
252
|
+
errors: [{ message: bulkResponse.statusText }],
|
|
253
|
+
}));
|
|
254
|
+
const errorMessage =
|
|
255
|
+
errorData.errors?.[0]?.message || bulkResponse.statusText;
|
|
256
|
+
|
|
257
|
+
// Throw error to trigger retry
|
|
258
|
+
throw new Error(`Error writing KV batch: ${errorMessage}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return bulkResponse;
|
|
262
|
+
},
|
|
263
|
+
(error) => {
|
|
264
|
+
// Retry on "namespace not found" errors as they're likely propagation delays
|
|
265
|
+
return error.message?.includes("not found");
|
|
266
|
+
},
|
|
267
|
+
5, // 5 retry attempts
|
|
268
|
+
1000 // Start with 1 second delay
|
|
269
|
+
);
|
|
270
|
+
} catch (error: any) {
|
|
271
|
+
console.warn(error.message);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -72,6 +72,10 @@ export type PermissionGroups = Resource<"cloudflare::PermissionGroups"> & {
|
|
|
72
72
|
*
|
|
73
73
|
* This is primarily used when creating API tokens for Cloudflare services like R2.
|
|
74
74
|
*
|
|
75
|
+
* Note: Requires a Cloudflare API Key or Token with account read access.
|
|
76
|
+
* The API token must have permission to read token permission groups.
|
|
77
|
+
* The OAuth token from `wrangler login` is NOT sufficient for this operation.
|
|
78
|
+
*
|
|
75
79
|
* @example
|
|
76
80
|
* // Get all permission groups including those for R2
|
|
77
81
|
* const permissions = await PermissionGroups("cloudflare-permissions");
|
|
@@ -99,7 +103,7 @@ export const PermissionGroups = Resource(
|
|
|
99
103
|
async function (
|
|
100
104
|
this: Context<PermissionGroups>,
|
|
101
105
|
id: string,
|
|
102
|
-
options
|
|
106
|
+
options: CloudflareApiOptions = {}
|
|
103
107
|
): Promise<PermissionGroups> {
|
|
104
108
|
// Only create and update phases are supported
|
|
105
109
|
if (this.phase === "delete") {
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { Scope } from "../scope";
|
|
2
|
-
import type { Secret } from "../secret";
|
|
3
2
|
import { deserialize, serialize } from "../serde";
|
|
4
3
|
import type { State, StateStore } from "../state";
|
|
5
4
|
import { withExponentialBackoff } from "../util/retry";
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type CloudflareApi,
|
|
7
|
+
type CloudflareApiOptions,
|
|
8
|
+
createCloudflareApi,
|
|
9
|
+
} from "./api";
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Options for CloudflareR2StateStore
|
|
10
13
|
*/
|
|
11
|
-
export interface CloudflareR2StateStoreOptions {
|
|
14
|
+
export interface CloudflareR2StateStoreOptions extends CloudflareApiOptions {
|
|
12
15
|
/**
|
|
13
16
|
* The prefix to use for object keys in the R2 bucket
|
|
14
17
|
* This allows multiple state stores to use the same R2 bucket
|
|
@@ -20,21 +23,6 @@ export interface CloudflareR2StateStoreOptions {
|
|
|
20
23
|
* Required - the bucket must already exist
|
|
21
24
|
*/
|
|
22
25
|
bucketName: string;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* API key to use (overrides CLOUDFLARE_API_KEY env var)
|
|
26
|
-
*/
|
|
27
|
-
apiKey?: Secret;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Account ID to use (overrides CLOUDFLARE_ACCOUNT_ID env var)
|
|
31
|
-
*/
|
|
32
|
-
accountId?: string;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Email to use with API Key authentication (overrides CLOUDFLARE_EMAIL env var)
|
|
36
|
-
*/
|
|
37
|
-
email?: string;
|
|
38
26
|
}
|
|
39
27
|
|
|
40
28
|
/**
|
|
@@ -45,9 +33,6 @@ export class R2RestStateStore implements StateStore {
|
|
|
45
33
|
private api: CloudflareApi;
|
|
46
34
|
private prefix: string;
|
|
47
35
|
private bucketName: string;
|
|
48
|
-
private apiKey: Secret | undefined;
|
|
49
|
-
private accountId: string | undefined;
|
|
50
|
-
private email: string | undefined;
|
|
51
36
|
private initialized = false;
|
|
52
37
|
|
|
53
38
|
/**
|
|
@@ -58,7 +43,7 @@ export class R2RestStateStore implements StateStore {
|
|
|
58
43
|
*/
|
|
59
44
|
constructor(
|
|
60
45
|
public readonly scope: Scope,
|
|
61
|
-
options: CloudflareR2StateStoreOptions
|
|
46
|
+
private readonly options: CloudflareR2StateStoreOptions
|
|
62
47
|
) {
|
|
63
48
|
// Use the scope's chain to build the prefix, similar to how FileSystemStateStore builds its directory
|
|
64
49
|
const scopePath = scope.chain.join("/");
|
|
@@ -71,10 +56,6 @@ export class R2RestStateStore implements StateStore {
|
|
|
71
56
|
}
|
|
72
57
|
this.bucketName = options.bucketName;
|
|
73
58
|
|
|
74
|
-
this.apiKey = options.apiKey;
|
|
75
|
-
this.accountId = options.accountId;
|
|
76
|
-
this.email = options.email;
|
|
77
|
-
|
|
78
59
|
// We'll initialize the API in init() to allow for async creation
|
|
79
60
|
this.api = null as any;
|
|
80
61
|
}
|
|
@@ -86,11 +67,7 @@ export class R2RestStateStore implements StateStore {
|
|
|
86
67
|
if (this.initialized) return;
|
|
87
68
|
|
|
88
69
|
// Create Cloudflare API client with automatic account discovery
|
|
89
|
-
this.api = await createCloudflareApi(
|
|
90
|
-
apiKey: this.apiKey,
|
|
91
|
-
accountId: this.accountId,
|
|
92
|
-
email: this.email,
|
|
93
|
-
});
|
|
70
|
+
this.api = await createCloudflareApi(this.options);
|
|
94
71
|
|
|
95
72
|
this.initialized = true;
|
|
96
73
|
}
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -7,7 +7,11 @@ import { isSecret } from "../secret";
|
|
|
7
7
|
import { getContentType } from "../util/content-type";
|
|
8
8
|
import { withExponentialBackoff } from "../util/retry";
|
|
9
9
|
import { slugify } from "../util/slugify";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type CloudflareApi,
|
|
12
|
+
type CloudflareApiOptions,
|
|
13
|
+
createCloudflareApi,
|
|
14
|
+
} from "./api";
|
|
11
15
|
import type { Assets } from "./assets";
|
|
12
16
|
import {
|
|
13
17
|
type Bindings,
|
|
@@ -24,7 +28,8 @@ import type { SingleStepMigration } from "./worker-migration";
|
|
|
24
28
|
/**
|
|
25
29
|
* Properties for creating or updating a Worker
|
|
26
30
|
*/
|
|
27
|
-
export interface WorkerProps<B extends Bindings = Bindings>
|
|
31
|
+
export interface WorkerProps<B extends Bindings = Bindings>
|
|
32
|
+
extends CloudflareApiOptions {
|
|
28
33
|
/**
|
|
29
34
|
* The worker script content (JavaScript or WASM)
|
|
30
35
|
* One of script, entryPoint, or bundle must be provided
|
|
@@ -58,12 +63,6 @@ export interface WorkerProps<B extends Bindings = Bindings> {
|
|
|
58
63
|
*/
|
|
59
64
|
name: string;
|
|
60
65
|
|
|
61
|
-
/**
|
|
62
|
-
* Routes to associate with this worker
|
|
63
|
-
* Format: example.com/* or *.example.com/*
|
|
64
|
-
*/
|
|
65
|
-
routes?: string[];
|
|
66
|
-
|
|
67
66
|
/**
|
|
68
67
|
* Bindings to attach to the worker
|
|
69
68
|
*/
|
|
@@ -236,7 +235,7 @@ export const Worker = Resource(
|
|
|
236
235
|
props: WorkerProps<B>
|
|
237
236
|
): Promise<Worker<B>> {
|
|
238
237
|
// Create Cloudflare API client with automatic account discovery
|
|
239
|
-
const api = await createCloudflareApi();
|
|
238
|
+
const api = await createCloudflareApi(props);
|
|
240
239
|
|
|
241
240
|
// Use the provided name
|
|
242
241
|
const workerName = props.name;
|
|
@@ -299,9 +298,6 @@ export const Worker = Resource(
|
|
|
299
298
|
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
300
299
|
await this.set("bindings", props.bindings);
|
|
301
300
|
|
|
302
|
-
// Handle routes if requested
|
|
303
|
-
await setupRoutes(api, workerName, props.routes || []);
|
|
304
|
-
|
|
305
301
|
// Handle worker URL if requested
|
|
306
302
|
const workerUrl = await configureURL(
|
|
307
303
|
this,
|
|
@@ -320,7 +316,6 @@ export const Worker = Resource(
|
|
|
320
316
|
name: workerName,
|
|
321
317
|
script: scriptContent,
|
|
322
318
|
format: props.format || "esm", // Include format in the output
|
|
323
|
-
routes: props.routes || [],
|
|
324
319
|
bindings: props.bindings ?? ({} as B),
|
|
325
320
|
env: props.env,
|
|
326
321
|
observability: scriptMetadata.observability,
|
|
@@ -354,35 +349,6 @@ async function deleteWorker<B extends Bindings>(
|
|
|
354
349
|
);
|
|
355
350
|
}
|
|
356
351
|
|
|
357
|
-
// Also remove any associated routes if they exist
|
|
358
|
-
if (ctx.output?.routes && ctx.output.routes.length > 0 && api.zoneId) {
|
|
359
|
-
// First get existing routes to find their IDs
|
|
360
|
-
const routesResponse = await api.get(`/zones/${api.zoneId}/workers/routes`);
|
|
361
|
-
|
|
362
|
-
if (!routesResponse.ok) {
|
|
363
|
-
throw new Error(
|
|
364
|
-
`Could not fetch routes for cleanup: ${routesResponse.status} ${routesResponse.statusText}`
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
const routesData: any = await routesResponse.json();
|
|
368
|
-
const existingRoutes = routesData.result || [];
|
|
369
|
-
|
|
370
|
-
for (const route of existingRoutes) {
|
|
371
|
-
if (ctx.output.routes.includes(route.pattern)) {
|
|
372
|
-
// Delete the route
|
|
373
|
-
const routeDeleteResponse = await api.delete(
|
|
374
|
-
`/zones/${api.zoneId}/workers/routes/${route.id}`
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
if (!routeDeleteResponse.ok) {
|
|
378
|
-
console.warn(
|
|
379
|
-
`Failed to delete route ${route.pattern}: ${routeDeleteResponse.status} ${routeDeleteResponse.statusText}`
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
352
|
// Disable the URL if it was enabled
|
|
387
353
|
if (ctx.output?.url) {
|
|
388
354
|
try {
|
|
@@ -717,67 +683,6 @@ async function bundleWorkerScript<B extends Bindings>(props: WorkerProps) {
|
|
|
717
683
|
}
|
|
718
684
|
}
|
|
719
685
|
|
|
720
|
-
async function setupRoutes(
|
|
721
|
-
api: CloudflareApi,
|
|
722
|
-
workerName: string,
|
|
723
|
-
routes: string[]
|
|
724
|
-
) {
|
|
725
|
-
// Set up routes if provided
|
|
726
|
-
if (routes && routes.length > 0 && api.zoneId) {
|
|
727
|
-
// First get existing routes
|
|
728
|
-
const routesResponse = await api.get(`/zones/${api.zoneId}/workers/routes`);
|
|
729
|
-
|
|
730
|
-
if (!routesResponse.ok) {
|
|
731
|
-
throw new Error(
|
|
732
|
-
`Could not fetch routes: ${routesResponse.status} ${routesResponse.statusText}`
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
const routesData: any = await routesResponse.json();
|
|
736
|
-
const existingRoutes = routesData.result || [];
|
|
737
|
-
|
|
738
|
-
// For each desired route
|
|
739
|
-
for (const pattern of routes) {
|
|
740
|
-
const existingRoute = existingRoutes.find(
|
|
741
|
-
(r: any) => r.pattern === pattern
|
|
742
|
-
);
|
|
743
|
-
|
|
744
|
-
if (existingRoute) {
|
|
745
|
-
// Update if script name doesn't match
|
|
746
|
-
if (existingRoute.script !== workerName) {
|
|
747
|
-
const updateRouteResponse = await api.put(
|
|
748
|
-
`/zones/${api.zoneId}/workers/routes/${existingRoute.id}`,
|
|
749
|
-
{
|
|
750
|
-
pattern,
|
|
751
|
-
script: workerName,
|
|
752
|
-
}
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
if (!updateRouteResponse.ok) {
|
|
756
|
-
console.warn(
|
|
757
|
-
`Failed to update route ${pattern}: ${updateRouteResponse.status} ${updateRouteResponse.statusText}`
|
|
758
|
-
);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
} else {
|
|
762
|
-
// Create new route
|
|
763
|
-
const createRouteResponse = await api.post(
|
|
764
|
-
`/zones/${api.zoneId}/workers/routes`,
|
|
765
|
-
{
|
|
766
|
-
pattern,
|
|
767
|
-
script: workerName,
|
|
768
|
-
}
|
|
769
|
-
);
|
|
770
|
-
|
|
771
|
-
if (!createRouteResponse.ok) {
|
|
772
|
-
throw new Error(
|
|
773
|
-
`Failed to create route ${pattern}: ${createRouteResponse.status} ${createRouteResponse.statusText}`
|
|
774
|
-
);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
686
|
async function configureURL<B extends Bindings>(
|
|
782
687
|
ctx: Context<Worker<B>>,
|
|
783
688
|
api: CloudflareApi,
|
|
@@ -978,6 +883,8 @@ async function uploadAssets(
|
|
|
978
883
|
for (const bucket of buckets) {
|
|
979
884
|
const formData = new FormData();
|
|
980
885
|
|
|
886
|
+
let totalBytes = 0;
|
|
887
|
+
|
|
981
888
|
// Add each file in the bucket to the form
|
|
982
889
|
for (const fileHash of bucket) {
|
|
983
890
|
// Find the file with this hash
|
|
@@ -1000,6 +907,7 @@ async function uploadAssets(
|
|
|
1000
907
|
const blob = new Blob([base64Content], {
|
|
1001
908
|
type: getContentType(file.filePath),
|
|
1002
909
|
});
|
|
910
|
+
totalBytes += blob.size;
|
|
1003
911
|
formData.append(fileHash, blob, fileHash);
|
|
1004
912
|
}
|
|
1005
913
|
|
package/src/cloudflare/zone.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Context } from "../context";
|
|
2
2
|
import { Resource } from "../resource";
|
|
3
|
-
import { createCloudflareApi } from "./api";
|
|
3
|
+
import { createCloudflareApi, type CloudflareApiOptions } from "./api";
|
|
4
4
|
import type {
|
|
5
5
|
AlwaysUseHTTPSValue,
|
|
6
6
|
AutomaticHTTPSRewritesValue,
|
|
@@ -24,18 +24,12 @@ import type {
|
|
|
24
24
|
/**
|
|
25
25
|
* Properties for creating or updating a Zone
|
|
26
26
|
*/
|
|
27
|
-
export interface ZoneProps {
|
|
27
|
+
export interface ZoneProps extends CloudflareApiOptions {
|
|
28
28
|
/**
|
|
29
29
|
* The domain name for the zone
|
|
30
30
|
*/
|
|
31
31
|
name: string;
|
|
32
32
|
|
|
33
|
-
/**
|
|
34
|
-
* Account ID to use for the zone
|
|
35
|
-
* If not provided, will use the default account
|
|
36
|
-
*/
|
|
37
|
-
accountId?: string;
|
|
38
|
-
|
|
39
33
|
/**
|
|
40
34
|
* The type of zone to create
|
|
41
35
|
* "full" - Full zone implies that DNS is hosted with Cloudflare
|
|
@@ -305,12 +299,10 @@ export const Zone = Resource(
|
|
|
305
299
|
async function (
|
|
306
300
|
this: Context<Zone>,
|
|
307
301
|
id: string,
|
|
308
|
-
props: ZoneProps
|
|
302
|
+
props: ZoneProps
|
|
309
303
|
): Promise<Zone> {
|
|
310
304
|
// Create Cloudflare API client with automatic account discovery
|
|
311
|
-
const api = await createCloudflareApi(
|
|
312
|
-
accountId: props.accountId,
|
|
313
|
-
});
|
|
305
|
+
const api = await createCloudflareApi(props);
|
|
314
306
|
|
|
315
307
|
if (this.phase === "delete") {
|
|
316
308
|
if (this.output?.id) {
|
|
@@ -322,7 +314,7 @@ export const Zone = Resource(
|
|
|
322
314
|
errors: [{ message: deleteResponse.statusText }],
|
|
323
315
|
}));
|
|
324
316
|
throw new Error(
|
|
325
|
-
`Error deleting zone '${props.name}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}
|
|
317
|
+
`Error deleting zone '${props.name}': ${errorData.errors?.[0]?.message || deleteResponse.statusText}`
|
|
326
318
|
);
|
|
327
319
|
}
|
|
328
320
|
} else {
|
|
@@ -337,7 +329,7 @@ export const Zone = Resource(
|
|
|
337
329
|
|
|
338
330
|
if (!response.ok) {
|
|
339
331
|
throw new Error(
|
|
340
|
-
`Error getting zone '${props.name}': ${response.statusText}
|
|
332
|
+
`Error getting zone '${props.name}': ${response.statusText}`
|
|
341
333
|
);
|
|
342
334
|
}
|
|
343
335
|
|
|
@@ -385,13 +377,13 @@ export const Zone = Resource(
|
|
|
385
377
|
if (response.status === 400 && body.includes("already exists")) {
|
|
386
378
|
// Zone already exists, fetch it instead
|
|
387
379
|
console.warn(
|
|
388
|
-
`Zone '${props.name}' already exists during Zone create, adopting it
|
|
380
|
+
`Zone '${props.name}' already exists during Zone create, adopting it...`
|
|
389
381
|
);
|
|
390
382
|
const getResponse = await api.get(`/zones?name=${props.name}`);
|
|
391
383
|
|
|
392
384
|
if (!getResponse.ok) {
|
|
393
385
|
throw new Error(
|
|
394
|
-
`Error fetching existing zone '${props.name}': ${getResponse.statusText}
|
|
386
|
+
`Error fetching existing zone '${props.name}': ${getResponse.statusText}`
|
|
395
387
|
);
|
|
396
388
|
}
|
|
397
389
|
|
|
@@ -400,13 +392,13 @@ export const Zone = Resource(
|
|
|
400
392
|
).result;
|
|
401
393
|
if (zones.length === 0) {
|
|
402
394
|
throw new Error(
|
|
403
|
-
`Zone '${props.name}' does not exist, but the name is reserved for another user
|
|
395
|
+
`Zone '${props.name}' does not exist, but the name is reserved for another user.`
|
|
404
396
|
);
|
|
405
397
|
}
|
|
406
398
|
zoneData = zones[0];
|
|
407
399
|
} else {
|
|
408
400
|
throw new Error(
|
|
409
|
-
`Error creating zone '${props.name}': ${response.statusText}\n${body}
|
|
401
|
+
`Error creating zone '${props.name}': ${response.statusText}\n${body}`
|
|
410
402
|
);
|
|
411
403
|
}
|
|
412
404
|
} else {
|
|
@@ -437,7 +429,7 @@ export const Zone = Resource(
|
|
|
437
429
|
settings: await getZoneSettings(api, zoneData.id),
|
|
438
430
|
});
|
|
439
431
|
}
|
|
440
|
-
}
|
|
432
|
+
}
|
|
441
433
|
);
|
|
442
434
|
|
|
443
435
|
/**
|
|
@@ -446,7 +438,7 @@ export const Zone = Resource(
|
|
|
446
438
|
async function updateZoneSettings(
|
|
447
439
|
api: any,
|
|
448
440
|
zoneId: string,
|
|
449
|
-
settings: ZoneProps["settings"]
|
|
441
|
+
settings: ZoneProps["settings"]
|
|
450
442
|
): Promise<void> {
|
|
451
443
|
if (!settings) return;
|
|
452
444
|
|
|
@@ -480,7 +472,7 @@ async function updateZoneSettings(
|
|
|
480
472
|
`/zones/${zoneId}/settings/${settingId}`,
|
|
481
473
|
{
|
|
482
474
|
value,
|
|
483
|
-
} as UpdateZoneSettingParams
|
|
475
|
+
} as UpdateZoneSettingParams
|
|
484
476
|
);
|
|
485
477
|
|
|
486
478
|
if (!response.ok) {
|
|
@@ -490,10 +482,10 @@ async function updateZoneSettings(
|
|
|
490
482
|
return;
|
|
491
483
|
}
|
|
492
484
|
throw new Error(
|
|
493
|
-
`Failed to update zone setting ${key}: ${response.statusText}
|
|
485
|
+
`Failed to update zone setting ${key}: ${response.statusText}`
|
|
494
486
|
);
|
|
495
487
|
}
|
|
496
|
-
})
|
|
488
|
+
})
|
|
497
489
|
);
|
|
498
490
|
}
|
|
499
491
|
|
|
@@ -502,13 +494,13 @@ async function updateZoneSettings(
|
|
|
502
494
|
*/
|
|
503
495
|
async function getZoneSettings(
|
|
504
496
|
api: any,
|
|
505
|
-
zoneId: string
|
|
497
|
+
zoneId: string
|
|
506
498
|
): Promise<Zone["settings"]> {
|
|
507
499
|
const settingsResponse = await api.get(`/zones/${zoneId}/settings`);
|
|
508
500
|
|
|
509
501
|
if (!settingsResponse.ok) {
|
|
510
502
|
throw new Error(
|
|
511
|
-
`Failed to fetch zone settings: ${settingsResponse.status} ${settingsResponse.statusText}
|
|
503
|
+
`Failed to fetch zone settings: ${settingsResponse.status} ${settingsResponse.statusText}`
|
|
512
504
|
);
|
|
513
505
|
}
|
|
514
506
|
|