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