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.
Files changed (44) 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 +50 -151
  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 +4 -62
  25. package/lib/cloudflare/zone.d.ts +2 -6
  26. package/lib/cloudflare/zone.js +1 -3
  27. package/lib/index.d.ts +1 -0
  28. package/lib/internal/docs/providers.js +0 -7
  29. package/package.json +5 -1
  30. package/src/cloudflare/account-api-token.ts +7 -3
  31. package/src/cloudflare/account-id.ts +12 -0
  32. package/src/cloudflare/api-error.ts +58 -0
  33. package/src/cloudflare/api.ts +70 -219
  34. package/src/cloudflare/auth.ts +262 -119
  35. package/src/cloudflare/bucket.ts +52 -29
  36. package/src/cloudflare/custom-domain.ts +8 -3
  37. package/src/cloudflare/dns-records.ts +7 -3
  38. package/src/cloudflare/kv-namespace.ts +117 -105
  39. package/src/cloudflare/permission-groups.ts +5 -1
  40. package/src/cloudflare/r2-rest-state-store.ts +8 -31
  41. package/src/cloudflare/worker.ts +11 -103
  42. package/src/cloudflare/zone.ts +17 -25
  43. package/src/index.ts +1 -0
  44. package/src/internal/docs/providers.ts +0 -7
@@ -1,12 +1,22 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import type { Secret } from "../secret";
5
+ import type { CloudflareAccountId } from "./account-id";
6
+ import { handleApiError } from "./api-error";
1
7
  /**
2
8
  * Authentication options for Cloudflare API
3
9
  */
4
10
  export interface CloudflareAuthOptions {
5
11
  /**
6
- * If true, throw an error when no authentication is found
7
- * @default true
12
+ * API Key to use with API Key
8
13
  */
9
- required?: boolean;
14
+ apiKey?: Secret;
15
+
16
+ /**
17
+ * API Token to use with API Key
18
+ */
19
+ apiToken?: Secret;
10
20
 
11
21
  /**
12
22
  * Email to use with API Key
@@ -15,126 +25,137 @@ export interface CloudflareAuthOptions {
15
25
  email?: string;
16
26
  }
17
27
 
18
- /**
19
- * Authentication headers for Cloudflare API
20
- */
21
- export interface CloudflareAuthHeaders {
22
- /**
23
- * Authentication headers to use with fetch
24
- */
25
- headers: Record<string, string>;
28
+ export interface CloudflareAccount {
29
+ name: string;
30
+ id: CloudflareAccountId;
31
+ type: "standard" | "zero_rating" | "full_control";
32
+ settings: {
33
+ enforce_twofactor: boolean;
34
+ api_access_enabled: null;
35
+ access_approval_expiry: null;
36
+ abuse_contact_email: null;
37
+ };
38
+ legacy_flags: {
39
+ enterprise_zone_quota: {
40
+ maximum: number;
41
+ current: number;
42
+ available: number;
43
+ };
44
+ };
45
+ }
26
46
 
27
- /**
28
- * Type of authentication being used
29
- */
30
- type: "token" | "key" | "none";
47
+ export interface CloudflareUserInfo {
48
+ apiToken?: Secret;
49
+ apiKey?: Secret;
50
+ email: string;
51
+ username: string;
52
+ accounts: CloudflareAccount[];
53
+ organizations: CloudflareOrganization[];
54
+ tokenPermissions: string[] | undefined;
55
+ first_name: string | null;
56
+ last_name: string | null;
57
+ telephone: string | null;
58
+ country: string | null;
59
+ zipcode: string | null;
60
+ two_factor_authentication_enabled: boolean;
61
+ two_factor_authentication_locked: boolean;
62
+ has_pro_zones: boolean;
63
+ has_business_zones: boolean;
64
+ has_enterprise_zones: boolean;
65
+ suspended: boolean;
66
+ betas: string[];
67
+ }
31
68
 
32
- /**
33
- * User email (only present when using API Key auth)
34
- */
35
- email?: string;
69
+ export interface CloudflareOrganization {
70
+ id: string;
71
+ name: string;
72
+ status: string;
73
+ permissions: string[];
74
+ roles: string[];
36
75
  }
37
76
 
38
- /**
39
- * Response from the Cloudflare User API
40
- */
41
- interface CloudflareUserResponse {
42
- success: boolean;
43
- result: {
44
- id: string;
45
- email: string;
46
- name: string;
47
- [key: string]: any;
77
+ const userInfoCache: Record<string, CloudflareUserInfo> = {};
78
+
79
+ export async function getCloudflareUserInfo(
80
+ options: CloudflareAuthOptions
81
+ ): Promise<CloudflareUserInfo> {
82
+ const cacheKey = JSON.stringify({
83
+ apiKey: options.apiKey?.unencrypted,
84
+ apiToken: options.apiToken?.unencrypted,
85
+ email: options.email,
86
+ });
87
+ if (userInfoCache[cacheKey]) {
88
+ return userInfoCache[cacheKey];
89
+ }
90
+ const headers = await getCloudflareAuthHeaders(options);
91
+ const user = await fetch("https://api.cloudflare.com/client/v4/user", {
92
+ headers,
93
+ });
94
+ if (!user.ok) {
95
+ if (user.status === 403) {
96
+ throw new Error(
97
+ "Cloudflare authentication required. Did you forget to login with `wrangler login` or set CLOUDFLARE_API_TOKEN, CLOUDFLARE_API_KEY, or CLOUDFLARE_EMAIL, or CLOUDFLARE_API_KEY?"
98
+ );
99
+ }
100
+ await handleApiError(user, "getting", "user", "user");
101
+ }
102
+ const accounts = await fetch(
103
+ "https://api.cloudflare.com/client/v4/accounts",
104
+ {
105
+ headers,
106
+ }
107
+ );
108
+ const userInfo: CloudflareUserInfo = {
109
+ ...((await user.json()) as any).result,
110
+ accounts: ((await accounts.json()) as any).result,
48
111
  };
112
+ userInfoCache[cacheKey] = userInfo;
113
+ return userInfo;
49
114
  }
50
115
 
51
- /**
52
- * Get authentication headers for Cloudflare API
53
- *
54
- * Priority:
55
- * 1. API Token (CLOUDFLARE_API_TOKEN) - Recommended by Cloudflare
56
- * 2. API Key (CLOUDFLARE_API_KEY) - Will attempt to discover email automatically
57
- *
58
- * @param options Authentication options
59
- * @returns Authentication headers
60
- */
61
- export async function getCloudflareAuth(
62
- options: CloudflareAuthOptions = {},
63
- ): Promise<CloudflareAuthHeaders> {
64
- const { required = true } = options;
65
-
116
+ export async function getCloudflareAuthHeaders(
117
+ options: CloudflareAuthOptions = {}
118
+ ): Promise<Record<string, string>> {
66
119
  // Check for API Token (preferred method)
67
- const apiToken = process.env.CLOUDFLARE_API_TOKEN;
120
+ const apiToken =
121
+ options.apiToken?.unencrypted ?? process.env.CLOUDFLARE_API_TOKEN;
68
122
  if (apiToken) {
69
123
  return {
70
- headers: {
71
- Authorization: `Bearer ${apiToken}`,
72
- },
73
- type: "token",
124
+ Authorization: `Bearer ${apiToken}`,
74
125
  };
75
126
  }
76
127
 
77
128
  // Check for API Key
78
- const apiKey = process.env.CLOUDFLARE_API_KEY;
129
+ const apiKey = options.apiKey?.unencrypted ?? process.env.CLOUDFLARE_API_KEY;
79
130
  if (apiKey) {
80
- // If email is provided directly, use it
81
- const providedEmail = options.email || process.env.CLOUDFLARE_EMAIL;
82
- if (providedEmail) {
83
- return {
84
- headers: {
85
- "X-Auth-Key": apiKey,
86
- "X-Auth-Email": providedEmail,
87
- },
88
- type: "key",
89
- email: providedEmail,
90
- };
91
- }
92
-
93
- // Otherwise, try to discover email from Cloudflare API
94
- try {
95
- const email = await getUserEmailFromApiKey(apiKey);
96
- return {
97
- headers: {
98
- "X-Auth-Key": apiKey,
99
- "X-Auth-Email": email,
100
- },
101
- type: "key",
102
- email,
103
- };
104
- } catch (error) {
105
- console.error("Failed to discover email from API key:", error);
106
- throw new Error(
107
- "Failed to automatically discover email for API Key authentication.\n" +
108
- "Options:\n" +
109
- "1. Set CLOUDFLARE_EMAIL environment variable\n" +
110
- "2. Use CLOUDFLARE_API_TOKEN instead (recommended by Cloudflare)",
111
- );
112
- }
131
+ return {
132
+ "X-Auth-Key": apiKey,
133
+ "X-Auth-Email":
134
+ options.email ??
135
+ process.env.CLOUDFLARE_EMAIL ??
136
+ (await getUserEmailFromApiKey(apiKey)),
137
+ };
113
138
  }
114
139
 
115
- // No authentication found
116
- if (required) {
117
- throw new Error(
118
- "Cloudflare authentication required. Set either:\n" +
119
- "1. CLOUDFLARE_API_TOKEN (recommended by Cloudflare)\n" +
120
- " Generate a token at: https://dash.cloudflare.com/profile/api-tokens\n" +
121
- "2. CLOUDFLARE_API_KEY environment variable",
122
- );
140
+ const authConfig = await getRefreshedAuthConfig();
141
+ if (authConfig.oauth_token) {
142
+ return {
143
+ Authorization: `Bearer ${authConfig.oauth_token}`,
144
+ };
123
145
  }
124
146
 
125
- return {
126
- headers: {},
127
- type: "none",
128
- };
147
+ throw new Error(
148
+ "Cloudflare authentication required. Did you forget to login with `wrangler login` or set CLOUDFLARE_API_TOKEN, CLOUDFLARE_API_KEY, or CLOUDFLARE_EMAIL, or CLOUDFLARE_API_KEY?"
149
+ );
129
150
  }
130
151
 
131
- /**
132
- * Fetch the user's email from Cloudflare API using API Key
133
- *
134
- * @param apiKey The Cloudflare API key
135
- * @returns The user's email
136
- */
152
+ const emailCache: Record<string, string> = {};
153
+
137
154
  async function getUserEmailFromApiKey(apiKey: string): Promise<string> {
155
+ if (emailCache[apiKey]) {
156
+ return emailCache[apiKey];
157
+ }
158
+
138
159
  try {
139
160
  const baseUrl = "https://api.cloudflare.com/client/v4";
140
161
 
@@ -148,40 +169,162 @@ async function getUserEmailFromApiKey(apiKey: string): Promise<string> {
148
169
 
149
170
  if (!response.ok) {
150
171
  throw new Error(
151
- `Failed to get user information: ${response.status} ${response.statusText}`,
172
+ `Failed to get user information: ${response.status} ${response.statusText}`
152
173
  );
153
174
  }
154
175
 
155
- const data = (await response.json()) as CloudflareUserResponse;
176
+ const data = (await response.json()) as {
177
+ success: boolean;
178
+ result: {
179
+ id: string;
180
+ email: string;
181
+ name: string;
182
+ [key: string]: any;
183
+ };
184
+ };
156
185
 
157
186
  if (!data.success || !data.result || !data.result.email) {
158
187
  throw new Error("Cloudflare API did not return valid user information");
159
188
  }
160
189
 
190
+ emailCache[apiKey] = data.result.email;
161
191
  return data.result.email;
162
192
  } catch (error) {
163
193
  console.error("Error retrieving email from Cloudflare API:", error);
164
194
  throw new Error(
165
- "Failed to automatically discover email for API Key authentication",
195
+ "Failed to automatically discover email for API Key authentication"
166
196
  );
167
197
  }
168
198
  }
169
199
 
170
- /**
171
- * Get headers for a Cloudflare API request, including content-type
172
- *
173
- * @param contentType Content type for the request
174
- * @param options Authentication options
175
- * @returns Complete headers for the request
176
- */
177
- export async function getCloudflareHeaders(
178
- contentType = "application/json",
179
- options: CloudflareAuthOptions = {},
180
- ): Promise<Record<string, string>> {
181
- const auth = await getCloudflareAuth(options);
200
+ async function refreshAuthToken(
201
+ options: WranglerConfig
202
+ ): Promise<WranglerConfig> {
203
+ const response = await fetch("https://dash.cloudflare.com/oauth2/token", {
204
+ method: "POST",
205
+ headers: {
206
+ "Content-Type": "application/x-www-form-urlencoded",
207
+ },
208
+ body: new URLSearchParams({
209
+ grant_type: "refresh_token",
210
+ refresh_token: options.refresh_token!,
211
+ client_id: "54d11594-84e4-41aa-b438-e81b8fa78ee7",
212
+ }).toString(),
213
+ });
214
+
215
+ if (!response.ok) {
216
+ throw new Error(
217
+ `Failed to refresh auth token: ${response.status} ${response.statusText}`
218
+ );
219
+ }
220
+
221
+ const data: any = await response.json();
222
+ if (!data.access_token) {
223
+ throw new Error("Failed to refresh auth token - no access token returned");
224
+ }
225
+
226
+ options.oauth_token = data.access_token;
227
+ options.refresh_token = data.refresh_token;
228
+ options.expiration_time = new Date(
229
+ Date.now() + data.expires_in * 1000
230
+ ).toISOString();
231
+ options.scopes = data.scope?.split(" ") || [];
232
+
233
+ return options;
234
+ }
235
+
236
+ interface WranglerConfig {
237
+ path: string;
238
+ oauth_token?: string;
239
+ refresh_token?: string;
240
+ expiration_time?: string;
241
+ scopes?: string[];
242
+ /** @deprecated - this field was only provided by the deprecated v1 `wrangler config` command. */
243
+ api_token?: string;
244
+ }
182
245
 
183
- return {
184
- "Content-Type": contentType,
185
- ...auth.headers,
246
+ async function getRefreshedAuthConfig(): Promise<WranglerConfig> {
247
+ let authConfig = await readWranglerConfig();
248
+ if (authConfig.expiration_time) {
249
+ const expiry = new Date(authConfig.expiration_time);
250
+ // if expiring in 10s
251
+ if (expiry.getTime() < Date.now() + 10 * 1000) {
252
+ authConfig = await refreshAuthToken(authConfig);
253
+ authConfigCache[authConfig.path] = authConfig;
254
+ await writeWranglerConfig(authConfig);
255
+ }
256
+ }
257
+ return authConfig;
258
+ }
259
+
260
+ async function writeWranglerConfig(config: WranglerConfig) {
261
+ const TOML = await import("@iarna/toml");
262
+ const configPath = await findWranglerConfig();
263
+ config = {
264
+ ...config,
186
265
  };
266
+ // @ts-ignore - i put this here
267
+ delete config.path;
268
+ const toml = TOML.stringify(config as any);
269
+ await fs.writeFile(configPath, toml);
270
+ }
271
+
272
+ // cache the file once per process
273
+ const authConfigCache: Record<string, WranglerConfig> = {};
274
+
275
+ async function readWranglerConfig(): Promise<WranglerConfig> {
276
+ const configPath = await findWranglerConfig();
277
+ const config = (authConfigCache[configPath] ??= await parseTOML(
278
+ await fs.readFile(configPath, "utf-8")
279
+ ));
280
+ config.path = configPath;
281
+ return config;
282
+ }
283
+
284
+ let wranglerConfigPath: string | undefined;
285
+
286
+ async function findWranglerConfig(): Promise<string> {
287
+ if (wranglerConfigPath) {
288
+ return wranglerConfigPath;
289
+ }
290
+ const environment = process.env.WRANGLER_API_ENVIRONMENT ?? "production";
291
+ const filePath = path.join(
292
+ "config",
293
+ `${environment === "production" ? "default.toml" : `${environment}.toml`}`
294
+ );
295
+
296
+ const xdgAppPaths = (await import("xdg-app-paths")).default;
297
+ //TODO: We should implement a custom path --global-config and/or the WRANGLER_HOME type environment variable
298
+ const configDir = xdgAppPaths(".wrangler").config(); // New XDG compliant config path
299
+ const legacyConfigDir = path.join(os.homedir(), ".wrangler"); // Legacy config in user's home directory
300
+
301
+ // Check for the .wrangler directory in root if it is not there then use the XDG compliant path.
302
+ wranglerConfigPath = path.join(
303
+ (await isDirectory(legacyConfigDir)) ? legacyConfigDir : configDir,
304
+ filePath
305
+ );
306
+ return wranglerConfigPath;
307
+ }
308
+
309
+ async function parseTOML(input: string): Promise<any> {
310
+ const TOML = await import("@iarna/toml");
311
+ try {
312
+ // Normalize CRLF to LF to avoid hitting https://github.com/iarna/iarna-toml/issues/33.
313
+ const normalizedInput = input.replace(/\r\n/g, "\n");
314
+ return TOML.parse(normalizedInput);
315
+ } catch (err: any) {
316
+ const { name } = err;
317
+ if (name !== "TomlError") {
318
+ throw err;
319
+ }
320
+ throw new Error("TOML parse error");
321
+ }
322
+ }
323
+
324
+ async function isDirectory(dir: string) {
325
+ try {
326
+ return (await fs.stat(dir)).isDirectory();
327
+ } catch (err) {
328
+ return false;
329
+ }
187
330
  }
@@ -1,19 +1,14 @@
1
1
  import { AwsClient } from "aws4fetch";
2
2
  import type { Context } from "../context";
3
3
  import { Resource } from "../resource";
4
- import {
5
- CloudflareAccountId,
6
- CloudflareApi,
7
- CloudflareApiError,
8
- createCloudflareApi,
9
- handleApiError,
10
- type CloudflareApiOptions,
11
- } from "./api";
4
+ import type { Secret } from "../secret";
5
+ import { CloudflareApi, createCloudflareApi } from "./api";
6
+ import { CloudflareApiError, handleApiError } from "./api-error";
12
7
 
13
8
  /**
14
9
  * Properties for creating or updating an R2 Bucket
15
10
  */
16
- export interface BucketProps extends CloudflareApiOptions {
11
+ export interface BucketProps {
17
12
  /**
18
13
  * Name of the bucket
19
14
  * Names can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-)
@@ -52,6 +47,36 @@ export interface BucketProps extends CloudflareApiOptions {
52
47
  * @default false
53
48
  */
54
49
  empty?: boolean;
50
+
51
+ /**
52
+ * API Token to use for the bucket
53
+ */
54
+ apiToken?: Secret;
55
+
56
+ /**
57
+ * API Key to use for the bucket
58
+ */
59
+ apiKey?: Secret;
60
+
61
+ /**
62
+ * Email to use for the bucket
63
+ */
64
+ email?: string;
65
+
66
+ /**
67
+ * Account ID to use for the bucket
68
+ */
69
+ accountId?: string;
70
+
71
+ /**
72
+ * Access Key to use for the bucket
73
+ */
74
+ accessKey?: Secret;
75
+
76
+ /**
77
+ * Secret Access Key to use for the bucket
78
+ */
79
+ secretAccessKey?: Secret;
55
80
  }
56
81
 
57
82
  /**
@@ -135,7 +160,10 @@ export const R2Bucket = Resource(
135
160
  if (props.delete !== false) {
136
161
  if (props.empty) {
137
162
  console.log("Emptying R2 bucket:", bucketName);
138
- const r2Client = await createR2Client(props);
163
+ const r2Client = await createR2Client({
164
+ ...props,
165
+ accountId: api.accountId,
166
+ });
139
167
  // Empty the bucket first by deleting all objects
140
168
  await emptyBucket(r2Client, bucketName, props.jurisdiction);
141
169
  }
@@ -166,6 +194,7 @@ export const R2Bucket = Resource(
166
194
  creationDate: new Date(),
167
195
  jurisdiction: props.jurisdiction || "default",
168
196
  type: "r2_bucket",
197
+ accountId: api.accountId,
169
198
  });
170
199
  }
171
200
  }
@@ -175,9 +204,9 @@ export const R2Bucket = Resource(
175
204
  * Configuration for R2 client to connect to Cloudflare R2
176
205
  */
177
206
  export interface R2ClientConfig {
178
- accountId?: string;
179
- accessKeyId?: string;
180
- secretAccessKey?: string;
207
+ accountId: string;
208
+ accessKeyId?: Secret;
209
+ secretAccessKey?: Secret;
181
210
  jurisdiction?: string;
182
211
  }
183
212
 
@@ -188,13 +217,12 @@ type R2Client = AwsClient & { accountId: string };
188
217
  *
189
218
  * @see https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/
190
219
  */
191
- export async function createR2Client(
192
- config: R2ClientConfig = {}
193
- ): Promise<R2Client> {
194
- const accountId = await CloudflareAccountId(config);
195
- const accessKeyId = config.accessKeyId || process.env.R2_ACCESS_KEY_ID;
220
+ export function createR2Client(config?: R2ClientConfig): Promise<R2Client> {
221
+ const accountId = config?.accountId ?? process.env.CLOUDFLARE_ACCOUNT_ID;
222
+ const accessKeyId =
223
+ config?.accessKeyId?.unencrypted || process.env.R2_ACCESS_KEY_ID;
196
224
  const secretAccessKey =
197
- config.secretAccessKey || process.env.R2_SECRET_ACCESS_KEY;
225
+ config?.secretAccessKey?.unencrypted || process.env.R2_SECRET_ACCESS_KEY;
198
226
 
199
227
  if (!accountId) {
200
228
  throw new Error("CLOUDFLARE_ACCOUNT_ID environment variable is required");
@@ -282,12 +310,11 @@ export async function createBucket(
282
310
  );
283
311
 
284
312
  if (!createResponse.ok) {
285
- const errorData: any = await createResponse.json().catch(() => ({
286
- errors: [{ message: createResponse.statusText }],
287
- }));
288
- throw new CloudflareApiError(
289
- `Error creating R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || createResponse.statusText}`,
290
- createResponse
313
+ return await handleApiError(
314
+ createResponse,
315
+ "creating",
316
+ "R2 bucket",
317
+ bucketName
291
318
  );
292
319
  }
293
320
 
@@ -469,10 +496,6 @@ export async function updatePublicAccess(
469
496
  allowPublicAccess: boolean,
470
497
  jurisdiction?: string
471
498
  ): Promise<void> {
472
- console.log(
473
- `/accounts/${api.accountId}/r2/buckets/${bucketName}/domains/managed`
474
- );
475
-
476
499
  const headers = withJurisdiction({}, jurisdiction);
477
500
 
478
501
  const response = await api.put(
@@ -1,11 +1,16 @@
1
1
  import type { Context } from "../context";
2
2
  import { Resource } from "../resource";
3
- import { CloudflareApi, createCloudflareApi, handleApiError } from "./api";
3
+ import {
4
+ CloudflareApi,
5
+ createCloudflareApi,
6
+ type CloudflareApiOptions,
7
+ } from "./api";
8
+ import { handleApiError } from "./api-error";
4
9
 
5
10
  /**
6
11
  * Properties for creating or updating a CustomDomain
7
12
  */
8
- export interface CustomDomainProps {
13
+ export interface CustomDomainProps extends CloudflareApiOptions {
9
14
  /**
10
15
  * The domain name to bind to the worker
11
16
  */
@@ -89,7 +94,7 @@ export const CustomDomain = Resource(
89
94
  props: CustomDomainProps
90
95
  ): Promise<CustomDomain> {
91
96
  // Create Cloudflare API client with automatic account discovery
92
- const api = await createCloudflareApi();
97
+ const api = await createCloudflareApi(props);
93
98
 
94
99
  // Validate required properties
95
100
  if (!props.name) {
@@ -5,7 +5,11 @@ import type {
5
5
  DnsRecordWithMetadata,
6
6
  } from "../dns/record";
7
7
  import { Resource } from "../resource";
8
- import { type CloudflareApi, createCloudflareApi } from "./api";
8
+ import {
9
+ type CloudflareApi,
10
+ type CloudflareApiOptions,
11
+ createCloudflareApi,
12
+ } from "./api";
9
13
  import type { CloudflareResponse } from "./response";
10
14
 
11
15
  /**
@@ -48,7 +52,7 @@ export interface DnsRecord extends DnsRecordWithMetadata {}
48
52
  /**
49
53
  * Properties for managing multiple DNS records
50
54
  */
51
- export interface DnsRecordsProps {
55
+ export interface DnsRecordsProps extends CloudflareApiOptions {
52
56
  /**
53
57
  * Zone ID or domain name where records will be created
54
58
  */
@@ -127,7 +131,7 @@ export const DnsRecords = Resource(
127
131
  props: DnsRecordsProps
128
132
  ): Promise<DnsRecords> {
129
133
  // Create Cloudflare API client
130
- const api = await createCloudflareApi();
134
+ const api = await createCloudflareApi(props);
131
135
 
132
136
  // Get zone ID if domain name was provided
133
137
  const zoneId = props.zoneId;