@lovrozagar/honey 0.7.3 → 1.0.0-rc.1

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 (1003) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -1969
  3. package/bin/honey.js +87 -27
  4. package/dist/adapters/bun/serve.d.ts +18 -0
  5. package/dist/adapters/bun/serve.js +89 -0
  6. package/dist/adapters/fetch-request.d.ts +20 -0
  7. package/dist/adapters/fetch-request.js +29 -0
  8. package/dist/adapters/node/headers.d.ts +27 -0
  9. package/dist/adapters/node/headers.js +86 -0
  10. package/dist/adapters/node/request.d.ts +16 -0
  11. package/dist/adapters/node/request.js +214 -0
  12. package/dist/adapters/node/response.d.ts +11 -0
  13. package/dist/adapters/node/response.js +126 -0
  14. package/dist/adapters/node/serve.d.ts +39 -0
  15. package/dist/adapters/node/serve.js +205 -0
  16. package/dist/adapters/node/url.d.ts +13 -0
  17. package/dist/adapters/node/url.js +87 -0
  18. package/dist/adapters/serve.d.ts +36 -0
  19. package/dist/adapters/serve.js +52 -0
  20. package/dist/build/artifacts.d.ts +34 -0
  21. package/dist/build/artifacts.js +123 -0
  22. package/dist/build/cli-args.d.ts +30 -0
  23. package/dist/build/cli-args.js +97 -0
  24. package/dist/build/cli.d.ts +11 -0
  25. package/dist/build/cli.js +150 -0
  26. package/dist/build/config.d.ts +102 -0
  27. package/dist/build/config.js +89 -0
  28. package/dist/build/generate.d.ts +19 -0
  29. package/dist/build/generate.js +80 -0
  30. package/dist/build/glob.d.ts +8 -0
  31. package/dist/build/glob.js +97 -0
  32. package/dist/build/index.d.ts +3 -58
  33. package/dist/build/index.js +3 -173
  34. package/dist/build/init.d.ts +3 -0
  35. package/dist/build/init.js +131 -0
  36. package/dist/build/loaders.d.ts +33 -0
  37. package/dist/build/loaders.js +105 -0
  38. package/dist/build/plugin.d.ts +52 -0
  39. package/dist/build/plugin.js +146 -0
  40. package/dist/build/source.d.ts +21 -0
  41. package/dist/build/source.js +100 -0
  42. package/dist/build/targets.d.ts +59 -0
  43. package/dist/build/targets.js +107 -0
  44. package/dist/context/context.d.ts +111 -0
  45. package/dist/context/context.js +213 -0
  46. package/dist/context/cookie-sign.d.ts +38 -0
  47. package/dist/context/cookie-sign.js +113 -0
  48. package/dist/context/cookie.d.ts +28 -0
  49. package/dist/context/cookie.js +142 -0
  50. package/dist/context/peer.d.ts +34 -0
  51. package/dist/context/peer.js +190 -0
  52. package/dist/errors/error.d.ts +60 -0
  53. package/dist/errors/error.js +84 -0
  54. package/dist/errors/errors.d.ts +45 -0
  55. package/dist/errors/errors.js +31 -0
  56. package/dist/errors/on-error.d.ts +10 -0
  57. package/dist/errors/on-error.js +204 -0
  58. package/dist/errors/problem-json.d.ts +14 -0
  59. package/dist/errors/problem-json.js +58 -0
  60. package/dist/errors/projector.d.ts +50 -0
  61. package/dist/errors/projector.js +107 -0
  62. package/dist/i18n/error-i18n.d.ts +26 -0
  63. package/dist/i18n/error-i18n.js +77 -0
  64. package/dist/i18n/i18n.d.ts +21 -0
  65. package/dist/i18n/i18n.js +40 -0
  66. package/dist/i18n/icu.d.ts +29 -0
  67. package/dist/i18n/icu.js +170 -0
  68. package/dist/i18n/index.d.ts +4 -0
  69. package/dist/i18n/index.js +3 -0
  70. package/dist/index.d.ts +30 -746
  71. package/dist/index.js +12 -2364
  72. package/dist/input/accepts.d.ts +5 -0
  73. package/dist/input/accepts.js +105 -0
  74. package/dist/input/binary.d.ts +20 -0
  75. package/dist/input/binary.js +40 -0
  76. package/dist/input/body-kind.d.ts +15 -0
  77. package/dist/input/body-kind.js +47 -0
  78. package/dist/input/body-limit.d.ts +21 -0
  79. package/dist/input/body-limit.js +99 -0
  80. package/dist/input/cookies.d.ts +7 -0
  81. package/dist/input/cookies.js +8 -0
  82. package/dist/input/form.d.ts +10 -0
  83. package/dist/input/form.js +24 -0
  84. package/dist/input/headers.d.ts +7 -0
  85. package/dist/input/headers.js +8 -0
  86. package/dist/input/input.d.ts +91 -0
  87. package/dist/input/input.js +88 -0
  88. package/dist/input/json.d.ts +10 -0
  89. package/dist/input/json.js +22 -0
  90. package/dist/input/params.d.ts +7 -0
  91. package/dist/input/params.js +8 -0
  92. package/dist/input/query.d.ts +10 -0
  93. package/dist/input/query.js +17 -0
  94. package/dist/mcp/mount.d.ts +116 -0
  95. package/dist/mcp/mount.js +45 -0
  96. package/dist/mcp/oauth.d.ts +14 -0
  97. package/dist/mcp/oauth.js +88 -0
  98. package/dist/mcp/uploads.d.ts +36 -0
  99. package/dist/mcp/uploads.js +171 -0
  100. package/dist/mcp/url-files.d.ts +32 -0
  101. package/dist/mcp/url-files.js +75 -0
  102. package/dist/meta/meta-spec-apply.d.ts +25 -0
  103. package/dist/meta/meta-spec-apply.js +83 -0
  104. package/dist/meta/meta-spec-merge.d.ts +103 -0
  105. package/dist/meta/meta-spec-merge.js +50 -0
  106. package/dist/meta/meta-spec-validate.d.ts +61 -0
  107. package/dist/meta/meta-spec-validate.js +166 -0
  108. package/dist/meta/meta-spec.d.ts +49 -0
  109. package/dist/meta/meta-spec.js +197 -0
  110. package/dist/middleware/compress.d.ts +15 -0
  111. package/dist/middleware/compress.js +93 -0
  112. package/dist/middleware/cors.d.ts +18 -0
  113. package/dist/middleware/cors.js +80 -0
  114. package/dist/middleware/csrf.d.ts +11 -0
  115. package/dist/middleware/csrf.js +65 -0
  116. package/dist/middleware/ip-restrict.d.ts +10 -0
  117. package/dist/middleware/ip-restrict.js +82 -0
  118. package/dist/middleware/middleware.d.ts +53 -0
  119. package/dist/middleware/middleware.js +112 -0
  120. package/dist/middleware/powered-by.d.ts +6 -0
  121. package/dist/middleware/powered-by.js +13 -0
  122. package/dist/middleware/rate-limit.d.ts +40 -0
  123. package/dist/middleware/rate-limit.js +90 -0
  124. package/dist/middleware/request-id.d.ts +25 -0
  125. package/dist/middleware/request-id.js +30 -0
  126. package/dist/middleware/secure-headers.d.ts +18 -0
  127. package/dist/middleware/secure-headers.js +36 -0
  128. package/dist/middleware/server-timing.d.ts +8 -0
  129. package/dist/middleware/server-timing.js +52 -0
  130. package/dist/middleware/timeout.d.ts +6 -0
  131. package/dist/middleware/timeout.js +29 -0
  132. package/dist/observability/curl-logger.d.ts +46 -0
  133. package/dist/observability/curl-logger.js +175 -0
  134. package/dist/observability/request-to-curl.d.ts +17 -0
  135. package/dist/observability/request-to-curl.js +47 -0
  136. package/dist/openapi/collect.d.ts +56 -18
  137. package/dist/openapi/collect.js +109 -23
  138. package/dist/openapi/components.d.ts +9 -0
  139. package/dist/openapi/components.js +90 -0
  140. package/dist/openapi/docs-page.d.ts +20 -18
  141. package/dist/openapi/docs-page.js +28 -46
  142. package/dist/openapi/document.d.ts +33 -26
  143. package/dist/openapi/document.js +21 -780
  144. package/dist/openapi/extract-schemas.d.ts +11 -0
  145. package/dist/openapi/extract-schemas.js +31 -0
  146. package/dist/openapi/generate.d.ts +47 -0
  147. package/dist/openapi/generate.js +180 -0
  148. package/dist/openapi/manifest.d.ts +41 -0
  149. package/dist/openapi/manifest.js +49 -0
  150. package/dist/openapi/output-keys.d.ts +6 -0
  151. package/dist/openapi/output-keys.js +24 -0
  152. package/dist/openapi/paths.d.ts +35 -0
  153. package/dist/openapi/paths.js +175 -0
  154. package/dist/openapi/register.d.ts +14 -3
  155. package/dist/openapi/register.js +13 -20
  156. package/dist/openapi/responses.d.ts +44 -0
  157. package/dist/openapi/responses.js +159 -0
  158. package/dist/openapi/scalar.d.ts +4 -14
  159. package/dist/openapi/scalar.js +5 -7
  160. package/dist/openapi/security.d.ts +4 -0
  161. package/dist/openapi/security.js +25 -0
  162. package/dist/openapi/serve.d.ts +25 -9
  163. package/dist/openapi/serve.js +108 -29
  164. package/dist/openapi/servers.d.ts +19 -0
  165. package/dist/openapi/servers.js +72 -0
  166. package/dist/openapi/swagger.d.ts +4 -14
  167. package/dist/openapi/swagger.js +5 -7
  168. package/dist/openapi/tags.d.ts +20 -0
  169. package/dist/openapi/tags.js +20 -0
  170. package/dist/openapi/webhooks.d.ts +19 -0
  171. package/dist/openapi/webhooks.js +70 -0
  172. package/dist/openapi/yaml.d.ts +8 -0
  173. package/dist/openapi/yaml.js +74 -0
  174. package/dist/proxy/headers.d.ts +30 -0
  175. package/dist/proxy/headers.js +86 -0
  176. package/dist/proxy/proxy.d.ts +39 -0
  177. package/dist/proxy/proxy.js +103 -0
  178. package/dist/proxy/upstream.d.ts +30 -0
  179. package/dist/proxy/upstream.js +116 -0
  180. package/dist/realtime/bus.d.ts +10 -13
  181. package/dist/realtime/bus.js +48 -93
  182. package/dist/realtime/invoke-user.d.ts +36 -0
  183. package/dist/realtime/invoke-user.js +103 -0
  184. package/dist/realtime/realtime.d.ts +7 -0
  185. package/dist/realtime/realtime.js +40 -0
  186. package/dist/realtime/route.d.ts +33 -27
  187. package/dist/realtime/route.js +27 -43
  188. package/dist/realtime/server.d.ts +24 -28
  189. package/dist/realtime/server.js +202 -225
  190. package/dist/response/binary.d.ts +18 -0
  191. package/dist/response/binary.js +10 -0
  192. package/dist/response/builder.d.ts +73 -0
  193. package/dist/response/builder.js +214 -0
  194. package/dist/response/cache-control.d.ts +30 -0
  195. package/dist/response/cache-control.js +74 -0
  196. package/dist/response/etag.d.ts +15 -0
  197. package/dist/response/etag.js +83 -0
  198. package/dist/response/media-type.d.ts +28 -0
  199. package/dist/response/media-type.js +97 -0
  200. package/dist/response/out-headers.d.ts +31 -0
  201. package/dist/response/out-headers.js +130 -0
  202. package/dist/response/pretty-json.d.ts +10 -0
  203. package/dist/response/pretty-json.js +44 -0
  204. package/dist/response/range.d.ts +19 -0
  205. package/dist/response/range.js +98 -0
  206. package/dist/response/response-headers.d.ts +11 -0
  207. package/dist/response/response-headers.js +21 -0
  208. package/dist/response/response.d.ts +69 -0
  209. package/dist/response/response.js +208 -0
  210. package/dist/response/sse.d.ts +41 -0
  211. package/dist/response/sse.js +73 -0
  212. package/dist/response/static.d.ts +20 -0
  213. package/dist/response/static.js +181 -0
  214. package/dist/response/status.d.ts +5 -0
  215. package/dist/response/status.js +68 -0
  216. package/dist/response/with-headers.d.ts +11 -0
  217. package/dist/response/with-headers.js +47 -0
  218. package/dist/routing/app-types.d.ts +181 -0
  219. package/dist/routing/app-types.js +1 -0
  220. package/dist/routing/app.d.ts +76 -0
  221. package/dist/routing/app.js +137 -0
  222. package/dist/routing/codegen-types.d.ts +17 -0
  223. package/dist/routing/codegen-types.js +1 -0
  224. package/dist/routing/delegate.d.ts +9 -0
  225. package/dist/routing/delegate.js +75 -0
  226. package/dist/routing/dispatch.d.ts +22 -0
  227. package/dist/routing/dispatch.js +255 -0
  228. package/dist/routing/finalize.d.ts +47 -0
  229. package/dist/routing/finalize.js +169 -0
  230. package/dist/routing/gateway-tree.d.ts +19 -0
  231. package/dist/routing/gateway-tree.js +74 -0
  232. package/dist/routing/invalidate-check.d.ts +58 -0
  233. package/dist/routing/invalidate-check.js +124 -0
  234. package/dist/routing/match.d.ts +22 -0
  235. package/dist/routing/match.js +108 -0
  236. package/dist/routing/middleware-chain.d.ts +16 -0
  237. package/dist/routing/middleware-chain.js +41 -0
  238. package/dist/routing/output-types.d.ts +91 -0
  239. package/dist/routing/output-types.js +1 -0
  240. package/dist/routing/pattern.d.ts +55 -0
  241. package/dist/routing/pattern.js +219 -0
  242. package/dist/routing/record.d.ts +18 -0
  243. package/dist/routing/record.js +4 -0
  244. package/dist/routing/register.d.ts +135 -0
  245. package/dist/routing/register.js +155 -0
  246. package/dist/routing/request-path.d.ts +30 -0
  247. package/dist/routing/request-path.js +173 -0
  248. package/dist/routing/route-builder.d.ts +160 -0
  249. package/dist/routing/route-builder.js +86 -0
  250. package/dist/routing/route-tree.d.ts +18 -0
  251. package/dist/routing/route-tree.js +91 -0
  252. package/dist/routing/settings-chain.d.ts +75 -0
  253. package/dist/routing/settings-chain.js +137 -0
  254. package/dist/routing/settings.d.ts +90 -0
  255. package/dist/routing/settings.js +44 -0
  256. package/dist/routing/tap.d.ts +31 -0
  257. package/dist/routing/tap.js +77 -0
  258. package/dist/routing/tree-merge.d.ts +7 -0
  259. package/dist/routing/tree-merge.js +77 -0
  260. package/dist/routing/tree.d.ts +85 -0
  261. package/dist/routing/tree.js +196 -0
  262. package/dist/routing/types.d.ts +20 -0
  263. package/dist/routing/types.js +1 -0
  264. package/dist/routing/ws-builder.d.ts +62 -0
  265. package/dist/routing/ws-builder.js +109 -0
  266. package/dist/runtime/detect-runtime.d.ts +16 -0
  267. package/dist/runtime/detect-runtime.js +11 -0
  268. package/dist/runtime/optional-peer.d.ts +10 -0
  269. package/dist/runtime/optional-peer.js +19 -0
  270. package/dist/schema/arktype.d.ts +7 -0
  271. package/dist/schema/arktype.js +13 -0
  272. package/dist/schema/canonicalize.d.ts +8 -0
  273. package/dist/schema/canonicalize.js +19 -0
  274. package/dist/schema/effect.d.ts +7 -0
  275. package/dist/schema/effect.js +4 -0
  276. package/dist/schema/naming.d.ts +31 -0
  277. package/dist/schema/naming.js +116 -0
  278. package/dist/schema/sanitize-spec.d.ts +15 -0
  279. package/dist/schema/sanitize-spec.js +43 -0
  280. package/dist/schema/slots.d.ts +15 -0
  281. package/dist/schema/slots.js +179 -0
  282. package/dist/schema/to-json-schema.d.ts +34 -0
  283. package/dist/schema/to-json-schema.js +94 -0
  284. package/dist/schema/valibot.d.ts +4 -0
  285. package/dist/schema/valibot.js +153 -0
  286. package/dist/schema/yup.d.ts +3 -0
  287. package/dist/schema/yup.js +57 -0
  288. package/dist/schema/zod.d.ts +6 -0
  289. package/dist/schema/zod.js +192 -0
  290. package/dist/security/crypto.d.ts +7 -0
  291. package/dist/security/crypto.js +30 -0
  292. package/dist/streaming/producer-stream.d.ts +45 -0
  293. package/dist/streaming/producer-stream.js +251 -0
  294. package/dist/telemetry/fields.d.ts +43 -0
  295. package/dist/telemetry/fields.js +64 -0
  296. package/dist/telemetry/log-format.d.ts +7 -0
  297. package/dist/telemetry/log-format.js +43 -0
  298. package/dist/telemetry/logger.d.ts +44 -0
  299. package/dist/telemetry/logger.js +122 -0
  300. package/dist/telemetry/otel-subpath.d.ts +7 -0
  301. package/dist/telemetry/otel-subpath.js +5 -0
  302. package/dist/telemetry/otel.d.ts +50 -57
  303. package/dist/telemetry/otel.js +54 -130
  304. package/dist/telemetry/request-log.d.ts +22 -0
  305. package/dist/telemetry/request-log.js +83 -0
  306. package/dist/telemetry/request-trace.d.ts +123 -0
  307. package/dist/telemetry/request-trace.js +199 -0
  308. package/dist/telemetry/span-lifecycle.d.ts +11 -0
  309. package/dist/telemetry/span-lifecycle.js +114 -0
  310. package/dist/telemetry/traceparent.d.ts +40 -0
  311. package/dist/telemetry/traceparent.js +73 -0
  312. package/dist/testing/testing.d.ts +42 -0
  313. package/dist/testing/testing.js +101 -0
  314. package/dist/validation/content-type.d.ts +21 -0
  315. package/dist/validation/content-type.js +42 -0
  316. package/dist/validation/standard-schema.d.ts +41 -0
  317. package/dist/validation/standard-schema.js +5 -0
  318. package/dist/validation/validate-input.d.ts +45 -0
  319. package/dist/validation/validate-input.js +174 -0
  320. package/dist/validation/validate-output.d.ts +34 -0
  321. package/dist/validation/validate-output.js +107 -0
  322. package/dist/ws/bun.d.ts +30 -25
  323. package/dist/ws/bun.js +42 -46
  324. package/dist/ws/cloudflare.d.ts +6 -75
  325. package/dist/ws/cloudflare.js +37 -104
  326. package/dist/ws/contract.d.ts +74 -0
  327. package/dist/ws/contract.js +83 -0
  328. package/dist/ws/node.d.ts +12 -21
  329. package/dist/ws/node.js +103 -151
  330. package/dist/ws/origin.d.ts +23 -0
  331. package/dist/ws/origin.js +48 -0
  332. package/dist/ws/route.d.ts +14 -0
  333. package/dist/ws/route.js +5 -0
  334. package/dist/ws/session.d.ts +15 -6
  335. package/dist/ws/session.js +22 -23
  336. package/dist/ws/shared.d.ts +23 -33
  337. package/dist/ws/shared.js +53 -39
  338. package/package.json +169 -251
  339. package/dist/accepts.d.ts +0 -12
  340. package/dist/accepts.d.ts.map +0 -1
  341. package/dist/accepts.js +0 -126
  342. package/dist/accepts.js.map +0 -1
  343. package/dist/body-kind.d.ts +0 -26
  344. package/dist/body-kind.d.ts.map +0 -1
  345. package/dist/body-kind.js +0 -70
  346. package/dist/body-kind.js.map +0 -1
  347. package/dist/body-limit.d.ts +0 -26
  348. package/dist/body-limit.d.ts.map +0 -1
  349. package/dist/body-limit.js +0 -126
  350. package/dist/body-limit.js.map +0 -1
  351. package/dist/build/index.d.ts.map +0 -1
  352. package/dist/build/index.js.map +0 -1
  353. package/dist/cli-args.d.ts +0 -33
  354. package/dist/cli-args.d.ts.map +0 -1
  355. package/dist/cli-args.js +0 -102
  356. package/dist/cli-args.js.map +0 -1
  357. package/dist/cli-go/auth.go +0 -26
  358. package/dist/cli-go/config.go +0 -192
  359. package/dist/cli-go/errors.go +0 -75
  360. package/dist/cli-go/multipart.go +0 -79
  361. package/dist/cli-go/output.go +0 -163
  362. package/dist/cli-go/root.go +0 -30
  363. package/dist/cli-go/stream.go +0 -88
  364. package/dist/cli-go/version.go +0 -5
  365. package/dist/cli.d.ts +0 -3
  366. package/dist/cli.d.ts.map +0 -1
  367. package/dist/cli.js +0 -209
  368. package/dist/cli.js.map +0 -1
  369. package/dist/client/defaults.d.ts +0 -12
  370. package/dist/client/defaults.d.ts.map +0 -1
  371. package/dist/client/defaults.js +0 -48
  372. package/dist/client/defaults.js.map +0 -1
  373. package/dist/client/error.d.ts +0 -49
  374. package/dist/client/error.d.ts.map +0 -1
  375. package/dist/client/error.js +0 -71
  376. package/dist/client/error.js.map +0 -1
  377. package/dist/client/http.d.ts +0 -155
  378. package/dist/client/http.d.ts.map +0 -1
  379. package/dist/client/http.js +0 -571
  380. package/dist/client/http.js.map +0 -1
  381. package/dist/client/index.d.ts +0 -23
  382. package/dist/client/index.d.ts.map +0 -1
  383. package/dist/client/index.js +0 -95
  384. package/dist/client/index.js.map +0 -1
  385. package/dist/client/path.d.ts +0 -26
  386. package/dist/client/path.d.ts.map +0 -1
  387. package/dist/client/path.js +0 -163
  388. package/dist/client/path.js.map +0 -1
  389. package/dist/client/redirect-policy.d.ts +0 -12
  390. package/dist/client/redirect-policy.d.ts.map +0 -1
  391. package/dist/client/redirect-policy.js +0 -20
  392. package/dist/client/redirect-policy.js.map +0 -1
  393. package/dist/client/sdk.d.ts +0 -70
  394. package/dist/client/sdk.d.ts.map +0 -1
  395. package/dist/client/sdk.js +0 -292
  396. package/dist/client/sdk.js.map +0 -1
  397. package/dist/client/sse.d.ts +0 -23
  398. package/dist/client/sse.d.ts.map +0 -1
  399. package/dist/client/sse.js +0 -129
  400. package/dist/client/sse.js.map +0 -1
  401. package/dist/client/types.d.ts +0 -124
  402. package/dist/client/types.d.ts.map +0 -1
  403. package/dist/client/types.js +0 -2
  404. package/dist/client/types.js.map +0 -1
  405. package/dist/client/ws.d.ts +0 -22
  406. package/dist/client/ws.d.ts.map +0 -1
  407. package/dist/client/ws.js +0 -112
  408. package/dist/client/ws.js.map +0 -1
  409. package/dist/client-go/errors.go +0 -236
  410. package/dist/client-go/export.go +0 -44
  411. package/dist/client-go/invalidation.go +0 -371
  412. package/dist/client-go/nullable.go +0 -62
  413. package/dist/client-go/realtime.go +0 -354
  414. package/dist/client-go/result.go +0 -20
  415. package/dist/client-go/runtime.go +0 -975
  416. package/dist/client-go/sse.go +0 -159
  417. package/dist/client-go/transport.go +0 -495
  418. package/dist/client-go/ws.go +0 -128
  419. package/dist/client-info.d.ts +0 -36
  420. package/dist/client-info.d.ts.map +0 -1
  421. package/dist/client-info.js +0 -53
  422. package/dist/client-info.js.map +0 -1
  423. package/dist/client-mcp/README.md +0 -10
  424. package/dist/client-mcp/server.ts +0 -221
  425. package/dist/client-mcp/types.ts +0 -19
  426. package/dist/client-python/_errors.py +0 -115
  427. package/dist/client-python/_invalidation.py +0 -494
  428. package/dist/client-python/_realtime.py +0 -179
  429. package/dist/client-python/_runtime.py +0 -860
  430. package/dist/client-python/_sse.py +0 -155
  431. package/dist/client-python/_transport.py +0 -366
  432. package/dist/client-python/_ws.py +0 -159
  433. package/dist/client-python/py.typed +0 -0
  434. package/dist/client-rust/errors.rs +0 -220
  435. package/dist/client-rust/invalidation.rs +0 -412
  436. package/dist/client-rust/invalidation_sync.rs +0 -93
  437. package/dist/client-rust/nullable.rs +0 -17
  438. package/dist/client-rust/realtime.rs +0 -702
  439. package/dist/client-rust/result.rs +0 -22
  440. package/dist/client-rust/runtime.rs +0 -980
  441. package/dist/client-rust/runtime_sync.rs +0 -484
  442. package/dist/client-rust/sse.rs +0 -188
  443. package/dist/client-rust/ws.rs +0 -167
  444. package/dist/codegen-go-cli.d.ts +0 -22
  445. package/dist/codegen-go-cli.d.ts.map +0 -1
  446. package/dist/codegen-go-cli.js +0 -964
  447. package/dist/codegen-go-cli.js.map +0 -1
  448. package/dist/codegen-go.d.ts +0 -41
  449. package/dist/codegen-go.d.ts.map +0 -1
  450. package/dist/codegen-go.js +0 -872
  451. package/dist/codegen-go.js.map +0 -1
  452. package/dist/codegen-hash.d.ts +0 -3
  453. package/dist/codegen-hash.d.ts.map +0 -1
  454. package/dist/codegen-hash.js +0 -9
  455. package/dist/codegen-hash.js.map +0 -1
  456. package/dist/codegen-ir.d.ts +0 -190
  457. package/dist/codegen-ir.d.ts.map +0 -1
  458. package/dist/codegen-ir.js +0 -661
  459. package/dist/codegen-ir.js.map +0 -1
  460. package/dist/codegen-lang.d.ts +0 -65
  461. package/dist/codegen-lang.d.ts.map +0 -1
  462. package/dist/codegen-lang.js +0 -526
  463. package/dist/codegen-lang.js.map +0 -1
  464. package/dist/codegen-load.d.ts +0 -31
  465. package/dist/codegen-load.d.ts.map +0 -1
  466. package/dist/codegen-load.js +0 -138
  467. package/dist/codegen-load.js.map +0 -1
  468. package/dist/codegen-loaders.d.ts +0 -12
  469. package/dist/codegen-loaders.d.ts.map +0 -1
  470. package/dist/codegen-loaders.js +0 -55
  471. package/dist/codegen-loaders.js.map +0 -1
  472. package/dist/codegen-mcp.d.ts +0 -13
  473. package/dist/codegen-mcp.d.ts.map +0 -1
  474. package/dist/codegen-mcp.js +0 -368
  475. package/dist/codegen-mcp.js.map +0 -1
  476. package/dist/codegen-python.d.ts +0 -33
  477. package/dist/codegen-python.d.ts.map +0 -1
  478. package/dist/codegen-python.js +0 -828
  479. package/dist/codegen-python.js.map +0 -1
  480. package/dist/codegen-route-tree-intern.d.ts +0 -40
  481. package/dist/codegen-route-tree-intern.d.ts.map +0 -1
  482. package/dist/codegen-route-tree-intern.js +0 -221
  483. package/dist/codegen-route-tree-intern.js.map +0 -1
  484. package/dist/codegen-rust.d.ts +0 -18
  485. package/dist/codegen-rust.d.ts.map +0 -1
  486. package/dist/codegen-rust.js +0 -1293
  487. package/dist/codegen-rust.js.map +0 -1
  488. package/dist/codegen-sanitize.d.ts +0 -3
  489. package/dist/codegen-sanitize.d.ts.map +0 -1
  490. package/dist/codegen-sanitize.js +0 -56
  491. package/dist/codegen-sanitize.js.map +0 -1
  492. package/dist/codegen-schema-naming.d.ts +0 -27
  493. package/dist/codegen-schema-naming.d.ts.map +0 -1
  494. package/dist/codegen-schema-naming.js +0 -150
  495. package/dist/codegen-schema-naming.js.map +0 -1
  496. package/dist/codegen-sdk-model.d.ts +0 -71
  497. package/dist/codegen-sdk-model.d.ts.map +0 -1
  498. package/dist/codegen-sdk-model.js +0 -179
  499. package/dist/codegen-sdk-model.js.map +0 -1
  500. package/dist/codegen.d.ts +0 -187
  501. package/dist/codegen.d.ts.map +0 -1
  502. package/dist/codegen.js +0 -4619
  503. package/dist/codegen.js.map +0 -1
  504. package/dist/context.d.ts +0 -86
  505. package/dist/context.d.ts.map +0 -1
  506. package/dist/context.js +0 -242
  507. package/dist/context.js.map +0 -1
  508. package/dist/cookie-sign.d.ts +0 -17
  509. package/dist/cookie-sign.d.ts.map +0 -1
  510. package/dist/cookie-sign.js +0 -104
  511. package/dist/cookie-sign.js.map +0 -1
  512. package/dist/cookie.d.ts +0 -24
  513. package/dist/cookie.d.ts.map +0 -1
  514. package/dist/cookie.js +0 -138
  515. package/dist/cookie.js.map +0 -1
  516. package/dist/cors.d.ts +0 -14
  517. package/dist/cors.d.ts.map +0 -1
  518. package/dist/cors.js +0 -87
  519. package/dist/cors.js.map +0 -1
  520. package/dist/crypto.d.ts +0 -7
  521. package/dist/crypto.d.ts.map +0 -1
  522. package/dist/crypto.js +0 -38
  523. package/dist/crypto.js.map +0 -1
  524. package/dist/csrf.d.ts +0 -28
  525. package/dist/csrf.d.ts.map +0 -1
  526. package/dist/csrf.js +0 -79
  527. package/dist/csrf.js.map +0 -1
  528. package/dist/curl-logger.d.ts +0 -44
  529. package/dist/curl-logger.d.ts.map +0 -1
  530. package/dist/curl-logger.js +0 -199
  531. package/dist/curl-logger.js.map +0 -1
  532. package/dist/detect-runtime.d.ts +0 -16
  533. package/dist/detect-runtime.d.ts.map +0 -1
  534. package/dist/detect-runtime.js +0 -13
  535. package/dist/detect-runtime.js.map +0 -1
  536. package/dist/dict.d.ts +0 -11
  537. package/dist/dict.d.ts.map +0 -1
  538. package/dist/dict.js +0 -13
  539. package/dist/dict.js.map +0 -1
  540. package/dist/error.d.ts +0 -21
  541. package/dist/error.d.ts.map +0 -1
  542. package/dist/error.js +0 -44
  543. package/dist/error.js.map +0 -1
  544. package/dist/errors.d.ts +0 -40
  545. package/dist/errors.d.ts.map +0 -1
  546. package/dist/errors.js +0 -39
  547. package/dist/errors.js.map +0 -1
  548. package/dist/etag.d.ts +0 -11
  549. package/dist/etag.d.ts.map +0 -1
  550. package/dist/etag.js +0 -82
  551. package/dist/etag.js.map +0 -1
  552. package/dist/feature-detect.d.ts +0 -25
  553. package/dist/feature-detect.d.ts.map +0 -1
  554. package/dist/feature-detect.js +0 -144
  555. package/dist/feature-detect.js.map +0 -1
  556. package/dist/feature-load.d.ts +0 -3
  557. package/dist/feature-load.d.ts.map +0 -1
  558. package/dist/feature-load.js +0 -36
  559. package/dist/feature-load.js.map +0 -1
  560. package/dist/feature-slots.d.ts +0 -44
  561. package/dist/feature-slots.d.ts.map +0 -1
  562. package/dist/feature-slots.js +0 -48
  563. package/dist/feature-slots.js.map +0 -1
  564. package/dist/fetch-request.d.ts +0 -10
  565. package/dist/fetch-request.d.ts.map +0 -1
  566. package/dist/fetch-request.js +0 -13
  567. package/dist/fetch-request.js.map +0 -1
  568. package/dist/gen-process.d.ts +0 -15
  569. package/dist/gen-process.d.ts.map +0 -1
  570. package/dist/gen-process.js +0 -43
  571. package/dist/gen-process.js.map +0 -1
  572. package/dist/gen-write.d.ts +0 -19
  573. package/dist/gen-write.d.ts.map +0 -1
  574. package/dist/gen-write.js +0 -120
  575. package/dist/gen-write.js.map +0 -1
  576. package/dist/glob.d.ts +0 -21
  577. package/dist/glob.d.ts.map +0 -1
  578. package/dist/glob.js +0 -115
  579. package/dist/glob.js.map +0 -1
  580. package/dist/go-type-emitter.d.ts +0 -47
  581. package/dist/go-type-emitter.d.ts.map +0 -1
  582. package/dist/go-type-emitter.js +0 -594
  583. package/dist/go-type-emitter.js.map +0 -1
  584. package/dist/honey-response.d.ts +0 -82
  585. package/dist/honey-response.d.ts.map +0 -1
  586. package/dist/honey-response.js +0 -338
  587. package/dist/honey-response.js.map +0 -1
  588. package/dist/i18n-slot.d.ts +0 -15
  589. package/dist/i18n-slot.d.ts.map +0 -1
  590. package/dist/i18n-slot.js +0 -14
  591. package/dist/i18n-slot.js.map +0 -1
  592. package/dist/i18n.d.ts +0 -29
  593. package/dist/i18n.d.ts.map +0 -1
  594. package/dist/i18n.js +0 -250
  595. package/dist/i18n.js.map +0 -1
  596. package/dist/index.d.ts.map +0 -1
  597. package/dist/index.js.map +0 -1
  598. package/dist/init.d.ts +0 -5
  599. package/dist/init.d.ts.map +0 -1
  600. package/dist/init.js +0 -140
  601. package/dist/init.js.map +0 -1
  602. package/dist/input.d.ts +0 -6
  603. package/dist/input.d.ts.map +0 -1
  604. package/dist/input.js +0 -4
  605. package/dist/input.js.map +0 -1
  606. package/dist/invalidate-check.d.ts +0 -61
  607. package/dist/invalidate-check.d.ts.map +0 -1
  608. package/dist/invalidate-check.js +0 -163
  609. package/dist/invalidate-check.js.map +0 -1
  610. package/dist/invoke-user.d.ts +0 -35
  611. package/dist/invoke-user.d.ts.map +0 -1
  612. package/dist/invoke-user.js +0 -73
  613. package/dist/invoke-user.js.map +0 -1
  614. package/dist/ip-restrict.d.ts +0 -24
  615. package/dist/ip-restrict.d.ts.map +0 -1
  616. package/dist/ip-restrict.js +0 -60
  617. package/dist/ip-restrict.js.map +0 -1
  618. package/dist/ip.d.ts +0 -39
  619. package/dist/ip.d.ts.map +0 -1
  620. package/dist/ip.js +0 -209
  621. package/dist/ip.js.map +0 -1
  622. package/dist/logger.d.ts +0 -45
  623. package/dist/logger.d.ts.map +0 -1
  624. package/dist/logger.js +0 -113
  625. package/dist/logger.js.map +0 -1
  626. package/dist/media-type.d.ts +0 -30
  627. package/dist/media-type.d.ts.map +0 -1
  628. package/dist/media-type.js +0 -97
  629. package/dist/media-type.js.map +0 -1
  630. package/dist/meta-spec-merge.d.ts +0 -32
  631. package/dist/meta-spec-merge.d.ts.map +0 -1
  632. package/dist/meta-spec-merge.js +0 -68
  633. package/dist/meta-spec-merge.js.map +0 -1
  634. package/dist/meta-spec.d.ts +0 -94
  635. package/dist/meta-spec.d.ts.map +0 -1
  636. package/dist/meta-spec.js +0 -535
  637. package/dist/meta-spec.js.map +0 -1
  638. package/dist/middleware.d.ts +0 -77
  639. package/dist/middleware.d.ts.map +0 -1
  640. package/dist/middleware.js +0 -253
  641. package/dist/middleware.js.map +0 -1
  642. package/dist/node-request.d.ts +0 -104
  643. package/dist/node-request.d.ts.map +0 -1
  644. package/dist/node-request.js +0 -407
  645. package/dist/node-request.js.map +0 -1
  646. package/dist/node.d.ts +0 -30
  647. package/dist/node.d.ts.map +0 -1
  648. package/dist/node.js +0 -396
  649. package/dist/node.js.map +0 -1
  650. package/dist/openapi/collect.d.ts.map +0 -1
  651. package/dist/openapi/collect.js.map +0 -1
  652. package/dist/openapi/docs-page.d.ts.map +0 -1
  653. package/dist/openapi/docs-page.js.map +0 -1
  654. package/dist/openapi/document.d.ts.map +0 -1
  655. package/dist/openapi/document.js.map +0 -1
  656. package/dist/openapi/json-schema-slot.d.ts +0 -12
  657. package/dist/openapi/json-schema-slot.d.ts.map +0 -1
  658. package/dist/openapi/json-schema-slot.js +0 -38
  659. package/dist/openapi/json-schema-slot.js.map +0 -1
  660. package/dist/openapi/register.d.ts.map +0 -1
  661. package/dist/openapi/register.js.map +0 -1
  662. package/dist/openapi/scalar.d.ts.map +0 -1
  663. package/dist/openapi/scalar.js.map +0 -1
  664. package/dist/openapi/serve.d.ts.map +0 -1
  665. package/dist/openapi/serve.js.map +0 -1
  666. package/dist/openapi/spec-factory.d.ts +0 -65
  667. package/dist/openapi/spec-factory.d.ts.map +0 -1
  668. package/dist/openapi/spec-factory.js +0 -52
  669. package/dist/openapi/spec-factory.js.map +0 -1
  670. package/dist/openapi/spec.d.ts +0 -29
  671. package/dist/openapi/spec.d.ts.map +0 -1
  672. package/dist/openapi/spec.js +0 -50
  673. package/dist/openapi/spec.js.map +0 -1
  674. package/dist/openapi/swagger.d.ts.map +0 -1
  675. package/dist/openapi/swagger.js.map +0 -1
  676. package/dist/pattern.d.ts +0 -71
  677. package/dist/pattern.d.ts.map +0 -1
  678. package/dist/pattern.js +0 -239
  679. package/dist/pattern.js.map +0 -1
  680. package/dist/peer.d.ts +0 -17
  681. package/dist/peer.d.ts.map +0 -1
  682. package/dist/peer.js +0 -50
  683. package/dist/peer.js.map +0 -1
  684. package/dist/plugin.d.ts +0 -228
  685. package/dist/plugin.d.ts.map +0 -1
  686. package/dist/plugin.js +0 -666
  687. package/dist/plugin.js.map +0 -1
  688. package/dist/powered-by.d.ts +0 -7
  689. package/dist/powered-by.d.ts.map +0 -1
  690. package/dist/powered-by.js +0 -11
  691. package/dist/powered-by.js.map +0 -1
  692. package/dist/pretty-json.d.ts +0 -10
  693. package/dist/pretty-json.d.ts.map +0 -1
  694. package/dist/pretty-json.js +0 -47
  695. package/dist/pretty-json.js.map +0 -1
  696. package/dist/producer-stream.d.ts +0 -47
  697. package/dist/producer-stream.d.ts.map +0 -1
  698. package/dist/producer-stream.js +0 -267
  699. package/dist/producer-stream.js.map +0 -1
  700. package/dist/proxy.d.ts +0 -90
  701. package/dist/proxy.d.ts.map +0 -1
  702. package/dist/proxy.js +0 -274
  703. package/dist/proxy.js.map +0 -1
  704. package/dist/python-type-emitter.d.ts +0 -24
  705. package/dist/python-type-emitter.d.ts.map +0 -1
  706. package/dist/python-type-emitter.js +0 -157
  707. package/dist/python-type-emitter.js.map +0 -1
  708. package/dist/realtime/bus.d.ts.map +0 -1
  709. package/dist/realtime/bus.js.map +0 -1
  710. package/dist/realtime/register.d.ts +0 -3
  711. package/dist/realtime/register.d.ts.map +0 -1
  712. package/dist/realtime/register.js +0 -10
  713. package/dist/realtime/register.js.map +0 -1
  714. package/dist/realtime/route.d.ts.map +0 -1
  715. package/dist/realtime/route.js.map +0 -1
  716. package/dist/realtime/server.d.ts.map +0 -1
  717. package/dist/realtime/server.js.map +0 -1
  718. package/dist/request-id.d.ts +0 -19
  719. package/dist/request-id.d.ts.map +0 -1
  720. package/dist/request-id.js +0 -16
  721. package/dist/request-id.js.map +0 -1
  722. package/dist/request-limits.d.ts +0 -3
  723. package/dist/request-limits.d.ts.map +0 -1
  724. package/dist/request-limits.js +0 -3
  725. package/dist/request-limits.js.map +0 -1
  726. package/dist/request-path.d.ts +0 -33
  727. package/dist/request-path.d.ts.map +0 -1
  728. package/dist/request-path.js +0 -178
  729. package/dist/request-path.js.map +0 -1
  730. package/dist/request-to-curl.d.ts +0 -15
  731. package/dist/request-to-curl.d.ts.map +0 -1
  732. package/dist/request-to-curl.js +0 -48
  733. package/dist/request-to-curl.js.map +0 -1
  734. package/dist/response.d.ts +0 -86
  735. package/dist/response.d.ts.map +0 -1
  736. package/dist/response.js +0 -313
  737. package/dist/response.js.map +0 -1
  738. package/dist/rust-type-emitter.d.ts +0 -44
  739. package/dist/rust-type-emitter.d.ts.map +0 -1
  740. package/dist/rust-type-emitter.js +0 -463
  741. package/dist/rust-type-emitter.js.map +0 -1
  742. package/dist/secure-headers.d.ts +0 -16
  743. package/dist/secure-headers.d.ts.map +0 -1
  744. package/dist/secure-headers.js +0 -53
  745. package/dist/secure-headers.js.map +0 -1
  746. package/dist/serve-register.d.ts +0 -6
  747. package/dist/serve-register.d.ts.map +0 -1
  748. package/dist/serve-register.js +0 -9
  749. package/dist/serve-register.js.map +0 -1
  750. package/dist/serve-slot.d.ts +0 -9
  751. package/dist/serve-slot.d.ts.map +0 -1
  752. package/dist/serve-slot.js +0 -52
  753. package/dist/serve-slot.js.map +0 -1
  754. package/dist/serve.d.ts +0 -38
  755. package/dist/serve.d.ts.map +0 -1
  756. package/dist/serve.js +0 -157
  757. package/dist/serve.js.map +0 -1
  758. package/dist/server-timing.d.ts +0 -9
  759. package/dist/server-timing.d.ts.map +0 -1
  760. package/dist/server-timing.js +0 -51
  761. package/dist/server-timing.js.map +0 -1
  762. package/dist/static.d.ts +0 -21
  763. package/dist/static.d.ts.map +0 -1
  764. package/dist/static.js +0 -102
  765. package/dist/static.js.map +0 -1
  766. package/dist/telemetry/otel.d.ts.map +0 -1
  767. package/dist/telemetry/otel.js.map +0 -1
  768. package/dist/testing.d.ts +0 -36
  769. package/dist/testing.d.ts.map +0 -1
  770. package/dist/testing.js +0 -124
  771. package/dist/testing.js.map +0 -1
  772. package/dist/timeout.d.ts +0 -13
  773. package/dist/timeout.d.ts.map +0 -1
  774. package/dist/timeout.js +0 -36
  775. package/dist/timeout.js.map +0 -1
  776. package/dist/tree.d.ts +0 -197
  777. package/dist/tree.d.ts.map +0 -1
  778. package/dist/tree.js +0 -437
  779. package/dist/tree.js.map +0 -1
  780. package/dist/trust.d.ts +0 -21
  781. package/dist/trust.d.ts.map +0 -1
  782. package/dist/trust.js +0 -100
  783. package/dist/trust.js.map +0 -1
  784. package/dist/ts-type-emitter.d.ts +0 -8
  785. package/dist/ts-type-emitter.d.ts.map +0 -1
  786. package/dist/ts-type-emitter.js +0 -150
  787. package/dist/ts-type-emitter.js.map +0 -1
  788. package/dist/type-emitter.d.ts +0 -36
  789. package/dist/type-emitter.d.ts.map +0 -1
  790. package/dist/type-emitter.js +0 -729
  791. package/dist/type-emitter.js.map +0 -1
  792. package/dist/type-extractor.d.ts +0 -43
  793. package/dist/type-extractor.d.ts.map +0 -1
  794. package/dist/type-extractor.js +0 -819
  795. package/dist/type-extractor.js.map +0 -1
  796. package/dist/types.d.ts +0 -467
  797. package/dist/types.d.ts.map +0 -1
  798. package/dist/types.js +0 -96
  799. package/dist/types.js.map +0 -1
  800. package/dist/validation.d.ts +0 -31
  801. package/dist/validation.d.ts.map +0 -1
  802. package/dist/validation.js +0 -335
  803. package/dist/validation.js.map +0 -1
  804. package/dist/with-headers.d.ts +0 -15
  805. package/dist/with-headers.d.ts.map +0 -1
  806. package/dist/with-headers.js +0 -45
  807. package/dist/with-headers.js.map +0 -1
  808. package/dist/ws/bun.d.ts.map +0 -1
  809. package/dist/ws/bun.js.map +0 -1
  810. package/dist/ws/cloudflare.d.ts.map +0 -1
  811. package/dist/ws/cloudflare.js.map +0 -1
  812. package/dist/ws/deno.d.ts +0 -13
  813. package/dist/ws/deno.d.ts.map +0 -1
  814. package/dist/ws/deno.js +0 -133
  815. package/dist/ws/deno.js.map +0 -1
  816. package/dist/ws/node.d.ts.map +0 -1
  817. package/dist/ws/node.js.map +0 -1
  818. package/dist/ws/session.d.ts.map +0 -1
  819. package/dist/ws/session.js.map +0 -1
  820. package/dist/ws/shared.d.ts.map +0 -1
  821. package/dist/ws/shared.js.map +0 -1
  822. package/dist/ws-origin.d.ts +0 -20
  823. package/dist/ws-origin.d.ts.map +0 -1
  824. package/dist/ws-origin.js +0 -41
  825. package/dist/ws-origin.js.map +0 -1
  826. package/dist/yaml.d.ts +0 -18
  827. package/dist/yaml.d.ts.map +0 -1
  828. package/dist/yaml.js +0 -101
  829. package/dist/yaml.js.map +0 -1
  830. package/docs/api-ref/go.md +0 -399
  831. package/docs/api-ref/python.md +0 -399
  832. package/docs/api-ref/rust.md +0 -399
  833. package/docs/api-ref/typescript.md +0 -399
  834. package/docs/invalidate-spec.md +0 -333
  835. package/docs/meta-spec.md +0 -555
  836. package/docs/packaging.md +0 -104
  837. package/docs/regression-matrix/README.md +0 -32
  838. package/docs/regression-matrix/review.md +0 -44
  839. package/docs/regression-matrix/ws1-3.md +0 -97
  840. package/docs/regression-matrix/ws4-8.md +0 -107
  841. package/docs/regression-matrix/ws9-12.md +0 -266
  842. package/docs/sdk.md +0 -798
  843. package/src/accepts.ts +0 -136
  844. package/src/body-kind.ts +0 -67
  845. package/src/body-limit.ts +0 -152
  846. package/src/build/index.ts +0 -217
  847. package/src/cli-args.ts +0 -123
  848. package/src/cli-go/auth.go +0 -26
  849. package/src/cli-go/config.go +0 -192
  850. package/src/cli-go/errors.go +0 -75
  851. package/src/cli-go/multipart.go +0 -79
  852. package/src/cli-go/output.go +0 -163
  853. package/src/cli-go/root.go +0 -30
  854. package/src/cli-go/stream.go +0 -88
  855. package/src/cli-go/version.go +0 -5
  856. package/src/cli.ts +0 -209
  857. package/src/client/defaults.ts +0 -47
  858. package/src/client/error.ts +0 -82
  859. package/src/client/http.ts +0 -737
  860. package/src/client/index.ts +0 -157
  861. package/src/client/path.ts +0 -169
  862. package/src/client/redirect-policy.ts +0 -19
  863. package/src/client/sdk.ts +0 -351
  864. package/src/client/sse.ts +0 -128
  865. package/src/client/types.ts +0 -206
  866. package/src/client/ws.ts +0 -149
  867. package/src/client-go/errors.go +0 -236
  868. package/src/client-go/export.go +0 -44
  869. package/src/client-go/invalidation.go +0 -371
  870. package/src/client-go/nullable.go +0 -62
  871. package/src/client-go/realtime.go +0 -354
  872. package/src/client-go/result.go +0 -20
  873. package/src/client-go/runtime.go +0 -975
  874. package/src/client-go/sse.go +0 -159
  875. package/src/client-go/transport.go +0 -495
  876. package/src/client-go/ws.go +0 -128
  877. package/src/client-info.ts +0 -79
  878. package/src/client-mcp/README.md +0 -10
  879. package/src/client-mcp/server.ts +0 -221
  880. package/src/client-mcp/types.ts +0 -19
  881. package/src/client-python/_errors.py +0 -115
  882. package/src/client-python/_invalidation.py +0 -494
  883. package/src/client-python/_realtime.py +0 -179
  884. package/src/client-python/_runtime.py +0 -860
  885. package/src/client-python/_sse.py +0 -155
  886. package/src/client-python/_transport.py +0 -366
  887. package/src/client-python/_ws.py +0 -159
  888. package/src/client-python/py.typed +0 -0
  889. package/src/client-rust/errors.rs +0 -220
  890. package/src/client-rust/invalidation.rs +0 -412
  891. package/src/client-rust/invalidation_sync.rs +0 -93
  892. package/src/client-rust/nullable.rs +0 -17
  893. package/src/client-rust/realtime.rs +0 -702
  894. package/src/client-rust/result.rs +0 -22
  895. package/src/client-rust/runtime.rs +0 -980
  896. package/src/client-rust/runtime_sync.rs +0 -484
  897. package/src/client-rust/sse.rs +0 -188
  898. package/src/client-rust/ws.rs +0 -167
  899. package/src/codegen-go-cli.ts +0 -1066
  900. package/src/codegen-go.ts +0 -970
  901. package/src/codegen-hash.ts +0 -8
  902. package/src/codegen-ir.ts +0 -812
  903. package/src/codegen-lang.ts +0 -517
  904. package/src/codegen-load.ts +0 -150
  905. package/src/codegen-loaders.ts +0 -50
  906. package/src/codegen-mcp.ts +0 -445
  907. package/src/codegen-python.ts +0 -916
  908. package/src/codegen-route-tree-intern.ts +0 -198
  909. package/src/codegen-rust.ts +0 -1402
  910. package/src/codegen-sanitize.ts +0 -57
  911. package/src/codegen-schema-naming.ts +0 -165
  912. package/src/codegen-sdk-model.ts +0 -228
  913. package/src/codegen.ts +0 -5089
  914. package/src/context.ts +0 -325
  915. package/src/cookie-sign.ts +0 -119
  916. package/src/cookie.ts +0 -143
  917. package/src/cors.ts +0 -95
  918. package/src/crypto.ts +0 -38
  919. package/src/csrf.ts +0 -86
  920. package/src/curl-logger.ts +0 -258
  921. package/src/detect-runtime.ts +0 -17
  922. package/src/dict.ts +0 -12
  923. package/src/error.ts +0 -55
  924. package/src/errors.ts +0 -85
  925. package/src/etag.ts +0 -97
  926. package/src/feature-detect.ts +0 -147
  927. package/src/feature-load.ts +0 -23
  928. package/src/feature-slots.ts +0 -70
  929. package/src/fetch-request.ts +0 -13
  930. package/src/gen-process.ts +0 -42
  931. package/src/gen-write.ts +0 -127
  932. package/src/glob.ts +0 -109
  933. package/src/go-type-emitter.ts +0 -660
  934. package/src/honey-response.ts +0 -379
  935. package/src/i18n-slot.ts +0 -30
  936. package/src/i18n.ts +0 -300
  937. package/src/index.ts +0 -3829
  938. package/src/init.ts +0 -161
  939. package/src/input.ts +0 -8
  940. package/src/invalidate-check.ts +0 -205
  941. package/src/invoke-user.ts +0 -95
  942. package/src/ip-restrict.ts +0 -79
  943. package/src/ip.ts +0 -194
  944. package/src/logger.ts +0 -178
  945. package/src/media-type.ts +0 -104
  946. package/src/meta-spec-merge.ts +0 -80
  947. package/src/meta-spec.ts +0 -718
  948. package/src/middleware.ts +0 -331
  949. package/src/node-request.ts +0 -431
  950. package/src/node.ts +0 -454
  951. package/src/openapi/collect.ts +0 -50
  952. package/src/openapi/docs-page.ts +0 -89
  953. package/src/openapi/document.ts +0 -902
  954. package/src/openapi/json-schema-slot.ts +0 -53
  955. package/src/openapi/register.ts +0 -23
  956. package/src/openapi/scalar.ts +0 -34
  957. package/src/openapi/serve.ts +0 -39
  958. package/src/openapi/spec-factory.ts +0 -100
  959. package/src/openapi/spec.ts +0 -74
  960. package/src/openapi/swagger.ts +0 -35
  961. package/src/pattern.ts +0 -245
  962. package/src/peer.ts +0 -53
  963. package/src/plugin.ts +0 -864
  964. package/src/powered-by.ts +0 -18
  965. package/src/pretty-json.ts +0 -56
  966. package/src/producer-stream.ts +0 -296
  967. package/src/proxy.ts +0 -370
  968. package/src/python-type-emitter.ts +0 -181
  969. package/src/realtime/bus.ts +0 -140
  970. package/src/realtime/register.ts +0 -13
  971. package/src/realtime/route.ts +0 -153
  972. package/src/realtime/server.ts +0 -298
  973. package/src/request-id.ts +0 -32
  974. package/src/request-limits.ts +0 -2
  975. package/src/request-path.ts +0 -165
  976. package/src/request-to-curl.ts +0 -52
  977. package/src/response.ts +0 -411
  978. package/src/rust-type-emitter.ts +0 -548
  979. package/src/secure-headers.ts +0 -74
  980. package/src/serve-register.ts +0 -12
  981. package/src/serve-slot.ts +0 -58
  982. package/src/serve.ts +0 -220
  983. package/src/server-timing.ts +0 -71
  984. package/src/static.ts +0 -112
  985. package/src/telemetry/otel.ts +0 -156
  986. package/src/testing.ts +0 -167
  987. package/src/timeout.ts +0 -45
  988. package/src/tree.ts +0 -558
  989. package/src/trust.ts +0 -117
  990. package/src/ts-type-emitter.ts +0 -168
  991. package/src/type-emitter.ts +0 -760
  992. package/src/type-extractor.ts +0 -855
  993. package/src/types.ts +0 -756
  994. package/src/validation.ts +0 -413
  995. package/src/with-headers.ts +0 -45
  996. package/src/ws/bun.ts +0 -120
  997. package/src/ws/cloudflare.ts +0 -192
  998. package/src/ws/deno.ts +0 -188
  999. package/src/ws/node.ts +0 -240
  1000. package/src/ws/session.ts +0 -73
  1001. package/src/ws/shared.ts +0 -140
  1002. package/src/ws-origin.ts +0 -53
  1003. package/src/yaml.ts +0 -103
package/src/index.ts DELETED
@@ -1,3829 +0,0 @@
1
- import { bodyKind, headResponse, isProducedStream, rawBodyOf } from "./body-kind.ts"
2
- import { clientInfo, HoneyContext } from "./context.ts"
3
- import { dict } from "./dict.ts"
4
- import { normalizePath, pathOfUrl, searchOfUrl } from "./request-path.ts"
5
- import { BUN_URL_FROM_HOST, hasValidHost, TRUST_OFF, type TrustSetting } from "./client-info.ts"
6
- import type { TrustProxy } from "./trust.ts"
7
- import { loadFeature, requireFeature } from "./feature-slots.ts"
8
- import { HoneyError } from "./error.ts"
9
- import { ERROR_META } from "./errors.ts"
10
- import type { ChainErrorConverter, MiddlewareFn, RuntimeMiddleware } from "./middleware.ts"
11
- import { collectMiddlewareMeta, compileChain, RESERVED_CTX_KEYS } from "./middleware.ts"
12
- import type { ProxyConfig } from "./proxy.ts"
13
- import type { CustomErrorFormatter, ErrorFormatter, ResponseOptions, TypedResponse } from "./response.ts"
14
- import { createErrorResponse, type HoneyRes } from "./response.ts"
15
- import type { RouteId, Segment } from "./pattern.ts"
16
- import {
17
- canonical,
18
- expandOptional,
19
- isStaticPattern,
20
- joinPatterns,
21
- normalizePattern,
22
- parsePattern,
23
- pathInScope,
24
- routeId,
25
- scopeCoverage,
26
- splitRouteId,
27
- } from "./pattern.ts"
28
- import type { OutputValidator, RouteEntry, RouteHandler, RouteTree, TreeNode, WSRouteHandler } from "./tree.ts"
29
- import {
30
- assertTreeFormat,
31
- cloneTree,
32
- createNode,
33
- findLeaf,
34
- forEachLeaf,
35
- freezeTree,
36
- hasWsLeaf,
37
- insertLeaf,
38
- matchRoute,
39
- matchWsRoute,
40
- ROUTE_TREE_VERSION,
41
- } from "./tree.ts"
42
- import type { RealtimeBus } from "./realtime/bus.ts"
43
- import type { RealtimeConfig, RealtimeRouteOpts } from "./realtime/route.ts"
44
- import type { RealtimePublisher } from "./realtime/server.ts"
45
- import type {
46
- ComputeErrorsByStatus,
47
- DefaultMeta,
48
- ExtractSchemas,
49
- FieldError,
50
- HttpMethod,
51
- InferInputMap,
52
- InferOutput,
53
- HoneyMetaSpec,
54
- InputSchemasDef,
55
- MergePath,
56
- MergeRoute,
57
- MetaSpecConfig,
58
- OutputSchemaDef,
59
- ParamsFromPath,
60
- PrefixRoutes,
61
- StandardSchemaLike,
62
- StatusKey,
63
- TapContext,
64
- } from "./types.ts"
65
- import { codeToStatusKey, EK, EMPTY_OBJ, SK } from "./types.ts"
66
- import { assertBodySchemaAllowed, assertRequestContentType, validateInput, validateOutput } from "./validation.ts"
67
- import type { WSAdapter, WSContext, WSHandler } from "./ws/cloudflare.ts"
68
- import type { InvokeLogger } from "./invoke-user.ts"
69
- import { type WSOriginPolicy, validateOriginPolicy } from "./ws-origin.ts"
70
- import { loadHoneyFeature } from "./feature-load.ts"
71
- import { getI18nRuntime } from "./i18n-slot.ts"
72
- import { bindInternalHandler, epochCached, getOpenApiRuntime } from "./openapi/spec-factory.ts"
73
- import type { ServedArtifact } from "./openapi/spec-factory.ts"
74
- import type { MetaSpecSource } from "./meta-spec-merge.ts"
75
- import { getServeRuntime, isCodegenProcess } from "./serve-slot.ts"
76
- import type { HoneyServeOptions, ServeHandle } from "./serve.ts"
77
-
78
- export { clientInfo, HoneyContext } from "./context.ts"
79
- export { toFetchRequest } from "./fetch-request.ts"
80
- export type { WSOriginPolicy } from "./ws-origin.ts"
81
- export type { ClientInfo } from "./client-info.ts"
82
- export type { TrustProxy } from "./trust.ts"
83
- /** HoneyContext without internal backing fields — use this for consumer-facing types */
84
- export type HoneyCtx<TEnv = Record<string, unknown>> = Omit<
85
- import("./context.ts").HoneyContext<TEnv>,
86
- | "_errorToResponse"
87
- | "_isErrorResponse"
88
- | "_lzClient"
89
- | "_lzCookies"
90
- | "_lzHeaders"
91
- | "_lzSearch"
92
- | "_lzSearchAll"
93
- | "_lzUrlFn"
94
- | "_rq"
95
- | "_setErrors"
96
- >
97
- export { HoneyError } from "./error.ts"
98
- export { defineErrors, ERROR_META } from "./errors.ts"
99
- export type { ErrorMetaEntry } from "./errors.ts"
100
- export type { MiddlewareFn } from "./middleware.ts"
101
- export { createMiddleware, defineMiddleware } from "./middleware.ts"
102
- export type { ProxyConfig } from "./proxy.ts"
103
- export type { PendingTap, TapContext, TapHandler } from "./types.ts"
104
- export type {
105
- CookieOptions,
106
- CustomErrorFormatter,
107
- ErrorFormatter,
108
- ResponseOptions,
109
- SSEEvent,
110
- SSEOptions,
111
- SSEStream,
112
- TypedResponse,
113
- } from "./response.ts"
114
- export { createErrorResponse, HoneyRes } from "./response.ts"
115
- export { mergeTree } from "./tree.ts"
116
- export type {
117
- DefaultMeta,
118
- HoneyCodegen,
119
- HoneyMeta,
120
- InferBasePath,
121
- InferCtx,
122
- InferEnv,
123
- InferErrorFactory,
124
- InferInputMap,
125
- InferMeta,
126
- InferMethods,
127
- InferOutput,
128
- InferRouteCtx,
129
- InferRouteErrors,
130
- InferRouteInput,
131
- InferRouteMeta,
132
- InferRouteMethods,
133
- InferRouteOutput,
134
- InferRoutePaths,
135
- InferRoutes,
136
- InputSchemasDef,
137
- MergePath,
138
- PrefixRoutes,
139
- HoneyMetaSpec,
140
- MetaSpecConfig,
141
- MetaSpecContext,
142
- MetaSpecEntry,
143
- MetaSpecMergeConflict,
144
- MetaSpecExpand,
145
- MetaSpecProfile,
146
- MetaSpecSchemaEntry,
147
- MetaSpecSchemaSearch,
148
- MetaSpecSchemaSource,
149
- MetaSpecSingle,
150
- MetaSpecStrictness,
151
- MetaSpecTarget,
152
- OpenApiMeta,
153
- OutputSchemaDef,
154
- Overwrite,
155
- ComputeErrorsByStatus,
156
- RouteRecord,
157
- StandardSchemaLike,
158
- StatusKey,
159
- SuccessStatusKey,
160
- } from "./types.ts"
161
- export type { WSAdapter, WSAdapterOptions, WSContext, WSHandler, WSPreUpgrade } from "./ws/cloudflare.ts"
162
- export type { ConnContext, RealtimeLimits, RealtimeRouteOpts } from "./realtime/route.ts"
163
- export type { RealtimePublisher } from "./realtime/server.ts"
164
- export type { RealtimeBus } from "./realtime/bus.ts"
165
- export type { HoneyServeOptions, ServeHandle } from "./serve.ts"
166
- export type { ServeRuntime } from "./detect-runtime.ts"
167
- export { detectRuntime } from "./detect-runtime.ts"
168
-
169
- type TelemetryAdapter = {
170
- onError?(ctx: { duration: number; error: HoneyError; method: string; path: string }): void
171
- /** `path` is the request path; `route` the matched pattern (`/users/:id`). */
172
- onHandler?(ctx: { duration: number; method: string; path: string; route: string; status: number }): void
173
- onMethodNotAllowed?(ctx: { allowed: string[]; method: string; path: string; req: Request }): void
174
- onMiddleware?(ctx: { duration: number; error?: unknown; name: string }): void
175
- onNotFound?(ctx: { method: string; path: string; req: Request }): void
176
- onRequest?(ctx: { env: unknown; req: Request }): void
177
- /** Fires exactly once per request that fired `onRequest`, whichever way it exits. */
178
- onResponse?(ctx: { duration: number; req: Request; status: number }): void
179
- /** `path` is the request path; `route` the matched pattern (`/users/:id`). */
180
- onRoute?(ctx: { method: string; params: Record<string, string>; path: string; req: Request; route: string }): void
181
- }
182
-
183
- type ErrorI18nConfig<TEnv> = {
184
- errors?: Record<string, Record<string, string>>
185
- fieldNames?: Record<string, Record<string, string>>
186
- resolveLocale: (ctx: {
187
- cookies: Record<string, string>
188
- env: TEnv
189
- headers: Record<string, string>
190
- params: Record<string, string>
191
- req: Request
192
- search: Record<string, string>
193
- }) => string | Promise<string>
194
- }
195
-
196
- /** The warn side of a structured logger (`warn(obj, msg)` or `warn(msg)`), e.g. honey/logger. */
197
- type Logger = {
198
- warn?(objOrMsg: Record<string, unknown> | string, msg?: string): void
199
- }
200
-
201
- type ErrorCtx<TEnv> = { env: TEnv; jsonFromError: (err: HoneyError) => Response; req: Request }
202
-
203
- type OnErrorFn<TEnv> = (
204
- error: unknown,
205
- ctx: ErrorCtx<TEnv>,
206
- ) => HoneyError | Response | Promise<HoneyError | Response | undefined | void> | undefined | void
207
-
208
- type OnMethodNotAllowedFn<TEnv> = (ctx: ErrorCtx<TEnv> & { allowed: string[] }) => Response | Promise<Response>
209
-
210
- type OnNotFoundFn<TEnv> = (ctx: ErrorCtx<TEnv>) => Response | Promise<Response>
211
-
212
- type TapFn<TEnv> = (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>
213
-
214
- type ErrorFactoryRecord = Record<string, (...args: never[]) => unknown>
215
-
216
- type OutputValidationMode = "always" | "dev" | "off"
217
-
218
- /**
219
- * Settings of one app — shared by every handle derived from it (`use()`, `basePath()`,
220
- * `context()`, `meta()`), so a setter called on any handle applies to all of them and
221
- * serving any handle behaves the same. Records point at the settings of the app they were
222
- * registered on, so a mounted sub-app's routes keep their own error factory, default errors,
223
- * boundary and output validation.
224
- */
225
- type AppSettings<TEnv = unknown> = {
226
- customErrorFormatter: CustomErrorFormatter | null
227
- customErrorSchema: StandardSchemaLike | null
228
- defaultBoundaryKey: string | null
229
- defaultErrorKeys: Set<string>
230
- errorFactory: ErrorFactoryRecord | null
231
- errorFormatter: ErrorFormatterFn
232
- errorI18n: ErrorI18nConfig<TEnv> | null
233
- errorSchema: StandardSchemaLike | null
234
- logger: Logger | null
235
- onError: OnErrorFn<TEnv> | null
236
- onMethodNotAllowed: OnMethodNotAllowedFn<TEnv> | null
237
- onNotFound: OnNotFoundFn<TEnv> | null
238
- /** null = never set: a mounted route follows the mounting app's mode */
239
- outputValidation: OutputValidationMode | null
240
- stripPrefix: string | null
241
- taps: Map<string, TapFn<TEnv>> | null
242
- telemetry: TelemetryAdapter | null
243
- trailingSlash: "enforce" | "ignore" | "strip"
244
- encodedSlashes: "allow" | "reject"
245
- trust: TrustSetting
246
- wsAdapter: WSAdapter | null
247
- }
248
-
249
- function createSettings<TEnv>(): AppSettings<TEnv> {
250
- return {
251
- customErrorFormatter: null,
252
- customErrorSchema: null,
253
- defaultBoundaryKey: null,
254
- defaultErrorKeys: new Set(),
255
- errorFactory: null,
256
- errorFormatter: defaultErrorFormatter,
257
- errorI18n: null,
258
- errorSchema: null,
259
- logger: null,
260
- onError: null,
261
- onMethodNotAllowed: null,
262
- onNotFound: null,
263
- outputValidation: null,
264
- stripPrefix: null,
265
- taps: null,
266
- telemetry: null,
267
- trailingSlash: "ignore",
268
- encodedSlashes: "reject",
269
- trust: TRUST_OFF,
270
- wsAdapter: null,
271
- }
272
- }
273
-
274
- /** Join a base path and a route or scope path into one canonical pattern. */
275
- function mergePath(base: string, path: string): string {
276
- return joinPatterns(base, path)
277
- }
278
-
279
- /**
280
- * Everything a matched route runs, compiled once at finalize: the record (with its resolved
281
- * error keys, meta and chain) and one function that runs chain + handler and never rejects.
282
- */
283
- type Plan = {
284
- /** `.context()` values copied onto each request context */
285
- cv: Record<string, unknown> | null
286
- /** declared error keys are enforced (the route, its middleware or the app declared any) */
287
- enf: boolean
288
- /** preflight runner — chain without input validation or handler; compiled on first preflight */
289
- pf: CompiledChain | null
290
- /** what the preflight runner runs */
291
- pfChain: RuntimeMiddleware[]
292
- r: RouteHandler | WSRouteHandler
293
- /** realtime route config when this is a realtime route */
294
- rt: RealtimeConfig | null
295
- run: CompiledChain
296
- /** taps of the serving app, plus those of the app the route came from */
297
- taps: Map<string, TapFn<unknown>> | null
298
- }
299
-
300
- type CompiledChain = (ctx: object) => Response | Promise<Response>
301
-
302
- /** What one app graph serves, resolved at finalize from records and the loaded tree. */
303
- type FinalTable = {
304
- byId: Map<RouteId, RouteHandler>
305
- convert: ChainErrorConverter
306
- epoch: number
307
- /** 404 / 405: the middleware every route runs, plus the scopes covering the request path */
308
- miss: Plan
309
- plans: Map<RouteId, Plan>
310
- /** `METHOD /path` → plan, for routes without params or wildcards */
311
- statics: Record<string, Plan>
312
- wsById: Map<RouteId, WSRouteHandler>
313
- wsPlans: Map<RouteId, Plan>
314
- }
315
-
316
- /** One `app.use(mw)` — the handle it returns has to register a route, or `mw` runs nowhere. */
317
- type ChainNode = { mw: RuntimeMiddleware; parent: ChainNode | null; used: boolean }
318
-
319
- /** A route builder still waiting for `.handler()` — reported at finalize. */
320
- type PendingRoute = { id: string }
321
-
322
- /** @internal — runtime entry for a scoped middleware */
323
- type ScopedEntry = {
324
- /** cached from mw.errors at registration; undefined when none */
325
- errors: readonly string[] | undefined
326
- /** the middleware behind a request-path check, for routes the scope covers only partly */
327
- guard: RuntimeMiddleware
328
- mw: RuntimeMiddleware
329
- /** canonical full-path pattern (already rebased against basePath at .use time) */
330
- prefix: string
331
- segs: Segment[]
332
- }
333
-
334
- /**
335
- * One app graph — shared by every handle derived with `use()`, `basePath()`, `context()`,
336
- * `meta()`. Records are per graph, keyed by `RouteId`; the tree holds ids only, so a loaded
337
- * (shared, frozen) tree never carries another app's handlers.
338
- */
339
- type HoneyGraph = {
340
- /** metaSpec policies of mounted sub-apps, merged under `metaSpec` when codegen reads it */
341
- absorbedMetaSpecs: MetaSpecSource[]
342
- /** gateway catch-alls by method — root wildcards registered over a loaded tree that lacks them */
343
- catchAll: Map<string, RouteHandler>
344
- /** every `use(mw)` handle — one that never registers a route is reported at finalize */
345
- chains: ChainNode[]
346
- /** bumped by every registration; finalize re-runs when it moves */
347
- epoch: number
348
- final: FinalTable | null
349
- /** app-wide middleware that runs before every chain (serve({ cors })), keyed so a re-serve replaces it */
350
- global: Map<string, RuntimeMiddleware>
351
- hasWs: boolean
352
- /** route data of the loaded tree (per-graph copies) — null when no tree was loaded */
353
- loaded: Map<RouteId, RouteEntry> | null
354
- /** Codegen-time meta → OpenAPI policy declared on this app. Never read on the request path */
355
- metaSpec: MetaSpecConfig | null
356
- /** route builders still waiting for `.handler()` */
357
- pending: Set<PendingRoute>
358
- realtimeBus: RealtimeBus | null
359
- /** `ctx.realtime`, shared by every request of this graph */
360
- realtimeCtx: RealtimePublisher | null
361
- realtimeRoutes: Map<string, RealtimeConfig>
362
- records: Map<RouteId, RouteHandler>
363
- root: TreeNode
364
- /** root is a loaded tree (frozen, possibly shared by other apps) — copy before inserting */
365
- rootShared: boolean
366
- /** path-scoped middleware, in registration order */
367
- scoped: ScopedEntry[]
368
- /** chains of the handles that served a request — decide what 404 and 405 run when there are no routes */
369
- served: Set<RuntimeMiddleware[]>
370
- settings: AppSettings<unknown>
371
- /** registered after routeTree() with no leaf in the loaded tree — stale generated file */
372
- unexpected: Set<RouteId>
373
- /** the loaded tree came in while `honey generate` loaded the app: a stale one is replaced, not fatal */
374
- unloadIfStale: boolean
375
- wsRecords: Map<RouteId, WSRouteHandler>
376
- }
377
-
378
- /** Outcome of mounting an internal route: done, or who already owns the path. */
379
- type MountResult = "internal" | "mounted" | "user"
380
-
381
- function createGraph(): HoneyGraph {
382
- return {
383
- absorbedMetaSpecs: [],
384
- catchAll: new Map(),
385
- chains: [],
386
- epoch: 0,
387
- final: null,
388
- global: new Map(),
389
- hasWs: false,
390
- loaded: null,
391
- metaSpec: null,
392
- pending: new Set(),
393
- realtimeBus: null,
394
- realtimeCtx: null,
395
- realtimeRoutes: new Map(),
396
- records: new Map(),
397
- root: createNode(),
398
- rootShared: false,
399
- scoped: [],
400
- served: new Set(),
401
- settings: createSettings(),
402
- unexpected: new Set(),
403
- unloadIfStale: false,
404
- wsRecords: new Map(),
405
- }
406
- }
407
-
408
- /** Fresh copy of a record for another graph — sources are copied, resolved fields recomputed there. */
409
- function copyRecord<T extends RouteHandler | WSRouteHandler>(r: T): T {
410
- const out = { ...r, ek: new Set(r.ek) } as T
411
- if (r.dk !== undefined) out.dk = new Set(r.dk)
412
- if (r.cm !== undefined) out.cm = [...r.cm]
413
- if (r.rm !== undefined) out.rm = [...r.rm]
414
- delete (out as RouteHandler).ca
415
- return out
416
- }
417
-
418
- /** Every leaf a pattern occupies: an optional last param places two. */
419
- function leafVariants(segments: readonly Segment[]): Segment[][] {
420
- return expandOptional(segments)
421
- }
422
-
423
- /** Record the error behind an error response on `ctx.error` (read-only for user code). */
424
- function setCtxError(ctx: HoneyContext<unknown>, error: HoneyError): void {
425
- ;(ctx as unknown as Record<string, unknown>)["error"] = error
426
- }
427
-
428
- /** What `ctx.error` holds for a throw the boundary could not resolve: the 500 it became. */
429
- function asHoneyError(thrown: unknown): HoneyError {
430
- return thrown instanceof HoneyError
431
- ? thrown
432
- : new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error })
433
- }
434
-
435
- function patternOf(id: RouteId): { method: string; segments: Segment[] } {
436
- const { method, pattern } = splitRouteId(id)
437
- return { method, segments: parsePattern(pattern) }
438
- }
439
-
440
- /** `fn` of a tree leaf no handler serves — dispatch answers 404, codegen still documents it. */
441
- const NOT_SERVED: RouteHandler["fn"] = () => {
442
- throw new Error("honey: unserved route reached its handler")
443
- }
444
-
445
- function isRootWildcard(segments: readonly Segment[]): boolean {
446
- return segments.length === 1 && segments[0].k === "wildcard"
447
- }
448
-
449
- const EMPTY_PARAMS = EMPTY_OBJ as Record<string, string>
450
-
451
- function requestIsWsUpgrade(request: Request): boolean {
452
- try {
453
- return request.headers.get("upgrade")?.toLowerCase() === "websocket"
454
- } catch {
455
- /* Deno closes the Request after upgradeWebSocket */
456
- return false
457
- }
458
- }
459
-
460
- /** The info object `Deno.serve` passes as the second handler argument. */
461
- function isDenoServeInfo(env: unknown): boolean {
462
- return env !== null && typeof env === "object" && (env as { remoteAddr?: unknown }).remoteAddr !== undefined
463
- }
464
-
465
- /** `obj[key]` for an own key only; `undefined` for inherited names such as `constructor`. */
466
- function safeFire(fn: (() => unknown) | undefined, logger?: Logger): void {
467
- if (fn === undefined) return
468
- try {
469
- const result = fn()
470
- if (result && typeof result === "object" && "catch" in result) {
471
- ;(result as Promise<unknown>).catch((e: unknown) => {
472
- logger?.warn?.({ err: e }, "telemetry callback failed")
473
- })
474
- }
475
- } catch (e) {
476
- logger?.warn?.({ err: e }, "telemetry callback failed")
477
- }
478
- }
479
-
480
- /** Per-request state, shared by the dispatcher, the error boundary and terminal handlers. */
481
- type FetchCtx<TEnv> = {
482
- /** methods a 405 lists; also marks the miss pipeline as a 405 */
483
- allowed: string[] | null
484
- env: TEnv
485
- executionCtx: { waitUntil?: (p: Promise<unknown>) => void } | undefined
486
- log: Logger | undefined
487
- method: string
488
- path: string
489
- plan: Plan | null
490
- request: Request
491
- startTime: number
492
- /** the app's trustProxy() setting, read by ctx.ip and clientInfo() */
493
- trust: TrustSetting
494
- url: () => URL
495
- /** matched websocket route, for the upgrade terminal */
496
- ws: { handler: WSRouteHandler; params: Record<string, string> } | null
497
- /** Already known from fetch() before Deno.upgradeWebSocket consumes the request. */
498
- wsUpgrade?: boolean
499
- /** Headers copied before Deno.upgradeWebSocket closes the Request. */
500
- headerSnap?: Headers
501
- }
502
-
503
- function requestWithHeaders(req: Request, headers: Headers): Request {
504
- return new Proxy(req, {
505
- get(target, prop) {
506
- if (prop === "headers") return headers
507
- const value = Reflect.get(target, prop, target)
508
- return typeof value === "function" ? (value as (...args: never[]) => unknown).bind(target) : value
509
- },
510
- })
511
- }
512
-
513
- function ctxRequest(fc: FetchCtx<unknown>): Request {
514
- if (!fc.headerSnap) return fc.request
515
- try {
516
- void fc.request.headers.get("upgrade")
517
- return fc.request
518
- } catch {
519
- return requestWithHeaders(fc.request, fc.headerSnap)
520
- }
521
- }
522
-
523
- function defaultErrorFormatter(_error: HoneyError, defaultShape: Record<string, unknown>): Record<string, unknown> {
524
- return defaultShape
525
- }
526
-
527
- type ErrorFormatterFn = ErrorFormatter
528
-
529
- /* errorKeys the framework throws on its own behalf — input/output validation, content negotiation,
530
- * routing, and the shipped middleware. Always passes the boundary check; users never declare these via .errors(). */
531
- const FRAMEWORK_EKS = new Set<string>([
532
- EK.validation_failed,
533
- EK.output_validation_failed,
534
- EK.output_content_type_mismatch,
535
- EK.unsupported_media_type,
536
- EK.content_too_large,
537
- EK.method_not_allowed,
538
- EK.not_found,
539
- EK.too_many_requests,
540
- EK.request_timeout,
541
- EK.gateway_timeout,
542
- EK.bad_gateway,
543
- EK.forbidden,
544
- EK.bad_request,
545
- EK.malformed_body,
546
- ])
547
-
548
- function shouldValidateOutput(mode: OutputValidationMode): boolean {
549
- if (mode === "always") return true
550
- if (mode === "off") return false
551
- return (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV !== "production"
552
- }
553
-
554
- /** The scoped middleware behind a request-path check: runs only when the path is inside the scope. */
555
- function scopeGuard(segs: readonly Segment[], mw: RuntimeMiddleware): RuntimeMiddleware {
556
- const guard: RuntimeMiddleware = (ctx, next) => (pathInScope(ctx["path"] as string, segs) ? mw(ctx, next) : next())
557
- Object.defineProperty(guard, "name", { configurable: true, value: mw.name })
558
- const tagged = mw as { errors?: readonly string[]; meta?: Readonly<Record<string, unknown>> }
559
- if (tagged.errors) Object.defineProperty(guard, "errors", { value: tagged.errors })
560
- if (tagged.meta) Object.defineProperty(guard, "meta", { value: tagged.meta })
561
- return guard
562
- }
563
-
564
- /** Wrap a middleware so telemetry sees its own duration and its own throw. Built once at finalize. */
565
- function timedMiddleware(
566
- mw: RuntimeMiddleware,
567
- onMw: NonNullable<TelemetryAdapter["onMiddleware"]>,
568
- log: Logger | undefined,
569
- ): RuntimeMiddleware {
570
- const name = mw.name || "anonymous"
571
- return async (ctx, next) => {
572
- const start = performance.now()
573
- try {
574
- const res = await mw(ctx, next)
575
- safeFire(() => onMw({ duration: performance.now() - start, name }), log)
576
- return res
577
- } catch (error) {
578
- safeFire(() => onMw({ duration: performance.now() - start, error, name }), log)
579
- throw error
580
- }
581
- }
582
- }
583
-
584
- function errorsOf(mw: RuntimeMiddleware): readonly string[] | undefined {
585
- return (mw as { errors?: readonly string[] }).errors
586
- }
587
-
588
- /** Longest run of middleware every record starts its chain with — what 404 and 405 run. */
589
- function commonPrefix(chains: ReadonlyArray<readonly RuntimeMiddleware[]>): RuntimeMiddleware[] {
590
- if (chains.length === 0) return []
591
- let out = [...(chains[0] as readonly RuntimeMiddleware[])]
592
- for (let i = 1; i < chains.length && out.length > 0; i++) {
593
- const c = chains[i] as readonly RuntimeMiddleware[]
594
- let n = 0
595
- while (n < out.length && n < c.length && out[n] === c[n]) n++
596
- out = out.slice(0, n)
597
- }
598
- return out
599
- }
600
-
601
- function describeMw(mw: RuntimeMiddleware): string {
602
- return mw.name ? `"${mw.name}"` : "an anonymous middleware"
603
- }
604
-
605
- export class Honey<
606
- TEnv,
607
- TCtx = HoneyContext<TEnv>,
608
- TRoutes = {},
609
- TMeta = never,
610
- TErrorFactory = never,
611
- TDefaultErrors extends string = never,
612
- TBasePath extends string = "/",
613
- TTaps extends Record<string, unknown> = {},
614
- TScopedMw extends readonly ScopedMwEntry[] = [],
615
- > {
616
- declare readonly $basePath: TBasePath
617
- declare readonly $ctx: TCtx
618
- declare readonly $env: TEnv
619
- declare readonly $errorFactory: TErrorFactory
620
- declare readonly $meta: TMeta
621
- declare readonly $routes: TRoutes
622
- declare readonly $taps: TTaps
623
- /* view state — what routes registered through this handle capture */
624
- private _basePath: string
625
- private _chain: RuntimeMiddleware[]
626
- private _chainMeta: Record<string, unknown> | null
627
- private _contextValues: Record<string, unknown> | null
628
- private _graph: HoneyGraph
629
- /** the `use(mw)` call that created this chain — null on the root handle */
630
- private _node: ChainNode | null
631
-
632
- constructor(opts?: { graph?: HoneyGraph }) {
633
- this._basePath = "/"
634
- this._chain = []
635
- this._chainMeta = null
636
- this._contextValues = null
637
- this._graph = opts?.graph ?? createGraph()
638
- this._node = null
639
- }
640
-
641
- private get _s(): AppSettings<TEnv> {
642
- return this._graph.settings as AppSettings<TEnv>
643
- }
644
-
645
- /**
646
- * @internal — read by codegen through `metaSpecOf()`: the declared policy and every mounted
647
- * sub-app's, unmerged. Merged on read, so `metaSpec()` may come before or after `route(sub)`;
648
- * the merge lives with codegen so a served app never loads it.
649
- */
650
- private get _metaSpecSource(): MetaSpecSource {
651
- return { absorbed: this._graph.absorbedMetaSpecs, declared: this._graph.metaSpec }
652
- }
653
-
654
- /** @internal — read by codegen and OpenAPI */
655
- get _errorFactory(): ErrorFactoryRecord | null {
656
- return this._graph.settings.errorFactory
657
- }
658
- set _errorFactory(factory: ErrorFactoryRecord | null) {
659
- this._graph.settings.errorFactory = factory
660
- this._bumpEpoch()
661
- }
662
-
663
- /** @internal — read by OpenAPI */
664
- get _errorSchema(): StandardSchemaLike | null {
665
- return this._graph.settings.errorSchema
666
- }
667
-
668
- /** @internal — read by OpenAPI */
669
- get _customErrorSchema(): StandardSchemaLike | null {
670
- return this._graph.settings.customErrorSchema
671
- }
672
-
673
- private get _root(): TreeNode {
674
- return this._graph.root
675
- }
676
-
677
- private get _realtimeBus(): RealtimeBus | null {
678
- return this._graph.realtimeBus
679
- }
680
-
681
- /** A new handle on the same graph, carrying this handle's view. */
682
- private _derive(chain: RuntimeMiddleware[] = this._chain): Honey<TEnv> {
683
- const next = new Honey<TEnv>({ graph: this._graph })
684
- next._basePath = this._basePath
685
- next._chain = chain
686
- next._chainMeta = this._chainMeta
687
- next._contextValues = this._contextValues
688
- next._node = this._node
689
- return next
690
- }
691
-
692
- /** This handle registers, mounts or serves — every `use()` on its way here is in use. */
693
- private _markUsed(): void {
694
- let node = this._node
695
- while (node !== null && !node.used) {
696
- node.used = true
697
- node = node.parent
698
- }
699
- }
700
-
701
- /** @internal — used by RouteBuilder for pre-filtered error factory */
702
- get _factory(): unknown {
703
- return this._graph.settings.errorFactory
704
- }
705
-
706
- /** @internal — used by runtime error boundary */
707
- get _boundaryKey(): string | null {
708
- return this._graph.settings.defaultBoundaryKey
709
- }
710
-
711
- /** Convert an unknown thrown value to a plain error Response — no boundary, no onError. */
712
- private _toErrorResponse(thrown: unknown): Response {
713
- const error =
714
- thrown instanceof HoneyError
715
- ? thrown
716
- : new HoneyError({
717
- cause: thrown,
718
- errorKey: EK.internal_server_error,
719
- status: SK.internal_server_error,
720
- })
721
- const s = this._s
722
- return createErrorResponse(error, s.errorFormatter, s.customErrorFormatter)
723
- }
724
-
725
- private _createBoundaryError(errorKey: string, cause: unknown, fac: ErrorFactoryRecord | null): HoneyError {
726
- const factory = fac as Record<string, ((opts?: { cause?: unknown }) => HoneyError) | undefined> | null
727
- const factoryFn = factory?.[errorKey]
728
- if (factoryFn) {
729
- /* check if this is a custom schema error via ERROR_META — boundary must use standard errors only */
730
- const meta = (factory as Record<symbol, Record<string, { schema: unknown }>>)?.[ERROR_META]
731
- if (meta?.[errorKey]?.schema) {
732
- /* custom schema error — cannot be used as boundary, fall through to manual construction */
733
- } else {
734
- return factoryFn({ cause })
735
- }
736
- }
737
- return new HoneyError({
738
- cause,
739
- errorKey,
740
- status: SK.internal_server_error,
741
- })
742
- }
743
-
744
- private _createError(errorKey: string, statusKey: StatusKey): HoneyError {
745
- const factory = this._s.errorFactory as Record<string, (() => HoneyError) | undefined> | null
746
- const factoryFn = factory?.[errorKey]
747
- if (factoryFn) {
748
- return factoryFn()
749
- }
750
- return new HoneyError({ errorKey, status: statusKey })
751
- }
752
-
753
- /**
754
- * The i18n-resolved copy of `honeyError`: message from the locale's template for its
755
- * errorKey, field paths from the locale's field names. Returns `honeyError` itself when
756
- * i18n is not configured or nothing matches. Never mutates it: an error instance can be
757
- * thrown again, on another request, in another locale.
758
- */
759
- private async _resolveI18n(
760
- honeyError: HoneyError,
761
- ctx: HoneyContext<TEnv>,
762
- env: TEnv,
763
- request: Request,
764
- log?: Logger,
765
- ): Promise<HoneyError> {
766
- const i18n = this._s.errorI18n
767
- if (!i18n) return honeyError
768
- try {
769
- const locale = await i18n.resolveLocale({
770
- cookies: ctx.cookies,
771
- env,
772
- headers: ctx.headers,
773
- params: ctx.params,
774
- req: request,
775
- search: ctx.search,
776
- })
777
- await loadHoneyFeature("i18n")
778
- return getI18nRuntime().translateError(honeyError, i18n, locale)
779
- } catch (e) {
780
- log?.warn?.({ err: e }, "i18n resolution failed")
781
- return honeyError
782
- }
783
- }
784
-
785
- /**
786
- * The error boundary of every `next()`: turns what a handler or middleware threw into the
787
- * Response that replaces it, so every middleware around it sees a Response. Never rejects.
788
- */
789
- private _convertError(thrown: unknown, ctx: HoneyContext<TEnv>): Response | Promise<Response> {
790
- const fc = ctx._rq as FetchCtx<TEnv> | null
791
- const plan = fc?.plan
792
- if (fc === null || fc === undefined || plan === null || plan === undefined) {
793
- setCtxError(ctx, asHoneyError(thrown))
794
- return this._safeErrorResponse(thrown)
795
- }
796
- const fallback = (e: unknown): Response => {
797
- fc.log?.warn?.({ err: e }, "error response failed")
798
- setCtxError(ctx, asHoneyError(thrown))
799
- return this._safeErrorResponse(thrown)
800
- }
801
- try {
802
- return this._resolveErrorResponse(thrown, plan, fc, ctx).catch(fallback)
803
- } catch (e) {
804
- return fallback(e)
805
- }
806
- }
807
-
808
- /** Last resort when even the configured formatter throws. */
809
- private _safeErrorResponse(thrown: unknown): Response {
810
- try {
811
- return this._toErrorResponse(thrown instanceof HoneyError ? thrown : undefined)
812
- } catch {
813
- return new Response(JSON.stringify({ error: { errorKey: EK.internal_server_error } }), {
814
- headers: { "content-type": "application/json" },
815
- status: 500,
816
- })
817
- }
818
- }
819
-
820
- /**
821
- * Convert thrown value into an error Response — resolves boundary wrapping, i18n
822
- * translation, the onError callback, and error telemetry. `onResponse` telemetry is not
823
- * fired here: the request fires it exactly once, on the way out.
824
- */
825
- private async _resolveErrorResponse(
826
- thrown: unknown,
827
- plan: Plan,
828
- fc: FetchCtx<TEnv>,
829
- ctx: HoneyContext<TEnv>,
830
- ): Promise<Response> {
831
- const { env, log, request, startTime, method, path } = fc
832
- const s = this._s
833
- const record = plan.r
834
- const fac = (record.fac ?? null) as ErrorFactoryRecord | null
835
- let honeyError: HoneyError
836
- const boundaryKey = record.bek
837
-
838
- if (thrown instanceof HoneyError) {
839
- /* framework-managed errorKeys (input/output validation, content negotiation, etc.) are always allowed
840
- * regardless of the declared keys — users never declare them, the framework owns them. */
841
- if (plan.enf && !FRAMEWORK_EKS.has(thrown.errorKey) && !record.ek.has(thrown.errorKey)) {
842
- honeyError = boundaryKey
843
- ? this._createBoundaryError(boundaryKey, thrown, fac)
844
- : new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error })
845
- } else {
846
- honeyError = thrown
847
- }
848
- } else {
849
- honeyError = boundaryKey
850
- ? this._createBoundaryError(boundaryKey, thrown, fac)
851
- : new HoneyError({ cause: thrown, errorKey: EK.internal_server_error, status: SK.internal_server_error })
852
- }
853
-
854
- honeyError = await this._resolveI18n(honeyError, ctx, env, request, log)
855
-
856
- if (s.onError) {
857
- try {
858
- const customResult = await s.onError(thrown, this._makeErrorCtx(fc))
859
- if (customResult instanceof HoneyError) {
860
- /* user-mapped boundary error — re-run i18n against new errorKey,
861
- * then fall through to the default response path */
862
- honeyError = await this._resolveI18n(customResult, ctx, env, request, log)
863
- } else if (customResult) {
864
- safeFire(
865
- () => s.telemetry?.onError?.({ duration: performance.now() - startTime, error: honeyError, method, path }),
866
- log,
867
- )
868
- setCtxError(ctx, honeyError)
869
- ctx._isErrorResponse = true
870
- return customResult
871
- }
872
- /* customResult === undefined | void → fall through to default path */
873
- } catch (e) {
874
- log?.warn?.({ err: e }, "onError failed")
875
- }
876
- }
877
-
878
- safeFire(
879
- () => s.telemetry?.onError?.({ duration: performance.now() - startTime, error: honeyError, method, path }),
880
- log,
881
- )
882
- setCtxError(ctx, honeyError)
883
- ctx._isErrorResponse = true
884
- return this._makeErrorCtx(fc).jsonFromError(honeyError)
885
- }
886
-
887
- basePath<P extends string>(
888
- prefix: P,
889
- ): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, MergePath<TBasePath, P>, TTaps, TScopedMw> {
890
- const next = this._derive()
891
- next._basePath = mergePath(this._basePath, prefix)
892
- return next as unknown as Honey<
893
- TEnv,
894
- TCtx,
895
- TRoutes,
896
- TMeta,
897
- TErrorFactory,
898
- TDefaultErrors,
899
- MergePath<TBasePath, P>,
900
- TTaps,
901
- TScopedMw
902
- >
903
- }
904
-
905
- context<TAdds extends Record<string, unknown>>(
906
- values: TAdds,
907
- ): Honey<TEnv, TCtx & Readonly<TAdds>, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
908
- for (const key in values) {
909
- if (RESERVED_CTX_KEYS.has(key)) {
910
- throw new Error(`context() cannot set reserved key "${key}"`)
911
- }
912
- }
913
- const next = this._derive()
914
- next._contextValues = this._contextValues ? { ...this._contextValues, ...values } : { ...values }
915
- return next as unknown as Honey<
916
- TEnv,
917
- TCtx & Readonly<TAdds>,
918
- TRoutes,
919
- TMeta,
920
- TErrorFactory,
921
- TDefaultErrors,
922
- TBasePath,
923
- TTaps,
924
- TScopedMw
925
- >
926
- }
927
-
928
- logger(logger: Logger): this {
929
- this._s.logger = logger
930
- return this
931
- }
932
-
933
- outputValidation(mode: OutputValidationMode): this {
934
- this._s.outputValidation = mode
935
- this._bumpEpoch()
936
- return this
937
- }
938
-
939
- trailingSlash(mode: "enforce" | "ignore" | "strip"): this {
940
- this._s.trailingSlash = mode
941
- return this
942
- }
943
-
944
- /**
945
- * Request paths containing an encoded `/` or `\` (`%2F`, `%5C`) answer 400 by default.
946
- * `"allow"` keeps them encoded in the path, so they decode into a param value
947
- * (`/repos/:id` with `/repos/group%2Fproject` gives `id === "group/project"`). Only allow
948
- * them when nothing downstream (a `proxy()` upstream, a file resolver) treats a decoded
949
- * slash as a separator.
950
- */
951
- encodedSlashes(mode: "allow" | "reject"): this {
952
- this._s.encodedSlashes = mode
953
- return this
954
- }
955
-
956
- /**
957
- * Which reverse proxies in front of the app to believe. Decides `ctx.ip`, `clientInfo()`,
958
- * `ipRestrict`, and every other feature that needs the client's address, scheme or host.
959
- *
960
- * - `false` (default): the peer address the runtime reports is the client; `X-Forwarded-*`
961
- * headers are ignored, since any client can send them.
962
- * - a hop count: exactly that many proxies, each appending its peer to `X-Forwarded-For`
963
- * (nginx `$proxy_add_x_forwarded_for`, most load balancers). The client is the entry the
964
- * outermost proxy wrote.
965
- * - addresses and CIDR ranges of your proxies: hops from those addresses are skipped; the
966
- * first other address is the client.
967
- *
968
- * A hop count or list needs `import "@lovrozagar/honey/trust"` in the app entry.
969
- */
970
- trustProxy(value: TrustProxy): this {
971
- this._s.trust =
972
- value === false || value === 0 ? TRUST_OFF : requireFeature("trust", "app.trustProxy()").compileTrust(value)
973
- return this
974
- }
975
-
976
- /** Strip a URL path prefix before route matching — boundary-safe (won't strip partial segments), paths without the prefix pass through unchanged */
977
- stripPrefix(prefix: string): this {
978
- let normalized = prefix.replace(/\/+$/, "")
979
- if (normalized.length > 0 && normalized.charCodeAt(0) !== 47) {
980
- normalized = `/${normalized}`
981
- }
982
- this._s.stripPrefix = normalized === "" || normalized === "/" ? null : normalized
983
- return this
984
- }
985
-
986
- wsAdapter(adapter: WSAdapter): this {
987
- this._s.wsAdapter = adapter
988
- return this
989
- }
990
-
991
- /**
992
- * Serve this app's OpenAPI document at `${path}.json`, `.yaml` and `.yml` (default
993
- * `/openapi`), and optionally a docs UI. Each call serves its own document: two calls with
994
- * different options (say an internal and a public profile) never share a cache. Documents
995
- * are generated once per route change, serialized once, and sent with an `ETag` and
996
- * `Cache-Control: no-cache`. Pass `enabled: false` (for example in production) to mount
997
- * nothing while keeping the call in place.
998
- */
999
- openapi(options: {
1000
- description?: string
1001
- docs?: "scalar" | "swagger"
1002
- docsPath?: string
1003
- /** Mount the document and docs routes. Default `true`. */
1004
- enabled?: boolean
1005
- filterRoutes?: (route: { meta: unknown; method: string; path: string }) => boolean
1006
- path?: string
1007
- /** Named metaSpec profile selecting which emitted keys this document carries */
1008
- profile?: string
1009
- securitySchemes?: Record<string, unknown>
1010
- title: string
1011
- version: string
1012
- }): this {
1013
- if (options.enabled === false) return this
1014
- const stem = options.path ?? "/openapi"
1015
- const epochOf = (): number => this._graph.epoch
1016
- /* per call, not per app: each openapi() serves the document its own options describe */
1017
- const document = epochCached(epochOf, async () => {
1018
- await loadHoneyFeature("openapi")
1019
- return getOpenApiRuntime().generateOpenApi(this, {
1020
- filterRoutes: options.filterRoutes,
1021
- info: { description: options.description, title: options.title, version: options.version },
1022
- /* a served document is not an authoring moment — the check belongs to `honey generate` */
1023
- invalidate: "off",
1024
- profile: options.profile,
1025
- securitySchemes: options.securitySchemes,
1026
- })
1027
- })
1028
- /* the document first: it loads the runtime the serializers come from */
1029
- const json = epochCached(epochOf, async () => {
1030
- const doc = await document()
1031
- return getOpenApiRuntime().toServedArtifact(JSON.stringify(doc), "application/json")
1032
- })
1033
- const yaml = epochCached(epochOf, async () => {
1034
- const doc = await document()
1035
- const runtime = getOpenApiRuntime()
1036
- return runtime.toServedArtifact(runtime.toYaml(doc), "application/yaml; charset=utf-8")
1037
- })
1038
- const jsonPath = `${stem}.json`
1039
- this._mountArtifact("openapi", jsonPath, json)
1040
- this._mountArtifact("openapi", `${stem}.yml`, yaml)
1041
- this._mountArtifact("openapi", `${stem}.yaml`, yaml)
1042
- if (options.docs) {
1043
- const docs = options.docs
1044
- const specPath = mergePath(this._basePath, jsonPath)
1045
- const ui = async (ctx: { res: HoneyRes }) => {
1046
- await loadHoneyFeature("openapi")
1047
- /* the browser asks for the URL it sees: a stripped prefix is part of it */
1048
- const prefix = this._s.stripPrefix ?? ""
1049
- return getOpenApiRuntime().docsUi(docs, prefix + specPath)(ctx)
1050
- }
1051
- /* without an explicit docsPath, a user route at /docs moves the UI to /reference */
1052
- const candidates = options.docsPath ? [options.docsPath] : ["/docs", "/reference"]
1053
- let mounted: MountResult = "user"
1054
- let tried = candidates[0]
1055
- for (const p of candidates) {
1056
- tried = p
1057
- mounted = this._mountInternalGet(p, ui)
1058
- if (mounted !== "user") break
1059
- }
1060
- if (mounted !== "mounted") {
1061
- const at = mergePath(this._basePath, tried)
1062
- throw new Error(
1063
- mounted === "internal"
1064
- ? `Honey.openapi({ docs }) cannot mount at ${at}: an earlier openapi() or manifest() serves it. Pass docsPath.`
1065
- : `Honey.openapi({ docs }) cannot mount at ${at}: a route already owns it. Pass docsPath.`,
1066
- )
1067
- }
1068
- }
1069
- return this
1070
- }
1071
-
1072
- /**
1073
- * Serve the route manifest at `path` (default `/manifest.json`), filtered by the same
1074
- * visibility policy as the OpenAPI document. Cached per route change, sent with an `ETag`.
1075
- * `enabled: false` mounts nothing.
1076
- */
1077
- manifest(options?: { enabled?: boolean; path?: string }): this {
1078
- if (options?.enabled === false) return this
1079
- const epochOf = (): number => this._graph.epoch
1080
- const artifact = epochCached(epochOf, async () => {
1081
- await loadHoneyFeature("openapi")
1082
- const runtime = getOpenApiRuntime()
1083
- return runtime.toServedArtifact(JSON.stringify(await runtime.generateManifest(this)), "application/json")
1084
- })
1085
- this._mountArtifact("manifest", options?.path ?? "/manifest.json", artifact)
1086
- return this
1087
- }
1088
-
1089
- /** Mount a served document; a path another route or an earlier call owns is an error. */
1090
- private _mountArtifact(owner: "manifest" | "openapi", path: string, load: () => Promise<ServedArtifact>): void {
1091
- const result = this._mountInternalGet(path, async (ctx) => {
1092
- const c = ctx as unknown as { req: Request; res: HoneyRes }
1093
- const artifact = await load()
1094
- return c.res.raw(getOpenApiRuntime().artifactResponse(c.req, artifact))
1095
- })
1096
- if (result === "mounted") return
1097
- const at = mergePath(this._basePath, path)
1098
- throw new Error(
1099
- result === "internal"
1100
- ? `Honey.${owner}() cannot mount GET ${at}: an earlier openapi() or manifest() serves it. Pass a different path.`
1101
- : `Honey.${owner}() cannot mount GET ${at}: a route already owns it. Pass a different path.`,
1102
- )
1103
- }
1104
-
1105
- async serve(options?: HoneyServeOptions): Promise<ServeHandle> {
1106
- await loadHoneyFeature("serve")
1107
- return getServeRuntime()(this, options)
1108
- }
1109
-
1110
- /**
1111
- * Mount an internal GET (spec, docs, manifest). Reports who owns the path when it cannot:
1112
- * a user route (`"user"`) or an earlier internal mount (`"internal"`). Only an exact leaf
1113
- * counts: a `/:slug` or `/*rest` route does not block a static internal path, which wins
1114
- * over it on precedence, with backtracking, in both the runtime and a loaded tree.
1115
- */
1116
- private _mountInternalGet(path: string, fn: (ctx: { res: HoneyRes }) => Response | Promise<Response>): MountResult {
1117
- const fullPath = mergePath(this._basePath, path)
1118
- const segments = parsePattern(fullPath)
1119
- const id = routeId("GET", fullPath)
1120
- const g = this._graph
1121
- const existing = g.records.get(id)
1122
- if (existing) return existing._skip === true ? "internal" : "user"
1123
- const leaf = findLeaf(g.root, segments, "GET")
1124
- if (leaf !== undefined && leaf !== id) return "user"
1125
- if (findLeaf(g.root, segments, "ALL") !== undefined) return "user"
1126
- /* a leaf the loaded tree holds for a user route the app has yet to register */
1127
- if (leaf === id && g.loaded?.has(id)) return "user"
1128
- const routeHandler: RouteHandler = {
1129
- _skip: true,
1130
- bek: null,
1131
- cm: [...this._chain],
1132
- cv: this._contextValues,
1133
- dk: new Set(),
1134
- ek: new Set(),
1135
- fn: (ctx) => fn(ctx as { res: HoneyRes }),
1136
- iv: null,
1137
- mt: null,
1138
- mw: [],
1139
- os: null,
1140
- ov: null,
1141
- own: g.settings,
1142
- rb: null,
1143
- rm: [],
1144
- xm: null,
1145
- }
1146
- this._markUsed()
1147
- this._addRoute("GET", segments, routeHandler)
1148
- return "mounted"
1149
- }
1150
-
1151
- private _bumpEpoch(): void {
1152
- this._graph.epoch++
1153
- }
1154
-
1155
- /** Place `id` at every leaf of `segments`, copying a shared (loaded) root first. */
1156
- private _placeLeaves(segments: readonly Segment[], method: string, id: RouteId): void {
1157
- const g = this._graph
1158
- const variants = leafVariants(segments)
1159
- if (variants.every((v) => findLeaf(g.root, v, method) === id)) return
1160
- if (g.rootShared) {
1161
- g.root = cloneTree(g.root)
1162
- g.rootShared = false
1163
- }
1164
- for (const v of variants) insertLeaf(g.root, v, method, id)
1165
- }
1166
-
1167
- /**
1168
- * @internal — register one route record. Over a loaded tree the record binds to the leaf
1169
- * the tree already holds for its id; a root wildcard the tree lacks becomes the gateway
1170
- * catch-all; any other unknown route marks the tree as stale (reported at finalize).
1171
- */
1172
- _addRoute(method: string, segments: readonly Segment[], record: RouteHandler): void {
1173
- const pattern = canonical(segments)
1174
- const id = routeId(method, pattern)
1175
- const g = this._graph
1176
- if (g.records.has(id) || (isRootWildcard(segments) && g.catchAll.has(method))) {
1177
- throw new Error(`Duplicate route: ${id}`)
1178
- }
1179
- record.id = id
1180
- record.rp = pattern
1181
- if (g.loaded !== null && record._skip !== true) {
1182
- const known = leafVariants(segments).every((v) => findLeaf(g.root, v, method) === id)
1183
- if (!known) {
1184
- if (isRootWildcard(segments)) {
1185
- record.ca = true
1186
- g.catchAll.set(method, record)
1187
- this._bumpEpoch()
1188
- return
1189
- }
1190
- g.unexpected.add(id)
1191
- }
1192
- }
1193
- this._placeLeaves(segments, method, id)
1194
- g.records.set(id, record)
1195
- this._bumpEpoch()
1196
- }
1197
-
1198
- /** @internal — register one websocket (or realtime) route record. */
1199
- _addWsRoute(segments: readonly Segment[], record: WSRouteHandler): void {
1200
- const pattern = canonical(segments)
1201
- const id = routeId("WS", pattern)
1202
- const g = this._graph
1203
- if (g.wsRecords.has(id)) throw new Error(`Duplicate WebSocket route: ${pattern}`)
1204
- record.id = id
1205
- record.rp = pattern
1206
- if (g.loaded !== null && leafVariants(segments).some((v) => findLeaf(g.root, v, "WS") !== id)) {
1207
- g.unexpected.add(id)
1208
- }
1209
- this._placeLeaves(segments, "WS", id)
1210
- g.wsRecords.set(id, record)
1211
- g.hasWs = true
1212
- this._bumpEpoch()
1213
- }
1214
-
1215
- /**
1216
- * Resolve what this graph serves: registered records, live records carried by a loaded
1217
- * snapshot, and delegated leaves the gateway catch-all serves. Then resolve every record —
1218
- * its full chain (`chain → scoped → route → input validation`), error keys, meta and error
1219
- * factory — and compile it once. Re-runs whenever the graph's epoch moved. Throws, naming
1220
- * every route, when a loaded tree and the registered routes disagree (the generated file is
1221
- * stale), and when a `use(mw)` handle or a route builder was left unused (the middleware or
1222
- * route would silently be missing).
1223
- */
1224
- _finalize(): FinalTable {
1225
- const g = this._graph
1226
- if (g.final !== null && g.final.epoch === g.epoch) return g.final
1227
- this._assertNothingDiscarded()
1228
- const byId = new Map<RouteId, RouteHandler>()
1229
- const wsById = new Map<RouteId, WSRouteHandler>()
1230
- for (const [id, r] of g.records) byId.set(id, r)
1231
- for (const [id, r] of g.wsRecords) wsById.set(id, r)
1232
- const problems: string[] = []
1233
- if (g.loaded !== null) {
1234
- for (const id of g.unexpected) problems.push(`${id} is registered but missing from the loaded route tree`)
1235
- let delegated = 0
1236
- for (const [id, entry] of g.loaded) {
1237
- const { method, pattern } = splitRouteId(id)
1238
- if (method === "WS") {
1239
- if (wsById.has(id)) continue
1240
- if (entry.h !== undefined) {
1241
- const live = copyRecord(entry.h as WSRouteHandler)
1242
- live.id = id
1243
- live.rp = pattern
1244
- wsById.set(id, live)
1245
- }
1246
- /* a websocket leaf nothing serves is not upgraded: the request falls through to HTTP */
1247
- continue
1248
- }
1249
- if (byId.has(id)) continue
1250
- if (entry.h !== undefined) {
1251
- const live = copyRecord(entry.h as RouteHandler)
1252
- live.id = id
1253
- live.rp = pattern
1254
- byId.set(id, live)
1255
- continue
1256
- }
1257
- const ca = g.catchAll.get(method) ?? g.catchAll.get("ALL")
1258
- if (ca !== undefined) {
1259
- const dl = copyRecord(ca)
1260
- dl.dl = true
1261
- dl.id = id
1262
- dl.rp = pattern
1263
- dl.xm = entry.mt ? { ...entry.mt } : null
1264
- dl.iv = entry.iv ?? null
1265
- dl.os = entry.os ?? null
1266
- dl.sfac = entry.sfac ?? null
1267
- dl.dk = new Set([...(ca.dk ?? []), ...(entry.ek ?? [])])
1268
- if (entry.bek !== undefined && entry.bek !== null) dl.rb = entry.bek
1269
- byId.set(id, dl)
1270
- delegated++
1271
- continue
1272
- }
1273
- /* no handler anywhere: documented (served specs list it) but answered with 404 */
1274
- byId.set(id, {
1275
- bek: null,
1276
- cm: [],
1277
- dk: new Set(entry.ek ?? []),
1278
- dl: true,
1279
- ek: new Set(),
1280
- fn: NOT_SERVED,
1281
- id,
1282
- iv: entry.iv ?? null,
1283
- mt: null,
1284
- mw: [],
1285
- os: entry.os ?? null,
1286
- rb: entry.bek ?? null,
1287
- rm: [],
1288
- rp: pattern,
1289
- sfac: entry.sfac ?? null,
1290
- xm: entry.mt ? { ...entry.mt } : null,
1291
- })
1292
- }
1293
- if (g.catchAll.size > 0 && delegated === 0) {
1294
- for (const id of g.catchAll.keys()) {
1295
- problems.push(`ALL root wildcard (${g.catchAll.get(id)?.id}) is missing from the loaded route tree`)
1296
- }
1297
- }
1298
- }
1299
- if (problems.length > 0) {
1300
- /* generation rewrites this file from the registered routes; serving it stale is the error */
1301
- if (g.unloadIfStale) {
1302
- this._unloadTree()
1303
- return this._finalize()
1304
- }
1305
- throw new Error(`Route tree out of date — regenerate it (\`honey generate\`):\n ${problems.join("\n ")}`)
1306
- }
1307
-
1308
- const convert: ChainErrorConverter = (thrown, ctx) => this._convertError(thrown, ctx as HoneyContext<TEnv>)
1309
- const plans = new Map<RouteId, Plan>()
1310
- const wsPlans = new Map<RouteId, Plan>()
1311
- const statics = Object.create(null) as Record<string, Plan>
1312
- const userChains: RuntimeMiddleware[][] = []
1313
- for (const [id, r] of byId) {
1314
- const plan = this._planRoute(r, convert)
1315
- plans.set(id, plan)
1316
- if (r._skip !== true && r.dl !== true) userChains.push(r.cm ?? [])
1317
- const { method, segments } = patternOf(id)
1318
- for (const v of leafVariants(segments)) {
1319
- if (isStaticPattern(v)) statics[`${method} ${canonical(v)}`] = plan
1320
- }
1321
- }
1322
- for (const [id, r] of wsById) {
1323
- wsPlans.set(id, this._planWs(r, convert))
1324
- userChains.push(r.cm ?? [])
1325
- }
1326
- /* an app with no routes (static files only) has nothing but the handles that serve it */
1327
- if (userChains.length === 0) for (const chain of g.served) userChains.push(chain)
1328
- const miss = this._planMiss(commonPrefix(userChains), convert)
1329
- g.final = { byId, convert, epoch: g.epoch, miss, plans, statics, wsById, wsPlans }
1330
- return g.final
1331
- }
1332
-
1333
- /** `use(mw)` handles that never registered, mounted or served, and builders without `.handler()`. */
1334
- private _assertNothingDiscarded(): void {
1335
- const g = this._graph
1336
- const problems: string[] = []
1337
- for (const node of g.chains) {
1338
- if (node.used) continue
1339
- problems.push(
1340
- `app.use(${describeMw(node.mw)}) returned a handle that never registers a route, so the middleware runs nowhere. ` +
1341
- "use() does not change the handle it is called on: keep the returned value and register routes on it " +
1342
- "(`const authed = app.use(auth); authed.get(...)`), or chain it (`honey().use(mw).get(...)`).",
1343
- )
1344
- }
1345
- for (const p of g.pending) {
1346
- problems.push(`${p.id} was declared but never got a .handler() (or .proxy()), so the route is not registered.`)
1347
- }
1348
- if (problems.length > 0) throw new Error(`honey: ${problems.join("\n ")}`)
1349
- }
1350
-
1351
- /**
1352
- * Resolve a record against this graph: chain = captured chain, the scopes that cover its
1353
- * pattern (behind a request-path check where only some of its paths are inside), then its
1354
- * route middleware. Error keys and contributed meta come from that chain, in run order.
1355
- * The error factory, default errors and boundary come from the app the route was
1356
- * registered on when that app has a factory, else from this one.
1357
- */
1358
- private _resolve(
1359
- r: RouteHandler | WSRouteHandler,
1360
- everyScope = false,
1361
- ): { enf: boolean; own: AppSettings<unknown>; taps: Map<string, TapFn<unknown>> | null } {
1362
- const s = this._graph.settings
1363
- const own = (r.own as AppSettings<unknown> | undefined) ?? s
1364
- const src = own.errorFactory !== null ? own : s
1365
- const segments = parsePattern(r.rp ?? "/")
1366
- const scoped: RuntimeMiddleware[] = []
1367
- for (const entry of this._graph.scoped) {
1368
- if (everyScope) {
1369
- scoped.push(entry.guard)
1370
- continue
1371
- }
1372
- const cov = scopeCoverage(segments, entry.segs)
1373
- if (cov === "all") scoped.push(entry.mw)
1374
- else if (cov === "some") scoped.push(entry.guard)
1375
- }
1376
- const mw = [...this._graph.global.values(), ...(r.cm ?? []), ...scoped, ...(r.rm ?? [])]
1377
- /* app defaults, then what the middleware declares in run order, then the route's own */
1378
- const ek = new Set<string>(src.defaultErrorKeys)
1379
- for (const m of mw) {
1380
- const errs = errorsOf(m)
1381
- if (errs) for (const k of errs) ek.add(k)
1382
- }
1383
- if (r.dk !== undefined) for (const k of r.dk) ek.add(k)
1384
- const bek = r.rb ?? src.defaultBoundaryKey
1385
- if (bek !== null) ek.add(bek)
1386
- const enf = ek.size > 0
1387
- const fac = src.errorFactory
1388
- let ef: ErrorFactoryRecord | null = fac
1389
- if (fac !== null && enf) {
1390
- const subset = Object.create(null) as ErrorFactoryRecord
1391
- for (const key of ek) {
1392
- if (key in fac) subset[key] = fac[key] as ErrorFactoryRecord[string]
1393
- }
1394
- ef = Object.freeze(subset)
1395
- }
1396
- r.mw = mw
1397
- r.ek = ek
1398
- r.bek = bek
1399
- r.fac = fac
1400
- r.ef = ef
1401
- r.mt = mergeContributedMeta(collectMiddlewareMeta([mw]), r.xm ? { ...r.xm } : null)
1402
- /* the serving app's taps win; a route from another app keeps that app's for the rest */
1403
- const taps =
1404
- own === s || own.taps === null ? s.taps : s.taps === null ? own.taps : new Map([...own.taps, ...s.taps])
1405
- return { enf, own, taps }
1406
- }
1407
-
1408
- /** Wrap each middleware for `telemetry.onMiddleware`, once per finalize. */
1409
- private _timed(chain: RuntimeMiddleware[]): RuntimeMiddleware[] {
1410
- const onMw = this._graph.settings.telemetry?.onMiddleware
1411
- if (onMw === undefined) return chain
1412
- const log = this._graph.settings.logger ?? undefined
1413
- return chain.map((m) => timedMiddleware(m, onMw, log))
1414
- }
1415
-
1416
- private _planRoute(r: RouteHandler, convert: ChainErrorConverter): Plan {
1417
- const { enf, own, taps } = this._resolve(r)
1418
- const s = this._graph.settings
1419
- const ovm = own.outputValidation ?? s.outputValidation ?? "off"
1420
- const base = this._timed(r.mw)
1421
- const chain = [...base]
1422
- /* a delegated route's schemas document it; the body is forwarded, never validated here */
1423
- const iv = r.iv
1424
- if (iv && r.dl !== true) {
1425
- chain.push((ctx, next) =>
1426
- validateInput(
1427
- iv,
1428
- ctx["req"] as Request,
1429
- ctx["params"] as Record<string, string>,
1430
- ctx as { searchAll?: Record<string, string[]> },
1431
- ).then((validated) => {
1432
- ctx["input"] = validated
1433
- return next()
1434
- }),
1435
- )
1436
- }
1437
- const fn = r.fn
1438
- const handler: CompiledChain =
1439
- r.os && ovm !== "off"
1440
- ? (ctx) => {
1441
- const res = fn(ctx)
1442
- if (!shouldValidateOutput(ovm)) return res
1443
- return res instanceof Promise
1444
- ? res.then((v) => this._validateOutput(r, ctx as HoneyContext<TEnv>, v))
1445
- : this._validateOutput(r, ctx as HoneyContext<TEnv>, res)
1446
- }
1447
- : (fn as CompiledChain)
1448
- return {
1449
- cv: r.cv ?? null,
1450
- enf,
1451
- pf: null,
1452
- pfChain: base,
1453
- r,
1454
- rt: null,
1455
- run: compileChain(chain, handler, convert),
1456
- taps,
1457
- }
1458
- }
1459
-
1460
- private _planWs(r: WSRouteHandler, convert: ChainErrorConverter): Plan {
1461
- const { enf } = this._resolve(r)
1462
- const rt = this._graph.realtimeRoutes.get(r.rp) ?? null
1463
- const chain = this._timed(r.mw)
1464
- const iv = r.iv
1465
- if (iv) {
1466
- chain.push((ctx, next) =>
1467
- validateInput(
1468
- iv,
1469
- ctx["req"] as Request,
1470
- ctx["params"] as Record<string, string>,
1471
- ctx as { searchAll?: Record<string, string[]> },
1472
- ).then((validated) => {
1473
- ctx["input"] = validated
1474
- return next()
1475
- }),
1476
- )
1477
- }
1478
- const terminal: CompiledChain =
1479
- rt !== null
1480
- ? (ctx) => this._realtimeUpgrade(ctx as HoneyContext<TEnv>, rt)
1481
- : (ctx) => this._wsUpgrade(ctx as HoneyContext<TEnv>)
1482
- return {
1483
- cv: r.cv ?? null,
1484
- enf,
1485
- pf: null,
1486
- pfChain: [],
1487
- r,
1488
- rt,
1489
- run: compileChain(chain, terminal, convert),
1490
- taps: null,
1491
- }
1492
- }
1493
-
1494
- /**
1495
- * 404 and 405 run through the same pipeline as a route: the middleware every route of this
1496
- * graph starts with (with no routes: every handle that served a request), then every scope
1497
- * (behind its request-path check), then the 404/405 answer. So a logger or CORS policy on the whole app also covers unknown paths, and a
1498
- * scope's guard answers before "not found" leaks what exists under it.
1499
- */
1500
- private _planMiss(prefix: RuntimeMiddleware[], convert: ChainErrorConverter): Plan {
1501
- const r: RouteHandler = {
1502
- bek: null,
1503
- cm: prefix,
1504
- dk: new Set(),
1505
- ek: new Set(),
1506
- fn: (ctx) => {
1507
- const fc = (ctx as HoneyContext<TEnv>)._rq as FetchCtx<TEnv>
1508
- return fc.allowed !== null ? this._make405(fc, fc.allowed) : this._make404(fc)
1509
- },
1510
- mt: null,
1511
- mw: [],
1512
- rm: [],
1513
- rp: "",
1514
- xm: null,
1515
- }
1516
- /* every scope, each behind its path check — the request path decides */
1517
- const { enf } = this._resolve(r, true)
1518
- return {
1519
- cv: null,
1520
- enf,
1521
- pf: null,
1522
- pfChain: [],
1523
- r,
1524
- rt: null,
1525
- run: compileChain(this._timed(r.mw), r.fn as CompiledChain, convert),
1526
- taps: null,
1527
- }
1528
- }
1529
-
1530
- /**
1531
- * @internal — every served route as one entry per leaf (an optional param yields two
1532
- * paths), in tree order. Codegen and OpenAPI read routes through this.
1533
- */
1534
- _collectRoutes(includeSkipped = false): Array<{ handler: RouteHandler; method: string; path: string }> {
1535
- const final = this._finalize()
1536
- const out: Array<{ handler: RouteHandler; method: string; path: string }> = []
1537
- forEachLeaf(this._root, (method, path, id) => {
1538
- if (method === "WS") return
1539
- const handler = final.byId.get(id)
1540
- if (handler === undefined) return
1541
- if (handler._skip && !includeSkipped) return
1542
- out.push({ handler, method, path })
1543
- })
1544
- return out
1545
- }
1546
-
1547
- /** @internal — websocket routes, one entry per leaf. */
1548
- _collectWsRoutes(): Array<{ handler: WSRouteHandler; path: string }> {
1549
- const final = this._finalize()
1550
- const out: Array<{ handler: WSRouteHandler; path: string }> = []
1551
- forEachLeaf(this._root, (method, path, id) => {
1552
- if (method !== "WS") return
1553
- const handler = final.wsById.get(id)
1554
- if (handler !== undefined) out.push({ handler, path })
1555
- })
1556
- return out
1557
- }
1558
-
1559
- /** @internal — the route-graph epoch; served documents cache against it. */
1560
- get _epoch(): number {
1561
- return this._graph.epoch
1562
- }
1563
-
1564
- defaultErrorFormatter<TSchema extends StandardSchemaLike>(
1565
- schema: TSchema,
1566
- fn: (error: HoneyError) => InferOutput<TSchema>,
1567
- ): this
1568
- defaultErrorFormatter(fn: ErrorFormatterFn): this
1569
- defaultErrorFormatter(
1570
- schemaOrFn: ErrorFormatterFn | StandardSchemaLike,
1571
- maybeFn?: (error: HoneyError) => unknown,
1572
- ): this {
1573
- const s = this._s
1574
- if (typeof schemaOrFn === "function") {
1575
- s.errorSchema = null
1576
- s.errorFormatter = schemaOrFn
1577
- } else {
1578
- s.errorSchema = schemaOrFn
1579
- const mapper = maybeFn as (error: HoneyError) => Record<string, unknown>
1580
- s.errorFormatter = (error) => mapper(error)
1581
- }
1582
- return this
1583
- }
1584
-
1585
- customErrorFormatter<TSchema extends StandardSchemaLike>(
1586
- schema: TSchema,
1587
- fn: (error: HoneyError, data: Record<string, unknown>) => InferOutput<TSchema>,
1588
- ): this
1589
- customErrorFormatter(fn: CustomErrorFormatter): this
1590
- customErrorFormatter(
1591
- schemaOrFn: CustomErrorFormatter | StandardSchemaLike,
1592
- maybeFn?: (error: HoneyError, data: Record<string, unknown>) => unknown,
1593
- ): this {
1594
- const s = this._s
1595
- if (typeof schemaOrFn === "function") {
1596
- s.customErrorSchema = null
1597
- s.customErrorFormatter = schemaOrFn
1598
- } else {
1599
- s.customErrorSchema = schemaOrFn
1600
- const mapper = maybeFn as (error: HoneyError, data: Record<string, unknown>) => Record<string, unknown>
1601
- s.customErrorFormatter = (error, data) => mapper(error, data)
1602
- }
1603
- return this
1604
- }
1605
-
1606
- errorI18n(config: ErrorI18nConfig<TEnv>): this {
1607
- this._s.errorI18n = config
1608
- return this
1609
- }
1610
-
1611
- onError(handler: OnErrorFn<TEnv>): this {
1612
- this._s.onError = handler
1613
- return this
1614
- }
1615
-
1616
- onMethodNotAllowed(handler: OnMethodNotAllowedFn<TEnv>): this {
1617
- this._s.onMethodNotAllowed = handler
1618
- return this
1619
- }
1620
-
1621
- onNotFound(handler: OnNotFoundFn<TEnv>): this {
1622
- this._s.onNotFound = handler
1623
- return this
1624
- }
1625
-
1626
- /** Register a tap handler keyed by name — fires after successful handler response */
1627
- tap<K extends string>(
1628
- key: K,
1629
- handler: (ctx: TapContext<TEnv>, payload: K extends keyof TTaps ? TTaps[K] : unknown) => void | Promise<void>,
1630
- ): this {
1631
- const s = this._s
1632
- if (s.taps === null) s.taps = new Map()
1633
- s.taps.set(key, handler as TapFn<TEnv>)
1634
- this._bumpEpoch()
1635
- return this
1636
- }
1637
-
1638
- telemetry(adapter: TelemetryAdapter): this {
1639
- this._s.telemetry = adapter
1640
- /* onMiddleware wraps the compiled chains */
1641
- this._bumpEpoch()
1642
- return this
1643
- }
1644
-
1645
- /**
1646
- * Load a route tree — a generated `routes.gen.ts`, an `app.toRouteTree()` snapshot, or a
1647
- * `mergeTree()` of either. The tree supplies the router topology; what each route runs
1648
- * comes from the routes this app registers afterwards (bound by `RouteId`), from live
1649
- * records in a snapshot, or — for leaves with neither — from a root wildcard the app
1650
- * registers as its gateway catch-all. Must be called before any route is registered.
1651
- */
1652
- routeTree(tree: RouteTree): this {
1653
- /* `honey generate` loads the app that imports the very file it is about to rewrite: a tree
1654
- from an older honey is ignored there, and the routes the app registers are used instead */
1655
- const generating = isCodegenProcess()
1656
- if (generating) {
1657
- try {
1658
- assertTreeFormat(tree)
1659
- } catch {
1660
- return this
1661
- }
1662
- }
1663
- assertTreeFormat(tree)
1664
- const g = this._graph
1665
- if (g.loaded !== null) throw new Error("routeTree() was already called on this app")
1666
- for (const r of g.records.values()) {
1667
- if (r._skip !== true) throw new Error(`routeTree() must be called before routes are registered (${r.id} is)`)
1668
- }
1669
- if (g.wsRecords.size > 0 || g.catchAll.size > 0) {
1670
- throw new Error("routeTree() must be called before routes are registered")
1671
- }
1672
- forEachLeaf(tree.root, (method, path, id) => {
1673
- if (!(id in tree.routes)) throw new Error(`routeTree(): leaf ${method} ${path} has no route entry (${id})`)
1674
- })
1675
- const internals = [...g.records.values()]
1676
- g.records.clear()
1677
- g.root = freezeTree(tree.root)
1678
- g.rootShared = true
1679
- g.loaded = new Map()
1680
- g.unloadIfStale = generating
1681
- for (const id of Object.keys(tree.routes)) g.loaded.set(id, { ...tree.routes[id] })
1682
- if (hasWsLeaf(g.root)) g.hasWs = true
1683
- /* internal routes mounted before the tree was loaded move onto the loaded topology */
1684
- for (const r of internals) {
1685
- const { method, segments } = patternOf(r.id as RouteId)
1686
- this._addRoute(method, segments, r)
1687
- }
1688
- this._bumpEpoch()
1689
- return this
1690
- }
1691
-
1692
- /**
1693
- * Snapshot this app's routes as a tree: a copy of the topology plus, per route, its data
1694
- * and its live record. Internal routes (spec, docs, manifest) are left out. Loading the
1695
- * snapshot into another app serves these records; nothing in it is shared with this app.
1696
- * Each record keeps this app's settings (error factory, defaults, boundary) and context
1697
- * values, and carries the scoped middleware that covers it here as part of its chain —
1698
- * scopes themselves do not travel in a tree.
1699
- */
1700
- toRouteTree(): RouteTree {
1701
- this._markUsed()
1702
- const final = this._finalize()
1703
- const routes = Object.create(null) as Record<RouteId, RouteEntry>
1704
- const keep = new Set<RouteId>()
1705
- for (const [id, r] of final.byId) {
1706
- if (r._skip) continue
1707
- keep.add(id)
1708
- routes[id] = {
1709
- bek: r.bek,
1710
- ek: [...r.ek],
1711
- h: this._bake(r),
1712
- iv: r.iv ?? null,
1713
- mt: r.mt,
1714
- os: r.os ?? null,
1715
- }
1716
- }
1717
- for (const [id, r] of final.wsById) {
1718
- keep.add(id)
1719
- routes[id] = { bek: r.bek, ek: [...r.ek], h: this._bake(r), iv: r.iv, mt: r.mt }
1720
- }
1721
- const root = createNode()
1722
- forEachLeaf(this._root, (method, path, id) => {
1723
- if (keep.has(id)) insertLeaf(root, parsePattern(path), method, id)
1724
- })
1725
- return { meta: {}, root, routes, v: ROUTE_TREE_VERSION }
1726
- }
1727
-
1728
- /** A resolved record as a standalone source: its resolved chain minus route middleware becomes its chain. */
1729
- private _bake<T extends RouteHandler | WSRouteHandler>(r: T): T {
1730
- const h = copyRecord(r)
1731
- const rm = r.rm ?? []
1732
- /* app-wide middleware stays with the app; scopes travel as part of the chain */
1733
- h.cm = r.mw.slice(this._graph.global.size, r.mw.length - rm.length)
1734
- h.rm = [...rm]
1735
- h.dk = new Set(r.ek)
1736
- h.rb = r.bek
1737
- h.xm = r.mt ? { ...r.mt } : null
1738
- h.own = r.own ?? this._graph.settings
1739
- return h
1740
- }
1741
-
1742
- /**
1743
- * @internal — generate-time: copy input/output schemas and the error factory from `source`
1744
- * onto this app's routes that lack them (a gateway serves its generated tree, which carries
1745
- * none). A downstream route's error keys resolve against its own app's factory, so the
1746
- * gateway documents each under the status that app gives it.
1747
- */
1748
- _overlaySchemas(source: RouteTree): void {
1749
- const g = this._graph
1750
- for (const [id, entry] of Object.entries(source.routes)) {
1751
- const h = entry.h as RouteHandler | undefined
1752
- const iv = entry.iv ?? h?.iv ?? null
1753
- const os = entry.os ?? h?.os ?? null
1754
- const sfac = h?.fac ?? null
1755
- if (iv === null && os === null && sfac === null) continue
1756
- const target = g.records.get(id) ?? g.loaded?.get(id)
1757
- if (target === undefined) continue
1758
- target.iv ??= iv
1759
- target.os ??= os
1760
- target.sfac ??= sfac
1761
- }
1762
- this._bumpEpoch()
1763
- }
1764
-
1765
- /**
1766
- * @internal — generate-time: the route tree a gateway serves. `source` is the merged
1767
- * downstream tree (`codegen.mergeTree`); the gateway's own registered routes join it as
1768
- * ordinary leaves, while its root wildcards stay out of the tree — they are the catch-all
1769
- * the downstream leaves delegate to. An own route that is also a downstream route, or
1770
- * whose topology conflicts with one, throws here instead of at request time. The app is
1771
- * rebound to the result, so codegen that runs next (OpenAPI, manifest) sees what it serves.
1772
- */
1773
- _gatewayTree(source: RouteTree): RouteTree {
1774
- assertTreeFormat(source)
1775
- const g = this._graph
1776
- const root = cloneTree(source.root)
1777
- const routes = dict<RouteEntry>()
1778
- for (const id of Object.keys(source.routes)) {
1779
- const { bek, ek, i, mt } = source.routes[id]
1780
- routes[id] = { bek, ek, i, mt }
1781
- }
1782
- const own: { method: string; segments: Segment[]; id: RouteId }[] = []
1783
- /* every route id this app serves, root wildcards included */
1784
- const served = new Set<string>()
1785
- for (const r of [...g.records.values(), ...g.catchAll.values(), ...g.wsRecords.values()]) {
1786
- if ((r as RouteHandler)._skip === true) continue
1787
- const id = r.id as RouteId
1788
- served.add(id)
1789
- const { method, segments } = patternOf(id)
1790
- if (!isRootWildcard(segments)) own.push({ id, method, segments })
1791
- }
1792
- const overlap = own.filter((o) => o.id in routes)
1793
- /* a merge source built from this app itself (`app.toRouteTree()`) holds exactly the routes
1794
- this app serves; anything else that overlaps is a gateway route shadowing a downstream
1795
- one — judged by the route sets, never by how many routes overlap */
1796
- const sourceIds = Object.keys(routes)
1797
- const isSelf = own.length > 0 && overlap.length === own.length && sourceIds.every((id) => served.has(id))
1798
- if (overlap.length > 0 && !isSelf) {
1799
- throw new Error(
1800
- `Gateway routes are also downstream routes in the merged tree: ${overlap.map((o) => o.id).join(", ")}`,
1801
- )
1802
- }
1803
- if (isSelf) own.length = 0
1804
- for (const { id, method, segments } of own) {
1805
- for (const v of leafVariants(segments)) insertLeaf(root, v, method, id)
1806
- routes[id] = {}
1807
- }
1808
- this._replaceLoadedTree({ meta: { ...source.meta }, root, routes, v: ROUTE_TREE_VERSION })
1809
- /* own routes resolved over the rebound tree carry their final error keys and meta */
1810
- const snapshot = this.toRouteTree()
1811
- for (const { id } of own) {
1812
- const { bek, ek, mt } = snapshot.routes[id]
1813
- routes[id] = { bek, ek, mt }
1814
- }
1815
- const tree: RouteTree = { meta: { ...source.meta }, root, routes, v: ROUTE_TREE_VERSION }
1816
- this._replaceLoadedTree(tree)
1817
- return tree
1818
- }
1819
-
1820
- /** @internal — drop the loaded tree and route by the registered routes alone. */
1821
- _unloadTree(): void {
1822
- const g = this._graph
1823
- const http = [...g.records.values(), ...g.catchAll.values()]
1824
- const ws = [...g.wsRecords.values()]
1825
- g.records.clear()
1826
- g.catchAll.clear()
1827
- g.wsRecords.clear()
1828
- g.unexpected.clear()
1829
- g.loaded = null
1830
- g.unloadIfStale = false
1831
- g.root = createNode()
1832
- g.rootShared = false
1833
- g.hasWs = false
1834
- for (const r of http) {
1835
- delete r.ca
1836
- const { method, segments } = patternOf(r.id as RouteId)
1837
- this._addRoute(method, segments, r)
1838
- }
1839
- for (const r of ws) this._addWsRoute(patternOf(r.id as RouteId).segments, r)
1840
- this._bumpEpoch()
1841
- }
1842
-
1843
- /** @internal — swap the loaded tree, re-binding every registered route to it. */
1844
- _replaceLoadedTree(tree: RouteTree): void {
1845
- const g = this._graph
1846
- const http = [...g.records.values(), ...g.catchAll.values()]
1847
- const ws = [...g.wsRecords.values()]
1848
- g.records.clear()
1849
- g.catchAll.clear()
1850
- g.wsRecords.clear()
1851
- g.unexpected.clear()
1852
- g.loaded = null
1853
- g.root = createNode()
1854
- g.rootShared = false
1855
- g.hasWs = false
1856
- this.routeTree(tree)
1857
- for (const r of http) {
1858
- delete r.ca
1859
- const { method, segments } = patternOf(r.id as RouteId)
1860
- this._addRoute(method, segments, r)
1861
- }
1862
- for (const r of ws) this._addWsRoute(patternOf(r.id as RouteId).segments, r)
1863
- }
1864
-
1865
- /**
1866
- * Set the app's error factory. Like every app setting it applies to every handle of this
1867
- * app, whenever it is called; routes mounted from another app keep that app's factory.
1868
- */
1869
- errorFactory<TFactory extends Record<string, (...args: never[]) => unknown>>(
1870
- factory: TFactory,
1871
- ): Honey<TEnv, TCtx, TRoutes, TMeta, TFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
1872
- this._s.errorFactory = factory
1873
- this._bumpEpoch()
1874
- return this as unknown as Honey<TEnv, TCtx, TRoutes, TMeta, TFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1875
- }
1876
-
1877
- /** Error keys every route of this app declares — applies to routes registered before and after the call. */
1878
- defaultErrors<TKeys extends ([TErrorFactory] extends [never] ? never : keyof TErrorFactory & string)>(
1879
- ...keys: TKeys[]
1880
- ): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors | TKeys, TBasePath, TTaps, TScopedMw> {
1881
- for (const k of keys) this._s.defaultErrorKeys.add(k)
1882
- this._bumpEpoch()
1883
- return this as unknown as Honey<
1884
- TEnv,
1885
- TCtx,
1886
- TRoutes,
1887
- TMeta,
1888
- TErrorFactory,
1889
- TDefaultErrors | TKeys,
1890
- TBasePath,
1891
- TTaps,
1892
- TScopedMw
1893
- >
1894
- }
1895
-
1896
- defaultBoundary<TKey extends ([TErrorFactory] extends [never] ? never : keyof TErrorFactory & string)>(
1897
- key: TKey,
1898
- ): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors | TKey, TBasePath, TTaps, TScopedMw> {
1899
- this._s.defaultBoundaryKey = key
1900
- this._s.defaultErrorKeys.add(key)
1901
- this._bumpEpoch()
1902
- return this as unknown as Honey<
1903
- TEnv,
1904
- TCtx,
1905
- TRoutes,
1906
- TMeta,
1907
- TErrorFactory,
1908
- TDefaultErrors | TKey,
1909
- TBasePath,
1910
- TTaps,
1911
- TScopedMw
1912
- >
1913
- }
1914
-
1915
- /** Phantom overload — constrains what route-level .meta() accepts */
1916
- meta<TNewMeta extends Record<string, unknown>>(): Honey<
1917
- TEnv,
1918
- TCtx,
1919
- TRoutes,
1920
- TNewMeta,
1921
- TErrorFactory,
1922
- TDefaultErrors,
1923
- TBasePath,
1924
- TTaps,
1925
- TScopedMw
1926
- >
1927
- /** Typed chain-level default meta — constrains route meta and sets runtime defaults in one call */
1928
- meta<TNewMeta extends Record<string, unknown>, TValues extends Partial<DefaultMeta> & Partial<TNewMeta>>(
1929
- values: TValues,
1930
- ): Honey<TEnv, TCtx, TRoutes, TNewMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1931
- /** Chain-level default meta — merged into every route registered on this chain */
1932
- meta<TValues extends Partial<DefaultMeta> & ([TMeta] extends [never] ? {} : Partial<TMeta>)>(
1933
- values: TValues,
1934
- ): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1935
- meta(
1936
- values?: Record<string, unknown>,
1937
- ): Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
1938
- if (values === undefined) {
1939
- /* phantom overload — type-only, no runtime effect */
1940
- return this as Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1941
- }
1942
- /* chain-level meta — copy-on-write */
1943
- const next = this._derive()
1944
- next._chainMeta = this._chainMeta ? { ...this._chainMeta, ...values } : { ...values }
1945
- return next as unknown as Honey<
1946
- TEnv,
1947
- TCtx,
1948
- TRoutes,
1949
- unknown,
1950
- TErrorFactory,
1951
- TDefaultErrors,
1952
- TBasePath,
1953
- TTaps,
1954
- TScopedMw
1955
- >
1956
- }
1957
-
1958
- /**
1959
- * Declare what flows from route meta and route schemas into the OpenAPI document.
1960
- * Codegen-time only — never consulted on the request path. See docs/meta-spec.md.
1961
- */
1962
- metaSpec<TSpec extends HoneyMetaSpec<TMeta>>(spec: TSpec): this {
1963
- if (this._graph.metaSpec !== null) {
1964
- throw new Error("metaSpec() was already declared on this app — declare the whole policy in one call")
1965
- }
1966
- this._graph.metaSpec = spec as MetaSpecConfig
1967
- return this
1968
- }
1969
-
1970
- /** Declare tap payload types — auto-extends meta with Partial<T> for meta-driven taps */
1971
- taps<TNewTaps extends Record<string, unknown>>(): Honey<
1972
- TEnv,
1973
- Omit<TCtx, "tap"> & { tap<K extends keyof TNewTaps>(key: K, payload: TNewTaps[K]): void },
1974
- TRoutes,
1975
- [TMeta] extends [never] ? Partial<TNewTaps> : TMeta & Partial<TNewTaps>,
1976
- TErrorFactory,
1977
- TDefaultErrors,
1978
- TBasePath,
1979
- TNewTaps,
1980
- TScopedMw
1981
- > {
1982
- return this as unknown as Honey<
1983
- TEnv,
1984
- Omit<TCtx, "tap"> & { tap<K extends keyof TNewTaps>(key: K, payload: TNewTaps[K]): void },
1985
- TRoutes,
1986
- [TMeta] extends [never] ? Partial<TNewTaps> : TMeta & Partial<TNewTaps>,
1987
- TErrorFactory,
1988
- TDefaultErrors,
1989
- TBasePath,
1990
- TNewTaps,
1991
- TScopedMw
1992
- >
1993
- }
1994
-
1995
- use<TAdds>(
1996
- mw: MiddlewareFn<TCtx, TAdds>,
1997
- ): Honey<TEnv, TCtx & TAdds, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1998
-
1999
- use<const TPath extends string, TAdds>(
2000
- path: TPath,
2001
- mw: MiddlewareFn<TCtx, TAdds>,
2002
- ): Honey<
2003
- TEnv,
2004
- TCtx,
2005
- TRoutes,
2006
- TMeta,
2007
- TErrorFactory,
2008
- TDefaultErrors,
2009
- TBasePath,
2010
- TTaps,
2011
- readonly [...TScopedMw, { readonly path: MergePath<TBasePath, TPath>; readonly adds: TAdds }]
2012
- >
2013
-
2014
- /* oxlint-disable-next-line typescript/no-explicit-any -- overload impl requires erased types */
2015
- use(pathOrMw: string | MiddlewareFn<any, any>, maybeMw?: MiddlewareFn<any, any>): any {
2016
- if (typeof pathOrMw !== "string") {
2017
- /*
2018
- * Chain middleware: a new handle whose routes run `mw`. This handle is unchanged —
2019
- * a handle that never registers a route is reported at finalize.
2020
- */
2021
- const mw = pathOrMw as RuntimeMiddleware
2022
- const node: ChainNode = { mw, parent: this._node, used: false }
2023
- this._graph.chains.push(node)
2024
- const next = this._derive([...this._chain, mw])
2025
- next._node = node
2026
- return next
2027
- }
2028
-
2029
- /* scoped: guards every request path under the prefix, whichever handle registered the route */
2030
- const prefix = mergePath(this._basePath, normalizePattern(pathOrMw))
2031
- const mw = maybeMw as RuntimeMiddleware
2032
- const segs = parsePattern(prefix)
2033
- this._graph.scoped.push({
2034
- errors: errorsOf(mw) ? [...(errorsOf(mw) as readonly string[])] : undefined,
2035
- guard: scopeGuard(segs, mw),
2036
- mw,
2037
- prefix,
2038
- segs,
2039
- })
2040
- this._bumpEpoch()
2041
- return this._derive()
2042
- }
2043
-
2044
- /**
2045
- * Mount a sub-app: its routes are served under this handle's basePath (and under `prefix`
2046
- * when given — `app.route("/v1", sub)` is `app.basePath("/v1").route(sub)`), and this
2047
- * handle's chain runs before theirs. The sub's own basePath stays part of each path.
2048
- */
2049
- route<TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors extends string, TSubBasePath extends string>(
2050
- sub: Honey<TEnv, TCtx, TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors, TSubBasePath>,
2051
- ): Honey<
2052
- TEnv,
2053
- TCtx,
2054
- TRoutes & PrefixRoutes<TBasePath, TSubRoutes>,
2055
- TMeta,
2056
- TErrorFactory,
2057
- TDefaultErrors,
2058
- TBasePath,
2059
- TTaps,
2060
- TScopedMw
2061
- >
2062
- route<
2063
- const TPrefix extends string,
2064
- TSubRoutes,
2065
- TSubMeta,
2066
- TSubErrorFactory,
2067
- TSubDefaultErrors extends string,
2068
- TSubBasePath extends string,
2069
- >(
2070
- prefix: TPrefix,
2071
- sub: Honey<TEnv, TCtx, TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors, TSubBasePath>,
2072
- ): Honey<
2073
- TEnv,
2074
- TCtx,
2075
- TRoutes & PrefixRoutes<MergePath<TBasePath, TPrefix>, TSubRoutes>,
2076
- TMeta,
2077
- TErrorFactory,
2078
- TDefaultErrors,
2079
- TBasePath,
2080
- TTaps,
2081
- TScopedMw
2082
- >
2083
- route(prefixOrSub: unknown, maybeSub?: unknown): unknown {
2084
- type Sub = Honey<TEnv, TCtx, unknown, unknown, unknown, string, string>
2085
- if (typeof prefixOrSub === "string") {
2086
- if (!(maybeSub instanceof Honey)) throw new TypeError("honey: route(prefix, sub) needs the sub-app")
2087
- this._markUsed()
2088
- ;(this.basePath(prefixOrSub) as unknown as Honey<TEnv, TCtx>)._mount(maybeSub as Sub)
2089
- return this
2090
- }
2091
- return this._mount(prefixOrSub as Sub)
2092
- }
2093
-
2094
- private _mount(sub: Honey<TEnv, TCtx, unknown, unknown, unknown, string, string>): this {
2095
- this._markUsed()
2096
- /* skip self-merge: .handler() already registered into the shared graph */
2097
- if (sub._graph !== this._graph) {
2098
- /*
2099
- * Re-register the sub's records under this handle: this handle's chain runs first,
2100
- * its context values and chain meta apply under the sub's own. Nothing is shared by
2101
- * reference, so routes the sub registers later stay its own, and two parents
2102
- * mounting one sub each get their own records. Each record keeps the settings of
2103
- * the sub (error factory, defaults, boundary, output validation). Internal routes
2104
- * (spec, docs, manifest) never travel.
2105
- */
2106
- sub._markUsed()
2107
- const subFinal = sub._finalize()
2108
- const subSettings = sub._graph.settings
2109
- const mount = <T extends RouteHandler | WSRouteHandler>(r: T): T => {
2110
- const c = copyRecord(r)
2111
- c.cm = [...this._chain, ...(r.cm ?? [])]
2112
- if (this._contextValues) c.cv = { ...this._contextValues, ...r.cv }
2113
- if (this._chainMeta) c.xm = { ...this._chainMeta, ...r.xm }
2114
- c.own = r.own ?? subSettings
2115
- return c
2116
- }
2117
- /* the sub's paths land under this handle's basePath, like Hono's `route("/v1", sub)` */
2118
- const base = this._basePath
2119
- const at = (pattern: string): string => (base === "/" ? pattern : mergePath(base, pattern))
2120
- for (const [id, r] of subFinal.byId) {
2121
- if (r._skip) continue
2122
- const { method, pattern } = splitRouteId(id)
2123
- this._addRoute(method, parsePattern(at(pattern)), mount(r))
2124
- }
2125
- for (const [id, r] of subFinal.wsById) {
2126
- const { pattern } = splitRouteId(id)
2127
- this._addWsRoute(parsePattern(at(pattern)), mount(r))
2128
- }
2129
- /* merged when codegen reads the policy (see `metaSpecOf`): the parent wins
2130
- * conflicts and keeps its resolved strictness, a sub's `false` still hides */
2131
- const subSource = sub._metaSpecSource
2132
- this._graph.absorbedMetaSpecs.push({ absorbed: [...subSource.absorbed], declared: subSource.declared })
2133
- /* the sub's scopes guard the paths it brought — appended after this app's own */
2134
- const g = this._graph
2135
- for (const entry of sub._graph.scoped) {
2136
- if (base === "/") {
2137
- if (!g.scoped.includes(entry)) g.scoped.push(entry)
2138
- continue
2139
- }
2140
- const prefix = at(entry.prefix)
2141
- if (g.scoped.some((e) => e.mw === entry.mw && e.prefix === prefix)) continue
2142
- const segs = parsePattern(prefix)
2143
- g.scoped.push({ errors: entry.errors, guard: scopeGuard(segs, entry.mw), mw: entry.mw, prefix, segs })
2144
- }
2145
- for (const [subPath, cfg] of sub._graph.realtimeRoutes) {
2146
- const path = at(subPath)
2147
- if (g.realtimeRoutes.has(path)) {
2148
- throw new Error(`Duplicate realtime route: ${path}`)
2149
- }
2150
- /* a default namespace is the route's full path, so it follows the mount; a named one is kept */
2151
- g.realtimeRoutes.set(path, { ...cfg, namespace: cfg.namespaceExplicit ? cfg.namespace : path, path })
2152
- }
2153
- if (!g.realtimeBus && sub._graph.realtimeBus) {
2154
- this._setBus(sub._graph.realtimeBus)
2155
- }
2156
- if (subSettings.taps !== null) {
2157
- const s = this._graph.settings
2158
- if (s.taps === null) s.taps = new Map()
2159
- for (const [key, fn] of subSettings.taps) {
2160
- if (!s.taps.has(key)) s.taps.set(key, fn)
2161
- }
2162
- }
2163
- this._bumpEpoch()
2164
- }
2165
- return this
2166
- }
2167
-
2168
- private _setBus(bus: RealtimeBus): RealtimeBus {
2169
- const g = this._graph
2170
- g.realtimeBus = bus
2171
- g.realtimeCtx = requireFeature("realtime", "app.realtime()").createRealtimePublisher(bus, () =>
2172
- [...g.realtimeRoutes.values()].map((cfg) => cfg.namespace),
2173
- )
2174
- return bus
2175
- }
2176
-
2177
- private _registerRoute<TPath extends string, TMethod extends HttpMethod | "ALL">(
2178
- method: TMethod,
2179
- path: TPath,
2180
- extraMethods?: (HttpMethod | "ALL")[],
2181
- ): BuilderChain<
2182
- TEnv,
2183
- TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
2184
- {},
2185
- never,
2186
- {},
2187
- MergePath<TBasePath, TPath>,
2188
- TMethod,
2189
- TRoutes,
2190
- never,
2191
- TCtx,
2192
- TMeta,
2193
- InitAccMeta<TMeta>,
2194
- TErrorFactory,
2195
- TDefaultErrors,
2196
- TBasePath,
2197
- TTaps,
2198
- TScopedMw
2199
- > {
2200
- const fullPath = mergePath(this._basePath, path)
2201
- const pending: PendingRoute = { id: `${[method, ...(extraMethods ?? [])].join(",")} ${fullPath}` }
2202
- this._graph.pending.add(pending)
2203
- return new RouteBuilder<
2204
- TEnv,
2205
- TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
2206
- {},
2207
- never,
2208
- {},
2209
- MergePath<TBasePath, TPath>,
2210
- TMethod,
2211
- TRoutes,
2212
- never,
2213
- TCtx,
2214
- TMeta,
2215
- InitAccMeta<TMeta>,
2216
- TErrorFactory,
2217
- TDefaultErrors,
2218
- TBasePath,
2219
- TTaps,
2220
- TScopedMw
2221
- >({
2222
- boundaryErrorKey: null,
2223
- errorKeys: new Set(),
2224
- extraMethods: extraMethods ?? null,
2225
- inputSchemas: null,
2226
- meta: this._chainMeta ? { ...this._chainMeta } : null,
2227
- method,
2228
- middlewares: [],
2229
- outputSchemas: null,
2230
- parent: this,
2231
- path: fullPath,
2232
- pending,
2233
- }) as unknown as BuilderChain<
2234
- TEnv,
2235
- TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
2236
- {},
2237
- never,
2238
- {},
2239
- MergePath<TBasePath, TPath>,
2240
- TMethod,
2241
- TRoutes,
2242
- never,
2243
- TCtx,
2244
- TMeta,
2245
- InitAccMeta<TMeta>,
2246
- TErrorFactory,
2247
- TDefaultErrors,
2248
- TBasePath,
2249
- TTaps,
2250
- TScopedMw
2251
- >
2252
- }
2253
-
2254
- /** @internal — set (or clear, with null) one app-wide middleware; used by `serve()` */
2255
- _setGlobal(key: string, mw: RuntimeMiddleware | null): void {
2256
- if (mw === null) {
2257
- if (!this._graph.global.delete(key)) return
2258
- } else {
2259
- this._graph.global.set(key, mw)
2260
- }
2261
- this._bumpEpoch()
2262
- }
2263
-
2264
- /** @internal — what a route registered through this handle captures */
2265
- _view(): { chain: RuntimeMiddleware[]; cv: Record<string, unknown> | null; own: AppSettings<unknown> } {
2266
- return { chain: this._chain, cv: this._contextValues, own: this._graph.settings }
2267
- }
2268
-
2269
- /** @internal — a builder got its handler (or was abandoned on purpose) */
2270
- _settle(pending: PendingRoute): void {
2271
- this._graph.pending.delete(pending)
2272
- this._markUsed()
2273
- }
2274
-
2275
- on<const TPath extends string, const TMethods extends readonly [HttpMethod | "ALL", ...(HttpMethod | "ALL")[]]>(
2276
- methods: TMethods,
2277
- path: TPath,
2278
- ): BuilderChain<
2279
- TEnv,
2280
- TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
2281
- {},
2282
- never,
2283
- {},
2284
- MergePath<TBasePath, TPath>,
2285
- TMethods[number],
2286
- TRoutes,
2287
- never,
2288
- TCtx,
2289
- TMeta,
2290
- InitAccMeta<TMeta>,
2291
- TErrorFactory,
2292
- TDefaultErrors,
2293
- TBasePath,
2294
- TTaps,
2295
- TScopedMw
2296
- > {
2297
- const [first, ...rest] = methods
2298
- return this._registerRoute<TPath, TMethods[number]>(
2299
- first,
2300
- path,
2301
- rest.length > 0 ? (rest as (HttpMethod | "ALL")[]) : undefined,
2302
- )
2303
- }
2304
-
2305
- all<const TPath extends string>(path: TPath) {
2306
- return this._registerRoute<TPath, "ALL">("ALL", path)
2307
- }
2308
- delete<const TPath extends string>(path: TPath) {
2309
- return this._registerRoute<TPath, "DELETE">("DELETE", path)
2310
- }
2311
- get<const TPath extends string>(path: TPath) {
2312
- return this._registerRoute<TPath, "GET">("GET", path)
2313
- }
2314
- head<const TPath extends string>(path: TPath) {
2315
- return this._registerRoute<TPath, "HEAD">("HEAD", path)
2316
- }
2317
- options<const TPath extends string>(path: TPath) {
2318
- return this._registerRoute<TPath, "OPTIONS">("OPTIONS", path)
2319
- }
2320
- patch<const TPath extends string>(path: TPath) {
2321
- return this._registerRoute<TPath, "PATCH">("PATCH", path)
2322
- }
2323
- post<const TPath extends string>(path: TPath) {
2324
- return this._registerRoute<TPath, "POST">("POST", path)
2325
- }
2326
- put<const TPath extends string>(path: TPath) {
2327
- return this._registerRoute<TPath, "PUT">("PUT", path)
2328
- }
2329
-
2330
- ws<const TPath extends string>(
2331
- path: TPath,
2332
- ): WSRouteBuilder<
2333
- TEnv,
2334
- TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
2335
- {},
2336
- never,
2337
- Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
2338
- > {
2339
- const fullPath = mergePath(this._basePath, path)
2340
- const pending: PendingRoute = { id: `WS ${fullPath}` }
2341
- this._graph.pending.add(pending)
2342
- return new WSRouteBuilder<
2343
- TEnv,
2344
- TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
2345
- {},
2346
- never,
2347
- Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
2348
- >({
2349
- errorKeys: new Set(),
2350
- inputSchemas: null,
2351
- meta: this._chainMeta ? { ...this._chainMeta } : null,
2352
- middlewares: [],
2353
- origins: null,
2354
- parent: this,
2355
- path: fullPath,
2356
- pending,
2357
- })
2358
- }
2359
-
2360
- realtime<const TPath extends string>(
2361
- path: TPath,
2362
- opts: RealtimeRouteOpts<TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>>,
2363
- ): this {
2364
- const fullPath = mergePath(this._basePath, path)
2365
- const g = this._graph
2366
- const rt = requireFeature("realtime", "app.realtime()")
2367
- const config = rt.resolveRealtimeConfig(fullPath, opts)
2368
- if (g.realtimeRoutes.has(fullPath)) {
2369
- throw new Error(`Duplicate realtime route: ${fullPath}`)
2370
- }
2371
- if (!g.realtimeBus) this._setBus(rt.createBus())
2372
- g.realtimeRoutes.set(fullPath, config)
2373
- this._markUsed()
2374
- this._addWsRoute(parsePattern(fullPath), {
2375
- bek: null,
2376
- cm: [...this._chain],
2377
- cv: this._contextValues,
2378
- dk: new Set(),
2379
- ek: new Set(),
2380
- fn: Object.create(null),
2381
- iv: null,
2382
- mt: null,
2383
- mw: [],
2384
- own: g.settings,
2385
- rb: null,
2386
- rm: (opts.use ?? []).map((fn) => fn as RuntimeMiddleware),
2387
- rp: fullPath,
2388
- xm: this._chainMeta ? { ...this._chainMeta } : null,
2389
- })
2390
- return this
2391
- }
2392
-
2393
- /**
2394
- * Deno must call `Deno.upgradeWebSocket` in the same turn as the serve
2395
- * callback. When the adapter implements `preUpgrade`, we do that here
2396
- * and return the 101 response before any middleware Promise.
2397
- */
2398
- fetch(
2399
- request: Request,
2400
- env: TEnv,
2401
- executionCtx?: { waitUntil?: (p: Promise<unknown>) => void },
2402
- ): Response | Promise<Response> {
2403
- if (!this._graph.served.has(this._chain)) {
2404
- this._markUsed()
2405
- this._graph.served.add(this._chain)
2406
- this._bumpEpoch()
2407
- }
2408
- const wsAdapter = this._graph.settings.wsAdapter
2409
- const path = normalizePath(pathOfUrl(request.url), this._graph.settings.encodedSlashes)
2410
- if (path === null) return this._badRequestTarget()
2411
- /* Deno and Bun before 1.4 build request.url by concatenating Host and the target, so a
2412
- * `Host: x/admin?` would choose the routed path. `serve()` checks Host on Deno; this covers
2413
- * `Deno.serve(app.fetch)`, which passes its serve info as env, and every Bun entry point on
2414
- * an affected version. Node's adapter checks it; Bun 1.4+ routes on the target. */
2415
- if ((BUN_URL_FROM_HOST || isDenoServeInfo(env)) && !hasValidHost(request)) return this._badRequestTarget()
2416
- /* an adapter alone (serve() always installs one) does not need the websocket path */
2417
- if (!this._graph.hasWs) {
2418
- return this._doFetch(request, env, executionCtx, path)
2419
- }
2420
- const isWsUpgrade = request.headers.get("upgrade")?.toLowerCase() === "websocket"
2421
- const canPreUpgrade =
2422
- isWsUpgrade &&
2423
- wsAdapter?.preUpgrade !== undefined &&
2424
- !this.trailingSlashRedirects(path) &&
2425
- this._matchWs(this._finalize(), this.pathAfterPrefix(path)) !== null
2426
- /* Deno closes the Request in preUpgrade: snapshot the headers the chain will read first */
2427
- const headerSnap = canPreUpgrade ? new Headers(request.headers) : undefined
2428
- let pre: ReturnType<NonNullable<WSAdapter["preUpgrade"]>>
2429
- try {
2430
- pre = canPreUpgrade ? wsAdapter?.preUpgrade?.(request) : undefined
2431
- } catch (err) {
2432
- /* the runtime refused the handshake: answer through the normal path, which fails the upgrade */
2433
- this._graph.settings.logger?.warn?.({ err }, "websocket preUpgrade failed")
2434
- pre = undefined
2435
- }
2436
- /* After Deno.upgradeWebSocket the Request is closed. A sync throw here
2437
- * used to be boxed by async _doFetch; keep 101 returning either way. */
2438
- let work: Response | Promise<Response>
2439
- try {
2440
- work = this._doFetch(request, env, executionCtx, path, isWsUpgrade === true, headerSnap)
2441
- } catch (err) {
2442
- work = Promise.reject(err)
2443
- }
2444
- if (!pre) return work
2445
- void Promise.resolve(work)
2446
- .then((res) => {
2447
- if (res.status === 101) return
2448
- const code = res.status === 401 ? 4401 : 1008
2449
- const reason = res.status === 401 ? "unauthorized" : "upgrade rejected"
2450
- const reject = () => {
2451
- try {
2452
- pre.socket.close(code, reason)
2453
- } catch {
2454
- /* already closed */
2455
- }
2456
- }
2457
- if (pre.whenOpen) pre.whenOpen(reject)
2458
- else reject()
2459
- })
2460
- .catch(() => {
2461
- try {
2462
- pre.socket.close(1011, "internal error")
2463
- } catch {
2464
- /* already closed */
2465
- }
2466
- })
2467
- return pre.response
2468
- }
2469
-
2470
- /** @internal 400 for a request path the normalization policy rejects (see `normalizePath`), or an invalid `Host`. */
2471
- _badRequestTarget(): Response {
2472
- return this._toErrorResponse(this._createError(EK.bad_request, SK.bad_request))
2473
- }
2474
-
2475
- private trailingSlashRedirects(path: string): boolean {
2476
- if (path.length <= 1) return false
2477
- const mode = this._graph.settings.trailingSlash
2478
- if (mode === "strip" && path.endsWith("/")) return true
2479
- if (mode === "enforce" && !path.endsWith("/")) return true
2480
- return false
2481
- }
2482
-
2483
- private pathAfterPrefix(path: string): string {
2484
- const prefix = this._graph.settings.stripPrefix
2485
- if (prefix === null) return path
2486
- if (path === prefix) return "/"
2487
- if (path.startsWith(prefix) && path.charCodeAt(prefix.length) === 47) {
2488
- return path.slice(prefix.length)
2489
- }
2490
- return path
2491
- }
2492
-
2493
- private _doFetch(
2494
- request: Request,
2495
- env: TEnv,
2496
- executionCtx: { waitUntil?: (p: Promise<unknown>) => void } | undefined,
2497
- fullPath: string,
2498
- knownWsUpgrade = false,
2499
- headerSnap?: Headers,
2500
- ): Response | Promise<Response> {
2501
- const startTime = performance.now()
2502
- const final = this._finalize()
2503
- const s = this._graph.settings
2504
- const rawUrl = request.url
2505
-
2506
- /* lazily create URL only when actually needed (search params, redirects) */
2507
- let _url: URL | undefined
2508
- const getUrl = (): URL => {
2509
- if (_url === undefined) _url = new URL(rawUrl)
2510
- return _url
2511
- }
2512
-
2513
- const log = s.logger ?? undefined
2514
- const method = request.method.toUpperCase() as HttpMethod
2515
- const fc: FetchCtx<TEnv> = {
2516
- allowed: null,
2517
- env,
2518
- executionCtx,
2519
- headerSnap,
2520
- log,
2521
- method,
2522
- path: fullPath,
2523
- plan: null,
2524
- request,
2525
- startTime,
2526
- trust: s.trust,
2527
- url: getUrl,
2528
- ws: null,
2529
- wsUpgrade: knownWsUpgrade,
2530
- }
2531
-
2532
- if (s.telemetry === null) return this._route(fc, final, fullPath, rawUrl, knownWsUpgrade)
2533
- safeFire(() => s.telemetry?.onRequest?.({ env, req: request }), log)
2534
- /* every exit after onRequest — redirects, 426, upgrades, misses, preflights, handlers —
2535
- * fires onResponse exactly once, here */
2536
- return this._withOnResponse(fc, this._route(fc, final, fullPath, rawUrl, knownWsUpgrade))
2537
- }
2538
-
2539
- private _route(
2540
- fc: FetchCtx<TEnv>,
2541
- final: FinalTable,
2542
- fullPath: string,
2543
- rawUrl: string,
2544
- knownWsUpgrade: boolean,
2545
- ): Response | Promise<Response> {
2546
- const s = this._graph.settings
2547
- const { method, request } = fc
2548
-
2549
- /* trailing slash handling — a relative Location: the scheme, host and port the client
2550
- * used are the ones it keeps, whatever proxy terminated TLS in front of the app */
2551
- if (this.trailingSlashRedirects(fullPath)) {
2552
- const target = s.trailingSlash === "strip" ? fullPath.slice(0, -1) : `${fullPath}/`
2553
- return new Response(null, {
2554
- headers: { location: target + searchOfUrl(rawUrl) },
2555
- status: 308,
2556
- })
2557
- }
2558
-
2559
- /* prefix stripping — must run AFTER trailing slash so redirects preserve the full prefixed URL */
2560
- const path = this.pathAfterPrefix(fullPath)
2561
- fc.path = path
2562
- const root = this._graph.root
2563
-
2564
- /* WebSocket route check — do not re-read headers after Deno.upgradeWebSocket */
2565
- const isWsUpgrade = knownWsUpgrade || requestIsWsUpgrade(request)
2566
- if (isWsUpgrade) {
2567
- const wsMatch = this._matchWs(final, path)
2568
- if (wsMatch !== null) {
2569
- return this._handleWs(fc, wsMatch)
2570
- }
2571
- }
2572
-
2573
- /* O(1) static route lookup — patterns without params or wildcards only */
2574
- const staticHit =
2575
- final.statics[`${method} ${path}`] ?? (method === "HEAD" ? final.statics[`GET ${path}`] : undefined)
2576
- if (staticHit !== undefined) {
2577
- return this._dispatchRecord(fc, staticHit, EMPTY_PARAMS)
2578
- }
2579
-
2580
- const result = matchRoute(root, method, path)
2581
- if (result === null) {
2582
- /* no HTTP route: a websocket or realtime route on this path asks for an upgrade */
2583
- if (!isWsUpgrade && this._graph.hasWs) {
2584
- const wsMatch = this._matchWs(final, path)
2585
- if (wsMatch !== null) {
2586
- if (this._graph.realtimeRoutes.has(wsMatch.handler.rp)) {
2587
- return new Response(null, { headers: { upgrade: "websocket" }, status: 426 })
2588
- }
2589
- return new Response("Upgrade Required", {
2590
- headers: { connection: "Upgrade", upgrade: "websocket" },
2591
- status: 426,
2592
- })
2593
- }
2594
- }
2595
- return this._handleMiss(fc, null)
2596
- }
2597
- if (!result.matched) {
2598
- if (method === "OPTIONS") {
2599
- const requested = request.headers.get("access-control-request-method")
2600
- if (requested) {
2601
- /* a preflight asks about one method: run that route's own chain */
2602
- const hit = matchRoute(root, requested.trim().toUpperCase(), path)
2603
- const plan = hit?.matched ? final.plans.get(hit.id) : undefined
2604
- if (hit?.matched && plan !== undefined && plan.r.fn !== NOT_SERVED) {
2605
- return this._handlePreflight(fc, plan, hit.params, result.allowed)
2606
- }
2607
- }
2608
- }
2609
- return this._handleMiss(fc, result.allowed)
2610
- }
2611
- const plan = final.plans.get(result.id)
2612
- if (plan === undefined) return this._handleMiss(fc, null)
2613
- return this._dispatchRecord(fc, plan, result.params)
2614
- }
2615
-
2616
- private _matchWs(
2617
- final: FinalTable,
2618
- path: string,
2619
- ): { handler: WSRouteHandler; params: Record<string, string> } | null {
2620
- const hit = matchWsRoute(this._graph.root, path)
2621
- if (hit === null) return null
2622
- const handler = final.wsById.get(hit.id)
2623
- return handler === undefined ? null : { handler, params: hit.params }
2624
- }
2625
-
2626
- private _dispatchRecord(
2627
- fc: FetchCtx<TEnv>,
2628
- plan: Plan,
2629
- params: Record<string, string>,
2630
- ): Response | Promise<Response> {
2631
- const record = plan.r as RouteHandler
2632
- if (record.fn === NOT_SERVED) return this._handleMiss(fc, null)
2633
- /* a delegated route keeps its own input schemas for docs and the 415 check; the body
2634
- * itself belongs to whatever the catch-all forwards it to */
2635
- if (record.dl === true && record.iv) {
2636
- try {
2637
- assertRequestContentType(record.iv, fc.request)
2638
- } catch (thrown) {
2639
- return this._toErrorResponse(thrown)
2640
- }
2641
- }
2642
- return this._handleMatched(fc, plan, params)
2643
- }
2644
-
2645
- private _makeErrorCtx(fc: FetchCtx<TEnv>, allowed?: string[]) {
2646
- const s = this._s
2647
- return {
2648
- env: fc.env,
2649
- jsonFromError: (err: HoneyError) => createErrorResponse(err, s.errorFormatter, s.customErrorFormatter),
2650
- req: fc.request,
2651
- ...(allowed ? { allowed } : {}),
2652
- }
2653
- }
2654
-
2655
- /** A request context for `plan`: route data, context values, `ctx.errors`, `ctx.realtime`. */
2656
- private _newCtx(fc: FetchCtx<TEnv>, plan: Plan, params: Record<string, string>, req?: Request): HoneyContext<TEnv> {
2657
- fc.plan = plan
2658
- const r = plan.r
2659
- const ctx = new HoneyContext<TEnv>({
2660
- env: fc.env,
2661
- executionCtx: fc.executionCtx,
2662
- meta: r.mt ?? undefined,
2663
- params,
2664
- path: fc.path,
2665
- req: req ?? fc.request,
2666
- routePattern: r.rp ?? "",
2667
- urlFn: fc.url,
2668
- })
2669
- if (plan.cv !== null) Object.assign(ctx, plan.cv)
2670
- const rt = this._graph.realtimeCtx
2671
- if (rt !== null) (ctx as { realtime: unknown }).realtime = rt
2672
- if (r.ef) ctx._setErrors(r.ef)
2673
- ctx._rq = fc
2674
- return ctx
2675
- }
2676
-
2677
- private _handleWs(
2678
- fc: FetchCtx<TEnv>,
2679
- wsMatch: { handler: WSRouteHandler; params: Record<string, string> },
2680
- ): Response | Promise<Response> {
2681
- const plan = this._finalize().wsPlans.get(wsMatch.handler.id as RouteId)
2682
- if (plan === undefined) return this._handleMiss(fc, null)
2683
- const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request)
2684
- if (!isUpgrade) {
2685
- return new Response(null, {
2686
- headers: { upgrade: "websocket" },
2687
- status: 426,
2688
- })
2689
- }
2690
- if (!this._graph.settings.wsAdapter) {
2691
- fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()")
2692
- return this._toErrorResponse(this._createError(EK.internal_server_error, SK.internal_server_error))
2693
- }
2694
- fc.ws = wsMatch
2695
- const ctx = this._newCtx(fc, plan, wsMatch.params, ctxRequest(fc))
2696
- return plan.run(ctx)
2697
- }
2698
-
2699
- /** Reject a cross-origin upgrade the route's origin policy does not allow (403). */
2700
- /** The websocket session runtime; a cross-origin upgrade the route does not allow is a 403. */
2701
- private async _wsSession(finalCtx: HoneyContext<TEnv>, policy: WSOriginPolicy | null) {
2702
- const ws = await loadFeature("ws", "a websocket route")
2703
- const headers = finalCtx.req.headers
2704
- const origin = headers.get("origin")
2705
- if (origin === null || policy === "*") return ws
2706
- const credentialed = headers.has("cookie") || headers.has("authorization")
2707
- if (!ws.originAllowed(policy, origin, clientInfo(finalCtx as unknown as HoneyContext).host, credentialed)) {
2708
- throw this._createError(EK.forbidden, SK.forbidden)
2709
- }
2710
- return ws
2711
- }
2712
-
2713
- private async _wsUpgrade(finalCtx: HoneyContext<TEnv>): Promise<Response> {
2714
- const fc = finalCtx._rq as FetchCtx<TEnv>
2715
- const wsMatch = fc.ws as { handler: WSRouteHandler; params: Record<string, string> }
2716
- const ws = await this._wsSession(finalCtx, wsMatch.handler.og ?? null)
2717
- const wsAdapter = this._graph.settings.wsAdapter as WSAdapter
2718
- const handler = ws.createWsSession({
2719
- callbacks: wsMatch.handler.fn,
2720
- ctx: finalCtx,
2721
- log: (this._graph.settings.logger ?? null) as InvokeLogger | null,
2722
- reconnectToken: fc.url().searchParams.get("reconnect_token"),
2723
- route: wsMatch.handler.rp,
2724
- })
2725
- const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, handler)
2726
- return upgradeResult.response
2727
- }
2728
-
2729
- /** Terminal of a realtime route's chain: identify, upgrade, and attach the connection to the bus. */
2730
- private async _realtimeUpgrade(finalCtx: HoneyContext<TEnv>, config: RealtimeConfig): Promise<Response> {
2731
- const fc = finalCtx._rq as FetchCtx<TEnv>
2732
- await this._wsSession(finalCtx, config.allowedOrigins)
2733
- const wsAdapter = this._graph.settings.wsAdapter as WSAdapter
2734
- const rt = requireFeature("realtime", "app.realtime()")
2735
- const bus = this._graph.realtimeBus ?? this._setBus(rt.createBus())
2736
- /* a throwing identify rejects the upgrade through the chain's error boundary */
2737
- const userId = config.identify === null ? null : ((await config.identify(finalCtx)) ?? null)
2738
- const session = rt.createRealtimeSession({ bus, config, ctx: finalCtx, log: fc.log, userId })
2739
- const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, session.handler)
2740
- /* Node/CF return an open socket from upgrade(); Bun and Deno deliver it through onOpen */
2741
- if (upgradeResult.socket && upgradeResult.socket.readyState === 1) session.attach(upgradeResult.socket)
2742
- return upgradeResult.response
2743
- }
2744
-
2745
- /**
2746
- * 404 (`allowed === null`) and 405: one pipeline with a synthetic record — full context,
2747
- * the app-wide middleware and the scopes covering the path run once, telemetry fires once.
2748
- */
2749
- private _handleMiss(fc: FetchCtx<TEnv>, allowed: string[] | null): Response | Promise<Response> {
2750
- const s = this._graph.settings
2751
- const { method, path, request } = fc
2752
- if (allowed === null) {
2753
- safeFire(() => s.telemetry?.onNotFound?.({ method, path, req: request }), fc.log)
2754
- } else {
2755
- safeFire(() => s.telemetry?.onMethodNotAllowed?.({ allowed, method, path, req: request }), fc.log)
2756
- }
2757
- fc.allowed = allowed
2758
- const plan = this._finalize().miss
2759
- const ctx = this._newCtx(fc, plan, EMPTY_PARAMS)
2760
- return plan.run(ctx)
2761
- }
2762
-
2763
- /** Fire `onResponse` once for the request's one exit and return. A dispatch that rejects
2764
- * (an adapter failure past every error boundary) still ends the request: it reports 500. */
2765
- private _withOnResponse(fc: FetchCtx<TEnv>, res: Response | Promise<Response>): Response | Promise<Response> {
2766
- const s = this._graph.settings
2767
- if (s.telemetry?.onResponse === undefined) return res
2768
- const fire = (status: number): void => {
2769
- safeFire(
2770
- () => s.telemetry?.onResponse?.({ duration: performance.now() - fc.startTime, req: fc.request, status }),
2771
- fc.log,
2772
- )
2773
- }
2774
- if (!(res instanceof Promise)) {
2775
- fire(res.status)
2776
- return res
2777
- }
2778
- return res.then(
2779
- (r) => {
2780
- fire(r.status)
2781
- return r
2782
- },
2783
- (error: unknown) => {
2784
- fire(500)
2785
- throw error
2786
- },
2787
- )
2788
- }
2789
-
2790
- private _make404(fc: FetchCtx<TEnv>): Response | Promise<Response> {
2791
- const s = this._s
2792
- if (s.onNotFound) return s.onNotFound(this._makeErrorCtx(fc))
2793
- return this._makeErrorCtx(fc).jsonFromError(this._createError(EK.not_found, SK.not_found))
2794
- }
2795
-
2796
- private async _make405(fc: FetchCtx<TEnv>, allowed: string[]): Promise<Response> {
2797
- const s = this._s
2798
- const res = s.onMethodNotAllowed
2799
- ? await s.onMethodNotAllowed(this._makeErrorCtx(fc, allowed) as ErrorCtx<TEnv> & { allowed: string[] })
2800
- : this._makeErrorCtx(fc).jsonFromError(this._createError(EK.method_not_allowed, SK.method_not_allowed))
2801
- const responseHeaders = new Headers(res.headers)
2802
- responseHeaders.set("allow", allowed.join(", "))
2803
- return new Response(res.body, {
2804
- headers: responseHeaders,
2805
- status: res.status,
2806
- })
2807
- }
2808
-
2809
- /**
2810
- * CORS preflight for a path that has no OPTIONS route: run the chain of the route the
2811
- * preflight asks about (`Access-Control-Request-Method`) — chain, scoped and route
2812
- * middleware, never input validation or the handler. Middleware that answers the
2813
- * preflight (cors) short-circuits; otherwise the answer is the 405 with `Allow`.
2814
- */
2815
- private _handlePreflight(
2816
- fc: FetchCtx<TEnv>,
2817
- plan: Plan,
2818
- params: Record<string, string>,
2819
- allowed: string[],
2820
- ): Response | Promise<Response> {
2821
- fc.allowed = allowed
2822
- const ctx = this._newCtx(fc, plan, params)
2823
- const final = this._finalize()
2824
- plan.pf ??= compileChain(
2825
- plan.pfChain,
2826
- (c) => {
2827
- const rq = (c as HoneyContext<TEnv>)._rq as FetchCtx<TEnv>
2828
- return this._make405(rq, rq.allowed ?? [])
2829
- },
2830
- final.convert,
2831
- )
2832
- return plan.pf(ctx)
2833
- }
2834
-
2835
- private _handleMatched(fc: FetchCtx<TEnv>, plan: Plan, params: Record<string, string>): Response | Promise<Response> {
2836
- const ctx = this._newCtx(fc, plan, params)
2837
- const telemetry = this._graph.settings.telemetry
2838
- if (telemetry !== null && telemetry.onRoute !== undefined) {
2839
- try {
2840
- telemetry.onRoute({ method: fc.method, params, path: fc.path, req: fc.request, route: plan.r.rp ?? "" })
2841
- } catch {
2842
- /* telemetry must not crash request */
2843
- }
2844
- }
2845
- const res = plan.run(ctx)
2846
- if (res instanceof Promise) return res.then((r) => this._finishMatched(fc, plan, ctx, r))
2847
- return this._finishMatched(fc, plan, ctx, res)
2848
- }
2849
-
2850
- /** Output validation, inside the chain: an invalid body becomes an error response the middleware sees. */
2851
- private async _validateOutput(handler: RouteHandler, ctx: HoneyContext<TEnv>, response: Response): Promise<Response> {
2852
- if (response === undefined || response === null || ctx._isErrorResponse) return response
2853
- /* read the creation tag, never `response.body`: on Node that would build a stream per response */
2854
- const kind = bodyKind(response)
2855
- if (kind === "empty") return response
2856
- const ct = response.headers.get("content-type")
2857
-
2858
- /* content-type mismatch check */
2859
- if (ct && handler.os) {
2860
- const declaredTypes = Object.keys(handler.os)
2861
- const matches = declaredTypes.some((t) => ct.startsWith(t))
2862
- if (!matches) {
2863
- throw new HoneyError({
2864
- errorKey: EK.output_content_type_mismatch,
2865
- status: SK.internal_server_error,
2866
- })
2867
- }
2868
- }
2869
-
2870
- /* JSON schema validation. A body Honey built from a string is validated from that string
2871
- * (no stream, no tee); anything else is read from a clone (Bun clone() drains the original).
2872
- * A stream Honey created (sse, stream, generate) is never buffered: it may never end. */
2873
- if (ct?.startsWith("application/json") && handler.ov && !isProducedStream(response)) {
2874
- const sk = codeToStatusKey[response.status]
2875
- if (sk) {
2876
- const raw = rawBodyOf(response)
2877
- if (raw !== null) {
2878
- const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw)
2879
- await handler.ov(sk, text.length === 0 ? null : JSON.parse(text))
2880
- return response
2881
- }
2882
- const forReturn = response.clone()
2883
- const data: unknown = await response.json()
2884
- await handler.ov(sk, data)
2885
- return forReturn
2886
- }
2887
- }
2888
- return response
2889
- }
2890
-
2891
- private _finishMatched(fc: FetchCtx<TEnv>, plan: Plan, ctx: HoneyContext<TEnv>, response: Response): Response {
2892
- const s = this._graph.settings
2893
- const handler = plan.r
2894
- /* taps — fire after successful handler, non-blocking */
2895
- if (plan.taps !== null && !ctx._isErrorResponse) {
2896
- const taps = plan.taps
2897
- const log = fc.log
2898
-
2899
- /* meta-driven taps — fire for each registered key found in route meta */
2900
- if (handler.mt !== null) {
2901
- for (const [key, tapFn] of taps) {
2902
- const metaValue = handler.mt[key]
2903
- if (metaValue !== undefined) {
2904
- ctx.background(
2905
- Promise.resolve()
2906
- .then(() => tapFn(ctx as unknown as TapContext<unknown>, metaValue))
2907
- .catch((e) => log?.warn?.({ err: e, tap: key }, "tap failed")),
2908
- )
2909
- }
2910
- }
2911
- }
2912
-
2913
- /* dynamic taps — fire for each c.tap() call */
2914
- if (ctx._pendingTaps !== null) {
2915
- for (const pending of ctx._pendingTaps) {
2916
- const tapFn = taps.get(pending.key)
2917
- if (tapFn !== undefined) {
2918
- ctx.background(
2919
- Promise.resolve()
2920
- .then(() => tapFn(ctx as unknown as TapContext<unknown>, pending.payload))
2921
- .catch((e) => log?.warn?.({ err: e, tap: pending.key }, "tap failed")),
2922
- )
2923
- }
2924
- }
2925
- ctx._pendingTaps = null
2926
- }
2927
- }
2928
-
2929
- if (s.telemetry !== null) {
2930
- try {
2931
- const duration = performance.now() - fc.startTime
2932
- s.telemetry.onHandler?.({
2933
- duration,
2934
- method: fc.method,
2935
- path: fc.path,
2936
- route: handler.rp ?? "",
2937
- status: response.status,
2938
- })
2939
- } catch {
2940
- /* telemetry must never crash the response path */
2941
- }
2942
- }
2943
- if (fc.method === "HEAD") return headResponse(response)
2944
- return response
2945
- }
2946
- }
2947
-
2948
- /*
2949
- * Handler Type Algebra
2950
- * These types compose the handler context from route configuration.
2951
- * NarrowMethod → ApplyOutput → ApplyParams → HandlerCtx
2952
- */
2953
-
2954
- /**
2955
- * When content type IS declared → narrow the method (constrain status keys + body).
2956
- * When content type NOT declared → remove the method (Omit).
2957
- * Narrowed methods return TypedResponse<CT, K> for compile-time CT+SK safety.
2958
- */
2959
- /** Resolve body type: schema → InferOutput, plain type → use as-is */
2960
- type ResolveBody<T, TBody> = TBody extends "infer" ? (T extends StandardSchemaLike ? InferOutput<T> : T) : TBody
2961
-
2962
- type NarrowMethod<TRes, TSchemas, Method extends string, CT extends string, TBody> = [TSchemas] extends [never]
2963
- ? Omit<TRes, Method>
2964
- : TSchemas extends Record<string, unknown>
2965
- ? Omit<TRes, Method> & {
2966
- [M in Method]: <K extends keyof TSchemas & string>(
2967
- statusKey: K,
2968
- body: ResolveBody<TSchemas[K], TBody>,
2969
- opts?: ResponseOptions,
2970
- ) => TypedResponse<CT, K>
2971
- }
2972
- : Omit<TRes, Method>
2973
-
2974
- /**
2975
- * SSE uses a callback signature, not statusKey+body like other methods.
2976
- * Gate presence on text/event-stream declaration, preserve original signature.
2977
- */
2978
- type NarrowSSE<TOutput> = [ExtractSchemas<TOutput, "text/event-stream">] extends [never] ? {} : { sse: HoneyRes["sse"] }
2979
-
2980
- /** Universal methods — always available, not gated by output declaration */
2981
- type UniversalRes = Pick<HoneyRes, "noContent" | "raw" | "redirect" | "stream">
2982
-
2983
- /**
2984
- * Constrain ctx.res when output schemas are declared.
2985
- * Declared content types → method constrained (status keys + body type).
2986
- * Undeclared content types → method removed.
2987
- * Universal methods (noContent, redirect, stream, raw) always available.
2988
- * HoneyContext has no private brand, so Omit<TCtx, "res"> replaces the wide HoneyRes.
2989
- */
2990
- type ApplyOutput<TCtx, TOutput> = [keyof TOutput] extends [never]
2991
- ? TCtx
2992
- : Omit<TCtx, "res"> & {
2993
- readonly res: UniversalRes &
2994
- NarrowMethod<{}, ExtractSchemas<TOutput, "application/json">, "json", "application/json", "infer"> &
2995
- NarrowMethod<{}, ExtractSchemas<TOutput, "text/plain">, "text", "text/plain", string> &
2996
- NarrowMethod<{}, ExtractSchemas<TOutput, "text/html">, "html", "text/html", string> &
2997
- NarrowMethod<{}, ExtractSchemas<TOutput, "application/xml">, "xml", "application/xml", string> &
2998
- NarrowMethod<{}, ExtractSchemas<TOutput, "text/csv">, "csv", "text/csv", string> &
2999
- NarrowMethod<
3000
- {},
3001
- ExtractSchemas<TOutput, "application/octet-stream">,
3002
- "binary",
3003
- "application/octet-stream",
3004
- ArrayBuffer | Uint8Array<ArrayBuffer>
3005
- > &
3006
- NarrowSSE<TOutput>
3007
- }
3008
-
3009
- /** Public alias for codegen — applies output schema constraints to a context type */
3010
- export type WithOutput<TCtx, TOutput> = ApplyOutput<TCtx, TOutput>
3011
-
3012
- /** @internal — tuple entry describing a scoped middleware at the type level */
3013
- type ScopedMwEntry = { readonly path: string; readonly adds: unknown }
3014
-
3015
- /**
3016
- * Walk TScopedMw tuple, intersect `adds` for every entry whose `path` is a prefix of TFullPath.
3017
- * Prefix semantics: TFullPath extends `${P}` (exact) | `${P}/${string}` (descendant).
3018
- *
3019
- * Non-literal-path guard: if Head["path"] is the base `string` type (happens when
3020
- * the user passes a widened variable), skip the entry via `string extends Head["path"] ? {}`.
3021
- * This prevents the always-true `'/anywhere' extends string` from polluting every route.
3022
- */
3023
- type ApplyScoped<TScopedMw extends readonly ScopedMwEntry[], TFullPath extends string> = TScopedMw extends readonly [
3024
- infer Head extends ScopedMwEntry,
3025
- ...infer Rest extends readonly ScopedMwEntry[],
3026
- ]
3027
- ? (string extends Head["path"]
3028
- ? {}
3029
- : TFullPath extends Head["path"]
3030
- ? Head["adds"]
3031
- : TFullPath extends `${Head["path"] & string}/${string}`
3032
- ? Head["adds"]
3033
- : {}) &
3034
- ApplyScoped<Rest, TFullPath>
3035
- : {}
3036
-
3037
- /** Apply typed params — override params with specific keys when route has :param segments */
3038
- type ApplyParams<TCtx, TParams> = [keyof TParams] extends [string]
3039
- ? string extends keyof TParams
3040
- ? TCtx
3041
- : TCtx & { readonly params: TParams }
3042
- : TCtx
3043
-
3044
- /** Typed tap method — constrained to registered tap keys when TTaps is non-empty */
3045
- type TypedTap<TTaps extends Record<string, unknown>> = [keyof TTaps] extends [never]
3046
- ? { tap(key: string, payload: unknown): void }
3047
- : { tap<K extends string & keyof TTaps>(key: K, payload: TTaps[K]): void }
3048
-
3049
- /** Build the full handler context: base ctx + params + input + meta + errors + taps + output-constrained methods */
3050
- type HandlerCtx<
3051
- TCtx,
3052
- TInput,
3053
- TOutput,
3054
- TParams,
3055
- TAccMeta = {},
3056
- TErrorFactory = never,
3057
- TErrorKeys extends string = never,
3058
- TPath extends string = string,
3059
- TTaps extends Record<string, unknown> = {},
3060
- > = ApplyOutput<
3061
- ApplyParams<[keyof TInput] extends [never] ? TCtx : TCtx & { input: TInput }, TParams> & {
3062
- readonly meta: Readonly<Omit<TAccMeta, "openApi">>
3063
- readonly routePattern: TPath
3064
- } & ([TErrorFactory] extends [never]
3065
- ? {}
3066
- : [TErrorKeys] extends [never]
3067
- ? { readonly errors: TErrorFactory }
3068
- : {
3069
- readonly errors: Pick<TErrorFactory, TErrorKeys & keyof TErrorFactory>
3070
- }) &
3071
- TypedTap<TTaps>,
3072
- TOutput
3073
- >
3074
-
3075
- /** @internal — exposes private Honey members for RouteBuilder/WSRouteBuilder access */
3076
- type HoneyInternal = {
3077
- _addRoute(method: string, segments: readonly Segment[], record: RouteHandler): void
3078
- _addWsRoute(segments: readonly Segment[], record: WSRouteHandler): void
3079
- _factory: unknown
3080
- _settle(pending: PendingRoute): void
3081
- _view(): { chain: RuntimeMiddleware[]; cv: Record<string, unknown> | null; own: unknown }
3082
- }
3083
-
3084
- type RouteBuilderState<TParent> = {
3085
- boundaryErrorKey: string | null
3086
- /** keys the route declares itself (`.errors()`, `.boundary()`) */
3087
- errorKeys: Set<string>
3088
- extraMethods: (HttpMethod | "ALL")[] | null
3089
- inputSchemas: InputSchemasDef | null
3090
- /** explicit meta — chain `.meta()` overlaid by route `.meta()` */
3091
- meta: Record<string, unknown> | null
3092
- method: HttpMethod | "ALL"
3093
- /** route-level middleware */
3094
- middlewares: RuntimeMiddleware[]
3095
- outputSchemas: OutputSchemaDef | null
3096
- parent: TParent
3097
- /** canonical full pattern */
3098
- path: string
3099
- /** reported at finalize until `.handler()` runs */
3100
- pending: PendingRoute
3101
- }
3102
-
3103
- /**
3104
- * Route meta = middleware-contributed meta, overlaid by everything stated explicitly
3105
- * (chain `.meta()`, then route `.meta()`). Explicit always wins.
3106
- */
3107
- function mergeContributedMeta(
3108
- contributed: Record<string, unknown> | null | undefined,
3109
- explicit: Record<string, unknown> | null,
3110
- ): Record<string, unknown> | null {
3111
- if (!contributed) return explicit ? Object.freeze(explicit) : null
3112
- return Object.freeze(explicit ? { ...contributed, ...explicit } : { ...contributed })
3113
- }
3114
-
3115
- /** Honey.TMeta defaults to never; never & X is never, so start acc meta at {}. */
3116
- type InitAccMeta<TMeta> = [TMeta] extends [never] ? {} : TMeta
3117
-
3118
- /** Skip $routes merge only for a real `{ internal: true }` meta — not for `never`. */
3119
- type SkipRouteRecord<TAccMeta> = [TAccMeta] extends [never]
3120
- ? false
3121
- : [TAccMeta] extends [{ internal: true }]
3122
- ? true
3123
- : false
3124
-
3125
- /** Return type for handler() — extracted to avoid 3x duplication */
3126
- type HandlerReturn<
3127
- TEnv,
3128
- TBaseCtx,
3129
- TRoutes,
3130
- TPath extends string,
3131
- TMethod extends string,
3132
- TInput,
3133
- TOutput,
3134
- TCtx,
3135
- TAccMeta,
3136
- TErrorFactory,
3137
- _TErrorKeys extends string,
3138
- TDefaultErrors extends string,
3139
- TMeta,
3140
- TBasePath extends string = "/",
3141
- TTaps extends Record<string, unknown> = {},
3142
- TScopedMw extends readonly ScopedMwEntry[] = [],
3143
- > = Honey<
3144
- TEnv,
3145
- TBaseCtx,
3146
- SkipRouteRecord<TAccMeta> extends true
3147
- ? TRoutes
3148
- : MergeRoute<
3149
- TRoutes,
3150
- TPath,
3151
- TMethod,
3152
- TInput,
3153
- TOutput,
3154
- HandlerCtx<
3155
- TCtx,
3156
- TInput,
3157
- TOutput,
3158
- ParamsFromPath<TPath>,
3159
- TAccMeta,
3160
- TErrorFactory,
3161
- _TErrorKeys | TDefaultErrors,
3162
- TPath,
3163
- TTaps
3164
- >,
3165
- TAccMeta,
3166
- _TErrorKeys | TDefaultErrors,
3167
- ComputeErrorsByStatus<TErrorFactory, _TErrorKeys | TDefaultErrors, typeof ERROR_META>
3168
- >,
3169
- TMeta,
3170
- TErrorFactory,
3171
- TDefaultErrors,
3172
- TBasePath,
3173
- TTaps,
3174
- TScopedMw
3175
- >
3176
-
3177
- type OneShotKey = "boundary" | "errors" | "input" | "meta" | "output"
3178
-
3179
- type BuilderChain<
3180
- TEnv,
3181
- TCtx,
3182
- TInput,
3183
- TErrorKeys extends string,
3184
- TOutput,
3185
- TPath extends string,
3186
- TMethod extends string,
3187
- TRoutes,
3188
- TUsed extends string,
3189
- TBaseCtx,
3190
- TMeta,
3191
- TAccMeta,
3192
- TErrorFactory,
3193
- TDefaultErrors extends string,
3194
- TBasePath extends string,
3195
- TTaps extends Record<string, unknown> = {},
3196
- TScopedMw extends readonly ScopedMwEntry[] = [],
3197
- > = Omit<
3198
- RouteBuilder<
3199
- TEnv,
3200
- TCtx,
3201
- TInput,
3202
- TErrorKeys,
3203
- TOutput,
3204
- TPath,
3205
- TMethod,
3206
- TRoutes,
3207
- TUsed,
3208
- TBaseCtx,
3209
- TMeta,
3210
- TAccMeta,
3211
- TErrorFactory,
3212
- TDefaultErrors,
3213
- TBasePath,
3214
- TTaps,
3215
- TScopedMw
3216
- >,
3217
- TUsed & OneShotKey
3218
- >
3219
-
3220
- class RouteBuilder<
3221
- TEnv,
3222
- TCtx,
3223
- TInput = {},
3224
- _TErrorKeys extends string = never,
3225
- TOutput = {},
3226
- TPath extends string = string,
3227
- TMethod extends string = string,
3228
- TRoutes = {},
3229
- TUsed extends string = never,
3230
- TBaseCtx = TCtx,
3231
- TMeta = never,
3232
- TAccMeta = {},
3233
- TErrorFactory = never,
3234
- TDefaultErrors extends string = never,
3235
- TBasePath extends string = "/",
3236
- TTaps extends Record<string, unknown> = {},
3237
- TScopedMw extends readonly ScopedMwEntry[] = [],
3238
- > {
3239
- private _s: RouteBuilderState<
3240
- Honey<TEnv, TBaseCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
3241
- >
3242
-
3243
- constructor(
3244
- state: RouteBuilderState<
3245
- Honey<TEnv, TBaseCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
3246
- >,
3247
- ) {
3248
- this._s = state
3249
- }
3250
-
3251
- errors<
3252
- TFactory extends Record<string, (...args: never[]) => unknown>,
3253
- TKeys extends Exclude<keyof TFactory & string, TDefaultErrors>,
3254
- >(
3255
- factory: TFactory,
3256
- ...keys: TKeys[]
3257
- ): BuilderChain<
3258
- TEnv,
3259
- TCtx,
3260
- TInput,
3261
- _TErrorKeys | TKeys,
3262
- TOutput,
3263
- TPath,
3264
- TMethod,
3265
- TRoutes,
3266
- TUsed | "errors",
3267
- TBaseCtx,
3268
- TMeta,
3269
- TAccMeta,
3270
- TErrorFactory,
3271
- TDefaultErrors,
3272
- TBasePath,
3273
- TTaps,
3274
- TScopedMw
3275
- >
3276
- errors<
3277
- TKeys extends ([TErrorFactory] extends [never] ? never : Exclude<keyof TErrorFactory & string, TDefaultErrors>),
3278
- >(
3279
- ...keys: TKeys[]
3280
- ): BuilderChain<
3281
- TEnv,
3282
- TCtx,
3283
- TInput,
3284
- _TErrorKeys | TKeys,
3285
- TOutput,
3286
- TPath,
3287
- TMethod,
3288
- TRoutes,
3289
- TUsed | "errors",
3290
- TBaseCtx,
3291
- TMeta,
3292
- TAccMeta,
3293
- TErrorFactory,
3294
- TDefaultErrors,
3295
- TBasePath,
3296
- TTaps,
3297
- TScopedMw
3298
- >
3299
- errors(
3300
- ...args: unknown[]
3301
- ): BuilderChain<
3302
- TEnv,
3303
- TCtx,
3304
- TInput,
3305
- string,
3306
- TOutput,
3307
- TPath,
3308
- TMethod,
3309
- TRoutes,
3310
- TUsed | "errors",
3311
- TBaseCtx,
3312
- TMeta,
3313
- TAccMeta,
3314
- TErrorFactory,
3315
- TDefaultErrors,
3316
- TBasePath,
3317
- TTaps,
3318
- TScopedMw
3319
- > {
3320
- const keys = typeof args[0] === "object" && args[0] !== null ? (args.slice(1) as string[]) : (args as string[])
3321
- for (const k of keys) {
3322
- this._s.errorKeys.add(k)
3323
- }
3324
- return new RouteBuilder<
3325
- TEnv,
3326
- TCtx,
3327
- TInput,
3328
- string,
3329
- TOutput,
3330
- TPath,
3331
- TMethod,
3332
- TRoutes,
3333
- TUsed | "errors",
3334
- TBaseCtx,
3335
- TMeta,
3336
- TAccMeta,
3337
- TErrorFactory,
3338
- TDefaultErrors,
3339
- TBasePath,
3340
- TTaps,
3341
- TScopedMw
3342
- >({
3343
- ...this._s,
3344
- errorKeys: this._s.errorKeys,
3345
- })
3346
- }
3347
-
3348
- boundary<
3349
- TKey extends ([TErrorFactory] extends [never] ? never : Exclude<keyof TErrorFactory & string, TDefaultErrors>),
3350
- >(
3351
- key: TKey,
3352
- ): BuilderChain<
3353
- TEnv,
3354
- TCtx,
3355
- TInput,
3356
- _TErrorKeys | TKey,
3357
- TOutput,
3358
- TPath,
3359
- TMethod,
3360
- TRoutes,
3361
- TUsed | "boundary",
3362
- TBaseCtx,
3363
- TMeta,
3364
- TAccMeta,
3365
- TErrorFactory,
3366
- TDefaultErrors,
3367
- TBasePath,
3368
- TTaps,
3369
- TScopedMw
3370
- > {
3371
- this._s.boundaryErrorKey = key
3372
- this._s.errorKeys.add(key)
3373
- return new RouteBuilder<
3374
- TEnv,
3375
- TCtx,
3376
- TInput,
3377
- _TErrorKeys | TKey,
3378
- TOutput,
3379
- TPath,
3380
- TMethod,
3381
- TRoutes,
3382
- TUsed | "boundary",
3383
- TBaseCtx,
3384
- TMeta,
3385
- TAccMeta,
3386
- TErrorFactory,
3387
- TDefaultErrors,
3388
- TBasePath,
3389
- TTaps,
3390
- TScopedMw
3391
- >({
3392
- ...this._s,
3393
- boundaryErrorKey: key,
3394
- errorKeys: this._s.errorKeys,
3395
- })
3396
- }
3397
-
3398
- handler(
3399
- fn: (
3400
- ctx: HandlerCtx<
3401
- TCtx,
3402
- TInput,
3403
- TOutput,
3404
- ParamsFromPath<TPath>,
3405
- TAccMeta,
3406
- TErrorFactory,
3407
- _TErrorKeys | TDefaultErrors,
3408
- TPath,
3409
- TTaps
3410
- >,
3411
- ) => TypedResponse | Promise<TypedResponse>,
3412
- ): HandlerReturn<
3413
- TEnv,
3414
- TBaseCtx,
3415
- TRoutes,
3416
- TPath,
3417
- TMethod,
3418
- TInput,
3419
- TOutput,
3420
- TCtx,
3421
- TAccMeta,
3422
- TErrorFactory,
3423
- _TErrorKeys,
3424
- TDefaultErrors,
3425
- TMeta,
3426
- TBasePath,
3427
- TTaps,
3428
- TScopedMw
3429
- > {
3430
- let ov: OutputValidator | null = null
3431
- const outputSchemas = this._s.outputSchemas
3432
- if (outputSchemas) {
3433
- const jsonSchemas = outputSchemas["application/json"]
3434
- if (jsonSchemas) {
3435
- ov = async (statusKey: string, data: unknown) => {
3436
- const schema = jsonSchemas[statusKey as keyof typeof jsonSchemas]
3437
- if (schema) {
3438
- await validateOutput(schema, statusKey, data)
3439
- }
3440
- }
3441
- }
3442
- }
3443
-
3444
- const isInternal = Symbol.for("honey.internal") in fn
3445
- /* a bound copy per app: one `spec()` or docs handler may be mounted on several apps */
3446
- if (isInternal) fn = bindInternalHandler(fn, this._s.parent)
3447
-
3448
- const parent = this._s.parent as unknown as HoneyInternal
3449
- const view = parent._view()
3450
- const base: RouteHandler = {
3451
- _skip: isInternal || undefined,
3452
- bek: null,
3453
- cm: [...view.chain],
3454
- cv: view.cv,
3455
- dk: new Set(this._s.errorKeys),
3456
- /* provisional until finalize resolves scopes and app defaults */
3457
- ek: new Set(this._s.errorKeys),
3458
- fn: fn as (ctx: unknown) => Response | Promise<Response>,
3459
- iv: this._s.inputSchemas,
3460
- mt: mergeContributedMeta(collectMiddlewareMeta([view.chain, this._s.middlewares]), this._s.meta),
3461
- mw: [],
3462
- os: this._s.outputSchemas,
3463
- ov,
3464
- own: view.own,
3465
- rb: this._s.boundaryErrorKey,
3466
- rm: [...this._s.middlewares],
3467
- xm: this._s.meta ? { ...this._s.meta } : null,
3468
- }
3469
-
3470
- /* one record per method — `.on([...])` registers each under its own RouteId */
3471
- parent._settle(this._s.pending)
3472
- const segments = parsePattern(this._s.path)
3473
- const methods = [this._s.method, ...(this._s.extraMethods ?? [])]
3474
- for (const method of methods) assertBodySchemaAllowed(this._s.inputSchemas, method, this._s.path)
3475
- for (let i = 0; i < methods.length; i++) {
3476
- parent._addRoute(methods[i], segments, i === 0 ? base : copyRecord(base))
3477
- }
3478
- return this._s.parent as HandlerReturn<
3479
- TEnv,
3480
- TBaseCtx,
3481
- TRoutes,
3482
- TPath,
3483
- TMethod,
3484
- TInput,
3485
- TOutput,
3486
- TCtx,
3487
- TAccMeta,
3488
- TErrorFactory,
3489
- _TErrorKeys,
3490
- TDefaultErrors,
3491
- TMeta,
3492
- TBasePath,
3493
- TTaps,
3494
- TScopedMw
3495
- >
3496
- }
3497
-
3498
- proxy(
3499
- config: ProxyConfig<
3500
- HandlerCtx<
3501
- TCtx,
3502
- TInput,
3503
- TOutput,
3504
- ParamsFromPath<TPath>,
3505
- TAccMeta,
3506
- TErrorFactory,
3507
- _TErrorKeys | TDefaultErrors,
3508
- TPath,
3509
- TTaps
3510
- >
3511
- >,
3512
- ): HandlerReturn<
3513
- TEnv,
3514
- TBaseCtx,
3515
- TRoutes,
3516
- TPath,
3517
- TMethod,
3518
- TInput,
3519
- TOutput,
3520
- TCtx,
3521
- TAccMeta,
3522
- TErrorFactory,
3523
- _TErrorKeys,
3524
- TDefaultErrors,
3525
- TMeta,
3526
- TBasePath,
3527
- TTaps,
3528
- TScopedMw
3529
- > {
3530
- const proxyHandler = requireFeature("proxy", ".proxy()").createProxyHandler(config)
3531
- return this.handler(
3532
- proxyHandler as (
3533
- ctx: HandlerCtx<
3534
- TCtx,
3535
- TInput,
3536
- TOutput,
3537
- ParamsFromPath<TPath>,
3538
- TAccMeta,
3539
- TErrorFactory,
3540
- _TErrorKeys | TDefaultErrors,
3541
- TPath,
3542
- TTaps
3543
- >,
3544
- ) => Promise<TypedResponse>,
3545
- )
3546
- }
3547
-
3548
- input<TSchemas extends InputSchemasDef>(
3549
- schemas: TSchemas,
3550
- ): BuilderChain<
3551
- TEnv,
3552
- TCtx,
3553
- TInput & InferInputMap<TSchemas>,
3554
- _TErrorKeys,
3555
- TOutput,
3556
- TPath,
3557
- TMethod,
3558
- TRoutes,
3559
- TUsed | "input",
3560
- TBaseCtx,
3561
- TMeta,
3562
- TAccMeta,
3563
- TErrorFactory,
3564
- TDefaultErrors,
3565
- TBasePath,
3566
- TTaps,
3567
- TScopedMw
3568
- > {
3569
- return new RouteBuilder<
3570
- TEnv,
3571
- TCtx,
3572
- TInput & InferInputMap<TSchemas>,
3573
- _TErrorKeys,
3574
- TOutput,
3575
- TPath,
3576
- TMethod,
3577
- TRoutes,
3578
- TUsed | "input",
3579
- TBaseCtx,
3580
- TMeta,
3581
- TAccMeta,
3582
- TErrorFactory,
3583
- TDefaultErrors,
3584
- TBasePath,
3585
- TTaps,
3586
- TScopedMw
3587
- >({
3588
- ...this._s,
3589
- inputSchemas: schemas,
3590
- })
3591
- }
3592
-
3593
- meta<TRouteMeta extends Partial<DefaultMeta> & ([TMeta] extends [never] ? {} : TMeta)>(
3594
- meta: TRouteMeta,
3595
- ): BuilderChain<
3596
- TEnv,
3597
- TCtx,
3598
- TInput,
3599
- _TErrorKeys,
3600
- TOutput,
3601
- TPath,
3602
- TMethod,
3603
- TRoutes,
3604
- TUsed | "meta",
3605
- TBaseCtx,
3606
- TMeta,
3607
- TAccMeta & TRouteMeta,
3608
- TErrorFactory,
3609
- TDefaultErrors,
3610
- TBasePath,
3611
- TTaps,
3612
- TScopedMw
3613
- > {
3614
- return new RouteBuilder<
3615
- TEnv,
3616
- TCtx,
3617
- TInput,
3618
- _TErrorKeys,
3619
- TOutput,
3620
- TPath,
3621
- TMethod,
3622
- TRoutes,
3623
- TUsed | "meta",
3624
- TBaseCtx,
3625
- TMeta,
3626
- TAccMeta & TRouteMeta,
3627
- TErrorFactory,
3628
- TDefaultErrors,
3629
- TBasePath,
3630
- TTaps,
3631
- TScopedMw
3632
- >({
3633
- ...this._s,
3634
- meta: (() => {
3635
- const merged: Record<string, unknown> = { ...this._s.meta, ...meta }
3636
- const inv = merged["invalidate"]
3637
- if (Array.isArray(inv) && inv.length > 1) {
3638
- merged["invalidate"] = [...new Set(inv)]
3639
- }
3640
- return merged
3641
- })(),
3642
- })
3643
- }
3644
-
3645
- output<TOutputSchemas extends OutputSchemaDef>(
3646
- _schemas: TOutputSchemas,
3647
- ): BuilderChain<
3648
- TEnv,
3649
- TCtx,
3650
- TInput,
3651
- _TErrorKeys,
3652
- TOutputSchemas,
3653
- TPath,
3654
- TMethod,
3655
- TRoutes,
3656
- TUsed | "output",
3657
- TBaseCtx,
3658
- TMeta,
3659
- TAccMeta,
3660
- TErrorFactory,
3661
- TDefaultErrors,
3662
- TBasePath,
3663
- TTaps,
3664
- TScopedMw
3665
- > {
3666
- return new RouteBuilder<
3667
- TEnv,
3668
- TCtx,
3669
- TInput,
3670
- _TErrorKeys,
3671
- TOutputSchemas,
3672
- TPath,
3673
- TMethod,
3674
- TRoutes,
3675
- TUsed | "output",
3676
- TBaseCtx,
3677
- TMeta,
3678
- TAccMeta,
3679
- TErrorFactory,
3680
- TDefaultErrors,
3681
- TBasePath,
3682
- TTaps,
3683
- TScopedMw
3684
- >({
3685
- ...this._s,
3686
- outputSchemas: _schemas,
3687
- })
3688
- }
3689
-
3690
- use<TAdds>(
3691
- mw: MiddlewareFn<TCtx, TAdds>,
3692
- ): BuilderChain<
3693
- TEnv,
3694
- TCtx & TAdds,
3695
- TInput,
3696
- _TErrorKeys,
3697
- TOutput,
3698
- TPath,
3699
- TMethod,
3700
- TRoutes,
3701
- TUsed,
3702
- TBaseCtx,
3703
- TMeta,
3704
- TAccMeta,
3705
- TErrorFactory,
3706
- TDefaultErrors,
3707
- TBasePath,
3708
- TTaps,
3709
- TScopedMw
3710
- > {
3711
- return new RouteBuilder<
3712
- TEnv,
3713
- TCtx & TAdds,
3714
- TInput,
3715
- _TErrorKeys,
3716
- TOutput,
3717
- TPath,
3718
- TMethod,
3719
- TRoutes,
3720
- TUsed,
3721
- TBaseCtx,
3722
- TMeta,
3723
- TAccMeta,
3724
- TErrorFactory,
3725
- TDefaultErrors,
3726
- TBasePath,
3727
- TTaps,
3728
- TScopedMw
3729
- >({
3730
- ...this._s,
3731
- middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
3732
- })
3733
- }
3734
- }
3735
-
3736
- type WSRouteBuilderState<TParent> = {
3737
- errorKeys: Set<string>
3738
- inputSchemas: InputSchemasDef | null
3739
- origins: WSOriginPolicy | null
3740
- meta: Record<string, unknown> | null
3741
- middlewares: RuntimeMiddleware[]
3742
- parent: TParent
3743
- path: string
3744
- pending: PendingRoute
3745
- }
3746
-
3747
- class WSRouteBuilder<TEnv, TCtx, TInput = {}, _TErrorKeys extends string = never, TParent = unknown> {
3748
- private _s: WSRouteBuilderState<TParent>
3749
-
3750
- constructor(state: WSRouteBuilderState<TParent>) {
3751
- this._s = state
3752
- }
3753
-
3754
- errors<TFactory extends Record<string, (...args: never[]) => unknown>>(
3755
- factory: TFactory,
3756
- ...keys: Array<keyof TFactory & string>
3757
- ): WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys | (keyof TFactory & string), TParent> {
3758
- void factory
3759
- for (const k of keys) {
3760
- this._s.errorKeys.add(k)
3761
- }
3762
- return new WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys | (keyof TFactory & string), TParent>({
3763
- ...this._s,
3764
- errorKeys: this._s.errorKeys,
3765
- })
3766
- }
3767
-
3768
- handler(wsHandler: WSHandler<TCtx>): TParent {
3769
- const parent = this._s.parent as unknown as HoneyInternal
3770
- const view = parent._view()
3771
- const routeHandler: WSRouteHandler = {
3772
- bek: null,
3773
- cm: [...view.chain],
3774
- cv: view.cv,
3775
- dk: new Set(this._s.errorKeys),
3776
- ek: new Set(),
3777
- fn: wsHandler as WSHandler<unknown>,
3778
- iv: this._s.inputSchemas,
3779
- mt: null,
3780
- mw: [],
3781
- og: this._s.origins,
3782
- own: view.own,
3783
- rb: null,
3784
- rm: [...this._s.middlewares],
3785
- rp: this._s.path,
3786
- xm: this._s.meta ? { ...this._s.meta } : null,
3787
- }
3788
- parent._settle(this._s.pending)
3789
- parent._addWsRoute(parsePattern(this._s.path), routeHandler)
3790
- return this._s.parent
3791
- }
3792
-
3793
- input<TSchemas extends Pick<InputSchemasDef, "cookies" | "headers" | "search">>(
3794
- schemas: TSchemas,
3795
- ): WSRouteBuilder<TEnv, TCtx, TInput & InferInputMap<TSchemas>, _TErrorKeys, TParent> {
3796
- return new WSRouteBuilder<TEnv, TCtx, TInput & InferInputMap<TSchemas>, _TErrorKeys, TParent>({
3797
- ...this._s,
3798
- inputSchemas: schemas,
3799
- })
3800
- }
3801
-
3802
- meta(meta: Record<string, unknown>): this {
3803
- this._s.meta = { ...this._s.meta, ...meta }
3804
- return this
3805
- }
3806
-
3807
- /**
3808
- * Browser origins allowed to open this socket: `"*"`, a list of origins
3809
- * (`https://app.example.com`), or a predicate. Same-origin always passes. Without it, a
3810
- * cross-origin upgrade that carries `Cookie` or `Authorization` gets 403.
3811
- */
3812
- origins(policy: WSOriginPolicy): WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys, TParent> {
3813
- return new WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys, TParent>({
3814
- ...this._s,
3815
- origins: validateOriginPolicy(policy),
3816
- })
3817
- }
3818
-
3819
- use<TAdds>(mw: MiddlewareFn<TCtx, TAdds>): WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent> {
3820
- return new WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent>({
3821
- ...this._s,
3822
- middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
3823
- })
3824
- }
3825
- }
3826
-
3827
- export function honey<TEnv>(): Honey<TEnv> {
3828
- return new Honey<TEnv>()
3829
- }