alchemy 0.12.9 → 0.12.11

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 (59) hide show
  1. package/lib/cloudflare/api.d.ts.map +1 -1
  2. package/lib/cloudflare/api.js +17 -12
  3. package/lib/cloudflare/api.js.map +1 -1
  4. package/lib/cloudflare/bucket.d.ts +16 -3
  5. package/lib/cloudflare/bucket.d.ts.map +1 -1
  6. package/lib/cloudflare/bucket.js +31 -6
  7. package/lib/cloudflare/bucket.js.map +1 -1
  8. package/lib/cloudflare/external.d.ts +2 -0
  9. package/lib/cloudflare/external.d.ts.map +1 -0
  10. package/lib/cloudflare/external.js +30 -0
  11. package/lib/cloudflare/external.js.map +1 -0
  12. package/lib/cloudflare/index.d.ts +1 -0
  13. package/lib/cloudflare/index.d.ts.map +1 -1
  14. package/lib/cloudflare/index.js +1 -0
  15. package/lib/cloudflare/index.js.map +1 -1
  16. package/lib/cloudflare/pipeline.d.ts +7 -1
  17. package/lib/cloudflare/pipeline.d.ts.map +1 -1
  18. package/lib/cloudflare/pipeline.js +1 -1
  19. package/lib/cloudflare/pipeline.js.map +1 -1
  20. package/lib/cloudflare/r2-rest-state-store.d.ts +2 -2
  21. package/lib/cloudflare/r2-rest-state-store.d.ts.map +1 -1
  22. package/lib/cloudflare/r2-rest-state-store.js +13 -5
  23. package/lib/cloudflare/r2-rest-state-store.js.map +1 -1
  24. package/lib/cloudflare/redwood.d.ts +1 -33
  25. package/lib/cloudflare/redwood.d.ts.map +1 -1
  26. package/lib/cloudflare/redwood.js +7 -46
  27. package/lib/cloudflare/redwood.js.map +1 -1
  28. package/lib/cloudflare/website.d.ts +4 -1
  29. package/lib/cloudflare/website.d.ts.map +1 -1
  30. package/lib/cloudflare/website.js +10 -2
  31. package/lib/cloudflare/website.js.map +1 -1
  32. package/lib/cloudflare/worker.d.ts.map +1 -1
  33. package/lib/cloudflare/worker.js +17 -33
  34. package/lib/cloudflare/worker.js.map +1 -1
  35. package/lib/cloudflare/wrangler.json.d.ts +6 -0
  36. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  37. package/lib/cloudflare/wrangler.json.js +1 -1
  38. package/lib/cloudflare/wrangler.json.js.map +1 -1
  39. package/lib/esbuild/bundle.d.ts +4 -4
  40. package/lib/esbuild/bundle.d.ts.map +1 -1
  41. package/lib/esbuild/bundle.js +9 -7
  42. package/lib/esbuild/bundle.js.map +1 -1
  43. package/lib/os/exec.d.ts +15 -15
  44. package/lib/os/exec.d.ts.map +1 -1
  45. package/lib/os/exec.js +47 -26
  46. package/lib/os/exec.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/cloudflare/api.ts +15 -13
  49. package/src/cloudflare/bucket.ts +56 -8
  50. package/src/cloudflare/external.ts +30 -0
  51. package/src/cloudflare/index.ts +1 -0
  52. package/src/cloudflare/pipeline.ts +9 -2
  53. package/src/cloudflare/r2-rest-state-store.ts +24 -6
  54. package/src/cloudflare/redwood.ts +8 -46
  55. package/src/cloudflare/website.ts +19 -3
  56. package/src/cloudflare/worker.ts +22 -42
  57. package/src/cloudflare/wrangler.json.ts +8 -1
  58. package/src/esbuild/bundle.ts +11 -9
  59. package/src/os/exec.ts +61 -34
package/lib/os/exec.js CHANGED
@@ -1,16 +1,21 @@
1
- import { exec } from "node:child_process";
2
- import { promisify } from "node:util";
1
+ import { spawn } from "node:child_process";
3
2
  import { Resource } from "../resource.js";
4
- const execAsync = promisify(exec);
5
3
  /**
6
4
  * Execute a shell command
7
5
  *
8
6
  * @example
9
- * // Run a simple command
7
+ * // Run a simple command with inherited stdio (default)
10
8
  * const result = await Exec("list-files", {
11
9
  * command: "ls -la"
12
10
  * });
13
11
  *
12
+ * @example
13
+ * // Run a command and capture output instead of inheriting stdio
14
+ * const result = await Exec("list-files", {
15
+ * command: "ls -la",
16
+ * inheritStdio: false
17
+ * });
18
+ *
14
19
  * console.log(result.stdout);
15
20
  *
16
21
  * @example
@@ -22,13 +27,6 @@ const execAsync = promisify(exec);
22
27
  * });
23
28
  *
24
29
  * @example
25
- * // Run a command with a larger buffer for output
26
- * const logs = await Exec("get-logs", {
27
- * command: "cat large-log-file.log",
28
- * maxBuffer: 10 * 1024 * 1024 // 10MB
29
- * });
30
- *
31
- * @example
32
30
  * // Run a memoized command that only re-executes when the command changes
33
31
  * const memoizedCmd = await Exec("status-check", {
34
32
  * command: "git status",
@@ -59,30 +57,53 @@ export const Exec = Resource("os::Exec", {
59
57
  let stdout = "";
60
58
  let stderr = "";
61
59
  let exitCode = 0;
60
+ // Default to inheriting stdio unless explicitly set to false
61
+ const inheritStdio = props.inheritStdio !== false;
62
62
  try {
63
- console.log(props.command);
64
- // Execute the command
65
- const result = await execAsync(props.command, {
63
+ // Parse the command into command and args
64
+ const [cmd, ...args] = props.command.split(/\s+/);
65
+ // Use spawn for better stdio control
66
+ const childProcess = spawn(cmd, args, {
66
67
  cwd: props.cwd || process.cwd(),
67
68
  env: { ...process.env, ...props.env },
68
- maxBuffer: props.maxBuffer || 1024 * 1024, // Default 1MB
69
+ shell: true, // Use shell to handle complex commands
70
+ stdio: inheritStdio ? "inherit" : "pipe", // Inherit stdio when requested
69
71
  });
70
- stdout = result.stdout;
71
- console.log(stdout);
72
- stderr = result.stderr;
73
- exitCode = 0; // Success
72
+ if (!inheritStdio) {
73
+ // If not inheriting stdio, collect output manually
74
+ childProcess.stdout?.on("data", (data) => {
75
+ stdout += data.toString();
76
+ });
77
+ childProcess.stderr?.on("data", (data) => {
78
+ stderr += data.toString();
79
+ });
80
+ }
81
+ // Wait for the process to complete
82
+ exitCode = await new Promise((resolve, reject) => {
83
+ childProcess.on("close", (code) => {
84
+ resolve(code || 0);
85
+ });
86
+ childProcess.on("error", (err) => {
87
+ if (props.throwOnError) {
88
+ reject(err);
89
+ }
90
+ else {
91
+ stderr += err.toString();
92
+ resolve(1);
93
+ }
94
+ });
95
+ });
96
+ if (exitCode !== 0 && props.throwOnError) {
97
+ throw new Error(`Command failed with exit code ${exitCode}: ${props.command}`);
98
+ }
74
99
  }
75
100
  catch (error) {
76
- console.log("error", error);
77
101
  if (props.throwOnError) {
78
102
  throw error;
79
103
  }
80
104
  // If not throwing, capture the error information
81
- exitCode = error.code || 1;
82
- stdout = error.stdout || "";
83
- stderr = error.stderr || String(error);
84
- console.log(stdout);
85
- console.error(stderr);
105
+ exitCode = 1;
106
+ stderr += String(error);
86
107
  }
87
108
  // Return the execution result
88
109
  return this({
@@ -90,9 +111,9 @@ export const Exec = Resource("os::Exec", {
90
111
  command: props.command,
91
112
  cwd: props.cwd,
92
113
  env: props.env,
93
- maxBuffer: props.maxBuffer,
94
114
  throwOnError: props.throwOnError,
95
115
  memoize: props.memoize,
116
+ inheritStdio,
96
117
  exitCode,
97
118
  stdout,
98
119
  stderr,
@@ -1 +1 @@
1
- {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AA2ElC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAC1B,UAAU,EACV;IACE,YAAY,EAAE,IAAI;CACnB,EACD,KAAK,WAEH,EAAU,EACV,KAAgB;IAEhB,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,iDAAiD;QACjD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;SAAM,IACL,IAAI,CAAC,KAAK,KAAK,QAAQ;QACvB,KAAK,CAAC,OAAO;QACb,IAAI,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,CAAC,OAAO,EACtC,CAAC;QACD,mFAAmF;QACnF,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,iBAAiB;QACjB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3B,sBAAsB;YACtB,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE;gBAC5C,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;gBAC/B,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE;gBACrC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,GAAG,IAAI,EAAE,cAAc;aAC1D,CAAC,CAAC;YAEH,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YACvB,QAAQ,GAAG,CAAC,CAAC,CAAC,UAAU;QAC1B,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC5B,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;gBACvB,MAAM,KAAK,CAAC;YACd,CAAC;YAED,iDAAiD;YACjD,QAAQ,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;YAC3B,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;YAC5B,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;YACvC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;QAED,8BAA8B;QAC9B,OAAO,IAAI,CAAC;YACV,EAAE;YACF,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ;YACR,MAAM;YACN,MAAM;YACN,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;YACtB,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CACF,CAAC"}
1
+ {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.12.9",
3
+ "version": "0.12.11",
4
4
  "type": "module",
5
5
  "module": "./lib/index.js",
6
6
  "scripts": {
@@ -117,22 +117,18 @@ export class CloudflareApi {
117
117
 
118
118
  // Use withExponentialBackoff for automatic retry on network errors
119
119
  return withExponentialBackoff(
120
- () =>
121
- fetch(`${this.baseUrl}${path}`, {
120
+ async () => {
121
+ const response = await fetch(`${this.baseUrl}${path}`, {
122
122
  ...init,
123
123
  headers,
124
- }),
125
- (error) => {
126
- // Only retry on network-related errors
127
- const errorMsg = (error as Error).message || "";
128
- const isNetworkError =
129
- errorMsg.includes("socket connection was closed") ||
130
- errorMsg.includes("ECONNRESET") ||
131
- errorMsg.includes("ETIMEDOUT") ||
132
- errorMsg.includes("ECONNREFUSED");
133
-
134
- return isNetworkError || error.status?.toString().startsWith("5");
124
+ });
125
+ if (response.status.toString().startsWith("5")) {
126
+ throw new InternalError("5xx error");
127
+ }
128
+ return response;
135
129
  },
130
+ // transient errors should be retried aggressively
131
+ (error) => error instanceof InternalError,
136
132
  5, // Maximum 5 attempts (1 initial + 4 retries)
137
133
  1000 // Start with 1s delay, will exponentially increase
138
134
  );
@@ -202,3 +198,9 @@ export class CloudflareApi {
202
198
  return this.fetch(path, { ...init, method: "DELETE" });
203
199
  }
204
200
  }
201
+
202
+ class InternalError extends Error {
203
+ constructor(message: string) {
204
+ super(message);
205
+ }
206
+ }
@@ -79,6 +79,11 @@ export interface BucketProps {
79
79
  * Secret Access Key to use for the bucket
80
80
  */
81
81
  secretAccessKey?: Secret;
82
+
83
+ /**
84
+ * Whether to adopt an existing bucket
85
+ */
86
+ adopt?: boolean;
82
87
  }
83
88
 
84
89
  /**
@@ -163,7 +168,6 @@ export const R2Bucket = Resource(
163
168
  const bucketName = props.name || this.id;
164
169
 
165
170
  if (this.phase === "delete") {
166
- console.log("Deleting R2 bucket:", bucketName);
167
171
  if (props.delete !== false) {
168
172
  if (props.empty) {
169
173
  console.log("Emptying R2 bucket:", bucketName);
@@ -175,8 +179,6 @@ export const R2Bucket = Resource(
175
179
  await emptyBucket(r2Client, bucketName, props.jurisdiction);
176
180
  }
177
181
 
178
- // Delete R2 bucket
179
- console.log("Deleting R2 bucket:", bucketName);
180
182
  await deleteBucket(api, bucketName, props);
181
183
  }
182
184
 
@@ -184,8 +186,17 @@ export const R2Bucket = Resource(
184
186
  return this.destroy();
185
187
  } else {
186
188
  if (this.phase === "create") {
187
- console.log("Creating R2 bucket:", bucketName);
188
- await createBucket(api, bucketName, props);
189
+ try {
190
+ await createBucket(api, bucketName, props);
191
+ } catch (err) {
192
+ if (err instanceof CloudflareApiError && err.status === 409) {
193
+ if (!props.adopt) {
194
+ throw err;
195
+ }
196
+ } else {
197
+ throw err;
198
+ }
199
+ }
189
200
  }
190
201
 
191
202
  await updatePublicAccess(
@@ -253,10 +264,15 @@ export function createR2Client(config?: R2ClientConfig): Promise<R2Client> {
253
264
  }
254
265
 
255
266
  interface CloudflareBucketResponse {
267
+ /**
268
+ * The bucket information returned from the Cloudflare REST API
269
+ * @see https://developers.cloudflare.com/api/node/resources/r2/subresources/buckets/models/bucket/#(schema)
270
+ */
256
271
  result: {
257
- name: string;
258
- location?: string;
259
272
  creation_date: string;
273
+ location?: "apac" | "eeur" | "enam" | "weur" | "wnam" | "oc";
274
+ name: string;
275
+ storage_class?: "Standard" | "InfrequentAccess";
260
276
  };
261
277
  success: boolean;
262
278
  errors: Array<{ code: number; message: string }>;
@@ -291,13 +307,45 @@ export function withJurisdiction(
291
307
  return result;
292
308
  }
293
309
 
310
+ /**
311
+ * Get a bucket
312
+ */
313
+ export async function getBucket(
314
+ api: CloudflareApi,
315
+ bucketName: string,
316
+ props: BucketProps = {}
317
+ ): Promise<CloudflareBucketResponse> {
318
+ const headers = withJurisdiction({}, props);
319
+ const getResponse = await api.get(
320
+ `/accounts/${api.accountId}/r2/buckets/${bucketName}`,
321
+ { headers }
322
+ );
323
+
324
+ if (!getResponse.ok) {
325
+ return await handleApiError(getResponse, "get", "R2 bucket", bucketName);
326
+ }
327
+
328
+ if (getResponse.status === 200) {
329
+ return (await getResponse.json()) as CloudflareBucketResponse;
330
+ }
331
+
332
+ const errorData: any = await getResponse.json().catch(() => ({
333
+ errors: [{ message: getResponse.statusText }],
334
+ }));
335
+
336
+ throw new CloudflareApiError(
337
+ `Error getting R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || getResponse.statusText}`,
338
+ getResponse
339
+ );
340
+ }
341
+
294
342
  /**
295
343
  * Create a new bucket
296
344
  */
297
345
  export async function createBucket(
298
346
  api: CloudflareApi,
299
347
  bucketName: string,
300
- props: BucketProps
348
+ props: BucketProps = {}
301
349
  ): Promise<CloudflareBucketResponse> {
302
350
  // Create new R2 bucket
303
351
  const createPayload: any = {
@@ -0,0 +1,30 @@
1
+ // https://developers.cloudflare.com/workers/runtime-apis/nodejs/#supported-nodejs-apis
2
+ const nodejs_compat = [
3
+ "node:async_hooks",
4
+ "node:assert",
5
+ "node:buffer",
6
+ "node:console",
7
+ "node:crypto",
8
+ "node:diagnostics_channel",
9
+ "node:dns",
10
+ "node:events",
11
+ "node:inspector",
12
+ "node:net",
13
+ "node:path",
14
+ "node:perf_hooks",
15
+ "node:process",
16
+ "node:querystring",
17
+ "node:stream",
18
+ "node:string_decoder",
19
+ "node:timers",
20
+ "node:tls", // partially supported
21
+ "node:url",
22
+ "node:util",
23
+ "node:zlib",
24
+ ];
25
+
26
+ export const external = [
27
+ ...nodejs_compat,
28
+ ...nodejs_compat.map((p) => p.split(":")[1]),
29
+ "cloudflare:workers",
30
+ ];
@@ -9,6 +9,7 @@ export * from "./custom-domain.js";
9
9
  export * from "./d1-database.js";
10
10
  export * from "./dns-records.js";
11
11
  export * from "./durable-object-namespace.js";
12
+ export * from "./external.js";
12
13
  export * from "./hyperdrive.js";
13
14
  export * from "./kv-namespace.js";
14
15
  export * from "./local-dev-cloudflare-shim.js";
@@ -181,8 +181,10 @@ export type PipelineDestination = R2DestinationConfig;
181
181
  export interface PipelineProps extends CloudflareApiOptions {
182
182
  /**
183
183
  * Name of the pipeline
184
+ *
185
+ * @default id
184
186
  */
185
- name: string;
187
+ name?: string;
186
188
 
187
189
  /**
188
190
  * Source configuration for the pipeline
@@ -232,6 +234,11 @@ export interface Pipeline<T extends PipelineRecord = PipelineRecord>
232
234
  */
233
235
  id: string;
234
236
 
237
+ /**
238
+ * The name of the pipeline
239
+ */
240
+ name: string;
241
+
235
242
  /**
236
243
  * HTTP endpoint URL for the pipeline
237
244
  */
@@ -315,7 +322,7 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function <
315
322
  Pipeline<T>
316
323
  > {
317
324
  const api = await createCloudflareApi(props);
318
- const pipelineName = props.name || id;
325
+ const pipelineName = props.name ?? id;
319
326
 
320
327
  if (this.phase === "delete") {
321
328
  if (props.delete !== false) {
@@ -8,6 +8,7 @@ import {
8
8
  type CloudflareApiOptions,
9
9
  createCloudflareApi,
10
10
  } from "./api.js";
11
+ import { createBucket, getBucket } from "./bucket.js";
11
12
 
12
13
  /**
13
14
  * Options for CloudflareR2StateStore
@@ -23,7 +24,7 @@ export interface CloudflareR2StateStoreOptions extends CloudflareApiOptions {
23
24
  * The R2 bucket name to use
24
25
  * Required - the bucket must already exist
25
26
  */
26
- bucketName: string;
27
+ bucketName?: string;
27
28
  }
28
29
 
29
30
  /**
@@ -44,7 +45,7 @@ export class R2RestStateStore implements StateStore {
44
45
  */
45
46
  constructor(
46
47
  public readonly scope: Scope,
47
- private readonly options: CloudflareR2StateStoreOptions
48
+ private readonly options: CloudflareR2StateStoreOptions = {}
48
49
  ) {
49
50
  // Use the scope's chain to build the prefix, similar to how FileSystemStateStore builds its directory
50
51
  const scopePath = scope.chain.join("/");
@@ -52,10 +53,7 @@ export class R2RestStateStore implements StateStore {
52
53
  ? `${options.prefix}${scopePath}/`
53
54
  : `alchemy/${scopePath}/`;
54
55
 
55
- if (!options.bucketName) {
56
- throw new Error("bucketName is required for CloudflareR2StateStore");
57
- }
58
- this.bucketName = options.bucketName;
56
+ this.bucketName = options.bucketName ?? "alchemy-state";
59
57
 
60
58
  // We'll initialize the API in init() to allow for async creation
61
59
  this.api = null as any;
@@ -70,6 +68,26 @@ export class R2RestStateStore implements StateStore {
70
68
  // Create Cloudflare API client with automatic account discovery
71
69
  this.api = await createCloudflareApi(this.options);
72
70
 
71
+ // Check if the alchemy state bucket exists
72
+ try {
73
+ await withExponentialBackoff(
74
+ () => getBucket(this.api, this.bucketName),
75
+ isRetryableError,
76
+ 5,
77
+ 1000
78
+ );
79
+ } catch (error) {
80
+ // If not, create the alchemy state bucket
81
+ if (error instanceof CloudflareApiError && error.status === 404) {
82
+ await withExponentialBackoff(
83
+ () => createBucket(this.api, this.bucketName),
84
+ isRetryableError,
85
+ 5,
86
+ 1000
87
+ );
88
+ }
89
+ }
90
+
73
91
  this.initialized = true;
74
92
  }
75
93
 
@@ -23,7 +23,7 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
23
23
  *
24
24
  * @example
25
25
  * // Deploy with a database binding
26
- * import { D1Database } from "../cloudflare/d1-database";
26
+ * import { D1Database } from alchemy/cloudflare";
27
27
  *
28
28
  * const database = await D1Database("redwood-db");
29
29
  *
@@ -33,38 +33,6 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
33
33
  * }
34
34
  * });
35
35
  *
36
- * @example
37
- * // Deploy with custom build command and environment variables
38
- * const redwoodApp = await Redwood("custom-redwood", {
39
- * command: "bun run test && RWSDK_DEPLOY=1 bun run build:production",
40
- * bindings: {
41
- * API_KEY: alchemy.secret("api-key-secret")
42
- * },
43
- * vars: {
44
- * NODE_ENV: "production",
45
- * APP_ENV: "staging"
46
- * }
47
- * });
48
- *
49
- * @example
50
- * // Deploy with custom paths and additional compatibility flags
51
- * const redwoodApp = await Redwood("redwood-custom-paths", {
52
- * main: "custom/worker.js",
53
- * assets: "custom/static",
54
- * compatibilityFlags: ["nodejs_compat", "fetch_refused_to_set_cookies"]
55
- * });
56
- *
57
- * @example
58
- * // Deploy with custom bundle configuration
59
- * const redwoodApp = await Redwood("redwood-custom-bundle", {
60
- * bundle: {
61
- * options: {
62
- * external: ["node:events", "node:stream", "node:crypto"],
63
- * format: "esm"
64
- * }
65
- * }
66
- * });
67
- *
68
36
  * @param id - Unique identifier for the RedwoodJS application
69
37
  * @param props - Configuration properties for the RedwoodJS deployment
70
38
  * @returns A Cloudflare Worker resource representing the deployed RedwoodJS application
@@ -76,21 +44,15 @@ export async function Redwood<B extends Bindings>(
76
44
  return Website(id, {
77
45
  ...props,
78
46
  command: props?.command ?? "bun run clean && RWSDK_DEPLOY=1 bun run build",
79
- wrangler: props?.wrangler ?? true,
80
- main: props?.main ?? path.join("src", "worker.tsx"),
47
+ wrangler:
48
+ props?.wrangler === false
49
+ ? false
50
+ : {
51
+ main: props?.main ?? path.join("src", "worker.tsx"),
52
+ },
53
+ main: props?.main ?? path.join("dist", "worker", "worker.js"),
81
54
  assets: props?.assets ?? path.join("dist", "client"),
82
55
  compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])],
83
56
  compatibilityDate: props?.compatibilityDate ?? "2025-04-02",
84
- bundle: {
85
- ...props?.bundle,
86
- options: {
87
- ...props?.bundle?.options,
88
- external: [
89
- "node:events",
90
- "node:stream",
91
- ...(props?.bundle?.options?.external ?? []),
92
- ],
93
- },
94
- },
95
57
  });
96
58
  }
@@ -45,7 +45,14 @@ export interface WebsiteProps<B extends Bindings>
45
45
  *
46
46
  * @default - no wrangler.jsonc file is written
47
47
  */
48
- wrangler?: boolean | string;
48
+ wrangler?:
49
+ | boolean
50
+ | string
51
+ | {
52
+ path?: string;
53
+ // override main
54
+ main?: string;
55
+ };
49
56
  }
50
57
 
51
58
  export type Website<B extends Bindings> = B extends { ASSETS: any }
@@ -66,9 +73,17 @@ export async function Website<B extends Bindings>(
66
73
 
67
74
  const cwd = path.resolve(props.cwd || process.cwd());
68
75
  const fileName =
69
- typeof props.wrangler === "boolean" ? "wrangler.jsonc" : props.wrangler;
76
+ typeof props.wrangler === "boolean"
77
+ ? "wrangler.jsonc"
78
+ : typeof props.wrangler === "string"
79
+ ? props.wrangler
80
+ : (props.wrangler?.path ?? "wrangler.jsonc");
70
81
  const wranglerPath =
71
82
  fileName && path.relative(cwd, path.join(cwd, fileName));
83
+ const wranglerMain =
84
+ typeof props.wrangler === "object"
85
+ ? (props.wrangler.main ?? props.main)
86
+ : props.main;
72
87
 
73
88
  if (props.wrangler) {
74
89
  try {
@@ -79,7 +94,7 @@ export async function Website<B extends Bindings>(
79
94
  JSON.stringify(
80
95
  {
81
96
  name: id,
82
- main: props.main,
97
+ main: wranglerMain,
83
98
  compatibility_date: new Date().toISOString().split("T")[0],
84
99
  },
85
100
  null,
@@ -128,6 +143,7 @@ export default {
128
143
  await WranglerJson("wrangler.jsonc", {
129
144
  path: wranglerPath,
130
145
  worker,
146
+ main: wranglerMain,
131
147
  });
132
148
  }
133
149
 
@@ -17,6 +17,7 @@ import type { Bindings, WorkerBindingSpec } from "./bindings.js";
17
17
  import type { Bound } from "./bound.js";
18
18
  import type { DurableObjectNamespace } from "./durable-object-namespace.js";
19
19
  import { type EventSource, isQueueEventSource } from "./event-source.js";
20
+ import { external } from "./external.js";
20
21
  import {
21
22
  QueueConsumer,
22
23
  deleteQueueConsumer,
@@ -576,8 +577,6 @@ async function putWorker(
576
577
  })
577
578
  );
578
579
 
579
- await new Promise((resolve) => setTimeout(resolve, 1000));
580
-
581
580
  // Upload worker script with bindings
582
581
  const uploadResponse = await api.put(
583
582
  `/accounts/${api.accountId}/workers/scripts/${workerName}`,
@@ -591,36 +590,22 @@ async function putWorker(
591
590
 
592
591
  // Check if the upload was successful
593
592
  if (!uploadResponse.ok) {
594
- const errorData: any = await uploadResponse
595
- .json()
596
- .catch(() => ({ errors: [{ message: uploadResponse.statusText }] }));
597
-
598
- const errorMessage = `Error (HTTP ${uploadResponse.status}) uploading worker script '${workerName}': ${errorData.errors?.[0]?.message || uploadResponse.statusText}`;
599
-
600
- if (
601
- uploadResponse.status === 400 &&
602
- errorMessage.includes("not found")
603
- ) {
604
- throw new NotFoundError(errorMessage);
605
- }
606
- throw new Error(errorMessage);
593
+ await handleApiError(
594
+ uploadResponse,
595
+ "uploading worker script",
596
+ "worker",
597
+ workerName
598
+ );
607
599
  }
608
600
 
609
601
  return formData;
610
602
  },
611
- (err) => err instanceof NotFoundError,
603
+ (err) => err.status === 404 || err.status === 500 || err.status === 503,
612
604
  10,
613
605
  100
614
606
  );
615
607
  }
616
608
 
617
- class NotFoundError extends Error {
618
- constructor(message: string) {
619
- super(message);
620
- this.name = "NotFoundError";
621
- }
622
- }
623
-
624
609
  interface WorkerMetadata {
625
610
  compatibility_date: string;
626
611
  compatibility_flags?: string[];
@@ -889,6 +874,9 @@ async function prepareWorkerMetadata<B extends Bindings>(
889
874
  // For service worker format (CJS)
890
875
  meta.body_part = scriptName;
891
876
  }
877
+ if (process.env.DEBUG) {
878
+ console.log(meta);
879
+ }
892
880
  return meta;
893
881
  }
894
882
 
@@ -931,34 +919,26 @@ async function assertWorkerDoesNotExist<B extends Bindings>(
931
919
  }
932
920
 
933
921
  async function bundleWorkerScript<B extends Bindings>(props: WorkerProps) {
934
- // Get the script content - either from props.script, or by bundling
935
-
936
- // Create and use a Bundle resource with worker-optimized configuration
937
- const defaultBundleOptions: Omit<BundleProps, "entryPoint"> = {
922
+ const bundle = await Bundle("bundle", {
923
+ entryPoint: props.entrypoint!,
938
924
  format: props.format === "cjs" ? "cjs" : "esm", // Use the specified format or default to ESM
939
- target: "es2020",
940
- platform: "browser",
925
+ target: "esnext",
926
+ platform: "neutral",
941
927
  minify: true,
928
+ ...(props.bundle || {}),
942
929
  options: {
930
+ ...(props.bundle?.options || {}),
943
931
  keepNames: true, // Important for Durable Object classes
944
932
  loader: {
945
933
  ".sql": "text",
946
934
  ".json": "json",
947
935
  },
948
936
  },
949
- };
950
-
951
- // Merge with user-provided options
952
- const bundleOptions = {
953
- ...defaultBundleOptions,
954
- ...(props.bundle || {}),
955
- };
956
-
957
- // Create the bundle
958
- const bundle = await Bundle("bundle", {
959
- entryPoint: props.entrypoint!,
960
- ...bundleOptions,
961
- external: [...(bundleOptions.external ?? []), "cloudflare:workers"],
937
+ external: [
938
+ ...external,
939
+ ...(props.bundle?.external ?? []),
940
+ ...(props.bundle?.options?.external ?? []),
941
+ ],
962
942
  });
963
943
 
964
944
  try {