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.
- package/README.md +3 -3
- package/lib/ai/approve.d.ts +99 -0
- package/lib/ai/approve.js +76 -0
- package/lib/ai/astro-file.d.ts +137 -0
- package/lib/ai/astro-file.js +156 -0
- package/lib/ai/client.d.ts +7 -8
- package/lib/ai/client.js +48 -19
- package/lib/ai/css-file.d.ts +131 -0
- package/lib/ai/css-file.js +141 -0
- package/lib/ai/data.d.ts +51 -6
- package/lib/ai/data.js +58 -12
- package/lib/ai/document.d.ts +108 -24
- package/lib/ai/document.js +166 -52
- package/lib/ai/html-file.d.ts +128 -0
- package/lib/ai/html-file.js +138 -0
- package/lib/ai/index.d.ts +9 -0
- package/lib/ai/index.js +9 -0
- package/lib/ai/json-file.d.ts +160 -0
- package/lib/ai/json-file.js +187 -0
- package/lib/ai/review.d.ts +122 -0
- package/lib/ai/review.js +101 -0
- package/lib/ai/typescript-file.d.ts +139 -0
- package/lib/ai/typescript-file.js +145 -0
- package/lib/ai/vue-file.d.ts +126 -0
- package/lib/ai/vue-file.js +136 -0
- package/lib/ai/yaml-file.d.ts +169 -0
- package/lib/ai/yaml-file.js +200 -0
- package/lib/alchemy.js +17 -2
- package/lib/apply.js +1 -1
- package/lib/cloudflare/dns.d.ts +88 -0
- package/lib/cloudflare/dns.js +192 -0
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/index.d.ts +2 -1
- package/lib/cloudflare/index.js +2 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
- package/lib/cloudflare/r2-rest-state-store.js +254 -0
- package/lib/cloudflare/response.d.ts +12 -0
- package/lib/cloudflare/response.js +0 -0
- package/lib/cloudflare/static-site-router.js +16 -7
- package/lib/cloudflare/static-site.js +3 -0
- package/lib/cloudflare/upload-asset-manifest.js +4 -2
- package/lib/cloudflare/zone.d.ts +18 -0
- package/lib/cloudflare/zone.js +17 -4
- package/lib/dns/godaddy.d.ts +7 -0
- package/lib/dns/godaddy.js +21 -0
- package/lib/dns/import-dns.d.ts +82 -0
- package/lib/dns/import-dns.js +114 -0
- package/lib/dns/index.d.ts +2 -0
- package/lib/dns/index.js +2 -0
- package/lib/dns/record.d.ts +88 -0
- package/lib/dns/record.js +13 -0
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file-system-state-store.d.ts +17 -0
- package/lib/fs/file-system-state-store.js +87 -0
- package/lib/fs/file.d.ts +13 -0
- package/lib/fs/file.js +24 -5
- package/lib/fs/folder.d.ts +10 -0
- package/lib/fs/folder.js +2 -2
- package/lib/fs/index.d.ts +10 -4
- package/lib/fs/index.js +10 -4
- package/lib/fs/static-astro-file.d.ts +34 -0
- package/lib/fs/static-astro-file.js +39 -0
- package/lib/fs/static-css-file.d.ts +24 -0
- package/lib/fs/static-css-file.js +29 -0
- package/lib/fs/static-html-file.d.ts +31 -0
- package/lib/fs/static-html-file.js +36 -0
- package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +3 -3
- package/lib/fs/static-json-file.js +15 -0
- package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
- package/lib/fs/static-text-file.js +8 -0
- package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
- package/lib/fs/{typescript-file.js → static-typescript-file.js} +3 -2
- package/lib/fs/static-vue-file.d.ts +28 -0
- package/lib/fs/static-vue-file.js +33 -0
- package/lib/fs/static-yaml-file.d.ts +22 -0
- package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
- package/lib/internal/getting-started.d.ts +21 -0
- package/lib/internal/getting-started.js +87 -0
- package/lib/internal/index.d.ts +3 -0
- package/lib/internal/index.js +3 -0
- package/lib/internal/providers.d.ts +27 -0
- package/lib/internal/providers.js +172 -0
- package/lib/internal/tutorial.d.ts +104 -0
- package/lib/internal/tutorial.js +251 -0
- package/lib/resource.js +8 -1
- package/lib/scope.d.ts +1 -1
- package/lib/scope.js +4 -2
- package/lib/state.d.ts +1 -16
- package/lib/state.js +0 -87
- package/lib/test/bun.d.ts +11 -0
- package/lib/test/bun.js +36 -8
- package/lib/web/astro.d.ts +147 -0
- package/lib/web/astro.js +414 -0
- package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
- package/lib/web/shadcn.d.ts +97 -0
- package/lib/web/shadcn.js +99 -0
- package/lib/web/tailwind.d.ts +64 -0
- package/lib/web/tailwind.js +97 -0
- package/lib/{vite → web}/vite.d.ts +5 -0
- package/lib/{vite → web}/vite.js +44 -44
- package/lib/web/vitepress/config.d.ts +10 -0
- package/lib/web/vitepress/config.js +31 -0
- package/lib/web/vitepress/custom-theme.d.ts +173 -0
- package/lib/web/vitepress/custom-theme.js +336 -0
- package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
- package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
- package/lib/web/vitepress/home-page.d.ts +246 -0
- package/lib/web/vitepress/home-page.js +147 -0
- package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
- package/lib/{vitepress → web/vitepress}/index.js +2 -0
- package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
- package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
- package/package.json +6 -2
- package/src/ai/approve.ts +163 -0
- package/src/ai/astro-file.ts +269 -0
- package/src/ai/client.ts +60 -21
- package/src/ai/css-file.ts +247 -0
- package/src/ai/data.ts +81 -19
- package/src/ai/document.ts +240 -62
- package/src/ai/html-file.ts +244 -0
- package/src/ai/index.ts +9 -0
- package/src/ai/json-file.ts +325 -0
- package/src/ai/review.ts +213 -0
- package/src/ai/typescript-file.ts +259 -0
- package/src/ai/vue-file.ts +242 -0
- package/src/ai/yaml-file.ts +328 -0
- package/src/alchemy.ts +18 -7
- package/src/apply.ts +8 -9
- package/src/cloudflare/dns.ts +344 -0
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/index.ts +2 -1
- package/src/cloudflare/r2-rest-state-store.ts +353 -0
- package/src/cloudflare/response.ts +9 -0
- package/src/cloudflare/static-site-router.ts +18 -8
- package/src/cloudflare/static-site.ts +11 -7
- package/src/cloudflare/upload-asset-manifest.ts +6 -4
- package/src/cloudflare/worker.ts +44 -43
- package/src/cloudflare/zone.ts +39 -23
- package/src/dns/godaddy.ts +29 -0
- package/src/dns/import-dns.ts +213 -0
- package/src/dns/index.ts +2 -0
- package/src/dns/record.ts +121 -0
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file-system-state-store.ts +109 -0
- package/src/fs/file.ts +35 -9
- package/src/fs/folder.ts +14 -2
- package/src/fs/index.ts +10 -4
- package/src/fs/static-astro-file.ts +45 -0
- package/src/fs/static-css-file.ts +35 -0
- package/src/fs/static-html-file.ts +42 -0
- package/src/fs/static-json-file.ts +34 -0
- package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
- package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
- package/src/fs/static-vue-file.ts +39 -0
- package/src/fs/static-yaml-file.ts +33 -0
- package/src/internal/getting-started.ts +115 -0
- package/src/internal/index.ts +3 -0
- package/src/internal/providers.ts +241 -0
- package/src/internal/tutorial.ts +392 -0
- package/src/resource.ts +10 -1
- package/src/scope.ts +5 -6
- package/src/state.ts +1 -111
- package/src/test/bun.ts +60 -10
- package/src/web/astro.ts +644 -0
- package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
- package/src/web/shadcn.ts +219 -0
- package/src/web/tailwind.ts +167 -0
- package/src/{vite → web}/vite.ts +54 -56
- package/src/web/vitepress/config.ts +48 -0
- package/src/web/vitepress/custom-theme.ts +514 -0
- package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
- package/src/web/vitepress/home-page.ts +375 -0
- package/src/{vitepress → web/vitepress}/index.ts +2 -0
- package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
- package/lib/cloudflare/state.d.ts +0 -82
- package/lib/cloudflare/state.js +0 -108
- package/lib/fs/json-file.js +0 -7
- package/lib/fs/text-file.js +0 -7
- package/lib/fs/yaml-file.d.ts +0 -19
- package/lib/internal/docs.d.ts +0 -5
- package/lib/internal/docs.js +0 -287
- package/lib/shadcn/index.d.ts +0 -1
- package/lib/shadcn/index.js +0 -1
- package/lib/vite/index.d.ts +0 -1
- package/lib/vite/index.js +0 -1
- package/lib/vitepress/home-page.d.ts +0 -133
- package/lib/vitepress/home-page.js +0 -11
- package/src/cloudflare/state.ts +0 -138
- package/src/fs/json-file.ts +0 -23
- package/src/fs/yaml-file.ts +0 -26
- package/src/internal/docs.ts +0 -338
- package/src/shadcn/index.ts +0 -1
- package/src/vite/index.ts +0 -1
- package/src/vitepress/home-page.ts +0 -166
- /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
- /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
|
+
}
|
|
@@ -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
|
-
|
|
135
|
-
//
|
|
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 };
|