@lovrozagar/honey 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +1596 -6
  2. package/dist/accepts.d.ts +6 -0
  3. package/dist/accepts.d.ts.map +1 -0
  4. package/dist/body-limit.d.ts +22 -0
  5. package/dist/body-limit.d.ts.map +1 -0
  6. package/dist/build/index.d.ts +42 -0
  7. package/dist/build/index.d.ts.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/client/error.d.ts +14 -0
  11. package/dist/client/error.d.ts.map +1 -0
  12. package/dist/client/http.d.ts +88 -0
  13. package/dist/client/http.d.ts.map +1 -0
  14. package/dist/client/index.d.ts +22 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/sdk.d.ts +34 -0
  17. package/dist/client/sdk.d.ts.map +1 -0
  18. package/dist/client/sse.d.ts +10 -0
  19. package/dist/client/sse.d.ts.map +1 -0
  20. package/dist/client/types.d.ts +124 -0
  21. package/dist/client/types.d.ts.map +1 -0
  22. package/dist/client/ws.d.ts +15 -0
  23. package/dist/client/ws.d.ts.map +1 -0
  24. package/dist/client-mcp/server.d.ts +10 -0
  25. package/dist/client-mcp/server.d.ts.map +1 -0
  26. package/dist/client-mcp/types.d.ts +16 -0
  27. package/dist/client-mcp/types.d.ts.map +1 -0
  28. package/dist/cloudflare/realtime.d.ts +30 -0
  29. package/dist/cloudflare/realtime.d.ts.map +1 -0
  30. package/dist/codegen-go-cli.d.ts +22 -0
  31. package/dist/codegen-go-cli.d.ts.map +1 -0
  32. package/dist/codegen-go.d.ts +29 -0
  33. package/dist/codegen-go.d.ts.map +1 -0
  34. package/dist/codegen-hash.d.ts +3 -0
  35. package/dist/codegen-hash.d.ts.map +1 -0
  36. package/dist/codegen-ir.d.ts +123 -0
  37. package/dist/codegen-ir.d.ts.map +1 -0
  38. package/dist/codegen-loaders.d.ts +12 -0
  39. package/dist/codegen-loaders.d.ts.map +1 -0
  40. package/dist/codegen-mcp.d.ts +13 -0
  41. package/dist/codegen-mcp.d.ts.map +1 -0
  42. package/dist/codegen-python.d.ts +21 -0
  43. package/dist/codegen-python.d.ts.map +1 -0
  44. package/dist/codegen-rust.d.ts +18 -0
  45. package/dist/codegen-rust.d.ts.map +1 -0
  46. package/dist/codegen-sanitize.d.ts +3 -0
  47. package/dist/codegen-sanitize.d.ts.map +1 -0
  48. package/dist/codegen-schema-naming.d.ts +22 -0
  49. package/dist/codegen-schema-naming.d.ts.map +1 -0
  50. package/dist/codegen.d.ts +178 -0
  51. package/dist/codegen.d.ts.map +1 -0
  52. package/dist/context.d.ts +56 -0
  53. package/dist/context.d.ts.map +1 -0
  54. package/dist/cookie-sign.d.ts +3 -0
  55. package/dist/cookie-sign.d.ts.map +1 -0
  56. package/dist/cookie.d.ts +12 -0
  57. package/dist/cookie.d.ts.map +1 -0
  58. package/dist/cors.d.ts +13 -0
  59. package/dist/cors.d.ts.map +1 -0
  60. package/dist/crypto.d.ts +2 -0
  61. package/dist/crypto.d.ts.map +1 -0
  62. package/dist/csrf.d.ts +9 -0
  63. package/dist/csrf.d.ts.map +1 -0
  64. package/dist/curl-logger.d.ts +33 -0
  65. package/dist/curl-logger.d.ts.map +1 -0
  66. package/dist/detect-runtime.d.ts +16 -0
  67. package/dist/detect-runtime.d.ts.map +1 -0
  68. package/dist/error.d.ts +21 -0
  69. package/dist/error.d.ts.map +1 -0
  70. package/dist/errors.d.ts +40 -0
  71. package/dist/errors.d.ts.map +1 -0
  72. package/dist/etag.d.ts +9 -0
  73. package/dist/etag.d.ts.map +1 -0
  74. package/dist/feature-load.d.ts +3 -0
  75. package/dist/feature-load.d.ts.map +1 -0
  76. package/dist/go-type-emitter.d.ts +33 -0
  77. package/dist/go-type-emitter.d.ts.map +1 -0
  78. package/dist/honey-response.d.ts +82 -0
  79. package/dist/honey-response.d.ts.map +1 -0
  80. package/dist/i18n-slot.d.ts +7 -0
  81. package/dist/i18n-slot.d.ts.map +1 -0
  82. package/dist/i18n.d.ts +26 -0
  83. package/dist/i18n.d.ts.map +1 -0
  84. package/dist/index.d.ts +470 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/init.d.ts +7 -0
  87. package/dist/init.d.ts.map +1 -0
  88. package/dist/input.d.ts +6 -0
  89. package/dist/input.d.ts.map +1 -0
  90. package/dist/ip-restrict.d.ts +12 -0
  91. package/dist/ip-restrict.d.ts.map +1 -0
  92. package/dist/logger.d.ts +45 -0
  93. package/dist/logger.d.ts.map +1 -0
  94. package/dist/meta-spec.d.ts +81 -0
  95. package/dist/meta-spec.d.ts.map +1 -0
  96. package/dist/middleware.d.ts +60 -0
  97. package/dist/middleware.d.ts.map +1 -0
  98. package/dist/node-request.d.ts +59 -0
  99. package/dist/node-request.d.ts.map +1 -0
  100. package/dist/node.d.ts +13 -0
  101. package/dist/node.d.ts.map +1 -0
  102. package/dist/openapi/register.d.ts +3 -0
  103. package/dist/openapi/register.d.ts.map +1 -0
  104. package/dist/openapi/scalar.d.ts +12 -0
  105. package/dist/openapi/scalar.d.ts.map +1 -0
  106. package/dist/openapi/spec-factory.d.ts +31 -0
  107. package/dist/openapi/spec-factory.d.ts.map +1 -0
  108. package/dist/openapi/spec.d.ts +19 -0
  109. package/dist/openapi/spec.d.ts.map +1 -0
  110. package/dist/openapi/swagger.d.ts +12 -0
  111. package/dist/openapi/swagger.d.ts.map +1 -0
  112. package/dist/plugin.d.ts +167 -0
  113. package/dist/plugin.d.ts.map +1 -0
  114. package/dist/powered-by.d.ts +7 -0
  115. package/dist/powered-by.d.ts.map +1 -0
  116. package/dist/pretty-json.d.ts +10 -0
  117. package/dist/pretty-json.d.ts.map +1 -0
  118. package/dist/proxy.d.ts +51 -0
  119. package/dist/proxy.d.ts.map +1 -0
  120. package/dist/python-type-emitter.d.ts +11 -0
  121. package/dist/python-type-emitter.d.ts.map +1 -0
  122. package/dist/realtime/buffer.d.ts +18 -0
  123. package/dist/realtime/buffer.d.ts.map +1 -0
  124. package/dist/realtime/bus.d.ts +11 -0
  125. package/dist/realtime/bus.d.ts.map +1 -0
  126. package/dist/realtime/longpoll.d.ts +23 -0
  127. package/dist/realtime/longpoll.d.ts.map +1 -0
  128. package/dist/realtime/protocol.d.ts +50 -0
  129. package/dist/realtime/protocol.d.ts.map +1 -0
  130. package/dist/realtime/route.d.ts +42 -0
  131. package/dist/realtime/route.d.ts.map +1 -0
  132. package/dist/realtime/runtime.d.ts +74 -0
  133. package/dist/realtime/runtime.d.ts.map +1 -0
  134. package/dist/request-id.d.ts +12 -0
  135. package/dist/request-id.d.ts.map +1 -0
  136. package/dist/request-to-curl.d.ts +9 -0
  137. package/dist/request-to-curl.d.ts.map +1 -0
  138. package/dist/response.d.ts +57 -0
  139. package/dist/response.d.ts.map +1 -0
  140. package/dist/rust-type-emitter.d.ts +32 -0
  141. package/dist/rust-type-emitter.d.ts.map +1 -0
  142. package/dist/secure-headers.d.ts +16 -0
  143. package/dist/secure-headers.d.ts.map +1 -0
  144. package/dist/serve-register.d.ts +2 -0
  145. package/dist/serve-register.d.ts.map +1 -0
  146. package/dist/serve-slot.d.ts +6 -0
  147. package/dist/serve-slot.d.ts.map +1 -0
  148. package/dist/serve.d.ts +21 -0
  149. package/dist/serve.d.ts.map +1 -0
  150. package/dist/server-timing.d.ts +9 -0
  151. package/dist/server-timing.d.ts.map +1 -0
  152. package/dist/static.d.ts +12 -0
  153. package/dist/static.d.ts.map +1 -0
  154. package/dist/telemetry/otel.d.ts +59 -0
  155. package/dist/telemetry/otel.d.ts.map +1 -0
  156. package/dist/testing.d.ts +24 -0
  157. package/dist/testing.d.ts.map +1 -0
  158. package/dist/timeout.d.ts +7 -0
  159. package/dist/timeout.d.ts.map +1 -0
  160. package/dist/tree.d.ts +90 -0
  161. package/dist/tree.d.ts.map +1 -0
  162. package/dist/ts-type-emitter.d.ts +4 -0
  163. package/dist/ts-type-emitter.d.ts.map +1 -0
  164. package/dist/type-emitter.d.ts +8 -0
  165. package/dist/type-emitter.d.ts.map +1 -0
  166. package/dist/type-extractor.d.ts +39 -0
  167. package/dist/type-extractor.d.ts.map +1 -0
  168. package/dist/types.d.ts +451 -0
  169. package/dist/types.d.ts.map +1 -0
  170. package/dist/validation.d.ts +18 -0
  171. package/dist/validation.d.ts.map +1 -0
  172. package/dist/ws/bun.d.ts +28 -0
  173. package/dist/ws/bun.d.ts.map +1 -0
  174. package/dist/ws/cloudflare.d.ts +55 -0
  175. package/dist/ws/cloudflare.d.ts.map +1 -0
  176. package/dist/ws/deno.d.ts +13 -0
  177. package/dist/ws/deno.d.ts.map +1 -0
  178. package/dist/ws/node.d.ts +10 -0
  179. package/dist/ws/node.d.ts.map +1 -0
  180. package/dist/yaml.d.ts +4 -0
  181. package/dist/yaml.d.ts.map +1 -0
  182. package/docs/api-ref/go.md +82 -82
  183. package/docs/api-ref/python.md +82 -82
  184. package/docs/api-ref/rust.md +82 -82
  185. package/docs/api-ref/typescript.md +82 -82
  186. package/docs/invalidate-spec.md +13 -13
  187. package/docs/meta-spec.md +513 -0
  188. package/docs/packaging.md +85 -0
  189. package/docs/sdk.md +87 -73
  190. package/package.json +195 -51
  191. package/src/body-limit.ts +2 -1
  192. package/src/build/index.ts +2 -9
  193. package/src/cli.ts +2 -3
  194. package/src/client/http.ts +204 -223
  195. package/src/client/index.ts +6 -14
  196. package/src/client/sdk.ts +24 -17
  197. package/src/client/types.ts +4 -9
  198. package/src/client/ws.ts +3 -5
  199. package/src/client-mcp/server.ts +1 -4
  200. package/src/cloudflare/realtime.ts +8 -10
  201. package/src/codegen-go-cli.ts +74 -67
  202. package/src/codegen-go.ts +71 -51
  203. package/src/codegen-ir.ts +16 -27
  204. package/src/codegen-loaders.ts +5 -8
  205. package/src/codegen-mcp.ts +9 -27
  206. package/src/codegen-python.ts +47 -27
  207. package/src/codegen-rust.ts +110 -66
  208. package/src/codegen-sanitize.ts +1 -7
  209. package/src/codegen-schema-naming.ts +8 -2
  210. package/src/codegen.ts +299 -196
  211. package/src/context.ts +91 -95
  212. package/src/cookie-sign.ts +5 -13
  213. package/src/cookie.ts +1 -2
  214. package/src/cors.ts +4 -6
  215. package/src/crypto.ts +1 -4
  216. package/src/csrf.ts +3 -3
  217. package/src/curl-logger.ts +14 -32
  218. package/src/detect-runtime.ts +1 -3
  219. package/src/errors.ts +1 -3
  220. package/src/etag.ts +3 -4
  221. package/src/go-type-emitter.ts +45 -40
  222. package/src/honey-response.ts +1 -3
  223. package/src/i18n.ts +3 -12
  224. package/src/index.ts +1168 -1412
  225. package/src/ip-restrict.ts +2 -1
  226. package/src/logger.ts +13 -13
  227. package/src/meta-spec.ts +0 -0
  228. package/src/middleware.ts +111 -79
  229. package/src/node-request.ts +2 -1
  230. package/src/node.ts +9 -10
  231. package/src/openapi/register.ts +1 -2
  232. package/src/openapi/scalar.ts +9 -9
  233. package/src/openapi/spec-factory.ts +2 -2
  234. package/src/openapi/spec.ts +26 -19
  235. package/src/openapi/swagger.ts +9 -9
  236. package/src/plugin.ts +226 -256
  237. package/src/powered-by.ts +2 -1
  238. package/src/pretty-json.ts +2 -1
  239. package/src/proxy.ts +1 -3
  240. package/src/python-type-emitter.ts +37 -12
  241. package/src/realtime/route.ts +1 -1
  242. package/src/realtime/runtime.ts +9 -12
  243. package/src/request-id.ts +3 -4
  244. package/src/request-to-curl.ts +1 -4
  245. package/src/response.ts +150 -199
  246. package/src/rust-type-emitter.ts +66 -25
  247. package/src/secure-headers.ts +2 -1
  248. package/src/serve.ts +3 -12
  249. package/src/server-timing.ts +2 -1
  250. package/src/static.ts +3 -4
  251. package/src/telemetry/otel.ts +1 -6
  252. package/src/testing.ts +2 -11
  253. package/src/timeout.ts +2 -1
  254. package/src/tree.ts +12 -27
  255. package/src/ts-type-emitter.ts +71 -15
  256. package/src/type-emitter.ts +226 -237
  257. package/src/type-extractor.ts +260 -334
  258. package/src/types.ts +168 -68
  259. package/src/validation.ts +7 -30
  260. package/src/ws/bun.ts +2 -9
  261. package/src/ws/cloudflare.ts +13 -16
  262. package/src/ws/deno.ts +31 -40
  263. package/src/ws/node.ts +4 -19
@@ -1,23 +1,23 @@
1
- import { existsSync } from "node:fs";
2
- import { dirname, relative, resolve } from "node:path";
3
- import type { ts } from "ts-morph";
1
+ import { existsSync } from "node:fs"
2
+ import { dirname, relative, resolve } from "node:path"
3
+ import type { ts } from "ts-morph"
4
4
 
5
5
  export type ExtractedBaseCtx = {
6
- envType: string;
7
- middlewareType: string | null;
8
- tapsType: string | null;
9
- };
6
+ envType: string
7
+ middlewareType: string | null
8
+ tapsType: string | null
9
+ }
10
10
 
11
11
  function findTsConfig(fromPath: string): string | undefined {
12
- let dir = dirname(resolve(fromPath));
13
- let prev = "";
12
+ let dir = dirname(resolve(fromPath))
13
+ let prev = ""
14
14
  while (dir !== prev) {
15
- const p = resolve(dir, "tsconfig.json");
16
- if (existsSync(p)) return p;
17
- prev = dir;
18
- dir = dirname(dir);
15
+ const p = resolve(dir, "tsconfig.json")
16
+ if (existsSync(p)) return p
17
+ prev = dir
18
+ dir = dirname(dir)
19
19
  }
20
- return undefined;
20
+ return undefined
21
21
  }
22
22
 
23
23
  /**
@@ -25,85 +25,76 @@ function findTsConfig(fromPath: string): string | undefined {
25
25
  * Returns inline type strings so the gen file has zero imports from user code.
26
26
  */
27
27
  export async function extractBaseCtx(options: {
28
- entryPath: string;
29
- exportName: string;
30
- outputDir?: string;
31
- tsconfigPath?: string;
28
+ entryPath: string
29
+ exportName: string
30
+ outputDir?: string
31
+ tsconfigPath?: string
32
32
  }): Promise<ExtractedBaseCtx> {
33
- const { Project, ts } = await import("ts-morph");
33
+ const { Project, ts } = await import("ts-morph")
34
34
 
35
- const tsConfigFilePath =
36
- options.tsconfigPath ?? findTsConfig(options.entryPath);
35
+ const tsConfigFilePath = options.tsconfigPath ?? findTsConfig(options.entryPath)
37
36
 
38
37
  const project = new Project({
39
38
  skipAddingFilesFromTsConfig: true,
40
39
  tsConfigFilePath,
41
- });
40
+ })
42
41
 
43
- project.addSourceFilesAtPaths(options.entryPath);
44
- project.resolveSourceFileDependencies();
42
+ project.addSourceFilesAtPaths(options.entryPath)
43
+ project.resolveSourceFileDependencies()
45
44
 
46
- const sourceFile = project.getSourceFileOrThrow(options.entryPath);
47
- const varDecl = sourceFile.getVariableDeclaration(options.exportName);
45
+ const sourceFile = project.getSourceFileOrThrow(options.entryPath)
46
+ const varDecl = sourceFile.getVariableDeclaration(options.exportName)
48
47
  if (!varDecl) {
49
- throw new Error(
50
- `Export "${options.exportName}" not found in ${options.entryPath}`,
51
- );
48
+ throw new Error(`Export "${options.exportName}" not found in ${options.entryPath}`)
52
49
  }
53
50
 
54
- const type = varDecl.getType();
51
+ const type = varDecl.getType()
55
52
  /* ts-morph bundles different TS version — structurally identical at runtime */
56
53
  function bridge<T>(value: unknown): T {
57
- return value as T;
54
+ return value as T
58
55
  }
59
- const checker: ts.TypeChecker = bridge(
60
- project.getTypeChecker().compilerObject,
61
- );
62
- const node: ts.Node = bridge(varDecl.compilerNode);
63
- const tsLib: typeof ts = bridge(ts);
64
-
65
- function getType(
66
- sym: ReturnType<typeof type.getProperty>,
67
- ): ts.Type {
68
- const s: ts.Symbol = bridge(sym?.compilerSymbol);
69
- return checker.getTypeOfSymbolAtLocation(s, node);
56
+ const checker: ts.TypeChecker = bridge(project.getTypeChecker().compilerObject)
57
+ const node: ts.Node = bridge(varDecl.compilerNode)
58
+ const tsLib: typeof ts = bridge(ts)
59
+
60
+ function getType(sym: ReturnType<typeof type.getProperty>): ts.Type {
61
+ const s: ts.Symbol = bridge(sym?.compilerSymbol)
62
+ return checker.getTypeOfSymbolAtLocation(s, node)
70
63
  }
71
64
 
72
65
  /* extract $env */
73
- let envType = "Record<string, unknown>";
74
- const envProp = type.getProperty("$env");
66
+ let envType = "Record<string, unknown>"
67
+ const envProp = type.getProperty("$env")
75
68
  if (envProp) {
76
- envType = serialize(getType(envProp), checker, node, tsLib);
69
+ envType = serialize(getType(envProp), checker, node, tsLib)
77
70
  }
78
71
 
79
72
  /* extract $ctx and isolate middleware additions */
80
- const ctxProp = type.getProperty("$ctx");
73
+ const ctxProp = type.getProperty("$ctx")
81
74
  if (!ctxProp) {
82
- throw new Error(
83
- `Export "${options.exportName}" has no $ctx — not a Honey instance`,
84
- );
75
+ throw new Error(`Export "${options.exportName}" has no $ctx — not a Honey instance`)
85
76
  }
86
77
 
87
- const ctxType = getType(ctxProp);
88
- const middlewareType = extractMiddleware(ctxType, checker, node, tsLib);
78
+ const ctxType = getType(ctxProp)
79
+ const middlewareType = extractMiddleware(ctxType, checker, node, tsLib)
89
80
 
90
81
  /* extract $taps — typed tap payloads declared via .taps<T>() */
91
- let tapsType: string | null = null;
92
- const tapsProp = type.getProperty("$taps");
82
+ let tapsType: string | null = null
83
+ const tapsProp = type.getProperty("$taps")
93
84
  if (tapsProp) {
94
- const tapsT = getType(tapsProp);
95
- const tapsStr = serialize(tapsT, checker, node, tsLib);
85
+ const tapsT = getType(tapsProp)
86
+ const tapsStr = serialize(tapsT, checker, node, tsLib)
96
87
  if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") {
97
- tapsType = tapsStr;
88
+ tapsType = tapsStr
98
89
  }
99
90
  }
100
91
 
101
- const sanitize = (s: string) => sanitizeImportPaths(s, options.outputDir);
92
+ const sanitize = (s: string) => sanitizeImportPaths(s, options.outputDir)
102
93
  return {
103
94
  envType: sanitize(envType),
104
95
  middlewareType: middlewareType ? sanitize(middlewareType) : null,
105
96
  tapsType: tapsType ? sanitize(tapsType) : null,
106
- };
97
+ }
107
98
  }
108
99
 
109
100
  /* properties that live on HoneyContext — everything else is middleware-added */
@@ -123,7 +114,7 @@ const HONEY_CTX_PROPS = new Set([
123
114
  "search",
124
115
  "searchAll",
125
116
  "tap",
126
- ]);
117
+ ])
127
118
 
128
119
  /** Resolve an import() reference from a declaration's type annotation */
129
120
  function resolveTypeRefFromDecl(
@@ -137,7 +128,7 @@ function resolveTypeRefFromDecl(
137
128
  const typeName = (typeRef as ts.TypeReferenceNode).typeName
138
129
 
139
130
  let refSym = checker.getSymbolAtLocation(typeName)
140
- while (refSym && (refSym.flags & compiler.SymbolFlags.Alias)) {
131
+ while (refSym && refSym.flags & compiler.SymbolFlags.Alias) {
141
132
  refSym = checker.getAliasedSymbol(refSym)
142
133
  }
143
134
  if (!refSym) return undefined
@@ -159,46 +150,46 @@ function extractMiddlewareProps(
159
150
  node: ts.Node,
160
151
  compiler: typeof ts,
161
152
  ): MwPropEntry[] {
162
- const allProps = ctxType.getProperties();
153
+ const allProps = ctxType.getProperties()
163
154
  const mwProps = allProps.filter((p) => {
164
- const name = p.getName();
165
- if (HONEY_CTX_PROPS.has(name)) return false;
155
+ const name = p.getName()
156
+ if (HONEY_CTX_PROPS.has(name)) return false
166
157
  /* skip internal/private properties (underscore-prefixed) */
167
- if (name.startsWith("_")) return false;
168
- return true;
169
- });
158
+ if (name.startsWith("_")) return false
159
+ return true
160
+ })
170
161
 
171
162
  return mwProps.map((p) => {
172
- const t = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? node);
173
- const opt = (p.flags & compiler.SymbolFlags.Optional) !== 0;
163
+ const t = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? node)
164
+ const opt = (p.flags & compiler.SymbolFlags.Optional) !== 0
174
165
 
175
166
  /* 1. try declaration-site TypeReference (explicit annotation on property or variable) */
176
- let typeStr: string | undefined;
177
- const mwDecl = p.valueDeclaration ?? p.declarations?.[0];
178
- typeStr = resolveTypeRefFromDecl(mwDecl, checker, compiler);
167
+ let typeStr: string | undefined
168
+ const mwDecl = p.valueDeclaration ?? p.declarations?.[0]
169
+ typeStr = resolveTypeRefFromDecl(mwDecl, checker, compiler)
179
170
 
180
171
  /* 1b. shorthand property — trace to the underlying variable's type annotation */
181
172
  if (!typeStr && mwDecl && compiler.isShorthandPropertyAssignment(mwDecl)) {
182
- const varSym = checker.getShorthandAssignmentValueSymbol(mwDecl);
173
+ const varSym = checker.getShorthandAssignmentValueSymbol(mwDecl)
183
174
  if (varSym) {
184
- const varDecl = varSym.valueDeclaration ?? varSym.declarations?.[0];
185
- typeStr = resolveTypeRefFromDecl(varDecl, checker, compiler);
175
+ const varDecl = varSym.valueDeclaration ?? varSym.declarations?.[0]
176
+ typeStr = resolveTypeRefFromDecl(varDecl, checker, compiler)
186
177
 
187
178
  /* 1c. destructured binding (e.g. const { shardDb } = openShardSession(...))
188
179
  * — trace through the function return type to find the original type annotation */
189
180
  if (!typeStr && varDecl && compiler.isBindingElement(varDecl)) {
190
- let parent: ts.Node = varDecl.parent;
191
- while (parent && !compiler.isVariableDeclaration(parent)) parent = parent.parent;
181
+ let parent: ts.Node = varDecl.parent
182
+ while (parent && !compiler.isVariableDeclaration(parent)) parent = parent.parent
192
183
  if (parent && compiler.isVariableDeclaration(parent)) {
193
184
  const initializer = (parent as ts.VariableDeclaration).initializer
194
185
  if (initializer) {
195
- const initType = checker.getTypeAtLocation(initializer);
196
- const retProp = initType.getProperty(p.getName());
186
+ const initType = checker.getTypeAtLocation(initializer)
187
+ const retProp = initType.getProperty(p.getName())
197
188
  if (retProp?.valueDeclaration && compiler.isShorthandPropertyAssignment(retProp.valueDeclaration)) {
198
- const innerSym = checker.getShorthandAssignmentValueSymbol(retProp.valueDeclaration);
189
+ const innerSym = checker.getShorthandAssignmentValueSymbol(retProp.valueDeclaration)
199
190
  if (innerSym) {
200
- const innerDecl = innerSym.valueDeclaration ?? innerSym.declarations?.[0];
201
- typeStr = resolveTypeRefFromDecl(innerDecl, checker, compiler);
191
+ const innerDecl = innerSym.valueDeclaration ?? innerSym.declarations?.[0]
192
+ typeStr = resolveTypeRefFromDecl(innerDecl, checker, compiler)
202
193
  }
203
194
  }
204
195
  }
@@ -209,16 +200,16 @@ function extractMiddlewareProps(
209
200
 
210
201
  /* 2. try resolved type as named import reference */
211
202
  if (!typeStr) {
212
- typeStr = serializeAsRef(t, checker, node, compiler) ?? undefined;
203
+ typeStr = serializeAsRef(t, checker, node, compiler) ?? undefined
213
204
  }
214
205
 
215
206
  /* 3. structural fallback */
216
207
  if (!typeStr) {
217
- typeStr = serialize(t, checker, node, compiler);
208
+ typeStr = serialize(t, checker, node, compiler)
218
209
  }
219
210
 
220
- return { name: p.getName(), opt, type: typeStr };
221
- });
211
+ return { name: p.getName(), opt, type: typeStr }
212
+ })
222
213
  }
223
214
 
224
215
  function extractMiddleware(
@@ -227,24 +218,20 @@ function extractMiddleware(
227
218
  node: ts.Node,
228
219
  compiler: typeof ts,
229
220
  ): string | null {
230
- const props = extractMiddlewareProps(ctxType, checker, node, compiler);
231
- if (props.length === 0) return null;
232
- const entries = props.map((p) => `${p.name}${p.opt ? "?" : ""}: ${p.type}`);
233
- return `{ ${entries.join("; ")} }`;
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("; ")} }`
234
225
  }
235
226
 
236
227
  /** Check if a symbol is exported from its source file */
237
- function isExportedFromFile(
238
- sym: ts.Symbol,
239
- sf: ts.SourceFile,
240
- checker: ts.TypeChecker,
241
- ): boolean {
228
+ function isExportedFromFile(sym: ts.Symbol, sf: ts.SourceFile, checker: ts.TypeChecker): boolean {
242
229
  /* .d.ts module types are always accessible */
243
- if (sf.isDeclarationFile) return true;
244
- const moduleSym = checker.getSymbolAtLocation(sf);
245
- if (!moduleSym) return false;
246
- const exports = checker.getExportsOfModule(moduleSym);
247
- return exports.some((e) => e.getName() === sym.getName());
230
+ if (sf.isDeclarationFile) return true
231
+ const moduleSym = checker.getSymbolAtLocation(sf)
232
+ if (!moduleSym) return false
233
+ const exports = checker.getExportsOfModule(moduleSym)
234
+ return exports.some((e) => e.getName() === sym.getName())
248
235
  }
249
236
 
250
237
  /**
@@ -263,153 +250,134 @@ function serializeAsRef(
263
250
  compiler: typeof ts,
264
251
  depth = 0,
265
252
  ): string | null {
266
- if (depth > 6) return null;
253
+ if (depth > 6) return null
267
254
 
268
255
  /* union with never members (from intersection narrowing) — simplify first */
269
256
  if (type.isUnion()) {
270
- const nonNever = (type as ts.UnionType).types.filter(
271
- (t) => !(t.flags & compiler.TypeFlags.Never),
272
- );
257
+ const nonNever = (type as ts.UnionType).types.filter((t) => !(t.flags & compiler.TypeFlags.Never))
273
258
  if (nonNever.length === 1) {
274
- return serializeAsRef(nonNever[0], checker, node, compiler, depth);
259
+ return serializeAsRef(nonNever[0], checker, node, compiler, depth)
275
260
  }
276
261
  }
277
262
 
278
- const sym = type.getSymbol() ?? type.aliasSymbol;
279
- if (!sym) return null;
263
+ const sym = type.getSymbol() ?? type.aliasSymbol
264
+ if (!sym) return null
280
265
 
281
- const name = sym.getName();
282
- if (!name || name.startsWith("__") || name === "Object") return null;
266
+ const name = sym.getName()
267
+ if (!name || name.startsWith("__") || name === "Object") return null
283
268
 
284
- const decls = sym.declarations;
285
- if (!decls?.length) return null;
269
+ const decls = sym.declarations
270
+ if (!decls?.length) return null
286
271
 
287
- const sf = decls[0].getSourceFile();
288
- const fileName = sf.fileName;
272
+ const sf = decls[0].getSourceFile()
273
+ const fileName = sf.fileName
289
274
 
290
275
  /* skip global/lib types — serialize() handles these with its globals set */
291
- if (fileName.includes("/lib.") || fileName.includes("/lib/")) return null;
276
+ if (fileName.includes("/lib.") || fileName.includes("/lib/")) return null
292
277
 
293
- const isDts = sf.isDeclarationFile;
278
+ const isDts = sf.isDeclarationFile
294
279
  const isTypeAlias =
295
- (sym.flags & compiler.SymbolFlags.TypeAlias) !== 0 ||
296
- decls.some((d) => compiler.isTypeAliasDeclaration(d));
280
+ (sym.flags & compiler.SymbolFlags.TypeAlias) !== 0 || decls.some((d) => compiler.isTypeAliasDeclaration(d))
297
281
 
298
282
  /* only reference .d.ts symbols or explicit type aliases from .ts files */
299
- if (!isDts && !isTypeAlias) return null;
283
+ if (!isDts && !isTypeAlias) return null
300
284
 
301
285
  /* skip ambient .d.ts scripts (no exports) — import() can't resolve them */
302
286
  if (isDts) {
303
- const tsSf = sf as unknown as { externalModuleIndicator?: unknown };
304
- if (!tsSf.externalModuleIndicator) return null;
287
+ const tsSf = sf as unknown as { externalModuleIndicator?: unknown }
288
+ if (!tsSf.externalModuleIndicator) return null
305
289
  }
306
290
 
307
291
  /* skip non-exported symbols — import() on them resolves to `any` */
308
- if (!isExportedFromFile(sym, sf, checker)) return null;
292
+ if (!isExportedFromFile(sym, sf, checker)) return null
309
293
 
310
294
  /* build type arguments if present */
311
- const ref = type as ts.TypeReference;
312
- const typeArgs = ref.typeArguments ?? type.aliasTypeArguments;
295
+ const ref = type as ts.TypeReference
296
+ const typeArgs = ref.typeArguments ?? type.aliasTypeArguments
313
297
  if (typeArgs?.length) {
314
298
  const args = typeArgs.map((arg) => {
315
- const argRef = serializeAsRef(arg, checker, node, compiler, depth + 1);
316
- return argRef ?? serialize(arg, checker, node, compiler);
317
- });
318
- return `import("${fileName}").${name}<${args.join(", ")}>`;
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(", ")}>`
319
303
  }
320
304
 
321
- return `import("${fileName}").${name}`;
305
+ return `import("${fileName}").${name}`
322
306
  }
323
307
 
324
- function serialize(
325
- type: ts.Type,
326
- checker: ts.TypeChecker,
327
- node: ts.Node,
328
- compiler: typeof ts,
329
- depth = 0,
330
- ): string {
331
- if (depth > 8) return "unknown";
308
+ function serialize(type: ts.Type, checker: ts.TypeChecker, node: ts.Node, compiler: typeof ts, depth = 0): string {
309
+ if (depth > 8) return "unknown"
332
310
 
333
- const next = (t: ts.Type) =>
334
- serialize(t, checker, node, compiler, depth + 1);
311
+ const next = (t: ts.Type) => serialize(t, checker, node, compiler, depth + 1)
335
312
 
336
313
  /* primitives */
337
- if (type.flags & compiler.TypeFlags.String) return "string";
338
- if (type.flags & compiler.TypeFlags.Number) return "number";
339
- if (type.flags & compiler.TypeFlags.Boolean) return "boolean";
340
- if (type.flags & compiler.TypeFlags.BigInt) return "bigint";
341
- if (type.flags & compiler.TypeFlags.ESSymbol) return "symbol";
342
- if (type.flags & compiler.TypeFlags.Void) return "void";
343
- if (type.flags & compiler.TypeFlags.Undefined) return "undefined";
344
- if (type.flags & compiler.TypeFlags.Null) return "null";
345
- if (type.flags & compiler.TypeFlags.Never) return "never";
346
- if (type.flags & compiler.TypeFlags.Unknown) return "unknown";
347
- if (type.flags & compiler.TypeFlags.Any) return "unknown";
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"
348
325
 
349
326
  /* literals */
350
- if (type.isStringLiteral())
351
- return `"${(type as ts.StringLiteralType).value}"`;
352
- if (type.isNumberLiteral())
353
- return `${(type as ts.NumberLiteralType).value}`;
327
+ if (type.isStringLiteral()) return `"${(type as ts.StringLiteralType).value}"`
328
+ if (type.isNumberLiteral()) return `${(type as ts.NumberLiteralType).value}`
354
329
 
355
330
  /* union */
356
331
  if (type.isUnion()) {
357
332
  return type.types
358
333
  .map((t) => {
359
- const s = next(t);
334
+ const s = next(t)
360
335
  /* parenthesize function types inside unions — TS syntax requires it */
361
- return s.includes("=>") ? `(${s})` : s;
336
+ return s.includes("=>") ? `(${s})` : s
362
337
  })
363
- .join(" | ");
338
+ .join(" | ")
364
339
  }
365
340
 
366
341
  /* intersection */
367
- if (type.isIntersection()) return type.types.map(next).join(" & ");
342
+ if (type.isIntersection()) return type.types.map(next).join(" & ")
368
343
 
369
344
  /* array */
370
345
  if (checker.isArrayType(type)) {
371
- const args = checker.getTypeArguments(
372
- type as ts.TypeReference,
373
- );
346
+ const args = checker.getTypeArguments(type as ts.TypeReference)
374
347
  if (args.length > 0) {
375
- const el = next(args[0]);
376
- return el.includes("|") || el.includes("&") ? `(${el})[]` : `${el}[]`;
348
+ const el = next(args[0])
349
+ return el.includes("|") || el.includes("&") ? `(${el})[]` : `${el}[]`
377
350
  }
378
- return "unknown[]";
351
+ return "unknown[]"
379
352
  }
380
353
 
381
354
  /* tuple */
382
355
  if (checker.isTupleType(type)) {
383
- const args = checker.getTypeArguments(
384
- type as ts.TypeReference,
385
- );
386
- return `[${args.map(next).join(", ")}]`;
356
+ const args = checker.getTypeArguments(type as ts.TypeReference)
357
+ return `[${args.map(next).join(", ")}]`
387
358
  }
388
359
 
389
360
  /* function (pure callable, no data properties) */
390
- const sigs = type.getCallSignatures();
361
+ const sigs = type.getCallSignatures()
391
362
  if (sigs.length > 0 && type.getProperties().length === 0) {
392
- const sig = sigs[0];
363
+ const sig = sigs[0]
393
364
  const params = sig.getParameters().map((p) => {
394
- const pt = checker.getTypeOfSymbolAtLocation(
395
- p,
396
- p.valueDeclaration ?? node,
397
- );
398
- const decl = p.valueDeclaration;
365
+ const pt = checker.getTypeOfSymbolAtLocation(p, p.valueDeclaration ?? node)
366
+ const decl = p.valueDeclaration
399
367
  const isRest =
400
368
  decl !== undefined &&
401
369
  compiler.isParameter(decl) &&
402
- (decl as ts.ParameterDeclaration).dotDotDotToken !== undefined;
403
- return `${isRest ? "..." : ""}${p.getName()}: ${next(pt)}`;
404
- });
405
- const ret = checker.getReturnTypeOfSignature(sig);
406
- return `(${params.join(", ")}) => ${next(ret)}`;
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)}`
407
375
  }
408
376
 
409
377
  /* well-known globals — keep name, serialize type args */
410
- const sym = type.getSymbol();
378
+ const sym = type.getSymbol()
411
379
  if (sym) {
412
- const name = sym.getName();
380
+ const name = sym.getName()
413
381
  const globals = new Set([
414
382
  "AbortController",
415
383
  "AbortSignal",
@@ -435,72 +403,65 @@ function serialize(
435
403
  "WeakMap",
436
404
  "WeakSet",
437
405
  "WritableStream",
438
- ]);
406
+ ])
439
407
  if (globals.has(name)) {
440
- const ref = type as ts.TypeReference;
408
+ const ref = type as ts.TypeReference
441
409
  if (ref.typeArguments?.length) {
442
- return `${name}<${ref.typeArguments.map(next).join(", ")}>`;
410
+ return `${name}<${ref.typeArguments.map(next).join(", ")}>`
443
411
  }
444
- return name;
412
+ return name
445
413
  }
446
414
 
447
415
  /* named types — prefer import() reference over structural expansion */
448
416
  if (name && !name.startsWith("__") && name !== "Object") {
449
- const ref = serializeAsRef(type, checker, node, compiler, 0);
450
- if (ref) return ref;
417
+ const ref = serializeAsRef(type, checker, node, compiler, 0)
418
+ if (ref) return ref
451
419
 
452
420
  /* fallback: typeToString for .d.ts types without clean ref */
453
- const decls = sym.declarations;
421
+ const decls = sym.declarations
454
422
  if (decls?.length) {
455
- const sf = decls[0].getSourceFile();
423
+ const sf = decls[0].getSourceFile()
456
424
  if (sf.isDeclarationFile) {
457
425
  return checker.typeToString(
458
426
  type,
459
427
  node,
460
- compiler.TypeFormatFlags.UseFullyQualifiedType |
461
- compiler.TypeFormatFlags.NoTruncation,
462
- );
428
+ compiler.TypeFormatFlags.UseFullyQualifiedType | compiler.TypeFormatFlags.NoTruncation,
429
+ )
463
430
  }
464
431
  }
465
432
  }
466
433
  }
467
434
 
468
435
  /* Record<string, V> — index signature only */
469
- const stringIdx = type.getStringIndexType();
436
+ const stringIdx = type.getStringIndexType()
470
437
  if (stringIdx && type.getProperties().length === 0) {
471
- return `Record<string, ${next(stringIdx)}>`;
438
+ return `Record<string, ${next(stringIdx)}>`
472
439
  }
473
440
 
474
441
  /* object — expand to structural form */
475
- const props = type.getProperties();
442
+ const props = type.getProperties()
476
443
  if (props.length > 50) {
477
- return checker.typeToString(type, node, compiler.TypeFormatFlags.NoTruncation);
444
+ return checker.typeToString(type, node, compiler.TypeFormatFlags.NoTruncation)
478
445
  }
479
446
  if (props.length > 0) {
480
- const entries: string[] = [];
447
+ const entries: string[] = []
481
448
  for (const p of props) {
482
- const decls = p.getDeclarations();
449
+ const decls = p.getDeclarations()
483
450
  if (decls?.length) {
484
- const flags = compiler.getCombinedModifierFlags(decls[0]);
485
- if (flags & (compiler.ModifierFlags.Private | compiler.ModifierFlags.Protected))
486
- continue;
451
+ const flags = compiler.getCombinedModifierFlags(decls[0])
452
+ if (flags & (compiler.ModifierFlags.Private | compiler.ModifierFlags.Protected)) continue
487
453
  }
488
- const pt = checker.getTypeOfSymbolAtLocation(p, decls?.[0] ?? node);
489
- const opt = (p.flags & compiler.SymbolFlags.Optional) !== 0;
454
+ const pt = checker.getTypeOfSymbolAtLocation(p, decls?.[0] ?? node)
455
+ const opt = (p.flags & compiler.SymbolFlags.Optional) !== 0
490
456
  const ro =
491
- decls?.some(
492
- (d) =>
493
- (compiler.getCombinedModifierFlags(d) & compiler.ModifierFlags.Readonly) !== 0,
494
- ) ?? false;
495
- entries.push(
496
- `${ro ? "readonly " : ""}${p.getName()}${opt ? "?" : ""}: ${next(pt)}`,
497
- );
457
+ decls?.some((d) => (compiler.getCombinedModifierFlags(d) & compiler.ModifierFlags.Readonly) !== 0) ?? false
458
+ entries.push(`${ro ? "readonly " : ""}${p.getName()}${opt ? "?" : ""}: ${next(pt)}`)
498
459
  }
499
- if (entries.length > 0) return `{ ${entries.join("; ")} }`;
460
+ if (entries.length > 0) return `{ ${entries.join("; ")} }`
500
461
  }
501
462
 
502
463
  /* fallback */
503
- return checker.typeToString(type, undefined, compiler.TypeFormatFlags.NoTruncation);
464
+ return checker.typeToString(type, undefined, compiler.TypeFormatFlags.NoTruncation)
504
465
  }
505
466
 
506
467
  /**
@@ -511,35 +472,26 @@ function serialize(
511
472
  */
512
473
  function sanitizeImportPaths(str: string, outputDir?: string): string {
513
474
  return str.replace(/import\("([^"]+)"\)/g, (_match, filePath: string) => {
514
- const nmIdx = filePath.lastIndexOf("/node_modules/");
475
+ const nmIdx = filePath.lastIndexOf("/node_modules/")
515
476
  if (nmIdx !== -1) {
516
- return `import("${filePath.slice(nmIdx + "/node_modules/".length)}")`;
477
+ return `import("${filePath.slice(nmIdx + "/node_modules/".length)}")`
517
478
  }
518
- if (!outputDir) return `import("${filePath}")`;
519
- let rel = relative(outputDir, filePath);
520
- if (!rel.startsWith(".")) rel = `./${rel}`;
521
- return `import("${rel}")`;
522
- });
479
+ if (!outputDir) return `import("${filePath}")`
480
+ let rel = relative(outputDir, filePath)
481
+ if (!rel.startsWith(".")) rel = `./${rel}`
482
+ return `import("${rel}")`
483
+ })
523
484
  }
524
485
 
525
486
  export type ExtractedChainTypes = {
526
- base: ExtractedBaseCtx;
487
+ base: ExtractedBaseCtx
527
488
  /** per-route middleware additions keyed by "method /path" */
528
- routeMiddleware: Record<string, string>;
489
+ routeMiddleware: Record<string, string>
529
490
  /** structured per-property middleware data for sub-type dedup */
530
- routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>>;
531
- };
532
-
533
- const HTTP_METHODS = new Set([
534
- "all",
535
- "delete",
536
- "get",
537
- "head",
538
- "options",
539
- "patch",
540
- "post",
541
- "put",
542
- ]);
491
+ routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>>
492
+ }
493
+
494
+ const HTTP_METHODS = new Set(["all", "delete", "get", "head", "options", "patch", "post", "put"])
543
495
 
544
496
  /**
545
497
  * Extracts base ctx and per-route middleware additions from sub-chains.
@@ -548,134 +500,110 @@ const HTTP_METHODS = new Set([
548
500
  * and returns per-route additions keyed by "method /path".
549
501
  */
550
502
  export async function extractChainTypes(options: {
551
- entryPath: string;
552
- exportName: string;
553
- outputDir?: string;
554
- tsconfigPath?: string;
503
+ entryPath: string
504
+ exportName: string
505
+ outputDir?: string
506
+ tsconfigPath?: string
555
507
  }): Promise<ExtractedChainTypes> {
556
- const { Project, ts } = await import("ts-morph");
508
+ const { Project, ts } = await import("ts-morph")
557
509
 
558
- const tsConfigFilePath =
559
- options.tsconfigPath ?? findTsConfig(options.entryPath);
510
+ const tsConfigFilePath = options.tsconfigPath ?? findTsConfig(options.entryPath)
560
511
 
561
512
  const project = new Project({
562
513
  skipAddingFilesFromTsConfig: true,
563
514
  tsConfigFilePath,
564
- });
515
+ })
565
516
 
566
- project.addSourceFilesAtPaths(options.entryPath);
567
- project.resolveSourceFileDependencies();
517
+ project.addSourceFilesAtPaths(options.entryPath)
518
+ project.resolveSourceFileDependencies()
568
519
 
569
- const sourceFile = project.getSourceFileOrThrow(options.entryPath);
570
- const baseVarDecl = sourceFile.getVariableDeclaration(options.exportName);
520
+ const sourceFile = project.getSourceFileOrThrow(options.entryPath)
521
+ const baseVarDecl = sourceFile.getVariableDeclaration(options.exportName)
571
522
  if (!baseVarDecl) {
572
- throw new Error(
573
- `Export "${options.exportName}" not found in ${options.entryPath}`,
574
- );
523
+ throw new Error(`Export "${options.exportName}" not found in ${options.entryPath}`)
575
524
  }
576
525
 
577
526
  function bridge<T>(value: unknown): T {
578
- return value as T;
527
+ return value as T
579
528
  }
580
- const checker: ts.TypeChecker = bridge(
581
- project.getTypeChecker().compilerObject,
582
- );
583
- const baseNode: ts.Node = bridge(
584
- baseVarDecl.compilerNode,
585
- );
586
- const tsLib: typeof ts = bridge(ts);
587
-
588
- function getCtxType(
589
- tsType: ts.Type,
590
- locationNode?: ts.Node,
591
- ): ts.Type | null {
592
- const sym = tsType.getProperty("$ctx");
593
- if (!sym) return null;
594
- return checker.getTypeOfSymbolAtLocation(sym, locationNode ?? sym.valueDeclaration ?? baseNode);
529
+ const checker: ts.TypeChecker = bridge(project.getTypeChecker().compilerObject)
530
+ const baseNode: ts.Node = bridge(baseVarDecl.compilerNode)
531
+ const tsLib: typeof ts = bridge(ts)
532
+
533
+ function getCtxType(tsType: ts.Type, locationNode?: ts.Node): ts.Type | null {
534
+ const sym = tsType.getProperty("$ctx")
535
+ if (!sym) return null
536
+ return checker.getTypeOfSymbolAtLocation(sym, locationNode ?? sym.valueDeclaration ?? baseNode)
595
537
  }
596
538
 
597
539
  /* extract base ctx */
598
- const baseType = bridge<ts.Type>(
599
- baseVarDecl.getType().compilerType,
600
- );
601
- let envType = "Record<string, unknown>";
602
- const envSym = baseType.getProperty("$env");
540
+ const baseType = bridge<ts.Type>(baseVarDecl.getType().compilerType)
541
+ let envType = "Record<string, unknown>"
542
+ const envSym = baseType.getProperty("$env")
603
543
  if (envSym) {
604
- envType = serialize(
605
- checker.getTypeOfSymbolAtLocation(envSym, baseNode),
606
- checker,
607
- baseNode,
608
- tsLib,
609
- );
544
+ envType = serialize(checker.getTypeOfSymbolAtLocation(envSym, baseNode), checker, baseNode, tsLib)
610
545
  }
611
546
 
612
- const baseCtxType = getCtxType(baseType);
547
+ const baseCtxType = getCtxType(baseType)
613
548
  if (!baseCtxType) {
614
- throw new Error(
615
- `Export "${options.exportName}" has no $ctx — not a Honey instance`,
616
- );
549
+ throw new Error(`Export "${options.exportName}" has no $ctx — not a Honey instance`)
617
550
  }
618
- const baseMwProps = extractMiddlewareProps(baseCtxType, checker, baseNode, tsLib);
619
- const baseMiddlewareType = baseMwProps.length === 0 ? null
620
- : `{ ${baseMwProps.map((p) => `${p.name}${p.opt ? "?" : ""}: ${p.type}`).join("; ")} }`;
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("; ")} }`
621
556
 
622
557
  /* build name→type map for reusing base type references in route middleware */
623
- const basePropTypeByName = new Map<string, string>();
558
+ const basePropTypeByName = new Map<string, string>()
624
559
  for (const p of baseMwProps) {
625
- basePropTypeByName.set(p.name, p.type);
560
+ basePropTypeByName.set(p.name, p.type)
626
561
  }
627
562
 
628
563
  /* collect base middleware property names for diffing */
629
- const baseMwPropNames = new Set<string>();
564
+ const baseMwPropNames = new Set<string>()
630
565
  for (const p of baseCtxType.getProperties()) {
631
- const name = p.getName();
566
+ const name = p.getName()
632
567
  if (!HONEY_CTX_PROPS.has(name) && !name.startsWith("_")) {
633
- baseMwPropNames.add(name);
568
+ baseMwPropNames.add(name)
634
569
  }
635
570
  }
636
571
 
637
- const sanitize = (s: string) => sanitizeImportPaths(s, options.outputDir);
572
+ const sanitize = (s: string) => sanitizeImportPaths(s, options.outputDir)
638
573
 
639
574
  /* scan for route-defining calls: <expr>.<httpMethod>(<pathLiteral>) */
640
- const routeMiddleware: Record<string, string> = {};
641
- const routeMiddlewareProps: Record<string, Array<{ name: string; opt: boolean; type: string }>> = {};
642
- const compilerSourceFile: ts.SourceFile = bridge(
643
- sourceFile.compilerNode,
644
- );
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)
645
578
 
646
579
  function visitNode(node: ts.Node): void {
647
- if (
648
- tsLib.isCallExpression(node) &&
649
- tsLib.isPropertyAccessExpression(node.expression)
650
- ) {
651
- const methodName = node.expression.name.text;
580
+ if (tsLib.isCallExpression(node) && tsLib.isPropertyAccessExpression(node.expression)) {
581
+ const methodName = node.expression.name.text
652
582
  if (HTTP_METHODS.has(methodName) && node.arguments.length > 0) {
653
- const pathArg = node.arguments[0];
583
+ const pathArg = node.arguments[0]
654
584
  if (tsLib.isStringLiteral(pathArg)) {
655
- const path = pathArg.text;
585
+ const path = pathArg.text
656
586
  /* get the type of the receiver (the expression before .get/.post/etc.) */
657
- const receiverExpr = node.expression.expression;
658
- const receiverType = checker.getTypeAtLocation(receiverExpr);
659
- const ctxType = getCtxType(receiverType, receiverExpr);
587
+ const receiverExpr = node.expression.expression
588
+ const receiverType = checker.getTypeAtLocation(receiverExpr)
589
+ const ctxType = getCtxType(receiverType, receiverExpr)
660
590
 
661
591
  if (ctxType) {
662
592
  /* extract extra middleware props by iterating intersection MEMBERS individually.
663
593
  * This preserves source-level declarations (e.g. ShorthandPropertyAssignment)
664
594
  * that get lost when extracting from the flattened intersection. */
665
- const extraEntries: MwPropEntry[] = [];
666
- const dupeNames = new Set<string>();
667
- const members = ctxType.isIntersection()
668
- ? (ctxType as ts.IntersectionType).types
669
- : [ctxType];
595
+ const extraEntries: MwPropEntry[] = []
596
+ const dupeNames = new Set<string>()
597
+ const members = ctxType.isIntersection() ? (ctxType as ts.IntersectionType).types : [ctxType]
670
598
  for (const member of members) {
671
- const memberProps = extractMiddlewareProps(member, checker, baseNode, tsLib);
599
+ const memberProps = extractMiddlewareProps(member, checker, baseNode, tsLib)
672
600
  for (const rp of memberProps) {
673
- if (baseMwPropNames.has(rp.name)) continue;
601
+ if (baseMwPropNames.has(rp.name)) continue
674
602
  if (extraEntries.some((e) => e.name === rp.name)) {
675
- dupeNames.add(rp.name);
676
- continue;
603
+ dupeNames.add(rp.name)
604
+ continue
677
605
  }
678
- extraEntries.push(rp);
606
+ extraEntries.push(rp)
679
607
  }
680
608
  }
681
609
  /* for properties that appeared in multiple intersection members
@@ -683,13 +611,13 @@ export async function extractChainTypes(options: {
683
611
  * flattened intersection so TypeScript computes the correct
684
612
  * intersected type instead of using the first member's type */
685
613
  for (const dupeName of dupeNames) {
686
- const sym = ctxType.getProperty(dupeName);
687
- if (!sym) continue;
688
- const t = checker.getTypeOfSymbolAtLocation(sym, sym.valueDeclaration ?? baseNode);
689
- const typeStr = serializeAsRef(t, checker, baseNode, tsLib) ?? serialize(t, checker, baseNode, tsLib);
690
- const idx = extraEntries.findIndex((e) => e.name === dupeName);
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)
691
619
  if (idx !== -1) {
692
- extraEntries[idx] = { ...extraEntries[idx], type: typeStr };
620
+ extraEntries[idx] = { ...extraEntries[idx], type: typeStr }
693
621
  }
694
622
  }
695
623
 
@@ -702,48 +630,46 @@ export async function extractChainTypes(options: {
702
630
  return { ...e, type: baseRef }
703
631
  }
704
632
  return e
705
- });
633
+ })
706
634
 
707
- const key = `${methodName} ${path}`;
635
+ const key = `${methodName} ${path}`
708
636
  routeMiddleware[key] = sanitize(
709
637
  `{ ${entries.map((e) => `${e.name}${e.opt ? "?" : ""}: ${e.type}`).join("; ")} }`,
710
- );
638
+ )
711
639
  routeMiddlewareProps[key] = entries.map((e) => ({
712
640
  name: e.name,
713
641
  opt: e.opt,
714
642
  type: sanitize(e.type),
715
- }));
643
+ }))
716
644
  }
717
645
  }
718
646
  }
719
647
  }
720
648
  }
721
649
 
722
- tsLib.forEachChild(node, visitNode);
650
+ tsLib.forEachChild(node, visitNode)
723
651
  }
724
652
 
725
- visitNode(compilerSourceFile);
653
+ visitNode(compilerSourceFile)
726
654
 
727
655
  /* extract $taps */
728
- let tapsType: string | null = null;
729
- const tapsSym = baseType.getProperty("$taps");
656
+ let tapsType: string | null = null
657
+ const tapsSym = baseType.getProperty("$taps")
730
658
  if (tapsSym) {
731
- const tapsT = checker.getTypeOfSymbolAtLocation(tapsSym, baseNode);
732
- const tapsStr = serialize(tapsT, checker, baseNode, tsLib);
659
+ const tapsT = checker.getTypeOfSymbolAtLocation(tapsSym, baseNode)
660
+ const tapsStr = serialize(tapsT, checker, baseNode, tsLib)
733
661
  if (tapsStr !== "{}" && tapsStr !== "Record<string, unknown>") {
734
- tapsType = tapsStr;
662
+ tapsType = tapsStr
735
663
  }
736
664
  }
737
665
 
738
666
  return {
739
667
  base: {
740
668
  envType: sanitize(envType),
741
- middlewareType: baseMiddlewareType
742
- ? sanitize(baseMiddlewareType)
743
- : null,
669
+ middlewareType: baseMiddlewareType ? sanitize(baseMiddlewareType) : null,
744
670
  tapsType: tapsType ? sanitize(tapsType) : null,
745
671
  },
746
672
  routeMiddleware,
747
673
  routeMiddlewareProps,
748
- };
674
+ }
749
675
  }