@lovrozagar/honey 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +1596 -6
  2. package/dist/accepts.d.ts +6 -0
  3. package/dist/accepts.d.ts.map +1 -0
  4. package/dist/body-limit.d.ts +22 -0
  5. package/dist/body-limit.d.ts.map +1 -0
  6. package/dist/build/index.d.ts +42 -0
  7. package/dist/build/index.d.ts.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/client/error.d.ts +14 -0
  11. package/dist/client/error.d.ts.map +1 -0
  12. package/dist/client/http.d.ts +88 -0
  13. package/dist/client/http.d.ts.map +1 -0
  14. package/dist/client/index.d.ts +22 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/sdk.d.ts +34 -0
  17. package/dist/client/sdk.d.ts.map +1 -0
  18. package/dist/client/sse.d.ts +10 -0
  19. package/dist/client/sse.d.ts.map +1 -0
  20. package/dist/client/types.d.ts +124 -0
  21. package/dist/client/types.d.ts.map +1 -0
  22. package/dist/client/ws.d.ts +15 -0
  23. package/dist/client/ws.d.ts.map +1 -0
  24. package/dist/client-mcp/server.d.ts +10 -0
  25. package/dist/client-mcp/server.d.ts.map +1 -0
  26. package/dist/client-mcp/types.d.ts +16 -0
  27. package/dist/client-mcp/types.d.ts.map +1 -0
  28. package/dist/cloudflare/realtime.d.ts +30 -0
  29. package/dist/cloudflare/realtime.d.ts.map +1 -0
  30. package/dist/codegen-go-cli.d.ts +22 -0
  31. package/dist/codegen-go-cli.d.ts.map +1 -0
  32. package/dist/codegen-go.d.ts +29 -0
  33. package/dist/codegen-go.d.ts.map +1 -0
  34. package/dist/codegen-hash.d.ts +3 -0
  35. package/dist/codegen-hash.d.ts.map +1 -0
  36. package/dist/codegen-ir.d.ts +123 -0
  37. package/dist/codegen-ir.d.ts.map +1 -0
  38. package/dist/codegen-loaders.d.ts +12 -0
  39. package/dist/codegen-loaders.d.ts.map +1 -0
  40. package/dist/codegen-mcp.d.ts +13 -0
  41. package/dist/codegen-mcp.d.ts.map +1 -0
  42. package/dist/codegen-python.d.ts +21 -0
  43. package/dist/codegen-python.d.ts.map +1 -0
  44. package/dist/codegen-rust.d.ts +18 -0
  45. package/dist/codegen-rust.d.ts.map +1 -0
  46. package/dist/codegen-sanitize.d.ts +3 -0
  47. package/dist/codegen-sanitize.d.ts.map +1 -0
  48. package/dist/codegen-schema-naming.d.ts +22 -0
  49. package/dist/codegen-schema-naming.d.ts.map +1 -0
  50. package/dist/codegen.d.ts +178 -0
  51. package/dist/codegen.d.ts.map +1 -0
  52. package/dist/context.d.ts +56 -0
  53. package/dist/context.d.ts.map +1 -0
  54. package/dist/cookie-sign.d.ts +3 -0
  55. package/dist/cookie-sign.d.ts.map +1 -0
  56. package/dist/cookie.d.ts +12 -0
  57. package/dist/cookie.d.ts.map +1 -0
  58. package/dist/cors.d.ts +13 -0
  59. package/dist/cors.d.ts.map +1 -0
  60. package/dist/crypto.d.ts +2 -0
  61. package/dist/crypto.d.ts.map +1 -0
  62. package/dist/csrf.d.ts +9 -0
  63. package/dist/csrf.d.ts.map +1 -0
  64. package/dist/curl-logger.d.ts +33 -0
  65. package/dist/curl-logger.d.ts.map +1 -0
  66. package/dist/detect-runtime.d.ts +16 -0
  67. package/dist/detect-runtime.d.ts.map +1 -0
  68. package/dist/error.d.ts +21 -0
  69. package/dist/error.d.ts.map +1 -0
  70. package/dist/errors.d.ts +40 -0
  71. package/dist/errors.d.ts.map +1 -0
  72. package/dist/etag.d.ts +9 -0
  73. package/dist/etag.d.ts.map +1 -0
  74. package/dist/feature-load.d.ts +3 -0
  75. package/dist/feature-load.d.ts.map +1 -0
  76. package/dist/go-type-emitter.d.ts +33 -0
  77. package/dist/go-type-emitter.d.ts.map +1 -0
  78. package/dist/honey-response.d.ts +82 -0
  79. package/dist/honey-response.d.ts.map +1 -0
  80. package/dist/i18n-slot.d.ts +7 -0
  81. package/dist/i18n-slot.d.ts.map +1 -0
  82. package/dist/i18n.d.ts +26 -0
  83. package/dist/i18n.d.ts.map +1 -0
  84. package/dist/index.d.ts +470 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/init.d.ts +7 -0
  87. package/dist/init.d.ts.map +1 -0
  88. package/dist/input.d.ts +6 -0
  89. package/dist/input.d.ts.map +1 -0
  90. package/dist/ip-restrict.d.ts +12 -0
  91. package/dist/ip-restrict.d.ts.map +1 -0
  92. package/dist/logger.d.ts +45 -0
  93. package/dist/logger.d.ts.map +1 -0
  94. package/dist/meta-spec.d.ts +81 -0
  95. package/dist/meta-spec.d.ts.map +1 -0
  96. package/dist/middleware.d.ts +60 -0
  97. package/dist/middleware.d.ts.map +1 -0
  98. package/dist/node-request.d.ts +59 -0
  99. package/dist/node-request.d.ts.map +1 -0
  100. package/dist/node.d.ts +13 -0
  101. package/dist/node.d.ts.map +1 -0
  102. package/dist/openapi/register.d.ts +3 -0
  103. package/dist/openapi/register.d.ts.map +1 -0
  104. package/dist/openapi/scalar.d.ts +12 -0
  105. package/dist/openapi/scalar.d.ts.map +1 -0
  106. package/dist/openapi/spec-factory.d.ts +31 -0
  107. package/dist/openapi/spec-factory.d.ts.map +1 -0
  108. package/dist/openapi/spec.d.ts +19 -0
  109. package/dist/openapi/spec.d.ts.map +1 -0
  110. package/dist/openapi/swagger.d.ts +12 -0
  111. package/dist/openapi/swagger.d.ts.map +1 -0
  112. package/dist/plugin.d.ts +167 -0
  113. package/dist/plugin.d.ts.map +1 -0
  114. package/dist/powered-by.d.ts +7 -0
  115. package/dist/powered-by.d.ts.map +1 -0
  116. package/dist/pretty-json.d.ts +10 -0
  117. package/dist/pretty-json.d.ts.map +1 -0
  118. package/dist/proxy.d.ts +51 -0
  119. package/dist/proxy.d.ts.map +1 -0
  120. package/dist/python-type-emitter.d.ts +11 -0
  121. package/dist/python-type-emitter.d.ts.map +1 -0
  122. package/dist/realtime/buffer.d.ts +18 -0
  123. package/dist/realtime/buffer.d.ts.map +1 -0
  124. package/dist/realtime/bus.d.ts +11 -0
  125. package/dist/realtime/bus.d.ts.map +1 -0
  126. package/dist/realtime/longpoll.d.ts +23 -0
  127. package/dist/realtime/longpoll.d.ts.map +1 -0
  128. package/dist/realtime/protocol.d.ts +50 -0
  129. package/dist/realtime/protocol.d.ts.map +1 -0
  130. package/dist/realtime/route.d.ts +42 -0
  131. package/dist/realtime/route.d.ts.map +1 -0
  132. package/dist/realtime/runtime.d.ts +74 -0
  133. package/dist/realtime/runtime.d.ts.map +1 -0
  134. package/dist/request-id.d.ts +12 -0
  135. package/dist/request-id.d.ts.map +1 -0
  136. package/dist/request-to-curl.d.ts +9 -0
  137. package/dist/request-to-curl.d.ts.map +1 -0
  138. package/dist/response.d.ts +57 -0
  139. package/dist/response.d.ts.map +1 -0
  140. package/dist/rust-type-emitter.d.ts +32 -0
  141. package/dist/rust-type-emitter.d.ts.map +1 -0
  142. package/dist/secure-headers.d.ts +16 -0
  143. package/dist/secure-headers.d.ts.map +1 -0
  144. package/dist/serve-register.d.ts +2 -0
  145. package/dist/serve-register.d.ts.map +1 -0
  146. package/dist/serve-slot.d.ts +6 -0
  147. package/dist/serve-slot.d.ts.map +1 -0
  148. package/dist/serve.d.ts +21 -0
  149. package/dist/serve.d.ts.map +1 -0
  150. package/dist/server-timing.d.ts +9 -0
  151. package/dist/server-timing.d.ts.map +1 -0
  152. package/dist/static.d.ts +12 -0
  153. package/dist/static.d.ts.map +1 -0
  154. package/dist/telemetry/otel.d.ts +59 -0
  155. package/dist/telemetry/otel.d.ts.map +1 -0
  156. package/dist/testing.d.ts +24 -0
  157. package/dist/testing.d.ts.map +1 -0
  158. package/dist/timeout.d.ts +7 -0
  159. package/dist/timeout.d.ts.map +1 -0
  160. package/dist/tree.d.ts +90 -0
  161. package/dist/tree.d.ts.map +1 -0
  162. package/dist/ts-type-emitter.d.ts +4 -0
  163. package/dist/ts-type-emitter.d.ts.map +1 -0
  164. package/dist/type-emitter.d.ts +8 -0
  165. package/dist/type-emitter.d.ts.map +1 -0
  166. package/dist/type-extractor.d.ts +39 -0
  167. package/dist/type-extractor.d.ts.map +1 -0
  168. package/dist/types.d.ts +451 -0
  169. package/dist/types.d.ts.map +1 -0
  170. package/dist/validation.d.ts +18 -0
  171. package/dist/validation.d.ts.map +1 -0
  172. package/dist/ws/bun.d.ts +28 -0
  173. package/dist/ws/bun.d.ts.map +1 -0
  174. package/dist/ws/cloudflare.d.ts +55 -0
  175. package/dist/ws/cloudflare.d.ts.map +1 -0
  176. package/dist/ws/deno.d.ts +13 -0
  177. package/dist/ws/deno.d.ts.map +1 -0
  178. package/dist/ws/node.d.ts +10 -0
  179. package/dist/ws/node.d.ts.map +1 -0
  180. package/dist/yaml.d.ts +4 -0
  181. package/dist/yaml.d.ts.map +1 -0
  182. package/docs/api-ref/go.md +82 -82
  183. package/docs/api-ref/python.md +82 -82
  184. package/docs/api-ref/rust.md +82 -82
  185. package/docs/api-ref/typescript.md +82 -82
  186. package/docs/invalidate-spec.md +13 -13
  187. package/docs/meta-spec.md +513 -0
  188. package/docs/packaging.md +85 -0
  189. package/docs/sdk.md +87 -73
  190. package/package.json +195 -51
  191. package/src/body-limit.ts +2 -1
  192. package/src/build/index.ts +2 -9
  193. package/src/cli.ts +2 -3
  194. package/src/client/http.ts +204 -223
  195. package/src/client/index.ts +6 -14
  196. package/src/client/sdk.ts +24 -17
  197. package/src/client/types.ts +4 -9
  198. package/src/client/ws.ts +3 -5
  199. package/src/client-mcp/server.ts +1 -4
  200. package/src/cloudflare/realtime.ts +8 -10
  201. package/src/codegen-go-cli.ts +74 -67
  202. package/src/codegen-go.ts +71 -51
  203. package/src/codegen-ir.ts +16 -27
  204. package/src/codegen-loaders.ts +5 -8
  205. package/src/codegen-mcp.ts +9 -27
  206. package/src/codegen-python.ts +47 -27
  207. package/src/codegen-rust.ts +110 -66
  208. package/src/codegen-sanitize.ts +1 -7
  209. package/src/codegen-schema-naming.ts +8 -2
  210. package/src/codegen.ts +299 -196
  211. package/src/context.ts +91 -95
  212. package/src/cookie-sign.ts +5 -13
  213. package/src/cookie.ts +1 -2
  214. package/src/cors.ts +4 -6
  215. package/src/crypto.ts +1 -4
  216. package/src/csrf.ts +3 -3
  217. package/src/curl-logger.ts +14 -32
  218. package/src/detect-runtime.ts +1 -3
  219. package/src/errors.ts +1 -3
  220. package/src/etag.ts +3 -4
  221. package/src/go-type-emitter.ts +45 -40
  222. package/src/honey-response.ts +1 -3
  223. package/src/i18n.ts +3 -12
  224. package/src/index.ts +1168 -1412
  225. package/src/ip-restrict.ts +2 -1
  226. package/src/logger.ts +13 -13
  227. package/src/meta-spec.ts +0 -0
  228. package/src/middleware.ts +111 -79
  229. package/src/node-request.ts +2 -1
  230. package/src/node.ts +9 -10
  231. package/src/openapi/register.ts +1 -2
  232. package/src/openapi/scalar.ts +9 -9
  233. package/src/openapi/spec-factory.ts +2 -2
  234. package/src/openapi/spec.ts +26 -19
  235. package/src/openapi/swagger.ts +9 -9
  236. package/src/plugin.ts +226 -256
  237. package/src/powered-by.ts +2 -1
  238. package/src/pretty-json.ts +2 -1
  239. package/src/proxy.ts +1 -3
  240. package/src/python-type-emitter.ts +37 -12
  241. package/src/realtime/route.ts +1 -1
  242. package/src/realtime/runtime.ts +9 -12
  243. package/src/request-id.ts +3 -4
  244. package/src/request-to-curl.ts +1 -4
  245. package/src/response.ts +150 -199
  246. package/src/rust-type-emitter.ts +66 -25
  247. package/src/secure-headers.ts +2 -1
  248. package/src/serve.ts +3 -12
  249. package/src/server-timing.ts +2 -1
  250. package/src/static.ts +3 -4
  251. package/src/telemetry/otel.ts +1 -6
  252. package/src/testing.ts +2 -11
  253. package/src/timeout.ts +2 -1
  254. package/src/tree.ts +12 -27
  255. package/src/ts-type-emitter.ts +71 -15
  256. package/src/type-emitter.ts +226 -237
  257. package/src/type-extractor.ts +260 -334
  258. package/src/types.ts +168 -68
  259. package/src/validation.ts +7 -30
  260. package/src/ws/bun.ts +2 -9
  261. package/src/ws/cloudflare.ts +13 -16
  262. package/src/ws/deno.ts +31 -40
  263. package/src/ws/node.ts +4 -19
package/src/types.ts CHANGED
@@ -171,10 +171,7 @@ export type FieldError = {
171
171
  }
172
172
 
173
173
  /* shared frozen empty objects — avoid per-call {} allocations */
174
- export const EMPTY_OBJ: Record<string, never> = Object.freeze(Object.create(null)) as Record<
175
- string,
176
- never
177
- >
174
+ export const EMPTY_OBJ: Record<string, never> = Object.freeze(Object.create(null)) as Record<string, never>
178
175
  export const EMPTY_FIELDS: Record<string, FieldError[]> = EMPTY_OBJ as Record<string, FieldError[]>
179
176
 
180
177
  export type NormalizedIssue = {
@@ -233,9 +230,7 @@ export type InferOutput<T> = T extends {
233
230
 
234
231
  /** Map input schema definitions to their inferred output types, dropping undefined keys */
235
232
  export type InferInputMap<T> = {
236
- [K in keyof T as T[K] extends undefined | null ? never : K]-?: InferOutput<
237
- UnwrapInputSchema<NonNullable<T[K]>>
238
- >
233
+ [K in keyof T as T[K] extends undefined | null ? never : K]-?: InferOutput<UnwrapInputSchema<NonNullable<T[K]>>>
239
234
  }
240
235
 
241
236
  /**
@@ -326,7 +321,7 @@ export type OutputSchemaDef = {
326
321
  "application/octet-stream"?: StatusSchemaMap
327
322
  "application/pdf"?: StatusSchemaMap
328
323
  "application/xml"?: StatusSchemaMap
329
- "redirect"?: RedirectSchemaMap
324
+ redirect?: RedirectSchemaMap
330
325
  "text/csv"?: StatusSchemaMap
331
326
  "text/event-stream"?: StatusSchemaMap
332
327
  "text/html"?: StatusSchemaMap
@@ -372,6 +367,8 @@ export type OpenApiMeta = {
372
367
  internal?: boolean
373
368
  deprecated?: boolean
374
369
  description?: string
370
+ /** Escape hatch — emitted verbatim at the highest precedence. Prefer a `.metaSpec()` entry. */
371
+ extensions?: Record<`x-${string}`, unknown>
375
372
  invalidate?: readonly string[] | null
376
373
  mcp?: boolean
377
374
  operationId?: string
@@ -380,20 +377,156 @@ export type OpenApiMeta = {
380
377
  tags?: string | string[]
381
378
  }
382
379
 
380
+ /* ---- Meta spec (codegen-time policy: meta → OpenAPI operation) ---- */
381
+
382
+ /** Which operation kinds a policy entry applies to */
383
+ export type MetaSpecTarget = "both" | "http" | "ws"
384
+
385
+ /** Where a schema-derived policy entry reads its source key from */
386
+ export type MetaSpecSchemaSource =
387
+ | "input.cookies"
388
+ | "input.form"
389
+ | "input.headers"
390
+ | "input.json"
391
+ | "input.search"
392
+ | "output"
393
+
394
+ /** Passed to a policy entry's `map` / `expand` */
395
+ export type MetaSpecContext = {
396
+ meta: Record<string, unknown>
397
+ method: string
398
+ path: string
399
+ profile: string
400
+ /** Where the source value came from — `"meta"`, or the schema source for schema entries */
401
+ source: "meta" | MetaSpecSchemaSource
402
+ }
403
+
404
+ type MetaSpecEntryBase = {
405
+ /** Which operation kinds this entry applies to. Default `"both"` */
406
+ on?: MetaSpecTarget
407
+ /** Restrict this entry to named profiles. Default: every profile */
408
+ profiles?: readonly string[]
409
+ /** Validates the produced value at codegen time. A failure is a build error */
410
+ schema?: StandardSchemaLike
411
+ }
412
+
413
+ /** Policy entry writing one operation key */
414
+ export type MetaSpecSingle<TValue = unknown> = MetaSpecEntryBase & {
415
+ /** Target operation key — an `x-*` extension or an allowed standard field */
416
+ key: string
417
+ /** Transform the source value. Returning `undefined` omits the key for that operation */
418
+ map?: (value: TValue, ctx: MetaSpecContext) => unknown
419
+ }
420
+
421
+ /** Policy entry fanning one source value out to several operation keys */
422
+ export type MetaSpecExpand<TValue = unknown> = MetaSpecEntryBase & {
423
+ /** Returns operation keys → values. `undefined` (returned or per key) omits */
424
+ expand: (value: TValue, ctx: MetaSpecContext) => Record<string, unknown> | undefined
425
+ }
426
+
427
+ /** One policy entry: hidden (`false`), verbatim (`"x-foo"`), single target, or fan-out */
428
+ export type MetaSpecEntry<TValue = unknown> = false | MetaSpecExpand<TValue> | MetaSpecSingle<TValue> | string
429
+
430
+ /**
431
+ * How deeply to look for a schema-metadata key.
432
+ * `"root"` reads the schema root, reading through one level of `items` for a bare array output.
433
+ * `"deep"` walks the schema (properties, items, composition members) and takes the shallowest
434
+ * match — for envelope shapes like `{ articles: [Article], nextCursor, count }`.
435
+ */
436
+ export type MetaSpecSchemaSearch = "deep" | "root"
437
+
438
+ /** Refuse a descriptor whose publisher contract version is newer than this policy understands */
439
+ export type MetaSpecVersionGuard = {
440
+ /** Field carrying the contract version. Default `"v"` */
441
+ field?: string
442
+ /** Highest version this entry's `map`/`expand` was written against */
443
+ max: number
444
+ }
445
+
446
+ /**
447
+ * Policy entry reading its source key off a route schema instead of route meta.
448
+ *
449
+ * The object key names the entry; `read` says which schema-metadata key it reads, so several
450
+ * entries can read one reserved key — the shape a publisher takes when it stamps a
451
+ * discriminated union under a single key.
452
+ */
453
+ export type MetaSpecSchemaEntry<TValue = unknown> = (MetaSpecExpand<TValue> | MetaSpecSingle<TValue>) & {
454
+ /** Schema sources to search, in order. First one carrying a matching descriptor wins */
455
+ from?: readonly MetaSpecSchemaSource[]
456
+ /**
457
+ * Shallow subset the descriptor must satisfy for this entry to claim it — the discriminant
458
+ * of a union, e.g. `{ kind: "entity" }`. A descriptor found under `read` that satisfies no
459
+ * entry's `match` is a build error, never a silently missing tag.
460
+ */
461
+ match?: Record<string, unknown>
462
+ /** Schema-metadata key to read. Defaults to this entry's own name */
463
+ read?: string
464
+ /** Where in the schema to look. Default `"root"` */
465
+ search?: MetaSpecSchemaSearch
466
+ /** Fail the build on a descriptor from a newer publisher contract than this entry handles */
467
+ version?: MetaSpecVersionGuard
468
+ }
469
+
470
+ /** Per-document filter over emitted operation keys */
471
+ export type MetaSpecProfile = {
472
+ /** Denylist of emitted keys */
473
+ exclude?: readonly string[]
474
+ /** Allowlist of emitted `x-*` keys — standard fields are always kept unless excluded */
475
+ include?: readonly string[]
476
+ }
477
+
478
+ /** How an unmapped meta key is reported at codegen time */
479
+ export type MetaSpecStrictness = "error" | "off" | "warn"
480
+
481
+ type MetaSpecSections<TMetaSection> = {
482
+ /** Route-meta entries, keyed by meta key */
483
+ meta?: TMetaSection
484
+ /** Named per-document filters. `"default"` filters nothing */
485
+ profiles?: Record<string, MetaSpecProfile>
486
+ /** Schema-derived entries, keyed by the key read off the schema's metadata */
487
+ schema?: Record<string, MetaSpecSchemaEntry<never>>
488
+ /** How to report a meta key with no entry. Default `"error"` */
489
+ strict?: MetaSpecStrictness
490
+ }
491
+
492
+ /** Built-in meta keys ship with a default policy — overriding them is optional */
493
+ type MetaSpecOptionalKeys<TMeta> = {
494
+ [K in Extract<keyof TMeta, keyof OpenApiMeta>]?: MetaSpecEntry<Exclude<TMeta[K], undefined>>
495
+ }
496
+
497
+ /** Every app-defined meta key must have an entry — a missing key is a type error */
498
+ type MetaSpecRequiredKeys<TMeta> = {
499
+ [K in Exclude<keyof TMeta, keyof OpenApiMeta>]-?: MetaSpecEntry<Exclude<TMeta[K], undefined>>
500
+ }
501
+
502
+ /** Declarative policy for what flows from route meta and route schemas into the document */
503
+ export type HoneyMetaSpec<TMeta = unknown> = [TMeta] extends [never]
504
+ ? MetaSpecSections<Record<string, MetaSpecEntry>>
505
+ : TMeta extends Record<string, unknown>
506
+ ? MetaSpecSections<MetaSpecOptionalKeys<TMeta> & MetaSpecRequiredKeys<TMeta>>
507
+ : MetaSpecSections<Record<string, MetaSpecEntry>>
508
+
509
+ /** Erased runtime shape of a resolved policy — what codegen consumes */
510
+ export type MetaSpecConfig = {
511
+ meta?: Record<string, MetaSpecEntry>
512
+ profiles?: Record<string, MetaSpecProfile>
513
+ schema?: Record<string, MetaSpecSchemaEntry>
514
+ strict?: MetaSpecStrictness
515
+ }
516
+
383
517
  /** Built-in meta shape — always available on .meta() */
384
518
  export type DefaultMeta = OpenApiMeta
385
519
 
386
520
  /** Augmented by generated code per-app to narrow invalidate selectors */
387
521
  export interface HoneyCodegen {}
388
522
 
389
- type ResolveSelector = HoneyCodegen extends { routeSelector: infer R }
390
- ? R extends string ? R : string
391
- : string
523
+ type ResolveSelector = HoneyCodegen extends { routeSelector: infer R } ? (R extends string ? R : string) : string
392
524
 
393
525
  /** Utility: merges built-in meta, custom meta T, and type-safe invalidate from codegen */
394
- export type HoneyMeta<T = {}> = DefaultMeta & T & {
395
- invalidate?: readonly ResolveSelector[] | null
396
- }
526
+ export type HoneyMeta<T = {}> = DefaultMeta &
527
+ T & {
528
+ invalidate?: readonly ResolveSelector[] | null
529
+ }
397
530
 
398
531
  /** Accumulated route schema entry for RPC client generation */
399
532
  export type RouteRecord<
@@ -450,17 +583,15 @@ export type InferCtx<T> = T extends { readonly $ctx: infer TCtx } ? Omit<TCtx, "
450
583
  export type InferEnv<T> = T extends { readonly $env: infer TEnv } ? TEnv : never
451
584
 
452
585
  /** All HTTP methods used across all routes */
453
- export type InferMethods<T> =
454
- InferRoutes<T> extends infer R ? { [P in keyof R]: keyof R[P] & string }[keyof R] : never
586
+ export type InferMethods<T> = InferRoutes<T> extends infer R ? { [P in keyof R]: keyof R[P] & string }[keyof R] : never
455
587
 
456
588
  /** Available route paths registered on a Honey instance */
457
589
  export type InferRoutePaths<T> = keyof InferRoutes<T> & string
458
590
 
459
591
  /** Available methods for a given path ("get", "post", etc.) */
460
- export type InferRouteMethods<
461
- T,
462
- TPath extends InferRoutePaths<T>,
463
- > = InferRoutes<T>[TPath] extends infer M ? keyof M & string : never
592
+ export type InferRouteMethods<T, TPath extends InferRoutePaths<T>> = InferRoutes<T>[TPath] extends infer M
593
+ ? keyof M & string
594
+ : never
464
595
 
465
596
  /** Lookup a specific route's record from the accumulated route map */
466
597
  type RouteLookup<T, TPath extends string, TMethod extends string> =
@@ -478,39 +609,24 @@ type RouteLookup<T, TPath extends string, TMethod extends string> =
478
609
  export type InferMeta<T> = T extends { readonly $meta: infer M } ? M : never
479
610
 
480
611
  /** Extract the full handler context type for a specific route */
481
- export type InferRouteCtx<
482
- T,
483
- TPath extends InferRoutePaths<T>,
484
- TMethod extends InferRouteMethods<T, TPath>,
485
- > = RouteLookup<T, TPath, TMethod> extends { ctx: infer C } ? C : never
612
+ export type InferRouteCtx<T, TPath extends InferRoutePaths<T>, TMethod extends InferRouteMethods<T, TPath>> =
613
+ RouteLookup<T, TPath, TMethod> extends { ctx: infer C } ? C : never
486
614
 
487
615
  /** Extract input type for a specific route */
488
- export type InferRouteInput<
489
- T,
490
- TPath extends InferRoutePaths<T>,
491
- TMethod extends InferRouteMethods<T, TPath>,
492
- > = RouteLookup<T, TPath, TMethod> extends { input: infer I } ? I : never
616
+ export type InferRouteInput<T, TPath extends InferRoutePaths<T>, TMethod extends InferRouteMethods<T, TPath>> =
617
+ RouteLookup<T, TPath, TMethod> extends { input: infer I } ? I : never
493
618
 
494
619
  /** Extract meta type for a specific route */
495
- export type InferRouteMeta<
496
- T,
497
- TPath extends InferRoutePaths<T>,
498
- TMethod extends InferRouteMethods<T, TPath>,
499
- > = RouteLookup<T, TPath, TMethod> extends { meta: infer M } ? M : never
620
+ export type InferRouteMeta<T, TPath extends InferRoutePaths<T>, TMethod extends InferRouteMethods<T, TPath>> =
621
+ RouteLookup<T, TPath, TMethod> extends { meta: infer M } ? M : never
500
622
 
501
623
  /** Extract output type for a specific route */
502
- export type InferRouteOutput<
503
- T,
504
- TPath extends InferRoutePaths<T>,
505
- TMethod extends InferRouteMethods<T, TPath>,
506
- > = RouteLookup<T, TPath, TMethod> extends { output: infer O } ? O : never
624
+ export type InferRouteOutput<T, TPath extends InferRoutePaths<T>, TMethod extends InferRouteMethods<T, TPath>> =
625
+ RouteLookup<T, TPath, TMethod> extends { output: infer O } ? O : never
507
626
 
508
627
  /** Extract error keys for a specific route */
509
- export type InferRouteErrors<
510
- T,
511
- TPath extends InferRoutePaths<T>,
512
- TMethod extends InferRouteMethods<T, TPath>,
513
- > = RouteLookup<T, TPath, TMethod> extends { errors: infer E } ? E : never
628
+ export type InferRouteErrors<T, TPath extends InferRoutePaths<T>, TMethod extends InferRouteMethods<T, TPath>> =
629
+ RouteLookup<T, TPath, TMethod> extends { errors: infer E } ? E : never
514
630
 
515
631
  /** Extract the error factory type from a Honey instance */
516
632
  export type InferErrorFactory<T> = T extends { readonly $errorFactory: infer F } ? F : never
@@ -519,26 +635,17 @@ export type InferErrorFactory<T> = T extends { readonly $errorFactory: infer F }
519
635
  * Compute errorsByStatus from error factory + declared error keys.
520
636
  * Maps status codes to body shapes for client-side discrimination.
521
637
  */
522
- export type ComputeErrorsByStatus<
523
- TFactory,
524
- TErrorKeys extends string,
525
- TMetaSymbol extends symbol,
526
- > = TFactory extends { readonly [K in TMetaSymbol]: infer TMeta }
638
+ export type ComputeErrorsByStatus<TFactory, TErrorKeys extends string, TMetaSymbol extends symbol> = TFactory extends {
639
+ readonly [K in TMetaSymbol]: infer TMeta
640
+ }
527
641
  ? TMeta extends Record<string, { schema: infer _S; statusKey: infer _SK }>
528
642
  ? _BuildByStatus<TMeta, TErrorKeys>
529
643
  : never
530
644
  : never
531
645
 
532
646
  /** Internal: build { [statusCode]: bodyShape } from meta + error keys */
533
- type _BuildByStatus<
534
- TMeta extends Record<string, { schema: unknown; statusKey: unknown }>,
535
- TKeys extends string,
536
- > = {
537
- [SK in _UsedStatusKeys<TMeta, TKeys> & StatusKey as StatusKeyToCodeType<SK>]: _ShapesForStatus<
538
- TMeta,
539
- TKeys,
540
- SK
541
- >
647
+ type _BuildByStatus<TMeta extends Record<string, { schema: unknown; statusKey: unknown }>, TKeys extends string> = {
648
+ [SK in _UsedStatusKeys<TMeta, TKeys> & StatusKey as StatusKeyToCodeType<SK>]: _ShapesForStatus<TMeta, TKeys, SK>
542
649
  }
543
650
 
544
651
  /** Internal: collect all statusKeys used by the declared error keys */
@@ -607,11 +714,7 @@ type StatusKeyToCodeType<SK extends StatusKey> = SK extends "ok"
607
714
  export type InferBasePath<T> = T extends { readonly $basePath: infer P } ? P : never
608
715
 
609
716
  /** Merge two path segments — "/" is identity */
610
- export type MergePath<A extends string, B extends string> = A extends "/"
611
- ? B
612
- : B extends "/"
613
- ? A
614
- : `${A}${B}`
717
+ export type MergePath<A extends string, B extends string> = A extends "/" ? B : B extends "/" ? A : `${A}${B}`
615
718
 
616
719
  /* ── Tap types ── */
617
720
 
@@ -619,10 +722,7 @@ export type MergePath<A extends string, B extends string> = A extends "/"
619
722
  export type PendingTap = { key: string; payload: unknown }
620
723
 
621
724
  /** Handler registered via app.tap(key, handler) — receives context + payload */
622
- export type TapHandler<TEnv = unknown> = (
623
- ctx: TapContext<TEnv>,
624
- payload: unknown,
625
- ) => void | Promise<void>
725
+ export type TapHandler<TEnv = unknown> = (ctx: TapContext<TEnv>, payload: unknown) => void | Promise<void>
626
726
 
627
727
  /** Subset of HoneyContext exposed to tap handlers — no res, no recursive tap */
628
728
  export type TapContext<TEnv = unknown> = {
package/src/validation.ts CHANGED
@@ -64,11 +64,7 @@ export function parseCookies(header: string): Record<string, string> {
64
64
  let value = trimmed.slice(eqIdx + 1)
65
65
  if (value.indexOf("%") !== -1) value = tryDecodeCookieValue(value)
66
66
  /* RFC 6265: strip surrounding double quotes */
67
- if (
68
- value.length >= 2 &&
69
- value.charCodeAt(0) === 34 &&
70
- value.charCodeAt(value.length - 1) === 34
71
- ) {
67
+ if (value.length >= 2 && value.charCodeAt(0) === 34 && value.charCodeAt(value.length - 1) === 34) {
72
68
  value = value.slice(1, -1)
73
69
  }
74
70
  result[name] = value
@@ -96,10 +92,7 @@ function extractVendorCode(issue: StandardSchemaIssue, vendor: string): string {
96
92
  return "unknown"
97
93
  }
98
94
 
99
- export function normalizeIssues(
100
- issues: ReadonlyArray<StandardSchemaIssue>,
101
- vendor: string,
102
- ): NormalizedIssue[] {
95
+ export function normalizeIssues(issues: ReadonlyArray<StandardSchemaIssue>, vendor: string): NormalizedIssue[] {
103
96
  return issues.map((issue) => {
104
97
  const path = issue.path ? issue.path.map(toPropertyKey) : []
105
98
  return {
@@ -113,10 +106,7 @@ export function normalizeIssues(
113
106
  })
114
107
  }
115
108
 
116
- export function issuesToFieldErrors(
117
- issues: NormalizedIssue[],
118
- prefix: string,
119
- ): Record<string, FieldError[]> {
109
+ export function issuesToFieldErrors(issues: NormalizedIssue[], prefix: string): Record<string, FieldError[]> {
120
110
  const fields: Record<string, FieldError[]> = {}
121
111
  for (const issue of issues) {
122
112
  const fieldName = issue.path.at(-1)?.toString() ?? "unknown"
@@ -134,11 +124,7 @@ export function issuesToFieldErrors(
134
124
  return fields
135
125
  }
136
126
 
137
- async function runSchema(
138
- schema: StandardSchemaLike,
139
- data: unknown,
140
- prefix: string,
141
- ): Promise<unknown> {
127
+ async function runSchema(schema: StandardSchemaLike, data: unknown, prefix: string): Promise<unknown> {
142
128
  const result = await schema["~standard"].validate(data)
143
129
  if (result.issues) {
144
130
  const normalized = normalizeIssues(result.issues, schema["~standard"].vendor)
@@ -234,8 +220,7 @@ export async function validateInput(
234
220
  if (schemas.headers) {
235
221
  const { mode, schema } = resolveSchema(schemas.headers)
236
222
  const headerRecord = headersToRecord(req.headers)
237
- result.headers =
238
- mode === "readableStream" ? headerRecord : await runSchema(schema, headerRecord, "headers")
223
+ result.headers = mode === "readableStream" ? headerRecord : await runSchema(schema, headerRecord, "headers")
239
224
  }
240
225
 
241
226
  /* cookies */
@@ -247,11 +232,7 @@ export async function validateInput(
247
232
  }
248
233
 
249
234
  /* body-based: json or form — skip for methods without a request body */
250
- const hasBody =
251
- req.method !== "DELETE" &&
252
- req.method !== "GET" &&
253
- req.method !== "HEAD" &&
254
- req.method !== "OPTIONS"
235
+ const hasBody = req.method !== "DELETE" && req.method !== "GET" && req.method !== "HEAD" && req.method !== "OPTIONS"
255
236
  if (hasBody && (schemas.json || schemas.form)) {
256
237
  const contentType = req.headers.get("content-type")
257
238
  const parser = selectParser(contentType)
@@ -284,11 +265,7 @@ export async function validateInput(
284
265
  return result
285
266
  }
286
267
 
287
- export async function validateOutput(
288
- schema: StandardSchemaLike,
289
- statusKey: string,
290
- data: unknown,
291
- ): Promise<void> {
268
+ export async function validateOutput(schema: StandardSchemaLike, statusKey: string, data: unknown): Promise<void> {
292
269
  const result = await schema["~standard"].validate(data)
293
270
  if (result.issues) {
294
271
  throw new HoneyError({
package/src/ws/bun.ts CHANGED
@@ -1,10 +1,4 @@
1
- export {
2
- type RawSocket,
3
- type WSAdapter,
4
- type WSContext,
5
- WSContextImpl,
6
- type WSHandler,
7
- } from "./cloudflare.ts"
1
+ export { type RawSocket, type WSAdapter, type WSContext, WSContextImpl, type WSHandler } from "./cloudflare.ts"
8
2
 
9
3
  import type { WSAdapter, WSContext, WSHandler } from "./cloudflare.ts"
10
4
  import { WSContextImpl } from "./cloudflare.ts"
@@ -76,8 +70,7 @@ export function bunWebSocket(): BunWSAdapter {
76
70
  message(ws: BunRawSocket, data: ArrayBufferView | string) {
77
71
  if (!ws.data.socket) ws.data.socket = new WSContextImpl(ws)
78
72
  /* Bun passes Buffer (Uint8Array) for binary — normalize to ArrayBuffer for WSHandler */
79
- const normalized =
80
- typeof data === "string" ? data : ((data as Uint8Array).buffer as ArrayBuffer)
73
+ const normalized = typeof data === "string" ? data : ((data as Uint8Array).buffer as ArrayBuffer)
81
74
  ws.data.handler.onMessage?.(undefined, ws.data.socket, normalized)
82
75
  },
83
76
  open(ws: BunRawSocket) {
@@ -87,13 +87,10 @@ export class WSContextImpl<T extends RawSocket = RawSocket> implements WSContext
87
87
  * Uses WebSocketPair + server.accept() + addEventListener.
88
88
  */
89
89
  export function cfWebSocket(): WSAdapter {
90
- return {
91
- upgrade(_req: Request, _env: unknown, handler: WSHandler<unknown>) {
92
- type CFWebSocket = RawSocket & { accept(): void }
93
- const Ctor = (globalThis as Record<string, unknown>)["WebSocketPair"] as new () => [
94
- CFWebSocket,
95
- CFWebSocket,
96
- ]
90
+ return {
91
+ upgrade(_req: Request, _env: unknown, handler: WSHandler<unknown>) {
92
+ type CFWebSocket = RawSocket & { accept(): void }
93
+ const Ctor = (globalThis as Record<string, unknown>)["WebSocketPair"] as new () => [CFWebSocket, CFWebSocket]
97
94
  const [client, server] = new Ctor()
98
95
  server.accept()
99
96
  const socket = new WSContextImpl(server)
@@ -120,14 +117,14 @@ export function cfWebSocket(): WSAdapter {
120
117
  }
121
118
  handler.onOpen?.(undefined, socket)
122
119
 
123
- return {
124
- response: new Response(null, {
125
- headers: { upgrade: "websocket" },
126
- status: 101,
127
- webSocket: client,
128
- } as unknown as ResponseInit),
129
- socket,
130
- }
131
- },
120
+ return {
121
+ response: new Response(null, {
122
+ headers: { upgrade: "websocket" },
123
+ status: 101,
124
+ webSocket: client,
125
+ } as unknown as ResponseInit),
126
+ socket,
127
+ }
128
+ },
132
129
  }
133
130
  }
package/src/ws/deno.ts CHANGED
@@ -5,30 +5,27 @@ export {
5
5
  type WSPreUpgrade,
6
6
  WSContextImpl,
7
7
  type WSHandler,
8
- } from "./cloudflare.ts";
8
+ } from "./cloudflare.ts"
9
9
 
10
- import type { WSAdapter, WSHandler, WSPreUpgrade } from "./cloudflare.ts";
11
- import { WSContextImpl } from "./cloudflare.ts";
10
+ import type { WSAdapter, WSHandler, WSPreUpgrade } from "./cloudflare.ts"
11
+ import { WSContextImpl } from "./cloudflare.ts"
12
12
 
13
13
  type DenoNs = {
14
14
  Deno: {
15
15
  upgradeWebSocket(req: Request): {
16
- response: Response;
16
+ response: Response
17
17
  socket: {
18
- addEventListener(
19
- type: string,
20
- listener: (...args: never[]) => void,
21
- ): void;
22
- } & DenoRawSocket;
23
- };
24
- };
25
- };
18
+ addEventListener(type: string, listener: (...args: never[]) => void): void
19
+ } & DenoRawSocket
20
+ }
21
+ }
22
+ }
26
23
 
27
24
  type DenoRawSocket = {
28
- close(code?: number, reason?: string): void;
29
- readyState: number;
30
- send(data: ArrayBuffer | Uint8Array | string): void;
31
- };
25
+ close(code?: number, reason?: string): void
26
+ readyState: number
27
+ send(data: ArrayBuffer | Uint8Array | string): void
28
+ }
32
29
 
33
30
  type DenoSocket = {
34
31
  addEventListener(type: string, listener: (...args: never[]) => void): void
@@ -36,23 +33,17 @@ type DenoSocket = {
36
33
 
37
34
  function bindHandlers(rawSocket: DenoSocket, socket: WSContextImpl<DenoRawSocket>, handler: WSHandler<unknown>): void {
38
35
  rawSocket.addEventListener("open", () => {
39
- handler.onOpen?.(undefined, socket);
40
- });
41
- rawSocket.addEventListener(
42
- "message",
43
- (evt: { data: ArrayBuffer | string }) => {
44
- handler.onMessage?.(undefined, socket, evt.data);
45
- },
46
- );
47
- rawSocket.addEventListener(
48
- "close",
49
- (evt: { code: number; reason: string }) => {
50
- handler.onClose?.(undefined, socket, evt.code, evt.reason);
51
- },
52
- );
36
+ handler.onOpen?.(undefined, socket)
37
+ })
38
+ rawSocket.addEventListener("message", (evt: { data: ArrayBuffer | string }) => {
39
+ handler.onMessage?.(undefined, socket, evt.data)
40
+ })
41
+ rawSocket.addEventListener("close", (evt: { code: number; reason: string }) => {
42
+ handler.onClose?.(undefined, socket, evt.code, evt.reason)
43
+ })
53
44
  rawSocket.addEventListener("error", (evt: unknown) => {
54
- handler.onError?.(undefined, socket, evt);
55
- });
45
+ handler.onError?.(undefined, socket, evt)
46
+ })
56
47
  }
57
48
 
58
49
  /**
@@ -69,9 +60,9 @@ export function denoWebSocket(): WSAdapter {
69
60
 
70
61
  return {
71
62
  preUpgrade(req: Request): WSPreUpgrade {
72
- const denoNs = globalThis as unknown as DenoNs;
73
- const { response, socket: rawSocket } = denoNs.Deno.upgradeWebSocket(req);
74
- const socket = new WSContextImpl(rawSocket as DenoRawSocket);
63
+ const denoNs = globalThis as unknown as DenoNs
64
+ const { response, socket: rawSocket } = denoNs.Deno.upgradeWebSocket(req)
65
+ const socket = new WSContextImpl(rawSocket as DenoRawSocket)
75
66
  const pre: WSPreUpgrade = {
76
67
  response,
77
68
  socket,
@@ -99,11 +90,11 @@ export function denoWebSocket(): WSAdapter {
99
90
  }
100
91
 
101
92
  /* globalThis cast: Deno namespace only exists at runtime on Deno — no way to type it statically */
102
- const denoNs = globalThis as unknown as DenoNs;
103
- const { response, socket: rawSocket } = denoNs.Deno.upgradeWebSocket(req);
104
- const socket = new WSContextImpl(rawSocket as DenoRawSocket);
93
+ const denoNs = globalThis as unknown as DenoNs
94
+ const { response, socket: rawSocket } = denoNs.Deno.upgradeWebSocket(req)
95
+ const socket = new WSContextImpl(rawSocket as DenoRawSocket)
105
96
  bindHandlers(rawSocket, socket, handler)
106
- return { response, socket };
97
+ return { response, socket }
107
98
  },
108
- };
99
+ }
109
100
  }
package/src/ws/node.ts CHANGED
@@ -1,19 +1,10 @@
1
- export {
2
- type RawSocket,
3
- type WSAdapter,
4
- type WSContext,
5
- WSContextImpl,
6
- type WSHandler,
7
- } from "./cloudflare.ts"
1
+ export { type RawSocket, type WSAdapter, type WSContext, WSContextImpl, type WSHandler } from "./cloudflare.ts"
8
2
 
9
3
  import type { WSAdapter, WSHandler } from "./cloudflare.ts"
10
4
  import { WSContextImpl } from "./cloudflare.ts"
11
5
 
12
6
  type WsModule = {
13
- WebSocketServer: new (opts: {
14
- noServer: true
15
- perMessageDeflate: boolean
16
- }) => {
7
+ WebSocketServer: new (opts: { noServer: true; perMessageDeflate: boolean }) => {
17
8
  handleUpgrade(req: unknown, socket: unknown, head: unknown, cb: (ws: NodeWS) => void): void
18
9
  }
19
10
  }
@@ -88,20 +79,14 @@ export function nodeWebSocket(opts?: { keepalive?: KeepaliveConfig }): WSAdapter
88
79
  handler.onMessage?.(
89
80
  undefined,
90
81
  socket,
91
- data.buffer.slice(
92
- data.byteOffset,
93
- data.byteOffset + data.byteLength,
94
- ) as ArrayBuffer,
82
+ data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer,
95
83
  )
96
84
  } else if (Array.isArray(data)) {
97
85
  const merged = Buffer.concat(data)
98
86
  handler.onMessage?.(
99
87
  undefined,
100
88
  socket,
101
- merged.buffer.slice(
102
- merged.byteOffset,
103
- merged.byteOffset + merged.byteLength,
104
- ) as ArrayBuffer,
89
+ merged.buffer.slice(merged.byteOffset, merged.byteOffset + merged.byteLength) as ArrayBuffer,
105
90
  )
106
91
  } else {
107
92
  handler.onMessage?.(undefined, socket, data)