alchemy 0.22.2 → 0.22.4

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 (57) hide show
  1. package/lib/cloudflare/bindings.d.ts +2 -1
  2. package/lib/cloudflare/bindings.d.ts.map +1 -1
  3. package/lib/cloudflare/bindings.js.map +1 -1
  4. package/lib/cloudflare/bound.d.ts +2 -1
  5. package/lib/cloudflare/bound.d.ts.map +1 -1
  6. package/lib/cloudflare/bundle/bundle-worker.d.ts +4 -1
  7. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
  8. package/lib/cloudflare/bundle/bundle-worker.js +35 -24
  9. package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
  10. package/lib/cloudflare/index.d.ts +1 -0
  11. package/lib/cloudflare/index.d.ts.map +1 -1
  12. package/lib/cloudflare/index.js +1 -0
  13. package/lib/cloudflare/index.js.map +1 -1
  14. package/lib/cloudflare/react-router.js +1 -1
  15. package/lib/cloudflare/version-metadata.d.ts +7 -0
  16. package/lib/cloudflare/version-metadata.d.ts.map +1 -0
  17. package/lib/cloudflare/version-metadata.js +7 -0
  18. package/lib/cloudflare/version-metadata.js.map +1 -0
  19. package/lib/cloudflare/website.d.ts.map +1 -1
  20. package/lib/cloudflare/website.js +1 -0
  21. package/lib/cloudflare/website.js.map +1 -1
  22. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  23. package/lib/cloudflare/worker-metadata.js +12 -1
  24. package/lib/cloudflare/worker-metadata.js.map +1 -1
  25. package/lib/cloudflare/worker.d.ts +25 -2
  26. package/lib/cloudflare/worker.d.ts.map +1 -1
  27. package/lib/cloudflare/worker.js +29 -11
  28. package/lib/cloudflare/worker.js.map +1 -1
  29. package/lib/cloudflare/wrangler.json.d.ts +9 -3
  30. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  31. package/lib/cloudflare/wrangler.json.js +13 -2
  32. package/lib/cloudflare/wrangler.json.js.map +1 -1
  33. package/lib/esbuild/bundle.d.ts +56 -61
  34. package/lib/esbuild/bundle.d.ts.map +1 -1
  35. package/lib/esbuild/bundle.js +2 -3
  36. package/lib/esbuild/bundle.js.map +1 -1
  37. package/lib/fs/static-json-file.d.ts +1 -0
  38. package/lib/fs/static-json-file.d.ts.map +1 -1
  39. package/lib/fs/static-json-file.js +11 -8
  40. package/lib/fs/static-json-file.js.map +1 -1
  41. package/lib/util/content-type.d.ts.map +1 -1
  42. package/lib/util/content-type.js +2 -0
  43. package/lib/util/content-type.js.map +1 -1
  44. package/package.json +92 -22
  45. package/src/cloudflare/bindings.ts +2 -0
  46. package/src/cloudflare/bound.ts +8 -5
  47. package/src/cloudflare/bundle/bundle-worker.ts +59 -25
  48. package/src/cloudflare/index.ts +1 -0
  49. package/src/cloudflare/react-router.ts +1 -1
  50. package/src/cloudflare/version-metadata.ts +6 -0
  51. package/src/cloudflare/website.ts +1 -0
  52. package/src/cloudflare/worker-metadata.ts +11 -1
  53. package/src/cloudflare/worker.ts +63 -16
  54. package/src/cloudflare/wrangler.json.ts +30 -11
  55. package/src/esbuild/bundle.ts +2 -9
  56. package/src/fs/static-json-file.ts +12 -8
  57. package/src/util/content-type.ts +2 -0
@@ -15,6 +15,7 @@ import type { QueueResource as _Queue } from "./queue.ts";
15
15
  import type { VectorizeIndexResource as _VectorizeIndex } from "./vectorize-index.ts";
16
16
  import type { Worker as _Worker } from "./worker.ts";
17
17
  import type { Workflow as _Workflow } from "./workflow.ts";
18
+ import type { VersionMetadata as _VersionMetadata } from "./version-metadata.ts";
18
19
 
19
20
  export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
20
21
  infer O
@@ -61,8 +62,10 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace<
61
62
  ? Fetcher
62
63
  : T extends _Ai<infer M>
63
64
  ? Ai<M>
64
- : T extends Self
65
- ? Service
66
- : T extends Json<infer T>
67
- ? T
68
- : Service;
65
+ : T extends _VersionMetadata
66
+ ? Fetcher
67
+ : T extends Self
68
+ ? Service
69
+ : T extends Json<infer T>
70
+ ? T
71
+ : Service;
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs/promises";
2
+ import path from "node:path";
2
3
  import { Bundle } from "../../esbuild/bundle.ts";
3
4
  import type { Bindings } from "../bindings.ts";
4
5
  import type { WorkerProps } from "../worker.ts";
@@ -12,13 +13,17 @@ import { getNodeJSCompatMode } from "./nodejs-compat-mode.ts";
12
13
  import { nodeJsCompatPlugin } from "./nodejs-compat.ts";
13
14
  import { wasmPlugin } from "./wasm-plugin.ts";
14
15
 
16
+ export type NoBundleResult = {
17
+ [fileName: string]: Buffer;
18
+ };
19
+
15
20
  export async function bundleWorkerScript<B extends Bindings>(
16
21
  props: WorkerProps<B> & {
17
22
  entrypoint: string;
18
23
  compatibilityDate: string;
19
24
  compatibilityFlags: string[];
20
25
  },
21
- ) {
26
+ ): Promise<string | NoBundleResult> {
22
27
  const projectRoot = props.projectRoot ?? process.cwd();
23
28
 
24
29
  const nodeJsCompatMode = await getNodeJSCompatMode(
@@ -33,6 +38,40 @@ export async function bundleWorkerScript<B extends Bindings>(
33
38
  }
34
39
  const main = props.entrypoint;
35
40
 
41
+ if (props.noBundle) {
42
+ const rootDir = path.dirname(path.resolve(main));
43
+ const rules = (
44
+ props.rules ?? [
45
+ {
46
+ globs: ["**/*.js", "**/*.mjs", "**/*.wasm"],
47
+ },
48
+ ]
49
+ ).flatMap((rule) => rule.globs);
50
+ const files = Array.from(
51
+ new Set(
52
+ (
53
+ await Promise.all(
54
+ rules.map((rule) =>
55
+ Array.fromAsync(
56
+ fs.glob(rule, {
57
+ cwd: rootDir,
58
+ }),
59
+ ),
60
+ ),
61
+ )
62
+ ).flat(),
63
+ ),
64
+ );
65
+ return Object.fromEntries(
66
+ await Promise.all(
67
+ files.map(async (file) => [
68
+ file,
69
+ await fs.readFile(path.resolve(rootDir, file)),
70
+ ]),
71
+ ),
72
+ );
73
+ }
74
+
36
75
  try {
37
76
  const bundle = await Bundle("bundle", {
38
77
  entryPoint: main,
@@ -42,34 +81,29 @@ export async function bundleWorkerScript<B extends Bindings>(
42
81
  minify: false,
43
82
  ...(props.bundle || {}),
44
83
  conditions: ["workerd", "worker", "browser"],
45
- options: {
46
- absWorkingDir: projectRoot,
47
- ...(props.bundle?.options || {}),
48
- keepNames: true, // Important for Durable Object classes
49
- loader: {
50
- ".sql": "text",
51
- ".json": "json",
52
- ...props.bundle?.loader,
53
- ...props.bundle?.options?.loader,
54
- },
55
- plugins: [
56
- wasmPlugin,
57
- ...(props.bundle?.plugins ?? []),
58
- ...(nodeJsCompatMode === "v2" ? [await nodeJsCompatPlugin()] : []),
59
- ...(props.bundle?.alias
60
- ? [
61
- createAliasPlugin({
62
- alias: props.bundle?.alias,
63
- projectRoot,
64
- }),
65
- ]
66
- : []),
67
- ],
84
+ absWorkingDir: projectRoot,
85
+ keepNames: true, // Important for Durable Object classes
86
+ loader: {
87
+ ".sql": "text",
88
+ ".json": "json",
89
+ ...props.bundle?.loader,
68
90
  },
91
+ plugins: [
92
+ wasmPlugin,
93
+ ...(props.bundle?.plugins ?? []),
94
+ ...(nodeJsCompatMode === "v2" ? [await nodeJsCompatPlugin()] : []),
95
+ ...(props.bundle?.alias
96
+ ? [
97
+ createAliasPlugin({
98
+ alias: props.bundle?.alias,
99
+ projectRoot,
100
+ }),
101
+ ]
102
+ : []),
103
+ ],
69
104
  external: [
70
105
  ...(nodeJsCompatMode === "als" ? external_als : external),
71
106
  ...(props.bundle?.external ?? []),
72
- ...(props.bundle?.options?.external ?? []),
73
107
  ],
74
108
  });
75
109
  if (bundle.content) {
@@ -36,5 +36,6 @@ export * from "./vite.ts";
36
36
  export * from "./website.ts";
37
37
  export * from "./worker.ts";
38
38
  export { Workflow } from "./workflow.ts";
39
+ export * from "./version-metadata.ts";
39
40
  export * from "./wrangler.json.ts";
40
41
  export * from "./zone.ts";
@@ -18,7 +18,7 @@ export async function ReactRouter<B extends Bindings>(
18
18
  const defaultAssets = path.join("build", "client");
19
19
  return Website(id, {
20
20
  ...props,
21
- // Alchemy should bundle the result of `vite built`, not the user's main
21
+ // Alchemy should bundle the result of `vite build`, not the user's main
22
22
  // TODO: we probably need bundling to properly handle WASM/rules
23
23
  main: path.join(props.cwd ?? process.cwd(), "build", "server", "index.js"),
24
24
  wrangler: {
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/
3
+ */
4
+ export class VersionMetadata {
5
+ public readonly type = "version_metadata";
6
+ }
@@ -131,6 +131,7 @@ export default {
131
131
  await Exec("build", {
132
132
  cwd,
133
133
  command: props.command,
134
+ env: props.env,
134
135
  });
135
136
 
136
137
  return (await Worker("worker", {
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import type { Context } from "../context.ts";
2
3
  import { slugify } from "../util/slugify.ts";
3
4
  import {
@@ -460,6 +461,11 @@ export async function prepareWorkerMetadata<B extends Bindings>(
460
461
  name: bindingName,
461
462
  dataset: binding.dataset,
462
463
  });
464
+ } else if (binding.type === "version_metadata") {
465
+ meta.bindings.push({
466
+ type: "version_metadata",
467
+ name: bindingName,
468
+ });
463
469
  } else {
464
470
  // @ts-expect-error - we should never reach here
465
471
  throw new Error(`Unsupported binding type: ${binding.type}`);
@@ -525,7 +531,11 @@ export async function prepareWorkerMetadata<B extends Bindings>(
525
531
 
526
532
  // Determine if we're using ESM or service worker format
527
533
  const isEsModule = props.format !== "cjs"; // Default to ESM unless CJS is specified
528
- const scriptName = isEsModule ? "worker.js" : "script";
534
+ const scriptName = props.noBundle
535
+ ? path.basename(props.entrypoint!)
536
+ : isEsModule
537
+ ? "worker.js"
538
+ : "script";
529
539
 
530
540
  if (isEsModule) {
531
541
  // For ES modules format
@@ -9,6 +9,7 @@ import { Scope } from "../scope.ts";
9
9
  import { Secret, secret } from "../secret.ts";
10
10
  import { serializeScope } from "../serde.ts";
11
11
  import type { type } from "../type.ts";
12
+ import { getContentType } from "../util/content-type.ts";
12
13
  import { withExponentialBackoff } from "../util/retry.ts";
13
14
  import { slugify } from "../util/slugify.ts";
14
15
  import { CloudflareApiError, handleApiError } from "./api-error.ts";
@@ -26,7 +27,10 @@ import {
26
27
  } from "./bindings.ts";
27
28
  import type { Bound } from "./bound.ts";
28
29
  import { isBucket } from "./bucket.ts";
29
- import { bundleWorkerScript } from "./bundle/bundle-worker.ts";
30
+ import {
31
+ type NoBundleResult,
32
+ bundleWorkerScript,
33
+ } from "./bundle/bundle-worker.ts";
30
34
  import { isD1Database } from "./d1-database.ts";
31
35
  import {
32
36
  DurableObjectNamespace,
@@ -225,14 +229,38 @@ export interface InlineWorkerProps<
225
229
  > extends BaseWorkerProps<B, RPC> {
226
230
  script: string;
227
231
  entrypoint?: undefined;
232
+ noBundle?: false;
228
233
  }
229
234
 
230
235
  export interface EntrypointWorkerProps<
231
236
  B extends Bindings | undefined = Bindings,
232
237
  RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
233
238
  > extends BaseWorkerProps<B, RPC> {
234
- entrypoint: string;
235
239
  script?: undefined;
240
+ /**
241
+ * The entrypoint for the worker script.
242
+ */
243
+ entrypoint: string;
244
+
245
+ /**
246
+ * Whether to disable bundling of the worker script.
247
+ *
248
+ * If true, the worker script and any files it imports will be deployed in the Worker.
249
+ *
250
+ * @default false
251
+ */
252
+ noBundle?: boolean;
253
+
254
+ /**
255
+ * Rules for adding additional files to the bundle.
256
+ *
257
+ * If {@link noBundle} is false | undefined, this will be ignored.
258
+ *
259
+ * @default - all .js, .mjs, and .wasm files under the entrypoint directory
260
+ */
261
+ rules?: {
262
+ globs: string[];
263
+ }[];
236
264
  }
237
265
 
238
266
  /**
@@ -695,6 +723,10 @@ export const _Worker = Resource(
695
723
  id: string,
696
724
  props: WorkerProps<B>,
697
725
  ): Promise<Worker<B>> {
726
+ if (props.noBundle && !props.entrypoint) {
727
+ throw new Error("entrypoint must be provided when noBundle is true");
728
+ }
729
+
698
730
  // Create Cloudflare API client with automatic account discovery
699
731
  const api = await createCloudflareApi(props);
700
732
 
@@ -713,7 +745,7 @@ export const _Worker = Resource(
713
745
  const oldTags = oldMetadata?.default_environment?.script?.tags;
714
746
 
715
747
  // Get the script content - either from props.script, or by bundling
716
- const scriptContent =
748
+ const scriptBundle =
717
749
  props.script ??
718
750
  (await bundleWorkerScript({
719
751
  ...props,
@@ -767,7 +799,7 @@ export const _Worker = Resource(
767
799
  assetUploadResult,
768
800
  );
769
801
 
770
- await putWorker(api, workerName, scriptContent, scriptMetadata);
802
+ await putWorker(api, workerName, scriptBundle, scriptMetadata);
771
803
 
772
804
  for (const workflow of workflowsBindings) {
773
805
  await upsertWorkflow(api, {
@@ -824,16 +856,18 @@ export const _Worker = Resource(
824
856
  }
825
857
  }
826
858
 
827
- return { scriptContent, scriptMetadata, workerUrl, now };
859
+ return { scriptBundle, scriptMetadata, workerUrl, now };
828
860
  };
829
861
 
830
862
  if (this.phase === "delete") {
831
863
  // Delete any queue consumers attached to this worker first
832
864
  await deleteQueueConsumers(this, api, workerName);
833
865
 
866
+ // @ts-ignore
834
867
  await uploadWorkerScript({
835
868
  ...props,
836
869
  entrypoint: undefined,
870
+ noBundle: false,
837
871
  script:
838
872
  props.format === "cjs"
839
873
  ? "addEventListener('fetch', event => { event.respondWith(new Response('hello world')); });"
@@ -960,7 +994,7 @@ export async function deleteWorker<B extends Bindings>(
960
994
  export async function putWorker(
961
995
  api: CloudflareApi,
962
996
  workerName: string,
963
- scriptContent: string,
997
+ scriptBundle: string | NoBundleResult,
964
998
  scriptMetadata: WorkerMetadata,
965
999
  ) {
966
1000
  return withExponentialBackoff(
@@ -971,16 +1005,29 @@ export async function putWorker(
971
1005
  // Create FormData for the upload
972
1006
  const formData = new FormData();
973
1007
 
974
- // Add the actual script content as a named file part
975
- formData.append(
976
- scriptName,
977
- new Blob([scriptContent], {
978
- type: scriptMetadata.main_module
979
- ? "application/javascript+module"
980
- : "application/javascript",
981
- }),
982
- scriptName,
983
- );
1008
+ function addFile(fileName: string, content: Buffer | string) {
1009
+ const contentType = getContentType(fileName) ?? "application/null";
1010
+ formData.append(
1011
+ fileName,
1012
+ new Blob([content], {
1013
+ type:
1014
+ contentType === "application/javascript" &&
1015
+ scriptMetadata.main_module
1016
+ ? "application/javascript+module"
1017
+ : contentType,
1018
+ }),
1019
+ fileName,
1020
+ );
1021
+ }
1022
+
1023
+ if (typeof scriptBundle === "string") {
1024
+ addFile(scriptName, scriptBundle);
1025
+ } else {
1026
+ for (const [fileName, content] of Object.entries(scriptBundle)) {
1027
+ // Add the actual script content as a named file part
1028
+ addFile(fileName, content);
1029
+ }
1030
+ }
984
1031
 
985
1032
  // Add metadata as JSON
986
1033
  formData.append(
@@ -1,13 +1,15 @@
1
- import type { Context } from "../context.js";
2
- import { StaticJsonFile } from "../fs/static-json-file.js";
3
- import { Resource } from "../resource.js";
4
- import { assertNever } from "../util/assert-never.js";
5
- import { Self, type Bindings } from "./bindings.js";
6
- import type { DurableObjectNamespace } from "./durable-object-namespace.js";
7
- import type { EventSource } from "./event-source.js";
8
- import { isQueueEventSource } from "./event-source.js";
9
- import { isQueue } from "./queue.js";
10
- import type { Worker, WorkerProps } from "./worker.js";
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type { Context } from "../context.ts";
4
+ import { formatJson } from "../fs/static-json-file.ts";
5
+ import { Resource } from "../resource.ts";
6
+ import { assertNever } from "../util/assert-never.ts";
7
+ import { Self, type Bindings } from "./bindings.ts";
8
+ import type { DurableObjectNamespace } from "./durable-object-namespace.ts";
9
+ import type { EventSource } from "./event-source.ts";
10
+ import { isQueueEventSource } from "./event-source.ts";
11
+ import { isQueue } from "./queue.ts";
12
+ import type { Worker, WorkerProps } from "./worker.ts";
11
13
 
12
14
  /**
13
15
  * Properties for wrangler.json configuration file
@@ -126,7 +128,8 @@ export const WranglerJson = Resource(
126
128
  spec.triggers = { crons: worker.crons };
127
129
  }
128
130
 
129
- await StaticJsonFile(filePath, spec);
131
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
132
+ await fs.writeFile(filePath, await formatJson(spec));
130
133
 
131
134
  // Return the resource
132
135
  return this({
@@ -336,6 +339,13 @@ export interface WranglerJsonSpec {
336
339
  * Pipelines
337
340
  */
338
341
  pipelines?: { binding: string; pipeline: string }[];
342
+
343
+ /**
344
+ * Version metadata bindings
345
+ */
346
+ version_metadata?: {
347
+ binding: string;
348
+ };
339
349
  }
340
350
 
341
351
  /**
@@ -513,6 +523,15 @@ function processBindings(
513
523
  });
514
524
  } else if (binding.type === "ai_gateway") {
515
525
  // no-op
526
+ } else if (binding.type === "version_metadata") {
527
+ if (spec.version_metadata) {
528
+ throw new Error(
529
+ `Version metadata already bound to ${spec.version_metadata.binding}`,
530
+ );
531
+ }
532
+ spec.version_metadata = {
533
+ binding: bindingName,
534
+ };
516
535
  } else if (binding.type === "hyperdrive") {
517
536
  const password =
518
537
  "password" in binding.origin
@@ -67,12 +67,6 @@ export interface BundleProps extends Partial<esbuild.BuildOptions> {
67
67
  * neutral: Platform-agnostic
68
68
  */
69
69
  platform?: "browser" | "node" | "neutral";
70
-
71
- /**
72
- * Additional esbuild options
73
- * Any other valid esbuild BuildOptions
74
- */
75
- options?: Partial<esbuild.BuildOptions>;
76
70
  }
77
71
 
78
72
  /**
@@ -185,10 +179,9 @@ export const Bundle = Resource(
185
179
  );
186
180
 
187
181
  export async function bundle(props: BundleProps) {
188
- const { entryPoint, options: _, ...rest } = props;
182
+ const { entryPoint, ...rest } = props;
189
183
  const options = {
190
184
  ...rest,
191
- ...props.options,
192
185
  write: !(props.outdir === undefined && props.outfile === undefined),
193
186
  // write:
194
187
  // props.outdir === undefined && props.outfile === undefined ? false : true,
@@ -201,7 +194,7 @@ export async function bundle(props: BundleProps) {
201
194
  target: props.target,
202
195
  minify: props.minify,
203
196
  sourcemap: props.sourcemap,
204
- external: [...(props.external ?? []), ...(props.options?.external ?? [])],
197
+ external: props.external,
205
198
  platform: props.platform,
206
199
  metafile: true,
207
200
  };
@@ -20,15 +20,19 @@ export async function StaticJsonFile(
20
20
  ...args: [content: any] | [path: string, content: any]
21
21
  ): Promise<StaticJsonFile> {
22
22
  const [path, content] = args.length === 1 ? [id, args[0]] : args;
23
- const prettier = await import("prettier");
24
23
  return File(id, {
25
24
  path,
26
- content: await prettier.format(JSON.stringify(content), {
27
- parser: "json",
28
- editor: {
29
- tabWidth: 2,
30
- indentWidth: 2,
31
- },
32
- }),
25
+ content: await formatJson(content),
26
+ });
27
+ }
28
+
29
+ export async function formatJson(content: any) {
30
+ const prettier = await import("prettier");
31
+ return prettier.format(JSON.stringify(content), {
32
+ parser: "json",
33
+ editor: {
34
+ tabWidth: 2,
35
+ indentWidth: 2,
36
+ },
33
37
  });
34
38
  }
@@ -6,6 +6,7 @@ const mimeTypes: Record<string, string> = {
6
6
  ".htm": "text/html",
7
7
  ".css": "text/css",
8
8
  ".js": "application/javascript",
9
+ ".mjs": "application/javascript+module",
9
10
  ".json": "application/json",
10
11
  ".xml": "application/xml",
11
12
  ".txt": "text/plain",
@@ -28,6 +29,7 @@ const mimeTypes: Record<string, string> = {
28
29
  ".webm": "video/webm",
29
30
  ".mp3": "audio/mpeg",
30
31
  ".wav": "audio/wav",
32
+ ".wasm": "application/wasm",
31
33
  };
32
34
 
33
35
  /**