@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/node.ts CHANGED
@@ -1,38 +1,94 @@
1
- import type { IncomingMessage, Server, ServerResponse } from "node:http"
2
- import { createServer } from "node:http"
1
+ import type { IncomingMessage, Server } from "node:http"
2
+ import { createServer, ServerResponse } from "node:http"
3
+ import type { Socket } from "node:net"
3
4
  import { Readable, type Duplex } from "node:stream"
4
5
  import { pipeline } from "node:stream/promises"
5
6
  import type { Honey } from "./index.ts"
7
+ import { bodyKind, rawBodyOf } from "./body-kind.ts"
6
8
  import { isHoneyResponse } from "./honey-response.ts"
7
- import { incomingToNodeRequest } from "./node-request.ts"
9
+ import {
10
+ ABORT_REQUEST,
11
+ BODY_OVERFLOW,
12
+ DEFAULT_MAX_REQUEST_BODY,
13
+ incomingToNodeRequest,
14
+ nodeRequestUrl,
15
+ type NodeRequestOptions,
16
+ } from "./node-request.ts"
17
+ import type { WSAdapter } from "./ws/cloudflare.ts"
18
+ import type { NodeUpgradeData } from "./ws/node.ts"
8
19
 
9
- type ServeOptions<TEnv> = {
20
+ export type NodeServeOptions<TEnv> = {
10
21
  env: TEnv
11
22
  hostname?: string
12
23
  port?: number
24
+ /**
25
+ * Largest request body a read accepts, in bytes. A bigger one fails the read with 413 and the
26
+ * connection is closed. `0` disables the cap. Default 128 MiB, as on Bun.
27
+ */
28
+ maxRequestBodySize?: number
29
+ /** Node's `server.headersTimeout`, in ms (Node's default: 60 000). */
30
+ headersTimeout?: number
31
+ /** Node's `server.requestTimeout`, in ms (Node's default: 300 000). */
32
+ requestTimeout?: number
33
+ /** Node's `server.keepAliveTimeout`, in ms (Node's default: 5 000). */
34
+ keepAliveTimeout?: number
35
+ /**
36
+ * How long an upgrade request may take to become a WebSocket (or be answered), in ms.
37
+ * Default 30 000.
38
+ */
39
+ upgradeTimeout?: number
40
+ /** Errors the adapter could not hand to the app (it then answers 500). Default: `console.error`. */
41
+ onError?: (error: unknown) => void
13
42
  }
14
43
 
15
44
  /** Buffer known-size bodies up to this many bytes, then `res.end(buf)`. */
16
45
  const BUFFER_BODY_MAX = 256 * 1024
17
- const RAW_BODY = Symbol.for("honey.rawBody")
46
+ const DEFAULT_UPGRADE_TIMEOUT = 30_000
47
+
48
+ /**
49
+ * Hop-by-hop headers (RFC 9110 §7.6.1): they describe one connection, so a response from
50
+ * somewhere else (a proxied `fetch()`) must not carry them onto this one. Node writes its own.
51
+ */
52
+ const HOP_BY_HOP = new Set([
53
+ "connection",
54
+ "keep-alive",
55
+ "proxy-connection",
56
+ "te",
57
+ "trailer",
58
+ "transfer-encoding",
59
+ "upgrade",
60
+ ])
18
61
 
19
62
  // Node's `Readable.fromWeb` wants `node:stream/web` streams; DOM/Bun brands do not overlap.
20
63
  function asNodeWebStream(stream: ReadableStream<Uint8Array>): import("node:stream/web").ReadableStream {
21
64
  return stream as unknown as import("node:stream/web").ReadableStream
22
65
  }
23
66
 
24
- function incomingToRequest(req: IncomingMessage): Request {
25
- return incomingToNodeRequest(req)
67
+ /** `null` when the request target or `Host` is invalid (see `nodeRequestUrl`); the caller answers 400. */
68
+ function incomingToRequest(req: IncomingMessage, opts: NodeRequestOptions): Request | null {
69
+ const url = nodeRequestUrl(req)
70
+ return url === null ? null : incomingToNodeRequest(req, url, opts)
26
71
  }
27
72
 
28
73
  function collectNodeHeaders(response: Response, extra?: Record<string, string>): Record<string, string | string[]> {
29
74
  const headerObj: Record<string, string | string[]> = {}
30
75
  let hasSetCookie = false
76
+ /* headers a `Connection` header names are hop-by-hop too */
77
+ const named = response.headers.get("connection")
78
+ const dropNamed =
79
+ named === null
80
+ ? null
81
+ : named
82
+ .toLowerCase()
83
+ .split(",")
84
+ .map((t) => t.trim())
31
85
  response.headers.forEach((value, key) => {
32
86
  if (key === "set-cookie") {
33
87
  hasSetCookie = true
34
88
  return
35
89
  }
90
+ if (HOP_BY_HOP.has(key) && !(key === "upgrade" && response.status === 101)) return
91
+ if (dropNamed !== null && dropNamed.includes(key)) return
36
92
  headerObj[key] = value
37
93
  })
38
94
  if (hasSetCookie) {
@@ -43,25 +99,40 @@ function collectNodeHeaders(response: Response, extra?: Record<string, string>):
43
99
  return headerObj
44
100
  }
45
101
 
102
+ /**
103
+ * Buffer a body only when its size is known and small: a body Honey built in memory, or one
104
+ * that declares `content-length`. Everything else (SSE, `generate()`, a proxied stream, an
105
+ * untagged `new Response(stream)`) is piped as it is produced, whatever its content type.
106
+ */
46
107
  function shouldBufferBody(response: Response): boolean {
47
- const rawLen = response.headers.get("content-length")
48
- if (rawLen !== null) {
49
- const len = Number(rawLen)
50
- return Number.isFinite(len) && len <= BUFFER_BODY_MAX
108
+ if (bodyKind(response) !== "buffered") return false
109
+ const len = Number(response.headers.get("content-length"))
110
+ return Number.isFinite(len) && len <= BUFFER_BODY_MAX
111
+ }
112
+
113
+ /**
114
+ * Pipe a body to the socket. When the response closes first (client gone, shutdown), the
115
+ * reader is cancelled so the producer stops; nothing is left reading.
116
+ */
117
+ async function pipeBody(stream: ReadableStream<Uint8Array>, res: ServerResponse): Promise<void> {
118
+ const readable = Readable.fromWeb(asNodeWebStream(stream))
119
+ const onClose = (): void => {
120
+ if (!res.writableFinished) readable.destroy()
121
+ }
122
+ res.once("close", onClose)
123
+ try {
124
+ await pipeline(readable, res)
125
+ } catch {
126
+ if (!res.destroyed) res.destroy()
127
+ /* `destroy()` cancels the web reader; this covers a stream that errored on its own */
128
+ stream.cancel().catch(() => {})
129
+ } finally {
130
+ res.off("close", onClose)
51
131
  }
52
- const ct = response.headers.get("content-type") ?? ""
53
- if (ct.startsWith("text/event-stream")) return false
54
- return (
55
- ct.startsWith("application/json") ||
56
- ct.startsWith("text/plain") ||
57
- ct.startsWith("text/html") ||
58
- ct.startsWith("text/csv") ||
59
- ct.startsWith("application/xml")
60
- )
61
132
  }
62
133
 
63
- function rawBodyOf(response: Response): string | Uint8Array | undefined {
64
- return (response as Response & { [RAW_BODY]?: string | Uint8Array })[RAW_BODY]
134
+ function statusMessage(response: Response): string | undefined {
135
+ return response.statusText === "" ? undefined : response.statusText
65
136
  }
66
137
 
67
138
  async function responseToNode(response: Response, res: ServerResponse): Promise<void> {
@@ -80,25 +151,25 @@ async function responseToNode(response: Response, res: ServerResponse): Promise<
80
151
  return
81
152
  }
82
153
  res.writeHead(response.status, response.plainHeaders)
83
- try {
84
- await pipeline(Readable.fromWeb(asNodeWebStream(stream)), res)
85
- } catch {
86
- if (!res.destroyed) res.destroy()
87
- }
154
+ await pipeBody(stream, res)
88
155
  return
89
156
  }
90
157
 
91
158
  const raw = rawBodyOf(response)
92
159
  if (typeof raw === "string" || raw instanceof Uint8Array) {
93
160
  const byteLength = typeof raw === "string" ? Buffer.byteLength(raw) : raw.byteLength
94
- res.writeHead(response.status, collectNodeHeaders(response, { "content-length": String(byteLength) }))
161
+ res.writeHead(
162
+ response.status,
163
+ statusMessage(response),
164
+ collectNodeHeaders(response, { "content-length": String(byteLength) }),
165
+ )
95
166
  res.end(raw)
96
167
  return
97
168
  }
98
169
 
99
170
  const body = response.body
100
171
  if (body === null) {
101
- res.writeHead(response.status, collectNodeHeaders(response))
172
+ res.writeHead(response.status, statusMessage(response), collectNodeHeaders(response))
102
173
  res.end()
103
174
  return
104
175
  }
@@ -106,17 +177,79 @@ async function responseToNode(response: Response, res: ServerResponse): Promise<
106
177
  if (shouldBufferBody(response)) {
107
178
  const buf = Buffer.from(await response.arrayBuffer())
108
179
  if (res.destroyed) return
109
- res.writeHead(response.status, collectNodeHeaders(response, { "content-length": String(buf.byteLength) }))
180
+ res.writeHead(
181
+ response.status,
182
+ statusMessage(response),
183
+ collectNodeHeaders(response, { "content-length": String(buf.byteLength) }),
184
+ )
110
185
  res.end(buf)
111
186
  return
112
187
  }
113
188
 
114
- res.writeHead(response.status, collectNodeHeaders(response))
115
- try {
116
- await pipeline(Readable.fromWeb(asNodeWebStream(body)), res)
117
- } catch {
118
- if (!res.destroyed) res.destroy()
119
- }
189
+ const headers = collectNodeHeaders(response)
190
+ /* a stream's declared length is never trusted (`fetch()` keeps the encoded length of a body it
191
+ * decoded): Node chunks it, so a wrong length cannot truncate it or desynchronize keep-alive */
192
+ delete headers["content-length"]
193
+ res.writeHead(response.status, statusMessage(response), headers)
194
+ await pipeBody(body, res)
195
+ }
196
+
197
+ function isWebSocketUpgrade(req: IncomingMessage): boolean {
198
+ return req.headers.upgrade?.toLowerCase() === "websocket"
199
+ }
200
+
201
+ /**
202
+ * The body of a request that came in on Node's `'upgrade'` path but is served as a normal
203
+ * request. Node with `shouldUpgradeCallback` never sends those there; before that,
204
+ * some versions leave the body on the socket (the first bytes in `head`). Only a declared
205
+ * `content-length` can be read back that way.
206
+ */
207
+ function upgradeBodySource(
208
+ req: IncomingMessage,
209
+ socket: Duplex,
210
+ head: Buffer,
211
+ ): AsyncIterable<Buffer> | null | "unsupported" {
212
+ /* newer Node parses the body into `req` itself */
213
+ if (!req.complete) return null
214
+ if (req.headers["transfer-encoding"] !== undefined) return "unsupported"
215
+ const declared = Number(req.headers["content-length"] ?? 0)
216
+ if (!Number.isSafeInteger(declared) || declared <= 0) return null
217
+ return (async function* () {
218
+ let remaining = declared
219
+ if (head.byteLength > 0) {
220
+ const first = head.subarray(0, remaining)
221
+ remaining -= first.byteLength
222
+ yield first
223
+ }
224
+ if (remaining <= 0) return
225
+ for await (const chunk of socket as AsyncIterable<Buffer>) {
226
+ const part = chunk.subarray(0, remaining)
227
+ remaining -= part.byteLength
228
+ yield part
229
+ if (remaining <= 0) return
230
+ }
231
+ })()
232
+ }
233
+
234
+ /** Answer on a socket Node handed over through `'upgrade'`, with a real `ServerResponse`. */
235
+ function responseOnSocket(req: IncomingMessage, socket: Socket): ServerResponse {
236
+ const res = new ServerResponse(req)
237
+ /* the connection ends with this response */
238
+ res.shouldKeepAlive = false
239
+ res.assignSocket(socket)
240
+ res.once("finish", () => {
241
+ res.detachSocket(socket)
242
+ socket.end()
243
+ })
244
+ return res
245
+ }
246
+
247
+ /** A response written before any Request exists (bad target or `Host`): plain text, then close. */
248
+ function writeRawStatus(socket: Duplex, status: 400 | 503, text: string): void {
249
+ if (socket.destroyed) return
250
+ socket.end(
251
+ `HTTP/1.1 ${status} ${text}\r\nconnection: close\r\ncontent-type: text/plain\r\ncontent-length: ${text.length}\r\n\r\n${text}`,
252
+ )
120
253
  }
121
254
 
122
255
  export type HoneyServer = Server & {
@@ -125,83 +258,193 @@ export type HoneyServer = Server & {
125
258
 
126
259
  export function serve<TEnv>(
127
260
  app: Honey<TEnv, unknown, unknown, unknown, unknown, string, string>,
128
- options: ServeOptions<TEnv>,
261
+ options: NodeServeOptions<TEnv>,
129
262
  ): HoneyServer {
130
263
  const env = options.env
131
- let inflight = 0
264
+ const requestOpts: NodeRequestOptions = { maxBodySize: options.maxRequestBodySize ?? DEFAULT_MAX_REQUEST_BODY }
265
+ const upgradeTimeout = options.upgradeTimeout ?? DEFAULT_UPGRADE_TIMEOUT
266
+ const reportError =
267
+ options.onError ??
268
+ ((err: unknown): void => {
269
+ console.error("honey: request failed in the Node adapter", err)
270
+ })
271
+ /* in-flight requests and their responses: shutdown aborts their signals */
272
+ const inflight = new Map<ServerResponse, Request>()
273
+ /* sockets that became WebSockets: shutdown closes them */
274
+ const upgraded = new Set<Duplex>()
132
275
  let draining = false
133
- let drainResolve: (() => void) | null = null
134
276
 
135
- const server = createServer(async (req, res) => {
277
+ const abort = (request: Request, reason: unknown): void => {
278
+ ;(request as unknown as { [ABORT_REQUEST]?: (r?: unknown) => void })[ABORT_REQUEST]?.(reason)
279
+ }
280
+ const wsAdapter = (): WSAdapter | null =>
281
+ (app as unknown as { _graph: { settings: { wsAdapter: WSAdapter | null } } })._graph.settings.wsAdapter
282
+
283
+ /** One HTTP request, whether it came in normally or on the `'upgrade'` path. */
284
+ const handle = async (
285
+ req: IncomingMessage,
286
+ res: ServerResponse,
287
+ bodySource: AsyncIterable<Buffer> | null = null,
288
+ ): Promise<void> => {
136
289
  if (draining) {
137
- res.writeHead(503)
290
+ res.writeHead(503, { connection: "close" })
138
291
  res.end("Service Unavailable")
139
292
  return
140
293
  }
141
- inflight++
294
+ let request: Request | null = null
142
295
  try {
143
- const request = incomingToRequest(req)
296
+ request = incomingToRequest(req, bodySource === null ? requestOpts : { ...requestOpts, bodySource })
297
+ if (request === null) {
298
+ res.writeHead(400, { connection: "close", "content-type": "text/plain" })
299
+ res.end("Bad Request")
300
+ return
301
+ }
302
+ const tracked = request
303
+ inflight.set(res, tracked)
304
+ /* the response closed before it finished: the client left (or shutdown cut it) */
305
+ res.once("close", () => {
306
+ if (!res.writableFinished) abort(tracked, new DOMException("The client disconnected", "AbortError"))
307
+ })
144
308
  const maybe = app.fetch(request, env)
145
309
  const response = maybe instanceof Promise ? await maybe : maybe
310
+ /* while draining, tell keep-alive clients this connection ends with the response */
311
+ if (draining) res.shouldKeepAlive = false
312
+ /* a body over the cap was left unread on the socket: never reuse the connection */
313
+ if ((request as unknown as { [BODY_OVERFLOW]?: boolean })[BODY_OVERFLOW] === true) res.shouldKeepAlive = false
146
314
  await responseToNode(response, res)
147
- } catch {
315
+ } catch (err) {
316
+ reportError(err)
148
317
  if (!res.headersSent) {
149
- res.writeHead(500)
318
+ res.writeHead(500, { "content-type": "text/plain" })
150
319
  res.end("Internal Server Error")
151
320
  } else if (!res.destroyed) {
152
321
  res.destroy()
153
322
  }
154
323
  } finally {
155
- inflight--
156
- if (draining && inflight === 0) {
157
- drainResolve?.()
158
- }
324
+ if (request !== null) inflight.delete(res)
325
+ /* a keep-alive connection that just went idle would hold `close()` open until it times out */
326
+ if (draining) setImmediate(() => server.closeIdleConnections())
159
327
  }
160
- })
328
+ }
329
+
330
+ const server = createServer(
331
+ {
332
+ /* only a WebSocket handshake takes the upgrade path; `Upgrade: h2c` and the like are
333
+ * served as the normal requests they are (where Node has `shouldUpgradeCallback`; older Node ignores the option) */
334
+ shouldUpgradeCallback: isWebSocketUpgrade,
335
+ } as Parameters<typeof createServer>[0],
336
+ (req, res) => {
337
+ void handle(req, res)
338
+ },
339
+ )
340
+ if (options.headersTimeout !== undefined) server.headersTimeout = options.headersTimeout
341
+ if (options.requestTimeout !== undefined) server.requestTimeout = options.requestTimeout
342
+ if (options.keepAliveTimeout !== undefined) server.keepAliveTimeout = options.keepAliveTimeout
161
343
 
162
- /* WebSocket upgrade handling */
163
- server.on("upgrade", async (req: IncomingMessage, socket: Duplex, head: Buffer) => {
344
+ /** A WebSocket handshake, or (on Node without `shouldUpgradeCallback`) any request with `Upgrade`. */
345
+ const handleUpgrade = async (req: IncomingMessage, socket: Socket, head: Buffer): Promise<void> => {
346
+ if (!isWebSocketUpgrade(req)) {
347
+ const source = upgradeBodySource(req, socket, head)
348
+ if (source === "unsupported") {
349
+ writeRawStatus(socket, 400, "Bad Request")
350
+ return
351
+ }
352
+ socket.setTimeout(0)
353
+ await handle(req, responseOnSocket(req, socket), source)
354
+ return
355
+ }
356
+ if (draining) {
357
+ writeRawStatus(socket, 503, "Service Unavailable")
358
+ return
359
+ }
360
+ const request = incomingToRequest(req, requestOpts)
361
+ if (request === null) {
362
+ writeRawStatus(socket, 400, "Bad Request")
363
+ return
364
+ }
365
+ socket.once("close", () => {
366
+ abort(request, new DOMException("The client disconnected", "AbortError"))
367
+ })
368
+ const state: NodeUpgradeData = { head, req, socket, upgraded: false }
369
+ const envWithUpgrade = { ...env, __nodeUpgrade: state } as TEnv
370
+ let response: Response
164
371
  try {
165
- const request = incomingToRequest(req)
166
- const envWithUpgrade = { ...env, __nodeUpgrade: { head, req, socket } } as TEnv
167
372
  const maybe = app.fetch(request, envWithUpgrade)
168
- const response = maybe instanceof Promise ? await maybe : maybe
169
-
170
- if (response.status !== 101) {
171
- const text = await response.text()
172
- socket.write(
173
- `HTTP/1.1 ${response.status} ${response.statusText ?? "Error"}\r\n` +
174
- "content-type: application/json\r\n" +
175
- `\r\n${text}`,
176
- )
177
- socket.destroy()
373
+ response = maybe instanceof Promise ? await maybe : maybe
374
+ } catch (err) {
375
+ reportError(err)
376
+ /* once ws wrote the 101 the socket is a WebSocket: an HTTP status would corrupt it */
377
+ if (!state.upgraded) {
378
+ socket.setTimeout(0)
379
+ const res = responseOnSocket(req, socket)
380
+ res.writeHead(500, { "content-type": "text/plain" })
381
+ res.end("Internal Server Error")
178
382
  }
179
- } catch {
180
- socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n")
383
+ return
384
+ }
385
+ if (state.upgraded) {
386
+ socket.setTimeout(0)
387
+ upgraded.add(socket)
388
+ socket.once("close", () => upgraded.delete(socket))
389
+ return
390
+ }
391
+ if (socket.destroyed) {
392
+ await response.body?.cancel().catch(() => {})
393
+ return
394
+ }
395
+ /* not upgraded (auth rejected, 404, 426, an SSE route…): a real response, streamed, every header kept */
396
+ socket.setTimeout(0)
397
+ try {
398
+ await responseToNode(response, responseOnSocket(req, socket))
399
+ } catch (err) {
400
+ reportError(err)
181
401
  socket.destroy()
182
402
  }
403
+ }
404
+
405
+ server.on("upgrade", (req: IncomingMessage, duplex: Duplex, head: Buffer) => {
406
+ const socket = duplex as Socket
407
+ /* first: a reset while the app is still deciding must not become an uncaught 'error' event */
408
+ socket.on("error", () => {
409
+ socket.destroy()
410
+ })
411
+ if (upgradeTimeout > 0) socket.setTimeout(upgradeTimeout, () => socket.destroy())
412
+ handleUpgrade(req, socket, head).catch((err: unknown) => {
413
+ reportError(err)
414
+ socket.destroy()
415
+ })
183
416
  })
184
417
 
185
418
  server.listen(options?.port ?? 0, options?.hostname)
186
419
 
187
420
  const honeyServer = server as HoneyServer
421
+ /**
422
+ * Stop accepting connections and let in-flight requests finish. Responses already streaming
423
+ * (SSE, `generate()`) never finish on their own, so their `ctx.signal` aborts now and they
424
+ * end; WebSockets get a 1001 close. Handlers still working get until `timeout`; then their
425
+ * signal aborts and every connection, WebSockets included, is closed.
426
+ */
188
427
  honeyServer.shutdown = (timeout?: number): Promise<void> => {
189
428
  draining = true
429
+ const shutdownReason = (): DOMException => new DOMException("The server is shutting down", "AbortError")
190
430
 
191
431
  return new Promise<void>((resolve) => {
432
+ let timer: ReturnType<typeof setTimeout> | undefined
433
+ /* fires once every connection has closed */
192
434
  server.close(() => {
193
- if (inflight === 0) {
194
- resolve()
195
- return
196
- }
197
- drainResolve = resolve
435
+ if (timer !== undefined) clearTimeout(timer)
436
+ resolve()
198
437
  })
199
-
438
+ server.closeIdleConnections()
439
+ for (const [res, request] of inflight) {
440
+ if (res.headersSent) abort(request, shutdownReason())
441
+ }
442
+ wsAdapter()?.closeAll?.(1001, "server shutting down")
200
443
  if (timeout !== undefined) {
201
- setTimeout(() => {
202
- drainResolve = null
444
+ timer = setTimeout(() => {
445
+ for (const request of inflight.values()) abort(request, shutdownReason())
446
+ for (const socket of upgraded) socket.destroy()
203
447
  server.closeAllConnections()
204
- resolve()
205
448
  }, timeout)
206
449
  }
207
450
  })
@@ -1,4 +1,5 @@
1
- import type { RouteHandler, TreeNode, WSRouteHandler } from "../tree.ts"
1
+ import { parsePattern, toOpenApiTemplate, UNNAMED_WILDCARD } from "../pattern.ts"
2
+ import type { RouteHandler, WSRouteHandler } from "../tree.ts"
2
3
  import type { InputSchemaEntry, StandardSchemaLike } from "../types.ts"
3
4
 
4
5
  export type CollectedRoute = {
@@ -12,59 +13,33 @@ export type CollectedWSRoute = {
12
13
  path: string
13
14
  }
14
15
 
16
+ /** Path parameter names of a route pattern, as OpenAPI names them (wildcards included). */
15
17
  export function extractParams(path: string): string[] {
16
- const params: string[] = []
17
- for (const seg of path.split("/")) {
18
- if (seg.startsWith(":")) {
19
- params.push(seg.endsWith("?") ? seg.slice(1, -1) : seg.slice(1))
20
- }
18
+ const out: string[] = []
19
+ for (const seg of parsePattern(path)) {
20
+ if (seg.k === "param") out.push(seg.n)
21
+ else if (seg.k === "wildcard") out.push(seg.n === UNNAMED_WILDCARD ? "wildcard" : seg.n)
21
22
  }
22
- return params
23
+ return out
23
24
  }
24
25
 
25
26
  export function toOpenApiPath(path: string): string {
26
- return path.replace(/:(\w+)\??/g, "{$1}")
27
+ return toOpenApiTemplate(parsePattern(path))
27
28
  }
28
29
 
29
- export function walkTree(
30
- node: TreeNode,
31
- currentPath: string,
32
- routes: CollectedRoute[],
33
- includeSkipped?: boolean,
34
- ): void {
35
- if (node.m !== null) {
36
- for (const [method, handler] of Object.entries(node.m)) {
37
- if (handler._skip && !includeSkipped) continue
38
- routes.push({ handler, method, path: currentPath || "/" })
39
- }
40
- }
41
-
42
- for (const [seg, child] of Object.entries(node.s)) {
43
- walkTree(child, `${currentPath}/${seg}`, routes, includeSkipped)
44
- }
45
-
46
- if (node.d !== null) {
47
- walkTree(node.d.c, `${currentPath}/:${node.d.n}`, routes, includeSkipped)
48
- }
30
+ type RouteSource = {
31
+ _collectRoutes(includeSkipped?: boolean): CollectedRoute[]
32
+ _collectWsRoutes(): CollectedWSRoute[]
33
+ }
49
34
 
50
- if (node.w !== null) {
51
- for (const [method, handler] of Object.entries(node.w.m)) {
52
- if (handler._skip && !includeSkipped) continue
53
- routes.push({ handler, method, path: `${currentPath}/*${node.w.n}` })
54
- }
55
- }
35
+ /** Every served HTTP route of an app, one entry per concrete path. */
36
+ export function collectRoutes(app: unknown, includeSkipped?: boolean): CollectedRoute[] {
37
+ return (app as RouteSource)._collectRoutes(includeSkipped)
56
38
  }
57
39
 
58
- export function walkWSRoutes(node: TreeNode, currentPath: string, routes: CollectedWSRoute[]): void {
59
- if (node.ws !== null) {
60
- routes.push({ handler: node.ws, path: currentPath || "/" })
61
- }
62
- for (const [seg, child] of Object.entries(node.s)) {
63
- walkWSRoutes(child, `${currentPath}/${seg}`, routes)
64
- }
65
- if (node.d !== null) {
66
- walkWSRoutes(node.d.c, `${currentPath}/:${node.d.n}`, routes)
67
- }
40
+ /** Every websocket route of an app, one entry per concrete path. */
41
+ export function collectWsRoutes(app: unknown): CollectedWSRoute[] {
42
+ return (app as RouteSource)._collectWsRoutes()
68
43
  }
69
44
 
70
45
  export function unwrapEntry(entry: InputSchemaEntry): StandardSchemaLike {
@@ -0,0 +1,89 @@
1
+ import type { TypedResponse } from "../response.ts"
2
+
3
+ /**
4
+ * Pinned docs UI assets with Subresource Integrity. The page runs on the API's own origin, so
5
+ * an unpinned CDN script could read anything the origin can; a pinned version with an
6
+ * integrity hash can only ever be these bytes.
7
+ */
8
+ export const SWAGGER_UI = {
9
+ css: {
10
+ integrity: "sha384-Ov4/wv3j2bmct8cDc5X4ngJZohVPzEmc6uDPH8WeljUxO5vtoykvMEfbu9Vh6RaW",
11
+ url: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.1/swagger-ui.css",
12
+ },
13
+ js: {
14
+ integrity: "sha384-ZPehFMQommnnuaZ4rpxgkgTT2DKFVp4hZC/7pLit+9Lek9T1YGSo23eHFbvNkXkw",
15
+ url: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.1/swagger-ui-bundle.js",
16
+ },
17
+ } as const
18
+
19
+ export const SCALAR = {
20
+ js: {
21
+ integrity: "sha384-kYDGzV91Jnn3TbHINV3nt54riK2uMJDfN5Al8dAkz4FssELTBWbD8rgw32sTKfOi",
22
+ url: "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.73.1/dist/browser/standalone.js",
23
+ },
24
+ } as const
25
+
26
+ const CDN_ORIGIN = "https://cdn.jsdelivr.net"
27
+
28
+ /* built from char codes: a formatter may turn an escape in a literal into the raw separator */
29
+ const LINE_SEPARATOR = String.fromCharCode(0x2028)
30
+ const PARAGRAPH_SEPARATOR = String.fromCharCode(0x2029)
31
+
32
+ /**
33
+ * JSON for an inline `<script>`: `<` cannot end the element, and U+2028/U+2029 cannot end a
34
+ * string literal in older engines.
35
+ */
36
+ export function inlineJson(value: unknown): string {
37
+ return JSON.stringify(value)
38
+ .replace(/</g, "\\u003c")
39
+ .replace(/>/g, "\\u003e")
40
+ .replace(/&/g, "\\u0026")
41
+ .replaceAll(LINE_SEPARATOR, "\\u2028")
42
+ .replaceAll(PARAGRAPH_SEPARATOR, "\\u2029")
43
+ }
44
+
45
+ export function escapeAttribute(value: string): string {
46
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
47
+ }
48
+
49
+ function nonce(): string {
50
+ const bytes = new Uint8Array(16)
51
+ crypto.getRandomValues(bytes)
52
+ let s = ""
53
+ for (const b of bytes) s += String.fromCharCode(b)
54
+ return btoa(s)
55
+ }
56
+
57
+ type DocsCtx = { res: { html(sk: "ok", body: string, opts?: { headers?: Record<string, string> }): TypedResponse } }
58
+
59
+ /**
60
+ * A docs page handler: a fresh script nonce per response, a CSP that allows only that inline
61
+ * script and the pinned CDN, and `nosniff`.
62
+ */
63
+ export function docsPage(render: (nonce: string) => string): (ctx: DocsCtx) => TypedResponse {
64
+ const handler = (ctx: DocsCtx) => {
65
+ const n = nonce()
66
+ const csp = [
67
+ "default-src 'none'",
68
+ `script-src 'nonce-${n}' ${CDN_ORIGIN}`,
69
+ `style-src 'self' 'unsafe-inline' ${CDN_ORIGIN}`,
70
+ "img-src 'self' data: https:",
71
+ "font-src 'self' data: https:",
72
+ "connect-src 'self' https:",
73
+ "worker-src blob:",
74
+ "base-uri 'none'",
75
+ "form-action 'none'",
76
+ "frame-ancestors 'self'",
77
+ "object-src 'none'",
78
+ ].join("; ")
79
+ return ctx.res.html("ok", render(n), {
80
+ headers: {
81
+ "content-security-policy": csp,
82
+ "referrer-policy": "no-referrer",
83
+ "x-content-type-options": "nosniff",
84
+ },
85
+ })
86
+ }
87
+ Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
88
+ return handler
89
+ }