@shortlink-org/portolan 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 (826) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +387 -0
  3. package/catalog/enum_test.go +46 -0
  4. package/catalog/model.go +932 -0
  5. package/catalog/roundtrip_test.go +185 -0
  6. package/catalog/via_test.go +38 -0
  7. package/cli/portolan.mjs +368 -0
  8. package/go.mod +14 -0
  9. package/go.sum +14 -0
  10. package/index.html +13 -0
  11. package/internal/commands/cargo.go +154 -0
  12. package/internal/commands/commands.go +143 -0
  13. package/internal/commands/commands_test.go +323 -0
  14. package/internal/commands/gradle.go +77 -0
  15. package/internal/commands/justfile.go +82 -0
  16. package/internal/commands/makefile.go +106 -0
  17. package/internal/commands/maven.go +93 -0
  18. package/internal/commands/packagejson.go +159 -0
  19. package/internal/commands/pyproject.go +169 -0
  20. package/internal/commands/taskfile.go +101 -0
  21. package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
  22. package/internal/commands/testdata/estate/Makefile +33 -0
  23. package/internal/commands/testdata/estate/Taskfile.yml +21 -0
  24. package/internal/commands/testdata/estate/build.gradle.kts +21 -0
  25. package/internal/commands/testdata/estate/justfile +20 -0
  26. package/internal/commands/testdata/estate/package.json +13 -0
  27. package/internal/commands/testdata/estate/pom.xml +29 -0
  28. package/internal/commands/testdata/estate/pyproject.toml +27 -0
  29. package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
  30. package/internal/commands/testdata/golden/commands.json +305 -0
  31. package/internal/gohttp/analyze.go +2500 -0
  32. package/internal/gohttp/endpoints.go +1067 -0
  33. package/internal/gohttp/roots.go +320 -0
  34. package/internal/gohttp/typed.go +143 -0
  35. package/internal/goscan/constants.go +85 -0
  36. package/internal/goscan/goscan_test.go +227 -0
  37. package/internal/goscan/names.go +52 -0
  38. package/internal/goscan/parse_test.go +11 -0
  39. package/internal/goscan/source.go +36 -0
  40. package/internal/goscan/tree.go +155 -0
  41. package/internal/goscan/types.go +99 -0
  42. package/internal/wsdl/ids.go +127 -0
  43. package/internal/wsdl/ids_test.go +21 -0
  44. package/internal/wsdl/model.go +62 -0
  45. package/internal/wsdl/parse.go +920 -0
  46. package/internal/wsdl/parse_test.go +133 -0
  47. package/package.json +117 -0
  48. package/plugin/describe.go +107 -0
  49. package/plugin/describe_test.go +114 -0
  50. package/plugin/protocol.go +117 -0
  51. package/plugin/schematest/schematest.go +126 -0
  52. package/plugins/README.md +1160 -0
  53. package/plugins/extract-adr/describe.go +19 -0
  54. package/plugins/extract-adr/describe_test.go +11 -0
  55. package/plugins/extract-adr/extract.go +153 -0
  56. package/plugins/extract-adr/extract_test.go +350 -0
  57. package/plugins/extract-adr/history.go +99 -0
  58. package/plugins/extract-adr/main.go +65 -0
  59. package/plugins/extract-adr/options.schema.json +26 -0
  60. package/plugins/extract-adr/parse.go +642 -0
  61. package/plugins/extract-adr/parse_test.go +405 -0
  62. package/plugins/extract-asyncapi/describe.go +19 -0
  63. package/plugins/extract-asyncapi/describe_test.go +11 -0
  64. package/plugins/extract-asyncapi/extract.go +315 -0
  65. package/plugins/extract-asyncapi/extract_test.go +197 -0
  66. package/plugins/extract-asyncapi/main.go +48 -0
  67. package/plugins/extract-asyncapi/options.schema.json +26 -0
  68. package/plugins/extract-asyncapi/spec.go +151 -0
  69. package/plugins/extract-celery/README.md +169 -0
  70. package/plugins/extract-celery/extract.py +346 -0
  71. package/plugins/extract-celery/extract_test.py +192 -0
  72. package/plugins/extract-celery/main.py +66 -0
  73. package/plugins/extract-celery/options.py +31 -0
  74. package/plugins/extract-celery/options.schema.json +31 -0
  75. package/plugins/extract-celery/producers.py +156 -0
  76. package/plugins/extract-celery/routes_test.py +106 -0
  77. package/plugins/extract-commands/describe.go +19 -0
  78. package/plugins/extract-commands/describe_test.go +11 -0
  79. package/plugins/extract-commands/extract.go +79 -0
  80. package/plugins/extract-commands/extract_test.go +92 -0
  81. package/plugins/extract-commands/main.go +55 -0
  82. package/plugins/extract-commands/options.schema.json +20 -0
  83. package/plugins/extract-csr/avro.go +258 -0
  84. package/plugins/extract-csr/describe.go +19 -0
  85. package/plugins/extract-csr/describe_test.go +11 -0
  86. package/plugins/extract-csr/extract.go +338 -0
  87. package/plugins/extract-csr/extract_test.go +374 -0
  88. package/plugins/extract-csr/jsonschema.go +343 -0
  89. package/plugins/extract-csr/lock.go +25 -0
  90. package/plugins/extract-csr/main.go +84 -0
  91. package/plugins/extract-csr/options.schema.json +43 -0
  92. package/plugins/extract-csr/subject.go +102 -0
  93. package/plugins/extract-django/README.md +386 -0
  94. package/plugins/extract-django/apps.py +100 -0
  95. package/plugins/extract-django/auth.py +302 -0
  96. package/plugins/extract-django/auth_test.py +188 -0
  97. package/plugins/extract-django/clients.py +121 -0
  98. package/plugins/extract-django/contracts.py +458 -0
  99. package/plugins/extract-django/database.py +119 -0
  100. package/plugins/extract-django/domain.py +318 -0
  101. package/plugins/extract-django/events.py +86 -0
  102. package/plugins/extract-django/extract.py +601 -0
  103. package/plugins/extract-django/extract_test.py +397 -0
  104. package/plugins/extract-django/flows.py +1278 -0
  105. package/plugins/extract-django/flows_test.py +338 -0
  106. package/plugins/extract-django/ids.py +42 -0
  107. package/plugins/extract-django/ids_test.py +30 -0
  108. package/plugins/extract-django/lifecycle.py +222 -0
  109. package/plugins/extract-django/lifecycle_test.py +76 -0
  110. package/plugins/extract-django/main.py +66 -0
  111. package/plugins/extract-django/openapi.py +229 -0
  112. package/plugins/extract-django/openapi_test.py +60 -0
  113. package/plugins/extract-django/operations.py +96 -0
  114. package/plugins/extract-django/options.py +61 -0
  115. package/plugins/extract-django/options.schema.json +126 -0
  116. package/plugins/extract-django/routing.py +214 -0
  117. package/plugins/extract-django/routing_test.py +198 -0
  118. package/plugins/extract-django/serializers.py +558 -0
  119. package/plugins/extract-django/store.py +434 -0
  120. package/plugins/extract-django/transport.py +242 -0
  121. package/plugins/extract-flows/describe.go +19 -0
  122. package/plugins/extract-flows/describe_test.go +11 -0
  123. package/plugins/extract-flows/extract.go +91 -0
  124. package/plugins/extract-flows/main.go +45 -0
  125. package/plugins/extract-flows/options.schema.json +16 -0
  126. package/plugins/extract-flows/parse.go +593 -0
  127. package/plugins/extract-flows/parse_test.go +204 -0
  128. package/plugins/extract-glossary/describe.go +19 -0
  129. package/plugins/extract-glossary/describe_test.go +11 -0
  130. package/plugins/extract-glossary/extract.go +115 -0
  131. package/plugins/extract-glossary/extract_test.go +220 -0
  132. package/plugins/extract-glossary/main.go +59 -0
  133. package/plugins/extract-glossary/options.schema.json +21 -0
  134. package/plugins/extract-glossary/parse.go +214 -0
  135. package/plugins/extract-glossary/parse_test.go +203 -0
  136. package/plugins/extract-go/aggregate.go +214 -0
  137. package/plugins/extract-go/client.go +409 -0
  138. package/plugins/extract-go/client_test.go +266 -0
  139. package/plugins/extract-go/describe.go +19 -0
  140. package/plugins/extract-go/describe_test.go +11 -0
  141. package/plugins/extract-go/enum.go +195 -0
  142. package/plugins/extract-go/enum_test.go +82 -0
  143. package/plugins/extract-go/event.go +99 -0
  144. package/plugins/extract-go/extract.go +191 -0
  145. package/plugins/extract-go/extract_test.go +261 -0
  146. package/plugins/extract-go/flow.go +1441 -0
  147. package/plugins/extract-go/flow_test.go +609 -0
  148. package/plugins/extract-go/httpclient.go +174 -0
  149. package/plugins/extract-go/httpclient_test.go +296 -0
  150. package/plugins/extract-go/ids.go +92 -0
  151. package/plugins/extract-go/layout.go +225 -0
  152. package/plugins/extract-go/layout_test.go +125 -0
  153. package/plugins/extract-go/lifecycle.go +324 -0
  154. package/plugins/extract-go/lifecycle_test.go +108 -0
  155. package/plugins/extract-go/main.go +79 -0
  156. package/plugins/extract-go/operation.go +157 -0
  157. package/plugins/extract-go/options.schema.json +66 -0
  158. package/plugins/extract-go/source.go +316 -0
  159. package/plugins/extract-go/transport.go +321 -0
  160. package/plugins/extract-go/transport_test.go +145 -0
  161. package/plugins/extract-go/wiring.go +434 -0
  162. package/plugins/extract-go-nats/describe.go +19 -0
  163. package/plugins/extract-go-nats/describe_test.go +11 -0
  164. package/plugins/extract-go-nats/extract.go +177 -0
  165. package/plugins/extract-go-nats/extract_test.go +389 -0
  166. package/plugins/extract-go-nats/index.go +379 -0
  167. package/plugins/extract-go-nats/main.go +42 -0
  168. package/plugins/extract-go-nats/options.schema.json +21 -0
  169. package/plugins/extract-go-nats/resolve.go +161 -0
  170. package/plugins/extract-go-nats/sites.go +224 -0
  171. package/plugins/extract-graphql/describe.go +19 -0
  172. package/plugins/extract-graphql/describe_test.go +11 -0
  173. package/plugins/extract-graphql/extract.go +433 -0
  174. package/plugins/extract-graphql/extract_test.go +256 -0
  175. package/plugins/extract-graphql/ids.go +49 -0
  176. package/plugins/extract-graphql/lex.go +237 -0
  177. package/plugins/extract-graphql/main.go +51 -0
  178. package/plugins/extract-graphql/options.schema.json +31 -0
  179. package/plugins/extract-graphql/parse.go +621 -0
  180. package/plugins/extract-graphql/parse_test.go +122 -0
  181. package/plugins/extract-http-clients/describe.go +19 -0
  182. package/plugins/extract-http-clients/describe_test.go +11 -0
  183. package/plugins/extract-http-clients/extract.go +705 -0
  184. package/plugins/extract-http-clients/extract_test.go +1263 -0
  185. package/plugins/extract-http-clients/main.go +42 -0
  186. package/plugins/extract-http-clients/options.schema.json +33 -0
  187. package/plugins/extract-java/README.md +190 -0
  188. package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
  189. package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
  190. package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
  191. package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
  192. package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
  193. package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
  194. package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
  195. package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
  196. package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
  197. package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
  198. package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
  199. package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
  200. package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
  201. package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
  202. package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
  203. package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
  204. package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
  205. package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
  206. package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
  207. package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
  208. package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
  209. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
  210. package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
  211. package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
  212. package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
  213. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
  214. package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
  215. package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
  216. package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
  217. package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
  218. package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
  219. package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
  220. package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
  221. package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
  222. package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
  223. package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
  224. package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
  225. package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
  226. package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
  227. package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
  228. package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
  229. package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
  230. package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
  231. package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
  232. package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
  233. package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
  234. package/plugins/extract-java/options.schema.json +70 -0
  235. package/plugins/extract-openapi/describe.go +19 -0
  236. package/plugins/extract-openapi/describe_test.go +11 -0
  237. package/plugins/extract-openapi/discover.go +243 -0
  238. package/plugins/extract-openapi/extract.go +526 -0
  239. package/plugins/extract-openapi/extract_test.go +545 -0
  240. package/plugins/extract-openapi/main.go +75 -0
  241. package/plugins/extract-openapi/options.schema.json +62 -0
  242. package/plugins/extract-openapi/spec.go +350 -0
  243. package/plugins/extract-project/README.md +22 -0
  244. package/plugins/extract-project/describe.go +19 -0
  245. package/plugins/extract-project/describe_test.go +11 -0
  246. package/plugins/extract-project/extract.go +221 -0
  247. package/plugins/extract-project/extract_test.go +109 -0
  248. package/plugins/extract-project/main.go +41 -0
  249. package/plugins/extract-project/options.schema.json +48 -0
  250. package/plugins/extract-proto/ast.go +125 -0
  251. package/plugins/extract-proto/consumes.go +77 -0
  252. package/plugins/extract-proto/describe.go +19 -0
  253. package/plugins/extract-proto/extract.go +293 -0
  254. package/plugins/extract-proto/extract_test.go +459 -0
  255. package/plugins/extract-proto/ids.go +89 -0
  256. package/plugins/extract-proto/ids_test.go +57 -0
  257. package/plugins/extract-proto/lex.go +285 -0
  258. package/plugins/extract-proto/main.go +100 -0
  259. package/plugins/extract-proto/module.go +120 -0
  260. package/plugins/extract-proto/options.schema.json +51 -0
  261. package/plugins/extract-proto/parse.go +720 -0
  262. package/plugins/extract-proto/parse_test.go +307 -0
  263. package/plugins/extract-proto/provides.go +236 -0
  264. package/plugins/extract-proto/resolve.go +222 -0
  265. package/plugins/extract-proto/resolve_test.go +119 -0
  266. package/plugins/extract-python-kafka/README.md +36 -0
  267. package/plugins/extract-python-kafka/extract.py +211 -0
  268. package/plugins/extract-python-kafka/extract_test.py +68 -0
  269. package/plugins/extract-python-kafka/main.py +59 -0
  270. package/plugins/extract-python-kafka/options.py +29 -0
  271. package/plugins/extract-python-kafka/options.schema.json +31 -0
  272. package/plugins/extract-redis/README.md +11 -0
  273. package/plugins/extract-redis/describe.go +19 -0
  274. package/plugins/extract-redis/describe_test.go +11 -0
  275. package/plugins/extract-redis/extract.go +183 -0
  276. package/plugins/extract-redis/extract_test.go +168 -0
  277. package/plugins/extract-redis/keyspaces.go +469 -0
  278. package/plugins/extract-redis/main.go +44 -0
  279. package/plugins/extract-redis/options.schema.json +30 -0
  280. package/plugins/extract-river/describe.go +19 -0
  281. package/plugins/extract-river/describe_test.go +11 -0
  282. package/plugins/extract-river/extract.go +507 -0
  283. package/plugins/extract-river/extract_test.go +132 -0
  284. package/plugins/extract-river/main.go +42 -0
  285. package/plugins/extract-river/options.schema.json +21 -0
  286. package/plugins/extract-rust/Cargo.lock +133 -0
  287. package/plugins/extract-rust/Cargo.toml +23 -0
  288. package/plugins/extract-rust/README.md +204 -0
  289. package/plugins/extract-rust/options.schema.json +65 -0
  290. package/plugins/extract-rust/rustfmt.toml +1 -0
  291. package/plugins/extract-sql/ddl.go +893 -0
  292. package/plugins/extract-sql/ddl_test.go +401 -0
  293. package/plugins/extract-sql/describe.go +19 -0
  294. package/plugins/extract-sql/describe_test.go +11 -0
  295. package/plugins/extract-sql/layout.go +221 -0
  296. package/plugins/extract-sql/layout_test.go +55 -0
  297. package/plugins/extract-sql/lineage.go +107 -0
  298. package/plugins/extract-sql/main.go +141 -0
  299. package/plugins/extract-sql/maps.go +564 -0
  300. package/plugins/extract-sql/maps_java.go +117 -0
  301. package/plugins/extract-sql/maps_rust.go +333 -0
  302. package/plugins/extract-sql/maps_rust_test.go +70 -0
  303. package/plugins/extract-sql/maps_test.go +204 -0
  304. package/plugins/extract-sql/maps_ts.go +398 -0
  305. package/plugins/extract-sql/maps_ts_test.go +136 -0
  306. package/plugins/extract-sql/options.schema.json +53 -0
  307. package/plugins/extract-sql/projection.go +70 -0
  308. package/plugins/extract-sql/projection_test.go +50 -0
  309. package/plugins/extract-sql/store.go +420 -0
  310. package/plugins/extract-sql/store_test.go +233 -0
  311. package/plugins/extract-sql/view.go +295 -0
  312. package/plugins/extract-ts/README.md +306 -0
  313. package/plugins/extract-ts/ast.test.ts +178 -0
  314. package/plugins/extract-ts/ast.ts +648 -0
  315. package/plugins/extract-ts/clients.ts +185 -0
  316. package/plugins/extract-ts/domain.ts +164 -0
  317. package/plugins/extract-ts/extract.test.ts +208 -0
  318. package/plugins/extract-ts/extract.ts +200 -0
  319. package/plugins/extract-ts/flows.ts +783 -0
  320. package/plugins/extract-ts/graphql.test.ts +73 -0
  321. package/plugins/extract-ts/graphql.ts +181 -0
  322. package/plugins/extract-ts/ids.test.ts +21 -0
  323. package/plugins/extract-ts/ids.ts +52 -0
  324. package/plugins/extract-ts/lifecycle.ts +138 -0
  325. package/plugins/extract-ts/main.ts +48 -0
  326. package/plugins/extract-ts/openapi.test.ts +64 -0
  327. package/plugins/extract-ts/openapi.ts +99 -0
  328. package/plugins/extract-ts/operations.ts +93 -0
  329. package/plugins/extract-ts/options.schema.json +96 -0
  330. package/plugins/extract-ts/source.test.ts +174 -0
  331. package/plugins/extract-ts/source.ts +465 -0
  332. package/plugins/extract-ts/transport.ts +147 -0
  333. package/plugins/extract-ts/tsconfig.json +13 -0
  334. package/plugins/extract-ts/wiring.ts +190 -0
  335. package/plugins/extract-watermill/describe.go +19 -0
  336. package/plugins/extract-watermill/describe_test.go +11 -0
  337. package/plugins/extract-watermill/extract.go +1228 -0
  338. package/plugins/extract-watermill/extract_test.go +234 -0
  339. package/plugins/extract-watermill/main.go +41 -0
  340. package/plugins/extract-watermill/options.schema.json +21 -0
  341. package/plugins/extract-wsdl/describe.go +19 -0
  342. package/plugins/extract-wsdl/describe_test.go +11 -0
  343. package/plugins/extract-wsdl/extract.go +148 -0
  344. package/plugins/extract-wsdl/extract_test.go +45 -0
  345. package/plugins/extract-wsdl/main.go +53 -0
  346. package/plugins/extract-wsdl/options.schema.json +59 -0
  347. package/plugins/fetch-bsr/auth.go +114 -0
  348. package/plugins/fetch-bsr/bsr.go +240 -0
  349. package/plugins/fetch-bsr/cache.go +66 -0
  350. package/plugins/fetch-bsr/describe.go +19 -0
  351. package/plugins/fetch-bsr/fetch.go +192 -0
  352. package/plugins/fetch-bsr/fetch_test.go +465 -0
  353. package/plugins/fetch-bsr/lock.go +71 -0
  354. package/plugins/fetch-bsr/main.go +117 -0
  355. package/plugins/fetch-bsr/options.schema.json +42 -0
  356. package/plugins/fetch-csr/auth.go +95 -0
  357. package/plugins/fetch-csr/cache.go +69 -0
  358. package/plugins/fetch-csr/describe.go +19 -0
  359. package/plugins/fetch-csr/describe_test.go +11 -0
  360. package/plugins/fetch-csr/fetch.go +254 -0
  361. package/plugins/fetch-csr/fetch_test.go +494 -0
  362. package/plugins/fetch-csr/lock.go +95 -0
  363. package/plugins/fetch-csr/main.go +122 -0
  364. package/plugins/fetch-csr/options.schema.json +42 -0
  365. package/plugins/fetch-csr/registry.go +209 -0
  366. package/plugins/fetch-git/cache.go +65 -0
  367. package/plugins/fetch-git/describe.go +19 -0
  368. package/plugins/fetch-git/describe_test.go +11 -0
  369. package/plugins/fetch-git/fetch.go +181 -0
  370. package/plugins/fetch-git/fetch_test.go +332 -0
  371. package/plugins/fetch-git/git.go +169 -0
  372. package/plugins/fetch-git/lock.go +72 -0
  373. package/plugins/fetch-git/main.go +127 -0
  374. package/plugins/fetch-git/offline.go +39 -0
  375. package/plugins/fetch-git/options.schema.json +42 -0
  376. package/plugins/fetch-git/pin.go +90 -0
  377. package/plugins/fetch-git/pin_test.go +74 -0
  378. package/plugins/gen-backstage/describe.go +17 -0
  379. package/plugins/gen-backstage/main.go +22 -0
  380. package/plugins/gen-backstage/options.schema.json +30 -0
  381. package/plugins/gen-backstage/plugin.go +473 -0
  382. package/plugins/gen-backstage/plugin_test.go +145 -0
  383. package/plugins/gen-backstage.wasm +0 -0
  384. package/plugins/gen-markdown/adr.go +162 -0
  385. package/plugins/gen-markdown/aggregate.go +362 -0
  386. package/plugins/gen-markdown/canonical.go +93 -0
  387. package/plugins/gen-markdown/context.go +90 -0
  388. package/plugins/gen-markdown/coverage_test.go +89 -0
  389. package/plugins/gen-markdown/describe.go +19 -0
  390. package/plugins/gen-markdown/describe_test.go +11 -0
  391. package/plugins/gen-markdown/external.go +71 -0
  392. package/plugins/gen-markdown/flow.go +277 -0
  393. package/plugins/gen-markdown/glossary.go +76 -0
  394. package/plugins/gen-markdown/glossary_test.go +148 -0
  395. package/plugins/gen-markdown/llms.go +302 -0
  396. package/plugins/gen-markdown/main.go +22 -0
  397. package/plugins/gen-markdown/markdown.go +254 -0
  398. package/plugins/gen-markdown/markdown_test.go +100 -0
  399. package/plugins/gen-markdown/module.go +107 -0
  400. package/plugins/gen-markdown/options.schema.json +16 -0
  401. package/plugins/gen-markdown/plugin.go +39 -0
  402. package/plugins/gen-markdown/quality_test.go +176 -0
  403. package/plugins/gen-markdown/redis_test.go +38 -0
  404. package/plugins/gen-markdown/render.go +344 -0
  405. package/plugins/gen-markdown/render_test.go +186 -0
  406. package/plugins/gen-markdown/service.go +456 -0
  407. package/plugins/gen-markdown/source.go +177 -0
  408. package/plugins/gen-markdown/store.go +201 -0
  409. package/plugins/gen-markdown.wasm +0 -0
  410. package/plugins/gen-mermaid/describe.go +17 -0
  411. package/plugins/gen-mermaid/main.go +22 -0
  412. package/plugins/gen-mermaid/options.schema.json +10 -0
  413. package/plugins/gen-mermaid/plugin.go +104 -0
  414. package/plugins/gen-mermaid/plugin_test.go +33 -0
  415. package/plugins/gen-mermaid.wasm +0 -0
  416. package/plugins/openapi/ids.go +261 -0
  417. package/plugins/openapi/ids_test.go +98 -0
  418. package/plugins/pyplugin/catalog.py +209 -0
  419. package/plugins/pyplugin/celery_conf.py +436 -0
  420. package/plugins/pyplugin/celery_tasks.py +76 -0
  421. package/plugins/pyplugin/kafka.py +570 -0
  422. package/plugins/pyplugin/names.py +38 -0
  423. package/plugins/pyplugin/protocol.py +79 -0
  424. package/plugins/pyplugin/source.py +285 -0
  425. package/plugins/verify-codeowners/describe.go +19 -0
  426. package/plugins/verify-codeowners/describe_test.go +11 -0
  427. package/plugins/verify-codeowners/main.go +75 -0
  428. package/plugins/verify-codeowners/match.go +85 -0
  429. package/plugins/verify-codeowners/match_test.go +47 -0
  430. package/plugins/verify-codeowners/options.schema.json +15 -0
  431. package/plugins/verify-codeowners/owners.go +164 -0
  432. package/plugins/verify-codeowners/owners_test.go +225 -0
  433. package/plugins/verify-codeowners/parse.go +90 -0
  434. package/plugins/verify-codeowners/parse_test.go +62 -0
  435. package/plugins/verify-otel/describe.go +19 -0
  436. package/plugins/verify-otel/describe_test.go +11 -0
  437. package/plugins/verify-otel/main.go +53 -0
  438. package/plugins/verify-otel/match.go +336 -0
  439. package/plugins/verify-otel/options.schema.json +30 -0
  440. package/plugins/verify-otel/otlp.go +200 -0
  441. package/plugins/verify-otel/verify.go +734 -0
  442. package/plugins/verify-otel/verify_test.go +460 -0
  443. package/portolan.json +1073 -0
  444. package/public/favicon.svg +12 -0
  445. package/schema/portolan.schema.json +2558 -0
  446. package/scripts/README.md +53 -0
  447. package/scripts/build-report.mjs +60 -0
  448. package/scripts/build-report.test.mjs +44 -0
  449. package/scripts/builtin-plugins.mjs +49 -0
  450. package/scripts/catalog-sources.mjs +67 -0
  451. package/scripts/diff.mjs +315 -0
  452. package/scripts/diff.test.mjs +151 -0
  453. package/scripts/forge-comment.mjs +144 -0
  454. package/scripts/forge-comment.test.mjs +151 -0
  455. package/scripts/forge-release.mjs +138 -0
  456. package/scripts/forge-release.test.mjs +106 -0
  457. package/scripts/forge.mjs +61 -0
  458. package/scripts/gen-likec4.mjs +854 -0
  459. package/scripts/gen-likec4.test.mjs +258 -0
  460. package/scripts/gen.mjs +603 -0
  461. package/scripts/generated-docs.test.mjs +66 -0
  462. package/scripts/local-api.mjs +1192 -0
  463. package/scripts/local-api.test.mjs +387 -0
  464. package/scripts/manifest.mjs +214 -0
  465. package/scripts/manifest.test.mjs +148 -0
  466. package/scripts/output-path.mjs +79 -0
  467. package/scripts/output-path.test.mjs +45 -0
  468. package/scripts/package-smoke.mjs +37 -0
  469. package/scripts/plugin-host.mjs +419 -0
  470. package/scripts/plugin-host.test.mjs +109 -0
  471. package/scripts/plugin-wasm-worker.mjs +34 -0
  472. package/scripts/run-builtin.mjs +81 -0
  473. package/scripts/schema.mjs +357 -0
  474. package/scripts/site-docs.mjs +295 -0
  475. package/scripts/site-docs.test.mjs +181 -0
  476. package/scripts/vendor-lock.mjs +58 -0
  477. package/scripts/vendor-lock.test.mjs +69 -0
  478. package/src/README.md +38 -0
  479. package/src/app/App.tsx +448 -0
  480. package/src/app/BranchPicker.test.ts +46 -0
  481. package/src/app/BranchPicker.tsx +153 -0
  482. package/src/app/Breadcrumbs.test.ts +38 -0
  483. package/src/app/Breadcrumbs.tsx +223 -0
  484. package/src/app/BuildStamp.tsx +146 -0
  485. package/src/app/CommandPalette.tsx +292 -0
  486. package/src/app/HashScroll.tsx +45 -0
  487. package/src/app/Sidebar.tsx +1742 -0
  488. package/src/app/TopBar.tsx +393 -0
  489. package/src/app/density.tsx +64 -0
  490. package/src/app/forge-access.test.ts +27 -0
  491. package/src/app/forge-access.tsx +80 -0
  492. package/src/app/panels.tsx +141 -0
  493. package/src/app/pins.tsx +242 -0
  494. package/src/app/query-client.ts +24 -0
  495. package/src/app/responsive.ts +55 -0
  496. package/src/app/shortcuts.test.ts +39 -0
  497. package/src/app/shortcuts.tsx +282 -0
  498. package/src/app/theme.tsx +51 -0
  499. package/src/app/title.test.ts +14 -0
  500. package/src/app/title.ts +30 -0
  501. package/src/app/toast.tsx +59 -0
  502. package/src/app/tree-keys.test.ts +80 -0
  503. package/src/app/tree-keys.ts +87 -0
  504. package/src/app/ui-store.ts +52 -0
  505. package/src/catalog-error.test.ts +105 -0
  506. package/src/catalog-profile.test.ts +59 -0
  507. package/src/catalog-profile.ts +101 -0
  508. package/src/catalog-stores.test.ts +431 -0
  509. package/src/catalog.test.ts +1215 -0
  510. package/src/catalog.ts +3125 -0
  511. package/src/chat/Cards.tsx +408 -0
  512. package/src/chat/ChatPanel.tsx +79 -0
  513. package/src/chat/ChatSettings.tsx +219 -0
  514. package/src/chat/Composer.tsx +99 -0
  515. package/src/chat/Conversation.tsx +281 -0
  516. package/src/chat/Header.tsx +46 -0
  517. package/src/chat/Message.tsx +49 -0
  518. package/src/chat/Notice.tsx +195 -0
  519. package/src/chat/Starter.tsx +67 -0
  520. package/src/chat/Steps.tsx +45 -0
  521. package/src/chat/Waiting.tsx +87 -0
  522. package/src/chat/flags.test.ts +59 -0
  523. package/src/chat/flags.ts +155 -0
  524. package/src/chat/linkify.test.ts +30 -0
  525. package/src/chat/linkify.ts +45 -0
  526. package/src/chat/prefs.ts +56 -0
  527. package/src/chat/prompt.test.ts +43 -0
  528. package/src/chat/prompt.ts +157 -0
  529. package/src/chat/store.ts +35 -0
  530. package/src/chat/tools.ts +58 -0
  531. package/src/chat/transport.ts +70 -0
  532. package/src/components/AdrRow.tsx +29 -0
  533. package/src/components/AnchorLink.tsx +67 -0
  534. package/src/components/ApiReference.tsx +134 -0
  535. package/src/components/AsyncApiReference.tsx +120 -0
  536. package/src/components/CatalogStamp.tsx +155 -0
  537. package/src/components/ChannelRows.tsx +132 -0
  538. package/src/components/CommandRows.tsx +139 -0
  539. package/src/components/DiagramSkeleton.tsx +71 -0
  540. package/src/components/EditorLink.tsx +85 -0
  541. package/src/components/FieldTree.tsx +305 -0
  542. package/src/components/FlowTrigger.tsx +34 -0
  543. package/src/components/Ident.tsx +89 -0
  544. package/src/components/Integrations.tsx +243 -0
  545. package/src/components/LifecycleDiagram.tsx +110 -0
  546. package/src/components/MachineDocs.tsx +42 -0
  547. package/src/components/Markdown.tsx +173 -0
  548. package/src/components/MarkdownTable.tsx +206 -0
  549. package/src/components/Mermaid.tsx +211 -0
  550. package/src/components/MethodRows.tsx +309 -0
  551. package/src/components/Overlay.tsx +125 -0
  552. package/src/components/PageHeader.tsx +189 -0
  553. package/src/components/RowActions.tsx +97 -0
  554. package/src/components/SchemaDocument.tsx +89 -0
  555. package/src/components/Select.tsx +126 -0
  556. package/src/components/ShapeRows.tsx +118 -0
  557. package/src/components/SourceDoc.tsx +82 -0
  558. package/src/components/SourcePreview.tsx +441 -0
  559. package/src/components/TabRow.tsx +182 -0
  560. package/src/components/TechIcon.tsx +34 -0
  561. package/src/components/Toc.tsx +132 -0
  562. package/src/components/WhatLinksHere.tsx +273 -0
  563. package/src/components/WsdlReference.tsx +84 -0
  564. package/src/components/ddd-icons.tsx +107 -0
  565. package/src/components/kind.tsx +157 -0
  566. package/src/components/logo.tsx +61 -0
  567. package/src/components/primitives.tsx +264 -0
  568. package/src/data.test.ts +18 -0
  569. package/src/data.ts +202 -0
  570. package/src/enrich.test.ts +1150 -0
  571. package/src/enrich.ts +672 -0
  572. package/src/er/ErCanvas.tsx +533 -0
  573. package/src/er/RedisSchema.tsx +74 -0
  574. package/src/er/RelationCard.tsx +267 -0
  575. package/src/er/StoreHeader.tsx +155 -0
  576. package/src/er/TableNode.tsx +84 -0
  577. package/src/er/ViewNode.tsx +83 -0
  578. package/src/er/layout.test.ts +115 -0
  579. package/src/er/layout.ts +77 -0
  580. package/src/er/lineage.test.ts +140 -0
  581. package/src/er/lineage.ts +107 -0
  582. package/src/er/markers.tsx +79 -0
  583. package/src/er/spec.test.ts +344 -0
  584. package/src/er/spec.ts +426 -0
  585. package/src/flow/ChainList.tsx +162 -0
  586. package/src/flow/FlowTable.tsx +123 -0
  587. package/src/flow/FlowToolbar.tsx +372 -0
  588. package/src/flow/StepDetail.tsx +380 -0
  589. package/src/flow/StepRail.tsx +428 -0
  590. package/src/flow/answers.test.ts +50 -0
  591. package/src/flow/answers.ts +58 -0
  592. package/src/flow/chain.test.ts +383 -0
  593. package/src/flow/chain.ts +353 -0
  594. package/src/flow/chapters.test.ts +185 -0
  595. package/src/flow/chapters.ts +225 -0
  596. package/src/flow/continues.test.ts +102 -0
  597. package/src/flow/continues.ts +65 -0
  598. package/src/flow/cross-context.test.ts +97 -0
  599. package/src/flow/cross-context.ts +35 -0
  600. package/src/flow/mermaid.test.ts +22 -0
  601. package/src/flow/mermaid.ts +141 -0
  602. package/src/flow/outline.test.ts +212 -0
  603. package/src/flow/outline.ts +170 -0
  604. package/src/flow/paths.test.ts +129 -0
  605. package/src/flow/paths.ts +147 -0
  606. package/src/flow/prefs.test.ts +80 -0
  607. package/src/flow/prefs.ts +104 -0
  608. package/src/graph/DependencyGraph.tsx +283 -0
  609. package/src/graph/DependencyNodes.tsx +162 -0
  610. package/src/graph/EdgeLabel.tsx +61 -0
  611. package/src/graph/FocusedEventGraph.tsx +211 -0
  612. package/src/graph/GraphToolbar.tsx +187 -0
  613. package/src/graph/RoutedEdge.tsx +80 -0
  614. package/src/graph/dependency-layout.test.ts +360 -0
  615. package/src/graph/dependency-layout.ts +444 -0
  616. package/src/graph/elk.test.ts +70 -0
  617. package/src/graph/elk.ts +287 -0
  618. package/src/graph/nodes.tsx +97 -0
  619. package/src/graph/theme.ts +81 -0
  620. package/src/graph/useElkFlow.ts +125 -0
  621. package/src/index.css +1547 -0
  622. package/src/language/InLanguage.tsx +40 -0
  623. package/src/language/TermCard.tsx +132 -0
  624. package/src/language/cards.test.ts +114 -0
  625. package/src/language/cards.ts +111 -0
  626. package/src/lib/admonition.test.ts +49 -0
  627. package/src/lib/admonition.ts +48 -0
  628. package/src/lib/adr.test.ts +131 -0
  629. package/src/lib/adr.ts +68 -0
  630. package/src/lib/api.test.ts +122 -0
  631. package/src/lib/api.ts +135 -0
  632. package/src/lib/backlinks.test.ts +330 -0
  633. package/src/lib/backlinks.ts +567 -0
  634. package/src/lib/branch-compare.ts +16 -0
  635. package/src/lib/build-info.test.ts +76 -0
  636. package/src/lib/build-info.ts +80 -0
  637. package/src/lib/catalog-diff.test.ts +275 -0
  638. package/src/lib/catalog-diff.ts +655 -0
  639. package/src/lib/clipboard.ts +43 -0
  640. package/src/lib/comparison-memory.test.ts +40 -0
  641. package/src/lib/comparison-memory.ts +39 -0
  642. package/src/lib/context-color.ts +27 -0
  643. package/src/lib/context-map.test.ts +148 -0
  644. package/src/lib/context-map.ts +399 -0
  645. package/src/lib/data-model.test.ts +245 -0
  646. package/src/lib/data-model.ts +399 -0
  647. package/src/lib/data-problems.test.ts +178 -0
  648. package/src/lib/data-problems.ts +314 -0
  649. package/src/lib/derive.test.ts +251 -0
  650. package/src/lib/derive.ts +515 -0
  651. package/src/lib/editor-link.test.ts +125 -0
  652. package/src/lib/editor-link.ts +96 -0
  653. package/src/lib/editor-prefs.ts +27 -0
  654. package/src/lib/event-graph.test.ts +237 -0
  655. package/src/lib/event-graph.ts +327 -0
  656. package/src/lib/export-canvas.ts +37 -0
  657. package/src/lib/export-file.test.ts +30 -0
  658. package/src/lib/export-file.ts +20 -0
  659. package/src/lib/flow-tree.test.ts +177 -0
  660. package/src/lib/flow-tree.ts +160 -0
  661. package/src/lib/forge-refs.test.ts +50 -0
  662. package/src/lib/forge-refs.ts +60 -0
  663. package/src/lib/format.test.ts +35 -0
  664. package/src/lib/format.ts +64 -0
  665. package/src/lib/github-catalog.test.ts +301 -0
  666. package/src/lib/github-catalog.ts +610 -0
  667. package/src/lib/integrations.test.ts +119 -0
  668. package/src/lib/integrations.ts +133 -0
  669. package/src/lib/kinds.test.ts +91 -0
  670. package/src/lib/kinds.ts +204 -0
  671. package/src/lib/lifecycle.test.ts +93 -0
  672. package/src/lib/lifecycle.ts +165 -0
  673. package/src/lib/local-api.ts +194 -0
  674. package/src/lib/motion-features.ts +11 -0
  675. package/src/lib/motion.test.ts +120 -0
  676. package/src/lib/motion.tsx +269 -0
  677. package/src/lib/palette-recent.test.ts +80 -0
  678. package/src/lib/palette-recent.ts +90 -0
  679. package/src/lib/palette.test.ts +365 -0
  680. package/src/lib/palette.ts +593 -0
  681. package/src/lib/pins.test.ts +139 -0
  682. package/src/lib/pins.ts +130 -0
  683. package/src/lib/problems.test.ts +112 -0
  684. package/src/lib/project-preview.ts +5 -0
  685. package/src/lib/proto-problems.test.ts +121 -0
  686. package/src/lib/proto-problems.ts +65 -0
  687. package/src/lib/queries.test.ts +113 -0
  688. package/src/lib/queries.ts +131 -0
  689. package/src/lib/registry.test.ts +219 -0
  690. package/src/lib/registry.ts +208 -0
  691. package/src/lib/scenarios.ts +547 -0
  692. package/src/lib/setup-info.test.ts +199 -0
  693. package/src/lib/setup-info.ts +415 -0
  694. package/src/lib/shape.test.ts +289 -0
  695. package/src/lib/shape.ts +454 -0
  696. package/src/lib/sidebar-prefs.test.ts +32 -0
  697. package/src/lib/sidebar-prefs.ts +75 -0
  698. package/src/lib/source-code.test.ts +159 -0
  699. package/src/lib/source-code.ts +286 -0
  700. package/src/lib/source-doc.test.ts +131 -0
  701. package/src/lib/source-doc.ts +103 -0
  702. package/src/lib/source-highlight.test.ts +29 -0
  703. package/src/lib/source-highlight.ts +24 -0
  704. package/src/lib/source-link.test.ts +153 -0
  705. package/src/lib/source-link.ts +201 -0
  706. package/src/lib/spec-files.test.ts +24 -0
  707. package/src/lib/spec-files.ts +23 -0
  708. package/src/lib/table-directive.test.ts +139 -0
  709. package/src/lib/table-directive.ts +106 -0
  710. package/src/lib/tech.test.ts +120 -0
  711. package/src/lib/tech.ts +421 -0
  712. package/src/lib/terms.test.ts +67 -0
  713. package/src/lib/terms.ts +179 -0
  714. package/src/lib/wire-problems.test.ts +263 -0
  715. package/src/lib/wire-problems.ts +281 -0
  716. package/src/likec4/C4View.tsx +55 -0
  717. package/src/likec4/CanvasBridge.tsx +187 -0
  718. package/src/likec4/FlowView.tsx +235 -0
  719. package/src/likec4/InteractiveView.tsx +144 -0
  720. package/src/likec4/canvas-viewport.test.ts +149 -0
  721. package/src/likec4/canvas-viewport.ts +135 -0
  722. package/src/likec4/flow-edges.test.ts +51 -0
  723. package/src/likec4/flow-edges.ts +89 -0
  724. package/src/likec4/frame-theme.test.ts +76 -0
  725. package/src/likec4/frame-theme.ts +101 -0
  726. package/src/likec4/highlight-css.test.ts +49 -0
  727. package/src/likec4/highlight-css.ts +52 -0
  728. package/src/likec4/ids.test.ts +151 -0
  729. package/src/likec4/ids.ts +115 -0
  730. package/src/likec4/levels.tsx +70 -0
  731. package/src/likec4/mapping.test.ts +66 -0
  732. package/src/likec4/mapping.ts +54 -0
  733. package/src/likec4/reserved.json +141 -0
  734. package/src/likec4/view-index.ts +78 -0
  735. package/src/main.tsx +13 -0
  736. package/src/map/ContextMapGraph.tsx +274 -0
  737. package/src/map/ContextNode.tsx +70 -0
  738. package/src/map/FloatingEdge.tsx +86 -0
  739. package/src/merge.test.ts +1038 -0
  740. package/src/merge.ts +1004 -0
  741. package/src/pages/AdrDetail.tsx +242 -0
  742. package/src/pages/AdrIndex.tsx +114 -0
  743. package/src/pages/AggregatePage.tsx +727 -0
  744. package/src/pages/BlockPage.tsx +478 -0
  745. package/src/pages/CatalogFailure.tsx +111 -0
  746. package/src/pages/Changes.tsx +313 -0
  747. package/src/pages/ContextMap.tsx +534 -0
  748. package/src/pages/ContextPage.tsx +346 -0
  749. package/src/pages/EnumPage.tsx +282 -0
  750. package/src/pages/EventPage.tsx +686 -0
  751. package/src/pages/ExternalPage.tsx +204 -0
  752. package/src/pages/FlowDetail.tsx +663 -0
  753. package/src/pages/FlowIndex.tsx +303 -0
  754. package/src/pages/GraphPage.tsx +217 -0
  755. package/src/pages/Language.tsx +257 -0
  756. package/src/pages/ModulePage.tsx +366 -0
  757. package/src/pages/NotFound.tsx +54 -0
  758. package/src/pages/Overview.tsx +344 -0
  759. package/src/pages/Problems.tsx +360 -0
  760. package/src/pages/RegistryIndex.tsx +118 -0
  761. package/src/pages/ServicePage.tsx +746 -0
  762. package/src/pages/Settings.tsx +975 -0
  763. package/src/pages/StorePage.tsx +114 -0
  764. package/src/peek/PeekCard.test.ts +35 -0
  765. package/src/peek/PeekCard.tsx +118 -0
  766. package/src/peek/PeekLayer.tsx +147 -0
  767. package/src/peek/model.test.ts +156 -0
  768. package/src/peek/model.ts +253 -0
  769. package/src/routes.test.ts +222 -0
  770. package/src/routes.ts +449 -0
  771. package/src/selection/DetailPanel.tsx +1407 -0
  772. package/src/selection/hash.test.ts +73 -0
  773. package/src/selection/hash.ts +58 -0
  774. package/src/selection/model.test.ts +199 -0
  775. package/src/selection/model.ts +473 -0
  776. package/src/selection/pages.test.ts +115 -0
  777. package/src/selection/pages.ts +268 -0
  778. package/src/selection/store.test.ts +62 -0
  779. package/src/selection/store.ts +46 -0
  780. package/src/selection/sync.tsx +99 -0
  781. package/src/table/ColumnsMenu.tsx +72 -0
  782. package/src/table/DataTable.tsx +780 -0
  783. package/src/table/Facets.tsx +54 -0
  784. package/src/table/GroupMenu.tsx +69 -0
  785. package/src/table/Toolbar.tsx +190 -0
  786. package/src/table/cells.tsx +117 -0
  787. package/src/table/compare.test.ts +117 -0
  788. package/src/table/compare.ts +196 -0
  789. package/src/table/export.test.ts +88 -0
  790. package/src/table/export.ts +66 -0
  791. package/src/table/facet-groups.test.ts +74 -0
  792. package/src/table/facet-groups.ts +56 -0
  793. package/src/table/grouping.test.ts +84 -0
  794. package/src/table/grouping.ts +82 -0
  795. package/src/table/infer.test.ts +57 -0
  796. package/src/table/infer.ts +50 -0
  797. package/src/table/persist.test.ts +76 -0
  798. package/src/table/persist.ts +112 -0
  799. package/src/table/sort-url.test.ts +75 -0
  800. package/src/table/sort-url.ts +52 -0
  801. package/src/table/types.ts +80 -0
  802. package/src/table/useTableState.ts +220 -0
  803. package/src/test-catalog.ts +7 -0
  804. package/src/testing/estate/data__catalog.json +2847 -0
  805. package/src/testing/estate/data__flows.json +1724 -0
  806. package/src/testing/estate/examples__auth__portolan__api.json +212 -0
  807. package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
  808. package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
  809. package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
  810. package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
  811. package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
  812. package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
  813. package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
  814. package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
  815. package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
  816. package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
  817. package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
  818. package/src/testing/estate.ts +49 -0
  819. package/src/trail/Trail.tsx +81 -0
  820. package/src/trail/model.test.ts +157 -0
  821. package/src/trail/model.ts +189 -0
  822. package/src/trail/record.tsx +32 -0
  823. package/src/trail/store.test.ts +66 -0
  824. package/src/trail/store.ts +102 -0
  825. package/tsconfig.json +35 -0
  826. package/vite.config.ts +171 -0
@@ -0,0 +1,854 @@
1
+ // Generates likec4/ sources from the merged catalog.
2
+ //
3
+ // Everything LikeC4 renders is DECLARED here: the C4 views — the estate at
4
+ // level 1, its containers at level 2 as one picture and one per context, two
5
+ // per service — and one dynamic view per flow (plus a cross-context-only
6
+ // twin). Nothing in the app draws these pictures itself.
7
+ //
8
+ // node scripts/gen-likec4.mjs
9
+
10
+ import { writeFileSync, mkdirSync } from "node:fs";
11
+
12
+ import { loadCatalog } from "./catalog-sources.mjs";
13
+ import reserved from "../src/likec4/reserved.json" with { type: "json" };
14
+
15
+ // Every source, not one file: a service that publishes its own facts gets a
16
+ // C4 view like any other, and generating from a single file would leave it out
17
+ // of the pictures while the rest of the app knows about it.
18
+ const { catalog, manifest } = await loadCatalog();
19
+ const profiles = manifest.catalogs ?? [];
20
+
21
+ // --- ids (mirrors src/likec4/ids.ts; kept in step by src/likec4/ids.test.ts) ---
22
+ // The reserved words are not mirrored, they are the same file: a word the
23
+ // grammar has taken must be escaped identically on both sides or a clicked
24
+ // node stops finding what it stands for.
25
+ const RESERVED = new Set(reserved);
26
+ const safeId = (raw) => {
27
+ const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
28
+ return /^[0-9]/.test(cleaned) || RESERVED.has(cleaned) ? `_${cleaned}` : cleaned;
29
+ };
30
+ const fqn = (id) => id.split(".").map(safeId).join(".");
31
+ const flowViewId = (flow) => `flow_${safeId(flow.slug)}`;
32
+ const flowCrossViewId = (flow) => `${flowViewId(flow)}_cross`;
33
+ const contextViewId = (c) => `ctx_${safeId(c.id)}`;
34
+ const serviceViewId = (s) => `svc_${safeId(s.id)}`;
35
+ const serviceInsideViewId = (s) => `${serviceViewId(s)}_inside`;
36
+ const LANDSCAPE_VIEW = "landscape";
37
+ const CONTAINERS_VIEW = "containers";
38
+ const profileLandscapeViewId = (profile) => `${LANDSCAPE_VIEW}_${safeId(profile.id)}`;
39
+ const profileContainersViewId = (profile) => `${CONTAINERS_VIEW}_${safeId(profile.id)}`;
40
+
41
+ const q = (text) => `'${String(text).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
42
+
43
+ // The protocol a call travels on, read off the document that declares it.
44
+ // Mirrors sourceDocKind in src/lib/source-doc.ts, plus the generated stub a
45
+ // Go client is read from when no proto is vendored. Empty when the source
46
+ // says nothing a reader could name.
47
+ const protocolOf = (source) => {
48
+ const path = String(source ?? "").replace(/:\d+$/, "").toLowerCase();
49
+ if (path.endsWith(".proto") || path.endsWith(".pb.go")) return "gRPC";
50
+ if (path.endsWith(".wsdl")) return "SOAP";
51
+ if (path.endsWith(".graphql") || path.endsWith(".graphqls")) return "GraphQL";
52
+ if (/\.(ya?ml|json)$/.test(path)) return "HTTP";
53
+ return "";
54
+ };
55
+
56
+ // What a container box says under its name at level 2: what the service is
57
+ // built with when an extractor recorded it, and what it speaks, read off the
58
+ // contracts it provides. Both are facts the catalog holds; neither is guessed.
59
+ const technologyOf = (service) => {
60
+ const protocols = new Set(
61
+ service.provides.map((provided) => protocolOf(provided.source)).filter(Boolean),
62
+ );
63
+ return [...(service.technologies ?? []), ...[...protocols].sort()].join(" · ");
64
+ };
65
+
66
+ // --- shared visual language ------------------------------------------------
67
+ // These hex values are the light/dark-neutral midpoints of the --ctx-N and
68
+ // --status-* tokens in src/index.css. LikeC4 bakes colours into its own theme,
69
+ // so the seam between renderers is kept invisible by hand here.
70
+ const CTX_COLORS = ["#4a86c8", "#9a66cc", "#2aa196", "#c4703f", "#6b7cb4", "#b46b8b"];
71
+ const STATUS_COLORS = {
72
+ verified: "#2f9e63",
73
+ declared: "#b8912f",
74
+ unresolved: "#e0453f",
75
+ };
76
+ const STATUS_LINE = { verified: "solid", declared: "dashed", unresolved: "dotted" };
77
+ const STATUS_RANK = { verified: 0, declared: 1, unresolved: 2 };
78
+ const KIND_HEAD = { event: "onormal", rpc: "normal", call: "none" };
79
+
80
+ // Every relation carries the kind of fact it is, so a view can say which
81
+ // facts it draws: a level-2 picture with the bus on it leaves out the arrows
82
+ // that skip the bus, and nothing else has to change for it to do so.
83
+ const RELATION_KINDS = ["consumes", "calls", "bus", "reads", "persists", "uses"];
84
+
85
+ const contextColorName = (contextId) => {
86
+ const i = catalog.contexts.findIndex((c) => c.id === contextId);
87
+ return `ctx${(i < 0 ? 0 : i) % CTX_COLORS.length}`;
88
+ };
89
+
90
+ // --- collect participants that are not catalog services --------------------
91
+ const serviceIds = new Set(
92
+ catalog.contexts.flatMap((c) => c.services.map((s) => s.id)),
93
+ );
94
+ const rootParticipants = new Map(); // id -> kind
95
+ for (const flow of catalog.flows) {
96
+ for (const p of flow.participants) {
97
+ if (serviceIds.has(p.id)) continue;
98
+ rootParticipants.set(p.id, { kind: p.kind, label: p.label ?? p.id });
99
+ }
100
+ }
101
+ // A system outside the estate with a contract is a root participant whether or
102
+ // not a flow has walked to it yet: a call recorded on `consumes` lands on it,
103
+ // and the catalog knows what it is called, which a lane's bare id does not.
104
+ for (const external of catalog.externals ?? []) {
105
+ rootParticipants.set(external.id, { kind: "external", label: external.name || external.id });
106
+ }
107
+ // Event consumers that no service accounts for are real dependencies too.
108
+ for (const context of catalog.contexts) {
109
+ for (const service of context.services) {
110
+ for (const aggregate of service.aggregates) {
111
+ for (const event of aggregate.events) {
112
+ for (const consumer of event.consumers) {
113
+ if (serviceIds.has(consumer.service)) continue;
114
+ if (rootParticipants.has(consumer.service)) continue;
115
+ rootParticipants.set(consumer.service, {
116
+ kind: "unknown",
117
+ label: consumer.service,
118
+ });
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ // An unresolved call keeps the raw peer name, `risk.v1`, as its contract says;
126
+ // the flow that made the same call put the peer on a lane whose id carries no
127
+ // dot, `risk-v1`, because a dot would read as containment here. The two are
128
+ // one participant, joined by the label the lane kept, so the call resolves to
129
+ // the lane and not to a `v1` nested inside a `risk` that nobody declared.
130
+ const participantByLabel = new Map();
131
+ for (const [id, meta] of rootParticipants) participantByLabel.set(meta.label, id);
132
+ // A dot means containment only for catalog services. Root participants are
133
+ // declared as one safe identifier, so a broker named `river.orders` must be
134
+ // referenced as `river_orders`, not as an undeclared `orders` inside `river`.
135
+ const participantRef = (id) =>
136
+ rootParticipants.has(id) && !serviceIds.has(id) ? safeId(id) : fqn(id);
137
+ function peerParticipant(peer) {
138
+ if (serviceIds.has(peer) || rootParticipants.has(peer)) return peer;
139
+ return participantByLabel.get(peer);
140
+ }
141
+
142
+ // A store is a container the estate keeps its state in, so it belongs inside
143
+ // the service that owns it — not at the model root, where a flow's own store
144
+ // participants sit. The two are different ids and the catalog says nothing
145
+ // that would join them, so neither is guessed into the other.
146
+ const storesByOwner = new Map();
147
+ const storeById = new Map();
148
+ for (const store of catalog.stores ?? []) {
149
+ storeById.set(store.id, store);
150
+ const owned = storesByOwner.get(store.owner) ?? [];
151
+ owned.push(store);
152
+ storesByOwner.set(store.owner, owned);
153
+ }
154
+
155
+ /** Every step of a flow, branches and loops included, in declaration order. */
156
+ function walkFlowSteps(nodes, visit) {
157
+ for (const node of nodes) {
158
+ if (node.type === "step") visit(node);
159
+ else if (node.type === "parallel") node.branches.forEach((b) => walkFlowSteps(b, visit));
160
+ else if (node.type === "loop") walkFlowSteps(node.steps, visit);
161
+ else if (node.type === "alt") node.branches.forEach((b) => walkFlowSteps(b.steps, visit));
162
+ }
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // specification
167
+ // ---------------------------------------------------------------------------
168
+ const spec = [];
169
+ spec.push("// GENERATED by scripts/gen-likec4.mjs from the merged catalog — do not edit.");
170
+ spec.push("specification {");
171
+ CTX_COLORS.forEach((hex, i) => spec.push(` color ctx${i} ${hex}`));
172
+ for (const [name, hex] of Object.entries(STATUS_COLORS)) {
173
+ spec.push(` color ${name} ${hex}`);
174
+ }
175
+ spec.push("");
176
+ spec.push(" element context {");
177
+ spec.push(" style { shape rectangle opacity 5% border dashed }");
178
+ spec.push(" }");
179
+ spec.push(" element service {");
180
+ spec.push(" style { shape rectangle }");
181
+ spec.push(" }");
182
+ spec.push(" element aggregate {");
183
+ spec.push(" style { shape rectangle opacity 20% }");
184
+ spec.push(" }");
185
+ spec.push(" element event {");
186
+ spec.push(" style { shape rectangle opacity 30% }");
187
+ spec.push(" }");
188
+ spec.push(" element actor {");
189
+ spec.push(" style { shape person color muted }");
190
+ spec.push(" }");
191
+ spec.push(" element broker {");
192
+ spec.push(" style { shape queue color muted }");
193
+ spec.push(" }");
194
+ spec.push(" element store {");
195
+ spec.push(" style { shape storage color muted }");
196
+ spec.push(" }");
197
+ spec.push(" element external {");
198
+ spec.push(" style { shape rectangle color muted }");
199
+ spec.push(" }");
200
+ spec.push(" element unknown {");
201
+ spec.push(" style { shape rectangle color unresolved border dashed }");
202
+ spec.push(" }");
203
+ spec.push("");
204
+ for (const kind of RELATION_KINDS) spec.push(` relationship ${kind}`);
205
+ spec.push("}");
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // model
209
+ // ---------------------------------------------------------------------------
210
+ const model = [];
211
+ model.push("model {");
212
+
213
+ for (const [id, meta] of rootParticipants) {
214
+ model.push(` ${safeId(id)} = ${meta.kind} ${q(meta.label)}`);
215
+ }
216
+ model.push("");
217
+
218
+ for (const context of catalog.contexts) {
219
+ model.push(` ${safeId(context.id)} = context ${q(context.name)} {`);
220
+ model.push(` description ${q([context.kind, context.summary].filter(Boolean).join(" · "))}`);
221
+ model.push(` style { color ${contextColorName(context.id)} }`);
222
+ for (const service of context.services) {
223
+ model.push(` ${safeId(service.slug)} = service ${q(service.name)} {`);
224
+ model.push(` description ${q([service.kind, ...(service.technologies ?? []), `${service.repo}/${service.path}`].filter(Boolean).join(" · "))}`);
225
+ const technology = technologyOf(service);
226
+ if (technology) model.push(` technology ${q(technology)}`);
227
+ model.push(` style { color ${contextColorName(context.id)} }`);
228
+ for (const aggregate of service.aggregates) {
229
+ model.push(` ${safeId(aggregate.slug)} = aggregate ${q(aggregate.name)} {`);
230
+ for (const event of aggregate.events) {
231
+ const latest = event.versions[event.versions.length - 1];
232
+ model.push(` ${safeId(event.name)} = event ${q(event.name)} {`);
233
+ model.push(` description ${q(latest?.doc ?? "")}`);
234
+ model.push(" }");
235
+ }
236
+ model.push(" }");
237
+ }
238
+ for (const store of storesByOwner.get(service.id) ?? []) {
239
+ model.push(` ${safeId(store.slug)} = store ${q(store.name)} {`);
240
+ model.push(` technology ${q(store.kind)}`);
241
+ model.push(" }");
242
+ }
243
+ model.push(" }");
244
+ }
245
+ model.push(" }");
246
+ }
247
+ model.push("");
248
+
249
+ // relations: event consumption, then rpc calls
250
+ const relations = [];
251
+ for (const context of catalog.contexts) {
252
+ for (const service of context.services) {
253
+ for (const aggregate of service.aggregates) {
254
+ for (const event of aggregate.events) {
255
+ for (const consumer of event.consumers) {
256
+ // A service hearing its own event is a fact about the event, not
257
+ // an arrow between two boxes; the app's graph keeps it on the
258
+ // event for the same reason.
259
+ if (consumer.service === service.id) continue;
260
+ relations.push(
261
+ ` ${fqn(service.id)} -[consumes]-> ${participantRef(consumer.service)} ${q(event.name)} {\n` +
262
+ ` style { color ${consumer.status} line ${STATUS_LINE[consumer.status]} head onormal }\n` +
263
+ ` }`,
264
+ );
265
+ }
266
+ }
267
+ }
268
+ for (const call of service.consumes) {
269
+ const peer = peerParticipant(call.peer);
270
+ if (!peer) continue;
271
+ const method = call.id.split("/").pop() ?? call.id;
272
+ const protocol = protocolOf(call.source);
273
+ relations.push(
274
+ ` ${fqn(service.id)} -[calls]-> ${participantRef(peer)} ${q(method)}${protocol ? ` ${q(protocol)}` : ""} {\n` +
275
+ ` style { color ${call.status} line ${STATUS_LINE[call.status]} head normal }\n` +
276
+ ` }`,
277
+ );
278
+ }
279
+ // Owning a store is containment and needs no arrow. Reading one somebody
280
+ // else owns is the arrow worth drawing, and it is the same fact the
281
+ // Problems page reports: a database with a second reader.
282
+ for (const storeId of service.stores ?? []) {
283
+ const store = storeById.get(storeId);
284
+ if (!store || store.owner === service.id) continue;
285
+ relations.push(
286
+ ` ${fqn(service.id)} -[reads]-> ${fqn(store.id)} 'reads' {\n` +
287
+ ` style { color declared line ${STATUS_LINE.declared} head normal }\n` +
288
+ ` }`,
289
+ );
290
+ }
291
+ }
292
+ }
293
+
294
+ // What a table says it persists is the one arrow inside a service that is read
295
+ // off the schema rather than off the code: the aggregate goes into the store,
296
+ // and the tables that carry it are the label. A table persisting an aggregate
297
+ // another service owns draws the same arrow across the boundary, which is the
298
+ // crossing the Problems page reports.
299
+ const persists = new Map(); // "aggregate|store" -> { aggregate, store, tables:[] }
300
+ for (const store of catalog.stores ?? []) {
301
+ for (const table of store.tables) {
302
+ const aggregate = table.persists?.aggregate;
303
+ if (!aggregate) continue;
304
+ const key = `${aggregate}|${store.id}`;
305
+ const edge = persists.get(key) ?? { aggregate, store: store.id, tables: [] };
306
+ edge.tables.push(table.name);
307
+ persists.set(key, edge);
308
+ }
309
+ }
310
+ for (const edge of persists.values()) {
311
+ const label =
312
+ edge.tables.length > 3 ? `${edge.tables.length} tables` : edge.tables.join(", ");
313
+ relations.push(
314
+ ` ${fqn(edge.aggregate)} -[persists]-> ${fqn(edge.store)} ${q(label)} {\n` +
315
+ ` style { color declared line ${STATUS_LINE.declared} head normal }\n` +
316
+ ` }`,
317
+ );
318
+ }
319
+
320
+ // A broker is a container too, and the one the catalog only meets in flows:
321
+ // no repository imports the bus, and an event records its consumers service
322
+ // to service, as if the message went straight across. The hop through the
323
+ // broker is read off the steps that walk it — once per pair and direction,
324
+ // however many events or jobs travel that way — so a level-2 picture can put
325
+ // the bus between publisher and subscriber instead of an arrow that skips it.
326
+ const brokerIds = new Set(
327
+ [...rootParticipants].filter(([, meta]) => meta.kind === "broker").map(([id]) => id),
328
+ );
329
+ const busEdges = new Map(); // "from|to" -> { from, to, kind, labels:Set, status }
330
+ for (const flow of catalog.flows) {
331
+ walkFlowSteps(flow.steps, (step) => {
332
+ if (brokerIds.has(step.from) === brokerIds.has(step.to)) return;
333
+ const key = `${step.from}|${step.to}`;
334
+ const edge = busEdges.get(key) ?? {
335
+ from: step.from,
336
+ to: step.to,
337
+ kind: step.kind,
338
+ labels: new Set(),
339
+ status: "unresolved",
340
+ };
341
+ if (step.label) edge.labels.add(step.label);
342
+ if (STATUS_RANK[step.status] < STATUS_RANK[edge.status]) edge.status = step.status;
343
+ busEdges.set(key, edge);
344
+ });
345
+ }
346
+ for (const edge of busEdges.values()) {
347
+ const names = [...edge.labels].sort();
348
+ const noun = edge.kind === "event" ? "events" : "calls";
349
+ const label = names.length === 1 ? names[0] : `${names.length} ${noun}`;
350
+ relations.push(
351
+ ` ${participantRef(edge.from)} -[bus]-> ${participantRef(edge.to)} ${q(label)} {\n` +
352
+ ` style { color ${edge.status} line ${STATUS_LINE[edge.status]} head ${KIND_HEAD[edge.kind]} }\n` +
353
+ ` }`,
354
+ );
355
+ }
356
+
357
+ // An actor is the one participant nothing else in the catalog can place: no
358
+ // repository imports the customer, and no event names them. A flow step is
359
+ // the only evidence that they touch the estate at all, so it is read as a
360
+ // relation — once per pair, however many flows walk it.
361
+ const actorIds = new Set(
362
+ [...rootParticipants].filter(([, meta]) => meta.kind === "actor").map(([id]) => id),
363
+ );
364
+ //
365
+ // The lane is a fallback, though: an extractor opens every inbound endpoint
366
+ // with `client` because it has no way to see who is on the other end. Once a
367
+ // service inside the estate is known to call the same service, "somebody
368
+ // outside" is no longer the best explanation for its endpoints, and a declared
369
+ // actor arrow beside a real caller would only repeat the guess. An observed
370
+ // crossing is different: the collector saw a call with nobody of ours behind
371
+ // it, and that stays drawn whoever else calls the service.
372
+ const calledFromInside = new Set(); // service ids some other service calls over rpc
373
+ for (const context of catalog.contexts) {
374
+ for (const service of context.services) {
375
+ for (const call of service.consumes) {
376
+ const peer = peerParticipant(call.peer);
377
+ if (peer && peer !== service.id) calledFromInside.add(peer);
378
+ }
379
+ }
380
+ }
381
+ const actorEdges = new Map(); // "from|to" -> { from, to, flows:Set, status }
382
+ for (const flow of catalog.flows) {
383
+ walkFlowSteps(flow.steps, (step) => {
384
+ if (!actorIds.has(step.from) && !actorIds.has(step.to)) return;
385
+ if (step.from === step.to) return;
386
+ const key = `${step.from}|${step.to}`;
387
+ const edge = actorEdges.get(key) ?? {
388
+ from: step.from,
389
+ to: step.to,
390
+ flows: new Set(),
391
+ status: "unresolved",
392
+ };
393
+ edge.flows.add(flow.name);
394
+ // The best evidence any step offers: one observed crossing is enough to
395
+ // say the actor really does touch the estate there.
396
+ if (STATUS_RANK[step.status] < STATUS_RANK[edge.status]) edge.status = step.status;
397
+ actorEdges.set(key, edge);
398
+ });
399
+ }
400
+ for (const edge of actorEdges.values()) {
401
+ if (edge.status !== "verified" && calledFromInside.has(edge.to)) continue;
402
+ const names = [...edge.flows];
403
+ const label = names.length === 1 ? names[0] : `${names.length} flows`;
404
+ relations.push(
405
+ ` ${participantRef(edge.from)} -[uses]-> ${participantRef(edge.to)} ${q(label)} {\n` +
406
+ ` style { color ${edge.status} line ${STATUS_LINE[edge.status]} head normal }\n` +
407
+ ` }`,
408
+ );
409
+ }
410
+
411
+ model.push(...relations);
412
+ model.push("}");
413
+
414
+ // --- one arrow per pair of containers --------------------------------------
415
+ // The model keeps one relation per method, which is what the neighbours view
416
+ // and the flows are about. A container diagram is about which boxes talk and
417
+ // over what, and LikeC4 folds a pair's relations into one edge on its own but
418
+ // labels it `[...]`. The fold is given a label here, once per pair: the
419
+ // method when there is one, a count when there are more, the protocol read
420
+ // off the contract, and the best status any of the calls has.
421
+ const callPairs = new Map(); // "from|to" -> { from, to, methods:[], protocols:Set, status }
422
+ for (const context of catalog.contexts) {
423
+ for (const service of context.services) {
424
+ for (const call of service.consumes) {
425
+ const peer = peerParticipant(call.peer);
426
+ if (!peer) continue;
427
+ const key = `${service.id}|${peer}`;
428
+ const pair = callPairs.get(key) ?? {
429
+ from: service.id,
430
+ to: peer,
431
+ methods: [],
432
+ protocols: new Set(),
433
+ status: "unresolved",
434
+ };
435
+ pair.methods.push(call.id.split("/").pop() ?? call.id);
436
+ const protocol = protocolOf(call.source);
437
+ if (protocol) pair.protocols.add(protocol);
438
+ if (STATUS_RANK[call.status] < STATUS_RANK[pair.status]) pair.status = call.status;
439
+ callPairs.set(key, pair);
440
+ }
441
+ }
442
+ }
443
+
444
+ /** The `include a -> b with { … }` line that labels one pair's folded edge. */
445
+ function pairEdge(pair) {
446
+ const title =
447
+ pair.methods.length === 1 ? pair.methods[0] : `${pair.methods.length} calls`;
448
+ const technology = [...pair.protocols].sort().join(" · ");
449
+ const props = [
450
+ `title ${q(title)}`,
451
+ technology ? `technology ${q(technology)}` : "",
452
+ `color ${pair.status}`,
453
+ `line ${STATUS_LINE[pair.status]}`,
454
+ ].filter(Boolean);
455
+ return `include ${participantRef(pair.from)} -> ${participantRef(pair.to)} with { ${props.join(" ")} }`;
456
+ }
457
+
458
+ // A consumer arrow the bus already carries: the publisher's hop onto a broker
459
+ // and that broker's hop to the consumer are both drawn, so the arrow that
460
+ // skips the broker would say the same thing twice. One that is not carried —
461
+ // a consumer no flow has walked to — stays, as the only sign it listens.
462
+ const carriedByBus = []; // [publisher, consumer]
463
+ for (const context of catalog.contexts) {
464
+ for (const service of context.services) {
465
+ const consumers = new Set();
466
+ for (const aggregate of service.aggregates) {
467
+ for (const event of aggregate.events) {
468
+ for (const consumer of event.consumers) consumers.add(consumer.service);
469
+ }
470
+ }
471
+ consumers.delete(service.id);
472
+ for (const consumer of consumers) {
473
+ const via = [...brokerIds].some(
474
+ (broker) => busEdges.has(`${service.id}|${broker}`) && busEdges.has(`${broker}|${consumer}`),
475
+ );
476
+ if (via) carriedByBus.push([service.id, consumer]);
477
+ }
478
+ }
479
+ }
480
+
481
+ /**
482
+ * The level-2 predicates shared by the estate's container view and each
483
+ * context's: the folded call edges, then the consumer arrows the bus carries
484
+ * taken away.
485
+ */
486
+ function containerPredicates(pairs, carried, indent) {
487
+ return [
488
+ ...pairs.map((pair) => `${indent}${pairEdge(pair)}`),
489
+ ...carried.map(
490
+ ([from, to]) =>
491
+ `${indent}exclude ${participantRef(from)} -> ${participantRef(to)} where kind is consumes`,
492
+ ),
493
+ ];
494
+ }
495
+
496
+ // ---------------------------------------------------------------------------
497
+ // views
498
+ // ---------------------------------------------------------------------------
499
+
500
+ /**
501
+ * Emits one flow as LikeC4 dynamic-view steps.
502
+ *
503
+ * Every frame in the catalog has a frame here: `alt` becomes `alt { when … else
504
+ * … }`, which is the same vocabulary the step rail uses, so the picture and the
505
+ * list read alike. Terminality has no LikeC4 keyword of its own; it is wrapped
506
+ * in a `break` frame, which is what a sequence diagram calls a branch that
507
+ * leaves the flow rather than rejoining it.
508
+ */
509
+ // --- what comes back from a call (mirrors src/flow/answers.ts) -------------
510
+ // The catalog records hops and never replies, so a reply is looked up in the
511
+ // contract the step reaches. It rides on the step's own label rather than
512
+ // becoming an arrow of its own: a dynamic view numbers its steps, and those
513
+ // numbers are the ones the rail beside the canvas counts.
514
+ const methodOf = new Map();
515
+ const serviceById = new Map();
516
+ for (const context of catalog.contexts) {
517
+ for (const service of context.services) {
518
+ serviceById.set(service.id, service);
519
+ for (const provided of service.provides) {
520
+ for (const method of provided.methods) {
521
+ methodOf.set(`${provided.id}/${method.name}`, method);
522
+ }
523
+ }
524
+ }
525
+ }
526
+ // What comes back from a third party is in its document too.
527
+ for (const external of catalog.externals ?? []) {
528
+ for (const provided of external.provides) {
529
+ for (const method of provided.methods) {
530
+ methodOf.set(`${provided.id}/${method.name}`, method);
531
+ }
532
+ }
533
+ }
534
+
535
+ function answerOf(step) {
536
+ if (step.kind !== "rpc") return "";
537
+ if (step.ref) return methodOf.get(step.ref)?.response ?? "";
538
+ const service = serviceById.get(step.to);
539
+ if (!service || !step.label) return "";
540
+ for (const provided of service.provides) {
541
+ const found = provided.methods.find((m) => m.name === step.label);
542
+ if (found) return found.response ?? "";
543
+ }
544
+ return "";
545
+ }
546
+
547
+ function emitSteps(nodes, out, indent) {
548
+ for (const node of nodes) {
549
+ if (node.type === "step") {
550
+ const answer = answerOf(node);
551
+ const label =
552
+ (node.label ?? node.ref ?? node.kind) + (answer ? ` → ${answer}` : "");
553
+ const attrs = [
554
+ `color ${node.status}`,
555
+ `line ${STATUS_LINE[node.status]}`,
556
+ `head ${KIND_HEAD[node.kind]}`,
557
+ ];
558
+ // The condition used to be pasted onto every label because there was no
559
+ // frame to carry it. There is one now, so the label is just the message.
560
+ const notes = [];
561
+ if (node.note) notes.push(node.note);
562
+ if (node.line) notes.push(node.line);
563
+ out.push(`${indent}${participantRef(node.from)} -> ${participantRef(node.to)} ${q(label)} {`);
564
+ out.push(`${indent} ${attrs.join(" ")}`);
565
+ if (notes.length > 0) out.push(`${indent} notes ${q(notes.join(" — "))}`);
566
+ out.push(`${indent}}`);
567
+ continue;
568
+ }
569
+ if (node.type === "parallel") {
570
+ out.push(`${indent}par ${node.title ? `${q(node.title)} ` : ""}{`);
571
+ for (const branch of node.branches) emitSteps(branch, out, `${indent} `);
572
+ out.push(`${indent}}`);
573
+ continue;
574
+ }
575
+ if (node.type === "loop") {
576
+ out.push(`${indent}loop ${q(node.title)} {`);
577
+ emitSteps(node.steps, out, `${indent} `);
578
+ out.push(`${indent}}`);
579
+ continue;
580
+ }
581
+ if (node.type === "alt") {
582
+ out.push(`${indent}alt {`);
583
+ node.branches.forEach((branch, i) => {
584
+ const keyword = i === 0 ? "when" : "else";
585
+ out.push(`${indent} ${keyword} ${q(branch.title)} {`);
586
+ if (branch.terminal) {
587
+ out.push(`${indent} break 'ends the flow' {`);
588
+ emitSteps(branch.steps, out, `${indent} `);
589
+ out.push(`${indent} }`);
590
+ } else {
591
+ emitSteps(branch.steps, out, `${indent} `);
592
+ }
593
+ out.push(`${indent} }`);
594
+ });
595
+ out.push(`${indent}}`);
596
+ }
597
+ }
598
+ }
599
+
600
+ /** Keeps only steps that actually leave a bounded context. */
601
+ function crossContextOnly(nodes, contextOf) {
602
+ const keep = (step) => {
603
+ if (step.kind === "call") return false;
604
+ if (step.from === step.to) return false;
605
+ const a = contextOf(step.from);
606
+ const b = contextOf(step.to);
607
+ return !(a !== null && a === b);
608
+ };
609
+ const walk = (list) => {
610
+ const out = [];
611
+ for (const node of list) {
612
+ if (node.type === "step") {
613
+ if (keep(node)) out.push(node);
614
+ } else if (node.type === "parallel") {
615
+ const branches = node.branches.map(walk).filter((b) => b.length > 0);
616
+ if (branches.length > 0) out.push({ ...node, branches });
617
+ } else if (node.type === "loop") {
618
+ const steps = walk(node.steps);
619
+ if (steps.length > 0) out.push({ ...node, steps });
620
+ } else if (node.type === "alt") {
621
+ const branches = node.branches
622
+ .map((b) => ({ ...b, steps: walk(b.steps) }))
623
+ .filter((b) => b.steps.length > 0);
624
+ if (branches.length > 0) out.push({ ...node, branches });
625
+ }
626
+ }
627
+ return out;
628
+ };
629
+ return walk(nodes);
630
+ }
631
+
632
+ const views = [];
633
+ views.push("views {");
634
+
635
+ // --- C4 level 1: the estate and what stands outside it ---------------------
636
+ // Contexts as black boxes, and the participants that are not the estate's to
637
+ // build: the people who use it, the systems it pays and asks, and the
638
+ // consumers nothing in the catalog accounts for. Brokers and stores are left
639
+ // out on purpose — they are containers, and they belong to the level below.
640
+ //
641
+ // This is not /graph's picture: that one is services against the events they
642
+ // carry, and it is drawn by React Flow. No picture is drawn by both.
643
+ const OUTSIDE = new Set(["actor", "external", "unknown"]);
644
+ const outside = [...rootParticipants]
645
+ .filter(([, meta]) => OUTSIDE.has(meta.kind))
646
+ .map(([id]) => safeId(id));
647
+ views.push(` view ${LANDSCAPE_VIEW} {`);
648
+ views.push(" title 'Estate'");
649
+ views.push(
650
+ catalog.contexts.some((context) => context.kind && context.kind !== "bounded-context")
651
+ ? " description 'Every architecture group, and everything outside the estate that touches one.'"
652
+ : " description 'Every bounded context, and everything outside the estate that touches one.'",
653
+ );
654
+ views.push(
655
+ ` include ${[...catalog.contexts.map((c) => safeId(c.id)), ...outside].join(", ")}`,
656
+ );
657
+ views.push(" }");
658
+ views.push("");
659
+
660
+ // --- C4 level 2: every container in the estate -----------------------------
661
+ // The same boxes as the landscape, opened: each context with its services
662
+ // inside, each service with the store it owns inside that, the brokers the
663
+ // flows walk through, and the same outsiders as above. One picture of what
664
+ // runs, what it talks to and over which protocol — the diagram most people
665
+ // mean when they ask for "the architecture".
666
+ const brokersOfService = new Map(); // service id -> Set of broker ids
667
+ for (const edge of busEdges.values()) {
668
+ const [service, broker] = brokerIds.has(edge.to) ? [edge.from, edge.to] : [edge.to, edge.from];
669
+ const set = brokersOfService.get(service) ?? new Set();
670
+ set.add(broker);
671
+ brokersOfService.set(service, set);
672
+ }
673
+ const allServices = catalog.contexts.flatMap((c) => c.services);
674
+ const drawnBrokers = [...new Set(allServices.flatMap((s) => [...(brokersOfService.get(s.id) ?? [])]))];
675
+ views.push(` view ${CONTAINERS_VIEW} {`);
676
+ views.push(" title 'Containers'");
677
+ views.push(
678
+ " description 'Every service, the store it keeps its state in, the brokers between them, and everything outside the estate that touches one.'",
679
+ );
680
+ views.push(
681
+ ` include ${[
682
+ ...catalog.contexts.map((c) => safeId(c.id)),
683
+ ...allServices.map((s) => fqn(s.id)),
684
+ ...(catalog.stores ?? []).map((store) => fqn(store.id)),
685
+ ...drawnBrokers.map(safeId),
686
+ ...outside,
687
+ ].join(", ")}`,
688
+ );
689
+ views.push(...containerPredicates([...callPairs.values()], carriedByBus, " "));
690
+ views.push(" }");
691
+ views.push("");
692
+
693
+ // The model is the union so shared edges and per-context views are declared
694
+ // once. These two views per profile are the isolated estate entry points the
695
+ // UI uses: only the configured groups and the outsiders their own flows name
696
+ // are included.
697
+ for (const profile of profiles) {
698
+ const profileContexts = catalog.contexts.filter((context) => profile.contexts.includes(context.id));
699
+ const profileContextIds = new Set(profileContexts.map((context) => context.id));
700
+ const profileServices = profileContexts.flatMap((context) => context.services);
701
+ const profileServiceIds = new Set(profileServices.map((service) => service.id));
702
+ const profileRoots = new Set();
703
+ for (const flow of catalog.flows) {
704
+ if (!profileContextIds.has(flow.owner)) continue;
705
+ for (const participant of flow.participants) {
706
+ if (!profileServiceIds.has(participant.id)) profileRoots.add(participant.id);
707
+ }
708
+ }
709
+ for (const service of profileServices) {
710
+ for (const call of service.consumes) {
711
+ const peer = peerParticipant(call.peer);
712
+ if (peer && !profileServiceIds.has(peer)) profileRoots.add(peer);
713
+ }
714
+ for (const aggregate of service.aggregates) {
715
+ for (const event of aggregate.events) {
716
+ for (const consumer of event.consumers) {
717
+ if (!profileServiceIds.has(consumer.service)) profileRoots.add(consumer.service);
718
+ }
719
+ }
720
+ }
721
+ }
722
+ const profileOutside = [...profileRoots]
723
+ .filter((id) => OUTSIDE.has(rootParticipants.get(id)?.kind))
724
+ .map(safeId);
725
+ const profileBrokers = [...profileRoots]
726
+ .filter((id) => rootParticipants.get(id)?.kind === "broker")
727
+ .map(safeId);
728
+ const profileStores = (catalog.stores ?? []).filter((store) => profileServiceIds.has(store.owner));
729
+
730
+ views.push(` view ${profileLandscapeViewId(profile)} {`);
731
+ views.push(` title ${q(profile.title)}`);
732
+ views.push(` description 'The groups and outside systems selected by this catalog profile.'`);
733
+ views.push(` include ${[...profileContexts.map((context) => safeId(context.id)), ...profileOutside].join(", ")}`);
734
+ views.push(" }");
735
+ views.push("");
736
+
737
+ views.push(` view ${profileContainersViewId(profile)} {`);
738
+ views.push(` title ${q(`${profile.title} containers`)}`);
739
+ views.push(` description 'The services, stores and transports selected by this catalog profile.'`);
740
+ views.push(` include ${[
741
+ ...profileContexts.map((context) => safeId(context.id)),
742
+ ...profileServices.map((service) => fqn(service.id)),
743
+ ...profileStores.map((store) => fqn(store.id)),
744
+ ...profileBrokers,
745
+ ...profileOutside,
746
+ ].join(", ")}`);
747
+ views.push(...containerPredicates(
748
+ [...callPairs.values()].filter((pair) => profileServiceIds.has(pair.from) && profileServiceIds.has(pair.to)),
749
+ carriedByBus.filter((pair) => profileServiceIds.has(pair.from) && profileServiceIds.has(pair.to)),
750
+ " ",
751
+ ));
752
+ views.push(" }");
753
+ views.push("");
754
+ }
755
+
756
+ for (const context of catalog.contexts) {
757
+ // --- C4 level 2: the containers of one context --------------------------
758
+ // Services, and the databases they keep their state in. A store is a
759
+ // grandchild of the context, so `*` does not reach it and each one is named:
760
+ // the ones this context's services own, and the ones they only read, which
761
+ // is how a service reading someone else's database shows up as a crossing
762
+ // rather than as a box inside its own walls. The brokers these services
763
+ // publish on or listen to arrive with `*` as neighbours, the way the other
764
+ // contexts do, and are not named: naming the bus would also pull in every
765
+ // other context's hops onto it, which are not this context's picture.
766
+ const contextStores = new Set();
767
+ const contextServiceIds = new Set(context.services.map((s) => s.id));
768
+ for (const service of context.services) {
769
+ for (const store of storesByOwner.get(service.id) ?? []) contextStores.add(store.id);
770
+ for (const storeId of service.stores ?? []) {
771
+ if (storeById.has(storeId)) contextStores.add(storeId);
772
+ }
773
+ }
774
+ const include = ["*", ...[...contextStores].map(fqn)].join(", ");
775
+ // Only the pairs the picture holds both ends of: a neighbour in another
776
+ // context is drawn folded into its context, and naming one of its services
777
+ // would unfold it into a second box.
778
+ const inside = ([from, to]) => contextServiceIds.has(from) && contextServiceIds.has(to);
779
+ const pairs = [...callPairs.values()].filter((pair) => inside([pair.from, pair.to]));
780
+ views.push(` view ${contextViewId(context)} of ${safeId(context.id)} {`);
781
+ views.push(` title ${q(context.name)}`);
782
+ views.push(` include ${include}`);
783
+ views.push(...containerPredicates(pairs, carriedByBus.filter(inside), " "));
784
+ views.push(" }");
785
+
786
+ for (const service of context.services) {
787
+ // Still level 2, one service deep: the service as a box, and everything
788
+ // that reaches it or that it reaches. Its own parts are excluded by name
789
+ // rather than the subject included by name, because inside a scoped view a
790
+ // dotted reference is read relative to the scope first — and `auth.auth`
791
+ // read from inside `auth.auth` resolves to nothing at all.
792
+ const parts = [
793
+ ...service.aggregates.map((a) => safeId(a.slug)),
794
+ ...(storesByOwner.get(service.id) ?? []).map((store) => safeId(store.slug)),
795
+ ];
796
+ views.push(` view ${serviceViewId(service)} of ${fqn(service.id)} {`);
797
+ views.push(` title ${q(`${service.name} — neighbours`)}`);
798
+ views.push(" include *, -> *, * ->");
799
+ if (parts.length > 0) views.push(` exclude ${parts.join(", ")}`);
800
+ views.push(" }");
801
+
802
+ // --- C4 level 3: inside one service ------------------------------------
803
+ // Its aggregates and its stores. Events are in the model but not in this
804
+ // picture: a service with eleven of them would draw a wall of boxes where
805
+ // the page already lists them, one line each.
806
+ views.push(` view ${serviceInsideViewId(service)} of ${fqn(service.id)} {`);
807
+ views.push(` title ${q(`${service.name} — inside`)}`);
808
+ views.push(" include *");
809
+ views.push(" }");
810
+ }
811
+ }
812
+ views.push("");
813
+
814
+ for (const flow of catalog.flows) {
815
+ const contexts = new Map(flow.participants.map((p) => [p.id, p.context]));
816
+ const contextOf = (id) => contexts.get(id) ?? null;
817
+
818
+ views.push(` dynamic view ${flowViewId(flow)} {`);
819
+ views.push(` title ${q(flow.name)}`);
820
+ views.push(` description ${q(flow.summary)}`);
821
+ const body = [];
822
+ emitSteps(flow.steps, body, " ");
823
+ views.push(...body);
824
+ views.push(" }");
825
+ views.push("");
826
+
827
+ const cross = crossContextOnly(flow.steps, contextOf);
828
+ views.push(` dynamic view ${flowCrossViewId(flow)} {`);
829
+ views.push(` title ${q(`${flow.name} — crossings only`)}`);
830
+ const crossBody = [];
831
+ emitSteps(cross, crossBody, " ");
832
+ if (crossBody.length === 0) {
833
+ // A flow with no crossing at all still needs a renderable view.
834
+ const first = flow.participants[0];
835
+ if (first) {
836
+ const firstRef = participantRef(first.id);
837
+ crossBody.push(` ${firstRef} -> ${firstRef} 'no cross-context step'`);
838
+ }
839
+ }
840
+ views.push(...crossBody);
841
+ views.push(" }");
842
+ views.push("");
843
+ }
844
+ views.push("}");
845
+
846
+ mkdirSync("likec4", { recursive: true });
847
+ writeFileSync("likec4/spec.c4", `${spec.join("\n")}\n`);
848
+ writeFileSync("likec4/model.c4", `// GENERATED — do not edit.\n${model.join("\n")}\n`);
849
+ writeFileSync("likec4/views.c4", `// GENERATED — do not edit.\n${views.join("\n")}\n`);
850
+
851
+ console.log(
852
+ `wrote likec4/spec.c4, likec4/model.c4, likec4/views.c4 ` +
853
+ `(${catalog.flows.length * 2} dynamic views, ${relations.length} relations)`,
854
+ );