alchemy 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +8 -3
  2. package/lib/ai/document.d.ts +12 -1
  3. package/lib/ai/document.js +10 -1
  4. package/lib/ai/index.d.ts +0 -2
  5. package/lib/ai/index.js +0 -2
  6. package/lib/alchemy.d.ts +5 -0
  7. package/lib/alchemy.js +44 -1
  8. package/lib/apply.js +17 -10
  9. package/lib/aws/account-id.d.ts +4 -1
  10. package/lib/aws/account-id.js +1 -1
  11. package/lib/aws/credentials.d.ts +5 -0
  12. package/lib/aws/credentials.js +0 -0
  13. package/lib/aws/oidc/oidc-provider.js +2 -2
  14. package/lib/aws/role.js +35 -23
  15. package/lib/cloudflare/account-api-token.d.ts +168 -0
  16. package/lib/cloudflare/account-api-token.js +179 -0
  17. package/lib/cloudflare/account-id.d.ts +1 -0
  18. package/lib/cloudflare/account-id.js +0 -0
  19. package/lib/cloudflare/api.d.ts +38 -1
  20. package/lib/cloudflare/api.js +55 -4
  21. package/lib/cloudflare/assets.d.ts +74 -0
  22. package/lib/cloudflare/assets.js +73 -0
  23. package/lib/cloudflare/bindings.d.ts +6 -1
  24. package/lib/cloudflare/bindings.js +6 -0
  25. package/lib/cloudflare/bucket.d.ts +118 -3
  26. package/lib/cloudflare/bucket.js +269 -89
  27. package/lib/cloudflare/custom-domain.d.ts +75 -0
  28. package/lib/cloudflare/custom-domain.js +154 -0
  29. package/lib/cloudflare/{dns.js → dns-records.js} +14 -1
  30. package/lib/cloudflare/index.d.ts +6 -1
  31. package/lib/cloudflare/index.js +6 -1
  32. package/lib/cloudflare/permission-groups.d.ts +78 -0
  33. package/lib/cloudflare/permission-groups.js +48 -0
  34. package/lib/cloudflare/r2-rest-state-store.d.ts +2 -1
  35. package/lib/cloudflare/r2-rest-state-store.js +55 -24
  36. package/lib/cloudflare/static-site.d.ts +17 -18
  37. package/lib/cloudflare/static-site.js +19 -10
  38. package/lib/cloudflare/worker.d.ts +14 -0
  39. package/lib/cloudflare/worker.js +150 -3
  40. package/lib/{util/encrypt.d.ts → encrypt.d.ts} +1 -1
  41. package/lib/{util/encrypt.js → encrypt.js} +1 -1
  42. package/lib/fs/file-system-state-store.js +1 -1
  43. package/lib/fs/file.d.ts +18 -0
  44. package/lib/fs/file.js +6 -0
  45. package/lib/internal/{providers.d.ts → docs/providers.d.ts} +10 -4
  46. package/lib/internal/docs/providers.js +196 -0
  47. package/lib/secret.d.ts +3 -0
  48. package/lib/secret.js +13 -0
  49. package/lib/{util/serde.d.ts → serde.d.ts} +1 -1
  50. package/lib/{util/serde.js → serde.js} +12 -4
  51. package/lib/test/bun.js +3 -2
  52. package/lib/util/sha256.d.ts +1 -0
  53. package/lib/util/sha256.js +4 -0
  54. package/lib/web/vitepress/config.d.ts +2 -1
  55. package/lib/web/vitepress/config.js +1 -0
  56. package/lib/web/vitepress/index.d.ts +1 -0
  57. package/lib/web/vitepress/index.js +1 -0
  58. package/lib/web/vitepress/process-front-matter-files.d.ts +18 -0
  59. package/lib/web/vitepress/process-front-matter-files.js +68 -0
  60. package/lib/web/vitepress/vitepress.js +3 -2
  61. package/package.json +8 -4
  62. package/src/ai/document.ts +26 -2
  63. package/src/ai/index.ts +0 -2
  64. package/src/alchemy.ts +56 -2
  65. package/src/apply.ts +21 -20
  66. package/src/aws/account-id.ts +6 -2
  67. package/src/aws/credentials.ts +6 -0
  68. package/src/aws/oidc/oidc-provider.ts +17 -17
  69. package/src/aws/role.ts +68 -54
  70. package/src/cloudflare/account-api-token.ts +365 -0
  71. package/src/cloudflare/account-id.ts +0 -0
  72. package/src/cloudflare/api.ts +88 -17
  73. package/src/cloudflare/assets.ts +155 -0
  74. package/src/cloudflare/bindings.ts +11 -2
  75. package/src/cloudflare/bucket.ts +493 -133
  76. package/src/cloudflare/custom-domain.ts +318 -0
  77. package/src/cloudflare/{dns.ts → dns-records.ts} +36 -22
  78. package/src/cloudflare/index.ts +6 -1
  79. package/src/cloudflare/permission-groups.ts +137 -0
  80. package/src/cloudflare/r2-rest-state-store.ts +87 -42
  81. package/src/cloudflare/static-site.ts +20 -32
  82. package/src/cloudflare/worker.ts +253 -5
  83. package/src/dns/import-dns.ts +4 -4
  84. package/src/{util/encrypt.ts → encrypt.ts} +7 -10
  85. package/src/fs/file-system-state-store.ts +5 -5
  86. package/src/fs/file.ts +29 -0
  87. package/src/internal/docs/providers.ts +281 -0
  88. package/src/secret.ts +16 -0
  89. package/src/{util/serde.ts → serde.ts} +16 -10
  90. package/src/test/bun.ts +8 -7
  91. package/src/util/sha256.ts +5 -0
  92. package/src/web/vitepress/config.ts +3 -1
  93. package/src/web/vitepress/index.ts +1 -0
  94. package/src/web/vitepress/process-front-matter-files.ts +98 -0
  95. package/src/web/vitepress/vitepress.ts +3 -2
  96. package/lib/ai/approve.d.ts +0 -99
  97. package/lib/ai/approve.js +0 -76
  98. package/lib/ai/review.d.ts +0 -122
  99. package/lib/ai/review.js +0 -101
  100. package/lib/internal/getting-started.d.ts +0 -21
  101. package/lib/internal/getting-started.js +0 -87
  102. package/lib/internal/index.d.ts +0 -3
  103. package/lib/internal/index.js +0 -3
  104. package/lib/internal/providers.js +0 -172
  105. package/lib/internal/tutorial.d.ts +0 -104
  106. package/lib/internal/tutorial.js +0 -251
  107. package/src/ai/approve.ts +0 -163
  108. package/src/ai/review.ts +0 -213
  109. package/src/internal/getting-started.ts +0 -115
  110. package/src/internal/index.ts +0 -3
  111. package/src/internal/providers.ts +0 -241
  112. package/src/internal/tutorial.ts +0 -392
  113. /package/lib/cloudflare/{dns.d.ts → dns-records.d.ts} +0 -0
@@ -0,0 +1,179 @@
1
+ import { alchemy } from "../alchemy";
2
+ import { Resource } from "../resource";
3
+ import { Secret } from "../secret";
4
+ import { sha256 } from "../util/sha256";
5
+ import { createCloudflareApi } from "./api";
6
+ /**
7
+ * Creates a Cloudflare Account API Token with specified permissions.
8
+ *
9
+ * @see https://developers.cloudflare.com/api/resources/accounts/subresources/tokens/methods/create/
10
+ *
11
+ * @example
12
+ * // First, fetch all permission groups
13
+ * const permissions = await PermissionGroups("cloudflare-permissions", {
14
+ * accountId: cfAccountId,
15
+ * });
16
+ *
17
+ * // Create a token with read-only permissions for specific zones
18
+ * const readOnlyToken = await AccountApiToken("readonly-token", {
19
+ * name: "Readonly Zone Token",
20
+ * policies: [
21
+ * {
22
+ * effect: "allow",
23
+ * permissionGroups: [
24
+ * { id: permissions["Zone Read"].id },
25
+ * { id: permissions["Analytics Read"].id }
26
+ * ],
27
+ * resources: {
28
+ * "com.cloudflare.api.account.zone.22b1de5f1c0e4b3ea97bb1e963b06a43": "*",
29
+ * "com.cloudflare.api.account.zone.eb78d65290b24279ba6f44721b3ea3c4": "*"
30
+ * }
31
+ * }
32
+ * ],
33
+ * expiresOn: "2024-12-31T23:59:59Z"
34
+ * });
35
+ *
36
+ * @example
37
+ * // Create a token with time and IP restrictions
38
+ * const restrictedToken = await AccountApiToken("restricted-token", {
39
+ * name: "Restricted Access Token",
40
+ * policies: [
41
+ * {
42
+ * effect: "allow",
43
+ * permissionGroups: [
44
+ * { id: permissions["Worker Routes Edit"].id }
45
+ * ],
46
+ * resources: {
47
+ * "com.cloudflare.api.account.worker.route.*": "*"
48
+ * }
49
+ * }
50
+ * ],
51
+ * notBefore: "2023-01-01T00:00:00Z",
52
+ * expiresOn: "2023-12-31T23:59:59Z",
53
+ * condition: {
54
+ * requestIp: {
55
+ * in: ["192.168.1.0/24", "10.0.0.0/8"],
56
+ * notIn: ["192.168.1.100/32"]
57
+ * }
58
+ * }
59
+ * });
60
+ */
61
+ export const AccountApiToken = Resource("cloudflare::AccountApiToken", async function (id, props) {
62
+ // Create Cloudflare API client with automatic account discovery
63
+ const api = await createCloudflareApi();
64
+ if (this.phase === "delete") {
65
+ // Delete token if we have an ID
66
+ if (this.output?.id) {
67
+ try {
68
+ const deleteResponse = await api.delete(`/accounts/${api.accountId}/tokens/${this.output.id}`);
69
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
70
+ const errorData = await deleteResponse.json().catch(() => ({
71
+ errors: [{ message: deleteResponse.statusText }],
72
+ }));
73
+ console.error(`Error deleting token '${props.name}':`, errorData);
74
+ }
75
+ }
76
+ catch (error) {
77
+ console.error(`Error deleting token '${props.name}':`, error);
78
+ }
79
+ }
80
+ // Return destroyed state
81
+ return this.destroy();
82
+ }
83
+ // Transform our properties to API format
84
+ const apiPayload = {
85
+ name: props.name,
86
+ policies: props.policies.map((policy) => ({
87
+ effect: policy.effect,
88
+ permission_groups: policy.permissionGroups.map((pg) => ({
89
+ id: pg.id,
90
+ meta: pg.meta || {},
91
+ })),
92
+ resources: policy.resources,
93
+ })),
94
+ // Format dates for Cloudflare API (removing milliseconds)
95
+ ...(props.expiresOn
96
+ ? { expires_on: formatCloudflareDate(props.expiresOn) }
97
+ : {}),
98
+ ...(props.notBefore
99
+ ? { not_before: formatCloudflareDate(props.notBefore) }
100
+ : {}),
101
+ ...(props.condition
102
+ ? {
103
+ condition: {
104
+ request_ip: props.condition.requestIp
105
+ ? {
106
+ in: props.condition.requestIp.in || [],
107
+ not_in: props.condition.requestIp.notIn || [],
108
+ }
109
+ : undefined,
110
+ },
111
+ }
112
+ : {}),
113
+ };
114
+ /**
115
+ * Formats a date string for Cloudflare API by removing milliseconds
116
+ * Converts from "2023-01-01T00:00:00.000Z" to "2023-01-01T00:00:00Z"
117
+ */
118
+ function formatCloudflareDate(dateStr) {
119
+ return dateStr.replace(/\.\d{3}Z$/, "Z");
120
+ }
121
+ let response;
122
+ let tokenValue;
123
+ if (this.phase === "update" && this.output?.id) {
124
+ // Update existing token
125
+ response = await api.put(`/accounts/${api.accountId}/tokens/${this.output.id}`, apiPayload);
126
+ }
127
+ else {
128
+ // Create new token
129
+ response = await api.post(`/accounts/${api.accountId}/tokens`, apiPayload);
130
+ }
131
+ if (!response.ok) {
132
+ const errorData = await response.json().catch(() => ({
133
+ errors: [{ message: response.statusText }],
134
+ }));
135
+ throw new Error(`Error ${this.phase === "update" ? "updating" : "creating"} token '${props.name}': ${errorData.errors?.[0]?.message || response.statusText}`);
136
+ }
137
+ const result = await response.json();
138
+ const tokenData = result.result;
139
+ if (tokenData.value) {
140
+ tokenValue = alchemy.secret(tokenData.value);
141
+ }
142
+ else {
143
+ if (!this.output?.value) {
144
+ throw new Error(`Token '${props.name}' was created but we have no record of its value. Try deleting and recreating the token.`);
145
+ }
146
+ tokenValue = this.output?.value;
147
+ }
148
+ // Transform API response to our format
149
+ return this({
150
+ id: tokenData.id,
151
+ name: tokenData.name,
152
+ status: tokenData.status,
153
+ policies: tokenData.policies.map((policy) => ({
154
+ effect: policy.effect,
155
+ permissionGroups: policy.permission_groups.map((pg) => ({
156
+ id: pg.id,
157
+ meta: pg.meta,
158
+ })),
159
+ resources: policy.resources,
160
+ })),
161
+ ...(tokenData.expires_on ? { expiresOn: tokenData.expires_on } : {}),
162
+ ...(tokenData.not_before ? { notBefore: tokenData.not_before } : {}),
163
+ ...(tokenData.condition
164
+ ? {
165
+ condition: {
166
+ requestIp: tokenData.condition.request_ip
167
+ ? {
168
+ in: tokenData.condition.request_ip.in || [],
169
+ notIn: tokenData.condition.request_ip.not_in || [],
170
+ }
171
+ : undefined,
172
+ },
173
+ }
174
+ : {}),
175
+ value: tokenValue,
176
+ accessKeyId: tokenData.id,
177
+ secretAccessKey: sha256(tokenValue.unencrypted),
178
+ });
179
+ });
@@ -0,0 +1 @@
1
+ export {};
File without changes
@@ -1,3 +1,4 @@
1
+ import type { Secret } from "../secret";
1
2
  /**
2
3
  * Options for Cloudflare API requests
3
4
  */
@@ -5,7 +6,7 @@ export interface CloudflareApiOptions {
5
6
  /**
6
7
  * API Key to use (overrides CLOUDFLARE_API_KEY env var)
7
8
  */
8
- apiKey?: string;
9
+ apiKey?: Secret;
9
10
  /**
10
11
  * Account ID to use (overrides CLOUDFLARE_ACCOUNT_ID env var)
11
12
  * If not provided, will be automatically retrieved from the Cloudflare API
@@ -21,6 +22,38 @@ export interface CloudflareApiOptions {
21
22
  */
22
23
  email?: string;
23
24
  }
25
+ /**
26
+ * Custom error class for Cloudflare API errors
27
+ * Includes HTTP status information from the Response
28
+ */
29
+ export declare class CloudflareApiError extends Error {
30
+ /**
31
+ * HTTP status code
32
+ */
33
+ status: number;
34
+ /**
35
+ * HTTP status text
36
+ */
37
+ statusText: string;
38
+ /**
39
+ * Raw error data from the API
40
+ */
41
+ errorData?: any;
42
+ /**
43
+ * Create a new CloudflareApiError
44
+ */
45
+ constructor(message: string, response: Response, errorData?: any);
46
+ }
47
+ /**
48
+ * Helper function to handle API errors
49
+ *
50
+ * @param response The fetch Response object
51
+ * @param action The action being performed (e.g., "creating", "deleting")
52
+ * @param resourceType The type of resource being acted upon (e.g., "R2 bucket", "Worker")
53
+ * @param resourceName The name/identifier of the specific resource
54
+ * @returns Never returns - always throws an error
55
+ */
56
+ export declare function handleApiError(response: Response, action: string, resourceType: string, resourceName: string): Promise<never>;
24
57
  /**
25
58
  * Creates a CloudflareApi instance with automatic account ID discovery if not provided
26
59
  *
@@ -28,6 +61,10 @@ export interface CloudflareApiOptions {
28
61
  * @returns Promise resolving to a CloudflareApi instance
29
62
  */
30
63
  export declare function createCloudflareApi(options?: CloudflareApiOptions): Promise<CloudflareApi>;
64
+ export type CloudflareAccountId = string & {
65
+ readonly __brand: "CloudflareAccountId";
66
+ };
67
+ export declare function CloudflareAccountId(options?: CloudflareApiOptions): Promise<CloudflareAccountId>;
31
68
  /**
32
69
  * Cloudflare API client using raw fetch
33
70
  */
@@ -1,4 +1,50 @@
1
1
  import { getCloudflareHeaders } from "./auth";
2
+ /**
3
+ * Custom error class for Cloudflare API errors
4
+ * Includes HTTP status information from the Response
5
+ */
6
+ export class CloudflareApiError extends Error {
7
+ /**
8
+ * HTTP status code
9
+ */
10
+ status;
11
+ /**
12
+ * HTTP status text
13
+ */
14
+ statusText;
15
+ /**
16
+ * Raw error data from the API
17
+ */
18
+ errorData;
19
+ /**
20
+ * Create a new CloudflareApiError
21
+ */
22
+ constructor(message, response, errorData) {
23
+ super(message);
24
+ this.name = "CloudflareApiError";
25
+ this.status = response.status;
26
+ this.statusText = response.statusText;
27
+ this.errorData = errorData;
28
+ // Ensure instanceof works correctly
29
+ Object.setPrototypeOf(this, CloudflareApiError.prototype);
30
+ }
31
+ }
32
+ /**
33
+ * Helper function to handle API errors
34
+ *
35
+ * @param response The fetch Response object
36
+ * @param action The action being performed (e.g., "creating", "deleting")
37
+ * @param resourceType The type of resource being acted upon (e.g., "R2 bucket", "Worker")
38
+ * @param resourceName The name/identifier of the specific resource
39
+ * @returns Never returns - always throws an error
40
+ */
41
+ export async function handleApiError(response, action, resourceType, resourceName) {
42
+ const json = await response.json();
43
+ console.log(json);
44
+ const errorData = json.errors || [{ message: response.statusText }];
45
+ const errorMessage = `Error ${action} ${resourceType} '${resourceName}': ${errorData.errors?.[0]?.message || response.statusText}`;
46
+ throw new CloudflareApiError(errorMessage, response, errorData);
47
+ }
2
48
  /**
3
49
  * Creates a CloudflareApi instance with automatic account ID discovery if not provided
4
50
  *
@@ -9,9 +55,7 @@ export async function createCloudflareApi(options = {}) {
9
55
  try {
10
56
  return new CloudflareApi({
11
57
  ...options,
12
- accountId: options.accountId ||
13
- process.env.CLOUDFLARE_ACCOUNT_ID ||
14
- (await fetchAccountId()),
58
+ accountId: await CloudflareAccountId(options),
15
59
  });
16
60
  }
17
61
  catch (error) {
@@ -19,6 +63,11 @@ export async function createCloudflareApi(options = {}) {
19
63
  throw new Error("Failed to automatically discover Cloudflare account ID. Please provide an account ID explicitly or ensure your API token/key has sufficient permissions.");
20
64
  }
21
65
  }
66
+ export async function CloudflareAccountId(options = {}) {
67
+ return (options.accountId ||
68
+ process.env.CLOUDFLARE_ACCOUNT_ID ||
69
+ (await fetchAccountId()));
70
+ }
22
71
  /**
23
72
  * Cloudflare API client using raw fetch
24
73
  */
@@ -44,7 +93,9 @@ export class CloudflareApi {
44
93
  */
45
94
  constructor(options = {}) {
46
95
  this.baseUrl = "https://api.cloudflare.com/client/v4";
47
- const apiKey = options.apiKey || process.env.CLOUDFLARE_API_KEY || undefined;
96
+ const apiKey = options.apiKey?.unencrypted ||
97
+ process.env.CLOUDFLARE_API_KEY ||
98
+ undefined;
48
99
  if (!apiKey) {
49
100
  throw new Error("No API key provided. Use createCloudflareApi() instead for automatic account discovery.");
50
101
  }
@@ -0,0 +1,74 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ /**
4
+ * Properties for creating or updating Assets
5
+ */
6
+ export interface AssetsProps {
7
+ /**
8
+ * Path to a directory containing static assets to be uploaded
9
+ * These files will be served by Cloudflare's Workers runtime
10
+ */
11
+ path: string;
12
+ }
13
+ /**
14
+ * Output returned after Assets creation/update
15
+ */
16
+ export interface Assets extends Resource<"cloudflare::Asset">, AssetsProps {
17
+ /**
18
+ * The type of binding
19
+ */
20
+ type: "assets";
21
+ /**
22
+ * The ID of the assets bundle
23
+ */
24
+ id: string;
25
+ /**
26
+ * Asset files that were found
27
+ */
28
+ files: AssetFile[];
29
+ /**
30
+ * Time at which the assets were created
31
+ */
32
+ createdAt: number;
33
+ /**
34
+ * Time at which the assets were last updated
35
+ */
36
+ updatedAt: number;
37
+ }
38
+ /**
39
+ * Represents a single asset file
40
+ */
41
+ export interface AssetFile {
42
+ /**
43
+ * Path relative to the assets directory
44
+ */
45
+ path: string;
46
+ /**
47
+ * Full filesystem path to the file
48
+ */
49
+ filePath: string;
50
+ /**
51
+ * Content type of the file
52
+ */
53
+ contentType: string;
54
+ }
55
+ /**
56
+ * Cloudflare Assets represent a collection of static files that can be uploaded and served
57
+ * by Cloudflare Workers.
58
+ *
59
+ * @example
60
+ * // Create a basic assets bundle from a local directory
61
+ * const staticAssets = await Assets("static", {
62
+ * path: "./src/assets"
63
+ * });
64
+ *
65
+ * // Use these assets with a worker
66
+ * const worker = await Worker("frontend", {
67
+ * name: "frontend-worker",
68
+ * entrypoint: "./src/worker.ts",
69
+ * bindings: {
70
+ * ASSETS: staticAssets
71
+ * }
72
+ * });
73
+ */
74
+ export declare const Assets: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Assets>, id: string, props: AssetsProps) => Promise<Assets>);
@@ -0,0 +1,73 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import { Resource } from "../resource";
4
+ import { getContentType } from "../util/content-type";
5
+ /**
6
+ * Cloudflare Assets represent a collection of static files that can be uploaded and served
7
+ * by Cloudflare Workers.
8
+ *
9
+ * @example
10
+ * // Create a basic assets bundle from a local directory
11
+ * const staticAssets = await Assets("static", {
12
+ * path: "./src/assets"
13
+ * });
14
+ *
15
+ * // Use these assets with a worker
16
+ * const worker = await Worker("frontend", {
17
+ * name: "frontend-worker",
18
+ * entrypoint: "./src/worker.ts",
19
+ * bindings: {
20
+ * ASSETS: staticAssets
21
+ * }
22
+ * });
23
+ */
24
+ export const Assets = Resource("cloudflare::Asset", async function (id, props) {
25
+ if (this.phase === "delete") {
26
+ return this.destroy();
27
+ }
28
+ try {
29
+ // Check if the assets directory exists
30
+ const stats = await fs.stat(props.path);
31
+ if (!stats.isDirectory()) {
32
+ throw new Error(`Assets path ${props.path} is not a directory`);
33
+ }
34
+ }
35
+ catch (error) {
36
+ throw new Error(`Assets directory ${props.path} does not exist or is not accessible`);
37
+ }
38
+ // Recursively get all files in the assets directory
39
+ const filesList = await getFilesRecursively(props.path);
40
+ // Create asset file objects
41
+ const files = filesList.map((filePath) => {
42
+ const relativePath = path.relative(props.path, filePath);
43
+ const normalizedPath = relativePath.split(path.sep).join("/"); // Ensure forward slashes for URLs
44
+ return {
45
+ path: normalizedPath,
46
+ filePath,
47
+ contentType: getContentType(filePath),
48
+ };
49
+ });
50
+ // Get current timestamp
51
+ const now = Date.now();
52
+ // Construct the output
53
+ return this({
54
+ id,
55
+ type: "assets",
56
+ path: props.path,
57
+ files,
58
+ createdAt: this.output?.createdAt || now,
59
+ updatedAt: now,
60
+ });
61
+ });
62
+ // Helper functions for file operations
63
+ async function getFilesRecursively(dir) {
64
+ const files = await fs.readdir(dir, { withFileTypes: true });
65
+ const allFiles = await Promise.all(files.map(async (file) => {
66
+ const path = `${dir}/${file.name}`;
67
+ if (file.isDirectory()) {
68
+ return getFilesRecursively(path);
69
+ }
70
+ return path;
71
+ }));
72
+ return allFiles.flat();
73
+ }
@@ -4,6 +4,7 @@
4
4
  * https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/
5
5
  */
6
6
  import type { Secret } from "../secret";
7
+ import type { Assets } from "./assets";
7
8
  import type { R2Bucket } from "./bucket";
8
9
  import type { DurableObjectNamespace } from "./durable-object-namespace";
9
10
  import type { KVNamespace } from "./kv-namespace";
@@ -14,8 +15,12 @@ export type Bindings = {
14
15
  /**
15
16
  * L2 Binding Resources.
16
17
  */
17
- export type Binding = DurableObjectNamespace | KVNamespace | Worker | R2Bucket | Secret | string;
18
+ export type Binding = DurableObjectNamespace | KVNamespace | Worker | R2Bucket | Secret | string | Assets;
18
19
  export declare function isDurableObjectNamespace(binding: Binding): binding is DurableObjectNamespace;
20
+ /**
21
+ * Check if a binding is an Assets resource
22
+ */
23
+ export declare function isAssets(binding: Binding): binding is Assets;
19
24
  /**
20
25
  * Union type for all Worker binding types (API spec)
21
26
  */
@@ -1,3 +1,9 @@
1
1
  export function isDurableObjectNamespace(binding) {
2
2
  return (typeof binding === "object" && binding.type === "durable_object_namespace");
3
3
  }
4
+ /**
5
+ * Check if a binding is an Assets resource
6
+ */
7
+ export function isAssets(binding) {
8
+ return typeof binding === "object" && binding.type === "assets";
9
+ }
@@ -1,9 +1,11 @@
1
+ import { AwsClient } from "aws4fetch";
1
2
  import type { Context } from "../context";
2
3
  import { Resource } from "../resource";
4
+ import { CloudflareApi, type CloudflareApiOptions } from "./api";
3
5
  /**
4
6
  * Properties for creating or updating an R2 Bucket
5
7
  */
6
- export interface BucketProps {
8
+ export interface BucketProps extends CloudflareApiOptions {
7
9
  /**
8
10
  * Name of the bucket
9
11
  * Names can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-)
@@ -25,6 +27,12 @@ export interface BucketProps {
25
27
  * Only for development purposes - use custom domains for production
26
28
  */
27
29
  allowPublicAccess?: boolean;
30
+ /**
31
+ * Whether to delete the bucket and its contents during resource deletion
32
+ * If set to false, the bucket will remain but the resource will be removed from state
33
+ * Default: true
34
+ */
35
+ delete?: boolean;
28
36
  }
29
37
  /**
30
38
  * Output returned after R2 Bucket creation/update
@@ -39,9 +47,9 @@ export interface R2Bucket extends Resource<"cloudflare::R2Bucket">, BucketProps
39
47
  */
40
48
  location: string;
41
49
  /**
42
- * Time at which the bucket was created (Unix timestamp in seconds)
50
+ * Time at which the bucket was created
43
51
  */
44
- creationDate: number;
52
+ creationDate: Date;
45
53
  }
46
54
  /**
47
55
  * Creates and manages Cloudflare R2 Buckets for object storage.
@@ -80,3 +88,110 @@ export interface R2Bucket extends Resource<"cloudflare::R2Bucket">, BucketProps
80
88
  * @see https://developers.cloudflare.com/r2/buckets/
81
89
  */
82
90
  export declare const R2Bucket: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<R2Bucket>, id: string, props: BucketProps) => Promise<R2Bucket>);
91
+ /**
92
+ * Configuration for R2 client to connect to Cloudflare R2
93
+ */
94
+ export interface R2ClientConfig {
95
+ accountId?: string;
96
+ accessKeyId?: string;
97
+ secretAccessKey?: string;
98
+ jurisdiction?: string;
99
+ }
100
+ type R2Client = AwsClient & {
101
+ accountId: string;
102
+ };
103
+ /**
104
+ * Creates an aws4fetch client configured for Cloudflare R2
105
+ *
106
+ * @see https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/
107
+ */
108
+ export declare function createR2Client(config?: R2ClientConfig): Promise<R2Client>;
109
+ interface CloudflareBucketResponse {
110
+ result: {
111
+ name: string;
112
+ location?: string;
113
+ creation_date: string;
114
+ };
115
+ success: boolean;
116
+ errors: Array<{
117
+ code: number;
118
+ message: string;
119
+ }>;
120
+ messages: string[];
121
+ }
122
+ /**
123
+ * Adds jurisdiction header to the headers object if specified in props
124
+ *
125
+ * @param headers Headers object to modify
126
+ * @param props Props or jurisdiction string
127
+ * @returns Modified headers object
128
+ */
129
+ export declare function withJurisdiction(headers: Record<string, string> | undefined, props: BucketProps | {
130
+ jurisdiction?: string;
131
+ } | string | undefined): Record<string, string>;
132
+ /**
133
+ * Create a new bucket
134
+ */
135
+ export declare function createBucket(api: CloudflareApi, bucketName: string, props: BucketProps): Promise<CloudflareBucketResponse>;
136
+ /**
137
+ * Delete a bucket
138
+ */
139
+ export declare function deleteBucket(api: CloudflareApi, bucketName: string, props: BucketProps): Promise<void>;
140
+ /**
141
+ * List objects in an R2 bucket
142
+ *
143
+ * @param r2 R2Client instance
144
+ * @param bucketName Name of the bucket
145
+ * @param continuationToken Optional token for pagination
146
+ * @param jurisdiction Optional jurisdiction for the bucket
147
+ * @returns Object containing the list of objects and the next continuation token
148
+ */
149
+ export declare function listObjects(r2: R2Client, bucketName: string, continuationToken?: string, jurisdiction?: string): Promise<{
150
+ objects: {
151
+ Key: string;
152
+ }[];
153
+ continuationToken?: string;
154
+ }>;
155
+ /**
156
+ * Helper function to empty a bucket by deleting all objects
157
+ */
158
+ export declare function emptyBucket(r2: R2Client, bucketName: string, jurisdiction?: string): Promise<void>;
159
+ /**
160
+ * Update public access setting for a bucket
161
+ *
162
+ * This operation is not available through the S3 API for R2,
163
+ * so we still use the Cloudflare API directly.
164
+ */
165
+ export declare function updatePublicAccess(api: CloudflareApi, bucketName: string, allowPublicAccess: boolean, jurisdiction?: string): Promise<void>;
166
+ /**
167
+ * Set CORS configuration for a bucket using aws4fetch
168
+ */
169
+ export declare function setCorsConfiguration(r2: R2Client, bucketName: string, allowedOrigins?: string[], allowedMethods?: string[], allowedHeaders?: string[], maxAgeSeconds?: number, jurisdiction?: string): Promise<void>;
170
+ /**
171
+ * Information about an R2 bucket returned by list operations
172
+ */
173
+ export interface R2BucketInfo {
174
+ /**
175
+ * Name of the bucket
176
+ */
177
+ Name: string;
178
+ /**
179
+ * Creation date of the bucket
180
+ */
181
+ CreationDate: Date;
182
+ }
183
+ /**
184
+ * List all R2 buckets in an account
185
+ *
186
+ * @param api CloudflareApi instance
187
+ * @param options Optional listing options
188
+ * @returns Array of bucket information
189
+ */
190
+ export declare function listBuckets(api: CloudflareApi, options?: {
191
+ nameContains?: string;
192
+ perPage?: number;
193
+ cursor?: string;
194
+ direction?: "asc" | "desc";
195
+ jurisdiction?: string;
196
+ }): Promise<R2BucketInfo[]>;
197
+ export {};