@lovrozagar/honey 0.6.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (674) hide show
  1. package/README.md +514 -192
  2. package/bin/honey.js +25 -0
  3. package/dist/accepts.d.ts +7 -1
  4. package/dist/accepts.d.ts.map +1 -1
  5. package/dist/accepts.js +126 -0
  6. package/dist/accepts.js.map +1 -0
  7. package/dist/body-kind.d.ts +26 -0
  8. package/dist/body-kind.d.ts.map +1 -0
  9. package/dist/body-kind.js +70 -0
  10. package/dist/body-kind.js.map +1 -0
  11. package/dist/body-limit.d.ts +5 -1
  12. package/dist/body-limit.d.ts.map +1 -1
  13. package/dist/body-limit.js +126 -0
  14. package/dist/body-limit.js.map +1 -0
  15. package/dist/build/index.d.ts +19 -3
  16. package/dist/build/index.d.ts.map +1 -1
  17. package/dist/build/index.js +173 -0
  18. package/dist/build/index.js.map +1 -0
  19. package/dist/cli-args.d.ts +33 -0
  20. package/dist/cli-args.d.ts.map +1 -0
  21. package/dist/cli-args.js +102 -0
  22. package/dist/cli-args.js.map +1 -0
  23. package/dist/cli-go/auth.go +26 -0
  24. package/dist/cli-go/config.go +192 -0
  25. package/dist/cli-go/errors.go +75 -0
  26. package/dist/cli-go/multipart.go +79 -0
  27. package/dist/cli-go/output.go +163 -0
  28. package/dist/cli-go/root.go +30 -0
  29. package/dist/cli-go/stream.go +88 -0
  30. package/dist/cli-go/version.go +5 -0
  31. package/dist/cli.d.ts +1 -1
  32. package/dist/cli.js +209 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/client/defaults.js +48 -0
  35. package/dist/client/defaults.js.map +1 -0
  36. package/dist/client/error.d.ts +35 -0
  37. package/dist/client/error.d.ts.map +1 -1
  38. package/dist/client/error.js +71 -0
  39. package/dist/client/error.js.map +1 -0
  40. package/dist/client/http.d.ts +67 -8
  41. package/dist/client/http.d.ts.map +1 -1
  42. package/dist/client/http.js +571 -0
  43. package/dist/client/http.js.map +1 -0
  44. package/dist/client/index.d.ts +3 -2
  45. package/dist/client/index.d.ts.map +1 -1
  46. package/dist/client/index.js +95 -0
  47. package/dist/client/index.js.map +1 -0
  48. package/dist/client/path.d.ts +26 -0
  49. package/dist/client/path.d.ts.map +1 -0
  50. package/dist/client/path.js +163 -0
  51. package/dist/client/path.js.map +1 -0
  52. package/dist/client/redirect-policy.d.ts +12 -0
  53. package/dist/client/redirect-policy.d.ts.map +1 -0
  54. package/dist/client/redirect-policy.js +20 -0
  55. package/dist/client/redirect-policy.js.map +1 -0
  56. package/dist/client/sdk.d.ts +36 -0
  57. package/dist/client/sdk.d.ts.map +1 -1
  58. package/dist/client/sdk.js +292 -0
  59. package/dist/client/sdk.js.map +1 -0
  60. package/dist/client/sse.d.ts +13 -0
  61. package/dist/client/sse.d.ts.map +1 -1
  62. package/dist/client/sse.js +129 -0
  63. package/dist/client/sse.js.map +1 -0
  64. package/dist/client/types.js +2 -0
  65. package/dist/client/types.js.map +1 -0
  66. package/dist/client/ws.d.ts +9 -2
  67. package/dist/client/ws.d.ts.map +1 -1
  68. package/dist/client/ws.js +112 -0
  69. package/dist/client/ws.js.map +1 -0
  70. package/dist/client-go/errors.go +236 -0
  71. package/dist/client-go/export.go +44 -0
  72. package/dist/client-go/invalidation.go +371 -0
  73. package/dist/client-go/nullable.go +62 -0
  74. package/dist/client-go/realtime.go +354 -0
  75. package/dist/client-go/result.go +20 -0
  76. package/dist/client-go/runtime.go +975 -0
  77. package/dist/client-go/sse.go +159 -0
  78. package/dist/client-go/transport.go +495 -0
  79. package/dist/client-go/ws.go +128 -0
  80. package/dist/client-info.d.ts +36 -0
  81. package/dist/client-info.d.ts.map +1 -0
  82. package/dist/client-info.js +53 -0
  83. package/dist/client-info.js.map +1 -0
  84. package/dist/client-mcp/README.md +10 -0
  85. package/dist/client-mcp/server.ts +221 -0
  86. package/dist/client-mcp/types.ts +19 -0
  87. package/dist/client-python/_errors.py +115 -0
  88. package/dist/client-python/_invalidation.py +494 -0
  89. package/dist/client-python/_realtime.py +179 -0
  90. package/dist/client-python/_runtime.py +860 -0
  91. package/dist/client-python/_sse.py +155 -0
  92. package/dist/client-python/_transport.py +366 -0
  93. package/dist/client-python/_ws.py +159 -0
  94. package/dist/client-python/py.typed +0 -0
  95. package/dist/client-rust/errors.rs +220 -0
  96. package/dist/client-rust/invalidation.rs +412 -0
  97. package/dist/client-rust/invalidation_sync.rs +93 -0
  98. package/dist/client-rust/nullable.rs +17 -0
  99. package/dist/client-rust/realtime.rs +702 -0
  100. package/dist/client-rust/result.rs +22 -0
  101. package/dist/client-rust/runtime.rs +980 -0
  102. package/dist/client-rust/runtime_sync.rs +484 -0
  103. package/dist/client-rust/sse.rs +188 -0
  104. package/dist/client-rust/ws.rs +167 -0
  105. package/dist/codegen-go-cli.d.ts.map +1 -1
  106. package/dist/codegen-go-cli.js +964 -0
  107. package/dist/codegen-go-cli.js.map +1 -0
  108. package/dist/codegen-go.d.ts +15 -3
  109. package/dist/codegen-go.d.ts.map +1 -1
  110. package/dist/codegen-go.js +872 -0
  111. package/dist/codegen-go.js.map +1 -0
  112. package/dist/codegen-hash.js +9 -0
  113. package/dist/codegen-hash.js.map +1 -0
  114. package/dist/codegen-ir.d.ts +69 -2
  115. package/dist/codegen-ir.d.ts.map +1 -1
  116. package/dist/codegen-ir.js +661 -0
  117. package/dist/codegen-ir.js.map +1 -0
  118. package/dist/codegen-lang.d.ts +65 -0
  119. package/dist/codegen-lang.d.ts.map +1 -0
  120. package/dist/codegen-lang.js +526 -0
  121. package/dist/codegen-lang.js.map +1 -0
  122. package/dist/codegen-load.d.ts +31 -0
  123. package/dist/codegen-load.d.ts.map +1 -0
  124. package/dist/codegen-load.js +138 -0
  125. package/dist/codegen-load.js.map +1 -0
  126. package/dist/codegen-loaders.js +55 -0
  127. package/dist/codegen-loaders.js.map +1 -0
  128. package/dist/codegen-mcp.d.ts.map +1 -1
  129. package/dist/codegen-mcp.js +368 -0
  130. package/dist/codegen-mcp.js.map +1 -0
  131. package/dist/codegen-python.d.ts +13 -1
  132. package/dist/codegen-python.d.ts.map +1 -1
  133. package/dist/codegen-python.js +828 -0
  134. package/dist/codegen-python.js.map +1 -0
  135. package/dist/codegen-route-tree-intern.d.ts +15 -5
  136. package/dist/codegen-route-tree-intern.d.ts.map +1 -1
  137. package/dist/codegen-route-tree-intern.js +221 -0
  138. package/dist/codegen-route-tree-intern.js.map +1 -0
  139. package/dist/codegen-rust.d.ts.map +1 -1
  140. package/dist/codegen-rust.js +1293 -0
  141. package/dist/codegen-rust.js.map +1 -0
  142. package/dist/codegen-sanitize.d.ts.map +1 -1
  143. package/dist/codegen-sanitize.js +56 -0
  144. package/dist/codegen-sanitize.js.map +1 -0
  145. package/dist/codegen-schema-naming.d.ts +5 -0
  146. package/dist/codegen-schema-naming.d.ts.map +1 -1
  147. package/dist/codegen-schema-naming.js +150 -0
  148. package/dist/codegen-schema-naming.js.map +1 -0
  149. package/dist/codegen-sdk-model.d.ts +71 -0
  150. package/dist/codegen-sdk-model.d.ts.map +1 -0
  151. package/dist/codegen-sdk-model.js +179 -0
  152. package/dist/codegen-sdk-model.js.map +1 -0
  153. package/dist/codegen.d.ts +41 -23
  154. package/dist/codegen.d.ts.map +1 -1
  155. package/dist/codegen.js +4610 -0
  156. package/dist/codegen.js.map +1 -0
  157. package/dist/context.d.ts +36 -6
  158. package/dist/context.d.ts.map +1 -1
  159. package/dist/context.js +242 -0
  160. package/dist/context.js.map +1 -0
  161. package/dist/cookie-sign.d.ts +16 -2
  162. package/dist/cookie-sign.d.ts.map +1 -1
  163. package/dist/cookie-sign.js +104 -0
  164. package/dist/cookie-sign.js.map +1 -0
  165. package/dist/cookie.d.ts +12 -0
  166. package/dist/cookie.d.ts.map +1 -1
  167. package/dist/cookie.js +138 -0
  168. package/dist/cookie.js.map +1 -0
  169. package/dist/cors.d.ts +1 -0
  170. package/dist/cors.d.ts.map +1 -1
  171. package/dist/cors.js +87 -0
  172. package/dist/cors.js.map +1 -0
  173. package/dist/crypto.d.ts +6 -1
  174. package/dist/crypto.d.ts.map +1 -1
  175. package/dist/crypto.js +38 -0
  176. package/dist/crypto.js.map +1 -0
  177. package/dist/csrf.d.ts +19 -0
  178. package/dist/csrf.d.ts.map +1 -1
  179. package/dist/csrf.js +79 -0
  180. package/dist/csrf.js.map +1 -0
  181. package/dist/curl-logger.d.ts +14 -3
  182. package/dist/curl-logger.d.ts.map +1 -1
  183. package/dist/curl-logger.js +199 -0
  184. package/dist/curl-logger.js.map +1 -0
  185. package/dist/detect-runtime.js +13 -0
  186. package/dist/detect-runtime.js.map +1 -0
  187. package/dist/dict.d.ts +11 -0
  188. package/dist/dict.d.ts.map +1 -0
  189. package/dist/dict.js +13 -0
  190. package/dist/dict.js.map +1 -0
  191. package/dist/error.d.ts.map +1 -1
  192. package/dist/error.js +44 -0
  193. package/dist/error.js.map +1 -0
  194. package/dist/errors.js +39 -0
  195. package/dist/errors.js.map +1 -0
  196. package/dist/etag.d.ts +2 -0
  197. package/dist/etag.d.ts.map +1 -1
  198. package/dist/etag.js +82 -0
  199. package/dist/etag.js.map +1 -0
  200. package/dist/feature-detect.d.ts +25 -0
  201. package/dist/feature-detect.d.ts.map +1 -0
  202. package/dist/feature-detect.js +144 -0
  203. package/dist/feature-detect.js.map +1 -0
  204. package/dist/feature-load.js +36 -0
  205. package/dist/feature-load.js.map +1 -0
  206. package/dist/feature-slots.d.ts +44 -0
  207. package/dist/feature-slots.d.ts.map +1 -0
  208. package/dist/feature-slots.js +48 -0
  209. package/dist/feature-slots.js.map +1 -0
  210. package/dist/fetch-request.d.ts +10 -0
  211. package/dist/fetch-request.d.ts.map +1 -0
  212. package/dist/fetch-request.js +13 -0
  213. package/dist/fetch-request.js.map +1 -0
  214. package/dist/gen-process.d.ts +15 -0
  215. package/dist/gen-process.d.ts.map +1 -0
  216. package/dist/gen-process.js +43 -0
  217. package/dist/gen-process.js.map +1 -0
  218. package/dist/gen-write.d.ts +19 -0
  219. package/dist/gen-write.d.ts.map +1 -0
  220. package/dist/gen-write.js +120 -0
  221. package/dist/gen-write.js.map +1 -0
  222. package/dist/glob.d.ts +21 -0
  223. package/dist/glob.d.ts.map +1 -0
  224. package/dist/glob.js +115 -0
  225. package/dist/glob.js.map +1 -0
  226. package/dist/go-type-emitter.d.ts +18 -4
  227. package/dist/go-type-emitter.d.ts.map +1 -1
  228. package/dist/go-type-emitter.js +594 -0
  229. package/dist/go-type-emitter.js.map +1 -0
  230. package/dist/honey-response.d.ts.map +1 -1
  231. package/dist/honey-response.js +338 -0
  232. package/dist/honey-response.js.map +1 -0
  233. package/dist/i18n-slot.d.ts +8 -0
  234. package/dist/i18n-slot.d.ts.map +1 -1
  235. package/dist/i18n-slot.js +14 -0
  236. package/dist/i18n-slot.js.map +1 -0
  237. package/dist/i18n.d.ts +3 -0
  238. package/dist/i18n.d.ts.map +1 -1
  239. package/dist/i18n.js +250 -0
  240. package/dist/i18n.js.map +1 -0
  241. package/dist/index.d.ts +410 -149
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +2324 -0
  244. package/dist/index.js.map +1 -0
  245. package/dist/init.d.ts +3 -5
  246. package/dist/init.d.ts.map +1 -1
  247. package/dist/init.js +140 -0
  248. package/dist/init.js.map +1 -0
  249. package/dist/input.js +4 -0
  250. package/dist/input.js.map +1 -0
  251. package/dist/invalidate-check.d.ts +11 -0
  252. package/dist/invalidate-check.d.ts.map +1 -1
  253. package/dist/invalidate-check.js +163 -0
  254. package/dist/invalidate-check.js.map +1 -0
  255. package/dist/invoke-user.d.ts +35 -0
  256. package/dist/invoke-user.d.ts.map +1 -0
  257. package/dist/invoke-user.js +73 -0
  258. package/dist/invoke-user.js.map +1 -0
  259. package/dist/ip-restrict.d.ts +13 -1
  260. package/dist/ip-restrict.d.ts.map +1 -1
  261. package/dist/ip-restrict.js +60 -0
  262. package/dist/ip-restrict.js.map +1 -0
  263. package/dist/ip.d.ts +39 -0
  264. package/dist/ip.d.ts.map +1 -0
  265. package/dist/ip.js +209 -0
  266. package/dist/ip.js.map +1 -0
  267. package/dist/logger.d.ts.map +1 -1
  268. package/dist/logger.js +113 -0
  269. package/dist/logger.js.map +1 -0
  270. package/dist/media-type.d.ts +30 -0
  271. package/dist/media-type.d.ts.map +1 -0
  272. package/dist/media-type.js +97 -0
  273. package/dist/media-type.js.map +1 -0
  274. package/dist/meta-spec-merge.d.ts +32 -0
  275. package/dist/meta-spec-merge.d.ts.map +1 -0
  276. package/dist/meta-spec-merge.js +68 -0
  277. package/dist/meta-spec-merge.js.map +1 -0
  278. package/dist/meta-spec.d.ts +16 -3
  279. package/dist/meta-spec.d.ts.map +1 -1
  280. package/dist/meta-spec.js +535 -0
  281. package/dist/meta-spec.js.map +1 -0
  282. package/dist/middleware.d.ts +23 -6
  283. package/dist/middleware.d.ts.map +1 -1
  284. package/dist/middleware.js +253 -0
  285. package/dist/middleware.js.map +1 -0
  286. package/dist/node-request.d.ts +57 -12
  287. package/dist/node-request.d.ts.map +1 -1
  288. package/dist/node-request.js +407 -0
  289. package/dist/node-request.js.map +1 -0
  290. package/dist/node.d.ts +20 -3
  291. package/dist/node.d.ts.map +1 -1
  292. package/dist/node.js +396 -0
  293. package/dist/node.js.map +1 -0
  294. package/dist/openapi/collect.d.ts +6 -3
  295. package/dist/openapi/collect.d.ts.map +1 -1
  296. package/dist/openapi/collect.js +30 -0
  297. package/dist/openapi/collect.js.map +1 -0
  298. package/dist/openapi/docs-page.d.ts +42 -0
  299. package/dist/openapi/docs-page.d.ts.map +1 -0
  300. package/dist/openapi/docs-page.js +80 -0
  301. package/dist/openapi/docs-page.js.map +1 -0
  302. package/dist/openapi/document.d.ts +4 -0
  303. package/dist/openapi/document.d.ts.map +1 -1
  304. package/dist/openapi/document.js +785 -0
  305. package/dist/openapi/document.js.map +1 -0
  306. package/dist/openapi/json-schema-slot.d.ts +7 -0
  307. package/dist/openapi/json-schema-slot.d.ts.map +1 -1
  308. package/dist/openapi/json-schema-slot.js +38 -0
  309. package/dist/openapi/json-schema-slot.js.map +1 -0
  310. package/dist/openapi/register.d.ts.map +1 -1
  311. package/dist/openapi/register.js +20 -0
  312. package/dist/openapi/register.js.map +1 -0
  313. package/dist/openapi/scalar.d.ts +3 -1
  314. package/dist/openapi/scalar.d.ts.map +1 -1
  315. package/dist/openapi/scalar.js +21 -0
  316. package/dist/openapi/scalar.js.map +1 -0
  317. package/dist/openapi/serve.d.ts +9 -0
  318. package/dist/openapi/serve.d.ts.map +1 -0
  319. package/dist/openapi/serve.js +36 -0
  320. package/dist/openapi/serve.js.map +1 -0
  321. package/dist/openapi/spec-factory.d.ts +30 -1
  322. package/dist/openapi/spec-factory.d.ts.map +1 -1
  323. package/dist/openapi/spec-factory.js +52 -0
  324. package/dist/openapi/spec-factory.js.map +1 -0
  325. package/dist/openapi/spec.d.ts +14 -5
  326. package/dist/openapi/spec.d.ts.map +1 -1
  327. package/dist/openapi/spec.js +50 -0
  328. package/dist/openapi/spec.js.map +1 -0
  329. package/dist/openapi/swagger.d.ts +3 -1
  330. package/dist/openapi/swagger.d.ts.map +1 -1
  331. package/dist/openapi/swagger.js +22 -0
  332. package/dist/openapi/swagger.js.map +1 -0
  333. package/dist/pattern.d.ts +71 -0
  334. package/dist/pattern.d.ts.map +1 -0
  335. package/dist/pattern.js +239 -0
  336. package/dist/pattern.js.map +1 -0
  337. package/dist/peer.d.ts +17 -0
  338. package/dist/peer.d.ts.map +1 -0
  339. package/dist/peer.js +50 -0
  340. package/dist/peer.js.map +1 -0
  341. package/dist/plugin.d.ts +67 -16
  342. package/dist/plugin.d.ts.map +1 -1
  343. package/dist/plugin.js +649 -0
  344. package/dist/plugin.js.map +1 -0
  345. package/dist/powered-by.d.ts.map +1 -1
  346. package/dist/powered-by.js +11 -0
  347. package/dist/powered-by.js.map +1 -0
  348. package/dist/pretty-json.d.ts.map +1 -1
  349. package/dist/pretty-json.js +47 -0
  350. package/dist/pretty-json.js.map +1 -0
  351. package/dist/producer-stream.d.ts +47 -0
  352. package/dist/producer-stream.d.ts.map +1 -0
  353. package/dist/producer-stream.js +267 -0
  354. package/dist/producer-stream.js.map +1 -0
  355. package/dist/proxy.d.ts +47 -8
  356. package/dist/proxy.d.ts.map +1 -1
  357. package/dist/proxy.js +274 -0
  358. package/dist/proxy.js.map +1 -0
  359. package/dist/python-type-emitter.d.ts +14 -1
  360. package/dist/python-type-emitter.d.ts.map +1 -1
  361. package/dist/python-type-emitter.js +157 -0
  362. package/dist/python-type-emitter.js.map +1 -0
  363. package/dist/realtime/bus.d.ts +17 -1
  364. package/dist/realtime/bus.d.ts.map +1 -1
  365. package/dist/realtime/bus.js +112 -0
  366. package/dist/realtime/bus.js.map +1 -0
  367. package/dist/realtime/register.d.ts +3 -0
  368. package/dist/realtime/register.d.ts.map +1 -0
  369. package/dist/realtime/register.js +10 -0
  370. package/dist/realtime/register.js.map +1 -0
  371. package/dist/realtime/route.d.ts +65 -26
  372. package/dist/realtime/route.d.ts.map +1 -1
  373. package/dist/realtime/route.js +72 -0
  374. package/dist/realtime/route.js.map +1 -0
  375. package/dist/realtime/server.d.ts +43 -0
  376. package/dist/realtime/server.d.ts.map +1 -0
  377. package/dist/realtime/server.js +255 -0
  378. package/dist/realtime/server.js.map +1 -0
  379. package/dist/request-id.d.ts +7 -0
  380. package/dist/request-id.d.ts.map +1 -1
  381. package/dist/request-id.js +16 -0
  382. package/dist/request-id.js.map +1 -0
  383. package/dist/request-limits.d.ts +3 -0
  384. package/dist/request-limits.d.ts.map +1 -0
  385. package/dist/request-limits.js +3 -0
  386. package/dist/request-limits.js.map +1 -0
  387. package/dist/request-path.d.ts +33 -0
  388. package/dist/request-path.d.ts.map +1 -0
  389. package/dist/request-path.js +178 -0
  390. package/dist/request-path.js.map +1 -0
  391. package/dist/request-to-curl.d.ts +7 -1
  392. package/dist/request-to-curl.d.ts.map +1 -1
  393. package/dist/request-to-curl.js +48 -0
  394. package/dist/request-to-curl.js.map +1 -0
  395. package/dist/response.d.ts +25 -2
  396. package/dist/response.d.ts.map +1 -1
  397. package/dist/response.js +313 -0
  398. package/dist/response.js.map +1 -0
  399. package/dist/rust-type-emitter.d.ts +14 -2
  400. package/dist/rust-type-emitter.d.ts.map +1 -1
  401. package/dist/rust-type-emitter.js +463 -0
  402. package/dist/rust-type-emitter.js.map +1 -0
  403. package/dist/secure-headers.d.ts.map +1 -1
  404. package/dist/secure-headers.js +53 -0
  405. package/dist/secure-headers.js.map +1 -0
  406. package/dist/serve-register.d.ts +4 -0
  407. package/dist/serve-register.d.ts.map +1 -1
  408. package/dist/serve-register.js +9 -0
  409. package/dist/serve-register.js.map +1 -0
  410. package/dist/serve-slot.d.ts +3 -0
  411. package/dist/serve-slot.d.ts.map +1 -1
  412. package/dist/serve-slot.js +52 -0
  413. package/dist/serve-slot.js.map +1 -0
  414. package/dist/serve.d.ts +18 -1
  415. package/dist/serve.d.ts.map +1 -1
  416. package/dist/serve.js +157 -0
  417. package/dist/serve.js.map +1 -0
  418. package/dist/server-timing.d.ts.map +1 -1
  419. package/dist/server-timing.js +51 -0
  420. package/dist/server-timing.js.map +1 -0
  421. package/dist/static.d.ts +9 -0
  422. package/dist/static.d.ts.map +1 -1
  423. package/dist/static.js +102 -0
  424. package/dist/static.js.map +1 -0
  425. package/dist/telemetry/otel.d.ts +2 -0
  426. package/dist/telemetry/otel.d.ts.map +1 -1
  427. package/dist/telemetry/otel.js +135 -0
  428. package/dist/telemetry/otel.js.map +1 -0
  429. package/dist/testing.d.ts +12 -0
  430. package/dist/testing.d.ts.map +1 -1
  431. package/dist/testing.js +124 -0
  432. package/dist/testing.js.map +1 -0
  433. package/dist/timeout.d.ts +6 -0
  434. package/dist/timeout.d.ts.map +1 -1
  435. package/dist/timeout.js +36 -0
  436. package/dist/timeout.js.map +1 -0
  437. package/dist/tree.d.ts +133 -36
  438. package/dist/tree.d.ts.map +1 -1
  439. package/dist/tree.js +437 -0
  440. package/dist/tree.js.map +1 -0
  441. package/dist/trust.d.ts +21 -0
  442. package/dist/trust.d.ts.map +1 -0
  443. package/dist/trust.js +100 -0
  444. package/dist/trust.js.map +1 -0
  445. package/dist/ts-type-emitter.d.ts.map +1 -1
  446. package/dist/ts-type-emitter.js +150 -0
  447. package/dist/ts-type-emitter.js.map +1 -0
  448. package/dist/type-emitter.d.ts +16 -2
  449. package/dist/type-emitter.d.ts.map +1 -1
  450. package/dist/type-emitter.js +729 -0
  451. package/dist/type-emitter.js.map +1 -0
  452. package/dist/type-extractor.d.ts +8 -4
  453. package/dist/type-extractor.d.ts.map +1 -1
  454. package/dist/type-extractor.js +776 -0
  455. package/dist/type-extractor.js.map +1 -0
  456. package/dist/types.d.ts +7 -0
  457. package/dist/types.d.ts.map +1 -1
  458. package/dist/types.js +96 -0
  459. package/dist/types.js.map +1 -0
  460. package/dist/validation.d.ts +14 -2
  461. package/dist/validation.d.ts.map +1 -1
  462. package/dist/validation.js +335 -0
  463. package/dist/validation.js.map +1 -0
  464. package/dist/with-headers.d.ts +15 -0
  465. package/dist/with-headers.d.ts.map +1 -0
  466. package/dist/with-headers.js +45 -0
  467. package/dist/with-headers.js.map +1 -0
  468. package/dist/ws/bun.d.ts +11 -5
  469. package/dist/ws/bun.d.ts.map +1 -1
  470. package/dist/ws/bun.js +82 -0
  471. package/dist/ws/bun.js.map +1 -0
  472. package/dist/ws/cloudflare.d.ts +25 -3
  473. package/dist/ws/cloudflare.d.ts.map +1 -1
  474. package/dist/ws/cloudflare.js +117 -0
  475. package/dist/ws/cloudflare.js.map +1 -0
  476. package/dist/ws/deno.d.ts +4 -4
  477. package/dist/ws/deno.d.ts.map +1 -1
  478. package/dist/ws/deno.js +133 -0
  479. package/dist/ws/deno.js.map +1 -0
  480. package/dist/ws/node.d.ts +20 -3
  481. package/dist/ws/node.d.ts.map +1 -1
  482. package/dist/ws/node.js +173 -0
  483. package/dist/ws/node.js.map +1 -0
  484. package/dist/ws/session.d.ts +11 -0
  485. package/dist/ws/session.d.ts.map +1 -0
  486. package/dist/ws/session.js +60 -0
  487. package/dist/ws/session.js.map +1 -0
  488. package/dist/ws/shared.d.ts +65 -0
  489. package/dist/ws/shared.d.ts.map +1 -0
  490. package/dist/ws/shared.js +103 -0
  491. package/dist/ws/shared.js.map +1 -0
  492. package/dist/ws-origin.d.ts +20 -0
  493. package/dist/ws-origin.d.ts.map +1 -0
  494. package/dist/ws-origin.js +41 -0
  495. package/dist/ws-origin.js.map +1 -0
  496. package/dist/yaml.d.ts +15 -1
  497. package/dist/yaml.d.ts.map +1 -1
  498. package/dist/yaml.js +101 -0
  499. package/dist/yaml.js.map +1 -0
  500. package/docs/invalidate-spec.md +13 -1
  501. package/docs/meta-spec.md +36 -12
  502. package/docs/packaging.md +43 -24
  503. package/docs/regression-matrix/README.md +32 -0
  504. package/docs/regression-matrix/review.md +44 -0
  505. package/docs/regression-matrix/ws1-3.md +97 -0
  506. package/docs/regression-matrix/ws4-8.md +107 -0
  507. package/docs/regression-matrix/ws9-12.md +266 -0
  508. package/docs/sdk.md +102 -52
  509. package/package.json +183 -52
  510. package/src/accepts.ts +91 -33
  511. package/src/body-kind.ts +67 -0
  512. package/src/body-limit.ts +102 -87
  513. package/src/build/index.ts +104 -54
  514. package/src/cli-args.ts +123 -0
  515. package/src/cli-go/auth.go +12 -1
  516. package/src/cli-go/config.go +29 -2
  517. package/src/cli-go/errors.go +28 -10
  518. package/src/cli-go/multipart.go +51 -27
  519. package/src/cli-go/output.go +58 -4
  520. package/src/cli-go/root.go +1 -1
  521. package/src/cli-go/version.go +2 -2
  522. package/src/cli.ts +154 -148
  523. package/src/client/error.ts +56 -1
  524. package/src/client/http.ts +479 -256
  525. package/src/client/index.ts +55 -17
  526. package/src/client/path.ts +169 -0
  527. package/src/client/redirect-policy.ts +19 -0
  528. package/src/client/sdk.ts +180 -101
  529. package/src/client/sse.ts +92 -66
  530. package/src/client/ws.ts +67 -31
  531. package/src/client-go/errors.go +41 -12
  532. package/src/client-go/export.go +44 -0
  533. package/src/client-go/invalidation.go +52 -54
  534. package/src/client-go/nullable.go +62 -0
  535. package/src/client-go/realtime.go +147 -110
  536. package/src/client-go/runtime.go +646 -88
  537. package/src/client-go/sse.go +104 -94
  538. package/src/client-go/transport.go +67 -27
  539. package/src/client-go/ws.go +49 -25
  540. package/src/client-info.ts +79 -0
  541. package/src/client-mcp/server.ts +133 -3
  542. package/src/client-python/_invalidation.py +207 -291
  543. package/src/client-python/_realtime.py +63 -40
  544. package/src/client-python/_runtime.py +501 -119
  545. package/src/client-python/_sse.py +130 -74
  546. package/src/client-python/_transport.py +37 -9
  547. package/src/client-python/_ws.py +50 -22
  548. package/src/client-rust/errors.rs +21 -1
  549. package/src/client-rust/invalidation.rs +202 -231
  550. package/src/client-rust/invalidation_sync.rs +16 -229
  551. package/src/client-rust/nullable.rs +17 -0
  552. package/src/client-rust/realtime.rs +217 -217
  553. package/src/client-rust/runtime.rs +602 -177
  554. package/src/client-rust/runtime_sync.rs +169 -145
  555. package/src/client-rust/sse.rs +142 -121
  556. package/src/client-rust/ws.rs +61 -37
  557. package/src/codegen-go-cli.ts +607 -728
  558. package/src/codegen-go.ts +710 -770
  559. package/src/codegen-ir.ts +381 -75
  560. package/src/codegen-lang.ts +517 -0
  561. package/src/codegen-load.ts +150 -0
  562. package/src/codegen-mcp.ts +207 -108
  563. package/src/codegen-python.ts +684 -816
  564. package/src/codegen-route-tree-intern.ts +51 -25
  565. package/src/codegen-rust.ts +946 -1240
  566. package/src/codegen-sanitize.ts +0 -4
  567. package/src/codegen-schema-naming.ts +19 -6
  568. package/src/codegen-sdk-model.ts +228 -0
  569. package/src/codegen.ts +1488 -1140
  570. package/src/context.ts +130 -11
  571. package/src/cookie-sign.ts +85 -16
  572. package/src/cookie.ts +66 -11
  573. package/src/cors.ts +55 -54
  574. package/src/crypto.ts +27 -5
  575. package/src/csrf.ts +57 -25
  576. package/src/curl-logger.ts +155 -114
  577. package/src/dict.ts +12 -0
  578. package/src/error.ts +4 -2
  579. package/src/etag.ts +58 -34
  580. package/src/feature-detect.ts +147 -0
  581. package/src/feature-slots.ts +70 -0
  582. package/src/fetch-request.ts +13 -0
  583. package/src/gen-process.ts +42 -0
  584. package/src/gen-write.ts +127 -0
  585. package/src/glob.ts +109 -0
  586. package/src/go-type-emitter.ts +406 -155
  587. package/src/honey-response.ts +36 -9
  588. package/src/i18n-slot.ts +10 -0
  589. package/src/i18n.ts +98 -23
  590. package/src/index.ts +1893 -1761
  591. package/src/init.ts +40 -28
  592. package/src/invalidate-check.ts +42 -4
  593. package/src/invoke-user.ts +95 -0
  594. package/src/ip-restrict.ts +47 -161
  595. package/src/ip.ts +194 -0
  596. package/src/logger.ts +65 -36
  597. package/src/media-type.ts +104 -0
  598. package/src/meta-spec-merge.ts +80 -0
  599. package/src/meta-spec.ts +0 -0
  600. package/src/middleware.ts +112 -12
  601. package/src/node-request.ts +242 -52
  602. package/src/node.ts +318 -75
  603. package/src/openapi/collect.ts +19 -44
  604. package/src/openapi/docs-page.ts +89 -0
  605. package/src/openapi/document.ts +503 -224
  606. package/src/openapi/json-schema-slot.ts +40 -0
  607. package/src/openapi/register.ts +7 -2
  608. package/src/openapi/scalar.ts +11 -10
  609. package/src/openapi/serve.ts +39 -0
  610. package/src/openapi/spec-factory.ts +50 -1
  611. package/src/openapi/spec.ts +53 -30
  612. package/src/openapi/swagger.ts +12 -11
  613. package/src/pattern.ts +245 -0
  614. package/src/peer.ts +53 -0
  615. package/src/plugin.ts +270 -185
  616. package/src/powered-by.ts +2 -2
  617. package/src/pretty-json.ts +25 -9
  618. package/src/producer-stream.ts +296 -0
  619. package/src/proxy.ts +271 -55
  620. package/src/python-type-emitter.ts +68 -56
  621. package/src/realtime/bus.ts +56 -9
  622. package/src/realtime/register.ts +13 -0
  623. package/src/realtime/route.ts +129 -80
  624. package/src/realtime/server.ts +298 -0
  625. package/src/request-id.ts +13 -3
  626. package/src/request-limits.ts +2 -0
  627. package/src/request-path.ts +165 -0
  628. package/src/request-to-curl.ts +28 -7
  629. package/src/response.ts +143 -103
  630. package/src/rust-type-emitter.ts +265 -170
  631. package/src/secure-headers.ts +8 -4
  632. package/src/serve-register.ts +4 -0
  633. package/src/serve-slot.ts +38 -0
  634. package/src/serve.ts +113 -25
  635. package/src/server-timing.ts +11 -7
  636. package/src/static.ts +85 -32
  637. package/src/telemetry/otel.ts +29 -7
  638. package/src/testing.ts +76 -14
  639. package/src/timeout.ts +15 -7
  640. package/src/tree.ts +405 -333
  641. package/src/trust.ts +117 -0
  642. package/src/ts-type-emitter.ts +18 -12
  643. package/src/type-emitter.ts +227 -89
  644. package/src/type-extractor.ts +562 -424
  645. package/src/types.ts +8 -0
  646. package/src/validation.ts +221 -96
  647. package/src/with-headers.ts +45 -0
  648. package/src/ws/bun.ts +53 -15
  649. package/src/ws/cloudflare.ts +81 -19
  650. package/src/ws/deno.ts +124 -36
  651. package/src/ws/node.ts +173 -79
  652. package/src/ws/session.ts +73 -0
  653. package/src/ws/shared.ts +140 -0
  654. package/src/ws-origin.ts +53 -0
  655. package/src/yaml.ts +45 -22
  656. package/dist/client-mcp/server.d.ts +0 -10
  657. package/dist/client-mcp/server.d.ts.map +0 -1
  658. package/dist/client-mcp/types.d.ts +0 -16
  659. package/dist/client-mcp/types.d.ts.map +0 -1
  660. package/dist/cloudflare/realtime.d.ts +0 -30
  661. package/dist/cloudflare/realtime.d.ts.map +0 -1
  662. package/dist/realtime/buffer.d.ts +0 -18
  663. package/dist/realtime/buffer.d.ts.map +0 -1
  664. package/dist/realtime/longpoll.d.ts +0 -23
  665. package/dist/realtime/longpoll.d.ts.map +0 -1
  666. package/dist/realtime/protocol.d.ts +0 -50
  667. package/dist/realtime/protocol.d.ts.map +0 -1
  668. package/dist/realtime/runtime.d.ts +0 -74
  669. package/dist/realtime/runtime.d.ts.map +0 -1
  670. package/src/cloudflare/realtime.ts +0 -234
  671. package/src/realtime/buffer.ts +0 -101
  672. package/src/realtime/longpoll.ts +0 -148
  673. package/src/realtime/protocol.ts +0 -128
  674. package/src/realtime/runtime.ts +0 -427
package/src/types.ts CHANGED
@@ -136,10 +136,13 @@ export const codeToStatusKey: Record<number, StatusKey> = Object.fromEntries(
136
136
  /** Framework error keys — centralized to prevent typo-induced silent failures */
137
137
  export const EK = {
138
138
  bad_gateway: "bad_gateway",
139
+ bad_request: "bad_request",
139
140
  content_too_large: "content_too_large",
140
141
  forbidden: "forbidden",
141
142
  gateway_timeout: "gateway_timeout",
142
143
  internal_server_error: "internal_server_error",
144
+ /** a request body the declared parser cannot read (bad JSON, broken multipart) — 400 */
145
+ malformed_body: "malformed_body",
143
146
  method_not_allowed: "method_not_allowed",
144
147
  not_found: "not_found",
145
148
  output_content_type_mismatch: "output_content_type_mismatch",
@@ -726,6 +729,11 @@ export type InferBasePath<T> = T extends { readonly $basePath: infer P } ? P : n
726
729
  /** Merge two path segments — "/" is identity */
727
730
  export type MergePath<A extends string, B extends string> = A extends "/" ? B : B extends "/" ? A : `${A}${B}`
728
731
 
732
+ /** The routes of a mounted sub-app, moved under the mounting handle's basePath. */
733
+ export type PrefixRoutes<TBase extends string, TRoutes> = TBase extends "/"
734
+ ? TRoutes
735
+ : { [P in keyof TRoutes & string as MergePath<TBase, P>]: TRoutes[P] }
736
+
729
737
  /* ── Tap types ── */
730
738
 
731
739
  /** Queued side-effect emitted by c.tap() inside a handler */
package/src/validation.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import { HoneyError } from "./error.ts"
2
+ import { parseCookieHeader } from "./cookie.ts"
3
+ import { dict } from "./dict.ts"
4
+ import { bodyParserFor, type BodyParser } from "./media-type.ts"
2
5
  import type {
3
6
  FieldError,
4
7
  InputSchemaEntry,
@@ -36,20 +39,33 @@ export function mapNormalizedCode(code: string): string {
36
39
  return CODE_MAP[code] ?? "field_invalid"
37
40
  }
38
41
 
42
+ /** `bodyParserFor` with both form encodings folded into `"form"`. */
39
43
  export function selectParser(contentType: string | null): "form" | "json" | null {
40
- if (contentType === null) return null
41
- if (contentType.startsWith("application/json")) return "json"
42
- if (contentType.startsWith("multipart/form-data")) return "form"
43
- if (contentType.startsWith("application/x-www-form-urlencoded")) return "form"
44
- return null
44
+ const parser = bodyParserFor(contentType)
45
+ return parser === null ? null : parser === "json" ? "json" : "form"
45
46
  }
46
47
 
47
- /* Content-Type vs declared json/form — no body read */
48
+ /**
49
+ * Throws when a route for `method` declares a `json` or `form` schema it can never receive:
50
+ * GET and HEAD requests cannot carry a body (Fetch forbids one). Every other method, DELETE and
51
+ * OPTIONS included, has its declared body validated on every request.
52
+ */
53
+ export function assertBodySchemaAllowed(iv: InputSchemasDef | null, method: string, path: string): void {
54
+ if (iv === null || (iv.json === undefined && iv.form === undefined)) return
55
+ if (method !== "GET" && method !== "HEAD") return
56
+ const kind = iv.json !== undefined ? "json" : "form"
57
+ throw new Error(
58
+ `${method} ${path} declares a ${kind} body schema, but ${method} requests have no body. ` +
59
+ `Validate query data with \`search\`, or register the route for a method that carries a body.`,
60
+ )
61
+ }
62
+
63
+ /* Content-Type vs declared json/form — no body read. Applies to every method: a declared body
64
+ * is required, so a GET reaching an `all()` route with a body schema is a 415 too. */
48
65
  export function assertRequestContentType(iv: InputSchemasDef, req: Request): void {
49
- const hasBody = req.method !== "DELETE" && req.method !== "GET" && req.method !== "HEAD" && req.method !== "OPTIONS"
50
- if (hasBody && (iv.json || iv.form)) {
51
- const parser = selectParser(req.headers.get("content-type"))
52
- const ok = (iv.json && parser === "json") || (iv.form && parser === "form")
66
+ if (iv.json || iv.form) {
67
+ const parser = bodyParserFor(req.headers.get("content-type"))
68
+ const ok = (iv.json && parser === "json") || (iv.form && (parser === "multipart" || parser === "urlencoded"))
53
69
  if (!ok) {
54
70
  throw new HoneyError({
55
71
  errorKey: EK.unsupported_media_type,
@@ -59,33 +75,8 @@ export function assertRequestContentType(iv: InputSchemasDef, req: Request): voi
59
75
  }
60
76
  }
61
77
 
62
- function tryDecodeCookieValue(value: string): string {
63
- try {
64
- return decodeURIComponent(value)
65
- } catch {
66
- return value
67
- }
68
- }
69
-
70
- export function parseCookies(header: string): Record<string, string> {
71
- const result: Record<string, string> = {}
72
- if (header.length === 0) return result
73
- for (const pair of header.split(";")) {
74
- const trimmed = pair.trim()
75
- const eqIdx = trimmed.indexOf("=")
76
- if (eqIdx === -1) continue
77
- const name = trimmed.slice(0, eqIdx)
78
- if (DANGEROUS_KEYS.has(name)) continue
79
- let value = trimmed.slice(eqIdx + 1)
80
- if (value.indexOf("%") !== -1) value = tryDecodeCookieValue(value)
81
- /* RFC 6265: strip surrounding double quotes */
82
- if (value.length >= 2 && value.charCodeAt(0) === 34 && value.charCodeAt(value.length - 1) === 34) {
83
- value = value.slice(1, -1)
84
- }
85
- result[name] = value
86
- }
87
- return result
88
- }
78
+ /** Cookie parsing lives with the serializer so the two round-trip; see `cookie.ts`. */
79
+ export const parseCookies: (header: string) => Record<string, string> = parseCookieHeader
89
80
 
90
81
  function toPropertyKey(segment: PropertyKey | { readonly key: PropertyKey }): PropertyKey {
91
82
  if (typeof segment === "object" && segment !== null && "key" in segment) {
@@ -122,7 +113,8 @@ export function normalizeIssues(issues: ReadonlyArray<StandardSchemaIssue>, vend
122
113
  }
123
114
 
124
115
  export function issuesToFieldErrors(issues: NormalizedIssue[], prefix: string): Record<string, FieldError[]> {
125
- const fields: Record<string, FieldError[]> = {}
116
+ /* field names come from the request (record keys): `toString` must not find a prototype method */
117
+ const fields = dict<FieldError[]>()
126
118
  for (const issue of issues) {
127
119
  const fieldName = issue.path.at(-1)?.toString() ?? "unknown"
128
120
  const fullPath = `${prefix}.${issue.path.map(String).join(".")}`
@@ -139,38 +131,162 @@ export function issuesToFieldErrors(issues: NormalizedIssue[], prefix: string):
139
131
  return fields
140
132
  }
141
133
 
142
- async function runSchema(schema: StandardSchemaLike, data: unknown, prefix: string): Promise<unknown> {
143
- const result = await schema["~standard"].validate(data)
144
- if (result.issues) {
145
- const normalized = normalizeIssues(result.issues, schema["~standard"].vendor)
146
- const fields = issuesToFieldErrors(normalized, prefix)
147
- throw new HoneyError({
148
- errorKey: EK.validation_failed,
149
- fields,
150
- status: SK.bad_request,
151
- })
134
+ function failValidation(schema: StandardSchemaLike, issues: ReadonlyArray<StandardSchemaIssue>, prefix: string): never {
135
+ const normalized = normalizeIssues(issues, schema["~standard"].vendor)
136
+ throw new HoneyError({
137
+ errorKey: EK.validation_failed,
138
+ fields: issuesToFieldErrors(normalized, prefix),
139
+ status: SK.bad_request,
140
+ })
141
+ }
142
+
143
+ type StandardResult = { issues?: ReadonlyArray<StandardSchemaIssue>; value?: unknown }
144
+
145
+ /* Most validators answer synchronously; only a schema that returns a promise costs an await. */
146
+ function runSchema(schema: StandardSchemaLike, data: unknown, prefix: string): unknown {
147
+ const result = schema["~standard"].validate(data) as StandardResult | Promise<StandardResult>
148
+ if (result instanceof Promise) {
149
+ return result.then((r) => (r.issues ? failValidation(schema, r.issues, prefix) : r.value))
152
150
  }
153
- return result.value
151
+ return result.issues ? failValidation(schema, result.issues, prefix) : result.value
152
+ }
153
+
154
+ /*
155
+ * Duplicate keys in search and form data. One policy for both:
156
+ *
157
+ * - A property the schema types as an array always gets an array, even for a single value
158
+ * (`?tag=a` → `["a"]`, one uploaded file → `[File]`).
159
+ * - A property the schema types as a scalar gets the FIRST value, like `ctx.search` and
160
+ * cookies (`role=user&role=admin` → `"user"`), so every layer that reads the key agrees.
161
+ * - Anything else (a union of scalar and array, an untyped or unknown key, or a schema that
162
+ * cannot describe itself) gets a scalar for one value and an array for repeated values.
163
+ *
164
+ * Shapes come from the schema's Standard JSON Schema (`~standard.jsonSchema`, zod ≥ 4.2 and
165
+ * arktype); validators without it (valibot, yup, effect) get the last rule for every key.
166
+ * Cookies are always first-wins (RFC 6265 sends the most specific cookie first); headers are
167
+ * joined with `, ` as Fetch does.
168
+ */
169
+ type KeyShape = "array" | "natural" | "scalar"
170
+ type Shape = Map<string, KeyShape>
171
+
172
+ const shapeCache = new WeakMap<object, Shape | null>()
173
+
174
+ type JsonSchemaNode = {
175
+ $ref?: string
176
+ allOf?: JsonSchemaNode[]
177
+ anyOf?: JsonSchemaNode[]
178
+ oneOf?: JsonSchemaNode[]
179
+ properties?: Record<string, JsonSchemaNode>
180
+ type?: string | string[]
181
+ $defs?: Record<string, JsonSchemaNode>
182
+ definitions?: Record<string, JsonSchemaNode>
183
+ }
184
+
185
+ function deref(node: JsonSchemaNode, root: JsonSchemaNode): JsonSchemaNode {
186
+ const ref = node.$ref
187
+ if (typeof ref !== "string") return node
188
+ const m = /^#\/(\$defs|definitions)\/(.+)$/.exec(ref)
189
+ if (m === null) return node
190
+ const defs = m[1] === "$defs" ? root.$defs : root.definitions
191
+ const target = defs !== undefined && Object.hasOwn(defs, m[2]!) ? defs[m[2]!] : undefined
192
+ return target ?? node
154
193
  }
155
194
 
156
- function parseSearchParams(url: string): Record<string, string | string[]> {
157
- const searchParams = new URL(url).searchParams
158
- const result: Record<string, string | string[]> = {}
159
- for (const [key, value] of searchParams) {
160
- const existing = result[key]
161
- if (existing === undefined) {
162
- result[key] = value
163
- } else if (Array.isArray(existing)) {
164
- existing.push(value)
165
- } else {
166
- result[key] = [existing, value]
195
+ function classify(node: JsonSchemaNode, root: JsonSchemaNode, depth = 0): KeyShape {
196
+ if (depth > 8) return "natural"
197
+ const n = deref(node, root)
198
+ const t = n.type
199
+ if (typeof t === "string") return t === "array" ? "array" : "scalar"
200
+ if (Array.isArray(t)) {
201
+ const nonNull = t.filter((x) => x !== "null")
202
+ if (nonNull.length === 0) return "natural"
203
+ if (nonNull.every((x) => x === "array")) return "array"
204
+ return nonNull.includes("array") ? "natural" : "scalar"
205
+ }
206
+ const branches = n.anyOf ?? n.oneOf
207
+ if (branches !== undefined && branches.length > 0) {
208
+ const kinds = branches.filter((b) => deref(b, root).type !== "null").map((b) => classify(b, root, depth + 1))
209
+ if (kinds.length === 0) return "natural"
210
+ if (kinds.every((k) => k === "array")) return "array"
211
+ if (kinds.every((k) => k === "scalar")) return "scalar"
212
+ return "natural"
213
+ }
214
+ if (n.allOf !== undefined && n.allOf.length > 0) {
215
+ const kinds = n.allOf.map((b) => classify(b, root, depth + 1))
216
+ if (kinds.includes("array")) return "array"
217
+ if (kinds.includes("scalar")) return "scalar"
218
+ }
219
+ return "natural"
220
+ }
221
+
222
+ function collectProperties(node: JsonSchemaNode, root: JsonSchemaNode, out: Shape, depth = 0): void {
223
+ if (depth > 8) return
224
+ const n = deref(node, root)
225
+ if (n.properties !== undefined) {
226
+ for (const key of Object.keys(n.properties)) {
227
+ if (!out.has(key)) out.set(key, classify(n.properties[key]!, root))
167
228
  }
168
229
  }
169
- return result
230
+ for (const part of n.allOf ?? []) collectProperties(part, root, out, depth + 1)
231
+ }
232
+
233
+ type StandardJsonSchemaLike = {
234
+ input?: (opts: { libraryOptions?: Record<string, unknown>; target: string }) => unknown
235
+ }
236
+
237
+ function shapeOf(schema: StandardSchemaLike): Shape | null {
238
+ const cached = shapeCache.get(schema)
239
+ if (cached !== undefined) return cached
240
+ let shape: Shape | null = null
241
+ const js = (schema["~standard"] as { jsonSchema?: StandardJsonSchemaLike }).jsonSchema
242
+ if (js !== undefined && typeof js.input === "function") {
243
+ try {
244
+ const root = js.input({ libraryOptions: { unrepresentable: "any" }, target: "draft-2020-12" }) as JsonSchemaNode
245
+ if (root !== null && typeof root === "object") {
246
+ shape = new Map()
247
+ collectProperties(root, root, shape)
248
+ }
249
+ } catch {
250
+ /* a schema that cannot describe itself falls back to the natural shape */
251
+ shape = null
252
+ }
253
+ }
254
+ shapeCache.set(schema, shape)
255
+ return shape
256
+ }
257
+
258
+ /** Applies the duplicate-key policy to `key → values` collected in request order. */
259
+ function shapeRecord<T>(all: Map<string, T[]>, shape: Shape | null): Record<string, T | T[]> {
260
+ const out = dict<T | T[]>()
261
+ for (const [key, values] of all) {
262
+ const kind = shape?.get(key) ?? "natural"
263
+ out[key] = kind === "array" ? values : kind === "scalar" || values.length === 1 ? values[0]! : values
264
+ }
265
+ return out
266
+ }
267
+
268
+ function collect<T>(entries: Iterable<[string, T]>, skipDangerous: boolean): Map<string, T[]> {
269
+ const all = new Map<string, T[]>()
270
+ for (const [key, value] of entries) {
271
+ if (skipDangerous && DANGEROUS_KEYS.has(key)) continue
272
+ const list = all.get(key)
273
+ if (list === undefined) all.set(key, [value])
274
+ else list.push(value)
275
+ }
276
+ return all
277
+ }
278
+
279
+ function searchEntries(req: Request, searchAll: Record<string, string[]> | undefined): Iterable<[string, string]> {
280
+ if (searchAll === undefined) return new URL(req.url).searchParams
281
+ const entries: [string, string][] = []
282
+ for (const key of Object.keys(searchAll)) {
283
+ for (const value of searchAll[key]!) entries.push([key, value])
284
+ }
285
+ return entries
170
286
  }
171
287
 
172
288
  function headersToRecord(headers: Headers): Record<string, string> {
173
- const result: Record<string, string> = {}
289
+ const result = dict<string>()
174
290
  headers.forEach((value, key) => {
175
291
  result[key] = value
176
292
  })
@@ -192,29 +308,43 @@ function resolveSchema(entry: InputSchemaEntry): ResolvedSchema {
192
308
  return { mode: "standard", schema: entry }
193
309
  }
194
310
 
195
- function formDataToRecord(formData: FormData): Record<string, unknown> {
196
- const record: Record<string, unknown> = {}
197
- formData.forEach((value, key) => {
198
- if (DANGEROUS_KEYS.has(key)) return
199
- record[key] = value
200
- })
201
- return record
311
+ /* A body the declared parser cannot read is the client's fault: 400 `malformed_body`, never a
312
+ * 500 through `onError`. Errors from reading the stream itself (a `bodyLimit` 413, a client
313
+ * abort) are not parse errors and pass through unchanged. */
314
+ function malformedBody(format: BodyParser, cause: unknown): HoneyError {
315
+ return new HoneyError({ cause, errorKey: EK.malformed_body, status: SK.bad_request, vars: { format } })
202
316
  }
203
317
 
204
- function urlEncodedToRecord(body: string): Record<string, string> {
205
- const params = new URLSearchParams(body)
206
- const record: Record<string, string> = {}
207
- params.forEach((value, key) => {
208
- if (DANGEROUS_KEYS.has(key)) return
209
- record[key] = value
210
- })
211
- return record
318
+ async function readJson(req: Request): Promise<unknown> {
319
+ const text = await req.text()
320
+ try {
321
+ return JSON.parse(text) as unknown
322
+ } catch (err) {
323
+ throw malformedBody("json", err)
324
+ }
325
+ }
326
+
327
+ async function readForm(req: Request, parser: "multipart" | "urlencoded"): Promise<Iterable<[string, unknown]>> {
328
+ if (parser === "urlencoded") return new URLSearchParams(await req.text())
329
+ const bytes = await req.arrayBuffer()
330
+ try {
331
+ const contentType = req.headers.get("content-type") ?? ""
332
+ return (await new Response(bytes, { headers: { "content-type": contentType } }).formData()) as unknown as Iterable<
333
+ [string, unknown]
334
+ >
335
+ } catch (err) {
336
+ throw malformedBody("multipart", err)
337
+ }
212
338
  }
213
339
 
340
+ /** What validation reads from the request context when it has one; `ctx.searchAll` is cached. */
341
+ type ValidationSource = { readonly searchAll?: Record<string, string[]> }
342
+
214
343
  export async function validateInput(
215
344
  schemas: InputSchemasDef,
216
345
  req: Request,
217
346
  params: Record<string, string>,
347
+ source?: ValidationSource,
218
348
  ): Promise<ValidatedInput> {
219
349
  const result: ValidatedInput = {}
220
350
 
@@ -227,7 +357,8 @@ export async function validateInput(
227
357
  /* search */
228
358
  if (schemas.search) {
229
359
  const { mode, schema } = resolveSchema(schemas.search)
230
- const parsed = parseSearchParams(req.url)
360
+ const all = collect(searchEntries(req, source?.searchAll), false)
361
+ const parsed = shapeRecord(all, mode === "readableStream" ? null : shapeOf(schema))
231
362
  result.search = mode === "readableStream" ? parsed : await runSchema(schema, parsed, "search")
232
363
  }
233
364
 
@@ -246,29 +377,23 @@ export async function validateInput(
246
377
  result.cookies = mode === "readableStream" ? parsed : await runSchema(schema, parsed, "cookies")
247
378
  }
248
379
 
249
- /* body-based: json or form — skip for methods without a request body */
250
- assertRequestContentType(schemas, req)
251
- const hasBody = req.method !== "DELETE" && req.method !== "GET" && req.method !== "HEAD" && req.method !== "OPTIONS"
252
- if (hasBody && (schemas.json || schemas.form)) {
253
- const contentType = req.headers.get("content-type")
254
- const parser = selectParser(contentType)
380
+ /* body: json or form, for every method that reaches here (GET/HEAD body schemas are
381
+ * rejected at registration) */
382
+ if (schemas.json || schemas.form) {
383
+ assertRequestContentType(schemas, req)
384
+ const parser = bodyParserFor(req.headers.get("content-type"))
255
385
 
256
386
  if (schemas.json && parser === "json") {
257
387
  const { mode, schema } = resolveSchema(schemas.json)
258
- if (mode === "readableStream") {
259
- /* body untouched — handler reads ctx.req.body directly */
260
- } else {
261
- const body: unknown = await req.json()
262
- result.json = await runSchema(schema, body, "json")
388
+ if (mode !== "readableStream") {
389
+ result.json = await runSchema(schema, await readJson(req), "json")
263
390
  }
264
- } else if (schemas.form && parser === "form") {
391
+ /* readableStream: body untouched — the handler reads ctx.req.body directly */
392
+ } else if (schemas.form && (parser === "multipart" || parser === "urlencoded")) {
265
393
  const { mode, schema } = resolveSchema(schemas.form)
266
- if (mode === "readableStream") {
267
- /* body untouched — handler reads ctx.req.body directly */
268
- } else if (contentType?.startsWith("multipart/form-data")) {
269
- result.form = await runSchema(schema, formDataToRecord(await req.formData()), "form")
270
- } else {
271
- result.form = await runSchema(schema, urlEncodedToRecord(await req.text()), "form")
394
+ if (mode !== "readableStream") {
395
+ const record = shapeRecord(collect(await readForm(req, parser), true), shapeOf(schema))
396
+ result.form = await runSchema(schema, record, "form")
272
397
  }
273
398
  }
274
399
  }
@@ -0,0 +1,45 @@
1
+ import { isHoneyResponse } from "./honey-response.ts"
2
+
3
+ /**
4
+ * Apply header edits to a response a middleware got back from `next()`.
5
+ *
6
+ * - `101 Switching Protocols` passes through untouched. Upgrade responses are
7
+ * adapter sentinels (Node and Bun fake the status, Workers carry `webSocket`),
8
+ * and rebuilding one throws or drops the socket.
9
+ * - Honey's own responses and `new Response()` objects are edited in place.
10
+ * - Responses with immutable headers (`fetch()`, `Response.redirect()`,
11
+ * `cache.match()`) are copied first: same body stream, status, status text
12
+ * and headers, then edited.
13
+ *
14
+ * `edit` must not read the body.
15
+ */
16
+ export function withHeaders(response: Response, edit: (headers: Headers) => void): Response {
17
+ if (response.status === 101) return response
18
+ if (isHoneyResponse(response)) {
19
+ edit(response.headers as unknown as Headers)
20
+ return response
21
+ }
22
+ if (!headersAreImmutable(response)) {
23
+ edit(response.headers)
24
+ return response
25
+ }
26
+ const copy = new Response(response.body, {
27
+ headers: new Headers(response.headers),
28
+ status: response.status,
29
+ statusText: response.statusText,
30
+ })
31
+ edit(copy.headers)
32
+ return copy
33
+ }
34
+
35
+ /** Probe with a header name no one sends: immutable Headers throw on any write. */
36
+ const PROBE = "x-honey-mutability-probe"
37
+
38
+ function headersAreImmutable(response: Response): boolean {
39
+ try {
40
+ response.headers.delete(PROBE)
41
+ return false
42
+ } catch {
43
+ return true
44
+ }
45
+ }
package/src/ws/bun.ts CHANGED
@@ -1,11 +1,20 @@
1
- export { type RawSocket, type WSAdapter, type WSContext, WSContextImpl, type WSHandler } from "./cloudflare.ts"
1
+ export {
2
+ type RawSocket,
3
+ type WSAdapter,
4
+ type WSAdapterOptions,
5
+ type WSContext,
6
+ WSContextImpl,
7
+ type WSHandler,
8
+ } from "./cloudflare.ts"
2
9
 
3
10
  import type { WSAdapter, WSContext, WSHandler } from "./cloudflare.ts"
4
- import { WSContextImpl } from "./cloudflare.ts"
11
+ import { fireHandler, WSContextImpl } from "./cloudflare.ts"
12
+ import { resolveWSOptions, selectProtocol, toArrayBuffer, type WSAdapterOptions } from "./shared.ts"
5
13
 
6
14
  type BunRawSocket = {
7
15
  close(code?: number, reason?: string): void
8
16
  data: BunWSData
17
+ getBufferedAmount?(): number
9
18
  readyState: number
10
19
  send(data: ArrayBufferLike | ArrayBufferView | string, compress?: boolean): number
11
20
  }
@@ -16,12 +25,17 @@ type BunWSData = {
16
25
  }
17
26
 
18
27
  type BunServer = {
19
- upgrade<T>(req: Request, opts?: { data?: T }): boolean
28
+ upgrade<T>(req: Request, opts?: { data?: T; headers?: HeadersInit }): boolean
20
29
  }
21
30
 
22
31
  export type BunWSAdapter = WSAdapter & {
32
+ /** Pass to `Bun.serve({ websocket })`; honey's `serve()` does. */
23
33
  websocket: {
34
+ backpressureLimit: number
24
35
  close(ws: BunRawSocket, code: number, reason: string): void
36
+ closeOnBackpressureLimit: boolean
37
+ idleTimeout: number
38
+ maxPayloadLength: number
25
39
  message(ws: BunRawSocket, data: ArrayBufferView | string): void
26
40
  open(ws: BunRawSocket): void
27
41
  }
@@ -30,12 +44,27 @@ export type BunWSAdapter = WSAdapter & {
30
44
  /**
31
45
  * Bun WebSocket adapter.
32
46
  * Returns adapter + websocket handler object for Bun.serve().
33
- * Bun has no onError callback (noted limitation).
34
- * Keepalive is not supported — Bun's WS API lacks ping/pong control.
35
- * Use nodeWebSocket() for keepalive support.
47
+ * Bun has no onError callback (noted limitation). Bun pings idle sockets itself
48
+ * (`idleTimeout`, whole seconds, at most 960); there is no `keepalive` option.
36
49
  */
37
- export function bunWebSocket(): BunWSAdapter {
50
+ export function bunWebSocket(opts?: WSAdapterOptions): BunWSAdapter {
51
+ const options = resolveWSOptions(opts)
52
+ const open = new Set<BunRawSocket>()
53
+ const socketOf = (ws: BunRawSocket): WSContext => {
54
+ if (!ws.data.socket) ws.data.socket = new WSContextImpl(ws, options)
55
+ return ws.data.socket
56
+ }
38
57
  return {
58
+ closeAll(code: number, reason: string) {
59
+ for (const ws of open) {
60
+ try {
61
+ ws.close(code, reason)
62
+ } catch {
63
+ /* already closing */
64
+ }
65
+ }
66
+ },
67
+ options,
39
68
  upgrade(req: Request, env: unknown, handler: WSHandler<unknown>) {
40
69
  const server = (env as Record<string, unknown>).server as BunServer
41
70
  /*
@@ -43,8 +72,10 @@ export function bunWebSocket(): BunWSAdapter {
43
72
  * WSContext cast is safe: actual socket is assigned in open/message/close
44
73
  * before any user code accesses it.
45
74
  */
75
+ const protocol = selectProtocol(req, options)
46
76
  const success = server.upgrade<BunWSData>(req, {
47
77
  data: { handler, socket: undefined as unknown as WSContext },
78
+ headers: protocol === null ? undefined : { "sec-websocket-protocol": protocol },
48
79
  })
49
80
  if (!success) {
50
81
  return {
@@ -63,19 +94,26 @@ export function bunWebSocket(): BunWSAdapter {
63
94
  },
64
95
 
65
96
  websocket: {
97
+ /* honey applies its own policy on send (WSContext); Bun's limit only bounds its queue */
98
+ backpressureLimit: Math.max(options.backpressureLimit, 1),
66
99
  close(ws: BunRawSocket, code: number, reason: string) {
67
- if (!ws.data.socket) ws.data.socket = new WSContextImpl(ws)
68
- ws.data.handler.onClose?.(undefined, ws.data.socket, code, reason)
100
+ open.delete(ws)
101
+ const socket = socketOf(ws)
102
+ fireHandler("close", () => ws.data.handler.onClose?.(undefined, socket, code, reason))
69
103
  },
104
+ closeOnBackpressureLimit: false,
105
+ idleTimeout: options.idleTimeout === 0 ? 0 : Math.min(Math.max(Math.ceil(options.idleTimeout / 1000), 1), 960),
106
+ maxPayloadLength: options.maxPayload,
70
107
  message(ws: BunRawSocket, data: ArrayBufferView | string) {
71
- if (!ws.data.socket) ws.data.socket = new WSContextImpl(ws)
72
- /* Bun passes Buffer (Uint8Array) for binary — normalize to ArrayBuffer for WSHandler */
73
- const normalized = typeof data === "string" ? data : ((data as Uint8Array).buffer as ArrayBuffer)
74
- ws.data.handler.onMessage?.(undefined, ws.data.socket, normalized)
108
+ const socket = socketOf(ws)
109
+ /* Bun passes a Buffer for binary frames: hand over exactly its bytes, as an ArrayBuffer */
110
+ const normalized = typeof data === "string" ? data : toArrayBuffer(data)
111
+ fireHandler("message", () => ws.data.handler.onMessage?.(undefined, socket, normalized))
75
112
  },
76
113
  open(ws: BunRawSocket) {
77
- if (!ws.data.socket) ws.data.socket = new WSContextImpl(ws)
78
- ws.data.handler.onOpen?.(undefined, ws.data.socket)
114
+ open.add(ws)
115
+ const socket = socketOf(ws)
116
+ fireHandler("open", () => ws.data.handler.onOpen?.(undefined, socket))
79
117
  },
80
118
  },
81
119
  }