devflare 1.0.0-next.19 → 1.0.0-next.20

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 (360) hide show
  1. package/LLM.md +6807 -888
  2. package/README.md +375 -957
  3. package/dist/account-05zgta47.js +475 -0
  4. package/dist/account-b2ag1esh.js +475 -0
  5. package/dist/account-bxtcz61a.js +475 -0
  6. package/dist/account-gyfqg964.js +475 -0
  7. package/dist/account-q6pvs9d9.js +475 -0
  8. package/dist/account-rp4zbvw1.js +475 -0
  9. package/dist/bridge/client.d.ts +5 -0
  10. package/dist/bridge/client.d.ts.map +1 -1
  11. package/dist/bridge/gateway-runtime.d.ts +1 -1
  12. package/dist/bridge/gateway-runtime.d.ts.map +1 -1
  13. package/dist/bridge/miniflare.d.ts +67 -0
  14. package/dist/bridge/miniflare.d.ts.map +1 -1
  15. package/dist/bridge/proxy.d.ts +2 -1
  16. package/dist/bridge/proxy.d.ts.map +1 -1
  17. package/dist/bridge/server.d.ts.map +1 -1
  18. package/dist/browser.d.ts +2520 -40
  19. package/dist/browser.d.ts.map +1 -1
  20. package/dist/browser.js +3 -3
  21. package/dist/build-2s5paw5p.js +54 -0
  22. package/dist/build-4c350cp7.js +54 -0
  23. package/dist/build-e7wym63t.js +54 -0
  24. package/dist/build-ge6qp3t4.js +54 -0
  25. package/dist/build-ta8c6t11.js +54 -0
  26. package/dist/build-wvjj8f28.js +54 -0
  27. package/dist/build-ypg6f2kw.js +54 -0
  28. package/dist/build-yts8wwgf.js +54 -0
  29. package/dist/build-yzkdqexs.js +54 -0
  30. package/dist/cli/build-manifest.d.ts +12 -0
  31. package/dist/cli/build-manifest.d.ts.map +1 -1
  32. package/dist/cli/commands/deploy/metadata.d.ts +18 -0
  33. package/dist/cli/commands/deploy/metadata.d.ts.map +1 -0
  34. package/dist/cli/commands/deploy/prepare.d.ts +23 -0
  35. package/dist/cli/commands/deploy/prepare.d.ts.map +1 -0
  36. package/dist/cli/commands/deploy/runtime.d.ts +4 -0
  37. package/dist/cli/commands/deploy/runtime.d.ts.map +1 -0
  38. package/dist/cli/commands/deploy/verification.d.ts +36 -0
  39. package/dist/cli/commands/deploy/verification.d.ts.map +1 -0
  40. package/dist/cli/commands/deploy.d.ts +2 -2
  41. package/dist/cli/commands/deploy.d.ts.map +1 -1
  42. package/dist/cli/commands/secrets.d.ts +4 -0
  43. package/dist/cli/commands/secrets.d.ts.map +1 -0
  44. package/dist/cli/commands/type-generation/generator.d.ts +67 -1
  45. package/dist/cli/commands/type-generation/generator.d.ts.map +1 -1
  46. package/dist/cli/help-pages/pages/core.d.ts +1 -1
  47. package/dist/cli/help-pages/pages/core.d.ts.map +1 -1
  48. package/dist/cli/help-pages/shared.d.ts +1 -1
  49. package/dist/cli/help-pages/shared.d.ts.map +1 -1
  50. package/dist/cli/index.d.ts.map +1 -1
  51. package/dist/cli/index.js +2 -2
  52. package/dist/cli/package-metadata.d.ts.map +1 -1
  53. package/dist/cli/preview-bindings.d.ts.map +1 -1
  54. package/dist/cloudflare/index.js +2 -2
  55. package/dist/cloudflare/preview-registry-store.d.ts +1 -1
  56. package/dist/cloudflare/preview-registry-store.d.ts.map +1 -1
  57. package/dist/cloudflare/types.d.ts +1 -1
  58. package/dist/cloudflare/types.d.ts.map +1 -1
  59. package/dist/config/binding-resolution-helpers.d.ts +5 -0
  60. package/dist/config/binding-resolution-helpers.d.ts.map +1 -1
  61. package/dist/config/compiler/bindings.d.ts +14 -0
  62. package/dist/config/compiler/bindings.d.ts.map +1 -0
  63. package/dist/config/compiler/core-helpers.d.ts +6 -0
  64. package/dist/config/compiler/core-helpers.d.ts.map +1 -0
  65. package/dist/config/compiler/do-workers.d.ts +34 -0
  66. package/dist/config/compiler/do-workers.d.ts.map +1 -0
  67. package/dist/config/compiler/paths.d.ts +18 -0
  68. package/dist/config/compiler/paths.d.ts.map +1 -0
  69. package/dist/config/compiler/types.d.ts +267 -0
  70. package/dist/config/compiler/types.d.ts.map +1 -0
  71. package/dist/config/compiler.d.ts +11 -175
  72. package/dist/config/compiler.d.ts.map +1 -1
  73. package/dist/config/deploy-resources.d.ts.map +1 -1
  74. package/dist/config/index.d.ts +1 -1
  75. package/dist/config/index.d.ts.map +1 -1
  76. package/dist/config/local-dev-vars.d.ts +15 -0
  77. package/dist/config/local-dev-vars.d.ts.map +1 -0
  78. package/dist/config/preview-resources.d.ts.map +1 -1
  79. package/dist/config/preview.d.ts.map +1 -1
  80. package/dist/config/resolve.d.ts.map +1 -1
  81. package/dist/config/resource-resolution.d.ts.map +1 -1
  82. package/dist/config/schema-bindings.d.ts +559 -19
  83. package/dist/config/schema-bindings.d.ts.map +1 -1
  84. package/dist/config/schema-env.d.ts +1306 -34
  85. package/dist/config/schema-env.d.ts.map +1 -1
  86. package/dist/config/schema-normalization.d.ts +97 -1
  87. package/dist/config/schema-normalization.d.ts.map +1 -1
  88. package/dist/config/schema-runtime.d.ts +245 -7
  89. package/dist/config/schema-runtime.d.ts.map +1 -1
  90. package/dist/config/schema.d.ts +2976 -57
  91. package/dist/config/schema.d.ts.map +1 -1
  92. package/dist/config-6m0n7d84.js +59 -0
  93. package/dist/config-7cf004ag.js +59 -0
  94. package/dist/config-b98dp58n.js +59 -0
  95. package/dist/config-cf3djhqy.js +59 -0
  96. package/dist/config-entry.js +1 -1
  97. package/dist/config-wa7hm0w9.js +59 -0
  98. package/dist/deploy-1jfagtn9.js +1055 -0
  99. package/dist/deploy-2afw0jfg.js +1055 -0
  100. package/dist/deploy-2fzj68kq.js +1055 -0
  101. package/dist/deploy-57nzn9wj.js +1045 -0
  102. package/dist/deploy-asyryrvm.js +1055 -0
  103. package/dist/deploy-hc927rw6.js +1045 -0
  104. package/dist/deploy-pnnf8tgy.js +1045 -0
  105. package/dist/deploy-q33bw715.js +1055 -0
  106. package/dist/deploy-tmdgecs3.js +1055 -0
  107. package/dist/deploy-v0y8kczr.js +1055 -0
  108. package/dist/deploy-xhj6zbcx.js +1055 -0
  109. package/dist/dev-1mvcts8w.js +2515 -0
  110. package/dist/dev-2a283xts.js +2515 -0
  111. package/dist/dev-62nhytf8.js +2505 -0
  112. package/dist/dev-75acm2xj.js +2478 -0
  113. package/dist/dev-802rg9dp.js +2515 -0
  114. package/dist/dev-d1bb2t0f.js +2515 -0
  115. package/dist/dev-dwry8494.js +2489 -0
  116. package/dist/dev-g6112y4w.js +2515 -0
  117. package/dist/dev-h2kneh95.js +2496 -0
  118. package/dist/dev-kybq3mwr.js +2489 -0
  119. package/dist/dev-n8qndkyg.js +2512 -0
  120. package/dist/dev-p32fkbwf.js +2489 -0
  121. package/dist/dev-qm9d4mfh.js +2478 -0
  122. package/dist/dev-rcthnse5.js +2473 -0
  123. package/dist/dev-server/dev-server-state.d.ts +1 -0
  124. package/dist/dev-server/dev-server-state.d.ts.map +1 -1
  125. package/dist/dev-server/miniflare-bindings.d.ts +44 -1
  126. package/dist/dev-server/miniflare-bindings.d.ts.map +1 -1
  127. package/dist/dev-server/miniflare-dev-config.d.ts +1 -0
  128. package/dist/dev-server/miniflare-dev-config.d.ts.map +1 -1
  129. package/dist/dev-server/miniflare-log.d.ts +8 -0
  130. package/dist/dev-server/miniflare-log.d.ts.map +1 -1
  131. package/dist/dev-server/miniflare-worker-config.d.ts +31 -1
  132. package/dist/dev-server/miniflare-worker-config.d.ts.map +1 -1
  133. package/dist/dev-server/server.d.ts.map +1 -1
  134. package/dist/dev-server/vite-process.d.ts +1 -0
  135. package/dist/dev-server/vite-process.d.ts.map +1 -1
  136. package/dist/dev-tgwja5mz.js +2496 -0
  137. package/dist/doctor-2shhdak6.js +245 -0
  138. package/dist/doctor-5g73w40j.js +245 -0
  139. package/dist/doctor-gamefzcs.js +245 -0
  140. package/dist/doctor-rn53ctfs.js +245 -0
  141. package/dist/index-01kehw41.js +348 -0
  142. package/dist/index-06bg0z9y.js +185 -0
  143. package/dist/index-0d7tw5r4.js +136 -0
  144. package/dist/index-0m6e4mxz.js +133 -0
  145. package/dist/index-0vah20er.js +1410 -0
  146. package/dist/index-0wa0ebm1.js +68 -0
  147. package/dist/index-1714y3cz.js +1410 -0
  148. package/dist/index-1qs5gcm7.js +895 -0
  149. package/dist/index-29k04v43.js +574 -0
  150. package/dist/index-2jywf4pz.js +1372 -0
  151. package/dist/index-2qhk9nbx.js +1372 -0
  152. package/dist/index-2vq6bveq.js +574 -0
  153. package/dist/index-36h8gkhb.js +1088 -0
  154. package/dist/index-38fq7pww.js +560 -0
  155. package/dist/index-3bxqn033.js +1410 -0
  156. package/dist/index-3jme4hgw.js +1234 -0
  157. package/dist/index-3p7s9mk9.js +360 -0
  158. package/dist/index-47w35sft.js +244 -0
  159. package/dist/index-4by4c7rm.js +52 -0
  160. package/dist/index-4phjwd6h.js +412 -0
  161. package/dist/index-4z5jrw0j.js +594 -0
  162. package/dist/index-51mzqy0d.js +895 -0
  163. package/dist/index-53pqqpq9.js +74 -0
  164. package/dist/index-5enq8ntr.js +1766 -0
  165. package/dist/index-5fnq9r9m.js +1410 -0
  166. package/dist/index-5w9f2b17.js +695 -0
  167. package/dist/index-627srx16.js +45 -0
  168. package/dist/index-6bqgf5x8.js +227 -0
  169. package/dist/index-6xknvbyk.js +1088 -0
  170. package/dist/index-7ef3ktz5.js +1372 -0
  171. package/dist/index-7hpjfdzh.js +185 -0
  172. package/dist/index-8052df4m.js +627 -0
  173. package/dist/index-82epjzrr.js +1410 -0
  174. package/dist/index-82z7rvz6.js +1238 -0
  175. package/dist/index-8atc1yb9.js +68 -0
  176. package/dist/index-8tj0awnv.js +476 -0
  177. package/dist/index-8x745h59.js +1069 -0
  178. package/dist/index-9bawzcny.js +574 -0
  179. package/dist/index-9bjjqdfc.js +236 -0
  180. package/dist/index-9d7x3vfr.js +236 -0
  181. package/dist/index-9nf8zs4p.js +1069 -0
  182. package/dist/index-acwbmagz.js +412 -0
  183. package/dist/index-aqjdaem7.js +74 -0
  184. package/dist/index-b6448fd0.js +133 -0
  185. package/dist/index-b9j55r7q.js +240 -0
  186. package/dist/index-bdatd1za.js +1372 -0
  187. package/dist/index-c3nxftnp.js +699 -0
  188. package/dist/index-c643s0gv.js +488 -0
  189. package/dist/index-d2md1j3d.js +185 -0
  190. package/dist/index-dbr6bfz6.js +528 -0
  191. package/dist/index-dd1g0g7e.js +360 -0
  192. package/dist/index-dktb9az5.js +1372 -0
  193. package/dist/index-dm9q84c7.js +360 -0
  194. package/dist/index-f51mkh13.js +1088 -0
  195. package/dist/index-f86n1fpd.js +55 -0
  196. package/dist/index-fnk0tkw7.js +412 -0
  197. package/dist/index-g5aq66bj.js +1534 -0
  198. package/dist/index-gj5qh491.js +54 -0
  199. package/dist/index-gq39t0rx.js +895 -0
  200. package/dist/index-h5dqna7q.js +1410 -0
  201. package/dist/index-hjs9j2g9.js +895 -0
  202. package/dist/index-hn5nbxbt.js +147 -0
  203. package/dist/index-hpjh0qjx.js +1723 -0
  204. package/dist/index-hs6ekcfs.js +412 -0
  205. package/dist/index-jdzrvnfj.js +52 -0
  206. package/dist/index-jg720mq7.js +476 -0
  207. package/dist/index-jrzddxvt.js +2167 -0
  208. package/dist/index-kgstnk6g.js +239 -0
  209. package/dist/index-khnw972v.js +117 -0
  210. package/dist/index-kwqff3ba.js +1410 -0
  211. package/dist/index-m2v0fj08.js +74 -0
  212. package/dist/index-mjve6tqn.js +447 -0
  213. package/dist/index-mkxzgn0q.js +1372 -0
  214. package/dist/index-mzmq3v0d.js +1088 -0
  215. package/dist/index-ng9n3znd.js +1372 -0
  216. package/dist/index-nhbkm2ba.js +467 -0
  217. package/dist/index-nrfhk0k5.js +1088 -0
  218. package/dist/index-p0zppqxm.js +467 -0
  219. package/dist/index-pkxf6h87.js +895 -0
  220. package/dist/index-pqp4312v.js +52 -0
  221. package/dist/index-pw9jn6kz.js +574 -0
  222. package/dist/index-q31ne0xa.js +412 -0
  223. package/dist/index-qf2dkqxh.js +249 -0
  224. package/dist/index-qmtdf7k5.js +639 -0
  225. package/dist/index-qwgr4q7s.js +37 -0
  226. package/dist/index-rab2dfh3.js +494 -0
  227. package/dist/index-rz7rx80s.js +1410 -0
  228. package/dist/index-s37h3jgk.js +572 -0
  229. package/dist/index-sb705m7d.js +52 -0
  230. package/dist/index-syscwrjp.js +1576 -0
  231. package/dist/index-t14zr0ys.js +1063 -0
  232. package/dist/index-tjc99447.js +68 -0
  233. package/dist/index-v35460hf.js +574 -0
  234. package/dist/index-v7q00d1e.js +1410 -0
  235. package/dist/index-vkkmx4xe.js +1372 -0
  236. package/dist/index-vrps1gky.js +2202 -0
  237. package/dist/index-w4c9vmvg.js +1517 -0
  238. package/dist/index-wqd8n2qk.js +574 -0
  239. package/dist/index-x12e6fzy.js +476 -0
  240. package/dist/index-xagpz645.js +2199 -0
  241. package/dist/index-xbth1r6e.js +572 -0
  242. package/dist/index-xm9fqhcb.js +447 -0
  243. package/dist/index-y59hnmd0.js +132 -0
  244. package/dist/index-y7mkb00x.js +133 -0
  245. package/dist/index-z40mjts9.js +212 -0
  246. package/dist/index-z5k5bjc7.js +1218 -0
  247. package/dist/index-z73sytma.js +895 -0
  248. package/dist/index-zjv6apef.js +1410 -0
  249. package/dist/index.js +8 -8
  250. package/dist/init-cwpergap.js +180 -0
  251. package/dist/login-83bjfhvz.js +77 -0
  252. package/dist/login-ddw888xb.js +77 -0
  253. package/dist/login-e7pytkdc.js +77 -0
  254. package/dist/login-fe0brfcr.js +77 -0
  255. package/dist/login-h7sm5trm.js +77 -0
  256. package/dist/login-vd0m3xr4.js +77 -0
  257. package/dist/previews-2wfvsjfy.js +1337 -0
  258. package/dist/previews-31feb8r3.js +1337 -0
  259. package/dist/previews-3w4pxqby.js +1337 -0
  260. package/dist/previews-93ttrf5f.js +1337 -0
  261. package/dist/previews-bdrefjzx.js +1337 -0
  262. package/dist/previews-cfcn56b4.js +1337 -0
  263. package/dist/previews-mssq1hrm.js +1337 -0
  264. package/dist/previews-tcaz1gt8.js +1337 -0
  265. package/dist/productions-4h80j2c7.js +505 -0
  266. package/dist/productions-86jaqt7m.js +505 -0
  267. package/dist/productions-bn2q31my.js +505 -0
  268. package/dist/productions-dv8g7f6g.js +505 -0
  269. package/dist/productions-e2m9s4tr.js +505 -0
  270. package/dist/productions-fgshs1m7.js +505 -0
  271. package/dist/productions-hphmt68n.js +505 -0
  272. package/dist/productions-vhq7yx86.js +505 -0
  273. package/dist/runtime/index.js +8 -8
  274. package/dist/secrets/local-secrets.d.ts +46 -0
  275. package/dist/secrets/local-secrets.d.ts.map +1 -0
  276. package/dist/secrets-8wcj47nh.js +91 -0
  277. package/dist/secrets-b2ww34ta.js +91 -0
  278. package/dist/secrets-b7g4z621.js +91 -0
  279. package/dist/shims/local-media-bindings.d.ts +19 -0
  280. package/dist/shims/local-media-bindings.d.ts.map +1 -0
  281. package/dist/shims/local-worker-loader.d.ts +3 -0
  282. package/dist/shims/local-worker-loader.d.ts.map +1 -0
  283. package/dist/sveltekit/index.js +163 -26
  284. package/dist/sveltekit/local-bindings.d.ts +4 -0
  285. package/dist/sveltekit/local-bindings.d.ts.map +1 -0
  286. package/dist/sveltekit/platform.d.ts +8 -0
  287. package/dist/sveltekit/platform.d.ts.map +1 -1
  288. package/dist/test/ai-search.d.ts +39 -0
  289. package/dist/test/ai-search.d.ts.map +1 -0
  290. package/dist/test/binding-hints.d.ts.map +1 -1
  291. package/dist/test/cf.d.ts +3 -3
  292. package/dist/test/containers.d.ts +87 -0
  293. package/dist/test/containers.d.ts.map +1 -0
  294. package/dist/test/index.d.ts +4 -1
  295. package/dist/test/index.d.ts.map +1 -1
  296. package/dist/test/index.js +2833 -543
  297. package/dist/test/local-worker-loader.d.ts +3 -0
  298. package/dist/test/local-worker-loader.d.ts.map +1 -0
  299. package/dist/test/offline-bindings.d.ts +65 -0
  300. package/dist/test/offline-bindings.d.ts.map +1 -0
  301. package/dist/test/queue.d.ts.map +1 -1
  302. package/dist/test/remote-ai.d.ts.map +1 -1
  303. package/dist/test/should-skip.d.ts +14 -0
  304. package/dist/test/should-skip.d.ts.map +1 -1
  305. package/dist/test/simple-context-bindings.d.ts.map +1 -1
  306. package/dist/test/simple-context-durable-objects.d.ts.map +1 -1
  307. package/dist/test/simple-context-gateway-script.d.ts +1 -1
  308. package/dist/test/simple-context-gateway-script.d.ts.map +1 -1
  309. package/dist/test/simple-context-lifecycle.d.ts.map +1 -1
  310. package/dist/test/simple-context-mfconfig.d.ts +4 -1
  311. package/dist/test/simple-context-mfconfig.d.ts.map +1 -1
  312. package/dist/test/simple-context-multi-worker.d.ts.map +1 -1
  313. package/dist/test/simple-context-startup.d.ts.map +1 -1
  314. package/dist/test/tail.d.ts.map +1 -1
  315. package/dist/test/utilities/artifacts.d.ts +11 -0
  316. package/dist/test/utilities/artifacts.d.ts.map +1 -0
  317. package/dist/test/utilities/context.d.ts +39 -0
  318. package/dist/test/utilities/context.d.ts.map +1 -0
  319. package/dist/test/utilities/d1.d.ts +21 -0
  320. package/dist/test/utilities/d1.d.ts.map +1 -0
  321. package/dist/test/utilities/env.d.ts +40 -0
  322. package/dist/test/utilities/env.d.ts.map +1 -0
  323. package/dist/test/utilities/kv.d.ts +11 -0
  324. package/dist/test/utilities/kv.d.ts.map +1 -0
  325. package/dist/test/utilities/media.d.ts +16 -0
  326. package/dist/test/utilities/media.d.ts.map +1 -0
  327. package/dist/test/utilities/platform.d.ts +38 -0
  328. package/dist/test/utilities/platform.d.ts.map +1 -0
  329. package/dist/test/utilities/queue.d.ts +5 -0
  330. package/dist/test/utilities/queue.d.ts.map +1 -0
  331. package/dist/test/utilities/r2.d.ts +12 -0
  332. package/dist/test/utilities/r2.d.ts.map +1 -0
  333. package/dist/test/utilities/workflows.d.ts +26 -0
  334. package/dist/test/utilities/workflows.d.ts.map +1 -0
  335. package/dist/test/utilities.d.ts +10 -106
  336. package/dist/test/utilities.d.ts.map +1 -1
  337. package/dist/types-2ejrbba1.js +695 -0
  338. package/dist/types-7jkbm95a.js +695 -0
  339. package/dist/types-a2fk9yns.js +695 -0
  340. package/dist/types-dyb3c6zw.js +695 -0
  341. package/dist/types-e2n9f3pd.js +695 -0
  342. package/dist/types-j4s6qcrc.js +695 -0
  343. package/dist/utils/send-email.d.ts.map +1 -1
  344. package/dist/utils/send-email.js +1 -1
  345. package/dist/vite/index.js +6 -6
  346. package/dist/vite/plugin-context.d.ts.map +1 -1
  347. package/dist/worker-663em30d.js +513 -0
  348. package/dist/worker-argxc7fb.js +513 -0
  349. package/dist/worker-entry/composed-worker.d.ts.map +1 -1
  350. package/dist/worker-entry/surface-paths.d.ts +2 -0
  351. package/dist/worker-entry/surface-paths.d.ts.map +1 -1
  352. package/dist/worker-fcdsnj14.js +513 -0
  353. package/dist/worker-fk42rzse.js +513 -0
  354. package/dist/worker-jkemk8d2.js +513 -0
  355. package/dist/worker-m4ze8djx.js +513 -0
  356. package/dist/worker-wnan5dca.js +513 -0
  357. package/dist/worker-yw3atfb1.js +513 -0
  358. package/dist/workflows/local-workflow-entrypoints.d.ts +7 -0
  359. package/dist/workflows/local-workflow-entrypoints.d.ts.map +1 -0
  360. package/package.json +13 -12
@@ -4,7 +4,7 @@ import {
4
4
  } from "../index-d8bdkx2h.js";
5
5
  import {
6
6
  discoverEntrypointsSync
7
- } from "../index-zt22fe2j.js";
7
+ } from "../index-gj5qh491.js";
8
8
  import {
9
9
  resolvePackageSpecifier
10
10
  } from "../index-t4fhcx1n.js";
@@ -14,56 +14,81 @@ import {
14
14
  import {
15
15
  transformWorkerEntrypoint
16
16
  } from "../index-7k278fgz.js";
17
+ import {
18
+ buildHyperdrivesConfig,
19
+ bundleWorkflowEntrypointScript
20
+ } from "../index-01kehw41.js";
21
+ import"../index-gq39t0rx.js";
17
22
  import {
18
23
  createRouteResolve,
19
24
  invokeFetchModule,
20
25
  matchFetchRoute,
21
26
  resolveFetchHandler
22
- } from "../index-tt4fsv91.js";
27
+ } from "../index-fnk0tkw7.js";
23
28
  import {
24
29
  __clearTestContext,
25
30
  __setTestContext,
26
- createEmailEvent,
27
- createFetchEvent,
28
- createQueueEvent,
29
- createScheduledEvent,
30
- createTailEvent,
31
- env,
32
- runWithContext,
33
- runWithEventContext
34
- } from "../index-esjrgt3y.js";
31
+ env
32
+ } from "../index-0wa0ebm1.js";
35
33
  import"../index-a855bdsx.js";
36
34
  import {
37
35
  discoverRoutes
38
- } from "../index-3e7by9sy.js";
36
+ } from "../index-qf2dkqxh.js";
39
37
  import {
40
38
  DEFAULT_DO_PATTERN,
41
39
  findFiles,
42
40
  findFilesSync
43
- } from "../index-rbht7m9r.js";
41
+ } from "../index-qwgr4q7s.js";
44
42
  import"../index-65e7xx1a.js";
45
43
  import {
46
44
  findDurableObjectClasses
47
45
  } from "../index-vhqww6tt.js";
48
46
  import {
47
+ createLocalWorkerLoaderBinding,
48
+ disposeLocalWorkerLoaderBindings,
49
49
  extractBindingHints
50
- } from "../index-xskax7r3.js";
50
+ } from "../index-y59hnmd0.js";
51
+ import {
52
+ buildLocalBindingShimServiceConfig
53
+ } from "../index-dm9q84c7.js";
54
+ import {
55
+ buildLocalSecretWrappedBindingConfig,
56
+ resolveLocalSecretValuesForBindings
57
+ } from "../index-hn5nbxbt.js";
51
58
  import {
52
59
  BridgeClient,
60
+ createEmailEvent,
53
61
  createEnvProxy,
62
+ createFetchEvent,
63
+ createQueueEvent,
64
+ createScheduledEvent,
65
+ createTailEvent,
66
+ runWithContext,
67
+ runWithEventContext,
54
68
  setBindingHints
55
- } from "../index-a75ejnvt.js";
69
+ } from "../index-vrps1gky.js";
56
70
  import {
57
71
  createLocalSendEmailBinding,
58
72
  wrapEnvSendEmailBindings
59
- } from "../index-124e9t4t.js";
60
- import"../index-kawa49m8.js";
73
+ } from "../index-kgstnk6g.js";
74
+ import {
75
+ applyLocalDevVarsToConfig
76
+ } from "../index-c3nxftnp.js";
61
77
  import {
62
78
  getLocalD1DatabaseIdentifier,
63
79
  loadConfig,
80
+ normalizeArtifactsBinding,
64
81
  normalizeDOBinding,
82
+ normalizeDispatchNamespaceBinding,
83
+ normalizeHyperdriveBinding,
84
+ normalizeImagesBinding,
85
+ normalizeMediaBinding,
86
+ normalizeMtlsCertificateBinding,
87
+ normalizePipelineBinding,
88
+ normalizeSecretsStoreBinding,
89
+ normalizeWorkflowBinding,
65
90
  resolveConfigPath
66
- } from "../index-5vzcszr2.js";
91
+ } from "../index-syscwrjp.js";
67
92
  import {
68
93
  getEffectiveAccountId,
69
94
  getPrimaryAccount
@@ -72,7 +97,7 @@ import {
72
97
  getApiToken,
73
98
  isAuthenticated
74
99
  } from "../index-mg8vwqxf.js";
75
- import"../index-c0whkev9.js";
100
+ import"../index-z40mjts9.js";
76
101
  import"../index-q8f4kawk.js";
77
102
  import {
78
103
  __require
@@ -456,7 +481,8 @@ import { readFile } from "fs/promises";
456
481
  import { dirname as dirname3, join as join3 } from "path";
457
482
 
458
483
  // src/test/simple-context-gateway-script.ts
459
- function buildGatewayScript(bundledCode, wrappers) {
484
+ function buildGatewayScript(bundledCode, wrappers, nativeRpcBindingNames = []) {
485
+ const nativeRpcBindingsLiteral = JSON.stringify(nativeRpcBindingNames);
460
486
  return `
461
487
  // Bundled transport + DO classes
462
488
  ${bundledCode}
@@ -464,6 +490,8 @@ ${bundledCode}
464
490
  // DO Wrappers with RPC
465
491
  ${wrappers}
466
492
 
493
+ const __nativeRpcBindings = new Set(${nativeRpcBindingsLiteral})
494
+
467
495
  // Transport encoding helper
468
496
  const __transportEncoders = typeof transport !== 'undefined' ? transport : {}
469
497
 
@@ -584,7 +612,7 @@ async function executeRpc(env, method, params) {
584
612
  const [, idSerialized, rpcMethod, rpcParams] = params
585
613
  const stub = binding.get(binding.idFromString(idSerialized.hex))
586
614
 
587
- if (typeof stub[rpcMethod] === 'function') {
615
+ if (__nativeRpcBindings.has(bindingName) && typeof stub[rpcMethod] === 'function') {
588
616
  let result = await stub[rpcMethod](...(rpcParams || []))
589
617
  result = __encodeTransport(result)
590
618
  return result
@@ -949,17 +977,22 @@ async function bundleDurableObjectModules(configDir, doInfos, transportFile) {
949
977
  return await result.outputs[0].text();
950
978
  }
951
979
  async function buildDurableObjectGateway(config, configDir, transportFile) {
980
+ const workflowEntrypointScript = await bundleWorkflowEntrypointScript(config, configDir);
952
981
  if (!config.bindings?.durableObjects) {
953
982
  return {
954
- script: buildGatewayScript("", "")
983
+ script: buildGatewayScript(workflowEntrypointScript, "")
955
984
  };
956
985
  }
957
986
  const { doConfig, doInfos } = await resolveLocalDurableObjects(config, configDir);
958
987
  const bundledCode = await bundleDurableObjectModules(configDir, doInfos, transportFile);
959
988
  const wrapperCode = buildWrapperCode(doInfos);
989
+ const entrypointCode = [workflowEntrypointScript, bundledCode].filter(Boolean).join(`
990
+
991
+ `);
992
+ const nativeRpcBindingNames = doInfos.filter((info) => info.nativeRpc).map((info) => info.name);
960
993
  return {
961
994
  durableObjects: doConfig,
962
- script: buildGatewayScript(bundledCode, wrapperCode)
995
+ script: buildGatewayScript(entrypointCode, wrapperCode, nativeRpcBindingNames)
963
996
  };
964
997
  }
965
998
 
@@ -1017,9 +1050,70 @@ function createRemoteCloudflareClient(accountId) {
1017
1050
  }
1018
1051
 
1019
1052
  // src/test/remote-ai.ts
1053
+ function encodePathSegment(value) {
1054
+ return encodeURIComponent(value);
1055
+ }
1056
+ function applyExtraHeaders(headers, extraHeaders) {
1057
+ if (!extraHeaders) {
1058
+ return;
1059
+ }
1060
+ for (const [key, value] of Object.entries(extraHeaders)) {
1061
+ headers.set(key, String(value));
1062
+ }
1063
+ }
1064
+ function createRemoteAIGateway(cloudflare, gatewayId, owner) {
1065
+ const encodedGatewayId = encodePathSegment(gatewayId);
1066
+ const gateway = {
1067
+ async patchLog(logId, data) {
1068
+ await cloudflare.jsonRequest({
1069
+ method: "PATCH",
1070
+ path: `/ai-gateway/gateways/${encodedGatewayId}/logs/${encodePathSegment(logId)}`,
1071
+ serviceLabel: "AI Gateway",
1072
+ body: JSON.stringify(data)
1073
+ });
1074
+ },
1075
+ async getLog(logId) {
1076
+ return cloudflare.jsonRequest({
1077
+ method: "GET",
1078
+ path: `/ai-gateway/gateways/${encodedGatewayId}/logs/${encodePathSegment(logId)}`,
1079
+ serviceLabel: "AI Gateway"
1080
+ });
1081
+ },
1082
+ async getUrl(provider) {
1083
+ const accountId = await cloudflare.getAccountId();
1084
+ const baseUrl = `https://gateway.ai.cloudflare.com/v1/${encodePathSegment(accountId)}/${encodedGatewayId}`;
1085
+ return provider ? `${baseUrl}/${encodePathSegment(provider)}` : `${baseUrl}/`;
1086
+ },
1087
+ async run(data, options) {
1088
+ const [url, token] = await Promise.all([gateway.getUrl(), cloudflare.getToken()]);
1089
+ const headers = new Headers({
1090
+ Authorization: `Bearer ${token}`,
1091
+ "Content-Type": "application/json"
1092
+ });
1093
+ applyExtraHeaders(headers, options?.extraHeaders);
1094
+ const response = await fetch(url, {
1095
+ method: "POST",
1096
+ headers,
1097
+ body: JSON.stringify(data),
1098
+ signal: options?.signal
1099
+ });
1100
+ const logId = response.headers.get("cf-aig-log-id") ?? response.headers.get("cf-ai-gateway-log-id");
1101
+ if (logId) {
1102
+ owner.aiGatewayLogId = logId;
1103
+ }
1104
+ if (!response.ok) {
1105
+ const errorText = await response.text();
1106
+ throw new Error(`AI Gateway API error (${response.status}): ${errorText}`);
1107
+ }
1108
+ return response;
1109
+ }
1110
+ };
1111
+ return gateway;
1112
+ }
1020
1113
  function createRemoteAI(accountId) {
1021
1114
  const cloudflare = createRemoteCloudflareClient(accountId);
1022
1115
  const ai = {
1116
+ aiGatewayLogId: null,
1023
1117
  async run(model, inputs) {
1024
1118
  return cloudflare.jsonRequest({
1025
1119
  method: "POST",
@@ -1028,9 +1122,8 @@ function createRemoteAI(accountId) {
1028
1122
  body: JSON.stringify(inputs)
1029
1123
  });
1030
1124
  },
1031
- gateway(_gatewayId) {
1032
- console.warn("AI Gateway is not supported in remote test mode");
1033
- return ai;
1125
+ gateway(gatewayId) {
1126
+ return createRemoteAIGateway(cloudflare, gatewayId, ai);
1034
1127
  }
1035
1128
  };
1036
1129
  return ai;
@@ -1101,169 +1194,1106 @@ function createRemoteVectorize(indexName, accountId) {
1101
1194
  return vectorize;
1102
1195
  }
1103
1196
 
1104
- // src/test/simple-context-bindings.ts
1105
- function buildRemoteAndStaticBindings(config) {
1106
- const remoteBindings = {};
1107
- if (isRemoteModeActive()) {
1108
- if (config.bindings?.ai) {
1109
- const aiBindingName = config.bindings.ai.binding || "AI";
1110
- remoteBindings[aiBindingName] = createRemoteAI(config.accountId);
1111
- }
1112
- if (config.bindings?.vectorize) {
1113
- for (const [name, vectorConfig] of Object.entries(config.bindings.vectorize)) {
1114
- remoteBindings[name] = createRemoteVectorize(vectorConfig.indexName, config.accountId);
1115
- }
1116
- }
1117
- }
1118
- if (config.vars) {
1119
- for (const [key, value] of Object.entries(config.vars)) {
1120
- remoteBindings[key] = value;
1121
- }
1122
- }
1123
- if (config.bindings?.sendEmail) {
1124
- for (const [name, binding] of Object.entries(config.bindings.sendEmail)) {
1125
- remoteBindings[name] = createLocalSendEmailBinding(binding);
1126
- }
1127
- }
1128
- return remoteBindings;
1197
+ // src/test/utilities/context.ts
1198
+ function createMockTestContext(options = {}) {
1199
+ const waitUntilPromises = [];
1200
+ const ctx = {
1201
+ waitUntil(promise) {
1202
+ waitUntilPromises.push(promise);
1203
+ },
1204
+ passThroughOnException() {},
1205
+ props: {}
1206
+ };
1207
+ return {
1208
+ env: options.env ?? {},
1209
+ ctx,
1210
+ request: options.request ?? null,
1211
+ waitUntilPromises
1212
+ };
1129
1213
  }
1130
-
1131
- // src/test/email.ts
1132
- import { join as join4 } from "path";
1133
- var miniflarePort = 8787;
1134
- var emailListeners = [];
1135
- var sentEmails = [];
1136
- var emailHandlerPath = null;
1137
- var configDir = null;
1138
- var testEnvGetter = null;
1139
- function configureEmail(options = {}) {
1140
- if (options.port) {
1141
- miniflarePort = options.port;
1142
- }
1143
- emailHandlerPath = options.handlerPath ?? emailHandlerPath;
1144
- configDir = options.configDir ?? configDir;
1145
- testEnvGetter = options.getEnv ?? testEnvGetter;
1214
+ async function withTestContext(options, handler) {
1215
+ const testCtx = createMockTestContext(options);
1216
+ return runWithContext(testCtx.env, testCtx.ctx, options.request ?? null, handler, options.type ?? "fetch");
1146
1217
  }
1147
- function buildRawEmail(options) {
1148
- if (options.raw) {
1149
- return options.raw;
1150
- }
1151
- const lines = [];
1152
- const messageId = `<${Date.now()}-${Math.random().toString(36).slice(2)}@devflare.dev>`;
1153
- const date = new Date().toUTCString();
1154
- lines.push(`From: ${options.from}`);
1155
- lines.push(`To: ${options.to}`);
1156
- lines.push(`Date: ${date}`);
1157
- lines.push(`Message-ID: ${messageId}`);
1158
- if (options.subject) {
1159
- lines.push(`Subject: ${options.subject}`);
1218
+ // src/test/utilities/kv.ts
1219
+ function createMockKV(initialData = {}) {
1220
+ const store = new Map;
1221
+ const metadata = new Map;
1222
+ const encoder = new TextEncoder;
1223
+ const decoder = new TextDecoder;
1224
+ for (const [key, value] of Object.entries(initialData)) {
1225
+ store.set(key, encoder.encode(value));
1160
1226
  }
1161
- if (options.headers) {
1162
- for (const [key, value] of Object.entries(options.headers)) {
1163
- lines.push(`${key}: ${value}`);
1227
+ const toBytes = async (value) => {
1228
+ if (typeof value === "string") {
1229
+ return encoder.encode(value);
1164
1230
  }
1165
- }
1166
- lines.push("MIME-Version: 1.0");
1167
- lines.push("Content-Type: text/plain; charset=UTF-8");
1168
- lines.push("");
1169
- lines.push(options.body ?? "");
1170
- return lines.join(`\r
1171
- `);
1172
- }
1173
- function createEmailHeaders(rawEmail) {
1174
- const headers = new Headers;
1175
- const lines = rawEmail.split(/\r?\n/);
1176
- for (const line of lines) {
1177
- if (!line.trim()) {
1178
- break;
1231
+ if (value instanceof ArrayBuffer) {
1232
+ return new Uint8Array(value.slice(0));
1179
1233
  }
1180
- const colonIndex = line.indexOf(":");
1181
- if (colonIndex <= 0) {
1182
- continue;
1234
+ if (ArrayBuffer.isView(value)) {
1235
+ const view = value;
1236
+ const copy = new Uint8Array(view.byteLength);
1237
+ copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
1238
+ return copy;
1183
1239
  }
1184
- headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim());
1185
- }
1186
- return headers;
1187
- }
1188
- function createRawEmailStream(rawEmail) {
1189
- return new ReadableStream({
1190
- start(controller) {
1191
- controller.enqueue(new TextEncoder().encode(rawEmail));
1192
- controller.close();
1240
+ const reader = value.getReader();
1241
+ const chunks = [];
1242
+ let total = 0;
1243
+ while (true) {
1244
+ const result = await reader.read();
1245
+ if (result.done)
1246
+ break;
1247
+ if (result.value) {
1248
+ const chunk = result.value instanceof Uint8Array ? result.value : new Uint8Array(result.value);
1249
+ chunks.push(chunk);
1250
+ total += chunk.length;
1251
+ }
1193
1252
  }
1194
- });
1195
- }
1196
- function resolveEmailHandler(module) {
1197
- if (typeof module.default === "function") {
1198
- return module.default;
1199
- }
1200
- if (module.default && typeof module.default.email === "function") {
1201
- return module.default.email.bind(module.default);
1202
- }
1203
- if (typeof module.email === "function") {
1204
- return module.email;
1205
- }
1206
- return null;
1207
- }
1208
- function getRecordedRawContent(raw) {
1209
- if (typeof raw === "string") {
1210
- return raw;
1211
- }
1212
- return;
1213
- }
1214
- async function send(options) {
1215
- const raw = buildRawEmail(options);
1216
- if (emailHandlerPath && configDir && testEnvGetter) {
1217
- const absolutePath = join4(configDir, emailHandlerPath);
1218
- const handlerModule = await import(absolutePath);
1219
- const emailHandler = resolveEmailHandler(handlerModule);
1220
- if (!emailHandler) {
1221
- throw new Error(`Email handler at "${emailHandlerPath}" must export a default function or named "email" export.
1222
- NaN`);
1253
+ const combined = new Uint8Array(total);
1254
+ let offset = 0;
1255
+ for (const chunk of chunks) {
1256
+ combined.set(chunk, offset);
1257
+ offset += chunk.length;
1223
1258
  }
1224
- const waitUntilPromises = [];
1225
- const ctx = {
1226
- waitUntil(promise) {
1227
- waitUntilPromises.push(promise);
1228
- },
1229
- passThroughOnException() {},
1230
- props: {}
1231
- };
1232
- const runtimeEnv = testEnvGetter();
1233
- const timestamp = new Date;
1234
- const message = {
1235
- from: options.from,
1236
- to: options.to,
1237
- headers: createEmailHeaders(raw),
1238
- raw: createRawEmailStream(raw),
1239
- rawSize: raw.length,
1240
- setReject(reason) {
1241
- throw new Error(`Email rejected: ${reason}`);
1242
- },
1243
- async forward(rcptTo) {
1244
- recordSentEmail({
1245
- type: "forward",
1246
- from: options.from,
1247
- to: rcptTo,
1248
- raw,
1249
- timestamp
1250
- });
1251
- },
1252
- async reply(message2) {
1253
- recordSentEmail({
1254
- type: "reply",
1255
- from: message2.from ?? options.to,
1256
- to: message2.to ?? options.from,
1257
- raw: getRecordedRawContent(message2.raw),
1258
- timestamp
1259
- });
1259
+ return combined;
1260
+ };
1261
+ const decodeBytes = (bytes, type) => {
1262
+ switch (type) {
1263
+ case "json":
1264
+ return JSON.parse(decoder.decode(bytes));
1265
+ case "arrayBuffer": {
1266
+ const copy = new Uint8Array(bytes.length);
1267
+ copy.set(bytes);
1268
+ return copy.buffer;
1260
1269
  }
1261
- };
1262
- const emailEvent = createEmailEvent(message, runtimeEnv, ctx);
1263
- await runWithEventContext(emailEvent, () => emailHandler(emailEvent));
1264
- await Promise.all(waitUntilPromises);
1265
- return new Response(JSON.stringify({ ok: true, from: options.from, to: options.to }), {
1266
- headers: { "Content-Type": "application/json" }
1270
+ case "stream": {
1271
+ const copy = new Uint8Array(bytes.length);
1272
+ copy.set(bytes);
1273
+ return new ReadableStream({
1274
+ start(controller) {
1275
+ controller.enqueue(copy);
1276
+ controller.close();
1277
+ }
1278
+ });
1279
+ }
1280
+ default:
1281
+ return decoder.decode(bytes);
1282
+ }
1283
+ };
1284
+ const resolveType = (options) => {
1285
+ const type = typeof options === "string" ? options : options?.type ?? "text";
1286
+ return type;
1287
+ };
1288
+ return {
1289
+ async get(key, options) {
1290
+ const bytes = store.get(key);
1291
+ if (bytes === undefined)
1292
+ return null;
1293
+ return decodeBytes(bytes, resolveType(options));
1294
+ },
1295
+ async put(key, value, _options) {
1296
+ const bytes = await toBytes(value);
1297
+ store.set(key, bytes);
1298
+ },
1299
+ async delete(key) {
1300
+ store.delete(key);
1301
+ metadata.delete(key);
1302
+ },
1303
+ async list(options) {
1304
+ const prefix = options?.prefix ?? "";
1305
+ const limit = options?.limit ?? 1000;
1306
+ const keys = Array.from(store.keys()).filter((key) => key.startsWith(prefix)).slice(0, limit).map((name) => ({ name }));
1307
+ return {
1308
+ keys,
1309
+ list_complete: keys.length < limit,
1310
+ cursor: undefined
1311
+ };
1312
+ },
1313
+ async getWithMetadata(key, options) {
1314
+ const bytes = store.get(key);
1315
+ return {
1316
+ value: bytes === undefined ? null : decodeBytes(bytes, resolveType(options)),
1317
+ metadata: metadata.get(key) ?? null
1318
+ };
1319
+ }
1320
+ };
1321
+ }
1322
+ // src/test/utilities/d1.ts
1323
+ var TABLE_NAME_RE = /(?:from|into|update)\s+["'`]?([a-zA-Z_][a-zA-Z0-9_]*)["'`]?/i;
1324
+ var extractTable = (sql) => {
1325
+ const match = TABLE_NAME_RE.exec(sql);
1326
+ return match ? match[1] : null;
1327
+ };
1328
+ var detectOp = (sql) => {
1329
+ const trimmed = sql.trimStart().toLowerCase();
1330
+ if (trimmed.startsWith("select"))
1331
+ return "select";
1332
+ if (trimmed.startsWith("insert"))
1333
+ return "insert";
1334
+ if (trimmed.startsWith("update"))
1335
+ return "update";
1336
+ if (trimmed.startsWith("delete"))
1337
+ return "delete";
1338
+ return "other";
1339
+ };
1340
+ function createMockD1(mockResultsOrOptions = []) {
1341
+ const options = Array.isArray(mockResultsOrOptions) ? { results: mockResultsOrOptions } : mockResultsOrOptions;
1342
+ const tables = new Map;
1343
+ for (const [name, rows] of Object.entries(options.fixtures ?? {})) {
1344
+ tables.set(name, [...rows]);
1345
+ }
1346
+ const fallback = options.results ?? [];
1347
+ const resolveRows = (sql) => {
1348
+ const op = detectOp(sql);
1349
+ const table = extractTable(sql);
1350
+ if (table && tables.has(table)) {
1351
+ return { rows: tables.get(table) ?? [], op, table };
1352
+ }
1353
+ return { rows: [...fallback], op, table };
1354
+ };
1355
+ const createStatement = (sql) => {
1356
+ let boundValues = [];
1357
+ const statement = {
1358
+ bind(...values) {
1359
+ boundValues = values;
1360
+ return statement;
1361
+ },
1362
+ async first(column) {
1363
+ const { rows } = resolveRows(sql);
1364
+ const row = rows[0];
1365
+ if (!row)
1366
+ return null;
1367
+ if (column)
1368
+ return row[column];
1369
+ return row;
1370
+ },
1371
+ async all() {
1372
+ const { rows } = resolveRows(sql);
1373
+ return {
1374
+ results: rows,
1375
+ success: true,
1376
+ meta: { duration: 0, changes: 0, last_row_id: 0 }
1377
+ };
1378
+ },
1379
+ async run() {
1380
+ const { op, table } = resolveRows(sql);
1381
+ let changes = 0;
1382
+ let lastRowId = 0;
1383
+ if (op === "insert" && table) {
1384
+ const rows = tables.get(table) ?? [];
1385
+ const bound = boundValues.length > 0 ? Object.fromEntries(boundValues.map((v, i) => [`col${i}`, v])) : {};
1386
+ rows.push(bound);
1387
+ tables.set(table, rows);
1388
+ changes = 1;
1389
+ lastRowId = rows.length;
1390
+ } else if (op === "delete" && table) {
1391
+ const rows = tables.get(table) ?? [];
1392
+ changes = rows.length;
1393
+ tables.set(table, []);
1394
+ } else if (op === "update" && table) {
1395
+ changes = (tables.get(table) ?? []).length;
1396
+ }
1397
+ return {
1398
+ results: [],
1399
+ success: true,
1400
+ meta: { duration: 0, changes, last_row_id: lastRowId }
1401
+ };
1402
+ },
1403
+ async raw(_options) {
1404
+ const { rows } = resolveRows(sql);
1405
+ return rows.map((row) => Object.values(row));
1406
+ }
1407
+ };
1408
+ return statement;
1409
+ };
1410
+ return {
1411
+ prepare(query) {
1412
+ return createStatement(query);
1413
+ },
1414
+ async exec(_query) {
1415
+ return {
1416
+ results: [],
1417
+ success: true,
1418
+ meta: { duration: 0, changes: 0, last_row_id: 0 }
1419
+ };
1420
+ },
1421
+ async batch(statements) {
1422
+ return statements.map(() => ({
1423
+ results: [],
1424
+ success: true,
1425
+ meta: { duration: 0, changes: 0, last_row_id: 0 }
1426
+ }));
1427
+ },
1428
+ async dump() {
1429
+ return new ArrayBuffer(0);
1430
+ },
1431
+ withSession(_constraintOrBookmark) {
1432
+ return this;
1433
+ }
1434
+ };
1435
+ }
1436
+ // src/test/utilities/r2.ts
1437
+ function createMockR2() {
1438
+ const store = new Map;
1439
+ const createR2Object = (key, content, metadata) => {
1440
+ const encoder = new TextEncoder;
1441
+ const data = encoder.encode(content);
1442
+ return {
1443
+ key,
1444
+ version: "1",
1445
+ size: data.length,
1446
+ etag: `"${key}-etag"`,
1447
+ httpEtag: `"${key}-etag"`,
1448
+ uploaded: new Date,
1449
+ httpMetadata: metadata ?? {},
1450
+ customMetadata: {},
1451
+ checksums: {},
1452
+ storageClass: "Standard",
1453
+ body: new ReadableStream({
1454
+ start(controller) {
1455
+ controller.enqueue(data);
1456
+ controller.close();
1457
+ }
1458
+ }),
1459
+ bodyUsed: false,
1460
+ async arrayBuffer() {
1461
+ return new Uint8Array(data).buffer;
1462
+ },
1463
+ async text() {
1464
+ return content;
1465
+ },
1466
+ async json() {
1467
+ return JSON.parse(content);
1468
+ },
1469
+ async blob() {
1470
+ return new Blob([data]);
1471
+ },
1472
+ writeHttpMetadata(headers) {}
1473
+ };
1474
+ };
1475
+ return {
1476
+ async put(key, value, options) {
1477
+ let content;
1478
+ if (typeof value === "string") {
1479
+ content = value;
1480
+ } else if (value instanceof ArrayBuffer) {
1481
+ content = new TextDecoder().decode(value);
1482
+ } else if (value instanceof Blob) {
1483
+ content = await value.text();
1484
+ } else if (value === null) {
1485
+ content = "";
1486
+ } else {
1487
+ const reader = value.getReader();
1488
+ const chunks = [];
1489
+ let done = false;
1490
+ while (!done) {
1491
+ const result = await reader.read();
1492
+ done = result.done;
1493
+ if (result.value) {
1494
+ chunks.push(new TextDecoder().decode(result.value));
1495
+ }
1496
+ }
1497
+ content = chunks.join("");
1498
+ }
1499
+ store.set(key, { content });
1500
+ return createR2Object(key, content);
1501
+ },
1502
+ async get(key, options) {
1503
+ const item = store.get(key);
1504
+ if (!item)
1505
+ return null;
1506
+ return createR2Object(key, item.content, item.metadata);
1507
+ },
1508
+ async head(key) {
1509
+ const item = store.get(key);
1510
+ if (!item)
1511
+ return null;
1512
+ return {
1513
+ key,
1514
+ version: "1",
1515
+ size: new TextEncoder().encode(item.content).length,
1516
+ etag: `"${key}-etag"`,
1517
+ httpEtag: `"${key}-etag"`,
1518
+ uploaded: new Date,
1519
+ httpMetadata: item.metadata ?? {},
1520
+ customMetadata: {},
1521
+ checksums: {},
1522
+ storageClass: "Standard",
1523
+ writeHttpMetadata(headers) {}
1524
+ };
1525
+ },
1526
+ async delete(keys) {
1527
+ const keyArray = Array.isArray(keys) ? keys : [keys];
1528
+ for (const key of keyArray) {
1529
+ store.delete(key);
1530
+ }
1531
+ },
1532
+ async list(options) {
1533
+ const objects = Array.from(store.entries()).map(([key, { content, metadata }]) => createR2Object(key, content, metadata));
1534
+ return {
1535
+ objects,
1536
+ truncated: false,
1537
+ delimitedPrefixes: []
1538
+ };
1539
+ },
1540
+ async createMultipartUpload(key, options) {
1541
+ throw new Error("Multipart upload not implemented in mock");
1542
+ },
1543
+ async resumeMultipartUpload(key, uploadId) {
1544
+ throw new Error("Multipart upload not implemented in mock");
1545
+ }
1546
+ };
1547
+ }
1548
+ // src/test/utilities/queue.ts
1549
+ function createMockQueue() {
1550
+ const messages = [];
1551
+ const metrics = {
1552
+ backlogCount: 0,
1553
+ backlogBytes: 0
1554
+ };
1555
+ const response = { metadata: { metrics } };
1556
+ return {
1557
+ async metrics() {
1558
+ return metrics;
1559
+ },
1560
+ async send(message, options) {
1561
+ messages.push({ body: message, options });
1562
+ return response;
1563
+ },
1564
+ async sendBatch(batch, options) {
1565
+ for (const message of batch) {
1566
+ messages.push({
1567
+ body: message.body,
1568
+ options: {
1569
+ contentType: message.contentType,
1570
+ delaySeconds: message.delaySeconds ?? options?.delaySeconds
1571
+ }
1572
+ });
1573
+ }
1574
+ return response;
1575
+ },
1576
+ _getMessages() {
1577
+ return messages;
1578
+ }
1579
+ };
1580
+ }
1581
+ // src/test/utilities/platform.ts
1582
+ function createMockRateLimit(options = {}) {
1583
+ const limit = options.limit ?? Number.MAX_SAFE_INTEGER;
1584
+ const periodMs = (options.period ?? 60) * 1000;
1585
+ const windows = new Map;
1586
+ return {
1587
+ async limit({ key }) {
1588
+ const now = Date.now();
1589
+ const existing = windows.get(key);
1590
+ if (!existing || existing.resetAt <= now) {
1591
+ windows.set(key, { count: 1, resetAt: now + periodMs });
1592
+ return { success: limit >= 1 };
1593
+ }
1594
+ existing.count += 1;
1595
+ return { success: existing.count <= limit };
1596
+ }
1597
+ };
1598
+ }
1599
+ function createMockVersionMetadata(metadata = {}) {
1600
+ return {
1601
+ id: metadata.id ?? "devflare-local-version",
1602
+ tag: metadata.tag ?? "local",
1603
+ timestamp: metadata.timestamp ?? "1970-01-01T00:00:00.000Z"
1604
+ };
1605
+ }
1606
+ function createMockSecretsStoreSecret(value) {
1607
+ return {
1608
+ async get() {
1609
+ return value;
1610
+ }
1611
+ };
1612
+ }
1613
+ function defaultPortForDatabaseUrl(url) {
1614
+ if (url.port) {
1615
+ return Number(url.port);
1616
+ }
1617
+ return url.protocol === "mysql:" ? 3306 : 5432;
1618
+ }
1619
+ function createMockHyperdrive(connectionString) {
1620
+ const url = new URL(connectionString);
1621
+ return {
1622
+ connectionString,
1623
+ host: url.hostname,
1624
+ port: defaultPortForDatabaseUrl(url),
1625
+ user: decodeURIComponent(url.username),
1626
+ password: decodeURIComponent(url.password),
1627
+ database: decodeURIComponent(url.pathname.replace(/^\//, "")),
1628
+ connect() {
1629
+ throw new Error("Mock Hyperdrive connect() is not implemented. Use connectionString with your database client, or run a Miniflare-backed test for socket behavior.");
1630
+ }
1631
+ };
1632
+ }
1633
+ function createDefaultWorkerStub() {
1634
+ return {
1635
+ getEntrypoint() {
1636
+ throw new Error("Mock WorkerLoader stub has no entrypoint. Pass createMockWorkerLoader({ stub }) for behavior.");
1637
+ },
1638
+ getDurableObjectClass() {
1639
+ throw new Error("Mock WorkerLoader stub has no Durable Object class. Pass createMockWorkerLoader({ stub }) for behavior.");
1640
+ }
1641
+ };
1642
+ }
1643
+ function createMockWorkerLoader(options = {}) {
1644
+ const stub = options.stub ?? createDefaultWorkerStub();
1645
+ return {
1646
+ get(_name, _getCode) {
1647
+ return stub;
1648
+ },
1649
+ load(_code) {
1650
+ return stub;
1651
+ }
1652
+ };
1653
+ }
1654
+ function defaultMTLSCertificateHandler() {
1655
+ throw new Error("Mock mTLS Certificate Fetcher has no handler. Pass createMockMTLSCertificate(handler) for behavior.");
1656
+ }
1657
+ function createMockMTLSCertificate(handler = defaultMTLSCertificateHandler) {
1658
+ return {
1659
+ async fetch(input, init) {
1660
+ return handler(input, init);
1661
+ }
1662
+ };
1663
+ }
1664
+ function createMockDispatchNamespace(options = {}) {
1665
+ return {
1666
+ get(name) {
1667
+ const worker = options.workers?.[name];
1668
+ if (!worker) {
1669
+ throw new Error(`Mock DispatchNamespace has no worker named "${name}".`);
1670
+ }
1671
+ return typeof worker === "function" ? createMockMTLSCertificate(worker) : worker;
1672
+ }
1673
+ };
1674
+ }
1675
+ // src/test/utilities/workflows.ts
1676
+ function createMockWorkflowInstance(id, options = {}) {
1677
+ let status = options.status ?? "queued";
1678
+ let output = options.output;
1679
+ let error = options.error;
1680
+ return {
1681
+ id,
1682
+ async pause() {
1683
+ status = "paused";
1684
+ },
1685
+ async resume() {
1686
+ status = "running";
1687
+ },
1688
+ async terminate() {
1689
+ status = "terminated";
1690
+ },
1691
+ async restart() {
1692
+ status = "queued";
1693
+ error = undefined;
1694
+ output = undefined;
1695
+ },
1696
+ async status() {
1697
+ return {
1698
+ status,
1699
+ ...error && { error },
1700
+ ...output !== undefined && { output }
1701
+ };
1702
+ },
1703
+ async sendEvent(_event) {}
1704
+ };
1705
+ }
1706
+ function createMockWorkflow(options = {}) {
1707
+ const instances = new Map;
1708
+ let sequence = 0;
1709
+ for (const [id, instanceOptions] of Object.entries(options.instances ?? {})) {
1710
+ instances.set(id, createMockWorkflowInstance(id, instanceOptions));
1711
+ }
1712
+ const createInstance = (id) => {
1713
+ if (instances.has(id)) {
1714
+ throw new Error(`Mock Workflow already has an instance named "${id}".`);
1715
+ }
1716
+ const instance = createMockWorkflowInstance(id);
1717
+ instances.set(id, instance);
1718
+ return instance;
1719
+ };
1720
+ return {
1721
+ async get(id) {
1722
+ const instance = instances.get(id);
1723
+ if (!instance) {
1724
+ throw new Error(`Mock Workflow has no instance named "${id}".`);
1725
+ }
1726
+ return instance;
1727
+ },
1728
+ async create(options2) {
1729
+ const id = options2?.id ?? `mock-workflow-${++sequence}`;
1730
+ return createInstance(id);
1731
+ },
1732
+ async createBatch(batch) {
1733
+ return batch.map((options2) => {
1734
+ const id = options2.id ?? `mock-workflow-${++sequence}`;
1735
+ return createInstance(id);
1736
+ });
1737
+ }
1738
+ };
1739
+ }
1740
+ function createMockPipeline() {
1741
+ const records = [];
1742
+ return {
1743
+ async send(batch) {
1744
+ records.push(...batch);
1745
+ },
1746
+ _getRecords() {
1747
+ return [...records];
1748
+ }
1749
+ };
1750
+ }
1751
+ // src/test/utilities/media.ts
1752
+ function createEmptyImageStream() {
1753
+ return new ReadableStream({
1754
+ start(controller) {
1755
+ controller.close();
1756
+ }
1757
+ });
1758
+ }
1759
+ function createMockImageTransformationResult(response) {
1760
+ return {
1761
+ response() {
1762
+ return response.clone();
1763
+ },
1764
+ contentType() {
1765
+ return response.headers.get("Content-Type") ?? "image/png";
1766
+ },
1767
+ image() {
1768
+ const cloned = response.clone();
1769
+ return cloned.body ?? createEmptyImageStream();
1770
+ }
1771
+ };
1772
+ }
1773
+ function createMockImageTransformer(response) {
1774
+ const transformer = {
1775
+ transform(_transform) {
1776
+ return transformer;
1777
+ },
1778
+ draw(_image, _options) {
1779
+ return transformer;
1780
+ },
1781
+ async output(_options) {
1782
+ return createMockImageTransformationResult(response);
1783
+ }
1784
+ };
1785
+ return transformer;
1786
+ }
1787
+ function createMockHostedImagesBinding() {
1788
+ const unsupported = () => {
1789
+ throw new Error("Mock Images hosted API is not implemented. Pass a custom ImagesBinding through createMockEnv({ images }) if your test needs hosted image behavior.");
1790
+ };
1791
+ return {
1792
+ image(_imageId) {
1793
+ return {
1794
+ details: unsupported,
1795
+ bytes: unsupported,
1796
+ update: unsupported,
1797
+ delete: unsupported
1798
+ };
1799
+ },
1800
+ upload: unsupported,
1801
+ list: unsupported
1802
+ };
1803
+ }
1804
+ function createMockImagesBinding(options = {}) {
1805
+ const response = options.response ?? new Response("", {
1806
+ headers: { "Content-Type": "image/png" }
1807
+ });
1808
+ const info = options.info ?? {
1809
+ format: response.headers.get("Content-Type") ?? "image/png",
1810
+ fileSize: 0,
1811
+ width: 0,
1812
+ height: 0
1813
+ };
1814
+ return {
1815
+ async info(_stream, _options) {
1816
+ return info;
1817
+ },
1818
+ input(_stream, _options) {
1819
+ return createMockImageTransformer(response);
1820
+ },
1821
+ hosted: createMockHostedImagesBinding()
1822
+ };
1823
+ }
1824
+ function createEmptyMediaStream() {
1825
+ return new ReadableStream({
1826
+ start(controller) {
1827
+ controller.close();
1828
+ }
1829
+ });
1830
+ }
1831
+ function createMockMediaTransformationResult(response) {
1832
+ return {
1833
+ async media() {
1834
+ const cloned = response.clone();
1835
+ return cloned.body ?? createEmptyMediaStream();
1836
+ },
1837
+ async response() {
1838
+ return response.clone();
1839
+ },
1840
+ async contentType() {
1841
+ return response.headers.get("Content-Type") ?? "video/mp4";
1842
+ }
1843
+ };
1844
+ }
1845
+ function createMockMediaTransformer(response) {
1846
+ const transformer = {
1847
+ transform(_transform) {
1848
+ return {
1849
+ output(_output) {
1850
+ return createMockMediaTransformationResult(response);
1851
+ }
1852
+ };
1853
+ },
1854
+ output(_output) {
1855
+ return createMockMediaTransformationResult(response);
1856
+ }
1857
+ };
1858
+ return transformer;
1859
+ }
1860
+ function createMockMediaBinding(options = {}) {
1861
+ const response = options.response ?? new Response("", {
1862
+ headers: { "Content-Type": "video/mp4" }
1863
+ });
1864
+ return {
1865
+ input(_media) {
1866
+ return createMockMediaTransformer(response);
1867
+ }
1868
+ };
1869
+ }
1870
+ // src/test/utilities/artifacts.ts
1871
+ function createArtifactTimestamp() {
1872
+ return new Date("2026-04-26T00:00:00.000Z").toISOString();
1873
+ }
1874
+ function createArtifactsRepoInfo(name, options = {}) {
1875
+ const now = createArtifactTimestamp();
1876
+ return {
1877
+ id: `repo-${name}`,
1878
+ name,
1879
+ description: options.description ?? null,
1880
+ defaultBranch: options.defaultBranch ?? "main",
1881
+ createdAt: now,
1882
+ updatedAt: now,
1883
+ lastPushAt: null,
1884
+ source: options.source ?? null,
1885
+ readOnly: options.readOnly ?? false,
1886
+ remote: `https://example.com/artifacts/default/${name}.git`
1887
+ };
1888
+ }
1889
+ function isArtifactsBinding(value) {
1890
+ return typeof value.create === "function";
1891
+ }
1892
+ function createMockArtifacts(options = {}) {
1893
+ const repos = new Map;
1894
+ const tokens = new Map;
1895
+ const addRepo = (info) => {
1896
+ repos.set(info.name, info);
1897
+ if (!tokens.has(info.name)) {
1898
+ tokens.set(info.name, []);
1899
+ }
1900
+ };
1901
+ for (const repo of options.repos ?? []) {
1902
+ addRepo({
1903
+ ...createArtifactsRepoInfo(repo.name),
1904
+ ...repo
1905
+ });
1906
+ }
1907
+ const createToken = (repoName, scope = "write", ttl = 86400) => {
1908
+ const existing = tokens.get(repoName) ?? [];
1909
+ const id = `token-${repoName}-${existing.length + 1}`;
1910
+ const expiresAt = new Date(Date.parse(createArtifactTimestamp()) + ttl * 1000).toISOString();
1911
+ const token = {
1912
+ id,
1913
+ scope,
1914
+ state: "active",
1915
+ createdAt: createArtifactTimestamp(),
1916
+ expiresAt
1917
+ };
1918
+ tokens.set(repoName, [...existing, token]);
1919
+ return {
1920
+ id,
1921
+ plaintext: `${id}-plaintext`,
1922
+ scope,
1923
+ expiresAt
1924
+ };
1925
+ };
1926
+ const createRepoHandle = (info) => ({
1927
+ ...info,
1928
+ async createToken(scope, ttl) {
1929
+ return createToken(info.name, scope, ttl);
1930
+ },
1931
+ async listTokens() {
1932
+ const repoTokens = tokens.get(info.name) ?? [];
1933
+ return {
1934
+ tokens: repoTokens,
1935
+ total: repoTokens.length
1936
+ };
1937
+ },
1938
+ async revokeToken(tokenOrId) {
1939
+ const repoTokens = tokens.get(info.name) ?? [];
1940
+ const index = repoTokens.findIndex((token) => token.id === tokenOrId);
1941
+ if (index === -1) {
1942
+ return false;
1943
+ }
1944
+ repoTokens[index] = {
1945
+ ...repoTokens[index],
1946
+ state: "revoked"
1947
+ };
1948
+ tokens.set(info.name, repoTokens);
1949
+ return true;
1950
+ },
1951
+ async fork(name, forkOptions) {
1952
+ return createRepo(name, {
1953
+ description: forkOptions?.description ?? info.description ?? undefined,
1954
+ readOnly: forkOptions?.readOnly ?? info.readOnly,
1955
+ setDefaultBranch: info.defaultBranch,
1956
+ source: `artifacts:default/${info.name}`
1957
+ });
1958
+ }
1959
+ });
1960
+ const createRepo = async (name, createOptions = {}) => {
1961
+ const info = createArtifactsRepoInfo(name, {
1962
+ description: createOptions.description,
1963
+ readOnly: createOptions.readOnly,
1964
+ defaultBranch: createOptions.setDefaultBranch,
1965
+ source: createOptions.source
1966
+ });
1967
+ addRepo(info);
1968
+ const token = createToken(name);
1969
+ return {
1970
+ id: info.id,
1971
+ name: info.name,
1972
+ description: info.description,
1973
+ defaultBranch: info.defaultBranch,
1974
+ remote: info.remote,
1975
+ token: token.plaintext,
1976
+ tokenExpiresAt: token.expiresAt
1977
+ };
1978
+ };
1979
+ return {
1980
+ create: createRepo,
1981
+ async get(name) {
1982
+ const repo = repos.get(name);
1983
+ return repo ? createRepoHandle(repo) : null;
1984
+ },
1985
+ async import(params) {
1986
+ return createRepo(params.target.name, {
1987
+ description: params.target.opts?.description,
1988
+ readOnly: params.target.opts?.readOnly,
1989
+ source: params.source.url
1990
+ });
1991
+ },
1992
+ async list(opts) {
1993
+ const limit = opts?.limit ?? 50;
1994
+ const repoList = Array.from(repos.values()).slice(0, limit).map((repo) => {
1995
+ const { remote: _remote, ...rest } = repo;
1996
+ return rest;
1997
+ });
1998
+ return {
1999
+ repos: repoList,
2000
+ total: repos.size,
2001
+ ...repos.size > repoList.length && { cursor: String(repoList.length) }
2002
+ };
2003
+ },
2004
+ async delete(name) {
2005
+ tokens.delete(name);
2006
+ return repos.delete(name);
2007
+ }
2008
+ };
2009
+ }
2010
+ // src/test/utilities/env.ts
2011
+ function createMockEnv(options = {}) {
2012
+ const env2 = {};
2013
+ if (options.kv) {
2014
+ for (const name of options.kv) {
2015
+ env2[name] = createMockKV();
2016
+ }
2017
+ }
2018
+ if (options.d1) {
2019
+ for (const name of options.d1) {
2020
+ env2[name] = createMockD1();
2021
+ }
2022
+ }
2023
+ if (options.r2) {
2024
+ for (const name of options.r2) {
2025
+ env2[name] = createMockR2();
2026
+ }
2027
+ }
2028
+ if (options.queues) {
2029
+ for (const name of options.queues) {
2030
+ env2[name] = createMockQueue();
2031
+ }
2032
+ }
2033
+ if (options.rateLimits) {
2034
+ for (const [name, rateLimitOptions] of Object.entries(options.rateLimits)) {
2035
+ env2[name] = createMockRateLimit(rateLimitOptions);
2036
+ }
2037
+ }
2038
+ if (options.versionMetadata) {
2039
+ env2[options.versionMetadata] = createMockVersionMetadata();
2040
+ }
2041
+ if (options.hyperdrive) {
2042
+ for (const [name, binding] of Object.entries(options.hyperdrive)) {
2043
+ env2[name] = typeof binding === "string" ? createMockHyperdrive(binding) : binding;
2044
+ }
2045
+ }
2046
+ if (Array.isArray(options.workerLoaders)) {
2047
+ for (const name of options.workerLoaders) {
2048
+ env2[name] = createMockWorkerLoader();
2049
+ }
2050
+ } else if (options.workerLoaders) {
2051
+ for (const [name, workerLoaderOptions] of Object.entries(options.workerLoaders)) {
2052
+ env2[name] = createMockWorkerLoader(workerLoaderOptions);
2053
+ }
2054
+ }
2055
+ if (Array.isArray(options.mtlsCertificates)) {
2056
+ for (const name of options.mtlsCertificates) {
2057
+ env2[name] = createMockMTLSCertificate();
2058
+ }
2059
+ } else if (options.mtlsCertificates) {
2060
+ for (const [name, handler] of Object.entries(options.mtlsCertificates)) {
2061
+ env2[name] = createMockMTLSCertificate(handler);
2062
+ }
2063
+ }
2064
+ if (Array.isArray(options.dispatchNamespaces)) {
2065
+ for (const name of options.dispatchNamespaces) {
2066
+ env2[name] = createMockDispatchNamespace();
2067
+ }
2068
+ } else if (options.dispatchNamespaces) {
2069
+ for (const [name, dispatchNamespaceOptions] of Object.entries(options.dispatchNamespaces)) {
2070
+ env2[name] = createMockDispatchNamespace(dispatchNamespaceOptions);
2071
+ }
2072
+ }
2073
+ if (Array.isArray(options.workflows)) {
2074
+ for (const name of options.workflows) {
2075
+ env2[name] = createMockWorkflow();
2076
+ }
2077
+ } else if (options.workflows) {
2078
+ for (const [name, workflowOptions] of Object.entries(options.workflows)) {
2079
+ env2[name] = "create" in workflowOptions ? workflowOptions : createMockWorkflow(workflowOptions);
2080
+ }
2081
+ }
2082
+ if (Array.isArray(options.pipelines)) {
2083
+ for (const name of options.pipelines) {
2084
+ env2[name] = createMockPipeline();
2085
+ }
2086
+ } else if (options.pipelines) {
2087
+ for (const [name, pipeline] of Object.entries(options.pipelines)) {
2088
+ env2[name] = pipeline;
2089
+ }
2090
+ }
2091
+ if (typeof options.images === "string") {
2092
+ env2[options.images] = createMockImagesBinding();
2093
+ } else if (options.images) {
2094
+ env2.IMAGES = options.images;
2095
+ }
2096
+ if (typeof options.media === "string") {
2097
+ env2[options.media] = createMockMediaBinding();
2098
+ } else if (options.media) {
2099
+ env2.MEDIA = options.media;
2100
+ }
2101
+ if (Array.isArray(options.artifacts)) {
2102
+ for (const name of options.artifacts) {
2103
+ env2[name] = createMockArtifacts();
2104
+ }
2105
+ } else if (options.artifacts) {
2106
+ for (const [name, artifactsOptions] of Object.entries(options.artifacts)) {
2107
+ env2[name] = isArtifactsBinding(artifactsOptions) ? artifactsOptions : createMockArtifacts(artifactsOptions);
2108
+ }
2109
+ }
2110
+ if (options.secretsStore) {
2111
+ for (const [name, value] of Object.entries(options.secretsStore)) {
2112
+ env2[name] = createMockSecretsStoreSecret(value);
2113
+ }
2114
+ }
2115
+ if (options.vars) {
2116
+ Object.assign(env2, options.vars);
2117
+ }
2118
+ if (options.secrets) {
2119
+ Object.assign(env2, options.secrets);
2120
+ }
2121
+ if (options.custom) {
2122
+ Object.assign(env2, options.custom);
2123
+ }
2124
+ return env2;
2125
+ }
2126
+ // src/test/simple-context-bindings.ts
2127
+ function buildRemoteAndStaticBindings(config) {
2128
+ const remoteBindings = {};
2129
+ if (isRemoteModeActive()) {
2130
+ if (config.bindings?.ai) {
2131
+ const aiBindingName = config.bindings.ai.binding || "AI";
2132
+ remoteBindings[aiBindingName] = createRemoteAI(config.accountId);
2133
+ }
2134
+ if (config.bindings?.vectorize) {
2135
+ for (const [name, vectorConfig] of Object.entries(config.bindings.vectorize)) {
2136
+ remoteBindings[name] = createRemoteVectorize(vectorConfig.indexName, config.accountId);
2137
+ }
2138
+ }
2139
+ }
2140
+ if (config.vars) {
2141
+ for (const [key, value] of Object.entries(config.vars)) {
2142
+ remoteBindings[key] = value;
2143
+ }
2144
+ }
2145
+ if (config.bindings?.sendEmail) {
2146
+ for (const [name, binding] of Object.entries(config.bindings.sendEmail)) {
2147
+ remoteBindings[name] = createLocalSendEmailBinding(binding);
2148
+ }
2149
+ }
2150
+ if (config.bindings?.workerLoaders) {
2151
+ for (const name of Object.keys(config.bindings.workerLoaders)) {
2152
+ remoteBindings[name] = createLocalWorkerLoaderBinding();
2153
+ }
2154
+ }
2155
+ if (config.bindings?.versionMetadata) {
2156
+ remoteBindings[config.bindings.versionMetadata.binding] = createMockVersionMetadata();
2157
+ }
2158
+ return remoteBindings;
2159
+ }
2160
+
2161
+ // src/test/email.ts
2162
+ import { join as join4 } from "path";
2163
+ var miniflarePort = 8787;
2164
+ var emailListeners = [];
2165
+ var sentEmails = [];
2166
+ var emailHandlerPath = null;
2167
+ var configDir = null;
2168
+ var testEnvGetter = null;
2169
+ function configureEmail(options = {}) {
2170
+ if (options.port) {
2171
+ miniflarePort = options.port;
2172
+ }
2173
+ emailHandlerPath = options.handlerPath ?? emailHandlerPath;
2174
+ configDir = options.configDir ?? configDir;
2175
+ testEnvGetter = options.getEnv ?? testEnvGetter;
2176
+ }
2177
+ function buildRawEmail(options) {
2178
+ if (options.raw) {
2179
+ return options.raw;
2180
+ }
2181
+ const lines = [];
2182
+ const messageId = `<${Date.now()}-${Math.random().toString(36).slice(2)}@devflare.dev>`;
2183
+ const date = new Date().toUTCString();
2184
+ lines.push(`From: ${options.from}`);
2185
+ lines.push(`To: ${options.to}`);
2186
+ lines.push(`Date: ${date}`);
2187
+ lines.push(`Message-ID: ${messageId}`);
2188
+ if (options.subject) {
2189
+ lines.push(`Subject: ${options.subject}`);
2190
+ }
2191
+ if (options.headers) {
2192
+ for (const [key, value] of Object.entries(options.headers)) {
2193
+ lines.push(`${key}: ${value}`);
2194
+ }
2195
+ }
2196
+ lines.push("MIME-Version: 1.0");
2197
+ lines.push("Content-Type: text/plain; charset=UTF-8");
2198
+ lines.push("");
2199
+ lines.push(options.body ?? "");
2200
+ return lines.join(`\r
2201
+ `);
2202
+ }
2203
+ function createEmailHeaders(rawEmail) {
2204
+ const headers = new Headers;
2205
+ const lines = rawEmail.split(/\r?\n/);
2206
+ for (const line of lines) {
2207
+ if (!line.trim()) {
2208
+ break;
2209
+ }
2210
+ const colonIndex = line.indexOf(":");
2211
+ if (colonIndex <= 0) {
2212
+ continue;
2213
+ }
2214
+ headers.append(line.slice(0, colonIndex).trim(), line.slice(colonIndex + 1).trim());
2215
+ }
2216
+ return headers;
2217
+ }
2218
+ function createRawEmailStream(rawEmail) {
2219
+ return new ReadableStream({
2220
+ start(controller) {
2221
+ controller.enqueue(new TextEncoder().encode(rawEmail));
2222
+ controller.close();
2223
+ }
2224
+ });
2225
+ }
2226
+ function resolveEmailHandler(module) {
2227
+ if (typeof module.default === "function") {
2228
+ return module.default;
2229
+ }
2230
+ if (module.default && typeof module.default.email === "function") {
2231
+ return module.default.email.bind(module.default);
2232
+ }
2233
+ if (typeof module.email === "function") {
2234
+ return module.email;
2235
+ }
2236
+ return null;
2237
+ }
2238
+ function getRecordedRawContent(raw) {
2239
+ if (typeof raw === "string") {
2240
+ return raw;
2241
+ }
2242
+ return;
2243
+ }
2244
+ async function send(options) {
2245
+ const raw = buildRawEmail(options);
2246
+ if (emailHandlerPath && configDir && testEnvGetter) {
2247
+ const absolutePath = join4(configDir, emailHandlerPath);
2248
+ const handlerModule = await import(absolutePath);
2249
+ const emailHandler = resolveEmailHandler(handlerModule);
2250
+ if (!emailHandler) {
2251
+ throw new Error(`Email handler at "${emailHandlerPath}" must export a default function or named "email" export.
2252
+ NaN`);
2253
+ }
2254
+ const waitUntilPromises = [];
2255
+ const ctx = {
2256
+ waitUntil(promise) {
2257
+ waitUntilPromises.push(promise);
2258
+ },
2259
+ passThroughOnException() {},
2260
+ props: {}
2261
+ };
2262
+ const runtimeEnv = testEnvGetter();
2263
+ const timestamp = new Date;
2264
+ const message = {
2265
+ from: options.from,
2266
+ to: options.to,
2267
+ headers: createEmailHeaders(raw),
2268
+ raw: createRawEmailStream(raw),
2269
+ rawSize: raw.length,
2270
+ setReject(reason) {
2271
+ throw new Error(`Email rejected: ${reason}`);
2272
+ },
2273
+ async forward(rcptTo) {
2274
+ recordSentEmail({
2275
+ type: "forward",
2276
+ from: options.from,
2277
+ to: rcptTo,
2278
+ raw,
2279
+ timestamp
2280
+ });
2281
+ },
2282
+ async reply(message2) {
2283
+ recordSentEmail({
2284
+ type: "reply",
2285
+ from: message2.from ?? options.to,
2286
+ to: message2.to ?? options.from,
2287
+ raw: getRecordedRawContent(message2.raw),
2288
+ timestamp
2289
+ });
2290
+ }
2291
+ };
2292
+ const emailEvent = createEmailEvent(message, runtimeEnv, ctx);
2293
+ await runWithEventContext(emailEvent, () => emailHandler(emailEvent));
2294
+ await Promise.all(waitUntilPromises);
2295
+ return new Response(JSON.stringify({ ok: true, from: options.from, to: options.to }), {
2296
+ headers: { "Content-Type": "application/json" }
1267
2297
  });
1268
2298
  }
1269
2299
  const url = new URL(`http://localhost:${miniflarePort}/cdn-cgi/handler/email`);
@@ -1330,6 +2360,12 @@ function resetQueueState() {
1330
2360
  configDir2 = null;
1331
2361
  testEnvGetter2 = null;
1332
2362
  }
2363
+ var EMPTY_QUEUE_METADATA = {
2364
+ metrics: {
2365
+ backlogCount: 0,
2366
+ backlogBytes: 0
2367
+ }
2368
+ };
1333
2369
  function createMessage(options) {
1334
2370
  const id = options.id ?? crypto.randomUUID();
1335
2371
  const timestamp = options.timestamp ?? new Date;
@@ -1357,6 +2393,7 @@ function createMessage(options) {
1357
2393
  function createMessageBatch(messages) {
1358
2394
  return {
1359
2395
  queue: "test-queue",
2396
+ metadata: EMPTY_QUEUE_METADATA,
1360
2397
  messages,
1361
2398
  ackAll() {
1362
2399
  for (const msg of messages) {
@@ -1400,8 +2437,8 @@ NaN`);
1400
2437
  passThroughOnException() {},
1401
2438
  props: {}
1402
2439
  };
1403
- const env2 = testEnvGetter2();
1404
- const queueEvent = createQueueEvent(batch, env2, ctx);
2440
+ const env3 = testEnvGetter2();
2441
+ const queueEvent = createQueueEvent(batch, env3, ctx);
1405
2442
  await runWithEventContext(queueEvent, () => queueHandler(queueEvent));
1406
2443
  await Promise.all(waitUntilPromises);
1407
2444
  const acked = [];
@@ -1430,7 +2467,7 @@ NaN`);
1430
2467
  async function send2(message) {
1431
2468
  return trigger([message]);
1432
2469
  }
1433
- var queue = {
2470
+ var queue2 = {
1434
2471
  trigger,
1435
2472
  send: send2
1436
2473
  };
@@ -1480,8 +2517,8 @@ NaN`);
1480
2517
  passThroughOnException() {},
1481
2518
  props: {}
1482
2519
  };
1483
- const env2 = testEnvGetter3();
1484
- const scheduledEvent = createScheduledEvent(controller, env2, ctx);
2520
+ const env3 = testEnvGetter3();
2521
+ const scheduledEvent = createScheduledEvent(controller, env3, ctx);
1485
2522
  try {
1486
2523
  await runWithEventContext(scheduledEvent, () => scheduledHandler(scheduledEvent));
1487
2524
  await Promise.all(waitUntilPromises);
@@ -1552,7 +2589,8 @@ async function trigger3(items) {
1552
2589
  });
1553
2590
  const absolutePath = join7(configDir4, tailHandlerPath);
1554
2591
  const handlerModule = await import(absolutePath);
1555
- const tailHandler = handlerModule.default ?? handlerModule.tail;
2592
+ const defaultExport = handlerModule.default;
2593
+ const tailHandler = typeof defaultExport === "function" ? defaultExport : defaultExport && typeof defaultExport.tail === "function" ? defaultExport.tail.bind(defaultExport) : handlerModule.tail;
1556
2594
  if (typeof tailHandler !== "function") {
1557
2595
  throw new Error(`Tail handler at "${tailHandlerPath}" must export a default function or named "tail" export.
1558
2596
  NaN`);
@@ -1565,10 +2603,10 @@ NaN`);
1565
2603
  passThroughOnException() {},
1566
2604
  props: {}
1567
2605
  };
1568
- const env2 = testEnvGetter4();
1569
- const tailEvent = createTailEvent(traceItems, env2, ctx);
2606
+ const env3 = testEnvGetter4();
2607
+ const tailEvent = createTailEvent(traceItems, env3, ctx);
1570
2608
  try {
1571
- await runWithEventContext(tailEvent, () => tailHandler(tailEvent));
2609
+ await runWithEventContext(tailEvent, () => tailHandler.length >= 2 ? tailHandler(traceItems, env3, ctx) : tailHandler(tailEvent, env3, ctx));
1572
2610
  await Promise.all(waitUntilPromises);
1573
2611
  return {
1574
2612
  success: true,
@@ -1666,9 +2704,9 @@ async function fetch2(request, options) {
1666
2704
  passThroughOnException() {},
1667
2705
  props: {}
1668
2706
  };
1669
- const env2 = getEnv();
2707
+ const env3 = getEnv();
1670
2708
  const initialRouteMatch = routeModules.length > 0 ? matchFetchRoute(routeModules, req) : null;
1671
- const fetchEvent = createFetchEvent(req, env2, ctx, {
2709
+ const fetchEvent = createFetchEvent(req, env3, ctx, {
1672
2710
  params: initialRouteMatch?.params ?? {}
1673
2711
  });
1674
2712
  const response = await runWithEventContext(fetchEvent, () => invokeFetchModule(handlerModule, fetchEvent, routeModules.length > 0 ? createRouteResolve(routeModules, fetchEvent) : undefined));
@@ -1798,23 +2836,464 @@ async function resolveHandlerPath(configDir6, configValue, defaultPath) {
1798
2836
  }
1799
2837
  }
1800
2838
  async function resolveHandlerPaths(configDir6, config) {
1801
- const [fetch3, queue2, scheduled2, email2, tail2, routes] = await Promise.all([
2839
+ const [fetch3, queue3, scheduled2, email2, tail2, routes] = await Promise.all([
1802
2840
  resolveHandlerPath(configDir6, config.files?.fetch, DEFAULT_FETCH_PATH),
1803
2841
  resolveHandlerPath(configDir6, config.files?.queue, DEFAULT_QUEUE_PATH),
1804
2842
  resolveHandlerPath(configDir6, config.files?.scheduled, DEFAULT_SCHEDULED_PATH),
1805
2843
  resolveHandlerPath(configDir6, config.files?.email, DEFAULT_EMAIL_PATH),
1806
- resolveHandlerPath(configDir6, undefined, DEFAULT_TAIL_PATH),
2844
+ resolveHandlerPath(configDir6, config.files?.tail, DEFAULT_TAIL_PATH),
1807
2845
  discoverRoutes(configDir6, config)
1808
2846
  ]);
1809
- return { fetch: fetch3, queue: queue2, scheduled: scheduled2, email: email2, tail: tail2, routes };
2847
+ return { fetch: fetch3, queue: queue3, scheduled: scheduled2, email: email2, tail: tail2, routes };
2848
+ }
2849
+
2850
+ // src/test/simple-context-lifecycle.ts
2851
+ import { dirname as dirname5, resolve as resolve3 } from "path";
2852
+
2853
+ // src/test/containers.ts
2854
+ import { createHash, randomUUID } from "node:crypto";
2855
+ import { existsSync as existsSync3 } from "node:fs";
2856
+ import { stat } from "node:fs/promises";
2857
+ import { createConnection } from "node:net";
2858
+ import { basename, dirname as dirname4, isAbsolute, join as join10, resolve as resolve2 } from "node:path";
2859
+ import { setTimeout as delay } from "node:timers/promises";
2860
+ import { execa } from "execa";
2861
+ var realContainerCommandRunner = {
2862
+ async exec(command, args, options = {}) {
2863
+ try {
2864
+ const result = await execa(command, args, {
2865
+ cwd: options.cwd,
2866
+ env: options.env,
2867
+ reject: false,
2868
+ timeout: options.timeoutMs ?? 15000
2869
+ });
2870
+ return {
2871
+ exitCode: result.exitCode ?? 0,
2872
+ stdout: String(result.stdout ?? ""),
2873
+ stderr: String(result.stderr ?? "")
2874
+ };
2875
+ } catch (error) {
2876
+ const err = error;
2877
+ return {
2878
+ exitCode: err.exitCode ?? 1,
2879
+ stdout: String(err.stdout ?? ""),
2880
+ stderr: String(err.stderr ?? err.message ?? "Container command failed")
2881
+ };
2882
+ }
2883
+ }
2884
+ };
2885
+ async function detectContainerEngine(options = {}) {
2886
+ const runner = options.runner ?? realContainerCommandRunner;
2887
+ const candidates = options.engine && options.engine !== "auto" ? [options.engine] : ["docker", "podman"];
2888
+ const checked = [];
2889
+ for (const engine of candidates) {
2890
+ let result;
2891
+ try {
2892
+ result = await runner.exec(engine, ["info"], { timeoutMs: 1e4 });
2893
+ } catch (error) {
2894
+ result = {
2895
+ exitCode: 1,
2896
+ stdout: "",
2897
+ stderr: error instanceof Error ? error.message : String(error)
2898
+ };
2899
+ }
2900
+ if (result.exitCode === 0) {
2901
+ checked.push({ engine, available: true });
2902
+ return { available: true, engine, checked };
2903
+ }
2904
+ checked.push({
2905
+ engine,
2906
+ available: false,
2907
+ reason: formatCommandFailure(result)
2908
+ });
2909
+ }
2910
+ return {
2911
+ available: false,
2912
+ reason: checked.map((check) => `${check.engine}: ${check.reason ?? "not available"}`).join("; "),
2913
+ checked
2914
+ };
2915
+ }
2916
+ async function getContainerSkipReason(options = {}) {
2917
+ const env3 = options.env ?? process.env;
2918
+ if (!isTruthyEnvFlag(env3.DEVFLARE_CONTAINER_TESTS)) {
2919
+ return "Container tests require DEVFLARE_CONTAINER_TESTS=1 because they launch local Docker/Podman containers.";
2920
+ }
2921
+ const status = await detectContainerEngine(options);
2922
+ if (!status.available) {
2923
+ return `No reachable Docker or Podman engine found: ${status.reason}`;
2924
+ }
2925
+ return null;
2926
+ }
2927
+ function createContainerManager(options = {}) {
2928
+ const active = new Set;
2929
+ const getRunner = () => options.runner ?? realContainerCommandRunner;
2930
+ const manager = {
2931
+ detectEngine(engineOptions = {}) {
2932
+ return detectContainerEngine({
2933
+ engine: engineOptions.engine ?? options.engine,
2934
+ runner: engineOptions.runner ?? getRunner()
2935
+ });
2936
+ },
2937
+ async start(className, startOptions) {
2938
+ const cwd = options.cwd ?? getCallerDirectory();
2939
+ const resolved = await resolveContainerStart(className, startOptions, cwd);
2940
+ const status = await manager.detectEngine();
2941
+ const engine = getAvailableContainerEngine(status, className);
2942
+ const runner = getRunner();
2943
+ const offline = startOptions.offline ?? true;
2944
+ const prepared = await prepareImage({
2945
+ engine,
2946
+ runner,
2947
+ image: resolved.image,
2948
+ className,
2949
+ configDir: resolved.configDir,
2950
+ imageBuildContext: resolved.config?.imageBuildContext,
2951
+ offline
2952
+ });
2953
+ const host = startOptions.host ?? "127.0.0.1";
2954
+ const hostPort = startOptions.hostPort ?? await (options.allocatePort ?? getAvailablePort)();
2955
+ const containerName = makeContainerName(className, startOptions.instance);
2956
+ const runArgs = buildRunArgs({
2957
+ name: containerName,
2958
+ className,
2959
+ image: prepared.image,
2960
+ host,
2961
+ hostPort,
2962
+ containerPort: startOptions.port,
2963
+ envVars: startOptions.envVars,
2964
+ entrypoint: startOptions.entrypoint,
2965
+ command: startOptions.command
2966
+ });
2967
+ const runResult = await runner.exec(engine, runArgs, {
2968
+ cwd: resolved.configDir,
2969
+ env: options.env ?? process.env
2970
+ });
2971
+ if (runResult.exitCode !== 0) {
2972
+ throw new Error(`Failed to start Devflare container "${className}": ${formatCommandFailure(runResult)}`);
2973
+ }
2974
+ const instance = new LocalDevflareContainer({
2975
+ id: runResult.stdout.trim() || containerName,
2976
+ name: containerName,
2977
+ className,
2978
+ engine,
2979
+ host,
2980
+ hostPort,
2981
+ port: startOptions.port,
2982
+ runner,
2983
+ fetchImpl: options.fetch ?? fetch,
2984
+ onDispose: (container) => active.delete(container)
2985
+ });
2986
+ active.add(instance);
2987
+ await waitForContainerReadiness(instance, startOptions, options.waitForPort ?? waitForTcpPort);
2988
+ return instance;
2989
+ },
2990
+ async stopAll() {
2991
+ await Promise.all([...active].map((container) => container.stop()));
2992
+ }
2993
+ };
2994
+ return manager;
2995
+ }
2996
+ var defaultContainerManager = createContainerManager();
2997
+ var containers = defaultContainerManager;
2998
+ async function stopActiveContainers() {
2999
+ await defaultContainerManager.stopAll();
3000
+ }
3001
+ function getAvailableContainerEngine(status, className) {
3002
+ if (!status.available) {
3003
+ throw new Error(`Cannot start Devflare container "${className}": ${status.reason}`);
3004
+ }
3005
+ return status.engine;
3006
+ }
3007
+ async function waitForContainerReadiness(instance, options, waitForPort) {
3008
+ if (!(options.waitForReady ?? true)) {
3009
+ return;
3010
+ }
3011
+ try {
3012
+ await waitForPort(instance.host, instance.hostPort, options.readyTimeoutMs ?? 20000);
3013
+ } catch (error) {
3014
+ await instance.destroy();
3015
+ throw error;
3016
+ }
3017
+ }
3018
+ async function resolveContainerStart(className, options, cwd) {
3019
+ if (options.image) {
3020
+ return {
3021
+ configDir: cwd,
3022
+ image: options.image
3023
+ };
3024
+ }
3025
+ const { config, configDir: configDir6 } = await loadContainerConfig(options.configPath, cwd);
3026
+ const container = config.containers?.find((candidate) => candidate.className === className || candidate.name === className);
3027
+ if (!container) {
3028
+ throw new Error(`Container "${className}" was not found in devflare config.`);
3029
+ }
3030
+ return {
3031
+ configDir: configDir6,
3032
+ image: container.image,
3033
+ config: container
3034
+ };
3035
+ }
3036
+ async function loadContainerConfig(configPath, cwd) {
3037
+ const absolutePath = configPath ? resolve2(cwd, configPath) : await findNearestConfig(cwd);
3038
+ if (!absolutePath) {
3039
+ throw new Error(`Could not find a devflare config file for container lookup. Searched upward from: ${cwd}`);
3040
+ }
3041
+ const configDir6 = dirname4(absolutePath);
3042
+ const loadedConfig = await loadConfig({
3043
+ cwd: configDir6,
3044
+ configFile: basename(absolutePath)
3045
+ });
3046
+ const config = await applyLocalDevVarsToConfig(loadedConfig, {
3047
+ cwd: configDir6,
3048
+ configPath: absolutePath
3049
+ });
3050
+ return { config, configDir: configDir6 };
3051
+ }
3052
+ async function prepareImage(options) {
3053
+ if (looksLikeLocalPath(options.image)) {
3054
+ return {
3055
+ image: await buildLocalImage(options)
3056
+ };
3057
+ }
3058
+ const inspect = await options.runner.exec(options.engine, ["image", "inspect", options.image], {
3059
+ cwd: options.configDir
3060
+ });
3061
+ if (inspect.exitCode === 0) {
3062
+ return { image: options.image };
3063
+ }
3064
+ if (options.offline) {
3065
+ 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.`);
3066
+ }
3067
+ const pull = await options.runner.exec(options.engine, ["pull", options.image], {
3068
+ cwd: options.configDir
3069
+ });
3070
+ if (pull.exitCode !== 0) {
3071
+ throw new Error(`Failed to pull container image "${options.image}": ${formatCommandFailure(pull)}`);
3072
+ }
3073
+ return { image: options.image };
3074
+ }
3075
+ async function buildLocalImage(options) {
3076
+ const imagePath = resolve2(options.configDir, options.image);
3077
+ const imageStat = await stat(imagePath);
3078
+ const dockerfile = imageStat.isDirectory() ? join10(imagePath, "Dockerfile") : imagePath;
3079
+ const context2 = resolve2(options.configDir, options.imageBuildContext ?? (imageStat.isDirectory() ? options.image : dirname4(options.image)));
3080
+ if (!existsSync3(dockerfile)) {
3081
+ throw new Error(`Container Dockerfile does not exist: ${dockerfile}`);
3082
+ }
3083
+ const tag = makeLocalImageTag(options.className, options.configDir, options.image);
3084
+ const args = [
3085
+ "build",
3086
+ ...options.offline ? [getOfflineBuildPullArg(options.engine)] : [],
3087
+ "-t",
3088
+ tag,
3089
+ "-f",
3090
+ dockerfile,
3091
+ context2
3092
+ ];
3093
+ const result = await options.runner.exec(options.engine, args, {
3094
+ cwd: options.configDir
3095
+ });
3096
+ if (result.exitCode !== 0) {
3097
+ throw new Error(`Failed to build local container image "${options.image}": ${formatCommandFailure(result)}`);
3098
+ }
3099
+ return tag;
3100
+ }
3101
+ function buildRunArgs(options) {
3102
+ const args = [
3103
+ "run",
3104
+ "-d",
3105
+ "--name",
3106
+ options.name,
3107
+ "--label",
3108
+ "devflare.managed=true",
3109
+ "--label",
3110
+ `devflare.container.class=${options.className}`,
3111
+ "-p",
3112
+ `${options.host}:${options.hostPort}:${options.containerPort}`
3113
+ ];
3114
+ for (const [key, value] of Object.entries(options.envVars ?? {})) {
3115
+ args.push("-e", `${key}=${value}`);
3116
+ }
3117
+ const command = [...options.command ?? []];
3118
+ if (options.entrypoint && options.entrypoint.length > 0) {
3119
+ args.push("--entrypoint", options.entrypoint[0]);
3120
+ command.unshift(...options.entrypoint.slice(1));
3121
+ }
3122
+ args.push(options.image, ...command);
3123
+ return args;
3124
+ }
3125
+
3126
+ class LocalDevflareContainer {
3127
+ id;
3128
+ name;
3129
+ className;
3130
+ engine;
3131
+ host;
3132
+ hostPort;
3133
+ port;
3134
+ runner;
3135
+ fetchImpl;
3136
+ onDispose;
3137
+ disposed = false;
3138
+ constructor(options) {
3139
+ this.id = options.id;
3140
+ this.name = options.name;
3141
+ this.className = options.className;
3142
+ this.engine = options.engine;
3143
+ this.host = options.host;
3144
+ this.hostPort = options.hostPort;
3145
+ this.port = options.port;
3146
+ this.runner = options.runner;
3147
+ this.fetchImpl = options.fetchImpl;
3148
+ this.onDispose = options.onDispose;
3149
+ }
3150
+ fetch(input, init) {
3151
+ return fetchWithStartupRetries(this.fetchImpl, this.toLocalRequest(input, init));
3152
+ }
3153
+ async logs() {
3154
+ const result = await this.runner.exec(this.engine, ["logs", this.name]);
3155
+ if (result.exitCode !== 0) {
3156
+ throw new Error(`Failed to read logs for Devflare container "${this.name}": ${formatCommandFailure(result)}`);
3157
+ }
3158
+ return result.stdout;
3159
+ }
3160
+ async getState() {
3161
+ const result = await this.runner.exec(this.engine, [
3162
+ "inspect",
3163
+ "--format",
3164
+ "{{json .State}}",
3165
+ this.name
3166
+ ]);
3167
+ if (result.exitCode !== 0) {
3168
+ throw new Error(`Failed to inspect Devflare container "${this.name}": ${formatCommandFailure(result)}`);
3169
+ }
3170
+ const parsed = JSON.parse(result.stdout || "{}");
3171
+ return {
3172
+ status: parsed.Status ?? (parsed.Running ? "running" : "stopped"),
3173
+ running: Boolean(parsed.Running),
3174
+ ...typeof parsed.ExitCode === "number" && { exitCode: parsed.ExitCode }
3175
+ };
3176
+ }
3177
+ async stop() {
3178
+ if (this.disposed)
3179
+ return;
3180
+ try {
3181
+ await this.runner.exec(this.engine, ["stop", this.name]);
3182
+ } finally {
3183
+ await this.runner.exec(this.engine, ["rm", "-f", this.name]);
3184
+ this.disposed = true;
3185
+ this.onDispose(this);
3186
+ }
3187
+ }
3188
+ async destroy() {
3189
+ if (this.disposed)
3190
+ return;
3191
+ await this.runner.exec(this.engine, ["rm", "-f", this.name]);
3192
+ this.disposed = true;
3193
+ this.onDispose(this);
3194
+ }
3195
+ toLocalRequest(input, init) {
3196
+ const base = `http://${this.host}:${this.hostPort}/`;
3197
+ if (input instanceof Request) {
3198
+ const source = init ? new Request(input, init) : input;
3199
+ const sourceUrl2 = new URL(source.url);
3200
+ const localUrl2 = new URL(`${sourceUrl2.pathname}${sourceUrl2.search}`, base);
3201
+ return new Request(localUrl2, {
3202
+ method: source.method,
3203
+ headers: source.headers,
3204
+ body: source.body,
3205
+ redirect: source.redirect,
3206
+ signal: source.signal
3207
+ });
3208
+ }
3209
+ const sourceUrl = new URL(String(input), base);
3210
+ const localUrl = new URL(`${sourceUrl.pathname}${sourceUrl.search}`, base);
3211
+ return new Request(localUrl, init);
3212
+ }
3213
+ }
3214
+ async function fetchWithStartupRetries(fetchImpl, request) {
3215
+ if (!canRetryRequest(request)) {
3216
+ return fetchImpl(request);
3217
+ }
3218
+ const deadline = Date.now() + 5000;
3219
+ let lastError;
3220
+ while (Date.now() < deadline) {
3221
+ try {
3222
+ return await fetchImpl(request.clone());
3223
+ } catch (error) {
3224
+ if (!isTransientContainerFetchError(error)) {
3225
+ throw error;
3226
+ }
3227
+ lastError = error;
3228
+ await delay(100);
3229
+ }
3230
+ }
3231
+ throw lastError;
3232
+ }
3233
+ function canRetryRequest(request) {
3234
+ return (request.method === "GET" || request.method === "HEAD") && request.body === null;
3235
+ }
3236
+ function isTransientContainerFetchError(error) {
3237
+ const value = error;
3238
+ const code = value.code ?? value.cause?.code;
3239
+ if (code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE" || code === "UND_ERR_SOCKET") {
3240
+ return true;
3241
+ }
3242
+ return typeof value.message === "string" && (value.message.includes("socket connection was closed") || value.message.includes("fetch failed"));
3243
+ }
3244
+ async function waitForTcpPort(host, port, timeoutMs) {
3245
+ const start = Date.now();
3246
+ let lastError;
3247
+ while (Date.now() - start < timeoutMs) {
3248
+ try {
3249
+ await connectOnce(host, port);
3250
+ return;
3251
+ } catch (error) {
3252
+ lastError = error;
3253
+ await delay(100);
3254
+ }
3255
+ }
3256
+ const message = lastError instanceof Error ? lastError.message : "timed out";
3257
+ throw new Error(`Timed out waiting for container port ${host}:${port}: ${message}`);
3258
+ }
3259
+ function connectOnce(host, port) {
3260
+ return new Promise((resolveConnection, rejectConnection) => {
3261
+ const socket = createConnection({ host, port });
3262
+ socket.once("connect", () => {
3263
+ socket.destroy();
3264
+ resolveConnection();
3265
+ });
3266
+ socket.once("error", rejectConnection);
3267
+ });
3268
+ }
3269
+ function looksLikeLocalPath(image) {
3270
+ return image === "Dockerfile" || image.startsWith(".") || image.startsWith("/") || image.startsWith("\\") || isAbsolute(image) || image.endsWith("/Dockerfile") || image.endsWith("\\Dockerfile");
3271
+ }
3272
+ function getOfflineBuildPullArg(engine) {
3273
+ return engine === "podman" ? "--pull=never" : "--pull=false";
3274
+ }
3275
+ function makeLocalImageTag(className, configDir6, image) {
3276
+ const hash = createHash("sha256").update(`${configDir6}:${image}`).digest("hex").slice(0, 12);
3277
+ return `devflare-local-${sanitizeName(className)}:${hash}`;
3278
+ }
3279
+ function makeContainerName(className, instance) {
3280
+ return `devflare-${sanitizeName(className)}-${sanitizeName(instance ?? "default")}-${randomUUID().slice(0, 8)}`;
3281
+ }
3282
+ function sanitizeName(value) {
3283
+ return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "") || "container";
3284
+ }
3285
+ function formatCommandFailure(result) {
3286
+ return (result.stderr || result.stdout || `exit code ${result.exitCode}`).trim();
3287
+ }
3288
+ function isTruthyEnvFlag(value) {
3289
+ return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
1810
3290
  }
1811
3291
 
1812
3292
  // src/test/simple-context-lifecycle.ts
1813
- import { dirname as dirname4, resolve as resolve2 } from "path";
1814
3293
  async function resolveTestContextConfig(configPath, callerDir = getCallerDirectory()) {
1815
3294
  let absolutePath;
1816
3295
  if (configPath) {
1817
- absolutePath = resolve2(callerDir, configPath);
3296
+ absolutePath = resolve3(callerDir, configPath);
1818
3297
  } else {
1819
3298
  const found = await findNearestConfig(callerDir);
1820
3299
  if (!found) {
@@ -1824,11 +3303,15 @@ async function resolveTestContextConfig(configPath, callerDir = getCallerDirecto
1824
3303
  }
1825
3304
  absolutePath = found;
1826
3305
  }
1827
- const configDir6 = dirname4(absolutePath);
1828
- const config = await loadConfig({
3306
+ const configDir6 = dirname5(absolutePath);
3307
+ const loadedConfig = await loadConfig({
1829
3308
  cwd: configDir6,
1830
3309
  configFile: absolutePath.split(/[/\\]/).pop()
1831
3310
  });
3311
+ const config = await applyLocalDevVarsToConfig(loadedConfig, {
3312
+ cwd: configDir6,
3313
+ configPath: absolutePath
3314
+ });
1832
3315
  return { absolutePath, configDir: configDir6, config };
1833
3316
  }
1834
3317
  function createDisposeContext(state) {
@@ -1841,6 +3324,8 @@ function createDisposeContext(state) {
1841
3324
  await state.miniflare.dispose();
1842
3325
  state.miniflare = null;
1843
3326
  }
3327
+ await disposeLocalWorkerLoaderBindings();
3328
+ await stopActiveContainers();
1844
3329
  state.envProxy = null;
1845
3330
  state.transportDecode = null;
1846
3331
  state.remoteBindings = null;
@@ -1867,10 +3352,10 @@ function isRetriableTestContextStartupError(error) {
1867
3352
  return message.includes("websocket connection failed") || message.includes("connection timeout: ws://") || message.includes("econnrefused") || message.includes("eaddrinuse") || message.includes("address already in use");
1868
3353
  }
1869
3354
  async function waitForTestContextStartupRetry() {
1870
- await new Promise((resolve3) => setTimeout(resolve3, TEST_CONTEXT_STARTUP_RETRY_DELAY_MS));
3355
+ await new Promise((resolve4) => setTimeout(resolve4, TEST_CONTEXT_STARTUP_RETRY_DELAY_MS));
1871
3356
  }
1872
3357
  async function waitForBridgeClientRetry() {
1873
- await new Promise((resolve3) => setTimeout(resolve3, TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS));
3358
+ await new Promise((resolve4) => setTimeout(resolve4, TEST_CONTEXT_BRIDGE_CONNECT_RETRY_DELAY_MS));
1874
3359
  }
1875
3360
  async function connectBridgeClientWithRetry(url) {
1876
3361
  let lastError;
@@ -1890,6 +3375,48 @@ async function connectBridgeClientWithRetry(url) {
1890
3375
  }
1891
3376
  throw lastError instanceof Error ? lastError : new Error("Bridge-backed test context could not connect to the WebSocket gateway.");
1892
3377
  }
3378
+ function expandLocalBindingWorkers(mfConfig) {
3379
+ const auxiliaryWorkers = [
3380
+ ...mfConfig.__devflareLocalSecretWorkers ?? [],
3381
+ ...mfConfig.__devflareLocalBindingWorkers ?? []
3382
+ ];
3383
+ if (!Array.isArray(auxiliaryWorkers) || auxiliaryWorkers.length === 0) {
3384
+ return mfConfig;
3385
+ }
3386
+ const {
3387
+ __devflareLocalSecretWorkers,
3388
+ __devflareLocalBindingWorkers,
3389
+ port,
3390
+ host,
3391
+ log,
3392
+ kvPersist,
3393
+ r2Persist,
3394
+ d1Persist,
3395
+ durableObjectsPersist,
3396
+ workflowsPersist,
3397
+ imagesPersist,
3398
+ ...primaryWorker
3399
+ } = mfConfig;
3400
+ const primaryWorkerName = typeof primaryWorker.name === "string" ? primaryWorker.name : "primary";
3401
+ return {
3402
+ ...port !== undefined && { port },
3403
+ ...host && { host },
3404
+ ...log && { log },
3405
+ ...kvPersist && { kvPersist },
3406
+ ...r2Persist && { r2Persist },
3407
+ ...d1Persist && { d1Persist },
3408
+ ...durableObjectsPersist && { durableObjectsPersist },
3409
+ ...workflowsPersist && { workflowsPersist },
3410
+ ...imagesPersist && { imagesPersist },
3411
+ workers: [
3412
+ {
3413
+ ...primaryWorker,
3414
+ name: primaryWorkerName
3415
+ },
3416
+ ...auxiliaryWorkers
3417
+ ]
3418
+ };
3419
+ }
1893
3420
  async function startBridgeBackedTestContext(mfConfig) {
1894
3421
  const { Miniflare } = await import("miniflare");
1895
3422
  for (let attempt = 1;attempt <= TEST_CONTEXT_STARTUP_RETRY_ATTEMPTS; attempt++) {
@@ -1897,10 +3424,10 @@ async function startBridgeBackedTestContext(mfConfig) {
1897
3424
  let miniflare = null;
1898
3425
  let client = null;
1899
3426
  try {
1900
- miniflare = new Miniflare({
3427
+ miniflare = new Miniflare(expandLocalBindingWorkers({
1901
3428
  ...mfConfig,
1902
3429
  port
1903
- });
3430
+ }));
1904
3431
  await miniflare.ready;
1905
3432
  const miniflareBindings = wrapEnvSendEmailBindings(await miniflare.getBindings());
1906
3433
  client = await connectBridgeClientWithRetry(`ws://localhost:${port}`);
@@ -1949,9 +3476,9 @@ async function bootTestRuntime(mfConfig, usesMultiWorker) {
1949
3476
  }
1950
3477
 
1951
3478
  // src/test/simple-context-transport.ts
1952
- import { join as join10 } from "path";
3479
+ import { join as join11 } from "path";
1953
3480
  async function loadTransportDecoders(configDir6, transportFile) {
1954
- const transportPath = join10(configDir6, transportFile);
3481
+ const transportPath = join11(configDir6, transportFile);
1955
3482
  const transportModule = await import(transportPath);
1956
3483
  if (!transportModule.transport) {
1957
3484
  console.warn(`[devflare] Warning: Transport file "${transportFile}" does not export a named "transport" object.
@@ -2001,15 +3528,32 @@ function applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBi
2001
3528
  ...mfConfig.kvNamespaces && { kvNamespaces: mfConfig.kvNamespaces },
2002
3529
  ...mfConfig.r2Buckets && { r2Buckets: mfConfig.r2Buckets },
2003
3530
  ...mfConfig.d1Databases && { d1Databases: mfConfig.d1Databases },
3531
+ ...mfConfig.ratelimits && { ratelimits: mfConfig.ratelimits },
3532
+ ...mfConfig.versionMetadata && { versionMetadata: mfConfig.versionMetadata },
3533
+ ...mfConfig.workerLoaders && { workerLoaders: mfConfig.workerLoaders },
3534
+ ...mfConfig.mtlsCertificates && { mtlsCertificates: mfConfig.mtlsCertificates },
3535
+ ...mfConfig.dispatchNamespaces && { dispatchNamespaces: mfConfig.dispatchNamespaces },
3536
+ ...mfConfig.workflows && { workflows: mfConfig.workflows },
3537
+ ...mfConfig.pipelines && { pipelines: mfConfig.pipelines },
3538
+ ...mfConfig.images && { images: mfConfig.images },
3539
+ ...mfConfig.media && { media: mfConfig.media },
3540
+ ...mfConfig.artifacts && { artifacts: mfConfig.artifacts },
3541
+ ...mfConfig.secretsStoreSecrets && { secretsStoreSecrets: mfConfig.secretsStoreSecrets },
3542
+ ...mfConfig.wrappedBindings && { wrappedBindings: mfConfig.wrappedBindings },
2004
3543
  ...mfConfig.email && { email: mfConfig.email },
2005
3544
  ...Object.keys(primaryDurableObjects).length > 0 && { durableObjects: primaryDurableObjects },
2006
- ...serviceBindingResolution?.primaryServiceBindings && {
2007
- serviceBindings: serviceBindingResolution.primaryServiceBindings
2008
- }
3545
+ ...mfConfig.serviceBindings || serviceBindingResolution?.primaryServiceBindings ? {
3546
+ serviceBindings: {
3547
+ ...mfConfig.serviceBindings ?? {},
3548
+ ...serviceBindingResolution?.primaryServiceBindings ?? {}
3549
+ }
3550
+ } : {}
2009
3551
  };
2010
3552
  const additionalWorkers = [
2011
3553
  ...serviceBindingResolution?.workers || [],
2012
- ...doBindingResolution?.workers || []
3554
+ ...doBindingResolution?.workers || [],
3555
+ ...mfConfig.__devflareLocalSecretWorkers || [],
3556
+ ...mfConfig.__devflareLocalBindingWorkers || []
2013
3557
  ];
2014
3558
  const workersByName = new Map;
2015
3559
  for (const worker2 of additionalWorkers) {
@@ -2031,15 +3575,35 @@ function applyMultiWorkerConfig(mfConfig, config, serviceBindingResolution, doBi
2031
3575
  delete mfConfig.kvNamespaces;
2032
3576
  delete mfConfig.r2Buckets;
2033
3577
  delete mfConfig.d1Databases;
3578
+ delete mfConfig.ratelimits;
3579
+ delete mfConfig.versionMetadata;
3580
+ delete mfConfig.workerLoaders;
3581
+ delete mfConfig.mtlsCertificates;
3582
+ delete mfConfig.dispatchNamespaces;
3583
+ delete mfConfig.workflows;
3584
+ delete mfConfig.pipelines;
3585
+ delete mfConfig.images;
3586
+ delete mfConfig.media;
3587
+ delete mfConfig.artifacts;
3588
+ delete mfConfig.secretsStoreSecrets;
3589
+ delete mfConfig.wrappedBindings;
3590
+ delete mfConfig.serviceBindings;
3591
+ delete mfConfig.__devflareLocalSecretWorkers;
3592
+ delete mfConfig.__devflareLocalBindingWorkers;
2034
3593
  delete mfConfig.durableObjects;
2035
3594
  mfConfig.workers = workers;
2036
3595
  }
2037
3596
 
2038
3597
  // src/test/simple-context-mfconfig.ts
2039
- function buildInlineBridgeMfConfig(config) {
3598
+ function buildInlineBridgeMfConfig(config, options = {}) {
2040
3599
  const localWorkerBindings = config.vars ?? {};
3600
+ const localSecretWrappedBindingConfig = options.cwd ? buildLocalSecretWrappedBindingConfig(config, options.cwd) : undefined;
3601
+ const localBindingShimServiceConfig = buildLocalBindingShimServiceConfig(config);
3602
+ const localSecretBindingNames = new Set(localSecretWrappedBindingConfig?.localBindingNames ?? []);
2041
3603
  const mfConfig = {
2042
- modules: true
3604
+ modules: true,
3605
+ compatibilityDate: config.compatibilityDate ?? "2025-01-01",
3606
+ ...config.compatibilityFlags && { compatibilityFlags: config.compatibilityFlags }
2043
3607
  };
2044
3608
  if (config.bindings?.kv) {
2045
3609
  mfConfig.kvNamespaces = Object.keys(config.bindings.kv);
@@ -2052,6 +3616,10 @@ function buildInlineBridgeMfConfig(config) {
2052
3616
  return [bindingName, getLocalD1DatabaseIdentifier(bindingConfig)];
2053
3617
  }));
2054
3618
  }
3619
+ const hyperdrivesConfig = buildHyperdrivesConfig(config.bindings ?? {});
3620
+ if (hyperdrivesConfig) {
3621
+ mfConfig.hyperdrives = hyperdrivesConfig;
3622
+ }
2055
3623
  if (config.bindings?.queues?.producers) {
2056
3624
  const queueProducers = {};
2057
3625
  for (const [bindingName, queueName] of Object.entries(config.bindings.queues.producers)) {
@@ -2059,6 +3627,152 @@ function buildInlineBridgeMfConfig(config) {
2059
3627
  }
2060
3628
  mfConfig.queueProducers = queueProducers;
2061
3629
  }
3630
+ if (config.bindings?.rateLimits) {
3631
+ mfConfig.ratelimits = Object.fromEntries(Object.entries(config.bindings.rateLimits).map(([bindingName, binding]) => [
3632
+ bindingName,
3633
+ {
3634
+ simple: {
3635
+ limit: binding.simple.limit,
3636
+ period: binding.simple.period
3637
+ }
3638
+ }
3639
+ ]));
3640
+ }
3641
+ if (config.bindings?.versionMetadata) {
3642
+ mfConfig.versionMetadata = config.bindings.versionMetadata.binding;
3643
+ }
3644
+ if (config.bindings?.workerLoaders) {
3645
+ mfConfig.workerLoaders = Object.fromEntries(Object.keys(config.bindings.workerLoaders).map((bindingName) => [bindingName, {}]));
3646
+ }
3647
+ if (config.bindings?.mtlsCertificates) {
3648
+ mfConfig.mtlsCertificates = Object.fromEntries(Object.entries(config.bindings.mtlsCertificates).map(([bindingName, binding]) => {
3649
+ const normalized = normalizeMtlsCertificateBinding(binding);
3650
+ return [
3651
+ bindingName,
3652
+ {
3653
+ certificate_id: normalized.certificateId
3654
+ }
3655
+ ];
3656
+ }));
3657
+ }
3658
+ if (config.bindings?.dispatchNamespaces) {
3659
+ mfConfig.dispatchNamespaces = Object.fromEntries(Object.entries(config.bindings.dispatchNamespaces).map(([bindingName, binding]) => {
3660
+ const normalized = normalizeDispatchNamespaceBinding(binding);
3661
+ return [
3662
+ bindingName,
3663
+ {
3664
+ namespace: normalized.namespace
3665
+ }
3666
+ ];
3667
+ }));
3668
+ }
3669
+ if (config.bindings?.workflows) {
3670
+ mfConfig.workflows = Object.fromEntries(Object.entries(config.bindings.workflows).map(([bindingName, binding]) => {
3671
+ const normalized = normalizeWorkflowBinding(binding);
3672
+ return [
3673
+ bindingName,
3674
+ {
3675
+ name: normalized.name,
3676
+ className: normalized.className,
3677
+ ...normalized.scriptName && { scriptName: normalized.scriptName },
3678
+ ...normalized.limits && { stepLimit: normalized.limits.steps }
3679
+ }
3680
+ ];
3681
+ }));
3682
+ }
3683
+ if (config.bindings?.pipelines) {
3684
+ mfConfig.pipelines = Object.fromEntries(Object.entries(config.bindings.pipelines).map(([bindingName, binding]) => {
3685
+ const normalized = normalizePipelineBinding(binding);
3686
+ return [
3687
+ bindingName,
3688
+ typeof binding === "string" ? normalized.pipeline : { pipeline: normalized.pipeline }
3689
+ ];
3690
+ }));
3691
+ }
3692
+ if (config.bindings?.images && localBindingShimServiceConfig.localBindingNames.length === 0) {
3693
+ const [entry] = Object.entries(config.bindings.images);
3694
+ if (entry) {
3695
+ const [bindingName, binding] = entry;
3696
+ const normalized = normalizeImagesBinding(bindingName, binding);
3697
+ mfConfig.images = {
3698
+ binding: normalized.binding
3699
+ };
3700
+ }
3701
+ }
3702
+ if (config.bindings?.media && localBindingShimServiceConfig.localBindingNames.length === 0) {
3703
+ const [entry] = Object.entries(config.bindings.media);
3704
+ if (entry) {
3705
+ const [bindingName, binding] = entry;
3706
+ const normalized = normalizeMediaBinding(bindingName, binding);
3707
+ mfConfig.media = {
3708
+ binding: normalized.binding
3709
+ };
3710
+ }
3711
+ }
3712
+ if (config.bindings?.artifacts) {
3713
+ mfConfig.artifacts = Object.fromEntries(Object.entries(config.bindings.artifacts).map(([bindingName, binding]) => {
3714
+ const normalized = normalizeArtifactsBinding(binding);
3715
+ return [
3716
+ bindingName,
3717
+ {
3718
+ namespace: normalized.namespace
3719
+ }
3720
+ ];
3721
+ }));
3722
+ }
3723
+ if (config.bindings?.aiSearchNamespaces) {
3724
+ mfConfig.aiSearchNamespaces = Object.fromEntries(Object.entries(config.bindings.aiSearchNamespaces).map(([bindingName, binding]) => [
3725
+ bindingName,
3726
+ {
3727
+ namespace: binding.namespace
3728
+ }
3729
+ ]));
3730
+ }
3731
+ if (config.bindings?.aiSearch) {
3732
+ mfConfig.aiSearchInstances = Object.fromEntries(Object.entries(config.bindings.aiSearch).map(([bindingName, binding]) => [
3733
+ bindingName,
3734
+ {
3735
+ instance_name: binding.instanceName
3736
+ }
3737
+ ]));
3738
+ }
3739
+ if (config.bindings?.secretsStore) {
3740
+ const secretsStoreEntries = Object.entries(config.bindings.secretsStore).flatMap(([bindingName, binding]) => {
3741
+ if (localSecretBindingNames.has(bindingName)) {
3742
+ return [];
3743
+ }
3744
+ const normalized = normalizeSecretsStoreBinding(binding, config.secretsStoreId, bindingName);
3745
+ return [[
3746
+ bindingName,
3747
+ {
3748
+ store_id: normalized.storeId,
3749
+ secret_name: normalized.secretName
3750
+ }
3751
+ ]];
3752
+ });
3753
+ if (secretsStoreEntries.length > 0) {
3754
+ mfConfig.secretsStoreSecrets = Object.fromEntries(secretsStoreEntries);
3755
+ }
3756
+ }
3757
+ const wrappedBindings = {
3758
+ ...localSecretWrappedBindingConfig?.wrappedBindings ?? {}
3759
+ };
3760
+ const localBindingWorkers = [
3761
+ ...localSecretWrappedBindingConfig?.workers ?? [],
3762
+ ...localBindingShimServiceConfig.workers
3763
+ ];
3764
+ if (Object.keys(wrappedBindings).length > 0) {
3765
+ mfConfig.wrappedBindings = wrappedBindings;
3766
+ }
3767
+ if (localBindingShimServiceConfig.localBindingNames.length > 0) {
3768
+ mfConfig.serviceBindings = {
3769
+ ...mfConfig.serviceBindings ?? {},
3770
+ ...localBindingShimServiceConfig.serviceBindings
3771
+ };
3772
+ }
3773
+ if (localBindingWorkers.length > 0) {
3774
+ mfConfig.__devflareLocalBindingWorkers = localBindingWorkers;
3775
+ }
2062
3776
  if (Object.keys(localWorkerBindings).length > 0) {
2063
3777
  mfConfig.bindings = localWorkerBindings;
2064
3778
  }
@@ -2112,7 +3826,7 @@ async function createTestContext(configPath) {
2112
3826
  if (needsMultiWorkerForDOs) {
2113
3827
  doBindingResolution = await resolveDOBindings(config, configDir6);
2114
3828
  }
2115
- const mfConfig = buildInlineBridgeMfConfig(config);
3829
+ const mfConfig = buildInlineBridgeMfConfig(config, { cwd: configDir6 });
2116
3830
  const transportFile = resolveTransportFile(configDir6, config.files?.transport);
2117
3831
  if (transportFile) {
2118
3832
  state.transportDecode = await loadTransportDecoders(configDir6, transportFile);
@@ -2182,7 +3896,7 @@ async function createTestContext(configPath) {
2182
3896
  // src/test/cf.ts
2183
3897
  var cf = {
2184
3898
  email,
2185
- queue,
3899
+ queue: queue2,
2186
3900
  scheduled,
2187
3901
  worker,
2188
3902
  tail
@@ -2190,9 +3904,16 @@ var cf = {
2190
3904
  // src/test/should-skip.ts
2191
3905
  var REMOTE_ONLY_SERVICES = new Set([
2192
3906
  "ai",
3907
+ "ai_search",
3908
+ "ai_gateway",
3909
+ "media",
3910
+ "mtls_certificates",
3911
+ "artifacts",
3912
+ "builds",
2193
3913
  "vectorize"
2194
3914
  ]);
2195
3915
  var skipResults = new Map;
3916
+ var containerSkipResult = null;
2196
3917
  var EXPECTED_ERROR_PATTERNS = [
2197
3918
  "ECONNREFUSED",
2198
3919
  "ETIMEDOUT",
@@ -2263,12 +3984,30 @@ function getSkipResult(service) {
2263
3984
  result = computeSkip(service);
2264
3985
  skipResults.set(service, result);
2265
3986
  }
2266
- return result;
3987
+ return result;
3988
+ }
3989
+ function getContainerSkipResult() {
3990
+ if (!containerSkipResult) {
3991
+ containerSkipResult = getContainerSkipReason().then((reason) => {
3992
+ if (reason) {
3993
+ console.log(`CONTAINERS tests skipped: ${reason}`);
3994
+ return true;
3995
+ }
3996
+ return false;
3997
+ });
3998
+ }
3999
+ return containerSkipResult;
2267
4000
  }
2268
4001
  var shouldSkip = {
2269
4002
  get ai() {
2270
4003
  return getSkipResult("ai");
2271
4004
  },
4005
+ get aiSearch() {
4006
+ return getSkipResult("ai_search");
4007
+ },
4008
+ get aiGateway() {
4009
+ return getSkipResult("ai_gateway");
4010
+ },
2272
4011
  get vectorize() {
2273
4012
  return getSkipResult("vectorize");
2274
4013
  },
@@ -2289,423 +4028,974 @@ var shouldSkip = {
2289
4028
  },
2290
4029
  get durableObjects() {
2291
4030
  return getSkipResult("durable_objects");
4031
+ },
4032
+ get media() {
4033
+ return getSkipResult("media");
4034
+ },
4035
+ get mtlsCertificates() {
4036
+ return getSkipResult("mtls_certificates");
4037
+ },
4038
+ get artifacts() {
4039
+ return getSkipResult("artifacts");
4040
+ },
4041
+ get builds() {
4042
+ return getSkipResult("builds");
4043
+ },
4044
+ get containers() {
4045
+ return getContainerSkipResult();
2292
4046
  }
2293
4047
  };
2294
- // src/test/utilities.ts
2295
- function createMockTestContext(options = {}) {
2296
- const waitUntilPromises = [];
2297
- const ctx = {
2298
- waitUntil(promise) {
2299
- waitUntilPromises.push(promise);
2300
- },
2301
- passThroughOnException() {},
2302
- props: {}
4048
+ // src/test/ai-search.ts
4049
+ var MOCK_TIMESTAMP = "2026-04-26T00:00:00.000Z";
4050
+ function cloneInfo(value) {
4051
+ return { ...value };
4052
+ }
4053
+ function extractSearchQuery(params) {
4054
+ if ("query" in params && typeof params.query === "string") {
4055
+ return params.query;
4056
+ }
4057
+ const messages = "messages" in params ? params.messages : [];
4058
+ return messages.filter((message) => message.role === "user" && message.content).map((message) => message.content).join(" ");
4059
+ }
4060
+ function tokenize(value) {
4061
+ return value.toLowerCase().split(/[^a-z0-9_]+/).filter((token) => token.length > 2);
4062
+ }
4063
+ function scoreText(query, text) {
4064
+ const tokens = tokenize(query);
4065
+ if (tokens.length === 0) {
4066
+ return 1;
4067
+ }
4068
+ const haystack = text.toLowerCase();
4069
+ const matches = tokens.filter((token) => haystack.includes(token)).length;
4070
+ return matches === 0 ? 0 : Math.max(0.1, matches / tokens.length);
4071
+ }
4072
+ function streamFromText(text) {
4073
+ const encoded = new TextEncoder().encode(text);
4074
+ return new ReadableStream({
4075
+ start(controller) {
4076
+ controller.enqueue(encoded);
4077
+ controller.close();
4078
+ }
4079
+ });
4080
+ }
4081
+ async function contentToText(content) {
4082
+ if (typeof content === "string") {
4083
+ return content;
4084
+ }
4085
+ if (content instanceof Blob) {
4086
+ return content.text();
4087
+ }
4088
+ return new Response(content).text();
4089
+ }
4090
+ function createItemInfo(id, key, content, options = {}) {
4091
+ return {
4092
+ id,
4093
+ key,
4094
+ status: options.status ?? "completed",
4095
+ next_action: null,
4096
+ namespace: options.namespace,
4097
+ chunks_count: content.length > 0 ? 1 : 0,
4098
+ file_size: new TextEncoder().encode(content).byteLength,
4099
+ source_id: options.sourceId ?? null,
4100
+ last_seen_at: MOCK_TIMESTAMP,
4101
+ created_at: MOCK_TIMESTAMP,
4102
+ metadata: options.metadata
2303
4103
  };
4104
+ }
4105
+ function createItemChunks(item, chunks) {
4106
+ let offset = 0;
4107
+ return chunks.map((text, index) => {
4108
+ const start = offset;
4109
+ const end = start + new TextEncoder().encode(text).byteLength;
4110
+ offset = end;
4111
+ return {
4112
+ id: `${item.id}:chunk-${index + 1}`,
4113
+ text,
4114
+ start_byte: start,
4115
+ end_byte: end,
4116
+ item: {
4117
+ timestamp: Date.parse(MOCK_TIMESTAMP),
4118
+ key: item.key,
4119
+ metadata: item.metadata
4120
+ }
4121
+ };
4122
+ });
4123
+ }
4124
+ function createStoredItem(sequence, fixture, namespace) {
4125
+ const content = fixture.content ?? fixture.chunks?.join(`
4126
+ `) ?? "";
4127
+ const id = fixture.id ?? `item-${sequence}`;
4128
+ const info = createItemInfo(id, fixture.key, content, {
4129
+ metadata: fixture.metadata,
4130
+ status: fixture.status,
4131
+ namespace
4132
+ });
4133
+ const chunks = createItemChunks(info, fixture.chunks ?? [content]);
2304
4134
  return {
2305
- env: options.env ?? {},
2306
- ctx,
2307
- request: options.request ?? null,
2308
- waitUntilPromises
4135
+ info,
4136
+ content,
4137
+ contentType: fixture.contentType ?? "text/plain",
4138
+ chunks,
4139
+ logs: [
4140
+ {
4141
+ timestamp: MOCK_TIMESTAMP,
4142
+ action: "index",
4143
+ message: `Indexed ${fixture.key}`,
4144
+ fileKey: fixture.key,
4145
+ chunkCount: chunks.length,
4146
+ processingTimeMs: 0
4147
+ }
4148
+ ]
2309
4149
  };
2310
4150
  }
2311
- async function withTestContext(options, handler) {
2312
- const testCtx = createMockTestContext(options);
2313
- return runWithContext(testCtx.env, testCtx.ctx, options.request ?? null, handler, options.type ?? "fetch");
4151
+ function isAISearchInstance(value) {
4152
+ return typeof value.search === "function";
2314
4153
  }
2315
- function createMockKV(initialData = {}) {
2316
- const store = new Map;
2317
- const metadata = new Map;
2318
- const encoder = new TextEncoder;
2319
- const decoder = new TextDecoder;
2320
- for (const [key, value] of Object.entries(initialData)) {
2321
- store.set(key, encoder.encode(value));
4154
+ function createMockAISearchInstance(options = {}) {
4155
+ const instanceId = options.id ?? options.info?.id ?? "mock-ai-search";
4156
+ const namespace = options.namespace ?? options.info?.namespace;
4157
+ const items = new Map;
4158
+ const jobs = new Map;
4159
+ const searches = [];
4160
+ let itemSequence = 0;
4161
+ let jobSequence = 0;
4162
+ let info = {
4163
+ id: instanceId,
4164
+ type: "builtin",
4165
+ namespace,
4166
+ status: "ready",
4167
+ created_at: MOCK_TIMESTAMP,
4168
+ modified_at: MOCK_TIMESTAMP,
4169
+ index_method: {
4170
+ vector: true,
4171
+ keyword: true
4172
+ },
4173
+ fusion_method: "rrf",
4174
+ ...options.info
4175
+ };
4176
+ for (const fixture of options.items ?? []) {
4177
+ const stored = createStoredItem(++itemSequence, fixture, namespace);
4178
+ items.set(stored.info.id, stored);
2322
4179
  }
2323
- const toBytes = async (value) => {
2324
- if (typeof value === "string") {
2325
- return encoder.encode(value);
2326
- }
2327
- if (value instanceof ArrayBuffer) {
2328
- return new Uint8Array(value.slice(0));
2329
- }
2330
- if (ArrayBuffer.isView(value)) {
2331
- const view = value;
2332
- const copy = new Uint8Array(view.byteLength);
2333
- copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
2334
- return copy;
2335
- }
2336
- const reader = value.getReader();
4180
+ const findMatches = (params) => {
4181
+ searches.push(params);
4182
+ const query = extractSearchQuery(params);
4183
+ const maxResults = params.ai_search_options?.retrieval?.max_num_results ?? 10;
2337
4184
  const chunks = [];
2338
- let total = 0;
2339
- while (true) {
2340
- const result = await reader.read();
2341
- if (result.done)
2342
- break;
2343
- if (result.value) {
2344
- const chunk = result.value instanceof Uint8Array ? result.value : new Uint8Array(result.value);
2345
- chunks.push(chunk);
2346
- total += chunk.length;
2347
- }
2348
- }
2349
- const combined = new Uint8Array(total);
2350
- let offset = 0;
2351
- for (const chunk of chunks) {
2352
- combined.set(chunk, offset);
2353
- offset += chunk.length;
2354
- }
2355
- return combined;
2356
- };
2357
- const decodeBytes = (bytes, type) => {
2358
- switch (type) {
2359
- case "json":
2360
- return JSON.parse(decoder.decode(bytes));
2361
- case "arrayBuffer": {
2362
- const copy = new Uint8Array(bytes.length);
2363
- copy.set(bytes);
2364
- return copy.buffer;
2365
- }
2366
- case "stream": {
2367
- const copy = new Uint8Array(bytes.length);
2368
- copy.set(bytes);
2369
- return new ReadableStream({
2370
- start(controller) {
2371
- controller.enqueue(copy);
2372
- controller.close();
4185
+ for (const item of items.values()) {
4186
+ for (const chunk of item.chunks) {
4187
+ const score = scoreText(query, `${item.info.key} ${chunk.text}`);
4188
+ if (score === 0) {
4189
+ continue;
4190
+ }
4191
+ chunks.push({
4192
+ id: chunk.id,
4193
+ type: "text",
4194
+ score,
4195
+ text: chunk.text,
4196
+ item: {
4197
+ timestamp: Date.parse(MOCK_TIMESTAMP),
4198
+ key: item.info.key,
4199
+ metadata: item.info.metadata
4200
+ },
4201
+ scoring_details: {
4202
+ keyword_score: score,
4203
+ vector_score: score,
4204
+ keyword_rank: chunks.length + 1,
4205
+ vector_rank: chunks.length + 1,
4206
+ fusion_method: "rrf"
2373
4207
  }
2374
4208
  });
2375
4209
  }
2376
- default:
2377
- return decoder.decode(bytes);
2378
4210
  }
4211
+ return {
4212
+ search_query: query,
4213
+ chunks: chunks.slice(0, maxResults)
4214
+ };
2379
4215
  };
2380
- const resolveType = (options) => {
2381
- const type = typeof options === "string" ? options : options?.type ?? "text";
2382
- return type;
2383
- };
2384
- return {
2385
- async get(key, options) {
2386
- const bytes = store.get(key);
2387
- if (bytes === undefined)
2388
- return null;
2389
- return decodeBytes(bytes, resolveType(options));
4216
+ const createItemHandle = (itemId) => ({
4217
+ async info() {
4218
+ const item = items.get(itemId);
4219
+ if (!item) {
4220
+ throw new Error(`Mock AI Search item "${itemId}" was not found.`);
4221
+ }
4222
+ return cloneInfo(item.info);
2390
4223
  },
2391
- async put(key, value, _options) {
2392
- const bytes = await toBytes(value);
2393
- store.set(key, bytes);
4224
+ async download() {
4225
+ const item = items.get(itemId);
4226
+ if (!item) {
4227
+ throw new Error(`Mock AI Search item "${itemId}" was not found.`);
4228
+ }
4229
+ return {
4230
+ body: streamFromText(item.content),
4231
+ contentType: item.contentType,
4232
+ filename: item.info.key,
4233
+ size: new TextEncoder().encode(item.content).byteLength
4234
+ };
2394
4235
  },
2395
- async delete(key) {
2396
- store.delete(key);
2397
- metadata.delete(key);
4236
+ async sync() {
4237
+ const item = items.get(itemId);
4238
+ if (!item) {
4239
+ throw new Error(`Mock AI Search item "${itemId}" was not found.`);
4240
+ }
4241
+ item.info.status = "completed";
4242
+ item.info.last_seen_at = MOCK_TIMESTAMP;
4243
+ return cloneInfo(item.info);
2398
4244
  },
2399
- async list(options) {
2400
- const prefix = options?.prefix ?? "";
2401
- const limit = options?.limit ?? 1000;
2402
- const keys = Array.from(store.keys()).filter((key) => key.startsWith(prefix)).slice(0, limit).map((name) => ({ name }));
4245
+ async logs(params) {
4246
+ const item = items.get(itemId);
4247
+ if (!item) {
4248
+ throw new Error(`Mock AI Search item "${itemId}" was not found.`);
4249
+ }
4250
+ const limit = params?.limit ?? 50;
4251
+ const result = item.logs.slice(0, limit);
2403
4252
  return {
2404
- keys,
2405
- list_complete: keys.length < limit,
2406
- cursor: undefined
4253
+ result,
4254
+ result_info: {
4255
+ count: result.length,
4256
+ per_page: limit,
4257
+ cursor: null,
4258
+ truncated: item.logs.length > result.length
4259
+ }
2407
4260
  };
2408
4261
  },
2409
- async getWithMetadata(key, options) {
2410
- const bytes = store.get(key);
4262
+ async chunks(params) {
4263
+ const item = items.get(itemId);
4264
+ if (!item) {
4265
+ throw new Error(`Mock AI Search item "${itemId}" was not found.`);
4266
+ }
4267
+ const offset = params?.offset ?? 0;
4268
+ const limit = params?.limit ?? 20;
4269
+ const result = item.chunks.slice(offset, offset + limit);
2411
4270
  return {
2412
- value: bytes === undefined ? null : decodeBytes(bytes, resolveType(options)),
2413
- metadata: metadata.get(key) ?? null
4271
+ result,
4272
+ result_info: {
4273
+ count: result.length,
4274
+ total: item.chunks.length,
4275
+ limit,
4276
+ offset
4277
+ }
2414
4278
  };
2415
4279
  }
4280
+ });
4281
+ const uploadItem = async (name, content, uploadOptions) => {
4282
+ const text = await contentToText(content);
4283
+ const existing = Array.from(items.values()).find((item) => item.info.key === name);
4284
+ const stored = createStoredItem(existing ? Number(existing.info.id.replace(/^item-/, "")) || ++itemSequence : ++itemSequence, {
4285
+ id: existing?.info.id,
4286
+ key: name,
4287
+ content: text,
4288
+ metadata: uploadOptions?.metadata
4289
+ }, namespace);
4290
+ items.set(stored.info.id, stored);
4291
+ return cloneInfo(stored.info);
2416
4292
  };
2417
- }
2418
- var TABLE_NAME_RE = /(?:from|into|update)\s+["'`]?([a-zA-Z_][a-zA-Z0-9_]*)["'`]?/i;
2419
- var extractTable = (sql) => {
2420
- const match = TABLE_NAME_RE.exec(sql);
2421
- return match ? match[1] : null;
2422
- };
2423
- var detectOp = (sql) => {
2424
- const trimmed = sql.trimStart().toLowerCase();
2425
- if (trimmed.startsWith("select"))
2426
- return "select";
2427
- if (trimmed.startsWith("insert"))
2428
- return "insert";
2429
- if (trimmed.startsWith("update"))
2430
- return "update";
2431
- if (trimmed.startsWith("delete"))
2432
- return "delete";
2433
- return "other";
2434
- };
2435
- function createMockD1(mockResultsOrOptions = []) {
2436
- const options = Array.isArray(mockResultsOrOptions) ? { results: mockResultsOrOptions } : mockResultsOrOptions;
2437
- const tables = new Map;
2438
- for (const [name, rows] of Object.entries(options.fixtures ?? {})) {
2439
- tables.set(name, [...rows]);
2440
- }
2441
- const fallback = options.results ?? [];
2442
- const resolveRows = (sql) => {
2443
- const op = detectOp(sql);
2444
- const table = extractTable(sql);
2445
- if (table && tables.has(table)) {
2446
- return { rows: tables.get(table) ?? [], op, table };
4293
+ const itemsApi = {
4294
+ async list(params) {
4295
+ let result = Array.from(items.values()).map((item) => cloneInfo(item.info));
4296
+ if (params?.search) {
4297
+ const search = params.search.toLowerCase();
4298
+ result = result.filter((item) => item.key.toLowerCase().includes(search));
4299
+ }
4300
+ if (params?.status) {
4301
+ result = result.filter((item) => item.status === params.status);
4302
+ }
4303
+ const page = params?.page ?? 1;
4304
+ const perPage = (params?.per_page ?? result.length) || 50;
4305
+ const start = (page - 1) * perPage;
4306
+ const paged = result.slice(start, start + perPage);
4307
+ return {
4308
+ result: paged,
4309
+ result_info: {
4310
+ count: paged.length,
4311
+ page,
4312
+ per_page: perPage,
4313
+ total_count: result.length
4314
+ }
4315
+ };
4316
+ },
4317
+ upload: uploadItem,
4318
+ async uploadAndPoll(name, content, uploadOptions) {
4319
+ return uploadItem(name, content, uploadOptions);
4320
+ },
4321
+ get(itemId) {
4322
+ return createItemHandle(itemId);
4323
+ },
4324
+ async delete(itemId) {
4325
+ items.delete(itemId);
2447
4326
  }
2448
- return { rows: [...fallback], op, table };
2449
4327
  };
2450
- const createStatement = (sql) => {
2451
- let boundValues = [];
2452
- const statement = {
2453
- bind(...values) {
2454
- boundValues = values;
2455
- return statement;
2456
- },
2457
- async first(column) {
2458
- const { rows } = resolveRows(sql);
2459
- const row = rows[0];
2460
- if (!row)
2461
- return null;
2462
- if (column)
2463
- return row[column];
2464
- return row;
2465
- },
2466
- async all() {
2467
- const { rows } = resolveRows(sql);
2468
- return {
2469
- results: rows,
2470
- success: true,
2471
- meta: { duration: 0, changes: 0, last_row_id: 0 }
2472
- };
2473
- },
2474
- async run() {
2475
- const { op, table } = resolveRows(sql);
2476
- let changes = 0;
2477
- let lastRowId = 0;
2478
- if (op === "insert" && table) {
2479
- const rows = tables.get(table) ?? [];
2480
- const bound = boundValues.length > 0 ? Object.fromEntries(boundValues.map((v, i) => [`col${i}`, v])) : {};
2481
- rows.push(bound);
2482
- tables.set(table, rows);
2483
- changes = 1;
2484
- lastRowId = rows.length;
2485
- } else if (op === "delete" && table) {
2486
- const rows = tables.get(table) ?? [];
2487
- changes = rows.length;
2488
- tables.set(table, []);
2489
- } else if (op === "update" && table) {
2490
- changes = (tables.get(table) ?? []).length;
4328
+ const createJobHandle = (jobId) => ({
4329
+ async info() {
4330
+ const job = jobs.get(jobId);
4331
+ if (!job) {
4332
+ throw new Error(`Mock AI Search job "${jobId}" was not found.`);
4333
+ }
4334
+ return cloneInfo(job);
4335
+ },
4336
+ async logs(params) {
4337
+ const perPage = params?.per_page ?? 50;
4338
+ return {
4339
+ result: [
4340
+ {
4341
+ id: 1,
4342
+ message: `Mock job ${jobId}`,
4343
+ message_type: 0,
4344
+ created_at: Date.parse(MOCK_TIMESTAMP)
4345
+ }
4346
+ ].slice(0, perPage),
4347
+ result_info: {
4348
+ count: 1,
4349
+ page: params?.page ?? 1,
4350
+ per_page: perPage,
4351
+ total_count: 1
2491
4352
  }
2492
- return {
2493
- results: [],
2494
- success: true,
2495
- meta: { duration: 0, changes, last_row_id: lastRowId }
2496
- };
2497
- },
2498
- async raw(_options) {
2499
- const { rows } = resolveRows(sql);
2500
- return rows.map((row) => Object.values(row));
4353
+ };
4354
+ },
4355
+ async cancel() {
4356
+ const job = jobs.get(jobId);
4357
+ if (!job) {
4358
+ throw new Error(`Mock AI Search job "${jobId}" was not found.`);
2501
4359
  }
2502
- };
2503
- return statement;
4360
+ const updated = {
4361
+ ...job,
4362
+ ended_at: MOCK_TIMESTAMP,
4363
+ end_reason: "cancelled"
4364
+ };
4365
+ jobs.set(jobId, updated);
4366
+ return cloneInfo(updated);
4367
+ }
4368
+ });
4369
+ const jobsApi = {
4370
+ async list(params) {
4371
+ const allJobs = Array.from(jobs.values()).map((job) => cloneInfo(job));
4372
+ const page = params?.page ?? 1;
4373
+ const perPage = (params?.per_page ?? allJobs.length) || 50;
4374
+ const start = (page - 1) * perPage;
4375
+ const result = allJobs.slice(start, start + perPage);
4376
+ return {
4377
+ result,
4378
+ result_info: {
4379
+ count: result.length,
4380
+ page,
4381
+ per_page: perPage,
4382
+ total_count: allJobs.length
4383
+ }
4384
+ };
4385
+ },
4386
+ async create(params) {
4387
+ const id = `job-${++jobSequence}`;
4388
+ const job = {
4389
+ id,
4390
+ source: "user",
4391
+ description: params?.description,
4392
+ started_at: MOCK_TIMESTAMP
4393
+ };
4394
+ jobs.set(id, job);
4395
+ return cloneInfo(job);
4396
+ },
4397
+ get(jobId) {
4398
+ return createJobHandle(jobId);
4399
+ }
2504
4400
  };
2505
4401
  return {
2506
- prepare(query) {
2507
- return createStatement(query);
4402
+ async search(params) {
4403
+ return findMatches(params);
2508
4404
  },
2509
- async exec(_query) {
4405
+ async chatCompletions(params) {
4406
+ const search = findMatches({
4407
+ messages: params.messages,
4408
+ ai_search_options: params.ai_search_options
4409
+ });
4410
+ const content = typeof options.chatMessage === "function" ? options.chatMessage(search.chunks, params) : options.chatMessage ?? (search.chunks.map((chunk) => chunk.text).join(`
4411
+ `) || "No matching offline AI Search chunks.");
4412
+ if (params.stream) {
4413
+ const payload = JSON.stringify({
4414
+ choices: [{ delta: { content } }],
4415
+ chunks: search.chunks
4416
+ });
4417
+ return streamFromText(`data: ${payload}
4418
+
4419
+ `);
4420
+ }
2510
4421
  return {
2511
- results: [],
2512
- success: true,
2513
- meta: { duration: 0, changes: 0, last_row_id: 0 }
4422
+ id: "mock-ai-search-chat",
4423
+ object: "chat.completion",
4424
+ model: params.model ?? "mock-ai-search",
4425
+ choices: [
4426
+ {
4427
+ index: 0,
4428
+ message: {
4429
+ role: "assistant",
4430
+ content
4431
+ }
4432
+ }
4433
+ ],
4434
+ chunks: search.chunks
2514
4435
  };
2515
4436
  },
2516
- async batch(statements) {
2517
- return statements.map(() => ({
2518
- results: [],
2519
- success: true,
2520
- meta: { duration: 0, changes: 0, last_row_id: 0 }
2521
- }));
4437
+ async update(config) {
4438
+ info = {
4439
+ ...info,
4440
+ ...config,
4441
+ modified_at: MOCK_TIMESTAMP
4442
+ };
4443
+ return cloneInfo(info);
2522
4444
  },
2523
- async dump() {
2524
- return new ArrayBuffer(0);
4445
+ async info() {
4446
+ return cloneInfo(info);
2525
4447
  },
2526
- withSession(_constraintOrBookmark) {
2527
- return this;
4448
+ async stats() {
4449
+ const stats = {
4450
+ queued: 0,
4451
+ running: 0,
4452
+ completed: 0,
4453
+ error: 0,
4454
+ skipped: 0,
4455
+ outdated: 0,
4456
+ last_activity: MOCK_TIMESTAMP,
4457
+ engine: {
4458
+ vectorize: {
4459
+ vectorsCount: items.size,
4460
+ dimensions: 0
4461
+ },
4462
+ r2: {
4463
+ payloadSizeBytes: Array.from(items.values()).reduce((sum, item) => sum + item.info.file_size, 0),
4464
+ metadataSizeBytes: 0,
4465
+ objectCount: items.size
4466
+ }
4467
+ }
4468
+ };
4469
+ for (const item of items.values()) {
4470
+ stats[item.info.status] = (stats[item.info.status] ?? 0) + 1;
4471
+ }
4472
+ return stats;
4473
+ },
4474
+ get items() {
4475
+ return itemsApi;
4476
+ },
4477
+ get jobs() {
4478
+ return jobsApi;
4479
+ },
4480
+ _getSearches() {
4481
+ return [...searches];
4482
+ },
4483
+ _getItems() {
4484
+ return Array.from(items.values()).map((item) => cloneInfo(item.info));
4485
+ },
4486
+ _getJobs() {
4487
+ return Array.from(jobs.values()).map((job) => cloneInfo(job));
2528
4488
  }
2529
4489
  };
2530
4490
  }
2531
- function createMockR2() {
2532
- const store = new Map;
2533
- const createR2Object = (key, content, metadata) => {
2534
- const encoder = new TextEncoder;
2535
- const data = encoder.encode(content);
2536
- return {
2537
- key,
2538
- version: "1",
2539
- size: data.length,
2540
- etag: `"${key}-etag"`,
2541
- httpEtag: `"${key}-etag"`,
2542
- uploaded: new Date,
2543
- httpMetadata: metadata ?? {},
2544
- customMetadata: {},
2545
- checksums: {},
2546
- storageClass: "Standard",
2547
- body: new ReadableStream({
2548
- start(controller) {
2549
- controller.enqueue(data);
2550
- controller.close();
2551
- }
2552
- }),
2553
- bodyUsed: false,
2554
- async arrayBuffer() {
2555
- return new Uint8Array(data).buffer;
2556
- },
2557
- async text() {
2558
- return content;
2559
- },
2560
- async json() {
2561
- return JSON.parse(content);
2562
- },
2563
- async blob() {
2564
- return new Blob([data]);
2565
- },
2566
- writeHttpMetadata(headers) {}
2567
- };
4491
+ function createMockAISearchNamespace(options = {}) {
4492
+ const namespaceName = options.namespace ?? "default";
4493
+ const instances = new Map;
4494
+ for (const [name, instanceOptions] of Object.entries(options.instances ?? {})) {
4495
+ instances.set(name, isAISearchInstance(instanceOptions) ? instanceOptions : createMockAISearchInstance({
4496
+ id: name,
4497
+ namespace: namespaceName,
4498
+ ...instanceOptions
4499
+ }));
4500
+ }
4501
+ const getInstance = (name) => {
4502
+ const instance = instances.get(name);
4503
+ if (!instance) {
4504
+ throw new Error(`Mock AI Search namespace has no instance named "${name}".`);
4505
+ }
4506
+ return instance;
2568
4507
  };
2569
4508
  return {
2570
- async put(key, value, options) {
2571
- let content;
2572
- if (typeof value === "string") {
2573
- content = value;
2574
- } else if (value instanceof ArrayBuffer) {
2575
- content = new TextDecoder().decode(value);
2576
- } else if (value instanceof Blob) {
2577
- content = await value.text();
2578
- } else if (value === null) {
2579
- content = "";
2580
- } else {
2581
- const reader = value.getReader();
2582
- const chunks = [];
2583
- let done = false;
2584
- while (!done) {
2585
- const result = await reader.read();
2586
- done = result.done;
2587
- if (result.value) {
2588
- chunks.push(new TextDecoder().decode(result.value));
2589
- }
2590
- }
2591
- content = chunks.join("");
4509
+ get(name) {
4510
+ return getInstance(name);
4511
+ },
4512
+ async list(params) {
4513
+ let result = await Promise.all(Array.from(instances.entries()).map(async ([name, instance]) => ({
4514
+ ...await instance.info(),
4515
+ namespace: namespaceName,
4516
+ id: name
4517
+ })));
4518
+ if (params?.search) {
4519
+ const search = params.search.toLowerCase();
4520
+ result = result.filter((instance) => instance.id.toLowerCase().includes(search));
2592
4521
  }
2593
- store.set(key, { content });
2594
- return createR2Object(key, content);
4522
+ const page = params?.page ?? 1;
4523
+ const perPage = (params?.per_page ?? result.length) || 50;
4524
+ const start = (page - 1) * perPage;
4525
+ const paged = result.slice(start, start + perPage);
4526
+ return {
4527
+ result: paged,
4528
+ result_info: {
4529
+ count: paged.length,
4530
+ page,
4531
+ per_page: perPage,
4532
+ total_count: result.length
4533
+ }
4534
+ };
2595
4535
  },
2596
- async get(key, options) {
2597
- const item = store.get(key);
2598
- if (!item)
2599
- return null;
2600
- return createR2Object(key, item.content, item.metadata);
4536
+ async create(config) {
4537
+ const instance = createMockAISearchInstance({
4538
+ id: config.id,
4539
+ namespace: namespaceName,
4540
+ info: config
4541
+ });
4542
+ instances.set(config.id, instance);
4543
+ return instance;
2601
4544
  },
2602
- async head(key) {
2603
- const item = store.get(key);
2604
- if (!item)
2605
- return null;
4545
+ async delete(name) {
4546
+ instances.delete(name);
4547
+ },
4548
+ async search(params) {
4549
+ const query = extractSearchQuery(params);
4550
+ const chunks = [];
4551
+ const errors = [];
4552
+ for (const instanceId of params.ai_search_options.instance_ids) {
4553
+ const instance = instances.get(instanceId);
4554
+ if (!instance) {
4555
+ errors.push({
4556
+ instance_id: instanceId,
4557
+ message: `Mock AI Search namespace has no instance named "${instanceId}".`
4558
+ });
4559
+ continue;
4560
+ }
4561
+ const response = "query" in params ? await instance.search({ query, ai_search_options: params.ai_search_options }) : await instance.search({
4562
+ messages: params.messages,
4563
+ ai_search_options: params.ai_search_options
4564
+ });
4565
+ chunks.push(...response.chunks.map((chunk) => ({
4566
+ ...chunk,
4567
+ instance_id: instanceId
4568
+ })));
4569
+ }
2606
4570
  return {
2607
- key,
2608
- version: "1",
2609
- size: new TextEncoder().encode(item.content).length,
2610
- etag: `"${key}-etag"`,
2611
- httpEtag: `"${key}-etag"`,
2612
- uploaded: new Date,
2613
- httpMetadata: item.metadata ?? {},
2614
- customMetadata: {},
2615
- checksums: {},
2616
- storageClass: "Standard",
2617
- writeHttpMetadata(headers) {}
4571
+ search_query: query,
4572
+ chunks,
4573
+ ...errors.length > 0 && { errors }
2618
4574
  };
2619
4575
  },
2620
- async delete(keys) {
2621
- const keyArray = Array.isArray(keys) ? keys : [keys];
2622
- for (const key of keyArray) {
2623
- store.delete(key);
4576
+ async chatCompletions(params) {
4577
+ const search = await this.search({
4578
+ messages: params.messages,
4579
+ ai_search_options: params.ai_search_options
4580
+ });
4581
+ const content = search.chunks.map((chunk) => chunk.text).join(`
4582
+ `) || "No matching offline AI Search chunks.";
4583
+ if (params.stream) {
4584
+ return streamFromText(`data: ${JSON.stringify({ chunks: search.chunks })}
4585
+
4586
+ `);
2624
4587
  }
2625
- },
2626
- async list(options) {
2627
- const objects = Array.from(store.entries()).map(([key, { content, metadata }]) => createR2Object(key, content, metadata));
2628
4588
  return {
2629
- objects,
2630
- truncated: false,
2631
- delimitedPrefixes: []
4589
+ id: "mock-ai-search-namespace-chat",
4590
+ object: "chat.completion",
4591
+ model: params.model ?? "mock-ai-search",
4592
+ choices: [
4593
+ {
4594
+ index: 0,
4595
+ message: {
4596
+ role: "assistant",
4597
+ content
4598
+ }
4599
+ }
4600
+ ],
4601
+ chunks: search.chunks,
4602
+ errors: search.errors
2632
4603
  };
2633
4604
  },
2634
- async createMultipartUpload(key, options) {
2635
- throw new Error("Multipart upload not implemented in mock");
2636
- },
2637
- async resumeMultipartUpload(key, uploadId) {
2638
- throw new Error("Multipart upload not implemented in mock");
4605
+ _getInstances() {
4606
+ return Array.from(instances.keys());
2639
4607
  }
2640
4608
  };
2641
4609
  }
2642
- function createMockQueue() {
2643
- const messages = [];
4610
+
4611
+ // src/test/offline-bindings.ts
4612
+ var SUPPORT_MATRIX = {
4613
+ rateLimits: {
4614
+ service: "rateLimits",
4615
+ tier: "offline-native",
4616
+ reason: "Miniflare and devflare/test can simulate fixed-window RateLimit bindings locally.",
4617
+ recommendation: "Use createOfflineEnv() or createMockRateLimit() for pure tests; use createTestContext() for Miniflare-backed tests."
4618
+ },
4619
+ versionMetadata: {
4620
+ service: "versionMetadata",
4621
+ tier: "offline-native",
4622
+ reason: "Version metadata can be deterministic in tests without Cloudflare state.",
4623
+ recommendation: "Use createOfflineEnv() or createMockVersionMetadata() when asserting version-aware code."
4624
+ },
4625
+ secretsStore: {
4626
+ service: "secretsStore",
4627
+ tier: "offline-native",
4628
+ reason: "Secrets Store binding shape is local-testable with local secret-store values or fixed fixtures.",
4629
+ recommendation: "Use devflare secrets --local for dev/test values, or pass fixtures.secretsStore values for pure tests."
4630
+ },
4631
+ hyperdrive: {
4632
+ service: "hyperdrive",
4633
+ tier: "offline-native",
4634
+ reason: "Hyperdrive can run locally through Miniflare when Devflare has a local connection string or test fixture for the target database.",
4635
+ recommendation: "Use bindings.hyperdrive.*.localConnectionString, CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>, or fixtures.hyperdrive for offline tests."
4636
+ },
4637
+ workerLoaders: {
4638
+ service: "workerLoaders",
4639
+ tier: "offline-native",
4640
+ reason: "Worker Loader bindings run locally through Miniflare and can use explicit Worker stubs for pure tests.",
4641
+ recommendation: "Use createTestContext() for local WorkerLoader execution; pass fixtures.workerLoaders with a WorkerStub for pure tests."
4642
+ },
4643
+ mtlsCertificates: {
4644
+ service: "mtlsCertificates",
4645
+ tier: "offline-fixture",
4646
+ reason: "Fetcher call paths can be tested locally, but real certificate presentation is Cloudflare/Wrangler remote behavior.",
4647
+ recommendation: "Pass fixtures.mtlsCertificates handlers for unit tests; use remote/deployed tests for certificate presentation."
4648
+ },
4649
+ dispatchNamespaces: {
4650
+ service: "dispatchNamespaces",
4651
+ tier: "offline-fixture",
4652
+ reason: "Tenant dispatch can be backed by explicit test fetchers, but namespace uploads and lifecycle are Cloudflare-managed.",
4653
+ recommendation: "Pass fixtures.dispatchNamespaces workers for deterministic tenant routing tests."
4654
+ },
4655
+ workflows: {
4656
+ service: "workflows",
4657
+ tier: "offline-native",
4658
+ reason: "Workflow binding calls can run through Miniflare or a deterministic local Workflow mock for app-level tests.",
4659
+ recommendation: "Use createOfflineEnv() for pure tests and createTestContext() when Miniflare execution semantics matter."
4660
+ },
4661
+ pipelines: {
4662
+ service: "pipelines",
4663
+ tier: "offline-native",
4664
+ reason: "Pipeline sends can be recorded locally; Cloudflare owns production batching and sinks.",
4665
+ recommendation: "Use createOfflineEnv() or createMockPipeline() to assert records sent by application code."
4666
+ },
4667
+ images: {
4668
+ service: "images",
4669
+ tier: "offline-native",
4670
+ reason: "Images has local development support and Devflare provides a low-fidelity deterministic pure mock.",
4671
+ recommendation: "Use createOfflineEnv() for chain-shape tests; use Cloudflare for hosted image APIs and transform fidelity."
4672
+ },
4673
+ media: {
4674
+ service: "media",
4675
+ tier: "offline-native",
4676
+ reason: "Media Transformations can run through Miniflare wiring locally, and Devflare provides a deterministic pure mock for app-level chain tests.",
4677
+ recommendation: "Use createTestContext() for local Worker binding tests and createMockMediaBinding() for pure tests; use Cloudflare for codec/output fidelity."
4678
+ },
4679
+ artifacts: {
4680
+ service: "artifacts",
4681
+ tier: "offline-fixture",
4682
+ reason: "Artifacts repo metadata and token flows can be modeled in memory; real Git remotes are Cloudflare-managed.",
4683
+ recommendation: "Use createMockArtifacts() for unit tests; use Cloudflare for Git protocol and namespace access."
4684
+ },
4685
+ aiSearch: {
4686
+ service: "aiSearch",
4687
+ tier: "offline-fixture",
4688
+ reason: "AI Search application flows can use deterministic in-memory instances and namespaces, but indexing/ranking/crawling are hosted Cloudflare behavior.",
4689
+ recommendation: "Use createMockAISearchInstance(), createMockAISearchNamespace(), or createOfflineEnv(); use remote tests for real relevance behavior."
4690
+ },
4691
+ aiSearchNamespaces: {
4692
+ service: "aiSearchNamespaces",
4693
+ tier: "offline-fixture",
4694
+ reason: "AI Search namespace management can be backed by an explicit in-memory instance registry for tests.",
4695
+ recommendation: "Use fixtures.aiSearchNamespaces to model tenant instances and multi-instance searches."
4696
+ },
4697
+ containers: {
4698
+ service: "containers",
4699
+ tier: "offline-native",
4700
+ reason: "Devflare can launch local Docker/Podman containers in explicit tests when an engine and cached images are available.",
4701
+ recommendation: "Use devflare/test containers helpers and shouldSkip.containers; keep ordinary unit tests engine-free."
4702
+ },
4703
+ browser: {
4704
+ service: "browser",
4705
+ tier: "offline-native",
4706
+ reason: "Cloudflare lists Browser Run as locally simulatable, while live view, HITL, recordings, and external CDP remain hosted features.",
4707
+ recommendation: "Use Cloudflare/Wrangler Browser local development for browser execution and remote/deployed tests for hosted-only features."
4708
+ },
4709
+ ai: {
4710
+ service: "ai",
4711
+ tier: "remote-boundary",
4712
+ reason: "Workers AI inference has no local simulation in Cloudflare local development.",
4713
+ recommendation: "Use DEVFLARE_REMOTE=1/devflare remote enable for real calls, or inject a custom fake for pure tests."
4714
+ },
4715
+ aiGateway: {
4716
+ service: "aiGateway",
4717
+ tier: "remote-boundary",
4718
+ reason: "AI Gateway routing and logs are Cloudflare account resources reached through the Workers AI binding.",
4719
+ recommendation: "Use remote-mode AI Gateway helpers for integration tests and custom fakes for offline unit tests."
4720
+ },
4721
+ vectorize: {
4722
+ service: "vectorize",
4723
+ tier: "remote-boundary",
4724
+ reason: "Cloudflare lists Vectorize with no local simulation.",
4725
+ recommendation: "Use DEVFLARE_REMOTE=1/devflare remote enable for real indexes, or inject a fake Vectorize binding for pure tests."
4726
+ },
4727
+ builds: {
4728
+ service: "builds",
4729
+ tier: "remote-boundary",
4730
+ reason: "Cloudflare Builds/Git-connected Workers are CI/CD orchestration, not a Worker runtime binding.",
4731
+ recommendation: "Run Devflare commands inside your CI; validate Cloudflare build integration with Cloudflare/Wrangler tests."
4732
+ }
4733
+ };
4734
+ function copySupport(entry) {
4735
+ return { ...entry };
4736
+ }
4737
+ function getOfflineSupportMatrix() {
4738
+ return Object.fromEntries(Object.entries(SUPPORT_MATRIX).map(([service, entry]) => [service, copySupport(entry)]));
4739
+ }
4740
+ function describeOfflineSupport(service) {
4741
+ const entry = SUPPORT_MATRIX[service];
4742
+ if (entry) {
4743
+ return copySupport(entry);
4744
+ }
2644
4745
  return {
2645
- async send(message, options) {
2646
- messages.push({ body: message, options });
2647
- },
2648
- async sendBatch(batch) {
2649
- messages.push(...batch);
2650
- },
2651
- _getMessages() {
2652
- return messages;
2653
- }
4746
+ service,
4747
+ tier: "remote-boundary",
4748
+ reason: `No offline support classification exists for "${service}".`,
4749
+ recommendation: "Treat this as a remote Cloudflare boundary until Devflare documents a local simulator or fixture."
2654
4750
  };
2655
4751
  }
2656
- function createMockEnv(options = {}) {
2657
- const env2 = {};
2658
- if (options.kv) {
2659
- for (const name of options.kv) {
2660
- env2[name] = createMockKV();
4752
+ function createMissingSecret(binding) {
4753
+ return {
4754
+ async get() {
4755
+ throw new Error(`Offline Secrets Store binding "${binding}" has no value. Pass fixtures.secretsStore.${binding} or write a local secret with devflare secrets --local.`);
2661
4756
  }
4757
+ };
4758
+ }
4759
+ function isWorkflowBinding(value) {
4760
+ return typeof value.create === "function";
4761
+ }
4762
+ function isPipelineBinding(value) {
4763
+ return typeof value?.send === "function";
4764
+ }
4765
+ function isImagesBinding(value) {
4766
+ return typeof value?.input === "function";
4767
+ }
4768
+ function isHyperdriveBinding(value) {
4769
+ return typeof value?.connectionString === "string";
4770
+ }
4771
+ function isMediaBinding(value) {
4772
+ return typeof value?.input === "function";
4773
+ }
4774
+ function isArtifactsBinding2(value) {
4775
+ return typeof value?.create === "function";
4776
+ }
4777
+ function isAISearchInstance2(value) {
4778
+ return typeof value?.search === "function";
4779
+ }
4780
+ function isAISearchNamespace(value) {
4781
+ return typeof value?.get === "function";
4782
+ }
4783
+ function addBoundary(remoteBoundaries, service, binding, reason) {
4784
+ remoteBoundaries.push({
4785
+ service,
4786
+ binding,
4787
+ reason,
4788
+ recommendation: describeOfflineSupport(service).recommendation
4789
+ });
4790
+ }
4791
+ function addStaticBindings(env3, config) {
4792
+ if (config.vars) {
4793
+ Object.assign(env3, config.vars);
2662
4794
  }
2663
- if (options.d1) {
2664
- for (const name of options.d1) {
2665
- env2[name] = createMockD1();
2666
- }
4795
+ }
4796
+ function addRateLimitBindings(env3, bindings) {
4797
+ for (const [name, binding] of Object.entries(bindings?.rateLimits ?? {})) {
4798
+ env3[name] = createMockRateLimit(binding.simple);
2667
4799
  }
2668
- if (options.r2) {
2669
- for (const name of options.r2) {
2670
- env2[name] = createMockR2();
4800
+ }
4801
+ function addVersionMetadataBinding(env3, bindings) {
4802
+ if (bindings?.versionMetadata) {
4803
+ env3[bindings.versionMetadata.binding] = createMockVersionMetadata();
4804
+ }
4805
+ }
4806
+ function getHyperdriveConnectionString(name, binding, fixture) {
4807
+ if (typeof fixture === "string") {
4808
+ return fixture;
4809
+ }
4810
+ const envValue = process.env[`CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_${name}`] ?? process.env[`WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_${name}`];
4811
+ if (envValue?.trim()) {
4812
+ return envValue;
4813
+ }
4814
+ return normalizeHyperdriveBinding(binding).localConnectionString;
4815
+ }
4816
+ function addHyperdriveBindings(env3, bindings, fixtures, missingFixtures) {
4817
+ for (const [name, binding] of Object.entries(bindings?.hyperdrive ?? {})) {
4818
+ const fixture = fixtures.hyperdrive?.[name];
4819
+ if (isHyperdriveBinding(fixture)) {
4820
+ env3[name] = fixture;
4821
+ continue;
4822
+ }
4823
+ const connectionString = getHyperdriveConnectionString(name, binding, fixture);
4824
+ if (connectionString) {
4825
+ env3[name] = createMockHyperdrive(connectionString);
4826
+ continue;
2671
4827
  }
4828
+ missingFixtures.push({
4829
+ service: "hyperdrive",
4830
+ binding: name,
4831
+ reason: `Hyperdrive binding "${name}" has no local connection string. Configure bindings.hyperdrive.${name}.localConnectionString or pass fixtures.hyperdrive.${name}.`
4832
+ });
2672
4833
  }
2673
- if (options.queues) {
2674
- for (const name of options.queues) {
2675
- env2[name] = createMockQueue();
4834
+ }
4835
+ function addWorkerLoaderBindings(env3, bindings, fixtures) {
4836
+ for (const name of Object.keys(bindings?.workerLoaders ?? {})) {
4837
+ env3[name] = createMockWorkerLoader(fixtures.workerLoaders?.[name]);
4838
+ }
4839
+ }
4840
+ function addMTLSCertificateBindings(env3, bindings, fixtures) {
4841
+ for (const name of Object.keys(bindings?.mtlsCertificates ?? {})) {
4842
+ env3[name] = createMockMTLSCertificate(fixtures.mtlsCertificates?.[name]);
4843
+ }
4844
+ }
4845
+ function addDispatchNamespaceBindings(env3, bindings, fixtures) {
4846
+ for (const name of Object.keys(bindings?.dispatchNamespaces ?? {})) {
4847
+ env3[name] = createMockDispatchNamespace(fixtures.dispatchNamespaces?.[name]);
4848
+ }
4849
+ }
4850
+ function addWorkflowBindings(env3, bindings, fixtures) {
4851
+ for (const name of Object.keys(bindings?.workflows ?? {})) {
4852
+ const fixture = fixtures.workflows?.[name];
4853
+ env3[name] = fixture && isWorkflowBinding(fixture) ? fixture : createMockWorkflow(fixture);
4854
+ }
4855
+ }
4856
+ function addPipelineBindings(env3, bindings, fixtures) {
4857
+ for (const name of Object.keys(bindings?.pipelines ?? {})) {
4858
+ const fixture = fixtures.pipelines?.[name];
4859
+ env3[name] = isPipelineBinding(fixture) ? fixture : createMockPipeline();
4860
+ }
4861
+ }
4862
+ function addImagesBindings(env3, bindings, fixtures) {
4863
+ for (const name of Object.keys(bindings?.images ?? {})) {
4864
+ const fixture = fixtures.images?.[name];
4865
+ env3[name] = isImagesBinding(fixture) ? fixture : createMockImagesBinding(fixture);
4866
+ }
4867
+ }
4868
+ function addMediaBindings(env3, bindings, fixtures) {
4869
+ for (const name of Object.keys(bindings?.media ?? {})) {
4870
+ const fixture = fixtures.media?.[name];
4871
+ env3[name] = isMediaBinding(fixture) ? fixture : createMockMediaBinding(fixture);
4872
+ }
4873
+ }
4874
+ function addArtifactsBindings(env3, bindings, fixtures) {
4875
+ for (const name of Object.keys(bindings?.artifacts ?? {})) {
4876
+ const fixture = fixtures.artifacts?.[name];
4877
+ env3[name] = isArtifactsBinding2(fixture) ? fixture : createMockArtifacts(fixture);
4878
+ }
4879
+ }
4880
+ function addSecretsStoreBindings(env3, bindings, fixtures, localSecretValues, missingFixtures) {
4881
+ for (const name of Object.keys(bindings?.secretsStore ?? {})) {
4882
+ const value = fixtures.secretsStore?.[name] ?? localSecretValues[name];
4883
+ if (value === undefined) {
4884
+ missingFixtures.push({
4885
+ service: "secretsStore",
4886
+ binding: name,
4887
+ reason: `Secrets Store values are not present in fixtures or the local secret store; pass fixtures.secretsStore.${name} or run devflare secrets --local.`
4888
+ });
4889
+ env3[name] = createMissingSecret(name);
4890
+ } else {
4891
+ env3[name] = createMockSecretsStoreSecret(value);
2676
4892
  }
2677
4893
  }
2678
- if (options.vars) {
2679
- Object.assign(env2, options.vars);
4894
+ }
4895
+ function addAISearchBindings(env3, bindings, fixtures) {
4896
+ for (const [name, binding] of Object.entries(bindings?.aiSearch ?? {})) {
4897
+ const fixture = fixtures.aiSearch?.[name];
4898
+ env3[name] = isAISearchInstance2(fixture) ? fixture : createMockAISearchInstance({
4899
+ id: binding.instanceName,
4900
+ ...fixture
4901
+ });
2680
4902
  }
2681
- if (options.secrets) {
2682
- Object.assign(env2, options.secrets);
4903
+ }
4904
+ function addAISearchNamespaceBindings(env3, bindings, fixtures) {
4905
+ for (const [name, binding] of Object.entries(bindings?.aiSearchNamespaces ?? {})) {
4906
+ const fixture = fixtures.aiSearchNamespaces?.[name];
4907
+ env3[name] = isAISearchNamespace(fixture) ? fixture : createMockAISearchNamespace({
4908
+ namespace: binding.namespace,
4909
+ ...fixture
4910
+ });
2683
4911
  }
2684
- if (options.custom) {
2685
- Object.assign(env2, options.custom);
4912
+ }
4913
+ function addRemoteBoundaries(remoteBoundaries, bindings) {
4914
+ if (bindings?.ai) {
4915
+ addBoundary(remoteBoundaries, "ai", bindings.ai.binding || "AI", "Workers AI inference is not available in offline local simulations.");
2686
4916
  }
2687
- return env2;
4917
+ for (const name of Object.keys(bindings?.vectorize ?? {})) {
4918
+ addBoundary(remoteBoundaries, "vectorize", name, "Vectorize has no offline local simulation in Cloudflare local development.");
4919
+ }
4920
+ }
4921
+ function createOfflineBindings(config, fixtures = {}, options = {}) {
4922
+ const env3 = {};
4923
+ const remoteBoundaries = [];
4924
+ const missingFixtures = [];
4925
+ const bindings = config.bindings;
4926
+ const localSecretValues = options.cwd && options.useLocalSecrets !== false ? resolveLocalSecretValuesForBindings(config, options.cwd) : {};
4927
+ addStaticBindings(env3, config);
4928
+ addRateLimitBindings(env3, bindings);
4929
+ addVersionMetadataBinding(env3, bindings);
4930
+ addHyperdriveBindings(env3, bindings, fixtures, missingFixtures);
4931
+ addWorkerLoaderBindings(env3, bindings, fixtures);
4932
+ addMTLSCertificateBindings(env3, bindings, fixtures);
4933
+ addDispatchNamespaceBindings(env3, bindings, fixtures);
4934
+ addWorkflowBindings(env3, bindings, fixtures);
4935
+ addPipelineBindings(env3, bindings, fixtures);
4936
+ addImagesBindings(env3, bindings, fixtures);
4937
+ addMediaBindings(env3, bindings, fixtures);
4938
+ addArtifactsBindings(env3, bindings, fixtures);
4939
+ addSecretsStoreBindings(env3, bindings, fixtures, localSecretValues, missingFixtures);
4940
+ addAISearchBindings(env3, bindings, fixtures);
4941
+ addAISearchNamespaceBindings(env3, bindings, fixtures);
4942
+ addRemoteBoundaries(remoteBoundaries, bindings);
4943
+ if (fixtures.custom) {
4944
+ Object.assign(env3, fixtures.custom);
4945
+ }
4946
+ return {
4947
+ env: env3,
4948
+ support: getOfflineSupportMatrix(),
4949
+ remoteBoundaries,
4950
+ missingFixtures
4951
+ };
4952
+ }
4953
+ function createOfflineEnv(config, fixtures = {}, options = {}) {
4954
+ return createOfflineBindings(config, fixtures, options).env;
2688
4955
  }
2689
4956
  export {
2690
4957
  worker,
2691
4958
  withTestContext,
2692
4959
  tail,
4960
+ stopActiveContainers,
2693
4961
  shouldSkip,
2694
4962
  scheduled,
2695
4963
  resolveServiceBindings,
2696
4964
  resolveDOBindings,
2697
- queue,
4965
+ queue2 as queue,
2698
4966
  hasServiceBindings,
2699
4967
  hasCrossWorkerDOs,
4968
+ getOfflineSupportMatrix,
4969
+ getContainerSkipReason,
2700
4970
  env,
2701
4971
  email,
4972
+ detectContainerEngine,
4973
+ describeOfflineSupport,
2702
4974
  createTestContext,
4975
+ createOfflineEnv,
4976
+ createOfflineBindings,
4977
+ createMockWorkflow,
4978
+ createMockWorkerLoader,
4979
+ createMockVersionMetadata,
2703
4980
  createMockTestContext,
4981
+ createMockSecretsStoreSecret,
4982
+ createMockRateLimit,
2704
4983
  createMockR2,
2705
4984
  createMockQueue,
4985
+ createMockPipeline,
4986
+ createMockMediaBinding,
4987
+ createMockMTLSCertificate,
2706
4988
  createMockKV,
4989
+ createMockImagesBinding,
4990
+ createMockHyperdrive,
2707
4991
  createMockEnv,
4992
+ createMockDispatchNamespace,
2708
4993
  createMockD1,
4994
+ createMockArtifacts,
4995
+ createMockAISearchNamespace,
4996
+ createMockAISearchInstance,
4997
+ createContainerManager,
4998
+ containers,
2709
4999
  clearBundleCache,
2710
5000
  cf
2711
5001
  };