devflare 1.0.0-next.7 → 1.0.0-next.71

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