@lovrozagar/honey 0.6.8 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (674) hide show
  1. package/README.md +514 -192
  2. package/bin/honey.js +25 -0
  3. package/dist/accepts.d.ts +7 -1
  4. package/dist/accepts.d.ts.map +1 -1
  5. package/dist/accepts.js +126 -0
  6. package/dist/accepts.js.map +1 -0
  7. package/dist/body-kind.d.ts +26 -0
  8. package/dist/body-kind.d.ts.map +1 -0
  9. package/dist/body-kind.js +70 -0
  10. package/dist/body-kind.js.map +1 -0
  11. package/dist/body-limit.d.ts +5 -1
  12. package/dist/body-limit.d.ts.map +1 -1
  13. package/dist/body-limit.js +126 -0
  14. package/dist/body-limit.js.map +1 -0
  15. package/dist/build/index.d.ts +19 -3
  16. package/dist/build/index.d.ts.map +1 -1
  17. package/dist/build/index.js +173 -0
  18. package/dist/build/index.js.map +1 -0
  19. package/dist/cli-args.d.ts +33 -0
  20. package/dist/cli-args.d.ts.map +1 -0
  21. package/dist/cli-args.js +102 -0
  22. package/dist/cli-args.js.map +1 -0
  23. package/dist/cli-go/auth.go +26 -0
  24. package/dist/cli-go/config.go +192 -0
  25. package/dist/cli-go/errors.go +75 -0
  26. package/dist/cli-go/multipart.go +79 -0
  27. package/dist/cli-go/output.go +163 -0
  28. package/dist/cli-go/root.go +30 -0
  29. package/dist/cli-go/stream.go +88 -0
  30. package/dist/cli-go/version.go +5 -0
  31. package/dist/cli.d.ts +1 -1
  32. package/dist/cli.js +209 -0
  33. package/dist/cli.js.map +1 -0
  34. package/dist/client/defaults.js +48 -0
  35. package/dist/client/defaults.js.map +1 -0
  36. package/dist/client/error.d.ts +35 -0
  37. package/dist/client/error.d.ts.map +1 -1
  38. package/dist/client/error.js +71 -0
  39. package/dist/client/error.js.map +1 -0
  40. package/dist/client/http.d.ts +67 -8
  41. package/dist/client/http.d.ts.map +1 -1
  42. package/dist/client/http.js +571 -0
  43. package/dist/client/http.js.map +1 -0
  44. package/dist/client/index.d.ts +3 -2
  45. package/dist/client/index.d.ts.map +1 -1
  46. package/dist/client/index.js +95 -0
  47. package/dist/client/index.js.map +1 -0
  48. package/dist/client/path.d.ts +26 -0
  49. package/dist/client/path.d.ts.map +1 -0
  50. package/dist/client/path.js +163 -0
  51. package/dist/client/path.js.map +1 -0
  52. package/dist/client/redirect-policy.d.ts +12 -0
  53. package/dist/client/redirect-policy.d.ts.map +1 -0
  54. package/dist/client/redirect-policy.js +20 -0
  55. package/dist/client/redirect-policy.js.map +1 -0
  56. package/dist/client/sdk.d.ts +36 -0
  57. package/dist/client/sdk.d.ts.map +1 -1
  58. package/dist/client/sdk.js +292 -0
  59. package/dist/client/sdk.js.map +1 -0
  60. package/dist/client/sse.d.ts +13 -0
  61. package/dist/client/sse.d.ts.map +1 -1
  62. package/dist/client/sse.js +129 -0
  63. package/dist/client/sse.js.map +1 -0
  64. package/dist/client/types.js +2 -0
  65. package/dist/client/types.js.map +1 -0
  66. package/dist/client/ws.d.ts +9 -2
  67. package/dist/client/ws.d.ts.map +1 -1
  68. package/dist/client/ws.js +112 -0
  69. package/dist/client/ws.js.map +1 -0
  70. package/dist/client-go/errors.go +236 -0
  71. package/dist/client-go/export.go +44 -0
  72. package/dist/client-go/invalidation.go +371 -0
  73. package/dist/client-go/nullable.go +62 -0
  74. package/dist/client-go/realtime.go +354 -0
  75. package/dist/client-go/result.go +20 -0
  76. package/dist/client-go/runtime.go +975 -0
  77. package/dist/client-go/sse.go +159 -0
  78. package/dist/client-go/transport.go +495 -0
  79. package/dist/client-go/ws.go +128 -0
  80. package/dist/client-info.d.ts +36 -0
  81. package/dist/client-info.d.ts.map +1 -0
  82. package/dist/client-info.js +53 -0
  83. package/dist/client-info.js.map +1 -0
  84. package/dist/client-mcp/README.md +10 -0
  85. package/dist/client-mcp/server.ts +221 -0
  86. package/dist/client-mcp/types.ts +19 -0
  87. package/dist/client-python/_errors.py +115 -0
  88. package/dist/client-python/_invalidation.py +494 -0
  89. package/dist/client-python/_realtime.py +179 -0
  90. package/dist/client-python/_runtime.py +860 -0
  91. package/dist/client-python/_sse.py +155 -0
  92. package/dist/client-python/_transport.py +366 -0
  93. package/dist/client-python/_ws.py +159 -0
  94. package/dist/client-python/py.typed +0 -0
  95. package/dist/client-rust/errors.rs +220 -0
  96. package/dist/client-rust/invalidation.rs +412 -0
  97. package/dist/client-rust/invalidation_sync.rs +93 -0
  98. package/dist/client-rust/nullable.rs +17 -0
  99. package/dist/client-rust/realtime.rs +702 -0
  100. package/dist/client-rust/result.rs +22 -0
  101. package/dist/client-rust/runtime.rs +980 -0
  102. package/dist/client-rust/runtime_sync.rs +484 -0
  103. package/dist/client-rust/sse.rs +188 -0
  104. package/dist/client-rust/ws.rs +167 -0
  105. package/dist/codegen-go-cli.d.ts.map +1 -1
  106. package/dist/codegen-go-cli.js +964 -0
  107. package/dist/codegen-go-cli.js.map +1 -0
  108. package/dist/codegen-go.d.ts +15 -3
  109. package/dist/codegen-go.d.ts.map +1 -1
  110. package/dist/codegen-go.js +872 -0
  111. package/dist/codegen-go.js.map +1 -0
  112. package/dist/codegen-hash.js +9 -0
  113. package/dist/codegen-hash.js.map +1 -0
  114. package/dist/codegen-ir.d.ts +69 -2
  115. package/dist/codegen-ir.d.ts.map +1 -1
  116. package/dist/codegen-ir.js +661 -0
  117. package/dist/codegen-ir.js.map +1 -0
  118. package/dist/codegen-lang.d.ts +65 -0
  119. package/dist/codegen-lang.d.ts.map +1 -0
  120. package/dist/codegen-lang.js +526 -0
  121. package/dist/codegen-lang.js.map +1 -0
  122. package/dist/codegen-load.d.ts +31 -0
  123. package/dist/codegen-load.d.ts.map +1 -0
  124. package/dist/codegen-load.js +138 -0
  125. package/dist/codegen-load.js.map +1 -0
  126. package/dist/codegen-loaders.js +55 -0
  127. package/dist/codegen-loaders.js.map +1 -0
  128. package/dist/codegen-mcp.d.ts.map +1 -1
  129. package/dist/codegen-mcp.js +368 -0
  130. package/dist/codegen-mcp.js.map +1 -0
  131. package/dist/codegen-python.d.ts +13 -1
  132. package/dist/codegen-python.d.ts.map +1 -1
  133. package/dist/codegen-python.js +828 -0
  134. package/dist/codegen-python.js.map +1 -0
  135. package/dist/codegen-route-tree-intern.d.ts +15 -5
  136. package/dist/codegen-route-tree-intern.d.ts.map +1 -1
  137. package/dist/codegen-route-tree-intern.js +221 -0
  138. package/dist/codegen-route-tree-intern.js.map +1 -0
  139. package/dist/codegen-rust.d.ts.map +1 -1
  140. package/dist/codegen-rust.js +1293 -0
  141. package/dist/codegen-rust.js.map +1 -0
  142. package/dist/codegen-sanitize.d.ts.map +1 -1
  143. package/dist/codegen-sanitize.js +56 -0
  144. package/dist/codegen-sanitize.js.map +1 -0
  145. package/dist/codegen-schema-naming.d.ts +5 -0
  146. package/dist/codegen-schema-naming.d.ts.map +1 -1
  147. package/dist/codegen-schema-naming.js +150 -0
  148. package/dist/codegen-schema-naming.js.map +1 -0
  149. package/dist/codegen-sdk-model.d.ts +71 -0
  150. package/dist/codegen-sdk-model.d.ts.map +1 -0
  151. package/dist/codegen-sdk-model.js +179 -0
  152. package/dist/codegen-sdk-model.js.map +1 -0
  153. package/dist/codegen.d.ts +41 -23
  154. package/dist/codegen.d.ts.map +1 -1
  155. package/dist/codegen.js +4610 -0
  156. package/dist/codegen.js.map +1 -0
  157. package/dist/context.d.ts +36 -6
  158. package/dist/context.d.ts.map +1 -1
  159. package/dist/context.js +242 -0
  160. package/dist/context.js.map +1 -0
  161. package/dist/cookie-sign.d.ts +16 -2
  162. package/dist/cookie-sign.d.ts.map +1 -1
  163. package/dist/cookie-sign.js +104 -0
  164. package/dist/cookie-sign.js.map +1 -0
  165. package/dist/cookie.d.ts +12 -0
  166. package/dist/cookie.d.ts.map +1 -1
  167. package/dist/cookie.js +138 -0
  168. package/dist/cookie.js.map +1 -0
  169. package/dist/cors.d.ts +1 -0
  170. package/dist/cors.d.ts.map +1 -1
  171. package/dist/cors.js +87 -0
  172. package/dist/cors.js.map +1 -0
  173. package/dist/crypto.d.ts +6 -1
  174. package/dist/crypto.d.ts.map +1 -1
  175. package/dist/crypto.js +38 -0
  176. package/dist/crypto.js.map +1 -0
  177. package/dist/csrf.d.ts +19 -0
  178. package/dist/csrf.d.ts.map +1 -1
  179. package/dist/csrf.js +79 -0
  180. package/dist/csrf.js.map +1 -0
  181. package/dist/curl-logger.d.ts +14 -3
  182. package/dist/curl-logger.d.ts.map +1 -1
  183. package/dist/curl-logger.js +199 -0
  184. package/dist/curl-logger.js.map +1 -0
  185. package/dist/detect-runtime.js +13 -0
  186. package/dist/detect-runtime.js.map +1 -0
  187. package/dist/dict.d.ts +11 -0
  188. package/dist/dict.d.ts.map +1 -0
  189. package/dist/dict.js +13 -0
  190. package/dist/dict.js.map +1 -0
  191. package/dist/error.d.ts.map +1 -1
  192. package/dist/error.js +44 -0
  193. package/dist/error.js.map +1 -0
  194. package/dist/errors.js +39 -0
  195. package/dist/errors.js.map +1 -0
  196. package/dist/etag.d.ts +2 -0
  197. package/dist/etag.d.ts.map +1 -1
  198. package/dist/etag.js +82 -0
  199. package/dist/etag.js.map +1 -0
  200. package/dist/feature-detect.d.ts +25 -0
  201. package/dist/feature-detect.d.ts.map +1 -0
  202. package/dist/feature-detect.js +144 -0
  203. package/dist/feature-detect.js.map +1 -0
  204. package/dist/feature-load.js +36 -0
  205. package/dist/feature-load.js.map +1 -0
  206. package/dist/feature-slots.d.ts +44 -0
  207. package/dist/feature-slots.d.ts.map +1 -0
  208. package/dist/feature-slots.js +48 -0
  209. package/dist/feature-slots.js.map +1 -0
  210. package/dist/fetch-request.d.ts +10 -0
  211. package/dist/fetch-request.d.ts.map +1 -0
  212. package/dist/fetch-request.js +13 -0
  213. package/dist/fetch-request.js.map +1 -0
  214. package/dist/gen-process.d.ts +15 -0
  215. package/dist/gen-process.d.ts.map +1 -0
  216. package/dist/gen-process.js +43 -0
  217. package/dist/gen-process.js.map +1 -0
  218. package/dist/gen-write.d.ts +19 -0
  219. package/dist/gen-write.d.ts.map +1 -0
  220. package/dist/gen-write.js +120 -0
  221. package/dist/gen-write.js.map +1 -0
  222. package/dist/glob.d.ts +21 -0
  223. package/dist/glob.d.ts.map +1 -0
  224. package/dist/glob.js +115 -0
  225. package/dist/glob.js.map +1 -0
  226. package/dist/go-type-emitter.d.ts +18 -4
  227. package/dist/go-type-emitter.d.ts.map +1 -1
  228. package/dist/go-type-emitter.js +594 -0
  229. package/dist/go-type-emitter.js.map +1 -0
  230. package/dist/honey-response.d.ts.map +1 -1
  231. package/dist/honey-response.js +338 -0
  232. package/dist/honey-response.js.map +1 -0
  233. package/dist/i18n-slot.d.ts +8 -0
  234. package/dist/i18n-slot.d.ts.map +1 -1
  235. package/dist/i18n-slot.js +14 -0
  236. package/dist/i18n-slot.js.map +1 -0
  237. package/dist/i18n.d.ts +3 -0
  238. package/dist/i18n.d.ts.map +1 -1
  239. package/dist/i18n.js +250 -0
  240. package/dist/i18n.js.map +1 -0
  241. package/dist/index.d.ts +408 -148
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +2324 -0
  244. package/dist/index.js.map +1 -0
  245. package/dist/init.d.ts +3 -5
  246. package/dist/init.d.ts.map +1 -1
  247. package/dist/init.js +140 -0
  248. package/dist/init.js.map +1 -0
  249. package/dist/input.js +4 -0
  250. package/dist/input.js.map +1 -0
  251. package/dist/invalidate-check.d.ts +11 -0
  252. package/dist/invalidate-check.d.ts.map +1 -1
  253. package/dist/invalidate-check.js +163 -0
  254. package/dist/invalidate-check.js.map +1 -0
  255. package/dist/invoke-user.d.ts +35 -0
  256. package/dist/invoke-user.d.ts.map +1 -0
  257. package/dist/invoke-user.js +73 -0
  258. package/dist/invoke-user.js.map +1 -0
  259. package/dist/ip-restrict.d.ts +13 -1
  260. package/dist/ip-restrict.d.ts.map +1 -1
  261. package/dist/ip-restrict.js +60 -0
  262. package/dist/ip-restrict.js.map +1 -0
  263. package/dist/ip.d.ts +39 -0
  264. package/dist/ip.d.ts.map +1 -0
  265. package/dist/ip.js +209 -0
  266. package/dist/ip.js.map +1 -0
  267. package/dist/logger.d.ts.map +1 -1
  268. package/dist/logger.js +113 -0
  269. package/dist/logger.js.map +1 -0
  270. package/dist/media-type.d.ts +30 -0
  271. package/dist/media-type.d.ts.map +1 -0
  272. package/dist/media-type.js +97 -0
  273. package/dist/media-type.js.map +1 -0
  274. package/dist/meta-spec-merge.d.ts +32 -0
  275. package/dist/meta-spec-merge.d.ts.map +1 -0
  276. package/dist/meta-spec-merge.js +68 -0
  277. package/dist/meta-spec-merge.js.map +1 -0
  278. package/dist/meta-spec.d.ts +16 -3
  279. package/dist/meta-spec.d.ts.map +1 -1
  280. package/dist/meta-spec.js +535 -0
  281. package/dist/meta-spec.js.map +1 -0
  282. package/dist/middleware.d.ts +23 -6
  283. package/dist/middleware.d.ts.map +1 -1
  284. package/dist/middleware.js +253 -0
  285. package/dist/middleware.js.map +1 -0
  286. package/dist/node-request.d.ts +57 -12
  287. package/dist/node-request.d.ts.map +1 -1
  288. package/dist/node-request.js +407 -0
  289. package/dist/node-request.js.map +1 -0
  290. package/dist/node.d.ts +20 -3
  291. package/dist/node.d.ts.map +1 -1
  292. package/dist/node.js +396 -0
  293. package/dist/node.js.map +1 -0
  294. package/dist/openapi/collect.d.ts +6 -3
  295. package/dist/openapi/collect.d.ts.map +1 -1
  296. package/dist/openapi/collect.js +30 -0
  297. package/dist/openapi/collect.js.map +1 -0
  298. package/dist/openapi/docs-page.d.ts +42 -0
  299. package/dist/openapi/docs-page.d.ts.map +1 -0
  300. package/dist/openapi/docs-page.js +80 -0
  301. package/dist/openapi/docs-page.js.map +1 -0
  302. package/dist/openapi/document.d.ts +4 -0
  303. package/dist/openapi/document.d.ts.map +1 -1
  304. package/dist/openapi/document.js +785 -0
  305. package/dist/openapi/document.js.map +1 -0
  306. package/dist/openapi/json-schema-slot.d.ts +7 -0
  307. package/dist/openapi/json-schema-slot.d.ts.map +1 -1
  308. package/dist/openapi/json-schema-slot.js +38 -0
  309. package/dist/openapi/json-schema-slot.js.map +1 -0
  310. package/dist/openapi/register.d.ts.map +1 -1
  311. package/dist/openapi/register.js +20 -0
  312. package/dist/openapi/register.js.map +1 -0
  313. package/dist/openapi/scalar.d.ts +3 -1
  314. package/dist/openapi/scalar.d.ts.map +1 -1
  315. package/dist/openapi/scalar.js +21 -0
  316. package/dist/openapi/scalar.js.map +1 -0
  317. package/dist/openapi/serve.d.ts +9 -0
  318. package/dist/openapi/serve.d.ts.map +1 -0
  319. package/dist/openapi/serve.js +36 -0
  320. package/dist/openapi/serve.js.map +1 -0
  321. package/dist/openapi/spec-factory.d.ts +30 -1
  322. package/dist/openapi/spec-factory.d.ts.map +1 -1
  323. package/dist/openapi/spec-factory.js +52 -0
  324. package/dist/openapi/spec-factory.js.map +1 -0
  325. package/dist/openapi/spec.d.ts +14 -5
  326. package/dist/openapi/spec.d.ts.map +1 -1
  327. package/dist/openapi/spec.js +50 -0
  328. package/dist/openapi/spec.js.map +1 -0
  329. package/dist/openapi/swagger.d.ts +3 -1
  330. package/dist/openapi/swagger.d.ts.map +1 -1
  331. package/dist/openapi/swagger.js +22 -0
  332. package/dist/openapi/swagger.js.map +1 -0
  333. package/dist/pattern.d.ts +71 -0
  334. package/dist/pattern.d.ts.map +1 -0
  335. package/dist/pattern.js +239 -0
  336. package/dist/pattern.js.map +1 -0
  337. package/dist/peer.d.ts +17 -0
  338. package/dist/peer.d.ts.map +1 -0
  339. package/dist/peer.js +50 -0
  340. package/dist/peer.js.map +1 -0
  341. package/dist/plugin.d.ts +67 -16
  342. package/dist/plugin.d.ts.map +1 -1
  343. package/dist/plugin.js +649 -0
  344. package/dist/plugin.js.map +1 -0
  345. package/dist/powered-by.d.ts.map +1 -1
  346. package/dist/powered-by.js +11 -0
  347. package/dist/powered-by.js.map +1 -0
  348. package/dist/pretty-json.d.ts.map +1 -1
  349. package/dist/pretty-json.js +47 -0
  350. package/dist/pretty-json.js.map +1 -0
  351. package/dist/producer-stream.d.ts +47 -0
  352. package/dist/producer-stream.d.ts.map +1 -0
  353. package/dist/producer-stream.js +267 -0
  354. package/dist/producer-stream.js.map +1 -0
  355. package/dist/proxy.d.ts +47 -8
  356. package/dist/proxy.d.ts.map +1 -1
  357. package/dist/proxy.js +274 -0
  358. package/dist/proxy.js.map +1 -0
  359. package/dist/python-type-emitter.d.ts +14 -1
  360. package/dist/python-type-emitter.d.ts.map +1 -1
  361. package/dist/python-type-emitter.js +157 -0
  362. package/dist/python-type-emitter.js.map +1 -0
  363. package/dist/realtime/bus.d.ts +17 -1
  364. package/dist/realtime/bus.d.ts.map +1 -1
  365. package/dist/realtime/bus.js +112 -0
  366. package/dist/realtime/bus.js.map +1 -0
  367. package/dist/realtime/register.d.ts +3 -0
  368. package/dist/realtime/register.d.ts.map +1 -0
  369. package/dist/realtime/register.js +10 -0
  370. package/dist/realtime/register.js.map +1 -0
  371. package/dist/realtime/route.d.ts +65 -26
  372. package/dist/realtime/route.d.ts.map +1 -1
  373. package/dist/realtime/route.js +72 -0
  374. package/dist/realtime/route.js.map +1 -0
  375. package/dist/realtime/server.d.ts +43 -0
  376. package/dist/realtime/server.d.ts.map +1 -0
  377. package/dist/realtime/server.js +255 -0
  378. package/dist/realtime/server.js.map +1 -0
  379. package/dist/request-id.d.ts +7 -0
  380. package/dist/request-id.d.ts.map +1 -1
  381. package/dist/request-id.js +16 -0
  382. package/dist/request-id.js.map +1 -0
  383. package/dist/request-limits.d.ts +3 -0
  384. package/dist/request-limits.d.ts.map +1 -0
  385. package/dist/request-limits.js +3 -0
  386. package/dist/request-limits.js.map +1 -0
  387. package/dist/request-path.d.ts +33 -0
  388. package/dist/request-path.d.ts.map +1 -0
  389. package/dist/request-path.js +178 -0
  390. package/dist/request-path.js.map +1 -0
  391. package/dist/request-to-curl.d.ts +7 -1
  392. package/dist/request-to-curl.d.ts.map +1 -1
  393. package/dist/request-to-curl.js +48 -0
  394. package/dist/request-to-curl.js.map +1 -0
  395. package/dist/response.d.ts +25 -2
  396. package/dist/response.d.ts.map +1 -1
  397. package/dist/response.js +313 -0
  398. package/dist/response.js.map +1 -0
  399. package/dist/rust-type-emitter.d.ts +14 -2
  400. package/dist/rust-type-emitter.d.ts.map +1 -1
  401. package/dist/rust-type-emitter.js +463 -0
  402. package/dist/rust-type-emitter.js.map +1 -0
  403. package/dist/secure-headers.d.ts.map +1 -1
  404. package/dist/secure-headers.js +53 -0
  405. package/dist/secure-headers.js.map +1 -0
  406. package/dist/serve-register.d.ts +4 -0
  407. package/dist/serve-register.d.ts.map +1 -1
  408. package/dist/serve-register.js +9 -0
  409. package/dist/serve-register.js.map +1 -0
  410. package/dist/serve-slot.d.ts +3 -0
  411. package/dist/serve-slot.d.ts.map +1 -1
  412. package/dist/serve-slot.js +52 -0
  413. package/dist/serve-slot.js.map +1 -0
  414. package/dist/serve.d.ts +18 -1
  415. package/dist/serve.d.ts.map +1 -1
  416. package/dist/serve.js +157 -0
  417. package/dist/serve.js.map +1 -0
  418. package/dist/server-timing.d.ts.map +1 -1
  419. package/dist/server-timing.js +51 -0
  420. package/dist/server-timing.js.map +1 -0
  421. package/dist/static.d.ts +9 -0
  422. package/dist/static.d.ts.map +1 -1
  423. package/dist/static.js +102 -0
  424. package/dist/static.js.map +1 -0
  425. package/dist/telemetry/otel.d.ts +2 -0
  426. package/dist/telemetry/otel.d.ts.map +1 -1
  427. package/dist/telemetry/otel.js +135 -0
  428. package/dist/telemetry/otel.js.map +1 -0
  429. package/dist/testing.d.ts +12 -0
  430. package/dist/testing.d.ts.map +1 -1
  431. package/dist/testing.js +124 -0
  432. package/dist/testing.js.map +1 -0
  433. package/dist/timeout.d.ts +6 -0
  434. package/dist/timeout.d.ts.map +1 -1
  435. package/dist/timeout.js +36 -0
  436. package/dist/timeout.js.map +1 -0
  437. package/dist/tree.d.ts +133 -36
  438. package/dist/tree.d.ts.map +1 -1
  439. package/dist/tree.js +437 -0
  440. package/dist/tree.js.map +1 -0
  441. package/dist/trust.d.ts +21 -0
  442. package/dist/trust.d.ts.map +1 -0
  443. package/dist/trust.js +100 -0
  444. package/dist/trust.js.map +1 -0
  445. package/dist/ts-type-emitter.d.ts.map +1 -1
  446. package/dist/ts-type-emitter.js +150 -0
  447. package/dist/ts-type-emitter.js.map +1 -0
  448. package/dist/type-emitter.d.ts +16 -2
  449. package/dist/type-emitter.d.ts.map +1 -1
  450. package/dist/type-emitter.js +729 -0
  451. package/dist/type-emitter.js.map +1 -0
  452. package/dist/type-extractor.d.ts +8 -4
  453. package/dist/type-extractor.d.ts.map +1 -1
  454. package/dist/type-extractor.js +819 -0
  455. package/dist/type-extractor.js.map +1 -0
  456. package/dist/types.d.ts +7 -0
  457. package/dist/types.d.ts.map +1 -1
  458. package/dist/types.js +96 -0
  459. package/dist/types.js.map +1 -0
  460. package/dist/validation.d.ts +14 -2
  461. package/dist/validation.d.ts.map +1 -1
  462. package/dist/validation.js +335 -0
  463. package/dist/validation.js.map +1 -0
  464. package/dist/with-headers.d.ts +15 -0
  465. package/dist/with-headers.d.ts.map +1 -0
  466. package/dist/with-headers.js +45 -0
  467. package/dist/with-headers.js.map +1 -0
  468. package/dist/ws/bun.d.ts +11 -5
  469. package/dist/ws/bun.d.ts.map +1 -1
  470. package/dist/ws/bun.js +82 -0
  471. package/dist/ws/bun.js.map +1 -0
  472. package/dist/ws/cloudflare.d.ts +25 -3
  473. package/dist/ws/cloudflare.d.ts.map +1 -1
  474. package/dist/ws/cloudflare.js +117 -0
  475. package/dist/ws/cloudflare.js.map +1 -0
  476. package/dist/ws/deno.d.ts +4 -4
  477. package/dist/ws/deno.d.ts.map +1 -1
  478. package/dist/ws/deno.js +133 -0
  479. package/dist/ws/deno.js.map +1 -0
  480. package/dist/ws/node.d.ts +20 -3
  481. package/dist/ws/node.d.ts.map +1 -1
  482. package/dist/ws/node.js +173 -0
  483. package/dist/ws/node.js.map +1 -0
  484. package/dist/ws/session.d.ts +11 -0
  485. package/dist/ws/session.d.ts.map +1 -0
  486. package/dist/ws/session.js +60 -0
  487. package/dist/ws/session.js.map +1 -0
  488. package/dist/ws/shared.d.ts +65 -0
  489. package/dist/ws/shared.d.ts.map +1 -0
  490. package/dist/ws/shared.js +103 -0
  491. package/dist/ws/shared.js.map +1 -0
  492. package/dist/ws-origin.d.ts +20 -0
  493. package/dist/ws-origin.d.ts.map +1 -0
  494. package/dist/ws-origin.js +41 -0
  495. package/dist/ws-origin.js.map +1 -0
  496. package/dist/yaml.d.ts +15 -1
  497. package/dist/yaml.d.ts.map +1 -1
  498. package/dist/yaml.js +101 -0
  499. package/dist/yaml.js.map +1 -0
  500. package/docs/invalidate-spec.md +13 -1
  501. package/docs/meta-spec.md +36 -12
  502. package/docs/packaging.md +43 -24
  503. package/docs/regression-matrix/README.md +32 -0
  504. package/docs/regression-matrix/review.md +44 -0
  505. package/docs/regression-matrix/ws1-3.md +97 -0
  506. package/docs/regression-matrix/ws4-8.md +107 -0
  507. package/docs/regression-matrix/ws9-12.md +266 -0
  508. package/docs/sdk.md +102 -52
  509. package/package.json +183 -52
  510. package/src/accepts.ts +91 -33
  511. package/src/body-kind.ts +67 -0
  512. package/src/body-limit.ts +102 -87
  513. package/src/build/index.ts +104 -54
  514. package/src/cli-args.ts +123 -0
  515. package/src/cli-go/auth.go +12 -1
  516. package/src/cli-go/config.go +29 -2
  517. package/src/cli-go/errors.go +28 -10
  518. package/src/cli-go/multipart.go +51 -27
  519. package/src/cli-go/output.go +58 -4
  520. package/src/cli-go/root.go +1 -1
  521. package/src/cli-go/version.go +2 -2
  522. package/src/cli.ts +154 -148
  523. package/src/client/error.ts +56 -1
  524. package/src/client/http.ts +479 -256
  525. package/src/client/index.ts +55 -17
  526. package/src/client/path.ts +169 -0
  527. package/src/client/redirect-policy.ts +19 -0
  528. package/src/client/sdk.ts +180 -101
  529. package/src/client/sse.ts +92 -66
  530. package/src/client/ws.ts +67 -31
  531. package/src/client-go/errors.go +41 -12
  532. package/src/client-go/export.go +44 -0
  533. package/src/client-go/invalidation.go +52 -54
  534. package/src/client-go/nullable.go +62 -0
  535. package/src/client-go/realtime.go +147 -110
  536. package/src/client-go/runtime.go +646 -88
  537. package/src/client-go/sse.go +104 -94
  538. package/src/client-go/transport.go +67 -27
  539. package/src/client-go/ws.go +49 -25
  540. package/src/client-info.ts +79 -0
  541. package/src/client-mcp/server.ts +133 -3
  542. package/src/client-python/_invalidation.py +207 -291
  543. package/src/client-python/_realtime.py +63 -40
  544. package/src/client-python/_runtime.py +501 -119
  545. package/src/client-python/_sse.py +130 -74
  546. package/src/client-python/_transport.py +37 -9
  547. package/src/client-python/_ws.py +50 -22
  548. package/src/client-rust/errors.rs +21 -1
  549. package/src/client-rust/invalidation.rs +202 -231
  550. package/src/client-rust/invalidation_sync.rs +16 -229
  551. package/src/client-rust/nullable.rs +17 -0
  552. package/src/client-rust/realtime.rs +217 -217
  553. package/src/client-rust/runtime.rs +602 -177
  554. package/src/client-rust/runtime_sync.rs +169 -145
  555. package/src/client-rust/sse.rs +142 -121
  556. package/src/client-rust/ws.rs +61 -37
  557. package/src/codegen-go-cli.ts +607 -728
  558. package/src/codegen-go.ts +710 -770
  559. package/src/codegen-ir.ts +381 -75
  560. package/src/codegen-lang.ts +517 -0
  561. package/src/codegen-load.ts +150 -0
  562. package/src/codegen-mcp.ts +207 -108
  563. package/src/codegen-python.ts +684 -816
  564. package/src/codegen-route-tree-intern.ts +51 -25
  565. package/src/codegen-rust.ts +946 -1240
  566. package/src/codegen-sanitize.ts +0 -4
  567. package/src/codegen-schema-naming.ts +19 -6
  568. package/src/codegen-sdk-model.ts +228 -0
  569. package/src/codegen.ts +1488 -1140
  570. package/src/context.ts +130 -11
  571. package/src/cookie-sign.ts +85 -16
  572. package/src/cookie.ts +66 -11
  573. package/src/cors.ts +55 -54
  574. package/src/crypto.ts +27 -5
  575. package/src/csrf.ts +57 -25
  576. package/src/curl-logger.ts +155 -114
  577. package/src/dict.ts +12 -0
  578. package/src/error.ts +4 -2
  579. package/src/etag.ts +58 -34
  580. package/src/feature-detect.ts +147 -0
  581. package/src/feature-slots.ts +70 -0
  582. package/src/fetch-request.ts +13 -0
  583. package/src/gen-process.ts +42 -0
  584. package/src/gen-write.ts +127 -0
  585. package/src/glob.ts +109 -0
  586. package/src/go-type-emitter.ts +406 -155
  587. package/src/honey-response.ts +36 -9
  588. package/src/i18n-slot.ts +10 -0
  589. package/src/i18n.ts +98 -23
  590. package/src/index.ts +1887 -1756
  591. package/src/init.ts +40 -28
  592. package/src/invalidate-check.ts +42 -4
  593. package/src/invoke-user.ts +95 -0
  594. package/src/ip-restrict.ts +47 -161
  595. package/src/ip.ts +194 -0
  596. package/src/logger.ts +65 -36
  597. package/src/media-type.ts +104 -0
  598. package/src/meta-spec-merge.ts +80 -0
  599. package/src/meta-spec.ts +0 -0
  600. package/src/middleware.ts +112 -12
  601. package/src/node-request.ts +242 -52
  602. package/src/node.ts +318 -75
  603. package/src/openapi/collect.ts +19 -44
  604. package/src/openapi/docs-page.ts +89 -0
  605. package/src/openapi/document.ts +503 -224
  606. package/src/openapi/json-schema-slot.ts +40 -0
  607. package/src/openapi/register.ts +7 -2
  608. package/src/openapi/scalar.ts +11 -10
  609. package/src/openapi/serve.ts +39 -0
  610. package/src/openapi/spec-factory.ts +50 -1
  611. package/src/openapi/spec.ts +53 -30
  612. package/src/openapi/swagger.ts +12 -11
  613. package/src/pattern.ts +245 -0
  614. package/src/peer.ts +53 -0
  615. package/src/plugin.ts +270 -185
  616. package/src/powered-by.ts +2 -2
  617. package/src/pretty-json.ts +25 -9
  618. package/src/producer-stream.ts +296 -0
  619. package/src/proxy.ts +253 -55
  620. package/src/python-type-emitter.ts +68 -56
  621. package/src/realtime/bus.ts +56 -9
  622. package/src/realtime/register.ts +13 -0
  623. package/src/realtime/route.ts +129 -80
  624. package/src/realtime/server.ts +298 -0
  625. package/src/request-id.ts +13 -3
  626. package/src/request-limits.ts +2 -0
  627. package/src/request-path.ts +165 -0
  628. package/src/request-to-curl.ts +28 -7
  629. package/src/response.ts +143 -103
  630. package/src/rust-type-emitter.ts +265 -170
  631. package/src/secure-headers.ts +8 -4
  632. package/src/serve-register.ts +4 -0
  633. package/src/serve-slot.ts +38 -0
  634. package/src/serve.ts +113 -25
  635. package/src/server-timing.ts +11 -7
  636. package/src/static.ts +85 -32
  637. package/src/telemetry/otel.ts +29 -7
  638. package/src/testing.ts +76 -14
  639. package/src/timeout.ts +15 -7
  640. package/src/tree.ts +405 -333
  641. package/src/trust.ts +117 -0
  642. package/src/ts-type-emitter.ts +18 -12
  643. package/src/type-emitter.ts +227 -89
  644. package/src/type-extractor.ts +603 -423
  645. package/src/types.ts +8 -0
  646. package/src/validation.ts +221 -96
  647. package/src/with-headers.ts +45 -0
  648. package/src/ws/bun.ts +53 -15
  649. package/src/ws/cloudflare.ts +81 -19
  650. package/src/ws/deno.ts +124 -36
  651. package/src/ws/node.ts +173 -79
  652. package/src/ws/session.ts +73 -0
  653. package/src/ws/shared.ts +140 -0
  654. package/src/ws-origin.ts +53 -0
  655. package/src/yaml.ts +45 -22
  656. package/dist/client-mcp/server.d.ts +0 -10
  657. package/dist/client-mcp/server.d.ts.map +0 -1
  658. package/dist/client-mcp/types.d.ts +0 -16
  659. package/dist/client-mcp/types.d.ts.map +0 -1
  660. package/dist/cloudflare/realtime.d.ts +0 -30
  661. package/dist/cloudflare/realtime.d.ts.map +0 -1
  662. package/dist/realtime/buffer.d.ts +0 -18
  663. package/dist/realtime/buffer.d.ts.map +0 -1
  664. package/dist/realtime/longpoll.d.ts +0 -23
  665. package/dist/realtime/longpoll.d.ts.map +0 -1
  666. package/dist/realtime/protocol.d.ts +0 -50
  667. package/dist/realtime/protocol.d.ts.map +0 -1
  668. package/dist/realtime/runtime.d.ts +0 -74
  669. package/dist/realtime/runtime.d.ts.map +0 -1
  670. package/src/cloudflare/realtime.ts +0 -234
  671. package/src/realtime/buffer.ts +0 -101
  672. package/src/realtime/longpoll.ts +0 -148
  673. package/src/realtime/protocol.ts +0 -128
  674. package/src/realtime/runtime.ts +0 -427
@@ -1,6 +1,8 @@
1
- import { existsSync } from "node:fs"
2
- import { dirname, relative, resolve } from "node:path"
1
+ import { existsSync, readFileSync } from "node:fs"
2
+ import { dirname, join, relative, resolve, sep } from "node:path"
3
3
  import type { ts } from "ts-morph"
4
+ import { canonical, expandOptional, joinPatterns, parsePattern } from "./pattern.ts"
5
+ import { quoteKey } from "./type-emitter.ts"
4
6
 
5
7
  export type ExtractedBaseCtx = {
6
8
  envType: string
@@ -8,85 +10,105 @@ export type ExtractedBaseCtx = {
8
10
  tapsType: string | null
9
11
  }
10
12
 
13
+ /**
14
+ * The tsconfig that governs the entry: the nearest one walking up, but never past the
15
+ * project root (the nearest directory with a package.json) — a stray tsconfig in a parent
16
+ * directory or the home folder is not this project's.
17
+ */
11
18
  function findTsConfig(fromPath: string): string | undefined {
12
19
  let dir = dirname(resolve(fromPath))
13
20
  let prev = ""
14
21
  while (dir !== prev) {
15
22
  const p = resolve(dir, "tsconfig.json")
16
23
  if (existsSync(p)) return p
24
+ if (existsSync(resolve(dir, "package.json"))) return undefined
17
25
  prev = dir
18
26
  dir = dirname(dir)
19
27
  }
20
28
  return undefined
21
29
  }
22
30
 
31
+ /* ts-morph bundles a different TS version — structurally identical at runtime */
32
+ function bridge<T>(value: unknown): T {
33
+ return value as T
34
+ }
35
+
36
+ type LoadedEntry = {
37
+ /** The app's type */
38
+ appType: ts.Type
39
+ checker: ts.TypeChecker
40
+ compiler: typeof ts
41
+ /** A node inside the entry, for type lookups that need a location */
42
+ node: ts.Node
43
+ project: import("ts-morph").Project
44
+ sourceFile: import("ts-morph").SourceFile
45
+ }
46
+
23
47
  /**
24
- * Uses ts-morph to extract the middleware context type from a Honey app export.
25
- * Returns inline type strings so the gen file has zero imports from user code.
48
+ * Load the entry and find the app export: `export const app = …`, any other exported
49
+ * declaration of that name, or `export default honey()` for `exportName: "default"`.
26
50
  */
27
- export async function extractBaseCtx(options: {
51
+ async function loadEntry(options: {
28
52
  entryPath: string
29
53
  exportName: string
30
- outputDir?: string
31
54
  tsconfigPath?: string
32
- }): Promise<ExtractedBaseCtx> {
33
- const { Project, ts } = await import("ts-morph")
34
-
55
+ }): Promise<LoadedEntry> {
56
+ const { Project, ts: tsLib } = await import("ts-morph")
35
57
  const tsConfigFilePath = options.tsconfigPath ?? findTsConfig(options.entryPath)
36
-
37
- const project = new Project({
38
- skipAddingFilesFromTsConfig: true,
39
- tsConfigFilePath,
40
- })
41
-
58
+ const project = new Project({ skipAddingFilesFromTsConfig: true, tsConfigFilePath })
42
59
  project.addSourceFilesAtPaths(options.entryPath)
43
60
  project.resolveSourceFileDependencies()
44
61
 
45
62
  const sourceFile = project.getSourceFileOrThrow(options.entryPath)
46
- const varDecl = sourceFile.getVariableDeclaration(options.exportName)
47
- if (!varDecl) {
63
+ const declarations = sourceFile.getExportedDeclarations().get(options.exportName)
64
+ const decl = declarations?.[0] ?? sourceFile.getVariableDeclaration(options.exportName)
65
+ if (!decl) {
48
66
  throw new Error(`Export "${options.exportName}" not found in ${options.entryPath}`)
49
67
  }
50
-
51
- const type = varDecl.getType()
52
- /* ts-morph bundles different TS version — structurally identical at runtime */
53
- function bridge<T>(value: unknown): T {
54
- return value as T
55
- }
56
68
  const checker: ts.TypeChecker = bridge(project.getTypeChecker().compilerObject)
57
- const node: ts.Node = bridge(varDecl.compilerNode)
58
- const tsLib: typeof ts = bridge(ts)
69
+ const node: ts.Node = bridge(decl.compilerNode)
70
+ const compiler: typeof ts = bridge(tsLib)
71
+ /* `export default <expr>` declares an ExportAssignment; its type is the expression's */
72
+ const target = compiler.isExportAssignment(node) ? node.expression : node
73
+ const appType = checker.getTypeAtLocation(target)
74
+ return { appType, checker, compiler, node: target, project, sourceFile }
75
+ }
76
+
77
+ /**
78
+ * Uses ts-morph to extract the middleware context type from a Honey app export.
79
+ * Returns inline type strings so the gen file has zero imports from user code.
80
+ */
81
+ export async function extractBaseCtx(options: {
82
+ entryPath: string
83
+ exportName: string
84
+ outputDir?: string
85
+ tsconfigPath?: string
86
+ }): Promise<ExtractedBaseCtx> {
87
+ const { appType, checker, compiler, node } = await loadEntry(options)
88
+ const ser = new Serializer(checker, node, compiler)
59
89
 
60
- function getType(sym: ReturnType<typeof type.getProperty>): ts.Type {
61
- const s: ts.Symbol = bridge(sym?.compilerSymbol)
62
- return checker.getTypeOfSymbolAtLocation(s, node)
90
+ const typeOfProp = (name: string): ts.Type | null => {
91
+ const sym = appType.getProperty(name)
92
+ return sym ? checker.getTypeOfSymbolAtLocation(sym, node) : null
63
93
  }
64
94
 
65
95
  /* extract $env */
66
- let envType = "Record<string, unknown>"
67
- const envProp = type.getProperty("$env")
68
- if (envProp) {
69
- envType = serialize(getType(envProp), checker, node, tsLib)
70
- }
96
+ const envT = typeOfProp("$env")
97
+ const envType = envT ? ser.serialize(envT) : "Record<string, unknown>"
71
98
 
72
99
  /* extract $ctx and isolate middleware additions */
73
- const ctxProp = type.getProperty("$ctx")
74
- if (!ctxProp) {
100
+ const ctxType = typeOfProp("$ctx")
101
+ if (!ctxType) {
75
102
  throw new Error(`Export "${options.exportName}" has no $ctx — not a Honey instance`)
76
103
  }
77
-
78
- const ctxType = getType(ctxProp)
79
- const middlewareType = extractMiddleware(ctxType, checker, node, tsLib)
104
+ const middlewareType = propsType(extractMiddlewareProps(ctxType, ser))
80
105
 
81
106
  /* extract $taps — typed tap payloads declared via .taps<T>() */
82
107
  let tapsType: string | null = null
83
- const tapsProp = type.getProperty("$taps")
84
- if (tapsProp) {
85
- const tapsT = getType(tapsProp)
86
- const tapsStr = serialize(tapsT, checker, node, tsLib)
87
- if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") {
88
- tapsType = tapsStr
89
- }
108
+ const tapsT = typeOfProp("$taps")
109
+ if (tapsT) {
110
+ const tapsStr = ser.serialize(tapsT)
111
+ if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") tapsType = tapsStr
90
112
  }
91
113
 
92
114
  const sanitize = (s: string) => sanitizeImportPaths(s, options.outputDir)
@@ -116,47 +138,27 @@ const HONEY_CTX_PROPS = new Set([
116
138
  "tap",
117
139
  ])
118
140
 
119
- /** Resolve an import() reference from a declaration's type annotation */
120
- function resolveTypeRefFromDecl(
121
- decl: ts.Declaration | undefined,
122
- checker: ts.TypeChecker,
123
- compiler: typeof ts,
124
- ): string | undefined {
125
- if (!decl) return undefined
126
- const typeRef = (decl as { type?: ts.TypeNode }).type
127
- if (!typeRef || !compiler.isTypeReferenceNode(typeRef)) return undefined
128
- const typeName = (typeRef as ts.TypeReferenceNode).typeName
129
-
130
- let refSym = checker.getSymbolAtLocation(typeName)
131
- while (refSym && refSym.flags & compiler.SymbolFlags.Alias) {
132
- refSym = checker.getAliasedSymbol(refSym)
133
- }
134
- if (!refSym) return undefined
135
-
136
- const refDecls = refSym.declarations
137
- if (!refDecls?.length) return undefined
138
-
139
- const srcFile = refDecls[0].getSourceFile()
140
- if (!isExportedFromFile(refSym, srcFile, checker)) return undefined
141
+ type MwPropEntry = { name: string; opt: boolean; type: string }
141
142
 
142
- return `import("${srcFile.fileName}").${refSym.getName()}`
143
+ function propsType(props: readonly MwPropEntry[]): string | null {
144
+ if (props.length === 0) return null
145
+ return `{ ${props.map((p) => `${quoteKey(p.name)}${p.opt ? "?" : ""}: ${p.type}`).join("; ")} }`
143
146
  }
144
147
 
145
- type MwPropEntry = { name: string; opt: boolean; type: string }
148
+ /** Is a property name something a type literal can spell and a consumer can see? */
149
+ function isPublicName(name: string): boolean {
150
+ /* `#secret` private fields surface as `__#123@#secret` — never part of a type */
151
+ return !name.startsWith("#") && !name.startsWith("__#")
152
+ }
146
153
 
147
- function extractMiddlewareProps(
148
- ctxType: ts.Type,
149
- checker: ts.TypeChecker,
150
- node: ts.Node,
151
- compiler: typeof ts,
152
- ): MwPropEntry[] {
153
- const allProps = ctxType.getProperties()
154
- const mwProps = allProps.filter((p) => {
154
+ function extractMiddlewareProps(ctxType: ts.Type, ser: Serializer): MwPropEntry[] {
155
+ const { checker, compiler, node } = ser
156
+ const mwProps = ctxType.getProperties().filter((p) => {
155
157
  const name = p.getName()
156
158
  if (HONEY_CTX_PROPS.has(name)) return false
157
159
  /* skip internal/private properties (underscore-prefixed) */
158
160
  if (name.startsWith("_")) return false
159
- return true
161
+ return isPublicName(name)
160
162
  })
161
163
 
162
164
  return mwProps.map((p) => {
@@ -166,14 +168,14 @@ function extractMiddlewareProps(
166
168
  /* 1. try declaration-site TypeReference (explicit annotation on property or variable) */
167
169
  let typeStr: string | undefined
168
170
  const mwDecl = p.valueDeclaration ?? p.declarations?.[0]
169
- typeStr = resolveTypeRefFromDecl(mwDecl, checker, compiler)
171
+ typeStr = ser.refFromDeclaration(mwDecl)
170
172
 
171
173
  /* 1b. shorthand property — trace to the underlying variable's type annotation */
172
174
  if (!typeStr && mwDecl && compiler.isShorthandPropertyAssignment(mwDecl)) {
173
175
  const varSym = checker.getShorthandAssignmentValueSymbol(mwDecl)
174
176
  if (varSym) {
175
177
  const varDecl = varSym.valueDeclaration ?? varSym.declarations?.[0]
176
- typeStr = resolveTypeRefFromDecl(varDecl, checker, compiler)
178
+ typeStr = ser.refFromDeclaration(varDecl)
177
179
 
178
180
  /* 1c. destructured binding (e.g. const { shardDb } = openShardSession(...))
179
181
  * — trace through the function return type to find the original type annotation */
@@ -189,7 +191,7 @@ function extractMiddlewareProps(
189
191
  const innerSym = checker.getShorthandAssignmentValueSymbol(retProp.valueDeclaration)
190
192
  if (innerSym) {
191
193
  const innerDecl = innerSym.valueDeclaration ?? innerSym.declarations?.[0]
192
- typeStr = resolveTypeRefFromDecl(innerDecl, checker, compiler)
194
+ typeStr = ser.refFromDeclaration(innerDecl)
193
195
  }
194
196
  }
195
197
  }
@@ -198,32 +200,13 @@ function extractMiddlewareProps(
198
200
  }
199
201
  }
200
202
 
201
- /* 2. try resolved type as named import reference */
202
- if (!typeStr) {
203
- typeStr = serializeAsRef(t, checker, node, compiler) ?? undefined
204
- }
205
-
206
- /* 3. structural fallback */
207
- if (!typeStr) {
208
- typeStr = serialize(t, checker, node, compiler)
209
- }
203
+ /* 2. a named type as an import() reference; 3. structural */
204
+ typeStr ??= ser.asRef(t) ?? ser.serialize(t)
210
205
 
211
206
  return { name: p.getName(), opt, type: typeStr }
212
207
  })
213
208
  }
214
209
 
215
- function extractMiddleware(
216
- ctxType: ts.Type,
217
- checker: ts.TypeChecker,
218
- node: ts.Node,
219
- compiler: typeof ts,
220
- ): string | null {
221
- const props = extractMiddlewareProps(ctxType, checker, node, compiler)
222
- if (props.length === 0) return null
223
- const entries = props.map((p) => `${p.name}${p.opt ? "?" : ""}: ${p.type}`)
224
- return `{ ${entries.join("; ")} }`
225
- }
226
-
227
210
  /** Check if a symbol is exported from its source file */
228
211
  function isExportedFromFile(sym: ts.Symbol, sf: ts.SourceFile, checker: ts.TypeChecker): boolean {
229
212
  /* .d.ts module types are always accessible */
@@ -234,216 +217,257 @@ function isExportedFromFile(sym: ts.Symbol, sf: ts.SourceFile, checker: ts.TypeC
234
217
  return exports.some((e) => e.getName() === sym.getName())
235
218
  }
236
219
 
220
+ /** Lib types every program has in scope; their names are written as is, never imported. */
221
+ function isLibFile(fileName: string): boolean {
222
+ return /(?:^|[\\/])lib\.[\w.-]*\.d\.ts$/.test(fileName) || /[\\/]typescript[\\/]lib[\\/]/.test(fileName)
223
+ }
224
+
237
225
  /**
238
- * Attempts to build an `import("path").TypeName` reference for a type.
239
- * Returns null if the type has no usable named symbol.
226
+ * TypeScript types → type source text.
240
227
  *
241
- * Prefers compact references over structural expansion:
242
- * - .d.ts types: import("drizzle-orm/d1").DrizzleD1Database
243
- * - .ts type aliases: import("./db.orm.gen").CoreDb
244
- * - globals (Promise, Map, etc.) are skipped — handled by serialize()
228
+ * Named types become `import("…").Name<Args>` references when the declaring module exports
229
+ * them; everything else is written structurally. The structural writer is precise about the
230
+ * things a reader relies on: optional and rest parameters, generic signatures, tuple rest and
231
+ * optional elements, literal types (quoted), boolean literals, quoted keys, and precedence
232
+ * (`(() => void)[]`, `(A | B) & C`). A type it is still expanding when it meets itself again
233
+ * is written as `unknown` there, never expanded without end.
245
234
  */
246
- function serializeAsRef(
247
- type: ts.Type,
248
- checker: ts.TypeChecker,
249
- node: ts.Node,
250
- compiler: typeof ts,
251
- depth = 0,
252
- ): string | null {
253
- if (depth > 6) return null
254
-
255
- /* union with never members (from intersection narrowing) — simplify first */
256
- if (type.isUnion()) {
257
- const nonNever = (type as ts.UnionType).types.filter((t) => !(t.flags & compiler.TypeFlags.Never))
258
- if (nonNever.length === 1) {
259
- return serializeAsRef(nonNever[0], checker, node, compiler, depth)
260
- }
235
+ class Serializer {
236
+ readonly checker: ts.TypeChecker
237
+ readonly compiler: typeof ts
238
+ readonly node: ts.Node
239
+ readonly #active = new Set<ts.Type>()
240
+ /* type parameters of the signatures being written: inside them a parameter keeps its name */
241
+ readonly #bound = new Set<ts.Symbol>()
242
+
243
+ constructor(checker: ts.TypeChecker, node: ts.Node, compiler: typeof ts) {
244
+ this.checker = checker
245
+ this.node = node
246
+ this.compiler = compiler
261
247
  }
262
248
 
263
- const sym = type.getSymbol() ?? type.aliasSymbol
264
- if (!sym) return null
265
-
266
- const name = sym.getName()
267
- if (!name || name.startsWith("__") || name === "Object") return null
268
-
269
- const decls = sym.declarations
270
- if (!decls?.length) return null
271
-
272
- const sf = decls[0].getSourceFile()
273
- const fileName = sf.fileName
274
-
275
- /* skip global/lib types — serialize() handles these with its globals set */
276
- if (fileName.includes("/lib.") || fileName.includes("/lib/")) return null
249
+ /** An `import()` reference with type arguments for an annotation like `x: Foo<Bar>`. */
250
+ refFromDeclaration(decl: ts.Declaration | undefined): string | undefined {
251
+ const { checker, compiler } = this
252
+ if (!decl) return undefined
253
+ const typeRef = (decl as { type?: ts.TypeNode }).type
254
+ if (!typeRef || !compiler.isTypeReferenceNode(typeRef)) return undefined
255
+ let refSym = checker.getSymbolAtLocation(typeRef.typeName)
256
+ while (refSym && refSym.flags & compiler.SymbolFlags.Alias) refSym = checker.getAliasedSymbol(refSym)
257
+ if (!refSym?.declarations?.length) return undefined
258
+ const srcFile = refSym.declarations[0].getSourceFile()
259
+ if (isLibFile(srcFile.fileName)) return undefined
260
+ if (!isExportedFromFile(refSym, srcFile, checker)) return undefined
261
+ const args = typeRef.typeArguments?.map((a) => {
262
+ const t = checker.getTypeFromTypeNode(a)
263
+ return this.asRef(t) ?? this.serialize(t)
264
+ })
265
+ const argText = args && args.length > 0 ? `<${args.join(", ")}>` : ""
266
+ return `import("${srcFile.fileName}").${refSym.getName()}${argText}`
267
+ }
277
268
 
278
- const isDts = sf.isDeclarationFile
279
- const isTypeAlias =
280
- (sym.flags & compiler.SymbolFlags.TypeAlias) !== 0 || decls.some((d) => compiler.isTypeAliasDeclaration(d))
269
+ /**
270
+ * `import("path").TypeName<Args>` for a named type whose module exports it, else null.
271
+ * Globals (Promise, Map, …) are left to `serialize`.
272
+ */
273
+ asRef(type: ts.Type, depth = 0): string | null {
274
+ const { checker, compiler } = this
275
+ if (depth > 6) return null
276
+
277
+ /* union with never members (from intersection narrowing) — simplify first */
278
+ if (type.isUnion()) {
279
+ const nonNever = type.types.filter((t) => !(t.flags & compiler.TypeFlags.Never))
280
+ if (nonNever.length === 1) return this.asRef(nonNever[0], depth)
281
+ }
281
282
 
282
- /* only reference .d.ts symbols or explicit type aliases from .ts files */
283
- if (!isDts && !isTypeAlias) return null
283
+ const sym = type.aliasSymbol ?? type.getSymbol()
284
+ if (!sym) return null
285
+ const name = sym.getName()
286
+ if (!name || name.startsWith("__") || name === "Object") return null
287
+ const decls = sym.declarations
288
+ if (!decls?.length) return null
289
+ const sf = decls[0].getSourceFile()
290
+ if (isLibFile(sf.fileName)) return null
291
+
292
+ const named =
293
+ (sym.flags &
294
+ (compiler.SymbolFlags.TypeAlias |
295
+ compiler.SymbolFlags.Interface |
296
+ compiler.SymbolFlags.Class |
297
+ compiler.SymbolFlags.Enum)) !==
298
+ 0
299
+ /* only reference .d.ts symbols or explicitly declared named types from .ts files */
300
+ if (!sf.isDeclarationFile && !named) return null
301
+
302
+ const typeArgs = type.aliasSymbol ? type.aliasTypeArguments : (type as ts.TypeReference).typeArguments
303
+ /* a class or interface reference carries its `this` type as a trailing argument */
304
+ const declared = sym.declarations?.[0]
305
+ const paramCount =
306
+ declared && "typeParameters" in declared
307
+ ? ((declared as { typeParameters?: unknown[] }).typeParameters?.length ?? 0)
308
+ : (typeArgs?.length ?? 0)
309
+ const args = (typeArgs ?? []).slice(0, paramCount)
310
+ const withArgs = (ref: string): string =>
311
+ args.length > 0
312
+ ? `${ref}<${args.map((arg) => this.asRef(arg, depth + 1) ?? this.serialize(arg)).join(", ")}>`
313
+ : ref
314
+
315
+ /* a global type (an ambient script such as wrangler's worker-configuration.d.ts, or
316
+ * `declare global`) is in scope wherever the app compiles: write its name. Expanding it
317
+ * loses overloads and generics, and the copy is not assignable back to the original. */
318
+ if (named) {
319
+ const globalName = this.#globalName(sym, decls[0])
320
+ if (globalName !== null) return withArgs(globalName)
321
+ }
322
+ /* any other ambient script declaration has no import() path */
323
+ if (sf.isDeclarationFile && !(sf as unknown as { externalModuleIndicator?: unknown }).externalModuleIndicator) {
324
+ return null
325
+ }
326
+ /* skip non-exported symbols — import() on them resolves to `any` */
327
+ if (!isExportedFromFile(sym, sf, checker)) return null
284
328
 
285
- /* skip ambient .d.ts scripts (no exports) — import() can't resolve them */
286
- if (isDts) {
287
- const tsSf = sf as unknown as { externalModuleIndicator?: unknown }
288
- if (!tsSf.externalModuleIndicator) return null
329
+ return withArgs(`import("${sf.fileName}").${name}`)
289
330
  }
290
331
 
291
- /* skip non-exported symbols — import() on them resolves to `any` */
292
- if (!isExportedFromFile(sym, sf, checker)) return null
293
-
294
- /* build type arguments if present */
295
- const ref = type as ts.TypeReference
296
- const typeArgs = ref.typeArguments ?? type.aliasTypeArguments
297
- if (typeArgs?.length) {
298
- const args = typeArgs.map((arg) => {
299
- const argRef = serializeAsRef(arg, checker, node, compiler, depth + 1)
300
- return argRef ?? serialize(arg, checker, node, compiler)
301
- })
302
- return `import("${fileName}").${name}<${args.join(", ")}>`
332
+ /** The name a global declaration is reachable by (`Cloudflare.Env`), or null if it is module-scoped. */
333
+ #globalName(sym: ts.Symbol, decl: ts.Declaration): string | null {
334
+ const { compiler } = this
335
+ let global = false
336
+ for (let n: ts.Node | undefined = decl.parent; n; n = n.parent) {
337
+ if (compiler.isModuleDeclaration(n) && n.flags & compiler.NodeFlags.GlobalAugmentation) {
338
+ global = true
339
+ break
340
+ }
341
+ if (compiler.isSourceFile(n)) {
342
+ global = !(n as unknown as { externalModuleIndicator?: unknown }).externalModuleIndicator
343
+ break
344
+ }
345
+ }
346
+ if (!global) return null
347
+ /* a `declare global` member is qualified as `global.Name`; in scope it is `Name` */
348
+ const name = this.checker.getFullyQualifiedName(sym).replace(/^global\./, "")
349
+ return /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(name) ? name : null
303
350
  }
304
351
 
305
- return `import("${fileName}").${name}`
306
- }
307
-
308
- function serialize(type: ts.Type, checker: ts.TypeChecker, node: ts.Node, compiler: typeof ts, depth = 0): string {
309
- if (depth > 8) return "unknown"
310
-
311
- const next = (t: ts.Type) => serialize(t, checker, node, compiler, depth + 1)
312
-
313
- /* primitives */
314
- if (type.flags & compiler.TypeFlags.String) return "string"
315
- if (type.flags & compiler.TypeFlags.Number) return "number"
316
- if (type.flags & compiler.TypeFlags.Boolean) return "boolean"
317
- if (type.flags & compiler.TypeFlags.BigInt) return "bigint"
318
- if (type.flags & compiler.TypeFlags.ESSymbol) return "symbol"
319
- if (type.flags & compiler.TypeFlags.Void) return "void"
320
- if (type.flags & compiler.TypeFlags.Undefined) return "undefined"
321
- if (type.flags & compiler.TypeFlags.Null) return "null"
322
- if (type.flags & compiler.TypeFlags.Never) return "never"
323
- if (type.flags & compiler.TypeFlags.Unknown) return "unknown"
324
- if (type.flags & compiler.TypeFlags.Any) return "unknown"
325
-
326
- /* literals */
327
- if (type.isStringLiteral()) return `"${(type as ts.StringLiteralType).value}"`
328
- if (type.isNumberLiteral()) return `${(type as ts.NumberLiteralType).value}`
329
-
330
- /* union */
331
- if (type.isUnion()) {
332
- return type.types
333
- .map((t) => {
334
- const s = next(t)
335
- /* parenthesize function types inside unions — TS syntax requires it */
336
- return s.includes("=>") ? `(${s})` : s
337
- })
338
- .join(" | ")
352
+ serialize(type: ts.Type): string {
353
+ if (this.#active.has(type)) return "unknown"
354
+ this.#active.add(type)
355
+ try {
356
+ return this.#serialize(type)
357
+ } finally {
358
+ this.#active.delete(type)
359
+ }
339
360
  }
340
361
 
341
- /* intersection */
342
- if (type.isIntersection()) return type.types.map(next).join(" & ")
362
+ #serialize(type: ts.Type): string {
363
+ const { checker, compiler, node } = this
364
+ const F = compiler.TypeFlags
365
+ const next = (t: ts.Type) => this.serialize(t)
343
366
 
344
- /* array */
345
- if (checker.isArrayType(type)) {
346
- const args = checker.getTypeArguments(type as ts.TypeReference)
347
- if (args.length > 0) {
348
- const el = next(args[0])
349
- return el.includes("|") || el.includes("&") ? `(${el})[]` : `${el}[]`
367
+ /* an enum is nominal: keep its name when it can be referenced */
368
+ const enumSym = type.getSymbol()
369
+ if (enumSym && enumSym.flags & compiler.SymbolFlags.Enum) {
370
+ const ref = this.asRef(type)
371
+ if (ref) return ref
350
372
  }
351
- return "unknown[]"
352
- }
353
373
 
354
- /* tuple */
355
- if (checker.isTupleType(type)) {
356
- const args = checker.getTypeArguments(type as ts.TypeReference)
357
- return `[${args.map(next).join(", ")}]`
358
- }
374
+ /* primitives */
375
+ if (type.flags & F.String) return "string"
376
+ if (type.flags & F.Number) return "number"
377
+ if (type.flags & F.Boolean) return "boolean"
378
+ if (type.flags & F.BigInt) return "bigint"
379
+ if (type.flags & F.ESSymbol) return "symbol"
380
+ if (type.flags & F.UniqueESSymbol) return "symbol"
381
+ if (type.flags & F.Void) return "void"
382
+ if (type.flags & F.Undefined) return "undefined"
383
+ if (type.flags & F.Null) return "null"
384
+ if (type.flags & F.Never) return "never"
385
+ if (type.flags & F.Unknown) return "unknown"
386
+ if (type.flags & F.Any) return "unknown"
387
+ if (type.flags & F.NonPrimitive) return "object"
388
+
389
+ /* literals */
390
+ if (type.isStringLiteral()) return JSON.stringify(type.value)
391
+ if (type.isNumberLiteral()) return `${type.value}`
392
+ if (type.flags & F.BigIntLiteral) {
393
+ const v = (type as ts.BigIntLiteralType).value
394
+ return `${v.negative ? "-" : ""}${v.base10Value}n`
395
+ }
396
+ if (type.flags & F.BooleanLiteral) return checker.typeToString(type)
397
+ if (type.flags & F.TemplateLiteral) return "string"
398
+
399
+ /* a type parameter: by name inside the signature that declares it, else its constraint or unknown */
400
+ if (type.flags & F.TypeParameter) {
401
+ if (type.symbol && this.#bound.has(type.symbol)) return type.symbol.getName()
402
+ const constraint = checker.getBaseConstraintOfType(type)
403
+ return constraint && constraint !== type ? next(constraint) : "unknown"
404
+ }
359
405
 
360
- /* function (pure callable, no data properties) */
361
- const sigs = type.getCallSignatures()
362
- if (sigs.length > 0 && type.getProperties().length === 0) {
363
- const sig = sigs[0]
364
- const params = sig.getParameters().map((p) => {
365
- const pt = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? node)
366
- const decl = p.valueDeclaration
367
- const isRest =
368
- decl !== undefined &&
369
- compiler.isParameter(decl) &&
370
- (decl as ts.ParameterDeclaration).dotDotDotToken !== undefined
371
- return `${isRest ? "..." : ""}${p.getName()}: ${next(pt)}`
372
- })
373
- const ret = checker.getReturnTypeOfSignature(sig)
374
- return `(${params.join(", ")}) => ${next(ret)}`
375
- }
406
+ /* union */
407
+ if (type.isUnion()) return unionText(type.types.map(next))
376
408
 
377
- /* well-known globals — keep name, serialize type args */
378
- const sym = type.getSymbol()
379
- if (sym) {
380
- const name = sym.getName()
381
- const globals = new Set([
382
- "AbortController",
383
- "AbortSignal",
384
- "ArrayBuffer",
385
- "Blob",
386
- "DataView",
387
- "Date",
388
- "Error",
389
- "File",
390
- "FormData",
391
- "Headers",
392
- "Map",
393
- "Promise",
394
- "ReadableStream",
395
- "RegExp",
396
- "Request",
397
- "Response",
398
- "Set",
399
- "SharedArrayBuffer",
400
- "TransformStream",
401
- "URL",
402
- "URLSearchParams",
403
- "WeakMap",
404
- "WeakSet",
405
- "WritableStream",
406
- ])
407
- if (globals.has(name)) {
409
+ /* intersection */
410
+ if (type.isIntersection()) return intersectionText(type.types.map(next))
411
+
412
+ /* array */
413
+ if (checker.isArrayType(type)) {
414
+ const args = checker.getTypeArguments(type as ts.TypeReference)
415
+ const readonlyArray = type.getSymbol()?.getName() === "ReadonlyArray"
416
+ const el = args.length > 0 ? arrayText(next(args[0])) : "unknown[]"
417
+ return readonlyArray ? `readonly ${el}` : el
418
+ }
419
+
420
+ /* tuple — rest and optional elements kept */
421
+ if (checker.isTupleType(type)) {
408
422
  const ref = type as ts.TypeReference
409
- if (ref.typeArguments?.length) {
410
- return `${name}<${ref.typeArguments.map(next).join(", ")}>`
411
- }
412
- return name
423
+ const args = checker.getTypeArguments(ref)
424
+ const target = ref.target as ts.TupleType
425
+ const parts = args.map((arg, i) => {
426
+ const flags = target.elementFlags?.[i] ?? compiler.ElementFlags.Required
427
+ if (flags & compiler.ElementFlags.Rest) return `...${arrayText(next(arg))}`
428
+ if (flags & compiler.ElementFlags.Variadic) return `...${next(arg)}`
429
+ if (flags & compiler.ElementFlags.Optional) return `${next(arg)}?`
430
+ return next(arg)
431
+ })
432
+ return `${target.readonly ? "readonly " : ""}[${parts.join(", ")}]`
413
433
  }
414
434
 
415
- /* named types — prefer import() reference over structural expansion */
416
- if (name && !name.startsWith("__") && name !== "Object") {
417
- const ref = serializeAsRef(type, checker, node, compiler, 0)
418
- if (ref) return ref
435
+ /* function (pure callable, no data properties) */
436
+ const sigs = type.getCallSignatures()
437
+ if (sigs.length > 0 && type.getProperties().length === 0) {
438
+ const parts = sigs.map((sig) => this.#signature(sig))
439
+ return parts.length === 1 ? parts[0] : parts.map((p) => `(${p})`).join(" & ")
440
+ }
419
441
 
420
- /* fallback: typeToString for .d.ts types without clean ref */
421
- const decls = sym.declarations
422
- if (decls?.length) {
423
- const sf = decls[0].getSourceFile()
424
- if (sf.isDeclarationFile) {
425
- return checker.typeToString(
426
- type,
427
- node,
428
- compiler.TypeFormatFlags.UseFullyQualifiedType | compiler.TypeFormatFlags.NoTruncation,
429
- )
430
- }
442
+ /* well-known globals — keep name, serialize type args */
443
+ const sym = type.getSymbol()
444
+ if (sym) {
445
+ const name = sym.getName()
446
+ const decl = sym.declarations?.[0]
447
+ if (decl && isLibFile(decl.getSourceFile().fileName) && name !== "Object" && !name.startsWith("__")) {
448
+ const args = (type as ts.TypeReference).typeArguments ?? []
449
+ const declaredParams = (decl as { typeParameters?: unknown[] }).typeParameters?.length ?? args.length
450
+ const kept = args.slice(0, declaredParams)
451
+ return kept.length > 0 ? `${name}<${kept.map(next).join(", ")}>` : name
452
+ }
453
+
454
+ /* named types — prefer import() reference over structural expansion */
455
+ if (name && !name.startsWith("__") && name !== "Object") {
456
+ const ref = this.asRef(type)
457
+ if (ref) return ref
431
458
  }
432
459
  }
433
- }
460
+ const aliasRef = type.aliasSymbol ? this.asRef(type) : null
461
+ if (aliasRef) return aliasRef
434
462
 
435
- /* Record<string, V> — index signature only */
436
- const stringIdx = type.getStringIndexType()
437
- if (stringIdx && type.getProperties().length === 0) {
438
- return `Record<string, ${next(stringIdx)}>`
439
- }
463
+ /* Record<string, V> — index signature only */
464
+ const stringIdx = type.getStringIndexType()
465
+ if (stringIdx && type.getProperties().length === 0) {
466
+ return `Record<string, ${next(stringIdx)}>`
467
+ }
440
468
 
441
- /* object — expand to structural form */
442
- const props = type.getProperties()
443
- if (props.length > 50) {
444
- return checker.typeToString(type, node, compiler.TypeFormatFlags.NoTruncation)
445
- }
446
- if (props.length > 0) {
469
+ /* object — expand to structural form */
470
+ const props = type.getProperties().filter((p) => isPublicName(p.getName()))
447
471
  const entries: string[] = []
448
472
  for (const p of props) {
449
473
  const decls = p.getDeclarations()
@@ -455,37 +479,220 @@ function serialize(type: ts.Type, checker: ts.TypeChecker, node: ts.Node, compil
455
479
  const opt = (p.flags & compiler.SymbolFlags.Optional) !== 0
456
480
  const ro =
457
481
  decls?.some((d) => (compiler.getCombinedModifierFlags(d) & compiler.ModifierFlags.Readonly) !== 0) ?? false
458
- entries.push(`${ro ? "readonly " : ""}${p.getName()}${opt ? "?" : ""}: ${next(pt)}`)
482
+ entries.push(`${ro ? "readonly " : ""}${quoteKey(p.getName())}${opt ? "?" : ""}: ${next(pt)}`)
459
483
  }
484
+ if (stringIdx) entries.push(`[key: string]: ${next(stringIdx)}`)
485
+ for (const sig of sigs) entries.push(this.#signature(sig, true))
460
486
  if (entries.length > 0) return `{ ${entries.join("; ")} }`
487
+ if (props.length === 0) return "{}"
488
+ return "unknown"
489
+ }
490
+
491
+ /** `<T extends X>(a: A, b?: B, ...rest: C[]) => R`, or the call-signature member form */
492
+ #signature(sig: ts.Signature, member = false): string {
493
+ const typeParams = sig.getTypeParameters() ?? []
494
+ const added = typeParams.map((t) => t.symbol).filter((s) => s !== undefined && !this.#bound.has(s))
495
+ for (const s of added) this.#bound.add(s)
496
+ try {
497
+ return this.#signatureText(sig, typeParams, member)
498
+ } finally {
499
+ for (const s of added) this.#bound.delete(s)
500
+ }
501
+ }
502
+
503
+ #signatureText(sig: ts.Signature, typeParams: readonly ts.TypeParameter[], member: boolean): string {
504
+ const { checker, compiler, node } = this
505
+ const tp =
506
+ typeParams.length > 0
507
+ ? `<${typeParams
508
+ .map((t) => {
509
+ const c = t.getConstraint()
510
+ return `${t.symbol.getName()}${c ? ` extends ${this.#typeParamText(c)}` : ""}`
511
+ })
512
+ .join(", ")}>`
513
+ : ""
514
+ const params = sig.getParameters().map((p) => {
515
+ const pt = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? node)
516
+ const decl = p.valueDeclaration
517
+ const param = decl && compiler.isParameter(decl) ? (decl as ts.ParameterDeclaration) : undefined
518
+ const isRest = param?.dotDotDotToken !== undefined
519
+ const optional = !isRest && param !== undefined && checker.isOptionalParameter(param)
520
+ /* an optional parameter's type carries `| undefined`; `?` already says so */
521
+ const text = optional ? this.#typeParamText(checker.getNonNullableType(pt)) : this.#typeParamText(pt)
522
+ return `${isRest ? "..." : ""}${p.getName()}${optional ? "?" : ""}: ${text}`
523
+ })
524
+ const ret = this.#typeParamText(checker.getReturnTypeOfSignature(sig))
525
+ return member ? `${tp}(${params.join(", ")}): ${ret}` : `${tp}(${params.join(", ")}) => ${ret}`
461
526
  }
462
527
 
463
- /* fallback */
464
- return checker.typeToString(type, undefined, compiler.TypeFormatFlags.NoTruncation)
528
+ /** A type inside a signature: a bare type parameter stays by name, bound by the signature. */
529
+ #typeParamText(t: ts.Type): string {
530
+ if (t.flags & this.compiler.TypeFlags.TypeParameter) return t.symbol.getName()
531
+ return this.asRef(t) ?? this.serialize(t)
532
+ }
465
533
  }
466
534
 
467
535
  /**
468
- * Converts absolute `import()` paths emitted by `typeToString` into
469
- * resolvable module specifiers:
470
- * - node_modules paths → bare package specifiers
471
- * - project-internal paths → relative from outputDir
536
+ * Does `type` contain `token` outside every bracket and string? Used to parenthesize only
537
+ * where precedence needs it.
472
538
  */
473
- function sanitizeImportPaths(str: string, outputDir?: string): string {
474
- return str.replace(/import\("([^"]+)"\)/g, (_match, filePath: string) => {
475
- const nmIdx = filePath.lastIndexOf("/node_modules/")
476
- if (nmIdx !== -1) {
477
- return `import("${filePath.slice(nmIdx + "/node_modules/".length)}")`
539
+ function hasTopLevel(type: string, token: "|" | "&" | "=>"): boolean {
540
+ let depth = 0
541
+ let quote: string | null = null
542
+ for (let i = 0; i < type.length; i++) {
543
+ const c = type[i]
544
+ if (quote !== null) {
545
+ if (c === "\\") i++
546
+ else if (c === quote) quote = null
547
+ continue
548
+ }
549
+ if (c === '"' || c === "'" || c === "`") quote = c
550
+ else if (c === "(" || c === "{" || c === "[" || c === "<") depth++
551
+ else if (c === ")" || c === "}" || c === "]" || (c === ">" && type[i - 1] !== "=")) depth--
552
+ else if (depth === 0 && type.startsWith(token, i)) return true
553
+ }
554
+ return false
555
+ }
556
+
557
+ function arrayText(el: string): string {
558
+ return hasTopLevel(el, "|") || hasTopLevel(el, "&") || hasTopLevel(el, "=>") || el.startsWith("readonly ")
559
+ ? `(${el})[]`
560
+ : `${el}[]`
561
+ }
562
+
563
+ function unionText(parts: string[]): string {
564
+ return [...new Set(parts)].map((p) => (hasTopLevel(p, "=>") ? `(${p})` : p)).join(" | ")
565
+ }
566
+
567
+ function intersectionText(parts: string[]): string {
568
+ return parts.map((p) => (hasTopLevel(p, "|") || hasTopLevel(p, "=>") ? `(${p})` : p)).join(" & ")
569
+ }
570
+
571
+ /* ---- module specifiers ---- */
572
+
573
+ type PackageInfo = { dir: string; json: Record<string, unknown> }
574
+
575
+ const packageCache = new Map<string, PackageInfo | null>()
576
+
577
+ function nearestPackage(fromFile: string): PackageInfo | null {
578
+ let dir = dirname(fromFile)
579
+ const visited: string[] = []
580
+ let found: PackageInfo | null = null
581
+ let prev = ""
582
+ while (dir !== prev) {
583
+ const cached = packageCache.get(dir)
584
+ if (cached !== undefined) {
585
+ found = cached
586
+ break
587
+ }
588
+ visited.push(dir)
589
+ const file = join(dir, "package.json")
590
+ if (existsSync(file)) {
591
+ try {
592
+ const json = JSON.parse(readFileSync(file, "utf8")) as Record<string, unknown>
593
+ /* a package.json without a name (a "type": "module" marker) is not a package boundary */
594
+ if (typeof json.name === "string") {
595
+ found = { dir, json }
596
+ break
597
+ }
598
+ } catch {
599
+ /* unreadable — keep walking */
600
+ }
601
+ }
602
+ prev = dir
603
+ dir = dirname(dir)
604
+ }
605
+ for (const d of visited) packageCache.set(d, found)
606
+ return found
607
+ }
608
+
609
+ /** Every file an `exports` value can point at, with the subpath that names it. */
610
+ function exportTargets(exports: unknown): Array<{ subpath: string; target: string }> {
611
+ const out: Array<{ subpath: string; target: string }> = []
612
+ const collect = (subpath: string, value: unknown): void => {
613
+ if (typeof value === "string") out.push({ subpath, target: value })
614
+ else if (Array.isArray(value)) for (const v of value) collect(subpath, v)
615
+ else if (value !== null && typeof value === "object") {
616
+ for (const v of Object.values(value as Record<string, unknown>)) collect(subpath, v)
478
617
  }
479
- if (!outputDir) return `import("${filePath}")`
480
- let rel = relative(outputDir, filePath)
618
+ }
619
+ if (typeof exports === "string" || Array.isArray(exports)) collect(".", exports)
620
+ else if (exports !== null && typeof exports === "object") {
621
+ const entries = Object.entries(exports as Record<string, unknown>)
622
+ if (entries.length > 0 && entries.every(([k]) => !k.startsWith("."))) collect(".", exports)
623
+ else for (const [k, v] of entries) collect(k, v)
624
+ }
625
+ return out
626
+ }
627
+
628
+ function stripTypeExtension(path: string): string {
629
+ return path
630
+ .replace(/\.d\.[mc]?ts$/, "")
631
+ .replace(/\.[mc]?tsx?$/, "")
632
+ .replace(/\.[mc]?js$/, "")
633
+ }
634
+
635
+ /** `@types/foo` types the `foo` package; `@types/scope__name` types `@scope/name`. */
636
+ function typedPackageName(name: string): string {
637
+ if (!name.startsWith("@types/")) return name
638
+ const bare = name.slice("@types/".length)
639
+ return bare.includes("__") ? `@${bare.replace("__", "/")}` : bare
640
+ }
641
+
642
+ /**
643
+ * The specifier a generated file should use to import `filePath`.
644
+ *
645
+ * A file of another package is named through that package — its `exports` subpath when one
646
+ * points at the file (any condition: `types`, `default`, or a source condition), its `types`
647
+ * entry, or (no `exports` map) its path without extension; an `@types` package by the package it
648
+ * types. A file of the output's own package is a relative path. Lib files never get here.
649
+ */
650
+ function moduleSpecifier(filePath: string, outputDir: string | undefined): string {
651
+ const pkg = nearestPackage(filePath)
652
+ const ownPkg = outputDir ? nearestPackage(join(outputDir, "_")) : null
653
+ const relativeSpecifier = (): string => {
654
+ if (!outputDir) return filePath
655
+ let rel = relative(outputDir, filePath).split(sep).join("/")
481
656
  if (!rel.startsWith(".")) rel = `./${rel}`
482
- return `import("${rel}")`
657
+ return rel
658
+ }
659
+ if (!pkg || (ownPkg && pkg.dir === ownPkg.dir)) return relativeSpecifier()
660
+ const name = typedPackageName(String(pkg.json.name))
661
+ const rel = `./${relative(pkg.dir, filePath).split(sep).join("/")}`
662
+
663
+ if (pkg.json.exports !== undefined) {
664
+ const match = exportTargets(pkg.json.exports).find(
665
+ (e) => e.target === rel || stripTypeExtension(e.target) === stripTypeExtension(rel),
666
+ )
667
+ if (match) return match.subpath === "." ? name : `${name}/${match.subpath.replace(/^\.\//, "")}`
668
+ /* the package does not export this file — a path into it would not resolve */
669
+ return relativeSpecifier()
670
+ }
671
+ const typesEntry = pkg.json.types ?? pkg.json.typings
672
+ if (
673
+ typeof typesEntry === "string" &&
674
+ stripTypeExtension(`./${typesEntry.replace(/^\.\//, "")}`) === stripTypeExtension(rel)
675
+ ) {
676
+ return name
677
+ }
678
+ const sub = stripTypeExtension(rel.slice(2)).replace(/\/index$/, "")
679
+ return sub === "index" || sub === "" ? name : `${name}/${sub}`
680
+ }
681
+
682
+ /**
683
+ * Rewrite absolute `import("…")` paths into resolvable module specifiers, and drop the
684
+ * `import()` around lib types (`Date`, `Promise`), which are global.
685
+ */
686
+ function sanitizeImportPaths(str: string, outputDir?: string): string {
687
+ return str.replace(/import\("([^"]+)"\)\./g, (_match, filePath: string) => {
688
+ if (isLibFile(filePath)) return ""
689
+ return `import("${moduleSpecifier(filePath, outputDir)}").`
483
690
  })
484
691
  }
485
692
 
486
693
  export type ExtractedChainTypes = {
487
694
  base: ExtractedBaseCtx
488
- /** per-route middleware additions keyed by "method /path" */
695
+ /** per-route middleware additions keyed by "method /full/path" (basePath included) */
489
696
  routeMiddleware: Record<string, string>
490
697
  /** structured per-property middleware data for sub-type dedup */
491
698
  routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>>
@@ -495,9 +702,13 @@ const HTTP_METHODS = new Set(["all", "delete", "get", "head", "options", "patch"
495
702
 
496
703
  /**
497
704
  * Extracts base ctx and per-route middleware additions from sub-chains.
498
- * Traces HTTP method calls (`.get()`, `.post()`, etc.) back to their
499
- * Honey instance receiver, computes the ctx diff vs the base export,
500
- * and returns per-route additions keyed by "method /path".
705
+ *
706
+ * Every source file of the program is scanned for HTTP method calls (`.get()`, `.post()`, …)
707
+ * on a Honey instance. A route is keyed by its full path — the receiver's `$basePath` type
708
+ * joined with the literal path, exactly as the router registers it — so `v1.get("/items")`
709
+ * and `pub.get("/items")` never share an entry. A receiver whose base path is not a literal
710
+ * type cannot be keyed and is skipped: a route without an entry gets the base ctx, never
711
+ * another route's.
501
712
  */
502
713
  export async function extractChainTypes(options: {
503
714
  entryPath: string
@@ -505,30 +716,8 @@ export async function extractChainTypes(options: {
505
716
  outputDir?: string
506
717
  tsconfigPath?: string
507
718
  }): Promise<ExtractedChainTypes> {
508
- const { Project, ts } = await import("ts-morph")
509
-
510
- const tsConfigFilePath = options.tsconfigPath ?? findTsConfig(options.entryPath)
511
-
512
- const project = new Project({
513
- skipAddingFilesFromTsConfig: true,
514
- tsConfigFilePath,
515
- })
516
-
517
- project.addSourceFilesAtPaths(options.entryPath)
518
- project.resolveSourceFileDependencies()
519
-
520
- const sourceFile = project.getSourceFileOrThrow(options.entryPath)
521
- const baseVarDecl = sourceFile.getVariableDeclaration(options.exportName)
522
- if (!baseVarDecl) {
523
- throw new Error(`Export "${options.exportName}" not found in ${options.entryPath}`)
524
- }
525
-
526
- function bridge<T>(value: unknown): T {
527
- return value as T
528
- }
529
- const checker: ts.TypeChecker = bridge(project.getTypeChecker().compilerObject)
530
- const baseNode: ts.Node = bridge(baseVarDecl.compilerNode)
531
- const tsLib: typeof ts = bridge(ts)
719
+ const { appType, checker, compiler, node: baseNode, project } = await loadEntry(options)
720
+ const ser = new Serializer(checker, baseNode, compiler)
532
721
 
533
722
  function getCtxType(tsType: ts.Type, locationNode?: ts.Node): ts.Type | null {
534
723
  const sym = tsType.getProperty("$ctx")
@@ -536,131 +725,122 @@ export async function extractChainTypes(options: {
536
725
  return checker.getTypeOfSymbolAtLocation(sym, locationNode ?? sym.valueDeclaration ?? baseNode)
537
726
  }
538
727
 
728
+ function basePathOf(tsType: ts.Type, locationNode: ts.Node): string | null {
729
+ const sym = tsType.getProperty("$basePath")
730
+ if (!sym) return null
731
+ const t = checker.getTypeOfSymbolAtLocation(sym, locationNode)
732
+ return t.isStringLiteral() ? t.value : null
733
+ }
734
+
539
735
  /* extract base ctx */
540
- const baseType = bridge<ts.Type>(baseVarDecl.getType().compilerType)
541
736
  let envType = "Record<string, unknown>"
542
- const envSym = baseType.getProperty("$env")
543
- if (envSym) {
544
- envType = serialize(checker.getTypeOfSymbolAtLocation(envSym, baseNode), checker, baseNode, tsLib)
545
- }
737
+ const envSym = appType.getProperty("$env")
738
+ if (envSym) envType = ser.serialize(checker.getTypeOfSymbolAtLocation(envSym, baseNode))
546
739
 
547
- const baseCtxType = getCtxType(baseType)
740
+ const baseCtxType = getCtxType(appType, baseNode)
548
741
  if (!baseCtxType) {
549
742
  throw new Error(`Export "${options.exportName}" has no $ctx — not a Honey instance`)
550
743
  }
551
- const baseMwProps = extractMiddlewareProps(baseCtxType, checker, baseNode, tsLib)
552
- const baseMiddlewareType =
553
- baseMwProps.length === 0
554
- ? null
555
- : `{ ${baseMwProps.map((p) => `${p.name}${p.opt ? "?" : ""}: ${p.type}`).join("; ")} }`
744
+ const baseMwProps = extractMiddlewareProps(baseCtxType, ser)
745
+ const baseMiddlewareType = propsType(baseMwProps)
556
746
 
557
747
  /* build name→type map for reusing base type references in route middleware */
558
748
  const basePropTypeByName = new Map<string, string>()
559
- for (const p of baseMwProps) {
560
- basePropTypeByName.set(p.name, p.type)
561
- }
749
+ for (const p of baseMwProps) basePropTypeByName.set(p.name, p.type)
562
750
 
563
751
  /* collect base middleware property names for diffing */
564
752
  const baseMwPropNames = new Set<string>()
565
753
  for (const p of baseCtxType.getProperties()) {
566
754
  const name = p.getName()
567
- if (!HONEY_CTX_PROPS.has(name) && !name.startsWith("_")) {
568
- baseMwPropNames.add(name)
569
- }
755
+ if (!HONEY_CTX_PROPS.has(name) && !name.startsWith("_")) baseMwPropNames.add(name)
570
756
  }
571
757
 
572
758
  const sanitize = (s: string) => sanitizeImportPaths(s, options.outputDir)
573
759
 
574
- /* scan for route-defining calls: <expr>.<httpMethod>(<pathLiteral>) */
575
- const routeMiddleware: Record<string, string> = {}
576
- const routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>> = {}
577
- const compilerSourceFile: ts.SourceFile = bridge(sourceFile.compilerNode)
760
+ const routeMiddleware: Record<string, string> = Object.create(null)
761
+ const routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>> = Object.create(null)
578
762
 
579
763
  function visitNode(node: ts.Node): void {
580
- if (tsLib.isCallExpression(node) && tsLib.isPropertyAccessExpression(node.expression)) {
764
+ if (compiler.isCallExpression(node) && compiler.isPropertyAccessExpression(node.expression)) {
581
765
  const methodName = node.expression.name.text
582
- if (HTTP_METHODS.has(methodName) && node.arguments.length > 0) {
583
- const pathArg = node.arguments[0]
584
- if (tsLib.isStringLiteral(pathArg)) {
585
- const path = pathArg.text
586
- /* get the type of the receiver (the expression before .get/.post/etc.) */
587
- const receiverExpr = node.expression.expression
588
- const receiverType = checker.getTypeAtLocation(receiverExpr)
589
- const ctxType = getCtxType(receiverType, receiverExpr)
590
-
591
- if (ctxType) {
592
- /* extract extra middleware props by iterating intersection MEMBERS individually.
593
- * This preserves source-level declarations (e.g. ShorthandPropertyAssignment)
594
- * that get lost when extracting from the flattened intersection. */
595
- const extraEntries: MwPropEntry[] = []
596
- const dupeNames = new Set<string>()
597
- const members = ctxType.isIntersection() ? (ctxType as ts.IntersectionType).types : [ctxType]
598
- for (const member of members) {
599
- const memberProps = extractMiddlewareProps(member, checker, baseNode, tsLib)
600
- for (const rp of memberProps) {
601
- if (baseMwPropNames.has(rp.name)) continue
602
- if (extraEntries.some((e) => e.name === rp.name)) {
603
- dupeNames.add(rp.name)
604
- continue
605
- }
606
- extraEntries.push(rp)
607
- }
608
- }
609
- /* for properties that appeared in multiple intersection members
610
- * (e.g. auth narrowed by a later middleware), resolve from the
611
- * flattened intersection so TypeScript computes the correct
612
- * intersected type instead of using the first member's type */
613
- for (const dupeName of dupeNames) {
614
- const sym = ctxType.getProperty(dupeName)
615
- if (!sym) continue
616
- const t = checker.getTypeOfSymbolAtLocation(sym, sym.valueDeclaration ?? baseNode)
617
- const typeStr = serializeAsRef(t, checker, baseNode, tsLib) ?? serialize(t, checker, baseNode, tsLib)
618
- const idx = extraEntries.findIndex((e) => e.name === dupeName)
619
- if (idx !== -1) {
620
- extraEntries[idx] = { ...extraEntries[idx], type: typeStr }
621
- }
622
- }
766
+ const pathArg = node.arguments[0]
767
+ if (HTTP_METHODS.has(methodName) && pathArg && compiler.isStringLiteralLike(pathArg)) {
768
+ const receiverExpr = node.expression.expression
769
+ const receiverType = checker.getTypeAtLocation(receiverExpr)
770
+ const ctxType = getCtxType(receiverType, receiverExpr)
771
+ const basePath = ctxType ? basePathOf(receiverType, receiverExpr) : null
772
+ if (ctxType && basePath !== null) {
773
+ for (const variant of fullPaths(basePath, pathArg.text)) recordRoute(`${methodName} ${variant}`, ctxType)
774
+ }
775
+ }
776
+ }
777
+ compiler.forEachChild(node, visitNode)
778
+ }
623
779
 
624
- if (extraEntries.length > 0) {
625
- /* reuse compact base type references when the route has the same property
626
- * (e.g. shardDb: ShardDb from base → reuse import ref instead of structural expansion) */
627
- const entries = extraEntries.map((e) => {
628
- const baseRef = basePropTypeByName.get(e.name)
629
- if (baseRef && baseRef.length < e.type.length) {
630
- return { ...e, type: baseRef }
631
- }
632
- return e
633
- })
634
-
635
- const key = `${methodName} ${path}`
636
- routeMiddleware[key] = sanitize(
637
- `{ ${entries.map((e) => `${e.name}${e.opt ? "?" : ""}: ${e.type}`).join("; ")} }`,
638
- )
639
- routeMiddlewareProps[key] = entries.map((e) => ({
640
- name: e.name,
641
- opt: e.opt,
642
- type: sanitize(e.type),
643
- }))
644
- }
645
- }
780
+ /** The paths the router serves for this registration — an optional last param yields two. */
781
+ function fullPaths(basePath: string, path: string): string[] {
782
+ try {
783
+ return expandOptional(parsePattern(joinPatterns(basePath, path))).map((segments) => canonical(segments))
784
+ } catch {
785
+ /* a pattern the router would refuse registers nothing */
786
+ return []
787
+ }
788
+ }
789
+
790
+ function recordRoute(key: string, ctxType: ts.Type): void {
791
+ /* extract extra middleware props by iterating intersection MEMBERS individually.
792
+ * This preserves source-level declarations (e.g. ShorthandPropertyAssignment)
793
+ * that get lost when extracting from the flattened intersection. */
794
+ const extraEntries: MwPropEntry[] = []
795
+ const dupeNames = new Set<string>()
796
+ const members = ctxType.isIntersection() ? (ctxType as ts.IntersectionType).types : [ctxType]
797
+ for (const member of members) {
798
+ for (const rp of extractMiddlewareProps(member, ser)) {
799
+ if (baseMwPropNames.has(rp.name)) continue
800
+ if (extraEntries.some((e) => e.name === rp.name)) {
801
+ dupeNames.add(rp.name)
802
+ continue
646
803
  }
804
+ extraEntries.push(rp)
647
805
  }
648
806
  }
807
+ /* a property in several intersection members (e.g. auth narrowed by a later middleware)
808
+ * is read from the flattened intersection, so TypeScript computes the intersected type */
809
+ for (const dupeName of dupeNames) {
810
+ const sym = ctxType.getProperty(dupeName)
811
+ if (!sym) continue
812
+ const t = checker.getTypeOfSymbolAtLocation(sym, sym.valueDeclaration ?? baseNode)
813
+ const typeStr = ser.asRef(t) ?? ser.serialize(t)
814
+ const idx = extraEntries.findIndex((e) => e.name === dupeName)
815
+ if (idx !== -1) extraEntries[idx] = { ...extraEntries[idx], type: typeStr }
816
+ }
817
+ if (extraEntries.length === 0) return
649
818
 
650
- tsLib.forEachChild(node, visitNode)
819
+ /* reuse compact base type references when the route has the same property
820
+ * (e.g. shardDb: ShardDb from base → reuse import ref instead of structural expansion) */
821
+ const entries = extraEntries.map((e) => {
822
+ const baseRef = basePropTypeByName.get(e.name)
823
+ return baseRef && baseRef.length < e.type.length ? { ...e, type: baseRef } : e
824
+ })
825
+ routeMiddleware[key] = sanitize(propsType(entries) ?? "{}")
826
+ routeMiddlewareProps[key] = entries.map((e) => ({ name: e.name, opt: e.opt, type: sanitize(e.type) }))
651
827
  }
652
828
 
653
- visitNode(compilerSourceFile)
829
+ /* every source file of the program, not only the entry: routes are often registered in
830
+ modules the entry imports */
831
+ for (const sf of project.getSourceFiles()) {
832
+ const compilerSf: ts.SourceFile = bridge(sf.compilerNode)
833
+ if (compilerSf.isDeclarationFile) continue
834
+ if (/[\\/]node_modules[\\/]/.test(compilerSf.fileName)) continue
835
+ visitNode(compilerSf)
836
+ }
654
837
 
655
838
  /* extract $taps */
656
839
  let tapsType: string | null = null
657
- const tapsSym = baseType.getProperty("$taps")
840
+ const tapsSym = appType.getProperty("$taps")
658
841
  if (tapsSym) {
659
- const tapsT = checker.getTypeOfSymbolAtLocation(tapsSym, baseNode)
660
- const tapsStr = serialize(tapsT, checker, baseNode, tsLib)
661
- if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") {
662
- tapsType = tapsStr
663
- }
842
+ const tapsStr = ser.serialize(checker.getTypeOfSymbolAtLocation(tapsSym, baseNode))
843
+ if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") tapsType = tapsStr
664
844
  }
665
845
 
666
846
  return {
@@ -669,7 +849,7 @@ export async function extractChainTypes(options: {
669
849
  middlewareType: baseMiddlewareType ? sanitize(baseMiddlewareType) : null,
670
850
  tapsType: tapsType ? sanitize(tapsType) : null,
671
851
  },
672
- routeMiddleware,
673
- routeMiddlewareProps,
852
+ routeMiddleware: { ...routeMiddleware },
853
+ routeMiddlewareProps: { ...routeMiddlewareProps },
674
854
  }
675
855
  }