@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
package/src/lib/adr.ts ADDED
@@ -0,0 +1,68 @@
1
+ // Pure derivations over the decision records. Same rule as derive.ts: nothing
2
+ // here reads the DOM or the router, so every list an ADR page shows is testable.
3
+
4
+ import type { Adr, AdrScope, Catalog } from "../catalog";
5
+ import { byDateDesc } from "../catalog";
6
+
7
+ /** "ADR-0007". The padded number is the name people use in prose and commits. */
8
+ export function adrNumber(adr: Adr): string {
9
+ return `ADR-${String(adr.number).padStart(4, "0")}`;
10
+ }
11
+
12
+ /** Superseded and deprecated records are history: true, but no longer in force. */
13
+ export function isCurrent(adr: Adr): boolean {
14
+ return adr.status !== "superseded" && adr.status !== "deprecated";
15
+ }
16
+
17
+ export function sortAdrs(adrs: Adr[]): Adr[] {
18
+ return [...adrs].sort(byDateDesc);
19
+ }
20
+
21
+ export function scopeLabel(scope: AdrScope): string {
22
+ switch (scope.kind) {
23
+ case "org":
24
+ return "org";
25
+ case "context":
26
+ return scope.context;
27
+ case "service":
28
+ return scope.service;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Does this record govern that service? An org-wide decision governs every
34
+ * service, a context-wide one governs the services inside it, and `relates`
35
+ * names services individually.
36
+ */
37
+ export function adrCoversService(
38
+ adr: Adr,
39
+ serviceId: string,
40
+ contextId: string,
41
+ ): boolean {
42
+ if (adr.scope.kind === "org") return true;
43
+ if (adr.scope.kind === "service" && adr.scope.service === serviceId)
44
+ return true;
45
+ if (adr.scope.kind === "context" && adr.scope.context === contextId)
46
+ return true;
47
+ return (adr.relates.services ?? []).includes(serviceId);
48
+ }
49
+
50
+ /** Decisions governing a service, newest first. */
51
+ export function adrsForService(
52
+ catalog: Catalog,
53
+ serviceId: string,
54
+ contextId: string,
55
+ ): Adr[] {
56
+ return sortAdrs(
57
+ catalog.adrs.filter((a) => adrCoversService(a, serviceId, contextId)),
58
+ );
59
+ }
60
+
61
+ /** The n newest accepted decisions, for the sidebar. */
62
+ export function newestAccepted(catalog: Catalog, n: number): Adr[] {
63
+ return sortAdrs(catalog.adrs.filter((a) => a.status === "accepted")).slice(
64
+ 0,
65
+ n,
66
+ );
67
+ }
68
+
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { catalog } from "../data";
3
+ import type { Service } from "../catalog";
4
+ import {
5
+ declaredMethods,
6
+ interfaceDeclaring,
7
+ methodCount,
8
+ methodId,
9
+ methodNamed,
10
+ operationsExposedBy,
11
+ streamingKind,
12
+ unexposed,
13
+ } from "./api";
14
+
15
+ function auth(): Service {
16
+ const service = catalog.contexts
17
+ .find((c) => c.id === "auth")
18
+ ?.services.find((s) => s.slug === "auth");
19
+ if (!service) throw new Error("the auth service is not in the catalog");
20
+
21
+ return service;
22
+ }
23
+
24
+ describe("interfaceDeclaring", () => {
25
+ it("finds the interface a method belongs to", () => {
26
+ expect(interfaceDeclaring(auth(), "registerUser")?.id).toBe(
27
+ "auth.v1.Users",
28
+ );
29
+ expect(interfaceDeclaring(auth(), "login")?.id).toBe("auth.v1.Sessions");
30
+ expect(interfaceDeclaring(auth(), "nothing")).toBeUndefined();
31
+ });
32
+
33
+ it("writes a method the way the rest of the app writes one", () => {
34
+ expect(methodId(auth(), "registerUser")).toBe("auth.v1.Users/registerUser");
35
+ });
36
+ });
37
+
38
+ describe("operationsExposedBy", () => {
39
+ it("finds the operation an endpoint runs", () => {
40
+ const run = operationsExposedBy(auth(), "registerUser");
41
+ expect(run.map((r) => r.operation.id)).toEqual(["Register"]);
42
+ expect(run[0]?.aggregate.id).toBe("auth.auth.user");
43
+ });
44
+
45
+ // The change-password endpoint resolves the bearer token itself before it
46
+ // changes anything, so it has run two use cases - and they belong to two
47
+ // different aggregates.
48
+ it("finds every operation an endpoint runs, across aggregates", () => {
49
+ const run = operationsExposedBy(auth(), "changePassword");
50
+ expect(
51
+ run.map((r) => `${r.aggregate.slug}/${r.operation.id}`).sort(),
52
+ ).toEqual(["session/Validate", "user/ChangePassword"]);
53
+ });
54
+ });
55
+
56
+ describe("unexposed", () => {
57
+ // Both of these are deliberate. The service documents why there is no
58
+ // endpoint that checks a password without issuing a session, and the other
59
+ // is run by a policy reacting to an event rather than by any caller.
60
+ it("finds the operations nothing outside the service can reach", () => {
61
+ expect(
62
+ unexposed(auth())
63
+ .map((r) => r.operation.id)
64
+ .sort(),
65
+ ).toEqual([
66
+ // The lockout's three are asked by the credential check through a
67
+ // port, and the check itself by login: none of the four has a route of
68
+ // its own.
69
+ "Check",
70
+ "CheckCredentials",
71
+ "EndAfterCredentialChange",
72
+ "RecordFailure",
73
+ "RecordSuccess",
74
+ ]);
75
+ });
76
+ });
77
+
78
+ // The boundary between the two halves of an interface is BY NAME, and stayed
79
+ // that way when a method stopped being a string: `Operation.exposedBy` names a
80
+ // method, and so does a flow step's ref. That every assertion above passes
81
+ // unmodified is the proof the boundary did not move.
82
+ describe("methods, now that a method is more than a name", () => {
83
+ it("finds one method of one interface by name", () => {
84
+ const users = interfaceDeclaring(auth(), "registerUser");
85
+ if (!users) throw new Error("no interface");
86
+
87
+ expect(methodNamed(users, "registerUser")?.name).toBe("registerUser");
88
+ expect(methodNamed(users, "nothing")).toBeUndefined();
89
+ });
90
+
91
+ it("lists every method the service answers on, in catalog order", () => {
92
+ const declared = declaredMethods(auth());
93
+
94
+ expect(declared.length).toBe(methodCount(auth()));
95
+ expect(declared[0]?.provided.id).toBe("auth.v1.Users");
96
+ expect(declared.map((d) => d.method.name)).toContain("login");
97
+ });
98
+
99
+ it("counts across every interface, not just the first", () => {
100
+ const service = auth();
101
+
102
+ expect(methodCount(service)).toBe(
103
+ service.provides.reduce((n, p) => n + p.methods.length, 0),
104
+ );
105
+ });
106
+
107
+ // The catalog leaves unary out because it is the case not worth writing
108
+ // down; a reader deciding what to draw wants all four cases named.
109
+ it("names all four streaming shapes, unary included", () => {
110
+ expect(streamingKind({ name: "a" })).toBe("unary");
111
+ expect(streamingKind({ name: "a", streaming: "client" })).toBe("client");
112
+ expect(streamingKind({ name: "a", streaming: "server" })).toBe("server");
113
+ expect(streamingKind({ name: "a", streaming: "bidi" })).toBe("bidi");
114
+ });
115
+
116
+ it("counts nothing for a service that only listens", () => {
117
+ const silent: Service = { ...auth(), provides: [] };
118
+
119
+ expect(methodCount(silent)).toBe(0);
120
+ expect(declaredMethods(silent)).toEqual([]);
121
+ });
122
+ });
package/src/lib/api.ts ADDED
@@ -0,0 +1,135 @@
1
+ // The two halves of an interface, joined.
2
+ //
3
+ // An operation says which methods expose it, by name; an interface says which
4
+ // methods it declares. Neither half knows the other, because they are read out
5
+ // of different files by different generators - the handlers say what runs, the
6
+ // document says what is offered - and they meet here, after the merge, which is
7
+ // the first place both are present.
8
+
9
+ import type {
10
+ Aggregate,
11
+ Operation,
12
+ RpcMethod,
13
+ RpcService,
14
+ Service,
15
+ } from "../catalog";
16
+
17
+ /**
18
+ * The interface that declares a method. Undefined when nothing this service
19
+ * offers carries that name, which the validator refuses in a catalog and is
20
+ * therefore a shape callers may ignore rather than one they must handle.
21
+ */
22
+ export function interfaceDeclaring(
23
+ service: Service,
24
+ method: string,
25
+ ): RpcService | undefined {
26
+ return service.provides.find((provided) =>
27
+ provided.methods.some((declared) => declared.name === method),
28
+ );
29
+ }
30
+
31
+ /**
32
+ * A method by name, within one interface.
33
+ *
34
+ * By name rather than by index, because a name is what every other half of the
35
+ * catalog holds: `Operation.exposedBy` names one, a flow step's ref ends in
36
+ * one, and `rpcProviderByMethod` is keyed by one.
37
+ */
38
+ export function methodNamed(
39
+ provided: RpcService,
40
+ name: string,
41
+ ): RpcMethod | undefined {
42
+ return provided.methods.find((method) => method.name === name);
43
+ }
44
+
45
+ export interface DeclaredMethod {
46
+ provided: RpcService;
47
+ method: RpcMethod;
48
+ }
49
+
50
+ /**
51
+ * Every method the service answers on, flat, in catalog order.
52
+ *
53
+ * Written once because the sidebar, the palette and three separate counts all
54
+ * want the same list, and four copies of one flatMap is how they drift.
55
+ */
56
+ export function declaredMethods(service: Service): DeclaredMethod[] {
57
+ return service.provides.flatMap((provided) =>
58
+ provided.methods.map((method) => ({ provided, method })),
59
+ );
60
+ }
61
+
62
+ /** How many methods the service answers on, across every interface. */
63
+ export function methodCount(service: Service): number {
64
+ return service.provides.reduce(
65
+ (n, provided) => n + provided.methods.length,
66
+ 0,
67
+ );
68
+ }
69
+
70
+ /**
71
+ * How a method streams, as a value rather than as an absence.
72
+ *
73
+ * `undefined` is unary in the catalog because that is the case not worth
74
+ * writing down; a reader deciding what to draw wants the four cases named.
75
+ */
76
+ export function streamingKind(
77
+ method: RpcMethod,
78
+ ): "unary" | "client" | "server" | "bidi" {
79
+ return method.streaming ?? "unary";
80
+ }
81
+
82
+ /** The interface method, written the way the rest of the app writes one. */
83
+ export function methodId(service: Service, method: string): string {
84
+ const declaring = interfaceDeclaring(service, method);
85
+
86
+ return declaring ? `${declaring.id}/${method}` : method;
87
+ }
88
+
89
+ export interface ExposedOperation {
90
+ aggregate: Aggregate;
91
+ operation: Operation;
92
+ }
93
+
94
+ /**
95
+ * What a method actually runs, across every aggregate of the service.
96
+ *
97
+ * More than one is normal rather than a mistake: an endpoint that resolves a
98
+ * token before changing a password has run two use cases, and a page that
99
+ * showed only the second would be describing half of what happened.
100
+ */
101
+ export function operationsExposedBy(
102
+ service: Service,
103
+ method: string,
104
+ ): ExposedOperation[] {
105
+ const out: ExposedOperation[] = [];
106
+
107
+ for (const aggregate of service.aggregates) {
108
+ for (const operation of aggregate.operations) {
109
+ if (operation.exposedBy?.includes(method)) {
110
+ out.push({ aggregate, operation });
111
+ }
112
+ }
113
+ }
114
+
115
+ return out;
116
+ }
117
+
118
+ /**
119
+ * Operations nothing outside the service can reach.
120
+ *
121
+ * Not a defect on its own: a use case run by a policy rather than by a caller
122
+ * is unreachable on purpose, and so is one deliberately left off the API. It
123
+ * is worth being able to ask the question.
124
+ */
125
+ export function unexposed(service: Service): ExposedOperation[] {
126
+ const out: ExposedOperation[] = [];
127
+
128
+ for (const aggregate of service.aggregates) {
129
+ for (const operation of aggregate.operations) {
130
+ if (!operation.exposedBy?.length) out.push({ aggregate, operation });
131
+ }
132
+ }
133
+
134
+ return out;
135
+ }
@@ -0,0 +1,330 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { catalog, index } from "../testing/estate";
3
+ import { backlinkCount, backlinksFor, stepsInto } from "./backlinks";
4
+ import type { BacklinkGroup, BacklinkTarget } from "./backlinks";
5
+ import type { Kind } from "./kinds";
6
+
7
+ function groups(target: BacklinkTarget): BacklinkGroup[] {
8
+ return backlinksFor(catalog, index, target);
9
+ }
10
+
11
+ function of(target: BacklinkTarget, kind: Kind) {
12
+ return groups(target).find((g) => g.kind === kind)?.links ?? [];
13
+ }
14
+
15
+ describe("stepsInto", () => {
16
+ it("numbers steps by their place in the whole flow, frames included", () => {
17
+ const steps = stepsInto(catalog, new Set(["shop.oms.order.OrderPlaced"]));
18
+ expect(steps.map((s) => `${s.flow.slug}#${s.stepId}`)).toEqual([
19
+ "checkout#s12",
20
+ "checkout#s14",
21
+ "checkout#s15",
22
+ "order-accepted#a2",
23
+ "order-accepted#a3",
24
+ ]);
25
+ expect(steps[0]?.number).toBe(12);
26
+ });
27
+
28
+ it("takes several events at once and still comes out in flow order", () => {
29
+ const steps = stepsInto(
30
+ catalog,
31
+ new Set([
32
+ "shop.oms.order.OrderPlaced",
33
+ "shop.oms.order.OrderCancelled",
34
+ ]),
35
+ );
36
+ const flows = steps.map((s) => s.flow.slug);
37
+ // Flows in catalog order, never interleaved.
38
+ expect([...new Set(flows)]).toEqual(
39
+ flows.filter((f, i) => flows[i - 1] !== f),
40
+ );
41
+ });
42
+ });
43
+
44
+ describe("an event", () => {
45
+ const target: BacklinkTarget = {
46
+ kind: "event",
47
+ id: "shop.oms.order.OrderPlaced",
48
+ };
49
+
50
+ it("is pointed at by its consumers, carrying their status", () => {
51
+ expect(of(target, "service").map((l) => [l.id, l.via, l.status])).toEqual([
52
+ ["payments.ledger", "consumes", "verified"],
53
+ ["shop.pricing", "consumes", "declared"],
54
+ ["analytics-sink", "consumes", "unresolved"],
55
+ ]);
56
+ });
57
+
58
+ it("names a consumer the catalog does not have, rather than dropping it", () => {
59
+ const ghost = of(target, "service").find((l) => l.id === "analytics-sink");
60
+ expect(ghost?.owner).toBe("not in the catalog");
61
+ expect(ghost?.context).toBeNull();
62
+ });
63
+
64
+ it("points at the step of a flow, not the top of it", () => {
65
+ expect(of(target, "flow").map((l) => `${l.id}#${l.at} ${l.via}`)).toEqual([
66
+ "checkout#s12 step 12",
67
+ "checkout#s14 step 14",
68
+ "checkout#s15 step 15",
69
+ "order-accepted#a2 step 2",
70
+ "order-accepted#a3 step 3",
71
+ ]);
72
+ });
73
+
74
+ it("lists the decisions that name it, and says which field named it", () => {
75
+ const adrs = of(
76
+ { kind: "event", id: "shop.cart.basket.BasketCheckedOut" },
77
+ "adr",
78
+ );
79
+ expect(adrs.map((l) => [l.id, l.name, l.via])).toEqual([
80
+ ["org.0002", "ADR-0002", "relates.events"],
81
+ ]);
82
+ });
83
+ });
84
+
85
+ describe("a service", () => {
86
+ const target: BacklinkTarget = { kind: "service", id: "shop.oms" };
87
+
88
+ it("is pointed at by its rpc callers — the half its own page never shows", () => {
89
+ const calls = of(target, "service").filter((l) =>
90
+ l.via.startsWith("calls"),
91
+ );
92
+ expect(calls.map((l) => `${l.id} ${l.via}`)).toEqual([
93
+ "shop.pricing calls shop.v1.Orders/GetOrder",
94
+ "payments.ledger calls shop.v1.Orders/GetOrder",
95
+ "delivery.core calls shop.v1.Orders/GetOrder",
96
+ ]);
97
+ });
98
+
99
+ it("says which event each listener listens to", () => {
100
+ const heard = of(target, "service").filter((l) =>
101
+ l.via.startsWith("consumes"),
102
+ );
103
+ expect(heard).toContainEqual(
104
+ expect.objectContaining({
105
+ id: "delivery.core",
106
+ via: "consumes OrderConfirmed",
107
+ status: "verified",
108
+ }),
109
+ );
110
+ });
111
+
112
+ it("counts a service that listens to two of its events twice, once per event", () => {
113
+ const ledger = of(target, "service").filter(
114
+ (l) => l.id === "payments.ledger" && l.via.startsWith("consumes"),
115
+ );
116
+ expect(ledger.map((l) => l.via)).toEqual([
117
+ "consumes OrderPlaced",
118
+ "consumes OrderCancelled",
119
+ ]);
120
+ });
121
+
122
+ it("takes only the decisions that name it — org-wide is not a backlink", () => {
123
+ const adrs = of(target, "adr");
124
+ expect(adrs.map((l) => [l.name, l.via])).toEqual([
125
+ ["ADR-0007", "scope"],
126
+ ["ADR-0003", "scope"],
127
+ ["ADR-0001", "relates.services"],
128
+ ]);
129
+ });
130
+
131
+ it("is not its own backlink", () => {
132
+ expect(of(target, "service").every((l) => l.id !== "shop.oms")).toBe(true);
133
+ });
134
+ });
135
+
136
+ describe("a context", () => {
137
+ const target: BacklinkTarget = { kind: "context", id: "shop" };
138
+
139
+ it("ignores traffic between its own services", () => {
140
+ // shop.pricing calls shop.oms and listens to it, but both sit inside.
141
+ expect(of(target, "service").every((l) => l.context !== "shop")).toBe(true);
142
+ });
143
+
144
+ it("keeps the callers from outside", () => {
145
+ expect(
146
+ of(target, "service")
147
+ .filter((l) => l.via.startsWith("calls"))
148
+ .map((l) => l.id),
149
+ ).toEqual(["payments.ledger", "delivery.core"]);
150
+ });
151
+
152
+ it("says which of its participants a flow actually runs through", () => {
153
+ const checkout = of(target, "flow").find((l) => l.id === "checkout");
154
+ // A store the context owns counts: a flow that reaches into it is a flow
155
+ // that depends on this context, whatever kind of participant it is.
156
+ expect(checkout?.via).toBe("shop.oms, oms-db, shop.pricing");
157
+ });
158
+ });
159
+
160
+ describe("an aggregate", () => {
161
+ const target: BacklinkTarget = { kind: "aggregate", id: "shop.oms.order" };
162
+
163
+ it("is reached through its events, and every row says which one", () => {
164
+ for (const link of of(target, "service")) {
165
+ expect(link.via).toMatch(/^consumes /);
166
+ }
167
+ for (const link of of(target, "flow")) {
168
+ expect(link.via).toMatch(/^step \d+ · /);
169
+ }
170
+ });
171
+
172
+ it("draws a decision naming two of its events once, not twice", () => {
173
+ const ids = of(
174
+ { kind: "aggregate", id: "payments.ledger.payment" },
175
+ "adr",
176
+ ).map((l) => l.id);
177
+ expect(ids.length).toBe(new Set(ids).size);
178
+ });
179
+ });
180
+
181
+ describe("a value object", () => {
182
+ const money: BacklinkTarget = { kind: "vo", id: "shop.oms.order.money" };
183
+
184
+ it("finds the other blocks that name the same shared type", () => {
185
+ const others = of(money, "vo").map((l) => l.id);
186
+ expect(others).toContain("shop.pricing.quote.money");
187
+ expect(others).toContain("payments.ledger.payment.money");
188
+ // Never itself.
189
+ expect(others).not.toContain("shop.oms.order.money");
190
+ });
191
+
192
+ it("says which fields carry the type, and in which event versions", () => {
193
+ const events = of(money, "event");
194
+ expect(events.length).toBeGreaterThan(0);
195
+ for (const link of events) expect(link.via).not.toBe("");
196
+ expect(events.some((l) => (l.versions?.length ?? 0) > 0)).toBe(true);
197
+ });
198
+
199
+ it("has nothing to find for an inline shape", () => {
200
+ const inline = catalog.contexts
201
+ .flatMap((c) => c.services)
202
+ .flatMap((s) => s.aggregates)
203
+ .flatMap((a) => a.valueObjects)
204
+ .find((b) => !b.ref);
205
+ if (!inline) throw new Error("no inline value object in the catalog");
206
+ expect(backlinkCount(groups({ kind: "vo", id: inline.id }))).toBe(0);
207
+ });
208
+ });
209
+
210
+ describe("an enum", () => {
211
+ const code: BacklinkTarget = { kind: "enum", id: "payments.ledger.payment.decline-code" };
212
+
213
+ it("finds the fields that switch on it, and says the match is by name", () => {
214
+ const events = of(code, "event");
215
+ expect(events.map((l) => l.id)).toContain("payments.ledger.payment.PaymentDeclined");
216
+ for (const link of events) {
217
+ expect(link.via).toContain("code");
218
+ expect(link.via).toContain("by name");
219
+ }
220
+ });
221
+
222
+ it("has nothing for a set the catalog lacks", () => {
223
+ expect(backlinkCount(groups({ kind: "enum", id: "x.y.z" }))).toBe(0);
224
+ });
225
+ });
226
+
227
+ describe("a flow", () => {
228
+ it("is pointed at only by the decisions that name it", () => {
229
+ const adrs = of({ kind: "flow", id: "checkout" }, "adr");
230
+ expect(adrs.map((l) => l.name).sort()).toEqual([
231
+ "ADR-0003",
232
+ "ADR-0004",
233
+ "ADR-0007",
234
+ ]);
235
+ for (const link of adrs) expect(link.via).toBe("relates.flows");
236
+ });
237
+ });
238
+
239
+ describe("a store", () => {
240
+ const target: BacklinkTarget = { kind: "store", id: "shop.oms.pg" };
241
+
242
+ it("is pointed at by the services that read it, and says so", () => {
243
+ const readers = of(target, "service");
244
+ expect(readers.map((l) => [l.id, l.via])).toContainEqual([
245
+ "delivery.core",
246
+ "reads",
247
+ ]);
248
+ expect(readers.some((l) => l.id === "shop.oms")).toBe(false);
249
+ });
250
+
251
+ it("names the aggregates its tables hold, once per table", () => {
252
+ const held = of(target, "aggregate");
253
+ expect(held.length).toBeGreaterThan(0);
254
+ for (const link of held) expect(link.via).toMatch(/^persisted in /);
255
+ expect(held.map((l) => l.id)).toContain("shop.oms.order");
256
+ });
257
+ });
258
+
259
+ describe("targets the catalog does not have", () => {
260
+ it("answer nothing rather than throwing", () => {
261
+ for (const kind of [
262
+ "context",
263
+ "service",
264
+ "aggregate",
265
+ "event",
266
+ "vo",
267
+ "flow",
268
+ ] as const) {
269
+ expect(backlinksFor(catalog, index, { kind, id: "nope" })).toEqual([]);
270
+ }
271
+ });
272
+
273
+ it("include decision records, whose incoming edges are their banners", () => {
274
+ expect(
275
+ backlinksFor(catalog, index, { kind: "adr", id: "org.0001" }),
276
+ ).toEqual([]);
277
+ });
278
+ });
279
+
280
+ describe("grouping", () => {
281
+ it("orders groups by how loudly they depend, services first", () => {
282
+ const kinds = groups({
283
+ kind: "event",
284
+ id: "shop.oms.order.OrderPlaced",
285
+ }).map((g) => g.kind);
286
+ expect(kinds).toEqual(["service", "flow"]);
287
+ });
288
+
289
+ it("counts every link across every group", () => {
290
+ const g = groups({ kind: "service", id: "shop.oms" });
291
+ expect(backlinkCount(g)).toBe(g.reduce((n, x) => n + x.links.length, 0));
292
+ });
293
+ });
294
+
295
+ describe("the word that names it", () => {
296
+ it("is a backlink on the thing the glossary names", () => {
297
+ const links = backlinksFor(catalog, index, {
298
+ kind: "aggregate",
299
+ id: "auth.auth.session",
300
+ });
301
+ const terms = links.find((g) => g.kind === "term");
302
+
303
+ expect(terms?.links).toHaveLength(1);
304
+ expect(terms?.links[0]?.name).toBe("Session");
305
+ expect(terms?.links[0]?.via).toBe("names it");
306
+ });
307
+
308
+ // Last group on the page. Every other row answers "who breaks when this
309
+ // changes"; this one answers "what is it called", which is read after.
310
+ it("comes after everything that depends on the thing", () => {
311
+ const links = backlinksFor(catalog, index, {
312
+ kind: "aggregate",
313
+ id: "auth.auth.session",
314
+ });
315
+
316
+ expect(links[links.length - 1]?.kind).toBe("term");
317
+ });
318
+
319
+ // Most of the estate. Only two contexts keep a glossary, and inside them the
320
+ // pairing is exact - so a page with no word on it is the ordinary page, and
321
+ // it says nothing rather than reporting a gap.
322
+ it("says nothing about a thing no word names", () => {
323
+ const links = backlinksFor(catalog, index, {
324
+ kind: "aggregate",
325
+ id: "payments.ledger.payment",
326
+ });
327
+
328
+ expect(links.some((g) => g.kind === "term")).toBe(false);
329
+ });
330
+ });