alchemy 0.7.2 → 0.7.3
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.
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { deserialize, serialize } from "../serde";
|
|
2
2
|
import { withExponentialBackoff } from "../util/retry";
|
|
3
3
|
import { createCloudflareApi, } from "./api";
|
|
4
|
+
import { CloudflareApiError, handleApiError } from "./api-error";
|
|
4
5
|
/**
|
|
5
6
|
* State store implementation using Cloudflare R2 API
|
|
6
7
|
* Uses R2 for immediate consistency compared to KV's eventual consistency
|
|
@@ -69,17 +70,12 @@ export class R2RestStateStore {
|
|
|
69
70
|
const response = await withExponentialBackoff(async () => {
|
|
70
71
|
const response = await this.api.get(listPath);
|
|
71
72
|
if (!response.ok) {
|
|
72
|
-
|
|
73
|
-
errors: [{ message: response.statusText }],
|
|
74
|
-
}));
|
|
75
|
-
throw new Error(`Error listing R2 objects: ${errorData.errors?.[0]?.message || response.statusText}`);
|
|
73
|
+
await handleApiError(response, "list", "bucket", this.bucketName);
|
|
76
74
|
}
|
|
77
75
|
return response;
|
|
78
76
|
},
|
|
79
77
|
// Retry on transient errors
|
|
80
|
-
|
|
81
|
-
error.message?.includes("503") ||
|
|
82
|
-
error.message?.includes("timeout"), 5, // 5 retry attempts
|
|
78
|
+
isRetryableError, 5, // 5 retry attempts
|
|
83
79
|
1000 // Start with 1 second delay
|
|
84
80
|
);
|
|
85
81
|
const data = (await response.json());
|
|
@@ -117,21 +113,13 @@ export class R2RestStateStore {
|
|
|
117
113
|
try {
|
|
118
114
|
const response = await withExponentialBackoff(async () => {
|
|
119
115
|
const response = await this.api.get(`/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`);
|
|
120
|
-
if (!response.ok) {
|
|
121
|
-
|
|
122
|
-
return response;
|
|
123
|
-
}
|
|
124
|
-
const errorData = await response.json().catch(() => ({
|
|
125
|
-
errors: [{ message: response.statusText }],
|
|
126
|
-
}));
|
|
127
|
-
throw new Error(`Error getting R2 object: ${errorData.errors?.[0]?.message || response.statusText}`);
|
|
116
|
+
if (!response.ok && response.status !== 404) {
|
|
117
|
+
await handleApiError(response, "get", "object", key);
|
|
128
118
|
}
|
|
129
119
|
return response;
|
|
130
120
|
},
|
|
131
121
|
// Retry on transient errors
|
|
132
|
-
|
|
133
|
-
error.message?.includes("503") ||
|
|
134
|
-
error.message?.includes("timeout"), 5, // 5 retry attempts
|
|
122
|
+
isRetryableError, 5, // 5 retry attempts
|
|
135
123
|
1000 // Start with 1 second delay
|
|
136
124
|
);
|
|
137
125
|
if (response.status === 404) {
|
|
@@ -202,15 +190,12 @@ export class R2RestStateStore {
|
|
|
202
190
|
},
|
|
203
191
|
});
|
|
204
192
|
if (!response.ok) {
|
|
205
|
-
|
|
206
|
-
errors: [{ message: response.statusText }],
|
|
207
|
-
}));
|
|
208
|
-
throw new Error(`Error writing to R2: ${errorData.errors?.[0]?.message || response.statusText}`);
|
|
193
|
+
await handleApiError(response, "put", "object", objectKey);
|
|
209
194
|
}
|
|
210
195
|
return response;
|
|
211
196
|
},
|
|
212
197
|
// Retry on transient errors
|
|
213
|
-
|
|
198
|
+
isRetryableError, 5, // 5 retry attempts
|
|
214
199
|
1000 // Start with 1 second delay
|
|
215
200
|
);
|
|
216
201
|
}
|
|
@@ -224,17 +209,10 @@ export class R2RestStateStore {
|
|
|
224
209
|
await withExponentialBackoff(async () => {
|
|
225
210
|
const response = await this.api.delete(`/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`);
|
|
226
211
|
if (!response.ok && response.status !== 404) {
|
|
227
|
-
|
|
228
|
-
errors: [{ message: response.statusText }],
|
|
229
|
-
}));
|
|
230
|
-
throw new Error(`Error deleting from R2: ${errorData.errors?.[0]?.message || response.statusText}`);
|
|
212
|
+
await handleApiError(response, "delete", "object", key);
|
|
231
213
|
}
|
|
232
214
|
return response;
|
|
233
|
-
},
|
|
234
|
-
// Retry on transient errors
|
|
235
|
-
(error) => error.message?.includes("502") ||
|
|
236
|
-
error.message?.includes("503") ||
|
|
237
|
-
error.message?.includes("timeout"), 5, // 5 retry attempts
|
|
215
|
+
}, isRetryableError, 5, // 5 retry attempts
|
|
238
216
|
1000 // Start with 1 second delay
|
|
239
217
|
);
|
|
240
218
|
}
|
|
@@ -275,3 +253,13 @@ export class R2RestStateStore {
|
|
|
275
253
|
}
|
|
276
254
|
}
|
|
277
255
|
}
|
|
256
|
+
function isRetryableError(error) {
|
|
257
|
+
if (error instanceof CloudflareApiError) {
|
|
258
|
+
return (error.status === 500 ||
|
|
259
|
+
error.status === 502 ||
|
|
260
|
+
error.status === 503 ||
|
|
261
|
+
error.message.includes("timeout") ||
|
|
262
|
+
error.message.includes("internal error"));
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
package/lib/cloudflare/worker.js
CHANGED
|
@@ -431,6 +431,10 @@ async function bundleWorkerScript(props) {
|
|
|
431
431
|
minify: true,
|
|
432
432
|
options: {
|
|
433
433
|
keepNames: true, // Important for Durable Object classes
|
|
434
|
+
loader: {
|
|
435
|
+
".sql": "text",
|
|
436
|
+
".json": "json",
|
|
437
|
+
},
|
|
434
438
|
},
|
|
435
439
|
};
|
|
436
440
|
// Merge with user-provided options
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type CloudflareApiOptions,
|
|
8
8
|
createCloudflareApi,
|
|
9
9
|
} from "./api";
|
|
10
|
+
import { CloudflareApiError, handleApiError } from "./api-error";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Options for CloudflareR2StateStore
|
|
@@ -106,21 +107,13 @@ export class R2RestStateStore implements StateStore {
|
|
|
106
107
|
const response = await this.api.get(listPath);
|
|
107
108
|
|
|
108
109
|
if (!response.ok) {
|
|
109
|
-
|
|
110
|
-
errors: [{ message: response.statusText }],
|
|
111
|
-
}));
|
|
112
|
-
throw new Error(
|
|
113
|
-
`Error listing R2 objects: ${errorData.errors?.[0]?.message || response.statusText}`
|
|
114
|
-
);
|
|
110
|
+
await handleApiError(response, "list", "bucket", this.bucketName);
|
|
115
111
|
}
|
|
116
112
|
|
|
117
113
|
return response;
|
|
118
114
|
},
|
|
119
115
|
// Retry on transient errors
|
|
120
|
-
|
|
121
|
-
error.message?.includes("502") ||
|
|
122
|
-
error.message?.includes("503") ||
|
|
123
|
-
error.message?.includes("timeout"),
|
|
116
|
+
isRetryableError,
|
|
124
117
|
5, // 5 retry attempts
|
|
125
118
|
1000 // Start with 1 second delay
|
|
126
119
|
);
|
|
@@ -173,26 +166,14 @@ export class R2RestStateStore implements StateStore {
|
|
|
173
166
|
`/accounts/${this.api.accountId}/r2/buckets/${this.bucketName}/objects/${this.getObjectKey(key)}`
|
|
174
167
|
);
|
|
175
168
|
|
|
176
|
-
if (!response.ok) {
|
|
177
|
-
|
|
178
|
-
return response;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const errorData: any = await response.json().catch(() => ({
|
|
182
|
-
errors: [{ message: response.statusText }],
|
|
183
|
-
}));
|
|
184
|
-
throw new Error(
|
|
185
|
-
`Error getting R2 object: ${errorData.errors?.[0]?.message || response.statusText}`
|
|
186
|
-
);
|
|
169
|
+
if (!response.ok && response.status !== 404) {
|
|
170
|
+
await handleApiError(response, "get", "object", key);
|
|
187
171
|
}
|
|
188
172
|
|
|
189
173
|
return response;
|
|
190
174
|
},
|
|
191
175
|
// Retry on transient errors
|
|
192
|
-
|
|
193
|
-
error.message?.includes("502") ||
|
|
194
|
-
error.message?.includes("503") ||
|
|
195
|
-
error.message?.includes("timeout"),
|
|
176
|
+
isRetryableError,
|
|
196
177
|
5, // 5 retry attempts
|
|
197
178
|
1000 // Start with 1 second delay
|
|
198
179
|
);
|
|
@@ -280,19 +261,12 @@ export class R2RestStateStore implements StateStore {
|
|
|
280
261
|
);
|
|
281
262
|
|
|
282
263
|
if (!response.ok) {
|
|
283
|
-
|
|
284
|
-
errors: [{ message: response.statusText }],
|
|
285
|
-
}));
|
|
286
|
-
throw new Error(
|
|
287
|
-
`Error writing to R2: ${errorData.errors?.[0]?.message || response.statusText}`
|
|
288
|
-
);
|
|
264
|
+
await handleApiError(response, "put", "object", objectKey);
|
|
289
265
|
}
|
|
290
|
-
|
|
291
266
|
return response;
|
|
292
267
|
},
|
|
293
268
|
// Retry on transient errors
|
|
294
|
-
|
|
295
|
-
error.message?.includes("503") || error.message?.includes("timeout"),
|
|
269
|
+
isRetryableError,
|
|
296
270
|
5, // 5 retry attempts
|
|
297
271
|
1000 // Start with 1 second delay
|
|
298
272
|
);
|
|
@@ -313,21 +287,12 @@ export class R2RestStateStore implements StateStore {
|
|
|
313
287
|
);
|
|
314
288
|
|
|
315
289
|
if (!response.ok && response.status !== 404) {
|
|
316
|
-
|
|
317
|
-
errors: [{ message: response.statusText }],
|
|
318
|
-
}));
|
|
319
|
-
throw new Error(
|
|
320
|
-
`Error deleting from R2: ${errorData.errors?.[0]?.message || response.statusText}`
|
|
321
|
-
);
|
|
290
|
+
await handleApiError(response, "delete", "object", key);
|
|
322
291
|
}
|
|
323
292
|
|
|
324
293
|
return response;
|
|
325
294
|
},
|
|
326
|
-
|
|
327
|
-
(error) =>
|
|
328
|
-
error.message?.includes("502") ||
|
|
329
|
-
error.message?.includes("503") ||
|
|
330
|
-
error.message?.includes("timeout"),
|
|
295
|
+
isRetryableError,
|
|
331
296
|
5, // 5 retry attempts
|
|
332
297
|
1000 // Start with 1 second delay
|
|
333
298
|
);
|
|
@@ -373,3 +338,16 @@ export class R2RestStateStore implements StateStore {
|
|
|
373
338
|
}
|
|
374
339
|
}
|
|
375
340
|
}
|
|
341
|
+
|
|
342
|
+
function isRetryableError(error: any): boolean {
|
|
343
|
+
if (error instanceof CloudflareApiError) {
|
|
344
|
+
return (
|
|
345
|
+
error.status === 500 ||
|
|
346
|
+
error.status === 502 ||
|
|
347
|
+
error.status === 503 ||
|
|
348
|
+
error.message.includes("timeout") ||
|
|
349
|
+
error.message.includes("internal error")
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return false;
|
|
353
|
+
}
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -720,6 +720,10 @@ async function bundleWorkerScript<B extends Bindings>(props: WorkerProps) {
|
|
|
720
720
|
minify: true,
|
|
721
721
|
options: {
|
|
722
722
|
keepNames: true, // Important for Durable Object classes
|
|
723
|
+
loader: {
|
|
724
|
+
".sql": "text",
|
|
725
|
+
".json": "json",
|
|
726
|
+
},
|
|
723
727
|
},
|
|
724
728
|
};
|
|
725
729
|
|
package/src/esbuild/bundle.ts
CHANGED
|
@@ -116,7 +116,7 @@ export const Bundle = Resource(
|
|
|
116
116
|
async function (
|
|
117
117
|
this: Context<Bundle>,
|
|
118
118
|
id: string,
|
|
119
|
-
props: BundleProps
|
|
119
|
+
props: BundleProps
|
|
120
120
|
): Promise<Bundle> {
|
|
121
121
|
// Determine output path
|
|
122
122
|
const outputPath = getOutputPath(props);
|
|
@@ -145,7 +145,7 @@ export const Bundle = Resource(
|
|
|
145
145
|
path: outputPath,
|
|
146
146
|
hash,
|
|
147
147
|
});
|
|
148
|
-
}
|
|
148
|
+
}
|
|
149
149
|
);
|
|
150
150
|
|
|
151
151
|
export async function bundle(props: BundleProps) {
|
|
@@ -172,7 +172,7 @@ function getOutputPath(props: BundleProps) {
|
|
|
172
172
|
props.outfile ||
|
|
173
173
|
path.join(
|
|
174
174
|
props.outdir || "dist",
|
|
175
|
-
path.basename(props.entryPoint, path.extname(props.entryPoint)) + ".js"
|
|
175
|
+
path.basename(props.entryPoint, path.extname(props.entryPoint)) + ".js"
|
|
176
176
|
)
|
|
177
177
|
);
|
|
178
178
|
}
|