alchemy 0.5.2 → 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.
Files changed (42) hide show
  1. package/lib/cloudflare/account-api-token.d.ts +6 -1
  2. package/lib/cloudflare/account-api-token.js +5 -1
  3. package/lib/cloudflare/account-id.d.ts +5 -1
  4. package/lib/cloudflare/account-id.js +5 -0
  5. package/lib/cloudflare/api-error.d.ts +32 -0
  6. package/lib/cloudflare/api-error.js +47 -0
  7. package/lib/cloudflare/api.d.ts +16 -49
  8. package/lib/cloudflare/api.js +37 -149
  9. package/lib/cloudflare/auth.d.ts +55 -38
  10. package/lib/cloudflare/auth.js +148 -77
  11. package/lib/cloudflare/bucket.d.ts +30 -5
  12. package/lib/cloudflare/bucket.js +12 -11
  13. package/lib/cloudflare/custom-domain.d.ts +2 -1
  14. package/lib/cloudflare/custom-domain.js +3 -2
  15. package/lib/cloudflare/dns-records.d.ts +2 -1
  16. package/lib/cloudflare/dns-records.js +2 -2
  17. package/lib/cloudflare/kv-namespace.d.ts +7 -1
  18. package/lib/cloudflare/kv-namespace.js +72 -71
  19. package/lib/cloudflare/permission-groups.d.ts +4 -0
  20. package/lib/cloudflare/permission-groups.js +5 -1
  21. package/lib/cloudflare/r2-rest-state-store.d.ts +3 -17
  22. package/lib/cloudflare/r2-rest-state-store.js +4 -12
  23. package/lib/cloudflare/worker.d.ts +2 -6
  24. package/lib/cloudflare/worker.js +2 -63
  25. package/lib/cloudflare/zone.d.ts +2 -6
  26. package/lib/cloudflare/zone.js +1 -3
  27. package/lib/internal/docs/providers.js +0 -7
  28. package/package.json +5 -1
  29. package/src/cloudflare/account-api-token.ts +7 -3
  30. package/src/cloudflare/account-id.ts +12 -0
  31. package/src/cloudflare/api-error.ts +58 -0
  32. package/src/cloudflare/api.ts +50 -216
  33. package/src/cloudflare/auth.ts +262 -119
  34. package/src/cloudflare/bucket.ts +52 -29
  35. package/src/cloudflare/custom-domain.ts +8 -3
  36. package/src/cloudflare/dns-records.ts +7 -3
  37. package/src/cloudflare/kv-namespace.ts +117 -105
  38. package/src/cloudflare/permission-groups.ts +5 -1
  39. package/src/cloudflare/r2-rest-state-store.ts +8 -31
  40. package/src/cloudflare/worker.ts +8 -105
  41. package/src/cloudflare/zone.ts +17 -25
  42. 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 { createCloudflareApi } from "./api";
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
- // Delete KV namespace
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
- // Handle KV pairs if provided
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?: CloudflareApiOptions
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 { type CloudflareApi, createCloudflareApi } from "./api";
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
  }
@@ -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 { type CloudflareApi, createCloudflareApi } from "./api";
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,
@@ -1006,8 +911,6 @@ async function uploadAssets(
1006
911
  formData.append(fileHash, blob, fileHash);
1007
912
  }
1008
913
 
1009
- console.log(`Uploading ${totalBytes} bytes of assets`);
1010
-
1011
914
  // Upload this batch of files
1012
915
  const uploadResponse = await api.post(
1013
916
  `/accounts/${api.accountId}/workers/assets/upload?base64=true`,
@@ -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
 
@@ -67,13 +67,6 @@ export async function Providers({
67
67
  .filter((dirent) => dirent.isDirectory() && !exclude.includes(dirent.name))
68
68
  .map((dirent) => path.join(dirent.parentPath, dirent.name));
69
69
 
70
- // For each provider, list all files
71
- if (filter === false) {
72
- return [];
73
- } else if (typeof filter === "number") {
74
- providers = providers.slice(0, filter);
75
- }
76
-
77
70
  if (parallel) {
78
71
  return await Promise.all(
79
72
  providers.map((provider) =>