alchemy 0.55.3 → 0.57.0

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 (293) hide show
  1. package/bin/alchemy.js +14245 -14406
  2. package/bin/commands/create.ts +7 -7
  3. package/bin/commands/deploy.ts +2 -2
  4. package/bin/commands/destroy.ts +2 -2
  5. package/bin/commands/dev.ts +2 -2
  6. package/bin/commands/init.ts +6 -6
  7. package/bin/commands/login.ts +3 -3
  8. package/bin/commands/run.ts +2 -2
  9. package/bin/services/execute-alchemy.ts +3 -3
  10. package/bin/trpc.ts +49 -0
  11. package/lib/alchemy.d.ts.map +1 -1
  12. package/lib/alchemy.js +7 -0
  13. package/lib/alchemy.js.map +1 -1
  14. package/lib/apply.d.ts.map +1 -1
  15. package/lib/apply.js +3 -4
  16. package/lib/apply.js.map +1 -1
  17. package/lib/cloudflare/{astro.d.ts → astro/astro.d.ts} +6 -5
  18. package/lib/cloudflare/astro/astro.d.ts.map +1 -0
  19. package/lib/cloudflare/astro/astro.js +91 -0
  20. package/lib/cloudflare/astro/astro.js.map +1 -0
  21. package/lib/cloudflare/astro/plugin.d.ts +4 -0
  22. package/lib/cloudflare/astro/plugin.d.ts.map +1 -0
  23. package/lib/cloudflare/astro/plugin.js +10 -0
  24. package/lib/cloudflare/astro/plugin.js.map +1 -0
  25. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +1 -3
  26. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -1
  27. package/lib/cloudflare/bundle/plugin-hot-reload.js +4 -1
  28. package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -1
  29. package/lib/cloudflare/cloudflare-env-proxy.d.ts +4 -0
  30. package/lib/cloudflare/cloudflare-env-proxy.d.ts.map +1 -0
  31. package/lib/cloudflare/cloudflare-env-proxy.js +34 -0
  32. package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -0
  33. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  34. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  35. package/lib/cloudflare/compatibility-presets.d.ts +1 -1
  36. package/lib/cloudflare/compatibility-presets.js +1 -1
  37. package/lib/cloudflare/compatibility-presets.js.map +1 -1
  38. package/lib/cloudflare/d1-local-migrations.js +1 -1
  39. package/lib/cloudflare/d1-local-migrations.js.map +1 -1
  40. package/lib/cloudflare/index.d.ts +8 -8
  41. package/lib/cloudflare/index.d.ts.map +1 -1
  42. package/lib/cloudflare/index.js +8 -8
  43. package/lib/cloudflare/index.js.map +1 -1
  44. package/lib/cloudflare/miniflare/build-worker-options.d.ts +22 -0
  45. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -0
  46. package/lib/cloudflare/miniflare/build-worker-options.js +394 -0
  47. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -0
  48. package/lib/cloudflare/miniflare/miniflare-controller.d.ts +23 -0
  49. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -0
  50. package/lib/cloudflare/miniflare/miniflare-controller.js +111 -0
  51. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -0
  52. package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
  53. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +5 -15
  54. package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
  55. package/lib/cloudflare/miniflare/paths.d.ts +5 -0
  56. package/lib/cloudflare/miniflare/paths.d.ts.map +1 -0
  57. package/lib/cloudflare/miniflare/paths.js +35 -0
  58. package/lib/cloudflare/miniflare/paths.js.map +1 -0
  59. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts +1 -5
  60. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
  61. package/lib/cloudflare/miniflare/remote-binding-proxy.js +17 -22
  62. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
  63. package/lib/cloudflare/{nuxt.d.ts → nuxt/nuxt.d.ts} +4 -4
  64. package/lib/cloudflare/nuxt/nuxt.d.ts.map +1 -0
  65. package/lib/cloudflare/{nuxt.js → nuxt/nuxt.js} +8 -13
  66. package/lib/cloudflare/nuxt/nuxt.js.map +1 -0
  67. package/lib/cloudflare/nuxt/plugin.d.ts +4 -0
  68. package/lib/cloudflare/nuxt/plugin.d.ts.map +1 -0
  69. package/lib/cloudflare/nuxt/plugin.js +14 -0
  70. package/lib/cloudflare/nuxt/plugin.js.map +1 -0
  71. package/lib/cloudflare/oauth.js +2 -2
  72. package/lib/cloudflare/oauth.js.map +1 -1
  73. package/lib/cloudflare/orange.d.ts +3 -3
  74. package/lib/cloudflare/orange.d.ts.map +1 -1
  75. package/lib/cloudflare/orange.js +11 -21
  76. package/lib/cloudflare/orange.js.map +1 -1
  77. package/lib/cloudflare/react-router/plugin.d.ts +4 -0
  78. package/lib/cloudflare/react-router/plugin.d.ts.map +1 -0
  79. package/lib/cloudflare/react-router/plugin.js +11 -0
  80. package/lib/cloudflare/react-router/plugin.js.map +1 -0
  81. package/lib/cloudflare/react-router/react-router.d.ts +17 -0
  82. package/lib/cloudflare/react-router/react-router.d.ts.map +1 -0
  83. package/lib/cloudflare/react-router/react-router.js +21 -0
  84. package/lib/cloudflare/react-router/react-router.js.map +1 -0
  85. package/lib/cloudflare/redwood/plugin.d.ts +4 -0
  86. package/lib/cloudflare/redwood/plugin.d.ts.map +1 -0
  87. package/lib/cloudflare/redwood/plugin.js +19 -0
  88. package/lib/cloudflare/redwood/plugin.js.map +1 -0
  89. package/lib/cloudflare/{redwood.d.ts → redwood/redwood.d.ts} +5 -5
  90. package/lib/cloudflare/redwood/redwood.d.ts.map +1 -0
  91. package/lib/cloudflare/{redwood.js → redwood/redwood.js} +12 -13
  92. package/lib/cloudflare/redwood/redwood.js.map +1 -0
  93. package/lib/cloudflare/sveltekit/plugin.d.ts +4 -0
  94. package/lib/cloudflare/sveltekit/plugin.d.ts.map +1 -0
  95. package/lib/cloudflare/sveltekit/plugin.js +11 -0
  96. package/lib/cloudflare/sveltekit/plugin.js.map +1 -0
  97. package/lib/cloudflare/{sveltekit.d.ts → sveltekit/sveltekit.d.ts} +5 -5
  98. package/lib/cloudflare/sveltekit/sveltekit.d.ts.map +1 -0
  99. package/lib/cloudflare/{sveltekit.js → sveltekit/sveltekit.js} +4 -4
  100. package/lib/cloudflare/sveltekit/sveltekit.js.map +1 -0
  101. package/lib/cloudflare/tanstack-start/plugin.d.ts +16 -0
  102. package/lib/cloudflare/tanstack-start/plugin.d.ts.map +1 -0
  103. package/lib/cloudflare/tanstack-start/plugin.js +28 -0
  104. package/lib/cloudflare/tanstack-start/plugin.js.map +1 -0
  105. package/lib/cloudflare/{tanstack-start.d.ts → tanstack-start/tanstack-start.d.ts} +5 -5
  106. package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -0
  107. package/lib/cloudflare/tanstack-start/tanstack-start.js +11 -0
  108. package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -0
  109. package/lib/cloudflare/vite/plugin.d.ts +4 -0
  110. package/lib/cloudflare/vite/plugin.d.ts.map +1 -0
  111. package/lib/cloudflare/vite/plugin.js +23 -0
  112. package/lib/cloudflare/vite/plugin.js.map +1 -0
  113. package/lib/cloudflare/{vite.d.ts → vite/vite.d.ts} +4 -4
  114. package/lib/cloudflare/vite/vite.d.ts.map +1 -0
  115. package/lib/cloudflare/vite/vite.js +19 -0
  116. package/lib/cloudflare/vite/vite.js.map +1 -0
  117. package/lib/cloudflare/website.d.ts +92 -70
  118. package/lib/cloudflare/website.d.ts.map +1 -1
  119. package/lib/cloudflare/website.js +225 -94
  120. package/lib/cloudflare/website.js.map +1 -1
  121. package/lib/cloudflare/worker-bundle.d.ts +3 -1
  122. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  123. package/lib/cloudflare/worker-bundle.js +42 -16
  124. package/lib/cloudflare/worker-bundle.js.map +1 -1
  125. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  126. package/lib/cloudflare/worker-stub.js +3 -6
  127. package/lib/cloudflare/worker-stub.js.map +1 -1
  128. package/lib/cloudflare/worker.d.ts +2 -7
  129. package/lib/cloudflare/worker.d.ts.map +1 -1
  130. package/lib/cloudflare/worker.js +17 -157
  131. package/lib/cloudflare/worker.js.map +1 -1
  132. package/lib/cloudflare/wrangler.json.d.ts +6 -0
  133. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  134. package/lib/cloudflare/wrangler.json.js +23 -6
  135. package/lib/cloudflare/wrangler.json.js.map +1 -1
  136. package/lib/context.d.ts +11 -0
  137. package/lib/context.d.ts.map +1 -1
  138. package/lib/context.js +8 -0
  139. package/lib/context.js.map +1 -1
  140. package/lib/env.d.ts +2 -2
  141. package/lib/env.d.ts.map +1 -1
  142. package/lib/env.js +5 -6
  143. package/lib/env.js.map +1 -1
  144. package/lib/random/index.d.ts +2 -0
  145. package/lib/random/index.d.ts.map +1 -0
  146. package/lib/random/index.js +2 -0
  147. package/lib/random/index.js.map +1 -0
  148. package/lib/random/random-string.d.ts +87 -0
  149. package/lib/random/random-string.d.ts.map +1 -0
  150. package/lib/random/random-string.js +72 -0
  151. package/lib/random/random-string.js.map +1 -0
  152. package/lib/runtime/bind.d.ts +1 -1
  153. package/lib/runtime/bind.d.ts.map +1 -1
  154. package/lib/runtime/bind.js.map +1 -1
  155. package/lib/scope.d.ts +11 -0
  156. package/lib/scope.d.ts.map +1 -1
  157. package/lib/scope.js +24 -0
  158. package/lib/scope.js.map +1 -1
  159. package/lib/secret.d.ts +2 -2
  160. package/lib/secret.d.ts.map +1 -1
  161. package/lib/secret.js +2 -2
  162. package/lib/secret.js.map +1 -1
  163. package/lib/state/cloudflare-state-store.js +1 -1
  164. package/lib/state/cloudflare-state-store.js.map +1 -1
  165. package/lib/state/dofs-state-store/index.js +1 -1
  166. package/lib/state/dofs-state-store/index.js.map +1 -1
  167. package/lib/util/detect-package-manager.d.ts +1 -0
  168. package/lib/util/detect-package-manager.d.ts.map +1 -1
  169. package/lib/util/detect-package-manager.js +10 -0
  170. package/lib/util/detect-package-manager.js.map +1 -1
  171. package/lib/util/telemetry/client.d.ts +1 -1
  172. package/lib/util/telemetry/client.d.ts.map +1 -1
  173. package/lib/util/telemetry/client.js +22 -1
  174. package/lib/util/telemetry/client.js.map +1 -1
  175. package/lib/util/telemetry/constants.d.ts.map +1 -1
  176. package/lib/util/telemetry/constants.js +2 -3
  177. package/lib/util/telemetry/constants.js.map +1 -1
  178. package/lib/util/telemetry/context.d.ts +1 -1
  179. package/lib/util/telemetry/context.d.ts.map +1 -1
  180. package/lib/util/telemetry/types.d.ts +7 -3
  181. package/lib/util/telemetry/types.d.ts.map +1 -1
  182. package/lib/vercel/api.d.ts.map +1 -1
  183. package/lib/vercel/api.js +1 -1
  184. package/lib/vercel/api.js.map +1 -1
  185. package/package.json +43 -4
  186. package/src/alchemy.ts +7 -0
  187. package/src/apply.ts +3 -7
  188. package/src/cloudflare/{astro.ts → astro/astro.ts} +46 -29
  189. package/src/cloudflare/astro/plugin.ts +11 -0
  190. package/src/cloudflare/bundle/plugin-hot-reload.ts +4 -3
  191. package/src/cloudflare/cloudflare-env-proxy.ts +50 -0
  192. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  193. package/src/cloudflare/compatibility-presets.ts +1 -1
  194. package/src/cloudflare/d1-local-migrations.ts +1 -1
  195. package/src/cloudflare/index.ts +8 -8
  196. package/src/cloudflare/miniflare/build-worker-options.ts +454 -0
  197. package/src/cloudflare/miniflare/miniflare-controller.ts +129 -0
  198. package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +6 -15
  199. package/src/cloudflare/miniflare/paths.ts +52 -0
  200. package/src/cloudflare/miniflare/remote-binding-proxy.ts +18 -23
  201. package/src/cloudflare/{nuxt.ts → nuxt/nuxt.ts} +11 -16
  202. package/src/cloudflare/nuxt/plugin.ts +18 -0
  203. package/src/cloudflare/oauth.ts +2 -2
  204. package/src/cloudflare/orange.ts +13 -23
  205. package/src/cloudflare/react-router/plugin.ts +13 -0
  206. package/src/cloudflare/react-router/react-router.ts +41 -0
  207. package/src/cloudflare/redwood/plugin.ts +20 -0
  208. package/src/cloudflare/{redwood.ts → redwood/redwood.ts} +16 -19
  209. package/src/cloudflare/sveltekit/plugin.ts +12 -0
  210. package/src/cloudflare/{sveltekit.ts → sveltekit/sveltekit.ts} +9 -8
  211. package/src/cloudflare/tanstack-start/plugin.ts +30 -0
  212. package/src/cloudflare/tanstack-start/tanstack-start.ts +24 -0
  213. package/src/cloudflare/vite/plugin.ts +30 -0
  214. package/src/cloudflare/vite/vite.ts +34 -0
  215. package/src/cloudflare/website.ts +379 -204
  216. package/src/cloudflare/worker-bundle.ts +55 -16
  217. package/src/cloudflare/worker-stub.ts +11 -14
  218. package/src/cloudflare/worker.ts +23 -205
  219. package/src/cloudflare/wrangler.json.ts +32 -4
  220. package/src/context.ts +19 -0
  221. package/src/env.ts +8 -9
  222. package/src/random/index.ts +1 -0
  223. package/src/random/random-string.ts +126 -0
  224. package/src/runtime/bind.ts +2 -2
  225. package/src/scope.ts +25 -0
  226. package/src/secret.ts +4 -8
  227. package/src/state/cloudflare-state-store.ts +2 -2
  228. package/src/state/dofs-state-store/index.ts +1 -1
  229. package/src/util/detect-package-manager.ts +11 -0
  230. package/src/util/telemetry/client.ts +25 -2
  231. package/src/util/telemetry/constants.ts +2 -4
  232. package/src/util/telemetry/context.ts +1 -1
  233. package/src/util/telemetry/types.ts +8 -2
  234. package/src/vercel/api.ts +2 -4
  235. package/templates/astro/alchemy.run.ts +1 -3
  236. package/templates/astro/astro.config.mjs +2 -2
  237. package/templates/nuxt/alchemy.run.ts +1 -3
  238. package/templates/nuxt/nuxt.config.ts +4 -5
  239. package/templates/nuxt/package.json +0 -1
  240. package/templates/react-router/alchemy.run.ts +1 -4
  241. package/templates/react-router/vite.config.ts +2 -2
  242. package/templates/rwsdk/alchemy.run.ts +0 -1
  243. package/templates/rwsdk/package.json +0 -2
  244. package/templates/rwsdk/vite.config.mts +2 -2
  245. package/templates/sveltekit/alchemy.run.ts +1 -3
  246. package/templates/sveltekit/svelte.config.js +2 -2
  247. package/templates/tanstack-start/vite.config.ts +2 -2
  248. package/templates/vite/alchemy.run.ts +1 -1
  249. package/templates/vite/vite.config.ts +4 -5
  250. package/workers/cloudflare-state-store.js +12 -14
  251. package/lib/cloudflare/astro.d.ts.map +0 -1
  252. package/lib/cloudflare/astro.js +0 -69
  253. package/lib/cloudflare/astro.js.map +0 -1
  254. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts +0 -13
  255. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +0 -1
  256. package/lib/cloudflare/local-dev-cloudflare-shim.js +0 -26
  257. package/lib/cloudflare/local-dev-cloudflare-shim.js.map +0 -1
  258. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts +0 -14
  259. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +0 -1
  260. package/lib/cloudflare/miniflare/miniflare-worker-options.js +0 -468
  261. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +0 -1
  262. package/lib/cloudflare/miniflare/miniflare.d.ts +0 -40
  263. package/lib/cloudflare/miniflare/miniflare.d.ts.map +0 -1
  264. package/lib/cloudflare/miniflare/miniflare.js +0 -142
  265. package/lib/cloudflare/miniflare/miniflare.js.map +0 -1
  266. package/lib/cloudflare/nuxt.d.ts.map +0 -1
  267. package/lib/cloudflare/nuxt.js.map +0 -1
  268. package/lib/cloudflare/react-router.d.ts +0 -17
  269. package/lib/cloudflare/react-router.d.ts.map +0 -1
  270. package/lib/cloudflare/react-router.js +0 -35
  271. package/lib/cloudflare/react-router.js.map +0 -1
  272. package/lib/cloudflare/redwood.d.ts.map +0 -1
  273. package/lib/cloudflare/redwood.js.map +0 -1
  274. package/lib/cloudflare/sveltekit.d.ts.map +0 -1
  275. package/lib/cloudflare/sveltekit.js.map +0 -1
  276. package/lib/cloudflare/tanstack-start.d.ts.map +0 -1
  277. package/lib/cloudflare/tanstack-start.js +0 -21
  278. package/lib/cloudflare/tanstack-start.js.map +0 -1
  279. package/lib/cloudflare/vite.d.ts.map +0 -1
  280. package/lib/cloudflare/vite.js +0 -20
  281. package/lib/cloudflare/vite.js.map +0 -1
  282. package/src/cloudflare/local-dev-cloudflare-shim.ts +0 -25
  283. package/src/cloudflare/miniflare/miniflare-worker-options.ts +0 -599
  284. package/src/cloudflare/miniflare/miniflare.ts +0 -183
  285. package/src/cloudflare/react-router.ts +0 -56
  286. package/src/cloudflare/tanstack-start.ts +0 -35
  287. package/src/cloudflare/vite.ts +0 -35
  288. package/templates/react-router/wrangler.jsonc +0 -6
  289. package/templates/rwsdk/wrangler.jsonc +0 -31
  290. package/templates/sveltekit/wrangler.jsonc +0 -7
  291. package/templates/tanstack-start/wrangler.jsonc +0 -7
  292. package/templates/typescript/index.ts +0 -1
  293. package/templates/vite/wrangler.jsonc +0 -6
@@ -0,0 +1,454 @@
1
+ import * as miniflare from "miniflare";
2
+ import { assertNever } from "../../util/assert-never.ts";
3
+ import type { HTTPServer } from "../../util/http.ts";
4
+ import {
5
+ Self,
6
+ type Binding,
7
+ type Bindings,
8
+ type WorkerBindingService,
9
+ type WorkerBindingSpec,
10
+ } from "../bindings.ts";
11
+ import { isQueueEventSource, type EventSource } from "../event-source.ts";
12
+ import type { WorkerBundle, WorkerBundleSource } from "../worker-bundle.ts";
13
+ import type { AssetsConfig } from "../worker.ts";
14
+ import { createRemoteProxyWorker } from "./remote-binding-proxy.ts";
15
+
16
+ export interface MiniflareWorkerInput {
17
+ id: string;
18
+ name: string;
19
+ compatibilityDate: string | undefined;
20
+ compatibilityFlags: string[] | undefined;
21
+ bindings: Bindings | undefined;
22
+ eventSources: EventSource[] | undefined;
23
+ assets: AssetsConfig | undefined;
24
+ bundle: WorkerBundleSource;
25
+ port: number | undefined;
26
+ }
27
+
28
+ type RemoteOnlyBindingType =
29
+ | "ai"
30
+ | "ai_gateway"
31
+ | "browser"
32
+ | "dispatch_namespace"
33
+ | "mtls_certificate"
34
+ | "vectorize";
35
+ type RemoteOptionalBindingType =
36
+ | "d1"
37
+ | "images"
38
+ | "kv_namespace"
39
+ | "queue"
40
+ | "r2_bucket";
41
+
42
+ type RemoteBinding =
43
+ | (Extract<
44
+ WorkerBindingSpec,
45
+ {
46
+ type: RemoteOnlyBindingType | RemoteOptionalBindingType;
47
+ }
48
+ > & {
49
+ raw: true;
50
+ })
51
+ | WorkerBindingService;
52
+
53
+ type BaseWorkerOptions = {
54
+ [K in keyof miniflare.WorkerOptions]: K extends "compatibilityFlags"
55
+ ? miniflare.WorkerOptions[K]
56
+ : Exclude<miniflare.WorkerOptions[K], string[]>;
57
+ };
58
+
59
+ export const buildWorkerOptions = async (
60
+ input: MiniflareWorkerInput,
61
+ ): Promise<{
62
+ watch: (signal: AbortSignal) => AsyncGenerator<miniflare.WorkerOptions>;
63
+ remoteProxy: HTTPServer | undefined;
64
+ }> => {
65
+ const remoteBindings: RemoteBinding[] = [];
66
+ const options: Partial<BaseWorkerOptions> = {
67
+ name: input.name,
68
+ compatibilityDate: input.compatibilityDate,
69
+ compatibilityFlags: input.compatibilityFlags,
70
+ // TODO: Setting `proxy: true` here causes the following error when connecting via a websocket:
71
+ // workerd/io/worker.c++:2164: info: uncaught exception; source = Uncaught (in promise); stack = TypeError: Invalid URL string.
72
+ unsafeDirectSockets: [{ proxy: false }],
73
+ containerEngine: {
74
+ localDocker: {
75
+ socketPath:
76
+ process.platform === "win32"
77
+ ? "//./pipe/docker_engine"
78
+ : "unix:///var/run/docker.sock",
79
+ },
80
+ },
81
+ };
82
+ for (const [key, binding] of Object.entries(input.bindings ?? {})) {
83
+ if (typeof binding === "string") {
84
+ (options.bindings ??= {})[key] = binding;
85
+ continue;
86
+ }
87
+ if (binding === Self) {
88
+ (options.serviceBindings ??= {})[key] = miniflare.kCurrentWorker;
89
+ continue;
90
+ }
91
+ switch (binding.type) {
92
+ case "ai":
93
+ case "ai_gateway": {
94
+ remoteBindings.push({
95
+ type: "ai",
96
+ name: key,
97
+ raw: true,
98
+ });
99
+ break;
100
+ }
101
+ case "analytics_engine": {
102
+ (options.analyticsEngineDatasets ??= {})[key] = {
103
+ dataset: binding.dataset,
104
+ };
105
+ break;
106
+ }
107
+ case "assets": {
108
+ options.assets = {
109
+ binding: key,
110
+ directory: binding.path,
111
+ assetConfig: {
112
+ html_handling: input.assets?.html_handling,
113
+ not_found_handling: input.assets?.not_found_handling,
114
+ },
115
+ };
116
+ break;
117
+ }
118
+ case "browser": {
119
+ remoteBindings.push({
120
+ type: "browser",
121
+ name: key,
122
+ raw: true,
123
+ });
124
+ break;
125
+ }
126
+ case "container": {
127
+ (options.durableObjects ??= {})[key] = {
128
+ className: binding.className,
129
+ scriptName: binding.scriptName,
130
+ useSQLite: binding.sqlite,
131
+ container: {
132
+ imageName: binding.image.imageRef,
133
+ },
134
+ };
135
+ break;
136
+ }
137
+ case "d1": {
138
+ if (isRemoteBinding(binding)) {
139
+ remoteBindings.push({
140
+ type: "d1",
141
+ name: key,
142
+ id: binding.id,
143
+ raw: true,
144
+ });
145
+ } else {
146
+ (options.d1Databases ??= {})[key] = binding.id;
147
+ }
148
+ break;
149
+ }
150
+ case "dispatch_namespace": {
151
+ remoteBindings.push({
152
+ type: "dispatch_namespace",
153
+ name: key,
154
+ namespace: binding.namespace,
155
+ raw: true,
156
+ });
157
+ break;
158
+ }
159
+ case "durable_object_namespace": {
160
+ (options.durableObjects ??= {})[key] = {
161
+ className: binding.className,
162
+ scriptName: binding.scriptName,
163
+ useSQLite: binding.sqlite,
164
+ };
165
+ break;
166
+ }
167
+ case "hyperdrive": {
168
+ throw new Error("Hyperdrive bindings are not supported in local mode");
169
+ }
170
+ case "images": {
171
+ if (isRemoteBinding(binding)) {
172
+ remoteBindings.push({
173
+ type: "images",
174
+ name: key,
175
+ raw: true,
176
+ });
177
+ } else {
178
+ options.images = {
179
+ binding: key,
180
+ };
181
+ }
182
+ break;
183
+ }
184
+ case "kv_namespace": {
185
+ const namespaceId =
186
+ "namespaceId" in binding ? binding.namespaceId : binding.id;
187
+ if (isRemoteBinding(binding)) {
188
+ remoteBindings.push({
189
+ type: "kv_namespace",
190
+ name: key,
191
+ namespace_id: namespaceId,
192
+ raw: true,
193
+ });
194
+ } else {
195
+ (options.kvNamespaces ??= {})[key] = namespaceId;
196
+ }
197
+ break;
198
+ }
199
+ case "json": {
200
+ (options.bindings ??= {})[key] = binding.json;
201
+ break;
202
+ }
203
+ case "queue": {
204
+ if (isRemoteBinding(binding)) {
205
+ remoteBindings.push({
206
+ type: "queue",
207
+ name: key,
208
+ queue_name: binding.name,
209
+ raw: true,
210
+ });
211
+ } else {
212
+ (options.queueProducers ??= {})[key] = {
213
+ queueName: binding.name,
214
+ deliveryDelay: binding.settings?.deliveryDelay,
215
+ };
216
+ }
217
+ break;
218
+ }
219
+ case "pipeline": {
220
+ (options.pipelines ??= {})[key] = binding.id;
221
+ break;
222
+ }
223
+ case "ratelimit": {
224
+ (options.ratelimits ??= {})[key] = {
225
+ simple: binding.simple,
226
+ };
227
+ break;
228
+ }
229
+ case "secret": {
230
+ (options.bindings ??= {})[key] = binding.unencrypted;
231
+ break;
232
+ }
233
+ case "secret_key": {
234
+ throw new Error("Secret keys are not supported in local mode");
235
+ }
236
+ case "secrets_store_secret": {
237
+ (options.secretsStoreSecrets ??= {})[key] = {
238
+ store_id: binding.storeId,
239
+ secret_name: binding.name,
240
+ };
241
+ break;
242
+ }
243
+ case "r2_bucket": {
244
+ if (isRemoteBinding(binding)) {
245
+ remoteBindings.push({
246
+ type: "r2_bucket",
247
+ name: key,
248
+ bucket_name: binding.name,
249
+ raw: true,
250
+ });
251
+ } else {
252
+ (options.r2Buckets ??= {})[key] = binding.name;
253
+ }
254
+ break;
255
+ }
256
+ case "service": {
257
+ const service = "service" in binding ? binding.service : binding.name;
258
+ const normalized: WorkerBindingService = {
259
+ type: "service",
260
+ name: key,
261
+ service,
262
+ environment:
263
+ "environment" in binding ? binding.environment : undefined,
264
+ };
265
+ if (isRemoteBinding(binding)) {
266
+ remoteBindings.push(normalized);
267
+ } else {
268
+ (options.serviceBindings ??= {})[key] = service;
269
+ }
270
+ break;
271
+ }
272
+ case "vectorize": {
273
+ remoteBindings.push({
274
+ type: "vectorize",
275
+ name: key,
276
+ index_name: binding.name,
277
+ raw: true,
278
+ });
279
+ break;
280
+ }
281
+ case "version_metadata": {
282
+ (options.bindings ??= {})[key] = {
283
+ id: crypto.randomUUID(),
284
+ tag: "",
285
+ timestamp: "0",
286
+ };
287
+ break;
288
+ }
289
+ case "workflow": {
290
+ (options.workflows ??= {})[key] = {
291
+ name: binding.workflowName,
292
+ className: binding.className,
293
+ scriptName: binding.scriptName,
294
+ };
295
+ break;
296
+ }
297
+ default: {
298
+ assertNever(binding);
299
+ }
300
+ }
301
+ }
302
+ for (const eventSource of input.eventSources ?? []) {
303
+ const queue = "queue" in eventSource ? eventSource.queue : eventSource;
304
+ if (queue.dev?.remote) {
305
+ throw new Error(
306
+ `Locally emulated workers cannot consume remote queues. Worker "${input.name}" is locally emulated but is consuming remote queue "${queue.name}".`,
307
+ );
308
+ }
309
+ if (isQueueEventSource(eventSource)) {
310
+ (options.queueConsumers ??= {})[queue.name] = {
311
+ maxBatchSize: eventSource.settings?.batchSize,
312
+ maxBatchTimeout: eventSource.settings?.maxWaitTimeMs,
313
+ maxRetries: eventSource.settings?.maxRetries,
314
+ retryDelay: eventSource.settings?.retryDelay,
315
+ };
316
+ } else {
317
+ (options.queueConsumers ??= {})[eventSource.name] = {};
318
+ }
319
+ }
320
+ async function* watch(signal: AbortSignal) {
321
+ for await (const bundle of input.bundle.watch(signal)) {
322
+ const { modules, rootPath } = normalizeBundle(bundle);
323
+ yield {
324
+ ...options,
325
+ modules,
326
+ rootPath,
327
+ };
328
+ }
329
+ }
330
+ if (remoteBindings.length > 0) {
331
+ const remoteProxy = await createRemoteProxyWorker({
332
+ name: input.name,
333
+ bindings: remoteBindings,
334
+ });
335
+ for (const binding of remoteBindings) {
336
+ switch (binding.type) {
337
+ case "ai":
338
+ options.ai = {
339
+ binding: binding.name,
340
+ remoteProxyConnectionString: remoteProxy.connectionString,
341
+ };
342
+ break;
343
+ case "browser":
344
+ options.browserRendering = {
345
+ binding: binding.name,
346
+ remoteProxyConnectionString: remoteProxy.connectionString,
347
+ };
348
+ break;
349
+ case "d1":
350
+ (options.d1Databases ??= {})[binding.name] = {
351
+ id: binding.id,
352
+ remoteProxyConnectionString: remoteProxy.connectionString,
353
+ };
354
+ break;
355
+ case "dispatch_namespace":
356
+ (options.dispatchNamespaces ??= {})[binding.name] = {
357
+ namespace: binding.namespace,
358
+ remoteProxyConnectionString: remoteProxy.connectionString,
359
+ };
360
+ break;
361
+ case "kv_namespace":
362
+ (options.kvNamespaces ??= {})[binding.name] = {
363
+ id: binding.namespace_id,
364
+ remoteProxyConnectionString: remoteProxy.connectionString,
365
+ };
366
+ break;
367
+ case "images":
368
+ options.images = {
369
+ binding: binding.name,
370
+ remoteProxyConnectionString: remoteProxy.connectionString,
371
+ };
372
+ break;
373
+ case "mtls_certificate":
374
+ (options.mtlsCertificates ??= {})[binding.name] = {
375
+ certificate_id: binding.certificate_id,
376
+ remoteProxyConnectionString: remoteProxy.connectionString,
377
+ };
378
+ break;
379
+ case "queue":
380
+ (options.queueProducers ??= {})[binding.name] = {
381
+ queueName: binding.queue_name,
382
+ remoteProxyConnectionString: remoteProxy.connectionString,
383
+ };
384
+ break;
385
+ case "r2_bucket":
386
+ (options.r2Buckets ??= {})[binding.name] = {
387
+ id: binding.bucket_name,
388
+ remoteProxyConnectionString: remoteProxy.connectionString,
389
+ };
390
+ break;
391
+ case "service":
392
+ (options.serviceBindings ??= {})[binding.name] = {
393
+ name: binding.name,
394
+ remoteProxyConnectionString: remoteProxy.connectionString,
395
+ };
396
+ break;
397
+ case "vectorize":
398
+ (options.vectorize ??= {})[binding.name] = {
399
+ index_name: binding.index_name,
400
+ remoteProxyConnectionString: remoteProxy.connectionString,
401
+ };
402
+ break;
403
+ default: {
404
+ assertNever(binding);
405
+ }
406
+ }
407
+ }
408
+ return {
409
+ watch,
410
+ remoteProxy: remoteProxy.server,
411
+ };
412
+ }
413
+ return {
414
+ watch,
415
+ remoteProxy: undefined,
416
+ };
417
+ };
418
+
419
+ const moduleTypes = {
420
+ esm: "ESModule",
421
+ cjs: "CommonJS",
422
+ text: "Text",
423
+ data: "Data",
424
+ wasm: "CompiledWasm",
425
+ sourcemap: "Text",
426
+ } as const;
427
+
428
+ const normalizeBundle = (bundle: WorkerBundle) => {
429
+ const modules = bundle.modules.map((module) => ({
430
+ type: moduleTypes[module.type],
431
+ path: module.path,
432
+ contents: module.content,
433
+ }));
434
+ const entry = modules.find((module) => module.path === bundle.entrypoint);
435
+ if (!entry) {
436
+ throw new Error(`Entrypoint "${bundle.entrypoint}" not found in bundle.`);
437
+ }
438
+ return {
439
+ modules: [entry, ...modules.filter((module) => module.path !== entry.path)],
440
+ rootPath: bundle.root,
441
+ };
442
+ };
443
+
444
+ const isRemoteBinding = (binding: Binding) => {
445
+ if (typeof binding === "string" || binding === Self) {
446
+ return false;
447
+ }
448
+ return (
449
+ "dev" in binding &&
450
+ typeof binding.dev === "object" &&
451
+ "remote" in binding.dev &&
452
+ !!binding.dev.remote
453
+ );
454
+ };
@@ -0,0 +1,129 @@
1
+ import * as miniflare from "miniflare";
2
+ import assert from "node:assert";
3
+ import path from "node:path";
4
+ import { findOpenPort } from "../../util/find-open-port.ts";
5
+ import type { HTTPServer } from "../../util/http.ts";
6
+ import { logger } from "../../util/logger.ts";
7
+ import { AsyncMutex } from "../../util/mutex.ts";
8
+ import {
9
+ buildWorkerOptions,
10
+ type MiniflareWorkerInput,
11
+ } from "./build-worker-options.ts";
12
+ import { MiniflareWorkerProxy } from "./miniflare-worker-proxy.ts";
13
+ import { DEFAULT_PERSIST_PATH } from "./paths.ts";
14
+
15
+ declare global {
16
+ var ALCHEMY_MINIFLARE_CONTROLLER: MiniflareController | undefined;
17
+ }
18
+
19
+ export class MiniflareController {
20
+ abort = new AbortController();
21
+ miniflare: miniflare.Miniflare | undefined;
22
+ options = new Map<string, miniflare.WorkerOptions>();
23
+ localProxies = new Map<string, MiniflareWorkerProxy>();
24
+ remoteProxies = new Map<string, HTTPServer>();
25
+ mutex = new AsyncMutex();
26
+
27
+ static get singleton() {
28
+ globalThis.ALCHEMY_MINIFLARE_CONTROLLER ??= new MiniflareController();
29
+ return globalThis.ALCHEMY_MINIFLARE_CONTROLLER;
30
+ }
31
+
32
+ async add(input: MiniflareWorkerInput) {
33
+ const { watch, remoteProxy } = await buildWorkerOptions(input);
34
+ if (remoteProxy) {
35
+ this.remoteProxies.set(input.name, remoteProxy);
36
+ }
37
+ const watcher = watch(this.abort.signal);
38
+ const first = await watcher.next();
39
+ assert(first.value, "First value is undefined");
40
+ this.options.set(input.name, first.value);
41
+ const miniflare = await this.update();
42
+ const proxy = new MiniflareWorkerProxy({
43
+ name: input.name,
44
+ port: input.port ?? (await findOpenPort()),
45
+ miniflare,
46
+ });
47
+ this.localProxies.set(input.name, proxy);
48
+ void this.watch(input.id, watcher);
49
+ logger.task(input.id, {
50
+ message: `Ready at ${proxy.url}`,
51
+ status: "success",
52
+ resource: input.id,
53
+ prefix: "dev",
54
+ prefixColor: "cyanBright",
55
+ });
56
+ return proxy.url;
57
+ }
58
+
59
+ private async watch(
60
+ id: string,
61
+ watcher: AsyncGenerator<miniflare.WorkerOptions>,
62
+ ) {
63
+ for await (const options of watcher) {
64
+ this.options.set(options.name!, options);
65
+ await this.update();
66
+ logger.task(id, {
67
+ message: "Updated",
68
+ status: "success",
69
+ resource: id,
70
+ prefix: "dev",
71
+ prefixColor: "cyanBright",
72
+ });
73
+ }
74
+ }
75
+
76
+ private async update() {
77
+ return await this.mutex.lock(async () => {
78
+ const options: miniflare.MiniflareOptions = {
79
+ workers: Array.from(this.options.values()),
80
+ defaultPersistRoot: path.resolve(DEFAULT_PERSIST_PATH),
81
+ unsafeDevRegistryPath: miniflare.getDefaultDevRegistryPath(),
82
+ analyticsEngineDatasetsPersist: true,
83
+ cachePersist: true,
84
+ d1Persist: true,
85
+ durableObjectsPersist: true,
86
+ kvPersist: true,
87
+ r2Persist: true,
88
+ secretsStorePersist: true,
89
+ workflowsPersist: true,
90
+ log: process.env.DEBUG
91
+ ? new miniflare.Log(miniflare.LogLevel.DEBUG)
92
+ : undefined,
93
+ };
94
+ return await this.setMiniflareOptions(options);
95
+ });
96
+ }
97
+
98
+ private async setMiniflareOptions(options: miniflare.MiniflareOptions) {
99
+ try {
100
+ if (this.miniflare) {
101
+ await this.miniflare.setOptions(options);
102
+ } else {
103
+ this.miniflare = new miniflare.Miniflare(options);
104
+ await this.miniflare.ready;
105
+ }
106
+ return this.miniflare;
107
+ } catch (error) {
108
+ if (
109
+ error instanceof miniflare.MiniflareCoreError &&
110
+ error.code === "ERR_MODULE_STRING_SCRIPT"
111
+ ) {
112
+ throw new Error(
113
+ 'Miniflare detected an external dependency that could not be resolved. This typically occurs when the "nodejs_compat" or "nodejs_als" compatibility flag is not enabled.',
114
+ );
115
+ } else {
116
+ throw error;
117
+ }
118
+ }
119
+ }
120
+
121
+ async dispose() {
122
+ this.abort.abort();
123
+ await Promise.all([
124
+ this.miniflare?.dispose(),
125
+ ...this.localProxies.values().map((proxy) => proxy.close()),
126
+ ...this.remoteProxies.values().map((proxy) => proxy.close()),
127
+ ]);
128
+ }
129
+ }
@@ -55,18 +55,10 @@ export class MiniflareWorkerProxy {
55
55
  return;
56
56
  }
57
57
  this.wss.handleUpgrade(req, socket, head, (client) => {
58
- client.on("message", (event, binary) => {
59
- server.send(event, { binary });
60
- });
61
- client.on("close", (code, reason) => {
62
- server.close(code, reason);
63
- });
64
- server.on("message", (event, binary) => {
65
- client.send(event, { binary });
66
- });
67
- server.on("close", (code, reason) => {
68
- client.close(code, reason);
69
- });
58
+ client.on("message", (event, binary) => server.send(event, { binary }));
59
+ client.on("close", (code, reason) => server.close(code, reason));
60
+ server.on("message", (event, binary) => client.send(event, { binary }));
61
+ server.on("close", (code, reason) => client.close(code, reason));
70
62
  this.wss.emit("connection", client, req);
71
63
  });
72
64
  }
@@ -157,9 +149,8 @@ const writeMiniflareResponseToNode = (
157
149
  out: http.ServerResponse,
158
150
  ) => {
159
151
  out.statusCode = res.status;
160
- res.headers.forEach((value, key) => {
161
- out.setHeader(key, value);
162
- });
152
+ res.headers.forEach((value, key) => out.setHeader(key, value));
153
+
163
154
  if (res.body) {
164
155
  Readable.fromWeb(res.body).pipe(out, { end: true });
165
156
  } else {
@@ -0,0 +1,52 @@
1
+ import { existsSync, readlinkSync, statSync } from "node:fs";
2
+
3
+ export const DEFAULT_CONFIG_PATH = ".alchemy/local/wrangler.jsonc";
4
+ export const DEFAULT_PERSIST_PATH = ".alchemy/miniflare/v3";
5
+
6
+ export const validateConfigPath = (
7
+ path = DEFAULT_CONFIG_PATH,
8
+ throws = true,
9
+ ) => {
10
+ if (!existsSync(path)) {
11
+ warnOrThrow(
12
+ `The Wrangler config path, "${path}", could not be found. Please run \`alchemy dev\` or \`alchemy deploy\` to create it.`,
13
+ throws,
14
+ );
15
+ }
16
+ return path;
17
+ };
18
+
19
+ const DEFAULT_VALIDATE_PERSIST =
20
+ process.env.NODE_ENV === "development" ||
21
+ !process.argv.some((arg) => ["build", "prepare", "typegen"].includes(arg));
22
+
23
+ export const validatePersistPath = (
24
+ path = DEFAULT_PERSIST_PATH,
25
+ throws = DEFAULT_VALIDATE_PERSIST,
26
+ ) => {
27
+ try {
28
+ const stat = statSync(path);
29
+ if (stat.isSymbolicLink()) {
30
+ return readlinkSync(path);
31
+ }
32
+ return path;
33
+ } catch {
34
+ warnOrThrow(
35
+ `The Alchemy data path, "${path}", could not be resolved. This is required for Cloudflare bindings during development. Please run \`alchemy dev\` to create it.`,
36
+ throws,
37
+ );
38
+ return path;
39
+ }
40
+ };
41
+
42
+ const warned = new Set<string>();
43
+
44
+ const warnOrThrow = (message: string, throws: boolean) => {
45
+ if (throws) {
46
+ throw new Error(message);
47
+ }
48
+ if (!warned.has(message)) {
49
+ console.warn(message);
50
+ warned.add(message);
51
+ }
52
+ };