@backthread/extractor 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 (379) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/cluster/domain-grouping.d.ts +113 -0
  4. package/dist/cluster/domain-grouping.d.ts.map +1 -0
  5. package/dist/cluster/domain-grouping.js +277 -0
  6. package/dist/cluster/domain-grouping.js.map +1 -0
  7. package/dist/cluster/grouping-gate.d.ts +54 -0
  8. package/dist/cluster/grouping-gate.d.ts.map +1 -0
  9. package/dist/cluster/grouping-gate.js +0 -0
  10. package/dist/cluster/grouping-gate.js.map +1 -0
  11. package/dist/cluster/louvain.d.ts +88 -0
  12. package/dist/cluster/louvain.d.ts.map +1 -0
  13. package/dist/cluster/louvain.js +674 -0
  14. package/dist/cluster/louvain.js.map +1 -0
  15. package/dist/cluster/overrides.d.ts +19 -0
  16. package/dist/cluster/overrides.d.ts.map +1 -0
  17. package/dist/cluster/overrides.js +32 -0
  18. package/dist/cluster/overrides.js.map +1 -0
  19. package/dist/cluster/subsystem.d.ts +45 -0
  20. package/dist/cluster/subsystem.d.ts.map +1 -0
  21. package/dist/cluster/subsystem.js +299 -0
  22. package/dist/cluster/subsystem.js.map +1 -0
  23. package/dist/cluster/workspaces.d.ts +64 -0
  24. package/dist/cluster/workspaces.d.ts.map +1 -0
  25. package/dist/cluster/workspaces.js +739 -0
  26. package/dist/cluster/workspaces.js.map +1 -0
  27. package/dist/extract.d.ts +52 -0
  28. package/dist/extract.d.ts.map +1 -0
  29. package/dist/extract.js +48 -0
  30. package/dist/extract.js.map +1 -0
  31. package/dist/framework/celery/celery.d.ts +17 -0
  32. package/dist/framework/celery/celery.d.ts.map +1 -0
  33. package/dist/framework/celery/celery.js +524 -0
  34. package/dist/framework/celery/celery.js.map +1 -0
  35. package/dist/framework/contribute-step.d.ts +38 -0
  36. package/dist/framework/contribute-step.d.ts.map +1 -0
  37. package/dist/framework/contribute-step.js +403 -0
  38. package/dist/framework/contribute-step.js.map +1 -0
  39. package/dist/framework/detect-step.d.ts +8 -0
  40. package/dist/framework/detect-step.d.ts.map +1 -0
  41. package/dist/framework/detect-step.js +54 -0
  42. package/dist/framework/detect-step.js.map +1 -0
  43. package/dist/framework/detect-util.d.ts +24 -0
  44. package/dist/framework/detect-util.d.ts.map +1 -0
  45. package/dist/framework/detect-util.js +59 -0
  46. package/dist/framework/detect-util.js.map +1 -0
  47. package/dist/framework/django/django.d.ts +22 -0
  48. package/dist/framework/django/django.d.ts.map +1 -0
  49. package/dist/framework/django/django.js +940 -0
  50. package/dist/framework/django/django.js.map +1 -0
  51. package/dist/framework/fastapi/fastapi.d.ts +27 -0
  52. package/dist/framework/fastapi/fastapi.d.ts.map +1 -0
  53. package/dist/framework/fastapi/fastapi.js +477 -0
  54. package/dist/framework/fastapi/fastapi.js.map +1 -0
  55. package/dist/framework/faststream/faststream.d.ts +19 -0
  56. package/dist/framework/faststream/faststream.d.ts.map +1 -0
  57. package/dist/framework/faststream/faststream.js +765 -0
  58. package/dist/framework/faststream/faststream.js.map +1 -0
  59. package/dist/framework/flask/flask.d.ts +17 -0
  60. package/dist/framework/flask/flask.d.ts.map +1 -0
  61. package/dist/framework/flask/flask.js +507 -0
  62. package/dist/framework/flask/flask.js.map +1 -0
  63. package/dist/framework/graphql/graphql.d.ts +17 -0
  64. package/dist/framework/graphql/graphql.d.ts.map +1 -0
  65. package/dist/framework/graphql/graphql.js +708 -0
  66. package/dist/framework/graphql/graphql.js.map +1 -0
  67. package/dist/framework/grpc/grpc.d.ts +22 -0
  68. package/dist/framework/grpc/grpc.d.ts.map +1 -0
  69. package/dist/framework/grpc/grpc.js +618 -0
  70. package/dist/framework/grpc/grpc.js.map +1 -0
  71. package/dist/framework/litestar/litestar.d.ts +17 -0
  72. package/dist/framework/litestar/litestar.d.ts.map +1 -0
  73. package/dist/framework/litestar/litestar.js +536 -0
  74. package/dist/framework/litestar/litestar.js.map +1 -0
  75. package/dist/framework/nest/nest.d.ts +19 -0
  76. package/dist/framework/nest/nest.d.ts.map +1 -0
  77. package/dist/framework/nest/nest.js +613 -0
  78. package/dist/framework/nest/nest.js.map +1 -0
  79. package/dist/framework/next/next.d.ts +22 -0
  80. package/dist/framework/next/next.d.ts.map +1 -0
  81. package/dist/framework/next/next.js +449 -0
  82. package/dist/framework/next/next.js.map +1 -0
  83. package/dist/framework/node/node.d.ts +24 -0
  84. package/dist/framework/node/node.d.ts.map +1 -0
  85. package/dist/framework/node/node.js +1009 -0
  86. package/dist/framework/node/node.js.map +1 -0
  87. package/dist/framework/orchestrator/orchestrator.d.ts +34 -0
  88. package/dist/framework/orchestrator/orchestrator.d.ts.map +1 -0
  89. package/dist/framework/orchestrator/orchestrator.js +691 -0
  90. package/dist/framework/orchestrator/orchestrator.js.map +1 -0
  91. package/dist/framework/orm/orm.d.ts +61 -0
  92. package/dist/framework/orm/orm.d.ts.map +1 -0
  93. package/dist/framework/orm/orm.js +620 -0
  94. package/dist/framework/orm/orm.js.map +1 -0
  95. package/dist/framework/python/analyze.d.ts +38 -0
  96. package/dist/framework/python/analyze.d.ts.map +1 -0
  97. package/dist/framework/python/analyze.js +98 -0
  98. package/dist/framework/python/analyze.js.map +1 -0
  99. package/dist/framework/python/py-ast.d.ts +130 -0
  100. package/dist/framework/python/py-ast.d.ts.map +1 -0
  101. package/dist/framework/python/py-ast.js +344 -0
  102. package/dist/framework/python/py-ast.js.map +1 -0
  103. package/dist/framework/python-orm/python-orm.d.ts +19 -0
  104. package/dist/framework/python-orm/python-orm.d.ts.map +1 -0
  105. package/dist/framework/python-orm/python-orm.js +516 -0
  106. package/dist/framework/python-orm/python-orm.js.map +1 -0
  107. package/dist/framework/react-native/react-native.d.ts +35 -0
  108. package/dist/framework/react-native/react-native.d.ts.map +1 -0
  109. package/dist/framework/react-native/react-native.js +1181 -0
  110. package/dist/framework/react-native/react-native.js.map +1 -0
  111. package/dist/framework/register.d.ts +2 -0
  112. package/dist/framework/register.d.ts.map +1 -0
  113. package/dist/framework/register.js +59 -0
  114. package/dist/framework/register.js.map +1 -0
  115. package/dist/framework/registry.d.ts +31 -0
  116. package/dist/framework/registry.d.ts.map +1 -0
  117. package/dist/framework/registry.js +70 -0
  118. package/dist/framework/registry.js.map +1 -0
  119. package/dist/framework/routing/expo-router.d.ts +31 -0
  120. package/dist/framework/routing/expo-router.d.ts.map +1 -0
  121. package/dist/framework/routing/expo-router.js +356 -0
  122. package/dist/framework/routing/expo-router.js.map +1 -0
  123. package/dist/framework/routing/index.d.ts +4 -0
  124. package/dist/framework/routing/index.d.ts.map +1 -0
  125. package/dist/framework/routing/index.js +16 -0
  126. package/dist/framework/routing/index.js.map +1 -0
  127. package/dist/framework/routing/next-router.d.ts +45 -0
  128. package/dist/framework/routing/next-router.d.ts.map +1 -0
  129. package/dist/framework/routing/next-router.js +378 -0
  130. package/dist/framework/routing/next-router.js.map +1 -0
  131. package/dist/framework/routing/route-tree.d.ts +73 -0
  132. package/dist/framework/routing/route-tree.d.ts.map +1 -0
  133. package/dist/framework/routing/route-tree.js +120 -0
  134. package/dist/framework/routing/route-tree.js.map +1 -0
  135. package/dist/framework/types.d.ts +230 -0
  136. package/dist/framework/types.d.ts.map +1 -0
  137. package/dist/framework/types.js +25 -0
  138. package/dist/framework/types.js.map +1 -0
  139. package/dist/graph/cross-language.d.ts +11 -0
  140. package/dist/graph/cross-language.d.ts.map +1 -0
  141. package/dist/graph/cross-language.js +123 -0
  142. package/dist/graph/cross-language.js.map +1 -0
  143. package/dist/graph/extract.d.ts +13 -0
  144. package/dist/graph/extract.d.ts.map +1 -0
  145. package/dist/graph/extract.js +79 -0
  146. package/dist/graph/extract.js.map +1 -0
  147. package/dist/graph/file-graph.d.ts +186 -0
  148. package/dist/graph/file-graph.d.ts.map +1 -0
  149. package/dist/graph/file-graph.js +427 -0
  150. package/dist/graph/file-graph.js.map +1 -0
  151. package/dist/graph/http-callsites.d.ts +14 -0
  152. package/dist/graph/http-callsites.d.ts.map +1 -0
  153. package/dist/graph/http-callsites.js +107 -0
  154. package/dist/graph/http-callsites.js.map +1 -0
  155. package/dist/graph/incremental.d.ts +122 -0
  156. package/dist/graph/incremental.d.ts.map +1 -0
  157. package/dist/graph/incremental.js +448 -0
  158. package/dist/graph/incremental.js.map +1 -0
  159. package/dist/graph/language.d.ts +31 -0
  160. package/dist/graph/language.d.ts.map +1 -0
  161. package/dist/graph/language.js +146 -0
  162. package/dist/graph/language.js.map +1 -0
  163. package/dist/graph/noise-filter.d.ts +46 -0
  164. package/dist/graph/noise-filter.d.ts.map +1 -0
  165. package/dist/graph/noise-filter.js +199 -0
  166. package/dist/graph/noise-filter.js.map +1 -0
  167. package/dist/graph/python-adapter.d.ts +36 -0
  168. package/dist/graph/python-adapter.d.ts.map +1 -0
  169. package/dist/graph/python-adapter.js +402 -0
  170. package/dist/graph/python-adapter.js.map +1 -0
  171. package/dist/graph/python-manifest.d.ts +22 -0
  172. package/dist/graph/python-manifest.d.ts.map +1 -0
  173. package/dist/graph/python-manifest.js +137 -0
  174. package/dist/graph/python-manifest.js.map +1 -0
  175. package/dist/graph/python-stdlib.d.ts +2 -0
  176. package/dist/graph/python-stdlib.d.ts.map +1 -0
  177. package/dist/graph/python-stdlib.js +53 -0
  178. package/dist/graph/python-stdlib.js.map +1 -0
  179. package/dist/graph/ts-morph-adapter.d.ts +65 -0
  180. package/dist/graph/ts-morph-adapter.d.ts.map +1 -0
  181. package/dist/graph/ts-morph-adapter.js +265 -0
  182. package/dist/graph/ts-morph-adapter.js.map +1 -0
  183. package/dist/graph/types.d.ts +37 -0
  184. package/dist/graph/types.d.ts.map +1 -0
  185. package/dist/graph/types.js +50 -0
  186. package/dist/graph/types.js.map +1 -0
  187. package/dist/index.d.ts +45 -0
  188. package/dist/index.d.ts.map +1 -0
  189. package/dist/index.js +51 -0
  190. package/dist/index.js.map +1 -0
  191. package/dist/infra/aws/aws-parse.d.ts +39 -0
  192. package/dist/infra/aws/aws-parse.d.ts.map +1 -0
  193. package/dist/infra/aws/aws-parse.js +149 -0
  194. package/dist/infra/aws/aws-parse.js.map +1 -0
  195. package/dist/infra/aws/aws.d.ts +6 -0
  196. package/dist/infra/aws/aws.d.ts.map +1 -0
  197. package/dist/infra/aws/aws.js +425 -0
  198. package/dist/infra/aws/aws.js.map +1 -0
  199. package/dist/infra/azure/azure-parse.d.ts +73 -0
  200. package/dist/infra/azure/azure-parse.d.ts.map +1 -0
  201. package/dist/infra/azure/azure-parse.js +178 -0
  202. package/dist/infra/azure/azure-parse.js.map +1 -0
  203. package/dist/infra/azure/azure.d.ts +12 -0
  204. package/dist/infra/azure/azure.d.ts.map +1 -0
  205. package/dist/infra/azure/azure.js +673 -0
  206. package/dist/infra/azure/azure.js.map +1 -0
  207. package/dist/infra/cloudflare/cloudflare.d.ts +50 -0
  208. package/dist/infra/cloudflare/cloudflare.d.ts.map +1 -0
  209. package/dist/infra/cloudflare/cloudflare.js +382 -0
  210. package/dist/infra/cloudflare/cloudflare.js.map +1 -0
  211. package/dist/infra/cloudflare/wrangler-parse.d.ts +6 -0
  212. package/dist/infra/cloudflare/wrangler-parse.d.ts.map +1 -0
  213. package/dist/infra/cloudflare/wrangler-parse.js +278 -0
  214. package/dist/infra/cloudflare/wrangler-parse.js.map +1 -0
  215. package/dist/infra/compose/compose-parse.d.ts +24 -0
  216. package/dist/infra/compose/compose-parse.d.ts.map +1 -0
  217. package/dist/infra/compose/compose-parse.js +77 -0
  218. package/dist/infra/compose/compose-parse.js.map +1 -0
  219. package/dist/infra/compose/compose.d.ts +12 -0
  220. package/dist/infra/compose/compose.d.ts.map +1 -0
  221. package/dist/infra/compose/compose.js +201 -0
  222. package/dist/infra/compose/compose.js.map +1 -0
  223. package/dist/infra/convex/convex-parse.d.ts +28 -0
  224. package/dist/infra/convex/convex-parse.d.ts.map +1 -0
  225. package/dist/infra/convex/convex-parse.js +74 -0
  226. package/dist/infra/convex/convex-parse.js.map +1 -0
  227. package/dist/infra/convex/convex.d.ts +18 -0
  228. package/dist/infra/convex/convex.d.ts.map +1 -0
  229. package/dist/infra/convex/convex.js +199 -0
  230. package/dist/infra/convex/convex.js.map +1 -0
  231. package/dist/infra/digitalocean/digitalocean.d.ts +33 -0
  232. package/dist/infra/digitalocean/digitalocean.d.ts.map +1 -0
  233. package/dist/infra/digitalocean/digitalocean.js +204 -0
  234. package/dist/infra/digitalocean/digitalocean.js.map +1 -0
  235. package/dist/infra/firebase/firebase.d.ts +43 -0
  236. package/dist/infra/firebase/firebase.d.ts.map +1 -0
  237. package/dist/infra/firebase/firebase.js +271 -0
  238. package/dist/infra/firebase/firebase.js.map +1 -0
  239. package/dist/infra/fly/fly-parse.d.ts +52 -0
  240. package/dist/infra/fly/fly-parse.d.ts.map +1 -0
  241. package/dist/infra/fly/fly-parse.js +114 -0
  242. package/dist/infra/fly/fly-parse.js.map +1 -0
  243. package/dist/infra/fly/fly.d.ts +15 -0
  244. package/dist/infra/fly/fly.d.ts.map +1 -0
  245. package/dist/infra/fly/fly.js +265 -0
  246. package/dist/infra/fly/fly.js.map +1 -0
  247. package/dist/infra/gcp/gcp-parse.d.ts +24 -0
  248. package/dist/infra/gcp/gcp-parse.d.ts.map +1 -0
  249. package/dist/infra/gcp/gcp-parse.js +123 -0
  250. package/dist/infra/gcp/gcp-parse.js.map +1 -0
  251. package/dist/infra/gcp/gcp.d.ts +10 -0
  252. package/dist/infra/gcp/gcp.d.ts.map +1 -0
  253. package/dist/infra/gcp/gcp.js +502 -0
  254. package/dist/infra/gcp/gcp.js.map +1 -0
  255. package/dist/infra/heroku/heroku-parse.d.ts +33 -0
  256. package/dist/infra/heroku/heroku-parse.d.ts.map +1 -0
  257. package/dist/infra/heroku/heroku-parse.js +63 -0
  258. package/dist/infra/heroku/heroku-parse.js.map +1 -0
  259. package/dist/infra/heroku/heroku.d.ts +24 -0
  260. package/dist/infra/heroku/heroku.d.ts.map +1 -0
  261. package/dist/infra/heroku/heroku.js +253 -0
  262. package/dist/infra/heroku/heroku.js.map +1 -0
  263. package/dist/infra/image-resolve.d.ts +56 -0
  264. package/dist/infra/image-resolve.d.ts.map +1 -0
  265. package/dist/infra/image-resolve.js +229 -0
  266. package/dist/infra/image-resolve.js.map +1 -0
  267. package/dist/infra/infra-step.d.ts +39 -0
  268. package/dist/infra/infra-step.d.ts.map +1 -0
  269. package/dist/infra/infra-step.js +57 -0
  270. package/dist/infra/infra-step.js.map +1 -0
  271. package/dist/infra/k8s/index.d.ts +78 -0
  272. package/dist/infra/k8s/index.d.ts.map +1 -0
  273. package/dist/infra/k8s/index.js +251 -0
  274. package/dist/infra/k8s/index.js.map +1 -0
  275. package/dist/infra/kamal/kamal-parse.d.ts +23 -0
  276. package/dist/infra/kamal/kamal-parse.d.ts.map +1 -0
  277. package/dist/infra/kamal/kamal-parse.js +66 -0
  278. package/dist/infra/kamal/kamal-parse.js.map +1 -0
  279. package/dist/infra/kamal/kamal.d.ts +17 -0
  280. package/dist/infra/kamal/kamal.d.ts.map +1 -0
  281. package/dist/infra/kamal/kamal.js +162 -0
  282. package/dist/infra/kamal/kamal.js.map +1 -0
  283. package/dist/infra/netlify/netlify.d.ts +33 -0
  284. package/dist/infra/netlify/netlify.d.ts.map +1 -0
  285. package/dist/infra/netlify/netlify.js +247 -0
  286. package/dist/infra/netlify/netlify.js.map +1 -0
  287. package/dist/infra/opentofu/opentofu.d.ts +3 -0
  288. package/dist/infra/opentofu/opentofu.d.ts.map +1 -0
  289. package/dist/infra/opentofu/opentofu.js +157 -0
  290. package/dist/infra/opentofu/opentofu.js.map +1 -0
  291. package/dist/infra/pulumi/pulumi-parse.d.ts +60 -0
  292. package/dist/infra/pulumi/pulumi-parse.d.ts.map +1 -0
  293. package/dist/infra/pulumi/pulumi-parse.js +155 -0
  294. package/dist/infra/pulumi/pulumi-parse.js.map +1 -0
  295. package/dist/infra/pulumi/pulumi.d.ts +23 -0
  296. package/dist/infra/pulumi/pulumi.d.ts.map +1 -0
  297. package/dist/infra/pulumi/pulumi.js +260 -0
  298. package/dist/infra/pulumi/pulumi.js.map +1 -0
  299. package/dist/infra/railway/railway-parse.d.ts +54 -0
  300. package/dist/infra/railway/railway-parse.d.ts.map +1 -0
  301. package/dist/infra/railway/railway-parse.js +200 -0
  302. package/dist/infra/railway/railway-parse.js.map +1 -0
  303. package/dist/infra/railway/railway.d.ts +19 -0
  304. package/dist/infra/railway/railway.d.ts.map +1 -0
  305. package/dist/infra/railway/railway.js +397 -0
  306. package/dist/infra/railway/railway.js.map +1 -0
  307. package/dist/infra/register.d.ts +2 -0
  308. package/dist/infra/register.d.ts.map +1 -0
  309. package/dist/infra/register.js +81 -0
  310. package/dist/infra/register.js.map +1 -0
  311. package/dist/infra/registry.d.ts +64 -0
  312. package/dist/infra/registry.d.ts.map +1 -0
  313. package/dist/infra/registry.js +205 -0
  314. package/dist/infra/registry.js.map +1 -0
  315. package/dist/infra/relevance.d.ts +16 -0
  316. package/dist/infra/relevance.d.ts.map +1 -0
  317. package/dist/infra/relevance.js +132 -0
  318. package/dist/infra/relevance.js.map +1 -0
  319. package/dist/infra/render/render-parse.d.ts +76 -0
  320. package/dist/infra/render/render-parse.d.ts.map +1 -0
  321. package/dist/infra/render/render-parse.js +155 -0
  322. package/dist/infra/render/render-parse.js.map +1 -0
  323. package/dist/infra/render/render.d.ts +11 -0
  324. package/dist/infra/render/render.d.ts.map +1 -0
  325. package/dist/infra/render/render.js +303 -0
  326. package/dist/infra/render/render.js.map +1 -0
  327. package/dist/infra/sst/sst-parse.d.ts +25 -0
  328. package/dist/infra/sst/sst-parse.d.ts.map +1 -0
  329. package/dist/infra/sst/sst-parse.js +45 -0
  330. package/dist/infra/sst/sst-parse.js.map +1 -0
  331. package/dist/infra/sst/sst.d.ts +19 -0
  332. package/dist/infra/sst/sst.d.ts.map +1 -0
  333. package/dist/infra/sst/sst.js +236 -0
  334. package/dist/infra/sst/sst.js.map +1 -0
  335. package/dist/infra/supabase/supabase-parse.d.ts +34 -0
  336. package/dist/infra/supabase/supabase-parse.d.ts.map +1 -0
  337. package/dist/infra/supabase/supabase-parse.js +113 -0
  338. package/dist/infra/supabase/supabase-parse.js.map +1 -0
  339. package/dist/infra/supabase/supabase.d.ts +15 -0
  340. package/dist/infra/supabase/supabase.d.ts.map +1 -0
  341. package/dist/infra/supabase/supabase.js +198 -0
  342. package/dist/infra/supabase/supabase.js.map +1 -0
  343. package/dist/infra/terraform/hcl-parse.d.ts +34 -0
  344. package/dist/infra/terraform/hcl-parse.d.ts.map +1 -0
  345. package/dist/infra/terraform/hcl-parse.js +203 -0
  346. package/dist/infra/terraform/hcl-parse.js.map +1 -0
  347. package/dist/infra/terraform/terraform.d.ts +14 -0
  348. package/dist/infra/terraform/terraform.d.ts.map +1 -0
  349. package/dist/infra/terraform/terraform.js +264 -0
  350. package/dist/infra/terraform/terraform.js.map +1 -0
  351. package/dist/infra/types.d.ts +151 -0
  352. package/dist/infra/types.d.ts.map +1 -0
  353. package/dist/infra/types.js +14 -0
  354. package/dist/infra/types.js.map +1 -0
  355. package/dist/infra/vercel/vercel-parse.d.ts +70 -0
  356. package/dist/infra/vercel/vercel-parse.d.ts.map +1 -0
  357. package/dist/infra/vercel/vercel-parse.js +194 -0
  358. package/dist/infra/vercel/vercel-parse.js.map +1 -0
  359. package/dist/infra/vercel/vercel.d.ts +30 -0
  360. package/dist/infra/vercel/vercel.d.ts.map +1 -0
  361. package/dist/infra/vercel/vercel.js +523 -0
  362. package/dist/infra/vercel/vercel.js.map +1 -0
  363. package/dist/infra/walk.d.ts +36 -0
  364. package/dist/infra/walk.d.ts.map +1 -0
  365. package/dist/infra/walk.js +85 -0
  366. package/dist/infra/walk.js.map +1 -0
  367. package/dist/safety.d.ts +13 -0
  368. package/dist/safety.d.ts.map +1 -0
  369. package/dist/safety.js +67 -0
  370. package/dist/safety.js.map +1 -0
  371. package/dist/types.d.ts +47 -0
  372. package/dist/types.d.ts.map +1 -0
  373. package/dist/types.js +111 -0
  374. package/dist/types.js.map +1 -0
  375. package/dist/version.d.ts +2 -0
  376. package/dist/version.d.ts.map +1 -0
  377. package/dist/version.js +35 -0
  378. package/dist/version.js.map +1 -0
  379. package/package.json +65 -0
@@ -0,0 +1,765 @@
1
+ // the FastStream (+ Taskiq) FrameworkAdapter. Net-new adapter
2
+ // following the FastAPI / Flask template, reusing the shared
3
+ // Python core (py-ast + parsePythonScope). FastStream is the cleanest
4
+ // declarative pub/sub in Python: a broker object + `@broker.subscriber("topic")` /
5
+ // `@broker.publisher("topic")` decorators wire event-driven services through
6
+ // message brokers (Kafka / RabbitMQ / NATS / Redis). Taskiq is its distributed
7
+ // task-queue sibling (`@broker.task` def + `some_task.kiq(...)` enqueue).
8
+ //
9
+ // We read this surface STATICALLY (install-free, never-store-source — a pure
10
+ // syntactic Pyright parse; never executes repo code) and persist only the derived
11
+ // groups/edges/roles:
12
+ //
13
+ // * detect() — the `faststream` and/or `taskiq` dependency (either alone
14
+ // is a valid match; both raises confidence). Shallow nested
15
+ // scan for a `backend/`|`server/` package (mirrors ).
16
+ // * groupingPrior — one FrameworkGroup per FastStream *Router (KafkaRouter,
17
+ // RabbitRouter, …): its defining file + the files that
18
+ // register subscribers/publishers to it, so a repo that
19
+ // organises handlers behind routers splits into per-router
20
+ // subsystems (the FastAPI router / Flask blueprint mechanism).
21
+ // Optional — a broker-only app (no routers) yields no groups
22
+ // and stays in directory grouping, byte-for-byte unchanged.
23
+ // * syntheticEdges — the ASYNC wiring the import graph structurally cannot see:
24
+ // (a) PUB/SUB — a module that publishes to topic T is linked
25
+ // to every module subscribing to T (kind 'publishes',
26
+ // publisher-file → subscriber-file, topic in metadata),
27
+ // (b) Taskiq `.kiq()` enqueue (kind 'publishes', caller →
28
+ // task file — the async decoupling the import graph would
29
+ // render as a plain call), and
30
+ // (c) `broker.include_router(router)` mounting (kind 'calls').
31
+ // * roleTags — FastStream app / broker / router objects → `gateway` (the
32
+ // system's MESSAGE entrypoint, the boundary where external
33
+ // events enter — the pub/sub analogue of FastAPI's app →
34
+ // gateway request entrypoint); a `@broker.subscriber` /
35
+ // `@broker.publisher` handler → `service` (an event-driven
36
+ // microservice — FastStream's own positioning); a Taskiq
37
+ // `@broker.task` → `job` (discrete queue-triggered own-code,
38
+ // matching the Celery-task precedent in ). METADATA
39
+ // onto the LOCKED MODULE_KINDS enum; never a new kind (only
40
+ // `role` renders; the module's `kind` is unchanged).
41
+ //
42
+ // Kind justification (the parent-task brief asked to pick locked kinds + justify):
43
+ // gateway/service/job are the three request/message-plane MODULE_KINDS. The broker
44
+ // is where messages CROSS the trust boundary → gateway (Linear's issue text floated
45
+ // `service` for the broker; the message-entrypoint reading is more faithful and
46
+ // matches FastAPI). A subscriber is long-lived event-consuming own-code → `service`
47
+ // (not a discrete `job`); a Taskiq task IS a discrete enqueued work unit → `job`.
48
+ // This is a deliberate, meaningful split of all three kinds; NO new kind is added.
49
+ //
50
+ // Pub/sub edge DIRECTION + verb: a single directed edge publisher-file →
51
+ // subscriber-file, kind 'publishes'. The topic is the MATCH KEY, not a node
52
+ // (FrameworkEdge endpoints must be code file ids), so it can't be an intermediate.
53
+ // 'publishes' points in the direction the message travels (producer → consumer),
54
+ // consistent with the Celery-enqueue precedent (enqueuer `publishes` to
55
+ // worker) and the infra queue precedent (Cloudflare: producer `publishes`, ARP-…).
56
+ // `subscribes` (the reverse-direction verb) is NOT used for a producer→consumer
57
+ // flow edge — it would invert the data-flow arrow; the subscriber side is captured
58
+ // by roleTags + the edge's `topic` metadata.
59
+ //
60
+ // Truly-dynamic topic names (a variable / f-string, not a static literal) and
61
+ // unresolvable kiq / include_router targets DEGRADE + LOG — no silent caps.
62
+ import { existsSync, readdirSync } from 'node:fs';
63
+ import { join } from 'node:path';
64
+ import { clampConfidence, resolveBase } from '../detect-util.js';
65
+ import { readPythonDeps } from '../../graph/python-manifest.js';
66
+ import { parsePythonScope } from '../python/analyze.js';
67
+ import { callCallee, keywordArg, memberChain, nameValue, positionalArgs, stringValue, PN, } from '../python/py-ast.js';
68
+ /** Gather the signal set for a single root dir (reads manifests only). */
69
+ export function gatherFastStreamSignals(baseDir) {
70
+ const deps = readPythonDeps(baseDir);
71
+ return {
72
+ hasFastStream: deps.has('faststream'),
73
+ // `taskiq` core OR any `taskiq-*` broker/result-backend (taskiq-aio-pika,
74
+ // taskiq-redis, taskiq-nats, …) — the task pass is worth running for either.
75
+ hasTaskiq: deps.has('taskiq') || [...deps].some((d) => d.startsWith('taskiq-')),
76
+ };
77
+ }
78
+ // Non-source dirs the nested scan skips (cheap + can't hold a first-party manifest).
79
+ const NESTED_SKIP_DIRS = new Set([
80
+ 'node_modules',
81
+ 'dist',
82
+ 'build',
83
+ 'out',
84
+ 'coverage',
85
+ 'venv',
86
+ '.venv',
87
+ '__pycache__',
88
+ 'site-packages',
89
+ ]);
90
+ /** True if `dir` holds a Python manifest worth scanning for deps. */
91
+ function hasPythonManifest(dir) {
92
+ return (existsSync(join(dir, 'pyproject.toml')) ||
93
+ existsSync(join(dir, 'setup.py')) ||
94
+ existsSync(join(dir, 'setup.cfg')) ||
95
+ existsSync(join(dir, 'requirements.txt')));
96
+ }
97
+ /**
98
+ * Immediate subdirs (depth 1) that contain a Python manifest — the shallow search
99
+ * for a nested backend (`backend/` | `server/` | `worker/`). Sorted, so the
100
+ * first-match pick is deterministic; skips dot-dirs + non-source dirs to stay cheap.
101
+ */
102
+ function shallowManifestSubdirs(base) {
103
+ let entries;
104
+ try {
105
+ entries = readdirSync(base, { withFileTypes: true });
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ const out = [];
111
+ for (const e of entries) {
112
+ if (!e.isDirectory() || e.name.startsWith('.') || NESTED_SKIP_DIRS.has(e.name))
113
+ continue;
114
+ if (hasPythonManifest(join(base, e.name)))
115
+ out.push(e.name);
116
+ }
117
+ return out.sort();
118
+ }
119
+ /**
120
+ * Decide FastStream/Taskiq from the signal set. EITHER `faststream` (pub/sub) or
121
+ * `taskiq` (task queue) is a valid match; both present raises confidence. Returns
122
+ * null → generic-Python fallthrough, byte-for-byte unchanged. The metadata GATES
123
+ * the two analysis passes (faststream → pub/sub; taskiq → tasks), mirroring how
124
+ * FastAPI gates its Celery pass.
125
+ */
126
+ export function scoreFastStream(s, rootPath = '') {
127
+ if (!s.hasFastStream && !s.hasTaskiq)
128
+ return null;
129
+ let confidence = 0.8;
130
+ if (s.hasFastStream && s.hasTaskiq)
131
+ confidence += 0.1;
132
+ return {
133
+ adapter: 'faststream',
134
+ confidence: clampConfidence(confidence),
135
+ rootPath,
136
+ metadata: {
137
+ faststream: s.hasFastStream,
138
+ taskiq: s.hasTaskiq,
139
+ signals: { faststream: s.hasFastStream, taskiq: s.hasTaskiq },
140
+ },
141
+ };
142
+ }
143
+ // Collapse priority when one FILE carries several roles, AND downstream when
144
+ // several files of different roles land in one module. The construction entrypoints
145
+ // (app/broker) outrank the concrete handlers, which outrank the bare aggregator
146
+ // router — mirroring FastAPI's app > route-handler > router: a single-file app
147
+ // (`FastStream(broker)` + a `@broker.subscriber` in the same module) reads as the
148
+ // app entrypoint; a router file that ALSO declares a handler reads as the handler
149
+ // (the informative leaf) rather than the aggregator; a pure aggregator router (its
150
+ // handlers all elsewhere) reads as `router`. A subscriber outranks a publisher —
151
+ // where a message is consumed is a more informative label than where one leaves.
152
+ const ROLE_PRIORITY = {
153
+ app: 9,
154
+ broker: 8,
155
+ subscriber: 7,
156
+ publisher: 6,
157
+ router: 5,
158
+ task: 4,
159
+ };
160
+ const ROLE_KIND = {
161
+ app: 'gateway',
162
+ broker: 'gateway',
163
+ router: 'gateway',
164
+ subscriber: 'service',
165
+ publisher: 'service',
166
+ task: 'job',
167
+ };
168
+ // The FastStream app entrypoint constructor.
169
+ const APP_CTOR = 'FastStream';
170
+ // FastStream router constructors (a KNOWN set — a suffix match on `*Router` would
171
+ // wrongly catch FastAPI's `APIRouter` in a FastAPI+FastStream polyglot repo). This
172
+ // set includes the broker-integration `faststream.<broker>.fastapi` routers, which
173
+ // share these class names.
174
+ const ROUTER_CTORS = new Set(['KafkaRouter', 'RabbitRouter', 'NatsRouter', 'RedisRouter']);
175
+ // Known destination keyword args across FastStream brokers (Kafka topic, NATS
176
+ // subject, Redis channel/list/stream, RabbitMQ queue) — used to read a topic name
177
+ // from `@broker.subscriber(channel="…")` / `broker.publish(m, subject="…")` forms.
178
+ const TOPIC_KWARGS = ['topic', 'subject', 'channel', 'queue', 'list', 'stream'];
179
+ /**
180
+ * A broker constructor: FastStream brokers (KafkaBroker / RabbitBroker /
181
+ * NatsBroker / RedisBroker) AND Taskiq brokers (AioPikaBroker / ListQueueBroker /
182
+ * RedisStreamBroker / InMemoryBroker / PubSubBroker …) all end in `Broker`. A
183
+ * suffix match is safe here: detect() already gated on the faststream/taskiq dep,
184
+ * and we only ACT on a broker var via framework-specific methods (`.subscriber` /
185
+ * `.publisher` / `.task` / `.publish` / `.include_router`), so a stray unrelated
186
+ * `*Broker` yields nothing. FastStream vs Taskiq usage is disambiguated by method
187
+ * name (subscriber/publisher vs task/kiq), not by broker class.
188
+ */
189
+ function isBrokerCtor(name) {
190
+ return name.endsWith('Broker');
191
+ }
192
+ // Memoized on the FrameworkContext OBJECT (not repoDir) so groupingPrior +
193
+ // syntheticEdges + roleTags share ONE parse, while the merge walk's per-checkpoint
194
+ // ctx gets a fresh analysis — no cross-tree staleness. Mirrors fastapi / flask.
195
+ const ANALYSIS_CACHE = new WeakMap();
196
+ function slugify(s) {
197
+ return s
198
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
199
+ .toLowerCase()
200
+ .replace(/[^a-z0-9]+/g, '-')
201
+ .replace(/^-+|-+$/g, '');
202
+ }
203
+ function humanize(s) {
204
+ const words = s
205
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
206
+ .replace(/[_\-.]+/g, ' ')
207
+ .trim()
208
+ .split(/\s+/)
209
+ .filter(Boolean);
210
+ if (words.length === 0)
211
+ return s;
212
+ return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
213
+ }
214
+ // The label source for a router definer file — its basename, but a package-level
215
+ // router lives in `<pkg>/__init__.py`, whose basename ('__init__') is useless, so
216
+ // fall back to the package dir name (`app/orders/__init__.py` → 'orders'). Generic
217
+ // definer basenames (`router`/`broker`) get the same treatment.
218
+ function moduleLabelSource(fileId) {
219
+ const parts = fileId.split('/');
220
+ const last = (parts[parts.length - 1] ?? fileId).replace(/\.[^.]+$/, '');
221
+ if ((last === '__init__' || last === 'router' || last === 'broker') && parts.length >= 2) {
222
+ return parts[parts.length - 2];
223
+ }
224
+ return last;
225
+ }
226
+ function dirSegment(fileId) {
227
+ const slash = fileId.indexOf('/');
228
+ return slash > 0 ? slugify(fileId.slice(0, slash)) : 'root';
229
+ }
230
+ // Keep the highest-priority candidate role per file (deterministic, lexical
231
+ // tiebreak) — mirrors the fastapi/flask addRole + the contribute-step's collapse.
232
+ function addRole(map, fileId, role) {
233
+ const cur = map.get(fileId);
234
+ if (cur === undefined ||
235
+ ROLE_PRIORITY[role] > ROLE_PRIORITY[cur] ||
236
+ (ROLE_PRIORITY[role] === ROLE_PRIORITY[cur] && role < cur)) {
237
+ map.set(fileId, role);
238
+ }
239
+ }
240
+ function addEdge(edges, from, to, kind, relation, extra) {
241
+ if (from === to)
242
+ return; // intra-file wiring collapses; the step drops self-edges too
243
+ const key = `${from}→${to}:${kind}`;
244
+ if (!edges.has(key)) {
245
+ edges.set(key, {
246
+ source: from,
247
+ target: to,
248
+ kind,
249
+ metadata: { framework: 'faststream', relation, ...extra },
250
+ });
251
+ }
252
+ }
253
+ // The constructor name of an `x = Ctor(...)` assignment RHS (last chain segment, so
254
+ // both `KafkaBroker(...)` and `faststream.kafka.KafkaBroker(...)` read as
255
+ // 'KafkaBroker'), or undefined.
256
+ function assignedCtorName(rhs) {
257
+ if (rhs.nodeType !== PN.Call)
258
+ return undefined;
259
+ const callee = callCallee(rhs);
260
+ if (!callee)
261
+ return undefined;
262
+ return callee.path.length ? callee.path[callee.path.length - 1] : callee.root;
263
+ }
264
+ // A decorator's callee chain (`@broker.subscriber("t")` → root 'broker', path
265
+ // ['subscriber']; `@broker.task` → root 'broker', path ['task']). The decorator
266
+ // expr is either a call (with args) or a bare name/attribute.
267
+ function decoratorChain(deco) {
268
+ const expr = deco.d.expr;
269
+ if (expr.nodeType === PN.Call)
270
+ return callCallee(expr);
271
+ return memberChain(expr);
272
+ }
273
+ // A single topic-name arg → its static string, unwrapping a broker-specific
274
+ // destination object (`RabbitQueue("orders")` / `PubSub("ch")` → its first
275
+ // positional string). Undefined when the arg isn't a static literal.
276
+ function topicFromExpr(expr) {
277
+ if (!expr)
278
+ return undefined;
279
+ const direct = stringValue(expr);
280
+ if (direct !== undefined)
281
+ return direct;
282
+ if (expr.nodeType === PN.Call) {
283
+ return stringValue(positionalArgs(expr)[0]);
284
+ }
285
+ return undefined;
286
+ }
287
+ /**
288
+ * Collect the static topic name(s) named by a call, scanning positional args in
289
+ * `[positionalStart, positionalEnd)` plus the known destination keyword args.
290
+ * A `subscriber`/`publisher` DECORATOR scans all positionals (Kafka allows several
291
+ * topics: `@broker.subscriber("t1","t2")`); imperative `publish(message, dest, …)`
292
+ * scans ONLY the single dest slot (index 1) — a later positional (e.g. a Kafka
293
+ * partition key passed positionally) is NOT a topic. `dynamic` = a topic arg was
294
+ * present but NONE resolved to a static literal (→ degrade + log).
295
+ */
296
+ function collectTopics(call, positionalStart, positionalEnd = Infinity) {
297
+ const topics = new Set();
298
+ const positionals = positionalArgs(call);
299
+ let sawArg = false;
300
+ for (let i = positionalStart; i < Math.min(positionals.length, positionalEnd); i++) {
301
+ sawArg = true;
302
+ const t = topicFromExpr(positionals[i]);
303
+ if (t)
304
+ topics.add(t);
305
+ }
306
+ for (const kw of TOPIC_KWARGS) {
307
+ const arg = keywordArg(call, kw);
308
+ if (arg === undefined)
309
+ continue;
310
+ sawArg = true;
311
+ const t = topicFromExpr(arg);
312
+ if (t)
313
+ topics.add(t);
314
+ }
315
+ return { topics: [...topics].sort(), dynamic: topics.size === 0 && sawArg };
316
+ }
317
+ // The topic set named by a `@broker.subscriber(...)`/`@broker.publisher(...)`
318
+ // decorator (a bare, uncalled decorator names no topic).
319
+ function decoratorTopics(deco) {
320
+ const expr = deco.d.expr;
321
+ if (expr.nodeType !== PN.Call)
322
+ return { topics: [], dynamic: false };
323
+ return collectTopics(expr, 0);
324
+ }
325
+ // Router group id source: the router var name (when not the generic `router`/
326
+ // `broker`), else the definer's package/basename.
327
+ function routerGroupName(varName, definerFileId) {
328
+ if (varName && varName !== 'router' && varName !== 'broker')
329
+ return varName;
330
+ return moduleLabelSource(definerFileId);
331
+ }
332
+ // Assign each router its final, collision-free group id ORDER-INDEPENDENTLY:
333
+ // process seeds by key (definerFileId#varName) so the SMALLEST wins the bare slug,
334
+ // and later collisions take a `-<dirSegment>` then `-<n>` suffix. The order is
335
+ // stable (not an iteration index), so the id set is identical run-to-run — the
336
+ // snapshot grouping-stability invariant. (Mirrors flask's assignBlueprintGroups.)
337
+ function assignRouterGroups(seeds) {
338
+ const taken = new Set();
339
+ const ordered = [...seeds].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
340
+ const groups = [];
341
+ for (const seed of ordered) {
342
+ let id = seed.baseSlug;
343
+ if (taken.has(id))
344
+ id = `${seed.baseSlug}-${dirSegment(seed.definerFileId)}`;
345
+ let n = 2;
346
+ while (taken.has(id))
347
+ id = `${seed.baseSlug}-${n++}`;
348
+ taken.add(id);
349
+ groups.push({ id, label: seed.label, fileIds: [...seed.fileIds].sort() });
350
+ }
351
+ return groups.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
352
+ }
353
+ function analyzeFastStream(ctx) {
354
+ const { parsed } = parsePythonScope(ctx);
355
+ const faststream = ctx.match.metadata?.faststream === true;
356
+ const taskiq = ctx.match.metadata?.taskiq === true;
357
+ // Pass 1 — per-file app / broker / router object variables (file-scoped, which
358
+ // matches how these module-level singletons are used).
359
+ const appVarsByFile = new Map();
360
+ const brokerVarsByFile = new Map();
361
+ const routerVarsByFile = new Map();
362
+ for (const [id, file] of parsed) {
363
+ const appVars = new Set();
364
+ const brokerVars = new Set();
365
+ const routerVars = new Set();
366
+ for (const a of file.nodes.assignments) {
367
+ const target = nameValue(a.d.leftExpr);
368
+ if (!target)
369
+ continue;
370
+ const ctor = assignedCtorName(a.d.rightExpr);
371
+ if (!ctor)
372
+ continue;
373
+ if (ctor === APP_CTOR)
374
+ appVars.add(target);
375
+ else if (ROUTER_CTORS.has(ctor))
376
+ routerVars.add(target);
377
+ else if (isBrokerCtor(ctor))
378
+ brokerVars.add(target);
379
+ }
380
+ appVarsByFile.set(id, appVars);
381
+ brokerVarsByFile.set(id, brokerVars);
382
+ routerVarsByFile.set(id, routerVars);
383
+ }
384
+ // Definer-file sets, so an IMPORTED broker/app (`from app.broker import broker` +
385
+ // `@broker.subscriber` elsewhere — the common layout) is recognized cross-file.
386
+ const appDefinerFiles = new Set();
387
+ const brokerDefinerFiles = new Set();
388
+ for (const [f, v] of appVarsByFile)
389
+ if (v.size > 0)
390
+ appDefinerFiles.add(f);
391
+ for (const [f, v] of brokerVarsByFile)
392
+ if (v.size > 0)
393
+ brokerDefinerFiles.add(f);
394
+ // Router seeds (grouping) + definer index (cross-file decorator attribution).
395
+ // Routers are a FastStream construct → only seeded on the faststream signal, so a
396
+ // Taskiq-only repo yields no groups (and never mis-attributes a `.task` to one).
397
+ const seedsByKey = new Map();
398
+ const routersByDefiner = new Map();
399
+ if (faststream) {
400
+ for (const [id, routerVars] of routerVarsByFile) {
401
+ for (const varName of routerVars) {
402
+ const key = `${id}#${varName}`;
403
+ const name = routerGroupName(varName, id);
404
+ seedsByKey.set(key, {
405
+ key,
406
+ definerFileId: id,
407
+ varName,
408
+ baseSlug: slugify(name) || 'router',
409
+ label: humanize(name) || name,
410
+ fileIds: new Set([id]),
411
+ });
412
+ const list = routersByDefiner.get(id) ?? [];
413
+ list.push({ varName, key });
414
+ routersByDefiner.set(id, list);
415
+ }
416
+ }
417
+ }
418
+ const roleByFile = new Map();
419
+ const edges = new Map();
420
+ const pubTopicsByFile = new Map(); // file → topics it publishes
421
+ const subTopicsByFile = new Map(); // file → topics it subscribes
422
+ const diag = {
423
+ dynamicTopics: new Set(),
424
+ unresolvedKiq: new Set(),
425
+ unresolvedMounts: new Set(),
426
+ ambiguousRouters: new Set(),
427
+ };
428
+ function recordTopics(map, file, topics) {
429
+ if (topics.length === 0)
430
+ return;
431
+ let set = map.get(file);
432
+ if (!set) {
433
+ set = new Set();
434
+ map.set(file, set);
435
+ }
436
+ for (const t of topics)
437
+ set.add(t);
438
+ }
439
+ // A FastStream broker is a `*Broker` that is actually USED by FastStream (a
440
+ // `.subscriber`/`.publisher`/`.publish`/`.include_router` resolves to it) — as
441
+ // opposed to a Taskiq `*Broker` (used only by `.task`/`.kiq`), which shares the
442
+ // class-name suffix. Populated during the walk; consumed by the object-role pass
443
+ // so a Taskiq broker in a mixed faststream+taskiq repo is NOT tagged `gateway`.
444
+ const faststreamBrokerFiles = new Set();
445
+ // Classify a `@<root>.…` / `<root>.…()` object root: which router (by key), the
446
+ // broker (with its definer file), or the app — resolving same-file vars first,
447
+ // then import bindings to a definer/app file. Undefined ⇒ not a recognized object.
448
+ function resolveBrokerLike(thisFile, root, binds) {
449
+ if (routerVarsByFile.get(thisFile)?.has(root))
450
+ return { kind: 'router', key: `${thisFile}#${root}` };
451
+ if (brokerVarsByFile.get(thisFile)?.has(root))
452
+ return { kind: 'broker', definer: thisFile };
453
+ if (appVarsByFile.get(thisFile)?.has(root))
454
+ return { kind: 'app' };
455
+ const target = binds.get(root);
456
+ if (target) {
457
+ const routers = routersByDefiner.get(target);
458
+ if (routers && routers.length > 0) {
459
+ // Prefer the router whose var name matches the imported local name; else, if
460
+ // the definer holds exactly one router, it's unambiguous; else DEGRADE.
461
+ const exact = routers.find((r) => r.varName === root);
462
+ if (exact)
463
+ return { kind: 'router', key: exact.key };
464
+ if (routers.length === 1)
465
+ return { kind: 'router', key: routers[0].key };
466
+ diag.ambiguousRouters.add(`${thisFile}: @${root}.… (multiple routers in ${target})`);
467
+ return undefined;
468
+ }
469
+ if (brokerDefinerFiles.has(target))
470
+ return { kind: 'broker', definer: target };
471
+ if (appDefinerFiles.has(target))
472
+ return { kind: 'app' };
473
+ }
474
+ return undefined;
475
+ }
476
+ // Mark the broker a faststream usage resolved to as FastStream-used (drives the
477
+ // gateway role). A no-op for router/app results.
478
+ function markFastStreamBroker(resolved) {
479
+ if (resolved?.kind === 'broker')
480
+ faststreamBrokerFiles.add(resolved.definer);
481
+ }
482
+ for (const [id, file] of parsed) {
483
+ const binds = file.bindings;
484
+ // --- Decorators: subscriber / publisher (faststream) + task (taskiq).
485
+ for (const fn of file.nodes.functions) {
486
+ for (const deco of fn.d.decorators) {
487
+ const chain = decoratorChain(deco);
488
+ if (!chain || chain.path.length !== 1)
489
+ continue;
490
+ const method = chain.path[0];
491
+ if (faststream && (method === 'subscriber' || method === 'publisher')) {
492
+ const resolved = resolveBrokerLike(id, chain.root, binds);
493
+ if (!resolved)
494
+ continue; // not a recognized broker/router object
495
+ markFastStreamBroker(resolved);
496
+ const topics = decoratorTopics(deco);
497
+ if (topics.dynamic)
498
+ diag.dynamicTopics.add(`${id}: @${chain.root}.${method}(<dynamic>)`);
499
+ if (method === 'subscriber') {
500
+ addRole(roleByFile, id, 'subscriber');
501
+ recordTopics(subTopicsByFile, id, topics.topics);
502
+ }
503
+ else {
504
+ addRole(roleByFile, id, 'publisher');
505
+ recordTopics(pubTopicsByFile, id, topics.topics);
506
+ }
507
+ // A handler decorated on a ROUTER joins that router's subsystem.
508
+ if (resolved.kind === 'router')
509
+ seedsByKey.get(resolved.key)?.fileIds.add(id);
510
+ }
511
+ else if (taskiq && method === 'task') {
512
+ // `@broker.task` (bare or called) — unambiguously Taskiq (FastStream
513
+ // brokers have no `.task`). Gate on a recognized broker object.
514
+ if (resolveBrokerLike(id, chain.root, binds))
515
+ addRole(roleByFile, id, 'task');
516
+ }
517
+ }
518
+ }
519
+ // --- Assigned publisher objects: `pub = broker.publisher("t")` (a publisher
520
+ // used imperatively via `pub.publish(...)`; the topic is fixed at construction).
521
+ if (faststream) {
522
+ for (const a of file.nodes.assignments) {
523
+ const rhs = a.d.rightExpr;
524
+ if (rhs.nodeType !== PN.Call)
525
+ continue;
526
+ const callee = callCallee(rhs);
527
+ if (!callee || callee.path.length !== 1 || callee.path[0] !== 'publisher')
528
+ continue;
529
+ const resolved = resolveBrokerLike(id, callee.root, binds);
530
+ if (!resolved)
531
+ continue;
532
+ markFastStreamBroker(resolved);
533
+ const topics = collectTopics(rhs, 0);
534
+ if (topics.dynamic)
535
+ diag.dynamicTopics.add(`${id}: ${callee.root}.publisher(<dynamic>)`);
536
+ addRole(roleByFile, id, 'publisher');
537
+ recordTopics(pubTopicsByFile, id, topics.topics);
538
+ if (resolved.kind === 'router')
539
+ seedsByKey.get(resolved.key)?.fileIds.add(id);
540
+ }
541
+ }
542
+ // --- Calls: imperative publish + include_router (faststream) · kiq (taskiq).
543
+ for (const call of file.nodes.calls) {
544
+ const callee = callCallee(call);
545
+ if (!callee || callee.path.length !== 1)
546
+ continue; // want `obj.method(...)`
547
+ const method = callee.path[0];
548
+ const obj = callee.root;
549
+ if (faststream && method === 'publish') {
550
+ // `broker.publish(message, "topic")` — imperative publish.
551
+ const resolved = resolveBrokerLike(id, obj, binds);
552
+ if (!resolved)
553
+ continue;
554
+ markFastStreamBroker(resolved);
555
+ addRole(roleByFile, id, 'publisher');
556
+ const topics = collectTopics(call, 1, 2); // only the single dest slot
557
+ if (topics.dynamic)
558
+ diag.dynamicTopics.add(`${id}: ${obj}.publish(<dynamic>)`);
559
+ recordTopics(pubTopicsByFile, id, topics.topics);
560
+ }
561
+ else if (faststream && method === 'include_router') {
562
+ // `broker.include_router(router)` — mounting (a plain call in the diagram).
563
+ const resolvedObj = resolveBrokerLike(id, obj, binds);
564
+ if (!resolvedObj)
565
+ continue;
566
+ markFastStreamBroker(resolvedObj);
567
+ const arg = positionalArgs(call)[0];
568
+ const argRoot = arg ? memberChain(arg)?.root : undefined;
569
+ const target = argRoot
570
+ ? routerVarsByFile.get(id)?.has(argRoot)
571
+ ? id // same-file router → self-edge (dropped by addEdge)
572
+ : binds.get(argRoot)
573
+ : undefined;
574
+ if (target)
575
+ addEdge(edges, id, target, 'calls', 'include-router');
576
+ else
577
+ diag.unresolvedMounts.add(`${id}: ${obj}.include_router(…)`);
578
+ }
579
+ else if (taskiq && method === 'kiq') {
580
+ // `some_task.kiq(...)` — the callee root is the imported task function; the
581
+ // enqueue is the async decoupling the import graph would render as a call.
582
+ const target = binds.get(obj);
583
+ if (target)
584
+ addEdge(edges, id, target, 'publishes', 'taskiq-kiq');
585
+ else
586
+ diag.unresolvedKiq.add(`${id}: ${obj}.kiq(…)`);
587
+ }
588
+ }
589
+ }
590
+ // --- Object files are gateways (the message entrypoint), even when their
591
+ // handlers live elsewhere. Deferred to a post-walk pass because the `broker` role
592
+ // is gated on the broker being FastStream-used (`faststreamBrokerFiles`, filled by
593
+ // the walk above) — a Taskiq broker in a mixed repo is a task dispatcher, not a
594
+ // message gateway, so it stays unroled. app (FastStream) + router (KafkaRouter …)
595
+ // are unambiguously FastStream, so they need only the faststream gate. addRole is
596
+ // priority-ordered, so combining these with the handler roles is order-independent.
597
+ if (faststream) {
598
+ for (const id of parsed.keys()) {
599
+ if (appVarsByFile.get(id).size > 0)
600
+ addRole(roleByFile, id, 'app');
601
+ if (routerVarsByFile.get(id).size > 0)
602
+ addRole(roleByFile, id, 'router');
603
+ if (brokerVarsByFile.get(id).size > 0 && faststreamBrokerFiles.has(id)) {
604
+ addRole(roleByFile, id, 'broker');
605
+ }
606
+ }
607
+ }
608
+ // --- Pub/sub topic-matching edges: publisher-file → subscriber-file (kind
609
+ // 'publishes'), one edge per (publisher, subscriber) pair that SHARES a topic;
610
+ // the shared topic name(s) ride in metadata. Iterate topic → publishers ×
611
+ // subscribers, all sorted, so the aggregation is order-independent.
612
+ const topicIndex = new Map();
613
+ for (const [file, topics] of pubTopicsByFile) {
614
+ for (const t of topics) {
615
+ let e = topicIndex.get(t);
616
+ if (!e) {
617
+ e = { pubs: new Set(), subs: new Set() };
618
+ topicIndex.set(t, e);
619
+ }
620
+ e.pubs.add(file);
621
+ }
622
+ }
623
+ for (const [file, topics] of subTopicsByFile) {
624
+ for (const t of topics) {
625
+ let e = topicIndex.get(t);
626
+ if (!e) {
627
+ e = { pubs: new Set(), subs: new Set() };
628
+ topicIndex.set(t, e);
629
+ }
630
+ e.subs.add(file);
631
+ }
632
+ }
633
+ // Nested map (publisher -> subscriber -> shared topics) — no string round-trip,
634
+ // so a file id can never be corrupted by a separator, and no non-printable byte
635
+ // lands in the source. Insertion order is stable (sorted topics/pubs/subs), so
636
+ // the resulting edge set is deterministic.
637
+ const pairTopics = new Map();
638
+ for (const topic of [...topicIndex.keys()].sort()) {
639
+ const { pubs, subs } = topicIndex.get(topic);
640
+ for (const p of [...pubs].sort()) {
641
+ for (const s of [...subs].sort()) {
642
+ if (p === s)
643
+ continue; // a passthrough file (pub+sub same topic) doesn't self-link
644
+ let bySub = pairTopics.get(p);
645
+ if (!bySub) {
646
+ bySub = new Map();
647
+ pairTopics.set(p, bySub);
648
+ }
649
+ let set = bySub.get(s);
650
+ if (!set) {
651
+ set = new Set();
652
+ bySub.set(s, set);
653
+ }
654
+ set.add(topic);
655
+ }
656
+ }
657
+ }
658
+ for (const [p, bySub] of pairTopics) {
659
+ for (const [s, topics] of bySub) {
660
+ addEdge(edges, p, s, 'publishes', 'pubsub', { topics: [...topics].sort() });
661
+ }
662
+ }
663
+ const groups = assignRouterGroups([...seedsByKey.values()]);
664
+ const roles = new Map();
665
+ for (const [fileId, role] of roleByFile) {
666
+ roles.set(fileId, {
667
+ role,
668
+ kind: ROLE_KIND[role],
669
+ priority: ROLE_PRIORITY[role],
670
+ metadata: { framework: 'faststream' },
671
+ });
672
+ }
673
+ const sortedEdges = [...edges.values()].sort((a, b) => a.source < b.source
674
+ ? -1
675
+ : a.source > b.source
676
+ ? 1
677
+ : a.target < b.target
678
+ ? -1
679
+ : a.target > b.target
680
+ ? 1
681
+ : a.kind < b.kind
682
+ ? -1
683
+ : a.kind > b.kind
684
+ ? 1
685
+ : 0);
686
+ // Positive signal for validation (mirrors fastapi/flask's log line).
687
+ if (groups.length > 0 || roleByFile.size > 0 || sortedEdges.length > 0) {
688
+ console.log(` [faststream] ${roleByFile.size} role(s) · ${groups.length} router group(s) · ${sortedEdges.length} edge(s)`);
689
+ }
690
+ // No silent caps (locked): log everything that degraded.
691
+ const degraded = [];
692
+ if (diag.dynamicTopics.size > 0) {
693
+ degraded.push(`${diag.dynamicTopics.size} dynamic topic(s): ${[...diag.dynamicTopics].sort().slice(0, 10).join(' · ')}`);
694
+ }
695
+ if (diag.unresolvedKiq.size > 0) {
696
+ degraded.push(`${diag.unresolvedKiq.size} unresolvable kiq(s): ${[...diag.unresolvedKiq].sort().slice(0, 10).join(' · ')}`);
697
+ }
698
+ if (diag.unresolvedMounts.size > 0) {
699
+ degraded.push(`${diag.unresolvedMounts.size} unresolvable mount(s): ${[...diag.unresolvedMounts].sort().slice(0, 10).join(' · ')}`);
700
+ }
701
+ if (diag.ambiguousRouters.size > 0) {
702
+ degraded.push(`${diag.ambiguousRouters.size} ambiguous router attribution(s): ${[...diag.ambiguousRouters].sort().slice(0, 10).join(' · ')}`);
703
+ }
704
+ if (degraded.length > 0) {
705
+ console.log(` [faststream] degraded: ${degraded.join(' · ')} (logged, not silently dropped)`);
706
+ }
707
+ return { groups, edges: sortedEdges, roles };
708
+ }
709
+ function getAnalysis(ctx) {
710
+ let a = ANALYSIS_CACHE.get(ctx);
711
+ if (!a) {
712
+ a = analyzeFastStream(ctx);
713
+ ANALYSIS_CACHE.set(ctx, a);
714
+ }
715
+ return a;
716
+ }
717
+ // ---------------------------------------------------------------------------
718
+ // The adapter.
719
+ export const fastStreamAdapter = {
720
+ name: 'faststream',
721
+ async detect(ctx) {
722
+ const { base, rootPath } = resolveBase(ctx);
723
+ // Root scan first (a repo whose manifest is at root reports rootPath '').
724
+ const rootMatch = scoreFastStream(gatherFastStreamSignals(base), rootPath);
725
+ if (rootMatch)
726
+ return rootMatch;
727
+ // A FastStream/Taskiq worker often lives one dir down (a `backend/` | `worker/`
728
+ // | `server/` package in a frontend+backend monorepo), so a root-only scan
729
+ // misses it. Shallow-scan immediate subdirs for a match and scope to it. Only
730
+ // when NOT already scoped to a workspace package (the path).
731
+ if (!ctx.packageDir) {
732
+ for (const sub of shallowManifestSubdirs(base)) {
733
+ const m = scoreFastStream(gatherFastStreamSignals(join(base, sub)), sub);
734
+ if (m)
735
+ return m;
736
+ }
737
+ }
738
+ return null;
739
+ },
740
+ // One grouping prior per FastStream *Router → its own subsystem, authoritative
741
+ // over directory grouping (the fastapi/flask mechanism). Fully deterministic
742
+ // (var/package-derived) → no classificationsNeeded. A broker-only app (no
743
+ // routers) yields no groups.
744
+ async groupingPrior(ctx) {
745
+ return { groups: getAnalysis(ctx).groups };
746
+ },
747
+ // Pub/sub topic matching (kind 'publishes') + Taskiq kiq enqueue (kind
748
+ // 'publishes') + include_router mounting (kind 'calls'). File-id endpoints; the
749
+ // step resolves to modules, drops self-edges, dedupes, 8-verb-validates.
750
+ async syntheticEdges(ctx) {
751
+ return getAnalysis(ctx).edges;
752
+ },
753
+ // app / broker / router → gateway; subscriber / publisher → service; task → job.
754
+ // METADATA; the module's `kind` is unchanged.
755
+ async roleTags(ctx) {
756
+ return getAnalysis(ctx).roles;
757
+ },
758
+ // The hooks READ SOURCE (Python). Declare the paths the diff-driven hosted walk
759
+ // must treat as framework-relevant. Never-store-source holds: parse server-side,
760
+ // persist only the derived groups/edges/roles.
761
+ scansSourcePath(path) {
762
+ return path.endsWith('.py') || path.endsWith('.pyi');
763
+ },
764
+ };
765
+ //# sourceMappingURL=faststream.js.map