alchemy 0.44.4 → 0.45.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.
Files changed (263) hide show
  1. package/drizzle/0000_busy_mauler.sql +19 -0
  2. package/drizzle/0001_concerned_iceman.sql +20 -0
  3. package/drizzle/meta/0000_snapshot.json +137 -0
  4. package/drizzle/meta/0001_snapshot.json +106 -0
  5. package/drizzle/meta/_journal.json +20 -0
  6. package/lib/alchemy.d.ts +3 -3
  7. package/lib/alchemy.d.ts.map +1 -1
  8. package/lib/alchemy.js +5 -2
  9. package/lib/alchemy.js.map +1 -1
  10. package/lib/build-date.d.ts +1 -1
  11. package/lib/build-date.js +1 -1
  12. package/lib/cloudflare/auth.js +1 -1
  13. package/lib/cloudflare/auth.js.map +1 -1
  14. package/lib/cloudflare/bundle/esbuild.d.ts +16 -0
  15. package/lib/cloudflare/bundle/esbuild.d.ts.map +1 -0
  16. package/lib/cloudflare/bundle/esbuild.js +159 -0
  17. package/lib/cloudflare/bundle/esbuild.js.map +1 -0
  18. package/lib/cloudflare/bundle/fs.d.ts +18 -0
  19. package/lib/cloudflare/bundle/fs.d.ts.map +1 -0
  20. package/lib/cloudflare/bundle/fs.js +75 -0
  21. package/lib/cloudflare/bundle/fs.js.map +1 -0
  22. package/lib/cloudflare/bundle/index.d.ts +3 -0
  23. package/lib/cloudflare/bundle/index.d.ts.map +1 -0
  24. package/lib/cloudflare/bundle/index.js +2 -0
  25. package/lib/cloudflare/bundle/index.js.map +1 -0
  26. package/lib/cloudflare/bundle/inline.d.ts +12 -0
  27. package/lib/cloudflare/bundle/inline.d.ts.map +1 -0
  28. package/lib/cloudflare/bundle/inline.js +32 -0
  29. package/lib/cloudflare/bundle/inline.js.map +1 -0
  30. package/lib/cloudflare/bundle/normalize.d.ts +21 -0
  31. package/lib/cloudflare/bundle/normalize.d.ts.map +1 -0
  32. package/lib/cloudflare/bundle/normalize.js +49 -0
  33. package/lib/cloudflare/bundle/normalize.js.map +1 -0
  34. package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.d.ts → plugin-compat-warning.d.ts} +2 -2
  35. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +1 -0
  36. package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.js → plugin-compat-warning.js} +3 -3
  37. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +1 -0
  38. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +8 -0
  39. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -0
  40. package/lib/cloudflare/bundle/plugin-hot-reload.js +19 -0
  41. package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -0
  42. package/lib/cloudflare/bundle/{nodejs-compat.d.ts → plugin-hybrid-node-compat.d.ts} +2 -2
  43. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -0
  44. package/lib/cloudflare/bundle/{nodejs-compat.js → plugin-hybrid-node-compat.js} +10 -10
  45. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -0
  46. package/lib/cloudflare/bundle/plugin-wasm.d.ts +3 -0
  47. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -0
  48. package/lib/cloudflare/bundle/plugin-wasm.js +55 -0
  49. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -0
  50. package/lib/cloudflare/bundle/shared.d.ts +30 -0
  51. package/lib/cloudflare/bundle/shared.d.ts.map +1 -0
  52. package/lib/cloudflare/bundle/shared.js +34 -0
  53. package/lib/cloudflare/bundle/shared.js.map +1 -0
  54. package/lib/cloudflare/bundle/{nodejs-compat-mode.d.ts → validate-node-compat.d.ts} +8 -4
  55. package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -0
  56. package/lib/cloudflare/bundle/{nodejs-compat-mode.js → validate-node-compat.js} +27 -7
  57. package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -0
  58. package/lib/cloudflare/container.d.ts +231 -0
  59. package/lib/cloudflare/container.d.ts.map +1 -1
  60. package/lib/cloudflare/container.js +7 -0
  61. package/lib/cloudflare/container.js.map +1 -1
  62. package/lib/cloudflare/d1-migrations.d.ts +1 -0
  63. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  64. package/lib/cloudflare/d1-migrations.js +18 -6
  65. package/lib/cloudflare/d1-migrations.js.map +1 -1
  66. package/lib/cloudflare/d1-state-store.d.ts +10 -0
  67. package/lib/cloudflare/d1-state-store.d.ts.map +1 -0
  68. package/lib/cloudflare/d1-state-store.js +70 -0
  69. package/lib/cloudflare/d1-state-store.js.map +1 -0
  70. package/lib/cloudflare/do-state-store/internal.js +1 -1
  71. package/lib/cloudflare/do-state-store/internal.js.map +1 -1
  72. package/lib/cloudflare/do-state-store/store.js +1 -1
  73. package/lib/cloudflare/do-state-store/store.js.map +1 -1
  74. package/lib/cloudflare/index.d.ts +3 -2
  75. package/lib/cloudflare/index.d.ts.map +1 -1
  76. package/lib/cloudflare/index.js +3 -2
  77. package/lib/cloudflare/index.js.map +1 -1
  78. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +1 -0
  79. package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.js → local-dev-cloudflare-shim.js} +1 -1
  80. package/lib/cloudflare/local-dev-cloudflare-shim.js.map +1 -0
  81. package/lib/cloudflare/miniflare/http-server.d.ts.map +1 -0
  82. package/lib/cloudflare/miniflare/http-server.js.map +1 -0
  83. package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.d.ts +4 -3
  84. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -0
  85. package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.js +6 -3
  86. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -0
  87. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -0
  88. package/lib/cloudflare/{worker → miniflare}/miniflare.js +1 -1
  89. package/lib/cloudflare/miniflare/miniflare.js.map +1 -0
  90. package/lib/cloudflare/miniflare/mixed-mode.d.ts.map +1 -0
  91. package/lib/cloudflare/{worker → miniflare}/mixed-mode.js +1 -2
  92. package/lib/cloudflare/miniflare/mixed-mode.js.map +1 -0
  93. package/lib/cloudflare/user.js +1 -1
  94. package/lib/cloudflare/user.js.map +1 -1
  95. package/lib/cloudflare/worker/shared.d.ts +4 -0
  96. package/lib/cloudflare/worker/shared.d.ts.map +1 -0
  97. package/lib/cloudflare/worker/{get-worker-template.js → shared.js} +10 -1
  98. package/lib/cloudflare/worker/shared.js.map +1 -0
  99. package/lib/cloudflare/worker/tail.d.ts +18 -0
  100. package/lib/cloudflare/worker/tail.d.ts.map +1 -0
  101. package/lib/cloudflare/worker/tail.js +140 -0
  102. package/lib/cloudflare/worker/tail.js.map +1 -0
  103. package/lib/cloudflare/worker-metadata.d.ts +6 -2
  104. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  105. package/lib/cloudflare/worker-metadata.js +7 -23
  106. package/lib/cloudflare/worker-metadata.js.map +1 -1
  107. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  108. package/lib/cloudflare/worker-stub.js +8 -7
  109. package/lib/cloudflare/worker-stub.js.map +1 -1
  110. package/lib/cloudflare/worker-subdomain.d.ts +31 -0
  111. package/lib/cloudflare/worker-subdomain.d.ts.map +1 -0
  112. package/lib/cloudflare/worker-subdomain.js +55 -0
  113. package/lib/cloudflare/worker-subdomain.js.map +1 -0
  114. package/lib/cloudflare/worker.d.ts +30 -36
  115. package/lib/cloudflare/worker.d.ts.map +1 -1
  116. package/lib/cloudflare/worker.js +641 -569
  117. package/lib/cloudflare/worker.js.map +1 -1
  118. package/lib/fs/file-system-state-store.js +1 -1
  119. package/lib/fs/file-system-state-store.js.map +1 -1
  120. package/lib/scope.d.ts +2 -2
  121. package/lib/scope.d.ts.map +1 -1
  122. package/lib/scope.js +23 -8
  123. package/lib/scope.js.map +1 -1
  124. package/lib/serde.d.ts +1 -1
  125. package/lib/serde.d.ts.map +1 -1
  126. package/lib/serde.js +4 -4
  127. package/lib/serde.js.map +1 -1
  128. package/lib/sqlite/base-sqlite-state-store.d.ts +31 -0
  129. package/lib/sqlite/base-sqlite-state-store.d.ts.map +1 -0
  130. package/lib/sqlite/base-sqlite-state-store.js +124 -0
  131. package/lib/sqlite/base-sqlite-state-store.js.map +1 -0
  132. package/lib/sqlite/index.d.ts +2 -0
  133. package/lib/sqlite/index.d.ts.map +1 -0
  134. package/lib/sqlite/index.js +2 -0
  135. package/lib/sqlite/index.js.map +1 -0
  136. package/lib/sqlite/schema.d.ts +198 -0
  137. package/lib/sqlite/schema.d.ts.map +1 -0
  138. package/lib/sqlite/schema.js +23 -0
  139. package/lib/sqlite/schema.js.map +1 -0
  140. package/lib/sqlite/sqlite-state-store.d.ts +60 -0
  141. package/lib/sqlite/sqlite-state-store.d.ts.map +1 -0
  142. package/lib/sqlite/sqlite-state-store.js +83 -0
  143. package/lib/sqlite/sqlite-state-store.js.map +1 -0
  144. package/lib/test/bun.d.ts.map +1 -1
  145. package/lib/test/bun.js +0 -6
  146. package/lib/test/bun.js.map +1 -1
  147. package/lib/test/vitest.d.ts.map +1 -1
  148. package/lib/test/vitest.js +19 -5
  149. package/lib/test/vitest.js.map +1 -1
  150. package/lib/util/memoize.d.ts +1 -1
  151. package/lib/util/memoize.d.ts.map +1 -1
  152. package/lib/util/memoize.js +11 -1
  153. package/lib/util/memoize.js.map +1 -1
  154. package/package.json +13 -3
  155. package/src/alchemy.ts +8 -6
  156. package/src/build-date.ts +1 -1
  157. package/src/cloudflare/auth.ts +1 -1
  158. package/src/cloudflare/bundle/esbuild.ts +194 -0
  159. package/src/cloudflare/bundle/fs.ts +98 -0
  160. package/src/cloudflare/bundle/index.ts +6 -0
  161. package/src/cloudflare/bundle/inline.ts +42 -0
  162. package/src/cloudflare/bundle/normalize.ts +76 -0
  163. package/src/cloudflare/bundle/{nodejs-import-warning-plugin.ts → plugin-compat-warning.ts} +4 -2
  164. package/src/cloudflare/bundle/plugin-hot-reload.ts +27 -0
  165. package/src/cloudflare/bundle/{nodejs-compat.ts → plugin-hybrid-node-compat.ts} +10 -10
  166. package/src/cloudflare/bundle/plugin-wasm.ts +62 -0
  167. package/src/cloudflare/bundle/shared.ts +73 -0
  168. package/src/cloudflare/bundle/{nodejs-compat-mode.ts → validate-node-compat.ts} +41 -16
  169. package/src/cloudflare/container.ts +296 -0
  170. package/src/cloudflare/d1-migrations.ts +29 -16
  171. package/src/cloudflare/d1-state-store.ts +105 -0
  172. package/src/cloudflare/do-state-store/internal.ts +1 -1
  173. package/src/cloudflare/do-state-store/store.ts +1 -1
  174. package/src/cloudflare/index.ts +3 -2
  175. package/src/cloudflare/{bundle/local-dev-cloudflare-shim.ts → local-dev-cloudflare-shim.ts} +1 -1
  176. package/src/cloudflare/{worker → miniflare}/miniflare-worker-options.ts +14 -6
  177. package/src/cloudflare/{worker → miniflare}/miniflare.ts +1 -1
  178. package/src/cloudflare/{worker → miniflare}/mixed-mode.ts +1 -2
  179. package/src/cloudflare/user.ts +1 -1
  180. package/src/cloudflare/worker/{subdomain.ts → shared.ts} +14 -0
  181. package/src/cloudflare/worker/tail.ts +287 -0
  182. package/src/cloudflare/worker-metadata.ts +18 -26
  183. package/src/cloudflare/worker-stub.ts +9 -7
  184. package/src/cloudflare/worker-subdomain.ts +123 -0
  185. package/src/cloudflare/worker.ts +863 -760
  186. package/src/fs/file-system-state-store.ts +1 -1
  187. package/src/scope.ts +30 -11
  188. package/src/serde.ts +4 -4
  189. package/src/sqlite/base-sqlite-state-store.ts +199 -0
  190. package/src/sqlite/index.ts +1 -0
  191. package/src/sqlite/schema.ts +37 -0
  192. package/src/sqlite/sqlite-state-store.ts +174 -0
  193. package/src/test/bun.ts +0 -8
  194. package/src/test/vitest.ts +19 -8
  195. package/src/util/memoize.ts +13 -1
  196. package/templates/astro/types/env.d.ts +1 -1
  197. package/templates/nuxt/types/env.d.ts +1 -1
  198. package/templates/react-router/types/env.d.ts +1 -1
  199. package/templates/rwsdk/types/env.d.ts +1 -1
  200. package/templates/sveltekit/types/env.d.ts +1 -1
  201. package/templates/tanstack-start/types/env.d.ts +1 -1
  202. package/templates/typescript/types/env.d.ts +1 -1
  203. package/templates/vite/types/env.d.ts +1 -1
  204. package/lib/cloudflare/bundle/alias-plugin.d.ts +0 -6
  205. package/lib/cloudflare/bundle/alias-plugin.d.ts.map +0 -1
  206. package/lib/cloudflare/bundle/alias-plugin.js +0 -36
  207. package/lib/cloudflare/bundle/alias-plugin.js.map +0 -1
  208. package/lib/cloudflare/bundle/build-failures.d.ts +0 -12
  209. package/lib/cloudflare/bundle/build-failures.d.ts.map +0 -1
  210. package/lib/cloudflare/bundle/build-failures.js +0 -45
  211. package/lib/cloudflare/bundle/build-failures.js.map +0 -1
  212. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +0 -26
  213. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +0 -1
  214. package/lib/cloudflare/bundle/bundle-worker-dev.js +0 -88
  215. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +0 -1
  216. package/lib/cloudflare/bundle/bundle-worker.d.ts +0 -13
  217. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +0 -1
  218. package/lib/cloudflare/bundle/bundle-worker.js +0 -98
  219. package/lib/cloudflare/bundle/bundle-worker.js.map +0 -1
  220. package/lib/cloudflare/bundle/external.d.ts +0 -4
  221. package/lib/cloudflare/bundle/external.d.ts.map +0 -1
  222. package/lib/cloudflare/bundle/external.js +0 -40
  223. package/lib/cloudflare/bundle/external.js.map +0 -1
  224. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.d.ts.map +0 -1
  225. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.js.map +0 -1
  226. package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +0 -1
  227. package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +0 -1
  228. package/lib/cloudflare/bundle/nodejs-compat.d.ts.map +0 -1
  229. package/lib/cloudflare/bundle/nodejs-compat.js.map +0 -1
  230. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts.map +0 -1
  231. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js.map +0 -1
  232. package/lib/cloudflare/bundle/wasm-plugin.d.ts +0 -3
  233. package/lib/cloudflare/bundle/wasm-plugin.d.ts.map +0 -1
  234. package/lib/cloudflare/bundle/wasm-plugin.js +0 -53
  235. package/lib/cloudflare/bundle/wasm-plugin.js.map +0 -1
  236. package/lib/cloudflare/worker/get-worker-template.d.ts +0 -2
  237. package/lib/cloudflare/worker/get-worker-template.d.ts.map +0 -1
  238. package/lib/cloudflare/worker/get-worker-template.js.map +0 -1
  239. package/lib/cloudflare/worker/http-server.d.ts.map +0 -1
  240. package/lib/cloudflare/worker/http-server.js.map +0 -1
  241. package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +0 -1
  242. package/lib/cloudflare/worker/miniflare-worker-options.js.map +0 -1
  243. package/lib/cloudflare/worker/miniflare.d.ts.map +0 -1
  244. package/lib/cloudflare/worker/miniflare.js.map +0 -1
  245. package/lib/cloudflare/worker/mixed-mode.d.ts.map +0 -1
  246. package/lib/cloudflare/worker/mixed-mode.js.map +0 -1
  247. package/lib/cloudflare/worker/subdomain.d.ts +0 -3
  248. package/lib/cloudflare/worker/subdomain.d.ts.map +0 -1
  249. package/lib/cloudflare/worker/subdomain.js +0 -10
  250. package/lib/cloudflare/worker/subdomain.js.map +0 -1
  251. package/src/cloudflare/bundle/alias-plugin.ts +0 -48
  252. package/src/cloudflare/bundle/build-failures.ts +0 -55
  253. package/src/cloudflare/bundle/bundle-worker-dev.ts +0 -129
  254. package/src/cloudflare/bundle/bundle-worker.ts +0 -150
  255. package/src/cloudflare/bundle/external.ts +0 -41
  256. package/src/cloudflare/bundle/wasm-plugin.ts +0 -57
  257. package/src/cloudflare/worker/get-worker-template.ts +0 -14
  258. /package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.d.ts → local-dev-cloudflare-shim.d.ts} +0 -0
  259. /package/lib/cloudflare/{worker → miniflare}/http-server.d.ts +0 -0
  260. /package/lib/cloudflare/{worker → miniflare}/http-server.js +0 -0
  261. /package/lib/cloudflare/{worker → miniflare}/miniflare.d.ts +0 -0
  262. /package/lib/cloudflare/{worker → miniflare}/mixed-mode.d.ts +0 -0
  263. /package/src/cloudflare/{worker → miniflare}/http-server.ts +0 -0
package/src/alchemy.ts CHANGED
@@ -34,12 +34,14 @@ function parseCliArgs(): Partial<AlchemyOptions> {
34
34
  options.phase = "read";
35
35
  }
36
36
 
37
- if (
38
- args.includes("--dev") ||
37
+ if (args.includes("--local") || args.includes("--dev")) {
38
+ options.dev = "prefer-local";
39
+ } else if (
40
+ args.includes("--remote") ||
39
41
  args.includes("--watch") ||
40
42
  process.execArgv.includes("--watch")
41
43
  ) {
42
- options.dev = true;
44
+ options.dev = "prefer-remote";
43
45
  }
44
46
 
45
47
  // Parse quiet flag
@@ -346,11 +348,11 @@ export interface AlchemyOptions {
346
348
  */
347
349
  phase?: Phase;
348
350
  /**
349
- * Determines whether Alchemy will run in dev mode.
351
+ * Determines how Alchemy will run in development mode.
350
352
  *
351
- * @default - `true` if `--dev` or `--watch` is passed as a CLI argument, `false` otherwise
353
+ * @default - `"prefer-local"` if `--dev` or `--local` is passed as a CLI argument, `"prefer-remote"` if `--remote` or `--watch` is passed as a CLI argument, `undefined` otherwise
352
354
  */
353
- dev?: boolean;
355
+ dev?: "prefer-local" | "prefer-remote";
354
356
  /**
355
357
  * Name to scope the resource state under (e.g. `.alchemy/{stage}/..`).
356
358
  *
package/src/build-date.ts CHANGED
@@ -5,4 +5,4 @@
5
5
  * The build date used as the default worker compatibility date.
6
6
  * This is set to the date when the package was built.
7
7
  */
8
- export const BUILD_DATE = "2025-07-08";
8
+ export const BUILD_DATE = "2025-07-09";
@@ -100,7 +100,7 @@ async function refreshAuthToken(
100
100
 
101
101
  if (!response.ok) {
102
102
  throw new Error(
103
- `Failed to refresh auth token: ${response.status} ${response.statusText}`,
103
+ `Failed to refresh auth token: ${response.status} ${response.statusText} ${await response.text()}`,
104
104
  );
105
105
  }
106
106
 
@@ -0,0 +1,194 @@
1
+ import esbuild from "esbuild";
2
+ import fs from "node:fs/promises";
3
+ import path from "pathe";
4
+ import { esbuildPluginCompatWarning } from "./plugin-compat-warning.ts";
5
+ import { esbuildPluginHotReload } from "./plugin-hot-reload.ts";
6
+ import { esbuildPluginHybridNodeCompat } from "./plugin-hybrid-node-compat.ts";
7
+ import { esbuildPluginWasm } from "./plugin-wasm.ts";
8
+ import type {
9
+ WorkerBundle,
10
+ WorkerBundleBaseProps,
11
+ WorkerBundleChunk,
12
+ WorkerBundleProvider,
13
+ } from "./shared.ts";
14
+ import { parseFiles } from "./shared.ts";
15
+
16
+ interface ESBuildBundleProps
17
+ extends WorkerBundleBaseProps,
18
+ Omit<
19
+ esbuild.BuildOptions,
20
+ "entryPoints" | "format" | "absWorkingDir" | "outdir"
21
+ > {
22
+ entrypoint: string;
23
+ cwd: string;
24
+ outdir: string;
25
+ }
26
+
27
+ export class ESBuildBundleProvider implements WorkerBundleProvider {
28
+ constructor(private props: ESBuildBundleProps) {
29
+ // Normalize paths to ensure consistency between esbuild inputs and outputs:
30
+ // - the `cwd` is absolute
31
+ // - the `entrypoint` and `outdir` are relative to the `cwd`
32
+ // This is so we can identify the entrypoint from the metafile.
33
+ this.props.cwd = path.resolve(this.props.cwd);
34
+ this.props.entrypoint = path.isAbsolute(props.entrypoint)
35
+ ? path.relative(this.props.cwd, props.entrypoint)
36
+ : path.normalize(props.entrypoint);
37
+ this.props.outdir = path.isAbsolute(props.outdir)
38
+ ? path.relative(this.props.cwd, props.outdir)
39
+ : path.normalize(props.outdir);
40
+ }
41
+
42
+ async create(): Promise<WorkerBundle> {
43
+ const options = buildOptions(this.props);
44
+ const result = await esbuild.build(options);
45
+ return resolveOutputs(result.metafile, this.props);
46
+ }
47
+
48
+ async watch(signal: AbortSignal): Promise<ReadableStream<WorkerBundleChunk>> {
49
+ const options = buildOptions(this.props);
50
+
51
+ return new ReadableStream<WorkerBundleChunk>({
52
+ start: async (controller) => {
53
+ const context = await esbuild.context({
54
+ ...options,
55
+ plugins: [
56
+ ...(options.plugins ?? []),
57
+ esbuildPluginHotReload({
58
+ onBuildStart: () => {
59
+ controller.enqueue({ type: "start" });
60
+ },
61
+ onBuildError: (errors) => {
62
+ controller.enqueue({ type: "error", errors });
63
+ },
64
+ onBuildEnd: async (result) => {
65
+ controller.enqueue({
66
+ type: "end",
67
+ result: await resolveOutputs(result.metafile!, this.props),
68
+ });
69
+ },
70
+ }),
71
+ ],
72
+ });
73
+ await context.watch();
74
+ signal.addEventListener("abort", async () => {
75
+ await context.dispose();
76
+ controller.close();
77
+ });
78
+ },
79
+ });
80
+ }
81
+
82
+ async delete(): Promise<void> {
83
+ await fs.rm(path.join(this.props.cwd, this.props.outdir), {
84
+ recursive: true,
85
+ force: true,
86
+ });
87
+ }
88
+ }
89
+ function buildOptions({
90
+ entrypoint,
91
+ nodeCompat,
92
+ cwd,
93
+ ...props
94
+ }: ESBuildBundleProps) {
95
+ return {
96
+ ...props,
97
+ entryPoints: [entrypoint],
98
+ absWorkingDir: cwd,
99
+ format: props.format,
100
+ target: "esnext",
101
+ platform: "node",
102
+ metafile: true,
103
+ write: true,
104
+ bundle: true,
105
+ conditions: ["workerd", "worker", "import", "module", "browser"],
106
+ mainFields: ["module", "main"],
107
+ loader: {
108
+ ".sql": "text",
109
+ ".json": "json",
110
+ ...props.loader,
111
+ },
112
+ plugins: [
113
+ esbuildPluginWasm(),
114
+ nodeCompat === "v2"
115
+ ? esbuildPluginHybridNodeCompat()
116
+ : esbuildPluginCompatWarning(nodeCompat ?? null),
117
+ ...(props.plugins ?? []),
118
+ ],
119
+ alias: props.alias,
120
+ external: [
121
+ ...(nodeCompat === "als" ? external_als : external),
122
+ ...(props.external ?? []),
123
+ ],
124
+ } satisfies esbuild.BuildOptions;
125
+ }
126
+
127
+ async function resolveOutputs(
128
+ metafile: esbuild.Metafile,
129
+ props: ESBuildBundleProps,
130
+ ): Promise<WorkerBundle> {
131
+ const outdir = path.resolve(props.cwd, props.outdir);
132
+ const paths: string[] = [];
133
+ let entrypoint: string | undefined;
134
+ for (const [key, value] of Object.entries(metafile.outputs)) {
135
+ const name = path.relative(outdir, path.resolve(props.cwd, key));
136
+ paths.push(name);
137
+ if (value.entryPoint === props.entrypoint) {
138
+ entrypoint = name;
139
+ }
140
+ }
141
+ if (!entrypoint) {
142
+ throw new Error(
143
+ `Failed to find entrypoint in metafile - expected ${props.entrypoint} but found ${Object.values(
144
+ metafile.outputs,
145
+ )
146
+ .map((v) => v.entryPoint)
147
+ .filter((v) => v !== undefined)
148
+ .join(", ")}`,
149
+ );
150
+ }
151
+ const { files, hash } = await parseFiles(outdir, paths, props.format);
152
+ return {
153
+ entrypoint,
154
+ files,
155
+ hash,
156
+ };
157
+ }
158
+
159
+ // https://developers.cloudflare.com/workers/runtime-apis/nodejs/#supported-nodejs-apis
160
+ const nodejs_compat = [
161
+ "node:async_hooks",
162
+ "node:assert",
163
+ "node:buffer",
164
+ "node:console",
165
+ "node:crypto",
166
+ "node:debug",
167
+ "node:diagnostics_channel",
168
+ "node:dns",
169
+ "node:events",
170
+ "node:inspector",
171
+ "node:net",
172
+ "node:path",
173
+ "node:perf_hooks", // partially supported
174
+ "node:process",
175
+ "node:querystring",
176
+ "node:stream",
177
+ "node:string_decoder",
178
+ "node:timers",
179
+ "node:tls", // partially supported
180
+ "node:url",
181
+ "node:util",
182
+ "node:zlib",
183
+ // "node:*",
184
+ ];
185
+
186
+ const external = [
187
+ ...nodejs_compat,
188
+ ...nodejs_compat.map((p) => p.split(":")[1]),
189
+ "cloudflare:workers",
190
+ "cloudflare:workflows",
191
+ "cloudflare:*",
192
+ ];
193
+
194
+ const external_als = ["node:async_hooks", "async_hooks", "cloudflare:*"];
@@ -0,0 +1,98 @@
1
+ import kleur from "kleur";
2
+ import fs from "node:fs/promises";
3
+ import path from "pathe";
4
+ import { logger } from "../../util/logger.ts";
5
+ import type {
6
+ WorkerBundle,
7
+ WorkerBundleBaseProps,
8
+ WorkerBundleChunk,
9
+ WorkerBundleProvider,
10
+ } from "./shared.ts";
11
+ import { parseFiles } from "./shared.ts";
12
+
13
+ interface FSBundleProps extends WorkerBundleBaseProps {
14
+ entrypoint: string;
15
+ globs: string[] | undefined;
16
+ cwd: string;
17
+ sourcemaps: boolean;
18
+ }
19
+
20
+ export class FSBundleProvider implements WorkerBundleProvider {
21
+ private root: string;
22
+ private entrypoint: string;
23
+ private globs: string[];
24
+ private format: "esm" | "cjs";
25
+
26
+ constructor(props: FSBundleProps) {
27
+ const entrypoint = path.resolve(props.cwd, props.entrypoint);
28
+ this.root = path.dirname(entrypoint);
29
+ this.entrypoint = path.relative(this.root, entrypoint);
30
+ this.globs = props.globs ?? [
31
+ "**/*.js",
32
+ "**/*.mjs",
33
+ "**/*.wasm",
34
+ ...(props.sourcemaps ? ["**/*.js.map"] : []),
35
+ ];
36
+ this.format = props.format;
37
+ }
38
+
39
+ async create(log = true): Promise<WorkerBundle> {
40
+ const fileNames = new Set<string>();
41
+ await Promise.all(
42
+ this.globs.map(async (glob) => {
43
+ for await (const file of fs.glob(glob, { cwd: this.root })) {
44
+ fileNames.add(file);
45
+ }
46
+ }),
47
+ );
48
+ const { files, hash } = await parseFiles(
49
+ this.root,
50
+ Array.from(fileNames),
51
+ this.format,
52
+ );
53
+ if (log) {
54
+ logger.log(`${kleur.gray("worker:")} ${kleur.blue(this.entrypoint)}`);
55
+ logger.log(`${kleur.gray("main:")} ${kleur.blue(this.entrypoint)}`);
56
+ logger.log(
57
+ `${kleur.gray("dist:")} ${kleur.blue(path.relative(process.cwd(), this.root))}:`,
58
+ );
59
+ }
60
+ return {
61
+ entrypoint: this.entrypoint,
62
+ files,
63
+ hash,
64
+ };
65
+ }
66
+
67
+ async watch(signal: AbortSignal): Promise<ReadableStream<WorkerBundleChunk>> {
68
+ let result = await this.create();
69
+ const watcher = fs.watch(this.root, {
70
+ recursive: true,
71
+ signal,
72
+ });
73
+ const iterator = watcher[Symbol.asyncIterator]();
74
+ return new ReadableStream({
75
+ start: async (controller) => {
76
+ controller.enqueue({ type: "end", result });
77
+ },
78
+ pull: async (controller) => {
79
+ const next = await iterator.next().catch((err) => {
80
+ if (err instanceof DOMException && err.name === "AbortError") {
81
+ return { done: true };
82
+ }
83
+ throw err;
84
+ });
85
+ if (next.done) {
86
+ controller.close();
87
+ } else {
88
+ controller.enqueue({ type: "start" });
89
+ const update = await this.create(false);
90
+ if (update.hash !== result.hash) {
91
+ result = update;
92
+ controller.enqueue({ type: "end", result });
93
+ }
94
+ }
95
+ },
96
+ });
97
+ }
98
+ }
@@ -0,0 +1,6 @@
1
+ export { normalizeWorkerBundle } from "./normalize.ts";
2
+ export type {
3
+ WorkerBundle,
4
+ WorkerBundleChunk,
5
+ WorkerBundleProvider,
6
+ } from "./shared.ts";
@@ -0,0 +1,42 @@
1
+ import crypto from "node:crypto";
2
+ import type {
3
+ WorkerBundle,
4
+ WorkerBundleBaseProps,
5
+ WorkerBundleChunk,
6
+ WorkerBundleProvider,
7
+ } from "./shared.ts";
8
+
9
+ interface InlineBundleProps extends WorkerBundleBaseProps {
10
+ content: string;
11
+ }
12
+
13
+ export class InlineBundleProvider implements WorkerBundleProvider {
14
+ constructor(private props: InlineBundleProps) {}
15
+
16
+ async create(): Promise<WorkerBundle> {
17
+ return {
18
+ entrypoint: "worker.js",
19
+ files: [
20
+ new File([this.props.content], "worker.js", {
21
+ type:
22
+ this.props.format === "cjs"
23
+ ? "application/javascript"
24
+ : "application/javascript+module",
25
+ }),
26
+ ],
27
+ hash: crypto
28
+ .createHash("sha256")
29
+ .update(this.props.content)
30
+ .digest("hex"),
31
+ };
32
+ }
33
+
34
+ async watch(): Promise<ReadableStream<WorkerBundleChunk>> {
35
+ return new ReadableStream<WorkerBundleChunk>({
36
+ start: async (controller) => {
37
+ controller.enqueue({ type: "end", result: await this.create() });
38
+ controller.close();
39
+ },
40
+ });
41
+ }
42
+ }
@@ -0,0 +1,76 @@
1
+ import type esbuild from "esbuild";
2
+ import { ESBuildBundleProvider } from "./esbuild.ts";
3
+ import { FSBundleProvider } from "./fs.ts";
4
+ import { InlineBundleProvider } from "./inline.ts";
5
+ import type { WorkerBundleProvider } from "./shared.ts";
6
+ import { validateNodeCompat } from "./validate-node-compat.ts";
7
+
8
+ interface NormalizeWorkerBundleProps {
9
+ script: string | undefined;
10
+ entrypoint: string | undefined;
11
+ noBundle: boolean | undefined;
12
+ format: "cjs" | "esm" | undefined;
13
+ compatibilityDate: string;
14
+ compatibilityFlags: string[];
15
+ rules:
16
+ | {
17
+ globs: string[];
18
+ }[]
19
+ | undefined;
20
+ bundle?: Omit<
21
+ esbuild.BuildOptions,
22
+ "entryPoints" | "format" | "absWorkingDir"
23
+ >;
24
+ cwd: string;
25
+ outdir: string;
26
+ sourceMap: boolean | undefined;
27
+ }
28
+
29
+ export function wrap<T>(fn: () => T): [T, null] | [null, Error] {
30
+ try {
31
+ return [fn(), null];
32
+ } catch (e) {
33
+ return [null, e instanceof Error ? e : new Error(String(e))];
34
+ }
35
+ }
36
+
37
+ export function normalizeWorkerBundle(
38
+ props: NormalizeWorkerBundleProps,
39
+ ): WorkerBundleProvider {
40
+ const nodeCompat = validateNodeCompat({
41
+ compatibilityDate: props.compatibilityDate,
42
+ compatibilityFlags: props.compatibilityFlags,
43
+ noBundle: props.noBundle ?? false,
44
+ });
45
+ if (props.script) {
46
+ return new InlineBundleProvider({
47
+ content: props.script,
48
+ format: props.format ?? "esm",
49
+ nodeCompat,
50
+ });
51
+ }
52
+ if (!props.entrypoint) {
53
+ throw new Error(
54
+ "Either `script` or `entrypoint` must be provided for workers",
55
+ );
56
+ }
57
+ if (props.noBundle) {
58
+ return new FSBundleProvider({
59
+ entrypoint: props.entrypoint,
60
+ format: props.format ?? "esm",
61
+ nodeCompat,
62
+ cwd: props.cwd,
63
+ globs: props.rules?.flatMap((rule) => rule.globs),
64
+ sourcemaps: props.sourceMap !== false,
65
+ });
66
+ }
67
+ return new ESBuildBundleProvider({
68
+ entrypoint: props.entrypoint,
69
+ format: props.format ?? "esm",
70
+ nodeCompat,
71
+ cwd: props.cwd,
72
+ outdir: props.outdir,
73
+ sourcemap: props.sourceMap !== false ? "linked" : undefined,
74
+ ...props.bundle,
75
+ });
76
+ }
@@ -15,7 +15,7 @@ const NODE_REGEX = new RegExp(
15
15
  * - Any node:* import is found without nodejs_compat flag
16
16
  * - node:async_hooks is imported without nodejs_compat or nodejs_als flags
17
17
  */
18
- export function nodeJsImportWarningPlugin(mode: "als" | null): Plugin {
18
+ export function esbuildPluginCompatWarning(mode: "als" | null): Plugin {
19
19
  return {
20
20
  name: "nodejs-import-warning",
21
21
  setup(build: PluginBuild) {
@@ -67,7 +67,9 @@ export function nodeJsImportWarningPlugin(mode: "als" | null): Plugin {
67
67
  ),
68
68
  ].join("\n"),
69
69
  );
70
- } else if (alsImporters.size > 0 && !mode) {
70
+ }
71
+
72
+ if (alsImporters.size > 0 && !mode) {
71
73
  logger.warn(
72
74
  [
73
75
  `Detected import of ${kleur.yellow("node:async_hooks")} but ${kleur.red("nodejs_als")} compatibility flag is not set. `,
@@ -0,0 +1,27 @@
1
+ import type esbuild from "esbuild";
2
+
3
+ export interface HotReloadPluginProps {
4
+ onBuildStart?: () => void | Promise<void>;
5
+ onBuildEnd?: (result: esbuild.BuildResult) => void | Promise<void>;
6
+ onBuildError?: (errors: esbuild.Message[]) => void | Promise<void>;
7
+ }
8
+
9
+ export function esbuildPluginHotReload(
10
+ props: HotReloadPluginProps,
11
+ ): esbuild.Plugin {
12
+ return {
13
+ name: "alchemy-hot-reload",
14
+ setup(build) {
15
+ if (props.onBuildStart) {
16
+ build.onStart(props.onBuildStart);
17
+ }
18
+ build.onEnd(async (result) => {
19
+ if (result.errors.length > 0) {
20
+ await props.onBuildError?.(result.errors);
21
+ } else {
22
+ await props.onBuildEnd?.(result);
23
+ }
24
+ });
25
+ },
26
+ };
27
+ }
@@ -19,18 +19,18 @@ const REQUIRED_UNENV_ALIAS_NAMESPACE = "required-unenv-alias";
19
19
  *
20
20
  * @returns ESBuild plugin
21
21
  */
22
- export async function nodeJsCompatPlugin(): Promise<Plugin> {
23
- // `unenv` and `@cloudflare/unenv-preset` only publish esm
24
- const { defineEnv } = await import("unenv");
25
- const { cloudflare } = await import("@cloudflare/unenv-preset");
26
- const { alias, inject, external, polyfill } = defineEnv({
27
- presets: [cloudflare],
28
- npmShims: true,
29
- }).env;
30
-
22
+ export function esbuildPluginHybridNodeCompat(): Plugin {
31
23
  return {
32
24
  name: "hybrid-nodejs_compat",
33
- setup(build) {
25
+ setup: async (build) => {
26
+ // `unenv` and `@cloudflare/unenv-preset` only publish esm
27
+ const { defineEnv } = await import("unenv");
28
+ const { cloudflare } = await import("@cloudflare/unenv-preset");
29
+ const { alias, inject, external, polyfill } = defineEnv({
30
+ presets: [cloudflare],
31
+ npmShims: true,
32
+ }).env;
33
+
34
34
  errorOnServiceWorkerFormat(build);
35
35
  handleRequireCallsToNodeJSBuiltins(build);
36
36
  handleUnenvAliasedPackages(build, alias, external);
@@ -0,0 +1,62 @@
1
+ import type { Plugin } from "esbuild";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ export function esbuildPluginWasm(): Plugin {
6
+ return {
7
+ name: "wasm",
8
+ setup: (build) => {
9
+ // Resolve ".wasm" files to a path with a namespace
10
+ build.onResolve({ filter: /\.wasm$/ }, (args) => {
11
+ // If this is the import inside the stub module, import the
12
+ // binary itself. Put the path in the "wasm-binary" namespace
13
+ // to tell our binary load callback to load the binary file.
14
+ if (args.namespace === "wasm-stub") {
15
+ return {
16
+ path: args.path,
17
+ namespace: "wasm-binary",
18
+ };
19
+ }
20
+
21
+ // Otherwise, generate the JavaScript stub module for this
22
+ // ".wasm" file. Put it in the "wasm-stub" namespace to tell
23
+ // our stub load callback to fill it with JavaScript.
24
+ //
25
+ // Resolve relative paths to absolute paths here since this
26
+ // resolve callback is given "resolveDir", the directory to
27
+ // resolve imports against.
28
+ if (args.resolveDir === "") {
29
+ return; // Ignore unresolvable paths
30
+ }
31
+ return {
32
+ path: path.isAbsolute(args.path)
33
+ ? args.path
34
+ : path.join(args.resolveDir, args.path),
35
+ namespace: "wasm-stub",
36
+ };
37
+ });
38
+
39
+ // Virtual modules in the "wasm-stub" namespace are filled with
40
+ // the JavaScript code for compiling the WebAssembly binary. The
41
+ // binary itself is imported from a second virtual module.
42
+ build.onLoad({ filter: /.*/, namespace: "wasm-stub" }, async (args) => ({
43
+ contents: `import wasm from ${JSON.stringify(args.path)}
44
+ export default (imports) =>
45
+ WebAssembly.instantiate(wasm, imports).then(
46
+ result => result.instance.exports)`,
47
+ }));
48
+
49
+ // Virtual modules in the "wasm-binary" namespace contain the
50
+ // actual bytes of the WebAssembly file. This uses esbuild's
51
+ // built-in "binary" loader instead of manually embedding the
52
+ // binary data inside JavaScript code ourselves.
53
+ build.onLoad(
54
+ { filter: /.*/, namespace: "wasm-binary" },
55
+ async (args) => ({
56
+ contents: await fs.readFile(args.path),
57
+ loader: "binary",
58
+ }),
59
+ );
60
+ },
61
+ };
62
+ }
@@ -0,0 +1,73 @@
1
+ import type esbuild from "esbuild";
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { getContentType } from "../../util/content-type.ts";
6
+
7
+ export interface WorkerBundleBaseProps {
8
+ format: "cjs" | "esm";
9
+ nodeCompat: "als" | "v2" | null;
10
+ }
11
+
12
+ export interface WorkerBundle {
13
+ entrypoint: string;
14
+ files: File[];
15
+ hash: string;
16
+ }
17
+
18
+ export type WorkerBundleChunk =
19
+ | {
20
+ type: "start";
21
+ }
22
+ | {
23
+ type: "end";
24
+ result: WorkerBundle;
25
+ }
26
+ | {
27
+ type: "error";
28
+ errors: esbuild.Message[];
29
+ };
30
+
31
+ export interface WorkerBundleProvider {
32
+ create(): Promise<WorkerBundle>;
33
+ watch(signal: AbortSignal): Promise<ReadableStream<WorkerBundleChunk>>;
34
+ delete?(): Promise<void>;
35
+ }
36
+
37
+ export const normalizeFileType = (file: string, format: "cjs" | "esm") => {
38
+ if (path.extname(file) === ".js" && format === "esm") {
39
+ return "application/javascript+module";
40
+ }
41
+ return getContentType(file);
42
+ };
43
+
44
+ export const parseFiles = async (
45
+ root: string,
46
+ paths: string[],
47
+ format: "esm" | "cjs",
48
+ ) => {
49
+ const outputs = await Promise.all(
50
+ paths.map(async (file) => {
51
+ const content = await fs.readFile(path.join(root, file));
52
+ return {
53
+ file: new File([content], file, {
54
+ type: normalizeFileType(file, format),
55
+ }),
56
+ hash: crypto.createHash("sha256").update(content).digest("hex"),
57
+ };
58
+ }),
59
+ );
60
+ return {
61
+ files: outputs.map(({ file }) => file),
62
+ hash: combineFileHashes(outputs),
63
+ };
64
+ };
65
+
66
+ const combineFileHashes = (files: { file: File; hash: string }[]) => {
67
+ const sorted = files.sort((a, b) => a.file.name.localeCompare(b.file.name));
68
+ const finalHash = crypto.createHash("sha256");
69
+ for (const { hash } of sorted) {
70
+ finalHash.update(hash);
71
+ }
72
+ return finalHash.digest("hex");
73
+ };