@lovrozagar/honey 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +1596 -6
  2. package/dist/accepts.d.ts +6 -0
  3. package/dist/accepts.d.ts.map +1 -0
  4. package/dist/body-limit.d.ts +22 -0
  5. package/dist/body-limit.d.ts.map +1 -0
  6. package/dist/build/index.d.ts +42 -0
  7. package/dist/build/index.d.ts.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/client/error.d.ts +14 -0
  11. package/dist/client/error.d.ts.map +1 -0
  12. package/dist/client/http.d.ts +88 -0
  13. package/dist/client/http.d.ts.map +1 -0
  14. package/dist/client/index.d.ts +22 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/sdk.d.ts +34 -0
  17. package/dist/client/sdk.d.ts.map +1 -0
  18. package/dist/client/sse.d.ts +10 -0
  19. package/dist/client/sse.d.ts.map +1 -0
  20. package/dist/client/types.d.ts +124 -0
  21. package/dist/client/types.d.ts.map +1 -0
  22. package/dist/client/ws.d.ts +15 -0
  23. package/dist/client/ws.d.ts.map +1 -0
  24. package/dist/client-mcp/server.d.ts +10 -0
  25. package/dist/client-mcp/server.d.ts.map +1 -0
  26. package/dist/client-mcp/types.d.ts +16 -0
  27. package/dist/client-mcp/types.d.ts.map +1 -0
  28. package/dist/cloudflare/realtime.d.ts +30 -0
  29. package/dist/cloudflare/realtime.d.ts.map +1 -0
  30. package/dist/codegen-go-cli.d.ts +22 -0
  31. package/dist/codegen-go-cli.d.ts.map +1 -0
  32. package/dist/codegen-go.d.ts +29 -0
  33. package/dist/codegen-go.d.ts.map +1 -0
  34. package/dist/codegen-hash.d.ts +3 -0
  35. package/dist/codegen-hash.d.ts.map +1 -0
  36. package/dist/codegen-ir.d.ts +123 -0
  37. package/dist/codegen-ir.d.ts.map +1 -0
  38. package/dist/codegen-loaders.d.ts +12 -0
  39. package/dist/codegen-loaders.d.ts.map +1 -0
  40. package/dist/codegen-mcp.d.ts +13 -0
  41. package/dist/codegen-mcp.d.ts.map +1 -0
  42. package/dist/codegen-python.d.ts +21 -0
  43. package/dist/codegen-python.d.ts.map +1 -0
  44. package/dist/codegen-rust.d.ts +18 -0
  45. package/dist/codegen-rust.d.ts.map +1 -0
  46. package/dist/codegen-sanitize.d.ts +3 -0
  47. package/dist/codegen-sanitize.d.ts.map +1 -0
  48. package/dist/codegen-schema-naming.d.ts +22 -0
  49. package/dist/codegen-schema-naming.d.ts.map +1 -0
  50. package/dist/codegen.d.ts +178 -0
  51. package/dist/codegen.d.ts.map +1 -0
  52. package/dist/context.d.ts +56 -0
  53. package/dist/context.d.ts.map +1 -0
  54. package/dist/cookie-sign.d.ts +3 -0
  55. package/dist/cookie-sign.d.ts.map +1 -0
  56. package/dist/cookie.d.ts +12 -0
  57. package/dist/cookie.d.ts.map +1 -0
  58. package/dist/cors.d.ts +13 -0
  59. package/dist/cors.d.ts.map +1 -0
  60. package/dist/crypto.d.ts +2 -0
  61. package/dist/crypto.d.ts.map +1 -0
  62. package/dist/csrf.d.ts +9 -0
  63. package/dist/csrf.d.ts.map +1 -0
  64. package/dist/curl-logger.d.ts +33 -0
  65. package/dist/curl-logger.d.ts.map +1 -0
  66. package/dist/detect-runtime.d.ts +16 -0
  67. package/dist/detect-runtime.d.ts.map +1 -0
  68. package/dist/error.d.ts +21 -0
  69. package/dist/error.d.ts.map +1 -0
  70. package/dist/errors.d.ts +40 -0
  71. package/dist/errors.d.ts.map +1 -0
  72. package/dist/etag.d.ts +9 -0
  73. package/dist/etag.d.ts.map +1 -0
  74. package/dist/feature-load.d.ts +3 -0
  75. package/dist/feature-load.d.ts.map +1 -0
  76. package/dist/go-type-emitter.d.ts +33 -0
  77. package/dist/go-type-emitter.d.ts.map +1 -0
  78. package/dist/honey-response.d.ts +82 -0
  79. package/dist/honey-response.d.ts.map +1 -0
  80. package/dist/i18n-slot.d.ts +7 -0
  81. package/dist/i18n-slot.d.ts.map +1 -0
  82. package/dist/i18n.d.ts +26 -0
  83. package/dist/i18n.d.ts.map +1 -0
  84. package/dist/index.d.ts +470 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/init.d.ts +7 -0
  87. package/dist/init.d.ts.map +1 -0
  88. package/dist/input.d.ts +6 -0
  89. package/dist/input.d.ts.map +1 -0
  90. package/dist/ip-restrict.d.ts +12 -0
  91. package/dist/ip-restrict.d.ts.map +1 -0
  92. package/dist/logger.d.ts +45 -0
  93. package/dist/logger.d.ts.map +1 -0
  94. package/dist/meta-spec.d.ts +81 -0
  95. package/dist/meta-spec.d.ts.map +1 -0
  96. package/dist/middleware.d.ts +60 -0
  97. package/dist/middleware.d.ts.map +1 -0
  98. package/dist/node-request.d.ts +59 -0
  99. package/dist/node-request.d.ts.map +1 -0
  100. package/dist/node.d.ts +13 -0
  101. package/dist/node.d.ts.map +1 -0
  102. package/dist/openapi/register.d.ts +3 -0
  103. package/dist/openapi/register.d.ts.map +1 -0
  104. package/dist/openapi/scalar.d.ts +12 -0
  105. package/dist/openapi/scalar.d.ts.map +1 -0
  106. package/dist/openapi/spec-factory.d.ts +31 -0
  107. package/dist/openapi/spec-factory.d.ts.map +1 -0
  108. package/dist/openapi/spec.d.ts +19 -0
  109. package/dist/openapi/spec.d.ts.map +1 -0
  110. package/dist/openapi/swagger.d.ts +12 -0
  111. package/dist/openapi/swagger.d.ts.map +1 -0
  112. package/dist/plugin.d.ts +167 -0
  113. package/dist/plugin.d.ts.map +1 -0
  114. package/dist/powered-by.d.ts +7 -0
  115. package/dist/powered-by.d.ts.map +1 -0
  116. package/dist/pretty-json.d.ts +10 -0
  117. package/dist/pretty-json.d.ts.map +1 -0
  118. package/dist/proxy.d.ts +51 -0
  119. package/dist/proxy.d.ts.map +1 -0
  120. package/dist/python-type-emitter.d.ts +11 -0
  121. package/dist/python-type-emitter.d.ts.map +1 -0
  122. package/dist/realtime/buffer.d.ts +18 -0
  123. package/dist/realtime/buffer.d.ts.map +1 -0
  124. package/dist/realtime/bus.d.ts +11 -0
  125. package/dist/realtime/bus.d.ts.map +1 -0
  126. package/dist/realtime/longpoll.d.ts +23 -0
  127. package/dist/realtime/longpoll.d.ts.map +1 -0
  128. package/dist/realtime/protocol.d.ts +50 -0
  129. package/dist/realtime/protocol.d.ts.map +1 -0
  130. package/dist/realtime/route.d.ts +42 -0
  131. package/dist/realtime/route.d.ts.map +1 -0
  132. package/dist/realtime/runtime.d.ts +74 -0
  133. package/dist/realtime/runtime.d.ts.map +1 -0
  134. package/dist/request-id.d.ts +12 -0
  135. package/dist/request-id.d.ts.map +1 -0
  136. package/dist/request-to-curl.d.ts +9 -0
  137. package/dist/request-to-curl.d.ts.map +1 -0
  138. package/dist/response.d.ts +57 -0
  139. package/dist/response.d.ts.map +1 -0
  140. package/dist/rust-type-emitter.d.ts +32 -0
  141. package/dist/rust-type-emitter.d.ts.map +1 -0
  142. package/dist/secure-headers.d.ts +16 -0
  143. package/dist/secure-headers.d.ts.map +1 -0
  144. package/dist/serve-register.d.ts +2 -0
  145. package/dist/serve-register.d.ts.map +1 -0
  146. package/dist/serve-slot.d.ts +6 -0
  147. package/dist/serve-slot.d.ts.map +1 -0
  148. package/dist/serve.d.ts +21 -0
  149. package/dist/serve.d.ts.map +1 -0
  150. package/dist/server-timing.d.ts +9 -0
  151. package/dist/server-timing.d.ts.map +1 -0
  152. package/dist/static.d.ts +12 -0
  153. package/dist/static.d.ts.map +1 -0
  154. package/dist/telemetry/otel.d.ts +59 -0
  155. package/dist/telemetry/otel.d.ts.map +1 -0
  156. package/dist/testing.d.ts +24 -0
  157. package/dist/testing.d.ts.map +1 -0
  158. package/dist/timeout.d.ts +7 -0
  159. package/dist/timeout.d.ts.map +1 -0
  160. package/dist/tree.d.ts +90 -0
  161. package/dist/tree.d.ts.map +1 -0
  162. package/dist/ts-type-emitter.d.ts +4 -0
  163. package/dist/ts-type-emitter.d.ts.map +1 -0
  164. package/dist/type-emitter.d.ts +8 -0
  165. package/dist/type-emitter.d.ts.map +1 -0
  166. package/dist/type-extractor.d.ts +39 -0
  167. package/dist/type-extractor.d.ts.map +1 -0
  168. package/dist/types.d.ts +451 -0
  169. package/dist/types.d.ts.map +1 -0
  170. package/dist/validation.d.ts +18 -0
  171. package/dist/validation.d.ts.map +1 -0
  172. package/dist/ws/bun.d.ts +28 -0
  173. package/dist/ws/bun.d.ts.map +1 -0
  174. package/dist/ws/cloudflare.d.ts +55 -0
  175. package/dist/ws/cloudflare.d.ts.map +1 -0
  176. package/dist/ws/deno.d.ts +13 -0
  177. package/dist/ws/deno.d.ts.map +1 -0
  178. package/dist/ws/node.d.ts +10 -0
  179. package/dist/ws/node.d.ts.map +1 -0
  180. package/dist/yaml.d.ts +4 -0
  181. package/dist/yaml.d.ts.map +1 -0
  182. package/docs/api-ref/go.md +82 -82
  183. package/docs/api-ref/python.md +82 -82
  184. package/docs/api-ref/rust.md +82 -82
  185. package/docs/api-ref/typescript.md +82 -82
  186. package/docs/invalidate-spec.md +13 -13
  187. package/docs/meta-spec.md +513 -0
  188. package/docs/packaging.md +85 -0
  189. package/docs/sdk.md +87 -73
  190. package/package.json +195 -51
  191. package/src/body-limit.ts +2 -1
  192. package/src/build/index.ts +2 -9
  193. package/src/cli.ts +2 -3
  194. package/src/client/http.ts +204 -223
  195. package/src/client/index.ts +6 -14
  196. package/src/client/sdk.ts +24 -17
  197. package/src/client/types.ts +4 -9
  198. package/src/client/ws.ts +3 -5
  199. package/src/client-mcp/server.ts +1 -4
  200. package/src/cloudflare/realtime.ts +8 -10
  201. package/src/codegen-go-cli.ts +74 -67
  202. package/src/codegen-go.ts +71 -51
  203. package/src/codegen-ir.ts +16 -27
  204. package/src/codegen-loaders.ts +5 -8
  205. package/src/codegen-mcp.ts +9 -27
  206. package/src/codegen-python.ts +47 -27
  207. package/src/codegen-rust.ts +110 -66
  208. package/src/codegen-sanitize.ts +1 -7
  209. package/src/codegen-schema-naming.ts +8 -2
  210. package/src/codegen.ts +299 -196
  211. package/src/context.ts +91 -95
  212. package/src/cookie-sign.ts +5 -13
  213. package/src/cookie.ts +1 -2
  214. package/src/cors.ts +4 -6
  215. package/src/crypto.ts +1 -4
  216. package/src/csrf.ts +3 -3
  217. package/src/curl-logger.ts +14 -32
  218. package/src/detect-runtime.ts +1 -3
  219. package/src/errors.ts +1 -3
  220. package/src/etag.ts +3 -4
  221. package/src/go-type-emitter.ts +45 -40
  222. package/src/honey-response.ts +1 -3
  223. package/src/i18n.ts +3 -12
  224. package/src/index.ts +1168 -1412
  225. package/src/ip-restrict.ts +2 -1
  226. package/src/logger.ts +13 -13
  227. package/src/meta-spec.ts +0 -0
  228. package/src/middleware.ts +111 -79
  229. package/src/node-request.ts +2 -1
  230. package/src/node.ts +9 -10
  231. package/src/openapi/register.ts +1 -2
  232. package/src/openapi/scalar.ts +9 -9
  233. package/src/openapi/spec-factory.ts +2 -2
  234. package/src/openapi/spec.ts +26 -19
  235. package/src/openapi/swagger.ts +9 -9
  236. package/src/plugin.ts +226 -256
  237. package/src/powered-by.ts +2 -1
  238. package/src/pretty-json.ts +2 -1
  239. package/src/proxy.ts +1 -3
  240. package/src/python-type-emitter.ts +37 -12
  241. package/src/realtime/route.ts +1 -1
  242. package/src/realtime/runtime.ts +9 -12
  243. package/src/request-id.ts +3 -4
  244. package/src/request-to-curl.ts +1 -4
  245. package/src/response.ts +150 -199
  246. package/src/rust-type-emitter.ts +66 -25
  247. package/src/secure-headers.ts +2 -1
  248. package/src/serve.ts +3 -12
  249. package/src/server-timing.ts +2 -1
  250. package/src/static.ts +3 -4
  251. package/src/telemetry/otel.ts +1 -6
  252. package/src/testing.ts +2 -11
  253. package/src/timeout.ts +2 -1
  254. package/src/tree.ts +12 -27
  255. package/src/ts-type-emitter.ts +71 -15
  256. package/src/type-emitter.ts +226 -237
  257. package/src/type-extractor.ts +260 -334
  258. package/src/types.ts +168 -68
  259. package/src/validation.ts +7 -30
  260. package/src/ws/bun.ts +2 -9
  261. package/src/ws/cloudflare.ts +13 -16
  262. package/src/ws/deno.ts +31 -40
  263. package/src/ws/node.ts +4 -19
@@ -1,3 +1,4 @@
1
+ import { namedMiddleware } from "./middleware.ts"
1
2
  import { HoneyError } from "./error.ts"
2
3
  import type { MiddlewareFn } from "./middleware.ts"
3
4
  import { EK, SK } from "./types.ts"
@@ -188,5 +189,5 @@ export function ipRestrict(opts: IpRestrictOptions): MiddlewareFn<{ req: Request
188
189
  return next()
189
190
  }
190
191
 
191
- return mw
192
+ return namedMiddleware("ipRestrict", mw)
192
193
  }
package/src/logger.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { namedMiddleware } from "./middleware.ts"
1
2
  import type { MiddlewareFn } from "./middleware.ts"
2
3
 
3
4
  /* ── log levels (pino-compatible numeric values) ── */
@@ -45,9 +46,10 @@ function createLogger(opts?: CreateLoggerOptions): LoggerInstance {
45
46
 
46
47
  function emit(level: number, first: Record<string, unknown> | string, second?: string): void {
47
48
  if (level < threshold) return
48
- const obj = typeof first === "string"
49
- ? { ...base, level, msg: first, time: Date.now() }
50
- : { ...base, ...first, level, msg: second ?? "", time: Date.now() }
49
+ const obj =
50
+ typeof first === "string"
51
+ ? { ...base, level, msg: first, time: Date.now() }
52
+ : { ...base, ...first, level, msg: second ?? "", time: Date.now() }
51
53
  write(JSON.stringify(obj))
52
54
  }
53
55
 
@@ -95,25 +97,23 @@ function defaultLog(data: LogData): void {
95
97
  const noop = () => {}
96
98
  const noopLogger: LoggerInstance = createLogger({ level: "fatal", write: noop })
97
99
 
98
- function logger(
99
- options?: LoggerOptions,
100
- ): MiddlewareFn<{ path: string, req: Request }, { log: LoggerInstance }> {
100
+ function logger(options?: LoggerOptions): MiddlewareFn<{ path: string; req: Request }, { log: LoggerInstance }> {
101
101
  const log = options?.log ?? defaultLog
102
102
  const skip = options?.skip
103
103
  const instance = options?.instance
104
104
 
105
- return async (ctx, next) => {
105
+ return namedMiddleware("logger", async (ctx, next) => {
106
106
  const start = performance.now()
107
107
  const method = ctx.req.method
108
108
  const path = ctx.path
109
- const rid = (ctx as Record<string, unknown>)["requestId"] as string | null ?? null
109
+ const rid = ((ctx as Record<string, unknown>)["requestId"] as string | null) ?? null
110
110
 
111
111
  const child = instance
112
112
  ? instance.child({
113
- method,
114
- path,
115
- ...(rid ? { requestId: rid } : {}),
116
- })
113
+ method,
114
+ path,
115
+ ...(rid ? { requestId: rid } : {}),
116
+ })
117
117
  : noopLogger
118
118
 
119
119
  if (instance) {
@@ -142,7 +142,7 @@ function logger(
142
142
  }
143
143
 
144
144
  return response
145
- }
145
+ })
146
146
  }
147
147
 
148
148
  export { createLogger, logger }
Binary file
package/src/middleware.ts CHANGED
@@ -1,27 +1,39 @@
1
1
  /** phantom wrapper — at runtime it's just Response, carries TAdds for inference */
2
2
  export type MiddlewareResult<TAdds = {}> = Response & {
3
- readonly __adds?: TAdds;
4
- };
5
-
6
- export type MiddlewareFn<
7
- TCtx = {},
8
- TAdds = {},
9
- TErrors extends string = string,
10
- > = ((
3
+ readonly __adds?: TAdds
4
+ }
5
+
6
+ export type MiddlewareFn<TCtx = {}, TAdds = {}, TErrors extends string = string> = ((
11
7
  ctx: TCtx,
12
8
  next: {
13
- <T>(additions: T): Promise<MiddlewareResult<T>>;
14
- (): Promise<MiddlewareResult<{}>>;
9
+ <T>(additions: T): Promise<MiddlewareResult<T>>
10
+ (): Promise<MiddlewareResult<{}>>
15
11
  },
16
12
  ) => Promise<MiddlewareResult<TAdds>>) & {
17
- errors?: readonly TErrors[];
18
- };
13
+ errors?: readonly TErrors[]
14
+ /** Meta this middleware contributes to every route that mounts it. Codegen + `ctx.meta` */
15
+ meta?: Readonly<Record<string, unknown>>
16
+ }
17
+
18
+ /**
19
+ * Stamp a stable name on a middleware closure.
20
+ *
21
+ * `Function.prototype.name` is inferred by whatever transpiled the module — jiti (used by
22
+ * `honey generate`) and rollup (used by `vite build`) disagree about a closure returned from
23
+ * a factory, which made the generated manifest differ between the two. Naming shipped
24
+ * middleware explicitly makes the manifest deterministic, and "cors" reads better than
25
+ * "anonymous" besides.
26
+ */
27
+ export function namedMiddleware<T extends object>(name: string, fn: T): T {
28
+ Object.defineProperty(fn, "name", { configurable: true, value: name })
29
+ return fn
30
+ }
19
31
 
20
32
  /** runtime type for stored middleware — erased generics, used internally */
21
33
  export type RuntimeMiddleware = (
22
34
  ctx: Record<string, unknown>,
23
35
  next: (additions?: Record<string, unknown>) => Promise<Response>,
24
- ) => Promise<Response>;
36
+ ) => Promise<Response>
25
37
 
26
38
  /**
27
39
  * Create middleware with error keys constrained to a factory.
@@ -33,21 +45,38 @@ export function defineMiddleware<
33
45
  TFactory extends Record<string, (...args: never[]) => unknown>,
34
46
  TKeys extends keyof TFactory & string,
35
47
  >(opts: {
36
- errors?: [TFactory, ...TKeys[]];
37
- fn: MiddlewareFn<TReqs, TAdds>;
48
+ errors?: [TFactory, ...TKeys[]]
49
+ fn: MiddlewareFn<TReqs, TAdds>
50
+ meta?: Record<string, unknown>
38
51
  }): MiddlewareFn<TReqs, TAdds, TKeys> {
39
- const fn = opts.fn as MiddlewareFn<TReqs, TAdds, TKeys>;
52
+ const fn = opts.fn as MiddlewareFn<TReqs, TAdds, TKeys>
40
53
  if (opts.errors) {
41
- const [, ...keys] = opts.errors;
42
- Object.defineProperty(fn, "errors", { value: keys });
54
+ const [, ...keys] = opts.errors
55
+ Object.defineProperty(fn, "errors", { value: keys })
56
+ }
57
+ if (opts.meta) attachMiddlewareMeta(fn, opts.meta)
58
+ return fn
59
+ }
60
+
61
+ /**
62
+ * `internal` decides whether a route appears in generated artifacts at all. A middleware that
63
+ * set it would silently remove routes from the document, which is not noticeable by reading
64
+ * either the route or the middleware — so it is the one key middleware may not contribute.
65
+ */
66
+ function attachMiddlewareMeta(fn: object, meta: Record<string, unknown>): void {
67
+ if ("internal" in meta) {
68
+ throw new Error(
69
+ 'middleware meta cannot set "internal" — it controls whether routes appear in generated ' +
70
+ "artifacts; set it on the route with .meta({ internal: true })",
71
+ )
43
72
  }
44
- return fn;
73
+ Object.defineProperty(fn, "meta", { value: Object.freeze({ ...meta }) })
45
74
  }
46
75
 
47
76
  type NextFn = {
48
- <T>(additions: T): Promise<MiddlewareResult<T>>;
49
- (): Promise<MiddlewareResult<{}>>;
50
- };
77
+ <T>(additions: T): Promise<MiddlewareResult<T>>
78
+ (): Promise<MiddlewareResult<{}>>
79
+ }
51
80
 
52
81
  /** Extract TAdds from callback return type — distributes over unions */
53
82
  type ExtractAdds<T> = T extends Promise<MiddlewareResult<infer A>> ? A : never
@@ -58,25 +87,38 @@ type ExtractAdds<T> = T extends Promise<MiddlewareResult<infer A>> ? A : never
58
87
  */
59
88
  export function createMiddleware<TReqs = {}, TRet extends Promise<Response> = Promise<MiddlewareResult<{}>>>(
60
89
  fn: (ctx: TReqs, next: NextFn) => TRet,
90
+ opts?: { meta?: Record<string, unknown> },
61
91
  ): MiddlewareFn<TReqs, ExtractAdds<TRet>> {
92
+ if (opts?.meta) attachMiddlewareMeta(fn, opts.meta)
62
93
  return fn as MiddlewareFn<TReqs, ExtractAdds<TRet>>
63
94
  }
64
95
 
96
+ /**
97
+ * Merge meta contributed by a middleware chain, in mount order. Resolved once at route
98
+ * registration — never per request, because the precompiled route tree bakes `mt` as a literal
99
+ * and a lazily-derived value would diverge from it.
100
+ */
101
+ export function collectMiddlewareMeta(
102
+ groups: ReadonlyArray<ReadonlyArray<RuntimeMiddleware>>,
103
+ ): Record<string, unknown> | null {
104
+ let out: Record<string, unknown> | undefined
105
+ for (const group of groups) {
106
+ for (const mw of group) {
107
+ const meta = (mw as { meta?: Readonly<Record<string, unknown>> }).meta
108
+ if (!meta) continue
109
+ if (out === undefined) out = {}
110
+ Object.assign(out, meta)
111
+ }
112
+ }
113
+ return out ?? null
114
+ }
115
+
65
116
  /**
66
117
  * Context properties that middleware additions must not overwrite.
67
118
  * These are set by the framework during request construction.
68
119
  * TypeScript prevents this at compile time; this guard catches runtime bypasses.
69
120
  */
70
- const RESERVED_CTX_KEYS = new Set([
71
- "background",
72
- "cookies",
73
- "env",
74
- "headers",
75
- "params",
76
- "req",
77
- "res",
78
- "search",
79
- ]);
121
+ const RESERVED_CTX_KEYS = new Set(["background", "cookies", "env", "headers", "params", "req", "res", "search"])
80
122
 
81
123
  /**
82
124
  * Compile a middleware chain + handler into a single function at registration time.
@@ -89,66 +131,60 @@ export function compileChain(
89
131
  if (middlewares.length === 0) {
90
132
  /* sync fast path — avoids microtask overhead for sync handlers */
91
133
  return (ctx) => {
92
- const result = handler(ctx);
134
+ const result = handler(ctx)
93
135
  if (result instanceof Promise) {
94
- return result.then(validateResponse);
136
+ return result.then(validateResponse)
95
137
  }
96
138
  if (result === undefined || result === null) {
97
- throw new Error(
98
- "handler must return a Response — did you forget 'return ctx.res...()'?",
99
- );
139
+ throw new Error("handler must return a Response — did you forget 'return ctx.res...()'?")
100
140
  }
101
- return result;
102
- };
141
+ return result
142
+ }
103
143
  }
104
144
 
105
145
  if (middlewares.length === 1) {
106
- const mw = middlewares[0];
146
+ const mw = middlewares[0]
107
147
  if (!mw) {
108
- throw new Error("middleware chain invariant violated");
148
+ throw new Error("middleware chain invariant violated")
109
149
  }
110
150
  return (ctx) => {
111
- const mwCtx = ctx as Record<string, unknown>;
112
- let called = false;
151
+ const mwCtx = ctx as Record<string, unknown>
152
+ let called = false
113
153
  return Promise.resolve(
114
154
  mw(mwCtx, (additions?: Record<string, unknown>) => {
115
- if (called) throw new Error("next() called multiple times");
116
- called = true;
117
- if (additions) mergeAdditions(ctx, additions);
118
- return Promise.resolve(handler(ctx)).then(validateResponse);
155
+ if (called) throw new Error("next() called multiple times")
156
+ called = true
157
+ if (additions) mergeAdditions(ctx, additions)
158
+ return Promise.resolve(handler(ctx)).then(validateResponse)
119
159
  }),
120
- ).then(validateMiddlewareResponse);
121
- };
160
+ ).then(validateMiddlewareResponse)
161
+ }
122
162
  }
123
163
 
124
164
  /* general case — pre-bind middleware array, no per-request index tracking */
125
- return (ctx) => executeChain(middlewares, ctx, handler);
165
+ return (ctx) => executeChain(middlewares, ctx, handler)
126
166
  }
127
167
 
128
168
  function mergeAdditions(ctx: object, additions: Record<string, unknown>): void {
129
169
  for (const key in additions) {
130
170
  if (!RESERVED_CTX_KEYS.has(key)) {
131
- (ctx as Record<string, unknown>)[key] = additions[key];
171
+ ;(ctx as Record<string, unknown>)[key] = additions[key]
132
172
  }
133
173
  }
134
174
  }
135
175
 
136
176
  function validateResponse(result: Response): Response {
137
177
  if (result === undefined || result === null) {
138
- throw new Error(
139
- "handler must return a Response — did you forget 'return ctx.res...()'?",
140
- );
178
+ throw new Error("handler must return a Response — did you forget 'return ctx.res...()'?")
141
179
  }
142
- return result;
180
+ return result
143
181
  }
144
182
 
145
183
  function validateMiddlewareResponse(result: Response): Response {
146
184
  if (result === undefined || result === null) {
147
- throw new Error(
148
- "middleware must return a Response — did you forget 'return next(...)'?",
149
- );
185
+ throw new Error("middleware must return a Response — did you forget 'return next(...)'?")
150
186
  }
151
- return result;
187
+ return result
152
188
  }
153
189
 
154
190
  export function executeChain(
@@ -156,44 +192,40 @@ export function executeChain(
156
192
  ctx: object,
157
193
  handler: (ctx: object) => Response | Promise<Response>,
158
194
  ): Promise<Response> {
159
- let index = 0;
195
+ let index = 0
160
196
  /* middleware runtime type uses Record<string, unknown> — object satisfies at runtime */
161
- const mwCtx = ctx as Record<string, unknown>;
197
+ const mwCtx = ctx as Record<string, unknown>
162
198
 
163
199
  function dispatch(): Promise<Response> {
164
200
  if (index >= middlewares.length) {
165
201
  return Promise.resolve(handler(ctx)).then((result) => {
166
202
  if (result === undefined || result === null) {
167
- throw new Error(
168
- "handler must return a Response — did you forget 'return ctx.res...()'?",
169
- );
203
+ throw new Error("handler must return a Response — did you forget 'return ctx.res...()'?")
170
204
  }
171
- return result;
172
- });
205
+ return result
206
+ })
173
207
  }
174
- const mw = middlewares[index++];
208
+ const mw = middlewares[index++]
175
209
  if (!mw) {
176
- throw new Error("middleware chain invariant violated");
210
+ throw new Error("middleware chain invariant violated")
177
211
  }
178
- let called = false;
212
+ let called = false
179
213
  return Promise.resolve(
180
214
  mw(mwCtx, (additions?: Record<string, unknown>) => {
181
215
  if (called) {
182
- throw new Error("next() called multiple times");
216
+ throw new Error("next() called multiple times")
183
217
  }
184
- called = true;
185
- if (additions) mergeAdditions(ctx, additions);
186
- return dispatch();
218
+ called = true
219
+ if (additions) mergeAdditions(ctx, additions)
220
+ return dispatch()
187
221
  }),
188
222
  ).then((result) => {
189
223
  if (result === undefined || result === null) {
190
- throw new Error(
191
- "middleware must return a Response — did you forget 'return next(...)'?",
192
- );
224
+ throw new Error("middleware must return a Response — did you forget 'return next(...)'?")
193
225
  }
194
- return result;
195
- });
226
+ return result
227
+ })
196
228
  }
197
229
 
198
- return dispatch();
230
+ return dispatch()
199
231
  }
@@ -121,7 +121,8 @@ export class NodeRequest {
121
121
  get body(): ReadableStream<Uint8Array> | null {
122
122
  if (!this.#hasBody) return null
123
123
  if (this.#webBody === undefined) {
124
- this.#webBody = Readable.toWeb(this.#incoming) as ReadableStream<Uint8Array>
124
+ // Node's web stream and the DOM/Bun ReadableStream brands do not overlap.
125
+ this.#webBody = Readable.toWeb(this.#incoming) as unknown as ReadableStream<Uint8Array>
125
126
  }
126
127
  return this.#webBody
127
128
  }
package/src/node.ts CHANGED
@@ -16,14 +16,16 @@ type ServeOptions<TEnv> = {
16
16
  const BUFFER_BODY_MAX = 256 * 1024
17
17
  const RAW_BODY = Symbol.for("honey.rawBody")
18
18
 
19
+ // Node's `Readable.fromWeb` wants `node:stream/web` streams; DOM/Bun brands do not overlap.
20
+ function asNodeWebStream(stream: ReadableStream<Uint8Array>): import("node:stream/web").ReadableStream {
21
+ return stream as unknown as import("node:stream/web").ReadableStream
22
+ }
23
+
19
24
  function incomingToRequest(req: IncomingMessage): Request {
20
25
  return incomingToNodeRequest(req)
21
26
  }
22
27
 
23
- function collectNodeHeaders(
24
- response: Response,
25
- extra?: Record<string, string>,
26
- ): Record<string, string | string[]> {
28
+ function collectNodeHeaders(response: Response, extra?: Record<string, string>): Record<string, string | string[]> {
27
29
  const headerObj: Record<string, string | string[]> = {}
28
30
  let hasSetCookie = false
29
31
  response.headers.forEach((value, key) => {
@@ -79,7 +81,7 @@ async function responseToNode(response: Response, res: ServerResponse): Promise<
79
81
  }
80
82
  res.writeHead(response.status, response.plainHeaders)
81
83
  try {
82
- await pipeline(Readable.fromWeb(stream as import("node:stream/web").ReadableStream), res)
84
+ await pipeline(Readable.fromWeb(asNodeWebStream(stream)), res)
83
85
  } catch {
84
86
  if (!res.destroyed) res.destroy()
85
87
  }
@@ -104,17 +106,14 @@ async function responseToNode(response: Response, res: ServerResponse): Promise<
104
106
  if (shouldBufferBody(response)) {
105
107
  const buf = Buffer.from(await response.arrayBuffer())
106
108
  if (res.destroyed) return
107
- res.writeHead(
108
- response.status,
109
- collectNodeHeaders(response, { "content-length": String(buf.byteLength) }),
110
- )
109
+ res.writeHead(response.status, collectNodeHeaders(response, { "content-length": String(buf.byteLength) }))
111
110
  res.end(buf)
112
111
  return
113
112
  }
114
113
 
115
114
  res.writeHead(response.status, collectNodeHeaders(response))
116
115
  try {
117
- await pipeline(Readable.fromWeb(body as import("node:stream/web").ReadableStream), res)
116
+ await pipeline(Readable.fromWeb(asNodeWebStream(body)), res)
118
117
  } catch {
119
118
  if (!res.destroyed) res.destroy()
120
119
  }
@@ -6,8 +6,7 @@ import { registerOpenApiRuntime } from "./spec-factory.ts"
6
6
 
7
7
  export function enableOpenApi(): void {
8
8
  registerOpenApiRuntime({
9
- docsUi: (kind, specUrl) =>
10
- kind === "swagger" ? swagger({ url: specUrl }) : scalar({ url: specUrl }),
9
+ docsUi: (kind, specUrl) => (kind === "swagger" ? swagger({ url: specUrl }) : scalar({ url: specUrl })),
11
10
  generateManifest: (app) => Promise.resolve(generateManifest(app as never)),
12
11
  generateOpenApi: (app, options) => generateOpenApi(app as never, options),
13
12
  toYaml,
@@ -1,17 +1,17 @@
1
1
  import type { TypedResponse } from "../response.ts"
2
2
 
3
3
  type ScalarOptions = {
4
- url: string
5
- [key: string]: unknown
4
+ url: string
5
+ [key: string]: unknown
6
6
  }
7
7
 
8
8
  export function scalar(
9
- options: ScalarOptions,
9
+ options: ScalarOptions,
10
10
  ): (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => TypedResponse {
11
- const { url, ...rest } = options
12
- const config = JSON.stringify({ url, ...rest })
11
+ const { url, ...rest } = options
12
+ const config = JSON.stringify({ url, ...rest })
13
13
 
14
- const html = `<!DOCTYPE html>
14
+ const html = `<!DOCTYPE html>
15
15
  <html>
16
16
  <head>
17
17
  <meta charset="utf-8" />
@@ -27,7 +27,7 @@ export function scalar(
27
27
  </body>
28
28
  </html>`
29
29
 
30
- const handler = (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => ctx.res.html("ok", html)
31
- Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
32
- return handler
30
+ const handler = (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => ctx.res.html("ok", html)
31
+ Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
32
+ return handler
33
33
  }
@@ -5,6 +5,7 @@ export type OpenApiGenerate = (
5
5
  options: {
6
6
  filterRoutes?: (route: { meta: unknown; method: string; path: string }) => boolean
7
7
  info: { description?: string; title: string; version: string }
8
+ profile?: string
8
9
  securitySchemes?: Record<string, unknown>
9
10
  },
10
11
  ) => Promise<unknown>
@@ -27,8 +28,7 @@ export type OpenApiRuntime = {
27
28
  toYaml: YamlGenerate
28
29
  }
29
30
 
30
- const MISSING =
31
- 'Runtime OpenAPI/manifest generation requires `import "@lovrozagar/honey/openapi"` in the app entry.'
31
+ const MISSING = 'Runtime OpenAPI/manifest generation requires `import "@lovrozagar/honey/openapi"` in the app entry.'
32
32
 
33
33
  let runtime: OpenApiRuntime | undefined
34
34
 
@@ -4,28 +4,35 @@ import type { DefaultMeta } from "../types.ts"
4
4
  import type { TypedResponse } from "../response.ts"
5
5
 
6
6
  type SpecOptions<TMeta = Record<string, unknown> | null> = {
7
- description?: string
8
- filterRoutes?: (route: OpenApiRouteInfo<DefaultMeta & TMeta>) => boolean
9
- securitySchemes?: Record<string, unknown>
10
- title: string
11
- version: string
7
+ description?: string
8
+ filterRoutes?: (route: OpenApiRouteInfo<DefaultMeta & TMeta>) => boolean
9
+ /** Named metaSpec profile selecting which emitted keys this document carries */
10
+ profile?: string
11
+ securitySchemes?: Record<string, unknown>
12
+ title: string
13
+ version: string
12
14
  }
13
15
 
14
16
  export function spec<TMeta = Record<string, unknown> | null>(
15
- options: SpecOptions<TMeta>,
17
+ options: SpecOptions<TMeta>,
16
18
  ): (ctx: { res: { json(sk: "ok", data: unknown): TypedResponse } }) => TypedResponse | Promise<TypedResponse> {
17
- let cached: string | null = null
19
+ let cached: string | null = null
18
20
 
19
- const handler = async (ctx: { res: { json(sk: "ok", data: unknown): TypedResponse } }) => {
20
- if (cached === null) {
21
- const desc = Object.getOwnPropertyDescriptor(handler, Symbol.for("honey.app"))
22
- const openApiSpec = desc
23
- ? await generateOpenApi(desc.value, { filterRoutes: options.filterRoutes, info: options, securitySchemes: options.securitySchemes })
24
- : {}
25
- cached = JSON.stringify(openApiSpec)
26
- }
27
- return ctx.res.json("ok", JSON.parse(cached))
28
- }
29
- Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
30
- return handler
21
+ const handler = async (ctx: { res: { json(sk: "ok", data: unknown): TypedResponse } }) => {
22
+ if (cached === null) {
23
+ const desc = Object.getOwnPropertyDescriptor(handler, Symbol.for("honey.app"))
24
+ const openApiSpec = desc
25
+ ? await generateOpenApi(desc.value, {
26
+ filterRoutes: options.filterRoutes,
27
+ info: options,
28
+ profile: options.profile,
29
+ securitySchemes: options.securitySchemes,
30
+ })
31
+ : {}
32
+ cached = JSON.stringify(openApiSpec)
33
+ }
34
+ return ctx.res.json("ok", JSON.parse(cached))
35
+ }
36
+ Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
37
+ return handler
31
38
  }
@@ -1,17 +1,17 @@
1
1
  import type { TypedResponse } from "../response.ts"
2
2
 
3
3
  type SwaggerOptions = {
4
- url: string
5
- [key: string]: unknown
4
+ url: string
5
+ [key: string]: unknown
6
6
  }
7
7
 
8
8
  export function swagger(
9
- options: SwaggerOptions,
9
+ options: SwaggerOptions,
10
10
  ): (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => TypedResponse {
11
- const { url, ...rest } = options
12
- const config = JSON.stringify({ dom_id: "#swagger-ui", url, ...rest })
11
+ const { url, ...rest } = options
12
+ const config = JSON.stringify({ dom_id: "#swagger-ui", url, ...rest })
13
13
 
14
- const html = `<!DOCTYPE html>
14
+ const html = `<!DOCTYPE html>
15
15
  <html>
16
16
  <head>
17
17
  <meta charset="utf-8" />
@@ -28,7 +28,7 @@ export function swagger(
28
28
  </body>
29
29
  </html>`
30
30
 
31
- const handler = (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => ctx.res.html("ok", html)
32
- Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
33
- return handler
31
+ const handler = (ctx: { res: { html(sk: "ok", body: string): TypedResponse } }) => ctx.res.html("ok", html)
32
+ Object.defineProperty(handler, Symbol.for("honey.internal"), { value: true })
33
+ return handler
34
34
  }