seer-mcp 0.1.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 (371) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/LICENSE +176 -0
  3. package/README.md +272 -0
  4. package/README_dev.md +199 -0
  5. package/dist/bundle/ci.d.ts +47 -0
  6. package/dist/bundle/ci.d.ts.map +1 -0
  7. package/dist/bundle/ci.js +113 -0
  8. package/dist/bundle/ci.js.map +1 -0
  9. package/dist/bundle/contract.d.ts +111 -0
  10. package/dist/bundle/contract.d.ts.map +1 -0
  11. package/dist/bundle/contract.js +352 -0
  12. package/dist/bundle/contract.js.map +1 -0
  13. package/dist/bundle/export.d.ts +36 -0
  14. package/dist/bundle/export.d.ts.map +1 -0
  15. package/dist/bundle/export.js +152 -0
  16. package/dist/bundle/export.js.map +1 -0
  17. package/dist/bundle/external.d.ts +66 -0
  18. package/dist/bundle/external.d.ts.map +1 -0
  19. package/dist/bundle/external.js +238 -0
  20. package/dist/bundle/external.js.map +1 -0
  21. package/dist/bundle/format.d.ts +94 -0
  22. package/dist/bundle/format.d.ts.map +1 -0
  23. package/dist/bundle/format.js +42 -0
  24. package/dist/bundle/format.js.map +1 -0
  25. package/dist/bundle/import.d.ts +49 -0
  26. package/dist/bundle/import.d.ts.map +1 -0
  27. package/dist/bundle/import.js +116 -0
  28. package/dist/bundle/import.js.map +1 -0
  29. package/dist/cli/index.d.ts +3 -0
  30. package/dist/cli/index.d.ts.map +1 -0
  31. package/dist/cli/index.js +1402 -0
  32. package/dist/cli/index.js.map +1 -0
  33. package/dist/cli/init.d.ts +48 -0
  34. package/dist/cli/init.d.ts.map +1 -0
  35. package/dist/cli/init.js +284 -0
  36. package/dist/cli/init.js.map +1 -0
  37. package/dist/db/schema.d.ts +3 -0
  38. package/dist/db/schema.d.ts.map +1 -0
  39. package/dist/db/schema.js +616 -0
  40. package/dist/db/schema.js.map +1 -0
  41. package/dist/db/store.d.ts +1011 -0
  42. package/dist/db/store.d.ts.map +1 -0
  43. package/dist/db/store.js +3888 -0
  44. package/dist/db/store.js.map +1 -0
  45. package/dist/graph/pagerank.d.ts +9 -0
  46. package/dist/graph/pagerank.d.ts.map +1 -0
  47. package/dist/graph/pagerank.js +47 -0
  48. package/dist/graph/pagerank.js.map +1 -0
  49. package/dist/indexer/architecture.d.ts +72 -0
  50. package/dist/indexer/architecture.d.ts.map +1 -0
  51. package/dist/indexer/architecture.js +112 -0
  52. package/dist/indexer/architecture.js.map +1 -0
  53. package/dist/indexer/behavior.d.ts +75 -0
  54. package/dist/indexer/behavior.d.ts.map +1 -0
  55. package/dist/indexer/behavior.js +395 -0
  56. package/dist/indexer/behavior.js.map +1 -0
  57. package/dist/indexer/boundaries.d.ts +60 -0
  58. package/dist/indexer/boundaries.d.ts.map +1 -0
  59. package/dist/indexer/boundaries.js +366 -0
  60. package/dist/indexer/boundaries.js.map +1 -0
  61. package/dist/indexer/churn.d.ts +15 -0
  62. package/dist/indexer/churn.d.ts.map +1 -0
  63. package/dist/indexer/churn.js +49 -0
  64. package/dist/indexer/churn.js.map +1 -0
  65. package/dist/indexer/classify.d.ts +9 -0
  66. package/dist/indexer/classify.d.ts.map +1 -0
  67. package/dist/indexer/classify.js +90 -0
  68. package/dist/indexer/classify.js.map +1 -0
  69. package/dist/indexer/context.d.ts +176 -0
  70. package/dist/indexer/context.d.ts.map +1 -0
  71. package/dist/indexer/context.js +193 -0
  72. package/dist/indexer/context.js.map +1 -0
  73. package/dist/indexer/continuity.d.ts +67 -0
  74. package/dist/indexer/continuity.d.ts.map +1 -0
  75. package/dist/indexer/continuity.js +288 -0
  76. package/dist/indexer/continuity.js.map +1 -0
  77. package/dist/indexer/detectchanges.d.ts +32 -0
  78. package/dist/indexer/detectchanges.d.ts.map +1 -0
  79. package/dist/indexer/detectchanges.js +74 -0
  80. package/dist/indexer/detectchanges.js.map +1 -0
  81. package/dist/indexer/discovery.d.ts +37 -0
  82. package/dist/indexer/discovery.d.ts.map +1 -0
  83. package/dist/indexer/discovery.js +136 -0
  84. package/dist/indexer/discovery.js.map +1 -0
  85. package/dist/indexer/externaldeps.d.ts +18 -0
  86. package/dist/indexer/externaldeps.d.ts.map +1 -0
  87. package/dist/indexer/externaldeps.js +288 -0
  88. package/dist/indexer/externaldeps.js.map +1 -0
  89. package/dist/indexer/freshness.d.ts +48 -0
  90. package/dist/indexer/freshness.d.ts.map +1 -0
  91. package/dist/indexer/freshness.js +128 -0
  92. package/dist/indexer/freshness.js.map +1 -0
  93. package/dist/indexer/git.d.ts +144 -0
  94. package/dist/indexer/git.d.ts.map +1 -0
  95. package/dist/indexer/git.js +444 -0
  96. package/dist/indexer/git.js.map +1 -0
  97. package/dist/indexer/index.d.ts +145 -0
  98. package/dist/indexer/index.d.ts.map +1 -0
  99. package/dist/indexer/index.js +930 -0
  100. package/dist/indexer/index.js.map +1 -0
  101. package/dist/indexer/modules.d.ts +62 -0
  102. package/dist/indexer/modules.d.ts.map +1 -0
  103. package/dist/indexer/modules.js +293 -0
  104. package/dist/indexer/modules.js.map +1 -0
  105. package/dist/indexer/preflight.d.ts +154 -0
  106. package/dist/indexer/preflight.d.ts.map +1 -0
  107. package/dist/indexer/preflight.js +399 -0
  108. package/dist/indexer/preflight.js.map +1 -0
  109. package/dist/indexer/protoScanner.d.ts +34 -0
  110. package/dist/indexer/protoScanner.d.ts.map +1 -0
  111. package/dist/indexer/protoScanner.js +133 -0
  112. package/dist/indexer/protoScanner.js.map +1 -0
  113. package/dist/indexer/risk.d.ts +115 -0
  114. package/dist/indexer/risk.d.ts.map +1 -0
  115. package/dist/indexer/risk.js +194 -0
  116. package/dist/indexer/risk.js.map +1 -0
  117. package/dist/indexer/serviceHostScanner.d.ts +25 -0
  118. package/dist/indexer/serviceHostScanner.d.ts.map +1 -0
  119. package/dist/indexer/serviceHostScanner.js +95 -0
  120. package/dist/indexer/serviceHostScanner.js.map +1 -0
  121. package/dist/indexer/serviceLinks.d.ts +105 -0
  122. package/dist/indexer/serviceLinks.d.ts.map +1 -0
  123. package/dist/indexer/serviceLinks.js +509 -0
  124. package/dist/indexer/serviceLinks.js.map +1 -0
  125. package/dist/indexer/shapehash.d.ts +98 -0
  126. package/dist/indexer/shapehash.d.ts.map +1 -0
  127. package/dist/indexer/shapehash.js +354 -0
  128. package/dist/indexer/shapehash.js.map +1 -0
  129. package/dist/indexer/skeleton.d.ts +15 -0
  130. package/dist/indexer/skeleton.d.ts.map +1 -0
  131. package/dist/indexer/skeleton.js +136 -0
  132. package/dist/indexer/skeleton.js.map +1 -0
  133. package/dist/indexer/symbolhistory.d.ts +41 -0
  134. package/dist/indexer/symbolhistory.d.ts.map +1 -0
  135. package/dist/indexer/symbolhistory.js +124 -0
  136. package/dist/indexer/symbolhistory.js.map +1 -0
  137. package/dist/indexer/watcher.d.ts +68 -0
  138. package/dist/indexer/watcher.d.ts.map +1 -0
  139. package/dist/indexer/watcher.js +179 -0
  140. package/dist/indexer/watcher.js.map +1 -0
  141. package/dist/mcp/server.d.ts +80 -0
  142. package/dist/mcp/server.d.ts.map +1 -0
  143. package/dist/mcp/server.js +1610 -0
  144. package/dist/mcp/server.js.map +1 -0
  145. package/dist/parser/index.d.ts +8 -0
  146. package/dist/parser/index.d.ts.map +1 -0
  147. package/dist/parser/index.js +33 -0
  148. package/dist/parser/index.js.map +1 -0
  149. package/dist/parser/languages/cpp.d.ts +3 -0
  150. package/dist/parser/languages/cpp.d.ts.map +1 -0
  151. package/dist/parser/languages/cpp.js +350 -0
  152. package/dist/parser/languages/cpp.js.map +1 -0
  153. package/dist/parser/languages/csharp.d.ts +3 -0
  154. package/dist/parser/languages/csharp.d.ts.map +1 -0
  155. package/dist/parser/languages/csharp.js +239 -0
  156. package/dist/parser/languages/csharp.js.map +1 -0
  157. package/dist/parser/languages/go.d.ts +3 -0
  158. package/dist/parser/languages/go.d.ts.map +1 -0
  159. package/dist/parser/languages/go.js +259 -0
  160. package/dist/parser/languages/go.js.map +1 -0
  161. package/dist/parser/languages/java.d.ts +3 -0
  162. package/dist/parser/languages/java.d.ts.map +1 -0
  163. package/dist/parser/languages/java.js +391 -0
  164. package/dist/parser/languages/java.js.map +1 -0
  165. package/dist/parser/languages/python.d.ts +3 -0
  166. package/dist/parser/languages/python.d.ts.map +1 -0
  167. package/dist/parser/languages/python.js +396 -0
  168. package/dist/parser/languages/python.js.map +1 -0
  169. package/dist/parser/languages/rust.d.ts +3 -0
  170. package/dist/parser/languages/rust.d.ts.map +1 -0
  171. package/dist/parser/languages/rust.js +159 -0
  172. package/dist/parser/languages/rust.js.map +1 -0
  173. package/dist/parser/languages/typescript.d.ts +3 -0
  174. package/dist/parser/languages/typescript.d.ts.map +1 -0
  175. package/dist/parser/languages/typescript.js +1442 -0
  176. package/dist/parser/languages/typescript.js.map +1 -0
  177. package/dist/parser/parserContext.d.ts +77 -0
  178. package/dist/parser/parserContext.d.ts.map +1 -0
  179. package/dist/parser/parserContext.js +354 -0
  180. package/dist/parser/parserContext.js.map +1 -0
  181. package/dist/parser/walker.d.ts +81 -0
  182. package/dist/parser/walker.d.ts.map +1 -0
  183. package/dist/parser/walker.js +217 -0
  184. package/dist/parser/walker.js.map +1 -0
  185. package/dist/parser/worker.d.ts +66 -0
  186. package/dist/parser/worker.d.ts.map +1 -0
  187. package/dist/parser/worker.js +129 -0
  188. package/dist/parser/worker.js.map +1 -0
  189. package/dist/parser/workerpool.d.ts +107 -0
  190. package/dist/parser/workerpool.d.ts.map +1 -0
  191. package/dist/parser/workerpool.js +383 -0
  192. package/dist/parser/workerpool.js.map +1 -0
  193. package/dist/scip/format.d.ts +87 -0
  194. package/dist/scip/format.d.ts.map +1 -0
  195. package/dist/scip/format.js +31 -0
  196. package/dist/scip/format.js.map +1 -0
  197. package/dist/scip/import.d.ts +37 -0
  198. package/dist/scip/import.d.ts.map +1 -0
  199. package/dist/scip/import.js +180 -0
  200. package/dist/scip/import.js.map +1 -0
  201. package/dist/types.d.ts +392 -0
  202. package/dist/types.d.ts.map +1 -0
  203. package/dist/types.js +4 -0
  204. package/dist/types.js.map +1 -0
  205. package/docs/architecture.md +105 -0
  206. package/docs/benchmarks/methodology.md +134 -0
  207. package/docs/benchmarks/raw-results.md +71 -0
  208. package/docs/benchmarks.md +74 -0
  209. package/docs/cli.md +148 -0
  210. package/docs/examples/behavior-tests.md +70 -0
  211. package/docs/examples/change-history.md +85 -0
  212. package/docs/examples/pre-edit-context.md +81 -0
  213. package/docs/examples/service-links.md +88 -0
  214. package/docs/examples.md +80 -0
  215. package/docs/faq.md +70 -0
  216. package/docs/internals.md +104 -0
  217. package/docs/languages.md +70 -0
  218. package/docs/limits.md +52 -0
  219. package/docs/mcp.md +199 -0
  220. package/docs/quickstart.md +119 -0
  221. package/docs/testing.md +123 -0
  222. package/docs/tools.md +115 -0
  223. package/package.json +52 -0
  224. package/research-codebase.md +578 -0
  225. package/seer-cli-docs.md +326 -0
  226. package/seer-master-guide.md +246 -0
  227. package/src/bundle/ci.ts +141 -0
  228. package/src/bundle/contract.ts +387 -0
  229. package/src/bundle/export.ts +175 -0
  230. package/src/bundle/external.ts +285 -0
  231. package/src/bundle/format.ts +92 -0
  232. package/src/bundle/import.ts +157 -0
  233. package/src/cli/index.ts +1249 -0
  234. package/src/cli/init.ts +389 -0
  235. package/src/db/schema.ts +614 -0
  236. package/src/db/store.ts +4306 -0
  237. package/src/graph/pagerank.ts +53 -0
  238. package/src/indexer/architecture.ts +148 -0
  239. package/src/indexer/behavior.ts +466 -0
  240. package/src/indexer/boundaries.ts +374 -0
  241. package/src/indexer/churn.ts +58 -0
  242. package/src/indexer/classify.ts +96 -0
  243. package/src/indexer/context.ts +340 -0
  244. package/src/indexer/continuity.ts +322 -0
  245. package/src/indexer/detectchanges.ts +94 -0
  246. package/src/indexer/discovery.ts +176 -0
  247. package/src/indexer/externaldeps.ts +243 -0
  248. package/src/indexer/freshness.ts +166 -0
  249. package/src/indexer/git.ts +453 -0
  250. package/src/indexer/index.ts +1092 -0
  251. package/src/indexer/modules.ts +358 -0
  252. package/src/indexer/preflight.ts +548 -0
  253. package/src/indexer/protoScanner.ts +147 -0
  254. package/src/indexer/risk.ts +304 -0
  255. package/src/indexer/serviceHostScanner.ts +92 -0
  256. package/src/indexer/serviceLinks.ts +543 -0
  257. package/src/indexer/shapehash.ts +370 -0
  258. package/src/indexer/skeleton.ts +169 -0
  259. package/src/indexer/symbolhistory.ts +172 -0
  260. package/src/indexer/watcher.ts +206 -0
  261. package/src/mcp/server.ts +1659 -0
  262. package/src/parser/index.ts +37 -0
  263. package/src/parser/languages/cpp.ts +361 -0
  264. package/src/parser/languages/csharp.ts +235 -0
  265. package/src/parser/languages/go.ts +259 -0
  266. package/src/parser/languages/java.ts +382 -0
  267. package/src/parser/languages/python.ts +370 -0
  268. package/src/parser/languages/rust.ts +164 -0
  269. package/src/parser/languages/typescript.ts +1435 -0
  270. package/src/parser/parserContext.ts +392 -0
  271. package/src/parser/walker.ts +306 -0
  272. package/src/parser/worker.ts +181 -0
  273. package/src/parser/workerpool.ts +448 -0
  274. package/src/scip/format.ts +83 -0
  275. package/src/scip/import.ts +216 -0
  276. package/src/types.ts +457 -0
  277. package/tests/benchmark-service-links.ts +244 -0
  278. package/tests/bug-regressions.ts +626 -0
  279. package/tests/filters.ts +264 -0
  280. package/tests/fixtures/Counter.tsx +38 -0
  281. package/tests/fixtures/caller.ts +7 -0
  282. package/tests/fixtures/collisions.ts +23 -0
  283. package/tests/fixtures/local_helper.ts +5 -0
  284. package/tests/fixtures/overloads.java +17 -0
  285. package/tests/fixtures/remote_helper.ts +4 -0
  286. package/tests/fixtures/sample.c +15 -0
  287. package/tests/fixtures/sample.cpp +47 -0
  288. package/tests/fixtures/sample.cs +62 -0
  289. package/tests/fixtures/sample.go +68 -0
  290. package/tests/fixtures/sample.h +30 -0
  291. package/tests/fixtures/sample.java +85 -0
  292. package/tests/fixtures/sample.py +46 -0
  293. package/tests/fixtures/sample.rs +78 -0
  294. package/tests/fixtures/sample.ts +76 -0
  295. package/tests/fixtures-service/HttpClients.cs +30 -0
  296. package/tests/fixtures-service/HttpClients.java +24 -0
  297. package/tests/fixtures-service/billing.ts +15 -0
  298. package/tests/fixtures-service/docker-compose.yml +15 -0
  299. package/tests/fixtures-service/gateway.ts +10 -0
  300. package/tests/fixtures-service/get_user.ts +11 -0
  301. package/tests/fixtures-service/graphql_client.ts +63 -0
  302. package/tests/fixtures-service/graphql_server.ts +30 -0
  303. package/tests/fixtures-service/grpc_client.go +30 -0
  304. package/tests/fixtures-service/http_clients.go +23 -0
  305. package/tests/fixtures-service/http_clients.py +38 -0
  306. package/tests/fixtures-service/http_clients.ts +49 -0
  307. package/tests/fixtures-service/k8s/payment-service.yaml +22 -0
  308. package/tests/fixtures-service/k8s_calls.ts +20 -0
  309. package/tests/fixtures-service/messaging.ts +87 -0
  310. package/tests/fixtures-service/trpc_client.ts +39 -0
  311. package/tests/fixtures-service/trpc_server.ts +39 -0
  312. package/tests/fixtures-service/user_service.proto +33 -0
  313. package/tests/fixtures-trackcd/Cargo.toml +11 -0
  314. package/tests/fixtures-trackcd/SpringController.java +36 -0
  315. package/tests/fixtures-trackcd/auth_service.ts +19 -0
  316. package/tests/fixtures-trackcd/complex_module.py +50 -0
  317. package/tests/fixtures-trackcd/express_app.js +30 -0
  318. package/tests/fixtures-trackcd/fastapi_app.py +49 -0
  319. package/tests/fixtures-trackcd/fastify_object_routes.js +32 -0
  320. package/tests/fixtures-trackcd/go.mod +8 -0
  321. package/tests/fixtures-trackcd/package.json +15 -0
  322. package/tests/fixtures-trackcd/requirements.txt +4 -0
  323. package/tests/fixtures-trackcd/tests/auth_service.test.ts +13 -0
  324. package/tests/fixtures-tracke/auth/AuthService.ts +23 -0
  325. package/tests/fixtures-tracke/auth/crypto.ts +7 -0
  326. package/tests/fixtures-tracke/billing/Billing.ts +20 -0
  327. package/tests/fixtures-tracke/billing/Invoice.ts +10 -0
  328. package/tests/fixtures-tracke/billing/server.ts +17 -0
  329. package/tests/fixtures-tracke/package.json +7 -0
  330. package/tests/fixtures-tracke/tests/auth.test.ts +23 -0
  331. package/tests/fixtures-tracke/tests/billing.test.ts +14 -0
  332. package/tests/fixtures-trackf/package.json +5 -0
  333. package/tests/fixtures-trackf/src/auth.ts +26 -0
  334. package/tests/fixtures-trackf/src/handlers.ts +35 -0
  335. package/tests/fixtures-tracki/billing/routes.ts +12 -0
  336. package/tests/fixtures-tracki/gateway/client.ts +13 -0
  337. package/tests/git-features.ts +267 -0
  338. package/tests/init.ts +141 -0
  339. package/tests/mcp-jit.ts +130 -0
  340. package/tests/mcp-smoke.ts +191 -0
  341. package/tests/mcp-trackcd.ts +169 -0
  342. package/tests/mcp-tracke.ts +229 -0
  343. package/tests/mcp-trackf.ts +330 -0
  344. package/tests/mcp-trackg.ts +219 -0
  345. package/tests/mcp-tracki.ts +174 -0
  346. package/tests/mcp-watcher.ts +126 -0
  347. package/tests/optspec.ts +194 -0
  348. package/tests/parallel-index.ts +333 -0
  349. package/tests/parallel-read.ts +125 -0
  350. package/tests/parallel-recovery.ts +241 -0
  351. package/tests/perf-callers.ts +145 -0
  352. package/tests/query-parity.ts +184 -0
  353. package/tests/query-perf.ts +55 -0
  354. package/tests/scale-parallel-parity.ts +225 -0
  355. package/tests/scale-test.ts +523 -0
  356. package/tests/smoke.ts +396 -0
  357. package/tests/trackcd.ts +325 -0
  358. package/tests/tracke-collisions.ts +255 -0
  359. package/tests/tracke.ts +314 -0
  360. package/tests/trackf-bugs.ts +406 -0
  361. package/tests/trackf.ts +390 -0
  362. package/tests/trackg.ts +1372 -0
  363. package/tests/tracki-boundaries.ts +202 -0
  364. package/tests/tracki-continuity.ts +253 -0
  365. package/tests/tracki-contract-diff.ts +249 -0
  366. package/tests/tracki-external-bundles.ts +341 -0
  367. package/tests/tracki-preflight.ts +251 -0
  368. package/tests/verify-roles.ts +51 -0
  369. package/tests/worker-parity.ts +286 -0
  370. package/tests/worker-pool.ts +262 -0
  371. package/tsconfig.json +20 -0
@@ -0,0 +1,1442 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.typescriptExtractor = void 0;
4
+ const walker_js_1 = require("../walker.js");
5
+ // Branch nodes for cyclomatic / cognitive complexity. tree-sitter-typescript
6
+ // shares its node grammar with tree-sitter-javascript and tree-sitter-tsx,
7
+ // so this set covers all three.
8
+ const TS_BRANCH_NODES = new Set([
9
+ 'if_statement',
10
+ 'switch_case',
11
+ 'switch_default',
12
+ 'while_statement',
13
+ 'do_statement',
14
+ 'for_statement',
15
+ 'for_in_statement',
16
+ 'for_of_statement',
17
+ 'catch_clause',
18
+ 'ternary_expression',
19
+ 'conditional_expression',
20
+ ]);
21
+ const TS_NESTING_NODES = new Set([
22
+ 'if_statement',
23
+ 'switch_statement',
24
+ 'while_statement',
25
+ 'do_statement',
26
+ 'for_statement',
27
+ 'for_in_statement',
28
+ 'for_of_statement',
29
+ 'catch_clause',
30
+ ]);
31
+ // HTTP method names used by Express/Fastify-style routers. Lower-cased because
32
+ // we compare against `member_expression.property` text.
33
+ const HTTP_METHODS = new Set([
34
+ 'get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'all', 'use',
35
+ ]);
36
+ // HTTP CLIENT method names (axios/fetch-style outbound calls). Subset of
37
+ // HTTP_METHODS because clients don't expose all/use.
38
+ const HTTP_CLIENT_METHODS = new Set([
39
+ 'get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'request',
40
+ ]);
41
+ // Receiver names that strongly indicate a route REGISTRATION (Express/
42
+ // Fastify). Used to distinguish `app.get('/x', handler)` (route) from
43
+ // `axios.get('/x')` (client call). Lowercase + capitalised variants.
44
+ const ROUTER_RECEIVER_NAMES = new Set([
45
+ 'app', 'router', 'Router', 'server', 'fastify', 'expressApp', 'expressRouter',
46
+ 'api', 'apiRouter',
47
+ ]);
48
+ // v9 Track-H — tRPC. Server-side procedure terminals (procedure.query(handler))
49
+ // and client-side call terminals (trpc.user.getById.query()). Server names are
50
+ // the ones that mark a node as a procedure DEFINITION; client names are how a
51
+ // call rendezvous through the tRPC proxy.
52
+ const TRPC_PROCEDURE_METHODS = new Set(['query', 'mutation', 'subscription']);
53
+ // Common tRPC procedure-builder identifiers. Hitting any of these in the
54
+ // receiver chain of a procedure.query(...) is what proves "this is a tRPC
55
+ // procedure definition" (vs. some other library that happens to expose a
56
+ // .query method).
57
+ const TRPC_PROCEDURE_BASES = new Set([
58
+ 'procedure', 'publicProcedure', 'protectedProcedure',
59
+ 'authedProcedure', 'adminProcedure', 'baseProcedure', 'loggedProcedure',
60
+ ]);
61
+ // Terminal client methods on the tRPC proxy. Maps each to its operation kind
62
+ // so query/useQuery both become "query", mutate/useMutation become "mutation",
63
+ // subscribe/useSubscription become "subscription".
64
+ const TRPC_CLIENT_METHODS = new Map([
65
+ ['query', 'query'],
66
+ ['mutate', 'mutation'],
67
+ ['useQuery', 'query'],
68
+ ['useMutation', 'mutation'],
69
+ ['useInfiniteQuery', 'query'],
70
+ ['useSuspenseQuery', 'query'],
71
+ ['useSubscription', 'subscription'],
72
+ ['subscribe', 'subscription'],
73
+ ]);
74
+ // Root receiver names that mark a member-chain as flowing through the tRPC
75
+ // client proxy. Anything ending in 'trpc' (case-insensitive) is accepted; we
76
+ // also allow bare 'api' / 'client' / 'rpc' since those are the other common
77
+ // proxy variable names in the wild.
78
+ const TRPC_CLIENT_ROOTS = new Set(['trpc', 'api', 'client', 'rpc']);
79
+ function isTrpcClientRoot(name) {
80
+ if (!name)
81
+ return false;
82
+ if (TRPC_CLIENT_ROOTS.has(name))
83
+ return true;
84
+ const lower = name.toLowerCase();
85
+ return lower.startsWith('trpc') || lower.endsWith('trpc');
86
+ }
87
+ // v9 Track-H — GraphQL.
88
+ //
89
+ // CLIENT METHODS — Apollo/urql/relay-style call terminals. Each maps to an
90
+ // operation kind. `useQuery` / `useMutation` / `useSubscription` are React-
91
+ // hook variants that take the document as their first arg.
92
+ const GRAPHQL_CLIENT_METHODS = new Map([
93
+ ['query', 'query'],
94
+ ['mutate', 'mutation'],
95
+ ['mutation', 'mutation'],
96
+ ['subscribe', 'subscription'],
97
+ ['useQuery', 'query'],
98
+ ['useMutation', 'mutation'],
99
+ ['useSubscription', 'subscription'],
100
+ ['useLazyQuery', 'query'],
101
+ ]);
102
+ // Server-side resolver-map top-level keys. Anything nested under one of these
103
+ // is a resolver. We emit one route per resolver with operation = field name
104
+ // and method = the parent kind.
105
+ const GRAPHQL_RESOLVER_KEYS = new Set(['Query', 'Mutation', 'Subscription']);
106
+ // Receiver-name hints that boost confidence we're looking at the right lib.
107
+ const MSG_RECV_HINTS = {
108
+ kafka: ['producer', 'kafkaProducer', 'consumer', 'kafkaConsumer', 'kafka'],
109
+ sqs: ['sqs', 'sqsClient'],
110
+ sns: ['sns', 'snsClient'],
111
+ rabbitmq: ['channel', 'amqp', 'rabbit', 'rabbitmq', 'ch'],
112
+ nats: ['nc', 'nats', 'natsClient', 'jetstream'],
113
+ redis_pubsub: ['redis', 'redisClient', 'pubsub', 'publisher', 'subscriber'],
114
+ };
115
+ function receiverHintsProtocol(recvName, protocol) {
116
+ if (!recvName)
117
+ return false;
118
+ const hints = MSG_RECV_HINTS[protocol] ?? [];
119
+ if (hints.includes(recvName))
120
+ return true;
121
+ const lower = recvName.toLowerCase();
122
+ for (const h of hints)
123
+ if (lower.includes(h.toLowerCase()))
124
+ return true;
125
+ return false;
126
+ }
127
+ // Union of every node type any tryExtract* on this extractor may accept.
128
+ // Must be a strict superset; missing a type would silently drop whatever the
129
+ // extractor would have emitted for it. The parser/index.ts compiles this into
130
+ // a Tree-Sitter Query and only fires the per-node extract calls on captures.
131
+ const TS_CANDIDATE_NODE_TYPES = [
132
+ // tryExtractDefinition
133
+ 'function_declaration',
134
+ 'generator_function_declaration',
135
+ 'class_declaration',
136
+ 'method_definition',
137
+ 'interface_declaration',
138
+ 'type_alias_declaration',
139
+ 'variable_declarator',
140
+ // tryExtractCallName + tryExtractImport + tryExtractRoute (all reuse call_expression)
141
+ 'call_expression',
142
+ 'new_expression',
143
+ // tryExtractImport (also)
144
+ 'import_statement',
145
+ // tryExtractConfigKey
146
+ 'member_expression',
147
+ 'subscript_expression',
148
+ ];
149
+ // Handles .ts, .tsx, .js, .jsx via separate WASM grammars but shared extractor logic
150
+ exports.typescriptExtractor = {
151
+ languageName: 'typescript',
152
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'],
153
+ branchNodeTypes: TS_BRANCH_NODES,
154
+ nestingNodeTypes: TS_NESTING_NODES,
155
+ candidateNodeTypes: TS_CANDIDATE_NODE_TYPES,
156
+ tryExtractDefinition(node) {
157
+ switch (node.type) {
158
+ case 'function_declaration':
159
+ case 'generator_function_declaration': {
160
+ const nameNode = node.childForFieldName('name');
161
+ if (!nameNode)
162
+ return null;
163
+ return mkDef(nameNode.text, 'function', node);
164
+ }
165
+ case 'class_declaration': {
166
+ const nameNode = node.childForFieldName('name');
167
+ if (!nameNode)
168
+ return null;
169
+ return mkDef(nameNode.text, 'class', node);
170
+ }
171
+ case 'method_definition': {
172
+ const nameNode = node.childForFieldName('name');
173
+ if (!nameNode)
174
+ return null;
175
+ const isConstructor = nameNode.text === 'constructor';
176
+ return mkDef(nameNode.text, isConstructor ? 'constructor' : 'method', node);
177
+ }
178
+ case 'interface_declaration': {
179
+ const nameNode = node.childForFieldName('name');
180
+ if (!nameNode)
181
+ return null;
182
+ return mkDef(nameNode.text, 'interface', node);
183
+ }
184
+ case 'type_alias_declaration': {
185
+ const nameNode = node.childForFieldName('name');
186
+ if (!nameNode)
187
+ return null;
188
+ return mkDef(nameNode.text, 'type', node);
189
+ }
190
+ case 'variable_declarator': {
191
+ const nameNode = node.childForFieldName('name');
192
+ const valueNode = node.childForFieldName('value');
193
+ if (!nameNode || !valueNode)
194
+ return null;
195
+ if (valueNode.type === 'arrow_function' ||
196
+ valueNode.type === 'function_expression' ||
197
+ valueNode.type === 'generator_function') {
198
+ return mkDef(nameNode.text, 'function', node);
199
+ }
200
+ return null;
201
+ }
202
+ default:
203
+ return null;
204
+ }
205
+ },
206
+ tryExtractCallName(node) {
207
+ if (node.type === 'call_expression') {
208
+ const funcNode = node.childForFieldName('function');
209
+ if (!funcNode)
210
+ return null;
211
+ if (funcNode.type === 'identifier')
212
+ return funcNode.text;
213
+ if (funcNode.type === 'member_expression') {
214
+ return funcNode.childForFieldName('property')?.text ?? null;
215
+ }
216
+ return null;
217
+ }
218
+ if (node.type === 'new_expression') {
219
+ const ctorNode = node.childForFieldName('constructor');
220
+ if (!ctorNode)
221
+ return null;
222
+ if (ctorNode.type === 'identifier')
223
+ return ctorNode.text;
224
+ if (ctorNode.type === 'member_expression') {
225
+ return ctorNode.childForFieldName('property')?.text ?? null;
226
+ }
227
+ return null;
228
+ }
229
+ return null;
230
+ },
231
+ tryExtractImport(node) {
232
+ if (node.type === 'import_statement') {
233
+ return node.childForFieldName('source')?.text?.replace(/['"]/g, '') ?? null;
234
+ }
235
+ if (node.type === 'call_expression') {
236
+ const funcNode = node.childForFieldName('function');
237
+ if (funcNode?.text === 'require') {
238
+ const args = node.childForFieldName('arguments');
239
+ const firstArg = args?.namedChildren[0];
240
+ if (firstArg?.type === 'string') {
241
+ return firstArg.text.replace(/['"]/g, '');
242
+ }
243
+ }
244
+ }
245
+ return null;
246
+ },
247
+ /**
248
+ * Recognize Express/Fastify-style route registrations:
249
+ * app.get('/users', handler)
250
+ * router.post('/login', handler)
251
+ * server.put('/items/:id', handler)
252
+ *
253
+ * Also handles Fastify object-style:
254
+ * app.route({ method: 'GET', url: '/users', handler: foo })
255
+ * app.route({ method: ['GET','POST'], url: '/users', handler: foo })
256
+ * — only when method and url are string (or string-array) literals so it
257
+ * stays deterministic. Returns one RouteDef per method when method is an
258
+ * array.
259
+ */
260
+ tryExtractRoute(node) {
261
+ // v9 Track-H: GraphQL resolver-map detection. Resolver definitions live in
262
+ // object literals (`const resolvers = { Query: { user: ... } }`) — not
263
+ // call expressions, so we dispatch by node type up front.
264
+ if (node.type === 'variable_declarator') {
265
+ return tryExtractGraphqlResolverMap(node);
266
+ }
267
+ if (node.type !== 'call_expression')
268
+ return null;
269
+ const funcNode = node.childForFieldName('function');
270
+ if (!funcNode || funcNode.type !== 'member_expression')
271
+ return null;
272
+ const prop = funcNode.childForFieldName('property');
273
+ if (!prop)
274
+ return null;
275
+ const method = prop.text.toLowerCase();
276
+ const args = node.childForFieldName('arguments');
277
+ if (!args)
278
+ return null;
279
+ const named = args.namedChildren;
280
+ if (named.length < 1)
281
+ return null;
282
+ // ── v9 Track-H: tRPC procedure definition ─────────────────────────────
283
+ // `procedure.query(handler)` / `publicProcedure.input(...).mutation(handler)`.
284
+ // We recognize this BEFORE the HTTP path because the prop name `query` /
285
+ // `mutation` / `subscription` would otherwise fall through to "unknown
286
+ // method" and return null — which is fine, but we want to actually emit
287
+ // a tRPC route row for the proc.
288
+ if (TRPC_PROCEDURE_METHODS.has(prop.text)) {
289
+ const trpcRoute = tryExtractTrpcProcedure(node, prop.text, named);
290
+ if (trpcRoute)
291
+ return [trpcRoute];
292
+ // fall through; not a tRPC procedure — could still be HTTP `app.query(...)`
293
+ // (extremely rare) so we don't return null yet.
294
+ }
295
+ // ── v9 Track-H: messaging CONSUMER detection ──────────────────────────
296
+ // Kafka consumer.subscribe({ topic|topics }) / Rabbit channel.consume /
297
+ // SQS receiveMessage / NATS nc.subscribe. The consumer side registers
298
+ // a route so producer→consumer linking works through the same resolver
299
+ // that handles HTTP route → service_call rendezvous.
300
+ const consumerRoutes = tryExtractMessagingConsumer(node, funcNode, named);
301
+ if (consumerRoutes)
302
+ return consumerRoutes;
303
+ // ── Fastify object-style: app.route({ method, url, handler }) ──────
304
+ if (method === 'route') {
305
+ const opts = named[0];
306
+ if (opts.type !== 'object')
307
+ return null;
308
+ const fields = readObjectLiteralFields(opts);
309
+ const urlNode = fields.get('url');
310
+ const methodNode = fields.get('method');
311
+ const handlerNode = fields.get('handler');
312
+ if (!urlNode || !methodNode)
313
+ return null;
314
+ const urlStr = stringLiteralValue(urlNode);
315
+ if (!urlStr || urlStr.length > 200)
316
+ return null;
317
+ const methods = stringOrStringArrayValues(methodNode);
318
+ if (methods.length === 0)
319
+ return null;
320
+ const handlerName = handlerNode ? identifierLikeName(handlerNode) : undefined;
321
+ return methods.map(m => ({
322
+ method: m.toUpperCase(),
323
+ path: urlStr,
324
+ framework: 'fastify',
325
+ handlerName,
326
+ line: node.startPosition.row,
327
+ }));
328
+ }
329
+ // ── Express/Fastify shorthand: app.<method>(path, handler) ──────────
330
+ if (!HTTP_METHODS.has(method))
331
+ return null;
332
+ // First arg must be a string literal route path
333
+ const pathNode = named[0];
334
+ if (pathNode.type !== 'string' && pathNode.type !== 'template_string')
335
+ return null;
336
+ const routePath = stripQuotes(pathNode.text);
337
+ if (!routePath || routePath.length > 200)
338
+ return null;
339
+ // Distinguish a route REGISTRATION (`app.get('/x', handler)`) from a
340
+ // client CALL (`axios.get('/x')`). A route registration must have either:
341
+ // (a) a router-like receiver name (app, router, server, Router, …), OR
342
+ // (b) ≥2 args where arg 2..N is a handler-shaped node (identifier,
343
+ // member_expression, arrow_function, or function_expression).
344
+ // Without one of those it's almost certainly a client GET/POST call and
345
+ // belongs to the service-call recognizer instead.
346
+ const receiver = funcNode.childForFieldName('object');
347
+ const recvName = receiver?.type === 'identifier' ? receiver.text : null;
348
+ const isRouterReceiver = recvName !== null && ROUTER_RECEIVER_NAMES.has(recvName);
349
+ let hasHandlerArg = false;
350
+ if (named.length >= 2) {
351
+ for (let i = 1; i < named.length; i++) {
352
+ const a = named[i];
353
+ if (a.type === 'identifier' || a.type === 'member_expression'
354
+ || a.type === 'arrow_function' || a.type === 'function_expression') {
355
+ hasHandlerArg = true;
356
+ break;
357
+ }
358
+ }
359
+ }
360
+ if (!isRouterReceiver && !hasHandlerArg)
361
+ return null;
362
+ // Last positional arg, if it's an identifier, is the handler name.
363
+ let handlerName;
364
+ for (let i = named.length - 1; i >= 1; i--) {
365
+ const a = named[i];
366
+ const found = identifierLikeName(a);
367
+ if (found) {
368
+ handlerName = found;
369
+ break;
370
+ }
371
+ // Inline arrow / function — don't try to name it (would be an empty handler)
372
+ if (a.type === 'arrow_function' || a.type === 'function_expression')
373
+ break;
374
+ }
375
+ return [{
376
+ method: method.toUpperCase(),
377
+ path: routePath,
378
+ framework: 'express',
379
+ handlerName,
380
+ line: node.startPosition.row,
381
+ }];
382
+ },
383
+ /**
384
+ * Detect HTTP client calls — fetch / axios.{get,post,…} / http.* /
385
+ * any.<method>(literalUrl, …). We only record a service call when the URL
386
+ * argument is a string or template_string whose literal portion contains a
387
+ * path-like fragment; everything else is ignored to keep results deterministic.
388
+ *
389
+ * The recognizer is intentionally conservative:
390
+ * fetch('/api/users') ← yes
391
+ * fetch(`${BASE_URL}/api/users`) ← yes (path lifted, env tagged)
392
+ * axios.get('/api/x') ← yes
393
+ * client.post('/api/x') ← yes (any bare member.<method>(literalUrl…))
394
+ * fetch(someVar) ← no
395
+ * logger.get(record) ← no (no string arg)
396
+ */
397
+ tryExtractServiceCalls(node) {
398
+ // v9 Track-H: gql document definition — `const X = gql\`...\``. Emitted
399
+ // as a sentinel service_call with framework='gql-doc' so the resolver can
400
+ // map document identifiers used in client calls back to the operation's
401
+ // top-level field name. Sentinels are filtered out of normal listings by
402
+ // their framework value.
403
+ if (node.type === 'variable_declarator') {
404
+ const sentinel = tryExtractGqlDocDefinition(node);
405
+ return sentinel ? [sentinel] : null;
406
+ }
407
+ if (node.type !== 'call_expression')
408
+ return null;
409
+ const funcNode = node.childForFieldName('function');
410
+ if (!funcNode)
411
+ return null;
412
+ // ── v9 Track-H: tRPC client call ─────────────────────────────────────
413
+ // trpc.user.getById.query({...}) / trpc.user.create.mutate(...) /
414
+ // trpc.user.getById.useQuery(...). The terminal method is one of
415
+ // TRPC_CLIENT_METHODS; the receiver chain must root at an identifier
416
+ // that looks like a tRPC proxy (`trpc*`, `api`, `client`, `rpc`).
417
+ if (funcNode.type === 'member_expression') {
418
+ const propNode = funcNode.childForFieldName('property');
419
+ const propTxt = propNode?.text ?? '';
420
+ const trpcKind = TRPC_CLIENT_METHODS.get(propTxt);
421
+ if (trpcKind) {
422
+ const trpcCall = tryExtractTrpcClientCall(node, funcNode, propTxt, trpcKind);
423
+ if (trpcCall)
424
+ return [trpcCall];
425
+ // fall through — not tRPC-shaped; the HTTP recognizers below may still match.
426
+ }
427
+ }
428
+ // ── v9 Track-H: GraphQL client call ──────────────────────────────────
429
+ // Apollo/urql/relay: client.query({ query: GET_USER }) / .mutate(...) /
430
+ // useQuery(GET_USER) / useMutation(CREATE_USER). The operation name is
431
+ // extracted from the document — either the imported const name (mapped
432
+ // back to its gql definition during indexing — done via a second pass)
433
+ // or directly from a gql template tagged template literal arg.
434
+ //
435
+ // For Seer-Core's purposes we emit the operation name found inline. The
436
+ // caller-side gql tag also gets its own service_call emitted (covers
437
+ // direct `gql\`query GetUser{...}\`` usage in client code).
438
+ if (funcNode.type === 'identifier') {
439
+ // Hook calls: useQuery(GET_USER, …) / useMutation(CREATE_USER, …)
440
+ const hookKind = GRAPHQL_CLIENT_METHODS.get(funcNode.text);
441
+ if (hookKind) {
442
+ const gqlCall = tryExtractGraphqlClientCall(node, funcNode.text, hookKind);
443
+ if (gqlCall)
444
+ return [gqlCall];
445
+ }
446
+ }
447
+ else if (funcNode.type === 'member_expression') {
448
+ const propNode = funcNode.childForFieldName('property');
449
+ const propTxt = propNode?.text ?? '';
450
+ const objNode = funcNode.childForFieldName('object');
451
+ const objTxt = objNode?.type === 'identifier' ? objNode.text : null;
452
+ const isGqlClient = objTxt !== null &&
453
+ (objTxt === 'client' || objTxt === 'apollo' || objTxt === 'apolloClient' ||
454
+ objTxt === 'gqlClient' || objTxt === 'urql' || objTxt === 'urqlClient');
455
+ const gqlKind = GRAPHQL_CLIENT_METHODS.get(propTxt);
456
+ if (isGqlClient && gqlKind) {
457
+ const gqlCall = tryExtractGraphqlClientCall(node, propTxt, gqlKind);
458
+ if (gqlCall)
459
+ return [gqlCall];
460
+ }
461
+ }
462
+ // ── v9 Track-H: messaging PRODUCER detection ──────────────────────────
463
+ // Kafka producer.send / SQS sendMessage / SNS publish / Rabbit publish |
464
+ // sendToQueue / NATS publish / Redis publish. Consumer-side detection
465
+ // lives in tryExtractRoute so consumers register as routes.
466
+ if (funcNode.type === 'member_expression') {
467
+ const msgCall = tryExtractMessagingProducer(node, funcNode);
468
+ if (msgCall)
469
+ return [msgCall];
470
+ }
471
+ let framework = null;
472
+ let method;
473
+ if (funcNode.type === 'identifier') {
474
+ // bare fetch('/x')
475
+ if (funcNode.text === 'fetch')
476
+ framework = 'fetch';
477
+ else
478
+ return null;
479
+ }
480
+ else if (funcNode.type === 'member_expression') {
481
+ const obj = funcNode.childForFieldName('object');
482
+ const prop = funcNode.childForFieldName('property');
483
+ if (!obj || !prop)
484
+ return null;
485
+ const propText = prop.text;
486
+ const propLower = propText.toLowerCase();
487
+ // axios.get / axios.post / axios.request / axios.{put,patch,delete,head,options}
488
+ if (obj.type === 'identifier' && obj.text === 'axios' && HTTP_CLIENT_METHODS.has(propLower)) {
489
+ framework = 'axios';
490
+ method = methodFromName(propLower);
491
+ }
492
+ else if (obj.type === 'identifier' && obj.text === 'fetch' && propLower === 'fetch') {
493
+ framework = 'fetch';
494
+ }
495
+ else if (HTTP_CLIENT_METHODS.has(propLower)) {
496
+ // Generic client.<method>(literalUrl, …) — record it when the URL arg
497
+ // is a string literal; the path itself is the strongest signal.
498
+ framework = 'http-client';
499
+ method = methodFromName(propLower);
500
+ }
501
+ else {
502
+ return null;
503
+ }
504
+ }
505
+ else {
506
+ return null;
507
+ }
508
+ // First argument must be a string-like literal we can read.
509
+ const args = node.childForFieldName('arguments');
510
+ if (!args)
511
+ return null;
512
+ const first = args.namedChildren[0];
513
+ if (!first)
514
+ return null;
515
+ let raw = null;
516
+ let envKey;
517
+ if (first.type === 'string') {
518
+ raw = stripQuotes(first.text);
519
+ }
520
+ else if (first.type === 'template_string') {
521
+ const lifted = readTemplateString(first);
522
+ if (!lifted)
523
+ return null;
524
+ raw = lifted.text;
525
+ envKey = lifted.envKey;
526
+ }
527
+ else {
528
+ return null;
529
+ }
530
+ if (!raw)
531
+ return null;
532
+ // Path-y heuristic: starts with '/', or contains a '/' and looks like a URL.
533
+ if (!looksLikeHttpTarget(raw))
534
+ return null;
535
+ // For fetch(url, { method: 'POST' }) — peek at the options arg if available.
536
+ if (!method && framework === 'fetch') {
537
+ const opts = args.namedChildren[1];
538
+ if (opts && opts.type === 'object') {
539
+ const fields = readObjectLiteralFields(opts);
540
+ const m = fields.get('method');
541
+ if (m) {
542
+ const v = stringLiteralValue(m);
543
+ if (v)
544
+ method = v.toUpperCase();
545
+ }
546
+ }
547
+ }
548
+ const def = {
549
+ protocol: 'http',
550
+ method: method ?? 'ANY',
551
+ rawTarget: raw.slice(0, 240),
552
+ framework,
553
+ line: node.startPosition.row,
554
+ confidence: 0.85,
555
+ };
556
+ if (envKey)
557
+ def.envKey = envKey;
558
+ return [def];
559
+ },
560
+ /**
561
+ * Static env var reads: `process.env.NAME` and `process.env["NAME"]`.
562
+ * Also `import.meta.env.NAME` for Vite-style projects.
563
+ */
564
+ tryExtractConfigKey(node) {
565
+ if (node.type !== 'member_expression' && node.type !== 'subscript_expression')
566
+ return null;
567
+ // process.env.NAME → member_expression(member_expression("process","env"), "NAME")
568
+ if (node.type === 'member_expression') {
569
+ const obj = node.childForFieldName('object');
570
+ const prop = node.childForFieldName('property');
571
+ if (!obj || !prop)
572
+ return null;
573
+ if (obj.type === 'member_expression') {
574
+ const objObj = obj.childForFieldName('object');
575
+ const objProp = obj.childForFieldName('property');
576
+ if (objObj && objProp) {
577
+ if (objObj.text === 'process' && objProp.text === 'env') {
578
+ return { key: prop.text, source: 'env', line: node.startPosition.row };
579
+ }
580
+ // import.meta.env.NAME
581
+ if (objObj.type === 'member_expression') {
582
+ const a = objObj.childForFieldName('object');
583
+ const b = objObj.childForFieldName('property');
584
+ if (a?.text === 'import' && b?.text === 'meta' && objProp.text === 'env') {
585
+ return { key: prop.text, source: 'env', line: node.startPosition.row };
586
+ }
587
+ }
588
+ }
589
+ }
590
+ return null;
591
+ }
592
+ // process.env["NAME"]
593
+ if (node.type === 'subscript_expression') {
594
+ const obj = node.childForFieldName('object');
595
+ const idx = node.childForFieldName('index');
596
+ if (!obj || !idx)
597
+ return null;
598
+ if (obj.type === 'member_expression') {
599
+ const objObj = obj.childForFieldName('object');
600
+ const objProp = obj.childForFieldName('property');
601
+ if (objObj?.text === 'process' && objProp?.text === 'env'
602
+ && (idx.type === 'string' || idx.type === 'template_string')) {
603
+ const key = stripQuotes(idx.text);
604
+ if (key)
605
+ return { key, source: 'env', line: node.startPosition.row };
606
+ }
607
+ }
608
+ }
609
+ return null;
610
+ },
611
+ };
612
+ function stripQuotes(s) {
613
+ return s.replace(/^[`'"]|[`'"]$/g, '');
614
+ }
615
+ /**
616
+ * Read a TypeScript/JavaScript object literal into a key→value-node map.
617
+ * Used by the Fastify route detector so we can pick out `url`, `method`,
618
+ * and `handler` fields regardless of declaration order. Computed keys
619
+ * (`[expr]: …`) are dropped — we only handle deterministic literal keys.
620
+ */
621
+ function readObjectLiteralFields(obj) {
622
+ const out = new Map();
623
+ for (const prop of obj.namedChildren) {
624
+ if (prop.type !== 'pair' && prop.type !== 'shorthand_property_identifier'
625
+ && prop.type !== 'property_identifier')
626
+ continue;
627
+ if (prop.type === 'pair') {
628
+ const k = prop.childForFieldName('key');
629
+ const v = prop.childForFieldName('value');
630
+ if (!k || !v)
631
+ continue;
632
+ let key = null;
633
+ if (k.type === 'property_identifier' || k.type === 'identifier')
634
+ key = k.text;
635
+ else if (k.type === 'string' || k.type === 'template_string')
636
+ key = stripQuotes(k.text);
637
+ if (key)
638
+ out.set(key, v);
639
+ }
640
+ }
641
+ return out;
642
+ }
643
+ /** Strip quotes from a string-literal node, returning null for non-strings. */
644
+ function stringLiteralValue(node) {
645
+ if (node.type !== 'string' && node.type !== 'template_string')
646
+ return null;
647
+ return stripQuotes(node.text);
648
+ }
649
+ /**
650
+ * Pull a list of string values out of a node that is either a single string
651
+ * literal or an array literal containing only string literals. Anything
652
+ * dynamic is dropped (`[]` returned) so route extraction stays deterministic.
653
+ */
654
+ function stringOrStringArrayValues(node) {
655
+ const single = stringLiteralValue(node);
656
+ if (single)
657
+ return [single];
658
+ if (node.type !== 'array')
659
+ return [];
660
+ const out = [];
661
+ for (const el of node.namedChildren) {
662
+ const v = stringLiteralValue(el);
663
+ if (v)
664
+ out.push(v);
665
+ }
666
+ return out;
667
+ }
668
+ /**
669
+ * Return the identifier-like name of a node passed as a handler — either a
670
+ * bare `identifier` (foo), a `member_expression` (a.b → "b"), or null for
671
+ * inline functions/arrows where there's no name to extract.
672
+ */
673
+ function identifierLikeName(node) {
674
+ if (node.type === 'identifier')
675
+ return node.text;
676
+ if (node.type === 'member_expression') {
677
+ return node.childForFieldName('property')?.text ?? undefined;
678
+ }
679
+ return undefined;
680
+ }
681
+ /** Upper-cased HTTP verb derived from an axios/fetch method name. */
682
+ function methodFromName(name) {
683
+ if (name === 'request')
684
+ return 'ANY';
685
+ return name.toUpperCase();
686
+ }
687
+ /**
688
+ * Read a template_string and return the literal portion plus the first
689
+ * env-key-looking identifier referenced via process.env.X or import.meta.env.X
690
+ * inside `${…}` placeholders. Used so `fetch(`${process.env.PAYMENT_URL}/charge`)`
691
+ * still records `/charge` + `envKey = PAYMENT_URL`.
692
+ *
693
+ * Returns null when there's nothing useful to extract.
694
+ */
695
+ function readTemplateString(node) {
696
+ let text = '';
697
+ let envKey;
698
+ for (const child of node.namedChildren) {
699
+ if (child.type === 'string_fragment') {
700
+ text += child.text;
701
+ }
702
+ else if (child.type === 'template_substitution') {
703
+ // ${…} — try to pull a process.env.X env key out of the expression.
704
+ const inner = child.namedChildren[0];
705
+ const k = tryPickEnvKey(inner);
706
+ if (k) {
707
+ if (!envKey)
708
+ envKey = k;
709
+ // An env-base substitution (`${process.env.PAYMENT_URL}/charge`) is the
710
+ // host/base, not a path segment — drop it; the literal `/charge` tail is
711
+ // what we match against routes.
712
+ }
713
+ else {
714
+ // A dynamic, NON-env value embedded in the URL is almost always a path
715
+ // parameter (`/api/users/${id}`). Emit a single placeholder segment so
716
+ // the segment COUNT is preserved and the route-pattern matcher can line
717
+ // it up against a parameterised route (`/api/users/:id`, `/users/{id}`,
718
+ // `/users/<id>`). Without this the segment vanished and a real call
719
+ // under-matched (e.g. `/api/users/${id}` collapsed to `/api/users`).
720
+ text += ':param';
721
+ }
722
+ }
723
+ }
724
+ if (!text && !envKey)
725
+ return null;
726
+ return { text, envKey };
727
+ }
728
+ /** Walk an expression node looking for process.env.X / import.meta.env.X. */
729
+ function tryPickEnvKey(node) {
730
+ if (!node)
731
+ return undefined;
732
+ if (node.type === 'member_expression') {
733
+ const obj = node.childForFieldName('object');
734
+ const prop = node.childForFieldName('property');
735
+ if (obj?.type === 'member_expression') {
736
+ const objObj = obj.childForFieldName('object');
737
+ const objProp = obj.childForFieldName('property');
738
+ if (objObj?.text === 'process' && objProp?.text === 'env' && prop)
739
+ return prop.text;
740
+ if (objObj?.type === 'member_expression') {
741
+ const a = objObj.childForFieldName('object');
742
+ const b = objObj.childForFieldName('property');
743
+ if (a?.text === 'import' && b?.text === 'meta' && objProp?.text === 'env' && prop)
744
+ return prop.text;
745
+ }
746
+ }
747
+ }
748
+ // Fall back: walk children breadth-first up to a small bound so something
749
+ // like `${(process.env.X ?? "y") + "/charge"}` still gets a hit.
750
+ for (const child of node.namedChildren) {
751
+ const found = tryPickEnvKey(child);
752
+ if (found)
753
+ return found;
754
+ }
755
+ return undefined;
756
+ }
757
+ /** Conservative path-likeness check. */
758
+ function looksLikeHttpTarget(s) {
759
+ if (!s)
760
+ return false;
761
+ if (s.startsWith('/'))
762
+ return true; // /api/users
763
+ if (/^https?:\/\//i.test(s))
764
+ return true; // https://x/y
765
+ if (/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9_-]/.test(s))
766
+ return true; // hostish/path
767
+ return false;
768
+ }
769
+ /**
770
+ * v9 Track-H — recognize a tRPC procedure definition.
771
+ *
772
+ * Returns a route with protocol='trpc' when the call_expression is the terminal
773
+ * `.query(handler)` / `.mutation(handler)` / `.subscription(handler)` of a
774
+ * tRPC procedure builder chain (`procedure.input(...).query(handler)`).
775
+ *
776
+ * Two signals must both hold:
777
+ * 1. The receiver chain bottoms out at a known procedure-builder identifier
778
+ * (`procedure`, `publicProcedure`, `protectedProcedure`, …).
779
+ * 2. The call is the VALUE of a `pair` in an object literal — that pair's
780
+ * KEY is the procedure name within its immediate router (we use this as
781
+ * `operation` and `path` so the resolver can rendezvous on it).
782
+ *
783
+ * When either signal is missing we return null so the caller can fall through
784
+ * to the HTTP route extractor.
785
+ */
786
+ function tryExtractTrpcProcedure(node, methodName, args) {
787
+ // (1) Walk receiver chain for a procedure-builder identifier.
788
+ const funcNode = node.childForFieldName('function');
789
+ if (!funcNode || funcNode.type !== 'member_expression')
790
+ return null;
791
+ let cur = funcNode.childForFieldName('object');
792
+ let foundBase = false;
793
+ // Bounded walk so a runaway chain can't burn time.
794
+ for (let i = 0; i < 12 && cur; i++) {
795
+ if (cur.type === 'identifier' && TRPC_PROCEDURE_BASES.has(cur.text)) {
796
+ foundBase = true;
797
+ break;
798
+ }
799
+ if (cur.type === 'member_expression') {
800
+ const obj = cur.childForFieldName('object');
801
+ if (obj?.type === 'identifier' && TRPC_PROCEDURE_BASES.has(obj.text)) {
802
+ foundBase = true;
803
+ break;
804
+ }
805
+ // Also accept member chains like `t.procedure` — check the property.
806
+ const prop = cur.childForFieldName('property');
807
+ if (prop && TRPC_PROCEDURE_BASES.has(prop.text)) {
808
+ foundBase = true;
809
+ break;
810
+ }
811
+ cur = obj;
812
+ }
813
+ else if (cur.type === 'call_expression') {
814
+ const f = cur.childForFieldName('function');
815
+ cur = f?.type === 'member_expression' ? f.childForFieldName('object') : null;
816
+ }
817
+ else {
818
+ cur = null;
819
+ }
820
+ }
821
+ if (!foundBase)
822
+ return null;
823
+ // (2) Walk UP to enclosing pair to harvest the procedure key.
824
+ let parent = node.parent;
825
+ let keyName = null;
826
+ for (let i = 0; i < 8 && parent; i++) {
827
+ if (parent.type === 'pair') {
828
+ const k = parent.childForFieldName('key');
829
+ if (k) {
830
+ if (k.type === 'property_identifier' || k.type === 'identifier')
831
+ keyName = k.text;
832
+ else if (k.type === 'string' || k.type === 'template_string')
833
+ keyName = stripQuotes(k.text);
834
+ }
835
+ break;
836
+ }
837
+ parent = parent.parent;
838
+ }
839
+ if (!keyName)
840
+ return null;
841
+ // Handler name when the first arg is a named function/identifier.
842
+ const handlerArg = args[0];
843
+ const handlerName = handlerArg ? identifierLikeName(handlerArg) : undefined;
844
+ const opKind = methodName === 'mutation' ? 'mutation'
845
+ : methodName === 'subscription' ? 'subscription'
846
+ : 'query';
847
+ return {
848
+ method: opKind.toUpperCase(),
849
+ path: keyName,
850
+ framework: 'trpc',
851
+ handlerName,
852
+ line: node.startPosition.row,
853
+ protocol: 'trpc',
854
+ operation: keyName,
855
+ };
856
+ }
857
+ /**
858
+ * v9 Track-H — recognize a tRPC client call.
859
+ *
860
+ * trpc.user.getById.query({...}) → operation = "user.getById", method=QUERY
861
+ * trpc.user.create.mutate(...) → operation = "user.create", method=MUTATION
862
+ * trpc.user.getById.useQuery(...) → operation = "user.getById", method=QUERY
863
+ *
864
+ * Recognition rules:
865
+ * - Terminal method is one of TRPC_CLIENT_METHODS.
866
+ * - The chain rooted at the leftmost identifier matches isTrpcClientRoot().
867
+ * - At least one procedure-name segment exists between the root and the terminal.
868
+ *
869
+ * The operation path is everything between the root and the terminal joined by
870
+ * '.'. The resolver matches client.operation == server.operation.
871
+ */
872
+ function tryExtractTrpcClientCall(node, funcNode, terminalMethod, kind) {
873
+ const segs = [];
874
+ // Walk receiver chain from .object downward, collecting property segments
875
+ // and stopping at the first non-member identifier.
876
+ let cur = funcNode.childForFieldName('object');
877
+ let rootName = null;
878
+ for (let i = 0; i < 16 && cur; i++) {
879
+ if (cur.type === 'identifier') {
880
+ rootName = cur.text;
881
+ break;
882
+ }
883
+ if (cur.type === 'member_expression') {
884
+ const prop = cur.childForFieldName('property');
885
+ if (prop)
886
+ segs.unshift(prop.text);
887
+ cur = cur.childForFieldName('object');
888
+ }
889
+ else {
890
+ // bracket access etc. break the chain — too risky to guess
891
+ return null;
892
+ }
893
+ }
894
+ if (!rootName)
895
+ return null;
896
+ if (!isTrpcClientRoot(rootName))
897
+ return null;
898
+ if (segs.length < 1)
899
+ return null; // need at least one procedure segment
900
+ const operation = segs.join('.');
901
+ return {
902
+ protocol: 'trpc',
903
+ method: kind.toUpperCase(),
904
+ rawTarget: operation,
905
+ framework: `trpc-${terminalMethod}`,
906
+ line: node.startPosition.row,
907
+ confidence: 0.9,
908
+ operation,
909
+ };
910
+ }
911
+ /**
912
+ * v9 Track-H — recognize a GraphQL resolver map.
913
+ *
914
+ * `const resolvers = { Query: { user: handler, ... }, Mutation: { ... } }`
915
+ * (or `{ Query: { user() { ... } } }` with shorthand method syntax).
916
+ *
917
+ * Emits one route per resolver field with operation = field name, method =
918
+ * 'QUERY' | 'MUTATION' | 'SUBSCRIPTION'. Only fires when the variable's
919
+ * value is an object literal whose top-level keys are exactly the GraphQL
920
+ * resolver-kind names — that gate keeps us from confusing this with arbitrary
921
+ * config objects.
922
+ */
923
+ function tryExtractGraphqlResolverMap(node) {
924
+ const valNode = node.childForFieldName('value');
925
+ if (!valNode || valNode.type !== 'object')
926
+ return null;
927
+ const fields = readObjectLiteralFields(valNode);
928
+ // At least one of Query/Mutation/Subscription must be present and itself be
929
+ // an object — otherwise this isn't a resolver map.
930
+ let hasResolverKey = false;
931
+ for (const k of GRAPHQL_RESOLVER_KEYS) {
932
+ if (fields.has(k)) {
933
+ hasResolverKey = true;
934
+ break;
935
+ }
936
+ }
937
+ if (!hasResolverKey)
938
+ return null;
939
+ const routes = [];
940
+ for (const kindKey of GRAPHQL_RESOLVER_KEYS) {
941
+ const inner = fields.get(kindKey);
942
+ if (!inner || inner.type !== 'object')
943
+ continue;
944
+ const kind = kindKey.toLowerCase();
945
+ for (const child of inner.namedChildren) {
946
+ let fieldName = null;
947
+ let handlerName;
948
+ const line = child.startPosition.row;
949
+ if (child.type === 'pair') {
950
+ const k = child.childForFieldName('key');
951
+ const v = child.childForFieldName('value');
952
+ if (!k)
953
+ continue;
954
+ if (k.type === 'property_identifier' || k.type === 'identifier')
955
+ fieldName = k.text;
956
+ else if (k.type === 'string' || k.type === 'template_string')
957
+ fieldName = stripQuotes(k.text);
958
+ if (v)
959
+ handlerName = identifierLikeName(v);
960
+ }
961
+ else if (child.type === 'method_definition' || child.type === 'shorthand_property_identifier') {
962
+ const k = child.childForFieldName('name');
963
+ if (k)
964
+ fieldName = k.text;
965
+ handlerName = fieldName ?? undefined;
966
+ }
967
+ else {
968
+ continue;
969
+ }
970
+ if (!fieldName)
971
+ continue;
972
+ routes.push({
973
+ method: kind.toUpperCase(),
974
+ path: fieldName,
975
+ framework: 'graphql',
976
+ handlerName,
977
+ line,
978
+ protocol: 'graphql',
979
+ operation: fieldName,
980
+ });
981
+ }
982
+ }
983
+ return routes.length > 0 ? routes : null;
984
+ }
985
+ /**
986
+ * v9 Track-H — recognize a GraphQL client call.
987
+ *
988
+ * Forms supported:
989
+ * client.query({ query: GET_USER }) — operation lifted from the
990
+ * document-identifier name
991
+ * client.mutate({ mutation: gql`mutation Foo { createUser { id } }` })
992
+ * — operation lifted from the gql
993
+ * body's first top-level field
994
+ * useQuery(GET_USER, { variables: ... }) — useQuery / useMutation hooks
995
+ * useQuery(gql`query Foo { user { id } }`, ...)
996
+ *
997
+ * Operation matching priority:
998
+ * 1. Top-level field name parsed from the gql body (matches resolver-map keys)
999
+ * 2. Operation name from the gql header
1000
+ * 3. Document-identifier (e.g. GET_USER) as a fallback so the call is still
1001
+ * recorded — won't link to a resolver but the row carries the evidence.
1002
+ */
1003
+ function tryExtractGraphqlClientCall(node, method, kind) {
1004
+ const args = node.childForFieldName('arguments');
1005
+ if (!args)
1006
+ return null;
1007
+ const first = args.namedChildren[0];
1008
+ if (!first)
1009
+ return null;
1010
+ let docNode = null;
1011
+ let documentIdent = null;
1012
+ if (first.type === 'object') {
1013
+ // client.{query,mutate,subscribe}({ query | mutation | subscription: DOC })
1014
+ const fields = readObjectLiteralFields(first);
1015
+ const fieldKey = kind === 'mutation' ? 'mutation'
1016
+ : kind === 'subscription' ? 'subscription' : 'query';
1017
+ const v = fields.get(fieldKey);
1018
+ if (!v)
1019
+ return null;
1020
+ if (v.type === 'identifier') {
1021
+ documentIdent = v.text;
1022
+ }
1023
+ else
1024
+ docNode = v;
1025
+ }
1026
+ else if (first.type === 'identifier') {
1027
+ // useQuery(GET_USER, ...)
1028
+ documentIdent = first.text;
1029
+ }
1030
+ else {
1031
+ // useQuery(gql`...`, ...) — the tagged template literal itself
1032
+ docNode = first;
1033
+ }
1034
+ let opName;
1035
+ let opField;
1036
+ let rawTarget;
1037
+ if (docNode) {
1038
+ const txt = docNode.text;
1039
+ rawTarget = txt.slice(0, 240);
1040
+ const parsed = parseGqlOperation(txt);
1041
+ if (parsed) {
1042
+ opName = parsed.opName;
1043
+ opField = parsed.fieldName;
1044
+ }
1045
+ }
1046
+ else if (documentIdent) {
1047
+ rawTarget = documentIdent;
1048
+ }
1049
+ else {
1050
+ return null;
1051
+ }
1052
+ // Operation field is what matches a resolver-map key on the server. Op name
1053
+ // is the user-given operation alias (GetUser). Doc-ident is the const name.
1054
+ const operation = opField ?? opName ?? documentIdent ?? undefined;
1055
+ if (!operation)
1056
+ return null;
1057
+ const def = {
1058
+ protocol: 'graphql',
1059
+ method: kind.toUpperCase(),
1060
+ rawTarget,
1061
+ framework: `graphql-${method}`,
1062
+ line: node.startPosition.row,
1063
+ // Higher confidence when we parsed the field name out of the gql body;
1064
+ // lower when all we have is a document const name.
1065
+ confidence: opField ? 0.9 : (opName ? 0.85 : 0.65),
1066
+ operation,
1067
+ };
1068
+ if (opName || documentIdent) {
1069
+ def.metadataJson = JSON.stringify({
1070
+ operationName: opName ?? null,
1071
+ documentIdent: documentIdent ?? null,
1072
+ fieldName: opField ?? null,
1073
+ });
1074
+ }
1075
+ return def;
1076
+ }
1077
+ /**
1078
+ * Pull operation kind, name, and first top-level selection field out of a
1079
+ * GraphQL document literal. Forgiving: handles `gql\`…\``, `\`…\``, bare
1080
+ * strings, and shorthand `{ field }` query bodies.
1081
+ *
1082
+ * Returns null when nothing GraphQL-shaped is found.
1083
+ */
1084
+ function parseGqlOperation(src) {
1085
+ let s = src;
1086
+ // strip a leading tag (gql, graphql, parse, etc.) if it's followed by a backtick
1087
+ s = s.replace(/^[A-Za-z_][A-Za-z0-9_]*`/, '`');
1088
+ // trim wrapping backticks / quotes
1089
+ s = s.replace(/^[`'"]/, '').replace(/[`'"]\s*$/, '').trim();
1090
+ if (!s)
1091
+ return null;
1092
+ // strip /* ... */ block comments and # line comments so the regexes below
1093
+ // don't trip on doc-comments before the operation header
1094
+ s = s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/#[^\n]*/g, '').trim();
1095
+ let opKind = 'query';
1096
+ let opName;
1097
+ const opMatch = s.match(/^(query|mutation|subscription)\b(?:\s+([A-Za-z_][A-Za-z0-9_]*))?/);
1098
+ if (opMatch) {
1099
+ opKind = opMatch[1];
1100
+ opName = opMatch[2];
1101
+ s = s.slice(opMatch[0].length).trim();
1102
+ // drop ($vars) declaration block when present
1103
+ if (s.startsWith('(')) {
1104
+ const close = balancedClose(s, '(', ')');
1105
+ if (close > 0)
1106
+ s = s.slice(close + 1).trim();
1107
+ }
1108
+ }
1109
+ // first '{' opens the selection set
1110
+ const openBrace = s.indexOf('{');
1111
+ if (openBrace < 0)
1112
+ return { opKind, opName };
1113
+ s = s.slice(openBrace + 1).trim();
1114
+ // first identifier in the selection set is the top-level field
1115
+ const fieldMatch = s.match(/^([A-Za-z_][A-Za-z0-9_]*)/);
1116
+ const fieldName = fieldMatch ? fieldMatch[1] : undefined;
1117
+ return { opKind, opName, fieldName };
1118
+ }
1119
+ /**
1120
+ * v9 Track-H — extract a `const X = gql\`...\`` document definition.
1121
+ *
1122
+ * Emitted as a sentinel service_call with framework='gql-doc' so the resolver
1123
+ * can map document identifiers (GET_USER) to their parsed operation field
1124
+ * (user). The row carries:
1125
+ * - raw_target = the document constant name
1126
+ * - operation = the parsed top-level field name (if any)
1127
+ * - method = operation kind (QUERY / MUTATION / SUBSCRIPTION)
1128
+ * - confidence = 0.4 — these aren't actual outbound calls, so we keep
1129
+ * confidence low to discourage them from showing up in
1130
+ * risk / context surfaces.
1131
+ */
1132
+ function tryExtractGqlDocDefinition(node) {
1133
+ const nameNode = node.childForFieldName('name');
1134
+ if (!nameNode || (nameNode.type !== 'identifier' && nameNode.type !== 'property_identifier')) {
1135
+ return null;
1136
+ }
1137
+ const valNode = node.childForFieldName('value');
1138
+ if (!valNode)
1139
+ return null;
1140
+ // Accept both `gql\`...\`` (tagged template) and a bare template literal
1141
+ // that obviously wraps a GraphQL operation header.
1142
+ let body = null;
1143
+ if (valNode.type === 'call_expression') {
1144
+ // Some tag wrappers parse as call_expression(text("gql"), template_string).
1145
+ body = valNode.text;
1146
+ }
1147
+ else if (valNode.type === 'template_string') {
1148
+ body = valNode.text;
1149
+ }
1150
+ if (!body)
1151
+ return null;
1152
+ // Only emit when this actually looks GraphQL-shaped.
1153
+ const parsed = parseGqlOperation(body);
1154
+ if (!parsed || (!parsed.opName && !parsed.fieldName))
1155
+ return null;
1156
+ const def = {
1157
+ protocol: 'graphql',
1158
+ method: parsed.opKind.toUpperCase(),
1159
+ rawTarget: nameNode.text,
1160
+ framework: 'gql-doc',
1161
+ line: node.startPosition.row,
1162
+ confidence: 0.4,
1163
+ operation: parsed.fieldName ?? parsed.opName ?? nameNode.text,
1164
+ };
1165
+ def.metadataJson = JSON.stringify({
1166
+ documentIdent: nameNode.text,
1167
+ operationName: parsed.opName ?? null,
1168
+ fieldName: parsed.fieldName ?? null,
1169
+ });
1170
+ return def;
1171
+ }
1172
+ /**
1173
+ * v9 Track-H — recognize a messaging PRODUCER call.
1174
+ *
1175
+ * Decisions made by inspecting (a) the method name and (b) the shape of the
1176
+ * first argument, with the receiver name as a final disambiguation cue when
1177
+ * multiple protocols share a method (publish is the prototypical example).
1178
+ *
1179
+ * Kafka: producer.send({ topic: 'orders', messages: [...] })
1180
+ * Kafka v1: kafkaProducer.send('orders', message)
1181
+ * SQS: sqs.sendMessage({ QueueUrl: '...', MessageBody: '...' })
1182
+ * SNS: sns.publish({ TopicArn: '...', Message: '...' })
1183
+ * RabbitMQ: channel.publish('exch', 'rk', body) / channel.sendToQueue('q', body)
1184
+ * NATS: nc.publish('subject', data)
1185
+ * Redis: redis.publish('chan', msg)
1186
+ *
1187
+ * On match returns a ServiceCallDef carrying the relevant protocol field
1188
+ * (topic / queue / exchange).
1189
+ */
1190
+ function tryExtractMessagingProducer(node, funcNode) {
1191
+ const propNode = funcNode.childForFieldName('property');
1192
+ const objNode = funcNode.childForFieldName('object');
1193
+ if (!propNode || !objNode)
1194
+ return null;
1195
+ const method = propNode.text;
1196
+ const recvName = objNode.type === 'identifier' ? objNode.text : null;
1197
+ const args = node.childForFieldName('arguments');
1198
+ if (!args)
1199
+ return null;
1200
+ const named = args.namedChildren;
1201
+ const first = named[0];
1202
+ if (!first)
1203
+ return null;
1204
+ // Helper: read a string literal from a node (returns null otherwise).
1205
+ const litString = (n) => {
1206
+ if (!n)
1207
+ return null;
1208
+ if (n.type === 'string' || n.type === 'template_string')
1209
+ return stripQuotes(n.text);
1210
+ return null;
1211
+ };
1212
+ // ── send: Kafka topic ─────────────────────────────────────────────────
1213
+ if (method === 'send') {
1214
+ if (first.type === 'object') {
1215
+ const fields = readObjectLiteralFields(first);
1216
+ const topicNode = fields.get('topic');
1217
+ const topic = litString(topicNode);
1218
+ if (topic) {
1219
+ return mkMsgCall(node, 'kafka', method, 'kafkajs', { topic, line: node.startPosition.row });
1220
+ }
1221
+ }
1222
+ else if (first.type === 'string' || first.type === 'template_string') {
1223
+ // Older kafkajs / node-rdkafka: producer.send('topic', msg)
1224
+ if (receiverHintsProtocol(recvName, 'kafka')) {
1225
+ return mkMsgCall(node, 'kafka', method, 'kafka', { topic: stripQuotes(first.text), line: node.startPosition.row });
1226
+ }
1227
+ }
1228
+ }
1229
+ // ── sendMessage: SQS ──────────────────────────────────────────────────
1230
+ if (method === 'sendMessage' && first.type === 'object') {
1231
+ const fields = readObjectLiteralFields(first);
1232
+ const queueUrl = litString(fields.get('QueueUrl'));
1233
+ if (queueUrl !== null) {
1234
+ return mkMsgCall(node, 'sqs', method, 'aws-sdk-sqs', {
1235
+ queue: extractQueueName(queueUrl),
1236
+ rawTarget: queueUrl,
1237
+ line: node.startPosition.row,
1238
+ });
1239
+ }
1240
+ }
1241
+ // ── sendToQueue: RabbitMQ ─────────────────────────────────────────────
1242
+ if (method === 'sendToQueue') {
1243
+ const queue = litString(first);
1244
+ if (queue) {
1245
+ return mkMsgCall(node, 'rabbitmq', method, 'amqplib', { queue, line: node.startPosition.row });
1246
+ }
1247
+ }
1248
+ // ── publish: SNS / RabbitMQ / NATS / Redis ────────────────────────────
1249
+ if (method === 'publish') {
1250
+ if (first.type === 'object') {
1251
+ // SNS: { TopicArn, Message }
1252
+ const fields = readObjectLiteralFields(first);
1253
+ const topicArn = litString(fields.get('TopicArn'));
1254
+ if (topicArn !== null) {
1255
+ return mkMsgCall(node, 'sns', method, 'aws-sdk-sns', {
1256
+ topic: extractTopicNameFromArn(topicArn),
1257
+ rawTarget: topicArn,
1258
+ line: node.startPosition.row,
1259
+ });
1260
+ }
1261
+ }
1262
+ // RabbitMQ: channel.publish('exchange', 'routingKey', body)
1263
+ if (receiverHintsProtocol(recvName, 'rabbitmq')) {
1264
+ const exchange = litString(first);
1265
+ const routingKey = litString(named[1]);
1266
+ if (exchange !== null) {
1267
+ return mkMsgCall(node, 'rabbitmq', method, 'amqplib', {
1268
+ exchange,
1269
+ metadataJson: routingKey ? JSON.stringify({ routingKey }) : undefined,
1270
+ line: node.startPosition.row,
1271
+ });
1272
+ }
1273
+ }
1274
+ // NATS / Redis: nc.publish('subject', data) / redis.publish('chan', msg)
1275
+ const subject = litString(first);
1276
+ if (subject !== null) {
1277
+ if (receiverHintsProtocol(recvName, 'nats')) {
1278
+ return mkMsgCall(node, 'nats', method, 'nats', { topic: subject, line: node.startPosition.row });
1279
+ }
1280
+ if (receiverHintsProtocol(recvName, 'redis_pubsub')) {
1281
+ return mkMsgCall(node, 'redis_pubsub', method, 'redis', { topic: subject, line: node.startPosition.row });
1282
+ }
1283
+ }
1284
+ }
1285
+ return null;
1286
+ }
1287
+ /**
1288
+ * v9 Track-H — recognize a messaging CONSUMER registration as a RouteDef.
1289
+ *
1290
+ * Kafka: consumer.subscribe({ topic | topics: [...] })
1291
+ * Rabbit: channel.consume('queue', handler)
1292
+ * SQS: sqs.receiveMessage({ QueueUrl })
1293
+ * NATS: nc.subscribe('subject')
1294
+ *
1295
+ * Returns one route per topic/queue. The handler is recovered when it's a
1296
+ * named identifier in the args list.
1297
+ */
1298
+ function tryExtractMessagingConsumer(node, funcNode, named) {
1299
+ if (funcNode.type !== 'member_expression')
1300
+ return null;
1301
+ const propNode = funcNode.childForFieldName('property');
1302
+ const objNode = funcNode.childForFieldName('object');
1303
+ if (!propNode || !objNode)
1304
+ return null;
1305
+ const method = propNode.text;
1306
+ const recvName = objNode.type === 'identifier' ? objNode.text : null;
1307
+ const first = named[0];
1308
+ if (!first)
1309
+ return null;
1310
+ const line = node.startPosition.row;
1311
+ const litString = (n) => {
1312
+ if (!n)
1313
+ return null;
1314
+ if (n.type === 'string' || n.type === 'template_string')
1315
+ return stripQuotes(n.text);
1316
+ return null;
1317
+ };
1318
+ // ── Kafka consumer.subscribe({ topic | topics }) ─────────────────────
1319
+ if (method === 'subscribe' && first.type === 'object'
1320
+ && receiverHintsProtocol(recvName, 'kafka')) {
1321
+ const fields = readObjectLiteralFields(first);
1322
+ const single = litString(fields.get('topic'));
1323
+ if (single) {
1324
+ return [mkMsgRoute('kafka', 'kafkajs', { topic: single, line, path: single })];
1325
+ }
1326
+ const arrNode = fields.get('topics');
1327
+ if (arrNode && arrNode.type === 'array') {
1328
+ const out = [];
1329
+ for (const el of arrNode.namedChildren) {
1330
+ const t = litString(el);
1331
+ if (t)
1332
+ out.push(mkMsgRoute('kafka', 'kafkajs', { topic: t, line, path: t }));
1333
+ }
1334
+ if (out.length > 0)
1335
+ return out;
1336
+ }
1337
+ }
1338
+ // ── NATS nc.subscribe('subject') ─────────────────────────────────────
1339
+ if (method === 'subscribe' && receiverHintsProtocol(recvName, 'nats')) {
1340
+ const subject = litString(first);
1341
+ if (subject) {
1342
+ return [mkMsgRoute('nats', 'nats', { topic: subject, line, path: subject })];
1343
+ }
1344
+ }
1345
+ // ── Redis subscribe('chan') ──────────────────────────────────────────
1346
+ if (method === 'subscribe' && receiverHintsProtocol(recvName, 'redis_pubsub')) {
1347
+ const chan = litString(first);
1348
+ if (chan) {
1349
+ return [mkMsgRoute('redis_pubsub', 'redis', { topic: chan, line, path: chan })];
1350
+ }
1351
+ }
1352
+ // ── RabbitMQ channel.consume('queue', handler) ────────────────────────
1353
+ if (method === 'consume' && receiverHintsProtocol(recvName, 'rabbitmq')) {
1354
+ const queue = litString(first);
1355
+ if (queue) {
1356
+ const handlerName = named[1] ? identifierLikeName(named[1]) : undefined;
1357
+ return [mkMsgRoute('rabbitmq', 'amqplib', {
1358
+ queue, line, path: queue, handlerName,
1359
+ })];
1360
+ }
1361
+ }
1362
+ // ── SQS receiveMessage({ QueueUrl }) ─────────────────────────────────
1363
+ if (method === 'receiveMessage' && first.type === 'object') {
1364
+ const fields = readObjectLiteralFields(first);
1365
+ const queueUrl = litString(fields.get('QueueUrl'));
1366
+ if (queueUrl !== null) {
1367
+ return [mkMsgRoute('sqs', 'aws-sdk-sqs', {
1368
+ queue: extractQueueName(queueUrl), line, path: queueUrl,
1369
+ })];
1370
+ }
1371
+ }
1372
+ return null;
1373
+ }
1374
+ function mkMsgCall(node, protocol, method, framework, opts) {
1375
+ const rawTarget = opts.rawTarget ?? opts.topic ?? opts.queue ?? opts.exchange ?? '';
1376
+ return {
1377
+ protocol,
1378
+ method: 'PUBLISH',
1379
+ rawTarget: rawTarget.slice(0, 240),
1380
+ framework,
1381
+ line: opts.line,
1382
+ confidence: 0.9,
1383
+ topic: opts.topic,
1384
+ queue: opts.queue,
1385
+ exchange: opts.exchange,
1386
+ metadataJson: opts.metadataJson,
1387
+ };
1388
+ }
1389
+ function mkMsgRoute(protocol, framework, opts) {
1390
+ return {
1391
+ method: 'CONSUME',
1392
+ path: opts.path,
1393
+ framework,
1394
+ handlerName: opts.handlerName,
1395
+ line: opts.line,
1396
+ protocol,
1397
+ topic: opts.topic,
1398
+ queue: opts.queue,
1399
+ exchange: opts.exchange,
1400
+ };
1401
+ }
1402
+ /** SQS QueueUrl → final path segment (queue name). */
1403
+ function extractQueueName(url) {
1404
+ if (!url)
1405
+ return url;
1406
+ const slash = url.lastIndexOf('/');
1407
+ return slash >= 0 ? url.slice(slash + 1) : url;
1408
+ }
1409
+ /** SNS TopicArn → topic name (the part after the last colon). */
1410
+ function extractTopicNameFromArn(arn) {
1411
+ if (!arn)
1412
+ return arn;
1413
+ const colon = arn.lastIndexOf(':');
1414
+ return colon >= 0 ? arn.slice(colon + 1) : arn;
1415
+ }
1416
+ /** Index of the matching close paren/brace for a string that starts with `open`. */
1417
+ function balancedClose(s, open, close) {
1418
+ let depth = 0;
1419
+ for (let i = 0; i < s.length; i++) {
1420
+ const ch = s[i];
1421
+ if (ch === open)
1422
+ depth++;
1423
+ else if (ch === close) {
1424
+ depth--;
1425
+ if (depth === 0)
1426
+ return i;
1427
+ }
1428
+ }
1429
+ return -1;
1430
+ }
1431
+ function mkDef(name, kind, node) {
1432
+ return {
1433
+ name,
1434
+ kind,
1435
+ lineStart: node.startPosition.row,
1436
+ lineEnd: node.endPosition.row,
1437
+ colStart: node.startPosition.column,
1438
+ colEnd: node.endPosition.column,
1439
+ signature: (0, walker_js_1.firstLine)(node),
1440
+ };
1441
+ }
1442
+ //# sourceMappingURL=typescript.js.map