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
- const errorData = await response.json().catch(() => ({
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
- (error) => error.message?.includes("502") ||
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
- if (response.status === 404) {
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
- (error) => error.message?.includes("502") ||
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
- const errorData = await response.json().catch(() => ({
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
- (error) => error.message?.includes("503") || error.message?.includes("timeout"), 5, // 5 retry attempts
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
- const errorData = await response.json().catch(() => ({
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
+ }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "type": "module",
5
5
  "module": "./lib/index.js",
6
6
  "scripts": {
@@ -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
- const errorData: any = await response.json().catch(() => ({
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
- (error) =>
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
- if (response.status === 404) {
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
- (error) =>
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
- const errorData: any = await response.json().catch(() => ({
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
- (error) =>
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
- const errorData: any = await response.json().catch(() => ({
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
- // Retry on transient errors
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
+ }
@@ -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
 
@@ -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
  }