@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,51 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { catalog } from "../data";
3
+ import { walkSteps } from "../catalog";
4
+ import { hiddenStepIds } from "../flow/cross-context";
5
+ import { drawnStepIds, pairEdgesToSteps } from "./flow-edges";
6
+
7
+ describe("pairEdgesToSteps", () => {
8
+ it("pairs by position, both ways", () => {
9
+ const pairing = pairEdgesToSteps(
10
+ ["step-01", "step-02:par.01", "step-02:par.02"],
11
+ ["s1", "s2", "s3"],
12
+ );
13
+ expect(pairing.stepOf.get("step-02:par.01")).toBe("s2");
14
+ expect(pairing.edgeOf.get("s3")).toBe("step-02:par.02");
15
+ });
16
+
17
+ /**
18
+ * A length mismatch means the generator and the view have drifted. Guessing
19
+ * would light the wrong arrow, which is worse than lighting none, so the
20
+ * pairing is abandoned and highlighting simply stops.
21
+ */
22
+ it("abandons the pairing rather than guessing when the lists differ", () => {
23
+ const pairing = pairEdgesToSteps(["step-01"], ["s1", "s2"]);
24
+ expect(pairing.stepOf.size).toBe(0);
25
+ expect(pairing.edgeOf.size).toBe(0);
26
+ });
27
+ });
28
+
29
+ describe("drawnStepIds", () => {
30
+ it("lists every step of the full view, in rail order", () => {
31
+ for (const flow of catalog.flows) {
32
+ expect(drawnStepIds(flow, false), flow.slug).toEqual(
33
+ walkSteps(flow.steps).map((s) => s.id),
34
+ );
35
+ }
36
+ });
37
+
38
+ it("drops exactly what the cross-context view hides", () => {
39
+ for (const flow of catalog.flows) {
40
+ const hidden = hiddenStepIds(flow);
41
+ const drawn = drawnStepIds(flow, true);
42
+ expect(
43
+ drawn.some((id) => hidden.has(id)),
44
+ flow.slug,
45
+ ).toBe(false);
46
+ expect(drawn.length, flow.slug).toBe(
47
+ walkSteps(flow.steps).length - hidden.size,
48
+ );
49
+ }
50
+ });
51
+ });
@@ -0,0 +1,89 @@
1
+ // Flow steps have catalog ids ("s6"); the LikeC4 edges that draw them have
2
+ // generated ones ("step-04:par.01"). Nothing in the model carries the catalog
3
+ // id across, so the two are paired by position.
4
+ //
5
+ // That is sound because it is the same walk twice: the generator emits steps in
6
+ // the order walkSteps visits them — a step, then each branch of a parallel,
7
+ // then each branch of an alt, then the body of a loop — and the view keeps that
8
+ // order in its edge list. If the two lists ever differ in length the pairing is
9
+ // abandoned rather than guessed at, and the diagram simply stops highlighting.
10
+
11
+ import type { Flow } from "../catalog";
12
+ import { walkSteps } from "../catalog";
13
+ import { hiddenStepIds } from "../flow/cross-context";
14
+
15
+ export interface EdgeStepPairing {
16
+ /** LikeC4 edge id -> catalog step id */
17
+ stepOf: Map<string, string>;
18
+ /** catalog step id -> LikeC4 edge id */
19
+ edgeOf: Map<string, string>;
20
+ }
21
+
22
+ export const EMPTY_PAIRING: EdgeStepPairing = {
23
+ stepOf: new Map(),
24
+ edgeOf: new Map(),
25
+ };
26
+
27
+ export function pairEdgesToSteps(
28
+ edgeIds: readonly string[],
29
+ stepIds: readonly string[],
30
+ ): EdgeStepPairing {
31
+ if (edgeIds.length !== stepIds.length) return EMPTY_PAIRING;
32
+ const stepOf = new Map<string, string>();
33
+ const edgeOf = new Map<string, string>();
34
+ edgeIds.forEach((edgeId, i) => {
35
+ const stepId = stepIds[i];
36
+ if (stepId === undefined) return;
37
+ stepOf.set(edgeId, stepId);
38
+ edgeOf.set(stepId, edgeId);
39
+ });
40
+ return { stepOf, edgeOf };
41
+ }
42
+
43
+ /**
44
+ * The frames an edge sits inside, outermost first.
45
+ *
46
+ * LikeC4 names an edge by the path that reaches it — "step-07:alt.02:else.01:
47
+ * break.01" is the first message of the break inside the second branch of the
48
+ * alt at step 7 — and every frame on the way has a node of its own under the
49
+ * prefix that names it. So the containment the app needs is already written in
50
+ * the id, and reading it back out beats pairing a second pair of trees.
51
+ */
52
+ export function frameAncestors(edgeId: string): string[] {
53
+ const parts = edgeId.split(".");
54
+ const out: string[] = [];
55
+ for (let i = 1; i < parts.length; i += 1) {
56
+ out.push(parts.slice(0, i).join("."));
57
+ }
58
+ return out;
59
+ }
60
+
61
+ /**
62
+ * Frames none of whose edges are on the chosen path.
63
+ *
64
+ * A frame with even one live edge inside it stays lit: the alt itself still
65
+ * happens on this path, it is the arms not taken that do not. Only a frame
66
+ * that contributes nothing at all is hidden.
67
+ */
68
+ export function offPathFrameIds(
69
+ edgeIds: readonly string[],
70
+ onPath: ReadonlySet<string>,
71
+ ): string[] {
72
+ const live = new Set<string>();
73
+ const seen: string[] = [];
74
+ for (const edgeId of edgeIds) {
75
+ for (const frame of frameAncestors(edgeId)) {
76
+ if (!seen.includes(frame)) seen.push(frame);
77
+ if (onPath.has(edgeId)) live.add(frame);
78
+ }
79
+ }
80
+ return seen.filter((frame) => !live.has(frame));
81
+ }
82
+
83
+ /** The step ids a flow view draws, in the order its edges are emitted. */
84
+ export function drawnStepIds(flow: Flow, crossOnly: boolean): string[] {
85
+ const steps = walkSteps(flow.steps);
86
+ if (!crossOnly) return steps.map((s) => s.id);
87
+ const hidden = hiddenStepIds(flow);
88
+ return steps.filter((s) => !hidden.has(s.id)).map((s) => s.id);
89
+ }
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ buildFrameCss,
4
+ buildFocusedPathCss,
5
+ buildWalkthroughCss,
6
+ } from "./frame-theme";
7
+
8
+ describe("buildFrameCss", () => {
9
+ const css = buildFrameCss();
10
+
11
+ it("retunes every subflow keyword the generator can emit", () => {
12
+ // `break` is the one portolan's own generator writes, for a terminal alt
13
+ // branch; the others come with LikeC4's vocabulary.
14
+ for (const keyword of ["alt", "par", "loop", "break"]) {
15
+ expect(css).toContain(`--colors-subflow-${keyword}:`);
16
+ expect(css).toContain(`--colors-subflow-${keyword}-border:`);
17
+ expect(css).toContain(`--colors-subflow-${keyword}-text:`);
18
+ }
19
+ });
20
+
21
+ it("paints only in portolan tokens, so a theme swap carries the canvas", () => {
22
+ const values = css
23
+ .split("\n")
24
+ .filter((line) => line.includes(":") && !line.includes("{"))
25
+ .map((line) => line.slice(line.indexOf(":") + 1));
26
+ expect(values.length).toBeGreaterThan(0);
27
+ for (const value of values) {
28
+ expect(value).toMatch(/var\(--(surface|surface-2|border|border-strong|fg-muted)\)/);
29
+ }
30
+ });
31
+
32
+ it("scopes itself to the canvas rather than the document", () => {
33
+ expect(css.startsWith(".react-flow {")).toBe(true);
34
+ });
35
+ });
36
+
37
+ describe("buildFocusedPathCss", () => {
38
+ it("says nothing when every edge, label, node and frame is on the path", () => {
39
+ expect(buildFocusedPathCss([], [], [], [])).toBe("");
40
+ });
41
+
42
+ it("hides off-path edges, labels, participants and empty frames", () => {
43
+ const css = buildFocusedPathCss(
44
+ ["step-08:alt.02:else.01"],
45
+ [8],
46
+ ["unused_provider"],
47
+ ["step-07:alt.02:else"],
48
+ );
49
+ expect(css).toContain('.react-flow__edge[data-id="step-08:alt.02:else.01"]');
50
+ expect(css).toContain(".react-flow__edgelabel-renderer > :nth-child(8)");
51
+ expect(css).toContain('.react-flow__node[data-id="unused_provider"]');
52
+ expect(css).toContain('[data-id="step-07:alt.02:else"]');
53
+ expect(css).toContain("display: none");
54
+ });
55
+
56
+ it("escapes a quote rather than letting it close the selector", () => {
57
+ expect(buildFocusedPathCss(['a"b'], [], [], [])).toContain('data-id="a\\"b"');
58
+ });
59
+ });
60
+
61
+ describe("buildWalkthroughCss", () => {
62
+ const css = buildWalkthroughCss();
63
+
64
+ it("hides the panel LikeC4 opens over the canvas during playback", () => {
65
+ expect(css).toContain("display: none");
66
+ });
67
+
68
+ it("aims only at a full-height box in the diagram's top-left corner", () => {
69
+ // Narrow on purpose: anything looser would take the canvas with it.
70
+ expect(css).toContain(".likec4-root >");
71
+ expect(css).toContain(".pos_absolute");
72
+ expect(css).toContain(".top_0");
73
+ expect(css).toContain(".left_0");
74
+ expect(css).toContain(".h_100cqh");
75
+ });
76
+ });
@@ -0,0 +1,101 @@
1
+ // The seam between two renderers, painted over.
2
+ //
3
+ // LikeC4 draws the alt/par/loop frames of a sequence in its own palette —
4
+ // saturated fills, one hue per keyword — which inside portolan reads as the
5
+ // loudest thing on the canvas. It is the wrong thing to be loudest. A frame
6
+ // says *under what condition*, and the arrows say *what happens*; the arrows
7
+ // already carry status colour, and a frame competing with them for the same
8
+ // glance costs the reader the one signal the picture is for.
9
+ //
10
+ // So the frames are turned down to the app's own surfaces. This is not a hack
11
+ // around a missing prop: `--colors-subflow-*` are the tokens LikeC4 publishes
12
+ // for exactly this, and every value below is a portolan token, so a theme swap
13
+ // carries the canvas with it.
14
+
15
+ /** Every subflow keyword LikeC4 paints, including the ones our generator emits. */
16
+ const KEYWORDS = ["alt", "par", "loop", "break", "opt", "try"] as const;
17
+
18
+ /** Frame fill: present enough to bound the branch, quiet enough to sit under it. */
19
+ const FILL = "color-mix(in srgb, var(--surface) 60%, transparent)";
20
+ /** The keyword badge and the header strip, one step up from the fill. */
21
+ const HEADER = "color-mix(in srgb, var(--surface-2) 70%, transparent)";
22
+
23
+ /**
24
+ * The stylesheet, as a string, for the diagram's own root. Scoped to
25
+ * `.react-flow` because that is the one class React Flow guarantees and the
26
+ * custom properties inherit from it down to every frame inside.
27
+ */
28
+ export function buildFrameCss(): string {
29
+ const vars = KEYWORDS.flatMap((keyword) => [
30
+ `--colors-subflow-${keyword}: ${FILL};`,
31
+ `--colors-subflow-${keyword}-border: var(--border);`,
32
+ `--colors-subflow-${keyword}-header: ${HEADER};`,
33
+ `--colors-subflow-${keyword}-hovered: var(--surface-2);`,
34
+ `--colors-subflow-${keyword}-label: var(--border-strong);`,
35
+ `--colors-subflow-${keyword}-text: var(--fg-muted);`,
36
+ ]);
37
+ // The walkthrough paints parallel frames from a token of its own, and a
38
+ // parallel that changes colour the moment playback starts reads as a
39
+ // different frame rather than the same one being visited.
40
+ vars.push("--colors-likec4-walkthrough-parallel-frame: " + FILL + ";");
41
+ return `.react-flow {\n ${vars.join("\n ")}\n}`;
42
+ }
43
+
44
+ /**
45
+ * LikeC4's own sequence outline, hidden.
46
+ *
47
+ * Starting a walkthrough opens a panel down the left of the canvas listing the
48
+ * steps and marking the active one — which is, to the pixel, what the rail
49
+ * beside the canvas already is. Two outlines is one too many, and this is the
50
+ * one lying on top of the picture.
51
+ *
52
+ * There is no prop for it, so it goes by selector. The class names are Panda's
53
+ * atomic ones, which are derived from the declarations rather than hand-named,
54
+ * so a LikeC4 upgrade that restyles the panel will bring it back — visibly, as
55
+ * a panel that reappeared, rather than silently as something broken. The
56
+ * combination is narrow enough that nothing else in the canvas matches it: a
57
+ * full-height box pinned to the top-left corner of the diagram root.
58
+ *
59
+ * Its width is not reclaimed. LikeC4 frames each walkthrough step inside the
60
+ * canvas MINUS this panel, so hiding it leaves that band empty rather than
61
+ * giving it to the diagram — the same space the panel would have taken, just
62
+ * not covering anything.
63
+ */
64
+ export function buildWalkthroughCss(): string {
65
+ return [
66
+ ".likec4-root > .pos_absolute.top_0.left_0.h_100cqh {",
67
+ " display: none;",
68
+ "}",
69
+ ].join("\n");
70
+ }
71
+
72
+ /**
73
+ * Removes everything outside the executable path the reader selected.
74
+ *
75
+ * The default canvas remains the complete decision tree. Once a path is
76
+ * chosen, keeping its siblings as pale scaffolding defeats the choice on the
77
+ * large provider graphs this control is for, so both their arrows and their
78
+ * now-empty frames leave the picture.
79
+ */
80
+ export function buildFocusedPathCss(
81
+ edgeIds: readonly string[],
82
+ edgeLabelPositions: readonly number[],
83
+ nodeIds: readonly string[],
84
+ frameIds: readonly string[],
85
+ ): string {
86
+ const selectors = [
87
+ ...edgeIds.map((id) => `.react-flow__edge[data-id="${cssQuote(id)}"]`),
88
+ ...edgeLabelPositions.map(
89
+ (position) => `.react-flow__edgelabel-renderer > :nth-child(${position})`,
90
+ ),
91
+ ...nodeIds.map((id) => `.react-flow__node[data-id="${cssQuote(id)}"]`),
92
+ ...frameIds.map((id) => `.react-flow__node[data-id="${cssQuote(id)}"]`),
93
+ ];
94
+ if (selectors.length === 0) return "";
95
+ return `${selectors.join(",\n")} {\n display: none;\n}`;
96
+ }
97
+
98
+ /** CSS string escape for an attribute value. */
99
+ function cssQuote(value: string): string {
100
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
101
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildHighlightCss } from "./highlight-css";
3
+
4
+ describe("buildHighlightCss", () => {
5
+ it("writes nothing when nothing is selected", () => {
6
+ expect(buildHighlightCss([], [])).toBe("");
7
+ });
8
+
9
+ it("dims the other nodes and outlines the one", () => {
10
+ const css = buildHighlightCss(["shop.oms"], []);
11
+ expect(css).toContain('.react-flow__node:not([data-id="shop.oms"])');
12
+ expect(css).toContain('.react-flow__node[data-id="shop.oms"]');
13
+ expect(css).toContain("outline: 2px solid var(--accent)");
14
+ });
15
+
16
+ /**
17
+ * Nodes and edges are dimmed independently. Lighting a step must not grey out
18
+ * the lanes it runs between, so an edge highlight leaves the nodes alone.
19
+ */
20
+ it("keeps the two categories apart", () => {
21
+ const edgesOnly = buildHighlightCss([], ["step-01", "step-02"]);
22
+ expect(edgesOnly).not.toContain(".react-flow__node");
23
+ expect(edgesOnly).toContain(
24
+ '.react-flow__edge:not([data-id="step-01"]):not([data-id="step-02"])',
25
+ );
26
+ });
27
+
28
+ it("touches nothing but opacity and outline, so no layout can move", () => {
29
+ const css = buildHighlightCss(["a"], ["b"]);
30
+ for (const rule of css.split("\n")) {
31
+ const body = rule.slice(rule.indexOf("{") + 1, rule.lastIndexOf("}"));
32
+ for (const decl of body.split(";")) {
33
+ const property = decl.split(":")[0]?.trim();
34
+ if (!property) continue;
35
+ expect([
36
+ "opacity",
37
+ "outline",
38
+ "outline-offset",
39
+ "transition",
40
+ ]).toContain(property);
41
+ }
42
+ }
43
+ });
44
+
45
+ it("escapes a quote in an id rather than breaking out of the selector", () => {
46
+ const css = buildHighlightCss(['a"b'], []);
47
+ expect(css).toContain('[data-id="a\\"b"]');
48
+ });
49
+ });
@@ -0,0 +1,52 @@
1
+ // Marking the selection on a LikeC4 canvas is a paint job, nothing more.
2
+ //
3
+ // The diagram is laid out ahead of time and rendered inside a shadow root, so
4
+ // the one safe way to mark something in it is a stylesheet placed inside that
5
+ // root, keyed on the `data-id` React Flow already puts on every node and edge.
6
+ // No prop the layout depends on is touched, which is what keeps a selection
7
+ // from nudging the picture.
8
+
9
+ const NODE = ".react-flow__node";
10
+ const EDGE = ".react-flow__edge";
11
+
12
+ /** CSS string escape for an attribute value. */
13
+ function q(value: string): string {
14
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
15
+ }
16
+
17
+ /** `:not()` chained rather than given a list, for older WebKit. */
18
+ function excluding(base: string, ids: readonly string[]): string {
19
+ return base + ids.map((id) => `:not([data-id="${q(id)}"])`).join("");
20
+ }
21
+
22
+ function matching(base: string, ids: readonly string[]): string {
23
+ return ids.map((id) => `${base}[data-id="${q(id)}"]`).join(", ");
24
+ }
25
+
26
+ /**
27
+ * Dims everything that is not selected and outlines what is. Dimming is done
28
+ * per category: highlighting a step must not grey out the lanes it runs
29
+ * between, and highlighting a service must not grey out the arrows into it.
30
+ */
31
+ export function buildHighlightCss(
32
+ nodeIds: readonly string[],
33
+ edgeIds: readonly string[],
34
+ ): string {
35
+ const rules: string[] = [];
36
+
37
+ if (nodeIds.length > 0) {
38
+ rules.push(
39
+ `${excluding(NODE, nodeIds)} { opacity: 0.3; transition: opacity 120ms ease; }`,
40
+ `${matching(NODE, nodeIds)} { outline: 2px solid var(--accent); outline-offset: 3px; }`,
41
+ );
42
+ }
43
+
44
+ if (edgeIds.length > 0) {
45
+ rules.push(
46
+ `${excluding(EDGE, edgeIds)} { opacity: 0.18; transition: opacity 120ms ease; }`,
47
+ `${matching(EDGE, edgeIds)} { opacity: 1; }`,
48
+ );
49
+ }
50
+
51
+ return rules.join("\n");
52
+ }
@@ -0,0 +1,151 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { readFileSync } from "node:fs";
3
+ import { catalog } from "../testing/estate";
4
+ // The generated sources are generated from the estate the app ships, so the
5
+ // block that reads them off disk is held against that one and not against the
6
+ // frozen fixture the id rules are checked with.
7
+ import { catalog as shipped, catalogProfiles } from "../data";
8
+ import {
9
+ CONTAINERS_VIEW,
10
+ LANDSCAPE_VIEW,
11
+ allViewIds,
12
+ contextViewId,
13
+ eventFqn,
14
+ flowCrossViewId,
15
+ flowViewId,
16
+ fqn,
17
+ participantFqn,
18
+ profileContainersViewId,
19
+ profileLandscapeViewId,
20
+ safeId,
21
+ serviceInsideViewId,
22
+ serviceViewId,
23
+ } from "./ids";
24
+
25
+ describe("safeId", () => {
26
+ it("replaces characters LikeC4 identifiers cannot hold", () => {
27
+ expect(safeId("fraud-scoring")).toBe("fraud_scoring");
28
+ expect(safeId("price-list")).toBe("price_list");
29
+ expect(safeId("analytics-sink")).toBe("analytics_sink");
30
+ });
31
+
32
+ it("escapes a word the grammar has taken", () => {
33
+ // An aggregate called `order` is what a shop calls its aggregate, and a
34
+ // model that declares one under that name does not parse at all.
35
+ expect(safeId("order")).toBe("_order");
36
+ expect(safeId("style")).toBe("_style");
37
+ expect(safeId("view")).toBe("_view");
38
+ });
39
+
40
+ it("never starts an identifier with a digit", () => {
41
+ expect(safeId("2fa")).toBe("_2fa");
42
+ });
43
+
44
+ it("leaves already-safe ids alone", () => {
45
+ expect(safeId("OrderPlaced")).toBe("OrderPlaced");
46
+ });
47
+ });
48
+
49
+ describe("fqn", () => {
50
+ it("keeps catalog dots as LikeC4 hierarchy separators", () => {
51
+ expect(fqn("shop.cart.basket")).toBe("shop.cart.basket");
52
+ expect(eventFqn("shop.cart.basket.ItemAdded")).toBe(
53
+ "shop.cart.basket.ItemAdded",
54
+ );
55
+ });
56
+
57
+ it("escapes only the segment that needs it", () => {
58
+ // The shop's aggregate is called `order`, and `order` is a word of the
59
+ // LikeC4 grammar; the segments around it are left alone.
60
+ expect(fqn("shop.oms.order")).toBe("shop.oms._order");
61
+ expect(eventFqn("shop.oms.order.OrderPlaced")).toBe(
62
+ "shop.oms._order.OrderPlaced",
63
+ );
64
+ });
65
+
66
+ it("sanitises each segment independently", () => {
67
+ expect(fqn("shop.price-list")).toBe("shop.price_list");
68
+ });
69
+
70
+ it("maps bare participants to root elements", () => {
71
+ expect(participantFqn("bus")).toBe("bus");
72
+ expect(participantFqn("fraud-scoring")).toBe("fraud_scoring");
73
+ });
74
+
75
+ it("keeps dots hierarchical for services but literal for root participants", () => {
76
+ expect(
77
+ participantFqn({ id: "shop.oms", kind: "service", context: "shop" }),
78
+ ).toBe("shop.oms");
79
+ expect(
80
+ participantFqn({
81
+ id: "river.order-jobs",
82
+ kind: "broker",
83
+ context: null,
84
+ }),
85
+ ).toBe("river_order_jobs");
86
+ });
87
+ });
88
+
89
+ describe("view ids", () => {
90
+ it("derives ids from catalog slugs, never from dotted ids", () => {
91
+ expect(flowViewId("checkout")).toBe("flow_checkout");
92
+ expect(flowCrossViewId("checkout")).toBe("flow_checkout_cross");
93
+ expect(contextViewId("shop")).toBe("ctx_shop");
94
+ expect(serviceViewId("shop.oms")).toBe("svc_shop_oms");
95
+ expect(serviceInsideViewId("shop.oms")).toBe("svc_shop_oms_inside");
96
+ });
97
+
98
+ it("is unique across the whole catalog", () => {
99
+ const ids = allViewIds(catalog);
100
+ expect(new Set(ids).size).toBe(ids.length);
101
+ });
102
+
103
+ it("covers the landscape, the containers, every context, both views of every service and both of every flow", () => {
104
+ const ids = allViewIds(catalog);
105
+ const services = catalog.contexts.flatMap((c) => c.services);
106
+ expect(ids).toHaveLength(
107
+ 2 +
108
+ catalog.contexts.length +
109
+ services.length * 2 +
110
+ catalog.flows.length * 2,
111
+ );
112
+ // The three C4 levels: the estate, its containers, a context, and a
113
+ // service opened up.
114
+ expect(ids).toContain(LANDSCAPE_VIEW);
115
+ expect(ids).toContain(CONTAINERS_VIEW);
116
+ expect(ids).toContain(contextViewId("shop"));
117
+ expect(ids).toContain(serviceInsideViewId("shop.oms"));
118
+ });
119
+
120
+ it("produces only valid LikeC4 identifiers", () => {
121
+ for (const id of allViewIds(catalog)) {
122
+ expect(id, id).toMatch(/^[A-Za-z_][A-Za-z0-9_]*$/);
123
+ }
124
+ });
125
+ });
126
+
127
+ describe("generated sources match the ids the app asks for", () => {
128
+ const views = readFileSync("likec4/views.c4", "utf8");
129
+
130
+ /** Every `view x {` and `dynamic view x {` declared in the generated source. */
131
+ const declared = [
132
+ ...views.matchAll(/^\s*(?:dynamic\s+)?view\s+([A-Za-z_][A-Za-z0-9_]*)\b/gm),
133
+ ].map((m) => m[1] as string);
134
+
135
+ it("declares every selected-catalog view and every profile entry point", () => {
136
+ const askedFor = [
137
+ ...allViewIds(shipped),
138
+ ...catalogProfiles.flatMap((profile) => [
139
+ profileLandscapeViewId(profile.id),
140
+ profileContainersViewId(profile.id),
141
+ ]),
142
+ ];
143
+ expect(new Set(declared).size).toBe(declared.length);
144
+ for (const id of askedFor) expect(declared).toContain(id);
145
+ });
146
+
147
+ it("declares the landscape and the containers once each, the only views with fixed names", () => {
148
+ expect(declared.filter((id) => id === LANDSCAPE_VIEW)).toHaveLength(1);
149
+ expect(declared.filter((id) => id === CONTAINERS_VIEW)).toHaveLength(1);
150
+ });
151
+ });
@@ -0,0 +1,115 @@
1
+ // LikeC4 identifiers derived from catalog ids. Pure and deterministic: the
2
+ // generator and the app must agree on every name without sharing state.
3
+
4
+ import type {
5
+ Catalog,
6
+ BoundedContext,
7
+ Event,
8
+ Flow,
9
+ Participant,
10
+ Service,
11
+ } from "../catalog";
12
+ import reserved from "./reserved.json";
13
+
14
+ /**
15
+ * Words the LikeC4 grammar has taken. An aggregate called `order` is an
16
+ * ordinary thing to find in a shop, and a model that declares one does not
17
+ * parse: the parser reads the keyword and expects the block to end. Whether a
18
+ * given word breaks depends on where it lands, so every word the grammar knows
19
+ * is escaped rather than the subset that happens to break today.
20
+ *
21
+ * `src/likec4/reserved.json` is read out of the installed grammar:
22
+ *
23
+ * [...bundle.matchAll(/"Keyword","value":"([A-Za-z_][A-Za-z0-9_]*)"/g)]
24
+ *
25
+ * over `node_modules/likec4/dist/chunks/node.mjs`. It is a copy, so it can go
26
+ * stale when likec4 is upgraded; `npm run likec4:validate` is what says so.
27
+ */
28
+ const RESERVED = new Set<string>(reserved);
29
+
30
+ /**
31
+ * LikeC4 identifiers allow letters, digits and underscore, cannot lead with a
32
+ * digit, and cannot be a word of the grammar. One escape for all three: a
33
+ * leading underscore, which LikeC4 takes and no catalog id starts with.
34
+ */
35
+ export function safeId(raw: string): string {
36
+ const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
37
+ return /^[0-9]/.test(cleaned) || RESERVED.has(cleaned) ? `_${cleaned}` : cleaned;
38
+ }
39
+
40
+ /** A catalog id like "shop.oms.basket" becomes the LikeC4 path "shop.oms.basket". */
41
+ export function fqn(catalogId: string): string {
42
+ return catalogId.split(".").map(safeId).join(".");
43
+ }
44
+
45
+ export const eventFqn = (event: Event | string): string =>
46
+ fqn(typeof event === "string" ? event : event.id);
47
+
48
+ /**
49
+ * Flow participants are either catalog services (dotted, hierarchical) or
50
+ * elements at the model root. A root id may itself contain a dot — for example
51
+ * `river.orders` — and that dot is data, not LikeC4 containment.
52
+ */
53
+ export function participantFqn(participant: Participant | string): string {
54
+ const id = typeof participant === "string" ? participant : participant.id;
55
+ return typeof participant !== "string" && participant.kind !== "service"
56
+ ? safeId(id)
57
+ : fqn(id);
58
+ }
59
+
60
+ // --- view ids -------------------------------------------------------------
61
+
62
+ /**
63
+ * C4 level 1, and one of the two views with a name of its own: the estate has
64
+ * one landscape, so nothing is derived from an id.
65
+ */
66
+ export const LANDSCAPE_VIEW = "landscape";
67
+
68
+ /**
69
+ * C4 level 2, the whole estate: every service inside its context, the store
70
+ * each keeps its state in, the brokers between them, and the same outsiders
71
+ * as the landscape. The other fixed name, for the same reason.
72
+ */
73
+ export const CONTAINERS_VIEW = "containers";
74
+
75
+ /** Whole-estate views narrowed to one configured catalog profile. */
76
+ export const profileLandscapeViewId = (profile: string): string =>
77
+ `${LANDSCAPE_VIEW}_${safeId(profile)}`;
78
+ export const profileContainersViewId = (profile: string): string =>
79
+ `${CONTAINERS_VIEW}_${safeId(profile)}`;
80
+
81
+ /** C4 level 2, one context: its services, their stores, and the brokers they use. */
82
+ export const contextViewId = (context: BoundedContext | string): string =>
83
+ `ctx_${safeId(typeof context === "string" ? context : context.id)}`;
84
+
85
+ /** C4 level 2, one service: the service as a box among the ones it touches. */
86
+ export const serviceViewId = (service: Service | string): string =>
87
+ `svc_${safeId(typeof service === "string" ? service : service.id)}`;
88
+
89
+ /** C4 level 3, the same service opened: its aggregates and its stores. */
90
+ export const serviceInsideViewId = (service: Service | string): string =>
91
+ `${serviceViewId(service)}_inside`;
92
+
93
+ /** Every flow has two declared views: the whole sequence, and the crossings only. */
94
+ export const flowViewId = (flow: Flow | string): string =>
95
+ `flow_${safeId(typeof flow === "string" ? flow : flow.slug)}`;
96
+
97
+ export const flowCrossViewId = (flow: Flow | string): string =>
98
+ `${flowViewId(flow)}_cross`;
99
+
100
+ /** Every view id the generator is expected to emit, in a stable order. */
101
+ export function allViewIds(catalog: Catalog): string[] {
102
+ const out: string[] = [LANDSCAPE_VIEW, CONTAINERS_VIEW];
103
+ for (const context of catalog.contexts) {
104
+ out.push(contextViewId(context));
105
+ for (const service of context.services) {
106
+ out.push(serviceViewId(service));
107
+ out.push(serviceInsideViewId(service));
108
+ }
109
+ }
110
+ for (const flow of catalog.flows) {
111
+ out.push(flowViewId(flow));
112
+ out.push(flowCrossViewId(flow));
113
+ }
114
+ return out;
115
+ }