@lovrozagar/honey 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +1596 -6
  2. package/dist/accepts.d.ts +6 -0
  3. package/dist/accepts.d.ts.map +1 -0
  4. package/dist/body-limit.d.ts +22 -0
  5. package/dist/body-limit.d.ts.map +1 -0
  6. package/dist/build/index.d.ts +42 -0
  7. package/dist/build/index.d.ts.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/client/error.d.ts +14 -0
  11. package/dist/client/error.d.ts.map +1 -0
  12. package/dist/client/http.d.ts +88 -0
  13. package/dist/client/http.d.ts.map +1 -0
  14. package/dist/client/index.d.ts +22 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/sdk.d.ts +34 -0
  17. package/dist/client/sdk.d.ts.map +1 -0
  18. package/dist/client/sse.d.ts +10 -0
  19. package/dist/client/sse.d.ts.map +1 -0
  20. package/dist/client/types.d.ts +124 -0
  21. package/dist/client/types.d.ts.map +1 -0
  22. package/dist/client/ws.d.ts +15 -0
  23. package/dist/client/ws.d.ts.map +1 -0
  24. package/dist/client-mcp/server.d.ts +10 -0
  25. package/dist/client-mcp/server.d.ts.map +1 -0
  26. package/dist/client-mcp/types.d.ts +16 -0
  27. package/dist/client-mcp/types.d.ts.map +1 -0
  28. package/dist/cloudflare/realtime.d.ts +30 -0
  29. package/dist/cloudflare/realtime.d.ts.map +1 -0
  30. package/dist/codegen-go-cli.d.ts +22 -0
  31. package/dist/codegen-go-cli.d.ts.map +1 -0
  32. package/dist/codegen-go.d.ts +29 -0
  33. package/dist/codegen-go.d.ts.map +1 -0
  34. package/dist/codegen-hash.d.ts +3 -0
  35. package/dist/codegen-hash.d.ts.map +1 -0
  36. package/dist/codegen-ir.d.ts +123 -0
  37. package/dist/codegen-ir.d.ts.map +1 -0
  38. package/dist/codegen-loaders.d.ts +12 -0
  39. package/dist/codegen-loaders.d.ts.map +1 -0
  40. package/dist/codegen-mcp.d.ts +13 -0
  41. package/dist/codegen-mcp.d.ts.map +1 -0
  42. package/dist/codegen-python.d.ts +21 -0
  43. package/dist/codegen-python.d.ts.map +1 -0
  44. package/dist/codegen-rust.d.ts +18 -0
  45. package/dist/codegen-rust.d.ts.map +1 -0
  46. package/dist/codegen-sanitize.d.ts +3 -0
  47. package/dist/codegen-sanitize.d.ts.map +1 -0
  48. package/dist/codegen-schema-naming.d.ts +22 -0
  49. package/dist/codegen-schema-naming.d.ts.map +1 -0
  50. package/dist/codegen.d.ts +178 -0
  51. package/dist/codegen.d.ts.map +1 -0
  52. package/dist/context.d.ts +56 -0
  53. package/dist/context.d.ts.map +1 -0
  54. package/dist/cookie-sign.d.ts +3 -0
  55. package/dist/cookie-sign.d.ts.map +1 -0
  56. package/dist/cookie.d.ts +12 -0
  57. package/dist/cookie.d.ts.map +1 -0
  58. package/dist/cors.d.ts +13 -0
  59. package/dist/cors.d.ts.map +1 -0
  60. package/dist/crypto.d.ts +2 -0
  61. package/dist/crypto.d.ts.map +1 -0
  62. package/dist/csrf.d.ts +9 -0
  63. package/dist/csrf.d.ts.map +1 -0
  64. package/dist/curl-logger.d.ts +33 -0
  65. package/dist/curl-logger.d.ts.map +1 -0
  66. package/dist/detect-runtime.d.ts +16 -0
  67. package/dist/detect-runtime.d.ts.map +1 -0
  68. package/dist/error.d.ts +21 -0
  69. package/dist/error.d.ts.map +1 -0
  70. package/dist/errors.d.ts +40 -0
  71. package/dist/errors.d.ts.map +1 -0
  72. package/dist/etag.d.ts +9 -0
  73. package/dist/etag.d.ts.map +1 -0
  74. package/dist/feature-load.d.ts +3 -0
  75. package/dist/feature-load.d.ts.map +1 -0
  76. package/dist/go-type-emitter.d.ts +33 -0
  77. package/dist/go-type-emitter.d.ts.map +1 -0
  78. package/dist/honey-response.d.ts +82 -0
  79. package/dist/honey-response.d.ts.map +1 -0
  80. package/dist/i18n-slot.d.ts +7 -0
  81. package/dist/i18n-slot.d.ts.map +1 -0
  82. package/dist/i18n.d.ts +26 -0
  83. package/dist/i18n.d.ts.map +1 -0
  84. package/dist/index.d.ts +470 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/init.d.ts +7 -0
  87. package/dist/init.d.ts.map +1 -0
  88. package/dist/input.d.ts +6 -0
  89. package/dist/input.d.ts.map +1 -0
  90. package/dist/ip-restrict.d.ts +12 -0
  91. package/dist/ip-restrict.d.ts.map +1 -0
  92. package/dist/logger.d.ts +45 -0
  93. package/dist/logger.d.ts.map +1 -0
  94. package/dist/meta-spec.d.ts +81 -0
  95. package/dist/meta-spec.d.ts.map +1 -0
  96. package/dist/middleware.d.ts +60 -0
  97. package/dist/middleware.d.ts.map +1 -0
  98. package/dist/node-request.d.ts +59 -0
  99. package/dist/node-request.d.ts.map +1 -0
  100. package/dist/node.d.ts +13 -0
  101. package/dist/node.d.ts.map +1 -0
  102. package/dist/openapi/register.d.ts +3 -0
  103. package/dist/openapi/register.d.ts.map +1 -0
  104. package/dist/openapi/scalar.d.ts +12 -0
  105. package/dist/openapi/scalar.d.ts.map +1 -0
  106. package/dist/openapi/spec-factory.d.ts +31 -0
  107. package/dist/openapi/spec-factory.d.ts.map +1 -0
  108. package/dist/openapi/spec.d.ts +19 -0
  109. package/dist/openapi/spec.d.ts.map +1 -0
  110. package/dist/openapi/swagger.d.ts +12 -0
  111. package/dist/openapi/swagger.d.ts.map +1 -0
  112. package/dist/plugin.d.ts +167 -0
  113. package/dist/plugin.d.ts.map +1 -0
  114. package/dist/powered-by.d.ts +7 -0
  115. package/dist/powered-by.d.ts.map +1 -0
  116. package/dist/pretty-json.d.ts +10 -0
  117. package/dist/pretty-json.d.ts.map +1 -0
  118. package/dist/proxy.d.ts +51 -0
  119. package/dist/proxy.d.ts.map +1 -0
  120. package/dist/python-type-emitter.d.ts +11 -0
  121. package/dist/python-type-emitter.d.ts.map +1 -0
  122. package/dist/realtime/buffer.d.ts +18 -0
  123. package/dist/realtime/buffer.d.ts.map +1 -0
  124. package/dist/realtime/bus.d.ts +11 -0
  125. package/dist/realtime/bus.d.ts.map +1 -0
  126. package/dist/realtime/longpoll.d.ts +23 -0
  127. package/dist/realtime/longpoll.d.ts.map +1 -0
  128. package/dist/realtime/protocol.d.ts +50 -0
  129. package/dist/realtime/protocol.d.ts.map +1 -0
  130. package/dist/realtime/route.d.ts +42 -0
  131. package/dist/realtime/route.d.ts.map +1 -0
  132. package/dist/realtime/runtime.d.ts +74 -0
  133. package/dist/realtime/runtime.d.ts.map +1 -0
  134. package/dist/request-id.d.ts +12 -0
  135. package/dist/request-id.d.ts.map +1 -0
  136. package/dist/request-to-curl.d.ts +9 -0
  137. package/dist/request-to-curl.d.ts.map +1 -0
  138. package/dist/response.d.ts +57 -0
  139. package/dist/response.d.ts.map +1 -0
  140. package/dist/rust-type-emitter.d.ts +32 -0
  141. package/dist/rust-type-emitter.d.ts.map +1 -0
  142. package/dist/secure-headers.d.ts +16 -0
  143. package/dist/secure-headers.d.ts.map +1 -0
  144. package/dist/serve-register.d.ts +2 -0
  145. package/dist/serve-register.d.ts.map +1 -0
  146. package/dist/serve-slot.d.ts +6 -0
  147. package/dist/serve-slot.d.ts.map +1 -0
  148. package/dist/serve.d.ts +21 -0
  149. package/dist/serve.d.ts.map +1 -0
  150. package/dist/server-timing.d.ts +9 -0
  151. package/dist/server-timing.d.ts.map +1 -0
  152. package/dist/static.d.ts +12 -0
  153. package/dist/static.d.ts.map +1 -0
  154. package/dist/telemetry/otel.d.ts +59 -0
  155. package/dist/telemetry/otel.d.ts.map +1 -0
  156. package/dist/testing.d.ts +24 -0
  157. package/dist/testing.d.ts.map +1 -0
  158. package/dist/timeout.d.ts +7 -0
  159. package/dist/timeout.d.ts.map +1 -0
  160. package/dist/tree.d.ts +90 -0
  161. package/dist/tree.d.ts.map +1 -0
  162. package/dist/ts-type-emitter.d.ts +4 -0
  163. package/dist/ts-type-emitter.d.ts.map +1 -0
  164. package/dist/type-emitter.d.ts +8 -0
  165. package/dist/type-emitter.d.ts.map +1 -0
  166. package/dist/type-extractor.d.ts +39 -0
  167. package/dist/type-extractor.d.ts.map +1 -0
  168. package/dist/types.d.ts +451 -0
  169. package/dist/types.d.ts.map +1 -0
  170. package/dist/validation.d.ts +18 -0
  171. package/dist/validation.d.ts.map +1 -0
  172. package/dist/ws/bun.d.ts +28 -0
  173. package/dist/ws/bun.d.ts.map +1 -0
  174. package/dist/ws/cloudflare.d.ts +55 -0
  175. package/dist/ws/cloudflare.d.ts.map +1 -0
  176. package/dist/ws/deno.d.ts +13 -0
  177. package/dist/ws/deno.d.ts.map +1 -0
  178. package/dist/ws/node.d.ts +10 -0
  179. package/dist/ws/node.d.ts.map +1 -0
  180. package/dist/yaml.d.ts +4 -0
  181. package/dist/yaml.d.ts.map +1 -0
  182. package/docs/api-ref/go.md +82 -82
  183. package/docs/api-ref/python.md +82 -82
  184. package/docs/api-ref/rust.md +82 -82
  185. package/docs/api-ref/typescript.md +82 -82
  186. package/docs/invalidate-spec.md +13 -13
  187. package/docs/meta-spec.md +513 -0
  188. package/docs/packaging.md +85 -0
  189. package/docs/sdk.md +87 -73
  190. package/package.json +195 -51
  191. package/src/body-limit.ts +2 -1
  192. package/src/build/index.ts +2 -9
  193. package/src/cli.ts +2 -3
  194. package/src/client/http.ts +204 -223
  195. package/src/client/index.ts +6 -14
  196. package/src/client/sdk.ts +24 -17
  197. package/src/client/types.ts +4 -9
  198. package/src/client/ws.ts +3 -5
  199. package/src/client-mcp/server.ts +1 -4
  200. package/src/cloudflare/realtime.ts +8 -10
  201. package/src/codegen-go-cli.ts +74 -67
  202. package/src/codegen-go.ts +71 -51
  203. package/src/codegen-ir.ts +16 -27
  204. package/src/codegen-loaders.ts +5 -8
  205. package/src/codegen-mcp.ts +9 -27
  206. package/src/codegen-python.ts +47 -27
  207. package/src/codegen-rust.ts +110 -66
  208. package/src/codegen-sanitize.ts +1 -7
  209. package/src/codegen-schema-naming.ts +8 -2
  210. package/src/codegen.ts +299 -196
  211. package/src/context.ts +91 -95
  212. package/src/cookie-sign.ts +5 -13
  213. package/src/cookie.ts +1 -2
  214. package/src/cors.ts +4 -6
  215. package/src/crypto.ts +1 -4
  216. package/src/csrf.ts +3 -3
  217. package/src/curl-logger.ts +14 -32
  218. package/src/detect-runtime.ts +1 -3
  219. package/src/errors.ts +1 -3
  220. package/src/etag.ts +3 -4
  221. package/src/go-type-emitter.ts +45 -40
  222. package/src/honey-response.ts +1 -3
  223. package/src/i18n.ts +3 -12
  224. package/src/index.ts +1168 -1412
  225. package/src/ip-restrict.ts +2 -1
  226. package/src/logger.ts +13 -13
  227. package/src/meta-spec.ts +0 -0
  228. package/src/middleware.ts +111 -79
  229. package/src/node-request.ts +2 -1
  230. package/src/node.ts +9 -10
  231. package/src/openapi/register.ts +1 -2
  232. package/src/openapi/scalar.ts +9 -9
  233. package/src/openapi/spec-factory.ts +2 -2
  234. package/src/openapi/spec.ts +26 -19
  235. package/src/openapi/swagger.ts +9 -9
  236. package/src/plugin.ts +226 -256
  237. package/src/powered-by.ts +2 -1
  238. package/src/pretty-json.ts +2 -1
  239. package/src/proxy.ts +1 -3
  240. package/src/python-type-emitter.ts +37 -12
  241. package/src/realtime/route.ts +1 -1
  242. package/src/realtime/runtime.ts +9 -12
  243. package/src/request-id.ts +3 -4
  244. package/src/request-to-curl.ts +1 -4
  245. package/src/response.ts +150 -199
  246. package/src/rust-type-emitter.ts +66 -25
  247. package/src/secure-headers.ts +2 -1
  248. package/src/serve.ts +3 -12
  249. package/src/server-timing.ts +2 -1
  250. package/src/static.ts +3 -4
  251. package/src/telemetry/otel.ts +1 -6
  252. package/src/testing.ts +2 -11
  253. package/src/timeout.ts +2 -1
  254. package/src/tree.ts +12 -27
  255. package/src/ts-type-emitter.ts +71 -15
  256. package/src/type-emitter.ts +226 -237
  257. package/src/type-extractor.ts +260 -334
  258. package/src/types.ts +168 -68
  259. package/src/validation.ts +7 -30
  260. package/src/ws/bun.ts +2 -9
  261. package/src/ws/cloudflare.ts +13 -16
  262. package/src/ws/deno.ts +31 -40
  263. package/src/ws/node.ts +4 -19
package/src/index.ts CHANGED
@@ -1,62 +1,47 @@
1
- import { HoneyContext } from "./context.ts";
2
- import { HoneyError } from "./error.ts";
3
- import { ERROR_META } from "./errors.ts";
4
- import type { MiddlewareFn, RuntimeMiddleware } from "./middleware.ts";
5
- import { compileChain, executeChain } from "./middleware.ts";
6
- import type { ProxyConfig } from "./proxy.ts";
7
- import { createProxyHandler } from "./proxy.ts";
1
+ import { HoneyContext } from "./context.ts"
2
+ import { HoneyError } from "./error.ts"
3
+ import { ERROR_META } from "./errors.ts"
4
+ import type { MiddlewareFn, RuntimeMiddleware } from "./middleware.ts"
5
+ import { collectMiddlewareMeta } from "./middleware.ts"
6
+ import { compileChain, executeChain } from "./middleware.ts"
7
+ import type { ProxyConfig } from "./proxy.ts"
8
+ import { createProxyHandler } from "./proxy.ts"
9
+ import type { CustomErrorFormatter, ErrorFormatter, ResponseOptions, TypedResponse } from "./response.ts"
10
+ import { createErrorResponse, type HoneyRes } from "./response.ts"
11
+ import type { OutputValidator, RouteHandler, RouteTree, TreeNode, WSRouteHandler } from "./tree.ts"
12
+ import { createNode, insertRoute, insertWsRoute, matchRoute, matchWsRoute, mergeInto } from "./tree.ts"
13
+ import { createBus } from "./realtime/bus.ts"
14
+ import type { RealtimeBus } from "./realtime/bus.ts"
15
+ import { createConnContext } from "./realtime/route.ts"
16
+ import type { RealtimeRouteOpts } from "./realtime/route.ts"
8
17
  import type {
9
- CustomErrorFormatter,
10
- ErrorFormatter,
11
- ResponseOptions,
12
- TypedResponse,
13
- } from "./response.ts";
14
- import { createErrorResponse, type HoneyRes } from "./response.ts";
15
- import type {
16
- OutputValidator,
17
- RouteHandler,
18
- RouteTree,
19
- TreeNode,
20
- WSRouteHandler,
21
- } from "./tree.ts";
22
- import {
23
- createNode,
24
- insertRoute,
25
- insertWsRoute,
26
- matchRoute,
27
- matchWsRoute,
28
- mergeInto,
29
- } from "./tree.ts";
30
- import { createBus } from "./realtime/bus.ts";
31
- import type { RealtimeBus } from "./realtime/bus.ts";
32
- import { createConnContext } from "./realtime/route.ts";
33
- import type { RealtimeRouteOpts } from "./realtime/route.ts";
34
- import type {
35
- ComputeErrorsByStatus,
36
- DefaultMeta,
18
+ ComputeErrorsByStatus,
19
+ DefaultMeta,
37
20
  ExtractSchemas,
38
21
  HttpMethod,
39
- InferInputMap,
40
- InferOutput,
41
- InputSchemasDef,
42
- MergePath,
43
- MergeRoute,
44
- OutputSchemaDef,
45
- ParamsFromPath,
46
- StandardSchemaLike,
47
- StatusKey,
22
+ InferInputMap,
23
+ InferOutput,
24
+ HoneyMetaSpec,
25
+ InputSchemasDef,
26
+ MergePath,
27
+ MergeRoute,
28
+ MetaSpecConfig,
29
+ OutputSchemaDef,
30
+ ParamsFromPath,
31
+ StandardSchemaLike,
32
+ StatusKey,
48
33
  TapContext,
49
- } from "./types.ts";
50
- import { codeToStatusKey, EK, EMPTY_OBJ, SK } from "./types.ts";
51
- import { validateInput, validateOutput } from "./validation.ts";
52
- import type { WSAdapter, WSContext, WSHandler } from "./ws/cloudflare.ts";
53
- import { loadHoneyFeature } from "./feature-load.ts";
54
- import { getI18nRuntime } from "./i18n-slot.ts";
55
- import { getOpenApiRuntime } from "./openapi/spec-factory.ts";
56
- import { getServeRuntime } from "./serve-slot.ts";
57
- import type { HoneyServeOptions, ServeHandle } from "./serve.ts";
58
-
59
- export { HoneyContext } from "./context.ts";
34
+ } from "./types.ts"
35
+ import { codeToStatusKey, EK, EMPTY_OBJ, SK } from "./types.ts"
36
+ import { validateInput, validateOutput } from "./validation.ts"
37
+ import type { WSAdapter, WSContext, WSHandler } from "./ws/cloudflare.ts"
38
+ import { loadHoneyFeature } from "./feature-load.ts"
39
+ import { getI18nRuntime } from "./i18n-slot.ts"
40
+ import { getOpenApiRuntime } from "./openapi/spec-factory.ts"
41
+ import { getServeRuntime } from "./serve-slot.ts"
42
+ import type { HoneyServeOptions, ServeHandle } from "./serve.ts"
43
+
44
+ export { HoneyContext } from "./context.ts"
60
45
  /** HoneyContext without internal backing fields — use this for consumer-facing types */
61
46
  export type HoneyCtx<TEnv = Record<string, unknown>> = Omit<
62
47
  import("./context.ts").HoneyContext<TEnv>,
@@ -68,14 +53,14 @@ export type HoneyCtx<TEnv = Record<string, unknown>> = Omit<
68
53
  | "_lzSearchAll"
69
54
  | "_lzUrlFn"
70
55
  | "_setErrors"
71
- >;
72
- export { HoneyError } from "./error.ts";
73
- export { defineErrors, ERROR_META } from "./errors.ts";
74
- export type { ErrorMetaEntry } from "./errors.ts";
75
- export type { MiddlewareFn } from "./middleware.ts";
76
- export { createMiddleware, defineMiddleware } from "./middleware.ts";
77
- export type { ProxyConfig } from "./proxy.ts";
78
- export type { PendingTap, TapContext, TapHandler } from "./types.ts";
56
+ >
57
+ export { HoneyError } from "./error.ts"
58
+ export { defineErrors, ERROR_META } from "./errors.ts"
59
+ export type { ErrorMetaEntry } from "./errors.ts"
60
+ export type { MiddlewareFn } from "./middleware.ts"
61
+ export { createMiddleware, defineMiddleware } from "./middleware.ts"
62
+ export type { ProxyConfig } from "./proxy.ts"
63
+ export type { PendingTap, TapContext, TapHandler } from "./types.ts"
79
64
  export type {
80
65
  CookieOptions,
81
66
  CustomErrorFormatter,
@@ -85,9 +70,9 @@ export type {
85
70
  SSEOptions,
86
71
  SSEStream,
87
72
  TypedResponse,
88
- } from "./response.ts";
89
- export { createErrorResponse, HoneyRes } from "./response.ts";
90
- export { mergeTree } from "./tree.ts";
73
+ } from "./response.ts"
74
+ export { createErrorResponse, HoneyRes } from "./response.ts"
75
+ export { mergeTree } from "./tree.ts"
91
76
  export type {
92
77
  DefaultMeta,
93
78
  HoneyCodegen,
@@ -110,6 +95,18 @@ export type {
110
95
  InferRoutes,
111
96
  InputSchemasDef,
112
97
  MergePath,
98
+ HoneyMetaSpec,
99
+ MetaSpecConfig,
100
+ MetaSpecContext,
101
+ MetaSpecEntry,
102
+ MetaSpecExpand,
103
+ MetaSpecProfile,
104
+ MetaSpecSchemaEntry,
105
+ MetaSpecSchemaSearch,
106
+ MetaSpecSchemaSource,
107
+ MetaSpecSingle,
108
+ MetaSpecStrictness,
109
+ MetaSpecTarget,
113
110
  OpenApiMeta,
114
111
  OutputSchemaDef,
115
112
  Overwrite,
@@ -118,171 +115,170 @@ export type {
118
115
  StandardSchemaLike,
119
116
  StatusKey,
120
117
  SuccessStatusKey,
121
- } from "./types.ts";
122
- export type { WSAdapter, WSContext, WSHandler, WSPreUpgrade } from "./ws/cloudflare.ts";
123
- export type { ConnContext, RealtimeRouteOpts } from "./realtime/route.ts";
124
- export type { RealtimeBus } from "./realtime/bus.ts";
125
- export type { HoneyServeOptions, ServeHandle } from "./serve.ts";
126
- export type { ServeRuntime } from "./detect-runtime.ts";
127
- export { detectRuntime } from "./detect-runtime.ts";
118
+ } from "./types.ts"
119
+ export type { WSAdapter, WSContext, WSHandler, WSPreUpgrade } from "./ws/cloudflare.ts"
120
+ export type { ConnContext, RealtimeRouteOpts } from "./realtime/route.ts"
121
+ export type { RealtimeBus } from "./realtime/bus.ts"
122
+ export type { HoneyServeOptions, ServeHandle } from "./serve.ts"
123
+ export type { ServeRuntime } from "./detect-runtime.ts"
124
+ export { detectRuntime } from "./detect-runtime.ts"
128
125
 
129
126
  /** Type predicate for incoming wire-protocol msg frames from the realtime client. */
130
127
  function isMsgFrame(value: unknown): value is { data: unknown; t: "msg" } {
131
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
132
- if (!("t" in value) || !("data" in value)) return false;
133
- return value.t === "msg";
128
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false
129
+ if (!("t" in value) || !("data" in value)) return false
130
+ return value.t === "msg"
134
131
  }
135
132
 
136
133
  type TelemetryAdapter = {
137
- onError?(ctx: {
138
- duration: number;
139
- error: HoneyError;
140
- method: string;
141
- path: string;
142
- }): void;
143
- onHandler?(ctx: {
144
- duration: number;
145
- method: string;
146
- path: string;
147
- status: number;
148
- }): void;
149
- onMethodNotAllowed?(ctx: {
150
- allowed: string[];
151
- method: string;
152
- path: string;
153
- req: Request;
154
- }): void;
155
- onMiddleware?(ctx: { duration: number; error?: unknown; name: string }): void;
156
- onNotFound?(ctx: { method: string; path: string; req: Request }): void;
157
- onRequest?(ctx: { env: unknown; req: Request }): void;
158
- onResponse?(ctx: { duration: number; req: Request; status: number }): void;
159
- onRoute?(ctx: {
160
- method: string;
161
- params: Record<string, string>;
162
- path: string;
163
- req: Request;
164
- }): void;
165
- };
134
+ onError?(ctx: { duration: number; error: HoneyError; method: string; path: string }): void
135
+ onHandler?(ctx: { duration: number; method: string; path: string; status: number }): void
136
+ onMethodNotAllowed?(ctx: { allowed: string[]; method: string; path: string; req: Request }): void
137
+ onMiddleware?(ctx: { duration: number; error?: unknown; name: string }): void
138
+ onNotFound?(ctx: { method: string; path: string; req: Request }): void
139
+ onRequest?(ctx: { env: unknown; req: Request }): void
140
+ onResponse?(ctx: { duration: number; req: Request; status: number }): void
141
+ onRoute?(ctx: { method: string; params: Record<string, string>; path: string; req: Request }): void
142
+ }
166
143
 
167
144
  type ErrorI18nConfig<TEnv> = {
168
- errors?: Record<string, Record<string, string>>;
169
- fieldNames?: Record<string, Record<string, string>>;
145
+ errors?: Record<string, Record<string, string>>
146
+ fieldNames?: Record<string, Record<string, string>>
170
147
  resolveLocale: (ctx: {
171
- cookies: Record<string, string>;
172
- env: TEnv;
173
- headers: Record<string, string>;
174
- params: Record<string, string>;
175
- req: Request;
176
- search: Record<string, string>;
177
- }) => string | Promise<string>;
178
- };
148
+ cookies: Record<string, string>
149
+ env: TEnv
150
+ headers: Record<string, string>
151
+ params: Record<string, string>
152
+ req: Request
153
+ search: Record<string, string>
154
+ }) => string | Promise<string>
155
+ }
179
156
 
180
157
  type Logger = {
181
- warn?(msg: string, ...args: unknown[]): void;
182
- };
158
+ warn?(msg: string, ...args: unknown[]): void
159
+ }
183
160
 
184
161
  function mergePath(base: string, path: string): string {
185
- if (base === "/") return path;
186
- if (path === "/") return base;
187
- return `${base}${path}`;
162
+ if (base === "/") return path
163
+ if (path === "/") return base
164
+ return `${base}${path}`
188
165
  }
189
166
 
190
167
  /** Normalize a scope prefix: ensure leading slash, strip trailing slash (except "/"). */
191
168
  function normalizeScopePath(raw: string): string {
192
- let p = raw;
193
- if (p.length === 0) return "/";
194
- if (p.charCodeAt(0) !== 47) p = `/${p}`;
195
- if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
196
- return p;
169
+ let p = raw
170
+ if (p.length === 0) return "/"
171
+ if (p.charCodeAt(0) !== 47) p = `/${p}`
172
+ if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1)
173
+ return p
197
174
  }
198
175
 
199
176
  /** Check whether fullPath falls under scope prefix — exact match or next char is '/'. */
200
177
  function scopeMatches(prefix: string, fullPath: string): boolean {
201
- if (prefix === "/") return true;
202
- if (fullPath === prefix) return true;
203
- if (fullPath.length <= prefix.length) return false;
204
- if (fullPath.charCodeAt(prefix.length) !== 47) return false;
205
- return fullPath.startsWith(prefix);
178
+ if (prefix === "/") return true
179
+ if (fullPath === prefix) return true
180
+ if (fullPath.length <= prefix.length) return false
181
+ if (fullPath.charCodeAt(prefix.length) !== 47) return false
182
+ return fullPath.startsWith(prefix)
206
183
  }
207
184
 
208
185
  type HoneyGraph = {
209
186
  handlerMap: Record<string, RouteHandler> | null
210
187
  hasRouteTree: boolean
188
+ /** Codegen-time meta → OpenAPI policy. Never read on the request path */
189
+ metaSpec: MetaSpecConfig | null
211
190
  realtimeBus: RealtimeBus | null
212
191
  root: TreeNode
213
192
  }
214
193
 
215
- function walkTreeHandlers(
216
- root: TreeNode,
217
- cb: (h: RouteHandler) => void,
218
- wsCb?: (h: WSRouteHandler) => void,
219
- ): void {
194
+ function walkTreeHandlers(root: TreeNode, cb: (h: RouteHandler) => void, wsCb?: (h: WSRouteHandler) => void): void {
220
195
  if (root.m) {
221
- for (const h of Object.values(root.m) as RouteHandler[]) cb(h);
196
+ for (const h of Object.values(root.m) as RouteHandler[]) cb(h)
222
197
  }
223
198
  if (root.w) {
224
- for (const h of Object.values(root.w.m) as RouteHandler[]) cb(h);
199
+ for (const h of Object.values(root.w.m) as RouteHandler[]) cb(h)
225
200
  }
226
- if (root.ws && wsCb) wsCb(root.ws);
227
- for (const child of Object.values(root.s)) walkTreeHandlers(child, cb, wsCb);
228
- if (root.d) walkTreeHandlers(root.d.c, cb, wsCb);
201
+ if (root.ws && wsCb) wsCb(root.ws)
202
+ for (const child of Object.values(root.s)) walkTreeHandlers(child, cb, wsCb)
203
+ if (root.d) walkTreeHandlers(root.d.c, cb, wsCb)
229
204
  }
230
205
 
231
206
  const EMPTY_PARAMS = EMPTY_OBJ as Record<string, string>
232
207
  const EMPTY_MW: RuntimeMiddleware[] = []
233
208
 
209
+ function requestIsWsUpgrade(request: Request): boolean {
210
+ try {
211
+ return request.headers.get("upgrade")?.toLowerCase() === "websocket"
212
+ } catch {
213
+ /* Deno closes the Request after upgradeWebSocket */
214
+ return false
215
+ }
216
+ }
217
+
234
218
  /** Find first '?' or '#' in url starting from pos */
235
219
  function findSearchOrHash(url: string, pos: number): number {
236
220
  for (let i = pos; i < url.length; i++) {
237
- const c = url.charCodeAt(i);
238
- if (c === 63 || c === 35) return i;
221
+ const c = url.charCodeAt(i)
222
+ if (c === 63 || c === 35) return i
239
223
  }
240
- return -1;
224
+ return -1
241
225
  }
242
226
 
243
227
  function safeFire(fn: (() => unknown) | undefined, logger?: Logger): void {
244
- if (fn === undefined) return;
228
+ if (fn === undefined) return
245
229
  try {
246
- const result = fn();
230
+ const result = fn()
247
231
  if (result && typeof result === "object" && "catch" in result) {
248
- (result as Promise<unknown>).catch((e: unknown) => {
249
- logger?.warn?.("telemetry callback failed", e);
250
- });
232
+ ;(result as Promise<unknown>).catch((e: unknown) => {
233
+ logger?.warn?.("telemetry callback failed", e)
234
+ })
251
235
  }
252
236
  } catch (e) {
253
- logger?.warn?.("telemetry callback failed", e);
237
+ logger?.warn?.("telemetry callback failed", e)
254
238
  }
255
239
  }
256
240
 
257
241
  /** Internal context shared across extracted fetch sub-methods */
258
242
  type FetchCtx<TEnv> = {
259
- env: TEnv;
260
- executionCtx: { waitUntil?: (p: Promise<unknown>) => void } | undefined;
261
- log: Logger | undefined;
262
- request: Request;
263
- startTime: number;
264
- url: () => URL;
265
- };
266
-
267
- function defaultErrorFormatter(
268
- _error: HoneyError,
269
- defaultShape: Record<string, unknown>,
270
- ): Record<string, unknown> {
271
- return defaultShape;
243
+ env: TEnv
244
+ executionCtx: { waitUntil?: (p: Promise<unknown>) => void } | undefined
245
+ log: Logger | undefined
246
+ request: Request
247
+ startTime: number
248
+ url: () => URL
249
+ /** Already known from fetch() before Deno.upgradeWebSocket consumes the request. */
250
+ wsUpgrade?: boolean
251
+ /** Headers copied before Deno.upgradeWebSocket closes the Request. */
252
+ headerSnap?: Headers
272
253
  }
273
254
 
274
- type ErrorFormatterFn = ErrorFormatter;
255
+ function requestWithHeaders(req: Request, headers: Headers): Request {
256
+ return new Proxy(req, {
257
+ get(target, prop) {
258
+ if (prop === "headers") return headers
259
+ const value = Reflect.get(target, prop, target)
260
+ return typeof value === "function" ? (value as (...args: never[]) => unknown).bind(target) : value
261
+ },
262
+ })
263
+ }
275
264
 
276
- const STATIC_CTX_RESERVED = new Set([
277
- "background",
278
- "cookies",
279
- "env",
280
- "headers",
281
- "params",
282
- "req",
283
- "res",
284
- "search",
285
- ]);
265
+ function ctxRequest(fc: FetchCtx<unknown>): Request {
266
+ if (!fc.headerSnap) return fc.request
267
+ try {
268
+ void fc.request.headers.get("upgrade")
269
+ return fc.request
270
+ } catch {
271
+ return requestWithHeaders(fc.request, fc.headerSnap)
272
+ }
273
+ }
274
+
275
+ function defaultErrorFormatter(_error: HoneyError, defaultShape: Record<string, unknown>): Record<string, unknown> {
276
+ return defaultShape
277
+ }
278
+
279
+ type ErrorFormatterFn = ErrorFormatter
280
+
281
+ const STATIC_CTX_RESERVED = new Set(["background", "cookies", "env", "headers", "params", "req", "res", "search"])
286
282
 
287
283
  /* errorKeys the framework throws on its own behalf — input/output validation, content negotiation,
288
284
  * routing. Always passes the boundary check; users never declare these via .errors(). */
@@ -298,7 +294,7 @@ const FRAMEWORK_EKS = new Set<(typeof EK)[keyof typeof EK]>([
298
294
  EK.request_timeout,
299
295
  EK.gateway_timeout,
300
296
  EK.bad_gateway,
301
- ]);
297
+ ])
302
298
 
303
299
  export class Honey<
304
300
  TEnv,
@@ -311,171 +307,176 @@ export class Honey<
311
307
  TTaps extends Record<string, unknown> = {},
312
308
  TScopedMw extends readonly ScopedMwEntry[] = [],
313
309
  > {
314
- declare readonly $basePath: TBasePath;
315
- declare readonly $ctx: TCtx;
316
- declare readonly $env: TEnv;
317
- declare readonly $errorFactory: TErrorFactory;
318
- declare readonly $meta: TMeta;
319
- declare readonly $routes: TRoutes;
320
- declare readonly $taps: TTaps;
321
- private _basePath: string;
322
- private _defaultBoundaryKey: string | null;
323
- private _defaultErrorKeys: Set<string>;
324
- private _errorFactory: unknown;
325
- private _errorSchema: StandardSchemaLike | null;
326
- private _customErrorFormatter: CustomErrorFormatter | null;
327
- private _customErrorSchema: StandardSchemaLike | null;
328
- private _chainMeta: Record<string, unknown> | null;
329
- private _chainMiddlewares: RuntimeMiddleware[];
330
- private _scopedMiddlewares: ScopedEntry[];
331
- private _contextValues: Record<string, unknown> | null;
332
- private _errorFormatter: ErrorFormatterFn;
333
- private _errorI18n: ErrorI18nConfig<TEnv> | null;
334
- private _globalMiddlewares: RuntimeMiddleware[];
335
- private _graph: HoneyGraph;
336
- private _hasWsRoutes: boolean;
337
- private _logger: Logger | null;
338
- private _outputValidation: "always" | "dev" | "off";
339
- private _staticRoutes: { map: Record<string, RouteHandler> | null };
340
- private _stripPrefix: string | null;
341
- private _trailingSlash: "enforce" | "ignore" | "strip";
342
- private _wsAdapter: WSAdapter | null;
343
- private _openApiCache: { epoch: number; value: Promise<unknown> } | null;
344
- private _openApiYamlCache: { epoch: number; value: Promise<string> } | null;
345
- private _manifestCache: { epoch: number; value: Promise<unknown> } | null;
310
+ declare readonly $basePath: TBasePath
311
+ declare readonly $ctx: TCtx
312
+ declare readonly $env: TEnv
313
+ declare readonly $errorFactory: TErrorFactory
314
+ declare readonly $meta: TMeta
315
+ declare readonly $routes: TRoutes
316
+ declare readonly $taps: TTaps
317
+ private _basePath: string
318
+ private _defaultBoundaryKey: string | null
319
+ private _defaultErrorKeys: Set<string>
320
+ private _errorFactory: unknown
321
+ private _errorSchema: StandardSchemaLike | null
322
+ private _customErrorFormatter: CustomErrorFormatter | null
323
+ private _customErrorSchema: StandardSchemaLike | null
324
+ private _chainMeta: Record<string, unknown> | null
325
+ private _chainMiddlewares: RuntimeMiddleware[]
326
+ private _scopedMiddlewares: ScopedEntry[]
327
+ private _contextValues: Record<string, unknown> | null
328
+ private _errorFormatter: ErrorFormatterFn
329
+ private _errorI18n: ErrorI18nConfig<TEnv> | null
330
+ private _globalMiddlewares: RuntimeMiddleware[]
331
+ private _graph: HoneyGraph
332
+ private _hasWsRoutes: boolean
333
+ private _logger: Logger | null
334
+ private _outputValidation: "always" | "dev" | "off"
335
+ private _staticRoutes: { map: Record<string, RouteHandler> | null }
336
+ private _stripPrefix: string | null
337
+ private _trailingSlash: "enforce" | "ignore" | "strip"
338
+ private _wsAdapter: WSAdapter | null
339
+ private _openApiCache: { epoch: number; value: Promise<unknown> } | null
340
+ private _openApiYamlCache: { epoch: number; value: Promise<string> } | null
341
+ private _manifestCache: { epoch: number; value: Promise<unknown> } | null
346
342
  private _onError:
347
343
  | ((
348
344
  error: unknown,
349
345
  ctx: {
350
- env: TEnv;
351
- jsonFromError: (err: HoneyError) => Response;
352
- req: Request;
346
+ env: TEnv
347
+ jsonFromError: (err: HoneyError) => Response
348
+ req: Request
353
349
  },
354
350
  ) => HoneyError | Response | Promise<HoneyError | Response | undefined | void> | undefined | void)
355
- | null;
351
+ | null
356
352
  private _onMethodNotAllowed:
357
353
  | ((ctx: {
358
- allowed: string[];
359
- env: TEnv;
360
- jsonFromError: (err: HoneyError) => Response;
361
- req: Request;
354
+ allowed: string[]
355
+ env: TEnv
356
+ jsonFromError: (err: HoneyError) => Response
357
+ req: Request
362
358
  }) => Response | Promise<Response>)
363
- | null;
359
+ | null
364
360
  private _onNotFound:
365
- | ((ctx: {
366
- env: TEnv;
367
- jsonFromError: (err: HoneyError) => Response;
368
- req: Request;
369
- }) => Response | Promise<Response>)
370
- | null;
371
- private _taps: Map<
361
+ | ((ctx: { env: TEnv; jsonFromError: (err: HoneyError) => Response; req: Request }) => Response | Promise<Response>)
362
+ | null
363
+ private _taps: Map<string, (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>> | null
364
+ private _telemetry: TelemetryAdapter | null
365
+ private _realtimeRoutes: Map<
372
366
  string,
373
- (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>
374
- > | null;
375
- private _telemetry: TelemetryAdapter | null;
376
- private _realtimeRoutes: Map<string, { handler: RealtimeRouteOpts["handler"]; middlewares?: RealtimeRouteOpts["use"]; reconnectBuffer?: number }>;
367
+ { handler: RealtimeRouteOpts["handler"]; middlewares?: RealtimeRouteOpts["use"]; reconnectBuffer?: number }
368
+ >
377
369
 
378
370
  constructor(opts?: {
379
- chainMiddlewares?: RuntimeMiddleware[];
380
- defaultErrorKeys?: Set<string>;
381
- globalMiddlewares?: RuntimeMiddleware[];
382
- graph?: HoneyGraph;
383
- handlerMap?: Record<string, RouteHandler> | null;
384
- root?: TreeNode;
385
- scopedMiddlewares?: ScopedEntry[];
371
+ chainMiddlewares?: RuntimeMiddleware[]
372
+ defaultErrorKeys?: Set<string>
373
+ globalMiddlewares?: RuntimeMiddleware[]
374
+ graph?: HoneyGraph
375
+ handlerMap?: Record<string, RouteHandler> | null
376
+ root?: TreeNode
377
+ scopedMiddlewares?: ScopedEntry[]
386
378
  }) {
387
- this._basePath = "/";
379
+ this._basePath = "/"
388
380
  this._graph = opts?.graph ?? {
389
381
  handlerMap: opts?.handlerMap ?? null,
390
382
  hasRouteTree: false,
383
+ metaSpec: null,
391
384
  realtimeBus: null,
392
385
  root: opts?.root ?? createNode(),
393
- };
394
- this._globalMiddlewares = opts?.globalMiddlewares ?? [];
395
- this._scopedMiddlewares = opts?.scopedMiddlewares ?? [];
396
- this._chainMeta = null;
397
- this._chainMiddlewares = opts?.chainMiddlewares ?? [];
398
- this._contextValues = null;
399
- this._defaultBoundaryKey = null;
400
- this._defaultErrorKeys = opts?.defaultErrorKeys ?? new Set();
401
- this._errorFactory = null;
402
- this._errorSchema = null;
403
- this._customErrorFormatter = null;
404
- this._customErrorSchema = null;
405
- this._errorFormatter = defaultErrorFormatter;
406
- this._errorI18n = null;
407
- this._hasWsRoutes = false;
408
- this._logger = null;
409
- this._outputValidation = "off";
410
- this._staticRoutes = { map: null };
411
- this._stripPrefix = null;
412
- this._trailingSlash = "ignore";
413
- this._wsAdapter = null;
414
- this._openApiCache = null;
415
- this._openApiYamlCache = null;
416
- this._manifestCache = null;
417
- this._onError = null;
418
- this._onNotFound = null;
419
- this._onMethodNotAllowed = null;
420
- this._taps = null;
421
- this._telemetry = null;
422
- this._realtimeRoutes = new Map();
386
+ }
387
+ this._globalMiddlewares = opts?.globalMiddlewares ?? []
388
+ this._scopedMiddlewares = opts?.scopedMiddlewares ?? []
389
+ this._chainMeta = null
390
+ this._chainMiddlewares = opts?.chainMiddlewares ?? []
391
+ this._contextValues = null
392
+ this._defaultBoundaryKey = null
393
+ this._defaultErrorKeys = opts?.defaultErrorKeys ?? new Set()
394
+ this._errorFactory = null
395
+ this._errorSchema = null
396
+ this._customErrorFormatter = null
397
+ this._customErrorSchema = null
398
+ this._errorFormatter = defaultErrorFormatter
399
+ this._errorI18n = null
400
+ this._hasWsRoutes = false
401
+ this._logger = null
402
+ this._outputValidation = "off"
403
+ this._staticRoutes = { map: null }
404
+ this._stripPrefix = null
405
+ this._trailingSlash = "ignore"
406
+ this._wsAdapter = null
407
+ this._openApiCache = null
408
+ this._openApiYamlCache = null
409
+ this._manifestCache = null
410
+ this._onError = null
411
+ this._onNotFound = null
412
+ this._onMethodNotAllowed = null
413
+ this._taps = null
414
+ this._telemetry = null
415
+ this._realtimeRoutes = new Map()
423
416
  }
424
417
 
425
418
  private get _handlerMap(): Record<string, RouteHandler> | null {
426
- return this._graph.handlerMap;
419
+ return this._graph.handlerMap
427
420
  }
428
421
  private set _handlerMap(value: Record<string, RouteHandler> | null) {
429
- this._graph.handlerMap = value;
422
+ this._graph.handlerMap = value
430
423
  }
431
424
 
432
425
  private get _hasRouteTree(): boolean {
433
- return this._graph.hasRouteTree;
426
+ return this._graph.hasRouteTree
434
427
  }
435
428
  private set _hasRouteTree(value: boolean) {
436
- this._graph.hasRouteTree = value;
429
+ this._graph.hasRouteTree = value
430
+ }
431
+
432
+ /** @internal — read by codegen */
433
+ private get _metaSpec(): MetaSpecConfig | null {
434
+ return this._graph.metaSpec
435
+ }
436
+ private set _metaSpec(value: MetaSpecConfig | null) {
437
+ this._graph.metaSpec = value
437
438
  }
438
439
 
439
440
  private get _root(): TreeNode {
440
- return this._graph.root;
441
+ return this._graph.root
441
442
  }
442
443
  private set _root(value: TreeNode) {
443
- this._graph.root = value;
444
+ this._graph.root = value
444
445
  }
445
446
 
446
447
  /** @internal — used by codegen */
447
448
  private get _tree(): TreeNode {
448
- return this._root;
449
+ return this._root
449
450
  }
450
451
 
451
452
  private get _realtimeBus(): RealtimeBus | null {
452
- return this._graph.realtimeBus;
453
+ return this._graph.realtimeBus
453
454
  }
454
455
  private set _realtimeBus(value: RealtimeBus | null) {
455
- this._graph.realtimeBus = value;
456
+ this._graph.realtimeBus = value
456
457
  }
457
458
 
458
459
  /** @internal — mark that this app has WS routes */
459
460
  _markWsRoutes(): void {
460
- this._hasWsRoutes = true;
461
+ this._hasWsRoutes = true
461
462
  }
462
463
 
463
464
  /** @internal — register a static route for O(1) lookup */
464
465
  _registerStatic(key: string, handler: RouteHandler): void {
465
466
  if (this._staticRoutes.map === null) {
466
- this._staticRoutes.map = Object.create(null) as Record<string, RouteHandler>;
467
+ this._staticRoutes.map = Object.create(null) as Record<string, RouteHandler>
467
468
  }
468
- this._staticRoutes.map[key] = handler;
469
+ this._staticRoutes.map[key] = handler
469
470
  }
470
471
 
471
472
  /** @internal — used by RouteBuilder for pre-filtered error factory */
472
473
  get _factory(): unknown {
473
- return this._errorFactory;
474
+ return this._errorFactory
474
475
  }
475
476
 
476
477
  /** @internal — used by runtime error boundary */
477
478
  get _boundaryKey(): string | null {
478
- return this._defaultBoundaryKey;
479
+ return this._defaultBoundaryKey
479
480
  }
480
481
 
481
482
  /** Convert unknown thrown value to error Response — used in WS, 404, 405 catch blocks */
@@ -487,78 +488,106 @@ export class Honey<
487
488
  cause: thrown,
488
489
  errorKey: EK.internal_server_error,
489
490
  status: SK.internal_server_error,
490
- });
491
- return createErrorResponse(error, this._errorFormatter, this._customErrorFormatter);
491
+ })
492
+ return createErrorResponse(error, this._errorFormatter, this._customErrorFormatter)
492
493
  }
493
494
 
494
495
  private _createBoundaryError(errorKey: string, cause: unknown): HoneyError {
495
496
  const factory = this._errorFactory as Record<
496
497
  string,
497
498
  ((opts?: { cause?: unknown }) => HoneyError) | undefined
498
- > | null;
499
- const factoryFn = factory?.[errorKey];
499
+ > | null
500
+ const factoryFn = factory?.[errorKey]
500
501
  if (factoryFn) {
501
502
  /* check if this is a custom schema error via ERROR_META — boundary must use standard errors only */
502
- const meta = (factory as Record<symbol, Record<string, { schema: unknown }>>)?.[ERROR_META];
503
+ const meta = (factory as Record<symbol, Record<string, { schema: unknown }>>)?.[ERROR_META]
503
504
  if (meta?.[errorKey]?.schema) {
504
505
  /* custom schema error — cannot be used as boundary, fall through to manual construction */
505
506
  } else {
506
- return factoryFn({ cause });
507
+ return factoryFn({ cause })
507
508
  }
508
509
  }
509
510
  return new HoneyError({
510
511
  cause,
511
512
  errorKey,
512
513
  status: SK.internal_server_error,
513
- });
514
+ })
514
515
  }
515
516
 
516
517
  private _createError(errorKey: string, statusKey: StatusKey): HoneyError {
517
- const factory = this._errorFactory as Record<
518
- string,
519
- (() => HoneyError) | undefined
520
- > | null;
521
- const factoryFn = factory?.[errorKey];
518
+ const factory = this._errorFactory as Record<string, (() => HoneyError) | undefined> | null
519
+ const factoryFn = factory?.[errorKey]
522
520
  if (factoryFn) {
523
- return factoryFn();
521
+ return factoryFn()
524
522
  }
525
- return new HoneyError({ errorKey, status: statusKey });
523
+ return new HoneyError({ errorKey, status: statusKey })
526
524
  }
527
525
 
528
526
  /** Apply error keys from a single scoped entry to every matching handler currently in the tree */
529
527
  private _applyScopedEntryErrors(entry: ScopedEntry): void {
530
- const errors = entry.errors;
531
- if (!errors || errors.length === 0) return;
528
+ const errors = entry.errors
529
+ if (!errors || errors.length === 0) return
532
530
  walkTreeHandlers(
533
531
  this._root,
534
532
  (h) => {
535
533
  if (scopeMatches(entry.prefix, h.rp)) {
536
- for (const k of errors) h.ek.add(k);
534
+ for (const k of errors) h.ek.add(k)
537
535
  }
538
536
  },
539
537
  (wh) => {
540
538
  if (scopeMatches(entry.prefix, wh.rp)) {
541
- for (const k of errors) wh.ek.add(k);
539
+ for (const k of errors) wh.ek.add(k)
542
540
  }
543
541
  },
544
- );
542
+ )
543
+ }
544
+
545
+ /**
546
+ * Meta contributed by every middleware that will run for `routePath`, in runtime order:
547
+ * global, chain, then scoped entries matching the path. Route-level `.use()` is folded in
548
+ * later, by the builder. Resolved here — at registration — because the precompiled route
549
+ * tree bakes `mt` as a literal, so a per-request derivation would diverge from it.
550
+ */
551
+ private _contributedMetaFor(routePath: string): Record<string, unknown> | null {
552
+ const scoped: RuntimeMiddleware[] = []
553
+ for (const entry of this._scopedMiddlewares) {
554
+ if (scopeMatches(entry.prefix, routePath)) scoped.push(entry.mw)
555
+ }
556
+ return collectMiddlewareMeta([this._globalMiddlewares, this._chainMiddlewares, scoped])
557
+ }
558
+
559
+ /**
560
+ * Back-fill meta from one scoped entry onto handlers already in the tree. Mirrors
561
+ * `_applyScopedEntryErrors` — `.use("/prefix", mw)` may be registered after the routes it
562
+ * covers, and a tag missing where enforcement happens is the worst failure direction.
563
+ * `mt` is frozen at registration, so this replaces the object rather than mutating it.
564
+ */
565
+ private _applyScopedEntryMeta(entry: ScopedEntry): void {
566
+ const meta = (entry.mw as { meta?: Record<string, unknown> }).meta
567
+ if (!meta) return
568
+ const apply = (h: { mt: Record<string, unknown> | null; rp: string }): void => {
569
+ if (!scopeMatches(entry.prefix, h.rp)) return
570
+ /* contributed meta never overwrites what the route or chain stated explicitly */
571
+ h.mt = Object.freeze(h.mt ? { ...meta, ...h.mt } : { ...meta })
572
+ }
573
+ walkTreeHandlers(this._root, apply, apply)
545
574
  }
546
575
 
547
576
  /** Apply error keys from every scoped entry on this chain to every matching handler in the tree */
548
577
  private _applyAllScopedErrors(): void {
549
578
  for (const entry of this._scopedMiddlewares) {
550
- this._applyScopedEntryErrors(entry);
579
+ this._applyScopedEntryErrors(entry)
551
580
  }
552
581
  }
553
582
 
554
583
  /** Return scoped middleware functions that match the given route path */
555
584
  private _filterScopedForPath(routePath: string): RuntimeMiddleware[] {
556
- if (this._scopedMiddlewares.length === 0) return EMPTY_MW;
557
- const out: RuntimeMiddleware[] = [];
585
+ if (this._scopedMiddlewares.length === 0) return EMPTY_MW
586
+ const out: RuntimeMiddleware[] = []
558
587
  for (const s of this._scopedMiddlewares) {
559
- if (scopeMatches(s.prefix, routePath)) out.push(s.mw);
588
+ if (scopeMatches(s.prefix, routePath)) out.push(s.mw)
560
589
  }
561
- return out;
590
+ return out
562
591
  }
563
592
 
564
593
  /** Mutates `honeyError.message` in-place with the i18n-resolved template for its errorKey.
@@ -570,7 +599,7 @@ export class Honey<
570
599
  request: Request,
571
600
  log?: Logger,
572
601
  ): Promise<void> {
573
- if (!this._errorI18n) return;
602
+ if (!this._errorI18n) return
574
603
  try {
575
604
  const locale = await this._errorI18n.resolveLocale({
576
605
  cookies: ctx.cookies,
@@ -579,39 +608,36 @@ export class Honey<
579
608
  params: ctx.params,
580
609
  req: request,
581
610
  search: ctx.search,
582
- });
583
- const translations = this._errorI18n.errors?.[locale];
611
+ })
612
+ const translations = this._errorI18n.errors?.[locale]
584
613
  if (translations) {
585
- const template = translations[honeyError.errorKey];
614
+ const template = translations[honeyError.errorKey]
586
615
  if (template) {
587
616
  await loadHoneyFeature("i18n")
588
- honeyError.message = getI18nRuntime().interpolate(
589
- template,
590
- honeyError.vars ?? {},
591
- );
617
+ honeyError.message = getI18nRuntime().interpolate(template, honeyError.vars ?? {})
592
618
  }
593
619
  }
594
620
 
595
- const fieldTranslations = this._errorI18n.fieldNames?.[locale];
621
+ const fieldTranslations = this._errorI18n.fieldNames?.[locale]
596
622
  if (fieldTranslations && Object.keys(honeyError.fields).length > 0) {
597
623
  for (const fieldErrors of Object.values(honeyError.fields)) {
598
624
  for (const fe of fieldErrors) {
599
- let candidate = fe.path;
625
+ let candidate = fe.path
600
626
  while (candidate) {
601
- const translated = fieldTranslations[candidate];
627
+ const translated = fieldTranslations[candidate]
602
628
  if (translated) {
603
- fe.path = translated;
604
- break;
629
+ fe.path = translated
630
+ break
605
631
  }
606
- const dotIdx = candidate.indexOf(".");
607
- if (dotIdx === -1) break;
608
- candidate = candidate.slice(dotIdx + 1);
632
+ const dotIdx = candidate.indexOf(".")
633
+ if (dotIdx === -1) break
634
+ candidate = candidate.slice(dotIdx + 1)
609
635
  }
610
636
  }
611
637
  }
612
638
  }
613
639
  } catch (e) {
614
- log?.warn?.("i18n resolution failed", e);
640
+ log?.warn?.("i18n resolution failed", e)
615
641
  }
616
642
  }
617
643
 
@@ -628,51 +654,51 @@ export class Honey<
628
654
  path: string,
629
655
  ctx: HoneyContext<TEnv>,
630
656
  ): Promise<Response> {
631
- const { env, log, request, startTime } = fc;
632
- let honeyError: HoneyError;
633
- const boundaryKey = handler.bek ?? this._defaultBoundaryKey;
657
+ const { env, log, request, startTime } = fc
658
+ let honeyError: HoneyError
659
+ const boundaryKey = handler.bek ?? this._defaultBoundaryKey
634
660
 
635
661
  if (thrown instanceof HoneyError) {
636
662
  /* framework-managed errorKeys (input/output validation, content negotiation, etc.) are always allowed
637
663
  * regardless of handler.ek — users never declare them, the framework owns them. */
638
- const isFrameworkEk = FRAMEWORK_EKS.has(thrown.errorKey as (typeof EK)[keyof typeof EK]);
664
+ const isFrameworkEk = FRAMEWORK_EKS.has(thrown.errorKey as (typeof EK)[keyof typeof EK])
639
665
  if (!isFrameworkEk && handler.ek.size > 0 && !handler.ek.has(thrown.errorKey)) {
640
666
  if (boundaryKey) {
641
- honeyError = this._createBoundaryError(boundaryKey, thrown);
667
+ honeyError = this._createBoundaryError(boundaryKey, thrown)
642
668
  } else {
643
669
  honeyError = new HoneyError({
644
670
  cause: thrown,
645
671
  errorKey: EK.internal_server_error,
646
672
  status: SK.internal_server_error,
647
- });
673
+ })
648
674
  }
649
675
  } else {
650
- honeyError = thrown;
676
+ honeyError = thrown
651
677
  }
652
678
  } else {
653
679
  if (boundaryKey) {
654
- honeyError = this._createBoundaryError(boundaryKey, thrown);
680
+ honeyError = this._createBoundaryError(boundaryKey, thrown)
655
681
  } else {
656
682
  honeyError = new HoneyError({
657
683
  cause: thrown,
658
684
  errorKey: EK.internal_server_error,
659
685
  status: SK.internal_server_error,
660
- });
686
+ })
661
687
  }
662
688
  }
663
689
 
664
- await this._resolveI18n(honeyError, ctx, env, request, log);
690
+ await this._resolveI18n(honeyError, ctx, env, request, log)
665
691
 
666
692
  /* onError handler */
667
693
  if (this._onError) {
668
694
  try {
669
- const customResult = await this._onError(thrown, this._makeErrorCtx(fc));
695
+ const customResult = await this._onError(thrown, this._makeErrorCtx(fc))
670
696
  if (customResult instanceof HoneyError) {
671
697
  /* user-mapped boundary error — re-run i18n against new errorKey,
672
698
  * then fall through to default response path so telemetry +
673
699
  * jsonFromError run exactly once. */
674
- honeyError = customResult;
675
- await this._resolveI18n(honeyError, ctx, env, request, log);
700
+ honeyError = customResult
701
+ await this._resolveI18n(honeyError, ctx, env, request, log)
676
702
  } else if (customResult) {
677
703
  safeFire(
678
704
  () =>
@@ -683,7 +709,7 @@ export class Honey<
683
709
  path,
684
710
  }),
685
711
  log,
686
- );
712
+ )
687
713
  safeFire(
688
714
  () =>
689
715
  this._telemetry?.onResponse?.({
@@ -692,9 +718,9 @@ export class Honey<
692
718
  status: customResult.status,
693
719
  }),
694
720
  log,
695
- );
696
- ctx._isErrorResponse = true;
697
- return customResult;
721
+ )
722
+ ctx._isErrorResponse = true
723
+ return customResult
698
724
  }
699
725
  /* customResult === undefined | void → fall through to default path */
700
726
  } catch {
@@ -712,8 +738,8 @@ export class Honey<
712
738
  path,
713
739
  }),
714
740
  log,
715
- );
716
- const res = this._makeErrorCtx(fc).jsonFromError(honeyError);
741
+ )
742
+ const res = this._makeErrorCtx(fc).jsonFromError(honeyError)
717
743
  safeFire(
718
744
  () =>
719
745
  this._telemetry?.onResponse?.({
@@ -722,24 +748,14 @@ export class Honey<
722
748
  status: res.status,
723
749
  }),
724
750
  log,
725
- );
726
- ctx._isErrorResponse = true;
727
- return res;
751
+ )
752
+ ctx._isErrorResponse = true
753
+ return res
728
754
  }
729
755
 
730
756
  basePath<P extends string>(
731
757
  prefix: P,
732
- ): Honey<
733
- TEnv,
734
- TCtx,
735
- TRoutes,
736
- TMeta,
737
- TErrorFactory,
738
- TDefaultErrors,
739
- MergePath<TBasePath, P>,
740
- TTaps,
741
- TScopedMw
742
- > {
758
+ ): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, MergePath<TBasePath, P>, TTaps, TScopedMw> {
743
759
  const next = new Honey<
744
760
  TEnv,
745
761
  TCtx,
@@ -756,32 +772,32 @@ export class Honey<
756
772
  globalMiddlewares: this._globalMiddlewares,
757
773
  graph: this._graph,
758
774
  scopedMiddlewares: this._scopedMiddlewares,
759
- });
760
- next._basePath = mergePath(this._basePath, prefix);
761
- next._defaultBoundaryKey = this._defaultBoundaryKey;
762
- next._errorFactory = this._errorFactory;
763
- next._errorSchema = this._errorSchema;
764
- next._customErrorFormatter = this._customErrorFormatter;
765
- next._customErrorSchema = this._customErrorSchema;
766
- next._errorFormatter = this._errorFormatter;
767
- next._errorI18n = this._errorI18n;
768
- next._logger = this._logger;
769
- next._outputValidation = this._outputValidation;
770
- next._stripPrefix = this._stripPrefix;
771
- next._trailingSlash = this._trailingSlash;
772
- next._wsAdapter = this._wsAdapter;
773
- next._onError = this._onError;
774
- next._onNotFound = this._onNotFound;
775
- next._onMethodNotAllowed = this._onMethodNotAllowed;
776
- next._chainMeta = this._chainMeta;
777
- next._contextValues = this._contextValues;
778
- next._taps = this._taps;
779
- next._telemetry = this._telemetry;
780
- next._realtimeBus = this._realtimeBus;
781
- next._realtimeRoutes = this._realtimeRoutes;
782
- next._staticRoutes = this._staticRoutes;
783
- next._hasRouteTree = this._hasRouteTree;
784
- next._hasWsRoutes = this._hasWsRoutes;
775
+ })
776
+ next._basePath = mergePath(this._basePath, prefix)
777
+ next._defaultBoundaryKey = this._defaultBoundaryKey
778
+ next._errorFactory = this._errorFactory
779
+ next._errorSchema = this._errorSchema
780
+ next._customErrorFormatter = this._customErrorFormatter
781
+ next._customErrorSchema = this._customErrorSchema
782
+ next._errorFormatter = this._errorFormatter
783
+ next._errorI18n = this._errorI18n
784
+ next._logger = this._logger
785
+ next._outputValidation = this._outputValidation
786
+ next._stripPrefix = this._stripPrefix
787
+ next._trailingSlash = this._trailingSlash
788
+ next._wsAdapter = this._wsAdapter
789
+ next._onError = this._onError
790
+ next._onNotFound = this._onNotFound
791
+ next._onMethodNotAllowed = this._onMethodNotAllowed
792
+ next._chainMeta = this._chainMeta
793
+ next._contextValues = this._contextValues
794
+ next._taps = this._taps
795
+ next._telemetry = this._telemetry
796
+ next._realtimeBus = this._realtimeBus
797
+ next._realtimeRoutes = this._realtimeRoutes
798
+ next._staticRoutes = this._staticRoutes
799
+ next._hasRouteTree = this._hasRouteTree
800
+ next._hasWsRoutes = this._hasWsRoutes
785
801
  return next as unknown as Honey<
786
802
  TEnv,
787
803
  TCtx,
@@ -792,22 +808,12 @@ export class Honey<
792
808
  MergePath<TBasePath, P>,
793
809
  TTaps,
794
810
  TScopedMw
795
- >;
811
+ >
796
812
  }
797
813
 
798
814
  context<TAdds extends Record<string, unknown>>(
799
815
  values: TAdds,
800
- ): Honey<
801
- TEnv,
802
- TCtx & Readonly<TAdds>,
803
- TRoutes,
804
- TMeta,
805
- TErrorFactory,
806
- TDefaultErrors,
807
- TBasePath,
808
- TTaps,
809
- TScopedMw
810
- > {
816
+ ): Honey<TEnv, TCtx & Readonly<TAdds>, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
811
817
  for (const key in values) {
812
818
  if (STATIC_CTX_RESERVED.has(key)) {
813
819
  throw new Error(`context() cannot set reserved key "${key}"`)
@@ -829,65 +835,73 @@ export class Honey<
829
835
  globalMiddlewares: this._globalMiddlewares,
830
836
  graph: this._graph,
831
837
  scopedMiddlewares: this._scopedMiddlewares,
832
- });
833
- next._basePath = this._basePath;
834
- next._chainMeta = this._chainMeta;
835
- next._contextValues = this._contextValues
836
- ? { ...this._contextValues, ...values }
837
- : { ...values };
838
- next._defaultBoundaryKey = this._defaultBoundaryKey;
839
- next._errorFactory = this._errorFactory;
840
- next._errorSchema = this._errorSchema;
841
- next._customErrorFormatter = this._customErrorFormatter;
842
- next._customErrorSchema = this._customErrorSchema;
843
- next._errorFormatter = this._errorFormatter;
844
- next._errorI18n = this._errorI18n;
845
- next._logger = this._logger;
846
- next._outputValidation = this._outputValidation;
847
- next._stripPrefix = this._stripPrefix;
848
- next._trailingSlash = this._trailingSlash;
849
- next._wsAdapter = this._wsAdapter;
850
- next._onError = this._onError;
851
- next._onNotFound = this._onNotFound;
852
- next._onMethodNotAllowed = this._onMethodNotAllowed;
853
- next._taps = this._taps;
854
- next._telemetry = this._telemetry;
855
- next._realtimeBus = this._realtimeBus;
856
- next._realtimeRoutes = this._realtimeRoutes;
857
- next._staticRoutes = this._staticRoutes;
858
- next._hasRouteTree = this._hasRouteTree;
859
- next._hasWsRoutes = this._hasWsRoutes;
860
- return next as unknown as Honey<TEnv, TCtx & Readonly<TAdds>, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>;
838
+ })
839
+ next._basePath = this._basePath
840
+ next._chainMeta = this._chainMeta
841
+ next._contextValues = this._contextValues ? { ...this._contextValues, ...values } : { ...values }
842
+ next._defaultBoundaryKey = this._defaultBoundaryKey
843
+ next._errorFactory = this._errorFactory
844
+ next._errorSchema = this._errorSchema
845
+ next._customErrorFormatter = this._customErrorFormatter
846
+ next._customErrorSchema = this._customErrorSchema
847
+ next._errorFormatter = this._errorFormatter
848
+ next._errorI18n = this._errorI18n
849
+ next._logger = this._logger
850
+ next._outputValidation = this._outputValidation
851
+ next._stripPrefix = this._stripPrefix
852
+ next._trailingSlash = this._trailingSlash
853
+ next._wsAdapter = this._wsAdapter
854
+ next._onError = this._onError
855
+ next._onNotFound = this._onNotFound
856
+ next._onMethodNotAllowed = this._onMethodNotAllowed
857
+ next._taps = this._taps
858
+ next._telemetry = this._telemetry
859
+ next._realtimeBus = this._realtimeBus
860
+ next._realtimeRoutes = this._realtimeRoutes
861
+ next._staticRoutes = this._staticRoutes
862
+ next._hasRouteTree = this._hasRouteTree
863
+ next._hasWsRoutes = this._hasWsRoutes
864
+ return next as unknown as Honey<
865
+ TEnv,
866
+ TCtx & Readonly<TAdds>,
867
+ TRoutes,
868
+ TMeta,
869
+ TErrorFactory,
870
+ TDefaultErrors,
871
+ TBasePath,
872
+ TTaps,
873
+ TScopedMw
874
+ >
861
875
  }
862
876
 
863
877
  logger(logger: Logger): this {
864
- this._logger = logger;
865
- return this;
878
+ this._logger = logger
879
+ return this
866
880
  }
867
881
 
868
882
  outputValidation(mode: "always" | "dev" | "off"): this {
869
- this._outputValidation = mode;
870
- return this;
883
+ this._outputValidation = mode
884
+ return this
871
885
  }
872
886
 
873
887
  trailingSlash(mode: "enforce" | "ignore" | "strip"): this {
874
- this._trailingSlash = mode;
875
- return this;
888
+ this._trailingSlash = mode
889
+ return this
876
890
  }
877
891
 
878
892
  /** Strip a URL path prefix before route matching — boundary-safe (won't strip partial segments), paths without the prefix pass through unchanged */
879
893
  stripPrefix(prefix: string): this {
880
- let normalized = prefix.replace(/\/+$/, "");
894
+ let normalized = prefix.replace(/\/+$/, "")
881
895
  if (normalized.length > 0 && normalized.charCodeAt(0) !== 47) {
882
- normalized = `/${normalized}`;
896
+ normalized = `/${normalized}`
883
897
  }
884
- this._stripPrefix = normalized === "" || normalized === "/" ? null : normalized;
885
- return this;
898
+ this._stripPrefix = normalized === "" || normalized === "/" ? null : normalized
899
+ return this
886
900
  }
887
901
 
888
902
  wsAdapter(adapter: WSAdapter): this {
889
- this._wsAdapter = adapter;
890
- return this;
903
+ this._wsAdapter = adapter
904
+ return this
891
905
  }
892
906
 
893
907
  openapi(options: {
@@ -896,6 +910,8 @@ export class Honey<
896
910
  docsPath?: string
897
911
  filterRoutes?: (route: { meta: unknown; method: string; path: string }) => boolean
898
912
  path?: string
913
+ /** Named metaSpec profile selecting which emitted keys this document carries */
914
+ profile?: string
899
915
  securitySchemes?: Record<string, unknown>
900
916
  title: string
901
917
  version: string
@@ -917,6 +933,7 @@ export class Honey<
917
933
  title: options.title,
918
934
  version: options.version,
919
935
  },
936
+ profile: options.profile,
920
937
  securitySchemes: options.securitySchemes,
921
938
  }),
922
939
  )
@@ -943,12 +960,8 @@ export class Honey<
943
960
  return pending
944
961
  }
945
962
  this._mountInternalGet(`${stem}.json`, async (ctx) => ctx.res.json("ok", await loadJson()))
946
- this._mountInternalGet(`${stem}.yml`, async (ctx) =>
947
- ctx.res.text("ok", await loadYaml(), yamlHeaders),
948
- )
949
- this._mountInternalGet(`${stem}.yaml`, async (ctx) =>
950
- ctx.res.text("ok", await loadYaml(), yamlHeaders),
951
- )
963
+ this._mountInternalGet(`${stem}.yml`, async (ctx) => ctx.res.text("ok", await loadYaml(), yamlHeaders))
964
+ this._mountInternalGet(`${stem}.yaml`, async (ctx) => ctx.res.text("ok", await loadYaml(), yamlHeaders))
952
965
  if (options.docs) {
953
966
  const specUrl = mergePath(this._basePath, `${stem}.json`)
954
967
  const docs = options.docs
@@ -966,9 +979,7 @@ export class Honey<
966
979
  }
967
980
  }
968
981
  if (!mounted) {
969
- throw new Error(
970
- `Honey.openapi({ docs }) cannot mount at ${preferred}: already registered. Pass docsPath.`,
971
- )
982
+ throw new Error(`Honey.openapi({ docs }) cannot mount at ${preferred}: already registered. Pass docsPath.`)
972
983
  }
973
984
  }
974
985
  return this
@@ -1009,10 +1020,7 @@ export class Honey<
1009
1020
  }
1010
1021
 
1011
1022
  /** Mount an internal GET. Returns false when a user route already owns the path. */
1012
- private _mountInternalGet(
1013
- path: string,
1014
- fn: (ctx: { res: HoneyRes }) => Response | Promise<Response>,
1015
- ): boolean {
1023
+ private _mountInternalGet(path: string, fn: (ctx: { res: HoneyRes }) => Response | Promise<Response>): boolean {
1016
1024
  const fullPath = mergePath(this._basePath, path)
1017
1025
  const existing = this._lookupGet(fullPath)
1018
1026
  if (existing) return existing._skip === true
@@ -1039,114 +1047,111 @@ export class Honey<
1039
1047
  defaultErrorFormatter<TSchema extends StandardSchemaLike>(
1040
1048
  schema: TSchema,
1041
1049
  fn: (error: HoneyError) => InferOutput<TSchema>,
1042
- ): this;
1043
- defaultErrorFormatter(fn: ErrorFormatterFn): this;
1050
+ ): this
1051
+ defaultErrorFormatter(fn: ErrorFormatterFn): this
1044
1052
  defaultErrorFormatter(
1045
1053
  schemaOrFn: ErrorFormatterFn | StandardSchemaLike,
1046
1054
  maybeFn?: (error: HoneyError) => unknown,
1047
1055
  ): this {
1048
1056
  if (typeof schemaOrFn === "function") {
1049
- this._errorSchema = null;
1050
- this._errorFormatter = schemaOrFn;
1057
+ this._errorSchema = null
1058
+ this._errorFormatter = schemaOrFn
1051
1059
  } else {
1052
- this._errorSchema = schemaOrFn;
1053
- const mapper = maybeFn as (error: HoneyError) => Record<string, unknown>;
1054
- this._errorFormatter = (error) => mapper(error);
1060
+ this._errorSchema = schemaOrFn
1061
+ const mapper = maybeFn as (error: HoneyError) => Record<string, unknown>
1062
+ this._errorFormatter = (error) => mapper(error)
1055
1063
  }
1056
- return this;
1064
+ return this
1057
1065
  }
1058
1066
 
1059
1067
  customErrorFormatter<TSchema extends StandardSchemaLike>(
1060
1068
  schema: TSchema,
1061
1069
  fn: (error: HoneyError, data: Record<string, unknown>) => InferOutput<TSchema>,
1062
- ): this;
1063
- customErrorFormatter(fn: CustomErrorFormatter): this;
1070
+ ): this
1071
+ customErrorFormatter(fn: CustomErrorFormatter): this
1064
1072
  customErrorFormatter(
1065
1073
  schemaOrFn: CustomErrorFormatter | StandardSchemaLike,
1066
1074
  maybeFn?: (error: HoneyError, data: Record<string, unknown>) => unknown,
1067
1075
  ): this {
1068
1076
  if (typeof schemaOrFn === "function") {
1069
- this._customErrorSchema = null;
1070
- this._customErrorFormatter = schemaOrFn;
1077
+ this._customErrorSchema = null
1078
+ this._customErrorFormatter = schemaOrFn
1071
1079
  } else {
1072
- this._customErrorSchema = schemaOrFn;
1073
- const mapper = maybeFn as (error: HoneyError, data: Record<string, unknown>) => Record<string, unknown>;
1074
- this._customErrorFormatter = (error, data) => mapper(error, data);
1080
+ this._customErrorSchema = schemaOrFn
1081
+ const mapper = maybeFn as (error: HoneyError, data: Record<string, unknown>) => Record<string, unknown>
1082
+ this._customErrorFormatter = (error, data) => mapper(error, data)
1075
1083
  }
1076
- return this;
1084
+ return this
1077
1085
  }
1078
1086
 
1079
1087
  errorI18n(config: ErrorI18nConfig<TEnv>): this {
1080
- this._errorI18n = config;
1081
- return this;
1088
+ this._errorI18n = config
1089
+ return this
1082
1090
  }
1083
1091
 
1084
1092
  onError(
1085
1093
  handler: (
1086
1094
  error: unknown,
1087
1095
  ctx: {
1088
- env: TEnv;
1089
- jsonFromError: (err: HoneyError) => Response;
1090
- req: Request;
1096
+ env: TEnv
1097
+ jsonFromError: (err: HoneyError) => Response
1098
+ req: Request
1091
1099
  },
1092
1100
  ) => HoneyError | Response | Promise<HoneyError | Response | undefined | void> | undefined | void,
1093
1101
  ): this {
1094
- this._onError = handler;
1095
- return this;
1102
+ this._onError = handler
1103
+ return this
1096
1104
  }
1097
1105
 
1098
1106
  onMethodNotAllowed(
1099
1107
  handler: (ctx: {
1100
- allowed: string[];
1101
- env: TEnv;
1102
- jsonFromError: (err: HoneyError) => Response;
1103
- req: Request;
1108
+ allowed: string[]
1109
+ env: TEnv
1110
+ jsonFromError: (err: HoneyError) => Response
1111
+ req: Request
1104
1112
  }) => Response | Promise<Response>,
1105
1113
  ): this {
1106
- this._onMethodNotAllowed = handler;
1107
- return this;
1114
+ this._onMethodNotAllowed = handler
1115
+ return this
1108
1116
  }
1109
1117
 
1110
1118
  onNotFound(
1111
1119
  handler: (ctx: {
1112
- env: TEnv;
1113
- jsonFromError: (err: HoneyError) => Response;
1114
- req: Request;
1120
+ env: TEnv
1121
+ jsonFromError: (err: HoneyError) => Response
1122
+ req: Request
1115
1123
  }) => Response | Promise<Response>,
1116
1124
  ): this {
1117
- this._onNotFound = handler;
1118
- return this;
1125
+ this._onNotFound = handler
1126
+ return this
1119
1127
  }
1120
1128
 
1121
1129
  /** Register a tap handler keyed by name — fires after successful handler response */
1122
1130
  tap<K extends string>(
1123
1131
  key: K,
1124
- handler: (
1125
- ctx: TapContext<TEnv>,
1126
- payload: K extends keyof TTaps ? TTaps[K] : unknown,
1127
- ) => void | Promise<void>,
1132
+ handler: (ctx: TapContext<TEnv>, payload: K extends keyof TTaps ? TTaps[K] : unknown) => void | Promise<void>,
1128
1133
  ): this {
1129
1134
  if (this._taps === null) {
1130
- this._taps = new Map();
1135
+ this._taps = new Map()
1131
1136
  }
1132
- this._taps.set(key, handler as (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>);
1133
- return this;
1137
+ this._taps.set(key, handler as (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>)
1138
+ return this
1134
1139
  }
1135
1140
 
1136
1141
  telemetry(adapter: TelemetryAdapter): this {
1137
- this._telemetry = adapter;
1138
- return this;
1142
+ this._telemetry = adapter
1143
+ return this
1139
1144
  }
1140
1145
 
1141
1146
  routeTree(tree: RouteTree): this {
1142
- this._root = tree.root;
1143
- this._handlerMap = tree.handlers ?? null;
1144
- this._hasRouteTree = true;
1145
- return this;
1147
+ this._root = tree.root
1148
+ this._handlerMap = tree.handlers ?? null
1149
+ this._hasRouteTree = true
1150
+ return this
1146
1151
  }
1147
1152
 
1148
1153
  toRouteTree(): RouteTree {
1149
- return { meta: {}, root: this._root };
1154
+ return { meta: {}, root: this._root }
1150
1155
  }
1151
1156
 
1152
1157
  errorFactory<TFactory extends Record<string, (...args: never[]) => unknown>>(
@@ -1162,28 +1167,14 @@ export class Honey<
1162
1167
  TBasePath,
1163
1168
  TTaps,
1164
1169
  TScopedMw
1165
- >;
1166
- next._errorFactory = factory;
1167
- return next;
1170
+ >
1171
+ next._errorFactory = factory
1172
+ return next
1168
1173
  }
1169
1174
 
1170
- defaultErrors<
1171
- TKeys extends [TErrorFactory] extends [never]
1172
- ? never
1173
- : keyof TErrorFactory & string,
1174
- >(
1175
+ defaultErrors<TKeys extends ([TErrorFactory] extends [never] ? never : keyof TErrorFactory & string)>(
1175
1176
  ...keys: TKeys[]
1176
- ): Honey<
1177
- TEnv,
1178
- TCtx,
1179
- TRoutes,
1180
- TMeta,
1181
- TErrorFactory,
1182
- TDefaultErrors | TKeys,
1183
- TBasePath,
1184
- TTaps,
1185
- TScopedMw
1186
- > {
1177
+ ): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors | TKeys, TBasePath, TTaps, TScopedMw> {
1187
1178
  const next = this as unknown as Honey<
1188
1179
  TEnv,
1189
1180
  TCtx,
@@ -1194,28 +1185,16 @@ export class Honey<
1194
1185
  TBasePath,
1195
1186
  TTaps,
1196
1187
  TScopedMw
1197
- >;
1188
+ >
1198
1189
  for (const k of keys) {
1199
- next._defaultErrorKeys.add(k);
1190
+ next._defaultErrorKeys.add(k)
1200
1191
  }
1201
- return next;
1192
+ return next
1202
1193
  }
1203
1194
 
1204
- defaultBoundary<
1205
- TKey extends [TErrorFactory] extends [never]
1206
- ? never
1207
- : keyof TErrorFactory & string,
1208
- >(key: TKey): Honey<
1209
- TEnv,
1210
- TCtx,
1211
- TRoutes,
1212
- TMeta,
1213
- TErrorFactory,
1214
- TDefaultErrors | TKey,
1215
- TBasePath,
1216
- TTaps,
1217
- TScopedMw
1218
- > {
1195
+ defaultBoundary<TKey extends ([TErrorFactory] extends [never] ? never : keyof TErrorFactory & string)>(
1196
+ key: TKey,
1197
+ ): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors | TKey, TBasePath, TTaps, TScopedMw> {
1219
1198
  const next = this as unknown as Honey<
1220
1199
  TEnv,
1221
1200
  TCtx,
@@ -1226,10 +1205,10 @@ export class Honey<
1226
1205
  TBasePath,
1227
1206
  TTaps,
1228
1207
  TScopedMw
1229
- >;
1230
- next._defaultBoundaryKey = key;
1231
- next._defaultErrorKeys.add(key);
1232
- return next;
1208
+ >
1209
+ next._defaultBoundaryKey = key
1210
+ next._defaultErrorKeys.add(key)
1211
+ return next
1233
1212
  }
1234
1213
 
1235
1214
  /** Phantom overload — constrains what route-level .meta() accepts */
@@ -1243,102 +1222,84 @@ export class Honey<
1243
1222
  TBasePath,
1244
1223
  TTaps,
1245
1224
  TScopedMw
1246
- >;
1225
+ >
1247
1226
  /** Typed chain-level default meta — constrains route meta and sets runtime defaults in one call */
1248
- meta<
1249
- TNewMeta extends Record<string, unknown>,
1250
- TValues extends Partial<DefaultMeta> & Partial<TNewMeta>,
1251
- >(
1227
+ meta<TNewMeta extends Record<string, unknown>, TValues extends Partial<DefaultMeta> & Partial<TNewMeta>>(
1252
1228
  values: TValues,
1253
- ): Honey<
1254
- TEnv,
1255
- TCtx,
1256
- TRoutes,
1257
- TNewMeta,
1258
- TErrorFactory,
1259
- TDefaultErrors,
1260
- TBasePath,
1261
- TTaps,
1262
- TScopedMw
1263
- >;
1229
+ ): Honey<TEnv, TCtx, TRoutes, TNewMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1264
1230
  /** Chain-level default meta — merged into every route registered on this chain */
1265
- meta<
1266
- TValues extends Partial<DefaultMeta> &
1267
- ([TMeta] extends [never] ? {} : Partial<TMeta>),
1268
- >(
1231
+ meta<TValues extends Partial<DefaultMeta> & ([TMeta] extends [never] ? {} : Partial<TMeta>)>(
1269
1232
  values: TValues,
1270
- ): Honey<
1271
- TEnv,
1272
- TCtx,
1273
- TRoutes,
1274
- TMeta,
1275
- TErrorFactory,
1276
- TDefaultErrors,
1277
- TBasePath,
1278
- TTaps,
1279
- TScopedMw
1280
- >;
1281
- meta(values?: Record<string, unknown>): Honey<
1282
- TEnv,
1283
- TCtx,
1284
- TRoutes,
1285
- unknown,
1286
- TErrorFactory,
1287
- TDefaultErrors,
1288
- TBasePath,
1289
- TTaps,
1290
- TScopedMw
1291
- > {
1233
+ ): Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1234
+ meta(
1235
+ values?: Record<string, unknown>,
1236
+ ): Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
1292
1237
  if (values === undefined) {
1293
1238
  /* phantom overload — type-only, no runtime effect */
1294
- return this as Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>;
1239
+ return this as Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1295
1240
  }
1296
1241
  /* chain-level meta — copy-on-write */
1297
- const next = new Honey<
1298
- TEnv,
1299
- TCtx,
1300
- TRoutes,
1301
- unknown,
1302
- TErrorFactory,
1303
- TDefaultErrors,
1304
- TBasePath,
1305
- TTaps,
1306
- TScopedMw
1307
- >({
1242
+ const next = new Honey<TEnv, TCtx, TRoutes, unknown, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>({
1308
1243
  chainMiddlewares: this._chainMiddlewares,
1309
1244
  defaultErrorKeys: this._defaultErrorKeys,
1310
1245
  globalMiddlewares: this._globalMiddlewares,
1311
1246
  graph: this._graph,
1312
1247
  scopedMiddlewares: this._scopedMiddlewares,
1313
- });
1314
- next._basePath = this._basePath;
1315
- next._chainMeta = this._chainMeta
1316
- ? { ...this._chainMeta, ...values }
1317
- : { ...values };
1318
- next._contextValues = this._contextValues;
1319
- next._defaultBoundaryKey = this._defaultBoundaryKey;
1320
- next._errorFactory = this._errorFactory;
1321
- next._errorSchema = this._errorSchema;
1322
- next._customErrorFormatter = this._customErrorFormatter;
1323
- next._customErrorSchema = this._customErrorSchema;
1324
- next._errorFormatter = this._errorFormatter;
1325
- next._errorI18n = this._errorI18n;
1326
- next._logger = this._logger;
1327
- next._outputValidation = this._outputValidation;
1328
- next._stripPrefix = this._stripPrefix;
1329
- next._trailingSlash = this._trailingSlash;
1330
- next._wsAdapter = this._wsAdapter;
1331
- next._onError = this._onError;
1332
- next._onNotFound = this._onNotFound;
1333
- next._onMethodNotAllowed = this._onMethodNotAllowed;
1334
- next._taps = this._taps;
1335
- next._telemetry = this._telemetry;
1336
- next._realtimeBus = this._realtimeBus;
1337
- next._realtimeRoutes = this._realtimeRoutes;
1338
- next._staticRoutes = this._staticRoutes;
1339
- next._hasRouteTree = this._hasRouteTree;
1340
- next._hasWsRoutes = this._hasWsRoutes;
1341
- return next;
1248
+ })
1249
+ next._basePath = this._basePath
1250
+ next._chainMeta = this._chainMeta ? { ...this._chainMeta, ...values } : { ...values }
1251
+ next._contextValues = this._contextValues
1252
+ next._defaultBoundaryKey = this._defaultBoundaryKey
1253
+ next._errorFactory = this._errorFactory
1254
+ next._errorSchema = this._errorSchema
1255
+ next._customErrorFormatter = this._customErrorFormatter
1256
+ next._customErrorSchema = this._customErrorSchema
1257
+ next._errorFormatter = this._errorFormatter
1258
+ next._errorI18n = this._errorI18n
1259
+ next._logger = this._logger
1260
+ next._outputValidation = this._outputValidation
1261
+ next._stripPrefix = this._stripPrefix
1262
+ next._trailingSlash = this._trailingSlash
1263
+ next._wsAdapter = this._wsAdapter
1264
+ next._onError = this._onError
1265
+ next._onNotFound = this._onNotFound
1266
+ next._onMethodNotAllowed = this._onMethodNotAllowed
1267
+ next._taps = this._taps
1268
+ next._telemetry = this._telemetry
1269
+ next._realtimeBus = this._realtimeBus
1270
+ next._realtimeRoutes = this._realtimeRoutes
1271
+ next._staticRoutes = this._staticRoutes
1272
+ next._hasRouteTree = this._hasRouteTree
1273
+ next._hasWsRoutes = this._hasWsRoutes
1274
+ return next
1275
+ }
1276
+
1277
+ /**
1278
+ * Declare what flows from route meta and route schemas into the OpenAPI document.
1279
+ * Codegen-time only — never consulted on the request path. See docs/meta-spec.md.
1280
+ */
1281
+ metaSpec<TSpec extends HoneyMetaSpec<TMeta>>(spec: TSpec): this {
1282
+ if (this._metaSpec !== null) {
1283
+ throw new Error("metaSpec() was already declared on this app — declare the whole policy in one call")
1284
+ }
1285
+ this._metaSpec = spec as MetaSpecConfig
1286
+ return this
1287
+ }
1288
+
1289
+ /** Mounted sub-apps fill policy gaps; the parent's entries win on conflict. */
1290
+ private _absorbMetaSpec(sub: MetaSpecConfig | null): void {
1291
+ if (!sub) return
1292
+ const own = this._metaSpec
1293
+ if (!own) {
1294
+ this._metaSpec = sub
1295
+ return
1296
+ }
1297
+ this._metaSpec = {
1298
+ meta: { ...sub.meta, ...own.meta },
1299
+ profiles: { ...sub.profiles, ...own.profiles },
1300
+ schema: { ...sub.schema, ...own.schema },
1301
+ strict: own.strict ?? sub.strict,
1302
+ }
1342
1303
  }
1343
1304
 
1344
1305
  /** Declare tap payload types — auto-extends meta with Partial<T> for meta-driven taps */
@@ -1363,12 +1324,12 @@ export class Honey<
1363
1324
  TBasePath,
1364
1325
  TNewTaps,
1365
1326
  TScopedMw
1366
- >;
1327
+ >
1367
1328
  }
1368
1329
 
1369
1330
  use<TAdds>(
1370
1331
  mw: MiddlewareFn<TCtx, TAdds>,
1371
- ): Honey<TEnv, TCtx & TAdds, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>;
1332
+ ): Honey<TEnv, TCtx & TAdds, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1372
1333
 
1373
1334
  use<const TPath extends string, TAdds>(
1374
1335
  path: TPath,
@@ -1383,153 +1344,132 @@ export class Honey<
1383
1344
  TBasePath,
1384
1345
  TTaps,
1385
1346
  readonly [...TScopedMw, { readonly path: MergePath<TBasePath, TPath>; readonly adds: TAdds }]
1386
- >;
1347
+ >
1387
1348
 
1388
1349
  /* oxlint-disable-next-line typescript/no-explicit-any -- overload impl requires erased types */
1389
1350
  use(pathOrMw: string | MiddlewareFn<any, any>, maybeMw?: MiddlewareFn<any, any>): any {
1390
1351
  if (typeof pathOrMw !== "string") {
1391
- const mw = pathOrMw as RuntimeMiddleware;
1352
+ const mw = pathOrMw as RuntimeMiddleware
1392
1353
  const newChain = new Honey<TEnv>({
1393
1354
  chainMiddlewares: [...this._chainMiddlewares, mw],
1394
1355
  defaultErrorKeys: this._defaultErrorKeys,
1395
1356
  globalMiddlewares: this._globalMiddlewares,
1396
1357
  graph: this._graph,
1397
1358
  scopedMiddlewares: this._scopedMiddlewares,
1398
- });
1399
- newChain._basePath = this._basePath;
1400
- newChain._chainMeta = this._chainMeta;
1401
- newChain._contextValues = this._contextValues;
1402
- newChain._defaultBoundaryKey = this._defaultBoundaryKey;
1403
- newChain._errorFactory = this._errorFactory;
1404
- newChain._errorSchema = this._errorSchema;
1405
- newChain._customErrorFormatter = this._customErrorFormatter;
1406
- newChain._customErrorSchema = this._customErrorSchema;
1407
- newChain._errorFormatter = this._errorFormatter;
1408
- newChain._errorI18n = this._errorI18n;
1409
- newChain._logger = this._logger;
1410
- newChain._outputValidation = this._outputValidation;
1411
- newChain._stripPrefix = this._stripPrefix;
1412
- newChain._trailingSlash = this._trailingSlash;
1413
- newChain._wsAdapter = this._wsAdapter;
1414
- newChain._onError = this._onError;
1415
- newChain._onNotFound = this._onNotFound;
1416
- newChain._onMethodNotAllowed = this._onMethodNotAllowed;
1417
- newChain._taps = this._taps;
1418
- newChain._telemetry = this._telemetry;
1419
- newChain._realtimeBus = this._realtimeBus;
1420
- newChain._realtimeRoutes = this._realtimeRoutes;
1421
- newChain._staticRoutes = this._staticRoutes;
1422
- newChain._hasRouteTree = this._hasRouteTree;
1423
- newChain._hasWsRoutes = this._hasWsRoutes;
1424
- return newChain;
1359
+ })
1360
+ newChain._basePath = this._basePath
1361
+ newChain._chainMeta = this._chainMeta
1362
+ newChain._contextValues = this._contextValues
1363
+ newChain._defaultBoundaryKey = this._defaultBoundaryKey
1364
+ newChain._errorFactory = this._errorFactory
1365
+ newChain._errorSchema = this._errorSchema
1366
+ newChain._customErrorFormatter = this._customErrorFormatter
1367
+ newChain._customErrorSchema = this._customErrorSchema
1368
+ newChain._errorFormatter = this._errorFormatter
1369
+ newChain._errorI18n = this._errorI18n
1370
+ newChain._logger = this._logger
1371
+ newChain._outputValidation = this._outputValidation
1372
+ newChain._stripPrefix = this._stripPrefix
1373
+ newChain._trailingSlash = this._trailingSlash
1374
+ newChain._wsAdapter = this._wsAdapter
1375
+ newChain._onError = this._onError
1376
+ newChain._onNotFound = this._onNotFound
1377
+ newChain._onMethodNotAllowed = this._onMethodNotAllowed
1378
+ newChain._taps = this._taps
1379
+ newChain._telemetry = this._telemetry
1380
+ newChain._realtimeBus = this._realtimeBus
1381
+ newChain._realtimeRoutes = this._realtimeRoutes
1382
+ newChain._staticRoutes = this._staticRoutes
1383
+ newChain._hasRouteTree = this._hasRouteTree
1384
+ newChain._hasWsRoutes = this._hasWsRoutes
1385
+ return newChain
1425
1386
  }
1426
1387
 
1427
1388
  /* scoped path */
1428
- const normalizedPrefix = normalizeScopePath(pathOrMw);
1429
- const fullPrefix = mergePath(this._basePath, normalizedPrefix);
1430
- const mw = maybeMw as RuntimeMiddleware;
1431
- const mwWithErrors = maybeMw as MiddlewareFn<unknown, unknown>;
1389
+ const normalizedPrefix = normalizeScopePath(pathOrMw)
1390
+ const fullPrefix = mergePath(this._basePath, normalizedPrefix)
1391
+ const mw = maybeMw as RuntimeMiddleware
1392
+ const mwWithErrors = maybeMw as MiddlewareFn<unknown, unknown>
1432
1393
  const entry: ScopedEntry = {
1433
1394
  errors: mwWithErrors.errors ? [...mwWithErrors.errors] : undefined,
1434
1395
  mw,
1435
1396
  prefix: fullPrefix,
1436
- };
1437
- this._scopedMiddlewares.push(entry);
1397
+ }
1398
+ this._scopedMiddlewares.push(entry)
1438
1399
  const newChain = new Honey<TEnv>({
1439
1400
  chainMiddlewares: this._chainMiddlewares,
1440
1401
  defaultErrorKeys: this._defaultErrorKeys,
1441
1402
  globalMiddlewares: this._globalMiddlewares,
1442
1403
  graph: this._graph,
1443
1404
  scopedMiddlewares: this._scopedMiddlewares,
1444
- });
1445
- newChain._basePath = this._basePath;
1446
- newChain._chainMeta = this._chainMeta;
1447
- newChain._contextValues = this._contextValues;
1448
- newChain._defaultBoundaryKey = this._defaultBoundaryKey;
1449
- newChain._errorFactory = this._errorFactory;
1450
- newChain._errorSchema = this._errorSchema;
1451
- newChain._customErrorFormatter = this._customErrorFormatter;
1452
- newChain._customErrorSchema = this._customErrorSchema;
1453
- newChain._errorFormatter = this._errorFormatter;
1454
- newChain._errorI18n = this._errorI18n;
1455
- newChain._logger = this._logger;
1456
- newChain._outputValidation = this._outputValidation;
1457
- newChain._stripPrefix = this._stripPrefix;
1458
- newChain._trailingSlash = this._trailingSlash;
1459
- newChain._wsAdapter = this._wsAdapter;
1460
- newChain._onError = this._onError;
1461
- newChain._onNotFound = this._onNotFound;
1462
- newChain._onMethodNotAllowed = this._onMethodNotAllowed;
1463
- newChain._taps = this._taps;
1464
- newChain._telemetry = this._telemetry;
1465
- newChain._realtimeBus = this._realtimeBus;
1466
- newChain._realtimeRoutes = this._realtimeRoutes;
1467
- newChain._staticRoutes = this._staticRoutes;
1468
- newChain._hasRouteTree = this._hasRouteTree;
1469
- newChain._hasWsRoutes = this._hasWsRoutes;
1470
- newChain._applyScopedEntryErrors(entry);
1471
- return newChain;
1472
- }
1473
-
1474
- route<
1475
- TSubRoutes,
1476
- TSubMeta,
1477
- TSubErrorFactory,
1478
- TSubDefaultErrors extends string,
1479
- TSubBasePath extends string,
1480
- >(
1481
- sub: Honey<
1482
- TEnv,
1483
- TCtx,
1484
- TSubRoutes,
1485
- TSubMeta,
1486
- TSubErrorFactory,
1487
- TSubDefaultErrors,
1488
- TSubBasePath
1489
- >,
1490
- ): Honey<
1491
- TEnv,
1492
- TCtx,
1493
- TRoutes & TSubRoutes,
1494
- TMeta,
1495
- TErrorFactory,
1496
- TDefaultErrors,
1497
- TBasePath,
1498
- TTaps,
1499
- TScopedMw
1500
- > {
1405
+ })
1406
+ newChain._basePath = this._basePath
1407
+ newChain._chainMeta = this._chainMeta
1408
+ newChain._contextValues = this._contextValues
1409
+ newChain._defaultBoundaryKey = this._defaultBoundaryKey
1410
+ newChain._errorFactory = this._errorFactory
1411
+ newChain._errorSchema = this._errorSchema
1412
+ newChain._customErrorFormatter = this._customErrorFormatter
1413
+ newChain._customErrorSchema = this._customErrorSchema
1414
+ newChain._errorFormatter = this._errorFormatter
1415
+ newChain._errorI18n = this._errorI18n
1416
+ newChain._logger = this._logger
1417
+ newChain._outputValidation = this._outputValidation
1418
+ newChain._stripPrefix = this._stripPrefix
1419
+ newChain._trailingSlash = this._trailingSlash
1420
+ newChain._wsAdapter = this._wsAdapter
1421
+ newChain._onError = this._onError
1422
+ newChain._onNotFound = this._onNotFound
1423
+ newChain._onMethodNotAllowed = this._onMethodNotAllowed
1424
+ newChain._taps = this._taps
1425
+ newChain._telemetry = this._telemetry
1426
+ newChain._realtimeBus = this._realtimeBus
1427
+ newChain._realtimeRoutes = this._realtimeRoutes
1428
+ newChain._staticRoutes = this._staticRoutes
1429
+ newChain._hasRouteTree = this._hasRouteTree
1430
+ newChain._hasWsRoutes = this._hasWsRoutes
1431
+ newChain._applyScopedEntryErrors(entry)
1432
+ newChain._applyScopedEntryMeta(entry)
1433
+ return newChain
1434
+ }
1435
+
1436
+ route<TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors extends string, TSubBasePath extends string>(
1437
+ sub: Honey<TEnv, TCtx, TSubRoutes, TSubMeta, TSubErrorFactory, TSubDefaultErrors, TSubBasePath>,
1438
+ ): Honey<TEnv, TCtx, TRoutes & TSubRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw> {
1501
1439
  /* skip self-merge: .handler() already inserted into shared _root */
1502
1440
  if (sub._tree !== this._root) {
1503
- mergeInto(this._root, sub._tree);
1504
- if (sub._hasWsRoutes) this._hasWsRoutes = true;
1505
- if (sub._hasRouteTree) this._hasRouteTree = true;
1441
+ mergeInto(this._root, sub._tree)
1442
+ this._absorbMetaSpec(sub._metaSpec)
1443
+ if (sub._hasWsRoutes) this._hasWsRoutes = true
1444
+ if (sub._hasRouteTree) this._hasRouteTree = true
1506
1445
  /* carry sub's scoped mw entries into parent's runtime list (parent scopes run first) */
1507
1446
  for (const entry of sub._scopedMiddlewares) {
1508
- this._scopedMiddlewares.push(entry);
1447
+ this._scopedMiddlewares.push(entry)
1509
1448
  }
1510
1449
  if (sub._staticRoutes.map !== null) {
1511
1450
  if (this._staticRoutes.map === null) {
1512
- this._staticRoutes.map = Object.create(null) as Record<string, RouteHandler>;
1451
+ this._staticRoutes.map = Object.create(null) as Record<string, RouteHandler>
1513
1452
  }
1514
- Object.assign(this._staticRoutes.map, sub._staticRoutes.map);
1453
+ Object.assign(this._staticRoutes.map, sub._staticRoutes.map)
1515
1454
  }
1516
1455
  for (const [path, cfg] of sub._realtimeRoutes) {
1517
1456
  if (this._realtimeRoutes.has(path)) {
1518
- throw new Error(`Duplicate realtime route: ${path}`);
1457
+ throw new Error(`Duplicate realtime route: ${path}`)
1519
1458
  }
1520
- this._realtimeRoutes.set(path, cfg);
1459
+ this._realtimeRoutes.set(path, cfg)
1521
1460
  }
1522
1461
  if (!this._realtimeBus && sub._realtimeBus) {
1523
- this._realtimeBus = sub._realtimeBus;
1462
+ this._realtimeBus = sub._realtimeBus
1524
1463
  }
1525
1464
  if (sub._taps !== null) {
1526
- if (this._taps === null) this._taps = new Map();
1465
+ if (this._taps === null) this._taps = new Map()
1527
1466
  for (const [key, fn] of sub._taps) {
1528
- if (!this._taps.has(key)) this._taps.set(key, fn);
1467
+ if (!this._taps.has(key)) this._taps.set(key, fn)
1529
1468
  }
1530
1469
  }
1531
- /* walk tree and apply all scoped error keys to matching handlers */
1532
- this._applyAllScopedErrors();
1470
+ /* walk tree and apply all scoped error keys + contributed meta to matching handlers */
1471
+ this._applyAllScopedErrors()
1472
+ for (const entry of this._scopedMiddlewares) this._applyScopedEntryMeta(entry)
1533
1473
  }
1534
1474
  return this as unknown as Honey<
1535
1475
  TEnv,
@@ -1541,13 +1481,10 @@ export class Honey<
1541
1481
  TBasePath,
1542
1482
  TTaps,
1543
1483
  TScopedMw
1544
- >;
1484
+ >
1545
1485
  }
1546
1486
 
1547
- private _registerRoute<
1548
- TPath extends string,
1549
- TMethod extends HttpMethod | "ALL",
1550
- >(
1487
+ private _registerRoute<TPath extends string, TMethod extends HttpMethod | "ALL">(
1551
1488
  method: TMethod,
1552
1489
  path: TPath,
1553
1490
  extraMethods?: (HttpMethod | "ALL")[],
@@ -1570,11 +1507,11 @@ export class Honey<
1570
1507
  TTaps,
1571
1508
  TScopedMw
1572
1509
  > {
1573
- const fullPath = mergePath(this._basePath, path);
1574
- const errorKeys = new Set(this._defaultErrorKeys);
1510
+ const fullPath = mergePath(this._basePath, path)
1511
+ const errorKeys = new Set(this._defaultErrorKeys)
1575
1512
  for (const entry of this._scopedMiddlewares) {
1576
1513
  if (entry.errors && scopeMatches(entry.prefix, fullPath)) {
1577
- for (const k of entry.errors) errorKeys.add(k);
1514
+ for (const k of entry.errors) errorKeys.add(k)
1578
1515
  }
1579
1516
  }
1580
1517
  return new RouteBuilder<
@@ -1604,6 +1541,7 @@ export class Honey<
1604
1541
  meta: this._chainMeta ? { ...this._chainMeta } : null,
1605
1542
  method,
1606
1543
  middlewares: [],
1544
+ mwMeta: this._contributedMetaFor(fullPath),
1607
1545
  outputSchemas: null,
1608
1546
  parent: this,
1609
1547
  parentMiddlewares: this._chainMiddlewares,
@@ -1627,15 +1565,12 @@ export class Honey<
1627
1565
  TBasePath,
1628
1566
  TTaps,
1629
1567
  TScopedMw
1630
- >;
1568
+ >
1631
1569
  }
1632
1570
 
1633
- on<
1634
- const TPath extends string,
1635
- const TMethods extends readonly [HttpMethod | "ALL", ...(HttpMethod | "ALL")[]],
1636
- >(
1637
- methods: TMethods,
1638
- path: TPath,
1571
+ on<const TPath extends string, const TMethods extends readonly [HttpMethod | "ALL", ...(HttpMethod | "ALL")[]]>(
1572
+ methods: TMethods,
1573
+ path: TPath,
1639
1574
  ): BuilderChain<
1640
1575
  TEnv,
1641
1576
  TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
@@ -1655,37 +1590,37 @@ export class Honey<
1655
1590
  TTaps,
1656
1591
  TScopedMw
1657
1592
  > {
1658
- const [first, ...rest] = methods;
1593
+ const [first, ...rest] = methods
1659
1594
  return this._registerRoute<TPath, TMethods[number]>(
1660
1595
  first,
1661
1596
  path,
1662
1597
  rest.length > 0 ? (rest as (HttpMethod | "ALL")[]) : undefined,
1663
- );
1598
+ )
1664
1599
  }
1665
1600
 
1666
1601
  all<const TPath extends string>(path: TPath) {
1667
- return this._registerRoute<TPath, "ALL">("ALL", path);
1602
+ return this._registerRoute<TPath, "ALL">("ALL", path)
1668
1603
  }
1669
1604
  delete<const TPath extends string>(path: TPath) {
1670
- return this._registerRoute<TPath, "DELETE">("DELETE", path);
1605
+ return this._registerRoute<TPath, "DELETE">("DELETE", path)
1671
1606
  }
1672
1607
  get<const TPath extends string>(path: TPath) {
1673
- return this._registerRoute<TPath, "GET">("GET", path);
1608
+ return this._registerRoute<TPath, "GET">("GET", path)
1674
1609
  }
1675
1610
  head<const TPath extends string>(path: TPath) {
1676
- return this._registerRoute<TPath, "HEAD">("HEAD", path);
1611
+ return this._registerRoute<TPath, "HEAD">("HEAD", path)
1677
1612
  }
1678
1613
  options<const TPath extends string>(path: TPath) {
1679
- return this._registerRoute<TPath, "OPTIONS">("OPTIONS", path);
1614
+ return this._registerRoute<TPath, "OPTIONS">("OPTIONS", path)
1680
1615
  }
1681
1616
  patch<const TPath extends string>(path: TPath) {
1682
- return this._registerRoute<TPath, "PATCH">("PATCH", path);
1617
+ return this._registerRoute<TPath, "PATCH">("PATCH", path)
1683
1618
  }
1684
1619
  post<const TPath extends string>(path: TPath) {
1685
- return this._registerRoute<TPath, "POST">("POST", path);
1620
+ return this._registerRoute<TPath, "POST">("POST", path)
1686
1621
  }
1687
1622
  put<const TPath extends string>(path: TPath) {
1688
- return this._registerRoute<TPath, "PUT">("PUT", path);
1623
+ return this._registerRoute<TPath, "PUT">("PUT", path)
1689
1624
  }
1690
1625
 
1691
1626
  ws<const TPath extends string>(
@@ -1702,51 +1637,42 @@ export class Honey<
1702
1637
  TCtx & ApplyScoped<TScopedMw, MergePath<TBasePath, TPath>>,
1703
1638
  {},
1704
1639
  never,
1705
- Honey<
1706
- TEnv,
1707
- TCtx,
1708
- TRoutes,
1709
- TMeta,
1710
- TErrorFactory,
1711
- TDefaultErrors,
1712
- TBasePath,
1713
- TTaps,
1714
- TScopedMw
1715
- >
1640
+ Honey<TEnv, TCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
1716
1641
  >({
1717
1642
  boundaryErrorKey: this._defaultBoundaryKey,
1718
1643
  errorKeys: new Set(),
1719
1644
  inputSchemas: null,
1720
1645
  meta: null,
1721
1646
  middlewares: [],
1647
+ mwMeta: this._contributedMetaFor(mergePath(this._basePath, path)),
1722
1648
  parent: this,
1723
1649
  parentMiddlewares: this._chainMiddlewares,
1724
1650
  path: mergePath(this._basePath, path),
1725
1651
  root: this._root,
1726
- });
1652
+ })
1727
1653
  }
1728
1654
 
1729
1655
  realtime(path: string, opts: RealtimeRouteOpts): this {
1730
- const fullPath = mergePath(this._basePath, path) || "/";
1656
+ const fullPath = mergePath(this._basePath, path) || "/"
1731
1657
 
1732
1658
  if (!this._realtimeBus) {
1733
- this._realtimeBus = createBus();
1659
+ this._realtimeBus = createBus()
1734
1660
  }
1735
1661
 
1736
1662
  if (this._realtimeRoutes.has(fullPath)) {
1737
- throw new Error(`Duplicate realtime route: ${fullPath}`);
1663
+ throw new Error(`Duplicate realtime route: ${fullPath}`)
1738
1664
  }
1739
1665
 
1740
1666
  this._realtimeRoutes.set(fullPath, {
1741
1667
  handler: opts.handler,
1742
1668
  middlewares: opts.use,
1743
1669
  reconnectBuffer: opts.reconnectBuffer,
1744
- });
1670
+ })
1745
1671
 
1746
- const mw: RuntimeMiddleware[] = [];
1672
+ const mw: RuntimeMiddleware[] = []
1747
1673
  if (opts.use) {
1748
1674
  for (const fn of opts.use) {
1749
- mw.push(fn as RuntimeMiddleware);
1675
+ mw.push(fn as RuntimeMiddleware)
1750
1676
  }
1751
1677
  }
1752
1678
 
@@ -1758,10 +1684,10 @@ export class Honey<
1758
1684
  mt: null,
1759
1685
  mw: [...this._chainMiddlewares, ...mw],
1760
1686
  rp: fullPath,
1761
- });
1687
+ })
1762
1688
 
1763
- this._hasWsRoutes = true;
1764
- return this;
1689
+ this._hasWsRoutes = true
1690
+ return this
1765
1691
  }
1766
1692
 
1767
1693
  /**
@@ -1775,9 +1701,10 @@ export class Honey<
1775
1701
  executionCtx?: { waitUntil?: (p: Promise<unknown>) => void },
1776
1702
  ): Response | Promise<Response> {
1777
1703
  if (this._wsAdapter === null && !this._hasWsRoutes) {
1778
- return this._doFetch(request, env, executionCtx);
1704
+ return this._doFetch(request, env, executionCtx)
1779
1705
  }
1780
1706
  const isWsUpgrade = request.headers.get("upgrade")?.toLowerCase() === "websocket"
1707
+ const headerSnap = isWsUpgrade ? new Headers(request.headers) : undefined
1781
1708
  const path = this.pathFromRequest(request)
1782
1709
  const canPreUpgrade =
1783
1710
  isWsUpgrade &&
@@ -1785,7 +1712,14 @@ export class Honey<
1785
1712
  !this.trailingSlashRedirects(path) &&
1786
1713
  matchWsRoute(this._root, this.pathAfterPrefix(path)) !== null
1787
1714
  const pre = canPreUpgrade ? this._wsAdapter?.preUpgrade?.(request) : undefined
1788
- const work = this._doFetch(request, env, executionCtx)
1715
+ /* After Deno.upgradeWebSocket the Request is closed. A sync throw here
1716
+ * used to be boxed by async _doFetch; keep 101 returning either way. */
1717
+ let work: Response | Promise<Response>
1718
+ try {
1719
+ work = this._doFetch(request, env, executionCtx, isWsUpgrade === true, headerSnap)
1720
+ } catch (err) {
1721
+ work = Promise.reject(err)
1722
+ }
1789
1723
  if (!pre) return work
1790
1724
  void Promise.resolve(work)
1791
1725
  .then((res) => {
@@ -1842,32 +1776,33 @@ export class Honey<
1842
1776
  request: Request,
1843
1777
  env: TEnv,
1844
1778
  executionCtx?: { waitUntil?: (p: Promise<unknown>) => void },
1779
+ knownWsUpgrade = false,
1780
+ headerSnap?: Headers,
1845
1781
  ): Response | Promise<Response> {
1846
- const startTime = performance.now();
1782
+ const startTime = performance.now()
1847
1783
 
1848
1784
  /* fast path extraction — avoids expensive new URL() allocation */
1849
- const rawUrl = request.url;
1850
- const protoEnd = rawUrl.indexOf("//");
1851
- const pathStart = protoEnd === -1 ? 0 : rawUrl.indexOf("/", protoEnd + 2);
1852
- const searchOrHash =
1853
- pathStart === -1 ? -1 : findSearchOrHash(rawUrl, pathStart);
1854
- let path: string;
1785
+ const rawUrl = request.url
1786
+ const protoEnd = rawUrl.indexOf("//")
1787
+ const pathStart = protoEnd === -1 ? 0 : rawUrl.indexOf("/", protoEnd + 2)
1788
+ const searchOrHash = pathStart === -1 ? -1 : findSearchOrHash(rawUrl, pathStart)
1789
+ let path: string
1855
1790
  if (pathStart === -1) {
1856
- path = "/";
1791
+ path = "/"
1857
1792
  } else if (searchOrHash === -1) {
1858
- path = rawUrl.substring(pathStart);
1793
+ path = rawUrl.substring(pathStart)
1859
1794
  } else {
1860
- path = rawUrl.substring(pathStart, searchOrHash);
1795
+ path = rawUrl.substring(pathStart, searchOrHash)
1861
1796
  }
1862
1797
 
1863
1798
  /* lazily create URL only when actually needed (search params, redirects) */
1864
- let _url: URL | undefined;
1799
+ let _url: URL | undefined
1865
1800
  const getUrl = (): URL => {
1866
- if (_url === undefined) _url = new URL(rawUrl);
1867
- return _url;
1868
- };
1801
+ if (_url === undefined) _url = new URL(rawUrl)
1802
+ return _url
1803
+ }
1869
1804
 
1870
- const log = this._logger ?? undefined;
1805
+ const log = this._logger ?? undefined
1871
1806
  const fc: FetchCtx<TEnv> = {
1872
1807
  env,
1873
1808
  executionCtx,
@@ -1875,109 +1810,96 @@ export class Honey<
1875
1810
  request,
1876
1811
  startTime,
1877
1812
  url: getUrl,
1878
- };
1813
+ wsUpgrade: knownWsUpgrade,
1814
+ headerSnap,
1815
+ }
1879
1816
 
1880
1817
  if (this._telemetry !== null) {
1881
- safeFire(() => this._telemetry?.onRequest?.({ env, req: request }), log);
1818
+ safeFire(() => this._telemetry?.onRequest?.({ env, req: request }), log)
1882
1819
  }
1883
1820
 
1884
1821
  /* trailing slash handling */
1885
1822
  if (path.length > 1) {
1886
1823
  if (this._trailingSlash === "strip" && path.endsWith("/")) {
1887
- const redirectUrl = getUrl();
1888
- redirectUrl.pathname = path.slice(0, -1);
1824
+ const redirectUrl = getUrl()
1825
+ redirectUrl.pathname = path.slice(0, -1)
1889
1826
  return new Response(null, {
1890
1827
  headers: { location: redirectUrl.toString() },
1891
1828
  status: 308,
1892
- });
1829
+ })
1893
1830
  }
1894
1831
  if (this._trailingSlash === "enforce" && !path.endsWith("/")) {
1895
- const redirectUrl = getUrl();
1896
- redirectUrl.pathname = `${path}/`;
1832
+ const redirectUrl = getUrl()
1833
+ redirectUrl.pathname = `${path}/`
1897
1834
  return new Response(null, {
1898
1835
  headers: { location: redirectUrl.toString() },
1899
1836
  status: 308,
1900
- });
1837
+ })
1901
1838
  }
1902
1839
  }
1903
1840
 
1904
1841
  /* prefix stripping — must run AFTER trailing slash so redirects preserve the full prefixed URL */
1905
1842
  if (this._stripPrefix !== null) {
1906
1843
  if (path === this._stripPrefix) {
1907
- path = "/";
1908
- } else if (
1909
- path.startsWith(this._stripPrefix) &&
1910
- path.charCodeAt(this._stripPrefix.length) === 47
1911
- ) {
1912
- path = path.slice(this._stripPrefix.length);
1844
+ path = "/"
1845
+ } else if (path.startsWith(this._stripPrefix) && path.charCodeAt(this._stripPrefix.length) === 47) {
1846
+ path = path.slice(this._stripPrefix.length)
1913
1847
  }
1914
1848
  }
1915
1849
 
1916
- /* WebSocket route check */
1917
- const isWsUpgrade = request.headers.get("upgrade") === "websocket";
1850
+ /* WebSocket route check — do not re-read headers after Deno.upgradeWebSocket */
1851
+ const isWsUpgrade = knownWsUpgrade || requestIsWsUpgrade(request)
1918
1852
  if (isWsUpgrade) {
1919
- const wsMatch = matchWsRoute(this._root, path);
1853
+ const wsMatch = matchWsRoute(this._root, path)
1920
1854
  if (wsMatch !== null) {
1921
- return this._handleWs(fc, wsMatch);
1855
+ return this._handleWs(fc, wsMatch)
1922
1856
  }
1923
1857
  } else if (this._realtimeRoutes.size > 0) {
1924
1858
  /* Non-upgrade request hitting a realtime-only path → 426 Upgrade Required */
1925
- const wsMatch = matchWsRoute(this._root, path);
1859
+ const wsMatch = matchWsRoute(this._root, path)
1926
1860
  if (wsMatch !== null && this._realtimeRoutes.has(wsMatch.handler.rp)) {
1927
- return new Response(null, { headers: { upgrade: "websocket" }, status: 426 });
1861
+ return new Response(null, { headers: { upgrade: "websocket" }, status: 426 })
1928
1862
  }
1929
1863
  }
1930
1864
 
1931
- const method = request.method.toUpperCase() as HttpMethod;
1865
+ const method = request.method.toUpperCase() as HttpMethod
1932
1866
 
1933
1867
  /* fn:null fallthrough — tree match sets meta, catch-all dispatches */
1934
- let fnNullMeta: Record<string, unknown> | null = null;
1935
- let fnNullParams: Record<string, string> | null = null;
1936
- let fnNullHit = false;
1868
+ let fnNullMeta: Record<string, unknown> | null = null
1869
+ let fnNullParams: Record<string, string> | null = null
1870
+ let fnNullHit = false
1937
1871
 
1938
1872
  /* Tier 2: O(1) static route lookup — checks both precompiled and runtime maps */
1939
- const smap = this._staticRoutes.map ?? this._handlerMap;
1873
+ const smap = this._staticRoutes.map ?? this._handlerMap
1940
1874
  if (smap !== null) {
1941
- const key = `${method} ${path}`;
1942
- const staticHandler = smap[key];
1875
+ const key = `${method} ${path}`
1876
+ const staticHandler = smap[key]
1943
1877
  if (staticHandler) {
1944
1878
  if (staticHandler.fn === null) {
1945
- fnNullMeta = staticHandler.mt;
1946
- fnNullParams = EMPTY_PARAMS;
1947
- fnNullHit = true;
1879
+ fnNullMeta = staticHandler.mt
1880
+ fnNullParams = EMPTY_PARAMS
1881
+ fnNullHit = true
1948
1882
  } else {
1949
- return this._handleMatched(
1950
- fc,
1951
- method,
1952
- path,
1953
- staticHandler,
1954
- EMPTY_PARAMS,
1955
- );
1883
+ return this._handleMatched(fc, method, path, staticHandler, EMPTY_PARAMS)
1956
1884
  }
1957
1885
  }
1958
1886
  /* HEAD falls back to GET */
1959
1887
  if (!fnNullHit && method === "HEAD") {
1960
- const getHandler = smap[`GET ${path}`];
1888
+ const getHandler = smap[`GET ${path}`]
1961
1889
  if (getHandler) {
1962
1890
  if (getHandler.fn === null) {
1963
- fnNullMeta = getHandler.mt;
1964
- fnNullParams = EMPTY_PARAMS;
1965
- fnNullHit = true;
1891
+ fnNullMeta = getHandler.mt
1892
+ fnNullParams = EMPTY_PARAMS
1893
+ fnNullHit = true
1966
1894
  } else {
1967
- return this._handleMatched(
1968
- fc,
1969
- method,
1970
- path,
1971
- getHandler,
1972
- EMPTY_PARAMS,
1973
- );
1895
+ return this._handleMatched(fc, method, path, getHandler, EMPTY_PARAMS)
1974
1896
  }
1975
1897
  }
1976
1898
  }
1977
1899
  }
1978
1900
 
1979
1901
  if (!fnNullHit) {
1980
- const result = matchRoute(this._root, method, path);
1902
+ const result = matchRoute(this._root, method, path)
1981
1903
 
1982
1904
  if (result?.matched) {
1983
1905
  /*
@@ -1989,41 +1911,34 @@ export class Honey<
1989
1911
  this._hasRouteTree &&
1990
1912
  this._root.w !== null &&
1991
1913
  result.handler.fn !== null &&
1992
- (this._root.w.m[method] === result.handler ||
1993
- this._root.w.m["ALL"] === result.handler);
1914
+ (this._root.w.m[method] === result.handler || this._root.w.m["ALL"] === result.handler)
1994
1915
 
1995
1916
  if (isWildcardCatchAll) {
1996
1917
  /* path not in routeTree — 404 */
1997
- return this._handle404(fc, method, path);
1918
+ return this._handle404(fc, method, path)
1998
1919
  }
1999
1920
 
2000
1921
  if (result.handler.fn === null) {
2001
- fnNullMeta = result.handler.mt;
2002
- fnNullParams = result.params;
2003
- fnNullHit = true;
1922
+ fnNullMeta = result.handler.mt
1923
+ fnNullParams = result.params
1924
+ fnNullHit = true
2004
1925
  } else {
2005
- return this._handleMatched(
2006
- fc,
2007
- method,
2008
- path,
2009
- result.handler,
2010
- result.params,
2011
- );
1926
+ return this._handleMatched(fc, method, path, result.handler, result.params)
2012
1927
  }
2013
1928
  }
2014
1929
 
2015
1930
  if (!fnNullHit) {
2016
1931
  if (result === null) {
2017
1932
  if (!isWsUpgrade) {
2018
- const wsMatch = matchWsRoute(this._root, path);
1933
+ const wsMatch = matchWsRoute(this._root, path)
2019
1934
  if (wsMatch !== null) {
2020
1935
  return new Response("Upgrade Required", {
2021
1936
  headers: { connection: "Upgrade", upgrade: "websocket" },
2022
1937
  status: 426,
2023
- });
1938
+ })
2024
1939
  }
2025
1940
  }
2026
- return this._handle404(fc, method, path);
1941
+ return this._handle404(fc, method, path)
2027
1942
  }
2028
1943
 
2029
1944
  if (!result.matched) {
@@ -2032,47 +1947,33 @@ export class Honey<
2032
1947
  fc.request.headers.has("access-control-request-method") &&
2033
1948
  result.allowed.length > 0
2034
1949
  ) {
2035
- const fallback = this._preflightFallbackMethod(result.allowed);
2036
- const retry = matchRoute(this._root, fallback, path);
1950
+ const fallback = this._preflightFallbackMethod(result.allowed)
1951
+ const retry = matchRoute(this._root, fallback, path)
2037
1952
  if (retry?.matched && retry.handler.fn !== null) {
2038
- return this._handleCorsPreflight(
2039
- fc,
2040
- path,
2041
- retry.handler,
2042
- retry.params,
2043
- result.allowed,
2044
- );
1953
+ return this._handleCorsPreflight(fc, path, retry.handler, retry.params, result.allowed)
2045
1954
  }
2046
1955
  }
2047
- return this._handle405(fc, method, path, result.allowed);
1956
+ return this._handle405(fc, method, path, result.allowed)
2048
1957
  }
2049
1958
  }
2050
1959
  }
2051
1960
 
2052
1961
  /* fn:null hit — find catch-all/wildcard to dispatch with stashed meta */
2053
- const wildcardResult = matchRoute(this._root, method, "/*");
1962
+ const wildcardResult = matchRoute(this._root, method, "/*")
2054
1963
  if (wildcardResult?.matched && wildcardResult.handler.fn !== null) {
2055
- return this._handleMatched(
2056
- fc,
2057
- method,
2058
- path,
2059
- wildcardResult.handler,
2060
- fnNullParams ?? EMPTY_PARAMS,
2061
- fnNullMeta,
2062
- );
1964
+ return this._handleMatched(fc, method, path, wildcardResult.handler, fnNullParams ?? EMPTY_PARAMS, fnNullMeta)
2063
1965
  }
2064
1966
 
2065
- return this._handle404(fc, method, path);
1967
+ return this._handle404(fc, method, path)
2066
1968
  }
2067
1969
 
2068
1970
  private _makeErrorCtx(fc: FetchCtx<TEnv>, allowed?: string[]) {
2069
1971
  return {
2070
1972
  env: fc.env,
2071
- jsonFromError: (err: HoneyError) =>
2072
- createErrorResponse(err, this._errorFormatter, this._customErrorFormatter),
1973
+ jsonFromError: (err: HoneyError) => createErrorResponse(err, this._errorFormatter, this._customErrorFormatter),
2073
1974
  req: fc.request,
2074
1975
  ...(allowed ? { allowed } : {}),
2075
- };
1976
+ }
2076
1977
  }
2077
1978
 
2078
1979
  private async _handleWs(
@@ -2080,73 +1981,68 @@ export class Honey<
2080
1981
  wsMatch: { handler: WSRouteHandler; params: Record<string, string> },
2081
1982
  ): Promise<Response> {
2082
1983
  /* Dispatch to realtime handler if this path is registered as a realtime route */
2083
- const realtimeConfig = this._realtimeRoutes.get(wsMatch.handler.rp);
1984
+ const realtimeConfig = this._realtimeRoutes.get(wsMatch.handler.rp)
2084
1985
  if (realtimeConfig) {
2085
- return this._handleRealtime(fc, wsMatch, realtimeConfig);
1986
+ return this._handleRealtime(fc, wsMatch, realtimeConfig)
2086
1987
  }
2087
1988
 
2088
- const isUpgrade =
2089
- fc.request.headers.get("upgrade")?.toLowerCase() === "websocket";
1989
+ const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request)
2090
1990
  if (!isUpgrade) {
2091
1991
  return new Response(null, {
2092
1992
  headers: { upgrade: "websocket" },
2093
1993
  status: 426,
2094
- });
1994
+ })
2095
1995
  }
2096
1996
 
2097
- const wsAdapter = this._wsAdapter;
1997
+ const wsAdapter = this._wsAdapter
2098
1998
  if (!wsAdapter) {
2099
- fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()");
1999
+ fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()")
2100
2000
  return createErrorResponse(
2101
2001
  this._createError(EK.internal_server_error, SK.internal_server_error),
2102
2002
  this._errorFormatter,
2103
2003
  this._customErrorFormatter,
2104
- );
2004
+ )
2105
2005
  }
2106
2006
 
2107
2007
  const wsCtx = new HoneyContext({
2108
2008
  env: fc.env,
2109
2009
  executionCtx: fc.executionCtx,
2110
2010
  params: wsMatch.params,
2111
- req: fc.request,
2011
+ req: ctxRequest(fc),
2112
2012
  urlFn: fc.url,
2113
- });
2114
- if (this._contextValues) Object.assign(wsCtx, this._contextValues);
2013
+ })
2014
+ if (this._contextValues) Object.assign(wsCtx, this._contextValues)
2115
2015
 
2116
2016
  /*
2117
2017
  * WS ordering: [global → scoped → chain+handler-route-specific]
2118
2018
  * WS bakes chain mw into handler.mw at registration, so scoped runs before chain.
2119
2019
  * This is an unavoidable inconsistency vs HTTP (where chain runs before scoped).
2120
2020
  */
2121
- const scopedForPath = this._filterScopedForPath(wsMatch.handler.rp);
2122
- const allWsMw: RuntimeMiddleware[] = [
2123
- ...this._globalMiddlewares,
2124
- ...scopedForPath,
2125
- ...wsMatch.handler.mw,
2126
- ];
2021
+ const scopedForPath = this._filterScopedForPath(wsMatch.handler.rp)
2022
+ const allWsMw: RuntimeMiddleware[] = [...this._globalMiddlewares, ...scopedForPath, ...wsMatch.handler.mw]
2127
2023
 
2128
2024
  try {
2129
2025
  return await executeChain(allWsMw, wsCtx, async (finalCtx) => {
2130
- const userHandler = wsMatch.handler.fn;
2131
- let messageQueue: Promise<void> = Promise.resolve();
2026
+ const userHandler = wsMatch.handler.fn
2027
+ let messageQueue: Promise<void> = Promise.resolve()
2132
2028
 
2133
- const onOpenFn = userHandler.onOpen;
2134
- const onMsgFn = userHandler.onMessage;
2135
- const onCloseFn = userHandler.onClose;
2136
- const onErrorFn = userHandler.onError;
2137
- const onReconnectFn = userHandler.onReconnect;
2138
- const reconnectToken = fc.url().searchParams.get("reconnect_token");
2029
+ const onOpenFn = userHandler.onOpen
2030
+ const onMsgFn = userHandler.onMessage
2031
+ const onCloseFn = userHandler.onClose
2032
+ const onErrorFn = userHandler.onError
2033
+ const onReconnectFn = userHandler.onReconnect
2034
+ const reconnectToken = fc.url().searchParams.get("reconnect_token")
2139
2035
 
2140
- const wrappedHandler: WSHandler<unknown> = {};
2036
+ const wrappedHandler: WSHandler<unknown> = {}
2141
2037
 
2142
2038
  if (reconnectToken && onReconnectFn) {
2143
2039
  wrappedHandler.onOpen = (_ctx, ws) => {
2144
- onReconnectFn(finalCtx, ws, reconnectToken);
2145
- };
2040
+ onReconnectFn(finalCtx, ws, reconnectToken)
2041
+ }
2146
2042
  } else if (onOpenFn) {
2147
2043
  wrappedHandler.onOpen = (_ctx, ws) => {
2148
- onOpenFn(finalCtx, ws);
2149
- };
2044
+ onOpenFn(finalCtx, ws)
2045
+ }
2150
2046
  }
2151
2047
 
2152
2048
  if (onMsgFn) {
@@ -2154,32 +2050,28 @@ export class Honey<
2154
2050
  messageQueue = messageQueue
2155
2051
  .then(() => onMsgFn(finalCtx, ws, data))
2156
2052
  .catch((err: unknown) => {
2157
- onErrorFn?.(finalCtx, ws, err);
2158
- });
2159
- };
2053
+ onErrorFn?.(finalCtx, ws, err)
2054
+ })
2055
+ }
2160
2056
  }
2161
2057
 
2162
2058
  if (onCloseFn) {
2163
2059
  wrappedHandler.onClose = (_ctx, ws, code, reason) => {
2164
- onCloseFn(finalCtx, ws, code, reason);
2165
- };
2060
+ onCloseFn(finalCtx, ws, code, reason)
2061
+ }
2166
2062
  }
2167
2063
 
2168
2064
  if (onErrorFn) {
2169
2065
  wrappedHandler.onError = (_ctx, ws, error) => {
2170
- onErrorFn(finalCtx, ws, error);
2171
- };
2066
+ onErrorFn(finalCtx, ws, error)
2067
+ }
2172
2068
  }
2173
2069
 
2174
- const upgradeResult = await wsAdapter.upgrade(
2175
- fc.request,
2176
- fc.env,
2177
- wrappedHandler,
2178
- );
2179
- return upgradeResult.response;
2180
- });
2070
+ const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, wrappedHandler)
2071
+ return upgradeResult.response
2072
+ })
2181
2073
  } catch (thrown) {
2182
- return this._toErrorResponse(thrown);
2074
+ return this._toErrorResponse(thrown)
2183
2075
  }
2184
2076
  }
2185
2077
 
@@ -2188,56 +2080,51 @@ export class Honey<
2188
2080
  wsMatch: { handler: WSRouteHandler; params: Record<string, string> },
2189
2081
  config: { handler: RealtimeRouteOpts["handler"]; middlewares?: RealtimeRouteOpts["use"]; reconnectBuffer?: number },
2190
2082
  ): Promise<Response> {
2191
- const isUpgrade =
2192
- fc.request.headers.get("upgrade")?.toLowerCase() === "websocket";
2083
+ const isUpgrade = fc.wsUpgrade === true || requestIsWsUpgrade(fc.request)
2193
2084
  if (!isUpgrade) {
2194
2085
  return new Response(null, {
2195
2086
  headers: { upgrade: "websocket" },
2196
2087
  status: 426,
2197
- });
2088
+ })
2198
2089
  }
2199
2090
 
2200
- const wsAdapter = this._wsAdapter;
2091
+ const wsAdapter = this._wsAdapter
2201
2092
  if (!wsAdapter) {
2202
- fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()");
2093
+ fc.log?.warn?.("WebSocket adapter not configured — call .wsAdapter()")
2203
2094
  return createErrorResponse(
2204
2095
  this._createError(EK.internal_server_error, SK.internal_server_error),
2205
2096
  this._errorFormatter,
2206
2097
  this._customErrorFormatter,
2207
- );
2098
+ )
2208
2099
  }
2209
2100
 
2210
2101
  /* Lazily create bus if not yet initialized (happens when realtime() was called on a child chain) */
2211
2102
  if (!this._realtimeBus) {
2212
- this._realtimeBus = createBus();
2103
+ this._realtimeBus = createBus()
2213
2104
  }
2214
- const bus = this._realtimeBus;
2105
+ const bus = this._realtimeBus
2215
2106
 
2216
2107
  const ctx = new HoneyContext({
2217
2108
  env: fc.env,
2218
2109
  executionCtx: fc.executionCtx,
2219
2110
  params: wsMatch.params,
2220
- req: fc.request,
2111
+ req: ctxRequest(fc),
2221
2112
  urlFn: fc.url,
2222
- });
2223
- if (this._contextValues) Object.assign(ctx, this._contextValues);
2113
+ })
2114
+ if (this._contextValues) Object.assign(ctx, this._contextValues)
2224
2115
  Object.assign(ctx, {
2225
2116
  realtime: { publish: (topic: string, data: unknown) => bus.publish(topic, data) },
2226
- });
2117
+ })
2227
2118
 
2228
- const scopedForPath = this._filterScopedForPath(wsMatch.handler.rp);
2229
- const allMw: RuntimeMiddleware[] = [
2230
- ...this._globalMiddlewares,
2231
- ...scopedForPath,
2232
- ...wsMatch.handler.mw,
2233
- ];
2119
+ const scopedForPath = this._filterScopedForPath(wsMatch.handler.rp)
2120
+ const allMw: RuntimeMiddleware[] = [...this._globalMiddlewares, ...scopedForPath, ...wsMatch.handler.mw]
2234
2121
 
2235
2122
  try {
2236
2123
  return await executeChain(allMw, ctx, async (finalCtx) => {
2237
- const connId = crypto.randomUUID();
2124
+ const connId = crypto.randomUUID()
2238
2125
 
2239
- let socket: WSContext<unknown> | null = null;
2240
- let conn: ReturnType<typeof createConnContext> | null = null;
2126
+ let socket: WSContext<unknown> | null = null
2127
+ let conn: ReturnType<typeof createConnContext> | null = null
2241
2128
 
2242
2129
  /*
2243
2130
  * initConn creates the ConnContext and calls the user handler.
@@ -2245,88 +2132,82 @@ export class Honey<
2245
2132
  * or inline after upgrade (for Node/CF where socket is immediate).
2246
2133
  */
2247
2134
  const initConn = (ws: WSContext<unknown>) => {
2248
- socket = ws;
2135
+ socket = ws
2249
2136
  conn = createConnContext({
2250
2137
  bus,
2251
2138
  closeFn: (reason) => {
2252
- if (socket) socket.close(1000, reason);
2139
+ if (socket) socket.close(1000, reason)
2253
2140
  },
2254
2141
  id: connId,
2255
2142
  sendFn: (payload) => {
2256
- if (socket) socket.send(typeof payload === "object" && payload !== null ? JSON.stringify(payload) : String(payload));
2143
+ if (socket)
2144
+ socket.send(typeof payload === "object" && payload !== null ? JSON.stringify(payload) : String(payload))
2257
2145
  },
2258
2146
  transport: "ws",
2259
2147
  userId: null,
2260
- });
2148
+ })
2261
2149
 
2262
2150
  bus.onMessage(connId, (data) => {
2263
2151
  if (socket) {
2264
- socket.send(typeof data === "object" && data !== null ? JSON.stringify(data) : String(data));
2152
+ socket.send(typeof data === "object" && data !== null ? JSON.stringify(data) : String(data))
2265
2153
  }
2266
- });
2154
+ })
2267
2155
 
2268
- config.handler(finalCtx, conn);
2269
- };
2156
+ config.handler(finalCtx, conn)
2157
+ }
2270
2158
 
2271
2159
  const wrappedHandler: WSHandler<unknown> = {
2272
2160
  onClose: (_ctx, _ws, _code, reason) => {
2273
- if (!conn) return;
2274
- const handlers = conn._handlers;
2161
+ if (!conn) return
2162
+ const handlers = conn._handlers
2275
2163
  if (handlers.close) {
2276
- handlers.close(reason || "normal");
2164
+ handlers.close(reason || "normal")
2277
2165
  }
2278
- bus.unsubscribeAll(connId);
2279
- bus.removeHandler(connId);
2166
+ bus.unsubscribeAll(connId)
2167
+ bus.removeHandler(connId)
2280
2168
  },
2281
2169
  onMessage: (_ctx, _ws, data) => {
2282
- if (!conn) return;
2283
- const handlers = conn._handlers;
2170
+ if (!conn) return
2171
+ const handlers = conn._handlers
2284
2172
  if (handlers.message && typeof data === "string") {
2285
2173
  try {
2286
- const parsed: unknown = JSON.parse(data);
2174
+ const parsed: unknown = JSON.parse(data)
2287
2175
  if (isMsgFrame(parsed)) {
2288
- handlers.message(parsed.data);
2176
+ handlers.message(parsed.data)
2289
2177
  }
2290
- } catch { /* ignore malformed frames */ }
2178
+ } catch {
2179
+ /* ignore malformed frames */
2180
+ }
2291
2181
  }
2292
2182
  },
2293
2183
  onOpen: (_ctx, ws) => {
2294
- if (!socket) initConn(ws);
2184
+ if (!socket) initConn(ws)
2295
2185
  },
2296
- };
2186
+ }
2297
2187
 
2298
- const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, wrappedHandler);
2188
+ const upgradeResult = await wsAdapter.upgrade(fc.request, fc.env, wrappedHandler)
2299
2189
 
2300
2190
  /* Node/CF adapters return the socket from upgrade(); Bun returns undefined (socket comes via onOpen).
2301
2191
  * Deno pre-upgrade may still be CONNECTING — wait for onOpen so the first send is not dropped. */
2302
2192
  if (upgradeResult.socket && !socket && upgradeResult.socket.readyState === 1) {
2303
- initConn(upgradeResult.socket);
2193
+ initConn(upgradeResult.socket)
2304
2194
  }
2305
2195
 
2306
- return upgradeResult.response;
2307
- });
2196
+ return upgradeResult.response
2197
+ })
2308
2198
  } catch (thrown) {
2309
- return this._toErrorResponse(thrown);
2199
+ return this._toErrorResponse(thrown)
2310
2200
  }
2311
2201
  }
2312
2202
 
2313
- private async _handle404(
2314
- fc: FetchCtx<TEnv>,
2315
- method: string,
2316
- path: string,
2317
- ): Promise<Response> {
2318
- safeFire(
2319
- () => this._telemetry?.onNotFound?.({ method, path, req: fc.request }),
2320
- fc.log,
2321
- );
2203
+ private async _handle404(fc: FetchCtx<TEnv>, method: string, path: string): Promise<Response> {
2204
+ safeFire(() => this._telemetry?.onNotFound?.({ method, path, req: fc.request }), fc.log)
2322
2205
  const make404 = () => {
2323
2206
  if (this._onNotFound) {
2324
- return this._onNotFound(this._makeErrorCtx(fc));
2207
+ return this._onNotFound(this._makeErrorCtx(fc))
2325
2208
  }
2326
- return this._makeErrorCtx(fc).jsonFromError(
2327
- this._createError(EK.not_found, SK.not_found),
2328
- );
2329
- };
2209
+ return this._makeErrorCtx(fc).jsonFromError(this._createError(EK.not_found, SK.not_found))
2210
+ }
2330
2211
  try {
2331
2212
  const ctx404 = new HoneyContext({
2332
2213
  env: fc.env,
@@ -2334,12 +2215,12 @@ export class Honey<
2334
2215
  params: {},
2335
2216
  req: fc.request,
2336
2217
  urlFn: fc.url,
2337
- });
2338
- if (this._contextValues) Object.assign(ctx404, this._contextValues);
2218
+ })
2219
+ if (this._contextValues) Object.assign(ctx404, this._contextValues)
2339
2220
  const res =
2340
2221
  this._chainMiddlewares.length > 0
2341
2222
  ? await executeChain(this._chainMiddlewares, ctx404, make404)
2342
- : await make404();
2223
+ : await make404()
2343
2224
  safeFire(
2344
2225
  () =>
2345
2226
  this._telemetry?.onResponse?.({
@@ -2348,18 +2229,18 @@ export class Honey<
2348
2229
  status: res.status,
2349
2230
  }),
2350
2231
  fc.log,
2351
- );
2352
- return res;
2232
+ )
2233
+ return res
2353
2234
  } catch (thrown) {
2354
- return this._toErrorResponse(thrown);
2235
+ return this._toErrorResponse(thrown)
2355
2236
  }
2356
2237
  }
2357
2238
 
2358
2239
  private _preflightFallbackMethod(allowed: string[]): HttpMethod {
2359
- if (allowed.includes("GET")) return "GET";
2360
- if (allowed.includes("HEAD")) return "HEAD";
2361
- if (allowed.includes("POST")) return "POST";
2362
- return allowed[0] as HttpMethod;
2240
+ if (allowed.includes("GET")) return "GET"
2241
+ if (allowed.includes("HEAD")) return "HEAD"
2242
+ if (allowed.includes("POST")) return "POST"
2243
+ return allowed[0] as HttpMethod
2363
2244
  }
2364
2245
 
2365
2246
  /** Run the existing method's middleware for a CORS preflight. Do not invoke the route handler. */
@@ -2379,25 +2260,18 @@ export class Honey<
2379
2260
  req: fc.request,
2380
2261
  routePattern: handler.rp,
2381
2262
  urlFn: fc.url,
2382
- });
2383
- if (this._contextValues) Object.assign(ctx, this._contextValues);
2263
+ })
2264
+ if (this._contextValues) Object.assign(ctx, this._contextValues)
2384
2265
  try {
2385
- return await executeChain(
2386
- [...this._globalMiddlewares, ...handler.mw],
2387
- ctx,
2388
- () => this._handle405(fc, "OPTIONS", path, allowed),
2389
- );
2266
+ return await executeChain([...this._globalMiddlewares, ...handler.mw], ctx, () =>
2267
+ this._handle405(fc, "OPTIONS", path, allowed),
2268
+ )
2390
2269
  } catch (thrown) {
2391
- return this._toErrorResponse(thrown);
2270
+ return this._toErrorResponse(thrown)
2392
2271
  }
2393
2272
  }
2394
2273
 
2395
- private async _handle405(
2396
- fc: FetchCtx<TEnv>,
2397
- method: string,
2398
- path: string,
2399
- allowed: string[],
2400
- ): Promise<Response> {
2274
+ private async _handle405(fc: FetchCtx<TEnv>, method: string, path: string, allowed: string[]): Promise<Response> {
2401
2275
  safeFire(
2402
2276
  () =>
2403
2277
  this._telemetry?.onMethodNotAllowed?.({
@@ -2407,36 +2281,33 @@ export class Honey<
2407
2281
  req: fc.request,
2408
2282
  }),
2409
2283
  fc.log,
2410
- );
2284
+ )
2411
2285
  const make405 = async () => {
2412
2286
  if (this._onMethodNotAllowed) {
2413
2287
  const res = await this._onMethodNotAllowed(
2414
2288
  this._makeErrorCtx(fc, allowed) as {
2415
- allowed: string[];
2416
- env: TEnv;
2417
- jsonFromError: (err: HoneyError) => Response;
2418
- req: Request;
2289
+ allowed: string[]
2290
+ env: TEnv
2291
+ jsonFromError: (err: HoneyError) => Response
2292
+ req: Request
2419
2293
  },
2420
- );
2421
- const responseHeaders = new Headers(res.headers);
2422
- responseHeaders.set("allow", allowed.join(", "));
2294
+ )
2295
+ const responseHeaders = new Headers(res.headers)
2296
+ responseHeaders.set("allow", allowed.join(", "))
2423
2297
  return new Response(res.body, {
2424
2298
  headers: responseHeaders,
2425
2299
  status: res.status,
2426
- });
2300
+ })
2427
2301
  }
2428
- const err = this._createError(
2429
- EK.method_not_allowed,
2430
- SK.method_not_allowed,
2431
- );
2432
- const res = this._makeErrorCtx(fc).jsonFromError(err);
2433
- const responseHeaders = new Headers(res.headers);
2434
- responseHeaders.set("allow", allowed.join(", "));
2302
+ const err = this._createError(EK.method_not_allowed, SK.method_not_allowed)
2303
+ const res = this._makeErrorCtx(fc).jsonFromError(err)
2304
+ const responseHeaders = new Headers(res.headers)
2305
+ responseHeaders.set("allow", allowed.join(", "))
2435
2306
  return new Response(res.body, {
2436
2307
  headers: responseHeaders,
2437
2308
  status: res.status,
2438
- });
2439
- };
2309
+ })
2310
+ }
2440
2311
  try {
2441
2312
  const ctx405 = new HoneyContext({
2442
2313
  env: fc.env,
@@ -2444,12 +2315,12 @@ export class Honey<
2444
2315
  params: {},
2445
2316
  req: fc.request,
2446
2317
  urlFn: fc.url,
2447
- });
2448
- if (this._contextValues) Object.assign(ctx405, this._contextValues);
2318
+ })
2319
+ if (this._contextValues) Object.assign(ctx405, this._contextValues)
2449
2320
  const finalRes =
2450
2321
  this._chainMiddlewares.length > 0
2451
2322
  ? await executeChain(this._chainMiddlewares, ctx405, make405)
2452
- : await make405();
2323
+ : await make405()
2453
2324
  safeFire(
2454
2325
  () =>
2455
2326
  this._telemetry?.onResponse?.({
@@ -2458,10 +2329,10 @@ export class Honey<
2458
2329
  status: finalRes.status,
2459
2330
  }),
2460
2331
  fc.log,
2461
- );
2462
- return finalRes;
2332
+ )
2333
+ return finalRes
2463
2334
  } catch (thrown) {
2464
- return this._toErrorResponse(thrown);
2335
+ return this._toErrorResponse(thrown)
2465
2336
  }
2466
2337
  }
2467
2338
 
@@ -2473,38 +2344,29 @@ export class Honey<
2473
2344
  params: Record<string, string>,
2474
2345
  stashedMeta?: Record<string, unknown> | null,
2475
2346
  ): Response | Promise<Response> {
2476
- const { env, executionCtx, log, request } = fc;
2347
+ const { env, executionCtx, log, request } = fc
2477
2348
 
2478
2349
  /* resolve error factory — pre-computed ef preferred, else build/use global */
2479
- let errors: Record<string, (...args: never[]) => unknown> | undefined;
2350
+ let errors: Record<string, (...args: never[]) => unknown> | undefined
2480
2351
  if (handler.ef !== null) {
2481
- errors = handler.ef;
2352
+ errors = handler.ef
2482
2353
  } else if (this._errorFactory !== null) {
2483
2354
  if (handler.ek.size > 0) {
2484
- const factory = this._errorFactory as Record<
2485
- string,
2486
- (...args: never[]) => unknown
2487
- >;
2488
- const subset = Object.create(null) as Record<string, unknown>;
2355
+ const factory = this._errorFactory as Record<string, (...args: never[]) => unknown>
2356
+ const subset = Object.create(null) as Record<string, unknown>
2489
2357
  for (const key of handler.ek) {
2490
2358
  if (key in factory) {
2491
- subset[key] = factory[key];
2359
+ subset[key] = factory[key]
2492
2360
  }
2493
2361
  }
2494
- errors = Object.freeze(subset) as Record<
2495
- string,
2496
- (...args: never[]) => unknown
2497
- >;
2362
+ errors = Object.freeze(subset) as Record<string, (...args: never[]) => unknown>
2498
2363
  } else {
2499
- errors = this._errorFactory as Record<
2500
- string,
2501
- (...args: never[]) => unknown
2502
- >;
2364
+ errors = this._errorFactory as Record<string, (...args: never[]) => unknown>
2503
2365
  }
2504
2366
  }
2505
2367
 
2506
2368
  /* meta: stashed meta from fn:null tree match takes priority over handler meta */
2507
- const resolvedMeta = stashedMeta ?? handler.mt;
2369
+ const resolvedMeta = stashedMeta ?? handler.mt
2508
2370
 
2509
2371
  const ctx = new HoneyContext({
2510
2372
  env,
@@ -2515,21 +2377,21 @@ export class Honey<
2515
2377
  req: request,
2516
2378
  routePattern: handler.rp,
2517
2379
  urlFn: fc.url,
2518
- });
2519
- if (this._contextValues) Object.assign(ctx, this._contextValues);
2380
+ })
2381
+ if (this._contextValues) Object.assign(ctx, this._contextValues)
2520
2382
  if (this._realtimeBus) {
2521
- const rtBus = this._realtimeBus;
2383
+ const rtBus = this._realtimeBus
2522
2384
  Object.assign(ctx, {
2523
2385
  realtime: { publish: (topic: string, data: unknown) => rtBus.publish(topic, data) },
2524
- });
2386
+ })
2525
2387
  }
2526
2388
  if (errors) {
2527
- ctx._setErrors(errors);
2389
+ ctx._setErrors(errors)
2528
2390
  }
2529
2391
 
2530
2392
  if (this._telemetry !== null) {
2531
2393
  try {
2532
- this._telemetry.onRoute?.({ method, params, path, req: request });
2394
+ this._telemetry.onRoute?.({ method, params, path, req: request })
2533
2395
  } catch {
2534
2396
  /* telemetry must not crash request */
2535
2397
  }
@@ -2542,69 +2404,65 @@ export class Honey<
2542
2404
  * scoped middleware is in play (scoped mw cannot be baked into the compiled cache
2543
2405
  * because each route may match a different subset).
2544
2406
  */
2545
- const hasTelemetryMw = this._telemetry?.onMiddleware !== undefined;
2546
- const hasInputValidation = handler.iv !== null;
2407
+ const hasTelemetryMw = this._telemetry?.onMiddleware !== undefined
2408
+ const hasInputValidation = handler.iv !== null
2547
2409
 
2548
2410
  /*
2549
2411
  * Error resolver — stored on ctx so the cached handler wrapper can read it.
2550
2412
  * Converts handler errors into error Responses inside the middleware chain,
2551
2413
  * allowing all post-next() middleware code (headers, logging, timing) to run.
2552
2414
  */
2553
- ctx._errorToResponse = (thrown: unknown) =>
2554
- this._resolveErrorResponse(thrown, handler, fc, method, path, ctx);
2415
+ ctx._errorToResponse = (thrown: unknown) => this._resolveErrorResponse(thrown, handler, fc, method, path, ctx)
2555
2416
 
2556
2417
  const onError = (thrown: unknown): Response | Promise<Response> => {
2557
- if (ctx._errorToResponse) return ctx._errorToResponse(thrown);
2558
- return this._toErrorResponse(thrown);
2559
- };
2418
+ if (ctx._errorToResponse) return ctx._errorToResponse(thrown)
2419
+ return this._toErrorResponse(thrown)
2420
+ }
2560
2421
 
2561
2422
  const after = (response: Response): Response | Promise<Response> => {
2562
2423
  try {
2563
- const done = this._afterMatched(fc, method, path, handler, ctx, response);
2564
- if (done instanceof Promise) return done.catch(onError);
2565
- return done;
2424
+ const done = this._afterMatched(fc, method, path, handler, ctx, response)
2425
+ if (done instanceof Promise) return done.catch(onError)
2426
+ return done
2566
2427
  } catch (thrown) {
2567
- return onError(thrown);
2428
+ return onError(thrown)
2568
2429
  }
2569
- };
2430
+ }
2570
2431
 
2571
2432
  try {
2572
2433
  if (!hasTelemetryMw && !hasInputValidation && this._scopedMiddlewares.length === 0) {
2573
2434
  if (!handler._compiled) {
2574
- const chainMw = this._chainMiddlewares;
2575
- const handlerHasChain =
2576
- chainMw.length > 0 && chainMw.every((mw, i) => handler.mw[i] === mw);
2435
+ const chainMw = this._chainMiddlewares
2436
+ const handlerHasChain = chainMw.length > 0 && chainMw.every((mw, i) => handler.mw[i] === mw)
2577
2437
  const allMw = handlerHasChain
2578
2438
  ? [...this._globalMiddlewares, ...handler.mw]
2579
- : [...this._globalMiddlewares, ...chainMw, ...handler.mw];
2439
+ : [...this._globalMiddlewares, ...chainMw, ...handler.mw]
2580
2440
  handler._compiled = compileChain(allMw, (c) => {
2581
2441
  try {
2582
- const result = handler.fn(c);
2442
+ const result = handler.fn(c)
2583
2443
  if (result instanceof Promise) {
2584
2444
  return result.catch((thrown: unknown) => {
2585
- const hCtx = c as HoneyContext<TEnv>;
2586
- if (hCtx._errorToResponse)
2587
- return hCtx._errorToResponse(thrown);
2588
- throw thrown;
2589
- });
2445
+ const hCtx = c as HoneyContext<TEnv>
2446
+ if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
2447
+ throw thrown
2448
+ })
2590
2449
  }
2591
- return result;
2450
+ return result
2592
2451
  } catch (thrown) {
2593
- const hCtx = c as HoneyContext<TEnv>;
2594
- if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown);
2595
- throw thrown;
2452
+ const hCtx = c as HoneyContext<TEnv>
2453
+ if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
2454
+ throw thrown
2596
2455
  }
2597
- });
2456
+ })
2598
2457
  }
2599
- const result = handler._compiled(ctx);
2600
- if (result instanceof Promise) return result.then(after, onError);
2601
- return after(result);
2458
+ const result = handler._compiled(ctx)
2459
+ if (result instanceof Promise) return result.then(after, onError)
2460
+ return after(result)
2602
2461
  }
2603
2462
 
2604
- const scopedForPath = this._filterScopedForPath(handler.rp);
2605
- const chainMw = this._chainMiddlewares;
2606
- const handlerHasChain =
2607
- chainMw.length > 0 && chainMw.every((mw, i) => handler.mw[i] === mw);
2463
+ const scopedForPath = this._filterScopedForPath(handler.rp)
2464
+ const chainMw = this._chainMiddlewares
2465
+ const handlerHasChain = chainMw.length > 0 && chainMw.every((mw, i) => handler.mw[i] === mw)
2608
2466
  /*
2609
2467
  * Ordering: [global → chain → scoped → handler-route-specific]
2610
2468
  * When handlerHasChain, handler.mw = [chain..., routeSpecific...].
@@ -2617,22 +2475,19 @@ export class Honey<
2617
2475
  ...scopedForPath,
2618
2476
  ...handler.mw.slice(chainMw.length),
2619
2477
  ]
2620
- : [...this._globalMiddlewares, ...chainMw, ...scopedForPath, ...handler.mw];
2478
+ : [...this._globalMiddlewares, ...chainMw, ...scopedForPath, ...handler.mw]
2621
2479
 
2622
2480
  if (hasTelemetryMw) {
2623
- const onMw = this._telemetry?.onMiddleware;
2481
+ const onMw = this._telemetry?.onMiddleware
2624
2482
  if (onMw) {
2625
2483
  allMiddlewares = allMiddlewares.map((mw) => {
2626
- const name = mw.name || "anonymous";
2484
+ const name = mw.name || "anonymous"
2627
2485
  const wrapped: RuntimeMiddleware = async (wCtx, wNext) => {
2628
- const mwStart = performance.now();
2486
+ const mwStart = performance.now()
2629
2487
  try {
2630
- const res = await mw(wCtx, wNext);
2631
- safeFire(
2632
- () => onMw({ duration: performance.now() - mwStart, name }),
2633
- log,
2634
- );
2635
- return res;
2488
+ const res = await mw(wCtx, wNext)
2489
+ safeFire(() => onMw({ duration: performance.now() - mwStart, name }), log)
2490
+ return res
2636
2491
  } catch (error) {
2637
2492
  safeFire(
2638
2493
  () =>
@@ -2642,50 +2497,46 @@ export class Honey<
2642
2497
  name,
2643
2498
  }),
2644
2499
  log,
2645
- );
2646
- throw error;
2500
+ )
2501
+ throw error
2647
2502
  }
2648
- };
2649
- return wrapped;
2650
- });
2503
+ }
2504
+ return wrapped
2505
+ })
2651
2506
  }
2652
2507
  }
2653
2508
 
2654
2509
  if (hasInputValidation) {
2655
- const schemas = handler.iv;
2510
+ const schemas = handler.iv
2656
2511
  if (schemas) {
2657
2512
  const inputMw: RuntimeMiddleware = async (inputCtx, inputNext) => {
2658
- const validated = await validateInput(
2659
- schemas,
2660
- inputCtx["req"] as Request,
2661
- params,
2662
- );
2663
- return inputNext({ input: validated });
2664
- };
2665
- allMiddlewares.push(inputMw);
2513
+ const validated = await validateInput(schemas, inputCtx["req"] as Request, params)
2514
+ return inputNext({ input: validated })
2515
+ }
2516
+ allMiddlewares.push(inputMw)
2666
2517
  }
2667
2518
  }
2668
2519
 
2669
2520
  return executeChain(allMiddlewares, ctx, (finalCtx) => {
2670
2521
  try {
2671
- const result = handler.fn(finalCtx);
2522
+ const result = handler.fn(finalCtx)
2672
2523
  if (result instanceof Promise) {
2673
2524
  return result.catch((thrown: unknown) => {
2674
- const hCtx = finalCtx as HoneyContext<TEnv>;
2675
- if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown);
2676
- throw thrown;
2677
- });
2525
+ const hCtx = finalCtx as HoneyContext<TEnv>
2526
+ if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
2527
+ throw thrown
2528
+ })
2678
2529
  }
2679
- return result;
2530
+ return result
2680
2531
  } catch (thrown) {
2681
- const hCtx = finalCtx as HoneyContext<TEnv>;
2682
- if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown);
2683
- throw thrown;
2532
+ const hCtx = finalCtx as HoneyContext<TEnv>
2533
+ if (hCtx._errorToResponse) return hCtx._errorToResponse(thrown)
2534
+ throw thrown
2684
2535
  }
2685
- }).then(after, onError);
2536
+ }).then(after, onError)
2686
2537
  } catch (thrown) {
2687
2538
  /* safety net — middleware-level errors (input validation, middleware crash) */
2688
- return onError(thrown);
2539
+ return onError(thrown)
2689
2540
  }
2690
2541
  }
2691
2542
 
@@ -2700,17 +2551,11 @@ export class Honey<
2700
2551
  const validateOut =
2701
2552
  this._outputValidation === "always" ||
2702
2553
  (this._outputValidation === "dev" &&
2703
- (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env
2704
- ?.NODE_ENV !== "production")
2705
- if (
2706
- handler.os &&
2707
- validateOut &&
2708
- response.body !== null &&
2709
- !ctx._isErrorResponse
2710
- ) {
2711
- return this._validateThenFinish(fc, method, path, handler, ctx, response);
2554
+ (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV !== "production")
2555
+ if (handler.os && validateOut && response.body !== null && !ctx._isErrorResponse) {
2556
+ return this._validateThenFinish(fc, method, path, handler, ctx, response)
2712
2557
  }
2713
- return this._finishMatched(fc, method, path, handler, ctx, response);
2558
+ return this._finishMatched(fc, method, path, handler, ctx, response)
2714
2559
  }
2715
2560
 
2716
2561
  private async _validateThenFinish(
@@ -2721,31 +2566,31 @@ export class Honey<
2721
2566
  ctx: HoneyContext<TEnv>,
2722
2567
  response: Response,
2723
2568
  ): Promise<Response> {
2724
- const ct = response.headers.get("content-type");
2569
+ const ct = response.headers.get("content-type")
2725
2570
 
2726
2571
  /* content-type mismatch check */
2727
2572
  if (ct && handler.os) {
2728
- const declaredTypes = Object.keys(handler.os);
2729
- const matches = declaredTypes.some((t) => ct.startsWith(t));
2573
+ const declaredTypes = Object.keys(handler.os)
2574
+ const matches = declaredTypes.some((t) => ct.startsWith(t))
2730
2575
  if (!matches) {
2731
2576
  throw new HoneyError({
2732
2577
  errorKey: EK.output_content_type_mismatch,
2733
2578
  status: SK.internal_server_error,
2734
- });
2579
+ })
2735
2580
  }
2736
2581
  }
2737
2582
 
2738
2583
  /* JSON schema validation — read original, return clone (Bun clone() drains original) */
2739
2584
  if (ct?.startsWith("application/json") && handler.ov) {
2740
- const sk = codeToStatusKey[response.status];
2585
+ const sk = codeToStatusKey[response.status]
2741
2586
  if (sk) {
2742
- const forReturn = response.clone();
2743
- const data: unknown = await response.json();
2744
- await handler.ov(sk, data);
2745
- response = forReturn;
2587
+ const forReturn = response.clone()
2588
+ const data: unknown = await response.json()
2589
+ await handler.ov(sk, data)
2590
+ response = forReturn
2746
2591
  }
2747
2592
  }
2748
- return this._finishMatched(fc, method, path, handler, ctx, response);
2593
+ return this._finishMatched(fc, method, path, handler, ctx, response)
2749
2594
  }
2750
2595
 
2751
2596
  private _finishMatched(
@@ -2758,19 +2603,19 @@ export class Honey<
2758
2603
  ): Response {
2759
2604
  /* taps — fire after successful handler, non-blocking */
2760
2605
  if (this._taps !== null && !ctx._isErrorResponse) {
2761
- const taps = this._taps;
2762
- const log = fc.log;
2606
+ const taps = this._taps
2607
+ const log = fc.log
2763
2608
 
2764
2609
  /* meta-driven taps — fire for each registered key found in route meta */
2765
2610
  if (handler.mt !== null) {
2766
2611
  for (const [key, tapFn] of taps) {
2767
- const metaValue = handler.mt[key];
2612
+ const metaValue = handler.mt[key]
2768
2613
  if (metaValue !== undefined) {
2769
2614
  ctx.background(
2770
2615
  Promise.resolve()
2771
2616
  .then(() => tapFn(ctx, metaValue))
2772
2617
  .catch((e) => log?.warn?.("tap failed", key, e)),
2773
- );
2618
+ )
2774
2619
  }
2775
2620
  }
2776
2621
  }
@@ -2778,33 +2623,33 @@ export class Honey<
2778
2623
  /* dynamic taps — fire for each c.tap() call */
2779
2624
  if (ctx._pendingTaps !== null) {
2780
2625
  for (const pending of ctx._pendingTaps) {
2781
- const tapFn = taps.get(pending.key);
2626
+ const tapFn = taps.get(pending.key)
2782
2627
  if (tapFn !== undefined) {
2783
2628
  ctx.background(
2784
2629
  Promise.resolve()
2785
2630
  .then(() => tapFn(ctx, pending.payload))
2786
2631
  .catch((e) => log?.warn?.("tap failed", pending.key, e)),
2787
- );
2632
+ )
2788
2633
  }
2789
2634
  }
2790
- ctx._pendingTaps = null;
2635
+ ctx._pendingTaps = null
2791
2636
  }
2792
2637
  }
2793
2638
 
2794
2639
  if (this._telemetry !== null) {
2795
2640
  try {
2796
- const duration = performance.now() - fc.startTime;
2641
+ const duration = performance.now() - fc.startTime
2797
2642
  this._telemetry.onHandler?.({
2798
2643
  duration,
2799
2644
  method,
2800
2645
  path,
2801
2646
  status: response.status,
2802
- });
2647
+ })
2803
2648
  this._telemetry.onResponse?.({
2804
2649
  duration,
2805
2650
  req: fc.request,
2806
2651
  status: response.status,
2807
- });
2652
+ })
2808
2653
  } catch {
2809
2654
  /* telemetry must never crash the response path */
2810
2655
  }
@@ -2814,9 +2659,9 @@ export class Honey<
2814
2659
  return new Response(null, {
2815
2660
  headers: response.headers,
2816
2661
  status: response.status,
2817
- });
2662
+ })
2818
2663
  }
2819
- return response;
2664
+ return response
2820
2665
  }
2821
2666
  }
2822
2667
 
@@ -2832,19 +2677,9 @@ export class Honey<
2832
2677
  * Narrowed methods return TypedResponse<CT, K> for compile-time CT+SK safety.
2833
2678
  */
2834
2679
  /** Resolve body type: schema → InferOutput, plain type → use as-is */
2835
- type ResolveBody<T, TBody> = TBody extends "infer"
2836
- ? T extends StandardSchemaLike
2837
- ? InferOutput<T>
2838
- : T
2839
- : TBody;
2840
-
2841
- type NarrowMethod<
2842
- TRes,
2843
- TSchemas,
2844
- Method extends string,
2845
- CT extends string,
2846
- TBody,
2847
- > = [TSchemas] extends [never]
2680
+ type ResolveBody<T, TBody> = TBody extends "infer" ? (T extends StandardSchemaLike ? InferOutput<T> : T) : TBody
2681
+
2682
+ type NarrowMethod<TRes, TSchemas, Method extends string, CT extends string, TBody> = [TSchemas] extends [never]
2848
2683
  ? Omit<TRes, Method>
2849
2684
  : TSchemas extends Record<string, unknown>
2850
2685
  ? Omit<TRes, Method> & {
@@ -2852,22 +2687,18 @@ type NarrowMethod<
2852
2687
  statusKey: K,
2853
2688
  body: ResolveBody<TSchemas[K], TBody>,
2854
2689
  opts?: ResponseOptions,
2855
- ) => TypedResponse<CT, K>;
2690
+ ) => TypedResponse<CT, K>
2856
2691
  }
2857
- : Omit<TRes, Method>;
2692
+ : Omit<TRes, Method>
2858
2693
 
2859
2694
  /**
2860
2695
  * SSE uses a callback signature, not statusKey+body like other methods.
2861
2696
  * Gate presence on text/event-stream declaration, preserve original signature.
2862
2697
  */
2863
- type NarrowSSE<TOutput> = [
2864
- ExtractSchemas<TOutput, "text/event-stream">,
2865
- ] extends [never]
2866
- ? {}
2867
- : { sse: HoneyRes["sse"] };
2698
+ type NarrowSSE<TOutput> = [ExtractSchemas<TOutput, "text/event-stream">] extends [never] ? {} : { sse: HoneyRes["sse"] }
2868
2699
 
2869
2700
  /** Universal methods — always available, not gated by output declaration */
2870
- type UniversalRes = Pick<HoneyRes, "noContent" | "raw" | "redirect" | "stream">;
2701
+ type UniversalRes = Pick<HoneyRes, "noContent" | "raw" | "redirect" | "stream">
2871
2702
 
2872
2703
  /**
2873
2704
  * Constrain ctx.res when output schemas are declared.
@@ -2880,41 +2711,11 @@ type ApplyOutput<TCtx, TOutput> = [keyof TOutput] extends [never]
2880
2711
  ? TCtx
2881
2712
  : Omit<TCtx, "res"> & {
2882
2713
  readonly res: UniversalRes &
2883
- NarrowMethod<
2884
- {},
2885
- ExtractSchemas<TOutput, "application/json">,
2886
- "json",
2887
- "application/json",
2888
- "infer"
2889
- > &
2890
- NarrowMethod<
2891
- {},
2892
- ExtractSchemas<TOutput, "text/plain">,
2893
- "text",
2894
- "text/plain",
2895
- string
2896
- > &
2897
- NarrowMethod<
2898
- {},
2899
- ExtractSchemas<TOutput, "text/html">,
2900
- "html",
2901
- "text/html",
2902
- string
2903
- > &
2904
- NarrowMethod<
2905
- {},
2906
- ExtractSchemas<TOutput, "application/xml">,
2907
- "xml",
2908
- "application/xml",
2909
- string
2910
- > &
2911
- NarrowMethod<
2912
- {},
2913
- ExtractSchemas<TOutput, "text/csv">,
2914
- "csv",
2915
- "text/csv",
2916
- string
2917
- > &
2714
+ NarrowMethod<{}, ExtractSchemas<TOutput, "application/json">, "json", "application/json", "infer"> &
2715
+ NarrowMethod<{}, ExtractSchemas<TOutput, "text/plain">, "text", "text/plain", string> &
2716
+ NarrowMethod<{}, ExtractSchemas<TOutput, "text/html">, "html", "text/html", string> &
2717
+ NarrowMethod<{}, ExtractSchemas<TOutput, "application/xml">, "xml", "application/xml", string> &
2718
+ NarrowMethod<{}, ExtractSchemas<TOutput, "text/csv">, "csv", "text/csv", string> &
2918
2719
  NarrowMethod<
2919
2720
  {},
2920
2721
  ExtractSchemas<TOutput, "application/octet-stream">,
@@ -2922,11 +2723,11 @@ type ApplyOutput<TCtx, TOutput> = [keyof TOutput] extends [never]
2922
2723
  "application/octet-stream",
2923
2724
  ArrayBuffer | Uint8Array<ArrayBuffer>
2924
2725
  > &
2925
- NarrowSSE<TOutput>;
2926
- };
2726
+ NarrowSSE<TOutput>
2727
+ }
2927
2728
 
2928
2729
  /** Public alias for codegen — applies output schema constraints to a context type */
2929
- export type WithOutput<TCtx, TOutput> = ApplyOutput<TCtx, TOutput>;
2730
+ export type WithOutput<TCtx, TOutput> = ApplyOutput<TCtx, TOutput>
2930
2731
 
2931
2732
  /** @internal — tuple entry describing a scoped middleware at the type level */
2932
2733
  type ScopedMwEntry = { readonly path: string; readonly adds: unknown }
@@ -2939,10 +2740,7 @@ type ScopedMwEntry = { readonly path: string; readonly adds: unknown }
2939
2740
  * the user passes a widened variable), skip the entry via `string extends Head["path"] ? {}`.
2940
2741
  * This prevents the always-true `'/anywhere' extends string` from polluting every route.
2941
2742
  */
2942
- type ApplyScoped<
2943
- TScopedMw extends readonly ScopedMwEntry[],
2944
- TFullPath extends string,
2945
- > = TScopedMw extends readonly [
2743
+ type ApplyScoped<TScopedMw extends readonly ScopedMwEntry[], TFullPath extends string> = TScopedMw extends readonly [
2946
2744
  infer Head extends ScopedMwEntry,
2947
2745
  ...infer Rest extends readonly ScopedMwEntry[],
2948
2746
  ]
@@ -2953,7 +2751,7 @@ type ApplyScoped<
2953
2751
  : TFullPath extends `${Head["path"] & string}/${string}`
2954
2752
  ? Head["adds"]
2955
2753
  : {}) &
2956
- ApplyScoped<Rest, TFullPath>
2754
+ ApplyScoped<Rest, TFullPath>
2957
2755
  : {}
2958
2756
 
2959
2757
  /** @internal — runtime entry for a scoped middleware */
@@ -2970,13 +2768,12 @@ type ApplyParams<TCtx, TParams> = [keyof TParams] extends [string]
2970
2768
  ? string extends keyof TParams
2971
2769
  ? TCtx
2972
2770
  : TCtx & { readonly params: TParams }
2973
- : TCtx;
2771
+ : TCtx
2974
2772
 
2975
2773
  /** Typed tap method — constrained to registered tap keys when TTaps is non-empty */
2976
- type TypedTap<TTaps extends Record<string, unknown>> =
2977
- [keyof TTaps] extends [never]
2978
- ? { tap(key: string, payload: unknown): void }
2979
- : { tap<K extends string & keyof TTaps>(key: K, payload: TTaps[K]): void };
2774
+ type TypedTap<TTaps extends Record<string, unknown>> = [keyof TTaps] extends [never]
2775
+ ? { tap(key: string, payload: unknown): void }
2776
+ : { tap<K extends string & keyof TTaps>(key: K, payload: TTaps[K]): void }
2980
2777
 
2981
2778
  /** Build the full handler context: base ctx + params + input + meta + errors + taps + output-constrained methods */
2982
2779
  type HandlerCtx<
@@ -2990,48 +2787,56 @@ type HandlerCtx<
2990
2787
  TPath extends string = string,
2991
2788
  TTaps extends Record<string, unknown> = {},
2992
2789
  > = ApplyOutput<
2993
- ApplyParams<
2994
- [keyof TInput] extends [never] ? TCtx : TCtx & { input: TInput },
2995
- TParams
2996
- > & {
2997
- readonly meta: Readonly<Omit<TAccMeta, "openApi">>;
2998
- readonly routePattern: TPath;
2790
+ ApplyParams<[keyof TInput] extends [never] ? TCtx : TCtx & { input: TInput }, TParams> & {
2791
+ readonly meta: Readonly<Omit<TAccMeta, "openApi">>
2792
+ readonly routePattern: TPath
2999
2793
  } & ([TErrorFactory] extends [never]
3000
2794
  ? {}
3001
2795
  : [TErrorKeys] extends [never]
3002
2796
  ? { readonly errors: TErrorFactory }
3003
2797
  : {
3004
- readonly errors: Pick<
3005
- TErrorFactory,
3006
- TErrorKeys & keyof TErrorFactory
3007
- >;
3008
- })
3009
- & TypedTap<TTaps>,
2798
+ readonly errors: Pick<TErrorFactory, TErrorKeys & keyof TErrorFactory>
2799
+ }) &
2800
+ TypedTap<TTaps>,
3010
2801
  TOutput
3011
- >;
2802
+ >
3012
2803
 
3013
2804
  /** @internal — exposes private Honey members for RouteBuilder/WSRouteBuilder access */
3014
2805
  type HoneyInternal = {
3015
- _factory: unknown;
3016
- _markWsRoutes(): void;
3017
- _registerStatic(key: string, handler: RouteHandler): void;
3018
- };
2806
+ _factory: unknown
2807
+ _markWsRoutes(): void
2808
+ _registerStatic(key: string, handler: RouteHandler): void
2809
+ }
3019
2810
 
3020
2811
  type RouteBuilderState<TParent> = {
3021
- boundaryErrorKey: string | null;
3022
- errorKeys: Set<string>;
3023
- extraMethods: (HttpMethod | "ALL")[] | null;
3024
- handlerMap: Record<string, RouteHandler> | null;
3025
- inputSchemas: InputSchemasDef | null;
3026
- meta: Record<string, unknown> | null;
3027
- method: HttpMethod | "ALL";
3028
- middlewares: RuntimeMiddleware[];
3029
- outputSchemas: OutputSchemaDef | null;
3030
- parent: TParent;
3031
- parentMiddlewares: RuntimeMiddleware[];
3032
- path: string;
3033
- root: TreeNode;
3034
- };
2812
+ boundaryErrorKey: string | null
2813
+ errorKeys: Set<string>
2814
+ extraMethods: (HttpMethod | "ALL")[] | null
2815
+ handlerMap: Record<string, RouteHandler> | null
2816
+ inputSchemas: InputSchemasDef | null
2817
+ meta: Record<string, unknown> | null
2818
+ /** meta contributed by middleware — kept apart so explicit .meta() always outranks it */
2819
+ mwMeta: Record<string, unknown> | null
2820
+ method: HttpMethod | "ALL"
2821
+ middlewares: RuntimeMiddleware[]
2822
+ outputSchemas: OutputSchemaDef | null
2823
+ parent: TParent
2824
+ parentMiddlewares: RuntimeMiddleware[]
2825
+ path: string
2826
+ root: TreeNode
2827
+ }
2828
+
2829
+ /**
2830
+ * Route meta = middleware-contributed meta, overlaid by everything stated explicitly
2831
+ * (chain `.meta()`, then route `.meta()`). Explicit always wins.
2832
+ */
2833
+ function mergeContributedMeta(
2834
+ contributed: Record<string, unknown> | null | undefined,
2835
+ explicit: Record<string, unknown> | null,
2836
+ ): Record<string, unknown> | null {
2837
+ if (!contributed) return explicit ? Object.freeze(explicit) : null
2838
+ return Object.freeze(explicit ? { ...contributed, ...explicit } : { ...contributed })
2839
+ }
3035
2840
 
3036
2841
  /** Honey.TMeta defaults to never; never & X is never, so start acc meta at {}. */
3037
2842
  type InitAccMeta<TMeta> = [TMeta] extends [never] ? {} : TMeta
@@ -3093,9 +2898,9 @@ type HandlerReturn<
3093
2898
  TBasePath,
3094
2899
  TTaps,
3095
2900
  TScopedMw
3096
- >;
2901
+ >
3097
2902
 
3098
- type OneShotKey = "boundary" | "errors" | "input" | "meta" | "output";
2903
+ type OneShotKey = "boundary" | "errors" | "input" | "meta" | "output"
3099
2904
 
3100
2905
  type BuilderChain<
3101
2906
  TEnv,
@@ -3117,12 +2922,26 @@ type BuilderChain<
3117
2922
  TScopedMw extends readonly ScopedMwEntry[] = [],
3118
2923
  > = Omit<
3119
2924
  RouteBuilder<
3120
- TEnv, TCtx, TInput, TErrorKeys, TOutput, TPath, TMethod,
3121
- TRoutes, TUsed, TBaseCtx, TMeta, TAccMeta, TErrorFactory,
3122
- TDefaultErrors, TBasePath, TTaps, TScopedMw
2925
+ TEnv,
2926
+ TCtx,
2927
+ TInput,
2928
+ TErrorKeys,
2929
+ TOutput,
2930
+ TPath,
2931
+ TMethod,
2932
+ TRoutes,
2933
+ TUsed,
2934
+ TBaseCtx,
2935
+ TMeta,
2936
+ TAccMeta,
2937
+ TErrorFactory,
2938
+ TDefaultErrors,
2939
+ TBasePath,
2940
+ TTaps,
2941
+ TScopedMw
3123
2942
  >,
3124
2943
  TUsed & OneShotKey
3125
- >;
2944
+ >
3126
2945
 
3127
2946
  class RouteBuilder<
3128
2947
  TEnv,
@@ -3144,35 +2963,15 @@ class RouteBuilder<
3144
2963
  TScopedMw extends readonly ScopedMwEntry[] = [],
3145
2964
  > {
3146
2965
  private _s: RouteBuilderState<
3147
- Honey<
3148
- TEnv,
3149
- TBaseCtx,
3150
- TRoutes,
3151
- TMeta,
3152
- TErrorFactory,
3153
- TDefaultErrors,
3154
- TBasePath,
3155
- TTaps,
3156
- TScopedMw
3157
- >
3158
- >;
2966
+ Honey<TEnv, TBaseCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
2967
+ >
3159
2968
 
3160
2969
  constructor(
3161
2970
  state: RouteBuilderState<
3162
- Honey<
3163
- TEnv,
3164
- TBaseCtx,
3165
- TRoutes,
3166
- TMeta,
3167
- TErrorFactory,
3168
- TDefaultErrors,
3169
- TBasePath,
3170
- TTaps,
3171
- TScopedMw
3172
- >
2971
+ Honey<TEnv, TBaseCtx, TRoutes, TMeta, TErrorFactory, TDefaultErrors, TBasePath, TTaps, TScopedMw>
3173
2972
  >,
3174
2973
  ) {
3175
- this._s = state;
2974
+ this._s = state
3176
2975
  }
3177
2976
 
3178
2977
  errors<
@@ -3199,11 +2998,9 @@ class RouteBuilder<
3199
2998
  TBasePath,
3200
2999
  TTaps,
3201
3000
  TScopedMw
3202
- >;
3001
+ >
3203
3002
  errors<
3204
- TKeys extends [TErrorFactory] extends [never]
3205
- ? never
3206
- : Exclude<keyof TErrorFactory & string, TDefaultErrors>,
3003
+ TKeys extends ([TErrorFactory] extends [never] ? never : Exclude<keyof TErrorFactory & string, TDefaultErrors>),
3207
3004
  >(
3208
3005
  ...keys: TKeys[]
3209
3006
  ): BuilderChain<
@@ -3224,7 +3021,7 @@ class RouteBuilder<
3224
3021
  TBasePath,
3225
3022
  TTaps,
3226
3023
  TScopedMw
3227
- >;
3024
+ >
3228
3025
  errors(
3229
3026
  ...args: unknown[]
3230
3027
  ): BuilderChain<
@@ -3246,12 +3043,9 @@ class RouteBuilder<
3246
3043
  TTaps,
3247
3044
  TScopedMw
3248
3045
  > {
3249
- const keys =
3250
- typeof args[0] === "object" && args[0] !== null
3251
- ? (args.slice(1) as string[])
3252
- : (args as string[]);
3046
+ const keys = typeof args[0] === "object" && args[0] !== null ? (args.slice(1) as string[]) : (args as string[])
3253
3047
  for (const k of keys) {
3254
- this._s.errorKeys.add(k);
3048
+ this._s.errorKeys.add(k)
3255
3049
  }
3256
3050
  return new RouteBuilder<
3257
3051
  TEnv,
@@ -3274,13 +3068,11 @@ class RouteBuilder<
3274
3068
  >({
3275
3069
  ...this._s,
3276
3070
  errorKeys: this._s.errorKeys,
3277
- });
3071
+ })
3278
3072
  }
3279
3073
 
3280
3074
  boundary<
3281
- TKey extends [TErrorFactory] extends [never]
3282
- ? never
3283
- : Exclude<keyof TErrorFactory & string, TDefaultErrors>,
3075
+ TKey extends ([TErrorFactory] extends [never] ? never : Exclude<keyof TErrorFactory & string, TDefaultErrors>),
3284
3076
  >(
3285
3077
  key: TKey,
3286
3078
  ): BuilderChain<
@@ -3302,8 +3094,8 @@ class RouteBuilder<
3302
3094
  TTaps,
3303
3095
  TScopedMw
3304
3096
  > {
3305
- this._s.boundaryErrorKey = key;
3306
- this._s.errorKeys.add(key);
3097
+ this._s.boundaryErrorKey = key
3098
+ this._s.errorKeys.add(key)
3307
3099
  return new RouteBuilder<
3308
3100
  TEnv,
3309
3101
  TCtx,
@@ -3326,7 +3118,7 @@ class RouteBuilder<
3326
3118
  ...this._s,
3327
3119
  boundaryErrorKey: key,
3328
3120
  errorKeys: this._s.errorKeys,
3329
- });
3121
+ })
3330
3122
  }
3331
3123
 
3332
3124
  handler(
@@ -3361,17 +3153,17 @@ class RouteBuilder<
3361
3153
  TTaps,
3362
3154
  TScopedMw
3363
3155
  > {
3364
- let ov: OutputValidator | null = null;
3365
- const outputSchemas = this._s.outputSchemas;
3156
+ let ov: OutputValidator | null = null
3157
+ const outputSchemas = this._s.outputSchemas
3366
3158
  if (outputSchemas) {
3367
- const jsonSchemas = outputSchemas["application/json"];
3159
+ const jsonSchemas = outputSchemas["application/json"]
3368
3160
  if (jsonSchemas) {
3369
3161
  ov = async (statusKey: string, data: unknown) => {
3370
- const schema = jsonSchemas[statusKey as keyof typeof jsonSchemas];
3162
+ const schema = jsonSchemas[statusKey as keyof typeof jsonSchemas]
3371
3163
  if (schema) {
3372
- await validateOutput(schema, statusKey, data);
3164
+ await validateOutput(schema, statusKey, data)
3373
3165
  }
3374
- };
3166
+ }
3375
3167
  }
3376
3168
  }
3377
3169
 
@@ -3387,39 +3179,36 @@ class RouteBuilder<
3387
3179
  ek: this._s.errorKeys,
3388
3180
  fn: fn as (ctx: unknown) => Response | Promise<Response>,
3389
3181
  iv: this._s.inputSchemas,
3390
- mt: this._s.meta ? Object.freeze(this._s.meta) : null,
3182
+ mt: mergeContributedMeta(this._s.mwMeta, this._s.meta),
3391
3183
  mw: [...this._s.parentMiddlewares, ...this._s.middlewares],
3392
3184
  os: this._s.outputSchemas,
3393
3185
  ov,
3394
3186
  rp: this._s.path,
3395
- };
3187
+ }
3396
3188
 
3397
3189
  /* patch mode: if handler map has this route, patch existing handler instead of insertRoute */
3398
- const handlerMap = this._s.handlerMap;
3190
+ const handlerMap = this._s.handlerMap
3399
3191
  if (handlerMap) {
3400
- const key = `${this._s.method} ${this._s.path}`;
3401
- const existing = handlerMap[key];
3192
+ const key = `${this._s.method} ${this._s.path}`
3193
+ const existing = handlerMap[key]
3402
3194
  if (existing) {
3403
- existing.fn = routeHandler.fn;
3404
- existing.mw = routeHandler.mw;
3405
- existing.iv = routeHandler.iv;
3406
- existing.os = routeHandler.os;
3407
- existing.ov = routeHandler.ov;
3195
+ existing.fn = routeHandler.fn
3196
+ existing.mw = routeHandler.mw
3197
+ existing.iv = routeHandler.iv
3198
+ existing.os = routeHandler.os
3199
+ existing.ov = routeHandler.ov
3408
3200
  /* pre-compute filtered error factory */
3409
- const factory = (this._s.parent as unknown as HoneyInternal)._factory;
3201
+ const factory = (this._s.parent as unknown as HoneyInternal)._factory
3410
3202
  if (factory !== null && existing.ek.size > 0) {
3411
- const ef = Object.create(null) as Record<
3412
- string,
3413
- (...args: never[]) => unknown
3414
- >;
3415
- const fac = factory as Record<string, (...args: never[]) => unknown>;
3416
- for (const k of existing.ek) {
3417
- const factoryFn = fac[k]
3418
- if (factoryFn !== undefined) {
3419
- ef[k] = factoryFn
3420
- }
3203
+ const ef = Object.create(null) as Record<string, (...args: never[]) => unknown>
3204
+ const fac = factory as Record<string, (...args: never[]) => unknown>
3205
+ for (const k of existing.ek) {
3206
+ const factoryFn = fac[k]
3207
+ if (factoryFn !== undefined) {
3208
+ ef[k] = factoryFn
3421
3209
  }
3422
- existing.ef = Object.freeze(ef);
3210
+ }
3211
+ existing.ef = Object.freeze(ef)
3423
3212
  }
3424
3213
  return this._s.parent as HandlerReturn<
3425
3214
  TEnv,
@@ -3438,28 +3227,25 @@ class RouteBuilder<
3438
3227
  TBasePath,
3439
3228
  TTaps,
3440
3229
  TScopedMw
3441
- >;
3230
+ >
3442
3231
  }
3443
3232
  }
3444
3233
 
3445
- insertRoute(this._s.root, this._s.method, this._s.path, routeHandler);
3234
+ insertRoute(this._s.root, this._s.method, this._s.path, routeHandler)
3446
3235
 
3447
3236
  /* .on() extra methods — insert same handler for each additional method */
3448
3237
  if (this._s.extraMethods) {
3449
3238
  for (const m of this._s.extraMethods) {
3450
- insertRoute(this._s.root, m, this._s.path, routeHandler);
3239
+ insertRoute(this._s.root, m, this._s.path, routeHandler)
3451
3240
  if (!this._s.path.includes(":") && !this._s.path.includes("*")) {
3452
- (this._s.parent as unknown as HoneyInternal)._registerStatic(`${m} ${this._s.path}`, routeHandler);
3241
+ ;(this._s.parent as unknown as HoneyInternal)._registerStatic(`${m} ${this._s.path}`, routeHandler)
3453
3242
  }
3454
3243
  }
3455
3244
  }
3456
3245
 
3457
3246
  /* populate static route map for O(1) lookup on non-parameterized routes */
3458
3247
  if (!this._s.path.includes(":") && !this._s.path.includes("*")) {
3459
- (this._s.parent as unknown as HoneyInternal)._registerStatic(
3460
- `${this._s.method} ${this._s.path}`,
3461
- routeHandler,
3462
- );
3248
+ ;(this._s.parent as unknown as HoneyInternal)._registerStatic(`${this._s.method} ${this._s.path}`, routeHandler)
3463
3249
  }
3464
3250
  return this._s.parent as HandlerReturn<
3465
3251
  TEnv,
@@ -3478,7 +3264,7 @@ class RouteBuilder<
3478
3264
  TBasePath,
3479
3265
  TTaps,
3480
3266
  TScopedMw
3481
- >;
3267
+ >
3482
3268
  }
3483
3269
 
3484
3270
  proxy(
@@ -3513,7 +3299,7 @@ class RouteBuilder<
3513
3299
  TTaps,
3514
3300
  TScopedMw
3515
3301
  > {
3516
- const proxyHandler = createProxyHandler(config);
3302
+ const proxyHandler = createProxyHandler(config)
3517
3303
  return this.handler(
3518
3304
  proxyHandler as (
3519
3305
  ctx: HandlerCtx<
@@ -3528,7 +3314,7 @@ class RouteBuilder<
3528
3314
  TTaps
3529
3315
  >,
3530
3316
  ) => Promise<TypedResponse>,
3531
- );
3317
+ )
3532
3318
  }
3533
3319
 
3534
3320
  input<TSchemas extends InputSchemasDef>(
@@ -3573,13 +3359,10 @@ class RouteBuilder<
3573
3359
  >({
3574
3360
  ...this._s,
3575
3361
  inputSchemas: schemas,
3576
- });
3362
+ })
3577
3363
  }
3578
3364
 
3579
- meta<
3580
- TRouteMeta extends Partial<DefaultMeta> &
3581
- ([TMeta] extends [never] ? {} : TMeta),
3582
- >(
3365
+ meta<TRouteMeta extends Partial<DefaultMeta> & ([TMeta] extends [never] ? {} : TMeta)>(
3583
3366
  meta: TRouteMeta,
3584
3367
  ): BuilderChain<
3585
3368
  TEnv,
@@ -3628,7 +3411,7 @@ class RouteBuilder<
3628
3411
  }
3629
3412
  return merged
3630
3413
  })(),
3631
- });
3414
+ })
3632
3415
  }
3633
3416
 
3634
3417
  output<TOutputSchemas extends OutputSchemaDef>(
@@ -3673,7 +3456,7 @@ class RouteBuilder<
3673
3456
  >({
3674
3457
  ...this._s,
3675
3458
  outputSchemas: _schemas,
3676
- });
3459
+ })
3677
3460
  }
3678
3461
 
3679
3462
  use<TAdds>(
@@ -3699,7 +3482,7 @@ class RouteBuilder<
3699
3482
  > {
3700
3483
  if (mw.errors) {
3701
3484
  for (const k of mw.errors) {
3702
- this._s.errorKeys.add(k);
3485
+ this._s.errorKeys.add(k)
3703
3486
  }
3704
3487
  }
3705
3488
  return new RouteBuilder<
@@ -3723,21 +3506,23 @@ class RouteBuilder<
3723
3506
  >({
3724
3507
  ...this._s,
3725
3508
  middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
3726
- });
3509
+ mwMeta: mw.meta ? { ...this._s.mwMeta, ...mw.meta } : this._s.mwMeta,
3510
+ })
3727
3511
  }
3728
3512
  }
3729
3513
 
3730
3514
  type WSRouteBuilderState<TParent> = {
3731
- boundaryErrorKey: string | null;
3732
- errorKeys: Set<string>;
3733
- inputSchemas: InputSchemasDef | null;
3734
- meta: Record<string, unknown> | null;
3735
- middlewares: RuntimeMiddleware[];
3736
- parent: TParent;
3737
- parentMiddlewares: RuntimeMiddleware[];
3738
- path: string;
3739
- root: TreeNode;
3740
- };
3515
+ mwMeta?: Record<string, unknown> | null
3516
+ boundaryErrorKey: string | null
3517
+ errorKeys: Set<string>
3518
+ inputSchemas: InputSchemasDef | null
3519
+ meta: Record<string, unknown> | null
3520
+ middlewares: RuntimeMiddleware[]
3521
+ parent: TParent
3522
+ parentMiddlewares: RuntimeMiddleware[]
3523
+ path: string
3524
+ root: TreeNode
3525
+ }
3741
3526
 
3742
3527
  class WSRouteBuilder<
3743
3528
  TEnv,
@@ -3746,36 +3531,24 @@ class WSRouteBuilder<
3746
3531
  _TErrorKeys extends string = never,
3747
3532
  TParent extends { _markWsRoutes(): void } = { _markWsRoutes(): void },
3748
3533
  > {
3749
- private _s: WSRouteBuilderState<TParent>;
3534
+ private _s: WSRouteBuilderState<TParent>
3750
3535
 
3751
3536
  constructor(state: WSRouteBuilderState<TParent>) {
3752
- this._s = state;
3537
+ this._s = state
3753
3538
  }
3754
3539
 
3755
3540
  errors<TFactory extends Record<string, (...args: never[]) => unknown>>(
3756
3541
  factory: TFactory,
3757
3542
  ...keys: Array<keyof TFactory & string>
3758
- ): WSRouteBuilder<
3759
- TEnv,
3760
- TCtx,
3761
- TInput,
3762
- _TErrorKeys | (keyof TFactory & string),
3763
- TParent
3764
- > {
3765
- void factory;
3543
+ ): WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys | (keyof TFactory & string), TParent> {
3544
+ void factory
3766
3545
  for (const k of keys) {
3767
- this._s.errorKeys.add(k);
3546
+ this._s.errorKeys.add(k)
3768
3547
  }
3769
- return new WSRouteBuilder<
3770
- TEnv,
3771
- TCtx,
3772
- TInput,
3773
- _TErrorKeys | (keyof TFactory & string),
3774
- TParent
3775
- >({
3548
+ return new WSRouteBuilder<TEnv, TCtx, TInput, _TErrorKeys | (keyof TFactory & string), TParent>({
3776
3549
  ...this._s,
3777
3550
  errorKeys: this._s.errorKeys,
3778
- });
3551
+ })
3779
3552
  }
3780
3553
 
3781
3554
  handler(wsHandler: WSHandler<TCtx>): TParent {
@@ -3784,60 +3557,43 @@ class WSRouteBuilder<
3784
3557
  ek: this._s.errorKeys,
3785
3558
  fn: wsHandler as WSHandler<unknown>,
3786
3559
  iv: this._s.inputSchemas,
3787
- mt: this._s.meta ? Object.freeze(this._s.meta) : null,
3560
+ mt: mergeContributedMeta(this._s.mwMeta, this._s.meta),
3788
3561
  mw: [...this._s.parentMiddlewares, ...this._s.middlewares],
3789
3562
  rp: this._s.path,
3790
- };
3791
- insertWsRoute(this._s.root, this._s.path, routeHandler);
3792
- ;(this._s.parent as unknown as HoneyInternal)._markWsRoutes();
3793
- return this._s.parent;
3563
+ }
3564
+ insertWsRoute(this._s.root, this._s.path, routeHandler)
3565
+ ;(this._s.parent as unknown as HoneyInternal)._markWsRoutes()
3566
+ return this._s.parent
3794
3567
  }
3795
3568
 
3796
- input<
3797
- TSchemas extends Pick<InputSchemasDef, "cookies" | "headers" | "search">,
3798
- >(
3569
+ input<TSchemas extends Pick<InputSchemasDef, "cookies" | "headers" | "search">>(
3799
3570
  schemas: TSchemas,
3800
- ): WSRouteBuilder<
3801
- TEnv,
3802
- TCtx,
3803
- TInput & InferInputMap<TSchemas>,
3804
- _TErrorKeys,
3805
- TParent
3806
- > {
3807
- return new WSRouteBuilder<
3808
- TEnv,
3809
- TCtx,
3810
- TInput & InferInputMap<TSchemas>,
3811
- _TErrorKeys,
3812
- TParent
3813
- >({
3571
+ ): WSRouteBuilder<TEnv, TCtx, TInput & InferInputMap<TSchemas>, _TErrorKeys, TParent> {
3572
+ return new WSRouteBuilder<TEnv, TCtx, TInput & InferInputMap<TSchemas>, _TErrorKeys, TParent>({
3814
3573
  ...this._s,
3815
3574
  inputSchemas: schemas,
3816
- });
3575
+ })
3817
3576
  }
3818
3577
 
3819
3578
  meta(meta: Record<string, unknown>): this {
3820
- this._s.meta = meta;
3821
- return this;
3579
+ this._s.meta = meta
3580
+ return this
3822
3581
  }
3823
3582
 
3824
- use<TAdds>(
3825
- mw: MiddlewareFn<TCtx, TAdds>,
3826
- ): WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent> {
3583
+ use<TAdds>(mw: MiddlewareFn<TCtx, TAdds>): WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent> {
3827
3584
  if (mw.errors) {
3828
3585
  for (const k of mw.errors) {
3829
- this._s.errorKeys.add(k);
3586
+ this._s.errorKeys.add(k)
3830
3587
  }
3831
3588
  }
3832
- return new WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent>(
3833
- {
3834
- ...this._s,
3835
- middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
3836
- },
3837
- );
3589
+ return new WSRouteBuilder<TEnv, TCtx & TAdds, TInput, _TErrorKeys, TParent>({
3590
+ ...this._s,
3591
+ middlewares: [...this._s.middlewares, mw as RuntimeMiddleware],
3592
+ mwMeta: mw.meta ? { ...this._s.mwMeta, ...mw.meta } : this._s.mwMeta,
3593
+ })
3838
3594
  }
3839
3595
  }
3840
3596
 
3841
3597
  export function honey<TEnv>(): Honey<TEnv> {
3842
- return new Honey<TEnv>();
3598
+ return new Honey<TEnv>()
3843
3599
  }