alchemy 0.2.5 → 0.3.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 (197) hide show
  1. package/README.md +3 -3
  2. package/lib/ai/approve.d.ts +99 -0
  3. package/lib/ai/approve.js +76 -0
  4. package/lib/ai/astro-file.d.ts +137 -0
  5. package/lib/ai/astro-file.js +156 -0
  6. package/lib/ai/client.d.ts +7 -8
  7. package/lib/ai/client.js +48 -19
  8. package/lib/ai/css-file.d.ts +131 -0
  9. package/lib/ai/css-file.js +141 -0
  10. package/lib/ai/data.d.ts +51 -6
  11. package/lib/ai/data.js +58 -12
  12. package/lib/ai/document.d.ts +108 -24
  13. package/lib/ai/document.js +166 -52
  14. package/lib/ai/html-file.d.ts +128 -0
  15. package/lib/ai/html-file.js +138 -0
  16. package/lib/ai/index.d.ts +9 -0
  17. package/lib/ai/index.js +9 -0
  18. package/lib/ai/json-file.d.ts +160 -0
  19. package/lib/ai/json-file.js +187 -0
  20. package/lib/ai/review.d.ts +122 -0
  21. package/lib/ai/review.js +101 -0
  22. package/lib/ai/typescript-file.d.ts +139 -0
  23. package/lib/ai/typescript-file.js +145 -0
  24. package/lib/ai/vue-file.d.ts +126 -0
  25. package/lib/ai/vue-file.js +136 -0
  26. package/lib/ai/yaml-file.d.ts +169 -0
  27. package/lib/ai/yaml-file.js +200 -0
  28. package/lib/alchemy.js +17 -2
  29. package/lib/apply.js +1 -1
  30. package/lib/cloudflare/dns.d.ts +88 -0
  31. package/lib/cloudflare/dns.js +192 -0
  32. package/lib/cloudflare/generate-asset-manifest.js +1 -1
  33. package/lib/cloudflare/index.d.ts +2 -1
  34. package/lib/cloudflare/index.js +2 -1
  35. package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
  36. package/lib/cloudflare/r2-rest-state-store.js +254 -0
  37. package/lib/cloudflare/response.d.ts +12 -0
  38. package/lib/cloudflare/response.js +0 -0
  39. package/lib/cloudflare/static-site-router.js +16 -7
  40. package/lib/cloudflare/static-site.js +3 -0
  41. package/lib/cloudflare/upload-asset-manifest.js +4 -2
  42. package/lib/cloudflare/zone.d.ts +18 -0
  43. package/lib/cloudflare/zone.js +17 -4
  44. package/lib/dns/godaddy.d.ts +7 -0
  45. package/lib/dns/godaddy.js +21 -0
  46. package/lib/dns/import-dns.d.ts +82 -0
  47. package/lib/dns/import-dns.js +114 -0
  48. package/lib/dns/index.d.ts +2 -0
  49. package/lib/dns/index.js +2 -0
  50. package/lib/dns/record.d.ts +88 -0
  51. package/lib/dns/record.js +13 -0
  52. package/lib/fs/copy-file.d.ts +54 -0
  53. package/lib/fs/copy-file.js +63 -0
  54. package/lib/fs/file-system-state-store.d.ts +17 -0
  55. package/lib/fs/file-system-state-store.js +87 -0
  56. package/lib/fs/file.d.ts +13 -0
  57. package/lib/fs/file.js +24 -5
  58. package/lib/fs/folder.d.ts +10 -0
  59. package/lib/fs/folder.js +2 -2
  60. package/lib/fs/index.d.ts +10 -4
  61. package/lib/fs/index.js +10 -4
  62. package/lib/fs/static-astro-file.d.ts +34 -0
  63. package/lib/fs/static-astro-file.js +39 -0
  64. package/lib/fs/static-css-file.d.ts +24 -0
  65. package/lib/fs/static-css-file.js +29 -0
  66. package/lib/fs/static-html-file.d.ts +31 -0
  67. package/lib/fs/static-html-file.js +36 -0
  68. package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +3 -3
  69. package/lib/fs/static-json-file.js +15 -0
  70. package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
  71. package/lib/fs/static-text-file.js +8 -0
  72. package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
  73. package/lib/fs/{typescript-file.js → static-typescript-file.js} +3 -2
  74. package/lib/fs/static-vue-file.d.ts +28 -0
  75. package/lib/fs/static-vue-file.js +33 -0
  76. package/lib/fs/static-yaml-file.d.ts +22 -0
  77. package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
  78. package/lib/internal/getting-started.d.ts +21 -0
  79. package/lib/internal/getting-started.js +87 -0
  80. package/lib/internal/index.d.ts +3 -0
  81. package/lib/internal/index.js +3 -0
  82. package/lib/internal/providers.d.ts +27 -0
  83. package/lib/internal/providers.js +172 -0
  84. package/lib/internal/tutorial.d.ts +104 -0
  85. package/lib/internal/tutorial.js +251 -0
  86. package/lib/resource.js +8 -1
  87. package/lib/scope.d.ts +1 -1
  88. package/lib/scope.js +4 -2
  89. package/lib/state.d.ts +1 -16
  90. package/lib/state.js +0 -87
  91. package/lib/test/bun.d.ts +11 -0
  92. package/lib/test/bun.js +36 -8
  93. package/lib/web/astro.d.ts +147 -0
  94. package/lib/web/astro.js +414 -0
  95. package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
  96. package/lib/web/shadcn.d.ts +97 -0
  97. package/lib/web/shadcn.js +99 -0
  98. package/lib/web/tailwind.d.ts +64 -0
  99. package/lib/web/tailwind.js +97 -0
  100. package/lib/{vite → web}/vite.d.ts +5 -0
  101. package/lib/{vite → web}/vite.js +44 -44
  102. package/lib/web/vitepress/config.d.ts +10 -0
  103. package/lib/web/vitepress/config.js +31 -0
  104. package/lib/web/vitepress/custom-theme.d.ts +173 -0
  105. package/lib/web/vitepress/custom-theme.js +336 -0
  106. package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
  107. package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
  108. package/lib/web/vitepress/home-page.d.ts +246 -0
  109. package/lib/web/vitepress/home-page.js +147 -0
  110. package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
  111. package/lib/{vitepress → web/vitepress}/index.js +2 -0
  112. package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
  113. package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
  114. package/package.json +6 -2
  115. package/src/ai/approve.ts +163 -0
  116. package/src/ai/astro-file.ts +269 -0
  117. package/src/ai/client.ts +60 -21
  118. package/src/ai/css-file.ts +247 -0
  119. package/src/ai/data.ts +81 -19
  120. package/src/ai/document.ts +240 -62
  121. package/src/ai/html-file.ts +244 -0
  122. package/src/ai/index.ts +9 -0
  123. package/src/ai/json-file.ts +325 -0
  124. package/src/ai/review.ts +213 -0
  125. package/src/ai/typescript-file.ts +259 -0
  126. package/src/ai/vue-file.ts +242 -0
  127. package/src/ai/yaml-file.ts +328 -0
  128. package/src/alchemy.ts +18 -7
  129. package/src/apply.ts +8 -9
  130. package/src/cloudflare/dns.ts +344 -0
  131. package/src/cloudflare/generate-asset-manifest.ts +4 -4
  132. package/src/cloudflare/index.ts +2 -1
  133. package/src/cloudflare/r2-rest-state-store.ts +353 -0
  134. package/src/cloudflare/response.ts +9 -0
  135. package/src/cloudflare/static-site-router.ts +18 -8
  136. package/src/cloudflare/static-site.ts +11 -7
  137. package/src/cloudflare/upload-asset-manifest.ts +6 -4
  138. package/src/cloudflare/worker.ts +44 -43
  139. package/src/cloudflare/zone.ts +39 -23
  140. package/src/dns/godaddy.ts +29 -0
  141. package/src/dns/import-dns.ts +213 -0
  142. package/src/dns/index.ts +2 -0
  143. package/src/dns/record.ts +121 -0
  144. package/src/fs/copy-file.ts +115 -0
  145. package/src/fs/file-system-state-store.ts +109 -0
  146. package/src/fs/file.ts +35 -9
  147. package/src/fs/folder.ts +14 -2
  148. package/src/fs/index.ts +10 -4
  149. package/src/fs/static-astro-file.ts +45 -0
  150. package/src/fs/static-css-file.ts +35 -0
  151. package/src/fs/static-html-file.ts +42 -0
  152. package/src/fs/static-json-file.ts +34 -0
  153. package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
  154. package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
  155. package/src/fs/static-vue-file.ts +39 -0
  156. package/src/fs/static-yaml-file.ts +33 -0
  157. package/src/internal/getting-started.ts +115 -0
  158. package/src/internal/index.ts +3 -0
  159. package/src/internal/providers.ts +241 -0
  160. package/src/internal/tutorial.ts +392 -0
  161. package/src/resource.ts +10 -1
  162. package/src/scope.ts +5 -6
  163. package/src/state.ts +1 -111
  164. package/src/test/bun.ts +60 -10
  165. package/src/web/astro.ts +644 -0
  166. package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
  167. package/src/web/shadcn.ts +219 -0
  168. package/src/web/tailwind.ts +167 -0
  169. package/src/{vite → web}/vite.ts +54 -56
  170. package/src/web/vitepress/config.ts +48 -0
  171. package/src/web/vitepress/custom-theme.ts +514 -0
  172. package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
  173. package/src/web/vitepress/home-page.ts +375 -0
  174. package/src/{vitepress → web/vitepress}/index.ts +2 -0
  175. package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
  176. package/lib/cloudflare/state.d.ts +0 -82
  177. package/lib/cloudflare/state.js +0 -108
  178. package/lib/fs/json-file.js +0 -7
  179. package/lib/fs/text-file.js +0 -7
  180. package/lib/fs/yaml-file.d.ts +0 -19
  181. package/lib/internal/docs.d.ts +0 -5
  182. package/lib/internal/docs.js +0 -287
  183. package/lib/shadcn/index.d.ts +0 -1
  184. package/lib/shadcn/index.js +0 -1
  185. package/lib/vite/index.d.ts +0 -1
  186. package/lib/vite/index.js +0 -1
  187. package/lib/vitepress/home-page.d.ts +0 -133
  188. package/lib/vitepress/home-page.js +0 -11
  189. package/src/cloudflare/state.ts +0 -138
  190. package/src/fs/json-file.ts +0 -23
  191. package/src/fs/yaml-file.ts +0 -26
  192. package/src/internal/docs.ts +0 -338
  193. package/src/shadcn/index.ts +0 -1
  194. package/src/vite/index.ts +0 -1
  195. package/src/vitepress/home-page.ts +0 -166
  196. /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
  197. /package/src/{vitepress → web/vitepress}/index.md +0 -0
@@ -9,7 +9,7 @@ export async function generateAssetManifest(dir, fileOptions = [
9
9
  cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
10
10
  },
11
11
  {
12
- files: ["**/*.js", "**/*.css"],
12
+ files: ["**/*.js", "**/*.css", "**/*.png", "**/*.webp"],
13
13
  cacheControl: "max-age=31536000,public,immutable",
14
14
  },
15
15
  ]) {
@@ -1,8 +1,9 @@
1
1
  export * from "./bindings";
2
2
  export * from "./bucket";
3
+ export * from "./dns";
3
4
  export * from "./durable-object-namespace";
4
5
  export * from "./kv-namespace";
5
- export * from "./state";
6
+ export * from "./r2-rest-state-store";
6
7
  export * from "./static-site";
7
8
  export * from "./worker";
8
9
  export * from "./wrangler.json";
@@ -1,8 +1,9 @@
1
1
  export * from "./bindings";
2
2
  export * from "./bucket";
3
+ export * from "./dns";
3
4
  export * from "./durable-object-namespace";
4
5
  export * from "./kv-namespace";
5
- export * from "./state";
6
+ export * from "./r2-rest-state-store";
6
7
  export * from "./static-site";
7
8
  export * from "./worker";
8
9
  export * from "./wrangler.json";
@@ -0,0 +1,125 @@
1
+ import type { Scope } from "../scope";
2
+ import type { State, StateStore } from "../state";
3
+ /**
4
+ * Options for CloudflareR2StateStore
5
+ */
6
+ export interface CloudflareR2StateStoreOptions {
7
+ /**
8
+ * The prefix to use for object keys in the R2 bucket
9
+ * This allows multiple state stores to use the same R2 bucket
10
+ */
11
+ prefix?: string;
12
+ /**
13
+ * The R2 bucket name to use
14
+ * Required - the bucket must already exist
15
+ */
16
+ bucketName: string;
17
+ /**
18
+ * API key to use (overrides CLOUDFLARE_API_KEY env var)
19
+ */
20
+ apiKey?: string;
21
+ /**
22
+ * Account ID to use (overrides CLOUDFLARE_ACCOUNT_ID env var)
23
+ */
24
+ accountId?: string;
25
+ /**
26
+ * Email to use with API Key authentication (overrides CLOUDFLARE_EMAIL env var)
27
+ */
28
+ email?: string;
29
+ }
30
+ /**
31
+ * State store implementation using Cloudflare R2 API
32
+ * Uses R2 for immediate consistency compared to KV's eventual consistency
33
+ */
34
+ export declare class R2RestStateStore implements StateStore {
35
+ readonly scope: Scope;
36
+ private api;
37
+ private prefix;
38
+ private bucketName;
39
+ private apiKey;
40
+ private accountId;
41
+ private email;
42
+ private initialized;
43
+ /**
44
+ * Create a new CloudflareR2StateStore
45
+ *
46
+ * @param scope The scope this store belongs to
47
+ * @param options Options for the state store
48
+ */
49
+ constructor(scope: Scope, options: CloudflareR2StateStoreOptions);
50
+ /**
51
+ * Initialize the R2 client
52
+ */
53
+ init(): Promise<void>;
54
+ /**
55
+ * R2 buckets cannot be deleted programmatically via this method
56
+ */
57
+ deinit(): Promise<void>;
58
+ /**
59
+ * List all resources in the state store
60
+ */
61
+ list(): Promise<string[]>;
62
+ /**
63
+ * Count the number of items in the state store
64
+ */
65
+ count(): Promise<number>;
66
+ /**
67
+ * Get a state by key
68
+ *
69
+ * @param key The key to look up
70
+ * @returns The state or undefined if not found
71
+ */
72
+ get(key: string): Promise<State | undefined>;
73
+ /**
74
+ * Get multiple states by their keys
75
+ *
76
+ * @param ids Array of keys to fetch
77
+ * @returns Record mapping keys to their states
78
+ */
79
+ getBatch(ids: string[]): Promise<Record<string, State>>;
80
+ /**
81
+ * Get all states in the store
82
+ *
83
+ * @returns Record mapping all keys to their states
84
+ */
85
+ all(): Promise<Record<string, State>>;
86
+ /**
87
+ * Set a state for a key
88
+ *
89
+ * @param key The key to set
90
+ * @param value The state to store
91
+ */
92
+ set(key: string, value: State): Promise<void>;
93
+ /**
94
+ * Delete a state by key
95
+ *
96
+ * @param key The key to delete
97
+ */
98
+ delete(key: string): Promise<void>;
99
+ /**
100
+ * Convert key for storage by replacing slashes with colons
101
+ * since R2 treats slashes as directory separators
102
+ *
103
+ * @param key The original key
104
+ * @returns Key with slashes replaced by colons
105
+ */
106
+ private convertKeyForStorage;
107
+ /**
108
+ * Convert key from storage by replacing colons with slashes
109
+ *
110
+ * @param key The storage key
111
+ * @returns Key with colons replaced by slashes
112
+ */
113
+ private convertKeyFromStorage;
114
+ /**
115
+ * Get the full object key for storage
116
+ *
117
+ * @param key The original key
118
+ * @returns The key with prefix for use in the R2 bucket
119
+ */
120
+ private getObjectKey;
121
+ /**
122
+ * Ensure the store is initialized before operations
123
+ */
124
+ private ensureInitialized;
125
+ }
@@ -0,0 +1,254 @@
1
+ import { withExponentialBackoff } from "../util/retry";
2
+ import { deserialize, serialize } from "../util/serde";
3
+ import { createCloudflareApi } from "./api";
4
+ /**
5
+ * State store implementation using Cloudflare R2 API
6
+ * Uses R2 for immediate consistency compared to KV's eventual consistency
7
+ */
8
+ export class R2RestStateStore {
9
+ scope;
10
+ api;
11
+ prefix;
12
+ bucketName;
13
+ apiKey;
14
+ accountId;
15
+ email;
16
+ initialized = false;
17
+ /**
18
+ * Create a new CloudflareR2StateStore
19
+ *
20
+ * @param scope The scope this store belongs to
21
+ * @param options Options for the state store
22
+ */
23
+ constructor(scope, options) {
24
+ this.scope = scope;
25
+ // Use the scope's chain to build the prefix, similar to how FileSystemStateStore builds its directory
26
+ const scopePath = scope.chain.join("/");
27
+ this.prefix = options.prefix
28
+ ? `${options.prefix}${scopePath}/`
29
+ : `alchemy/${scopePath}/`;
30
+ if (!options.bucketName) {
31
+ throw new Error("bucketName is required for CloudflareR2StateStore");
32
+ }
33
+ this.bucketName = options.bucketName;
34
+ this.apiKey = options.apiKey;
35
+ this.accountId = options.accountId;
36
+ this.email = options.email;
37
+ // We'll initialize the API in init() to allow for async creation
38
+ this.api = null;
39
+ }
40
+ /**
41
+ * Initialize the R2 client
42
+ */
43
+ async init() {
44
+ if (this.initialized)
45
+ return;
46
+ // Create Cloudflare API client with automatic account discovery
47
+ this.api = await createCloudflareApi({
48
+ apiKey: this.apiKey,
49
+ accountId: this.accountId,
50
+ email: this.email,
51
+ });
52
+ this.initialized = true;
53
+ }
54
+ /**
55
+ * R2 buckets cannot be deleted programmatically via this method
56
+ */
57
+ async deinit() {
58
+ // We don't delete the bucket here, only via explicit resource deletion
59
+ }
60
+ /**
61
+ * List all resources in the state store
62
+ */
63
+ async list() {
64
+ await this.ensureInitialized();
65
+ // Using pagination to get all objects
66
+ let keys = [];
67
+ let cursor = null;
68
+ do {
69
+ const params = new URLSearchParams({
70
+ prefix: this.prefix,
71
+ limit: "1000",
72
+ });
73
+ if (cursor) {
74
+ params.append("cursor", cursor);
75
+ }
76
+ const listPath = `/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects?${params.toString()}`;
77
+ const response = await this.api.get(listPath);
78
+ if (!response.ok) {
79
+ const errorData = await response.json().catch(() => ({
80
+ errors: [{ message: response.statusText }],
81
+ }));
82
+ throw new Error(`Error listing R2 objects: ${errorData.errors?.[0]?.message || response.statusText}`);
83
+ }
84
+ const data = (await response.json());
85
+ // The result structure may be under "result" key in Cloudflare's API
86
+ const result = data.result || data;
87
+ const objects = result.objects || [];
88
+ // Add keys to our list, removing the prefix and converting from storage format
89
+ keys = keys.concat(objects.map((obj) => {
90
+ const keyName = obj.key || obj.name;
91
+ return this.convertKeyFromStorage(keyName.slice(this.prefix.length));
92
+ }));
93
+ // Update cursor for next page if available
94
+ cursor =
95
+ result.truncated || result.cursor_pagination
96
+ ? result.cursor || null
97
+ : null;
98
+ } while (cursor);
99
+ return keys;
100
+ }
101
+ /**
102
+ * Count the number of items in the state store
103
+ */
104
+ async count() {
105
+ const keys = await this.list();
106
+ return keys.length;
107
+ }
108
+ /**
109
+ * Get a state by key
110
+ *
111
+ * @param key The key to look up
112
+ * @returns The state or undefined if not found
113
+ */
114
+ async get(key) {
115
+ await this.ensureInitialized();
116
+ try {
117
+ const response = await this.api.get(`/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`);
118
+ if (!response.ok) {
119
+ if (response.status === 404) {
120
+ return undefined;
121
+ }
122
+ const errorData = await response.json().catch(() => ({
123
+ errors: [{ message: response.statusText }],
124
+ }));
125
+ throw new Error(`Error getting R2 object: ${errorData.errors?.[0]?.message || response.statusText}`);
126
+ }
127
+ // Parse and deserialize the state data
128
+ const rawData = await response.json();
129
+ const state = (await deserialize(this.scope, rawData));
130
+ // Create a new state object with proper output
131
+ return {
132
+ ...state,
133
+ output: {
134
+ ...(state.output || {}),
135
+ Scope: this.scope,
136
+ },
137
+ };
138
+ }
139
+ catch (error) {
140
+ if (error.message?.includes("404")) {
141
+ return undefined;
142
+ }
143
+ throw error;
144
+ }
145
+ }
146
+ /**
147
+ * Get multiple states by their keys
148
+ *
149
+ * @param ids Array of keys to fetch
150
+ * @returns Record mapping keys to their states
151
+ */
152
+ async getBatch(ids) {
153
+ const result = {};
154
+ // R2 REST API doesn't have a batch get operation, so we need to make multiple requests
155
+ const promises = ids.map(async (id) => {
156
+ const state = await this.get(id);
157
+ if (state) {
158
+ result[id] = state;
159
+ }
160
+ });
161
+ await Promise.all(promises);
162
+ return result;
163
+ }
164
+ /**
165
+ * Get all states in the store
166
+ *
167
+ * @returns Record mapping all keys to their states
168
+ */
169
+ async all() {
170
+ const keys = await this.list();
171
+ return this.getBatch(keys);
172
+ }
173
+ /**
174
+ * Set a state for a key
175
+ *
176
+ * @param key The key to set
177
+ * @param value The state to store
178
+ */
179
+ async set(key, value) {
180
+ await this.ensureInitialized();
181
+ const objectKey = this.getObjectKey(key);
182
+ // Serialize the state to handle cyclic structures
183
+ const serializedData = await serialize(this.scope, value);
184
+ // Using withExponentialBackoff for reliability
185
+ await withExponentialBackoff(async () => {
186
+ const response = await this.api.put(`/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${objectKey}`, serializedData, {
187
+ headers: {
188
+ "Content-Type": "application/json",
189
+ },
190
+ });
191
+ if (!response.ok) {
192
+ const errorData = await response.json().catch(() => ({
193
+ errors: [{ message: response.statusText }],
194
+ }));
195
+ throw new Error(`Error writing to R2: ${errorData.errors?.[0]?.message || response.statusText}`);
196
+ }
197
+ return response;
198
+ },
199
+ // Retry on transient errors
200
+ (error) => error.message?.includes("503") || error.message?.includes("timeout"), 5, // 5 retry attempts
201
+ 1000);
202
+ }
203
+ /**
204
+ * Delete a state by key
205
+ *
206
+ * @param key The key to delete
207
+ */
208
+ async delete(key) {
209
+ await this.ensureInitialized();
210
+ const response = await this.api.delete(`/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`);
211
+ if (!response.ok && response.status !== 404) {
212
+ const errorData = await response.json().catch(() => ({
213
+ errors: [{ message: response.statusText }],
214
+ }));
215
+ throw new Error(`Error deleting from R2: ${errorData.errors?.[0]?.message || response.statusText}`);
216
+ }
217
+ }
218
+ /**
219
+ * Convert key for storage by replacing slashes with colons
220
+ * since R2 treats slashes as directory separators
221
+ *
222
+ * @param key The original key
223
+ * @returns Key with slashes replaced by colons
224
+ */
225
+ convertKeyForStorage(key) {
226
+ return key.replaceAll("/", ":");
227
+ }
228
+ /**
229
+ * Convert key from storage by replacing colons with slashes
230
+ *
231
+ * @param key The storage key
232
+ * @returns Key with colons replaced by slashes
233
+ */
234
+ convertKeyFromStorage(key) {
235
+ return key.replaceAll(":", "/");
236
+ }
237
+ /**
238
+ * Get the full object key for storage
239
+ *
240
+ * @param key The original key
241
+ * @returns The key with prefix for use in the R2 bucket
242
+ */
243
+ getObjectKey(key) {
244
+ return `${this.prefix}${this.convertKeyForStorage(key)}`;
245
+ }
246
+ /**
247
+ * Ensure the store is initialized before operations
248
+ */
249
+ async ensureInitialized() {
250
+ if (!this.initialized) {
251
+ await this.init();
252
+ }
253
+ }
254
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Cloudflare API response format
3
+ */
4
+ export interface CloudflareResponse<T = unknown> {
5
+ result: T;
6
+ success: boolean;
7
+ errors: Array<{
8
+ code: number;
9
+ message: string;
10
+ }>;
11
+ messages: string[];
12
+ }
File without changes
@@ -35,34 +35,44 @@ app.notFound(async (ctx) => {
35
35
  }
36
36
  // Fetch from KV
37
37
  {
38
- const object = await env.ASSETS.getWithMetadata(filePath);
38
+ const object = await env.ASSETS.getWithMetadata(filePath, {
39
+ type: "arrayBuffer",
40
+ });
39
41
  if (object.value) {
40
42
  return await respond(200, filePath, object);
41
43
  }
42
44
  }
43
45
  {
44
46
  const guess = filePath + (filePath.endsWith("/") ? "" : "/") + "index.html";
45
- const object = await env.ASSETS.getWithMetadata(guess);
47
+ const object = await env.ASSETS.getWithMetadata(guess, {
48
+ type: "arrayBuffer",
49
+ });
46
50
  if (object.value) {
47
51
  return await respond(200, guess, object);
48
52
  }
49
53
  }
50
54
  {
51
55
  const guess = filePath + ".html";
52
- const object = await env.ASSETS.getWithMetadata(guess);
56
+ const object = await env.ASSETS.getWithMetadata(guess, {
57
+ type: "arrayBuffer",
58
+ });
53
59
  if (object.value) {
54
60
  return await respond(200, guess, object);
55
61
  }
56
62
  }
57
63
  // Handle error page
58
64
  if (env.ERROR_PAGE) {
59
- const object = await env.ASSETS.getWithMetadata(env.ERROR_PAGE);
65
+ const object = await env.ASSETS.getWithMetadata(env.ERROR_PAGE, {
66
+ type: "arrayBuffer",
67
+ });
60
68
  if (object.value) {
61
69
  return await respond(404, env.ERROR_PAGE, object);
62
70
  }
63
71
  }
64
72
  else {
65
- const object = await env.ASSETS.getWithMetadata(env.INDEX_PAGE);
73
+ const object = await env.ASSETS.getWithMetadata(env.INDEX_PAGE, {
74
+ type: "arrayBuffer",
75
+ });
66
76
  if (object.value) {
67
77
  return await respond(200, env.INDEX_PAGE, object);
68
78
  }
@@ -92,8 +102,7 @@ app.notFound(async (ctx) => {
92
102
  headers.set("content-type", object.metadata.contentType);
93
103
  headers.set("cache-control", object.metadata.cacheControl);
94
104
  }
95
- // TODO: do we need base64 encoded here?
96
- // const response = new Response(base64ToArrayBuffer(object.value), {
105
+ // Create response with the raw data directly
97
106
  const response = new Response(object.value, {
98
107
  status,
99
108
  headers,
@@ -122,6 +122,9 @@ export const StaticSite = Resource("cloudflare::StaticSite", {
122
122
  }),
123
123
  generateAssetManifest(props.dir),
124
124
  ]);
125
+ console.log({
126
+ assetManifest,
127
+ });
125
128
  // Step 3: Upload assets to KV
126
129
  await uploadAssetManifest(api, kv.namespaceId, assetManifest);
127
130
  // Prepare the bindings for the worker
@@ -34,8 +34,10 @@ export async function uploadAssetManifest(api, namespaceId, manifest) {
34
34
  // Create FormData for multipart upload
35
35
  const formData = new FormData();
36
36
  formData.append("metadata", JSON.stringify(metadata));
37
- // Add file content as 'value'
38
- const blob = new Blob([content]);
37
+ // Add file content as 'value' - Fix for binary file corruption
38
+ const blob = new Blob([new Uint8Array(content)], {
39
+ type: item.contentType,
40
+ });
39
41
  formData.append("value", blob);
40
42
  try {
41
43
  const response = await api.put(`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/values/${item.key}`, formData);
@@ -249,3 +249,21 @@ export interface Zone extends Resource<"cloudflare::Zone"> {
249
249
  * @see https://developers.cloudflare.com/dns/zone-setups/
250
250
  */
251
251
  export declare const Zone: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Zone>, id: string, props: ZoneProps) => Promise<Zone>);
252
+ /**
253
+ * Cloudflare Zone response format
254
+ */
255
+ export interface CloudflareZone {
256
+ id: string;
257
+ name: string;
258
+ type: "full" | "partial" | "secondary";
259
+ status: string;
260
+ paused: boolean;
261
+ account: {
262
+ id: string;
263
+ };
264
+ name_servers: string[];
265
+ original_name_servers: string[] | null;
266
+ created_on: string;
267
+ modified_on: string;
268
+ activated_on: string | null;
269
+ }
@@ -82,8 +82,8 @@ export const Zone = Resource("cloudflare::Zone", async function (id, props) {
82
82
  if (!response.ok) {
83
83
  throw new Error(`Error getting zone '${props.name}': ${response.statusText}`);
84
84
  }
85
- const result = (await response.json());
86
- const zoneData = result.result;
85
+ const zoneData = (await response.json())
86
+ .result;
87
87
  // Update zone settings if provided
88
88
  if (props.settings) {
89
89
  await updateZoneSettings(api, this.output.id, props.settings);
@@ -118,15 +118,28 @@ export const Zone = Resource("cloudflare::Zone", async function (id, props) {
118
118
  },
119
119
  });
120
120
  const body = await response.text();
121
+ let zoneData;
121
122
  if (!response.ok) {
122
123
  if (response.status === 400 && body.includes("already exists")) {
123
- // TODO: should we error?
124
+ // Zone already exists, fetch it instead
125
+ console.warn(`Zone '${props.name}' already exists during Zone create, adopting it...`);
126
+ const getResponse = await api.get(`/zones?name=${props.name}`);
127
+ if (!getResponse.ok) {
128
+ throw new Error(`Error fetching existing zone '${props.name}': ${getResponse.statusText}`);
129
+ }
130
+ const zones = (await getResponse.json()).result;
131
+ if (zones.length === 0) {
132
+ throw new Error(`Zone '${props.name}' does not exist, but the name is reserved for another user.`);
133
+ }
134
+ zoneData = zones[0];
124
135
  }
125
136
  else {
126
137
  throw new Error(`Error creating zone '${props.name}': ${response.statusText}\n${body}`);
127
138
  }
128
139
  }
129
- const zoneData = JSON.parse(body).result;
140
+ else {
141
+ zoneData = JSON.parse(body).result;
142
+ }
130
143
  // Update zone settings if provided
131
144
  if (props.settings) {
132
145
  await updateZoneSettings(api, zoneData.id, props.settings);
@@ -0,0 +1,7 @@
1
+ export type UpdateNameserversOptions = {
2
+ domain: string;
3
+ apiKey: string;
4
+ apiSecret: string;
5
+ nameservers: string[];
6
+ };
7
+ export declare function updateNameservers(options: UpdateNameserversOptions): Promise<void>;
@@ -0,0 +1,21 @@
1
+ export async function updateNameservers(options) {
2
+ const url = `https://api.godaddy.com/v1/domains/${options.domain}/nameservers`;
3
+ const response = await fetch(url, {
4
+ method: "PUT",
5
+ headers: {
6
+ Authorization: `sso-key ${options.apiKey}:${options.apiSecret}`,
7
+ "Content-Type": "application/json",
8
+ Accept: "application/json",
9
+ },
10
+ body: JSON.stringify({
11
+ nameservers: options.nameservers,
12
+ }),
13
+ });
14
+ if (response.ok) {
15
+ console.log(`✅ Nameservers updated for ${options.domain}`);
16
+ }
17
+ else {
18
+ const error = await response.json();
19
+ console.error(`❌ Failed to update nameservers:`, error);
20
+ }
21
+ }