devflare 1.0.0-next.7 → 1.0.0-next.70

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 (632) hide show
  1. package/LICENSE +21 -0
  2. package/LLM.md +17315 -2144
  3. package/README.md +398 -607
  4. package/bin/devflare.js +30 -14
  5. package/dist/_chunks/account-BHY3fvrA.js +462 -0
  6. package/dist/_chunks/account-resources-BdKj8q1h.js +159 -0
  7. package/dist/_chunks/ai-CN_SREim.js +322 -0
  8. package/dist/_chunks/api-BKClUlUf.js +568 -0
  9. package/dist/_chunks/build-DplSo6Gc.js +24 -0
  10. package/dist/_chunks/build-artifacts-CkwCap5N.js +483 -0
  11. package/dist/_chunks/cli-ZJYtm-lW.js +1214 -0
  12. package/dist/_chunks/cloudflare-Ic5ubQza.js +157 -0
  13. package/dist/_chunks/colors-D0NVzAFN.js +13 -0
  14. package/dist/_chunks/command-utils-DjaaMQX7.js +45 -0
  15. package/dist/_chunks/compiler-UxXQgEPq.js +521 -0
  16. package/dist/_chunks/config-path-CYGlwKDs.js +42 -0
  17. package/dist/_chunks/config-zQd4jmXU.js +78 -0
  18. package/dist/_chunks/context-DGMev2AF.js +2515 -0
  19. package/dist/_chunks/decorators-eXNZ1S0N.js +19 -0
  20. package/dist/_chunks/dependencies-wKqrxKV_.js +57 -0
  21. package/dist/_chunks/deploy-z1_wbNn2.js +1032 -0
  22. package/dist/_chunks/dev-BagtFegV.js +952 -0
  23. package/dist/_chunks/doctor-CAsJ-rSZ.js +219 -0
  24. package/dist/_chunks/durable-object-DuMPM8re.js +234 -0
  25. package/dist/_chunks/env-D4uJqqgu.js +125 -0
  26. package/dist/_chunks/family-BTTpTwUk.js +143 -0
  27. package/dist/_chunks/generated-artifacts-Bb13pGUG.js +32 -0
  28. package/dist/_chunks/glob-DRw9sbfF.js +56 -0
  29. package/dist/_chunks/init-DJ5y3mu5.js +165 -0
  30. package/dist/_chunks/loader-BC0skbnH.js +1330 -0
  31. package/dist/_chunks/local-dev-vars-BFLS-HoI.js +81 -0
  32. package/dist/_chunks/local-hyperdrive-CcORzo76.js +119 -0
  33. package/dist/_chunks/local-media-bindings-ZQe-epuw.js +335 -0
  34. package/dist/_chunks/local-secrets-C0n5oRgI.js +123 -0
  35. package/dist/_chunks/login-CgNcbpEq.js +51 -0
  36. package/dist/_chunks/package-metadata-CLI488gB.js +39 -0
  37. package/dist/_chunks/preferences-CbYERA_m.js +241 -0
  38. package/dist/_chunks/preview-j9Kbx4-G.js +462 -0
  39. package/dist/_chunks/preview-registry-CTXU4mBc.js +1186 -0
  40. package/dist/_chunks/preview-resources-CQIu2-NY.js +359 -0
  41. package/dist/_chunks/previews-BIaw6NVC.js +1133 -0
  42. package/dist/_chunks/productions-CF8oQcX7.js +504 -0
  43. package/dist/_chunks/r2-presign-runtime-C75FkLMd.js +291 -0
  44. package/dist/_chunks/ref-CYJNAAe_.js +178 -0
  45. package/dist/_chunks/remote-8jEHLVGg.js +79 -0
  46. package/dist/_chunks/remote-config-Bw6qIVM4.js +132 -0
  47. package/dist/_chunks/resolve-package-DeJUGxoE.js +152 -0
  48. package/dist/_chunks/rolldown-runtime-CvQ6eiug.js +14 -0
  49. package/dist/_chunks/routes-CRXB3-iw.js +851 -0
  50. package/dist/_chunks/runtime-BlKTWIgD.js +874 -0
  51. package/dist/_chunks/schema-normalization-CTdiY17P.js +667 -0
  52. package/dist/_chunks/secrets-BJuEa9Jz.js +95 -0
  53. package/dist/_chunks/tail-DjGRt8gw.js +239 -0
  54. package/dist/_chunks/token-wmeF8-IQ.js +333 -0
  55. package/dist/_chunks/tokens-KaEr4B8e.js +221 -0
  56. package/dist/_chunks/types-BLGfxIq-.js +484 -0
  57. package/dist/_chunks/ui-BhCdDC2N.js +103 -0
  58. package/dist/_chunks/usage-rRusKejc.js +272 -0
  59. package/dist/_chunks/vite-Bml-Whp8.js +1267 -0
  60. package/dist/_chunks/vite-process-CDa3yQui.js +2929 -0
  61. package/dist/_chunks/vite-utils-D8kPPxUy.js +183 -0
  62. package/dist/_chunks/worker-DgczaXVW.js +410 -0
  63. package/dist/_chunks/worker-bundler-Isj5jiW6.js +458 -0
  64. package/dist/_chunks/worker-entrypoint-C8j23t6d.js +247 -0
  65. package/dist/_chunks/workerName-kEXwmV2m.js +23 -0
  66. package/dist/_chunks/workspace-CrvqPLTX.js +517 -0
  67. package/dist/_chunks/workspace-Ds3xMjOE.js +175 -0
  68. package/dist/bridge/client.d.ts +93 -8
  69. package/dist/bridge/client.d.ts.map +1 -1
  70. package/dist/bridge/do-rpc-dispatch.d.ts +23 -0
  71. package/dist/bridge/do-rpc-dispatch.d.ts.map +1 -0
  72. package/dist/bridge/do-websocket-connect.d.ts +33 -0
  73. package/dist/bridge/do-websocket-connect.d.ts.map +1 -0
  74. package/dist/bridge/gateway-runtime.d.ts +8 -0
  75. package/dist/bridge/gateway-runtime.d.ts.map +1 -0
  76. package/dist/bridge/index.d.ts +6 -6
  77. package/dist/bridge/index.d.ts.map +1 -1
  78. package/dist/bridge/log.d.ts +5 -0
  79. package/dist/bridge/log.d.ts.map +1 -0
  80. package/dist/bridge/miniflare-gateway.d.ts +45 -0
  81. package/dist/bridge/miniflare-gateway.d.ts.map +1 -0
  82. package/dist/bridge/miniflare.d.ts +92 -2
  83. package/dist/bridge/miniflare.d.ts.map +1 -1
  84. package/dist/bridge/proxy.d.ts +12 -11
  85. package/dist/bridge/proxy.d.ts.map +1 -1
  86. package/dist/bridge/r2-presign-runtime.d.ts +9 -0
  87. package/dist/bridge/r2-presign-runtime.d.ts.map +1 -0
  88. package/dist/bridge/server.d.ts +2 -1
  89. package/dist/bridge/server.d.ts.map +1 -1
  90. package/dist/bridge/v2/body-streams.d.ts +53 -0
  91. package/dist/bridge/v2/body-streams.d.ts.map +1 -0
  92. package/dist/bridge/v2/codec.d.ts +106 -0
  93. package/dist/bridge/v2/codec.d.ts.map +1 -0
  94. package/dist/bridge/v2/control-messages.d.ts +46 -0
  95. package/dist/bridge/v2/control-messages.d.ts.map +1 -0
  96. package/dist/bridge/v2/frames.d.ts +109 -0
  97. package/dist/bridge/v2/frames.d.ts.map +1 -0
  98. package/dist/bridge/v2/index.d.ts +11 -0
  99. package/dist/bridge/v2/index.d.ts.map +1 -0
  100. package/dist/bridge/v2/serialization.d.ts +48 -0
  101. package/dist/bridge/v2/serialization.d.ts.map +1 -0
  102. package/dist/bridge/v2/transport.d.ts +31 -0
  103. package/dist/bridge/v2/transport.d.ts.map +1 -0
  104. package/dist/bridge/v2/value-codec.d.ts +61 -0
  105. package/dist/bridge/v2/value-codec.d.ts.map +1 -0
  106. package/dist/bridge/v2/value-serialization.d.ts +139 -0
  107. package/dist/bridge/v2/value-serialization.d.ts.map +1 -0
  108. package/dist/bridge/{protocol.d.ts → v2/wire.d.ts} +32 -8
  109. package/dist/bridge/v2/wire.d.ts.map +1 -0
  110. package/dist/bridge/v2/ws-relay.d.ts +53 -0
  111. package/dist/bridge/v2/ws-relay.d.ts.map +1 -0
  112. package/dist/bridge/websocket-constructor.d.ts +38 -0
  113. package/dist/bridge/websocket-constructor.d.ts.map +1 -0
  114. package/dist/browser-shim/handler.d.ts +2 -2
  115. package/dist/browser-shim/handler.d.ts.map +1 -1
  116. package/dist/browser-shim/index.d.ts +2 -2
  117. package/dist/browser-shim/server.d.ts +63 -0
  118. package/dist/browser-shim/server.d.ts.map +1 -1
  119. package/dist/browser.d.ts +5778 -43
  120. package/dist/browser.d.ts.map +1 -1
  121. package/dist/browser.js +73 -142
  122. package/dist/bundler/defaults.d.ts +23 -0
  123. package/dist/bundler/defaults.d.ts.map +1 -0
  124. package/dist/bundler/do-bundler.d.ts +7 -0
  125. package/dist/bundler/do-bundler.d.ts.map +1 -1
  126. package/dist/bundler/index.d.ts +3 -1
  127. package/dist/bundler/index.d.ts.map +1 -1
  128. package/dist/bundler/rolldown-shared.d.ts +53 -0
  129. package/dist/bundler/rolldown-shared.d.ts.map +1 -0
  130. package/dist/bundler/worker-bundler.d.ts +13 -0
  131. package/dist/bundler/worker-bundler.d.ts.map +1 -0
  132. package/dist/bundler/worker-compat.d.ts +4 -0
  133. package/dist/bundler/worker-compat.d.ts.map +1 -0
  134. package/dist/cli/build-manifest.d.ts +66 -0
  135. package/dist/cli/build-manifest.d.ts.map +1 -0
  136. package/dist/cli/command-utils.d.ts +18 -0
  137. package/dist/cli/command-utils.d.ts.map +1 -0
  138. package/dist/cli/commands/account.d.ts +2 -2
  139. package/dist/cli/commands/account.d.ts.map +1 -1
  140. package/dist/cli/commands/ai.d.ts +1 -1
  141. package/dist/cli/commands/ai.d.ts.map +1 -1
  142. package/dist/cli/commands/build-artifacts.d.ts +40 -0
  143. package/dist/cli/commands/build-artifacts.d.ts.map +1 -0
  144. package/dist/cli/commands/build.d.ts +2 -2
  145. package/dist/cli/commands/build.d.ts.map +1 -1
  146. package/dist/cli/commands/config.d.ts +4 -0
  147. package/dist/cli/commands/config.d.ts.map +1 -0
  148. package/dist/cli/commands/deploy/metadata.d.ts +18 -0
  149. package/dist/cli/commands/deploy/metadata.d.ts.map +1 -0
  150. package/dist/cli/commands/deploy/prepare.d.ts +23 -0
  151. package/dist/cli/commands/deploy/prepare.d.ts.map +1 -0
  152. package/dist/cli/commands/deploy/runtime.d.ts +4 -0
  153. package/dist/cli/commands/deploy/runtime.d.ts.map +1 -0
  154. package/dist/cli/commands/deploy/verification.d.ts +36 -0
  155. package/dist/cli/commands/deploy/verification.d.ts.map +1 -0
  156. package/dist/cli/commands/deploy.d.ts +2 -2
  157. package/dist/cli/commands/deploy.d.ts.map +1 -1
  158. package/dist/cli/commands/dev.d.ts +9 -1
  159. package/dist/cli/commands/dev.d.ts.map +1 -1
  160. package/dist/cli/commands/doctor.d.ts +2 -2
  161. package/dist/cli/commands/doctor.d.ts.map +1 -1
  162. package/dist/cli/commands/init.d.ts +2 -2
  163. package/dist/cli/commands/init.d.ts.map +1 -1
  164. package/dist/cli/commands/login.d.ts +4 -0
  165. package/dist/cli/commands/login.d.ts.map +1 -0
  166. package/dist/cli/commands/previews-support/cleanup.d.ts +9 -0
  167. package/dist/cli/commands/previews-support/cleanup.d.ts.map +1 -0
  168. package/dist/cli/commands/previews-support/family.d.ts +10 -0
  169. package/dist/cli/commands/previews-support/family.d.ts.map +1 -0
  170. package/dist/cli/commands/previews-support/render.d.ts +8 -0
  171. package/dist/cli/commands/previews-support/render.d.ts.map +1 -0
  172. package/dist/cli/commands/previews-support/theme.d.ts +10 -0
  173. package/dist/cli/commands/previews-support/theme.d.ts.map +1 -0
  174. package/dist/cli/commands/previews-support/types.d.ts +70 -0
  175. package/dist/cli/commands/previews-support/types.d.ts.map +1 -0
  176. package/dist/cli/commands/previews.d.ts +4 -0
  177. package/dist/cli/commands/previews.d.ts.map +1 -0
  178. package/dist/cli/commands/productions.d.ts +4 -0
  179. package/dist/cli/commands/productions.d.ts.map +1 -0
  180. package/dist/cli/commands/remote.d.ts +1 -1
  181. package/dist/cli/commands/remote.d.ts.map +1 -1
  182. package/dist/cli/commands/secrets.d.ts +4 -0
  183. package/dist/cli/commands/secrets.d.ts.map +1 -0
  184. package/dist/cli/commands/tail.d.ts +44 -0
  185. package/dist/cli/commands/tail.d.ts.map +1 -0
  186. package/dist/cli/commands/token.d.ts +4 -0
  187. package/dist/cli/commands/token.d.ts.map +1 -0
  188. package/dist/cli/commands/type-generation/discovery.d.ts +7 -0
  189. package/dist/cli/commands/type-generation/discovery.d.ts.map +1 -0
  190. package/dist/cli/commands/type-generation/generator.d.ts +121 -0
  191. package/dist/cli/commands/type-generation/generator.d.ts.map +1 -0
  192. package/dist/cli/commands/type-generation/models.d.ts +27 -0
  193. package/dist/cli/commands/type-generation/models.d.ts.map +1 -0
  194. package/dist/cli/commands/types.d.ts +2 -2
  195. package/dist/cli/commands/types.d.ts.map +1 -1
  196. package/dist/cli/commands/worker.d.ts +4 -0
  197. package/dist/cli/commands/worker.d.ts.map +1 -0
  198. package/dist/cli/commands/workspace.d.ts +8 -0
  199. package/dist/cli/commands/workspace.d.ts.map +1 -0
  200. package/dist/cli/config-path.d.ts +6 -0
  201. package/dist/cli/config-path.d.ts.map +1 -0
  202. package/dist/cli/dependencies.d.ts +2 -1
  203. package/dist/cli/dependencies.d.ts.map +1 -1
  204. package/dist/cli/deploy-strategy.d.ts +17 -0
  205. package/dist/cli/deploy-strategy.d.ts.map +1 -0
  206. package/dist/cli/deploy-target.d.ts +17 -0
  207. package/dist/cli/deploy-target.d.ts.map +1 -0
  208. package/dist/cli/generated-artifacts.d.ts +12 -0
  209. package/dist/cli/generated-artifacts.d.ts.map +1 -0
  210. package/dist/cli/gradual-deploy.d.ts +49 -0
  211. package/dist/cli/gradual-deploy.d.ts.map +1 -0
  212. package/dist/cli/help-pages/pages/account.d.ts +3 -0
  213. package/dist/cli/help-pages/pages/account.d.ts.map +1 -0
  214. package/dist/cli/help-pages/pages/core.d.ts +4 -0
  215. package/dist/cli/help-pages/pages/core.d.ts.map +1 -0
  216. package/dist/cli/help-pages/pages/index.d.ts +3 -0
  217. package/dist/cli/help-pages/pages/index.d.ts.map +1 -0
  218. package/dist/cli/help-pages/pages/misc.d.ts +3 -0
  219. package/dist/cli/help-pages/pages/misc.d.ts.map +1 -0
  220. package/dist/cli/help-pages/pages/previews.d.ts +3 -0
  221. package/dist/cli/help-pages/pages/previews.d.ts.map +1 -0
  222. package/dist/cli/help-pages/pages/productions.d.ts +3 -0
  223. package/dist/cli/help-pages/pages/productions.d.ts.map +1 -0
  224. package/dist/cli/help-pages/render.d.ts +12 -0
  225. package/dist/cli/help-pages/render.d.ts.map +1 -0
  226. package/dist/cli/help-pages/shared.d.ts +15 -0
  227. package/dist/cli/help-pages/shared.d.ts.map +1 -0
  228. package/dist/cli/help-pages/types.d.ts +23 -0
  229. package/dist/cli/help-pages/types.d.ts.map +1 -0
  230. package/dist/cli/help.d.ts +6 -0
  231. package/dist/cli/help.d.ts.map +1 -0
  232. package/dist/cli/index.d.ts +1 -0
  233. package/dist/cli/index.d.ts.map +1 -1
  234. package/dist/cli/index.js +2 -0
  235. package/dist/cli/package-metadata.d.ts +16 -0
  236. package/dist/cli/package-metadata.d.ts.map +1 -0
  237. package/dist/cli/preview-bindings.d.ts +49 -0
  238. package/dist/cli/preview-bindings.d.ts.map +1 -0
  239. package/dist/cli/preview.d.ts +10 -0
  240. package/dist/cli/preview.d.ts.map +1 -0
  241. package/dist/cli/ui.d.ts +37 -0
  242. package/dist/cli/ui.d.ts.map +1 -0
  243. package/dist/cli/workspace-build-guard.d.ts +14 -0
  244. package/dist/cli/workspace-build-guard.d.ts.map +1 -0
  245. package/dist/cli/wrangler-auth.d.ts +1 -1
  246. package/dist/cloudflare/account-core.d.ts +6 -0
  247. package/dist/cloudflare/account-core.d.ts.map +1 -0
  248. package/dist/cloudflare/account-resources.d.ts +40 -0
  249. package/dist/cloudflare/account-resources.d.ts.map +1 -0
  250. package/dist/cloudflare/account-status.d.ts +11 -0
  251. package/dist/cloudflare/account-status.d.ts.map +1 -0
  252. package/dist/cloudflare/account-workers.d.ts +14 -0
  253. package/dist/cloudflare/account-workers.d.ts.map +1 -0
  254. package/dist/cloudflare/account.d.ts +7 -64
  255. package/dist/cloudflare/account.d.ts.map +1 -1
  256. package/dist/cloudflare/api.d.ts +71 -3
  257. package/dist/cloudflare/api.d.ts.map +1 -1
  258. package/dist/cloudflare/auth.d.ts +1 -1
  259. package/dist/cloudflare/auth.d.ts.map +1 -1
  260. package/dist/cloudflare/index.d.ts +61 -6
  261. package/dist/cloudflare/index.d.ts.map +1 -1
  262. package/dist/cloudflare/index.js +4 -13
  263. package/dist/cloudflare/known-permission-group-ids.generated.d.ts +10 -0
  264. package/dist/cloudflare/known-permission-group-ids.generated.d.ts.map +1 -0
  265. package/dist/cloudflare/kv-namespace.d.ts +4 -0
  266. package/dist/cloudflare/kv-namespace.d.ts.map +1 -0
  267. package/dist/cloudflare/preferences.d.ts +7 -0
  268. package/dist/cloudflare/preferences.d.ts.map +1 -1
  269. package/dist/cloudflare/preview-registry-cache.d.ts +6 -0
  270. package/dist/cloudflare/preview-registry-cache.d.ts.map +1 -0
  271. package/dist/cloudflare/preview-registry-inference.d.ts +14 -0
  272. package/dist/cloudflare/preview-registry-inference.d.ts.map +1 -0
  273. package/dist/cloudflare/preview-registry-records.d.ts +4 -0
  274. package/dist/cloudflare/preview-registry-records.d.ts.map +1 -0
  275. package/dist/cloudflare/preview-registry-shape.d.ts +47 -0
  276. package/dist/cloudflare/preview-registry-shape.d.ts.map +1 -0
  277. package/dist/cloudflare/preview-registry-store.d.ts +14 -0
  278. package/dist/cloudflare/preview-registry-store.d.ts.map +1 -0
  279. package/dist/cloudflare/preview-registry-transport.d.ts +4 -0
  280. package/dist/cloudflare/preview-registry-transport.d.ts.map +1 -0
  281. package/dist/cloudflare/preview-registry-types.d.ts +103 -0
  282. package/dist/cloudflare/preview-registry-types.d.ts.map +1 -0
  283. package/dist/cloudflare/preview-registry.d.ts +42 -0
  284. package/dist/cloudflare/preview-registry.d.ts.map +1 -0
  285. package/dist/cloudflare/preview-urls.d.ts +3 -0
  286. package/dist/cloudflare/preview-urls.d.ts.map +1 -0
  287. package/dist/cloudflare/registry-schema.d.ts +253 -0
  288. package/dist/cloudflare/registry-schema.d.ts.map +1 -0
  289. package/dist/cloudflare/remote-config.d.ts.map +1 -1
  290. package/dist/cloudflare/tokens.d.ts +44 -0
  291. package/dist/cloudflare/tokens.d.ts.map +1 -0
  292. package/dist/cloudflare/types.d.ts +124 -7
  293. package/dist/cloudflare/types.d.ts.map +1 -1
  294. package/dist/cloudflare/usage.d.ts +31 -3
  295. package/dist/cloudflare/usage.d.ts.map +1 -1
  296. package/dist/config/binding-resolution-helpers.d.ts +41 -0
  297. package/dist/config/binding-resolution-helpers.d.ts.map +1 -0
  298. package/dist/config/compatibility.d.ts +3 -0
  299. package/dist/config/compatibility.d.ts.map +1 -0
  300. package/dist/config/compiler/bindings.d.ts +14 -0
  301. package/dist/config/compiler/bindings.d.ts.map +1 -0
  302. package/dist/config/compiler/core-helpers.d.ts +6 -0
  303. package/dist/config/compiler/core-helpers.d.ts.map +1 -0
  304. package/dist/config/compiler/do-workers.d.ts +34 -0
  305. package/dist/config/compiler/do-workers.d.ts.map +1 -0
  306. package/dist/config/compiler/paths.d.ts +18 -0
  307. package/dist/config/compiler/paths.d.ts.map +1 -0
  308. package/dist/config/compiler/types.d.ts +308 -0
  309. package/dist/config/compiler/types.d.ts.map +1 -0
  310. package/dist/config/compiler.d.ts +22 -134
  311. package/dist/config/compiler.d.ts.map +1 -1
  312. package/dist/config/cron.d.ts +19 -0
  313. package/dist/config/cron.d.ts.map +1 -0
  314. package/dist/config/define.d.ts +8 -5
  315. package/dist/config/define.d.ts.map +1 -1
  316. package/dist/config/deploy-resources.d.ts +58 -0
  317. package/dist/config/deploy-resources.d.ts.map +1 -0
  318. package/dist/config/env-vars.d.ts +309 -0
  319. package/dist/config/env-vars.d.ts.map +1 -0
  320. package/dist/config/framework-providers.d.ts +9 -0
  321. package/dist/config/framework-providers.d.ts.map +1 -0
  322. package/dist/config/index.d.ts +13 -5
  323. package/dist/config/index.d.ts.map +1 -1
  324. package/dist/config/loader.d.ts +2 -1
  325. package/dist/config/loader.d.ts.map +1 -1
  326. package/dist/config/local-dev-vars.d.ts +15 -0
  327. package/dist/config/local-dev-vars.d.ts.map +1 -0
  328. package/dist/config/preview-resources.d.ts +83 -0
  329. package/dist/config/preview-resources.d.ts.map +1 -0
  330. package/dist/config/preview.d.ts +29 -0
  331. package/dist/config/preview.d.ts.map +1 -0
  332. package/dist/config/ref.d.ts +16 -25
  333. package/dist/config/ref.d.ts.map +1 -1
  334. package/dist/config/resolve-phased.d.ts +72 -0
  335. package/dist/config/resolve-phased.d.ts.map +1 -0
  336. package/dist/config/resolve.d.ts +11 -0
  337. package/dist/config/resolve.d.ts.map +1 -0
  338. package/dist/config/resource-resolution.d.ts +80 -0
  339. package/dist/config/resource-resolution.d.ts.map +1 -0
  340. package/dist/config/schema-bindings.d.ts +2124 -0
  341. package/dist/config/schema-bindings.d.ts.map +1 -0
  342. package/dist/config/schema-build.d.ts +67 -0
  343. package/dist/config/schema-build.d.ts.map +1 -0
  344. package/dist/config/schema-env.d.ts +3821 -0
  345. package/dist/config/schema-env.d.ts.map +1 -0
  346. package/dist/config/schema-normalization.d.ts +272 -0
  347. package/dist/config/schema-normalization.d.ts.map +1 -0
  348. package/dist/config/schema-runtime.d.ts +597 -0
  349. package/dist/config/schema-runtime.d.ts.map +1 -0
  350. package/dist/config/schema-types-bindings-platform.d.ts +520 -0
  351. package/dist/config/schema-types-bindings-platform.d.ts.map +1 -0
  352. package/dist/config/schema-types-bindings-resources.d.ts +802 -0
  353. package/dist/config/schema-types-bindings-resources.d.ts.map +1 -0
  354. package/dist/config/schema-types-bindings.d.ts +293 -0
  355. package/dist/config/schema-types-bindings.d.ts.map +1 -0
  356. package/dist/config/schema-types-build.d.ts +86 -0
  357. package/dist/config/schema-types-build.d.ts.map +1 -0
  358. package/dist/config/schema-types-runtime-server.d.ts +196 -0
  359. package/dist/config/schema-types-runtime-server.d.ts.map +1 -0
  360. package/dist/config/schema-types-runtime.d.ts +913 -0
  361. package/dist/config/schema-types-runtime.d.ts.map +1 -0
  362. package/dist/config/schema-types.d.ts +458 -0
  363. package/dist/config/schema-types.d.ts.map +1 -0
  364. package/dist/config/schema.d.ts +5796 -2023
  365. package/dist/config/schema.d.ts.map +1 -1
  366. package/dist/config/service-bindings-validation.d.ts +36 -0
  367. package/dist/config/service-bindings-validation.d.ts.map +1 -0
  368. package/dist/config/workspace.d.ts +282 -0
  369. package/dist/config/workspace.d.ts.map +1 -0
  370. package/dist/config-entry.d.ts +7 -0
  371. package/dist/config-entry.d.ts.map +1 -0
  372. package/dist/config-entry.js +3 -0
  373. package/dist/decorators/durable-object.d.ts.map +1 -1
  374. package/dist/decorators/index.d.ts +2 -2
  375. package/dist/decorators/index.js +2 -9
  376. package/dist/dev-server/d1-migrations.d.ts +36 -0
  377. package/dist/dev-server/d1-migrations.d.ts.map +1 -0
  378. package/dist/dev-server/dev-server-state.d.ts +59 -0
  379. package/dist/dev-server/dev-server-state.d.ts.map +1 -0
  380. package/dist/dev-server/gateway-script.d.ts +22 -0
  381. package/dist/dev-server/gateway-script.d.ts.map +1 -0
  382. package/dist/dev-server/index.d.ts +1 -1
  383. package/dist/dev-server/miniflare-bindings.d.ts +90 -0
  384. package/dist/dev-server/miniflare-bindings.d.ts.map +1 -0
  385. package/dist/dev-server/miniflare-dev-config.d.ts +57 -0
  386. package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -0
  387. package/dist/dev-server/miniflare-log.d.ts +20 -0
  388. package/dist/dev-server/miniflare-log.d.ts.map +1 -0
  389. package/dist/dev-server/miniflare-worker-config.d.ts +100 -0
  390. package/dist/dev-server/miniflare-worker-config.d.ts.map +1 -0
  391. package/dist/dev-server/reload-queue.d.ts +13 -0
  392. package/dist/dev-server/reload-queue.d.ts.map +1 -0
  393. package/dist/dev-server/runtime-health.d.ts +79 -0
  394. package/dist/dev-server/runtime-health.d.ts.map +1 -0
  395. package/dist/dev-server/runtime-stdio.d.ts +8 -0
  396. package/dist/dev-server/runtime-stdio.d.ts.map +1 -0
  397. package/dist/dev-server/server-startup-helpers.d.ts +84 -0
  398. package/dist/dev-server/server-startup-helpers.d.ts.map +1 -0
  399. package/dist/dev-server/server.d.ts +2 -0
  400. package/dist/dev-server/server.d.ts.map +1 -1
  401. package/dist/dev-server/vite-process.d.ts +24 -0
  402. package/dist/dev-server/vite-process.d.ts.map +1 -0
  403. package/dist/dev-server/vite-utils.d.ts +19 -1
  404. package/dist/dev-server/vite-utils.d.ts.map +1 -1
  405. package/dist/dev-server/worker-source-watcher.d.ts +17 -0
  406. package/dist/dev-server/worker-source-watcher.d.ts.map +1 -0
  407. package/dist/dev-server/worker-surface-paths.d.ts +6 -0
  408. package/dist/dev-server/worker-surface-paths.d.ts.map +1 -0
  409. package/dist/dev-server/workspace/merge-config.d.ts +81 -0
  410. package/dist/dev-server/workspace/merge-config.d.ts.map +1 -0
  411. package/dist/dev-server/workspace/prepare-app.d.ts +84 -0
  412. package/dist/dev-server/workspace/prepare-app.d.ts.map +1 -0
  413. package/dist/dev-server/workspace/server.d.ts +47 -0
  414. package/dist/dev-server/workspace/server.d.ts.map +1 -0
  415. package/dist/env.d.ts +10 -0
  416. package/dist/env.d.ts.map +1 -1
  417. package/dist/index.d.ts +8 -12
  418. package/dist/index.d.ts.map +1 -1
  419. package/dist/index.js +10 -297
  420. package/dist/runtime/context-events.d.ts +13 -0
  421. package/dist/runtime/context-events.d.ts.map +1 -0
  422. package/dist/runtime/context-types.d.ts +82 -0
  423. package/dist/runtime/context-types.d.ts.map +1 -0
  424. package/dist/runtime/context.d.ts +20 -44
  425. package/dist/runtime/context.d.ts.map +1 -1
  426. package/dist/runtime/exports.d.ts +70 -55
  427. package/dist/runtime/exports.d.ts.map +1 -1
  428. package/dist/runtime/index.d.ts +11 -5
  429. package/dist/runtime/index.d.ts.map +1 -1
  430. package/dist/runtime/index.js +5 -184
  431. package/dist/runtime/middleware.d.ts +119 -67
  432. package/dist/runtime/middleware.d.ts.map +1 -1
  433. package/dist/runtime/r2-presign.d.ts +164 -0
  434. package/dist/runtime/r2-presign.d.ts.map +1 -0
  435. package/dist/runtime/router/index.d.ts +7 -0
  436. package/dist/runtime/router/index.d.ts.map +1 -0
  437. package/dist/runtime/router/types.d.ts +24 -0
  438. package/dist/runtime/router/types.d.ts.map +1 -0
  439. package/dist/runtime/validation.d.ts +18 -4
  440. package/dist/runtime/validation.d.ts.map +1 -1
  441. package/dist/secrets/local-secrets.d.ts +46 -0
  442. package/dist/secrets/local-secrets.d.ts.map +1 -0
  443. package/dist/shims/local-hyperdrive.d.ts +12 -0
  444. package/dist/shims/local-hyperdrive.d.ts.map +1 -0
  445. package/dist/shims/local-media-bindings.d.ts +19 -0
  446. package/dist/shims/local-media-bindings.d.ts.map +1 -0
  447. package/dist/shims/local-worker-loader.d.ts +3 -0
  448. package/dist/shims/local-worker-loader.d.ts.map +1 -0
  449. package/dist/sveltekit/index.d.ts +1 -1
  450. package/dist/sveltekit/index.d.ts.map +1 -1
  451. package/dist/sveltekit/index.js +377 -169
  452. package/dist/sveltekit/local-bindings.d.ts +4 -0
  453. package/dist/sveltekit/local-bindings.d.ts.map +1 -0
  454. package/dist/sveltekit/platform.d.ts +42 -2
  455. package/dist/sveltekit/platform.d.ts.map +1 -1
  456. package/dist/test/ai-search.d.ts +39 -0
  457. package/dist/test/ai-search.d.ts.map +1 -0
  458. package/dist/test/alarm.d.ts +55 -0
  459. package/dist/test/alarm.d.ts.map +1 -0
  460. package/dist/test/binding-hints.d.ts +10 -0
  461. package/dist/test/binding-hints.d.ts.map +1 -0
  462. package/dist/test/cf.d.ts +71 -35
  463. package/dist/test/cf.d.ts.map +1 -1
  464. package/dist/test/containers.d.ts +87 -0
  465. package/dist/test/containers.d.ts.map +1 -0
  466. package/dist/test/email.d.ts +16 -7
  467. package/dist/test/email.d.ts.map +1 -1
  468. package/dist/test/index.d.ts +20 -21
  469. package/dist/test/index.d.ts.map +1 -1
  470. package/dist/test/index.js +5115 -75
  471. package/dist/test/offline-bindings.d.ts +124 -0
  472. package/dist/test/offline-bindings.d.ts.map +1 -0
  473. package/dist/test/queue.d.ts.map +1 -1
  474. package/dist/test/remote-ai.d.ts.map +1 -1
  475. package/dist/test/remote-cloudflare.d.ts +13 -0
  476. package/dist/test/remote-cloudflare.d.ts.map +1 -0
  477. package/dist/test/remote-vectorize.d.ts.map +1 -1
  478. package/dist/test/resolve-service-bindings.d.ts +64 -4
  479. package/dist/test/resolve-service-bindings.d.ts.map +1 -1
  480. package/dist/test/scheduled.d.ts.map +1 -1
  481. package/dist/test/should-skip.d.ts +14 -18
  482. package/dist/test/should-skip.d.ts.map +1 -1
  483. package/dist/test/simple-context-bindings.d.ts +13 -0
  484. package/dist/test/simple-context-bindings.d.ts.map +1 -0
  485. package/dist/test/simple-context-durable-objects.d.ts +6 -0
  486. package/dist/test/simple-context-durable-objects.d.ts.map +1 -0
  487. package/dist/test/simple-context-env.d.ts +35 -0
  488. package/dist/test/simple-context-env.d.ts.map +1 -0
  489. package/dist/test/simple-context-gateway-script.d.ts +2 -0
  490. package/dist/test/simple-context-gateway-script.d.ts.map +1 -0
  491. package/dist/test/simple-context-handlers.d.ts +12 -0
  492. package/dist/test/simple-context-handlers.d.ts.map +1 -0
  493. package/dist/test/simple-context-lifecycle.d.ts +32 -0
  494. package/dist/test/simple-context-lifecycle.d.ts.map +1 -0
  495. package/dist/test/simple-context-mfconfig.d.ts +18 -0
  496. package/dist/test/simple-context-mfconfig.d.ts.map +1 -0
  497. package/dist/test/simple-context-multi-worker.d.ts +16 -0
  498. package/dist/test/simple-context-multi-worker.d.ts.map +1 -0
  499. package/dist/test/simple-context-paths.d.ts +40 -0
  500. package/dist/test/simple-context-paths.d.ts.map +1 -0
  501. package/dist/test/simple-context-runtime.d.ts +19 -0
  502. package/dist/test/simple-context-runtime.d.ts.map +1 -0
  503. package/dist/test/simple-context-startup.d.ts +11 -0
  504. package/dist/test/simple-context-startup.d.ts.map +1 -0
  505. package/dist/test/simple-context-transport.d.ts +14 -0
  506. package/dist/test/simple-context-transport.d.ts.map +1 -0
  507. package/dist/test/simple-context.d.ts +8 -27
  508. package/dist/test/simple-context.d.ts.map +1 -1
  509. package/dist/test/tail.d.ts +3 -2
  510. package/dist/test/tail.d.ts.map +1 -1
  511. package/dist/test/utilities/analytics-engine.d.ts +22 -0
  512. package/dist/test/utilities/analytics-engine.d.ts.map +1 -0
  513. package/dist/test/utilities/artifacts.d.ts +11 -0
  514. package/dist/test/utilities/artifacts.d.ts.map +1 -0
  515. package/dist/test/utilities/context.d.ts +39 -0
  516. package/dist/test/utilities/context.d.ts.map +1 -0
  517. package/dist/test/utilities/d1.d.ts +21 -0
  518. package/dist/test/utilities/d1.d.ts.map +1 -0
  519. package/dist/test/utilities/env.d.ts +45 -0
  520. package/dist/test/utilities/env.d.ts.map +1 -0
  521. package/dist/test/utilities/kv.d.ts +11 -0
  522. package/dist/test/utilities/kv.d.ts.map +1 -0
  523. package/dist/test/utilities/media.d.ts +16 -0
  524. package/dist/test/utilities/media.d.ts.map +1 -0
  525. package/dist/test/utilities/platform.d.ts +41 -0
  526. package/dist/test/utilities/platform.d.ts.map +1 -0
  527. package/dist/test/utilities/queue.d.ts +5 -0
  528. package/dist/test/utilities/queue.d.ts.map +1 -0
  529. package/dist/test/utilities/r2.d.ts +2 -0
  530. package/dist/test/utilities/r2.d.ts.map +1 -0
  531. package/dist/test/utilities/send-email.d.ts +27 -0
  532. package/dist/test/utilities/send-email.d.ts.map +1 -0
  533. package/dist/test/utilities/stream-flagship.d.ts +27 -0
  534. package/dist/test/utilities/stream-flagship.d.ts.map +1 -0
  535. package/dist/test/utilities/vectorize.d.ts +38 -0
  536. package/dist/test/utilities/vectorize.d.ts.map +1 -0
  537. package/dist/test/utilities/workflows.d.ts +26 -0
  538. package/dist/test/utilities/workflows.d.ts.map +1 -0
  539. package/dist/test/utilities.d.ts +14 -98
  540. package/dist/test/utilities.d.ts.map +1 -1
  541. package/dist/test/worker.d.ts +6 -0
  542. package/dist/test/worker.d.ts.map +1 -1
  543. package/dist/transform/durable-object.d.ts.map +1 -1
  544. package/dist/transform/index.d.ts +2 -2
  545. package/dist/transform/worker-entrypoint.d.ts +5 -0
  546. package/dist/transform/worker-entrypoint.d.ts.map +1 -1
  547. package/dist/utils/entrypoint-discovery.d.ts +6 -3
  548. package/dist/utils/entrypoint-discovery.d.ts.map +1 -1
  549. package/dist/utils/glob.d.ts.map +1 -1
  550. package/dist/utils/resolve-package.d.ts.map +1 -1
  551. package/dist/utils/send-email.d.ts +15 -0
  552. package/dist/utils/send-email.d.ts.map +1 -0
  553. package/dist/utils/send-email.js +163 -0
  554. package/dist/vite/config-file.d.ts +25 -0
  555. package/dist/vite/config-file.d.ts.map +1 -0
  556. package/dist/vite/index.d.ts +3 -2
  557. package/dist/vite/index.d.ts.map +1 -1
  558. package/dist/vite/index.js +2 -407
  559. package/dist/vite/plugin-config-hook.d.ts +28 -0
  560. package/dist/vite/plugin-config-hook.d.ts.map +1 -0
  561. package/dist/vite/plugin-context.d.ts +27 -0
  562. package/dist/vite/plugin-context.d.ts.map +1 -0
  563. package/dist/vite/plugin-durable-objects.d.ts +18 -0
  564. package/dist/vite/plugin-durable-objects.d.ts.map +1 -0
  565. package/dist/vite/plugin-programmatic.d.ts +44 -0
  566. package/dist/vite/plugin-programmatic.d.ts.map +1 -0
  567. package/dist/vite/plugin-service-bindings.d.ts +13 -0
  568. package/dist/vite/plugin-service-bindings.d.ts.map +1 -0
  569. package/dist/vite/plugin-transform.d.ts +41 -0
  570. package/dist/vite/plugin-transform.d.ts.map +1 -0
  571. package/dist/vite/plugin.d.ts +10 -41
  572. package/dist/vite/plugin.d.ts.map +1 -1
  573. package/dist/worker-entry/composed-worker.d.ts +5 -8
  574. package/dist/worker-entry/composed-worker.d.ts.map +1 -1
  575. package/dist/worker-entry/durable-object-discovery.d.ts +20 -0
  576. package/dist/worker-entry/durable-object-discovery.d.ts.map +1 -0
  577. package/dist/worker-entry/extensions.d.ts +5 -0
  578. package/dist/worker-entry/extensions.d.ts.map +1 -0
  579. package/dist/worker-entry/routes.d.ts +22 -0
  580. package/dist/worker-entry/routes.d.ts.map +1 -0
  581. package/dist/worker-entry/surface-paths.d.ts +26 -0
  582. package/dist/worker-entry/surface-paths.d.ts.map +1 -0
  583. package/dist/worker-entry/validate-fetch-style.d.ts +12 -0
  584. package/dist/worker-entry/validate-fetch-style.d.ts.map +1 -0
  585. package/dist/workflows/local-workflow-entrypoints.d.ts +7 -0
  586. package/dist/workflows/local-workflow-entrypoints.d.ts.map +1 -0
  587. package/package.json +160 -117
  588. package/R2.md +0 -170
  589. package/dist/account-rvrj687w.js +0 -397
  590. package/dist/ai-dx4fr9jh.js +0 -107
  591. package/dist/bridge/protocol.d.ts.map +0 -1
  592. package/dist/bridge/serialization.d.ts +0 -83
  593. package/dist/bridge/serialization.d.ts.map +0 -1
  594. package/dist/browser-shim/worker.d.ts +0 -14
  595. package/dist/browser-shim/worker.d.ts.map +0 -1
  596. package/dist/build-9myaxf07.js +0 -70
  597. package/dist/deploy-h1wz5p7m.js +0 -86
  598. package/dist/dev-rsdssknb.js +0 -2707
  599. package/dist/doctor-v7jy4s3r.js +0 -190
  600. package/dist/durable-object-t4kbb0yt.js +0 -13
  601. package/dist/index-18hvb6gb.js +0 -194
  602. package/dist/index-1xpj0m4r.js +0 -57
  603. package/dist/index-37x76zdn.js +0 -4
  604. package/dist/index-3t6rypgc.js +0 -13
  605. package/dist/index-62b3gt2g.js +0 -12
  606. package/dist/index-67qcae0f.js +0 -183
  607. package/dist/index-9ats0s83.js +0 -70
  608. package/dist/index-a0fjkq68.js +0 -198
  609. package/dist/index-a855bdsx.js +0 -18
  610. package/dist/index-ccrh4w3t.js +0 -1133
  611. package/dist/index-d8bdkx2h.js +0 -109
  612. package/dist/index-f8qh2tyh.js +0 -2152
  613. package/dist/index-gz1gndna.js +0 -307
  614. package/dist/index-hcex3rgh.js +0 -266
  615. package/dist/index-k7r18na8.js +0 -0
  616. package/dist/index-n3np2d6t.js +0 -462
  617. package/dist/index-n7rs26ft.js +0 -77
  618. package/dist/index-npc1c8jx.js +0 -44
  619. package/dist/index-p7g30wd2.js +0 -281
  620. package/dist/index-rbht7m9r.js +0 -36
  621. package/dist/index-tfyxa77h.js +0 -850
  622. package/dist/index-tk6ej9dj.js +0 -94
  623. package/dist/index-v8vvsn9x.js +0 -169
  624. package/dist/index-z14anrqp.js +0 -226
  625. package/dist/init-f9mgmew3.js +0 -186
  626. package/dist/remote-q59qk463.js +0 -97
  627. package/dist/test/bridge-context.d.ts +0 -76
  628. package/dist/test/bridge-context.d.ts.map +0 -1
  629. package/dist/test/multi-worker-context.d.ts +0 -114
  630. package/dist/test/multi-worker-context.d.ts.map +0 -1
  631. package/dist/types-wdcpnfvy.js +0 -487
  632. package/dist/worker-entrypoint-m9th0rg0.js +0 -13
@@ -0,0 +1,2929 @@
1
+ import { i as resolveConfigPath } from "./loader-BC0skbnH.js";
2
+ import { g as normalizeR2Binding, n as getLocalKVNamespaceIdentifier, r as getSingleBrowserBindingName, t as getLocalD1DatabaseIdentifier } from "./schema-normalization-CTdiY17P.js";
3
+ import { C as buildSendEmailConfig, D as buildVersionMetadataConfig, E as buildTailConsumersConfig, O as buildWorkerLoadersConfig, S as buildSecretsStoreConfig, T as buildStreamingTailConsumersConfig, _ as buildMtlsCertificatesConfig, b as buildQueueProducers, c as buildAiSearchInstancesConfig, d as buildArtifactsConfig, f as buildDispatchNamespacesConfig, g as buildMediaConfig, h as buildImagesConfig, k as buildWorkflowsConfig, l as buildAiSearchNamespacesConfig, m as buildHyperdrivesConfig, n as getRouteDirectoryCandidate, p as buildFlagshipConfig, u as buildAnalyticsEngineConfig, v as buildPipelinesConfig, w as buildStreamConfig, x as buildRateLimitsConfig, y as buildQueueConsumers } from "./routes-CRXB3-iw.js";
4
+ import { r as transformDurableObject } from "./durable-object-DuMPM8re.js";
5
+ import { i as findFiles } from "./glob-DRw9sbfF.js";
6
+ import { d as DEFAULT_FETCH_ENTRY_FILES, f as DEFAULT_QUEUE_ENTRY_FILES, g as discoverDurableObjectFiles, h as resolveWorkerSurfacePaths, i as writeGeneratedViteConfig, m as hasWorkerSurfacePaths, p as DEFAULT_SCHEDULED_ENTRY_FILES, u as DEFAULT_EMAIL_ENTRY_FILES } from "./vite-Bml-Whp8.js";
7
+ import { a as createWorkerdBundlerDefaults, i as writeWorkerCompatibleBundle, n as ensureDebugShim, r as resolveWorkerCompatibleRolldownConfig } from "./worker-bundler-Isj5jiW6.js";
8
+ import { t as R2_PRESIGN_RUNTIME_JS } from "./r2-presign-runtime-C75FkLMd.js";
9
+ import { n as buildLocalSecretWrappedBindingConfig } from "./local-secrets-C0n5oRgI.js";
10
+ import { t as buildLocalBindingShimServiceConfig } from "./local-media-bindings-ZQe-epuw.js";
11
+ import { i as waitForViteReady, n as resolveViteMode } from "./vite-utils-D8kPPxUy.js";
12
+ import { existsSync } from "node:fs";
13
+ import { dirname, relative, resolve } from "pathe";
14
+ import { spawn } from "node:child_process";
15
+ import { homedir } from "node:os";
16
+ import { join as join$1 } from "node:path";
17
+ import picomatch from "picomatch";
18
+ import { createHash } from "node:crypto";
19
+ import { createInterface } from "node:readline";
20
+ import { createServer } from "node:http";
21
+ import { Browser, detectBrowserPlatform, install, resolveBuildId } from "@puppeteer/browsers";
22
+ import puppeteerCore from "puppeteer-core";
23
+ //#region src/bundler/do-bundler.ts
24
+ /**
25
+ * Convert PascalCase class name to SCREAMING_SNAKE_CASE binding name
26
+ */
27
+ function classToBindingName(className) {
28
+ return className.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toUpperCase();
29
+ }
30
+ /**
31
+ * Discover DO classes from a glob pattern.
32
+ * Respects .gitignore automatically.
33
+ */
34
+ async function discoverDOs(cwd, pattern) {
35
+ const discovered = [];
36
+ const files = await discoverDurableObjectFiles(cwd, pattern);
37
+ for (const [filePath, classNames] of files) for (const className of classNames) discovered.push({
38
+ filePath,
39
+ className,
40
+ bindingName: classToBindingName(className)
41
+ });
42
+ return discovered;
43
+ }
44
+ /**
45
+ * Strip @durableObject decorator and its import from source code
46
+ * For dev mode, we just need to remove the decorator syntax - the DO class works as-is
47
+ */
48
+ function stripDecoratorSyntax(code) {
49
+ let result = code;
50
+ result = result.replace(/@durableObject\s*\([^)]*\)\s*\n?\s*(?=export\s+class)/g, "");
51
+ result = result.replace(/import\s*\{([^}]*)\bdurableObject\b[^}]*\}\s*from\s*['"]devflare\/runtime['"]\s*;?/g, (match, imports) => {
52
+ const cleanedImports = imports.split(",").map((s) => s.trim()).filter((s) => !s.startsWith("durableObject")).join(", ");
53
+ if (cleanedImports.trim() === "") return "";
54
+ return `import { ${cleanedImports} } from 'devflare/runtime'`;
55
+ });
56
+ return result;
57
+ }
58
+ /**
59
+ * Bundle a single DO file using Rolldown
60
+ *
61
+ * Strategy:
62
+ * 1. Read the source file
63
+ * 2. Strip @durableObject decorator (not needed at runtime - just a marker)
64
+ * 3. Feed the cleaned code to Rolldown through a virtual-entry plugin whose id
65
+ * sits in the source directory, so relative imports resolve identically to
66
+ * the original Durable Object module — without ever writing a temp file
67
+ * next to user source.
68
+ * 4. Bundle with Rolldown.
69
+ */
70
+ async function bundleDOFile(sourcePath, className, outDir, cwd, bundleOptions) {
71
+ const fs = await import("node:fs/promises");
72
+ await fs.mkdir(outDir, { recursive: true });
73
+ const sourceCode = await fs.readFile(sourcePath, "utf-8");
74
+ const entryCode = `${(await transformDurableObject(sourceCode, sourcePath))?.code ?? stripDecoratorSyntax(sourceCode)}
75
+
76
+ // Default export for worker (required by Miniflare)
77
+ export default {
78
+ async fetch(request) {
79
+ return new Response('DO Worker for ${className}', { status: 200 });
80
+ }
81
+ };
82
+ `;
83
+ const virtualEntryId = resolve(dirname(sourcePath), `.devflare-do-${className}.virtual.ts`);
84
+ const classOutDir = resolve(outDir, className);
85
+ try {
86
+ await fs.rm(classOutDir, {
87
+ recursive: true,
88
+ force: true
89
+ });
90
+ } catch {}
91
+ await fs.mkdir(classOutDir, { recursive: true });
92
+ const debugShimPath = await ensureDebugShim(outDir);
93
+ const virtualEntryPlugin = {
94
+ name: "devflare-do-virtual-entry",
95
+ resolveId(id) {
96
+ if (id === virtualEntryId) return virtualEntryId;
97
+ return null;
98
+ },
99
+ load(id) {
100
+ if (id === virtualEntryId) return entryCode;
101
+ return null;
102
+ }
103
+ };
104
+ const userRolldownOptions = bundleOptions?.rolldownOptions;
105
+ const userPlugins = userRolldownOptions?.plugins;
106
+ const mergedPlugins = userPlugins === void 0 ? [virtualEntryPlugin] : Array.isArray(userPlugins) ? [virtualEntryPlugin, ...userPlugins] : [virtualEntryPlugin, userPlugins];
107
+ const outFile = resolve(classOutDir, "index.js");
108
+ const defaults = createWorkerdBundlerDefaults();
109
+ const { inputOptions, outputOptions } = resolveWorkerCompatibleRolldownConfig({
110
+ cwd,
111
+ inputFile: virtualEntryId,
112
+ outFile,
113
+ ...defaults,
114
+ platform: "neutral",
115
+ alias: { debug: debugShimPath },
116
+ rolldownOptions: {
117
+ ...userRolldownOptions,
118
+ plugins: mergedPlugins
119
+ },
120
+ sourcemap: bundleOptions?.sourcemap ?? defaults.sourcemap,
121
+ minify: bundleOptions?.minify ?? defaults.minify,
122
+ inlineDynamicImports: true,
123
+ defaultTsconfigMode: "always"
124
+ });
125
+ await writeWorkerCompatibleBundle({
126
+ inputOptions,
127
+ outputOptions,
128
+ outFile
129
+ });
130
+ return resolve(classOutDir, "index.js");
131
+ }
132
+ /**
133
+ * Bundle all discovered DOs
134
+ */
135
+ async function bundleAllDOs(discovered, outDir, cwd, logger, bundleOptions) {
136
+ await import("node:fs/promises");
137
+ const bundles = /* @__PURE__ */ new Map();
138
+ const classes = /* @__PURE__ */ new Map();
139
+ const sourceFiles = /* @__PURE__ */ new Map();
140
+ const errors = [];
141
+ for (const do_ of discovered) {
142
+ const existing = sourceFiles.get(do_.filePath) || [];
143
+ existing.push(do_.className);
144
+ sourceFiles.set(do_.filePath, existing);
145
+ }
146
+ for (const do_ of discovered) try {
147
+ logger?.debug(`Bundling ${do_.className} from ${do_.filePath}`);
148
+ const outFile = await bundleDOFile(do_.filePath, do_.className, outDir, cwd, bundleOptions);
149
+ bundles.set(do_.bindingName, outFile);
150
+ classes.set(do_.bindingName, do_.className);
151
+ logger?.debug(` → ${outFile}`);
152
+ } catch (error) {
153
+ const err = error instanceof Error ? error : new Error(String(error));
154
+ errors.push(err);
155
+ logger?.error(`Failed to bundle ${do_.className}:`, err.message);
156
+ }
157
+ return {
158
+ bundles,
159
+ classes,
160
+ sourceFiles,
161
+ errors
162
+ };
163
+ }
164
+ /**
165
+ * Create a DO bundler with watch support
166
+ */
167
+ function createDOBundler(options) {
168
+ const { cwd, pattern, outDir, logger, onRebuild, rolldownOptions, sourcemap, minify } = options;
169
+ let result = {
170
+ bundles: /* @__PURE__ */ new Map(),
171
+ classes: /* @__PURE__ */ new Map(),
172
+ sourceFiles: /* @__PURE__ */ new Map(),
173
+ errors: []
174
+ };
175
+ let watcher = null;
176
+ let chokidarWatcher = null;
177
+ /**
178
+ * Perform initial build
179
+ */
180
+ async function build() {
181
+ const discovered = await discoverDOs(cwd, pattern);
182
+ if (discovered.length === 0) {
183
+ logger?.debug("No DOs found matching pattern:", pattern);
184
+ return result;
185
+ }
186
+ logger?.info(`Found ${discovered.length} Durable Object(s)`);
187
+ for (const do_ of discovered) logger?.info(` • ${do_.className} → ${do_.bindingName}`);
188
+ result = await bundleAllDOs(discovered, outDir, cwd, logger, {
189
+ rolldownOptions,
190
+ sourcemap,
191
+ minify
192
+ });
193
+ if (result.errors.length === 0) logger?.success(`Bundled ${result.bundles.size} DO(s) to ${outDir}`);
194
+ return result;
195
+ }
196
+ /**
197
+ * Watch for changes and rebuild
198
+ *
199
+ * Strategy: Watch parent directories of DO files with a filter for matching files.
200
+ * This allows detection of new DO files created during dev.
201
+ * Uses compiled picomatch for fast pattern matching instead of re-globbing on every event.
202
+ */
203
+ async function watch() {
204
+ const chokidar = await import("chokidar");
205
+ const files = await findFiles(pattern, { cwd });
206
+ let dirsToWatch;
207
+ if (files.length > 0) dirsToWatch = [...new Set(files.map((f) => dirname(f)))];
208
+ else {
209
+ const patternDir = dirname(pattern);
210
+ const absolutePatternDir = resolve(cwd, patternDir === "." ? "" : patternDir) || cwd;
211
+ dirsToWatch = [absolutePatternDir];
212
+ logger?.debug(`No DO files yet, watching pattern directory: ${absolutePatternDir}`);
213
+ }
214
+ logger?.info(`Watching ${files.length} DO file(s) in ${dirsToWatch.length} director(ies)...`);
215
+ const isWindows = process.platform === "win32";
216
+ chokidarWatcher = chokidar.watch(dirsToWatch, {
217
+ ignoreInitial: true,
218
+ usePolling: isWindows,
219
+ interval: isWindows ? 300 : void 0,
220
+ awaitWriteFinish: {
221
+ stabilityThreshold: 100,
222
+ pollInterval: 50
223
+ },
224
+ depth: 0
225
+ });
226
+ const normalizePath = (p) => {
227
+ let normalized = p.replace(/\\/g, "/");
228
+ if (isWindows && /^[a-zA-Z]:/.test(normalized)) normalized = normalized[0].toLowerCase() + normalized.slice(1);
229
+ return normalized;
230
+ };
231
+ const isMatch = picomatch(pattern, {
232
+ dot: true,
233
+ matchBase: false
234
+ });
235
+ const matchesPattern = (filePath) => {
236
+ const normalizedPath = normalizePath(filePath);
237
+ return isMatch(relative(normalizePath(cwd), normalizedPath));
238
+ };
239
+ let isRebuilding = false;
240
+ let pendingRebuild = null;
241
+ let rebuildTimeout = null;
242
+ const scheduleRebuild = (changedPath) => {
243
+ if (rebuildTimeout) clearTimeout(rebuildTimeout);
244
+ rebuildTimeout = setTimeout(() => {
245
+ triggerRebuild(changedPath);
246
+ }, 150);
247
+ };
248
+ const triggerRebuild = async (changedPath) => {
249
+ if (isRebuilding) {
250
+ pendingRebuild = changedPath;
251
+ logger?.debug(`Rebuild already in progress, queuing: ${changedPath}`);
252
+ return;
253
+ }
254
+ isRebuilding = true;
255
+ try {
256
+ logger?.info(`DO file changed: ${changedPath}`);
257
+ logger?.info("Rebuilding DOs...");
258
+ const startTime = Date.now();
259
+ result = await build();
260
+ const elapsed = Date.now() - startTime;
261
+ logger?.success(`DO rebuild complete (${elapsed}ms)`);
262
+ await onRebuild?.(result);
263
+ } catch (error) {
264
+ logger?.error("DO rebuild failed:", error);
265
+ } finally {
266
+ isRebuilding = false;
267
+ if (pendingRebuild) {
268
+ const nextPath = pendingRebuild;
269
+ pendingRebuild = null;
270
+ triggerRebuild(nextPath);
271
+ }
272
+ }
273
+ };
274
+ chokidarWatcher.on("change", (filePath) => {
275
+ if (matchesPattern(filePath)) {
276
+ logger?.debug(`File changed: ${filePath}`);
277
+ scheduleRebuild(filePath);
278
+ }
279
+ });
280
+ chokidarWatcher.on("add", (filePath) => {
281
+ if (matchesPattern(filePath)) {
282
+ logger?.debug(`File added: ${filePath}`);
283
+ scheduleRebuild(filePath);
284
+ }
285
+ });
286
+ chokidarWatcher.on("unlink", (filePath) => {
287
+ if (matchesPattern(filePath)) {
288
+ logger?.debug(`File removed: ${filePath}`);
289
+ scheduleRebuild(filePath);
290
+ }
291
+ });
292
+ chokidarWatcher.on("ready", () => {
293
+ logger?.info("DO file watcher ready");
294
+ });
295
+ chokidarWatcher.on("error", (error) => {
296
+ logger?.error("DO file watcher error:", error);
297
+ });
298
+ }
299
+ /**
300
+ * Stop watching
301
+ */
302
+ async function close() {
303
+ if (watcher) {
304
+ await watcher.close();
305
+ watcher = null;
306
+ }
307
+ if (chokidarWatcher) {
308
+ await chokidarWatcher.close();
309
+ chokidarWatcher = null;
310
+ }
311
+ }
312
+ /**
313
+ * Get the latest result
314
+ */
315
+ function getResult() {
316
+ return result;
317
+ }
318
+ return {
319
+ build,
320
+ watch,
321
+ close,
322
+ getResult
323
+ };
324
+ }
325
+ //#endregion
326
+ //#region src/dev-server/miniflare-log.ts
327
+ const ANSI_ESCAPE_REGEX = /\u001B\[[0-9;]*m/g;
328
+ const COMPATIBILITY_DATE_FALLBACK_REGEX = /^The latest compatibility date supported by the installed Cloudflare Workers Runtime is "([^"]+)", but you've requested "([^"]+)"\. Falling back to "([^"]+)"\.\.\.$/;
329
+ const MINIFLARE_LOG_LEVEL_FALLBACKS = {
330
+ WARN: 2,
331
+ DEBUG: 4
332
+ };
333
+ function normalizeMiniflareMessage(message) {
334
+ return message.replace(ANSI_ESCAPE_REGEX, "").replace(/\s+/g, " ").trim();
335
+ }
336
+ function formatCompatibilityDateFallbackNotice(message) {
337
+ const normalizedMessage = normalizeMiniflareMessage(message);
338
+ const match = COMPATIBILITY_DATE_FALLBACK_REGEX.exec(normalizedMessage);
339
+ if (!match) return null;
340
+ const [, _supportedDate, requestedDate, fallbackDate] = match;
341
+ return `Using latest supported Cloudflare Workers Runtime compatibility date ${fallbackDate} (requested ${requestedDate})`;
342
+ }
343
+ function resolveMiniflareLogLevel(logLevel, levelName) {
344
+ return logLevel?.[levelName] ?? MINIFLARE_LOG_LEVEL_FALLBACKS[levelName];
345
+ }
346
+ function createMiniflareLog(BaseLog, logLevel, levelName, logger) {
347
+ if (!BaseLog) return;
348
+ return createCompatibilityAwareMiniflareLog(BaseLog, resolveMiniflareLogLevel(logLevel, levelName), logger);
349
+ }
350
+ function createCompatibilityAwareMiniflareLog(BaseLog, level, logger) {
351
+ const log = new BaseLog(level);
352
+ const originalWarn = log.warn.bind(log);
353
+ const originalInfo = log.info.bind(log);
354
+ log.warn = (message) => {
355
+ const notice = formatCompatibilityDateFallbackNotice(message);
356
+ if (!notice) {
357
+ originalWarn(message);
358
+ return;
359
+ }
360
+ if (logger) {
361
+ logger.info(notice);
362
+ return;
363
+ }
364
+ originalInfo(notice);
365
+ };
366
+ return log;
367
+ }
368
+ //#endregion
369
+ //#region src/bridge/gateway-runtime.ts
370
+ /**
371
+ * Shared gateway helpers (base64, R2 serializers, email) and the RPC method
372
+ * dispatcher. Designed to be embedded verbatim at the top level of a worker
373
+ * module. All symbols are declared with `function`/`const` so they are
374
+ * hoisted in both embedding sites.
375
+ */
376
+ const GATEWAY_RUNTIME_JS = `
377
+ const RAW_EMAIL = 'EmailMessage::raw'
378
+
379
+ // Inline body cap for proxied DO/service-fetch responses. Matches
380
+ // HTTP_TRANSFER_THRESHOLD in wire.ts (512 KB). A larger body would exceed
381
+ // workerd's ~1 MB WebSocket message limit once base64-encoded into the rpc.ok
382
+ // frame, so it is rejected with a clear error rather than silently truncated.
383
+ const HTTP_TRANSFER_THRESHOLD = 512 * 1024
384
+
385
+ function arrayBufferToBase64(buffer) {
386
+ const bytes = new Uint8Array(buffer)
387
+ let binary = ''
388
+ for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i])
389
+ return btoa(binary)
390
+ }
391
+
392
+ function base64ToArrayBuffer(base64) {
393
+ const binary = atob(base64)
394
+ const bytes = new Uint8Array(binary.length)
395
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)
396
+ return bytes.buffer
397
+ }
398
+
399
+ // Binary WsData frame codec (10-byte header: kind:u8, id:u32 LE, seq:u32 LE,
400
+ // flags:u8 + payload). MUST match wire.ts encodeBinaryFrame/decodeBinaryFrame so
401
+ // the bridge client and this gateway agree on the WS-data wire format
402
+ // (BinaryKind.WsData = 2, BinaryFlags.TEXT = 2).
403
+ function encodeWsDataFrame(wid, flags, payload) {
404
+ const frame = new Uint8Array(10 + payload.byteLength)
405
+ const view = new DataView(frame.buffer)
406
+ view.setUint8(0, 2)
407
+ view.setUint32(1, wid, true)
408
+ view.setUint32(5, 0, true)
409
+ view.setUint8(9, flags)
410
+ frame.set(payload, 10)
411
+ return frame
412
+ }
413
+
414
+ function decodeWsDataFrame(buffer) {
415
+ const bytes = new Uint8Array(buffer)
416
+ if (bytes.byteLength < 10) return null
417
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
418
+ return {
419
+ kind: view.getUint8(0),
420
+ id: view.getUint32(1, true),
421
+ flags: view.getUint8(9),
422
+ payload: bytes.subarray(10)
423
+ }
424
+ }
425
+
426
+ function serializeR2Object(obj) {
427
+ if (!obj) return null
428
+ return {
429
+ __type: 'R2Object',
430
+ key: obj.key,
431
+ version: obj.version,
432
+ size: obj.size,
433
+ etag: obj.etag,
434
+ httpEtag: obj.httpEtag,
435
+ checksums: obj.checksums,
436
+ uploaded: obj.uploaded?.toISOString(),
437
+ httpMetadata: obj.httpMetadata,
438
+ customMetadata: obj.customMetadata,
439
+ range: obj.range,
440
+ storageClass: obj.storageClass
441
+ }
442
+ }
443
+
444
+ function serializeR2ObjectBody(obj, bodyData) {
445
+ if (!obj) return null
446
+ return {
447
+ __type: 'R2ObjectBody',
448
+ key: obj.key,
449
+ version: obj.version,
450
+ size: obj.size,
451
+ etag: obj.etag,
452
+ httpEtag: obj.httpEtag,
453
+ checksums: obj.checksums,
454
+ uploaded: obj.uploaded?.toISOString(),
455
+ httpMetadata: obj.httpMetadata,
456
+ customMetadata: obj.customMetadata,
457
+ range: obj.range,
458
+ storageClass: obj.storageClass,
459
+ bodyData
460
+ }
461
+ }
462
+
463
+ function serializeR2Objects(result) {
464
+ if (!result) return null
465
+ return {
466
+ objects: result.objects.map(serializeR2Object),
467
+ truncated: result.truncated,
468
+ cursor: result.cursor,
469
+ delimitedPrefixes: result.delimitedPrefixes
470
+ }
471
+ }
472
+
473
+ // Read each Set-Cookie value separately. entries()/forEach() combine multiple
474
+ // Set-Cookie headers into one comma-joined value (Fetch sort-and-combine), which
475
+ // corrupts cookies. getSetCookie() keeps them apart on a compat date
476
+ // >= 2023-08-01; older workerd predates it but still exposes getAll('set-cookie').
477
+ // MUST match readSetCookieValues in v2/value-serialization.ts.
478
+ function readSetCookieValues(headers) {
479
+ if (typeof headers.getSetCookie === 'function') {
480
+ const values = headers.getSetCookie()
481
+ return Array.isArray(values) ? values : null
482
+ }
483
+ if (typeof headers.getAll === 'function') {
484
+ try {
485
+ const values = headers.getAll('set-cookie')
486
+ return Array.isArray(values) ? values : null
487
+ } catch (error) {
488
+ return null
489
+ }
490
+ }
491
+ return null
492
+ }
493
+
494
+ // Flatten headers to [name, value] pairs without collapsing multiple Set-Cookie
495
+ // into one. Each cookie becomes its own pair so the client's new Headers(pairs)
496
+ // re-appends them individually. Falls back to the combined value when the
497
+ // runtime can split neither. MUST match serializeHeaders in v2/value-serialization.ts.
498
+ function serializeHeaders(headers) {
499
+ const setCookies = readSetCookieValues(headers)
500
+ const pairs = []
501
+ headers.forEach((value, key) => {
502
+ if (setCookies !== null && key.toLowerCase() === 'set-cookie') return
503
+ pairs.push([key, value])
504
+ })
505
+ if (setCookies) {
506
+ for (const cookie of setCookies) pairs.push(['set-cookie', cookie])
507
+ }
508
+ return pairs
509
+ }
510
+
511
+ async function serializeResponse(response) {
512
+ let body = null
513
+ if (response.body) {
514
+ const bytes = await response.arrayBuffer()
515
+ if (bytes.byteLength > HTTP_TRANSFER_THRESHOLD) {
516
+ throw new Error(
517
+ '[devflare][bridge] Response body (' + bytes.byteLength + ' bytes) exceeds the '
518
+ + HTTP_TRANSFER_THRESHOLD + '-byte inline limit. DO and service-binding fetch responses '
519
+ + 'are delivered inline over the bridge WebSocket and cannot be streamed locally; keep '
520
+ + 'proxied response bodies under the limit (large R2 objects are exempt — read them through '
521
+ + 'the R2 binding, which uses the HTTP transfer side-channel).'
522
+ )
523
+ }
524
+ if (bytes.byteLength > 0) {
525
+ body = { type: 'bytes', data: arrayBufferToBase64(bytes) }
526
+ }
527
+ }
528
+ return {
529
+ status: response.status,
530
+ statusText: response.statusText,
531
+ headers: serializeHeaders(response.headers),
532
+ body
533
+ }
534
+ }
535
+
536
+ function deserializeRequest(serializedReq) {
537
+ return new Request(serializedReq.url, {
538
+ method: serializedReq.method,
539
+ headers: serializedReq.headers,
540
+ body: serializedReq.body?.type === 'bytes'
541
+ ? base64ToArrayBuffer(serializedReq.body.data)
542
+ : undefined,
543
+ redirect: serializedReq.redirect
544
+ })
545
+ }
546
+
547
+ function createEmailMessageRaw(raw) {
548
+ if (typeof raw === 'string' || raw instanceof ReadableStream) {
549
+ return raw
550
+ }
551
+ if (raw instanceof ArrayBuffer || raw instanceof Uint8Array) {
552
+ return new Response(raw).body
553
+ }
554
+ throw new Error('Unsupported EmailMessage raw payload')
555
+ }
556
+
557
+ function isDurableObjectNamespace(binding) {
558
+ return !!binding
559
+ && typeof binding.idFromName === 'function'
560
+ && typeof binding.idFromString === 'function'
561
+ && typeof binding.newUniqueId === 'function'
562
+ }
563
+
564
+ /**
565
+ * Resolve a (possibly jurisdiction-scoped) DurableObjectNamespace.
566
+ * Forwards the jurisdiction faithfully when the binding supports it
567
+ * (real Cloudflare), and degrades to the plain binding otherwise
568
+ * (miniflare/workerd local emulation, which does not pin jurisdictions).
569
+ */
570
+ function resolveDoNamespace(binding, jurisdiction) {
571
+ if (jurisdiction && typeof binding.jurisdiction === 'function') {
572
+ return binding.jurisdiction(jurisdiction)
573
+ }
574
+ return binding
575
+ }
576
+
577
+ // Dispatch a single Durable Object RPC method call to its stub.
578
+ //
579
+ // A DO that extends DurableObject (from cloudflare:workers) exposes its methods
580
+ // as native RPC on the stub, so the method is invoked DIRECTLY as
581
+ // stub[method](...args) — exactly as on real Cloudflare. This is the ONLY
582
+ // correct path for a DO that ALSO defines its own fetch(): routing the call
583
+ // through fetch() would hand devflare's internal _rpc probe to the user handler,
584
+ // which may reject it (e.g. a websocket-only fetch() returning 426) and yield a
585
+ // non-JSON body that then fails to parse (the reported crash).
586
+ //
587
+ // The fetch _rpc convention is kept only as a fallback for DOs that are not
588
+ // RPC-enabled: the stub proxies every property as callable, so such a DO only
589
+ // reveals itself when the native call throws "does not support RPC".
590
+ async function callDurableObjectRpc(stub, methodName, args) {
591
+ if (typeof stub[methodName] === 'function') {
592
+ try {
593
+ return await stub[methodName](...args)
594
+ } catch (error) {
595
+ if (!isDurableObjectRpcUnsupported(error)) throw error
596
+ }
597
+ }
598
+ const response = await stub.fetch(new Request('http://do/_rpc', {
599
+ method: 'POST',
600
+ headers: { 'Content-Type': 'application/json' },
601
+ body: JSON.stringify({ method: methodName, params: args })
602
+ }))
603
+ const result = await response.json()
604
+ if (!result.ok) throw new Error(result.error?.message || 'RPC failed')
605
+ return result.result
606
+ }
607
+
608
+ function isDurableObjectRpcUnsupported(error) {
609
+ const message = error && error.message ? String(error.message) : String(error)
610
+ return message.indexOf('does not support RPC') !== -1
611
+ }
612
+
613
+ /**
614
+ * Execute an RPC method against the gateway's bindings.
615
+ *
616
+ * Method format: "binding.operation". Operations must be namespaced by
617
+ * binding kind (e.g. "kv.get", "r2.head", "d1.stmt.first", "do.fetch",
618
+ * "service.fetch", "queue.send", "email.send", "ai.run"). Bare verbs and the legacy
619
+ * "stmt.*" / "stub.*" sub-prefixes were removed in B3-final and now throw.
620
+ * Method vocabulary must stay in sync with the canonical server in
621
+ * src/bridge/server.ts.
622
+ */
623
+ async function executeRpcMethod(method, params, env, _ctx) {
624
+ const parts = method.split('.')
625
+ if (parts.length < 2) throw new Error('Invalid method format: ' + method)
626
+
627
+ const bindingName = parts[0]
628
+ const operation = parts.slice(1).join('.')
629
+ const binding = env[bindingName]
630
+
631
+ if (!binding) throw new Error('Binding not found: ' + bindingName)
632
+
633
+ const isNamespaced =
634
+ operation.indexOf('kv.') === 0 ||
635
+ operation.indexOf('r2.') === 0 ||
636
+ operation.indexOf('d1.') === 0 ||
637
+ operation.indexOf('do.') === 0 ||
638
+ operation.indexOf('service.') === 0 ||
639
+ operation.indexOf('queue.') === 0 ||
640
+ operation.indexOf('email.') === 0 ||
641
+ operation.indexOf('ai.') === 0 ||
642
+ operation.indexOf('workflow.') === 0 ||
643
+ operation.indexOf('var.') === 0
644
+ if (!isNamespaced) {
645
+ throw new Error(createUnsupportedBridgeOperationErrorMessage(bindingName, operation))
646
+ }
647
+
648
+ // KV
649
+ if (operation === 'kv.get') return binding.get(params[0], params[1])
650
+ if (operation === 'kv.put') return binding.put(params[0], params[1], params[2])
651
+ if (operation === 'kv.delete') return binding.delete(params[0])
652
+ if (operation === 'kv.list') return binding.list(params[0])
653
+ if (operation === 'kv.getWithMetadata') return binding.getWithMetadata(params[0], params[1])
654
+
655
+ // DO get (returns DOStub reference)
656
+ if (operation === 'do.get') {
657
+ return { __type: 'DOStub', binding: bindingName, id: params[0] }
658
+ }
659
+
660
+ // R2
661
+ if (operation === 'r2.head') return serializeR2Object(await binding.head(params[0]))
662
+ if (operation === 'r2.get') {
663
+ const obj = await binding.get(params[0], params[1])
664
+ if (!obj) return null
665
+ const body = await obj.arrayBuffer()
666
+ return serializeR2ObjectBody(obj, arrayBufferToBase64(body))
667
+ }
668
+ if (operation === 'r2.put') {
669
+ let value = params[1]
670
+ if (value && typeof value === 'object') {
671
+ if (value.__type === 'ArrayBuffer' || value.__type === 'Uint8Array') {
672
+ value = base64ToArrayBuffer(value.data)
673
+ }
674
+ }
675
+ return serializeR2Object(await binding.put(params[0], value, params[2]))
676
+ }
677
+ if (operation === 'r2.delete') return binding.delete(params[0])
678
+ if (operation === 'r2.list') return serializeR2Objects(await binding.list(params[0]))
679
+
680
+ // D1
681
+ if (operation === 'd1.exec') return binding.exec(params[0])
682
+ if (operation === 'd1.batch') {
683
+ const statements = params[0].map((s) => binding.prepare(s.sql).bind(...(s.bindings || [])))
684
+ return binding.batch(statements)
685
+ }
686
+ if (operation.indexOf('d1.stmt.') === 0) {
687
+ const mode = operation.split('.')[2]
688
+ const [sql, ...rest] = params
689
+ let bindings = rest
690
+ let extraParam
691
+ if (mode === 'first' || mode === 'raw') {
692
+ extraParam = rest[rest.length - 1]
693
+ bindings = rest.slice(0, -1)
694
+ }
695
+ let stmt = binding.prepare(sql)
696
+ if (bindings.length > 0) stmt = stmt.bind(...bindings)
697
+ if (mode === 'first') {
698
+ if (typeof extraParam === 'string' && extraParam.length > 0) return stmt.first(extraParam)
699
+ return stmt.first()
700
+ }
701
+ if (mode === 'all') return stmt.all()
702
+ if (mode === 'run') return stmt.run()
703
+ if (mode === 'raw') return stmt.raw(extraParam)
704
+ throw new Error('Unknown stmt mode: ' + mode)
705
+ }
706
+
707
+ // Durable Objects
708
+ if (operation === 'do.idFromName') {
709
+ const ns = resolveDoNamespace(binding, params[1])
710
+ const id = ns.idFromName(params[0])
711
+ return { __type: 'DOId', hex: id.toString() }
712
+ }
713
+ if (operation === 'do.idFromString') {
714
+ const id = binding.idFromString(params[0])
715
+ return { __type: 'DOId', hex: id.toString() }
716
+ }
717
+ if (operation === 'do.newUniqueId') {
718
+ const ns = resolveDoNamespace(binding, params[1])
719
+ const id = ns.newUniqueId(params[0])
720
+ return { __type: 'DOId', hex: id.toString() }
721
+ }
722
+ if (operation === 'do.fetch') {
723
+ const [, serializedId, serializedReq] = params
724
+ const id = binding.idFromString(serializedId.hex)
725
+ const stub = binding.get(id)
726
+ const response = await stub.fetch(new Request(serializedReq.url, {
727
+ method: serializedReq.method,
728
+ headers: serializedReq.headers,
729
+ body: serializedReq.body?.type === 'bytes'
730
+ ? base64ToArrayBuffer(serializedReq.body.data)
731
+ : undefined
732
+ }))
733
+ return serializeResponse(response)
734
+ }
735
+ if (operation === 'do.rpc') {
736
+ const [, serializedId, methodName, args] = params
737
+ const id = binding.idFromString(serializedId.hex)
738
+ const stub = binding.get(id)
739
+ return callDurableObjectRpc(stub, methodName, Array.isArray(args) ? args : [])
740
+ }
741
+
742
+ // Service Bindings
743
+ if (operation === 'service.fetch') {
744
+ if (!binding || typeof binding.fetch !== 'function') {
745
+ throw new Error('Service binding ' + bindingName + ' does not support fetch()')
746
+ }
747
+ const response = await binding.fetch(deserializeRequest(params[0]))
748
+ return serializeResponse(response)
749
+ }
750
+ if (operation === 'service.rpc') {
751
+ const methodName = params[0]
752
+ if (typeof methodName !== 'string') {
753
+ throw new Error('Service binding ' + bindingName + ' RPC method name must be a string')
754
+ }
755
+ const args = Array.isArray(params[1]) ? params[1] : []
756
+ const method = binding && binding[methodName]
757
+ if (typeof method !== 'function') {
758
+ throw new Error('Service binding ' + bindingName + ' does not support ' + methodName + '()')
759
+ }
760
+ return method.apply(binding, args)
761
+ }
762
+
763
+ // Queues
764
+ if (operation === 'queue.send') return binding.send(params[0], params[1])
765
+ if (operation === 'queue.sendBatch') return binding.sendBatch(params[0], params[1])
766
+
767
+ // Send Email
768
+ if (operation === 'email.send') {
769
+ if (binding && typeof binding.send === 'function') {
770
+ const message = params[0]
771
+ if (message && typeof message === 'object' && 'from' in message && 'to' in message && 'raw' in message) {
772
+ return binding.send({
773
+ from: message.from,
774
+ to: message.to,
775
+ [RAW_EMAIL]: createEmailMessageRaw(message.raw)
776
+ })
777
+ }
778
+ return binding.send(message)
779
+ }
780
+ return { ok: true, simulated: true }
781
+ }
782
+
783
+ // Workflows
784
+ if (operation === 'workflow.create') {
785
+ return serializeWorkflowInstance(await binding.create(params[0]))
786
+ }
787
+ if (operation === 'workflow.get') {
788
+ return serializeWorkflowInstance(await binding.get(params[0]))
789
+ }
790
+ if (operation === 'workflow.status') {
791
+ return (await binding.get(params[0])).status()
792
+ }
793
+ if (operation === 'workflow.pause') {
794
+ return (await binding.get(params[0])).pause()
795
+ }
796
+ if (operation === 'workflow.resume') {
797
+ return (await binding.get(params[0])).resume()
798
+ }
799
+ if (operation === 'workflow.terminate') {
800
+ return (await binding.get(params[0])).terminate()
801
+ }
802
+ if (operation === 'workflow.restart') {
803
+ return (await binding.get(params[0])).restart()
804
+ }
805
+ if (operation === 'workflow.sendEvent') {
806
+ return (await binding.get(params[0])).sendEvent(params[1])
807
+ }
808
+
809
+ // AI / generic run()
810
+ if (operation === 'ai.run') {
811
+ if (typeof binding.run !== 'function') {
812
+ throw new Error('Binding ' + bindingName + ' does not support run(): ' + method)
813
+ }
814
+ return binding.run(params[0], params[1])
815
+ }
816
+
817
+ throw new Error('Unknown operation: ' + method)
818
+ }
819
+
820
+ function createUnsupportedBridgeOperationErrorMessage(bindingName, operation) {
821
+ const base = "[devflare][bridge] Unsupported bridge operation '" + operation + "' for binding '" + bindingName + "'."
822
+ if (operation === 'fetch') {
823
+ return base + ' Devflare could not dispatch fetch() for this binding through the local bridge. '
824
+ + 'Expected Cloudflare API: env.' + bindingName + '.fetch(request). '
825
+ + 'If this came from SvelteKit platform.env, make sure the binding is declared as a service binding; '
826
+ + 'this is a Devflare local bridge issue when service bindings fall back to a bare fetch operation.'
827
+ }
828
+ if (operation === 'toString') {
829
+ return base + ' A platform.env value was coerced to a string through the bridge. '
830
+ + 'For SvelteKit local dev, declared vars should be plain string values and missing env names should read as undefined.'
831
+ }
832
+ return base + ' Bare verbs and the legacy stmt.*/stub.* sub-prefixes are not supported; '
833
+ + 'use the namespaced form (e.g. kv.get, r2.put, d1.stmt.first, do.fetch, service.fetch).'
834
+ }
835
+
836
+ function serializeWorkflowInstance(instance) {
837
+ return {
838
+ __type: 'WorkflowInstance',
839
+ id: instance.id
840
+ }
841
+ }
842
+
843
+ // ---------------------------------------------------------------------------
844
+ // WebSocket bridge (shared with src/bridge/server.ts in shape)
845
+ // ---------------------------------------------------------------------------
846
+ // NOTE: wsProxies is intentionally created per handleBridgeWebSocket call so
847
+ // state never leaks across connections or across gateway-script regenerations.
848
+
849
+ async function handleBridgeRpcCall(msg, ws, env, ctx) {
850
+ try {
851
+ const result = await executeRpcMethod(msg.method, msg.params, env, ctx)
852
+ ws.send(JSON.stringify({ t: 'rpc.ok', id: msg.id, result }))
853
+ } catch (error) {
854
+ ws.send(JSON.stringify({
855
+ t: 'rpc.err',
856
+ id: msg.id,
857
+ error: {
858
+ code: error?.code || 'INTERNAL_ERROR',
859
+ message: error?.message || String(error)
860
+ }
861
+ }))
862
+ }
863
+ }
864
+
865
+ async function handleBridgeWsOpen(msg, ws, env, wsProxies) {
866
+ try {
867
+ const binding = env[msg.target.binding]
868
+ const id = binding.idFromString(msg.target.id)
869
+ const stub = binding.get(id)
870
+
871
+ const headers = new Headers(msg.target.headers || [])
872
+ headers.set('Upgrade', 'websocket')
873
+
874
+ const response = await stub.fetch(new Request(msg.target.url, { method: 'GET', headers }))
875
+ const doWs = response.webSocket
876
+
877
+ if (!doWs) {
878
+ ws.send(JSON.stringify({
879
+ t: 'rpc.err',
880
+ id: 'ws_' + msg.wid,
881
+ error: { code: 'WS_FAILED', message: 'No WebSocket returned' }
882
+ }))
883
+ return
884
+ }
885
+
886
+ doWs.accept()
887
+ wsProxies.set(msg.wid, { doWs })
888
+
889
+ doWs.addEventListener('message', (event) => {
890
+ const isText = typeof event.data === 'string'
891
+ const payload = isText
892
+ ? new TextEncoder().encode(event.data)
893
+ : new Uint8Array(event.data)
894
+ const flags = isText ? 2 : 0
895
+ ws.send(encodeWsDataFrame(msg.wid, flags, payload))
896
+ })
897
+
898
+ doWs.addEventListener('close', (event) => {
899
+ ws.send(JSON.stringify({ t: 'ws.close', wid: msg.wid, code: event.code, reason: event.reason }))
900
+ wsProxies.delete(msg.wid)
901
+ })
902
+
903
+ ws.send(JSON.stringify({ t: 'ws.opened', wid: msg.wid }))
904
+ } catch (error) {
905
+ ws.send(JSON.stringify({
906
+ t: 'rpc.err',
907
+ id: 'ws_' + msg.wid,
908
+ error: { code: 'WS_FAILED', message: error.message }
909
+ }))
910
+ }
911
+ }
912
+
913
+ function handleBridgeWsClose(msg, wsProxies) {
914
+ const proxy = wsProxies.get(msg.wid)
915
+ if (proxy) {
916
+ proxy.doWs.close(msg.code, msg.reason)
917
+ wsProxies.delete(msg.wid)
918
+ }
919
+ }
920
+
921
+ // Relay an inbound binary WsData frame (client -> DO). Mirrors server.ts
922
+ // handleBinaryMessage: decode the frame, look up the proxy by ws id, and forward
923
+ // the payload to the DO socket honoring the TEXT flag.
924
+ function handleBridgeBinaryMessage(buffer, wsProxies) {
925
+ const frame = decodeWsDataFrame(buffer)
926
+ if (!frame || frame.kind !== 2) return
927
+ const proxy = wsProxies.get(frame.id)
928
+ if (!proxy) return
929
+ if ((frame.flags & 2) !== 0) {
930
+ proxy.doWs.send(new TextDecoder().decode(frame.payload))
931
+ } else {
932
+ proxy.doWs.send(frame.payload)
933
+ }
934
+ }
935
+
936
+ async function handleBridgeJsonMessage(data, ws, env, ctx, wsProxies) {
937
+ const msg = JSON.parse(data)
938
+ switch (msg.t) {
939
+ case 'hello':
940
+ // v2 handshake — acknowledge with welcome echoing the negotiated
941
+ // capability intersection. The gateway advertises only what it
942
+ // implements end-to-end: 'ws-relay' (binary WsData relay to/from DO
943
+ // sockets) and 'http-transfer' (the R2 transfer HTTP side-channel).
944
+ // 'streams' is intentionally NOT advertised — large DO/service-fetch
945
+ // responses are inlined, not streamed, in this gateway.
946
+ ws.send(JSON.stringify({
947
+ t: 'welcome',
948
+ protocolVersion: 2,
949
+ capabilities: ['ws-relay', 'http-transfer']
950
+ .filter((c) => Array.isArray(msg.capabilities) && msg.capabilities.includes(c))
951
+ .sort()
952
+ }))
953
+ break
954
+ case 'rpc.call':
955
+ await handleBridgeRpcCall(msg, ws, env, ctx)
956
+ break
957
+ case 'ws.open':
958
+ await handleBridgeWsOpen(msg, ws, env, wsProxies)
959
+ break
960
+ case 'ws.close':
961
+ handleBridgeWsClose(msg, wsProxies)
962
+ break
963
+ }
964
+ }
965
+
966
+ // Pass-through DO WebSocket upgrade (bridge client -> gateway -> DO).
967
+ //
968
+ // The bridge client's openDoWebSocket() opens a REAL loopback WebSocket to this
969
+ // endpoint (/_devflare/do-ws?binding&id&u) instead of tunneling frames over the
970
+ // bridge socket. We resolve the DO by id and RETURN the DO's 101 response
971
+ // verbatim (webSocket included), so miniflare wires the inbound connection to
972
+ // the DO's client socket. That genuine inbound connection is what makes workerd
973
+ // dispatch the DO's hibernation handlers (webSocketMessage/webSocketClose) and
974
+ // deliver ctx.getWebSockets() broadcasts. Pumping the DO socket in-process (the
975
+ // legacy handleBridgeWsOpen path) never triggers hibernation, so two sockets to
976
+ // one getByName(id) could not see each other's messages. This mirrors the
977
+ // browser WS_ROUTES pass-through (handleDoWebSocket) and real Cloudflare.
978
+ //
979
+ // NOTE: the devflare/test gateway (src/test/simple-context-gateway-script.ts)
980
+ // keeps a byte-equivalent copy of this handler because it is a separately-bundled
981
+ // Worker template that cannot import this runtime — keep the two in sync.
982
+ async function handleBridgeDoWebSocket(request, env, url) {
983
+ try {
984
+ const bindingName = url.searchParams.get('binding')
985
+ const idHex = url.searchParams.get('id')
986
+ const targetUrl = url.searchParams.get('u') || url.toString()
987
+
988
+ const binding = env[bindingName]
989
+ if (!binding || typeof binding.idFromString !== 'function') {
990
+ return new Response('Durable Object binding not found: ' + bindingName, { status: 500 })
991
+ }
992
+ if (!idHex) {
993
+ return new Response('Missing Durable Object id', { status: 400 })
994
+ }
995
+
996
+ const stub = binding.get(binding.idFromString(idHex))
997
+ // Forward the real upgrade request (its headers carry any auth/cookies the
998
+ // caller passed to connect()) and pass the DO's 101 response straight back.
999
+ return stub.fetch(new Request(targetUrl, { method: 'GET', headers: request.headers }))
1000
+ } catch (error) {
1001
+ return new Response('Error opening DO WebSocket: ' + (error?.message || String(error)), {
1002
+ status: 500
1003
+ })
1004
+ }
1005
+ }
1006
+
1007
+ function handleBridgeWebSocket(request, env, ctx) {
1008
+ const { 0: client, 1: server } = new WebSocketPair()
1009
+ server.accept()
1010
+
1011
+ // Per-connection state: recreated for every bridge client so reloads and
1012
+ // concurrent clients never share WS proxy entries.
1013
+ const wsProxies = new Map()
1014
+
1015
+ server.addEventListener('message', async (event) => {
1016
+ try {
1017
+ if (typeof event.data === 'string') {
1018
+ await handleBridgeJsonMessage(event.data, server, env, ctx, wsProxies)
1019
+ } else {
1020
+ handleBridgeBinaryMessage(event.data, wsProxies)
1021
+ }
1022
+ } catch (error) {
1023
+ console.error('[Gateway] Error:', error)
1024
+ }
1025
+ })
1026
+
1027
+ server.addEventListener('close', () => {
1028
+ for (const proxy of wsProxies.values()) {
1029
+ // Best-effort cleanup: the DO-side WS may already be closed or in an
1030
+ // invalid state; any throw here would abort sibling closes. Surface
1031
+ // the swallowed error when DEVFLARE_DEBUG_BRIDGE is enabled.
1032
+ try { proxy.doWs.close() } catch (error) {
1033
+ if (globalThis.DEVFLARE_DEBUG_BRIDGE) {
1034
+ console.warn('[devflare:bridge] proxy.doWs.close() failed', error)
1035
+ }
1036
+ }
1037
+ }
1038
+ wsProxies.clear()
1039
+ })
1040
+
1041
+ return new Response(null, { status: 101, webSocket: client })
1042
+ }
1043
+
1044
+ // ---------------------------------------------------------------------------
1045
+ // HTTP transfer for R2 bodies (shared with src/bridge/server.ts in shape)
1046
+ // ---------------------------------------------------------------------------
1047
+
1048
+ async function handleHttpTransfer(request, env, url) {
1049
+ const transferIdEncoded = url.pathname.split('/').pop()
1050
+ const transferId = decodeURIComponent(transferIdEncoded || '')
1051
+ const [binding, ...keyParts] = transferId.split(':')
1052
+ const key = keyParts.join(':')
1053
+ const bucket = env[binding]
1054
+
1055
+ if (!bucket) return new Response('Bucket not found: ' + binding, { status: 404 })
1056
+
1057
+ if (request.method === 'PUT' || request.method === 'POST') {
1058
+ const result = await bucket.put(key, request.body)
1059
+ return new Response(JSON.stringify(serializeR2Object(result)), {
1060
+ headers: { 'Content-Type': 'application/json' }
1061
+ })
1062
+ }
1063
+
1064
+ if (request.method === 'GET') {
1065
+ const object = await bucket.get(key)
1066
+ if (!object) return new Response('Not found', { status: 404 })
1067
+ return new Response(object.body, {
1068
+ headers: {
1069
+ 'Content-Type': object.httpMetadata?.contentType || 'application/octet-stream',
1070
+ 'Content-Length': String(object.size)
1071
+ }
1072
+ })
1073
+ }
1074
+
1075
+ return new Response('Method not allowed', { status: 405 })
1076
+ }
1077
+ `;
1078
+ //#endregion
1079
+ //#region src/dev-server/d1-migrations.ts
1080
+ const MIGRATION_RETRY_DELAYS_MS = [
1081
+ 500,
1082
+ 1e3,
1083
+ 1500,
1084
+ 2e3
1085
+ ];
1086
+ function collectMigrationStatements(sql) {
1087
+ return sql.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n").split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
1088
+ }
1089
+ function hashSql(sql) {
1090
+ return createHash("sha256").update(sql).digest("hex");
1091
+ }
1092
+ function getErrorMessage(error) {
1093
+ return error instanceof Error ? error.message : String(error);
1094
+ }
1095
+ async function waitForRetry(delayMs) {
1096
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, delayMs));
1097
+ }
1098
+ async function applyMigrationsToBinding(options) {
1099
+ const { bindingName, statements, files, miniflarePort, logger } = options;
1100
+ let lastError;
1101
+ for (let attempt = 0; attempt <= MIGRATION_RETRY_DELAYS_MS.length; attempt++) {
1102
+ if (attempt > 0) await waitForRetry(MIGRATION_RETRY_DELAYS_MS[attempt - 1]);
1103
+ try {
1104
+ const response = await fetch(`http://127.0.0.1:${miniflarePort}/_devflare/migrate`, {
1105
+ method: "POST",
1106
+ headers: { "Content-Type": "application/json" },
1107
+ body: JSON.stringify({
1108
+ bindingName,
1109
+ statements,
1110
+ files
1111
+ })
1112
+ });
1113
+ if (!response.ok) {
1114
+ const text = await response.text();
1115
+ throw new Error(`HTTP ${response.status}: ${text}`);
1116
+ }
1117
+ const result = await response.json();
1118
+ if (result.success) {
1119
+ if (Array.isArray(result.warnings)) for (const warning of result.warnings) console.warn(`[devflare] D1 migration file "${warning.filename}" for binding ${bindingName} has changed since it was applied; skipping re-apply to protect existing data.`);
1120
+ const appliedCount = result.applied?.length ?? 0;
1121
+ const skippedCount = result.skipped?.length ?? 0;
1122
+ if (appliedCount > 0 || skippedCount > 0) logger?.success(`D1 migrations for ${bindingName}: ${appliedCount} applied, ${skippedCount} skipped`);
1123
+ else logger?.success(`D1 migrations applied to ${bindingName}`);
1124
+ return;
1125
+ }
1126
+ throw new Error(result.error || "Unknown error");
1127
+ } catch (error) {
1128
+ lastError = error;
1129
+ }
1130
+ }
1131
+ logger?.warn(`Failed to apply migrations to ${bindingName}: ${getErrorMessage(lastError)}`);
1132
+ }
1133
+ /**
1134
+ * Run D1 migrations from migrations/ directory.
1135
+ *
1136
+ * Resolution per D1 binding (in order):
1137
+ * 1. `<cwd>/migrations/<BINDING_NAME>/*.sql` — per-binding directory.
1138
+ * NOTE: if the per-binding directory EXISTS but contains no .sql files,
1139
+ * the binding is skipped — the shared fallback is NOT used.
1140
+ * 2. `<cwd>/migrations/*.sql` — shared fallback, used ONLY when the
1141
+ * per-binding directory does not exist.
1142
+ * 3. Otherwise, skip the binding with a debug log.
1143
+ *
1144
+ * Applied-migration ledger:
1145
+ * The gateway maintains a `_devflare_migrations` table per D1 binding with
1146
+ * columns (filename TEXT PRIMARY KEY, applied_at TEXT NOT NULL,
1147
+ * sha256 TEXT NOT NULL). On each run the gateway, for every file we send:
1148
+ * - if filename is present AND sha256 matches — skip silently
1149
+ * - if filename is present but sha256 differs — surface a warning that
1150
+ * the client turns into a `console.warn`; the file is skipped to
1151
+ * protect existing data (there is no force-reapply flag today)
1152
+ * - if filename is absent — apply the file's SQL, then record the entry
1153
+ * We pass each file (filename + sha256 + statements) in a single
1154
+ * /_devflare/migrate request so ledger read/write stays in-process inside
1155
+ * workerd.
1156
+ *
1157
+ * Uses the gateway worker HTTP endpoint to run migrations inside workerd.
1158
+ */
1159
+ async function runD1Migrations(options) {
1160
+ const { cwd, config, miniflarePort, logger } = options;
1161
+ if (!config?.bindings?.d1) return;
1162
+ const { existsSync, readdirSync, readFileSync, statSync } = await import("node:fs");
1163
+ const migrationsDir = resolve(cwd, "migrations");
1164
+ if (!existsSync(migrationsDir)) {
1165
+ logger?.debug("No migrations/ directory found, skipping D1 migrations");
1166
+ return;
1167
+ }
1168
+ const sharedFiles = readdirSync(migrationsDir).filter((file) => file.endsWith(".sql")).sort();
1169
+ let sharedFileEntries = null;
1170
+ if (sharedFiles.length > 0) {
1171
+ sharedFileEntries = [];
1172
+ for (const file of sharedFiles) {
1173
+ const sql = readFileSync(resolve(migrationsDir, file), "utf-8");
1174
+ const fileStatements = collectMigrationStatements(sql);
1175
+ sharedFileEntries.push({
1176
+ filename: file,
1177
+ sha256: hashSql(sql),
1178
+ statements: fileStatements
1179
+ });
1180
+ logger?.debug(`Shared file ${file}: ${fileStatements.length} statement(s)`);
1181
+ }
1182
+ }
1183
+ for (const [bindingName] of Object.entries(config.bindings.d1)) {
1184
+ const perBindingDir = resolve(migrationsDir, bindingName);
1185
+ const hasPerBindingDir = existsSync(perBindingDir) && statSync(perBindingDir).isDirectory();
1186
+ let files = [];
1187
+ let sourceLabel = "";
1188
+ if (hasPerBindingDir) {
1189
+ const perBindingFiles = readdirSync(perBindingDir).filter((file) => file.endsWith(".sql")).sort();
1190
+ if (perBindingFiles.length === 0) {
1191
+ logger?.debug(`No SQL migration files in migrations/${bindingName}/, skipping ${bindingName}`);
1192
+ continue;
1193
+ }
1194
+ for (const file of perBindingFiles) {
1195
+ const sql = readFileSync(resolve(perBindingDir, file), "utf-8");
1196
+ const fileStatements = collectMigrationStatements(sql);
1197
+ files.push({
1198
+ filename: file,
1199
+ sha256: hashSql(sql),
1200
+ statements: fileStatements
1201
+ });
1202
+ logger?.debug(`File ${bindingName}/${file}: ${fileStatements.length} statement(s)`);
1203
+ }
1204
+ sourceLabel = `migrations/${bindingName}/`;
1205
+ } else if (sharedFileEntries !== null) {
1206
+ files = sharedFileEntries;
1207
+ sourceLabel = "migrations/ [shared fallback]";
1208
+ } else {
1209
+ logger?.debug(`No migrations found for ${bindingName}, skipping`);
1210
+ continue;
1211
+ }
1212
+ const statements = files.flatMap((file) => file.statements);
1213
+ logger?.info(`Running ${files.length} D1 migration(s) for ${bindingName} (from ${sourceLabel})`);
1214
+ if (statements.length === 0) {
1215
+ logger?.debug(`No executable D1 migration statements for ${bindingName}`);
1216
+ continue;
1217
+ }
1218
+ await applyMigrationsToBinding({
1219
+ bindingName,
1220
+ statements,
1221
+ files,
1222
+ miniflarePort,
1223
+ logger
1224
+ });
1225
+ }
1226
+ }
1227
+ //#endregion
1228
+ //#region src/browser-shim/binding-worker.ts
1229
+ const MAX_CHUNK_SIZE = 1048575;
1230
+ /**
1231
+ * Generate the browser binding worker script
1232
+ * @param browserShimUrl - URL of the external browser shim server (e.g., http://127.0.0.1:8788)
1233
+ * @param debug - Enable debug logging (default: false)
1234
+ */
1235
+ function getBrowserBindingScript(browserShimUrl, debug = false) {
1236
+ return `
1237
+ // Browser Binding Worker — Proxies puppeteer requests to external browser shim
1238
+ // Handles WebSocket upgrades using WebSocketPair for @cloudflare/puppeteer compatibility
1239
+
1240
+ const BROWSER_SHIM_URL = ${JSON.stringify(browserShimUrl)}
1241
+ const MAX_CHUNK_SIZE = ${MAX_CHUNK_SIZE}
1242
+ const DEBUG = ${debug}
1243
+ const log = (...args) => DEBUG && console.log('[BrowserBinding]', ...args)
1244
+
1245
+ export default {
1246
+ async fetch(request, env, ctx) {
1247
+ const url = new URL(request.url)
1248
+ const upgradeHeader = request.headers.get('Upgrade')
1249
+ const isWebSocket = upgradeHeader && upgradeHeader.toLowerCase() === 'websocket'
1250
+
1251
+ log('Request:', url.pathname, isWebSocket ? '(WebSocket)' : '(HTTP)')
1252
+
1253
+ // Handle WebSocket upgrade for DevTools connection
1254
+ if (url.pathname === '/v1/connectDevtools' && isWebSocket) {
1255
+ return handleDevToolsWebSocket(request, url)
1256
+ }
1257
+
1258
+ // Proxy all other requests to the browser shim server
1259
+ return proxyToBrowserShim(request, url)
1260
+ }
1261
+ }
1262
+
1263
+ // Proxy HTTP requests to the external browser shim server
1264
+ async function proxyToBrowserShim(request, url) {
1265
+ const shimUrl = new URL(url.pathname + url.search, BROWSER_SHIM_URL)
1266
+
1267
+ log('Proxying to:', shimUrl.toString())
1268
+
1269
+ const response = await fetch(shimUrl.toString(), {
1270
+ method: request.method,
1271
+ headers: request.headers,
1272
+ body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined
1273
+ })
1274
+
1275
+ log('Response:', response.status)
1276
+
1277
+ // Return the response as-is
1278
+ return new Response(response.body, {
1279
+ status: response.status,
1280
+ statusText: response.statusText,
1281
+ headers: response.headers
1282
+ })
1283
+ }
1284
+
1285
+ // Validate WebSocket close code to be in valid range
1286
+ function validateCloseCode(code) {
1287
+ if (typeof code !== 'number' || isNaN(code)) return 1000
1288
+ if (code < 1000 || code > 4999) return 1000
1289
+ return code
1290
+ }
1291
+
1292
+ // Split a message into chunks following @cloudflare/puppeteer protocol
1293
+ // First chunk has 4-byte LE length header, subsequent chunks are raw payload
1294
+ function messageToChunks(message) {
1295
+ const data = typeof message === 'string'
1296
+ ? new TextEncoder().encode(message)
1297
+ : new Uint8Array(message)
1298
+
1299
+ const chunks = []
1300
+ const totalLength = data.length
1301
+ let offset = 0
1302
+ let isFirst = true
1303
+
1304
+ while (offset < totalLength) {
1305
+ const remaining = totalLength - offset
1306
+ let chunkSize
1307
+
1308
+ if (isFirst) {
1309
+ // First chunk: 4-byte header + payload
1310
+ chunkSize = Math.min(remaining, MAX_CHUNK_SIZE - 4)
1311
+ const chunk = new Uint8Array(chunkSize + 4)
1312
+ new DataView(chunk.buffer).setUint32(0, totalLength, true) // little-endian
1313
+ chunk.set(data.subarray(offset, offset + chunkSize), 4)
1314
+ chunks.push(chunk)
1315
+ isFirst = false
1316
+ } else {
1317
+ // Subsequent chunks: raw payload only
1318
+ chunkSize = Math.min(remaining, MAX_CHUNK_SIZE)
1319
+ const chunk = data.subarray(offset, offset + chunkSize)
1320
+ chunks.push(chunk)
1321
+ }
1322
+
1323
+ offset += chunkSize
1324
+ }
1325
+
1326
+ return chunks
1327
+ }
1328
+
1329
+ // Reassemble chunks back into a complete message
1330
+ // Returns null if more chunks are needed
1331
+ function chunksToMessage(chunks) {
1332
+ if (chunks.length === 0) return null
1333
+
1334
+ // First chunk must have 4-byte header
1335
+ const firstChunk = chunks[0]
1336
+ if (firstChunk.length < 4) return null
1337
+
1338
+ const expectedLength = new DataView(firstChunk.buffer, firstChunk.byteOffset).getUint32(0, true)
1339
+
1340
+ // Calculate total received payload
1341
+ let totalReceived = firstChunk.length - 4 // first chunk payload (minus header)
1342
+ for (let i = 1; i < chunks.length; i++) {
1343
+ totalReceived += chunks[i].length
1344
+ }
1345
+
1346
+ if (totalReceived < expectedLength) {
1347
+ return null // Need more chunks
1348
+ }
1349
+
1350
+ // Reassemble the message
1351
+ const assembled = new Uint8Array(expectedLength)
1352
+ let offset = 0
1353
+
1354
+ // Copy first chunk payload (skip 4-byte header)
1355
+ const firstPayload = firstChunk.subarray(4)
1356
+ assembled.set(firstPayload, offset)
1357
+ offset += firstPayload.length
1358
+
1359
+ // Copy remaining chunks
1360
+ for (let i = 1; i < chunks.length; i++) {
1361
+ const chunk = chunks[i]
1362
+ const toCopy = Math.min(chunk.length, expectedLength - offset)
1363
+ assembled.set(chunk.subarray(0, toCopy), offset)
1364
+ offset += toCopy
1365
+ }
1366
+
1367
+ return new TextDecoder().decode(assembled)
1368
+ }
1369
+
1370
+ // Handle WebSocket upgrade for DevTools connection
1371
+ // Creates a WebSocketPair and proxies to Chrome's DevTools WebSocket
1372
+ async function handleDevToolsWebSocket(request, url) {
1373
+ const sessionId = url.searchParams.get('browser_session')
1374
+ if (!sessionId) {
1375
+ return new Response('browser_session parameter required', { status: 400 })
1376
+ }
1377
+
1378
+ log('DevTools WebSocket request for session:', sessionId)
1379
+
1380
+ // Get session info from browser shim (includes Chrome's wsEndpoint)
1381
+ const sessionUrl = new URL('/v1/session/' + sessionId, BROWSER_SHIM_URL)
1382
+
1383
+ // Add timeout for session fetch
1384
+ const controller = new AbortController()
1385
+ const timeout = setTimeout(() => controller.abort(), 5000)
1386
+
1387
+ let sessionRes
1388
+ try {
1389
+ sessionRes = await fetch(sessionUrl.toString(), { signal: controller.signal })
1390
+ } catch (e) {
1391
+ DEBUG && console.error('[BrowserBinding] Session fetch timeout or error:', e.message)
1392
+ return new Response('Session fetch timeout', { status: 504 })
1393
+ } finally {
1394
+ clearTimeout(timeout)
1395
+ }
1396
+
1397
+ if (!sessionRes.ok) {
1398
+ DEBUG && console.error('[BrowserBinding] Session not found:', sessionId)
1399
+ return new Response('Session not found', { status: 404 })
1400
+ }
1401
+
1402
+ const sessionInfo = await sessionRes.json()
1403
+ const wsEndpoint = sessionInfo.wsEndpoint
1404
+
1405
+ if (!wsEndpoint) {
1406
+ DEBUG && console.error('[BrowserBinding] No wsEndpoint in session info')
1407
+ return new Response('No wsEndpoint for session', { status: 500 })
1408
+ }
1409
+
1410
+ log('Connecting to Chrome DevTools:', wsEndpoint)
1411
+
1412
+ // Connect to Chrome's DevTools WebSocket
1413
+ // Chrome uses ws:// but fetch expects http:// for WebSocket upgrade
1414
+ const chromeUrl = wsEndpoint.replace('ws://', 'http://').replace('wss://', 'https://')
1415
+
1416
+ const chromeRes = await fetch(chromeUrl, {
1417
+ headers: { Upgrade: 'websocket' }
1418
+ })
1419
+
1420
+ if (!chromeRes.webSocket) {
1421
+ DEBUG && console.error('[BrowserBinding] Failed to connect to Chrome DevTools')
1422
+ return new Response('Failed to connect to Chrome DevTools', { status: 502 })
1423
+ }
1424
+
1425
+ const chromeWs = chromeRes.webSocket
1426
+ chromeWs.accept()
1427
+
1428
+ log('Connected to Chrome DevTools')
1429
+
1430
+ // Create WebSocketPair for client connection
1431
+ const { 0: client, 1: server } = new WebSocketPair()
1432
+ server.accept()
1433
+
1434
+ // Chunk buffer for reassembling multi-chunk messages from puppeteer
1435
+ let chunks = []
1436
+ const MAX_BUFFER_SIZE = 50 * 1024 * 1024 // 50MB max buffer
1437
+ let bufferSize = 0
1438
+
1439
+ // Proxy messages from client (puppeteer) to Chrome
1440
+ // Handle multi-chunk framing protocol
1441
+ server.addEventListener('message', (event) => {
1442
+ // Keep-alive ping from puppeteer
1443
+ if (event.data === 'ping') {
1444
+ return
1445
+ }
1446
+
1447
+ // Handle binary data (chunked protocol)
1448
+ if (event.data instanceof ArrayBuffer) {
1449
+ const chunk = new Uint8Array(event.data)
1450
+ bufferSize += chunk.length
1451
+
1452
+ // Prevent unbounded buffering
1453
+ if (bufferSize > MAX_BUFFER_SIZE) {
1454
+ DEBUG && console.error('[BrowserBinding] Buffer overflow, closing connection')
1455
+ server.close(1009, 'Message too big')
1456
+ chromeWs.close(1009, 'Message too big')
1457
+ return
1458
+ }
1459
+
1460
+ chunks.push(chunk)
1461
+
1462
+ // Try to reassemble complete message
1463
+ const message = chunksToMessage(chunks)
1464
+ if (message !== null) {
1465
+ // Send complete message to Chrome
1466
+ if (chromeWs.readyState === 1) { // OPEN
1467
+ chromeWs.send(message)
1468
+ }
1469
+ // Clear buffer
1470
+ chunks = []
1471
+ bufferSize = 0
1472
+ }
1473
+ } else if (typeof event.data === 'string') {
1474
+ // Shouldn't happen in normal protocol, but handle it
1475
+ if (chromeWs.readyState === 1) {
1476
+ chromeWs.send(event.data)
1477
+ }
1478
+ }
1479
+ })
1480
+
1481
+ // Proxy messages from Chrome to client (puppeteer)
1482
+ // Split into chunks following the multi-chunk protocol
1483
+ chromeWs.addEventListener('message', (event) => {
1484
+ if (server.readyState !== 1) return // Not OPEN
1485
+
1486
+ // Split message into chunks
1487
+ const outChunks = messageToChunks(event.data)
1488
+ for (const chunk of outChunks) {
1489
+ server.send(chunk)
1490
+ }
1491
+ })
1492
+
1493
+ // Handle close events with validated codes
1494
+ server.addEventListener('close', (event) => {
1495
+ log('Client WebSocket closed:', event.code)
1496
+ const code = validateCloseCode(event.code)
1497
+ try {
1498
+ if (chromeWs.readyState === 1 || chromeWs.readyState === 0) {
1499
+ chromeWs.close(code, event.reason || '')
1500
+ }
1501
+ } catch {}
1502
+ })
1503
+
1504
+ chromeWs.addEventListener('close', (event) => {
1505
+ log('Chrome WebSocket closed:', event.code)
1506
+ const code = validateCloseCode(event.code)
1507
+ try {
1508
+ if (server.readyState === 1 || server.readyState === 0) {
1509
+ server.close(code, event.reason || '')
1510
+ }
1511
+ } catch {}
1512
+ })
1513
+
1514
+ // Handle errors
1515
+ server.addEventListener('error', (event) => {
1516
+ DEBUG && console.error('[BrowserBinding] Client WebSocket error')
1517
+ try { chromeWs.close(1011, 'Client error') } catch {}
1518
+ })
1519
+
1520
+ chromeWs.addEventListener('error', (event) => {
1521
+ DEBUG && console.error('[BrowserBinding] Chrome WebSocket error')
1522
+ try { server.close(1011, 'Chrome error') } catch {}
1523
+ })
1524
+
1525
+ log('WebSocket proxy established')
1526
+
1527
+ // Return Cloudflare-style WebSocket response
1528
+ return new Response(null, {
1529
+ status: 101,
1530
+ webSocket: client
1531
+ })
1532
+ }
1533
+ `;
1534
+ }
1535
+ //#endregion
1536
+ //#region src/dev-server/gateway-script.ts
1537
+ /**
1538
+ * Generates the dev-server gateway worker script inline.
1539
+ *
1540
+ * All in-sandbox RPC behavior (method dispatch, error envelope, serialization,
1541
+ * WebSocket bridge, HTTP transfer) lives in `GATEWAY_RUNTIME_JS` and is shared
1542
+ * with `src/bridge/miniflare.ts`. The canonical TypeScript equivalent lives in
1543
+ * `src/bridge/server.ts`. The local presigned-R2 endpoint handler is shared
1544
+ * via `R2_PRESIGN_RUNTIME_JS` (`src/bridge/r2-presign-runtime.ts`).
1545
+ *
1546
+ * This file only owns the pieces that are genuinely dev-server-specific:
1547
+ * - WebSocket route matching & DO WebSocket forwarding (`WS_ROUTES`)
1548
+ * - D1 migration endpoint
1549
+ * - Inbound email ingestion endpoint
1550
+ * - Service-binding fallthrough to the app worker
1551
+ *
1552
+ * @param wsRoutes - WebSocket routes for DO proxying
1553
+ * @param debug - Enable debug logging in gateway
1554
+ * @param appServiceBindingName - Service binding name for the app worker (if any)
1555
+ */
1556
+ function getGatewayScript(wsRoutes = [], debug = false, appServiceBindingName = null) {
1557
+ return `
1558
+ ${GATEWAY_RUNTIME_JS}
1559
+
1560
+ ${R2_PRESIGN_RUNTIME_JS}
1561
+
1562
+ // Bridge Gateway Worker — Dev Server
1563
+ // Dev-server-specific overlay on top of the shared GATEWAY_RUNTIME_JS:
1564
+ // WS route DO forwarding, D1 migration, email ingest, app-worker fallthrough.
1565
+
1566
+ const DEBUG = ${debug}
1567
+ const log = (...args) => DEBUG && console.log('[Gateway]', ...args)
1568
+
1569
+ const WS_ROUTES = ${JSON.stringify(wsRoutes)}
1570
+ const APP_SERVICE_BINDING = ${JSON.stringify(appServiceBindingName)}
1571
+
1572
+ export default {
1573
+ async fetch(request, env, ctx) {
1574
+ const url = new URL(request.url)
1575
+ const isWebSocket = request.headers.get('Upgrade') === 'websocket'
1576
+
1577
+ if (isWebSocket) {
1578
+ // Bridge DO connect(): a real pass-through upgrade to a Durable Object
1579
+ // (hibernation-correct), distinct from the bridge RPC/relay socket.
1580
+ if (url.pathname === '/_devflare/do-ws') {
1581
+ return handleBridgeDoWebSocket(request, env, url)
1582
+ }
1583
+ const matchedRoute = matchWsRoute(url.pathname)
1584
+ if (matchedRoute) {
1585
+ return handleDoWebSocket(request, env, url, matchedRoute)
1586
+ }
1587
+ // Worker mode: the app worker (SvelteKit etc.) owns app WebSocket routes.
1588
+ // A handler like GET /api/doc/:id/subscribe that does
1589
+ // \`return stub.fetch(clientUpgradeRequest)\` must reach the app worker so
1590
+ // its returned 101 (with the DO's client socket) flows back to the
1591
+ // browser and the DO's hibernation handlers fire. The gateway previously
1592
+ // hijacked EVERY unmatched WS upgrade into the in-worker bridge socket
1593
+ // (handleBridgeWebSocket), so the app route never ran and two tabs never
1594
+ // shared the DO. Forward to the app first; only fall back to the bridge
1595
+ // socket when the app did not answer with a WebSocket upgrade (e.g. a
1596
+ // Node-side bridge RPC client, which only exists when there is no app).
1597
+ const appWsResponse = await forwardWebSocketToApp(request, env)
1598
+ if (appWsResponse) {
1599
+ return appWsResponse
1600
+ }
1601
+ return handleBridgeWebSocket(request, env, ctx)
1602
+ }
1603
+
1604
+ if (url.pathname.startsWith('/_devflare/transfer/')) {
1605
+ return handleHttpTransfer(request, env, url)
1606
+ }
1607
+
1608
+ const presignResponse = await __devflareR2PresignHandle(request, env, url)
1609
+ if (presignResponse) {
1610
+ return presignResponse
1611
+ }
1612
+
1613
+ if (url.pathname === '/_devflare/migrate' && request.method === 'POST') {
1614
+ return handleMigration(request, env)
1615
+ }
1616
+
1617
+ if (url.pathname === '/cdn-cgi/handler/email' && request.method === 'POST') {
1618
+ return handleEmailIncoming(request, env, ctx, url)
1619
+ }
1620
+
1621
+ if (url.pathname === '/_devflare/health') {
1622
+ return new Response(JSON.stringify({
1623
+ ok: true,
1624
+ bindings: Object.keys(env),
1625
+ wsRoutes: WS_ROUTES
1626
+ }), { headers: { 'Content-Type': 'application/json' } })
1627
+ }
1628
+
1629
+ if (APP_SERVICE_BINDING) {
1630
+ const appWorker = env[APP_SERVICE_BINDING]
1631
+ if (appWorker && typeof appWorker.fetch === 'function') {
1632
+ return appWorker.fetch(request)
1633
+ }
1634
+ }
1635
+
1636
+ return new Response('Devflare Bridge Gateway', { status: 200 })
1637
+ }
1638
+ }
1639
+
1640
+ async function handleMigration(request, env) {
1641
+ try {
1642
+ const { bindingName, statements, files } = await request.json()
1643
+ log('Migration request for binding:', bindingName, 'statements count:', statements?.length, 'files:', files?.length, 'bindings:', Object.keys(env))
1644
+ const db = env[bindingName]
1645
+ if (!db) {
1646
+ return Response.json({ error: 'Binding not found: ' + bindingName }, { status: 404 })
1647
+ }
1648
+
1649
+ // Ledger-aware path: when the client sends per-file metadata, we track
1650
+ // applied migrations in a \`_devflare_migrations\` table and skip files
1651
+ // whose filename+sha256 is already recorded. Files with a drifting hash
1652
+ // are reported as warnings and skipped — we refuse to re-apply to avoid
1653
+ // stomping on user data.
1654
+ if (Array.isArray(files) && files.length > 0) {
1655
+ try {
1656
+ await db.prepare(
1657
+ 'CREATE TABLE IF NOT EXISTS _devflare_migrations (filename TEXT PRIMARY KEY, applied_at TEXT NOT NULL, sha256 TEXT NOT NULL)'
1658
+ ).run()
1659
+ } catch (error) {
1660
+ const msg = error?.message || String(error)
1661
+ log('Failed to ensure migration ledger:', msg)
1662
+ return Response.json({ error: 'Failed to ensure migration ledger: ' + msg }, { status: 500 })
1663
+ }
1664
+
1665
+ let ledgerRows = []
1666
+ try {
1667
+ const ledger = await db.prepare('SELECT filename, sha256 FROM _devflare_migrations').all()
1668
+ ledgerRows = ledger?.results || []
1669
+ } catch (error) {
1670
+ log('Failed to read migration ledger:', error?.message || String(error))
1671
+ }
1672
+ const ledgerByFilename = new Map()
1673
+ for (const row of ledgerRows) {
1674
+ ledgerByFilename.set(row.filename, row.sha256)
1675
+ }
1676
+
1677
+ const applied = []
1678
+ const skipped = []
1679
+ const warnings = []
1680
+ const results = []
1681
+
1682
+ for (const file of files) {
1683
+ const existingHash = ledgerByFilename.get(file.filename)
1684
+ if (existingHash === file.sha256) {
1685
+ skipped.push(file.filename)
1686
+ continue
1687
+ }
1688
+ if (existingHash && existingHash !== file.sha256) {
1689
+ warnings.push({
1690
+ filename: file.filename,
1691
+ message: 'sha256 drifted since last apply; skipped'
1692
+ })
1693
+ skipped.push(file.filename)
1694
+ continue
1695
+ }
1696
+
1697
+ let fileFailed = false
1698
+ for (const sql of file.statements || []) {
1699
+ try {
1700
+ log('Running migration SQL:', sql.slice(0, 80))
1701
+ await db.prepare(sql).run()
1702
+ results.push({ sql: sql.slice(0, 50), success: true })
1703
+ } catch (error) {
1704
+ const msg = error?.message || String(error)
1705
+ log('Migration SQL error:', msg)
1706
+ if (msg.includes('already exists')) {
1707
+ results.push({ sql: sql.slice(0, 50), success: true, skipped: true })
1708
+ } else {
1709
+ results.push({ sql: sql.slice(0, 50), success: false, error: msg })
1710
+ fileFailed = true
1711
+ }
1712
+ }
1713
+ }
1714
+
1715
+ if (!fileFailed) {
1716
+ try {
1717
+ await db.prepare(
1718
+ 'INSERT OR REPLACE INTO _devflare_migrations (filename, applied_at, sha256) VALUES (?, ?, ?)'
1719
+ ).bind(file.filename, new Date().toISOString(), file.sha256).run()
1720
+ applied.push(file.filename)
1721
+ } catch (error) {
1722
+ log('Failed to record migration in ledger:', error?.message || String(error))
1723
+ }
1724
+ }
1725
+ }
1726
+
1727
+ return Response.json({ success: true, results, applied, skipped, warnings })
1728
+ }
1729
+
1730
+ // Legacy path: flat statement list, no ledger tracking.
1731
+ const results = []
1732
+ for (const sql of statements) {
1733
+ try {
1734
+ log('Running migration SQL:', sql.slice(0, 80))
1735
+ await db.prepare(sql).run()
1736
+ results.push({ sql: sql.slice(0, 50), success: true })
1737
+ log('Migration SQL succeeded')
1738
+ } catch (error) {
1739
+ const msg = error?.message || String(error)
1740
+ log('Migration SQL error:', msg)
1741
+ if (msg.includes('already exists')) {
1742
+ results.push({ sql: sql.slice(0, 50), success: true, skipped: true })
1743
+ } else {
1744
+ results.push({ sql: sql.slice(0, 50), success: false, error: msg })
1745
+ }
1746
+ }
1747
+ }
1748
+
1749
+ try {
1750
+ const tables = await db.prepare(\"SELECT name FROM sqlite_master WHERE type='table'\").all()
1751
+ log('Tables after migration:', JSON.stringify(tables))
1752
+ } catch (e) {
1753
+ log('Error listing tables:', e.message)
1754
+ }
1755
+
1756
+ return Response.json({ success: true, results })
1757
+ } catch (error) {
1758
+ return Response.json({ error: error?.message || String(error) }, { status: 500 })
1759
+ }
1760
+ }
1761
+
1762
+ async function handleEmailIncoming(request, env, ctx, url) {
1763
+ try {
1764
+ const from = url.searchParams.get('from') || 'unknown@example.com'
1765
+ const to = url.searchParams.get('to') || 'worker@example.com'
1766
+ const rawBody = await request.text()
1767
+
1768
+ log('Email incoming:', { from, to, bodyLength: rawBody.length })
1769
+
1770
+ if (APP_SERVICE_BINDING) {
1771
+ const appWorker = env[APP_SERVICE_BINDING]
1772
+ if (appWorker && typeof appWorker.fetch === 'function') {
1773
+ const response = await appWorker.fetch(new Request('http://devflare.internal/_devflare/internal/email', {
1774
+ method: 'POST',
1775
+ headers: {
1776
+ 'x-devflare-event': 'email',
1777
+ 'x-devflare-email-from': from,
1778
+ 'x-devflare-email-to': to,
1779
+ 'content-type': request.headers.get('content-type') || 'text/plain'
1780
+ },
1781
+ body: rawBody
1782
+ }))
1783
+
1784
+ if (!response.ok) {
1785
+ return response
1786
+ }
1787
+ }
1788
+ }
1789
+
1790
+ return new Response(JSON.stringify({ ok: true, from, to }), {
1791
+ headers: { 'Content-Type': 'application/json' }
1792
+ })
1793
+ } catch (error) {
1794
+ console.error('[Gateway] Email handler error:', error)
1795
+ return Response.json({ error: error?.message || String(error) }, { status: 500 })
1796
+ }
1797
+ }
1798
+
1799
+ function matchWsRoute(pathname) {
1800
+ for (const route of WS_ROUTES) {
1801
+ if (pathname === route.pattern || pathname.startsWith(route.pattern + '?')) {
1802
+ return route
1803
+ }
1804
+ }
1805
+ return null
1806
+ }
1807
+
1808
+ // Forward a WebSocket upgrade to the app worker (worker mode) and pass through
1809
+ // its response ONLY when the app answered with a genuine WebSocket upgrade
1810
+ // (status 101 + a webSocket). Returns null otherwise so the caller falls back to
1811
+ // the in-worker bridge socket. This is what lets an app route that does
1812
+ // \`return stub.fetch(clientUpgradeRequest)\` reach the DO and stream the DO's
1813
+ // client socket back to the browser (hibernation handlers then fire, and two
1814
+ // tabs share one DO instance). A GET upgrade has no body, so forwarding the same
1815
+ // request and — on a non-101 answer — discarding it is safe (no stream consumed).
1816
+ async function forwardWebSocketToApp(request, env) {
1817
+ if (!APP_SERVICE_BINDING) return null
1818
+ const appWorker = env[APP_SERVICE_BINDING]
1819
+ if (!appWorker || typeof appWorker.fetch !== 'function') return null
1820
+ const response = await appWorker.fetch(request)
1821
+ if (response.status === 101 && response.webSocket) {
1822
+ return response
1823
+ }
1824
+ return null
1825
+ }
1826
+
1827
+ async function handleDoWebSocket(request, env, url, route) {
1828
+ try {
1829
+ const namespace = env[route.doNamespace]
1830
+ if (!namespace) {
1831
+ console.error('[Gateway] DO namespace not found:', route.doNamespace)
1832
+ return new Response('DO namespace not found: ' + route.doNamespace, { status: 500 })
1833
+ }
1834
+
1835
+ const idValue = url.searchParams.get(route.idParam) || 'default'
1836
+ const doId = namespace.idFromName(idValue)
1837
+ const stub = namespace.get(doId)
1838
+
1839
+ const forwardUrl = new URL(route.forwardPath, url.origin)
1840
+ url.searchParams.forEach((v, k) => forwardUrl.searchParams.set(k, v))
1841
+
1842
+ log('Forwarding WebSocket to DO:', route.doNamespace, 'id:', idValue, 'path:', forwardUrl.pathname)
1843
+
1844
+ return stub.fetch(forwardUrl.toString(), {
1845
+ method: request.method,
1846
+ headers: request.headers
1847
+ })
1848
+ } catch (error) {
1849
+ console.error('[Gateway] Error forwarding to DO:', error)
1850
+ return new Response('Error forwarding to DO: ' + error.message, { status: 500 })
1851
+ }
1852
+ }
1853
+ `;
1854
+ }
1855
+ //#endregion
1856
+ //#region src/dev-server/miniflare-worker-config.ts
1857
+ const DEFAULT_MODULE_RULES = [
1858
+ {
1859
+ type: "ESModule",
1860
+ include: [
1861
+ "**/*.ts",
1862
+ "**/*.tsx",
1863
+ "**/*.mts",
1864
+ "**/*.mjs"
1865
+ ]
1866
+ },
1867
+ {
1868
+ type: "CommonJS",
1869
+ include: ["**/*.js", "**/*.cjs"]
1870
+ },
1871
+ {
1872
+ type: "ESModule",
1873
+ include: ["**/*.jsx"]
1874
+ }
1875
+ ];
1876
+ function toMiniflareModuleRule(rule) {
1877
+ return {
1878
+ type: rule.type,
1879
+ include: rule.globs,
1880
+ ...rule.fallthrough !== void 0 && { fallthrough: rule.fallthrough }
1881
+ };
1882
+ }
1883
+ /**
1884
+ * Build the per-worker `serviceBindings` map. Combines user-declared
1885
+ * `bindings.services` (config) with any extra bindings the caller wants to
1886
+ * inject (e.g. internal gateway -> app routing).
1887
+ */
1888
+ function buildServiceBindings(bindings, extraBindings = {}) {
1889
+ const serviceBindings = {};
1890
+ if (bindings.services) for (const [bindingName, serviceConfig] of Object.entries(bindings.services)) serviceBindings[bindingName] = {
1891
+ name: serviceConfig.service,
1892
+ ...serviceConfig.entrypoint && { entrypoint: serviceConfig.entrypoint },
1893
+ ...serviceConfig.props !== void 0 && { props: serviceConfig.props }
1894
+ };
1895
+ for (const [bindingName, target] of Object.entries(extraBindings)) serviceBindings[bindingName] = target;
1896
+ return Object.keys(serviceBindings).length > 0 ? serviceBindings : void 0;
1897
+ }
1898
+ /**
1899
+ * Build a single worker config object for Miniflare's `workers` array.
1900
+ * All inputs are passed explicitly (no closures over the caller's locals).
1901
+ */
1902
+ function makeMiniflareWorker(context, options) {
1903
+ const { cwd, loadedConfig, bindings, sendEmailConfig, rateLimitsConfig, versionMetadataConfig, workerLoadersConfig, mtlsCertificatesConfig, dispatchNamespacesConfig, workflowsConfig, pipelinesConfig, hyperdrivesConfig, imagesConfig, mediaConfig, streamConfig, flagshipConfig, analyticsEngineConfig, tailConsumersConfig, streamingTailConsumersConfig, artifactsConfig, aiSearchNamespacesConfig, aiSearchInstancesConfig, secretsStoreConfig, localSecretWrappedBindingConfig, queueProducers } = context;
1904
+ const baseFlags = loadedConfig.compatibilityFlags ?? [];
1905
+ const compatFlags = baseFlags.includes("nodejs_compat") ? baseFlags : [...baseFlags, "nodejs_compat"];
1906
+ const workerBindings = {
1907
+ ...loadedConfig.vars ?? {},
1908
+ ...context.injectedVars ?? {}
1909
+ };
1910
+ const localWrappedBindings = { ...localSecretWrappedBindingConfig?.wrappedBindings ?? {} };
1911
+ const workerConfig = {
1912
+ name: options.name,
1913
+ modules: true,
1914
+ compatibilityDate: loadedConfig.compatibilityDate,
1915
+ compatibilityFlags: compatFlags,
1916
+ ...bindings.kv && { kvNamespaces: Object.fromEntries(Object.entries(bindings.kv).map(([bindingName, bindingConfig]) => {
1917
+ return [bindingName, getLocalKVNamespaceIdentifier(bindingConfig)];
1918
+ })) },
1919
+ ...bindings.r2 && { r2Buckets: Object.fromEntries(Object.entries(bindings.r2).map(([bindingName, bindingConfig]) => {
1920
+ return [bindingName, normalizeR2Binding(bindingConfig).bucketName];
1921
+ })) },
1922
+ ...bindings.d1 && { d1Databases: Object.fromEntries(Object.entries(bindings.d1).map(([bindingName, bindingConfig]) => {
1923
+ return [bindingName, getLocalD1DatabaseIdentifier(bindingConfig)];
1924
+ })) },
1925
+ ...Object.keys(workerBindings).length > 0 && { bindings: workerBindings },
1926
+ ...sendEmailConfig && { email: sendEmailConfig },
1927
+ ...rateLimitsConfig && { ratelimits: rateLimitsConfig },
1928
+ ...versionMetadataConfig && { versionMetadata: versionMetadataConfig },
1929
+ ...workerLoadersConfig && { workerLoaders: workerLoadersConfig },
1930
+ ...mtlsCertificatesConfig && { mtlsCertificates: mtlsCertificatesConfig },
1931
+ ...dispatchNamespacesConfig && { dispatchNamespaces: dispatchNamespacesConfig },
1932
+ ...workflowsConfig && { workflows: workflowsConfig },
1933
+ ...pipelinesConfig && { pipelines: pipelinesConfig },
1934
+ ...hyperdrivesConfig && { hyperdrives: hyperdrivesConfig },
1935
+ ...imagesConfig && { images: imagesConfig },
1936
+ ...mediaConfig && { media: mediaConfig },
1937
+ ...streamConfig && { stream: streamConfig },
1938
+ ...flagshipConfig && { flagship: flagshipConfig },
1939
+ ...analyticsEngineConfig && { analyticsEngineDatasets: analyticsEngineConfig },
1940
+ ...tailConsumersConfig && { tails: tailConsumersConfig },
1941
+ ...streamingTailConsumersConfig && { streamingTails: streamingTailConsumersConfig },
1942
+ ...artifactsConfig && { artifacts: artifactsConfig },
1943
+ ...aiSearchNamespacesConfig && { aiSearchNamespaces: aiSearchNamespacesConfig },
1944
+ ...aiSearchInstancesConfig && { aiSearchInstances: aiSearchInstancesConfig },
1945
+ ...secretsStoreConfig && { secretsStoreSecrets: secretsStoreConfig },
1946
+ ...Object.keys(localWrappedBindings).length > 0 && { wrappedBindings: localWrappedBindings },
1947
+ ...queueProducers && { queueProducers },
1948
+ ...options.queueConsumers && { queueConsumers: options.queueConsumers },
1949
+ ...options.triggers && { triggers: options.triggers }
1950
+ };
1951
+ if (options.scriptPath) {
1952
+ workerConfig.scriptPath = options.scriptPath;
1953
+ workerConfig.modulesRoot = loadedConfig.baseDir ? resolve(cwd, loadedConfig.baseDir) : cwd;
1954
+ workerConfig.modulesRules = [...loadedConfig.rules?.map(toMiniflareModuleRule) ?? [], ...DEFAULT_MODULE_RULES];
1955
+ }
1956
+ if (options.script) workerConfig.script = options.script;
1957
+ if (options.durableObjects && Object.keys(options.durableObjects).length > 0) workerConfig.durableObjects = options.durableObjects;
1958
+ if (options.serviceBindings && Object.keys(options.serviceBindings).length > 0) workerConfig.serviceBindings = options.serviceBindings;
1959
+ if (options.outboundService !== void 0) workerConfig.outboundService = options.outboundService;
1960
+ return workerConfig;
1961
+ }
1962
+ //#endregion
1963
+ //#region src/dev-server/worker-surface-paths.ts
1964
+ const DEFAULT_TRANSPORT_ENTRY_FILES = [
1965
+ "src/transport.ts",
1966
+ "src/transport.js",
1967
+ "src/transport.mts",
1968
+ "src/transport.mjs"
1969
+ ];
1970
+ const resolveMainWorkerSurfacePaths = resolveWorkerSurfacePaths;
1971
+ function addWorkerWatchRoots(roots, cwd, configuredPath, defaultEntries) {
1972
+ if (configuredPath === false || configuredPath === null) return;
1973
+ if (typeof configuredPath === "string" && configuredPath) {
1974
+ roots.add(dirname(resolve(cwd, configuredPath)));
1975
+ return;
1976
+ }
1977
+ for (const defaultEntry of defaultEntries) roots.add(dirname(resolve(cwd, defaultEntry)));
1978
+ }
1979
+ function collectWorkerWatchRoots(cwd, config, mainWorkerSurfacePaths) {
1980
+ const roots = /* @__PURE__ */ new Set();
1981
+ for (const surfacePath of Object.values(mainWorkerSurfacePaths)) if (surfacePath) roots.add(dirname(surfacePath));
1982
+ addWorkerWatchRoots(roots, cwd, config.files?.fetch, DEFAULT_FETCH_ENTRY_FILES);
1983
+ addWorkerWatchRoots(roots, cwd, config.files?.queue, DEFAULT_QUEUE_ENTRY_FILES);
1984
+ addWorkerWatchRoots(roots, cwd, config.files?.scheduled, DEFAULT_SCHEDULED_ENTRY_FILES);
1985
+ addWorkerWatchRoots(roots, cwd, config.files?.email, DEFAULT_EMAIL_ENTRY_FILES);
1986
+ addWorkerWatchRoots(roots, cwd, config.files?.transport, DEFAULT_TRANSPORT_ENTRY_FILES);
1987
+ const routeDirectory = getRouteDirectoryCandidate(cwd, config);
1988
+ if (routeDirectory) roots.add(routeDirectory.absoluteDir);
1989
+ return [...roots];
1990
+ }
1991
+ //#endregion
1992
+ //#region src/dev-server/miniflare-dev-config.ts
1993
+ const INTERNAL_APP_SERVICE_BINDING = "__DEVFLARE_APP";
1994
+ /**
1995
+ * Resolve the browser-facing origin of the local dev runtime, used inside
1996
+ * locally-presigned R2 URLs. Honors `server.publicUrl` (reverse proxy /
1997
+ * tunnel setups); otherwise derives `http(s)://<host>:<port>` from the
1998
+ * Miniflare listen address, normalizing wildcard hosts to `localhost`.
1999
+ */
2000
+ function resolveR2PresignOrigin(serverConfig, miniflareHost, miniflarePort) {
2001
+ if (serverConfig?.publicUrl) return serverConfig.publicUrl.replace(/\/$/, "");
2002
+ return `${serverConfig?.https ? "https" : "http"}://${miniflareHost === "0.0.0.0" || miniflareHost === "::" ? "localhost" : miniflareHost}:${miniflarePort}`;
2003
+ }
2004
+ /**
2005
+ * Build the complete Miniflare configuration for the dev server.
2006
+ *
2007
+ * IMPORTANT: When using multi-worker setup, ALL workers must go in the
2008
+ * `workers` array. The FIRST worker is the entrypoint and receives all
2009
+ * HTTP requests. Top-level script/modules options are NOT used when
2010
+ * workers array is present.
2011
+ */
2012
+ function buildMiniflareDevConfig(input) {
2013
+ const { config: loadedConfig, cwd, miniflarePort, miniflareHost = "127.0.0.1", persist, enableVite, debug, mainWorkerSurfacePaths, mainWorkerRoutes, mainWorkerScriptPath, bundledMainWorkerScriptPath, workflowEntrypointScript, browserShimPort, doResult, serviceBindingResolution, r2PresignSecret, additionalInjectedVars, logger } = input;
2014
+ const bindings = loadedConfig.bindings ?? {};
2015
+ const persistPath = process.env.DEVFLARE_PERSIST_DIR ? resolve(cwd, process.env.DEVFLARE_PERSIST_DIR) : resolve(cwd, ".devflare/data");
2016
+ const appWorkerName = loadedConfig.name;
2017
+ const shouldRunMainWorker = !enableVite && (hasWorkerSurfacePaths(mainWorkerSurfacePaths) || Boolean(mainWorkerRoutes?.routes.length));
2018
+ const queueProducers = buildQueueProducers(bindings);
2019
+ const queueConsumers = buildQueueConsumers(bindings);
2020
+ const serverConfig = loadedConfig.server;
2021
+ const sharedOptions = {
2022
+ port: miniflarePort,
2023
+ host: miniflareHost,
2024
+ kvPersist: persist ? `${persistPath}/kv` : void 0,
2025
+ r2Persist: persist ? `${persistPath}/r2` : void 0,
2026
+ d1Persist: persist ? `${persistPath}/d1` : void 0,
2027
+ cachePersist: persist ? `${persistPath}/cache` : void 0,
2028
+ durableObjectsPersist: persist ? `${persistPath}/do` : void 0,
2029
+ workflowsPersist: persist ? `${persistPath}/workflows` : void 0,
2030
+ imagesPersist: persist ? `${persistPath}/images` : void 0,
2031
+ streamPersist: persist ? `${persistPath}/stream` : void 0,
2032
+ ...serverConfig?.https !== void 0 && { https: serverConfig.https },
2033
+ ...serverConfig?.httpsKeyPath !== void 0 && { httpsKeyPath: serverConfig.httpsKeyPath },
2034
+ ...serverConfig?.httpsCertPath !== void 0 && { httpsCertPath: serverConfig.httpsCertPath },
2035
+ ...serverConfig?.inspectorPort !== void 0 && { inspectorPort: serverConfig.inspectorPort },
2036
+ ...serverConfig?.inspectorHost !== void 0 && { inspectorHost: serverConfig.inspectorHost },
2037
+ ...serverConfig?.upstream !== void 0 && { upstream: serverConfig.upstream },
2038
+ ...serverConfig?.liveReload !== void 0 && { liveReload: serverConfig.liveReload },
2039
+ ...serverConfig?.verbose !== void 0 && { verbose: serverConfig.verbose },
2040
+ ...serverConfig?.logRequests !== void 0 && { logRequests: serverConfig.logRequests },
2041
+ ...serverConfig?.cf !== void 0 && { cf: serverConfig.cf },
2042
+ ...serverConfig?.publicUrl !== void 0 && { publicUrl: serverConfig.publicUrl }
2043
+ };
2044
+ const localBindingShimServiceConfig = buildLocalBindingShimServiceConfig(loadedConfig);
2045
+ const createServiceBindings = (extraBindings = {}) => buildServiceBindings(bindings, {
2046
+ ...serviceBindingResolution?.primaryServiceBindings ?? {},
2047
+ ...localBindingShimServiceConfig.serviceBindings,
2048
+ ...extraBindings
2049
+ });
2050
+ const sendEmailConfig = buildSendEmailConfig(bindings);
2051
+ const rateLimitsConfig = buildRateLimitsConfig(bindings);
2052
+ const versionMetadataConfig = buildVersionMetadataConfig(bindings);
2053
+ const workerLoadersConfig = buildWorkerLoadersConfig(bindings);
2054
+ const mtlsCertificatesConfig = buildMtlsCertificatesConfig(bindings);
2055
+ const dispatchNamespacesConfig = buildDispatchNamespacesConfig(bindings);
2056
+ const workflowsConfig = buildWorkflowsConfig(bindings);
2057
+ const pipelinesConfig = buildPipelinesConfig(bindings);
2058
+ const hyperdrivesConfig = buildHyperdrivesConfig(bindings);
2059
+ const imagesConfig = bindings.images ? void 0 : buildImagesConfig(bindings);
2060
+ const mediaConfig = bindings.media ? void 0 : buildMediaConfig(bindings);
2061
+ const streamConfig = buildStreamConfig(bindings);
2062
+ const flagshipConfig = buildFlagshipConfig(bindings);
2063
+ const analyticsEngineConfig = buildAnalyticsEngineConfig(bindings);
2064
+ const tailConsumersConfig = buildTailConsumersConfig(loadedConfig);
2065
+ const streamingTailConsumersConfig = buildStreamingTailConsumersConfig(loadedConfig);
2066
+ const artifactsConfig = buildArtifactsConfig(bindings);
2067
+ const aiSearchNamespacesConfig = buildAiSearchNamespacesConfig(bindings);
2068
+ const aiSearchInstancesConfig = buildAiSearchInstancesConfig(bindings);
2069
+ const localSecretWrappedBindingConfig = buildLocalSecretWrappedBindingConfig(loadedConfig, cwd);
2070
+ const localSecretBindingNames = new Set(localSecretWrappedBindingConfig.localBindingNames);
2071
+ const secretsStoreConfig = buildSecretsStoreConfig(bindings, loadedConfig.secretsStoreId, localSecretBindingNames);
2072
+ const presignVars = bindings.r2 && r2PresignSecret ? {
2073
+ DEVFLARE_R2_PRESIGN_SECRET: r2PresignSecret,
2074
+ DEVFLARE_R2_PRESIGN_ORIGIN: resolveR2PresignOrigin(serverConfig, miniflareHost, miniflarePort)
2075
+ } : void 0;
2076
+ const hasAdditional = additionalInjectedVars !== void 0 && Object.keys(additionalInjectedVars).length > 0;
2077
+ const workerContext = {
2078
+ cwd,
2079
+ loadedConfig,
2080
+ bindings,
2081
+ sendEmailConfig,
2082
+ rateLimitsConfig,
2083
+ versionMetadataConfig,
2084
+ workerLoadersConfig,
2085
+ mtlsCertificatesConfig,
2086
+ dispatchNamespacesConfig,
2087
+ workflowsConfig,
2088
+ pipelinesConfig,
2089
+ hyperdrivesConfig,
2090
+ imagesConfig,
2091
+ mediaConfig,
2092
+ streamConfig,
2093
+ flagshipConfig,
2094
+ analyticsEngineConfig,
2095
+ tailConsumersConfig,
2096
+ streamingTailConsumersConfig,
2097
+ artifactsConfig,
2098
+ aiSearchNamespacesConfig,
2099
+ aiSearchInstancesConfig,
2100
+ secretsStoreConfig,
2101
+ localSecretWrappedBindingConfig,
2102
+ queueProducers,
2103
+ injectedVars: presignVars || hasAdditional ? {
2104
+ ...presignVars ?? {},
2105
+ ...hasAdditional ? additionalInjectedVars : {}
2106
+ } : void 0
2107
+ };
2108
+ const createWorkerConfig = (options) => makeMiniflareWorker(workerContext, options);
2109
+ const gatewayWorker = createWorkerConfig({
2110
+ name: "gateway",
2111
+ script: [workflowEntrypointScript, getGatewayScript(loadedConfig.wsRoutes, debug, shouldRunMainWorker ? INTERNAL_APP_SERVICE_BINDING : null)].filter(Boolean).join("\n\n"),
2112
+ serviceBindings: shouldRunMainWorker ? createServiceBindings({ [INTERNAL_APP_SERVICE_BINDING]: { name: appWorkerName } }) : createServiceBindings()
2113
+ });
2114
+ gatewayWorker.routes = ["*"];
2115
+ const hasDurableObjectBundles = !!doResult && doResult.bundles.size > 0;
2116
+ const browserBindingName = getSingleBrowserBindingName(bindings.browser);
2117
+ const needsBrowserWorker = Boolean(browserBindingName && (hasDurableObjectBundles || shouldRunMainWorker));
2118
+ const workers = [];
2119
+ const durableObjects = {};
2120
+ const browserShimUrl = `http://127.0.0.1:${browserShimPort}`;
2121
+ const browserWorkerName = "browser-binding";
2122
+ if (shouldRunMainWorker && mainWorkerScriptPath) {
2123
+ const mainWorkerServiceBindings = createServiceBindings(browserBindingName ? { [browserBindingName]: { name: browserWorkerName } } : {});
2124
+ const mainWorkerConfig = createWorkerConfig({
2125
+ name: appWorkerName,
2126
+ scriptPath: bundledMainWorkerScriptPath ?? mainWorkerScriptPath,
2127
+ serviceBindings: mainWorkerServiceBindings,
2128
+ queueConsumers,
2129
+ triggers: loadedConfig.triggers?.crons?.length ? { crons: loadedConfig.triggers.crons } : void 0
2130
+ });
2131
+ workers.push(mainWorkerConfig);
2132
+ }
2133
+ if (doResult) for (const [bindingName, bundlePath] of doResult.bundles) {
2134
+ const className = doResult.classes.get(bindingName);
2135
+ if (!className) continue;
2136
+ const workerName = `do-${bindingName.toLowerCase()}`;
2137
+ const workerConfig = createWorkerConfig({
2138
+ name: workerName,
2139
+ scriptPath: bundlePath,
2140
+ durableObjects: { [bindingName]: className },
2141
+ serviceBindings: createServiceBindings(browserBindingName ? { [browserBindingName]: { name: browserWorkerName } } : {})
2142
+ });
2143
+ if (browserBindingName) logger?.debug(`DO ${workerName} has browser service binding: ${browserBindingName} → ${browserWorkerName}`);
2144
+ logger?.debug(`DO ${workerName} config:`, JSON.stringify(workerConfig, null, 2));
2145
+ workers.push(workerConfig);
2146
+ durableObjects[bindingName] = {
2147
+ className,
2148
+ scriptName: workerName
2149
+ };
2150
+ }
2151
+ if (needsBrowserWorker) {
2152
+ const browserWorker = createWorkerConfig({
2153
+ name: browserWorkerName,
2154
+ script: getBrowserBindingScript(browserShimUrl, debug)
2155
+ });
2156
+ workers.push(browserWorker);
2157
+ logger?.info(`Browser binding worker configured: ${browserBindingName} → ${browserShimUrl}`);
2158
+ }
2159
+ if (Object.keys(durableObjects).length > 0) {
2160
+ gatewayWorker.durableObjects = durableObjects;
2161
+ if (shouldRunMainWorker) {
2162
+ const mainWorker = workers.find((worker) => worker.name === appWorkerName);
2163
+ if (mainWorker) mainWorker.durableObjects = durableObjects;
2164
+ }
2165
+ }
2166
+ return {
2167
+ ...sharedOptions,
2168
+ workers: [
2169
+ gatewayWorker,
2170
+ ...workers,
2171
+ ...serviceBindingResolution?.workers ?? [],
2172
+ ...localSecretWrappedBindingConfig.workers,
2173
+ ...localBindingShimServiceConfig.workers
2174
+ ]
2175
+ };
2176
+ }
2177
+ //#endregion
2178
+ //#region src/dev-server/runtime-stdio.ts
2179
+ function writeStdout(logger, message) {
2180
+ if (typeof logger?.log === "function") {
2181
+ logger.log(message);
2182
+ return;
2183
+ }
2184
+ if (typeof logger?.info === "function") {
2185
+ logger.info(message);
2186
+ return;
2187
+ }
2188
+ console.log(message);
2189
+ }
2190
+ function writeStderr(logger, message) {
2191
+ if (typeof logger?.error === "function") {
2192
+ logger.error(message);
2193
+ return;
2194
+ }
2195
+ console.error(message);
2196
+ }
2197
+ function createRuntimeStdioForwarder(logger) {
2198
+ return (stdout, stderr) => {
2199
+ createInterface({ input: stdout }).on("line", (data) => {
2200
+ writeStdout(logger, data);
2201
+ });
2202
+ createInterface({ input: stderr }).on("line", (data) => {
2203
+ writeStderr(logger, data);
2204
+ });
2205
+ };
2206
+ }
2207
+ //#endregion
2208
+ //#region src/browser-shim/server.ts
2209
+ /**
2210
+ * Devflare's **local browser-rendering shim**.
2211
+ *
2212
+ * Accepts only loopback browser origins (e.g. `http://127.0.0.1:*`,
2213
+ * `http://localhost:*`) plus origin-less tool traffic (Puppeteer, curl, and
2214
+ * other non-browser clients that do not send an `Origin` header). Cross-origin
2215
+ * browser traffic is rejected at the request boundary.
2216
+ *
2217
+ * This is NOT a user-facing app route — it is devflare's protected helper
2218
+ * endpoint used by the local Browser Rendering binding to satisfy the
2219
+ * `@cloudflare/puppeteer` contract during local dev. The loopback-only posture
2220
+ * applies to this shim only and does not apply to the user's normal worker
2221
+ * routes.
2222
+ */
2223
+ let cachedExecutablePath = null;
2224
+ /**
2225
+ * Default Chrome flags used when launching headless Chrome for local
2226
+ * browser-rendering emulation. Each flag is included for a deliberate reason;
2227
+ * edit cautiously.
2228
+ *
2229
+ * NOTE: `--no-sandbox` / `--disable-setuid-sandbox` are intentionally NOT part
2230
+ * of the defaults. Disabling the sandbox removes the primary boundary between
2231
+ * untrusted web content and the host and must be opted into explicitly via
2232
+ * `BrowserShimOptions.allowNoSandbox`.
2233
+ */
2234
+ const DEFAULT_CHROME_FLAGS = [
2235
+ "--disable-dev-shm-usage",
2236
+ "--disable-gpu",
2237
+ "--disable-software-rasterizer",
2238
+ "--disable-extensions",
2239
+ "--disable-background-networking",
2240
+ "--disable-background-timer-throttling",
2241
+ "--disable-backgrounding-occluded-windows",
2242
+ "--disable-renderer-backgrounding",
2243
+ "--disable-features=TranslateUI",
2244
+ "--disable-ipc-flooding-protection",
2245
+ "--disable-default-apps",
2246
+ "--mute-audio",
2247
+ "--js-flags=--max-old-space-size=4096"
2248
+ ];
2249
+ /**
2250
+ * Flags appended only when `allowNoSandbox` is explicitly enabled. Kept in a
2251
+ * separate constant so callers and tests can assert they are opt-in.
2252
+ */
2253
+ const NO_SANDBOX_FLAGS = ["--no-sandbox", "--disable-setuid-sandbox"];
2254
+ /**
2255
+ * Resolve the Chrome argv for a shim launch. Exported for testability.
2256
+ */
2257
+ function resolveChromeFlags(options = {}) {
2258
+ const flags = [...DEFAULT_CHROME_FLAGS];
2259
+ if (options.allowNoSandbox) flags.unshift(...NO_SANDBOX_FLAGS);
2260
+ return flags;
2261
+ }
2262
+ /**
2263
+ * Create a download progress logger that emits at most one "start" line and
2264
+ * exactly one "complete" line per download. Avoids the previous heuristic
2265
+ * percent-spam which could log the same bucket multiple times.
2266
+ *
2267
+ * The returned callback matches `@puppeteer/browsers` `downloadProgressCallback`.
2268
+ */
2269
+ function createDownloadProgressLogger(logger, label = "Chrome") {
2270
+ const state = {
2271
+ started: false,
2272
+ completed: false,
2273
+ progress: {
2274
+ bytesReceived: 0,
2275
+ totalBytes: 0
2276
+ }
2277
+ };
2278
+ return {
2279
+ onProgress(downloadedBytes, totalBytes) {
2280
+ if (state.completed) return;
2281
+ state.progress.bytesReceived = downloadedBytes;
2282
+ state.progress.totalBytes = totalBytes;
2283
+ if (!state.started) {
2284
+ state.started = true;
2285
+ logger?.info(`[BrowserShim] Downloading ${label}...`);
2286
+ }
2287
+ if (totalBytes > 0 && downloadedBytes >= totalBytes) {
2288
+ state.completed = true;
2289
+ logger?.info(`[BrowserShim] ${label} download complete`);
2290
+ }
2291
+ },
2292
+ finalize() {
2293
+ if (!state.started || state.completed) return;
2294
+ state.completed = true;
2295
+ logger?.info(`[BrowserShim] ${label} download complete`);
2296
+ },
2297
+ get progress() {
2298
+ return state.progress;
2299
+ },
2300
+ get started() {
2301
+ return state.started;
2302
+ },
2303
+ get completed() {
2304
+ return state.completed;
2305
+ }
2306
+ };
2307
+ }
2308
+ /**
2309
+ * Get or install Chrome Headless Shell
2310
+ * Uses a shared cache directory so Chrome is only installed once globally
2311
+ */
2312
+ async function ensureChrome(cacheDir, logger) {
2313
+ if (cachedExecutablePath && existsSync(cachedExecutablePath)) return cachedExecutablePath;
2314
+ const platform = detectBrowserPlatform();
2315
+ if (!platform) throw new Error("Could not detect browser platform");
2316
+ const buildId = await resolveBuildId(Browser.CHROMEHEADLESSSHELL, platform, "stable");
2317
+ logger?.debug(`[BrowserShim] Resolved Chrome Headless Shell build: ${buildId}`);
2318
+ const progressLogger = createDownloadProgressLogger(logger, "Chrome");
2319
+ const installedBrowser = await install({
2320
+ browser: Browser.CHROMEHEADLESSSHELL,
2321
+ buildId,
2322
+ cacheDir,
2323
+ downloadProgressCallback: (downloadedBytes, totalBytes) => {
2324
+ progressLogger.onProgress(downloadedBytes, totalBytes);
2325
+ }
2326
+ });
2327
+ progressLogger.finalize();
2328
+ cachedExecutablePath = installedBrowser.executablePath;
2329
+ logger?.success(`[BrowserShim] Chrome ready: ${installedBrowser.executablePath}`);
2330
+ return installedBrowser.executablePath;
2331
+ }
2332
+ function createBrowserShim(options = {}) {
2333
+ const { port = 8788, host = "127.0.0.1", logger, verbose = false, keepAlive = 6e4, cacheDir = join$1(homedir(), ".devflare", "chrome"), allowNoSandbox = false } = options;
2334
+ const chromeLaunchArgs = resolveChromeFlags({ allowNoSandbox });
2335
+ if (allowNoSandbox) logger?.warn("[BrowserShim] Launching Chrome with --no-sandbox (allowNoSandbox=true). Only use this in trusted CI/rootless environments.");
2336
+ let server = null;
2337
+ let executablePath = null;
2338
+ const sessions = /* @__PURE__ */ new Map();
2339
+ const history = [];
2340
+ let WebSocketServerClass = null;
2341
+ let WebSocketClass = null;
2342
+ const maxRequestBodyBytes = 1024 * 1024;
2343
+ function getRequestOrigin(req) {
2344
+ const origin = req.headers.origin;
2345
+ if (typeof origin === "string") return origin;
2346
+ if (Array.isArray(origin) && origin[0]) return origin[0];
2347
+ return null;
2348
+ }
2349
+ function isLoopbackOrigin(origin) {
2350
+ try {
2351
+ const url = new URL(origin);
2352
+ return url.hostname === "127.0.0.1" || url.hostname === "localhost" || url.hostname === "::1" || url.hostname === "[::1]";
2353
+ } catch {
2354
+ return false;
2355
+ }
2356
+ }
2357
+ function applyCorsHeaders(req, res) {
2358
+ const origin = getRequestOrigin(req);
2359
+ if (!origin) return true;
2360
+ if (!isLoopbackOrigin(origin)) {
2361
+ res.writeHead(403, { "Content-Type": "application/json" });
2362
+ res.end(JSON.stringify({ error: "Forbidden origin" }));
2363
+ return false;
2364
+ }
2365
+ res.setHeader("Access-Control-Allow-Origin", origin);
2366
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2367
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
2368
+ res.setHeader("Vary", "Origin");
2369
+ return true;
2370
+ }
2371
+ /**
2372
+ * Launch a new browser and create a session
2373
+ */
2374
+ async function acquireSession(acquireOptions) {
2375
+ if (!executablePath) throw new Error("Chrome not initialized");
2376
+ const browser = await puppeteerCore.launch({
2377
+ executablePath,
2378
+ headless: true,
2379
+ protocolTimeout: 12e4,
2380
+ args: chromeLaunchArgs
2381
+ });
2382
+ const wsEndpoint = browser.wsEndpoint();
2383
+ const sessionId = crypto.randomUUID();
2384
+ const session = {
2385
+ sessionId,
2386
+ browser,
2387
+ wsEndpoint,
2388
+ startTime: Date.now()
2389
+ };
2390
+ sessions.set(sessionId, session);
2391
+ const timeout = acquireOptions?.keep_alive ?? keepAlive;
2392
+ if (timeout > 0) session.idleTimeout = setTimeout(async () => {
2393
+ const s = sessions.get(sessionId);
2394
+ if (s && !s.connectionId) await closeSession(sessionId, 2, "BrowserIdle");
2395
+ }, timeout);
2396
+ if (verbose) logger?.debug(`[BrowserShim] Acquired session ${sessionId}`);
2397
+ return { sessionId };
2398
+ }
2399
+ /**
2400
+ * Close a browser session
2401
+ */
2402
+ async function closeSession(sessionId, closeReason = 1, closeReasonText = "NormalClosure") {
2403
+ const session = sessions.get(sessionId);
2404
+ if (!session) return;
2405
+ if (session.idleTimeout) clearTimeout(session.idleTimeout);
2406
+ try {
2407
+ await session.browser.close();
2408
+ } catch {}
2409
+ sessions.delete(sessionId);
2410
+ history.unshift({
2411
+ sessionId,
2412
+ startTime: session.startTime,
2413
+ endTime: Date.now(),
2414
+ closeReason,
2415
+ closeReasonText
2416
+ });
2417
+ if (history.length > 100) history.pop();
2418
+ if (verbose) logger?.debug(`[BrowserShim] Closed session ${sessionId}: ${closeReasonText}`);
2419
+ }
2420
+ /**
2421
+ * Handle HTTP requests
2422
+ */
2423
+ async function handleRequest(req, res) {
2424
+ const url = new URL(req.url || "/", `http://${host}:${port}`);
2425
+ const method = req.method || "GET";
2426
+ logger?.debug(`[BrowserShim] ${method} ${url.pathname}${url.search ? url.search : ""}`);
2427
+ if (!applyCorsHeaders(req, res)) return;
2428
+ if (method === "OPTIONS") {
2429
+ res.writeHead(204);
2430
+ res.end();
2431
+ return;
2432
+ }
2433
+ if (url.pathname === "/v1/acquire" && (method === "POST" || method === "GET")) {
2434
+ try {
2435
+ let acquireOptions = {};
2436
+ if (method === "GET") {
2437
+ const keepAlive = url.searchParams.get("keep_alive");
2438
+ if (keepAlive) acquireOptions.keep_alive = Number.parseInt(keepAlive, 10);
2439
+ } else try {
2440
+ const body = await readBody(req);
2441
+ acquireOptions = JSON.parse(body);
2442
+ } catch {}
2443
+ sendJson(res, 200, await acquireSession(acquireOptions));
2444
+ } catch (error) {
2445
+ const msg = error instanceof Error ? error.message : "Failed to acquire browser";
2446
+ logger?.error(`[BrowserShim] Acquire failed: ${msg}`);
2447
+ sendJson(res, 500, { error: msg });
2448
+ }
2449
+ return;
2450
+ }
2451
+ if (url.pathname === "/v1/sessions" && method === "GET") {
2452
+ sendJson(res, 200, Array.from(sessions.values()).map((s) => ({
2453
+ sessionId: s.sessionId,
2454
+ startTime: s.startTime,
2455
+ connectionId: s.connectionId,
2456
+ connectionStartTime: s.connectionStartTime
2457
+ })));
2458
+ return;
2459
+ }
2460
+ if (url.pathname === "/v1/history" && method === "GET") {
2461
+ sendJson(res, 200, history.slice(0, 50));
2462
+ return;
2463
+ }
2464
+ if (url.pathname === "/v1/limits" && method === "GET") {
2465
+ sendJson(res, 200, {
2466
+ activeSessions: Array.from(sessions.keys()).map((id) => ({ id })),
2467
+ allowedBrowserAcquisitions: 10,
2468
+ maxConcurrentSessions: 10,
2469
+ timeUntilNextAllowedBrowserAcquisition: 0
2470
+ });
2471
+ return;
2472
+ }
2473
+ if (url.pathname.startsWith("/v1/session/") && method === "GET") {
2474
+ const sessionId = url.pathname.slice(12);
2475
+ const session = sessions.get(sessionId);
2476
+ if (!session) {
2477
+ sendJson(res, 404, { error: "Session not found" });
2478
+ return;
2479
+ }
2480
+ sendJson(res, 200, {
2481
+ sessionId: session.sessionId,
2482
+ wsEndpoint: session.wsEndpoint,
2483
+ startTime: session.startTime,
2484
+ connectionId: session.connectionId,
2485
+ connectionStartTime: session.connectionStartTime
2486
+ });
2487
+ return;
2488
+ }
2489
+ if (url.pathname === "/_devflare/browser/health") {
2490
+ sendJson(res, 200, {
2491
+ ok: true,
2492
+ activeSessions: sessions.size,
2493
+ historySize: history.length,
2494
+ executablePath
2495
+ });
2496
+ return;
2497
+ }
2498
+ if (url.pathname === "/v1/connectDevtools") {
2499
+ res.writeHead(426, { "Content-Type": "text/plain" });
2500
+ res.end("WebSocket upgrade required");
2501
+ return;
2502
+ }
2503
+ res.writeHead(404, { "Content-Type": "text/plain" });
2504
+ res.end("Not found");
2505
+ }
2506
+ /**
2507
+ * Read request body as string
2508
+ */
2509
+ function readBody(req) {
2510
+ return new Promise((resolve, reject) => {
2511
+ const chunks = [];
2512
+ let totalBytes = 0;
2513
+ req.on("data", (chunk) => {
2514
+ totalBytes += chunk.length;
2515
+ if (totalBytes > maxRequestBodyBytes) {
2516
+ req.destroy();
2517
+ reject(/* @__PURE__ */ new Error(`Request body exceeds ${maxRequestBodyBytes} bytes`));
2518
+ return;
2519
+ }
2520
+ chunks.push(chunk);
2521
+ });
2522
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()));
2523
+ req.on("error", reject);
2524
+ });
2525
+ }
2526
+ /**
2527
+ * Send JSON response
2528
+ */
2529
+ function sendJson(res, status, data) {
2530
+ const body = JSON.stringify(data);
2531
+ res.writeHead(status, {
2532
+ "Content-Type": "application/json",
2533
+ "Content-Length": Buffer.byteLength(body)
2534
+ });
2535
+ res.end(body);
2536
+ }
2537
+ /**
2538
+ * Start the browser shim server
2539
+ */
2540
+ async function start() {
2541
+ logger?.info("[BrowserShim] Ensuring Chrome Headless Shell is available...");
2542
+ executablePath = await ensureChrome(cacheDir, logger);
2543
+ try {
2544
+ const wsModule = await import("ws");
2545
+ WebSocketServerClass = wsModule.WebSocketServer || wsModule.default?.WebSocketServer;
2546
+ WebSocketClass = wsModule.WebSocket || wsModule.default?.WebSocket || wsModule.default;
2547
+ } catch {
2548
+ logger?.warn("[BrowserShim] ws package not found, WebSocket proxy disabled");
2549
+ logger?.warn("[BrowserShim] Install with: npm install ws");
2550
+ }
2551
+ server = createServer((req, res) => {
2552
+ handleRequest(req, res).catch((error) => {
2553
+ logger?.error("[BrowserShim] Request error:", error);
2554
+ res.writeHead(500);
2555
+ res.end("Internal server error");
2556
+ });
2557
+ });
2558
+ if (WebSocketServerClass) {
2559
+ const wss = new WebSocketServerClass({ noServer: true });
2560
+ server.on("upgrade", (request, socket, head) => {
2561
+ const origin = getRequestOrigin(request);
2562
+ if (origin && !isLoopbackOrigin(origin)) {
2563
+ socket.write("HTTP/1.1 403 Forbidden\r\n\r\n");
2564
+ socket.destroy();
2565
+ return;
2566
+ }
2567
+ const url = new URL(request.url || "/", `http://${host}:${port}`);
2568
+ if (url.pathname !== "/v1/connectDevtools") {
2569
+ socket.destroy();
2570
+ return;
2571
+ }
2572
+ const sessionId = url.searchParams.get("browser_session");
2573
+ if (!sessionId) {
2574
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
2575
+ socket.destroy();
2576
+ return;
2577
+ }
2578
+ const session = sessions.get(sessionId);
2579
+ if (!session) {
2580
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
2581
+ socket.destroy();
2582
+ return;
2583
+ }
2584
+ const connectionId = crypto.randomUUID();
2585
+ session.connectionId = connectionId;
2586
+ session.connectionStartTime = Date.now();
2587
+ if (session.idleTimeout) {
2588
+ clearTimeout(session.idleTimeout);
2589
+ session.idleTimeout = void 0;
2590
+ }
2591
+ wss.handleUpgrade(request, socket, head, (ws) => {
2592
+ if (verbose) logger?.debug(`[BrowserShim] WebSocket connected for session ${sessionId}`);
2593
+ const chromeWs = new WebSocketClass(session.wsEndpoint);
2594
+ let chromeConnected = false;
2595
+ const connectTimeout = setTimeout(() => {
2596
+ if (!chromeConnected) {
2597
+ logger?.error("[BrowserShim] Chrome connection timeout");
2598
+ try {
2599
+ ws.close(1011, "Chrome connection timeout");
2600
+ chromeWs.close();
2601
+ } catch {}
2602
+ closeSession(sessionId, 5, "ChromeConnectionTimeout").catch(() => {});
2603
+ }
2604
+ }, 1e4);
2605
+ chromeWs.on("open", () => {
2606
+ chromeConnected = true;
2607
+ clearTimeout(connectTimeout);
2608
+ if (verbose) logger?.debug("[BrowserShim] Connected to Chrome DevTools");
2609
+ });
2610
+ chromeWs.on("message", (data) => {
2611
+ if (ws.readyState === 1) ws.send(data);
2612
+ });
2613
+ chromeWs.on("close", (code, reason) => {
2614
+ if (verbose) logger?.debug(`[BrowserShim] Chrome WS closed: ${code}`);
2615
+ const validCode = typeof code === "number" && code >= 1e3 && code <= 4999 ? code : 1e3;
2616
+ try {
2617
+ ws.close(validCode, reason?.toString?.() || "");
2618
+ } catch {}
2619
+ closeSession(sessionId, 2, "ChromeDisconnected").catch((err) => {
2620
+ logger?.error("[BrowserShim] Error closing session after Chrome disconnect:", err);
2621
+ });
2622
+ });
2623
+ chromeWs.on("error", (error) => {
2624
+ logger?.error("[BrowserShim] Chrome WS error:", error.message);
2625
+ try {
2626
+ ws.close(1011, "Chrome WebSocket error");
2627
+ } catch {}
2628
+ closeSession(sessionId, 4, "ChromeError").catch((err) => {
2629
+ logger?.error("[BrowserShim] Error closing session after Chrome error:", err);
2630
+ });
2631
+ });
2632
+ ws.on("message", (data) => {
2633
+ if (chromeWs.readyState === 1) chromeWs.send(data);
2634
+ });
2635
+ ws.on("close", (code, reason) => {
2636
+ if (verbose) logger?.debug(`[BrowserShim] Client WS closed for session ${sessionId}`);
2637
+ const validCode = typeof code === "number" && code >= 1e3 && code <= 4999 ? code : 1e3;
2638
+ try {
2639
+ chromeWs.close(validCode, reason?.toString?.() || "");
2640
+ } catch {}
2641
+ const s = sessions.get(sessionId);
2642
+ if (s && s.connectionId === connectionId) {
2643
+ s.connectionId = void 0;
2644
+ s.connectionStartTime = void 0;
2645
+ closeSession(sessionId, 1, "ClientDisconnected").catch((err) => {
2646
+ logger?.error("[BrowserShim] Error closing session after disconnect:", err);
2647
+ });
2648
+ }
2649
+ });
2650
+ ws.on("error", (error) => {
2651
+ logger?.error("[BrowserShim] Client WS error:", error.message);
2652
+ try {
2653
+ chromeWs.close();
2654
+ } catch {}
2655
+ });
2656
+ });
2657
+ });
2658
+ }
2659
+ await new Promise((resolve, reject) => {
2660
+ server.on("error", reject);
2661
+ server.listen(port, host, () => {
2662
+ resolve();
2663
+ });
2664
+ });
2665
+ logger?.success(`Browser shim server ready on http://${host}:${port}`);
2666
+ }
2667
+ /**
2668
+ * Stop the server and close all browsers
2669
+ */
2670
+ async function stop() {
2671
+ for (const sessionId of Array.from(sessions.keys())) await closeSession(sessionId, 3, "ServerShutdown");
2672
+ if (server) {
2673
+ await new Promise((resolve) => {
2674
+ server.close(() => resolve());
2675
+ });
2676
+ server = null;
2677
+ }
2678
+ logger?.info("Browser shim server stopped");
2679
+ }
2680
+ /**
2681
+ * Get the server URL
2682
+ */
2683
+ function getUrl() {
2684
+ return `http://${host}:${port}`;
2685
+ }
2686
+ return {
2687
+ start,
2688
+ stop,
2689
+ getUrl
2690
+ };
2691
+ }
2692
+ //#endregion
2693
+ //#region src/dev-server/server-startup-helpers.ts
2694
+ /**
2695
+ * Emit informational/warning lines about detected worker handlers in
2696
+ * worker-only (no-Vite) mode.
2697
+ */
2698
+ function logWorkerHandlerDetection(logger, enableVite, hasSurface, mainWorkerSurfacePaths, mainWorkerRoutes) {
2699
+ if (enableVite) return;
2700
+ if (hasSurface) {
2701
+ const detectedWorkerHandlers = Object.entries(mainWorkerSurfacePaths).filter(([, surfacePath]) => !!surfacePath).map(([surfaceName, surfacePath]) => `${surfaceName}=${surfacePath}`);
2702
+ const detectedRouteHandlers = mainWorkerRoutes?.routes.map((route) => `route=${route.filePath}`) ?? [];
2703
+ logger?.info(`Worker handlers detected: ${[...detectedWorkerHandlers, ...detectedRouteHandlers].join(", ")}`);
2704
+ } else logger?.warn("No local worker handler entry was found for worker-only mode");
2705
+ }
2706
+ /**
2707
+ * Emit warnings about remote-only bindings (AI, Vectorize) and any missing
2708
+ * prerequisites (accountId, wrangler login).
2709
+ */
2710
+ function logRemoteBindingRequirements(logger, remoteCheck) {
2711
+ if (!remoteCheck.hasRemoteBindings) return;
2712
+ logger?.info("");
2713
+ logger?.warn("⚠️ Remote-only bindings detected:");
2714
+ for (const binding of remoteCheck.remoteBindings) logger?.warn(` • ${binding}`);
2715
+ logger?.info("");
2716
+ if (remoteCheck.missingAccountId) {
2717
+ logger?.warn("⚠️ WARN: accountId is not set in devflare.config.ts");
2718
+ logger?.warn(" Remote bindings (AI, Vectorize) require accountId to charge the correct account.");
2719
+ logger?.warn(" Add: accountId: 'your-cloudflare-account-id'");
2720
+ logger?.info("");
2721
+ }
2722
+ if (remoteCheck.notLoggedIn) {
2723
+ logger?.warn("⚠️ WARN: Not logged in to Wrangler");
2724
+ logger?.warn(" Remote bindings require authentication.");
2725
+ logger?.warn(" Run: bunx wrangler login");
2726
+ logger?.info("");
2727
+ }
2728
+ if (!remoteCheck.missingAccountId && !remoteCheck.notLoggedIn) {
2729
+ logger?.success("✓ Remote binding requirements met");
2730
+ logger?.info("");
2731
+ }
2732
+ }
2733
+ function formatErrorMessage(error) {
2734
+ return error instanceof Error ? error.message : String(error);
2735
+ }
2736
+ /**
2737
+ * Resolve the path to watch for config changes. Prefers an explicit
2738
+ * `configPath` if it's directly accessible on disk, otherwise falls back to
2739
+ * the auto-discovered config path under `cwd`. Returns `null` when neither
2740
+ * is available.
2741
+ */
2742
+ async function resolveWorkerConfigWatchPath(cwd, configPath) {
2743
+ if (configPath) {
2744
+ const explicitPath = resolve(cwd, configPath);
2745
+ const fs = await import("node:fs/promises");
2746
+ try {
2747
+ await fs.access(explicitPath);
2748
+ return explicitPath;
2749
+ } catch {}
2750
+ }
2751
+ return await resolveConfigPath(cwd) ?? null;
2752
+ }
2753
+ /**
2754
+ * Pretty-print the resolved Miniflare config (truncating long inline scripts)
2755
+ * before `Miniflare` is constructed. Only emits when verbose/debug logging is
2756
+ * requested by the caller.
2757
+ */
2758
+ function logMiniflareConfigDiagnostics(logger, mfConfig) {
2759
+ logger?.info("=== MINIFLARE CONFIG DEBUG ===");
2760
+ logger?.info("Full config:", JSON.stringify(mfConfig, (key, value) => {
2761
+ if (key === "script" && typeof value === "string" && value.length > 200) return value.substring(0, 200) + "...[truncated]";
2762
+ return value;
2763
+ }, 2));
2764
+ if (mfConfig.workers) {
2765
+ logger?.info("Workers order:");
2766
+ for (const w of mfConfig.workers) {
2767
+ logger?.info(` → ${w.name}:`);
2768
+ logger?.info(` script: ${w.script ? "inline" : w.scriptPath}`);
2769
+ logger?.info(` browserRendering: ${JSON.stringify(w.browserRendering)}`);
2770
+ logger?.info(` durableObjects: ${JSON.stringify(w.durableObjects)}`);
2771
+ }
2772
+ }
2773
+ }
2774
+ /**
2775
+ * After `Miniflare` is `ready`, query each declared worker's bindings and
2776
+ * log them. Best-effort: any per-worker failure is logged at debug and does
2777
+ * not abort the rest of the diagnostics.
2778
+ */
2779
+ async function logMiniflareBindingDiagnostics(logger, miniflare, mfConfig) {
2780
+ try {
2781
+ const gatewayBindings = await miniflare.getBindings("gateway");
2782
+ logger?.info("Gateway worker bindings:", Object.keys(gatewayBindings));
2783
+ if (mfConfig.workers) {
2784
+ for (const w of mfConfig.workers) if (w.name !== "gateway") try {
2785
+ const doBindings = await miniflare.getBindings(w.name);
2786
+ logger?.info(`${w.name} worker bindings:`, Object.keys(doBindings));
2787
+ if ("BROWSER" in doBindings) logger?.success(`${w.name} has BROWSER binding!`);
2788
+ else logger?.warn(`${w.name} is MISSING BROWSER binding`);
2789
+ } catch (error) {
2790
+ logger?.debug(`Skipping binding diagnostics for ${w.name}: ${formatErrorMessage(error)}`);
2791
+ }
2792
+ }
2793
+ } catch (error) {
2794
+ logger?.debug(`Skipping Miniflare binding diagnostics: ${formatErrorMessage(error)}`);
2795
+ }
2796
+ }
2797
+ /**
2798
+ * If the config declares a single browser-rendering binding, construct and
2799
+ * start a `BrowserShim` listening on `browserShimPort`. Returns `null` when
2800
+ * no browser binding is configured (no shim needed).
2801
+ */
2802
+ async function maybeStartBrowserShim(config, options) {
2803
+ const browserBinding = getSingleBrowserBindingName(config.bindings?.browser);
2804
+ if (!browserBinding) return null;
2805
+ options.logger?.info(`Starting Browser Rendering shim (binding: ${browserBinding})...`);
2806
+ const shim = createBrowserShim({
2807
+ port: options.browserShimPort,
2808
+ host: "127.0.0.1",
2809
+ logger: options.logger,
2810
+ verbose: options.verbose
2811
+ });
2812
+ await shim.start();
2813
+ return shim;
2814
+ }
2815
+ /**
2816
+ * If the config declares a `files.durableObjects` glob, construct a
2817
+ * `DOBundler`, run an initial build, and start watching. Returns
2818
+ * `{ bundler, result }` (`{ null, null }` when no DO pattern is configured).
2819
+ * The `onRebuild` callback fires on each subsequent rebuild — typically used
2820
+ * to schedule a Miniflare reload.
2821
+ */
2822
+ async function maybeStartDOBundler(config, options) {
2823
+ const doPattern = config.files?.durableObjects;
2824
+ if (typeof doPattern !== "string" || !doPattern) return {
2825
+ bundler: null,
2826
+ result: null
2827
+ };
2828
+ const outDir = resolve(options.cwd, ".devflare/do-bundles");
2829
+ const bundler = createDOBundler({
2830
+ cwd: options.cwd,
2831
+ pattern: doPattern,
2832
+ outDir,
2833
+ rolldownOptions: config.rolldown?.options,
2834
+ sourcemap: config.rolldown?.sourcemap,
2835
+ minify: config.rolldown?.minify,
2836
+ logger: options.logger,
2837
+ onRebuild: options.onRebuild
2838
+ });
2839
+ const result = await bundler.build();
2840
+ await bundler.watch();
2841
+ return {
2842
+ bundler,
2843
+ result
2844
+ };
2845
+ }
2846
+ /**
2847
+ * Resolve whether Vite should run for this package, and if so, write the
2848
+ * generated Vite config under `.devflare/`. Returns:
2849
+ * - `{ enableVite: false, generatedViteConfigPath: null }` when Vite is
2850
+ * not requested or no Vite config exists for this package
2851
+ * - `{ enableVite: true, generatedViteConfigPath }` after writing the
2852
+ * generated config
2853
+ */
2854
+ async function resolveViteIntegration(options) {
2855
+ if (!options.enableViteRequested) return {
2856
+ enableVite: false,
2857
+ generatedViteConfigPath: null
2858
+ };
2859
+ const viteMode = await resolveViteMode(options.cwd, { requested: true });
2860
+ if (!viteMode.enableVite) {
2861
+ options.logger?.info("Vite disabled: no vite config found for this package");
2862
+ return {
2863
+ enableVite: false,
2864
+ generatedViteConfigPath: null
2865
+ };
2866
+ }
2867
+ const generatedViteConfigPath = await writeGeneratedViteConfig({
2868
+ cwd: options.cwd,
2869
+ configPath: options.configPath,
2870
+ localConfigPath: viteMode.viteConfigPath,
2871
+ bridgePort: options.miniflarePort
2872
+ });
2873
+ options.logger?.debug(`Generated Vite config → ${generatedViteConfigPath}`);
2874
+ return {
2875
+ enableVite: true,
2876
+ generatedViteConfigPath
2877
+ };
2878
+ }
2879
+ //#endregion
2880
+ //#region src/dev-server/vite-process.ts
2881
+ /**
2882
+ * Start the Vite dev server process.
2883
+ */
2884
+ async function startViteProcess(options) {
2885
+ const { cwd, configPath, vitePort, miniflarePort, generatedViteConfigPath, r2Presign, logger } = options;
2886
+ const args = [
2887
+ "vite",
2888
+ "dev",
2889
+ "--port",
2890
+ String(vitePort)
2891
+ ];
2892
+ if (generatedViteConfigPath) args.push("--config", generatedViteConfigPath);
2893
+ const viteProcess = spawn("bunx", args, {
2894
+ cwd,
2895
+ stdio: [
2896
+ "inherit",
2897
+ "pipe",
2898
+ "pipe"
2899
+ ],
2900
+ windowsHide: true,
2901
+ env: {
2902
+ ...process.env,
2903
+ DEVFLARE_DEV: "true",
2904
+ DEVFLARE_BRIDGE_PORT: String(miniflarePort),
2905
+ ...configPath ? { DEVFLARE_CONFIG_PATH: configPath } : {},
2906
+ ...r2Presign ? {
2907
+ DEVFLARE_R2_PRESIGN_SECRET: r2Presign.secret,
2908
+ DEVFLARE_R2_PRESIGN_ORIGIN: r2Presign.origin
2909
+ } : {},
2910
+ FORCE_COLOR: "1"
2911
+ }
2912
+ });
2913
+ const readyUrl = await waitForViteReady(viteProcess, {
2914
+ onStdout(chunk) {
2915
+ process.stdout.write(chunk);
2916
+ },
2917
+ onStderr(chunk) {
2918
+ process.stderr.write(chunk);
2919
+ }
2920
+ });
2921
+ if (readyUrl) {
2922
+ logger?.success(`Vite dev server started on ${readyUrl}`);
2923
+ return viteProcess;
2924
+ }
2925
+ logger?.warn("Vite process started, but the final local URL could not be confirmed yet");
2926
+ return viteProcess;
2927
+ }
2928
+ //#endregion
2929
+ export { createMiniflareLog as _, logWorkerHandlerDetection as a, resolveViteIntegration as c, createRuntimeStdioForwarder as d, buildMiniflareDevConfig as f, runD1Migrations as g, resolveMainWorkerSurfacePaths as h, logRemoteBindingRequirements as i, resolveWorkerConfigWatchPath as l, collectWorkerWatchRoots as m, logMiniflareBindingDiagnostics as n, maybeStartBrowserShim as o, resolveR2PresignOrigin as p, logMiniflareConfigDiagnostics as r, maybeStartDOBundler as s, startViteProcess as t, createBrowserShim as u, createDOBundler as v };