@lovrozagar/honey 0.6.8 → 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 +408 -148
  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 +1887 -1756
  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 +253 -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/crypto.ts CHANGED
@@ -1,5 +1,3 @@
1
- const encoder = new TextEncoder()
2
-
3
1
  let cachedKey: CryptoKey | null = null
4
2
 
5
3
  async function getKey(): Promise<CryptoKey> {
@@ -9,8 +7,32 @@ async function getKey(): Promise<CryptoKey> {
9
7
  return cachedKey
10
8
  }
11
9
 
12
- export async function timingSafeEqual(a: string, b: string): Promise<boolean> {
10
+ /**
11
+ * Strings are compared by UTF-16 code unit, not by their UTF-8 encoding: UTF-8
12
+ * replaces every lone surrogate with U+FFFD, so two different strings would
13
+ * compare equal.
14
+ */
15
+ function toBytes(value: string | Uint8Array): Uint8Array<ArrayBuffer> {
16
+ if (typeof value !== "string") return new Uint8Array(value)
17
+ const bytes = new Uint8Array(value.length * 2)
18
+ for (let i = 0; i < value.length; i++) {
19
+ const unit = value.charCodeAt(i)
20
+ bytes[i * 2] = unit >> 8
21
+ bytes[i * 2 + 1] = unit & 0xff
22
+ }
23
+ return bytes
24
+ }
25
+
26
+ /**
27
+ * Constant-time equality. Both sides are MACed with a per-process random key and
28
+ * the MACs are compared by `crypto.subtle.verify`, so neither content nor length
29
+ * leaks through timing.
30
+ */
31
+ export async function timingSafeEqual(a: string | Uint8Array, b: string | Uint8Array): Promise<boolean> {
32
+ /* A string never equals bytes: tag the kind so "ab" and its code units differ. */
33
+ const aIsString = typeof a === "string"
34
+ if (aIsString !== (typeof b === "string")) return false
13
35
  const key = await getKey()
14
- const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(a))
15
- return crypto.subtle.verify("HMAC", key, sig, encoder.encode(b))
36
+ const sig = await crypto.subtle.sign("HMAC", key, toBytes(a))
37
+ return crypto.subtle.verify("HMAC", key, sig, toBytes(b))
16
38
  }
package/src/csrf.ts CHANGED
@@ -4,11 +4,15 @@ import type { MiddlewareFn } from "./middleware.ts"
4
4
  import { EK, SK } from "./types.ts"
5
5
 
6
6
  type CSRFOptions = {
7
+ /**
8
+ * Cross-origin callers allowed to make unsafe requests, matched against the
9
+ * full `Origin` (`https://app.example.com`). Same-origin requests never need
10
+ * to be listed.
11
+ */
7
12
  origin?: ((origin: string) => boolean) | string | string[]
8
13
  }
9
14
 
10
- const safeMethodRe = /^(GET|HEAD|OPTIONS)$/
11
- const formContentTypeRe = /^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/i
15
+ const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"])
12
16
 
13
17
  function matchOrigin(origin: string, config: CSRFOptions["origin"]): boolean {
14
18
  if (config === undefined) return false
@@ -17,37 +21,65 @@ function matchOrigin(origin: string, config: CSRFOptions["origin"]): boolean {
17
21
  return config(origin)
18
22
  }
19
23
 
20
- export function csrf(options?: CSRFOptions): MiddlewareFn<{ req: Request }, {}> {
21
- const opts = options ?? {}
24
+ /** Host of the request as the client addressed it: `Host`, else the URL's host. */
25
+ function requestHost(req: Request): string | null {
26
+ const host = req.headers.get("host")
27
+ if (host !== null && host !== "") return host.toLowerCase()
28
+ try {
29
+ return new URL(req.url).host.toLowerCase()
30
+ } catch {
31
+ return null
32
+ }
33
+ }
22
34
 
23
- const mw: MiddlewareFn<{ req: Request }, {}> = (ctx, next) => {
24
- const req = ctx.req
35
+ function originHost(origin: string): string | null {
36
+ if (origin === "null") return null
37
+ try {
38
+ return new URL(origin).host.toLowerCase()
39
+ } catch {
40
+ return null
41
+ }
42
+ }
25
43
 
26
- if (safeMethodRe.test(req.method)) {
27
- return next()
28
- }
44
+ function reject(): never {
45
+ throw new HoneyError({ errorKey: EK.forbidden, status: SK.forbidden })
46
+ }
29
47
 
30
- const contentType = req.headers.get("content-type")
31
- if (contentType === null || !formContentTypeRe.test(contentType)) {
32
- return next()
33
- }
48
+ /**
49
+ * Rejects cross-origin unsafe requests, whatever their content type.
50
+ *
51
+ * 1. `GET`, `HEAD` and `OPTIONS` pass.
52
+ * 2. With `Sec-Fetch-Site` (every current browser sends it): `same-origin` and
53
+ * `none` (user-initiated) pass. `same-site` and `cross-site` pass only for an
54
+ * allow-listed `Origin`. A sibling subdomain is same-site but not trusted.
55
+ * 3. Without it: no `Origin` passes (curl, server-to-server, old clients that
56
+ * cannot be driven cross-site), an `Origin` whose host equals `Host` passes,
57
+ * an allow-listed `Origin` passes, anything else is rejected.
58
+ *
59
+ * Failure is 403 `forbidden`. This is the algorithm of Go's
60
+ * `http.CrossOriginProtection`.
61
+ */
62
+ export function csrf(options?: CSRFOptions): MiddlewareFn<{ req: Request }, {}> {
63
+ const allow = options?.origin
34
64
 
35
- /* check Sec-Fetch-Site first (modern browsers, unforgeable) */
36
- const secFetchSite = req.headers.get("sec-fetch-site")
37
- if (secFetchSite === "same-origin") {
38
- return next()
39
- }
65
+ const mw: MiddlewareFn<{ req: Request }, {}> = (ctx, next) => {
66
+ const req = ctx.req
67
+ if (SAFE_METHODS.has(req.method)) return next()
40
68
 
41
- /* fall back to Origin header */
42
69
  const origin = req.headers.get("origin")
43
- if (origin !== null && matchOrigin(origin, opts.origin)) {
44
- return next()
70
+ const site = req.headers.get("sec-fetch-site")
71
+
72
+ if (site !== null) {
73
+ if (site === "same-origin" || site === "none") return next()
74
+ if (origin !== null && matchOrigin(origin, allow)) return next()
75
+ return reject()
45
76
  }
46
77
 
47
- throw new HoneyError({
48
- errorKey: EK.forbidden,
49
- status: SK.forbidden,
50
- })
78
+ if (origin === null) return next()
79
+ const host = originHost(origin)
80
+ if (host !== null && host === requestHost(req)) return next()
81
+ if (matchOrigin(origin, allow)) return next()
82
+ return reject()
51
83
  }
52
84
 
53
85
  return namedMiddleware("csrf", mw)
@@ -1,6 +1,7 @@
1
1
  import { namedMiddleware } from "./middleware.ts"
2
2
  import type { MiddlewareFn } from "./middleware.ts"
3
3
  import type { LoggerInstance } from "./logger.ts"
4
+ import { shellQuote } from "./request-to-curl.ts"
4
5
 
5
6
  type BodyOmittedReason = "content-type" | "disabled" | "missing" | "read-error" | "too-large"
6
7
 
@@ -15,6 +16,9 @@ type CurlLogData = {
15
16
  status: number
16
17
  }
17
18
 
19
+ /** What `skip` sees: everything known before the curl command is built. */
20
+ type CurlSkipData = Pick<CurlLogData, "duration" | "method" | "path" | "requestId" | "status">
21
+
18
22
  type CurlLoggerBodyOptions = {
19
23
  allowContentTypes?: string[]
20
24
  maxBytes?: number
@@ -24,17 +28,46 @@ type CurlLoggerOptions = {
24
28
  body?: boolean | CurlLoggerBodyOptions
25
29
  instance?: LoggerInstance
26
30
  log?: (data: CurlLogData) => void
31
+ /**
32
+ * Return the value to log, or null to drop the header. Replaces the default,
33
+ * which masks `authorization`, `proxy-authorization`, `cookie`, `set-cookie`
34
+ * and API-key/token headers. Call `defaultRedactHeader` to keep it and add more.
35
+ */
27
36
  redactHeader?: (name: string, value: string) => string | null
37
+ /** Same for query parameters. The default masks token-, key-, secret- and password-like names. */
28
38
  redactQueryParam?: (name: string, value: string) => string | null
29
- skip?: (data: CurlLogData) => boolean
39
+ /** Runs before the curl command is built; returning true skips all of that work. */
40
+ skip?: (data: CurlSkipData) => boolean
30
41
  }
31
42
 
32
43
  const DEFAULT_BODY_CONTENT_TYPES = ["application/json", "application/x-www-form-urlencoded", "application/xml", "text/"]
33
44
 
34
45
  const DEFAULT_MAX_BODY_BYTES = 16_384
35
46
 
36
- function shellEscape(value: string): string {
37
- return value.replace(/'/g, "'\\''")
47
+ const REDACTED = "[REDACTED]"
48
+
49
+ const SENSITIVE_HEADERS = new Set([
50
+ "authorization",
51
+ "cookie",
52
+ "proxy-authorization",
53
+ "set-cookie",
54
+ "x-api-key",
55
+ "x-auth-token",
56
+ "x-csrf-token",
57
+ "x-xsrf-token",
58
+ ])
59
+ const SENSITIVE_HEADER_RE = /(?:^|-)(?:token|secret|api-?key|password|session)(?:$|-)/i
60
+ const SENSITIVE_PARAM_RE =
61
+ /(?:^|[_.-])(?:access_?token|refresh_?token|id_?token|token|secret|client_?secret|password|passwd|pwd|api_?key|apikey|key|sig|signature|auth|code|session|jwt)$/i
62
+
63
+ function defaultRedactHeader(name: string, value: string): string | null {
64
+ const lower = name.toLowerCase()
65
+ if (SENSITIVE_HEADERS.has(lower) || SENSITIVE_HEADER_RE.test(lower)) return REDACTED
66
+ return value
67
+ }
68
+
69
+ function defaultRedactQueryParam(name: string, value: string): string | null {
70
+ return SENSITIVE_PARAM_RE.test(name) ? REDACTED : value
38
71
  }
39
72
 
40
73
  function defaultLog(data: CurlLogData): void {
@@ -43,175 +76,183 @@ function defaultLog(data: CurlLogData): void {
43
76
 
44
77
  function shouldIncludeBody(contentType: string | null, allowContentTypes: string[]): boolean {
45
78
  if (contentType === null || contentType.length === 0) return false
46
- return allowContentTypes.some((allowed) => contentType.startsWith(allowed))
79
+ const lower = contentType.toLowerCase()
80
+ return allowContentTypes.some((allowed) => lower.startsWith(allowed.toLowerCase()))
47
81
  }
48
82
 
49
- function redactUrl(url: URL, redactQueryParam?: (name: string, value: string) => string | null): string {
50
- if (redactQueryParam === undefined || url.search.length === 0) return url.toString()
51
-
52
- const redacted = new URL(url.toString())
53
- redacted.search = ""
54
-
55
- for (const [name, value] of url.searchParams.entries()) {
83
+ function redactUrl(rawUrl: string, redactQueryParam: (name: string, value: string) => string | null): string {
84
+ let url: URL
85
+ try {
86
+ url = new URL(rawUrl)
87
+ } catch {
88
+ /* a malformed Host (Node builds the URL from it) must not fail the request */
89
+ return rawUrl.split("?")[0] ?? rawUrl
90
+ }
91
+ if (url.search.length === 0) return url.toString()
92
+ const params = new URLSearchParams()
93
+ for (const [name, value] of url.searchParams) {
56
94
  const nextValue = redactQueryParam(name, value)
57
- if (nextValue === null) continue
58
- redacted.searchParams.append(name, nextValue)
95
+ if (nextValue !== null) params.append(name, nextValue)
59
96
  }
60
-
61
- return redacted.toString()
97
+ url.search = params.toString()
98
+ return url.toString()
62
99
  }
63
100
 
64
- async function readBodyWithinLimit(
65
- request: Request,
66
- maxBytes: number,
67
- ): Promise<{
68
- body: string | null
69
- omittedReason: BodyOmittedReason | null
70
- }> {
71
- if (request.body === null) {
72
- return { body: null, omittedReason: "missing" }
101
+ type BodyResult = { body: string | null; omittedReason: BodyOmittedReason | null }
102
+
103
+ /**
104
+ * A second, independent reader of the request body. On Node the request is
105
+ * re-pointed at one branch of a tee (the same hook `bodyLimit` uses) rather
106
+ * than `clone()`d, so later body readers keep working.
107
+ */
108
+ function teeBody(request: Request): ReadableStream<Uint8Array> | null {
109
+ const body = request.body
110
+ if (body === null) return null
111
+ const replaceBody = (request as unknown as Record<symbol, unknown>)[Symbol.for("honey.replaceBody")]
112
+ if (typeof replaceBody === "function") {
113
+ const [forRequest, forLog] = body.tee()
114
+ ;(replaceBody as (stream: ReadableStream<Uint8Array>) => void).call(request, forRequest)
115
+ return forLog
73
116
  }
117
+ return request.clone().body
118
+ }
74
119
 
75
- const contentLength = request.headers.get("content-length")
76
- if (contentLength !== null) {
77
- const parsedLength = Number.parseInt(contentLength, 10)
78
- if (!Number.isNaN(parsedLength) && parsedLength > maxBytes) {
79
- return { body: null, omittedReason: "too-large" }
80
- }
81
- }
120
+ /** Never rejects: a body that cannot be read is logged as omitted. */
121
+ async function readBodyWithinLimit(request: Request, maxBytes: number): Promise<BodyResult> {
122
+ try {
123
+ if (request.body === null) return { body: null, omittedReason: "missing" }
82
124
 
83
- const clone = request.clone()
84
- if (clone.body === null) {
85
- return { body: null, omittedReason: "missing" }
86
- }
125
+ const contentLength = request.headers.get("content-length")
126
+ if (contentLength !== null) {
127
+ const parsedLength = Number.parseInt(contentLength, 10)
128
+ if (!Number.isNaN(parsedLength) && parsedLength > maxBytes) {
129
+ return { body: null, omittedReason: "too-large" }
130
+ }
131
+ }
87
132
 
88
- const reader = clone.body.getReader()
89
- const chunks: Uint8Array[] = []
90
- let totalBytes = 0
133
+ const stream = teeBody(request)
134
+ if (stream === null) return { body: null, omittedReason: "missing" }
91
135
 
92
- try {
136
+ const reader = stream.getReader()
137
+ const chunks: Uint8Array[] = []
138
+ let totalBytes = 0
93
139
  while (true) {
94
140
  const { done, value } = await reader.read()
95
141
  if (done) break
96
142
  totalBytes += value.byteLength
97
143
  if (totalBytes > maxBytes) {
98
- await reader.cancel()
144
+ await reader.cancel().catch(() => {})
99
145
  return { body: null, omittedReason: "too-large" }
100
146
  }
101
147
  chunks.push(value)
102
148
  }
149
+
150
+ const bytes = new Uint8Array(totalBytes)
151
+ let offset = 0
152
+ for (const chunk of chunks) {
153
+ bytes.set(chunk, offset)
154
+ offset += chunk.byteLength
155
+ }
156
+ return { body: new TextDecoder().decode(bytes), omittedReason: null }
103
157
  } catch {
104
158
  return { body: null, omittedReason: "read-error" }
105
159
  }
160
+ }
106
161
 
107
- const bytes = new Uint8Array(totalBytes)
108
- let offset = 0
162
+ type ResolvedOptions = {
163
+ redactHeader: (name: string, value: string) => string | null
164
+ redactQueryParam: (name: string, value: string) => string | null
165
+ }
109
166
 
110
- for (const chunk of chunks) {
111
- bytes.set(chunk, offset)
112
- offset += chunk.byteLength
167
+ function formatCurl(request: Request, body: BodyResult | null, opts: ResolvedOptions): string {
168
+ const parts: string[] = ["curl", "-X", shellQuote(request.method)]
169
+ for (const [name, value] of request.headers.entries()) {
170
+ const redactedValue = opts.redactHeader(name, value)
171
+ if (redactedValue === null) continue
172
+ parts.push("-H", shellQuote(`${name}: ${redactedValue}`))
113
173
  }
174
+ if (body !== null && body.body !== null) parts.push("--data-raw", shellQuote(body.body))
175
+ parts.push(shellQuote(redactUrl(request.url, opts.redactQueryParam)))
176
+ return parts.join(" ")
177
+ }
114
178
 
115
- return {
116
- body: new TextDecoder().decode(bytes),
117
- omittedReason: null,
179
+ /** Start reading the body (if logged) now, before a handler consumes it. */
180
+ function startBodyRead(request: Request, body: CurlLoggerOptions["body"]): Promise<BodyResult> {
181
+ if (body !== true && typeof body !== "object") {
182
+ return Promise.resolve({ body: null, omittedReason: "disabled" })
118
183
  }
184
+ const bodyOptions = typeof body === "object" ? body : {}
185
+ const allowContentTypes = bodyOptions.allowContentTypes ?? DEFAULT_BODY_CONTENT_TYPES
186
+ if (!shouldIncludeBody(request.headers.get("content-type"), allowContentTypes)) {
187
+ return Promise.resolve({ body: null, omittedReason: request.body === null ? "missing" : "content-type" })
188
+ }
189
+ return readBodyWithinLimit(request, bodyOptions.maxBytes ?? DEFAULT_MAX_BODY_BYTES)
119
190
  }
120
191
 
121
192
  async function buildCurlLogData(
122
193
  request: Request,
123
194
  options?: Pick<CurlLoggerOptions, "body" | "redactHeader" | "redactQueryParam">,
124
195
  ): Promise<Pick<CurlLogData, "bodyIncluded" | "bodyOmittedReason" | "curl">> {
125
- const parts: string[] = [`curl -X ${request.method}`]
126
-
127
- for (const [name, value] of request.headers.entries()) {
128
- const redactedValue = options?.redactHeader !== undefined ? options.redactHeader(name, value) : value
129
- if (redactedValue === null) continue
130
- parts.push(`-H '${shellEscape(name)}: ${shellEscape(redactedValue)}'`)
131
- }
132
-
133
- let bodyIncluded = false
134
- let bodyOmittedReason: BodyOmittedReason | null = null
135
-
136
- if (options?.body === true || typeof options?.body === "object") {
137
- const bodyOptions = typeof options.body === "object" ? options.body : {}
138
- const allowContentTypes = bodyOptions.allowContentTypes ?? DEFAULT_BODY_CONTENT_TYPES
139
- const contentType = request.headers.get("content-type")
140
-
141
- if (shouldIncludeBody(contentType, allowContentTypes) === false) {
142
- bodyOmittedReason = request.body === null ? "missing" : "content-type"
143
- } else {
144
- const result = await readBodyWithinLimit(request, bodyOptions.maxBytes ?? DEFAULT_MAX_BODY_BYTES)
145
-
146
- if (result.body === null) {
147
- bodyOmittedReason = result.omittedReason
148
- } else {
149
- bodyIncluded = true
150
- parts.push(`--data-raw '${shellEscape(result.body)}'`)
151
- }
152
- }
153
- } else {
154
- bodyOmittedReason = "disabled"
196
+ const resolved: ResolvedOptions = {
197
+ redactHeader: options?.redactHeader ?? defaultRedactHeader,
198
+ redactQueryParam: options?.redactQueryParam ?? defaultRedactQueryParam,
155
199
  }
156
-
157
- parts.push(`'${shellEscape(redactUrl(new URL(request.url), options?.redactQueryParam))}'`)
158
-
200
+ const body = await startBodyRead(request, options?.body)
159
201
  return {
160
- bodyIncluded,
161
- bodyOmittedReason,
162
- curl: parts.join(" "),
202
+ bodyIncluded: body.body !== null,
203
+ bodyOmittedReason: body.omittedReason,
204
+ curl: formatCurl(request, body, resolved),
163
205
  }
164
206
  }
165
207
 
166
208
  function curlLogger(options?: CurlLoggerOptions): MiddlewareFn<{ path: string; req: Request }, {}> {
167
209
  const log = options?.log ?? defaultLog
168
210
  const skip = options?.skip
211
+ const resolved: ResolvedOptions = {
212
+ redactHeader: options?.redactHeader ?? defaultRedactHeader,
213
+ redactQueryParam: options?.redactQueryParam ?? defaultRedactQueryParam,
214
+ }
169
215
 
170
216
  return namedMiddleware("curlLogger", async (ctx, next) => {
171
217
  const start = performance.now()
172
- const method = ctx.req.method
218
+ const request = ctx.req
219
+ const method = request.method
173
220
  const path = ctx.path
174
221
  const rid = ((ctx as Record<string, unknown>)["requestId"] as string | null) ?? null
175
- const curlDataPromise = buildCurlLogData(ctx.req, options)
222
+ /* the only work before `next()`: the body must be teed before a handler reads it */
223
+ const bodyPromise = startBodyRead(request, options?.body)
176
224
 
177
225
  const response = await next()
178
- const duration = performance.now() - start
179
- const curlData = await curlDataPromise
180
-
181
- const data: CurlLogData = {
182
- ...curlData,
183
- duration,
226
+ const skipData: CurlSkipData = {
227
+ duration: performance.now() - start,
184
228
  method,
185
229
  path,
186
230
  requestId: rid,
187
231
  status: response.status,
188
232
  }
189
233
 
190
- if (skip?.(data)) {
191
- return response
192
- }
193
-
194
- if (options?.instance) {
195
- options.instance.info(
196
- {
197
- bodyIncluded: data.bodyIncluded,
198
- bodyOmittedReason: data.bodyOmittedReason,
199
- curl: data.curl,
200
- duration: data.duration,
201
- method: data.method,
202
- path: data.path,
203
- requestId: data.requestId,
204
- status: data.status,
205
- },
206
- "request curl",
207
- )
208
- } else {
209
- log(data)
234
+ /* a failing redact callback, skip or sink never turns the response into a 500 */
235
+ try {
236
+ if (skip?.(skipData)) return response
237
+ const body = await bodyPromise
238
+ const data: CurlLogData = {
239
+ ...skipData,
240
+ bodyIncluded: body.body !== null,
241
+ bodyOmittedReason: body.omittedReason,
242
+ curl: formatCurl(request, body, resolved),
243
+ }
244
+ if (options?.instance) {
245
+ options.instance.info({ ...data }, "request curl")
246
+ } else {
247
+ log(data)
248
+ }
249
+ } catch (err) {
250
+ console.error("curlLogger: failed to log request", err)
210
251
  }
211
252
 
212
253
  return response
213
254
  })
214
255
  }
215
256
 
216
- export { buildCurlLogData, curlLogger }
217
- export type { BodyOmittedReason, CurlLogData, CurlLoggerBodyOptions, CurlLoggerOptions }
257
+ export { buildCurlLogData, curlLogger, defaultRedactHeader, defaultRedactQueryParam }
258
+ export type { BodyOmittedReason, CurlLogData, CurlLoggerBodyOptions, CurlLoggerOptions, CurlSkipData }
package/src/dict.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * An object for keys that come from request data: search params, form fields, header views,
3
+ * validation field maps. It has no prototype, so a key named `__proto__`, `constructor` or
4
+ * `toString` is plain data — it cannot hit `Object.prototype`, replace the object's prototype,
5
+ * or turn a missing key into a function.
6
+ *
7
+ * Read keys with `key in obj`, `obj[key] !== undefined` or `Object.hasOwn(obj, key)`; the
8
+ * object has no `hasOwnProperty` method of its own.
9
+ */
10
+ export function dict<T>(): Record<string, T> {
11
+ return Object.create(null) as Record<string, T>
12
+ }
package/src/error.ts CHANGED
@@ -23,8 +23,10 @@ export class HoneyError extends Error {
23
23
  this.data = opts.data
24
24
  this.errorKey = opts.errorKey
25
25
  this.headers = opts.headers
26
- this.statusKey = opts.status
27
- this.status = statusKeyToCode[opts.status]
26
+ /* an unknown status key must not turn an error into a 200 — it is a 500 */
27
+ const known = Object.hasOwn(statusKeyToCode, opts.status)
28
+ this.statusKey = known ? opts.status : "internal_server_error"
29
+ this.status = known ? statusKeyToCode[opts.status] : 500
28
30
  this.fields = opts.fields ?? EMPTY_FIELDS
29
31
  this.vars = opts.vars
30
32
  }
package/src/etag.ts CHANGED
@@ -1,22 +1,38 @@
1
- import { namedMiddleware } from "./middleware.ts"
1
+ import { bodyKind, rawBodyOf } from "./body-kind.ts"
2
2
  import { replaceResponse } from "./honey-response.ts"
3
+ import { namedMiddleware } from "./middleware.ts"
3
4
  import type { MiddlewareFn } from "./middleware.ts"
4
5
 
5
6
  type ETagOptions = {
6
7
  weak?: boolean
7
8
  }
8
9
 
9
- /**
10
- * FNV-1a 32-bit hash — fast, non-cryptographic, deterministic.
11
- * Good enough for ETag fingerprinting (not security-critical).
12
- */
13
- function fnv1a(data: Uint8Array): string {
14
- let hash = 0x811c9dc5
15
- for (let i = 0; i < data.length; i++) {
16
- hash ^= data[i]
17
- hash = Math.imul(hash, 0x01000193)
10
+ const encoder = new TextEncoder()
11
+
12
+ /** Base64url of the first 16 bytes of SHA-256: collision-resistant enough for a strong validator. */
13
+ async function digest(data: Uint8Array<ArrayBuffer>): Promise<string> {
14
+ const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", data)).subarray(0, 16)
15
+ let binary = ""
16
+ for (let i = 0; i < hash.length; i++) binary += String.fromCharCode(hash[i])
17
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")
18
+ }
19
+
20
+ const ENTITY_TAG_RE = /(?:W\/)?"[^"]*"/g
21
+
22
+ /** Weak comparison (RFC 9110 §8.8.3.2): opaque tags match regardless of the `W/` prefix. */
23
+ function opaque(tag: string): string {
24
+ return tag.startsWith("W/") ? tag.slice(2) : tag
25
+ }
26
+
27
+ /** RFC 9110 §13.1.2: `*` or a list of entity tags, compared weakly. */
28
+ export function ifNoneMatchHits(header: string | null, etag: string): boolean {
29
+ if (header === null) return false
30
+ if (header.trim() === "*") return true
31
+ const target = opaque(etag.trim())
32
+ for (const tag of header.match(ENTITY_TAG_RE) ?? []) {
33
+ if (opaque(tag) === target) return true
18
34
  }
19
- return (hash >>> 0).toString(16).padStart(8, "0")
35
+ return false
20
36
  }
21
37
 
22
38
  export function etag(options?: ETagOptions): MiddlewareFn<{ req: Request }, {}> {
@@ -30,40 +46,48 @@ export function etag(options?: ETagOptions): MiddlewareFn<{ req: Request }, {}>
30
46
 
31
47
  const response = await next()
32
48
 
33
- /* skip error responses — caching errors is wasteful and misleading */
34
- if (response.status >= 400) return response
49
+ /* Only a 200 has a selected representation a 304 can stand in for. */
50
+ if (response.status !== 200) return response
35
51
 
36
- /* skip streaming responses — can't hash without buffering entire stream */
37
- if (
38
- response.headers.get("transfer-encoding") === "chunked" ||
39
- (response.body !== null && !response.headers.has("content-length") && !response.headers.has("content-type"))
40
- ) {
41
- return response
52
+ const ifNoneMatch = ctx.req.headers.get("if-none-match")
53
+
54
+ /* A handler-set ETag is authoritative: never overwrite it or hash the body. */
55
+ const existing = response.headers.get("etag")
56
+ if (existing !== null) {
57
+ if (!ifNoneMatchHits(ifNoneMatch, existing)) return response
58
+ await response.body?.cancel().catch(() => {})
59
+ return replaceResponse(response, { body: null, headers: response.headers, status: 304 })
42
60
  }
43
61
 
44
- /* read body to compute hash */
45
- const body = await response.arrayBuffer()
46
- if (body.byteLength === 0) return response
62
+ /* Never buffer a stream: SSE and generate() can be endless. */
63
+ if (bodyKind(response) !== "buffered") return response
64
+
65
+ let bytes: Uint8Array<ArrayBuffer>
66
+ const raw = rawBodyOf(response)
67
+ if (raw !== null) {
68
+ bytes = typeof raw === "string" ? encoder.encode(raw) : new Uint8Array(raw)
69
+ } else {
70
+ bytes = new Uint8Array(await response.arrayBuffer())
71
+ }
47
72
 
48
- const hash = fnv1a(new Uint8Array(body))
73
+ /* Nothing to validate; hand back a fresh body (the original may have been read). */
74
+ if (bytes.byteLength === 0) {
75
+ return raw === null ? replaceResponse(response, { body: bytes, headers: response.headers }) : response
76
+ }
77
+
78
+ const hash = await digest(bytes)
49
79
  const etagValue = weak ? `W/"${hash}"` : `"${hash}"`
50
80
 
51
- const headers = new Headers(response.headers)
81
+ const headers = new Headers(response.headers as HeadersInit)
52
82
  headers.set("etag", etagValue)
53
83
 
54
- /* check If-None-Match — supports comma-separated list and wildcard per RFC 7232 */
55
- const ifNoneMatch = ctx.req.headers.get("if-none-match")
56
- if (ifNoneMatch === "*" || ifNoneMatch?.split(",").some((v) => v.trim() === etagValue)) {
57
- return replaceResponse(response, {
58
- body: null,
59
- headers,
60
- status: 304,
61
- })
84
+ if (ifNoneMatchHits(ifNoneMatch, etagValue)) {
85
+ return replaceResponse(response, { body: null, headers, status: 304 })
62
86
  }
63
87
 
64
- /* return response with ETag header and reconstituted body */
88
+ /* The original body was read (or is raw): always hand back a fresh one. */
65
89
  return replaceResponse(response, {
66
- body,
90
+ body: typeof raw === "string" ? raw : bytes,
67
91
  headers,
68
92
  status: response.status,
69
93
  })