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
@@ -0,0 +1,353 @@
1
+ import type { Scope } from "../scope";
2
+ import type { State, StateStore } from "../state";
3
+ import { withExponentialBackoff } from "../util/retry";
4
+ import { deserialize, serialize } from "../util/serde";
5
+ import { type CloudflareApi, createCloudflareApi } from "./api";
6
+
7
+ /**
8
+ * Options for CloudflareR2StateStore
9
+ */
10
+ export interface CloudflareR2StateStoreOptions {
11
+ /**
12
+ * The prefix to use for object keys in the R2 bucket
13
+ * This allows multiple state stores to use the same R2 bucket
14
+ */
15
+ prefix?: string;
16
+
17
+ /**
18
+ * The R2 bucket name to use
19
+ * Required - the bucket must already exist
20
+ */
21
+ bucketName: string;
22
+
23
+ /**
24
+ * API key to use (overrides CLOUDFLARE_API_KEY env var)
25
+ */
26
+ apiKey?: string;
27
+
28
+ /**
29
+ * Account ID to use (overrides CLOUDFLARE_ACCOUNT_ID env var)
30
+ */
31
+ accountId?: string;
32
+
33
+ /**
34
+ * Email to use with API Key authentication (overrides CLOUDFLARE_EMAIL env var)
35
+ */
36
+ email?: string;
37
+ }
38
+
39
+ /**
40
+ * State store implementation using Cloudflare R2 API
41
+ * Uses R2 for immediate consistency compared to KV's eventual consistency
42
+ */
43
+ export class R2RestStateStore implements StateStore {
44
+ private api: CloudflareApi;
45
+ private prefix: string;
46
+ private bucketName: string;
47
+ private apiKey: string | undefined;
48
+ private accountId: string | undefined;
49
+ private email: string | undefined;
50
+ private initialized = false;
51
+
52
+ /**
53
+ * Create a new CloudflareR2StateStore
54
+ *
55
+ * @param scope The scope this store belongs to
56
+ * @param options Options for the state store
57
+ */
58
+ constructor(
59
+ public readonly scope: Scope,
60
+ options: CloudflareR2StateStoreOptions,
61
+ ) {
62
+ // Use the scope's chain to build the prefix, similar to how FileSystemStateStore builds its directory
63
+ const scopePath = scope.chain.join("/");
64
+ this.prefix = options.prefix
65
+ ? `${options.prefix}${scopePath}/`
66
+ : `alchemy/${scopePath}/`;
67
+
68
+ if (!options.bucketName) {
69
+ throw new Error("bucketName is required for CloudflareR2StateStore");
70
+ }
71
+ this.bucketName = options.bucketName;
72
+
73
+ this.apiKey = options.apiKey;
74
+ this.accountId = options.accountId;
75
+ this.email = options.email;
76
+
77
+ // We'll initialize the API in init() to allow for async creation
78
+ this.api = null as any;
79
+ }
80
+
81
+ /**
82
+ * Initialize the R2 client
83
+ */
84
+ async init(): Promise<void> {
85
+ if (this.initialized) return;
86
+
87
+ // Create Cloudflare API client with automatic account discovery
88
+ this.api = await createCloudflareApi({
89
+ apiKey: this.apiKey,
90
+ accountId: this.accountId,
91
+ email: this.email,
92
+ });
93
+
94
+ this.initialized = true;
95
+ }
96
+
97
+ /**
98
+ * R2 buckets cannot be deleted programmatically via this method
99
+ */
100
+ async deinit(): Promise<void> {
101
+ // We don't delete the bucket here, only via explicit resource deletion
102
+ }
103
+
104
+ /**
105
+ * List all resources in the state store
106
+ */
107
+ async list(): Promise<string[]> {
108
+ await this.ensureInitialized();
109
+
110
+ // Using pagination to get all objects
111
+ let keys: string[] = [];
112
+ let cursor: string | null = null;
113
+
114
+ do {
115
+ const params = new URLSearchParams({
116
+ prefix: this.prefix,
117
+ limit: "1000",
118
+ });
119
+
120
+ if (cursor) {
121
+ params.append("cursor", cursor);
122
+ }
123
+
124
+ const listPath = `/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects?${params.toString()}`;
125
+ const response = await this.api.get(listPath);
126
+
127
+ if (!response.ok) {
128
+ const errorData: any = await response.json().catch(() => ({
129
+ errors: [{ message: response.statusText }],
130
+ }));
131
+ throw new Error(
132
+ `Error listing R2 objects: ${errorData.errors?.[0]?.message || response.statusText}`,
133
+ );
134
+ }
135
+
136
+ const data = (await response.json()) as any;
137
+
138
+ // The result structure may be under "result" key in Cloudflare's API
139
+ const result = data.result || data;
140
+ const objects = result.objects || [];
141
+
142
+ // Add keys to our list, removing the prefix and converting from storage format
143
+ keys = keys.concat(
144
+ objects.map((obj: any) => {
145
+ const keyName = obj.key || obj.name;
146
+ return this.convertKeyFromStorage(keyName.slice(this.prefix.length));
147
+ }),
148
+ );
149
+
150
+ // Update cursor for next page if available
151
+ cursor =
152
+ result.truncated || result.cursor_pagination
153
+ ? result.cursor || null
154
+ : null;
155
+ } while (cursor);
156
+
157
+ return keys;
158
+ }
159
+
160
+ /**
161
+ * Count the number of items in the state store
162
+ */
163
+ async count(): Promise<number> {
164
+ const keys = await this.list();
165
+ return keys.length;
166
+ }
167
+
168
+ /**
169
+ * Get a state by key
170
+ *
171
+ * @param key The key to look up
172
+ * @returns The state or undefined if not found
173
+ */
174
+ async get(key: string): Promise<State | undefined> {
175
+ await this.ensureInitialized();
176
+
177
+ try {
178
+ const response = await this.api.get(
179
+ `/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`,
180
+ );
181
+
182
+ if (!response.ok) {
183
+ if (response.status === 404) {
184
+ return undefined;
185
+ }
186
+
187
+ const errorData: any = await response.json().catch(() => ({
188
+ errors: [{ message: response.statusText }],
189
+ }));
190
+ throw new Error(
191
+ `Error getting R2 object: ${errorData.errors?.[0]?.message || response.statusText}`,
192
+ );
193
+ }
194
+
195
+ // Parse and deserialize the state data
196
+ const rawData = await response.json();
197
+ const state = (await deserialize(this.scope, rawData)) as State;
198
+
199
+ // Create a new state object with proper output
200
+ return {
201
+ ...state,
202
+ output: {
203
+ ...(state.output || {}),
204
+ Scope: this.scope,
205
+ },
206
+ };
207
+ } catch (error: any) {
208
+ if (error.message?.includes("404")) {
209
+ return undefined;
210
+ }
211
+ throw error;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Get multiple states by their keys
217
+ *
218
+ * @param ids Array of keys to fetch
219
+ * @returns Record mapping keys to their states
220
+ */
221
+ async getBatch(ids: string[]): Promise<Record<string, State>> {
222
+ const result: Record<string, State> = {};
223
+
224
+ // R2 REST API doesn't have a batch get operation, so we need to make multiple requests
225
+ const promises = ids.map(async (id) => {
226
+ const state = await this.get(id);
227
+ if (state) {
228
+ result[id] = state;
229
+ }
230
+ });
231
+
232
+ await Promise.all(promises);
233
+ return result;
234
+ }
235
+
236
+ /**
237
+ * Get all states in the store
238
+ *
239
+ * @returns Record mapping all keys to their states
240
+ */
241
+ async all(): Promise<Record<string, State>> {
242
+ const keys = await this.list();
243
+ return this.getBatch(keys);
244
+ }
245
+
246
+ /**
247
+ * Set a state for a key
248
+ *
249
+ * @param key The key to set
250
+ * @param value The state to store
251
+ */
252
+ async set(key: string, value: State): Promise<void> {
253
+ await this.ensureInitialized();
254
+
255
+ const objectKey = this.getObjectKey(key);
256
+
257
+ // Serialize the state to handle cyclic structures
258
+ const serializedData = await serialize(this.scope, value);
259
+
260
+ // Using withExponentialBackoff for reliability
261
+ await withExponentialBackoff(
262
+ async () => {
263
+ const response = await this.api.put(
264
+ `/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${objectKey}`,
265
+ serializedData,
266
+ {
267
+ headers: {
268
+ "Content-Type": "application/json",
269
+ },
270
+ },
271
+ );
272
+
273
+ if (!response.ok) {
274
+ const errorData: any = await response.json().catch(() => ({
275
+ errors: [{ message: response.statusText }],
276
+ }));
277
+ throw new Error(
278
+ `Error writing to R2: ${errorData.errors?.[0]?.message || response.statusText}`,
279
+ );
280
+ }
281
+
282
+ return response;
283
+ },
284
+ // Retry on transient errors
285
+ (error) =>
286
+ error.message?.includes("503") || error.message?.includes("timeout"),
287
+ 5, // 5 retry attempts
288
+ 1000, // Start with 1 second delay
289
+ );
290
+ }
291
+
292
+ /**
293
+ * Delete a state by key
294
+ *
295
+ * @param key The key to delete
296
+ */
297
+ async delete(key: string): Promise<void> {
298
+ await this.ensureInitialized();
299
+
300
+ const response = await this.api.delete(
301
+ `/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`,
302
+ );
303
+
304
+ if (!response.ok && response.status !== 404) {
305
+ const errorData: any = await response.json().catch(() => ({
306
+ errors: [{ message: response.statusText }],
307
+ }));
308
+ throw new Error(
309
+ `Error deleting from R2: ${errorData.errors?.[0]?.message || response.statusText}`,
310
+ );
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Convert key for storage by replacing slashes with colons
316
+ * since R2 treats slashes as directory separators
317
+ *
318
+ * @param key The original key
319
+ * @returns Key with slashes replaced by colons
320
+ */
321
+ private convertKeyForStorage(key: string): string {
322
+ return key.replaceAll("/", ":");
323
+ }
324
+
325
+ /**
326
+ * Convert key from storage by replacing colons with slashes
327
+ *
328
+ * @param key The storage key
329
+ * @returns Key with colons replaced by slashes
330
+ */
331
+ private convertKeyFromStorage(key: string): string {
332
+ return key.replaceAll(":", "/");
333
+ }
334
+
335
+ /**
336
+ * Get the full object key for storage
337
+ *
338
+ * @param key The original key
339
+ * @returns The key with prefix for use in the R2 bucket
340
+ */
341
+ private getObjectKey(key: string): string {
342
+ return `${this.prefix}${this.convertKeyForStorage(key)}`;
343
+ }
344
+
345
+ /**
346
+ * Ensure the store is initialized before operations
347
+ */
348
+ private async ensureInitialized(): Promise<void> {
349
+ if (!this.initialized) {
350
+ await this.init();
351
+ }
352
+ }
353
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Cloudflare API response format
3
+ */
4
+ export interface CloudflareResponse<T = unknown> {
5
+ result: T;
6
+ success: boolean;
7
+ errors: Array<{ code: number; message: string }>;
8
+ messages: string[];
9
+ }
@@ -65,21 +65,27 @@ app.notFound(async (ctx) => {
65
65
 
66
66
  // Fetch from KV
67
67
  {
68
- const object = await env.ASSETS.getWithMetadata(filePath);
68
+ const object = await env.ASSETS.getWithMetadata(filePath, {
69
+ type: "arrayBuffer",
70
+ });
69
71
  if (object.value) {
70
72
  return await respond(200, filePath, object);
71
73
  }
72
74
  }
73
75
  {
74
76
  const guess = filePath + (filePath.endsWith("/") ? "" : "/") + "index.html";
75
- const object = await env.ASSETS.getWithMetadata(guess);
77
+ const object = await env.ASSETS.getWithMetadata(guess, {
78
+ type: "arrayBuffer",
79
+ });
76
80
  if (object.value) {
77
81
  return await respond(200, guess, object);
78
82
  }
79
83
  }
80
84
  {
81
85
  const guess = filePath + ".html";
82
- const object = await env.ASSETS.getWithMetadata(guess);
86
+ const object = await env.ASSETS.getWithMetadata(guess, {
87
+ type: "arrayBuffer",
88
+ });
83
89
  if (object.value) {
84
90
  return await respond(200, guess, object);
85
91
  }
@@ -87,12 +93,16 @@ app.notFound(async (ctx) => {
87
93
 
88
94
  // Handle error page
89
95
  if (env.ERROR_PAGE) {
90
- const object = await env.ASSETS.getWithMetadata(env.ERROR_PAGE);
96
+ const object = await env.ASSETS.getWithMetadata(env.ERROR_PAGE, {
97
+ type: "arrayBuffer",
98
+ });
91
99
  if (object.value) {
92
100
  return await respond(404, env.ERROR_PAGE, object);
93
101
  }
94
102
  } else {
95
- const object = await env.ASSETS.getWithMetadata(env.INDEX_PAGE);
103
+ const object = await env.ASSETS.getWithMetadata(env.INDEX_PAGE, {
104
+ type: "arrayBuffer",
105
+ });
96
106
  if (object.value) {
97
107
  return await respond(200, env.INDEX_PAGE, object);
98
108
  }
@@ -122,7 +132,7 @@ app.notFound(async (ctx) => {
122
132
  async function respond(
123
133
  status: number,
124
134
  filePath: string,
125
- object: KVNamespaceGetWithMetadataResult<any, any>,
135
+ object: KVNamespaceGetWithMetadataResult<any, any>
126
136
  ) {
127
137
  // build response
128
138
  const headers = new Headers();
@@ -131,8 +141,8 @@ app.notFound(async (ctx) => {
131
141
  headers.set("content-type", object.metadata.contentType);
132
142
  headers.set("cache-control", object.metadata.cacheControl);
133
143
  }
134
- // TODO: do we need base64 encoded here?
135
- // const response = new Response(base64ToArrayBuffer(object.value), {
144
+
145
+ // Create response with the raw data directly
136
146
  const response = new Response(object.value, {
137
147
  status,
138
148
  headers,
@@ -262,7 +262,7 @@ export const StaticSite = Resource(
262
262
  async function (
263
263
  this: Context<StaticSite>,
264
264
  id: string,
265
- props: StaticSiteProps,
265
+ props: StaticSiteProps
266
266
  ) {
267
267
  if (this.phase === "delete") {
268
268
  // For delete operations, we'll rely on the Worker delete to clean up
@@ -273,7 +273,7 @@ export const StaticSite = Resource(
273
273
  // Validate that a name is provided
274
274
  if (!props.name) {
275
275
  throw new Error(
276
- "StaticSite name is required - must be explicitly specified",
276
+ "StaticSite name is required - must be explicitly specified"
277
277
  );
278
278
  }
279
279
 
@@ -302,7 +302,7 @@ export const StaticSite = Resource(
302
302
 
303
303
  // Throw a more descriptive error that includes the exit code and stderr
304
304
  throw new Error(
305
- `Build command "${props.build.command}" failed with exit code ${error.code}:\n${error.stderr || error.message}`,
305
+ `Build command "${props.build.command}" failed with exit code ${error.code}:\n${error.stderr || error.message}`
306
306
  );
307
307
  }
308
308
  }
@@ -314,7 +314,7 @@ export const StaticSite = Resource(
314
314
  }
315
315
  } catch (error: any) {
316
316
  throw new Error(
317
- `Directory "${props.dir}" does not exist: ${error.message}`,
317
+ `Directory "${props.dir}" does not exist: ${error.message}`
318
318
  );
319
319
  }
320
320
 
@@ -333,6 +333,10 @@ export const StaticSite = Resource(
333
333
  generateAssetManifest(props.dir),
334
334
  ]);
335
335
 
336
+ console.log({
337
+ assetManifest,
338
+ });
339
+
336
340
  // Step 3: Upload assets to KV
337
341
  await uploadAssetManifest(api, kv.namespaceId, assetManifest);
338
342
 
@@ -356,7 +360,7 @@ export const StaticSite = Resource(
356
360
 
357
361
  // Create asset manifest banner for static site router
358
362
  const assetManifestBanner = `const __ASSET_MANIFEST__ = ${JSON.stringify(
359
- Object.fromEntries(assetManifest.map((item) => [item.key, item.hash])),
363
+ Object.fromEntries(assetManifest.map((item) => [item.key, item.hash]))
360
364
  )};\n`;
361
365
 
362
366
  const bundleOptions = {
@@ -398,7 +402,7 @@ export const StaticSite = Resource(
398
402
  Object.entries(routes).map(([key, value]) => [
399
403
  `__ROUTE_${value}`,
400
404
  key,
401
- ]),
405
+ ])
402
406
  ),
403
407
  },
404
408
  });
@@ -420,5 +424,5 @@ export const StaticSite = Resource(
420
424
  url: worker.url,
421
425
  routes,
422
426
  });
423
- },
427
+ }
424
428
  );
@@ -17,7 +17,7 @@ type UploadResult = {
17
17
  export async function uploadAssetManifest(
18
18
  api: CloudflareApi,
19
19
  namespaceId: string,
20
- manifest: AssetManifest,
20
+ manifest: AssetManifest
21
21
  ): Promise<UploadResult[]> {
22
22
  const results: UploadResult[] = [];
23
23
 
@@ -59,14 +59,16 @@ export async function uploadAssetManifest(
59
59
  const formData = new FormData();
60
60
  formData.append("metadata", JSON.stringify(metadata));
61
61
 
62
- // Add file content as 'value'
63
- const blob = new Blob([content]);
62
+ // Add file content as 'value' - Fix for binary file corruption
63
+ const blob = new Blob([new Uint8Array(content)], {
64
+ type: item.contentType,
65
+ });
64
66
  formData.append("value", blob);
65
67
 
66
68
  try {
67
69
  const response = await api.put(
68
70
  `/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/values/${item.key}`,
69
- formData,
71
+ formData
70
72
  );
71
73
 
72
74
  const result = { item, success: response.ok, status: response.status };