devflare 1.0.0-next.4 → 1.0.0-next.41

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 (577) hide show
  1. package/LICENSE +21 -0
  2. package/LLM.md +15966 -2139
  3. package/README.md +393 -605
  4. package/bin/devflare.js +22 -14
  5. package/dist/_chunks/account-BtWcv3X9.js +462 -0
  6. package/dist/_chunks/account-resources-CsmVowJp.js +159 -0
  7. package/dist/_chunks/ai-CBEuLdoP.js +322 -0
  8. package/dist/_chunks/api-TzdliH-6.js +568 -0
  9. package/dist/_chunks/build-C0pZGUL7.js +24 -0
  10. package/dist/_chunks/build-artifacts-CGdnp2BT.js +483 -0
  11. package/dist/_chunks/cli-DPeh2U50.js +1128 -0
  12. package/dist/_chunks/cloudflare-BaloqI3H.js +157 -0
  13. package/dist/_chunks/colors-DiK6wPJ5.js +13 -0
  14. package/dist/_chunks/command-utils-BsfkmTqh.js +45 -0
  15. package/dist/_chunks/compiler-uoyAQ8zW.js +424 -0
  16. package/dist/_chunks/config-D5u-bEkn.js +78 -0
  17. package/dist/_chunks/config-path-DCPto4XB.js +42 -0
  18. package/dist/_chunks/context-CX50Y2Kb.js +2250 -0
  19. package/dist/_chunks/decorators-QmV57ixr.js +19 -0
  20. package/dist/_chunks/dependencies-C2oxFjU_.js +57 -0
  21. package/dist/_chunks/deploy-C2siy20T.js +915 -0
  22. package/dist/_chunks/dev-CvRHmRFZ.js +3456 -0
  23. package/dist/_chunks/doctor-C0g8X36G.js +219 -0
  24. package/dist/_chunks/durable-object-DMtH0XYX.js +234 -0
  25. package/dist/_chunks/env-S0_nMVz1.js +125 -0
  26. package/dist/_chunks/family-DXC5SdFK.js +143 -0
  27. package/dist/_chunks/generated-artifacts-dMJY4KAt.js +32 -0
  28. package/dist/_chunks/glob-CmQOvunB.js +56 -0
  29. package/dist/_chunks/init-DnkJOmpW.js +165 -0
  30. package/dist/_chunks/loader-DLXO60RO.js +1153 -0
  31. package/dist/_chunks/local-dev-vars-CTSa-wvF.js +81 -0
  32. package/dist/_chunks/local-hyperdrive-CJ90j46O.js +119 -0
  33. package/dist/_chunks/local-media-bindings-DsAW4AMF.js +335 -0
  34. package/dist/_chunks/local-secrets-DMmEOygL.js +123 -0
  35. package/dist/_chunks/local-workflow-entrypoints-Wip3o1u8.js +90 -0
  36. package/dist/_chunks/login-BmnDzDlH.js +51 -0
  37. package/dist/_chunks/package-metadata-DjHBLB-z.js +39 -0
  38. package/dist/_chunks/preferences-BKp_7XJx.js +241 -0
  39. package/dist/_chunks/preview-BhqDmq8I.js +456 -0
  40. package/dist/_chunks/preview-registry-ClAbLTHH.js +1186 -0
  41. package/dist/_chunks/preview-resources-DdXj8LMM.js +358 -0
  42. package/dist/_chunks/previews-Dlr13x5M.js +1134 -0
  43. package/dist/_chunks/productions-D3AnOHac.js +411 -0
  44. package/dist/_chunks/ref-CYJNAAe_.js +178 -0
  45. package/dist/_chunks/remote-CZnP-HLJ.js +79 -0
  46. package/dist/_chunks/remote-config-D39BY9ji.js +132 -0
  47. package/dist/_chunks/resolve-package-Y84HB3Nt.js +133 -0
  48. package/dist/_chunks/rolldown-runtime-CvQ6eiug.js +14 -0
  49. package/dist/_chunks/routes-C3FqES2W.js +796 -0
  50. package/dist/_chunks/runtime-FvXvfuZ2.js +550 -0
  51. package/dist/_chunks/schema-normalization-DWVleVxo.js +477 -0
  52. package/dist/_chunks/secrets-70eN0Q6C.js +95 -0
  53. package/dist/_chunks/token-kguIDmej.js +333 -0
  54. package/dist/_chunks/tokens-iVcgNXi-.js +221 -0
  55. package/dist/_chunks/types-OrLbzv4I.js +476 -0
  56. package/dist/_chunks/ui-BUoZApvE.js +103 -0
  57. package/dist/_chunks/usage-BBTGzjon.js +272 -0
  58. package/dist/_chunks/vite-CsI0E-uV.js +1235 -0
  59. package/dist/_chunks/vite-utils-OVLMV605.js +183 -0
  60. package/dist/_chunks/worker-CS1jgNFE.js +410 -0
  61. package/dist/_chunks/worker-bundler-dTTnYU3n.js +458 -0
  62. package/dist/_chunks/worker-entrypoint-CQW77lG8.js +247 -0
  63. package/dist/_chunks/workerName-CFJsLZA-.js +23 -0
  64. package/dist/bridge/client.d.ts +49 -7
  65. package/dist/bridge/client.d.ts.map +1 -1
  66. package/dist/bridge/gateway-runtime.d.ts +8 -0
  67. package/dist/bridge/gateway-runtime.d.ts.map +1 -0
  68. package/dist/bridge/index.d.ts +6 -6
  69. package/dist/bridge/index.d.ts.map +1 -1
  70. package/dist/bridge/log.d.ts +5 -0
  71. package/dist/bridge/log.d.ts.map +1 -0
  72. package/dist/bridge/miniflare.d.ts +75 -2
  73. package/dist/bridge/miniflare.d.ts.map +1 -1
  74. package/dist/bridge/proxy.d.ts +12 -11
  75. package/dist/bridge/proxy.d.ts.map +1 -1
  76. package/dist/bridge/server.d.ts +2 -1
  77. package/dist/bridge/server.d.ts.map +1 -1
  78. package/dist/bridge/v2/body-streams.d.ts +53 -0
  79. package/dist/bridge/v2/body-streams.d.ts.map +1 -0
  80. package/dist/bridge/v2/codec.d.ts +106 -0
  81. package/dist/bridge/v2/codec.d.ts.map +1 -0
  82. package/dist/bridge/v2/control-messages.d.ts +46 -0
  83. package/dist/bridge/v2/control-messages.d.ts.map +1 -0
  84. package/dist/bridge/v2/frames.d.ts +109 -0
  85. package/dist/bridge/v2/frames.d.ts.map +1 -0
  86. package/dist/bridge/v2/index.d.ts +11 -0
  87. package/dist/bridge/v2/index.d.ts.map +1 -0
  88. package/dist/bridge/v2/serialization.d.ts +48 -0
  89. package/dist/bridge/v2/serialization.d.ts.map +1 -0
  90. package/dist/bridge/v2/transport.d.ts +31 -0
  91. package/dist/bridge/v2/transport.d.ts.map +1 -0
  92. package/dist/bridge/v2/value-codec.d.ts +61 -0
  93. package/dist/bridge/v2/value-codec.d.ts.map +1 -0
  94. package/dist/bridge/{serialization.d.ts → v2/value-serialization.d.ts} +40 -18
  95. package/dist/bridge/v2/value-serialization.d.ts.map +1 -0
  96. package/dist/bridge/{protocol.d.ts → v2/wire.d.ts} +32 -8
  97. package/dist/bridge/v2/wire.d.ts.map +1 -0
  98. package/dist/bridge/v2/ws-relay.d.ts +53 -0
  99. package/dist/bridge/v2/ws-relay.d.ts.map +1 -0
  100. package/dist/browser-shim/handler.d.ts +2 -2
  101. package/dist/browser-shim/handler.d.ts.map +1 -1
  102. package/dist/browser-shim/index.d.ts +2 -2
  103. package/dist/browser-shim/server.d.ts +63 -0
  104. package/dist/browser-shim/server.d.ts.map +1 -1
  105. package/dist/browser.d.ts +4621 -0
  106. package/dist/browser.d.ts.map +1 -0
  107. package/dist/browser.js +80 -0
  108. package/dist/bundler/defaults.d.ts +23 -0
  109. package/dist/bundler/defaults.d.ts.map +1 -0
  110. package/dist/bundler/do-bundler.d.ts +7 -0
  111. package/dist/bundler/do-bundler.d.ts.map +1 -1
  112. package/dist/bundler/index.d.ts +3 -1
  113. package/dist/bundler/index.d.ts.map +1 -1
  114. package/dist/bundler/rolldown-shared.d.ts +53 -0
  115. package/dist/bundler/rolldown-shared.d.ts.map +1 -0
  116. package/dist/bundler/worker-bundler.d.ts +13 -0
  117. package/dist/bundler/worker-bundler.d.ts.map +1 -0
  118. package/dist/bundler/worker-compat.d.ts +4 -0
  119. package/dist/bundler/worker-compat.d.ts.map +1 -0
  120. package/dist/cli/build-manifest.d.ts +66 -0
  121. package/dist/cli/build-manifest.d.ts.map +1 -0
  122. package/dist/cli/command-utils.d.ts +18 -0
  123. package/dist/cli/command-utils.d.ts.map +1 -0
  124. package/dist/cli/commands/account.d.ts +2 -2
  125. package/dist/cli/commands/account.d.ts.map +1 -1
  126. package/dist/cli/commands/ai.d.ts +1 -1
  127. package/dist/cli/commands/ai.d.ts.map +1 -1
  128. package/dist/cli/commands/build-artifacts.d.ts +40 -0
  129. package/dist/cli/commands/build-artifacts.d.ts.map +1 -0
  130. package/dist/cli/commands/build.d.ts +2 -2
  131. package/dist/cli/commands/build.d.ts.map +1 -1
  132. package/dist/cli/commands/config.d.ts +4 -0
  133. package/dist/cli/commands/config.d.ts.map +1 -0
  134. package/dist/cli/commands/deploy/metadata.d.ts +18 -0
  135. package/dist/cli/commands/deploy/metadata.d.ts.map +1 -0
  136. package/dist/cli/commands/deploy/prepare.d.ts +23 -0
  137. package/dist/cli/commands/deploy/prepare.d.ts.map +1 -0
  138. package/dist/cli/commands/deploy/runtime.d.ts +4 -0
  139. package/dist/cli/commands/deploy/runtime.d.ts.map +1 -0
  140. package/dist/cli/commands/deploy/verification.d.ts +36 -0
  141. package/dist/cli/commands/deploy/verification.d.ts.map +1 -0
  142. package/dist/cli/commands/deploy.d.ts +2 -2
  143. package/dist/cli/commands/deploy.d.ts.map +1 -1
  144. package/dist/cli/commands/dev.d.ts +9 -1
  145. package/dist/cli/commands/dev.d.ts.map +1 -1
  146. package/dist/cli/commands/doctor.d.ts +2 -2
  147. package/dist/cli/commands/doctor.d.ts.map +1 -1
  148. package/dist/cli/commands/init.d.ts +2 -2
  149. package/dist/cli/commands/init.d.ts.map +1 -1
  150. package/dist/cli/commands/login.d.ts +4 -0
  151. package/dist/cli/commands/login.d.ts.map +1 -0
  152. package/dist/cli/commands/previews-support/cleanup.d.ts +9 -0
  153. package/dist/cli/commands/previews-support/cleanup.d.ts.map +1 -0
  154. package/dist/cli/commands/previews-support/family.d.ts +10 -0
  155. package/dist/cli/commands/previews-support/family.d.ts.map +1 -0
  156. package/dist/cli/commands/previews-support/render.d.ts +8 -0
  157. package/dist/cli/commands/previews-support/render.d.ts.map +1 -0
  158. package/dist/cli/commands/previews-support/theme.d.ts +10 -0
  159. package/dist/cli/commands/previews-support/theme.d.ts.map +1 -0
  160. package/dist/cli/commands/previews-support/types.d.ts +70 -0
  161. package/dist/cli/commands/previews-support/types.d.ts.map +1 -0
  162. package/dist/cli/commands/previews.d.ts +4 -0
  163. package/dist/cli/commands/previews.d.ts.map +1 -0
  164. package/dist/cli/commands/productions.d.ts +4 -0
  165. package/dist/cli/commands/productions.d.ts.map +1 -0
  166. package/dist/cli/commands/remote.d.ts +1 -1
  167. package/dist/cli/commands/remote.d.ts.map +1 -1
  168. package/dist/cli/commands/secrets.d.ts +4 -0
  169. package/dist/cli/commands/secrets.d.ts.map +1 -0
  170. package/dist/cli/commands/token.d.ts +4 -0
  171. package/dist/cli/commands/token.d.ts.map +1 -0
  172. package/dist/cli/commands/type-generation/discovery.d.ts +7 -0
  173. package/dist/cli/commands/type-generation/discovery.d.ts.map +1 -0
  174. package/dist/cli/commands/type-generation/generator.d.ts +112 -0
  175. package/dist/cli/commands/type-generation/generator.d.ts.map +1 -0
  176. package/dist/cli/commands/type-generation/models.d.ts +27 -0
  177. package/dist/cli/commands/type-generation/models.d.ts.map +1 -0
  178. package/dist/cli/commands/types.d.ts +2 -2
  179. package/dist/cli/commands/types.d.ts.map +1 -1
  180. package/dist/cli/commands/worker.d.ts +4 -0
  181. package/dist/cli/commands/worker.d.ts.map +1 -0
  182. package/dist/cli/config-path.d.ts +6 -0
  183. package/dist/cli/config-path.d.ts.map +1 -0
  184. package/dist/cli/dependencies.d.ts +2 -1
  185. package/dist/cli/dependencies.d.ts.map +1 -1
  186. package/dist/cli/deploy-strategy.d.ts +17 -0
  187. package/dist/cli/deploy-strategy.d.ts.map +1 -0
  188. package/dist/cli/deploy-target.d.ts +17 -0
  189. package/dist/cli/deploy-target.d.ts.map +1 -0
  190. package/dist/cli/generated-artifacts.d.ts +12 -0
  191. package/dist/cli/generated-artifacts.d.ts.map +1 -0
  192. package/dist/cli/help-pages/pages/account.d.ts +3 -0
  193. package/dist/cli/help-pages/pages/account.d.ts.map +1 -0
  194. package/dist/cli/help-pages/pages/core.d.ts +4 -0
  195. package/dist/cli/help-pages/pages/core.d.ts.map +1 -0
  196. package/dist/cli/help-pages/pages/index.d.ts +3 -0
  197. package/dist/cli/help-pages/pages/index.d.ts.map +1 -0
  198. package/dist/cli/help-pages/pages/misc.d.ts +3 -0
  199. package/dist/cli/help-pages/pages/misc.d.ts.map +1 -0
  200. package/dist/cli/help-pages/pages/previews.d.ts +3 -0
  201. package/dist/cli/help-pages/pages/previews.d.ts.map +1 -0
  202. package/dist/cli/help-pages/pages/productions.d.ts +3 -0
  203. package/dist/cli/help-pages/pages/productions.d.ts.map +1 -0
  204. package/dist/cli/help-pages/render.d.ts +12 -0
  205. package/dist/cli/help-pages/render.d.ts.map +1 -0
  206. package/dist/cli/help-pages/shared.d.ts +15 -0
  207. package/dist/cli/help-pages/shared.d.ts.map +1 -0
  208. package/dist/cli/help-pages/types.d.ts +23 -0
  209. package/dist/cli/help-pages/types.d.ts.map +1 -0
  210. package/dist/cli/help.d.ts +6 -0
  211. package/dist/cli/help.d.ts.map +1 -0
  212. package/dist/cli/index.d.ts +1 -0
  213. package/dist/cli/index.d.ts.map +1 -1
  214. package/dist/cli/index.js +2 -0
  215. package/dist/cli/package-metadata.d.ts +16 -0
  216. package/dist/cli/package-metadata.d.ts.map +1 -0
  217. package/dist/cli/preview-bindings.d.ts +49 -0
  218. package/dist/cli/preview-bindings.d.ts.map +1 -0
  219. package/dist/cli/preview.d.ts +10 -0
  220. package/dist/cli/preview.d.ts.map +1 -0
  221. package/dist/cli/ui.d.ts +37 -0
  222. package/dist/cli/ui.d.ts.map +1 -0
  223. package/dist/cli/workspace-build-guard.d.ts +14 -0
  224. package/dist/cli/workspace-build-guard.d.ts.map +1 -0
  225. package/dist/cli/wrangler-auth.d.ts +1 -1
  226. package/dist/cloudflare/account-core.d.ts +6 -0
  227. package/dist/cloudflare/account-core.d.ts.map +1 -0
  228. package/dist/cloudflare/account-resources.d.ts +40 -0
  229. package/dist/cloudflare/account-resources.d.ts.map +1 -0
  230. package/dist/cloudflare/account-status.d.ts +11 -0
  231. package/dist/cloudflare/account-status.d.ts.map +1 -0
  232. package/dist/cloudflare/account-workers.d.ts +14 -0
  233. package/dist/cloudflare/account-workers.d.ts.map +1 -0
  234. package/dist/cloudflare/account.d.ts +7 -64
  235. package/dist/cloudflare/account.d.ts.map +1 -1
  236. package/dist/cloudflare/api.d.ts +71 -3
  237. package/dist/cloudflare/api.d.ts.map +1 -1
  238. package/dist/cloudflare/auth.d.ts +1 -1
  239. package/dist/cloudflare/auth.d.ts.map +1 -1
  240. package/dist/cloudflare/index.d.ts +61 -6
  241. package/dist/cloudflare/index.d.ts.map +1 -1
  242. package/dist/cloudflare/index.js +4 -13
  243. package/dist/cloudflare/known-permission-group-ids.generated.d.ts +10 -0
  244. package/dist/cloudflare/known-permission-group-ids.generated.d.ts.map +1 -0
  245. package/dist/cloudflare/kv-namespace.d.ts +4 -0
  246. package/dist/cloudflare/kv-namespace.d.ts.map +1 -0
  247. package/dist/cloudflare/preferences.d.ts +7 -0
  248. package/dist/cloudflare/preferences.d.ts.map +1 -1
  249. package/dist/cloudflare/preview-registry-cache.d.ts +6 -0
  250. package/dist/cloudflare/preview-registry-cache.d.ts.map +1 -0
  251. package/dist/cloudflare/preview-registry-inference.d.ts +14 -0
  252. package/dist/cloudflare/preview-registry-inference.d.ts.map +1 -0
  253. package/dist/cloudflare/preview-registry-records.d.ts +4 -0
  254. package/dist/cloudflare/preview-registry-records.d.ts.map +1 -0
  255. package/dist/cloudflare/preview-registry-shape.d.ts +47 -0
  256. package/dist/cloudflare/preview-registry-shape.d.ts.map +1 -0
  257. package/dist/cloudflare/preview-registry-store.d.ts +14 -0
  258. package/dist/cloudflare/preview-registry-store.d.ts.map +1 -0
  259. package/dist/cloudflare/preview-registry-transport.d.ts +4 -0
  260. package/dist/cloudflare/preview-registry-transport.d.ts.map +1 -0
  261. package/dist/cloudflare/preview-registry-types.d.ts +103 -0
  262. package/dist/cloudflare/preview-registry-types.d.ts.map +1 -0
  263. package/dist/cloudflare/preview-registry.d.ts +42 -0
  264. package/dist/cloudflare/preview-registry.d.ts.map +1 -0
  265. package/dist/cloudflare/preview-urls.d.ts +3 -0
  266. package/dist/cloudflare/preview-urls.d.ts.map +1 -0
  267. package/dist/cloudflare/registry-schema.d.ts +253 -0
  268. package/dist/cloudflare/registry-schema.d.ts.map +1 -0
  269. package/dist/cloudflare/remote-config.d.ts.map +1 -1
  270. package/dist/cloudflare/tokens.d.ts +44 -0
  271. package/dist/cloudflare/tokens.d.ts.map +1 -0
  272. package/dist/cloudflare/types.d.ts +124 -7
  273. package/dist/cloudflare/types.d.ts.map +1 -1
  274. package/dist/cloudflare/usage.d.ts +31 -3
  275. package/dist/cloudflare/usage.d.ts.map +1 -1
  276. package/dist/config/binding-resolution-helpers.d.ts +41 -0
  277. package/dist/config/binding-resolution-helpers.d.ts.map +1 -0
  278. package/dist/config/compatibility.d.ts +3 -0
  279. package/dist/config/compatibility.d.ts.map +1 -0
  280. package/dist/config/compiler/bindings.d.ts +14 -0
  281. package/dist/config/compiler/bindings.d.ts.map +1 -0
  282. package/dist/config/compiler/core-helpers.d.ts +6 -0
  283. package/dist/config/compiler/core-helpers.d.ts.map +1 -0
  284. package/dist/config/compiler/do-workers.d.ts +34 -0
  285. package/dist/config/compiler/do-workers.d.ts.map +1 -0
  286. package/dist/config/compiler/paths.d.ts +18 -0
  287. package/dist/config/compiler/paths.d.ts.map +1 -0
  288. package/dist/config/compiler/types.d.ts +270 -0
  289. package/dist/config/compiler/types.d.ts.map +1 -0
  290. package/dist/config/compiler.d.ts +22 -134
  291. package/dist/config/compiler.d.ts.map +1 -1
  292. package/dist/config/define.d.ts +8 -5
  293. package/dist/config/define.d.ts.map +1 -1
  294. package/dist/config/deploy-resources.d.ts +58 -0
  295. package/dist/config/deploy-resources.d.ts.map +1 -0
  296. package/dist/config/env-vars.d.ts +309 -0
  297. package/dist/config/env-vars.d.ts.map +1 -0
  298. package/dist/config/framework-providers.d.ts +9 -0
  299. package/dist/config/framework-providers.d.ts.map +1 -0
  300. package/dist/config/index.d.ts +12 -5
  301. package/dist/config/index.d.ts.map +1 -1
  302. package/dist/config/loader.d.ts +2 -1
  303. package/dist/config/loader.d.ts.map +1 -1
  304. package/dist/config/local-dev-vars.d.ts +15 -0
  305. package/dist/config/local-dev-vars.d.ts.map +1 -0
  306. package/dist/config/preview-resources.d.ts +83 -0
  307. package/dist/config/preview-resources.d.ts.map +1 -0
  308. package/dist/config/preview.d.ts +29 -0
  309. package/dist/config/preview.d.ts.map +1 -0
  310. package/dist/config/ref.d.ts +16 -25
  311. package/dist/config/ref.d.ts.map +1 -1
  312. package/dist/config/resolve-phased.d.ts +72 -0
  313. package/dist/config/resolve-phased.d.ts.map +1 -0
  314. package/dist/config/resolve.d.ts +11 -0
  315. package/dist/config/resolve.d.ts.map +1 -0
  316. package/dist/config/resource-resolution.d.ts +80 -0
  317. package/dist/config/resource-resolution.d.ts.map +1 -0
  318. package/dist/config/schema-bindings.d.ts +1495 -0
  319. package/dist/config/schema-bindings.d.ts.map +1 -0
  320. package/dist/config/schema-build.d.ts +67 -0
  321. package/dist/config/schema-build.d.ts.map +1 -0
  322. package/dist/config/schema-env.d.ts +3023 -0
  323. package/dist/config/schema-env.d.ts.map +1 -0
  324. package/dist/config/schema-normalization.d.ts +184 -0
  325. package/dist/config/schema-normalization.d.ts.map +1 -0
  326. package/dist/config/schema-runtime.d.ts +500 -0
  327. package/dist/config/schema-runtime.d.ts.map +1 -0
  328. package/dist/config/schema-types-bindings-platform.d.ts +378 -0
  329. package/dist/config/schema-types-bindings-platform.d.ts.map +1 -0
  330. package/dist/config/schema-types-bindings-resources.d.ts +550 -0
  331. package/dist/config/schema-types-bindings-resources.d.ts.map +1 -0
  332. package/dist/config/schema-types-bindings.d.ts +254 -0
  333. package/dist/config/schema-types-bindings.d.ts.map +1 -0
  334. package/dist/config/schema-types-build.d.ts +86 -0
  335. package/dist/config/schema-types-build.d.ts.map +1 -0
  336. package/dist/config/schema-types-runtime.d.ts +917 -0
  337. package/dist/config/schema-types-runtime.d.ts.map +1 -0
  338. package/dist/config/schema-types.d.ts +424 -0
  339. package/dist/config/schema-types.d.ts.map +1 -0
  340. package/dist/config/schema.d.ts +4537 -2214
  341. package/dist/config/schema.d.ts.map +1 -1
  342. package/dist/config/service-bindings-validation.d.ts +36 -0
  343. package/dist/config/service-bindings-validation.d.ts.map +1 -0
  344. package/dist/config-entry.d.ts +7 -0
  345. package/dist/config-entry.d.ts.map +1 -0
  346. package/dist/config-entry.js +3 -0
  347. package/dist/decorators/durable-object.d.ts.map +1 -1
  348. package/dist/decorators/index.d.ts +2 -2
  349. package/dist/decorators/index.js +2 -9
  350. package/dist/dev-server/d1-migrations.d.ts +36 -0
  351. package/dist/dev-server/d1-migrations.d.ts.map +1 -0
  352. package/dist/dev-server/dev-server-state.d.ts +59 -0
  353. package/dist/dev-server/dev-server-state.d.ts.map +1 -0
  354. package/dist/dev-server/gateway-script.d.ts +21 -0
  355. package/dist/dev-server/gateway-script.d.ts.map +1 -0
  356. package/dist/dev-server/index.d.ts +1 -1
  357. package/dist/dev-server/miniflare-bindings.d.ts +59 -0
  358. package/dist/dev-server/miniflare-bindings.d.ts.map +1 -0
  359. package/dist/dev-server/miniflare-dev-config.d.ts +36 -0
  360. package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -0
  361. package/dist/dev-server/miniflare-log.d.ts +20 -0
  362. package/dist/dev-server/miniflare-log.d.ts.map +1 -0
  363. package/dist/dev-server/miniflare-worker-config.d.ts +74 -0
  364. package/dist/dev-server/miniflare-worker-config.d.ts.map +1 -0
  365. package/dist/dev-server/reload-queue.d.ts +13 -0
  366. package/dist/dev-server/reload-queue.d.ts.map +1 -0
  367. package/dist/dev-server/runtime-stdio.d.ts +8 -0
  368. package/dist/dev-server/runtime-stdio.d.ts.map +1 -0
  369. package/dist/dev-server/server-startup-helpers.d.ts +84 -0
  370. package/dist/dev-server/server-startup-helpers.d.ts.map +1 -0
  371. package/dist/dev-server/server.d.ts +2 -0
  372. package/dist/dev-server/server.d.ts.map +1 -1
  373. package/dist/dev-server/vite-process.d.ts +15 -0
  374. package/dist/dev-server/vite-process.d.ts.map +1 -0
  375. package/dist/dev-server/vite-utils.d.ts +19 -1
  376. package/dist/dev-server/vite-utils.d.ts.map +1 -1
  377. package/dist/dev-server/worker-source-watcher.d.ts +17 -0
  378. package/dist/dev-server/worker-source-watcher.d.ts.map +1 -0
  379. package/dist/dev-server/worker-surface-paths.d.ts +6 -0
  380. package/dist/dev-server/worker-surface-paths.d.ts.map +1 -0
  381. package/dist/env.d.ts +10 -0
  382. package/dist/env.d.ts.map +1 -1
  383. package/dist/index.d.ts +8 -12
  384. package/dist/index.d.ts.map +1 -1
  385. package/dist/index.js +9 -298
  386. package/dist/runtime/context-events.d.ts +13 -0
  387. package/dist/runtime/context-events.d.ts.map +1 -0
  388. package/dist/runtime/context-types.d.ts +82 -0
  389. package/dist/runtime/context-types.d.ts.map +1 -0
  390. package/dist/runtime/context.d.ts +20 -44
  391. package/dist/runtime/context.d.ts.map +1 -1
  392. package/dist/runtime/exports.d.ts +70 -55
  393. package/dist/runtime/exports.d.ts.map +1 -1
  394. package/dist/runtime/index.d.ts +10 -3
  395. package/dist/runtime/index.d.ts.map +1 -1
  396. package/dist/runtime/index.js +5 -111
  397. package/dist/runtime/middleware.d.ts +102 -60
  398. package/dist/runtime/middleware.d.ts.map +1 -1
  399. package/dist/runtime/router/index.d.ts +7 -0
  400. package/dist/runtime/router/index.d.ts.map +1 -0
  401. package/dist/runtime/router/types.d.ts +24 -0
  402. package/dist/runtime/router/types.d.ts.map +1 -0
  403. package/dist/runtime/validation.d.ts +18 -4
  404. package/dist/runtime/validation.d.ts.map +1 -1
  405. package/dist/secrets/local-secrets.d.ts +46 -0
  406. package/dist/secrets/local-secrets.d.ts.map +1 -0
  407. package/dist/shims/local-hyperdrive.d.ts +12 -0
  408. package/dist/shims/local-hyperdrive.d.ts.map +1 -0
  409. package/dist/shims/local-media-bindings.d.ts +19 -0
  410. package/dist/shims/local-media-bindings.d.ts.map +1 -0
  411. package/dist/shims/local-worker-loader.d.ts +3 -0
  412. package/dist/shims/local-worker-loader.d.ts.map +1 -0
  413. package/dist/sveltekit/index.d.ts +1 -1
  414. package/dist/sveltekit/index.d.ts.map +1 -1
  415. package/dist/sveltekit/index.js +313 -167
  416. package/dist/sveltekit/local-bindings.d.ts +4 -0
  417. package/dist/sveltekit/local-bindings.d.ts.map +1 -0
  418. package/dist/sveltekit/platform.d.ts +20 -2
  419. package/dist/sveltekit/platform.d.ts.map +1 -1
  420. package/dist/test/ai-search.d.ts +39 -0
  421. package/dist/test/ai-search.d.ts.map +1 -0
  422. package/dist/test/binding-hints.d.ts +10 -0
  423. package/dist/test/binding-hints.d.ts.map +1 -0
  424. package/dist/test/cf.d.ts +49 -35
  425. package/dist/test/cf.d.ts.map +1 -1
  426. package/dist/test/containers.d.ts +87 -0
  427. package/dist/test/containers.d.ts.map +1 -0
  428. package/dist/test/email.d.ts +16 -7
  429. package/dist/test/email.d.ts.map +1 -1
  430. package/dist/test/index.d.ts +18 -21
  431. package/dist/test/index.d.ts.map +1 -1
  432. package/dist/test/index.js +4495 -70
  433. package/dist/test/offline-bindings.d.ts +81 -0
  434. package/dist/test/offline-bindings.d.ts.map +1 -0
  435. package/dist/test/queue.d.ts.map +1 -1
  436. package/dist/test/remote-ai.d.ts.map +1 -1
  437. package/dist/test/remote-cloudflare.d.ts +13 -0
  438. package/dist/test/remote-cloudflare.d.ts.map +1 -0
  439. package/dist/test/remote-vectorize.d.ts.map +1 -1
  440. package/dist/test/resolve-service-bindings.d.ts +64 -4
  441. package/dist/test/resolve-service-bindings.d.ts.map +1 -1
  442. package/dist/test/scheduled.d.ts.map +1 -1
  443. package/dist/test/should-skip.d.ts +14 -18
  444. package/dist/test/should-skip.d.ts.map +1 -1
  445. package/dist/test/simple-context-bindings.d.ts +13 -0
  446. package/dist/test/simple-context-bindings.d.ts.map +1 -0
  447. package/dist/test/simple-context-durable-objects.d.ts +6 -0
  448. package/dist/test/simple-context-durable-objects.d.ts.map +1 -0
  449. package/dist/test/simple-context-env.d.ts +35 -0
  450. package/dist/test/simple-context-env.d.ts.map +1 -0
  451. package/dist/test/simple-context-gateway-script.d.ts +2 -0
  452. package/dist/test/simple-context-gateway-script.d.ts.map +1 -0
  453. package/dist/test/simple-context-handlers.d.ts +12 -0
  454. package/dist/test/simple-context-handlers.d.ts.map +1 -0
  455. package/dist/test/simple-context-lifecycle.d.ts +32 -0
  456. package/dist/test/simple-context-lifecycle.d.ts.map +1 -0
  457. package/dist/test/simple-context-mfconfig.d.ts +10 -0
  458. package/dist/test/simple-context-mfconfig.d.ts.map +1 -0
  459. package/dist/test/simple-context-multi-worker.d.ts +16 -0
  460. package/dist/test/simple-context-multi-worker.d.ts.map +1 -0
  461. package/dist/test/simple-context-paths.d.ts +40 -0
  462. package/dist/test/simple-context-paths.d.ts.map +1 -0
  463. package/dist/test/simple-context-runtime.d.ts +19 -0
  464. package/dist/test/simple-context-runtime.d.ts.map +1 -0
  465. package/dist/test/simple-context-startup.d.ts +11 -0
  466. package/dist/test/simple-context-startup.d.ts.map +1 -0
  467. package/dist/test/simple-context-transport.d.ts +14 -0
  468. package/dist/test/simple-context-transport.d.ts.map +1 -0
  469. package/dist/test/simple-context.d.ts +8 -27
  470. package/dist/test/simple-context.d.ts.map +1 -1
  471. package/dist/test/tail.d.ts +3 -2
  472. package/dist/test/tail.d.ts.map +1 -1
  473. package/dist/test/utilities/artifacts.d.ts +11 -0
  474. package/dist/test/utilities/artifacts.d.ts.map +1 -0
  475. package/dist/test/utilities/context.d.ts +39 -0
  476. package/dist/test/utilities/context.d.ts.map +1 -0
  477. package/dist/test/utilities/d1.d.ts +21 -0
  478. package/dist/test/utilities/d1.d.ts.map +1 -0
  479. package/dist/test/utilities/env.d.ts +40 -0
  480. package/dist/test/utilities/env.d.ts.map +1 -0
  481. package/dist/test/utilities/kv.d.ts +11 -0
  482. package/dist/test/utilities/kv.d.ts.map +1 -0
  483. package/dist/test/utilities/media.d.ts +16 -0
  484. package/dist/test/utilities/media.d.ts.map +1 -0
  485. package/dist/test/utilities/platform.d.ts +41 -0
  486. package/dist/test/utilities/platform.d.ts.map +1 -0
  487. package/dist/test/utilities/queue.d.ts +5 -0
  488. package/dist/test/utilities/queue.d.ts.map +1 -0
  489. package/dist/test/utilities/r2.d.ts +2 -0
  490. package/dist/test/utilities/r2.d.ts.map +1 -0
  491. package/dist/test/utilities/workflows.d.ts +26 -0
  492. package/dist/test/utilities/workflows.d.ts.map +1 -0
  493. package/dist/test/utilities.d.ts +10 -98
  494. package/dist/test/utilities.d.ts.map +1 -1
  495. package/dist/test/worker.d.ts +6 -0
  496. package/dist/test/worker.d.ts.map +1 -1
  497. package/dist/transform/durable-object.d.ts.map +1 -1
  498. package/dist/transform/index.d.ts +2 -2
  499. package/dist/transform/worker-entrypoint.d.ts +5 -0
  500. package/dist/transform/worker-entrypoint.d.ts.map +1 -1
  501. package/dist/utils/entrypoint-discovery.d.ts +6 -3
  502. package/dist/utils/entrypoint-discovery.d.ts.map +1 -1
  503. package/dist/utils/glob.d.ts.map +1 -1
  504. package/dist/utils/resolve-package.d.ts.map +1 -1
  505. package/dist/utils/send-email.d.ts +15 -0
  506. package/dist/utils/send-email.d.ts.map +1 -0
  507. package/dist/utils/send-email.js +163 -0
  508. package/dist/vite/config-file.d.ts +25 -0
  509. package/dist/vite/config-file.d.ts.map +1 -0
  510. package/dist/vite/index.d.ts +3 -2
  511. package/dist/vite/index.d.ts.map +1 -1
  512. package/dist/vite/index.js +2 -339
  513. package/dist/vite/plugin-config-hook.d.ts +28 -0
  514. package/dist/vite/plugin-config-hook.d.ts.map +1 -0
  515. package/dist/vite/plugin-context.d.ts +27 -0
  516. package/dist/vite/plugin-context.d.ts.map +1 -0
  517. package/dist/vite/plugin-durable-objects.d.ts +18 -0
  518. package/dist/vite/plugin-durable-objects.d.ts.map +1 -0
  519. package/dist/vite/plugin-programmatic.d.ts +44 -0
  520. package/dist/vite/plugin-programmatic.d.ts.map +1 -0
  521. package/dist/vite/plugin-service-bindings.d.ts +13 -0
  522. package/dist/vite/plugin-service-bindings.d.ts.map +1 -0
  523. package/dist/vite/plugin-transform.d.ts +41 -0
  524. package/dist/vite/plugin-transform.d.ts.map +1 -0
  525. package/dist/vite/plugin.d.ts +10 -41
  526. package/dist/vite/plugin.d.ts.map +1 -1
  527. package/dist/worker-entry/composed-worker.d.ts +7 -0
  528. package/dist/worker-entry/composed-worker.d.ts.map +1 -0
  529. package/dist/worker-entry/durable-object-discovery.d.ts +20 -0
  530. package/dist/worker-entry/durable-object-discovery.d.ts.map +1 -0
  531. package/dist/worker-entry/extensions.d.ts +5 -0
  532. package/dist/worker-entry/extensions.d.ts.map +1 -0
  533. package/dist/worker-entry/routes.d.ts +22 -0
  534. package/dist/worker-entry/routes.d.ts.map +1 -0
  535. package/dist/worker-entry/surface-paths.d.ts +26 -0
  536. package/dist/worker-entry/surface-paths.d.ts.map +1 -0
  537. package/dist/workflows/local-workflow-entrypoints.d.ts +7 -0
  538. package/dist/workflows/local-workflow-entrypoints.d.ts.map +1 -0
  539. package/package.json +159 -116
  540. package/R2.md +0 -170
  541. package/dist/account-rvrj687w.js +0 -397
  542. package/dist/ai-dx4fr9jh.js +0 -107
  543. package/dist/bridge/protocol.d.ts.map +0 -1
  544. package/dist/bridge/serialization.d.ts.map +0 -1
  545. package/dist/browser-shim/worker.d.ts +0 -14
  546. package/dist/browser-shim/worker.d.ts.map +0 -1
  547. package/dist/build-mnf6v8gd.js +0 -53
  548. package/dist/deploy-nhceck39.js +0 -70
  549. package/dist/dev-pa8dhm20.js +0 -2162
  550. package/dist/doctor-fmgb3d28.js +0 -190
  551. package/dist/durable-object-t4kbb0yt.js +0 -13
  552. package/dist/index-07q6yxyc.js +0 -168
  553. package/dist/index-18hvb6gb.js +0 -194
  554. package/dist/index-1xpj0m4r.js +0 -57
  555. package/dist/index-37x76zdn.js +0 -4
  556. package/dist/index-3t6rypgc.js +0 -13
  557. package/dist/index-67qcae0f.js +0 -183
  558. package/dist/index-a855bdsx.js +0 -18
  559. package/dist/index-d8bdkx2h.js +0 -109
  560. package/dist/index-ep3445yc.js +0 -2225
  561. package/dist/index-gz1gndna.js +0 -307
  562. package/dist/index-hcex3rgh.js +0 -266
  563. package/dist/index-m2q41jwa.js +0 -462
  564. package/dist/index-n7rs26ft.js +0 -77
  565. package/dist/index-pf5s73n9.js +0 -1413
  566. package/dist/index-rbht7m9r.js +0 -36
  567. package/dist/index-tfyxa77h.js +0 -850
  568. package/dist/index-tk6ej9dj.js +0 -94
  569. package/dist/index-z14anrqp.js +0 -226
  570. package/dist/init-f9mgmew3.js +0 -186
  571. package/dist/remote-q59qk463.js +0 -97
  572. package/dist/test/bridge-context.d.ts +0 -76
  573. package/dist/test/bridge-context.d.ts.map +0 -1
  574. package/dist/test/multi-worker-context.d.ts +0 -114
  575. package/dist/test/multi-worker-context.d.ts.map +0 -1
  576. package/dist/types-5nyrz1sz.js +0 -454
  577. package/dist/worker-entrypoint-m9th0rg0.js +0 -13
@@ -1,71 +1,4496 @@
1
- import {
2
- cf,
3
- clearBundleCache,
4
- createBridgeTestContext,
5
- createEntrypointScript,
6
- createMockD1,
7
- createMockEnv,
8
- createMockKV,
9
- createMockQueue,
10
- createMockR2,
11
- createMockTestContext,
12
- createMultiWorkerContext,
13
- createTestContext,
14
- email,
15
- env,
16
- getBridgeTestContext,
17
- hasCrossWorkerDOs,
18
- hasServiceBindings,
19
- isRemoteModeEnabled,
20
- queue,
21
- resolveDOBindings,
22
- resolveServiceBindings,
23
- scheduled,
24
- shouldSkip,
25
- stopBridgeTestContext,
26
- tail,
27
- testEnv,
28
- withTestContext,
29
- worker
30
- } from "../index-ep3445yc.js";
31
- import"../index-z14anrqp.js";
32
- import"../index-tk6ej9dj.js";
33
- import"../index-d8bdkx2h.js";
34
- import"../index-rbht7m9r.js";
35
- import"../index-gz1gndna.js";
36
- import"../index-pf5s73n9.js";
37
- import"../index-07q6yxyc.js";
38
- import"../index-67qcae0f.js";
39
- import"../index-hcex3rgh.js";
40
- import"../index-tfyxa77h.js";
41
- import"../index-37x76zdn.js";
42
- export {
43
- worker,
44
- withTestContext,
45
- testEnv,
46
- tail,
47
- stopBridgeTestContext,
48
- shouldSkip,
49
- scheduled,
50
- resolveServiceBindings,
51
- resolveDOBindings,
52
- queue,
53
- isRemoteModeEnabled,
54
- hasServiceBindings,
55
- hasCrossWorkerDOs,
56
- getBridgeTestContext,
57
- env,
58
- email,
59
- createTestContext,
60
- createMultiWorkerContext,
61
- createMockTestContext,
62
- createMockR2,
63
- createMockQueue,
64
- createMockKV,
65
- createMockEnv,
66
- createMockD1,
67
- createEntrypointScript,
68
- createBridgeTestContext,
69
- clearBundleCache,
70
- cf
1
+ import { d as resolveConfigEnvVars } from "../_chunks/preview-BhqDmq8I.js";
2
+ import { i as resolveConfigPath, r as loadConfig } from "../_chunks/loader-DLXO60RO.js";
3
+ import { c as normalizeHyperdriveBinding, d as normalizeMediaBinding, f as normalizeMtlsCertificateBinding, h as normalizeWorkflowBinding, i as normalizeArtifactsBinding, l as normalizeImagesBinding, m as normalizeSecretsStoreBinding, o as normalizeDOBinding, p as normalizePipelineBinding, s as normalizeDispatchNamespaceBinding, t as getLocalD1DatabaseIdentifier } from "../_chunks/schema-normalization-DWVleVxo.js";
4
+ import { t as applyLocalDevVarsToConfig } from "../_chunks/local-dev-vars-CTSa-wvF.js";
5
+ import { g as isAuthenticated, p as getApiToken } from "../_chunks/api-TzdliH-6.js";
6
+ import { d as getPrimaryAccount, n as getEffectiveAccountId } from "../_chunks/preferences-BKp_7XJx.js";
7
+ import { A as createTailEvent, D as createFetchEvent, E as createEmailEvent, F as BridgeClient, M as createEnvProxy, O as createQueueEvent, P as setBindingHints, _ as runWithContext, k as createScheduledEvent, v as runWithEventContext } from "../_chunks/context-CX50Y2Kb.js";
8
+ import { createLocalSendEmailBinding, wrapEnvSendEmailBindings } from "../utils/send-email.js";
9
+ import { n as __setTestContext, r as env, t as __clearTestContext } from "../_chunks/env-S0_nMVz1.js";
10
+ import { d as invokeFetchModule, m as resolveFetchHandler, r as matchFetchRoute, t as createRouteResolve } from "../_chunks/runtime-FvXvfuZ2.js";
11
+ import { i as extractBindingHints, n as createLocalWorkerLoaderBinding, r as disposeLocalWorkerLoaderBindings, t as createLocalHyperdrive } from "../_chunks/local-hyperdrive-CJ90j46O.js";
12
+ import { a as hasServiceBindings, f as buildHyperdrivesConfig, i as hasCrossWorkerDOs, o as resolveDOBindings, r as clearBundleCache, s as resolveServiceBindings, t as discoverRoutes } from "../_chunks/routes-C3FqES2W.js";
13
+ import { i as findFiles, t as DEFAULT_DO_PATTERN } from "../_chunks/glob-CmQOvunB.js";
14
+ import { a as isRemoteModeActive, i as getRemoteModeStatus } from "../_chunks/remote-config-D39BY9ji.js";
15
+ import { t as bundleWorkflowEntrypointScript } from "../_chunks/local-workflow-entrypoints-Wip3o1u8.js";
16
+ import { a as resolveLocalSecretValuesForBindings, n as buildLocalSecretWrappedBindingConfig } from "../_chunks/local-secrets-DMmEOygL.js";
17
+ import { t as buildLocalBindingShimServiceConfig } from "../_chunks/local-media-bindings-DsAW4AMF.js";
18
+ import { t as canProceedWithTest } from "../_chunks/usage-BBTGzjon.js";
19
+ import { existsSync } from "node:fs";
20
+ import { stat } from "node:fs/promises";
21
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
22
+ import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1 } from "fs";
23
+ import { dirname as dirname$1, join as join$1, resolve as resolve$1 } from "path";
24
+ import { readFile as readFile$1 } from "fs/promises";
25
+ import { createServer } from "net";
26
+ import { fileURLToPath } from "url";
27
+ import { createHash, randomUUID } from "node:crypto";
28
+ import { createConnection } from "node:net";
29
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
30
+ import { execa } from "execa";
31
+ //#region src/test/remote-cloudflare.ts
32
+ function createRemoteCloudflareClient(accountId) {
33
+ let resolvedAccountId = null;
34
+ async function getAccountId() {
35
+ if (accountId) return accountId;
36
+ if (resolvedAccountId) return resolvedAccountId;
37
+ const primary = await getPrimaryAccount();
38
+ if (!primary) throw new Error("No Cloudflare account found. Run: bunx wrangler login");
39
+ const { accountId: effectiveId } = await getEffectiveAccountId(primary.id);
40
+ resolvedAccountId = effectiveId;
41
+ return effectiveId;
42
+ }
43
+ async function getToken() {
44
+ const token = await getApiToken();
45
+ if (!token) throw new Error("Not authenticated. Run: bunx wrangler login");
46
+ return token;
47
+ }
48
+ async function jsonRequest(options) {
49
+ const [acctId, token] = await Promise.all([getAccountId(), getToken()]);
50
+ const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${acctId}${options.path}`, {
51
+ method: options.method,
52
+ headers: {
53
+ Authorization: `Bearer ${token}`,
54
+ "Content-Type": options.contentType ?? "application/json"
55
+ },
56
+ body: options.body
57
+ });
58
+ if (!response.ok) {
59
+ const errorText = await response.text();
60
+ throw new Error(`${options.serviceLabel} API error (${response.status}): ${errorText}`);
61
+ }
62
+ const result = await response.json();
63
+ if (!result.success) {
64
+ const message = result.errors?.[0]?.message || `Unknown ${options.serviceLabel} error`;
65
+ throw new Error(`${options.serviceLabel} API error: ${message}`);
66
+ }
67
+ return result.result;
68
+ }
69
+ return {
70
+ getAccountId,
71
+ getToken,
72
+ jsonRequest
73
+ };
74
+ }
75
+ //#endregion
76
+ //#region src/test/remote-ai.ts
77
+ function encodePathSegment(value) {
78
+ return encodeURIComponent(value);
79
+ }
80
+ function applyExtraHeaders(headers, extraHeaders) {
81
+ if (!extraHeaders) return;
82
+ for (const [key, value] of Object.entries(extraHeaders)) headers.set(key, String(value));
83
+ }
84
+ function createRemoteAIGateway(cloudflare, gatewayId, owner) {
85
+ const encodedGatewayId = encodePathSegment(gatewayId);
86
+ const gateway = {
87
+ async patchLog(logId, data) {
88
+ await cloudflare.jsonRequest({
89
+ method: "PATCH",
90
+ path: `/ai-gateway/gateways/${encodedGatewayId}/logs/${encodePathSegment(logId)}`,
91
+ serviceLabel: "AI Gateway",
92
+ body: JSON.stringify(data)
93
+ });
94
+ },
95
+ async getLog(logId) {
96
+ return cloudflare.jsonRequest({
97
+ method: "GET",
98
+ path: `/ai-gateway/gateways/${encodedGatewayId}/logs/${encodePathSegment(logId)}`,
99
+ serviceLabel: "AI Gateway"
100
+ });
101
+ },
102
+ async getUrl(provider) {
103
+ const baseUrl = `https://gateway.ai.cloudflare.com/v1/${encodePathSegment(await cloudflare.getAccountId())}/${encodedGatewayId}`;
104
+ return provider ? `${baseUrl}/${encodePathSegment(provider)}` : `${baseUrl}/`;
105
+ },
106
+ async run(data, options) {
107
+ const [url, token] = await Promise.all([gateway.getUrl(), cloudflare.getToken()]);
108
+ const headers = new Headers({
109
+ Authorization: `Bearer ${token}`,
110
+ "Content-Type": "application/json"
111
+ });
112
+ applyExtraHeaders(headers, options?.extraHeaders);
113
+ const response = await fetch(url, {
114
+ method: "POST",
115
+ headers,
116
+ body: JSON.stringify(data),
117
+ signal: options?.signal
118
+ });
119
+ const logId = response.headers.get("cf-aig-log-id") ?? response.headers.get("cf-ai-gateway-log-id");
120
+ if (logId) owner.aiGatewayLogId = logId;
121
+ if (!response.ok) {
122
+ const errorText = await response.text();
123
+ throw new Error(`AI Gateway API error (${response.status}): ${errorText}`);
124
+ }
125
+ return response;
126
+ }
127
+ };
128
+ return gateway;
129
+ }
130
+ /**
131
+ * Creates a remote AI binding that calls Cloudflare's REST API.
132
+ * Matches the Workers AI binding interface.
133
+ */
134
+ function createRemoteAI(accountId) {
135
+ const cloudflare = createRemoteCloudflareClient(accountId);
136
+ const ai = {
137
+ aiGatewayLogId: null,
138
+ async run(model, inputs) {
139
+ return cloudflare.jsonRequest({
140
+ method: "POST",
141
+ path: `/ai/run/${model}`,
142
+ serviceLabel: "AI",
143
+ body: JSON.stringify(inputs)
144
+ });
145
+ },
146
+ gateway(gatewayId) {
147
+ return createRemoteAIGateway(cloudflare, gatewayId, ai);
148
+ }
149
+ };
150
+ return ai;
151
+ }
152
+ //#endregion
153
+ //#region src/test/remote-vectorize.ts
154
+ /**
155
+ * Creates a remote Vectorize binding that calls Cloudflare's REST API.
156
+ * Matches the Workers Vectorize binding interface.
157
+ */
158
+ function createRemoteVectorize(indexName, accountId) {
159
+ const cloudflare = createRemoteCloudflareClient(accountId);
160
+ async function apiRequest(method, endpoint, body) {
161
+ return cloudflare.jsonRequest({
162
+ method,
163
+ path: `/vectorize/v2/indexes/${indexName}${endpoint}`,
164
+ serviceLabel: "Vectorize",
165
+ body: body ? JSON.stringify(body) : void 0
166
+ });
167
+ }
168
+ async function ndjsonRequest(endpoint, vectors) {
169
+ const ndjson = vectors.map((v) => JSON.stringify(v)).join("\n");
170
+ return cloudflare.jsonRequest({
171
+ method: "POST",
172
+ path: `/vectorize/v2/indexes/${indexName}${endpoint}`,
173
+ serviceLabel: "Vectorize",
174
+ contentType: "application/x-ndjson",
175
+ body: ndjson
176
+ });
177
+ }
178
+ return {
179
+ async describe() {
180
+ return apiRequest("GET", "");
181
+ },
182
+ async query(vector, options) {
183
+ return apiRequest("POST", "/query", {
184
+ vector: Array.isArray(vector) ? vector : Array.from(vector),
185
+ topK: options?.topK ?? 10,
186
+ returnValues: options?.returnValues ?? false,
187
+ returnMetadata: options?.returnMetadata ?? "none",
188
+ namespace: options?.namespace,
189
+ filter: options?.filter
190
+ });
191
+ },
192
+ async insert(vectors) {
193
+ const result = await ndjsonRequest("/insert", vectors);
194
+ return {
195
+ count: result.count,
196
+ ids: result.ids || vectors.map((v) => v.id)
197
+ };
198
+ },
199
+ async upsert(vectors) {
200
+ const result = await ndjsonRequest("/upsert", vectors);
201
+ return {
202
+ count: result.count,
203
+ ids: result.ids || vectors.map((v) => v.id)
204
+ };
205
+ },
206
+ async deleteByIds(ids) {
207
+ return {
208
+ count: (await apiRequest("POST", "/delete-by-ids", { ids })).count,
209
+ ids
210
+ };
211
+ },
212
+ async getByIds(ids) {
213
+ return apiRequest("POST", "/get-by-ids", { ids });
214
+ }
215
+ };
216
+ }
217
+ //#endregion
218
+ //#region src/test/utilities/context.ts
219
+ /**
220
+ * Creates a test context with mock ExecutionContext
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * const ctx = createTestContext({
225
+ * env: { API_KEY: 'test' },
226
+ * request: new Request('https://test.com')
227
+ * })
228
+ * ```
229
+ */
230
+ function createMockTestContext(options = {}) {
231
+ const waitUntilPromises = [];
232
+ return {
233
+ env: options.env ?? {},
234
+ ctx: {
235
+ waitUntil(promise) {
236
+ waitUntilPromises.push(promise);
237
+ },
238
+ passThroughOnException() {},
239
+ props: {}
240
+ },
241
+ request: options.request ?? null,
242
+ waitUntilPromises
243
+ };
244
+ }
245
+ /**
246
+ * Runs a function within a test context
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * const response = await withTestContext(
251
+ * { env: { DB: mockD1 } },
252
+ * async () => {
253
+ * // env, ctx, locals all work here
254
+ * return handler.fetch(new Request('https://test.com'))
255
+ * }
256
+ * )
257
+ * ```
258
+ */
259
+ async function withTestContext(options, handler) {
260
+ const testCtx = createMockTestContext(options);
261
+ return runWithContext(testCtx.env, testCtx.ctx, options.request ?? null, handler, options.type ?? "fetch");
262
+ }
263
+ //#endregion
264
+ //#region src/test/utilities/kv.ts
265
+ /**
266
+ * Creates a mock KVNamespace for testing
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * const kv = createMockKV({ 'key': 'value' })
271
+ * await kv.get('key') // 'value'
272
+ * ```
273
+ */
274
+ function createMockKV(initialData = {}) {
275
+ const store = /* @__PURE__ */ new Map();
276
+ const metadata = /* @__PURE__ */ new Map();
277
+ const encoder = new TextEncoder();
278
+ const decoder = new TextDecoder();
279
+ for (const [key, value] of Object.entries(initialData)) store.set(key, encoder.encode(value));
280
+ const toBytes = async (value) => {
281
+ if (typeof value === "string") return encoder.encode(value);
282
+ if (value instanceof ArrayBuffer) return new Uint8Array(value.slice(0));
283
+ if (ArrayBuffer.isView(value)) {
284
+ const view = value;
285
+ const copy = new Uint8Array(view.byteLength);
286
+ copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
287
+ return copy;
288
+ }
289
+ const reader = value.getReader();
290
+ const chunks = [];
291
+ let total = 0;
292
+ while (true) {
293
+ const result = await reader.read();
294
+ if (result.done) break;
295
+ if (result.value) {
296
+ const chunk = result.value instanceof Uint8Array ? result.value : new Uint8Array(result.value);
297
+ chunks.push(chunk);
298
+ total += chunk.length;
299
+ }
300
+ }
301
+ const combined = new Uint8Array(total);
302
+ let offset = 0;
303
+ for (const chunk of chunks) {
304
+ combined.set(chunk, offset);
305
+ offset += chunk.length;
306
+ }
307
+ return combined;
308
+ };
309
+ const decodeBytes = (bytes, type) => {
310
+ switch (type) {
311
+ case "json": return JSON.parse(decoder.decode(bytes));
312
+ case "arrayBuffer": {
313
+ const copy = new Uint8Array(bytes.length);
314
+ copy.set(bytes);
315
+ return copy.buffer;
316
+ }
317
+ case "stream": {
318
+ const copy = new Uint8Array(bytes.length);
319
+ copy.set(bytes);
320
+ return new ReadableStream({ start(controller) {
321
+ controller.enqueue(copy);
322
+ controller.close();
323
+ } });
324
+ }
325
+ default: return decoder.decode(bytes);
326
+ }
327
+ };
328
+ const resolveType = (options) => {
329
+ return typeof options === "string" ? options : options?.type ?? "text";
330
+ };
331
+ return {
332
+ async get(key, options) {
333
+ const bytes = store.get(key);
334
+ if (bytes === void 0) return null;
335
+ return decodeBytes(bytes, resolveType(options));
336
+ },
337
+ async put(key, value, _options) {
338
+ const bytes = await toBytes(value);
339
+ store.set(key, bytes);
340
+ },
341
+ async delete(key) {
342
+ store.delete(key);
343
+ metadata.delete(key);
344
+ },
345
+ async list(options) {
346
+ const prefix = options?.prefix ?? "";
347
+ const limit = options?.limit ?? 1e3;
348
+ const keys = Array.from(store.keys()).filter((key) => key.startsWith(prefix)).slice(0, limit).map((name) => ({ name }));
349
+ return {
350
+ keys,
351
+ list_complete: keys.length < limit,
352
+ cursor: void 0
353
+ };
354
+ },
355
+ async getWithMetadata(key, options) {
356
+ const bytes = store.get(key);
357
+ return {
358
+ value: bytes === void 0 ? null : decodeBytes(bytes, resolveType(options)),
359
+ metadata: metadata.get(key) ?? null
360
+ };
361
+ }
362
+ };
363
+ }
364
+ //#endregion
365
+ //#region src/test/utilities/d1.ts
366
+ const TABLE_NAME_RE = /(?:from|into|update)\s+["'`]?([a-zA-Z_][a-zA-Z0-9_]*)["'`]?/i;
367
+ const extractTable = (sql) => {
368
+ const match = TABLE_NAME_RE.exec(sql);
369
+ return match ? match[1] : null;
71
370
  };
371
+ const detectOp = (sql) => {
372
+ const trimmed = sql.trimStart().toLowerCase();
373
+ if (trimmed.startsWith("select")) return "select";
374
+ if (trimmed.startsWith("insert")) return "insert";
375
+ if (trimmed.startsWith("update")) return "update";
376
+ if (trimmed.startsWith("delete")) return "delete";
377
+ return "other";
378
+ };
379
+ /**
380
+ * Creates a mock D1Database for testing
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * // Legacy: fixed results for all queries
385
+ * const d1 = createMockD1([{ id: 1, name: 'Alice' }])
386
+ *
387
+ * // Preferred: per-table fixtures
388
+ * const d1 = createMockD1({ fixtures: { users: [{ id: 1, name: 'Alice' }] } })
389
+ * await d1.prepare('SELECT * FROM users').all() // returns users fixture
390
+ * ```
391
+ */
392
+ function createMockD1(mockResultsOrOptions = []) {
393
+ const options = Array.isArray(mockResultsOrOptions) ? { results: mockResultsOrOptions } : mockResultsOrOptions;
394
+ const tables = /* @__PURE__ */ new Map();
395
+ for (const [name, rows] of Object.entries(options.fixtures ?? {})) tables.set(name, [...rows]);
396
+ const fallback = options.results ?? [];
397
+ const resolveRows = (sql) => {
398
+ const op = detectOp(sql);
399
+ const table = extractTable(sql);
400
+ if (table && tables.has(table)) return {
401
+ rows: tables.get(table) ?? [],
402
+ op,
403
+ table
404
+ };
405
+ return {
406
+ rows: [...fallback],
407
+ op,
408
+ table
409
+ };
410
+ };
411
+ const createStatement = (sql) => {
412
+ let boundValues = [];
413
+ const statement = {
414
+ bind(...values) {
415
+ boundValues = values;
416
+ return statement;
417
+ },
418
+ async first(column) {
419
+ const { rows } = resolveRows(sql);
420
+ const row = rows[0];
421
+ if (!row) return null;
422
+ if (column) return row[column];
423
+ return row;
424
+ },
425
+ async all() {
426
+ const { rows } = resolveRows(sql);
427
+ return {
428
+ results: rows,
429
+ success: true,
430
+ meta: {
431
+ duration: 0,
432
+ changes: 0,
433
+ last_row_id: 0
434
+ }
435
+ };
436
+ },
437
+ async run() {
438
+ const { op, table } = resolveRows(sql);
439
+ let changes = 0;
440
+ let lastRowId = 0;
441
+ if (op === "insert" && table) {
442
+ const rows = tables.get(table) ?? [];
443
+ const bound = boundValues.length > 0 ? Object.fromEntries(boundValues.map((v, i) => [`col${i}`, v])) : {};
444
+ rows.push(bound);
445
+ tables.set(table, rows);
446
+ changes = 1;
447
+ lastRowId = rows.length;
448
+ } else if (op === "delete" && table) {
449
+ changes = (tables.get(table) ?? []).length;
450
+ tables.set(table, []);
451
+ } else if (op === "update" && table) changes = (tables.get(table) ?? []).length;
452
+ return {
453
+ results: [],
454
+ success: true,
455
+ meta: {
456
+ duration: 0,
457
+ changes,
458
+ last_row_id: lastRowId
459
+ }
460
+ };
461
+ },
462
+ async raw(_options) {
463
+ const { rows } = resolveRows(sql);
464
+ return rows.map((row) => Object.values(row));
465
+ }
466
+ };
467
+ return statement;
468
+ };
469
+ return {
470
+ prepare(query) {
471
+ return createStatement(query);
472
+ },
473
+ async exec(_query) {
474
+ return {
475
+ results: [],
476
+ success: true,
477
+ meta: {
478
+ duration: 0,
479
+ changes: 0,
480
+ last_row_id: 0
481
+ }
482
+ };
483
+ },
484
+ async batch(statements) {
485
+ return statements.map(() => ({
486
+ results: [],
487
+ success: true,
488
+ meta: {
489
+ duration: 0,
490
+ changes: 0,
491
+ last_row_id: 0
492
+ }
493
+ }));
494
+ },
495
+ async dump() {
496
+ return /* @__PURE__ */ new ArrayBuffer(0);
497
+ },
498
+ withSession(_constraintOrBookmark) {
499
+ return this;
500
+ }
501
+ };
502
+ }
503
+ //#endregion
504
+ //#region src/test/utilities/r2.ts
505
+ /**
506
+ * Creates a mock R2Bucket for testing
507
+ *
508
+ * @example
509
+ * ```ts
510
+ * const r2 = createMockR2()
511
+ * await r2.put('file.txt', 'content')
512
+ * const obj = await r2.get('file.txt')
513
+ * ```
514
+ */
515
+ async function coerceToString(value) {
516
+ if (typeof value === "string") return value;
517
+ if (value === null) return "";
518
+ if (value instanceof ArrayBuffer) return new TextDecoder().decode(value);
519
+ if (ArrayBuffer.isView(value)) return new TextDecoder().decode(value);
520
+ if (value instanceof Blob) return await value.text();
521
+ const reader = value.getReader();
522
+ const chunks = [];
523
+ let done = false;
524
+ while (!done) {
525
+ const result = await reader.read();
526
+ done = result.done;
527
+ if (result.value) chunks.push(new TextDecoder().decode(result.value));
528
+ }
529
+ return chunks.join("");
530
+ }
531
+ function createMockR2() {
532
+ const store = /* @__PURE__ */ new Map();
533
+ const uploads = /* @__PURE__ */ new Map();
534
+ const createR2Object = (key, content, metadata) => {
535
+ const data = new TextEncoder().encode(content);
536
+ return {
537
+ key,
538
+ version: "1",
539
+ size: data.length,
540
+ etag: `"${key}-etag"`,
541
+ httpEtag: `"${key}-etag"`,
542
+ uploaded: /* @__PURE__ */ new Date(),
543
+ httpMetadata: metadata ?? {},
544
+ customMetadata: {},
545
+ checksums: {},
546
+ storageClass: "Standard",
547
+ body: new ReadableStream({ start(controller) {
548
+ controller.enqueue(data);
549
+ controller.close();
550
+ } }),
551
+ bodyUsed: false,
552
+ async arrayBuffer() {
553
+ return new Uint8Array(data).buffer;
554
+ },
555
+ async text() {
556
+ return content;
557
+ },
558
+ async json() {
559
+ return JSON.parse(content);
560
+ },
561
+ async blob() {
562
+ return new Blob([data]);
563
+ },
564
+ writeHttpMetadata(headers) {}
565
+ };
566
+ };
567
+ return {
568
+ async put(key, value, options) {
569
+ const content = await coerceToString(value);
570
+ store.set(key, { content });
571
+ return createR2Object(key, content);
572
+ },
573
+ async get(key, options) {
574
+ const item = store.get(key);
575
+ if (!item) return null;
576
+ return createR2Object(key, item.content, item.metadata);
577
+ },
578
+ async head(key) {
579
+ const item = store.get(key);
580
+ if (!item) return null;
581
+ return {
582
+ key,
583
+ version: "1",
584
+ size: new TextEncoder().encode(item.content).length,
585
+ etag: `"${key}-etag"`,
586
+ httpEtag: `"${key}-etag"`,
587
+ uploaded: /* @__PURE__ */ new Date(),
588
+ httpMetadata: item.metadata ?? {},
589
+ customMetadata: {},
590
+ checksums: {},
591
+ storageClass: "Standard",
592
+ writeHttpMetadata(headers) {}
593
+ };
594
+ },
595
+ async delete(keys) {
596
+ const keyArray = Array.isArray(keys) ? keys : [keys];
597
+ for (const key of keyArray) store.delete(key);
598
+ },
599
+ async list(options) {
600
+ return {
601
+ objects: Array.from(store.entries()).map(([key, { content, metadata }]) => createR2Object(key, content, metadata)),
602
+ truncated: false,
603
+ delimitedPrefixes: []
604
+ };
605
+ },
606
+ async createMultipartUpload(key, options) {
607
+ const uploadId = crypto.randomUUID();
608
+ uploads.set(uploadId, {
609
+ key,
610
+ parts: /* @__PURE__ */ new Map(),
611
+ options
612
+ });
613
+ return createMultipart(key, uploadId);
614
+ },
615
+ resumeMultipartUpload(key, uploadId) {
616
+ if (!uploads.has(uploadId)) uploads.set(uploadId, {
617
+ key,
618
+ parts: /* @__PURE__ */ new Map()
619
+ });
620
+ return createMultipart(key, uploadId);
621
+ }
622
+ };
623
+ function createMultipart(key, uploadId) {
624
+ const requireEntry = () => {
625
+ const entry = uploads.get(uploadId);
626
+ if (!entry) throw new Error(`Mock R2 multipart upload "${uploadId}" for key "${key}" is not active (it was aborted or completed).`);
627
+ return entry;
628
+ };
629
+ return {
630
+ key,
631
+ uploadId,
632
+ async uploadPart(partNumber, value, _options) {
633
+ const entry = requireEntry();
634
+ const str = await coerceToString(value);
635
+ entry.parts.set(partNumber, str);
636
+ return {
637
+ partNumber,
638
+ etag: `"${key}-part-${partNumber}-etag"`
639
+ };
640
+ },
641
+ async abort() {
642
+ uploads.delete(uploadId);
643
+ },
644
+ async complete(uploadedParts) {
645
+ const entry = requireEntry();
646
+ const content = [...uploadedParts].sort((a, b) => a.partNumber - b.partNumber).map((part) => {
647
+ const chunk = entry.parts.get(part.partNumber);
648
+ if (chunk === void 0) throw new Error(`Mock R2 multipart complete() references part ${part.partNumber} that was never uploaded for upload "${uploadId}".`);
649
+ return chunk;
650
+ }).join("");
651
+ const metadata = entry.options?.customMetadata;
652
+ store.set(key, {
653
+ content,
654
+ metadata
655
+ });
656
+ uploads.delete(uploadId);
657
+ return createR2Object(key, content, metadata);
658
+ }
659
+ };
660
+ }
661
+ }
662
+ //#endregion
663
+ //#region src/test/utilities/queue.ts
664
+ /**
665
+ * Creates a mock Queue for testing
666
+ */
667
+ function createMockQueue() {
668
+ const messages = [];
669
+ const metrics = {
670
+ backlogCount: 0,
671
+ backlogBytes: 0
672
+ };
673
+ const response = { metadata: { metrics } };
674
+ return {
675
+ async metrics() {
676
+ return metrics;
677
+ },
678
+ async send(message, options) {
679
+ messages.push({
680
+ body: message,
681
+ options
682
+ });
683
+ return response;
684
+ },
685
+ async sendBatch(batch, options) {
686
+ for (const message of batch) messages.push({
687
+ body: message.body,
688
+ options: {
689
+ contentType: message.contentType,
690
+ delaySeconds: message.delaySeconds ?? options?.delaySeconds
691
+ }
692
+ });
693
+ return response;
694
+ },
695
+ _getMessages() {
696
+ return messages;
697
+ }
698
+ };
699
+ }
700
+ //#endregion
701
+ //#region src/test/utilities/platform.ts
702
+ /**
703
+ * Creates a local fixed-window RateLimit binding for testing.
704
+ */
705
+ function createMockRateLimit(options = {}) {
706
+ const limit = options.limit ?? Number.MAX_SAFE_INTEGER;
707
+ const periodMs = (options.period ?? 60) * 1e3;
708
+ const windows = /* @__PURE__ */ new Map();
709
+ return { async limit({ key }) {
710
+ const now = Date.now();
711
+ const existing = windows.get(key);
712
+ if (!existing || existing.resetAt <= now) {
713
+ windows.set(key, {
714
+ count: 1,
715
+ resetAt: now + periodMs
716
+ });
717
+ return { success: limit >= 1 };
718
+ }
719
+ existing.count += 1;
720
+ return { success: existing.count <= limit };
721
+ } };
722
+ }
723
+ function createMockVersionMetadata(metadata = {}) {
724
+ return {
725
+ id: metadata.id ?? "devflare-local-version",
726
+ tag: metadata.tag ?? "local",
727
+ timestamp: metadata.timestamp ?? "1970-01-01T00:00:00.000Z"
728
+ };
729
+ }
730
+ /**
731
+ * Creates a Secrets Store binding whose get() method returns a fixed value.
732
+ */
733
+ function createMockSecretsStoreSecret(value) {
734
+ return { async get() {
735
+ return value;
736
+ } };
737
+ }
738
+ /**
739
+ * Creates a Hyperdrive binding around a local database connection string.
740
+ *
741
+ * Connection fields are usable with any Node database client; the raw socket
742
+ * `connect()` is a documented limitation that throws a clear, actionable error.
743
+ */
744
+ function createMockHyperdrive(connectionString) {
745
+ return createLocalHyperdrive(connectionString);
746
+ }
747
+ function createDefaultWorkerStub() {
748
+ return {
749
+ getEntrypoint() {
750
+ throw new Error("Mock WorkerLoader stub has no entrypoint. Pass createMockWorkerLoader({ stub }) for behavior.");
751
+ },
752
+ getDurableObjectClass() {
753
+ throw new Error("Mock WorkerLoader stub cannot materialise a Durable Object class. getDurableObjectClass() returns an opaque facet-spawning reference that workerd only exposes inside the runtime. Pass createMockWorkerLoader({ stub }) to inject a stub, or use createTestContext() (a real Miniflare worker) for Durable Object behavior.");
754
+ }
755
+ };
756
+ }
757
+ /**
758
+ * Creates a Worker Loader binding for pure unit tests.
759
+ */
760
+ function createMockWorkerLoader(options = {}) {
761
+ const stub = options.stub ?? createDefaultWorkerStub();
762
+ return {
763
+ get(_name, _getCode) {
764
+ return stub;
765
+ },
766
+ load(_code) {
767
+ return stub;
768
+ }
769
+ };
770
+ }
771
+ function defaultMTLSCertificateHandler() {
772
+ throw new Error("Mock mTLS Certificate Fetcher has no handler. Pass createMockMTLSCertificate(handler) for behavior.");
773
+ }
774
+ /**
775
+ * Creates an mTLS certificate binding fetcher for pure unit tests.
776
+ */
777
+ function createMockMTLSCertificate(handler = defaultMTLSCertificateHandler) {
778
+ return { async fetch(input, init) {
779
+ return handler(input, init);
780
+ } };
781
+ }
782
+ /**
783
+ * Creates a Dispatch Namespace binding for pure unit tests.
784
+ */
785
+ function createMockDispatchNamespace(options = {}) {
786
+ return { get(name) {
787
+ const worker = options.workers?.[name];
788
+ if (!worker) throw new Error(`Mock DispatchNamespace has no worker named "${name}".`);
789
+ return typeof worker === "function" ? createMockMTLSCertificate(worker) : worker;
790
+ } };
791
+ }
792
+ //#endregion
793
+ //#region src/test/utilities/workflows.ts
794
+ function createMockWorkflowInstance(id, options = {}) {
795
+ let status = options.status ?? "queued";
796
+ let output = options.output;
797
+ let error = options.error;
798
+ return {
799
+ id,
800
+ async pause() {
801
+ status = "paused";
802
+ },
803
+ async resume() {
804
+ status = "running";
805
+ },
806
+ async terminate() {
807
+ status = "terminated";
808
+ },
809
+ async restart() {
810
+ status = "queued";
811
+ error = void 0;
812
+ output = void 0;
813
+ },
814
+ async status() {
815
+ return {
816
+ status,
817
+ ...error && { error },
818
+ ...output !== void 0 && { output }
819
+ };
820
+ },
821
+ async sendEvent(_event) {}
822
+ };
823
+ }
824
+ /**
825
+ * Creates a Workflow binding for pure unit tests.
826
+ */
827
+ function createMockWorkflow(options = {}) {
828
+ const instances = /* @__PURE__ */ new Map();
829
+ let sequence = 0;
830
+ for (const [id, instanceOptions] of Object.entries(options.instances ?? {})) instances.set(id, createMockWorkflowInstance(id, instanceOptions));
831
+ const createInstance = (id) => {
832
+ if (instances.has(id)) throw new Error(`Mock Workflow already has an instance named "${id}".`);
833
+ const instance = createMockWorkflowInstance(id);
834
+ instances.set(id, instance);
835
+ return instance;
836
+ };
837
+ return {
838
+ async get(id) {
839
+ const instance = instances.get(id);
840
+ if (!instance) throw new Error(`Mock Workflow has no instance named "${id}".`);
841
+ return instance;
842
+ },
843
+ async create(options) {
844
+ return createInstance(options?.id ?? `mock-workflow-${++sequence}`);
845
+ },
846
+ async createBatch(batch) {
847
+ return batch.map((options) => {
848
+ return createInstance(options.id ?? `mock-workflow-${++sequence}`);
849
+ });
850
+ }
851
+ };
852
+ }
853
+ /**
854
+ * Creates a Pipeline binding for pure unit tests.
855
+ */
856
+ function createMockPipeline() {
857
+ const records = [];
858
+ return {
859
+ async send(batch) {
860
+ records.push(...batch);
861
+ },
862
+ _getRecords() {
863
+ return [...records];
864
+ }
865
+ };
866
+ }
867
+ //#endregion
868
+ //#region src/test/utilities/media.ts
869
+ function createEmptyImageStream() {
870
+ return new ReadableStream({ start(controller) {
871
+ controller.close();
872
+ } });
873
+ }
874
+ function createMockImageTransformationResult(response) {
875
+ return {
876
+ response() {
877
+ return response.clone();
878
+ },
879
+ contentType() {
880
+ return response.headers.get("Content-Type") ?? "image/png";
881
+ },
882
+ image() {
883
+ return response.clone().body ?? createEmptyImageStream();
884
+ }
885
+ };
886
+ }
887
+ function createMockImageTransformer(response) {
888
+ const transformer = {
889
+ transform(_transform) {
890
+ return transformer;
891
+ },
892
+ draw(_image, _options) {
893
+ return transformer;
894
+ },
895
+ async output(_options) {
896
+ return createMockImageTransformationResult(response);
897
+ }
898
+ };
899
+ return transformer;
900
+ }
901
+ function createMockHostedImagesBinding() {
902
+ const unsupported = () => {
903
+ throw new Error("Mock Images hosted API is not implemented. Pass a custom ImagesBinding through createMockEnv({ images }) if your test needs hosted image behavior.");
904
+ };
905
+ return {
906
+ image(_imageId) {
907
+ return {
908
+ details: unsupported,
909
+ bytes: unsupported,
910
+ update: unsupported,
911
+ delete: unsupported
912
+ };
913
+ },
914
+ upload: unsupported,
915
+ list: unsupported
916
+ };
917
+ }
918
+ /**
919
+ * Creates an Images binding for pure unit tests.
920
+ */
921
+ function createMockImagesBinding(options = {}) {
922
+ const response = options.response ?? new Response("", { headers: { "Content-Type": "image/png" } });
923
+ const info = options.info ?? {
924
+ format: response.headers.get("Content-Type") ?? "image/png",
925
+ fileSize: 0,
926
+ width: 0,
927
+ height: 0
928
+ };
929
+ return {
930
+ async info(_stream, _options) {
931
+ return info;
932
+ },
933
+ input(_stream, _options) {
934
+ return createMockImageTransformer(response);
935
+ },
936
+ hosted: createMockHostedImagesBinding()
937
+ };
938
+ }
939
+ function createEmptyMediaStream() {
940
+ return new ReadableStream({ start(controller) {
941
+ controller.close();
942
+ } });
943
+ }
944
+ function createMockMediaTransformationResult(response) {
945
+ return {
946
+ async media() {
947
+ return response.clone().body ?? createEmptyMediaStream();
948
+ },
949
+ async response() {
950
+ return response.clone();
951
+ },
952
+ async contentType() {
953
+ return response.headers.get("Content-Type") ?? "video/mp4";
954
+ }
955
+ };
956
+ }
957
+ function createMockMediaTransformer(response) {
958
+ return {
959
+ transform(_transform) {
960
+ return { output(_output) {
961
+ return createMockMediaTransformationResult(response);
962
+ } };
963
+ },
964
+ output(_output) {
965
+ return createMockMediaTransformationResult(response);
966
+ }
967
+ };
968
+ }
969
+ /**
970
+ * Creates a Media Transformations binding for pure unit tests.
971
+ */
972
+ function createMockMediaBinding(options = {}) {
973
+ const response = options.response ?? new Response("", { headers: { "Content-Type": "video/mp4" } });
974
+ return { input(_media) {
975
+ return createMockMediaTransformer(response);
976
+ } };
977
+ }
978
+ //#endregion
979
+ //#region src/test/utilities/artifacts.ts
980
+ function createArtifactTimestamp() {
981
+ return (/* @__PURE__ */ new Date("2026-04-26T00:00:00.000Z")).toISOString();
982
+ }
983
+ function createArtifactsRepoInfo(name, options = {}) {
984
+ const now = createArtifactTimestamp();
985
+ return {
986
+ id: `repo-${name}`,
987
+ name,
988
+ description: options.description ?? null,
989
+ defaultBranch: options.defaultBranch ?? "main",
990
+ createdAt: now,
991
+ updatedAt: now,
992
+ lastPushAt: null,
993
+ source: options.source ?? null,
994
+ readOnly: options.readOnly ?? false,
995
+ remote: `https://example.com/artifacts/default/${name}.git`
996
+ };
997
+ }
998
+ function isArtifactsBinding$1(value) {
999
+ return typeof value.create === "function";
1000
+ }
1001
+ /**
1002
+ * Creates an in-memory Artifacts binding for pure unit tests.
1003
+ */
1004
+ function createMockArtifacts(options = {}) {
1005
+ const repos = /* @__PURE__ */ new Map();
1006
+ const tokens = /* @__PURE__ */ new Map();
1007
+ const addRepo = (info) => {
1008
+ repos.set(info.name, info);
1009
+ if (!tokens.has(info.name)) tokens.set(info.name, []);
1010
+ };
1011
+ for (const repo of options.repos ?? []) addRepo({
1012
+ ...createArtifactsRepoInfo(repo.name),
1013
+ ...repo
1014
+ });
1015
+ const createToken = (repoName, scope = "write", ttl = 86400) => {
1016
+ const existing = tokens.get(repoName) ?? [];
1017
+ const id = `token-${repoName}-${existing.length + 1}`;
1018
+ const expiresAt = new Date(Date.parse(createArtifactTimestamp()) + ttl * 1e3).toISOString();
1019
+ const token = {
1020
+ id,
1021
+ scope,
1022
+ state: "active",
1023
+ createdAt: createArtifactTimestamp(),
1024
+ expiresAt
1025
+ };
1026
+ tokens.set(repoName, [...existing, token]);
1027
+ return {
1028
+ id,
1029
+ plaintext: `${id}-plaintext`,
1030
+ scope,
1031
+ expiresAt
1032
+ };
1033
+ };
1034
+ const createRepoHandle = (info) => ({
1035
+ ...info,
1036
+ async createToken(scope, ttl) {
1037
+ return createToken(info.name, scope, ttl);
1038
+ },
1039
+ async listTokens() {
1040
+ const repoTokens = tokens.get(info.name) ?? [];
1041
+ return {
1042
+ tokens: repoTokens,
1043
+ total: repoTokens.length
1044
+ };
1045
+ },
1046
+ async revokeToken(tokenOrId) {
1047
+ const repoTokens = tokens.get(info.name) ?? [];
1048
+ const index = repoTokens.findIndex((token) => token.id === tokenOrId);
1049
+ if (index === -1) return false;
1050
+ repoTokens[index] = {
1051
+ ...repoTokens[index],
1052
+ state: "revoked"
1053
+ };
1054
+ tokens.set(info.name, repoTokens);
1055
+ return true;
1056
+ },
1057
+ async fork(name, forkOptions) {
1058
+ return createRepo(name, {
1059
+ description: forkOptions?.description ?? info.description ?? void 0,
1060
+ readOnly: forkOptions?.readOnly ?? info.readOnly,
1061
+ setDefaultBranch: info.defaultBranch,
1062
+ source: `artifacts:default/${info.name}`
1063
+ });
1064
+ }
1065
+ });
1066
+ const createRepo = async (name, createOptions = {}) => {
1067
+ const info = createArtifactsRepoInfo(name, {
1068
+ description: createOptions.description,
1069
+ readOnly: createOptions.readOnly,
1070
+ defaultBranch: createOptions.setDefaultBranch,
1071
+ source: createOptions.source
1072
+ });
1073
+ addRepo(info);
1074
+ const token = createToken(name);
1075
+ return {
1076
+ id: info.id,
1077
+ name: info.name,
1078
+ description: info.description,
1079
+ defaultBranch: info.defaultBranch,
1080
+ remote: info.remote,
1081
+ token: token.plaintext,
1082
+ tokenExpiresAt: token.expiresAt
1083
+ };
1084
+ };
1085
+ return {
1086
+ create: createRepo,
1087
+ async get(name) {
1088
+ const repo = repos.get(name);
1089
+ return repo ? createRepoHandle(repo) : null;
1090
+ },
1091
+ async import(params) {
1092
+ return createRepo(params.target.name, {
1093
+ description: params.target.opts?.description,
1094
+ readOnly: params.target.opts?.readOnly,
1095
+ source: params.source.url
1096
+ });
1097
+ },
1098
+ async list(opts) {
1099
+ const limit = opts?.limit ?? 50;
1100
+ const repoList = Array.from(repos.values()).slice(0, limit).map((repo) => {
1101
+ const { remote: _remote, ...rest } = repo;
1102
+ return rest;
1103
+ });
1104
+ return {
1105
+ repos: repoList,
1106
+ total: repos.size,
1107
+ ...repos.size > repoList.length && { cursor: String(repoList.length) }
1108
+ };
1109
+ },
1110
+ async delete(name) {
1111
+ tokens.delete(name);
1112
+ return repos.delete(name);
1113
+ }
1114
+ };
1115
+ }
1116
+ //#endregion
1117
+ //#region src/test/utilities/env.ts
1118
+ /**
1119
+ * Creates a complete mock environment with specified bindings
1120
+ *
1121
+ * @example
1122
+ * ```ts
1123
+ * const env = createMockEnv({
1124
+ * kv: ['CACHE'],
1125
+ * d1: ['DB'],
1126
+ * vars: { API_KEY: 'secret' }
1127
+ * })
1128
+ * ```
1129
+ */
1130
+ function createMockEnv(options = {}) {
1131
+ const env = {};
1132
+ if (options.kv) for (const name of options.kv) env[name] = createMockKV();
1133
+ if (options.d1) for (const name of options.d1) env[name] = createMockD1();
1134
+ if (options.r2) for (const name of options.r2) env[name] = createMockR2();
1135
+ if (options.queues) for (const name of options.queues) env[name] = createMockQueue();
1136
+ if (options.rateLimits) for (const [name, rateLimitOptions] of Object.entries(options.rateLimits)) env[name] = createMockRateLimit(rateLimitOptions);
1137
+ if (options.versionMetadata) env[options.versionMetadata] = createMockVersionMetadata();
1138
+ if (options.hyperdrive) for (const [name, binding] of Object.entries(options.hyperdrive)) env[name] = typeof binding === "string" ? createMockHyperdrive(binding) : binding;
1139
+ if (Array.isArray(options.workerLoaders)) for (const name of options.workerLoaders) env[name] = createMockWorkerLoader();
1140
+ else if (options.workerLoaders) for (const [name, workerLoaderOptions] of Object.entries(options.workerLoaders)) env[name] = createMockWorkerLoader(workerLoaderOptions);
1141
+ if (Array.isArray(options.mtlsCertificates)) for (const name of options.mtlsCertificates) env[name] = createMockMTLSCertificate();
1142
+ else if (options.mtlsCertificates) for (const [name, handler] of Object.entries(options.mtlsCertificates)) env[name] = createMockMTLSCertificate(handler);
1143
+ if (Array.isArray(options.dispatchNamespaces)) for (const name of options.dispatchNamespaces) env[name] = createMockDispatchNamespace();
1144
+ else if (options.dispatchNamespaces) for (const [name, dispatchNamespaceOptions] of Object.entries(options.dispatchNamespaces)) env[name] = createMockDispatchNamespace(dispatchNamespaceOptions);
1145
+ if (Array.isArray(options.workflows)) for (const name of options.workflows) env[name] = createMockWorkflow();
1146
+ else if (options.workflows) for (const [name, workflowOptions] of Object.entries(options.workflows)) env[name] = "create" in workflowOptions ? workflowOptions : createMockWorkflow(workflowOptions);
1147
+ if (Array.isArray(options.pipelines)) for (const name of options.pipelines) env[name] = createMockPipeline();
1148
+ else if (options.pipelines) for (const [name, pipeline] of Object.entries(options.pipelines)) env[name] = pipeline;
1149
+ if (typeof options.images === "string") env[options.images] = createMockImagesBinding();
1150
+ else if (options.images) env.IMAGES = options.images;
1151
+ if (typeof options.media === "string") env[options.media] = createMockMediaBinding();
1152
+ else if (options.media) env.MEDIA = options.media;
1153
+ if (Array.isArray(options.artifacts)) for (const name of options.artifacts) env[name] = createMockArtifacts();
1154
+ else if (options.artifacts) for (const [name, artifactsOptions] of Object.entries(options.artifacts)) env[name] = isArtifactsBinding$1(artifactsOptions) ? artifactsOptions : createMockArtifacts(artifactsOptions);
1155
+ if (options.secretsStore) for (const [name, value] of Object.entries(options.secretsStore)) env[name] = createMockSecretsStoreSecret(value);
1156
+ if (options.vars) Object.assign(env, options.vars);
1157
+ if (options.secrets) Object.assign(env, options.secrets);
1158
+ if (options.custom) Object.assign(env, options.custom);
1159
+ return env;
1160
+ }
1161
+ //#endregion
1162
+ //#region src/test/simple-context-bindings.ts
1163
+ /**
1164
+ * Build the initial remote/static binding map for a test context.
1165
+ *
1166
+ * - When `isRemoteModeActive()`, registers `RemoteAI` / `RemoteVectorize`
1167
+ * proxies for every `bindings.ai` / `bindings.vectorize` entry. Otherwise
1168
+ * those bindings are left to come from Miniflare (or remain absent).
1169
+ * - `config.vars` are always copied in as-is.
1170
+ * - `config.bindings.sendEmail` is always wired to the local SendEmail
1171
+ * binding (so tests can intercept outgoing mail without remote setup).
1172
+ */
1173
+ function buildRemoteAndStaticBindings(config) {
1174
+ const remoteBindings = {};
1175
+ if (isRemoteModeActive()) {
1176
+ if (config.bindings?.ai) {
1177
+ const aiBindingName = config.bindings.ai.binding || "AI";
1178
+ remoteBindings[aiBindingName] = createRemoteAI(config.accountId);
1179
+ }
1180
+ if (config.bindings?.vectorize) for (const [name, vectorConfig] of Object.entries(config.bindings.vectorize)) remoteBindings[name] = createRemoteVectorize(vectorConfig.indexName, config.accountId);
1181
+ }
1182
+ if (config.vars) for (const [key, value] of Object.entries(config.vars)) remoteBindings[key] = value;
1183
+ if (config.bindings?.sendEmail) for (const [name, binding] of Object.entries(config.bindings.sendEmail)) remoteBindings[name] = createLocalSendEmailBinding(binding);
1184
+ if (config.bindings?.workerLoaders) for (const name of Object.keys(config.bindings.workerLoaders)) remoteBindings[name] = createLocalWorkerLoaderBinding();
1185
+ if (config.bindings?.versionMetadata) remoteBindings[config.bindings.versionMetadata.binding] = createMockVersionMetadata();
1186
+ return remoteBindings;
1187
+ }
1188
+ //#endregion
1189
+ //#region src/test/simple-context-gateway-script.ts
1190
+ function buildGatewayScript(bundledCode, wrappers, nativeRpcBindingNames = []) {
1191
+ return `
1192
+ // Bundled transport + DO classes
1193
+ ${bundledCode}
1194
+
1195
+ // DO Wrappers with RPC
1196
+ ${wrappers}
1197
+
1198
+ const __nativeRpcBindings = new Set(${JSON.stringify(nativeRpcBindingNames)})
1199
+
1200
+ // Transport encoding helper
1201
+ const __transportEncoders = typeof transport !== 'undefined' ? transport : {}
1202
+
1203
+ function __encodeTransport(value) {
1204
+ if (value === null || value === undefined) return value
1205
+
1206
+ // Try each encoder
1207
+ for (const [typeName, transporter] of Object.entries(__transportEncoders)) {
1208
+ const encoded = transporter.encode(value)
1209
+ if (encoded !== false && encoded !== undefined) {
1210
+ return { __transport: typeName, value: encoded }
1211
+ }
1212
+ }
1213
+
1214
+ // Recursively encode arrays and objects
1215
+ if (Array.isArray(value)) {
1216
+ return value.map(__encodeTransport)
1217
+ }
1218
+ if (typeof value === 'object') {
1219
+ const result = {}
1220
+ for (const [k, v] of Object.entries(value)) {
1221
+ result[k] = __encodeTransport(v)
1222
+ }
1223
+ return result
1224
+ }
1225
+
1226
+ return value
1227
+ }
1228
+
1229
+ // Gateway with WebSocket RPC
1230
+ export default {
1231
+ async fetch(request, env) {
1232
+ if (request.headers.get('Upgrade') === 'websocket') {
1233
+ const { 0: client, 1: server } = new WebSocketPair()
1234
+ server.accept()
1235
+ server.addEventListener('message', async (e) => {
1236
+ try {
1237
+ const m = JSON.parse(e.data)
1238
+ if (m.t === 'rpc.call') {
1239
+ const result = await executeRpc(env, m.method, m.params)
1240
+ server.send(JSON.stringify({ t: 'rpc.ok', id: m.id, result }))
1241
+ }
1242
+ } catch (error) {
1243
+ server.send(JSON.stringify({ t: 'rpc.err', id: 'unknown', error: { code: 'RPC_ERROR', message: error.message } }))
1244
+ }
1245
+ })
1246
+ return new Response(null, { status: 101, webSocket: client })
1247
+ }
1248
+ return new Response('Gateway')
1249
+ }
1250
+ }
1251
+
1252
+ async function executeRpc(env, method, params) {
1253
+ const [bindingName, ...rest] = method.split('.')
1254
+ let op = rest.join('.')
1255
+ const binding = env[bindingName]
1256
+ const RAW_EMAIL = 'EmailMessage::raw'
1257
+ if (!binding) throw new Error('Binding not found: ' + bindingName)
1258
+
1259
+ // Normalize namespaced op names (kv.*, r2.*, d1.*, do.*, queue.*, ai.*, var.*)
1260
+ // down to the legacy verbs this dispatcher historically used. The bridge
1261
+ // proxy always emits namespaced forms (see src/bridge/proxy.ts and B3 in
1262
+ // REMAINING.md); this keeps the dispatcher backwards-compatible while the
1263
+ // rest of the codebase converges on the namespaced convention.
1264
+ if (op.indexOf('kv.') === 0) op = op.slice(3)
1265
+ else if (op.indexOf('do.') === 0) {
1266
+ const tail = op.slice(3)
1267
+ if (tail === 'fetch') op = 'stub.fetch'
1268
+ else if (tail === 'rpc') op = 'stub.rpc'
1269
+ else op = tail
1270
+ }
1271
+ else if (op.indexOf('queue.') === 0) op = op.slice(6)
1272
+ else if (op.indexOf('ai.') === 0) op = op.slice(3)
1273
+ else if (op.indexOf('var.') === 0) op = op.slice(4)
1274
+ else if (op.indexOf('d1.stmt.') === 0) op = 'prepare.' + op.slice('d1.stmt.'.length)
1275
+ else if (op.indexOf('d1.') === 0) op = op.slice(3)
1276
+ // r2.* and email.* keep their existing prefixes
1277
+
1278
+ // KV operations
1279
+ if (op === 'get') return binding.get(params[0], params[1])
1280
+ if (op === 'put') return binding.put(params[0], params[1], params[2])
1281
+ if (op === 'delete') return binding.delete(params[0])
1282
+ if (op === 'list') return binding.list(params[0])
1283
+ if (op === 'getWithMetadata') return binding.getWithMetadata(params[0], params[1])
1284
+
1285
+ // R2 operations
1286
+ if (op === 'r2.get') return binding.get(params[0], params[1])
1287
+ if (op === 'r2.put') return binding.put(params[0], params[1], params[2])
1288
+ if (op === 'r2.delete') return binding.delete(params[0])
1289
+ if (op === 'r2.list') return binding.list(params[0])
1290
+ if (op === 'r2.head' || op === 'head') return binding.head(params[0])
1291
+
1292
+ // D1 operations
1293
+ if (op === 'exec') return binding.exec(params[0])
1294
+ if (op === 'dump') return binding.dump()
1295
+ if (op === 'batch') {
1296
+ const stmts = params[0].map(s => {
1297
+ const stmt = binding.prepare(s.sql)
1298
+ return s.bindings?.length ? stmt.bind(...s.bindings) : stmt
1299
+ })
1300
+ return binding.batch(stmts)
1301
+ }
1302
+ if (op === 'prepare.run') return binding.prepare(params[0]).bind(...(params[1] || [])).run()
1303
+ if (op === 'prepare.all') return binding.prepare(params[0]).bind(...(params[1] || [])).all()
1304
+ if (op === 'prepare.first') return binding.prepare(params[0]).bind(...(params[1] || [])).first(params[2])
1305
+ if (op === 'prepare.raw') return binding.prepare(params[0]).bind(...(params[1] || [])).raw({ columnNames: params[2] })
1306
+
1307
+ // Send email operations
1308
+ if (op === 'email.send') {
1309
+ return binding.send(__normalizeEmailMessage(params[0]))
1310
+ }
1311
+
1312
+ // DO operations
1313
+ if (op === 'idFromName') {
1314
+ return { __type: 'DOId', hex: binding.idFromName(params[0]).toString() }
1315
+ }
1316
+ if (op === 'stub.rpc') {
1317
+ const [, idSerialized, rpcMethod, rpcParams] = params
1318
+ const stub = binding.get(binding.idFromString(idSerialized.hex))
1319
+
1320
+ if (__nativeRpcBindings.has(bindingName) && typeof stub[rpcMethod] === 'function') {
1321
+ let result = await stub[rpcMethod](...(rpcParams || []))
1322
+ result = __encodeTransport(result)
1323
+ return result
1324
+ }
1325
+
1326
+ const response = await stub.fetch(new Request('http://do/_rpc', {
1327
+ method: 'POST',
1328
+ headers: { 'Content-Type': 'application/json' },
1329
+ body: JSON.stringify({ method: rpcMethod, params: rpcParams || [] })
1330
+ }))
1331
+
1332
+ const payload = await response.json()
1333
+ if (!response.ok || !payload?.ok) {
1334
+ throw new Error(payload?.error?.message || ('DO RPC failed with status ' + response.status))
1335
+ }
1336
+
1337
+ return payload.result
1338
+ }
1339
+
1340
+ throw new Error('Unknown operation: ' + method)
1341
+ }
1342
+
1343
+ function __createEmailMessageRaw(raw) {
1344
+ if (typeof raw === 'string' || raw instanceof ReadableStream) {
1345
+ return raw
1346
+ }
1347
+ if (raw instanceof Uint8Array || raw instanceof ArrayBuffer) {
1348
+ return new Response(raw).body
1349
+ }
1350
+ throw new Error('Unsupported EmailMessage raw payload')
1351
+ }
1352
+
1353
+ function __buildRawEmail(message) {
1354
+ const lines = []
1355
+ const messageId = '<' + Date.now() + '-' + Math.random().toString(36).slice(2) + '@devflare.dev>'
1356
+
1357
+ lines.push('From: ' + message.from)
1358
+ lines.push('To: ' + (Array.isArray(message.to) ? message.to.join(', ') : message.to))
1359
+ lines.push('Date: ' + new Date().toUTCString())
1360
+ lines.push('Message-ID: ' + messageId)
1361
+
1362
+ if (message.subject) lines.push('Subject: ' + message.subject)
1363
+ if (message.replyTo) lines.push('Reply-To: ' + String(message.replyTo))
1364
+ if (message.cc) lines.push('Cc: ' + (Array.isArray(message.cc) ? message.cc.join(', ') : message.cc))
1365
+ if (message.bcc) lines.push('Bcc: ' + (Array.isArray(message.bcc) ? message.bcc.join(', ') : message.bcc))
1366
+
1367
+ for (const [key, value] of Object.entries(message.headers || {})) {
1368
+ lines.push(key + ': ' + value)
1369
+ }
1370
+
1371
+ lines.push('MIME-Version: 1.0')
1372
+ lines.push('Content-Type: ' + (message.html ? 'text/html' : 'text/plain') + '; charset=UTF-8')
1373
+ lines.push('')
1374
+ lines.push(String(message.html ?? message.text ?? '').replace(/\\r?\\n/g, '\\r\\n'))
1375
+
1376
+ return lines.join('\\r\\n')
1377
+ }
1378
+
1379
+ function __normalizeEmailMessage(message) {
1380
+ if (!message || typeof message !== 'object' || !('from' in message) || !('to' in message)) {
1381
+ return message
1382
+ }
1383
+ if ('EmailMessage::raw' in message) {
1384
+ return message
1385
+ }
1386
+ if ('raw' in message && message.raw !== undefined) {
1387
+ return {
1388
+ from: message.from,
1389
+ to: message.to,
1390
+ [RAW_EMAIL]: __createEmailMessageRaw(message.raw)
1391
+ }
1392
+ }
1393
+ return {
1394
+ from: message.from,
1395
+ to: message.to,
1396
+ [RAW_EMAIL]: __createEmailMessageRaw(__buildRawEmail(message))
1397
+ }
1398
+ }
1399
+ `;
1400
+ }
1401
+ //#endregion
1402
+ //#region src/test/simple-context-paths.ts
1403
+ const DEFAULT_TRANSPORT_ENTRY_FILES = [
1404
+ "src/transport.ts",
1405
+ "src/transport.js",
1406
+ "src/transport.mts",
1407
+ "src/transport.mjs"
1408
+ ];
1409
+ const CURRENT_PACKAGE_ROOT = findPackageRoot(dirname$1(fileURLToPath(import.meta.url)));
1410
+ /**
1411
+ * Access Bun global via globalThis to avoid shadowing richer @types/bun
1412
+ * when available. Returns undefined if not running in Bun.
1413
+ */
1414
+ function getBunRuntime() {
1415
+ const g = globalThis;
1416
+ if (typeof g.Bun === "object" && g.Bun !== null) return g.Bun;
1417
+ }
1418
+ /**
1419
+ * Get the directory of the test file.
1420
+ * Prefers stack trace parsing so bun test hooks resolve the actual test file,
1421
+ * then falls back to the current working directory.
1422
+ *
1423
+ * We intentionally do not use Bun.main here because workspace consumers often
1424
+ * import the built devflare package from `packages/devflare/dist`, which would
1425
+ * incorrectly anchor autodiscovery inside the devflare package instead of the
1426
+ * calling project under test.
1427
+ */
1428
+ function getCallerDirectory() {
1429
+ const stackCallerDirectory = getStackCallerDirectory();
1430
+ if (stackCallerDirectory) return stackCallerDirectory;
1431
+ return process.cwd();
1432
+ }
1433
+ function getStackCallerDirectory() {
1434
+ const originalPrepare = Error.prepareStackTrace;
1435
+ Error.prepareStackTrace = (_, stack) => stack;
1436
+ try {
1437
+ const stack = (/* @__PURE__ */ new Error()).stack;
1438
+ for (const site of stack ?? []) {
1439
+ const filename = site.getFileName?.();
1440
+ if (filename && !isInsideCurrentPackage(filename) && !filename.includes("simple-context") && !filename.includes("node_modules") && !filename.includes("[") && existsSync$1(filename)) return dirname$1(filename);
1441
+ }
1442
+ } finally {
1443
+ Error.prepareStackTrace = originalPrepare;
1444
+ }
1445
+ return null;
1446
+ }
1447
+ function findPackageRoot(startDir) {
1448
+ let currentDir = startDir;
1449
+ while (true) {
1450
+ if (existsSync$1(join$1(currentDir, "package.json"))) return currentDir;
1451
+ const parentDir = dirname$1(currentDir);
1452
+ if (parentDir === currentDir) return startDir;
1453
+ currentDir = parentDir;
1454
+ }
1455
+ }
1456
+ function isInsideCurrentPackage(filePath) {
1457
+ const normalizedFilePath = filePath.replace(/\\/g, "/");
1458
+ const normalizedPackageRoot = CURRENT_PACKAGE_ROOT.replace(/\\/g, "/");
1459
+ return normalizedFilePath === normalizedPackageRoot || normalizedFilePath.startsWith(`${normalizedPackageRoot}/`);
1460
+ }
1461
+ /**
1462
+ * Find the nearest supported devflare config by searching upward from startDir.
1463
+ */
1464
+ async function findNearestConfig(startDir) {
1465
+ let currentDir = startDir;
1466
+ while (true) {
1467
+ const configPath = await resolveConfigPath(currentDir);
1468
+ if (configPath) return configPath;
1469
+ const parentDir = dirname$1(currentDir);
1470
+ if (parentDir === currentDir) return null;
1471
+ currentDir = parentDir;
1472
+ }
1473
+ }
1474
+ async function getAvailablePort() {
1475
+ return await new Promise((resolvePort, reject) => {
1476
+ const server = createServer();
1477
+ server.once("error", reject);
1478
+ server.listen(0, "127.0.0.1", () => {
1479
+ const address = server.address();
1480
+ if (!address || typeof address === "string") {
1481
+ server.close(() => reject(/* @__PURE__ */ new Error("Could not determine an available port")));
1482
+ return;
1483
+ }
1484
+ const { port } = address;
1485
+ server.close((error) => {
1486
+ if (error) {
1487
+ reject(error);
1488
+ return;
1489
+ }
1490
+ resolvePort(port);
1491
+ });
1492
+ });
1493
+ });
1494
+ }
1495
+ function resolveTransportFile(configDir, configuredPath) {
1496
+ if (typeof configuredPath === "string") return configuredPath;
1497
+ if (configuredPath === null) return null;
1498
+ for (const defaultEntry of DEFAULT_TRANSPORT_ENTRY_FILES) if (existsSync$1(join$1(configDir, defaultEntry))) return defaultEntry;
1499
+ return null;
1500
+ }
1501
+ //#endregion
1502
+ //#region src/test/simple-context-durable-objects.ts
1503
+ /**
1504
+ * Find all exported class names in a TypeScript/JavaScript file.
1505
+ */
1506
+ function findExportedClasses(code) {
1507
+ const classes = [];
1508
+ const classPattern = /export\s+class\s+(\w+)/g;
1509
+ let match = classPattern.exec(code);
1510
+ while (match !== null) {
1511
+ classes.push(match[1]);
1512
+ match = classPattern.exec(code);
1513
+ }
1514
+ return classes;
1515
+ }
1516
+ function classSupportsNativeDurableObjectRpc(code, className) {
1517
+ return new RegExp(`export\\s+class\\s+${className}\\s+extends\\s+DurableObject\\b`).test(code);
1518
+ }
1519
+ function toGeneratedIdentifier(value) {
1520
+ const normalized = value.replace(/[^A-Za-z0-9_$]/g, "_");
1521
+ return /^[A-Za-z_$]/.test(normalized) ? normalized : `_${normalized}`;
1522
+ }
1523
+ async function discoverLocalDurableObjectClasses(config, configDir) {
1524
+ const classToFilePath = /* @__PURE__ */ new Map();
1525
+ const doPatternConfig = config.files?.durableObjects;
1526
+ const doPattern = typeof doPatternConfig === "string" ? doPatternConfig : DEFAULT_DO_PATTERN;
1527
+ if (doPatternConfig === false) return classToFilePath;
1528
+ const doFiles = await findFiles(doPattern, { cwd: configDir });
1529
+ for (const filePath of doFiles) try {
1530
+ const code = await readFile$1(filePath, "utf-8");
1531
+ const classNames = findExportedClasses(code);
1532
+ for (const className of classNames) classToFilePath.set(className, {
1533
+ filePath,
1534
+ nativeRpc: classSupportsNativeDurableObjectRpc(code, className)
1535
+ });
1536
+ } catch {}
1537
+ return classToFilePath;
1538
+ }
1539
+ async function resolveLocalDurableObjects(config, configDir) {
1540
+ const doConfig = {};
1541
+ const doInfos = [];
1542
+ const classToFilePath = await discoverLocalDurableObjectClasses(config, configDir);
1543
+ for (const [name, rawDoInfo] of Object.entries(config.bindings?.durableObjects ?? {})) {
1544
+ const doInfo = normalizeDOBinding(rawDoInfo);
1545
+ if (doInfo.__ref) continue;
1546
+ let scriptPath;
1547
+ let nativeRpc = false;
1548
+ if (doInfo.kind === "cross-worker" && doInfo.scriptName) {
1549
+ scriptPath = join$1(configDir, "src", doInfo.scriptName);
1550
+ try {
1551
+ nativeRpc = classSupportsNativeDurableObjectRpc(await readFile$1(scriptPath, "utf-8"), doInfo.className);
1552
+ } catch {
1553
+ nativeRpc = false;
1554
+ }
1555
+ } else {
1556
+ const discoveredClass = classToFilePath.get(doInfo.className);
1557
+ if (!discoveredClass) throw new Error(`Durable object ${name} (className: '${doInfo.className}') not found.\nEither:\n 1. Set files.durableObjects pattern in config (e.g., 'src/do.*.ts')\n 2. Use explicit scriptName: { className: '${doInfo.className}', scriptName: 'do.file.ts' }`);
1558
+ scriptPath = discoveredClass.filePath;
1559
+ nativeRpc = discoveredClass.nativeRpc;
1560
+ }
1561
+ const runtimeClassName = nativeRpc ? doInfo.className : `__Devflare${toGeneratedIdentifier(name)}RpcWrapper`;
1562
+ doConfig[name] = runtimeClassName;
1563
+ doInfos.push({
1564
+ name,
1565
+ className: doInfo.className,
1566
+ scriptPath,
1567
+ nativeRpc,
1568
+ runtimeClassName
1569
+ });
1570
+ }
1571
+ return {
1572
+ doConfig,
1573
+ doInfos
1574
+ };
1575
+ }
1576
+ function buildWrapperCode(doInfos) {
1577
+ return doInfos.filter((info) => !info.nativeRpc).map((info) => `
1578
+ export class ${info.runtimeClassName} {
1579
+ constructor(state, env) {
1580
+ this.__instance = new ${info.className}(state, env)
1581
+ }
1582
+
1583
+ async fetch(request) {
1584
+ const url = new URL(request.url)
1585
+ if (request.method !== 'POST' || url.pathname !== '/_rpc') {
1586
+ return new Response('Not found', { status: 404 })
1587
+ }
1588
+
1589
+ try {
1590
+ const payload = await request.json()
1591
+ const method = payload?.method
1592
+ const params = Array.isArray(payload?.params) ? payload.params : []
1593
+ const target = this.__instance?.[method]
1594
+
1595
+ if (typeof target !== 'function') {
1596
+ return new Response(JSON.stringify({
1597
+ ok: false,
1598
+ error: { message: 'Method not found: ' + String(method) }
1599
+ }), {
1600
+ status: 404,
1601
+ headers: { 'Content-Type': 'application/json' }
1602
+ })
1603
+ }
1604
+
1605
+ let result = await target.apply(this.__instance, params)
1606
+ result = __encodeTransport(result)
1607
+
1608
+ return new Response(JSON.stringify({ ok: true, result }), {
1609
+ headers: { 'Content-Type': 'application/json' }
1610
+ })
1611
+ } catch (error) {
1612
+ return new Response(JSON.stringify({
1613
+ ok: false,
1614
+ error: { message: error instanceof Error ? error.message : String(error) }
1615
+ }), {
1616
+ status: 500,
1617
+ headers: { 'Content-Type': 'application/json' }
1618
+ })
1619
+ }
1620
+ }
1621
+ }`.trim()).join("\n\n");
1622
+ }
1623
+ async function bundleDurableObjectModules(configDir, doInfos, transportFile) {
1624
+ const virtualImports = [];
1625
+ const virtualExports = [];
1626
+ if (transportFile) {
1627
+ const transportPath = join$1(configDir, transportFile);
1628
+ virtualImports.push(`import { transport } from '${transportPath.replace(/\\/g, "/")}'`);
1629
+ virtualExports.push("export { transport }");
1630
+ }
1631
+ for (const info of doInfos) {
1632
+ virtualImports.push(`import { ${info.className} } from '${info.scriptPath.replace(/\\/g, "/")}'`);
1633
+ virtualExports.push(`export { ${info.className} }`);
1634
+ }
1635
+ if (virtualImports.length === 0) return "";
1636
+ const virtualEntry = [
1637
+ ...virtualImports,
1638
+ "",
1639
+ ...virtualExports
1640
+ ].join("\n");
1641
+ const virtualPath = join$1(configDir, ".devflare", "__test_entry.ts");
1642
+ mkdirSync$1(dirname$1(virtualPath), { recursive: true });
1643
+ writeFileSync$1(virtualPath, virtualEntry);
1644
+ const bun = getBunRuntime();
1645
+ if (!bun) throw new Error("Bun runtime is required for createTestContext with Durable Objects");
1646
+ const result = await bun.build({
1647
+ entrypoints: [virtualPath],
1648
+ target: "browser",
1649
+ format: "esm",
1650
+ minify: false,
1651
+ external: ["cloudflare:workers", "cloudflare:*"]
1652
+ });
1653
+ if (!result.success) throw new Error(`Failed to bundle test entry: ${result.logs.join("\n")}`);
1654
+ return await result.outputs[0].text();
1655
+ }
1656
+ async function buildDurableObjectGateway(config, configDir, transportFile) {
1657
+ const workflowEntrypointScript = await bundleWorkflowEntrypointScript(config, configDir);
1658
+ if (!config.bindings?.durableObjects) return { script: buildGatewayScript(workflowEntrypointScript, "") };
1659
+ const { doConfig, doInfos } = await resolveLocalDurableObjects(config, configDir);
1660
+ const bundledCode = await bundleDurableObjectModules(configDir, doInfos, transportFile);
1661
+ const wrapperCode = buildWrapperCode(doInfos);
1662
+ return {
1663
+ durableObjects: doConfig,
1664
+ script: buildGatewayScript([workflowEntrypointScript, bundledCode].filter(Boolean).join("\n\n"), wrapperCode, doInfos.filter((info) => info.nativeRpc).map((info) => info.name))
1665
+ };
1666
+ }
1667
+ //#endregion
1668
+ //#region src/test/email.ts
1669
+ let miniflarePort = 8787;
1670
+ let emailListeners = [];
1671
+ let sentEmails = [];
1672
+ let emailHandlerPath = null;
1673
+ let configDir$4 = null;
1674
+ let testEnvGetter$4 = null;
1675
+ /**
1676
+ * Configure the email test helper
1677
+ * @internal
1678
+ */
1679
+ function configureEmail(options = {}) {
1680
+ if (options.port) miniflarePort = options.port;
1681
+ emailHandlerPath = options.handlerPath ?? emailHandlerPath;
1682
+ configDir$4 = options.configDir ?? configDir$4;
1683
+ testEnvGetter$4 = options.getEnv ?? testEnvGetter$4;
1684
+ }
1685
+ /**
1686
+ * Build a raw email string from options
1687
+ */
1688
+ function buildRawEmail(options) {
1689
+ if (options.raw) return options.raw;
1690
+ const lines = [];
1691
+ const messageId = `<${Date.now()}-${Math.random().toString(36).slice(2)}@devflare.dev>`;
1692
+ const date = (/* @__PURE__ */ new Date()).toUTCString();
1693
+ lines.push(`From: ${options.from}`);
1694
+ lines.push(`To: ${options.to}`);
1695
+ lines.push(`Date: ${date}`);
1696
+ lines.push(`Message-ID: ${messageId}`);
1697
+ if (options.subject) lines.push(`Subject: ${options.subject}`);
1698
+ if (options.headers) for (const [key, value] of Object.entries(options.headers)) lines.push(`${key}: ${value}`);
1699
+ lines.push("MIME-Version: 1.0");
1700
+ lines.push("Content-Type: text/plain; charset=UTF-8");
1701
+ lines.push("");
1702
+ lines.push(options.body ?? "");
1703
+ return lines.join("\r\n");
1704
+ }
1705
+ function createEmailHeaders(rawEmail) {
1706
+ const headers = new Headers();
1707
+ const lines = rawEmail.split(/\r?\n/);
1708
+ for (const line of lines) {
1709
+ if (!line.trim()) break;
1710
+ const colonIndex = line.indexOf(":");
1711
+ if (colonIndex <= 0) continue;
1712
+ headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim());
1713
+ }
1714
+ return headers;
1715
+ }
1716
+ function createRawEmailStream(rawEmail) {
1717
+ return new ReadableStream({ start(controller) {
1718
+ controller.enqueue(new TextEncoder().encode(rawEmail));
1719
+ controller.close();
1720
+ } });
1721
+ }
1722
+ function resolveEmailHandler(module) {
1723
+ if (typeof module.default === "function") return module.default;
1724
+ if (module.default && typeof module.default.email === "function") return module.default.email.bind(module.default);
1725
+ if (typeof module.email === "function") return module.email;
1726
+ return null;
1727
+ }
1728
+ function getRecordedRawContent(raw) {
1729
+ if (typeof raw === "string") return raw;
1730
+ }
1731
+ /**
1732
+ * Send an incoming email through the email test helper.
1733
+ *
1734
+ * When `createTestContext()` has configured an email handler, this imports and
1735
+ * invokes that handler directly and waits for queued `waitUntil()` work.
1736
+ * Otherwise it attempts the local `/cdn-cgi/handler/email` endpoint exposed by
1737
+ * compatible local runtimes.
1738
+ */
1739
+ async function send$1(options) {
1740
+ const raw = buildRawEmail(options);
1741
+ if (emailHandlerPath && configDir$4 && testEnvGetter$4) {
1742
+ const emailHandler = resolveEmailHandler(await import(join$1(configDir$4, emailHandlerPath)));
1743
+ if (!emailHandler) throw new Error(`Email handler at "${emailHandlerPath}" must export a default function or named "email" export.\nExpected: export async function email(message) { ... }`);
1744
+ const waitUntilPromises = [];
1745
+ const ctx = {
1746
+ waitUntil(promise) {
1747
+ waitUntilPromises.push(promise);
1748
+ },
1749
+ passThroughOnException() {},
1750
+ props: {}
1751
+ };
1752
+ const runtimeEnv = testEnvGetter$4();
1753
+ const timestamp = /* @__PURE__ */ new Date();
1754
+ const emailEvent = createEmailEvent({
1755
+ from: options.from,
1756
+ to: options.to,
1757
+ headers: createEmailHeaders(raw),
1758
+ raw: createRawEmailStream(raw),
1759
+ rawSize: raw.length,
1760
+ setReject(reason) {
1761
+ throw new Error(`Email rejected: ${reason}`);
1762
+ },
1763
+ async forward(rcptTo) {
1764
+ recordSentEmail({
1765
+ type: "forward",
1766
+ from: options.from,
1767
+ to: rcptTo,
1768
+ raw,
1769
+ timestamp
1770
+ });
1771
+ },
1772
+ async reply(message) {
1773
+ recordSentEmail({
1774
+ type: "reply",
1775
+ from: message.from ?? options.to,
1776
+ to: message.to ?? options.from,
1777
+ raw: getRecordedRawContent(message.raw),
1778
+ timestamp
1779
+ });
1780
+ }
1781
+ }, runtimeEnv, ctx);
1782
+ await runWithEventContext(emailEvent, () => emailHandler(emailEvent));
1783
+ await Promise.all(waitUntilPromises);
1784
+ return new Response(JSON.stringify({
1785
+ ok: true,
1786
+ from: options.from,
1787
+ to: options.to
1788
+ }), { headers: { "Content-Type": "application/json" } });
1789
+ }
1790
+ const url = new URL(`http://localhost:${miniflarePort}/cdn-cgi/handler/email`);
1791
+ url.searchParams.set("from", options.from);
1792
+ url.searchParams.set("to", options.to);
1793
+ return await fetch(url.toString(), {
1794
+ method: "POST",
1795
+ headers: { "Content-Type": "text/plain" },
1796
+ body: raw
1797
+ });
1798
+ }
1799
+ /**
1800
+ * Register a callback for outgoing emails.
1801
+ *
1802
+ * This only fires when the surrounding runtime wiring records outgoing emails.
1803
+ * @returns Unsubscribe function
1804
+ */
1805
+ function onReceive(callback) {
1806
+ emailListeners.push(callback);
1807
+ return () => {
1808
+ emailListeners = emailListeners.filter((cb) => cb !== callback);
1809
+ };
1810
+ }
1811
+ /**
1812
+ * Get all recorded outgoing emails since test context was created
1813
+ */
1814
+ function getSentEmails() {
1815
+ return [...sentEmails];
1816
+ }
1817
+ /**
1818
+ * Clear recorded outgoing email history
1819
+ */
1820
+ function clearSentEmails() {
1821
+ sentEmails = [];
1822
+ }
1823
+ /**
1824
+ * Add a sent email to the history
1825
+ * @internal Called by helper paths that explicitly record outgoing email
1826
+ * (currently direct `forward()`/`reply()` test flows).
1827
+ */
1828
+ function recordSentEmail(email) {
1829
+ sentEmails.push(email);
1830
+ for (const listener of emailListeners) try {
1831
+ listener(email);
1832
+ } catch (error) {
1833
+ console.error("[devflare/test] Email listener error:", error);
1834
+ }
1835
+ }
1836
+ /**
1837
+ * Reset email state
1838
+ * @internal Called when test context is disposed
1839
+ */
1840
+ function resetEmailState() {
1841
+ miniflarePort = 8787;
1842
+ emailHandlerPath = null;
1843
+ configDir$4 = null;
1844
+ testEnvGetter$4 = null;
1845
+ emailListeners = [];
1846
+ sentEmails = [];
1847
+ }
1848
+ const email = {
1849
+ send: send$1,
1850
+ onReceive,
1851
+ getSentEmails,
1852
+ clearSentEmails
1853
+ };
1854
+ //#endregion
1855
+ //#region src/test/queue.ts
1856
+ let queueHandlerPath = null;
1857
+ let configDir$3 = null;
1858
+ let testEnvGetter$3 = null;
1859
+ /**
1860
+ * Configure the queue test helper
1861
+ * @internal Called by createTestContext to set up handler path and env
1862
+ */
1863
+ function configureQueue(options) {
1864
+ queueHandlerPath = options.handlerPath;
1865
+ configDir$3 = options.configDir;
1866
+ testEnvGetter$3 = options.getEnv;
1867
+ }
1868
+ /**
1869
+ * Reset queue helper state
1870
+ * @internal Called when test context is disposed
1871
+ */
1872
+ function resetQueueState() {
1873
+ queueHandlerPath = null;
1874
+ configDir$3 = null;
1875
+ testEnvGetter$3 = null;
1876
+ }
1877
+ const EMPTY_QUEUE_METADATA = { metrics: {
1878
+ backlogCount: 0,
1879
+ backlogBytes: 0
1880
+ } };
1881
+ /**
1882
+ * Create a mock Message object that tracks ack/retry/noRetry calls
1883
+ */
1884
+ function createMessage(options) {
1885
+ const id = options.id ?? crypto.randomUUID();
1886
+ const timestamp = options.timestamp ?? /* @__PURE__ */ new Date();
1887
+ const attempts = options.attempts ?? 1;
1888
+ let state = "pending";
1889
+ return {
1890
+ id,
1891
+ timestamp,
1892
+ body: options.body,
1893
+ attempts,
1894
+ ack() {
1895
+ state = "acked";
1896
+ },
1897
+ retry(opts) {
1898
+ state = "retried";
1899
+ },
1900
+ retryAll() {
1901
+ state = "retried";
1902
+ },
1903
+ get _state() {
1904
+ return state;
1905
+ }
1906
+ };
1907
+ }
1908
+ /**
1909
+ * Create a mock MessageBatch from an array of messages
1910
+ */
1911
+ function createMessageBatch(messages) {
1912
+ return {
1913
+ queue: "test-queue",
1914
+ metadata: EMPTY_QUEUE_METADATA,
1915
+ messages,
1916
+ ackAll() {
1917
+ for (const msg of messages) msg.ack();
1918
+ },
1919
+ retryAll() {
1920
+ for (const msg of messages) msg.retry();
1921
+ }
1922
+ };
1923
+ }
1924
+ /**
1925
+ * Trigger the queue handler with a batch of messages.
1926
+ * This directly invokes the queue handler function from your config.
1927
+ *
1928
+ * @param messages - Array of message options or bodies
1929
+ * @returns Result object with acked/retried/failed message IDs
1930
+ *
1931
+ * @example
1932
+ * ```ts
1933
+ * // Trigger with message bodies (IDs auto-generated)
1934
+ * const result = await cf.queue.trigger([
1935
+ * { type: 'process', data: { x: 1 } },
1936
+ * { type: 'cleanup', data: {} }
1937
+ * ])
1938
+ *
1939
+ * // Trigger with full message options
1940
+ * const result = await cf.queue.trigger([
1941
+ * { id: 'msg-1', body: { type: 'process' }, attempts: 2 }
1942
+ * ])
1943
+ * ```
1944
+ */
1945
+ async function trigger$2(messages) {
1946
+ if (!queueHandlerPath) throw new Error("Queue handler not configured. Make sure your devflare.config.ts has files.queue set, and the file exists at the specified path (default: src/queue.ts)");
1947
+ if (!configDir$3 || !testEnvGetter$3) throw new Error("Queue helper not initialized. Call createTestContext() before using cf.queue.trigger()");
1948
+ const handlerModule = await import(join$1(configDir$3, queueHandlerPath));
1949
+ const queueHandler = handlerModule.default ?? handlerModule.queue;
1950
+ if (typeof queueHandler !== "function") throw new Error(`Queue handler at "${queueHandlerPath}" must export a default function or named "queue" export.\nExpected: export async function queue(event) { ... }`);
1951
+ const mockMessages = messages.map((msg) => {
1952
+ if (typeof msg === "object" && msg !== null && "body" in msg) return msg;
1953
+ return { body: msg };
1954
+ }).map((opts) => createMessage(opts));
1955
+ const batch = createMessageBatch(mockMessages);
1956
+ const waitUntilPromises = [];
1957
+ const queueEvent = createQueueEvent(batch, testEnvGetter$3(), {
1958
+ waitUntil(promise) {
1959
+ waitUntilPromises.push(promise);
1960
+ },
1961
+ passThroughOnException() {},
1962
+ props: {}
1963
+ });
1964
+ await runWithEventContext(queueEvent, () => queueHandler(queueEvent));
1965
+ await Promise.all(waitUntilPromises);
1966
+ const acked = [];
1967
+ const retried = [];
1968
+ const failed = [];
1969
+ for (const msg of mockMessages) switch (msg._state) {
1970
+ case "acked":
1971
+ acked.push(msg.id);
1972
+ break;
1973
+ case "retried":
1974
+ retried.push(msg.id);
1975
+ break;
1976
+ case "failed":
1977
+ failed.push(msg.id);
1978
+ break;
1979
+ }
1980
+ return {
1981
+ acked,
1982
+ retried,
1983
+ failed,
1984
+ total: mockMessages.length
1985
+ };
1986
+ }
1987
+ /**
1988
+ * Convenience method to trigger the queue handler with a single message.
1989
+ *
1990
+ * @param message - Message body or options
1991
+ * @returns Result object
1992
+ *
1993
+ * @example
1994
+ * ```ts
1995
+ * await cf.queue.send({ type: 'process', data: { x: 1 } })
1996
+ * ```
1997
+ */
1998
+ async function send(message) {
1999
+ return trigger$2([message]);
2000
+ }
2001
+ const queue = {
2002
+ trigger: trigger$2,
2003
+ send
2004
+ };
2005
+ //#endregion
2006
+ //#region src/test/scheduled.ts
2007
+ let scheduledHandlerPath = null;
2008
+ let configDir$2 = null;
2009
+ let testEnvGetter$2 = null;
2010
+ /**
2011
+ * Configure the scheduled test helper
2012
+ * @internal Called by createTestContext to set up handler path and env
2013
+ */
2014
+ function configureScheduled(options) {
2015
+ scheduledHandlerPath = options.handlerPath;
2016
+ configDir$2 = options.configDir;
2017
+ testEnvGetter$2 = options.getEnv;
2018
+ }
2019
+ /**
2020
+ * Reset scheduled helper state
2021
+ * @internal Called when test context is disposed
2022
+ */
2023
+ function resetScheduledState() {
2024
+ scheduledHandlerPath = null;
2025
+ configDir$2 = null;
2026
+ testEnvGetter$2 = null;
2027
+ }
2028
+ /**
2029
+ * Trigger the scheduled handler.
2030
+ * This directly invokes the scheduled handler function from your config.
2031
+ *
2032
+ * @param cronOrOptions - Cron expression string or options object
2033
+ * @returns Result object with success status
2034
+ *
2035
+ * @example
2036
+ * // Trigger with specific cron expression (every 6 hours)
2037
+ * await cf.scheduled.trigger('0 0,6,12,18 * * *')
2038
+ *
2039
+ * @example
2040
+ * // Trigger with options (Weekly Monday at midnight)
2041
+ * await cf.scheduled.trigger({
2042
+ * cron: '0 0 * * 1',
2043
+ * scheduledTime: new Date('2026-01-13T00:00:00Z')
2044
+ * })
2045
+ *
2046
+ * @example
2047
+ * // Trigger without cron (just scheduled time)
2048
+ * await cf.scheduled.trigger()
2049
+ */
2050
+ async function trigger$1(cronOrOptions) {
2051
+ if (!scheduledHandlerPath) throw new Error("Scheduled handler not configured. Make sure your devflare.config.ts has files.scheduled set, and the file exists at the specified path (default: src/scheduled.ts)");
2052
+ if (!configDir$2 || !testEnvGetter$2) throw new Error("Scheduled helper not initialized. Call createTestContext() before using cf.scheduled.trigger()");
2053
+ const options = typeof cronOrOptions === "string" ? { cron: cronOrOptions } : cronOrOptions ?? {};
2054
+ const cron = options.cron ?? "* * * * *";
2055
+ const scheduledTime = options.scheduledTime instanceof Date ? options.scheduledTime.getTime() : options.scheduledTime ?? Date.now();
2056
+ const handlerModule = await import(join$1(configDir$2, scheduledHandlerPath));
2057
+ const scheduledHandler = handlerModule.default ?? handlerModule.scheduled;
2058
+ if (typeof scheduledHandler !== "function") throw new Error(`Scheduled handler at "${scheduledHandlerPath}" must export a default function or named "scheduled" export.\nExpected: export async function scheduled(event) { ... }`);
2059
+ const controller = {
2060
+ scheduledTime,
2061
+ cron,
2062
+ noRetry() {}
2063
+ };
2064
+ const waitUntilPromises = [];
2065
+ const scheduledEvent = createScheduledEvent(controller, testEnvGetter$2(), {
2066
+ waitUntil(promise) {
2067
+ waitUntilPromises.push(promise);
2068
+ },
2069
+ passThroughOnException() {},
2070
+ props: {}
2071
+ });
2072
+ try {
2073
+ await runWithEventContext(scheduledEvent, () => scheduledHandler(scheduledEvent));
2074
+ await Promise.all(waitUntilPromises);
2075
+ return {
2076
+ success: true,
2077
+ cron,
2078
+ scheduledTime
2079
+ };
2080
+ } catch (error) {
2081
+ return {
2082
+ success: false,
2083
+ error: error instanceof Error ? error.message : String(error),
2084
+ cron,
2085
+ scheduledTime
2086
+ };
2087
+ }
2088
+ }
2089
+ const scheduled = { trigger: trigger$1 };
2090
+ //#endregion
2091
+ //#region src/test/tail.ts
2092
+ let tailHandlerPath = null;
2093
+ let configDir$1 = null;
2094
+ let testEnvGetter$1 = null;
2095
+ /**
2096
+ * Configure the tail test helper
2097
+ * @internal Called by createTestContext to set up handler path and env
2098
+ */
2099
+ function configureTail(options) {
2100
+ tailHandlerPath = options.handlerPath;
2101
+ configDir$1 = options.configDir;
2102
+ testEnvGetter$1 = options.getEnv;
2103
+ }
2104
+ /**
2105
+ * Reset tail helper state
2106
+ * @internal Called when test context is disposed
2107
+ */
2108
+ function resetTailState() {
2109
+ tailHandlerPath = null;
2110
+ configDir$1 = null;
2111
+ testEnvGetter$1 = null;
2112
+ }
2113
+ /**
2114
+ * Create a complete TraceItem from options
2115
+ */
2116
+ function createTraceItem(options) {
2117
+ return {
2118
+ scriptName: options.scriptName ?? "test-worker",
2119
+ outcome: options.outcome ?? "ok",
2120
+ eventTimestamp: options.eventTimestamp ?? Date.now(),
2121
+ event: options.event ?? { request: {
2122
+ url: "https://example.com/",
2123
+ method: "GET"
2124
+ } },
2125
+ logs: options.logs ?? [],
2126
+ exceptions: options.exceptions ?? [],
2127
+ diagnosticsChannelEvents: options.diagnosticsChannelEvents ?? [],
2128
+ scriptVersion: options.scriptVersion ?? { id: "test-version" },
2129
+ dispatchNamespace: options.dispatchNamespace,
2130
+ scriptTags: options.scriptTags ?? []
2131
+ };
2132
+ }
2133
+ /**
2134
+ * Trigger the tail handler with trace items.
2135
+ * This directly invokes the auto-detected `src/tail.ts` handler, or another
2136
+ * tail handler that has already been configured internally.
2137
+ *
2138
+ * @param items - Array of trace items or trace item options
2139
+ * @returns Result object with success status
2140
+ *
2141
+ * @example
2142
+ * ```ts
2143
+ * // Trigger with full trace items
2144
+ * await cf.tail.trigger([
2145
+ * {
2146
+ * scriptName: 'my-worker',
2147
+ * outcome: 'ok',
2148
+ * logs: [{ level: 'log', message: ['Request processed'], timestamp: Date.now() }]
2149
+ * }
2150
+ * ])
2151
+ *
2152
+ * // Trigger with minimal options (defaults applied)
2153
+ * await cf.tail.trigger([
2154
+ * { logs: [{ level: 'error', message: ['Something failed'], timestamp: Date.now() }] }
2155
+ * ])
2156
+ * ```
2157
+ */
2158
+ async function trigger(items) {
2159
+ if (!tailHandlerPath) throw new Error("Tail handler not configured. Add a src/tail.ts file exporting tail(), or configure a tail handler before calling cf.tail.trigger().");
2160
+ if (!configDir$1 || !testEnvGetter$1) throw new Error("Tail helper not initialized. Call createTestContext() before using cf.tail.trigger()");
2161
+ const traceItems = items.map((item) => {
2162
+ if ("eventTimestamp" in item && "outcome" in item && "scriptName" in item) return item;
2163
+ return createTraceItem(item);
2164
+ });
2165
+ const handlerModule = await import(join$1(configDir$1, tailHandlerPath));
2166
+ const defaultExport = handlerModule.default;
2167
+ const tailHandler = typeof defaultExport === "function" ? defaultExport : defaultExport && typeof defaultExport.tail === "function" ? defaultExport.tail.bind(defaultExport) : handlerModule.tail;
2168
+ if (typeof tailHandler !== "function") throw new Error(`Tail handler at "${tailHandlerPath}" must export a default function or named "tail" export.\nExpected: export async function tail(event) { ... } or export default { tail(events, env, ctx) { ... } }`);
2169
+ const waitUntilPromises = [];
2170
+ const ctx = {
2171
+ waitUntil(promise) {
2172
+ waitUntilPromises.push(promise);
2173
+ },
2174
+ passThroughOnException() {},
2175
+ props: {}
2176
+ };
2177
+ const env = testEnvGetter$1();
2178
+ const tailEvent = createTailEvent(traceItems, env, ctx);
2179
+ try {
2180
+ await runWithEventContext(tailEvent, () => tailHandler.length >= 2 ? tailHandler(traceItems, env, ctx) : tailHandler(tailEvent, env, ctx));
2181
+ await Promise.all(waitUntilPromises);
2182
+ return {
2183
+ success: true,
2184
+ itemCount: traceItems.length
2185
+ };
2186
+ } catch (error) {
2187
+ return {
2188
+ success: false,
2189
+ error: error instanceof Error ? error.message : String(error),
2190
+ itemCount: traceItems.length
2191
+ };
2192
+ }
2193
+ }
2194
+ /**
2195
+ * Create a TraceItem with sensible defaults.
2196
+ * Useful for building test data.
2197
+ *
2198
+ * @param options - Partial trace item options
2199
+ * @returns Complete TraceItem
2200
+ */
2201
+ function create(options = {}) {
2202
+ return createTraceItem(options);
2203
+ }
2204
+ const tail = {
2205
+ trigger,
2206
+ create
2207
+ };
2208
+ //#endregion
2209
+ //#region src/test/worker.ts
2210
+ let fetchHandlerPath = null;
2211
+ let configDir = null;
2212
+ let testEnvGetter = null;
2213
+ let fileRoutes = [];
2214
+ /**
2215
+ * Configure the worker test helper
2216
+ * @internal Called by createTestContext to set up handler path and env
2217
+ */
2218
+ function configureWorker(options) {
2219
+ fetchHandlerPath = options.handlerPath;
2220
+ fileRoutes = options.routes ?? [];
2221
+ configDir = options.configDir;
2222
+ testEnvGetter = options.getEnv;
2223
+ }
2224
+ /**
2225
+ * Reset worker helper state
2226
+ * @internal Called when test context is disposed
2227
+ */
2228
+ function resetWorkerState() {
2229
+ fetchHandlerPath = null;
2230
+ fileRoutes = [];
2231
+ configDir = null;
2232
+ testEnvGetter = null;
2233
+ }
2234
+ /**
2235
+ * Trigger the fetch handler with a request.
2236
+ * This directly invokes the fetch handler function from your config.
2237
+ *
2238
+ * @param request - Request object or URL string
2239
+ * @param options - Optional fetch options (method, headers, body)
2240
+ * @returns Response from the handler
2241
+ *
2242
+ * @example
2243
+ * ```ts
2244
+ * // With Request object
2245
+ * const response = await cf.worker.fetch(new Request('http://localhost/api'))
2246
+ *
2247
+ * // With URL string
2248
+ * const response = await cf.worker.fetch('http://localhost/api')
2249
+ *
2250
+ * // With options
2251
+ * const response = await cf.worker.fetch('/api/users', {
2252
+ * method: 'POST',
2253
+ * body: { name: 'Alice' }
2254
+ * })
2255
+ * ```
2256
+ */
2257
+ async function fetch$1(request, options) {
2258
+ if (!fetchHandlerPath && fileRoutes.length === 0) throw new Error("Fetch handler not configured. Make sure your devflare.config.ts has files.fetch set or a routes directory is available, and that the corresponding files exist (defaults: src/fetch.ts and src/routes/**).");
2259
+ if (!configDir || !testEnvGetter) throw new Error("Worker helper not initialized. Call createTestContext() before using cf.worker.fetch()");
2260
+ const workerConfigDir = configDir;
2261
+ const getEnv = testEnvGetter;
2262
+ let req;
2263
+ if (typeof request === "string") {
2264
+ const url = request.startsWith("http") ? request : `http://localhost${request.startsWith("/") ? "" : "/"}${request}`;
2265
+ const headers = new Headers(options?.headers);
2266
+ let body;
2267
+ if (options?.body !== void 0) if (typeof options.body === "string") body = options.body;
2268
+ else {
2269
+ body = JSON.stringify(options.body);
2270
+ if (!headers.has("Content-Type")) headers.set("Content-Type", "application/json");
2271
+ }
2272
+ req = new Request(url, {
2273
+ method: options?.method ?? "GET",
2274
+ headers,
2275
+ body
2276
+ });
2277
+ } else req = request;
2278
+ const handlerModule = fetchHandlerPath ? await import(join$1(workerConfigDir, fetchHandlerPath)) : {};
2279
+ const routeModules = await Promise.all(fileRoutes.map(async (route) => {
2280
+ return {
2281
+ ...route,
2282
+ module: await import(join$1(workerConfigDir, route.filePath))
2283
+ };
2284
+ }));
2285
+ const hasMethodHandler = [
2286
+ "GET",
2287
+ "HEAD",
2288
+ "POST",
2289
+ "PUT",
2290
+ "PATCH",
2291
+ "DELETE",
2292
+ "OPTIONS",
2293
+ "ALL"
2294
+ ].some((method) => {
2295
+ return typeof handlerModule[method] === "function" || typeof handlerModule.default?.[method] === "function";
2296
+ });
2297
+ if (!resolveFetchHandler(handlerModule) && !hasMethodHandler && routeModules.length === 0) throw new Error(`Fetch handler at "${fetchHandlerPath}" must export one of:\n- request-wide \"handle\" middleware\n- named \"fetch\"\n- default fetch handler\n- HTTP method exports such as \"GET\" or \"POST\"`);
2298
+ const waitUntilPromises = [];
2299
+ const ctx = {
2300
+ waitUntil(promise) {
2301
+ waitUntilPromises.push(promise);
2302
+ },
2303
+ passThroughOnException() {},
2304
+ props: {}
2305
+ };
2306
+ const env = getEnv();
2307
+ const initialRouteMatch = routeModules.length > 0 ? matchFetchRoute(routeModules, req) : null;
2308
+ const fetchEvent = createFetchEvent(req, env, ctx, { params: initialRouteMatch?.params ?? {} });
2309
+ return await runWithEventContext(fetchEvent, () => invokeFetchModule(handlerModule, fetchEvent, routeModules.length > 0 ? createRouteResolve(routeModules, fetchEvent) : void 0));
2310
+ }
2311
+ /**
2312
+ * Shorthand for GET requests
2313
+ */
2314
+ async function get(path, headers) {
2315
+ return fetch$1(path, {
2316
+ method: "GET",
2317
+ headers
2318
+ });
2319
+ }
2320
+ /**
2321
+ * Shorthand for POST requests with JSON body
2322
+ */
2323
+ async function post(path, body, headers) {
2324
+ return fetch$1(path, {
2325
+ method: "POST",
2326
+ body,
2327
+ headers
2328
+ });
2329
+ }
2330
+ /**
2331
+ * Shorthand for PUT requests with JSON body
2332
+ */
2333
+ async function put(path, body, headers) {
2334
+ return fetch$1(path, {
2335
+ method: "PUT",
2336
+ body,
2337
+ headers
2338
+ });
2339
+ }
2340
+ /**
2341
+ * Shorthand for DELETE requests
2342
+ */
2343
+ async function del(path, headers) {
2344
+ return fetch$1(path, {
2345
+ method: "DELETE",
2346
+ headers
2347
+ });
2348
+ }
2349
+ /**
2350
+ * Shorthand for PATCH requests with JSON body
2351
+ */
2352
+ async function patch(path, body, headers) {
2353
+ return fetch$1(path, {
2354
+ method: "PATCH",
2355
+ body,
2356
+ headers
2357
+ });
2358
+ }
2359
+ const worker = {
2360
+ fetch: fetch$1,
2361
+ get,
2362
+ post,
2363
+ put,
2364
+ delete: del,
2365
+ patch
2366
+ };
2367
+ //#endregion
2368
+ //#region src/test/simple-context-env.ts
2369
+ /**
2370
+ * Wire up every per-surface handler helper (queue / scheduled / worker /
2371
+ * tail / email) with the same `configDir` + `getEnv` accessor.
2372
+ */
2373
+ function configureSurfaceHandlers(input) {
2374
+ const { handlerPaths, configDir, activePort, getEnv } = input;
2375
+ configureQueue({
2376
+ handlerPath: handlerPaths.queue,
2377
+ configDir,
2378
+ getEnv
2379
+ });
2380
+ configureScheduled({
2381
+ handlerPath: handlerPaths.scheduled,
2382
+ configDir,
2383
+ getEnv
2384
+ });
2385
+ configureWorker({
2386
+ handlerPath: handlerPaths.fetch,
2387
+ routes: handlerPaths.routes?.routes.map((route) => ({
2388
+ filePath: route.filePath,
2389
+ routePath: route.routePath,
2390
+ segments: route.segments
2391
+ })) ?? [],
2392
+ configDir,
2393
+ getEnv
2394
+ });
2395
+ configureTail({
2396
+ handlerPath: handlerPaths.tail,
2397
+ configDir,
2398
+ getEnv
2399
+ });
2400
+ configureEmail({
2401
+ port: activePort,
2402
+ handlerPath: handlerPaths.email,
2403
+ configDir,
2404
+ getEnv
2405
+ });
2406
+ }
2407
+ /**
2408
+ * Build the bridge-backed env accessor used by single-worker test contexts.
2409
+ *
2410
+ * Resolution order, given a property access:
2411
+ * 1. Remote bindings (AI/Vectorize/vars/sendEmail) registered up-front.
2412
+ * 2. For non-DO/non-service hints: Miniflare binding (raw KV/D1/R2/etc).
2413
+ * 3. Bridge env proxy (for everything else, including DOs and services).
2414
+ * 4. Final fallback: Miniflare binding (when the hint preferred bridge but
2415
+ * the proxy did not surface it).
2416
+ */
2417
+ function createBridgeEnvAccessor(state, hints, shouldPreferBridgeBinding) {
2418
+ return new Proxy({}, {
2419
+ get(_, prop) {
2420
+ const hint = hints[prop];
2421
+ const prefersBridgeBinding = shouldPreferBridgeBinding(hint);
2422
+ if (state.remoteBindings && prop in state.remoteBindings) return state.remoteBindings[prop];
2423
+ if (!prefersBridgeBinding && state.miniflareBindings && prop in state.miniflareBindings) return state.miniflareBindings[prop];
2424
+ if (state.envProxy) return state.envProxy[prop];
2425
+ if (prefersBridgeBinding && state.miniflareBindings && prop in state.miniflareBindings) return state.miniflareBindings[prop];
2426
+ },
2427
+ has(_, prop) {
2428
+ return Boolean(state.remoteBindings && prop in state.remoteBindings || state.miniflareBindings && prop in state.miniflareBindings || state.envProxy !== null);
2429
+ }
2430
+ });
2431
+ }
2432
+ /**
2433
+ * Build the simpler env accessor used by multi-worker test contexts (no
2434
+ * bridge-backed proxy: services + DOs go through Miniflare's own bindings).
2435
+ */
2436
+ function createMultiWorkerEnvAccessor(state) {
2437
+ return new Proxy({}, {
2438
+ get(_, prop) {
2439
+ if (state.remoteBindings && prop in state.remoteBindings) return state.remoteBindings[prop];
2440
+ if (state.miniflareBindings && prop in state.miniflareBindings) return state.miniflareBindings[prop];
2441
+ },
2442
+ has(_, prop) {
2443
+ return Boolean(state.remoteBindings && prop in state.remoteBindings || state.miniflareBindings && prop in state.miniflareBindings);
2444
+ }
2445
+ });
2446
+ }
2447
+ //#endregion
2448
+ //#region src/test/simple-context-handlers.ts
2449
+ const DEFAULT_FETCH_PATH = "src/fetch.ts";
2450
+ const DEFAULT_QUEUE_PATH = "src/queue.ts";
2451
+ const DEFAULT_SCHEDULED_PATH = "src/scheduled.ts";
2452
+ const DEFAULT_EMAIL_PATH = "src/email.ts";
2453
+ const DEFAULT_TAIL_PATH = "src/tail.ts";
2454
+ async function resolveHandlerPath(configDir, configValue, defaultPath) {
2455
+ if (typeof configValue === "string") return configValue;
2456
+ if (configValue === false) return null;
2457
+ const defaultAbsolute = join$1(configDir, defaultPath);
2458
+ try {
2459
+ await (await import("fs/promises")).access(defaultAbsolute);
2460
+ return defaultPath;
2461
+ } catch {
2462
+ return null;
2463
+ }
2464
+ }
2465
+ async function resolveHandlerPaths(configDir, config) {
2466
+ const [fetch, queue, scheduled, email, tail, routes] = await Promise.all([
2467
+ resolveHandlerPath(configDir, config.files?.fetch, DEFAULT_FETCH_PATH),
2468
+ resolveHandlerPath(configDir, config.files?.queue, DEFAULT_QUEUE_PATH),
2469
+ resolveHandlerPath(configDir, config.files?.scheduled, DEFAULT_SCHEDULED_PATH),
2470
+ resolveHandlerPath(configDir, config.files?.email, DEFAULT_EMAIL_PATH),
2471
+ resolveHandlerPath(configDir, config.files?.tail, DEFAULT_TAIL_PATH),
2472
+ discoverRoutes(configDir, config)
2473
+ ]);
2474
+ return {
2475
+ fetch,
2476
+ queue,
2477
+ scheduled,
2478
+ email,
2479
+ tail,
2480
+ routes
2481
+ };
2482
+ }
2483
+ //#endregion
2484
+ //#region src/test/containers.ts
2485
+ const realContainerCommandRunner = { async exec(command, args, options = {}) {
2486
+ try {
2487
+ const result = await execa(command, args, {
2488
+ cwd: options.cwd,
2489
+ env: options.env,
2490
+ reject: false,
2491
+ timeout: options.timeoutMs ?? 15e3
2492
+ });
2493
+ return {
2494
+ exitCode: result.exitCode ?? 0,
2495
+ stdout: String(result.stdout ?? ""),
2496
+ stderr: String(result.stderr ?? "")
2497
+ };
2498
+ } catch (error) {
2499
+ const err = error;
2500
+ return {
2501
+ exitCode: err.exitCode ?? 1,
2502
+ stdout: String(err.stdout ?? ""),
2503
+ stderr: String(err.stderr ?? err.message ?? "Container command failed")
2504
+ };
2505
+ }
2506
+ } };
2507
+ async function detectContainerEngine(options = {}) {
2508
+ const runner = options.runner ?? realContainerCommandRunner;
2509
+ const candidates = options.engine && options.engine !== "auto" ? [options.engine] : ["docker", "podman"];
2510
+ const checked = [];
2511
+ for (const engine of candidates) {
2512
+ let result;
2513
+ try {
2514
+ result = await runner.exec(engine, ["info"], { timeoutMs: 1e4 });
2515
+ } catch (error) {
2516
+ result = {
2517
+ exitCode: 1,
2518
+ stdout: "",
2519
+ stderr: error instanceof Error ? error.message : String(error)
2520
+ };
2521
+ }
2522
+ if (result.exitCode === 0) {
2523
+ checked.push({
2524
+ engine,
2525
+ available: true
2526
+ });
2527
+ return {
2528
+ available: true,
2529
+ engine,
2530
+ checked
2531
+ };
2532
+ }
2533
+ checked.push({
2534
+ engine,
2535
+ available: false,
2536
+ reason: formatCommandFailure(result)
2537
+ });
2538
+ }
2539
+ return {
2540
+ available: false,
2541
+ reason: checked.map((check) => `${check.engine}: ${check.reason ?? "not available"}`).join("; "),
2542
+ checked
2543
+ };
2544
+ }
2545
+ async function getContainerSkipReason(options = {}) {
2546
+ if (!isTruthyEnvFlag((options.env ?? process.env).DEVFLARE_CONTAINER_TESTS)) return "Container tests require DEVFLARE_CONTAINER_TESTS=1 because they launch local Docker/Podman containers.";
2547
+ const status = await detectContainerEngine(options);
2548
+ if (!status.available) return `No reachable Docker or Podman engine found: ${status.reason}`;
2549
+ return null;
2550
+ }
2551
+ function createContainerManager(options = {}) {
2552
+ const active = /* @__PURE__ */ new Set();
2553
+ const getRunner = () => options.runner ?? realContainerCommandRunner;
2554
+ const manager = {
2555
+ detectEngine(engineOptions = {}) {
2556
+ return detectContainerEngine({
2557
+ engine: engineOptions.engine ?? options.engine,
2558
+ runner: engineOptions.runner ?? getRunner()
2559
+ });
2560
+ },
2561
+ async start(className, startOptions) {
2562
+ const resolved = await resolveContainerStart(className, startOptions, options.cwd ?? getCallerDirectory());
2563
+ const engine = getAvailableContainerEngine(await manager.detectEngine(), className);
2564
+ const runner = getRunner();
2565
+ const offline = startOptions.offline ?? true;
2566
+ const prepared = await prepareImage({
2567
+ engine,
2568
+ runner,
2569
+ image: resolved.image,
2570
+ className,
2571
+ configDir: resolved.configDir,
2572
+ imageBuildContext: resolved.config?.imageBuildContext,
2573
+ offline
2574
+ });
2575
+ const host = startOptions.host ?? "127.0.0.1";
2576
+ const hostPort = startOptions.hostPort ?? await (options.allocatePort ?? getAvailablePort)();
2577
+ const containerName = makeContainerName(className, startOptions.instance);
2578
+ const runArgs = buildRunArgs({
2579
+ name: containerName,
2580
+ className,
2581
+ image: prepared.image,
2582
+ host,
2583
+ hostPort,
2584
+ containerPort: startOptions.port,
2585
+ envVars: startOptions.envVars,
2586
+ entrypoint: startOptions.entrypoint,
2587
+ command: startOptions.command
2588
+ });
2589
+ const runResult = await runner.exec(engine, runArgs, {
2590
+ cwd: resolved.configDir,
2591
+ env: options.env ?? process.env
2592
+ });
2593
+ if (runResult.exitCode !== 0) throw new Error(`Failed to start Devflare container "${className}": ${formatCommandFailure(runResult)}`);
2594
+ const instance = new LocalDevflareContainer({
2595
+ id: runResult.stdout.trim() || containerName,
2596
+ name: containerName,
2597
+ className,
2598
+ engine,
2599
+ host,
2600
+ hostPort,
2601
+ port: startOptions.port,
2602
+ runner,
2603
+ fetchImpl: options.fetch ?? fetch,
2604
+ onDispose: (container) => active.delete(container)
2605
+ });
2606
+ active.add(instance);
2607
+ await waitForContainerReadiness(instance, startOptions, options.waitForPort ?? waitForTcpPort);
2608
+ return instance;
2609
+ },
2610
+ async stopAll() {
2611
+ await Promise.all([...active].map((container) => container.stop()));
2612
+ }
2613
+ };
2614
+ return manager;
2615
+ }
2616
+ const defaultContainerManager = createContainerManager();
2617
+ const containers = defaultContainerManager;
2618
+ async function stopActiveContainers() {
2619
+ await defaultContainerManager.stopAll();
2620
+ }
2621
+ function getAvailableContainerEngine(status, className) {
2622
+ if (!status.available) throw new Error(`Cannot start Devflare container "${className}": ${status.reason}`);
2623
+ return status.engine;
2624
+ }
2625
+ async function waitForContainerReadiness(instance, options, waitForPort) {
2626
+ if (!(options.waitForReady ?? true)) return;
2627
+ try {
2628
+ await waitForPort(instance.host, instance.hostPort, options.readyTimeoutMs ?? 2e4);
2629
+ } catch (error) {
2630
+ await instance.destroy();
2631
+ throw error;
2632
+ }
2633
+ }
2634
+ async function resolveContainerStart(className, options, cwd) {
2635
+ if (options.image) return {
2636
+ configDir: cwd,
2637
+ image: options.image
2638
+ };
2639
+ const { config, configDir } = await loadContainerConfig(options.configPath, cwd);
2640
+ const container = config.containers?.find((candidate) => candidate.className === className || candidate.name === className);
2641
+ if (!container) throw new Error(`Container "${className}" was not found in devflare config.`);
2642
+ return {
2643
+ configDir,
2644
+ image: container.image,
2645
+ config: container
2646
+ };
2647
+ }
2648
+ async function loadContainerConfig(configPath, cwd) {
2649
+ const absolutePath = configPath ? resolve(cwd, configPath) : await findNearestConfig(cwd);
2650
+ if (!absolutePath) throw new Error(`Could not find a devflare config file for container lookup. Searched upward from: ${cwd}`);
2651
+ const configDir = dirname(absolutePath);
2652
+ return {
2653
+ config: await applyLocalDevVarsToConfig(await resolveConfigEnvVars(await loadConfig({
2654
+ cwd: configDir,
2655
+ configFile: basename(absolutePath)
2656
+ }), {
2657
+ cwd: configDir,
2658
+ configPath: absolutePath,
2659
+ mode: "dev"
2660
+ }), {
2661
+ cwd: configDir,
2662
+ configPath: absolutePath
2663
+ }),
2664
+ configDir
2665
+ };
2666
+ }
2667
+ async function prepareImage(options) {
2668
+ if (looksLikeLocalPath(options.image)) return { image: await buildLocalImage(options) };
2669
+ if ((await options.runner.exec(options.engine, [
2670
+ "image",
2671
+ "inspect",
2672
+ options.image
2673
+ ], { cwd: options.configDir })).exitCode === 0) return { image: options.image };
2674
+ if (options.offline) throw new Error(`Container image "${options.image}" is not present locally. Devflare container tests are offline-first; pull/build the image ahead of time or pass offline: false.`);
2675
+ const pull = await options.runner.exec(options.engine, ["pull", options.image], { cwd: options.configDir });
2676
+ if (pull.exitCode !== 0) throw new Error(`Failed to pull container image "${options.image}": ${formatCommandFailure(pull)}`);
2677
+ return { image: options.image };
2678
+ }
2679
+ async function buildLocalImage(options) {
2680
+ const imagePath = resolve(options.configDir, options.image);
2681
+ const imageStat = await stat(imagePath);
2682
+ const dockerfile = imageStat.isDirectory() ? join(imagePath, "Dockerfile") : imagePath;
2683
+ const context = resolve(options.configDir, options.imageBuildContext ?? (imageStat.isDirectory() ? options.image : dirname(options.image)));
2684
+ if (!existsSync(dockerfile)) throw new Error(`Container Dockerfile does not exist: ${dockerfile}`);
2685
+ const tag = makeLocalImageTag(options.className, options.configDir, options.image);
2686
+ const args = [
2687
+ "build",
2688
+ ...options.offline ? [getOfflineBuildPullArg(options.engine)] : [],
2689
+ "-t",
2690
+ tag,
2691
+ "-f",
2692
+ dockerfile,
2693
+ context
2694
+ ];
2695
+ const result = await options.runner.exec(options.engine, args, { cwd: options.configDir });
2696
+ if (result.exitCode !== 0) throw new Error(`Failed to build local container image "${options.image}": ${formatCommandFailure(result)}`);
2697
+ return tag;
2698
+ }
2699
+ function buildRunArgs(options) {
2700
+ const args = [
2701
+ "run",
2702
+ "-d",
2703
+ "--name",
2704
+ options.name,
2705
+ "--label",
2706
+ "devflare.managed=true",
2707
+ "--label",
2708
+ `devflare.container.class=${options.className}`,
2709
+ "-p",
2710
+ `${options.host}:${options.hostPort}:${options.containerPort}`
2711
+ ];
2712
+ for (const [key, value] of Object.entries(options.envVars ?? {})) args.push("-e", `${key}=${value}`);
2713
+ const command = [...options.command ?? []];
2714
+ if (options.entrypoint && options.entrypoint.length > 0) {
2715
+ args.push("--entrypoint", options.entrypoint[0]);
2716
+ command.unshift(...options.entrypoint.slice(1));
2717
+ }
2718
+ args.push(options.image, ...command);
2719
+ return args;
2720
+ }
2721
+ var LocalDevflareContainer = class {
2722
+ id;
2723
+ name;
2724
+ className;
2725
+ engine;
2726
+ host;
2727
+ hostPort;
2728
+ port;
2729
+ runner;
2730
+ fetchImpl;
2731
+ onDispose;
2732
+ disposed = false;
2733
+ constructor(options) {
2734
+ this.id = options.id;
2735
+ this.name = options.name;
2736
+ this.className = options.className;
2737
+ this.engine = options.engine;
2738
+ this.host = options.host;
2739
+ this.hostPort = options.hostPort;
2740
+ this.port = options.port;
2741
+ this.runner = options.runner;
2742
+ this.fetchImpl = options.fetchImpl;
2743
+ this.onDispose = options.onDispose;
2744
+ }
2745
+ fetch(input, init) {
2746
+ return fetchWithStartupRetries(this.fetchImpl, this.toLocalRequest(input, init));
2747
+ }
2748
+ async logs() {
2749
+ const result = await this.runner.exec(this.engine, ["logs", this.name]);
2750
+ if (result.exitCode !== 0) throw new Error(`Failed to read logs for Devflare container "${this.name}": ${formatCommandFailure(result)}`);
2751
+ return result.stdout;
2752
+ }
2753
+ async getState() {
2754
+ const result = await this.runner.exec(this.engine, [
2755
+ "inspect",
2756
+ "--format",
2757
+ "{{json .State}}",
2758
+ this.name
2759
+ ]);
2760
+ if (result.exitCode !== 0) throw new Error(`Failed to inspect Devflare container "${this.name}": ${formatCommandFailure(result)}`);
2761
+ const parsed = JSON.parse(result.stdout || "{}");
2762
+ return {
2763
+ status: parsed.Status ?? (parsed.Running ? "running" : "stopped"),
2764
+ running: Boolean(parsed.Running),
2765
+ ...typeof parsed.ExitCode === "number" && { exitCode: parsed.ExitCode }
2766
+ };
2767
+ }
2768
+ async stop() {
2769
+ if (this.disposed) return;
2770
+ try {
2771
+ await this.runner.exec(this.engine, ["stop", this.name]);
2772
+ } finally {
2773
+ await this.runner.exec(this.engine, [
2774
+ "rm",
2775
+ "-f",
2776
+ this.name
2777
+ ]);
2778
+ this.disposed = true;
2779
+ this.onDispose(this);
2780
+ }
2781
+ }
2782
+ async destroy() {
2783
+ if (this.disposed) return;
2784
+ await this.runner.exec(this.engine, [
2785
+ "rm",
2786
+ "-f",
2787
+ this.name
2788
+ ]);
2789
+ this.disposed = true;
2790
+ this.onDispose(this);
2791
+ }
2792
+ toLocalRequest(input, init) {
2793
+ const base = `http://${this.host}:${this.hostPort}/`;
2794
+ if (input instanceof Request) {
2795
+ const source = init ? new Request(input, init) : input;
2796
+ const sourceUrl = new URL(source.url);
2797
+ const localUrl = new URL(`${sourceUrl.pathname}${sourceUrl.search}`, base);
2798
+ return new Request(localUrl, {
2799
+ method: source.method,
2800
+ headers: source.headers,
2801
+ body: source.body,
2802
+ redirect: source.redirect,
2803
+ signal: source.signal
2804
+ });
2805
+ }
2806
+ const sourceUrl = new URL(String(input), base);
2807
+ const localUrl = new URL(`${sourceUrl.pathname}${sourceUrl.search}`, base);
2808
+ return new Request(localUrl, init);
2809
+ }
2810
+ };
2811
+ async function fetchWithStartupRetries(fetchImpl, request) {
2812
+ if (!canRetryRequest(request)) return fetchImpl(request);
2813
+ const deadline = Date.now() + 5e3;
2814
+ let lastError;
2815
+ while (Date.now() < deadline) try {
2816
+ return await fetchImpl(request.clone());
2817
+ } catch (error) {
2818
+ if (!isTransientContainerFetchError(error)) throw error;
2819
+ lastError = error;
2820
+ await setTimeout$1(100);
2821
+ }
2822
+ throw lastError;
2823
+ }
2824
+ function canRetryRequest(request) {
2825
+ return (request.method === "GET" || request.method === "HEAD") && request.body === null;
2826
+ }
2827
+ function isTransientContainerFetchError(error) {
2828
+ const value = error;
2829
+ const code = value.code ?? value.cause?.code;
2830
+ if (code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE" || code === "UND_ERR_SOCKET") return true;
2831
+ return typeof value.message === "string" && (value.message.includes("socket connection was closed") || value.message.includes("fetch failed"));
2832
+ }
2833
+ async function waitForTcpPort(host, port, timeoutMs) {
2834
+ const start = Date.now();
2835
+ let lastError;
2836
+ while (Date.now() - start < timeoutMs) try {
2837
+ await connectOnce(host, port);
2838
+ return;
2839
+ } catch (error) {
2840
+ lastError = error;
2841
+ await setTimeout$1(100);
2842
+ }
2843
+ const message = lastError instanceof Error ? lastError.message : "timed out";
2844
+ throw new Error(`Timed out waiting for container port ${host}:${port}: ${message}`);
2845
+ }
2846
+ function connectOnce(host, port) {
2847
+ return new Promise((resolveConnection, rejectConnection) => {
2848
+ const socket = createConnection({
2849
+ host,
2850
+ port
2851
+ });
2852
+ socket.once("connect", () => {
2853
+ socket.destroy();
2854
+ resolveConnection();
2855
+ });
2856
+ socket.once("error", rejectConnection);
2857
+ });
2858
+ }
2859
+ function looksLikeLocalPath(image) {
2860
+ return image === "Dockerfile" || image.startsWith(".") || image.startsWith("/") || image.startsWith("\\") || isAbsolute(image) || image.endsWith("/Dockerfile") || image.endsWith("\\Dockerfile");
2861
+ }
2862
+ function getOfflineBuildPullArg(engine) {
2863
+ return engine === "podman" ? "--pull=never" : "--pull=false";
2864
+ }
2865
+ function makeLocalImageTag(className, configDir, image) {
2866
+ const hash = createHash("sha256").update(`${configDir}:${image}`).digest("hex").slice(0, 12);
2867
+ return `devflare-local-${sanitizeName(className)}:${hash}`;
2868
+ }
2869
+ function makeContainerName(className, instance) {
2870
+ return `devflare-${sanitizeName(className)}-${sanitizeName(instance ?? "default")}-${randomUUID().slice(0, 8)}`;
2871
+ }
2872
+ function sanitizeName(value) {
2873
+ return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "container";
2874
+ }
2875
+ function formatCommandFailure(result) {
2876
+ return (result.stderr || result.stdout || `exit code ${result.exitCode}`).trim();
2877
+ }
2878
+ function isTruthyEnvFlag(value) {
2879
+ return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
2880
+ }
2881
+ //#endregion
2882
+ //#region src/test/simple-context-lifecycle.ts
2883
+ /**
2884
+ * Resolve and load the devflare config for the test context.
2885
+ *
2886
+ * If `configPath` is given, it is interpreted relative to the caller's
2887
+ * directory (the file that invoked `createTestContext()`). Otherwise the
2888
+ * resolver walks upward from the caller's directory looking for a supported
2889
+ * `devflare.config.*` file.
2890
+ */
2891
+ async function resolveTestContextConfig(configPath, callerDir = getCallerDirectory()) {
2892
+ let absolutePath;
2893
+ if (configPath) absolutePath = resolve$1(callerDir, configPath);
2894
+ else {
2895
+ const found = await findNearestConfig(callerDir);
2896
+ if (!found) throw new Error(`Could not find a devflare config file. Searched upward from: ${callerDir}\nExpected one of: devflare.config.ts, devflare.config.mts, devflare.config.js, devflare.config.mjs\nEither create a config file or provide an explicit path: createTestContext('./path/to/config.ts')`);
2897
+ absolutePath = found;
2898
+ }
2899
+ const configDir = dirname$1(absolutePath);
2900
+ const config = await applyLocalDevVarsToConfig(await resolveConfigEnvVars(await loadConfig({
2901
+ cwd: configDir,
2902
+ configFile: absolutePath.split(/[/\\]/).pop()
2903
+ }), {
2904
+ cwd: configDir,
2905
+ configPath: absolutePath,
2906
+ mode: "dev"
2907
+ }), {
2908
+ cwd: configDir,
2909
+ configPath: absolutePath
2910
+ });
2911
+ return {
2912
+ absolutePath,
2913
+ configDir,
2914
+ config
2915
+ };
2916
+ }
2917
+ /**
2918
+ * Build the dispose() function that tears down a test context. Disconnects
2919
+ * the bridge client, disposes Miniflare, clears per-handler global state,
2920
+ * and clears the registered test-context env accessor.
2921
+ */
2922
+ function createDisposeContext(state) {
2923
+ return async () => {
2924
+ if (state.client) {
2925
+ await state.client.disconnect();
2926
+ state.client = null;
2927
+ }
2928
+ if (state.miniflare) {
2929
+ await state.miniflare.dispose();
2930
+ state.miniflare = null;
2931
+ }
2932
+ await disposeLocalWorkerLoaderBindings();
2933
+ await stopActiveContainers();
2934
+ state.envProxy = null;
2935
+ state.transportDecode = null;
2936
+ state.remoteBindings = null;
2937
+ state.miniflareBindings = null;
2938
+ resetQueueState();
2939
+ resetScheduledState();
2940
+ resetWorkerState();
2941
+ resetTailState();
2942
+ resetEmailState();
2943
+ __clearTestContext();
2944
+ };
2945
+ }
2946
+ //#endregion
2947
+ //#region src/test/simple-context-mfconfig.ts
2948
+ /**
2949
+ * Build the seed Miniflare config for an inline (single-worker) bridge.
2950
+ * Pure: no I/O, no closures.
2951
+ */
2952
+ function buildInlineBridgeMfConfig(config, options = {}) {
2953
+ const localWorkerBindings = config.vars ?? {};
2954
+ const localSecretWrappedBindingConfig = options.cwd ? buildLocalSecretWrappedBindingConfig(config, options.cwd) : void 0;
2955
+ const localBindingShimServiceConfig = buildLocalBindingShimServiceConfig(config);
2956
+ const localSecretBindingNames = new Set(localSecretWrappedBindingConfig?.localBindingNames ?? []);
2957
+ const mfConfig = {
2958
+ modules: true,
2959
+ compatibilityDate: config.compatibilityDate ?? "2025-01-01",
2960
+ ...config.compatibilityFlags && { compatibilityFlags: config.compatibilityFlags }
2961
+ };
2962
+ if (config.bindings?.kv) mfConfig.kvNamespaces = Object.keys(config.bindings.kv);
2963
+ if (config.bindings?.r2) mfConfig.r2Buckets = Object.keys(config.bindings.r2);
2964
+ if (config.bindings?.d1) mfConfig.d1Databases = Object.fromEntries(Object.entries(config.bindings.d1).map(([bindingName, bindingConfig]) => {
2965
+ return [bindingName, getLocalD1DatabaseIdentifier(bindingConfig)];
2966
+ }));
2967
+ const hyperdrivesConfig = buildHyperdrivesConfig(config.bindings ?? {});
2968
+ if (hyperdrivesConfig) mfConfig.hyperdrives = hyperdrivesConfig;
2969
+ if (config.bindings?.queues?.producers) {
2970
+ const queueProducers = {};
2971
+ for (const [bindingName, queueName] of Object.entries(config.bindings.queues.producers)) queueProducers[bindingName] = { queueName };
2972
+ mfConfig.queueProducers = queueProducers;
2973
+ }
2974
+ if (config.bindings?.rateLimits) mfConfig.ratelimits = Object.fromEntries(Object.entries(config.bindings.rateLimits).map(([bindingName, binding]) => [bindingName, { simple: {
2975
+ limit: binding.simple.limit,
2976
+ period: binding.simple.period
2977
+ } }]));
2978
+ if (config.bindings?.versionMetadata) mfConfig.versionMetadata = config.bindings.versionMetadata.binding;
2979
+ if (config.bindings?.workerLoaders) mfConfig.workerLoaders = Object.fromEntries(Object.keys(config.bindings.workerLoaders).map((bindingName) => [bindingName, {}]));
2980
+ if (config.bindings?.mtlsCertificates) mfConfig.mtlsCertificates = Object.fromEntries(Object.entries(config.bindings.mtlsCertificates).map(([bindingName, binding]) => {
2981
+ return [bindingName, { certificate_id: normalizeMtlsCertificateBinding(binding).certificateId }];
2982
+ }));
2983
+ if (config.bindings?.dispatchNamespaces) mfConfig.dispatchNamespaces = Object.fromEntries(Object.entries(config.bindings.dispatchNamespaces).map(([bindingName, binding]) => {
2984
+ return [bindingName, { namespace: normalizeDispatchNamespaceBinding(binding).namespace }];
2985
+ }));
2986
+ if (config.bindings?.workflows) mfConfig.workflows = Object.fromEntries(Object.entries(config.bindings.workflows).map(([bindingName, binding]) => {
2987
+ const normalized = normalizeWorkflowBinding(binding);
2988
+ return [bindingName, {
2989
+ name: normalized.name,
2990
+ className: normalized.className,
2991
+ ...normalized.scriptName && { scriptName: normalized.scriptName },
2992
+ ...normalized.limits && { stepLimit: normalized.limits.steps }
2993
+ }];
2994
+ }));
2995
+ if (config.bindings?.pipelines) mfConfig.pipelines = Object.fromEntries(Object.entries(config.bindings.pipelines).map(([bindingName, binding]) => {
2996
+ const normalized = normalizePipelineBinding(binding);
2997
+ return [bindingName, typeof binding === "string" ? normalized.pipeline : { pipeline: normalized.pipeline }];
2998
+ }));
2999
+ if (config.bindings?.images && localBindingShimServiceConfig.localBindingNames.length === 0) {
3000
+ const [entry] = Object.entries(config.bindings.images);
3001
+ if (entry) {
3002
+ const [bindingName, binding] = entry;
3003
+ mfConfig.images = { binding: normalizeImagesBinding(bindingName, binding).binding };
3004
+ }
3005
+ }
3006
+ if (config.bindings?.media && localBindingShimServiceConfig.localBindingNames.length === 0) {
3007
+ const [entry] = Object.entries(config.bindings.media);
3008
+ if (entry) {
3009
+ const [bindingName, binding] = entry;
3010
+ mfConfig.media = { binding: normalizeMediaBinding(bindingName, binding).binding };
3011
+ }
3012
+ }
3013
+ if (config.bindings?.artifacts) mfConfig.artifacts = Object.fromEntries(Object.entries(config.bindings.artifacts).map(([bindingName, binding]) => {
3014
+ return [bindingName, { namespace: normalizeArtifactsBinding(binding).namespace }];
3015
+ }));
3016
+ if (config.bindings?.aiSearchNamespaces) mfConfig.aiSearchNamespaces = Object.fromEntries(Object.entries(config.bindings.aiSearchNamespaces).map(([bindingName, binding]) => [bindingName, { namespace: binding.namespace }]));
3017
+ if (config.bindings?.aiSearch) mfConfig.aiSearchInstances = Object.fromEntries(Object.entries(config.bindings.aiSearch).map(([bindingName, binding]) => [bindingName, { instance_name: binding.instanceName }]));
3018
+ if (config.bindings?.secretsStore) {
3019
+ const secretsStoreEntries = Object.entries(config.bindings.secretsStore).flatMap(([bindingName, binding]) => {
3020
+ if (localSecretBindingNames.has(bindingName)) return [];
3021
+ const normalized = normalizeSecretsStoreBinding(binding, config.secretsStoreId, bindingName);
3022
+ return [[bindingName, {
3023
+ store_id: normalized.storeId,
3024
+ secret_name: normalized.secretName
3025
+ }]];
3026
+ });
3027
+ if (secretsStoreEntries.length > 0) mfConfig.secretsStoreSecrets = Object.fromEntries(secretsStoreEntries);
3028
+ }
3029
+ const wrappedBindings = { ...localSecretWrappedBindingConfig?.wrappedBindings ?? {} };
3030
+ const localBindingWorkers = [...localSecretWrappedBindingConfig?.workers ?? [], ...localBindingShimServiceConfig.workers];
3031
+ if (Object.keys(wrappedBindings).length > 0) mfConfig.wrappedBindings = wrappedBindings;
3032
+ if (localBindingShimServiceConfig.localBindingNames.length > 0) mfConfig.serviceBindings = {
3033
+ ...mfConfig.serviceBindings ?? {},
3034
+ ...localBindingShimServiceConfig.serviceBindings
3035
+ };
3036
+ if (localBindingWorkers.length > 0) mfConfig.__devflareLocalBindingWorkers = localBindingWorkers;
3037
+ if (Object.keys(localWorkerBindings).length > 0) mfConfig.bindings = localWorkerBindings;
3038
+ if (config.bindings?.sendEmail) mfConfig.email = { send_email: Object.entries(config.bindings.sendEmail).map(([name, binding]) => ({
3039
+ name,
3040
+ ...binding.destinationAddress && { destination_address: binding.destinationAddress },
3041
+ ...binding.allowedDestinationAddresses && { allowed_destination_addresses: binding.allowedDestinationAddresses },
3042
+ ...binding.allowedSenderAddresses && { allowed_sender_addresses: binding.allowedSenderAddresses }
3043
+ })) };
3044
+ return mfConfig;
3045
+ }
3046
+ //#endregion
3047
+ //#region src/test/simple-context-multi-worker.ts
3048
+ /**
3049
+ * Convert the in-progress single-worker `mfConfig` into a multi-worker
3050
+ * Miniflare config. Mutates `mfConfig` in place.
3051
+ *
3052
+ * The first worker (the "primary") inherits the bridge gateway script and the
3053
+ * KV/R2/D1/email/DO settings that were set on the top-level mfConfig;
3054
+ * additional workers come from `serviceBindingResolution.workers` and
3055
+ * `doBindingResolution.workers`, deduplicated by name.
3056
+ */
3057
+ function applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBindingResolution) {
3058
+ const primaryDurableObjects = {
3059
+ ...mfConfig.durableObjects || {},
3060
+ ...doBindingResolution?.crossWorkerDOBindings || {}
3061
+ };
3062
+ const primaryWorker = {
3063
+ name: config.name ?? "primary",
3064
+ modules: true,
3065
+ script: mfConfig.script,
3066
+ compatibilityDate: config.compatibilityDate ?? "2025-01-01",
3067
+ ...mfConfig.kvNamespaces && { kvNamespaces: mfConfig.kvNamespaces },
3068
+ ...mfConfig.r2Buckets && { r2Buckets: mfConfig.r2Buckets },
3069
+ ...mfConfig.d1Databases && { d1Databases: mfConfig.d1Databases },
3070
+ ...mfConfig.ratelimits && { ratelimits: mfConfig.ratelimits },
3071
+ ...mfConfig.versionMetadata && { versionMetadata: mfConfig.versionMetadata },
3072
+ ...mfConfig.workerLoaders && { workerLoaders: mfConfig.workerLoaders },
3073
+ ...mfConfig.mtlsCertificates && { mtlsCertificates: mfConfig.mtlsCertificates },
3074
+ ...mfConfig.dispatchNamespaces && { dispatchNamespaces: mfConfig.dispatchNamespaces },
3075
+ ...mfConfig.workflows && { workflows: mfConfig.workflows },
3076
+ ...mfConfig.pipelines && { pipelines: mfConfig.pipelines },
3077
+ ...mfConfig.images && { images: mfConfig.images },
3078
+ ...mfConfig.media && { media: mfConfig.media },
3079
+ ...mfConfig.artifacts && { artifacts: mfConfig.artifacts },
3080
+ ...mfConfig.secretsStoreSecrets && { secretsStoreSecrets: mfConfig.secretsStoreSecrets },
3081
+ ...mfConfig.wrappedBindings && { wrappedBindings: mfConfig.wrappedBindings },
3082
+ ...mfConfig.email && { email: mfConfig.email },
3083
+ ...Object.keys(primaryDurableObjects).length > 0 && { durableObjects: primaryDurableObjects },
3084
+ ...mfConfig.serviceBindings || serviceBindingResolution?.primaryServiceBindings ? { serviceBindings: {
3085
+ ...mfConfig.serviceBindings ?? {},
3086
+ ...serviceBindingResolution?.primaryServiceBindings ?? {}
3087
+ } } : {}
3088
+ };
3089
+ const additionalWorkers = [
3090
+ ...serviceBindingResolution?.workers || [],
3091
+ ...doBindingResolution?.workers || [],
3092
+ ...mfConfig.__devflareLocalSecretWorkers || [],
3093
+ ...mfConfig.__devflareLocalBindingWorkers || []
3094
+ ];
3095
+ const workersByName = /* @__PURE__ */ new Map();
3096
+ for (const worker of additionalWorkers) {
3097
+ if (!workersByName.has(worker.name)) {
3098
+ workersByName.set(worker.name, worker);
3099
+ continue;
3100
+ }
3101
+ const existing = workersByName.get(worker.name);
3102
+ if (worker.durableObjects) existing.durableObjects = {
3103
+ ...existing.durableObjects || {},
3104
+ ...worker.durableObjects
3105
+ };
3106
+ }
3107
+ const workers = [primaryWorker, ...workersByName.values()];
3108
+ delete mfConfig.script;
3109
+ delete mfConfig.modules;
3110
+ delete mfConfig.kvNamespaces;
3111
+ delete mfConfig.r2Buckets;
3112
+ delete mfConfig.d1Databases;
3113
+ delete mfConfig.ratelimits;
3114
+ delete mfConfig.versionMetadata;
3115
+ delete mfConfig.workerLoaders;
3116
+ delete mfConfig.mtlsCertificates;
3117
+ delete mfConfig.dispatchNamespaces;
3118
+ delete mfConfig.workflows;
3119
+ delete mfConfig.pipelines;
3120
+ delete mfConfig.images;
3121
+ delete mfConfig.media;
3122
+ delete mfConfig.artifacts;
3123
+ delete mfConfig.secretsStoreSecrets;
3124
+ delete mfConfig.wrappedBindings;
3125
+ delete mfConfig.serviceBindings;
3126
+ delete mfConfig.__devflareLocalSecretWorkers;
3127
+ delete mfConfig.__devflareLocalBindingWorkers;
3128
+ delete mfConfig.durableObjects;
3129
+ mfConfig.workers = workers;
3130
+ }
3131
+ //#endregion
3132
+ //#region src/test/simple-context-startup.ts
3133
+ const TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS = 3;
3134
+ const TEST_CONTEXT_STARTUP_RETRY_DELAY_MS = 75;
3135
+ const TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS = 8;
3136
+ const TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS = 150;
3137
+ function isRetriableTestContextStartupError(error) {
3138
+ if (!(error instanceof Error)) return false;
3139
+ const message = error.message.toLowerCase();
3140
+ return message.includes("websocket connection failed") || message.includes("connection timeout: ws://") || message.includes("econnrefused") || message.includes("eaddrinuse") || message.includes("address already in use");
3141
+ }
3142
+ async function waitForTestContextStartupRetry() {
3143
+ await new Promise((resolve) => setTimeout(resolve, TEST_CONTEXT_STARTUP_RETRY_DELAY_MS));
3144
+ }
3145
+ async function waitForBridgeClientRetry() {
3146
+ await new Promise((resolve) => setTimeout(resolve, TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS));
3147
+ }
3148
+ async function connectBridgeClientWithRetry(url) {
3149
+ let lastError;
3150
+ for (let attempt = 1; attempt <= TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS; attempt++) {
3151
+ const client = new BridgeClient({ url });
3152
+ try {
3153
+ await client.connect();
3154
+ return client;
3155
+ } catch (error) {
3156
+ lastError = error;
3157
+ client.disconnect();
3158
+ if (attempt >= TEST_CONTEXT_BRIDGE_CONNECT_RETRY_ATTEMPTS || !isRetriableTestContextStartupError(error)) throw error;
3159
+ await waitForBridgeClientRetry();
3160
+ }
3161
+ }
3162
+ throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Bridge-backed test context could not connect to the WebSocket gateway.");
3163
+ }
3164
+ function expandLocalBindingWorkers(mfConfig) {
3165
+ const auxiliaryWorkers = [...mfConfig.__devflareLocalSecretWorkers ?? [], ...mfConfig.__devflareLocalBindingWorkers ?? []];
3166
+ if (!Array.isArray(auxiliaryWorkers) || auxiliaryWorkers.length === 0) return mfConfig;
3167
+ const { __devflareLocalSecretWorkers, __devflareLocalBindingWorkers, port, host, log, kvPersist, r2Persist, d1Persist, durableObjectsPersist, workflowsPersist, imagesPersist, ...primaryWorker } = mfConfig;
3168
+ const primaryWorkerName = typeof primaryWorker.name === "string" ? primaryWorker.name : "primary";
3169
+ return {
3170
+ ...port !== void 0 && { port },
3171
+ ...host && { host },
3172
+ ...log && { log },
3173
+ ...kvPersist && { kvPersist },
3174
+ ...r2Persist && { r2Persist },
3175
+ ...d1Persist && { d1Persist },
3176
+ ...durableObjectsPersist && { durableObjectsPersist },
3177
+ ...workflowsPersist && { workflowsPersist },
3178
+ ...imagesPersist && { imagesPersist },
3179
+ workers: [{
3180
+ ...primaryWorker,
3181
+ name: primaryWorkerName
3182
+ }, ...auxiliaryWorkers]
3183
+ };
3184
+ }
3185
+ async function startBridgeBackedTestContext(mfConfig) {
3186
+ const { Miniflare } = await import("miniflare");
3187
+ for (let attempt = 1; attempt <= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS; attempt++) {
3188
+ const port = await getAvailablePort();
3189
+ let miniflare = null;
3190
+ let client = null;
3191
+ try {
3192
+ miniflare = new Miniflare(expandLocalBindingWorkers({
3193
+ ...mfConfig,
3194
+ port
3195
+ }));
3196
+ await miniflare.ready;
3197
+ const miniflareBindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
3198
+ client = await connectBridgeClientWithRetry(`ws://localhost:${port}`);
3199
+ return {
3200
+ port,
3201
+ client,
3202
+ miniflare,
3203
+ miniflareBindings
3204
+ };
3205
+ } catch (error) {
3206
+ client?.disconnect();
3207
+ if (miniflare) try {
3208
+ await miniflare.dispose();
3209
+ } catch {}
3210
+ if (attempt >= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS || !isRetriableTestContextStartupError(error)) throw error;
3211
+ await waitForTestContextStartupRetry();
3212
+ }
3213
+ }
3214
+ throw new Error("Bridge-backed test context startup exhausted all retry attempts.");
3215
+ }
3216
+ //#endregion
3217
+ //#region src/test/simple-context-runtime.ts
3218
+ /**
3219
+ * Boot the Miniflare runtime that backs `createTestContext()`.
3220
+ *
3221
+ * Two paths:
3222
+ * - **multi-worker**: when the caller has cross-worker DOs or service
3223
+ * bindings, spin up Miniflare directly on a free port. No bridge client.
3224
+ * - **bridge-backed**: otherwise, defer to `startBridgeBackedTestContext()`,
3225
+ * which also returns a connected `BridgeClient`.
3226
+ */
3227
+ async function bootTestRuntime(mfConfig, usesMultiWorker) {
3228
+ if (usesMultiWorker) {
3229
+ const { Miniflare } = await import("miniflare");
3230
+ const activePort = await getAvailablePort();
3231
+ const miniflare = new Miniflare({
3232
+ ...mfConfig,
3233
+ port: activePort
3234
+ });
3235
+ await miniflare.ready;
3236
+ return {
3237
+ activePort,
3238
+ miniflare,
3239
+ miniflareBindings: wrapEnvSendEmailBindings(await miniflare.getBindings()),
3240
+ client: null
3241
+ };
3242
+ }
3243
+ const started = await startBridgeBackedTestContext(mfConfig);
3244
+ return {
3245
+ activePort: started.port,
3246
+ miniflare: started.miniflare,
3247
+ miniflareBindings: started.miniflareBindings,
3248
+ client: started.client
3249
+ };
3250
+ }
3251
+ //#endregion
3252
+ //#region src/test/simple-context-transport.ts
3253
+ /**
3254
+ * Load the user's transport module from disk and build a name -> decoder map.
3255
+ * Returns `null` if the module exists but does not export a `transport` object
3256
+ * (a warning is logged in that case).
3257
+ */
3258
+ async function loadTransportDecoders(configDir, transportFile) {
3259
+ const transportModule = await import(join$1(configDir, transportFile));
3260
+ if (!transportModule.transport) {
3261
+ console.warn(`[devflare] Warning: Transport file "${transportFile}" does not export a named "transport" object.\nExpected: export const transport = { ... }\nTransport encoding/decoding will be disabled.`);
3262
+ return null;
3263
+ }
3264
+ const decoders = /* @__PURE__ */ new Map();
3265
+ for (const [typeName, transporter] of Object.entries(transportModule.transport)) {
3266
+ const t = transporter;
3267
+ decoders.set(typeName, t.decode);
3268
+ }
3269
+ return decoders;
3270
+ }
3271
+ /**
3272
+ * Recursively walk `value`, replacing `{ __transport, value }` envelopes with
3273
+ * the result of the registered decoder. Returns `value` unchanged when
3274
+ * `decoders` is `null` or no envelope is present.
3275
+ */
3276
+ function decodeTransportValue(decoders, value) {
3277
+ if (!decoders || value === null || typeof value !== "object") return value;
3278
+ if ("__transport" in value) {
3279
+ const encoded = value;
3280
+ const decoder = decoders.get(encoded.__transport);
3281
+ if (decoder) return decoder(encoded.value);
3282
+ }
3283
+ if (Array.isArray(value)) return value.map((item) => decodeTransportValue(decoders, item));
3284
+ const result = {};
3285
+ for (const [k, v] of Object.entries(value)) result[k] = decodeTransportValue(decoders, v);
3286
+ return result;
3287
+ }
3288
+ //#endregion
3289
+ //#region src/test/simple-context.ts
3290
+ function createTestContextState() {
3291
+ return {
3292
+ client: null,
3293
+ miniflare: null,
3294
+ envProxy: null,
3295
+ transportDecode: null,
3296
+ remoteBindings: null,
3297
+ miniflareBindings: null
3298
+ };
3299
+ }
3300
+ function shouldPreferBridgeBinding(hint) {
3301
+ return hint === "do" || hint === "service";
3302
+ }
3303
+ /**
3304
+ * Create a test context from a devflare config file.
3305
+ * This starts Miniflare with the configured bindings and sets up the bridge.
3306
+ *
3307
+ * @param configPath - Optional path to config file. If not provided, searches
3308
+ * upward from the test file for a supported devflare config.
3309
+ */
3310
+ async function createTestContext(configPath) {
3311
+ const state = createTestContextState();
3312
+ const { configDir, config } = await resolveTestContextConfig(configPath);
3313
+ state.remoteBindings = buildRemoteAndStaticBindings(config);
3314
+ const hints = extractBindingHints(config);
3315
+ const decodeTransport = (value) => decodeTransportValue(state.transportDecode, value);
3316
+ const needsMultiWorkerForServices = hasServiceBindings(config);
3317
+ const needsMultiWorkerForDOs = hasCrossWorkerDOs(config);
3318
+ let serviceBindingResolution = null;
3319
+ let doBindingResolution = null;
3320
+ if (needsMultiWorkerForServices) serviceBindingResolution = await resolveServiceBindings(config, configDir);
3321
+ if (needsMultiWorkerForDOs) doBindingResolution = await resolveDOBindings(config, configDir);
3322
+ const mfConfig = buildInlineBridgeMfConfig(config, { cwd: configDir });
3323
+ const transportFile = resolveTransportFile(configDir, config.files?.transport);
3324
+ if (transportFile) state.transportDecode = await loadTransportDecoders(configDir, transportFile);
3325
+ const gateway = await buildDurableObjectGateway(config, configDir, transportFile);
3326
+ mfConfig.script = gateway.script;
3327
+ if (gateway.durableObjects) mfConfig.durableObjects = gateway.durableObjects;
3328
+ const hasMultiWorkerServices = serviceBindingResolution && serviceBindingResolution.workers.length > 0;
3329
+ const hasMultiWorkerDOs = doBindingResolution && doBindingResolution.workers.length > 0;
3330
+ if (hasMultiWorkerServices || hasMultiWorkerDOs) applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBindingResolution);
3331
+ const usesMultiWorker = Boolean(hasMultiWorkerServices || hasMultiWorkerDOs);
3332
+ const runtime = await bootTestRuntime(mfConfig, usesMultiWorker);
3333
+ const activePort = runtime.activePort;
3334
+ state.miniflare = runtime.miniflare;
3335
+ state.miniflareBindings = runtime.miniflareBindings;
3336
+ state.client = runtime.client;
3337
+ const disposeContext = createDisposeContext(state);
3338
+ const getTestEnv = () => {
3339
+ return new Proxy({}, {
3340
+ get(_, prop) {
3341
+ if (state.remoteBindings && prop in state.remoteBindings) return state.remoteBindings[prop];
3342
+ if (hints[prop] === "sendEmail" && state.envProxy && prop in state.envProxy) return state.envProxy[prop];
3343
+ if (state.miniflareBindings && prop in state.miniflareBindings) return state.miniflareBindings[prop];
3344
+ if (state.envProxy && prop in state.envProxy) return state.envProxy[prop];
3345
+ },
3346
+ has(_, prop) {
3347
+ return Boolean(state.remoteBindings && prop in state.remoteBindings || state.miniflareBindings && prop in state.miniflareBindings || state.envProxy && prop in state.envProxy);
3348
+ }
3349
+ });
3350
+ };
3351
+ configureSurfaceHandlers({
3352
+ handlerPaths: await resolveHandlerPaths(configDir, config),
3353
+ configDir,
3354
+ activePort,
3355
+ getEnv: getTestEnv
3356
+ });
3357
+ if (usesMultiWorker) {
3358
+ setBindingHints(hints);
3359
+ __setTestContext(createMultiWorkerEnvAccessor(state), disposeContext);
3360
+ return;
3361
+ }
3362
+ const bridgeClient = state.client;
3363
+ if (!bridgeClient) throw new Error("Bridge-backed test context did not initialize a client.");
3364
+ setBindingHints(hints);
3365
+ state.envProxy = createEnvProxy({
3366
+ client: bridgeClient,
3367
+ transformResult: (result) => decodeTransport(result)
3368
+ });
3369
+ __setTestContext(createBridgeEnvAccessor(state, hints, shouldPreferBridgeBinding), disposeContext);
3370
+ }
3371
+ //#endregion
3372
+ //#region src/test/cf.ts
3373
+ /**
3374
+ * Unified Cloudflare test helpers.
3375
+ *
3376
+ * Provides a consistent API for triggering the main Cloudflare Worker handler surfaces:
3377
+ * - `cf.email` — Email helper surface with a direct-handler path and a best-effort local endpoint fallback
3378
+ * - `cf.queue` — Queue consumer testing
3379
+ * - `cf.scheduled` — Cron/scheduled handler testing
3380
+ * - `cf.worker` — Fetch handler testing
3381
+ * - `cf.tail` — Tail helper surface (uses `files.tail`, or auto-detects `src/tail.ts` when present)
3382
+ *
3383
+ * The helpers use the real Miniflare-backed bindings created by `createTestContext()`,
3384
+ * but several helper surfaces still synthesize event/controller objects around those
3385
+ * bindings rather than replaying the full Cloudflare runtime dispatch path.
3386
+ * They still install the active Devflare event into AsyncLocalStorage before
3387
+ * invoking user code, so getters such as `getFetchEvent()` and `getQueueEvent()`
3388
+ * work inside the triggered handler.
3389
+ *
3390
+ * @example
3391
+ * ```ts
3392
+ * import { describe, test, expect, beforeAll, afterAll } from 'bun:test'
3393
+ * import { createTestContext, cf } from 'devflare/test'
3394
+ * import { env } from 'devflare'
3395
+ *
3396
+ * beforeAll(() => createTestContext())
3397
+ * afterAll(() => env.dispose())
3398
+ *
3399
+ * describe('My Worker', () => {
3400
+ * test('fetch handler', async () => {
3401
+ * const response = await cf.worker.get('/api/health')
3402
+ * expect(response.status).toBe(200)
3403
+ * })
3404
+ *
3405
+ * test('queue handler', async () => {
3406
+ * // Trigger queue handler with messages
3407
+ * const result = await cf.queue.trigger([
3408
+ * { type: 'process', data: { id: 1 } }
3409
+ * ])
3410
+ * expect(result.acked).toHaveLength(1)
3411
+ *
3412
+ * // Verify side effects in real KV
3413
+ * const stored = await env.RESULTS.get('result:1')
3414
+ * expect(stored).toBeDefined()
3415
+ * })
3416
+ *
3417
+ * test('scheduled handler', async () => {
3418
+ * await cf.scheduled.trigger('0 0 * * 1') // Weekly Monday cron
3419
+ *
3420
+ * // Verify scheduled job ran
3421
+ * const report = await env.RESULTS.get('report:weekly')
3422
+ * expect(report).toBeDefined()
3423
+ * })
3424
+ * })
3425
+ * ```
3426
+ */
3427
+ const cf = {
3428
+ email,
3429
+ queue,
3430
+ scheduled,
3431
+ worker,
3432
+ tail
3433
+ };
3434
+ //#endregion
3435
+ //#region src/test/should-skip.ts
3436
+ /**
3437
+ * These services cannot be emulated locally — they ALWAYS require
3438
+ * a real connection to Cloudflare's infrastructure.
3439
+ */
3440
+ const REMOTE_ONLY_SERVICES = new Set([
3441
+ "ai",
3442
+ "ai_search",
3443
+ "ai_gateway",
3444
+ "media",
3445
+ "mtls_certificates",
3446
+ "artifacts",
3447
+ "builds",
3448
+ "vectorize"
3449
+ ]);
3450
+ /**
3451
+ * Cached skip results — computed once at module load
3452
+ * Each service gets a Promise<boolean> that resolves to true if should SKIP
3453
+ */
3454
+ const skipResults = /* @__PURE__ */ new Map();
3455
+ let containerSkipResult = null;
3456
+ /**
3457
+ * Known operational error patterns that should cause skipping rather than failing.
3458
+ * These are expected errors from network issues, API problems, auth failures, etc.
3459
+ */
3460
+ const EXPECTED_ERROR_PATTERNS = [
3461
+ "ECONNREFUSED",
3462
+ "ETIMEDOUT",
3463
+ "ENOTFOUND",
3464
+ "fetch failed",
3465
+ "network",
3466
+ "401",
3467
+ "403",
3468
+ "429",
3469
+ "500",
3470
+ "502",
3471
+ "503",
3472
+ "504",
3473
+ "rate limit",
3474
+ "unauthorized",
3475
+ "forbidden",
3476
+ "timeout"
3477
+ ];
3478
+ /**
3479
+ * Check if an error is an expected operational error that should cause skipping.
3480
+ */
3481
+ function isExpectedError(error) {
3482
+ if (!(error instanceof Error)) return false;
3483
+ const msg = error.message.toLowerCase();
3484
+ return EXPECTED_ERROR_PATTERNS.some((pattern) => msg.includes(pattern.toLowerCase()));
3485
+ }
3486
+ /**
3487
+ * Compute whether to skip tests for a given service.
3488
+ * Returns true if tests should be SKIPPED.
3489
+ * Logs the reason to console.
3490
+ *
3491
+ * Rethrows unexpected errors (programming bugs) to fail tests loudly.
3492
+ */
3493
+ async function computeSkip(service) {
3494
+ try {
3495
+ if (REMOTE_ONLY_SERVICES.has(service) && !isRemoteModeActive()) {
3496
+ const status = getRemoteModeStatus();
3497
+ console.log(`⏭️ ${service.toUpperCase()} tests skipped: Remote-only service.\n Enable with: ${status.isEnabled ? "" : "devflare remote enable"}\n Or set: DEVFLARE_REMOTE=1\n See: https://github.com/ArthurvdVenne/devflare#remote-testing`);
3498
+ return true;
3499
+ }
3500
+ if (!await isAuthenticated()) {
3501
+ console.log(`⏭️ ${service.toUpperCase()} tests skipped: Not authenticated. Run: bunx wrangler login\n See: https://github.com/ArthurvdVenne/devflare#authentication`);
3502
+ return true;
3503
+ }
3504
+ const primary = await getPrimaryAccount();
3505
+ if (!primary) {
3506
+ console.log(`⏭️ ${service.toUpperCase()} tests skipped: No Cloudflare account found\n See: https://github.com/ArthurvdVenne/devflare#authentication`);
3507
+ return true;
3508
+ }
3509
+ const { accountId } = await getEffectiveAccountId(primary.id);
3510
+ try {
3511
+ const { allowed, reason } = await canProceedWithTest(accountId, service);
3512
+ if (!allowed) {
3513
+ console.log(`⏭️ ${service.toUpperCase()} tests skipped: ${reason}`);
3514
+ return true;
3515
+ }
3516
+ } catch {}
3517
+ return false;
3518
+ } catch (error) {
3519
+ if (isExpectedError(error)) {
3520
+ const message = error instanceof Error ? error.message : "Unknown error";
3521
+ console.log(`⏭️ ${service.toUpperCase()} tests skipped: ${message}`);
3522
+ return true;
3523
+ }
3524
+ throw error;
3525
+ }
3526
+ }
3527
+ /**
3528
+ * Get or compute the skip result for a service
3529
+ */
3530
+ function getSkipResult(service) {
3531
+ let result = skipResults.get(service);
3532
+ if (!result) {
3533
+ result = computeSkip(service);
3534
+ skipResults.set(service, result);
3535
+ }
3536
+ return result;
3537
+ }
3538
+ function getContainerSkipResult() {
3539
+ if (!containerSkipResult) containerSkipResult = getContainerSkipReason().then((reason) => {
3540
+ if (reason) {
3541
+ console.log(`CONTAINERS tests skipped: ${reason}`);
3542
+ return true;
3543
+ }
3544
+ return false;
3545
+ });
3546
+ return containerSkipResult;
3547
+ }
3548
+ /**
3549
+ * Skip helper with property-based access for each service.
3550
+ * Each property returns a Promise<boolean> where true = SKIP the tests.
3551
+ *
3552
+ * Usage:
3553
+ * ```ts
3554
+ * import { shouldSkip } from 'devflare/test'
3555
+ *
3556
+ * describe.skipIf(shouldSkip.ai)('AI tests', () => {
3557
+ * // These tests only run when authenticated and within limits
3558
+ * })
3559
+ * ```
3560
+ */
3561
+ const shouldSkip = {
3562
+ get ai() {
3563
+ return getSkipResult("ai");
3564
+ },
3565
+ get aiSearch() {
3566
+ return getSkipResult("ai_search");
3567
+ },
3568
+ get aiGateway() {
3569
+ return getSkipResult("ai_gateway");
3570
+ },
3571
+ get vectorize() {
3572
+ return getSkipResult("vectorize");
3573
+ },
3574
+ get workers() {
3575
+ return getSkipResult("workers");
3576
+ },
3577
+ get kv() {
3578
+ return getSkipResult("kv");
3579
+ },
3580
+ get d1() {
3581
+ return getSkipResult("d1");
3582
+ },
3583
+ get r2() {
3584
+ return getSkipResult("r2");
3585
+ },
3586
+ get queues() {
3587
+ return getSkipResult("queues");
3588
+ },
3589
+ get durableObjects() {
3590
+ return getSkipResult("durable_objects");
3591
+ },
3592
+ get media() {
3593
+ return getSkipResult("media");
3594
+ },
3595
+ get mtlsCertificates() {
3596
+ return getSkipResult("mtls_certificates");
3597
+ },
3598
+ get artifacts() {
3599
+ return getSkipResult("artifacts");
3600
+ },
3601
+ get builds() {
3602
+ return getSkipResult("builds");
3603
+ },
3604
+ get containers() {
3605
+ return getContainerSkipResult();
3606
+ }
3607
+ };
3608
+ //#endregion
3609
+ //#region src/test/ai-search.ts
3610
+ const MOCK_TIMESTAMP = "2026-04-26T00:00:00.000Z";
3611
+ function cloneInfo(value) {
3612
+ return { ...value };
3613
+ }
3614
+ function extractSearchQuery(params) {
3615
+ if ("query" in params && typeof params.query === "string") return params.query;
3616
+ return ("messages" in params ? params.messages : []).filter((message) => message.role === "user" && message.content).map((message) => message.content).join(" ");
3617
+ }
3618
+ function tokenize(value) {
3619
+ return value.toLowerCase().split(/[^a-z0-9_]+/).filter((token) => token.length > 2);
3620
+ }
3621
+ function scoreText(query, text) {
3622
+ const tokens = tokenize(query);
3623
+ if (tokens.length === 0) return 1;
3624
+ const haystack = text.toLowerCase();
3625
+ const matches = tokens.filter((token) => haystack.includes(token)).length;
3626
+ return matches === 0 ? 0 : Math.max(.1, matches / tokens.length);
3627
+ }
3628
+ function streamFromText(text) {
3629
+ const encoded = new TextEncoder().encode(text);
3630
+ return new ReadableStream({ start(controller) {
3631
+ controller.enqueue(encoded);
3632
+ controller.close();
3633
+ } });
3634
+ }
3635
+ async function contentToText(content) {
3636
+ if (typeof content === "string") return content;
3637
+ if (content instanceof Blob) return content.text();
3638
+ return new Response(content).text();
3639
+ }
3640
+ function createItemInfo(id, key, content, options = {}) {
3641
+ return {
3642
+ id,
3643
+ key,
3644
+ status: options.status ?? "completed",
3645
+ next_action: null,
3646
+ namespace: options.namespace,
3647
+ chunks_count: content.length > 0 ? 1 : 0,
3648
+ file_size: new TextEncoder().encode(content).byteLength,
3649
+ source_id: options.sourceId ?? null,
3650
+ last_seen_at: MOCK_TIMESTAMP,
3651
+ created_at: MOCK_TIMESTAMP,
3652
+ metadata: options.metadata
3653
+ };
3654
+ }
3655
+ function createItemChunks(item, chunks) {
3656
+ let offset = 0;
3657
+ return chunks.map((text, index) => {
3658
+ const start = offset;
3659
+ const end = start + new TextEncoder().encode(text).byteLength;
3660
+ offset = end;
3661
+ return {
3662
+ id: `${item.id}:chunk-${index + 1}`,
3663
+ text,
3664
+ start_byte: start,
3665
+ end_byte: end,
3666
+ item: {
3667
+ timestamp: Date.parse(MOCK_TIMESTAMP),
3668
+ key: item.key,
3669
+ metadata: item.metadata
3670
+ }
3671
+ };
3672
+ });
3673
+ }
3674
+ function createStoredItem(sequence, fixture, namespace) {
3675
+ const content = fixture.content ?? fixture.chunks?.join("\n") ?? "";
3676
+ const info = createItemInfo(fixture.id ?? `item-${sequence}`, fixture.key, content, {
3677
+ metadata: fixture.metadata,
3678
+ status: fixture.status,
3679
+ namespace
3680
+ });
3681
+ const chunks = createItemChunks(info, fixture.chunks ?? [content]);
3682
+ return {
3683
+ info,
3684
+ content,
3685
+ contentType: fixture.contentType ?? "text/plain",
3686
+ chunks,
3687
+ logs: [{
3688
+ timestamp: MOCK_TIMESTAMP,
3689
+ action: "index",
3690
+ message: `Indexed ${fixture.key}`,
3691
+ fileKey: fixture.key,
3692
+ chunkCount: chunks.length,
3693
+ processingTimeMs: 0
3694
+ }]
3695
+ };
3696
+ }
3697
+ function isAISearchInstance$1(value) {
3698
+ return typeof value.search === "function";
3699
+ }
3700
+ /**
3701
+ * Creates a deterministic AI Search instance binding for pure unit tests.
3702
+ */
3703
+ function createMockAISearchInstance(options = {}) {
3704
+ const instanceId = options.id ?? options.info?.id ?? "mock-ai-search";
3705
+ const namespace = options.namespace ?? options.info?.namespace;
3706
+ const items = /* @__PURE__ */ new Map();
3707
+ const jobs = /* @__PURE__ */ new Map();
3708
+ const searches = [];
3709
+ let itemSequence = 0;
3710
+ let jobSequence = 0;
3711
+ let info = {
3712
+ id: instanceId,
3713
+ type: "builtin",
3714
+ namespace,
3715
+ status: "ready",
3716
+ created_at: MOCK_TIMESTAMP,
3717
+ modified_at: MOCK_TIMESTAMP,
3718
+ index_method: {
3719
+ vector: true,
3720
+ keyword: true
3721
+ },
3722
+ fusion_method: "rrf",
3723
+ ...options.info
3724
+ };
3725
+ for (const fixture of options.items ?? []) {
3726
+ const stored = createStoredItem(++itemSequence, fixture, namespace);
3727
+ items.set(stored.info.id, stored);
3728
+ }
3729
+ const findMatches = (params) => {
3730
+ searches.push(params);
3731
+ const query = extractSearchQuery(params);
3732
+ const maxResults = params.ai_search_options?.retrieval?.max_num_results ?? 10;
3733
+ const chunks = [];
3734
+ for (const item of items.values()) for (const chunk of item.chunks) {
3735
+ const score = scoreText(query, `${item.info.key} ${chunk.text}`);
3736
+ if (score === 0) continue;
3737
+ chunks.push({
3738
+ id: chunk.id,
3739
+ type: "text",
3740
+ score,
3741
+ text: chunk.text,
3742
+ item: {
3743
+ timestamp: Date.parse(MOCK_TIMESTAMP),
3744
+ key: item.info.key,
3745
+ metadata: item.info.metadata
3746
+ },
3747
+ scoring_details: {
3748
+ keyword_score: score,
3749
+ vector_score: score,
3750
+ keyword_rank: chunks.length + 1,
3751
+ vector_rank: chunks.length + 1,
3752
+ fusion_method: "rrf"
3753
+ }
3754
+ });
3755
+ }
3756
+ return {
3757
+ search_query: query,
3758
+ chunks: chunks.slice(0, maxResults)
3759
+ };
3760
+ };
3761
+ const createItemHandle = (itemId) => ({
3762
+ async info() {
3763
+ const item = items.get(itemId);
3764
+ if (!item) throw new Error(`Mock AI Search item "${itemId}" was not found.`);
3765
+ return cloneInfo(item.info);
3766
+ },
3767
+ async download() {
3768
+ const item = items.get(itemId);
3769
+ if (!item) throw new Error(`Mock AI Search item "${itemId}" was not found.`);
3770
+ return {
3771
+ body: streamFromText(item.content),
3772
+ contentType: item.contentType,
3773
+ filename: item.info.key,
3774
+ size: new TextEncoder().encode(item.content).byteLength
3775
+ };
3776
+ },
3777
+ async sync() {
3778
+ const item = items.get(itemId);
3779
+ if (!item) throw new Error(`Mock AI Search item "${itemId}" was not found.`);
3780
+ item.info.status = "completed";
3781
+ item.info.last_seen_at = MOCK_TIMESTAMP;
3782
+ return cloneInfo(item.info);
3783
+ },
3784
+ async logs(params) {
3785
+ const item = items.get(itemId);
3786
+ if (!item) throw new Error(`Mock AI Search item "${itemId}" was not found.`);
3787
+ const limit = params?.limit ?? 50;
3788
+ const result = item.logs.slice(0, limit);
3789
+ return {
3790
+ result,
3791
+ result_info: {
3792
+ count: result.length,
3793
+ per_page: limit,
3794
+ cursor: null,
3795
+ truncated: item.logs.length > result.length
3796
+ }
3797
+ };
3798
+ },
3799
+ async chunks(params) {
3800
+ const item = items.get(itemId);
3801
+ if (!item) throw new Error(`Mock AI Search item "${itemId}" was not found.`);
3802
+ const offset = params?.offset ?? 0;
3803
+ const limit = params?.limit ?? 20;
3804
+ const result = item.chunks.slice(offset, offset + limit);
3805
+ return {
3806
+ result,
3807
+ result_info: {
3808
+ count: result.length,
3809
+ total: item.chunks.length,
3810
+ limit,
3811
+ offset
3812
+ }
3813
+ };
3814
+ }
3815
+ });
3816
+ const uploadItem = async (name, content, uploadOptions) => {
3817
+ const text = await contentToText(content);
3818
+ const existing = Array.from(items.values()).find((item) => item.info.key === name);
3819
+ const stored = createStoredItem(existing ? Number(existing.info.id.replace(/^item-/, "")) || ++itemSequence : ++itemSequence, {
3820
+ id: existing?.info.id,
3821
+ key: name,
3822
+ content: text,
3823
+ metadata: uploadOptions?.metadata
3824
+ }, namespace);
3825
+ items.set(stored.info.id, stored);
3826
+ return cloneInfo(stored.info);
3827
+ };
3828
+ const itemsApi = {
3829
+ async list(params) {
3830
+ let result = Array.from(items.values()).map((item) => cloneInfo(item.info));
3831
+ if (params?.search) {
3832
+ const search = params.search.toLowerCase();
3833
+ result = result.filter((item) => item.key.toLowerCase().includes(search));
3834
+ }
3835
+ if (params?.status) result = result.filter((item) => item.status === params.status);
3836
+ const page = params?.page ?? 1;
3837
+ const perPage = (params?.per_page ?? result.length) || 50;
3838
+ const start = (page - 1) * perPage;
3839
+ const paged = result.slice(start, start + perPage);
3840
+ return {
3841
+ result: paged,
3842
+ result_info: {
3843
+ count: paged.length,
3844
+ page,
3845
+ per_page: perPage,
3846
+ total_count: result.length
3847
+ }
3848
+ };
3849
+ },
3850
+ upload: uploadItem,
3851
+ async uploadAndPoll(name, content, uploadOptions) {
3852
+ return uploadItem(name, content, uploadOptions);
3853
+ },
3854
+ get(itemId) {
3855
+ return createItemHandle(itemId);
3856
+ },
3857
+ async delete(itemId) {
3858
+ items.delete(itemId);
3859
+ }
3860
+ };
3861
+ const createJobHandle = (jobId) => ({
3862
+ async info() {
3863
+ const job = jobs.get(jobId);
3864
+ if (!job) throw new Error(`Mock AI Search job "${jobId}" was not found.`);
3865
+ return cloneInfo(job);
3866
+ },
3867
+ async logs(params) {
3868
+ const perPage = params?.per_page ?? 50;
3869
+ return {
3870
+ result: [{
3871
+ id: 1,
3872
+ message: `Mock job ${jobId}`,
3873
+ message_type: 0,
3874
+ created_at: Date.parse(MOCK_TIMESTAMP)
3875
+ }].slice(0, perPage),
3876
+ result_info: {
3877
+ count: 1,
3878
+ page: params?.page ?? 1,
3879
+ per_page: perPage,
3880
+ total_count: 1
3881
+ }
3882
+ };
3883
+ },
3884
+ async cancel() {
3885
+ const job = jobs.get(jobId);
3886
+ if (!job) throw new Error(`Mock AI Search job "${jobId}" was not found.`);
3887
+ const updated = {
3888
+ ...job,
3889
+ ended_at: MOCK_TIMESTAMP,
3890
+ end_reason: "cancelled"
3891
+ };
3892
+ jobs.set(jobId, updated);
3893
+ return cloneInfo(updated);
3894
+ }
3895
+ });
3896
+ const jobsApi = {
3897
+ async list(params) {
3898
+ const allJobs = Array.from(jobs.values()).map((job) => cloneInfo(job));
3899
+ const page = params?.page ?? 1;
3900
+ const perPage = (params?.per_page ?? allJobs.length) || 50;
3901
+ const start = (page - 1) * perPage;
3902
+ const result = allJobs.slice(start, start + perPage);
3903
+ return {
3904
+ result,
3905
+ result_info: {
3906
+ count: result.length,
3907
+ page,
3908
+ per_page: perPage,
3909
+ total_count: allJobs.length
3910
+ }
3911
+ };
3912
+ },
3913
+ async create(params) {
3914
+ const id = `job-${++jobSequence}`;
3915
+ const job = {
3916
+ id,
3917
+ source: "user",
3918
+ description: params?.description,
3919
+ started_at: MOCK_TIMESTAMP
3920
+ };
3921
+ jobs.set(id, job);
3922
+ return cloneInfo(job);
3923
+ },
3924
+ get(jobId) {
3925
+ return createJobHandle(jobId);
3926
+ }
3927
+ };
3928
+ return {
3929
+ async search(params) {
3930
+ return findMatches(params);
3931
+ },
3932
+ async chatCompletions(params) {
3933
+ const search = findMatches({
3934
+ messages: params.messages,
3935
+ ai_search_options: params.ai_search_options
3936
+ });
3937
+ const content = typeof options.chatMessage === "function" ? options.chatMessage(search.chunks, params) : options.chatMessage ?? (search.chunks.map((chunk) => chunk.text).join("\n") || "No matching offline AI Search chunks.");
3938
+ if (params.stream) return streamFromText(`data: ${JSON.stringify({
3939
+ choices: [{ delta: { content } }],
3940
+ chunks: search.chunks
3941
+ })}\n\n`);
3942
+ return {
3943
+ id: "mock-ai-search-chat",
3944
+ object: "chat.completion",
3945
+ model: params.model ?? "mock-ai-search",
3946
+ choices: [{
3947
+ index: 0,
3948
+ message: {
3949
+ role: "assistant",
3950
+ content
3951
+ }
3952
+ }],
3953
+ chunks: search.chunks
3954
+ };
3955
+ },
3956
+ async update(config) {
3957
+ info = {
3958
+ ...info,
3959
+ ...config,
3960
+ modified_at: MOCK_TIMESTAMP
3961
+ };
3962
+ return cloneInfo(info);
3963
+ },
3964
+ async info() {
3965
+ return cloneInfo(info);
3966
+ },
3967
+ async stats() {
3968
+ const stats = {
3969
+ queued: 0,
3970
+ running: 0,
3971
+ completed: 0,
3972
+ error: 0,
3973
+ skipped: 0,
3974
+ outdated: 0,
3975
+ last_activity: MOCK_TIMESTAMP,
3976
+ engine: {
3977
+ vectorize: {
3978
+ vectorsCount: items.size,
3979
+ dimensions: 0
3980
+ },
3981
+ r2: {
3982
+ payloadSizeBytes: Array.from(items.values()).reduce((sum, item) => sum + item.info.file_size, 0),
3983
+ metadataSizeBytes: 0,
3984
+ objectCount: items.size
3985
+ }
3986
+ }
3987
+ };
3988
+ for (const item of items.values()) stats[item.info.status] = (stats[item.info.status] ?? 0) + 1;
3989
+ return stats;
3990
+ },
3991
+ get items() {
3992
+ return itemsApi;
3993
+ },
3994
+ get jobs() {
3995
+ return jobsApi;
3996
+ },
3997
+ _getSearches() {
3998
+ return [...searches];
3999
+ },
4000
+ _getItems() {
4001
+ return Array.from(items.values()).map((item) => cloneInfo(item.info));
4002
+ },
4003
+ _getJobs() {
4004
+ return Array.from(jobs.values()).map((job) => cloneInfo(job));
4005
+ }
4006
+ };
4007
+ }
4008
+ /**
4009
+ * Creates a deterministic AI Search namespace binding for pure unit tests.
4010
+ */
4011
+ function createMockAISearchNamespace(options = {}) {
4012
+ const namespaceName = options.namespace ?? "default";
4013
+ const instances = /* @__PURE__ */ new Map();
4014
+ for (const [name, instanceOptions] of Object.entries(options.instances ?? {})) instances.set(name, isAISearchInstance$1(instanceOptions) ? instanceOptions : createMockAISearchInstance({
4015
+ id: name,
4016
+ namespace: namespaceName,
4017
+ ...instanceOptions
4018
+ }));
4019
+ const getInstance = (name) => {
4020
+ const instance = instances.get(name);
4021
+ if (!instance) throw new Error(`Mock AI Search namespace has no instance named "${name}".`);
4022
+ return instance;
4023
+ };
4024
+ return {
4025
+ get(name) {
4026
+ return getInstance(name);
4027
+ },
4028
+ async list(params) {
4029
+ let result = await Promise.all(Array.from(instances.entries()).map(async ([name, instance]) => ({
4030
+ ...await instance.info(),
4031
+ namespace: namespaceName,
4032
+ id: name
4033
+ })));
4034
+ if (params?.search) {
4035
+ const search = params.search.toLowerCase();
4036
+ result = result.filter((instance) => instance.id.toLowerCase().includes(search));
4037
+ }
4038
+ const page = params?.page ?? 1;
4039
+ const perPage = (params?.per_page ?? result.length) || 50;
4040
+ const start = (page - 1) * perPage;
4041
+ const paged = result.slice(start, start + perPage);
4042
+ return {
4043
+ result: paged,
4044
+ result_info: {
4045
+ count: paged.length,
4046
+ page,
4047
+ per_page: perPage,
4048
+ total_count: result.length
4049
+ }
4050
+ };
4051
+ },
4052
+ async create(config) {
4053
+ const instance = createMockAISearchInstance({
4054
+ id: config.id,
4055
+ namespace: namespaceName,
4056
+ info: config
4057
+ });
4058
+ instances.set(config.id, instance);
4059
+ return instance;
4060
+ },
4061
+ async delete(name) {
4062
+ instances.delete(name);
4063
+ },
4064
+ async search(params) {
4065
+ const query = extractSearchQuery(params);
4066
+ const chunks = [];
4067
+ const errors = [];
4068
+ for (const instanceId of params.ai_search_options.instance_ids) {
4069
+ const instance = instances.get(instanceId);
4070
+ if (!instance) {
4071
+ errors.push({
4072
+ instance_id: instanceId,
4073
+ message: `Mock AI Search namespace has no instance named "${instanceId}".`
4074
+ });
4075
+ continue;
4076
+ }
4077
+ const response = "query" in params ? await instance.search({
4078
+ query,
4079
+ ai_search_options: params.ai_search_options
4080
+ }) : await instance.search({
4081
+ messages: params.messages,
4082
+ ai_search_options: params.ai_search_options
4083
+ });
4084
+ chunks.push(...response.chunks.map((chunk) => ({
4085
+ ...chunk,
4086
+ instance_id: instanceId
4087
+ })));
4088
+ }
4089
+ return {
4090
+ search_query: query,
4091
+ chunks,
4092
+ ...errors.length > 0 && { errors }
4093
+ };
4094
+ },
4095
+ async chatCompletions(params) {
4096
+ const search = await this.search({
4097
+ messages: params.messages,
4098
+ ai_search_options: params.ai_search_options
4099
+ });
4100
+ const content = search.chunks.map((chunk) => chunk.text).join("\n") || "No matching offline AI Search chunks.";
4101
+ if (params.stream) return streamFromText(`data: ${JSON.stringify({ chunks: search.chunks })}\n\n`);
4102
+ return {
4103
+ id: "mock-ai-search-namespace-chat",
4104
+ object: "chat.completion",
4105
+ model: params.model ?? "mock-ai-search",
4106
+ choices: [{
4107
+ index: 0,
4108
+ message: {
4109
+ role: "assistant",
4110
+ content
4111
+ }
4112
+ }],
4113
+ chunks: search.chunks,
4114
+ errors: search.errors
4115
+ };
4116
+ },
4117
+ _getInstances() {
4118
+ return Array.from(instances.keys());
4119
+ }
4120
+ };
4121
+ }
4122
+ //#endregion
4123
+ //#region src/test/offline-bindings.ts
4124
+ const SUPPORT_MATRIX = {
4125
+ rateLimits: {
4126
+ service: "rateLimits",
4127
+ tier: "offline-native",
4128
+ reason: "Miniflare and devflare/test can simulate fixed-window RateLimit bindings locally.",
4129
+ recommendation: "Use createOfflineEnv() or createMockRateLimit() for pure tests; use createTestContext() for Miniflare-backed tests."
4130
+ },
4131
+ versionMetadata: {
4132
+ service: "versionMetadata",
4133
+ tier: "offline-native",
4134
+ reason: "Version metadata can be deterministic in tests without Cloudflare state.",
4135
+ recommendation: "Use createOfflineEnv() or createMockVersionMetadata() when asserting version-aware code."
4136
+ },
4137
+ secretsStore: {
4138
+ service: "secretsStore",
4139
+ tier: "offline-native",
4140
+ reason: "Secrets Store binding shape is local-testable with local secret-store values or fixed fixtures.",
4141
+ recommendation: "Use devflare secrets --local for dev/test values, or pass fixtures.secretsStore values for pure tests."
4142
+ },
4143
+ hyperdrive: {
4144
+ service: "hyperdrive",
4145
+ tier: "offline-native",
4146
+ reason: "Hyperdrive can run locally through Miniflare when Devflare has a local connection string or test fixture for the target database.",
4147
+ recommendation: "Use bindings.hyperdrive.*.localConnectionString, CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>, or fixtures.hyperdrive for offline tests."
4148
+ },
4149
+ workerLoaders: {
4150
+ service: "workerLoaders",
4151
+ tier: "offline-native",
4152
+ reason: "Worker Loader bindings run locally through Miniflare and can use explicit Worker stubs for pure tests.",
4153
+ recommendation: "Use createTestContext() for local WorkerLoader execution; pass fixtures.workerLoaders with a WorkerStub for pure tests."
4154
+ },
4155
+ mtlsCertificates: {
4156
+ service: "mtlsCertificates",
4157
+ tier: "offline-fixture",
4158
+ reason: "Fetcher call paths can be tested locally, but real certificate presentation is Cloudflare/Wrangler remote behavior.",
4159
+ recommendation: "Pass fixtures.mtlsCertificates handlers for unit tests; use remote/deployed tests for certificate presentation."
4160
+ },
4161
+ dispatchNamespaces: {
4162
+ service: "dispatchNamespaces",
4163
+ tier: "offline-fixture",
4164
+ reason: "Tenant dispatch can be backed by explicit test fetchers, but namespace uploads and lifecycle are Cloudflare-managed.",
4165
+ recommendation: "Pass fixtures.dispatchNamespaces workers for deterministic tenant routing tests."
4166
+ },
4167
+ workflows: {
4168
+ service: "workflows",
4169
+ tier: "offline-native",
4170
+ reason: "Workflow binding calls can run through Miniflare or a deterministic local Workflow mock for app-level tests.",
4171
+ recommendation: "Use createOfflineEnv() for pure tests and createTestContext() when Miniflare execution semantics matter."
4172
+ },
4173
+ pipelines: {
4174
+ service: "pipelines",
4175
+ tier: "offline-native",
4176
+ reason: "Pipeline sends can be recorded locally; Cloudflare owns production batching and sinks.",
4177
+ recommendation: "Use createOfflineEnv() or createMockPipeline() to assert records sent by application code."
4178
+ },
4179
+ images: {
4180
+ service: "images",
4181
+ tier: "offline-native",
4182
+ reason: "Images has local development support and Devflare provides a low-fidelity deterministic pure mock.",
4183
+ recommendation: "Use createOfflineEnv() for chain-shape tests; use Cloudflare for hosted image APIs and transform fidelity."
4184
+ },
4185
+ media: {
4186
+ service: "media",
4187
+ tier: "offline-native",
4188
+ reason: "Media Transformations can run through Miniflare wiring locally, and Devflare provides a deterministic pure mock for app-level chain tests.",
4189
+ recommendation: "Use createTestContext() for local Worker binding tests and createMockMediaBinding() for pure tests; use Cloudflare for codec/output fidelity."
4190
+ },
4191
+ artifacts: {
4192
+ service: "artifacts",
4193
+ tier: "offline-fixture",
4194
+ reason: "Artifacts repo metadata and token flows can be modeled in memory; real Git remotes are Cloudflare-managed.",
4195
+ recommendation: "Use createMockArtifacts() for unit tests; use Cloudflare for Git protocol and namespace access."
4196
+ },
4197
+ aiSearch: {
4198
+ service: "aiSearch",
4199
+ tier: "offline-fixture",
4200
+ reason: "AI Search application flows can use deterministic in-memory instances and namespaces, but indexing/ranking/crawling are hosted Cloudflare behavior.",
4201
+ recommendation: "Use createMockAISearchInstance(), createMockAISearchNamespace(), or createOfflineEnv(); use remote tests for real relevance behavior."
4202
+ },
4203
+ aiSearchNamespaces: {
4204
+ service: "aiSearchNamespaces",
4205
+ tier: "offline-fixture",
4206
+ reason: "AI Search namespace management can be backed by an explicit in-memory instance registry for tests.",
4207
+ recommendation: "Use fixtures.aiSearchNamespaces to model tenant instances and multi-instance searches."
4208
+ },
4209
+ containers: {
4210
+ service: "containers",
4211
+ tier: "offline-native",
4212
+ reason: "Devflare can launch local Docker/Podman containers in explicit tests when an engine and cached images are available.",
4213
+ recommendation: "Use devflare/test containers helpers and shouldSkip.containers; keep ordinary unit tests engine-free."
4214
+ },
4215
+ browser: {
4216
+ service: "browser",
4217
+ tier: "offline-native",
4218
+ reason: "Cloudflare lists Browser Run as locally simulatable, while live view, HITL, recordings, and external CDP remain hosted features.",
4219
+ recommendation: "Use Cloudflare/Wrangler Browser local development for browser execution and remote/deployed tests for hosted-only features."
4220
+ },
4221
+ ai: {
4222
+ service: "ai",
4223
+ tier: "remote-boundary",
4224
+ reason: "Workers AI inference has no local simulation in Cloudflare local development.",
4225
+ recommendation: "Use DEVFLARE_REMOTE=1/devflare remote enable for real calls, or inject a custom fake for pure tests."
4226
+ },
4227
+ aiGateway: {
4228
+ service: "aiGateway",
4229
+ tier: "remote-boundary",
4230
+ reason: "AI Gateway routing and logs are Cloudflare account resources reached through the Workers AI binding.",
4231
+ recommendation: "Use remote-mode AI Gateway helpers for integration tests and custom fakes for offline unit tests."
4232
+ },
4233
+ vectorize: {
4234
+ service: "vectorize",
4235
+ tier: "remote-boundary",
4236
+ reason: "Cloudflare lists Vectorize with no local simulation.",
4237
+ recommendation: "Use DEVFLARE_REMOTE=1/devflare remote enable for real indexes, or inject a fake Vectorize binding for pure tests."
4238
+ },
4239
+ builds: {
4240
+ service: "builds",
4241
+ tier: "remote-boundary",
4242
+ reason: "Cloudflare Builds/Git-connected Workers are CI/CD orchestration, not a Worker runtime binding.",
4243
+ recommendation: "Run Devflare commands inside your CI; validate Cloudflare build integration with Cloudflare/Wrangler tests."
4244
+ }
4245
+ };
4246
+ function copySupport(entry) {
4247
+ return { ...entry };
4248
+ }
4249
+ function getOfflineSupportMatrix() {
4250
+ return Object.fromEntries(Object.entries(SUPPORT_MATRIX).map(([service, entry]) => [service, copySupport(entry)]));
4251
+ }
4252
+ function describeOfflineSupport(service) {
4253
+ const entry = SUPPORT_MATRIX[service];
4254
+ if (entry) return copySupport(entry);
4255
+ return {
4256
+ service,
4257
+ tier: "remote-boundary",
4258
+ reason: `No offline support classification exists for "${service}".`,
4259
+ recommendation: "Treat this as a remote Cloudflare boundary until Devflare documents a local simulator or fixture."
4260
+ };
4261
+ }
4262
+ function createMissingSecret(binding) {
4263
+ return { async get() {
4264
+ throw new Error(`Offline Secrets Store binding "${binding}" has no value. Pass fixtures.secretsStore.${binding} or write a local secret with devflare secrets --local.`);
4265
+ } };
4266
+ }
4267
+ function isWorkflowBinding(value) {
4268
+ return typeof value.create === "function";
4269
+ }
4270
+ function isPipelineBinding(value) {
4271
+ return typeof value?.send === "function";
4272
+ }
4273
+ function isImagesBinding(value) {
4274
+ return typeof value?.input === "function";
4275
+ }
4276
+ function isHyperdriveBinding(value) {
4277
+ return typeof value?.connectionString === "string";
4278
+ }
4279
+ function isMediaBinding(value) {
4280
+ return typeof value?.input === "function";
4281
+ }
4282
+ function isArtifactsBinding(value) {
4283
+ return typeof value?.create === "function";
4284
+ }
4285
+ function isAISearchInstance(value) {
4286
+ return typeof value?.search === "function";
4287
+ }
4288
+ function isAISearchNamespace(value) {
4289
+ return typeof value?.get === "function";
4290
+ }
4291
+ function addBoundary(remoteBoundaries, service, binding, reason) {
4292
+ remoteBoundaries.push({
4293
+ service,
4294
+ binding,
4295
+ reason,
4296
+ recommendation: describeOfflineSupport(service).recommendation
4297
+ });
4298
+ }
4299
+ function addStaticBindings(env, config) {
4300
+ if (config.vars) Object.assign(env, config.vars);
4301
+ }
4302
+ function addRateLimitBindings(env, bindings) {
4303
+ for (const [name, binding] of Object.entries(bindings?.rateLimits ?? {})) env[name] = createMockRateLimit(binding.simple);
4304
+ }
4305
+ function addVersionMetadataBinding(env, bindings) {
4306
+ if (bindings?.versionMetadata) env[bindings.versionMetadata.binding] = createMockVersionMetadata();
4307
+ }
4308
+ function getHyperdriveConnectionString(name, binding, fixture) {
4309
+ if (typeof fixture === "string") return fixture;
4310
+ const envValue = process.env[`CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_${name}`] ?? process.env[`WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_${name}`];
4311
+ if (envValue?.trim()) return envValue;
4312
+ return normalizeHyperdriveBinding(binding).localConnectionString;
4313
+ }
4314
+ function addHyperdriveBindings(env, bindings, fixtures, missingFixtures) {
4315
+ for (const [name, binding] of Object.entries(bindings?.hyperdrive ?? {})) {
4316
+ const fixture = fixtures.hyperdrive?.[name];
4317
+ if (isHyperdriveBinding(fixture)) {
4318
+ env[name] = fixture;
4319
+ continue;
4320
+ }
4321
+ const connectionString = getHyperdriveConnectionString(name, binding, fixture);
4322
+ if (connectionString) {
4323
+ env[name] = createMockHyperdrive(connectionString);
4324
+ continue;
4325
+ }
4326
+ missingFixtures.push({
4327
+ service: "hyperdrive",
4328
+ binding: name,
4329
+ reason: `Hyperdrive binding "${name}" has no local connection string. Configure bindings.hyperdrive.${name}.localConnectionString or pass fixtures.hyperdrive.${name}.`
4330
+ });
4331
+ }
4332
+ }
4333
+ function addWorkerLoaderBindings(env, bindings, fixtures) {
4334
+ for (const name of Object.keys(bindings?.workerLoaders ?? {})) env[name] = createMockWorkerLoader(fixtures.workerLoaders?.[name]);
4335
+ }
4336
+ function addMTLSCertificateBindings(env, bindings, fixtures) {
4337
+ for (const name of Object.keys(bindings?.mtlsCertificates ?? {})) env[name] = createMockMTLSCertificate(fixtures.mtlsCertificates?.[name]);
4338
+ }
4339
+ function addDispatchNamespaceBindings(env, bindings, fixtures) {
4340
+ for (const name of Object.keys(bindings?.dispatchNamespaces ?? {})) env[name] = createMockDispatchNamespace(fixtures.dispatchNamespaces?.[name]);
4341
+ }
4342
+ function addWorkflowBindings(env, bindings, fixtures) {
4343
+ for (const name of Object.keys(bindings?.workflows ?? {})) {
4344
+ const fixture = fixtures.workflows?.[name];
4345
+ env[name] = fixture && isWorkflowBinding(fixture) ? fixture : createMockWorkflow(fixture);
4346
+ }
4347
+ }
4348
+ function addPipelineBindings(env, bindings, fixtures) {
4349
+ for (const name of Object.keys(bindings?.pipelines ?? {})) {
4350
+ const fixture = fixtures.pipelines?.[name];
4351
+ env[name] = isPipelineBinding(fixture) ? fixture : createMockPipeline();
4352
+ }
4353
+ }
4354
+ function addImagesBindings(env, bindings, fixtures) {
4355
+ for (const name of Object.keys(bindings?.images ?? {})) {
4356
+ const fixture = fixtures.images?.[name];
4357
+ env[name] = isImagesBinding(fixture) ? fixture : createMockImagesBinding(fixture);
4358
+ }
4359
+ }
4360
+ function addMediaBindings(env, bindings, fixtures) {
4361
+ for (const name of Object.keys(bindings?.media ?? {})) {
4362
+ const fixture = fixtures.media?.[name];
4363
+ env[name] = isMediaBinding(fixture) ? fixture : createMockMediaBinding(fixture);
4364
+ }
4365
+ }
4366
+ function addArtifactsBindings(env, bindings, fixtures) {
4367
+ for (const name of Object.keys(bindings?.artifacts ?? {})) {
4368
+ const fixture = fixtures.artifacts?.[name];
4369
+ env[name] = isArtifactsBinding(fixture) ? fixture : createMockArtifacts(fixture);
4370
+ }
4371
+ }
4372
+ function addSecretsStoreBindings(env, bindings, fixtures, localSecretValues, missingFixtures) {
4373
+ for (const name of Object.keys(bindings?.secretsStore ?? {})) {
4374
+ const value = fixtures.secretsStore?.[name] ?? localSecretValues[name];
4375
+ if (value === void 0) {
4376
+ missingFixtures.push({
4377
+ service: "secretsStore",
4378
+ binding: name,
4379
+ reason: `Secrets Store values are not present in fixtures or the local secret store; pass fixtures.secretsStore.${name} or run devflare secrets --local.`
4380
+ });
4381
+ env[name] = createMissingSecret(name);
4382
+ } else env[name] = createMockSecretsStoreSecret(value);
4383
+ }
4384
+ }
4385
+ function addAISearchBindings(env, bindings, fixtures) {
4386
+ for (const [name, binding] of Object.entries(bindings?.aiSearch ?? {})) {
4387
+ const fixture = fixtures.aiSearch?.[name];
4388
+ env[name] = isAISearchInstance(fixture) ? fixture : createMockAISearchInstance({
4389
+ id: binding.instanceName,
4390
+ ...fixture
4391
+ });
4392
+ }
4393
+ }
4394
+ function addAISearchNamespaceBindings(env, bindings, fixtures) {
4395
+ for (const [name, binding] of Object.entries(bindings?.aiSearchNamespaces ?? {})) {
4396
+ const fixture = fixtures.aiSearchNamespaces?.[name];
4397
+ env[name] = isAISearchNamespace(fixture) ? fixture : createMockAISearchNamespace({
4398
+ namespace: binding.namespace,
4399
+ ...fixture
4400
+ });
4401
+ }
4402
+ }
4403
+ /**
4404
+ * Core storage and wiring bindings (kv/d1/r2/queues/durableObjects/services)
4405
+ * have no pure-offline fixture: they need a real Miniflare runtime, which only
4406
+ * `createTestContext()` provides. `createOfflineBindings()` therefore cannot
4407
+ * populate them, and leaving them silently absent from `env` is a surprise.
4408
+ * Surface each one in `missingFixtures` (with `env` left unset rather than a fake)
4409
+ * so callers see exactly which bindings require `createTestContext()` /
4410
+ * `createMock*()` instead of getting an `undefined` lookup at use time.
4411
+ */
4412
+ const OFFLINE_UNAVAILABLE_STORAGE_BINDINGS = [
4413
+ "kv",
4414
+ "d1",
4415
+ "r2",
4416
+ "durableObjects",
4417
+ "services"
4418
+ ];
4419
+ function addUnsupportedStorageBindings(bindings, missingFixtures) {
4420
+ for (const service of OFFLINE_UNAVAILABLE_STORAGE_BINDINGS) {
4421
+ const group = bindings?.[service];
4422
+ for (const name of Object.keys(group ?? {})) missingFixtures.push({
4423
+ service,
4424
+ binding: name,
4425
+ reason: `${service} binding "${name}" needs a real Miniflare runtime and is not created by createOfflineBindings(); it will be undefined. Use createTestContext() (Miniflare-backed) or a createMock* helper for this binding.`
4426
+ });
4427
+ }
4428
+ const queueProducers = (bindings?.queues)?.producers ?? {};
4429
+ for (const name of Object.keys(queueProducers)) missingFixtures.push({
4430
+ service: "queues",
4431
+ binding: name,
4432
+ reason: `queues producer binding "${name}" needs a real Miniflare runtime and is not created by createOfflineBindings(); it will be undefined. Use createTestContext() or createMockQueue() for this binding.`
4433
+ });
4434
+ }
4435
+ function addRemoteBoundaries(remoteBoundaries, bindings) {
4436
+ if (bindings?.ai) addBoundary(remoteBoundaries, "ai", bindings.ai.binding || "AI", "Workers AI inference is not available in offline local simulations.");
4437
+ for (const name of Object.keys(bindings?.vectorize ?? {})) addBoundary(remoteBoundaries, "vectorize", name, "Vectorize has no offline local simulation in Cloudflare local development.");
4438
+ }
4439
+ /**
4440
+ * Builds a deterministic, pure-test env object from Devflare config.
4441
+ *
4442
+ * Covers the bindings that have a pure-offline simulator or fixture (rate
4443
+ * limits, version metadata, hyperdrive, worker loaders, mTLS, dispatch
4444
+ * namespaces, workflows, pipelines, images, media, artifacts, secrets store,
4445
+ * AI Search). It does **not** create the core storage/wiring bindings
4446
+ * (`kv`, `d1`, `r2`, `queues`, `durableObjects`, `services`) — those require a
4447
+ * real Miniflare runtime, which only `createTestContext()` provides. When such a
4448
+ * binding is present in config it is reported in the returned `missingFixtures`
4449
+ * (its `env` entry is left unset rather than silently faked); use
4450
+ * `createTestContext()` or a `createMock*()` helper for those bindings.
4451
+ */
4452
+ function createOfflineBindings(config, fixtures = {}, options = {}) {
4453
+ const env = {};
4454
+ const remoteBoundaries = [];
4455
+ const missingFixtures = [];
4456
+ const bindings = config.bindings;
4457
+ const localSecretValues = options.cwd && options.useLocalSecrets !== false ? resolveLocalSecretValuesForBindings(config, options.cwd) : {};
4458
+ addStaticBindings(env, config);
4459
+ addRateLimitBindings(env, bindings);
4460
+ addVersionMetadataBinding(env, bindings);
4461
+ addHyperdriveBindings(env, bindings, fixtures, missingFixtures);
4462
+ addWorkerLoaderBindings(env, bindings, fixtures);
4463
+ addMTLSCertificateBindings(env, bindings, fixtures);
4464
+ addDispatchNamespaceBindings(env, bindings, fixtures);
4465
+ addWorkflowBindings(env, bindings, fixtures);
4466
+ addPipelineBindings(env, bindings, fixtures);
4467
+ addImagesBindings(env, bindings, fixtures);
4468
+ addMediaBindings(env, bindings, fixtures);
4469
+ addArtifactsBindings(env, bindings, fixtures);
4470
+ addSecretsStoreBindings(env, bindings, fixtures, localSecretValues, missingFixtures);
4471
+ addAISearchBindings(env, bindings, fixtures);
4472
+ addAISearchNamespaceBindings(env, bindings, fixtures);
4473
+ addUnsupportedStorageBindings(bindings, missingFixtures);
4474
+ addRemoteBoundaries(remoteBoundaries, bindings);
4475
+ if (fixtures.custom) Object.assign(env, fixtures.custom);
4476
+ return {
4477
+ env,
4478
+ support: getOfflineSupportMatrix(),
4479
+ remoteBoundaries,
4480
+ missingFixtures
4481
+ };
4482
+ }
4483
+ /**
4484
+ * Convenience wrapper for callers that only need the derived env object.
4485
+ *
4486
+ * Note: the core storage/wiring bindings (`kv`, `d1`, `r2`, `queues`,
4487
+ * `durableObjects`, `services`) are **not** created here and will be
4488
+ * `undefined` in the returned env — they need a real Miniflare runtime. Use
4489
+ * `createTestContext()` or a `createMock*()` helper for them, or call
4490
+ * `createOfflineBindings()` to inspect `missingFixtures` for the exact list.
4491
+ */
4492
+ function createOfflineEnv(config, fixtures = {}, options = {}) {
4493
+ return createOfflineBindings(config, fixtures, options).env;
4494
+ }
4495
+ //#endregion
4496
+ export { cf, clearBundleCache, containers, createContainerManager, createMockAISearchInstance, createMockAISearchNamespace, createMockArtifacts, createMockD1, createMockDispatchNamespace, createMockEnv, createMockHyperdrive, createMockImagesBinding, createMockKV, createMockMTLSCertificate, createMockMediaBinding, createMockPipeline, createMockQueue, createMockR2, createMockRateLimit, createMockSecretsStoreSecret, createMockTestContext, createMockVersionMetadata, createMockWorkerLoader, createMockWorkflow, createOfflineBindings, createOfflineEnv, createTestContext, describeOfflineSupport, detectContainerEngine, email, env, getContainerSkipReason, getOfflineSupportMatrix, hasCrossWorkerDOs, hasServiceBindings, queue, resolveDOBindings, resolveServiceBindings, scheduled, shouldSkip, stopActiveContainers, tail, withTestContext, worker };