@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,1150 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ import { beforeEach, describe, expect, it } from "vitest";
4
+
5
+ import type {
6
+ BoundedContext,
7
+ Catalog,
8
+ Flow,
9
+ FlowNode,
10
+ Participant,
11
+ Service,
12
+ Status,
13
+ } from "./catalog";
14
+ import { validateCatalog, walkSteps } from "./catalog";
15
+ import { enrichCatalog } from "./enrich";
16
+ import { mergeCatalogs } from "./merge";
17
+ import { problems } from "./lib/derive";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // A tiny estate: two services, one event, one method, and whatever flow the
21
+ // case wants to say about them.
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const EVENT = "shop.oms.order.OrderPlaced";
25
+ const METHOD = "pricing.v1.Pricing/Quote";
26
+
27
+ function service(
28
+ contextId: string,
29
+ slug: string,
30
+ overrides: Partial<Service> = {},
31
+ ): Service {
32
+ return {
33
+ id: `${contextId}.${slug}`,
34
+ slug,
35
+ name: slug,
36
+ repo: "",
37
+ path: "",
38
+ readme: "",
39
+ provides: [],
40
+ consumes: [],
41
+ aggregates: [],
42
+ ...overrides,
43
+ };
44
+ }
45
+
46
+ function context(id: string, services: Service[]): BoundedContext {
47
+ return { id, slug: id, name: id, summary: "", services };
48
+ }
49
+
50
+ function oms(
51
+ consumers: { service: string; status: Status; note?: string }[] = [],
52
+ ): Service {
53
+ return service("shop", "oms", {
54
+ aggregates: [
55
+ {
56
+ id: "shop.oms.order",
57
+ slug: "order",
58
+ name: "Order",
59
+ readme: "",
60
+ root: "Order",
61
+ entities: [
62
+ {
63
+ id: "shop.oms.order.order",
64
+ slug: "order",
65
+ name: "Order",
66
+ doc: "",
67
+ fields: [{ name: "id", type: "string", doc: "" }],
68
+ },
69
+ ],
70
+ valueObjects: [],
71
+ operations: [],
72
+ events: [
73
+ {
74
+ id: EVENT,
75
+ slug: "orderplaced",
76
+ name: "OrderPlaced",
77
+ versions: [{ version: "v1", doc: "", source: "x.go", fields: [] }],
78
+ consumers,
79
+ },
80
+ ],
81
+ },
82
+ ],
83
+ });
84
+ }
85
+
86
+ function pricing(): Service {
87
+ return service("shop", "pricing", {
88
+ provides: [
89
+ {
90
+ id: "pricing.v1.Pricing",
91
+ methods: [{ name: "Quote", doc: "" }],
92
+ source: "pricing.proto",
93
+ },
94
+ ],
95
+ });
96
+ }
97
+
98
+ const LANES: Participant[] = [
99
+ { id: "client", kind: "actor", context: null },
100
+ { id: "shop.oms", kind: "service", context: "shop" },
101
+ { id: "shop.pricing", kind: "service", context: "shop" },
102
+ { id: "payments.ledger", kind: "service", context: "payments" },
103
+ { id: "oms-db", kind: "store", context: "shop" },
104
+ { id: "bus", kind: "broker", context: null },
105
+ { id: "risk", kind: "external", context: null },
106
+ { id: "ghost.svc", kind: "service", context: "ghost" },
107
+ ];
108
+
109
+ let n = 0;
110
+ beforeEach(() => {
111
+ n = 0;
112
+ });
113
+ function step(
114
+ from: string,
115
+ to: string,
116
+ kind: "rpc" | "event" | "call",
117
+ extra: Partial<Extract<FlowNode, { type: "step" }>> = {},
118
+ ): FlowNode {
119
+ n += 1;
120
+ return {
121
+ type: "step",
122
+ id: `s${n}`,
123
+ from,
124
+ to,
125
+ kind,
126
+ status: "declared",
127
+ ...extra,
128
+ };
129
+ }
130
+
131
+ function flow(slug: string, steps: FlowNode[], source?: string): Flow {
132
+ return {
133
+ id: `flow.${slug}`,
134
+ slug,
135
+ name: slug,
136
+ summary: "",
137
+ owner: "shop",
138
+ participants: LANES,
139
+ steps,
140
+ ...(source ? { source } : {}),
141
+ };
142
+ }
143
+
144
+ function estate(
145
+ flows: Flow[],
146
+ services: Service[] = [oms(), pricing()],
147
+ ): Catalog {
148
+ return {
149
+ generatedAt: "2026-01-01T00:00:00Z",
150
+ commit: "0000000",
151
+ contexts: [
152
+ context("shop", services),
153
+ context("payments", [service("payments", "ledger")]),
154
+ ],
155
+ defs: {},
156
+ flows,
157
+ adrs: [],
158
+ };
159
+ }
160
+
161
+ function consumersOf(catalog: Catalog) {
162
+ return catalog.contexts
163
+ .flatMap((c) => c.services)
164
+ .flatMap((s) => s.aggregates)
165
+ .flatMap((a) => a.events)
166
+ .find((e) => e.id === EVENT)!.consumers;
167
+ }
168
+
169
+ function serviceOf(catalog: Catalog, id: string): Service {
170
+ return catalog.contexts.flatMap((c) => c.services).find((s) => s.id === id)!;
171
+ }
172
+
173
+ // ---------------------------------------------------------------------------
174
+
175
+ describe("enrichCatalog: asynchronous outbound continuations", () => {
176
+ function outbound(slug: string, entrypoint: string, path: string): Flow {
177
+ return {
178
+ ...flow(slug, [
179
+ step("shop.oms", "risk", "rpc", {
180
+ ref: `http-client/POST ${path}`,
181
+ label: `POST ${path}`,
182
+ status: "unresolved",
183
+ }),
184
+ ]),
185
+ entrypoint,
186
+ };
187
+ }
188
+
189
+ it("joins a dispatched worker to its uniquely proven outbound flow", () => {
190
+ const entrypoint = "jobs/email:SendWorker.Work";
191
+ const queued = flow("send-job", [
192
+ step("bus", "shop.oms", "call", {
193
+ label: "SendWorker.Work",
194
+ continuesAt: entrypoint,
195
+ }),
196
+ ]);
197
+ const once = enrichCatalog(
198
+ estate([queued, outbound("send-http", entrypoint, "/mail")]),
199
+ ).catalog;
200
+
201
+ expect(once.flows.map((item) => item.slug)).toEqual(["send-job"]);
202
+ expect(walkSteps(once.flows[0]!.steps).map((item) => item.label)).toEqual([
203
+ "SendWorker.Work",
204
+ "POST /mail",
205
+ ]);
206
+ expect(walkSteps(once.flows[0]!.steps)[1]!.id).toContain(
207
+ "continuation-send-http",
208
+ );
209
+ expect(once.flows[0]!.participants.some((item) => item.id === "risk")).toBe(
210
+ true,
211
+ );
212
+ expect(enrichCatalog(once).catalog).toEqual(once);
213
+ });
214
+
215
+ it("does not choose between duplicate entrypoint flows", () => {
216
+ const entrypoint = "jobs/email:SendWorker.Work";
217
+ const queued = flow("send-job", [
218
+ step("bus", "shop.oms", "call", { continuesAt: entrypoint }),
219
+ ]);
220
+ const result = enrichCatalog(
221
+ estate([
222
+ queued,
223
+ outbound("send-primary", entrypoint, "/one"),
224
+ outbound("send-secondary", entrypoint, "/two"),
225
+ ]),
226
+ ).catalog;
227
+
228
+ expect(result.flows).toHaveLength(3);
229
+ expect(walkSteps(result.flows[0]!.steps)).toHaveLength(1);
230
+ });
231
+
232
+ it("composes an API path through a River job into its HTTP continuation", () => {
233
+ const producer = "connector/websky:Connector.Void";
234
+ const worker = "connector/websky:VoidWorker.Work";
235
+ const api = {
236
+ ...flow("void-api", [
237
+ step("client", "shop.oms", "rpc", {
238
+ label: "POST /void",
239
+ reaches: [producer],
240
+ }),
241
+ ]),
242
+ trigger: {
243
+ kind: "http",
244
+ label: "POST /void",
245
+ confidence: "high",
246
+ } as const,
247
+ };
248
+ const job = {
249
+ ...flow("void-job", [
250
+ step("shop.oms", "bus", "call", {
251
+ label: "enqueue void",
252
+ handoff: {
253
+ kind: "job",
254
+ transport: "river",
255
+ channel: "void",
256
+ message: "void-cancellation",
257
+ direction: "send",
258
+ },
259
+ }),
260
+ step("bus", "shop.oms", "call", {
261
+ label: "VoidWorker.Work",
262
+ continuesAt: worker,
263
+ handoff: {
264
+ kind: "job",
265
+ transport: "river",
266
+ channel: "void",
267
+ message: "void-cancellation",
268
+ direction: "receive",
269
+ },
270
+ }),
271
+ ]),
272
+ entrypoint: producer,
273
+ trigger: {
274
+ kind: "job",
275
+ label: "River · void",
276
+ confidence: "high",
277
+ } as const,
278
+ };
279
+
280
+ const once = enrichCatalog(
281
+ estate([api, job, outbound("void-http", worker, "/cancel")]),
282
+ ).catalog;
283
+ const root = once.flows.find((item) => item.slug === "void-api")!;
284
+
285
+ expect(walkSteps(root.steps).map((item) => item.label)).toEqual([
286
+ "POST /void",
287
+ "enqueue void",
288
+ "VoidWorker.Work",
289
+ "POST /cancel",
290
+ ]);
291
+ expect(root.includes).toEqual(["void-job", "void-http"]);
292
+ expect(once.flows.map((item) => item.slug)).toEqual([
293
+ "void-api",
294
+ "void-job",
295
+ ]);
296
+ expect(enrichCatalog(once).catalog).toEqual(once);
297
+ });
298
+
299
+ it("joins a Django enqueue at the Celery worker without replaying the task flow enqueue", () => {
300
+ const handoff = (direction: "send" | "receive") => ({
301
+ kind: "job" as const,
302
+ transport: "celery",
303
+ channel: "billing.mail",
304
+ message: "invoices.tasks.send_invoice_email",
305
+ direction,
306
+ });
307
+ const api = {
308
+ ...flow("issue-invoice", [
309
+ step("client", "shop.oms", "rpc", {
310
+ label: "POST /invoices/{id}/issue",
311
+ }),
312
+ step("shop.oms", "bus", "call", {
313
+ label: "enqueue send_invoice_email",
314
+ handoff: handoff("send"),
315
+ }),
316
+ ]),
317
+ trigger: {
318
+ kind: "http",
319
+ label: "POST /invoices/{id}/issue",
320
+ confidence: "high",
321
+ } as const,
322
+ };
323
+ const task = {
324
+ ...flow("send-invoice-email", [
325
+ step("shop.oms", "bus", "call", {
326
+ label: "enqueue send_invoice_email",
327
+ handoff: handoff("send"),
328
+ }),
329
+ step("bus", "shop.oms", "call", {
330
+ label: "send_invoice_email",
331
+ handoff: handoff("receive"),
332
+ }),
333
+ ]),
334
+ trigger: {
335
+ kind: "job",
336
+ label: "Celery · billing.mail",
337
+ confidence: "high",
338
+ } as const,
339
+ };
340
+
341
+ const result = enrichCatalog(estate([api, task])).catalog;
342
+ const root = result.flows.find((item) => item.slug === "issue-invoice")!;
343
+
344
+ expect(walkSteps(root.steps).map((item) => item.label)).toEqual([
345
+ "POST /invoices/{id}/issue",
346
+ "enqueue send_invoice_email",
347
+ "send_invoice_email",
348
+ ]);
349
+ expect(root.includes).toEqual(["send-invoice-email"]);
350
+ expect(enrichCatalog(result).catalog).toEqual(result);
351
+ });
352
+
353
+ it("composes a message handoff and its nested outbound call", () => {
354
+ const handler = "messages/email:Handle";
355
+ const publish = {
356
+ ...flow("request", [
357
+ step("shop.oms", "bus", "event", {
358
+ label: "publish email.requested",
359
+ handoff: {
360
+ kind: "message",
361
+ transport: "kafka",
362
+ channel: "email.requested",
363
+ direction: "send",
364
+ },
365
+ }),
366
+ ]),
367
+ trigger: {
368
+ kind: "http",
369
+ label: "POST /email",
370
+ confidence: "high",
371
+ } as const,
372
+ };
373
+ const consume = {
374
+ ...flow("email-handler", [
375
+ step("bus", "shop.oms", "event", {
376
+ label: "email.requested",
377
+ continuesAt: handler,
378
+ handoff: {
379
+ kind: "message",
380
+ transport: "kafka",
381
+ channel: "email.requested",
382
+ direction: "receive",
383
+ },
384
+ }),
385
+ ]),
386
+ trigger: {
387
+ kind: "event",
388
+ label: "email.requested",
389
+ confidence: "high",
390
+ } as const,
391
+ };
392
+
393
+ const once = enrichCatalog(
394
+ estate([publish, consume, outbound("email-http", handler, "/mail")]),
395
+ ).catalog;
396
+ const root = once.flows.find((item) => item.slug === "request")!;
397
+
398
+ expect(walkSteps(root.steps).map((item) => item.label)).toEqual([
399
+ "publish email.requested",
400
+ "email.requested",
401
+ "POST /mail",
402
+ ]);
403
+ expect(root.includes).toEqual(["email-handler", "email-http"]);
404
+ expect(enrichCatalog(once).catalog).toEqual(once);
405
+ });
406
+
407
+ it("refuses an ambiguous message handoff", () => {
408
+ const publisher = flow("publisher", [
409
+ step("shop.oms", "bus", "event", {
410
+ handoff: {
411
+ kind: "message",
412
+ transport: "kafka",
413
+ channel: "shared",
414
+ direction: "send",
415
+ },
416
+ }),
417
+ ]);
418
+ const consumer = (slug: string): Flow =>
419
+ flow(slug, [
420
+ step("bus", "shop.oms", "event", {
421
+ handoff: {
422
+ kind: "message",
423
+ transport: "kafka",
424
+ channel: "shared",
425
+ direction: "receive",
426
+ },
427
+ }),
428
+ ]);
429
+
430
+ const result = enrichCatalog(
431
+ estate([publisher, consumer("first"), consumer("second")]),
432
+ ).catalog;
433
+ expect(result.flows[0]!.includes).toBeUndefined();
434
+ expect(walkSteps(result.flows[0]!.steps)).toHaveLength(1);
435
+ });
436
+
437
+ it("composes an exact message handoff across service boundaries", () => {
438
+ const publisher = flow("publisher", [
439
+ step("shop.oms", "bus", "event", {
440
+ label: "publish payment.requested",
441
+ handoff: {
442
+ kind: "message",
443
+ transport: "kafka",
444
+ channel: "payment.requested",
445
+ direction: "send",
446
+ },
447
+ }),
448
+ ]);
449
+ const consumer: Flow = {
450
+ ...flow("ledger-consumer", [
451
+ step("bus", "payments.ledger", "event", {
452
+ label: "handle payment.requested",
453
+ handoff: {
454
+ kind: "message",
455
+ transport: "kafka",
456
+ channel: "payment.requested",
457
+ direction: "receive",
458
+ },
459
+ }),
460
+ ]),
461
+ owner: "payments",
462
+ trigger: {
463
+ kind: "event",
464
+ label: "payment.requested",
465
+ confidence: "high",
466
+ },
467
+ };
468
+
469
+ const result = enrichCatalog(estate([publisher, consumer])).catalog;
470
+ expect(result.flows[0]!.includes).toEqual(["ledger-consumer"]);
471
+ expect(
472
+ result.flows[0]!.participants.some(
473
+ (participant) => participant.id === "payments.ledger",
474
+ ),
475
+ ).toBe(true);
476
+ });
477
+
478
+ it("stops recursive handoff cycles and remains idempotent", () => {
479
+ const chained = (slug: string, input: string, output: string): Flow => ({
480
+ ...flow(slug, [
481
+ step("bus", "shop.oms", "event", {
482
+ label: `receive ${input}`,
483
+ handoff: {
484
+ kind: "message",
485
+ transport: "kafka",
486
+ channel: input,
487
+ direction: "receive",
488
+ },
489
+ }),
490
+ step("shop.oms", "bus", "event", {
491
+ label: `publish ${output}`,
492
+ handoff: {
493
+ kind: "message",
494
+ transport: "kafka",
495
+ channel: output,
496
+ direction: "send",
497
+ },
498
+ }),
499
+ ]),
500
+ trigger: { kind: "event", label: input, confidence: "high" },
501
+ });
502
+
503
+ const once = enrichCatalog(
504
+ estate([
505
+ chained("alpha", "alpha", "beta"),
506
+ chained("beta", "beta", "alpha"),
507
+ ]),
508
+ ).catalog;
509
+ expect(once.flows[0]!.includes).toEqual(["beta"]);
510
+ expect(walkSteps(once.flows[0]!.steps)).toHaveLength(4);
511
+ expect(enrichCatalog(once).catalog).toEqual(once);
512
+ });
513
+ });
514
+
515
+ // ---------------------------------------------------------------------------
516
+
517
+ describe("enrichCatalog: consumers from event steps", () => {
518
+ it("reads a consumer out of a broker -> service step, with the step's status", () => {
519
+ const c = estate([
520
+ flow("a", [
521
+ step("bus", "payments.ledger", "event", {
522
+ ref: EVENT,
523
+ status: "verified",
524
+ }),
525
+ ]),
526
+ ]);
527
+ const { catalog, derived } = enrichCatalog(c);
528
+
529
+ expect(consumersOf(catalog)).toEqual([
530
+ {
531
+ service: "payments.ledger",
532
+ status: "verified",
533
+ via: { flow: "a", step: "s1" },
534
+ },
535
+ ]);
536
+ expect(derived).toEqual([
537
+ {
538
+ kind: "consumer",
539
+ ref: EVENT,
540
+ service: "payments.ledger",
541
+ status: "verified",
542
+ via: { flow: "a", step: "s1" },
543
+ },
544
+ ]);
545
+ });
546
+
547
+ it("reads a consumer out of a service -> service step when no broker is drawn", () => {
548
+ const c = estate([
549
+ flow("a", [step("shop.oms", "payments.ledger", "event", { ref: EVENT })]),
550
+ ]);
551
+ expect(consumersOf(enrichCatalog(c).catalog).map((x) => x.service)).toEqual(
552
+ ["payments.ledger"],
553
+ );
554
+ });
555
+
556
+ it("does not read a publish, a write, a notification or a self-message as a consumer", () => {
557
+ const c = estate([
558
+ flow("a", [
559
+ step("shop.oms", "bus", "event", { ref: EVENT }),
560
+ step("shop.oms", "oms-db", "event", { ref: EVENT }),
561
+ step("shop.oms", "client", "event", { ref: EVENT }),
562
+ step("shop.oms", "shop.oms", "event", { ref: EVENT }),
563
+ ]),
564
+ ]);
565
+ const { catalog, derived } = enrichCatalog(c);
566
+ expect(derived).toEqual([]);
567
+ expect(catalog).toBe(c);
568
+ });
569
+
570
+ it("marks a consumer nobody in the catalog answers to as unresolved, and it lands on Problems", () => {
571
+ const c = estate([
572
+ flow("a", [
573
+ step("bus", "risk", "event", { ref: EVENT }),
574
+ step("bus", "ghost.svc", "event", { ref: EVENT }),
575
+ ]),
576
+ ]);
577
+ const { catalog } = enrichCatalog(c);
578
+ expect(consumersOf(catalog).map((x) => [x.service, x.status])).toEqual([
579
+ ["risk", "unresolved"],
580
+ ["ghost.svc", "unresolved"],
581
+ ]);
582
+ expect(problems(catalog).map((p) => [p.kind, p.peer])).toEqual([
583
+ ["consumer", "risk"],
584
+ ["consumer", "ghost.svc"],
585
+ ]);
586
+ });
587
+
588
+ it("lets a declared consumer win over the step, untouched", () => {
589
+ const c = estate(
590
+ [
591
+ flow("a", [
592
+ step("bus", "payments.ledger", "event", {
593
+ ref: EVENT,
594
+ status: "verified",
595
+ }),
596
+ ]),
597
+ ],
598
+ [
599
+ oms([
600
+ { service: "payments.ledger", status: "declared", note: "by hand" },
601
+ ]),
602
+ pricing(),
603
+ ],
604
+ );
605
+ const { catalog, derived } = enrichCatalog(c);
606
+ expect(derived).toEqual([]);
607
+ expect(consumersOf(catalog)).toEqual([
608
+ { service: "payments.ledger", status: "declared", note: "by hand" },
609
+ ]);
610
+ });
611
+
612
+ it("records one consumer when two flows imply the same edge, from the first flow", () => {
613
+ const c = estate([
614
+ flow("a", [step("bus", "payments.ledger", "event", { ref: EVENT })]),
615
+ flow("b", [
616
+ step("bus", "payments.ledger", "event", {
617
+ ref: EVENT,
618
+ status: "verified",
619
+ }),
620
+ ]),
621
+ ]);
622
+ const list = consumersOf(enrichCatalog(c).catalog);
623
+ expect(list).toHaveLength(1);
624
+ expect(list[0]?.via).toEqual({ flow: "a", step: "s1" });
625
+ });
626
+
627
+ it("ignores a step whose ref is not an event the catalog has", () => {
628
+ const c = estate([
629
+ flow("a", [
630
+ step("bus", "payments.ledger", "event", {
631
+ ref: "nope.Event",
632
+ status: "unresolved",
633
+ }),
634
+ ]),
635
+ ]);
636
+ expect(enrichCatalog(c).derived).toEqual([]);
637
+ });
638
+
639
+ it("sees steps inside alt, parallel and loop frames", () => {
640
+ const c = estate([
641
+ flow("a", [
642
+ {
643
+ type: "alt",
644
+ id: "alt1",
645
+ branches: [
646
+ {
647
+ title: "yes",
648
+ steps: [step("bus", "payments.ledger", "event", { ref: EVENT })],
649
+ },
650
+ {
651
+ title: "no",
652
+ steps: [step("bus", "shop.pricing", "event", { ref: EVENT })],
653
+ },
654
+ ],
655
+ },
656
+ {
657
+ type: "parallel",
658
+ id: "par1",
659
+ branches: [[step("bus", "ghost.svc", "event", { ref: EVENT })]],
660
+ },
661
+ {
662
+ type: "loop",
663
+ id: "loop1",
664
+ title: "retry",
665
+ steps: [step("bus", "risk", "event", { ref: EVENT })],
666
+ },
667
+ ]),
668
+ ]);
669
+ expect(consumersOf(enrichCatalog(c).catalog).map((x) => x.service)).toEqual(
670
+ ["payments.ledger", "shop.pricing", "ghost.svc", "risk"],
671
+ );
672
+ });
673
+ });
674
+
675
+ describe("enrichCatalog: calls from rpc steps", () => {
676
+ it("reads a call out of a service -> provider step, sourced from the flow", () => {
677
+ const c = estate([
678
+ flow(
679
+ "a",
680
+ [
681
+ step("shop.oms", "shop.pricing", "rpc", {
682
+ ref: METHOD,
683
+ status: "verified",
684
+ }),
685
+ ],
686
+ "checkout_test.go",
687
+ ),
688
+ ]);
689
+ const { catalog, derived } = enrichCatalog(c);
690
+ expect(serviceOf(catalog, "shop.oms").consumes).toEqual([
691
+ {
692
+ id: METHOD,
693
+ peer: "shop.pricing",
694
+ status: "verified",
695
+ source: "checkout_test.go",
696
+ via: { flow: "a", step: "s1" },
697
+ },
698
+ ]);
699
+ expect(derived[0]).toMatchObject({
700
+ kind: "rpc",
701
+ service: "shop.oms",
702
+ peer: "shop.pricing",
703
+ });
704
+ });
705
+
706
+ it("names the flow as the source when the flow has none", () => {
707
+ const c = estate([
708
+ flow("a", [step("shop.oms", "shop.pricing", "rpc", { ref: METHOD })]),
709
+ ]);
710
+ expect(
711
+ serviceOf(enrichCatalog(c).catalog, "shop.oms").consumes[0]?.source,
712
+ ).toBe("flow:a");
713
+ });
714
+
715
+ it("puts nothing on an actor, which has no consumes list", () => {
716
+ const c = estate([
717
+ flow("a", [step("client", "shop.pricing", "rpc", { ref: METHOD })]),
718
+ ]);
719
+ expect(enrichCatalog(c).derived).toEqual([]);
720
+ });
721
+
722
+ it("derives nothing from a method nobody provides or declares", () => {
723
+ const c = estate([
724
+ flow("a", [
725
+ step("shop.oms", "shop.pricing", "rpc", {
726
+ ref: "pricing.v1.Pricing/Nope",
727
+ status: "unresolved",
728
+ }),
729
+ ]),
730
+ ]);
731
+ const { catalog, derived } = enrichCatalog(c);
732
+ expect(derived).toEqual([]);
733
+ expect(() => validateCatalog(c)).not.toThrow();
734
+ expect(() => validateCatalog(catalog)).not.toThrow();
735
+ });
736
+
737
+ it("is what lets a step naming a provided method validate: the call is now known", () => {
738
+ // The validator resolves a step's ref against declared calls, not against
739
+ // what peers provide. Before this pass the step names a method no service
740
+ // is on record as calling; after it, shop.oms is.
741
+ const c = estate([
742
+ flow("a", [step("shop.oms", "shop.pricing", "rpc", { ref: METHOD })]),
743
+ ]);
744
+ expect(() => validateCatalog(c)).toThrow(/resolves to neither/);
745
+ expect(() => validateCatalog(enrichCatalog(c).catalog)).not.toThrow();
746
+ });
747
+
748
+ it("reads a call to a system outside the estate as declared when its contract answers on the method", () => {
749
+ const ASSESS = "risk.v1.Risk/Assess";
750
+ const c: Catalog = {
751
+ ...estate([
752
+ flow("a", [step("shop.oms", "risk", "rpc", { ref: ASSESS })]),
753
+ ]),
754
+ externals: [
755
+ {
756
+ id: "risk",
757
+ slug: "risk",
758
+ name: "Risk",
759
+ summary: "",
760
+ provides: [
761
+ {
762
+ id: "risk.v1.Risk",
763
+ methods: [{ name: "Assess" }],
764
+ source: "risk/openapi.yaml",
765
+ },
766
+ ],
767
+ },
768
+ ],
769
+ };
770
+ const { catalog, derived } = enrichCatalog(c);
771
+ expect(serviceOf(catalog, "shop.oms").consumes).toMatchObject([
772
+ { id: ASSESS, peer: "risk", status: "declared" },
773
+ ]);
774
+ expect(derived[0]).toMatchObject({
775
+ kind: "rpc",
776
+ peer: "risk",
777
+ status: "declared",
778
+ });
779
+ expect(() => validateCatalog(catalog)).not.toThrow();
780
+ // Declared, so not a problem: the far end is outside, and it is known.
781
+ expect(problems(catalog)).toEqual([]);
782
+ });
783
+
784
+ it("marks a call to a peer that does not provide the method as unresolved", () => {
785
+ // Somebody declared the call id, so the ref resolves; the step points it at
786
+ // an external that provides nothing.
787
+ const c = estate(
788
+ [flow("a", [step("shop.oms", "risk", "rpc", { ref: METHOD })])],
789
+ [
790
+ oms(),
791
+ pricing(),
792
+ service("shop", "other", {
793
+ consumes: [
794
+ {
795
+ id: METHOD,
796
+ peer: "shop.pricing",
797
+ status: "declared",
798
+ source: "x",
799
+ },
800
+ ],
801
+ }),
802
+ ],
803
+ );
804
+ expect(
805
+ serviceOf(enrichCatalog(c).catalog, "shop.oms").consumes,
806
+ ).toMatchObject([{ id: METHOD, peer: "risk", status: "unresolved" }]);
807
+ });
808
+
809
+ it("lets a declared call win over the step", () => {
810
+ const c = estate(
811
+ [
812
+ flow("a", [
813
+ step("shop.oms", "shop.pricing", "rpc", {
814
+ ref: METHOD,
815
+ status: "verified",
816
+ }),
817
+ ]),
818
+ ],
819
+ [oms(), pricing()].map((s) =>
820
+ s.id === "shop.oms"
821
+ ? {
822
+ ...s,
823
+ consumes: [
824
+ {
825
+ id: METHOD,
826
+ peer: "shop.pricing",
827
+ status: "declared",
828
+ source: "by hand",
829
+ },
830
+ ],
831
+ }
832
+ : s,
833
+ ),
834
+ );
835
+ const { catalog, derived } = enrichCatalog(c);
836
+ expect(derived).toEqual([]);
837
+ expect(serviceOf(catalog, "shop.oms").consumes).toEqual([
838
+ {
839
+ id: METHOD,
840
+ peer: "shop.pricing",
841
+ status: "declared",
842
+ source: "by hand",
843
+ },
844
+ ]);
845
+ });
846
+
847
+ it("ignores call steps", () => {
848
+ const c = estate([
849
+ flow("a", [step("shop.oms", "oms-db", "call", { ref: EVENT })]),
850
+ ]);
851
+ expect(enrichCatalog(c).derived).toEqual([]);
852
+ });
853
+ });
854
+
855
+ describe("enrichCatalog: invariants", () => {
856
+ const c = estate(
857
+ [
858
+ flow("a", [
859
+ step("shop.oms", "shop.pricing", "rpc", { ref: METHOD }),
860
+ step("shop.oms", "bus", "event", { ref: EVENT }),
861
+ step("bus", "payments.ledger", "event", { ref: EVENT }),
862
+ step("bus", "risk", "event", { ref: EVENT }),
863
+ ]),
864
+ ],
865
+ [
866
+ oms(),
867
+ pricing(),
868
+ service("shop", "other", {
869
+ consumes: [
870
+ { id: METHOD, peer: "shop.pricing", status: "declared", source: "x" },
871
+ ],
872
+ }),
873
+ ],
874
+ );
875
+
876
+ it("never writes into the input", () => {
877
+ const before = JSON.stringify(c);
878
+ enrichCatalog(c);
879
+ expect(JSON.stringify(c)).toBe(before);
880
+ });
881
+
882
+ it("is idempotent", () => {
883
+ const once = enrichCatalog(c);
884
+ const twice = enrichCatalog(once.catalog);
885
+ expect(twice.derived).toEqual([]);
886
+ expect(twice.catalog).toBe(once.catalog);
887
+ });
888
+
889
+ it("keeps a valid catalog valid", () => {
890
+ expect(() => validateCatalog(c)).not.toThrow();
891
+ expect(() => validateCatalog(enrichCatalog(c).catalog)).not.toThrow();
892
+ });
893
+ });
894
+
895
+ describe("enrichCatalog: the auth fragment", () => {
896
+ // The three fragments together, the way the app reads them: the domain one
897
+ // alone names endpoints the api one declares.
898
+ const raw = mergeCatalogs(
899
+ ["domain", "api", "stores"].map((name) => ({
900
+ path: `${name}.json`,
901
+ catalog: JSON.parse(
902
+ readFileSync(
903
+ new URL(`../examples/auth/portolan/${name}.json`, import.meta.url),
904
+ "utf8",
905
+ ),
906
+ ) as Catalog,
907
+ })),
908
+ ).catalog;
909
+
910
+ it("finds the one edge the extractor could not write: auth hears its own PasswordChanged", () => {
911
+ const { catalog, derived } = enrichCatalog(raw);
912
+ expect(derived).toEqual([
913
+ {
914
+ kind: "consumer",
915
+ ref: "auth.auth.user.PasswordChanged",
916
+ service: "auth.auth",
917
+ status: "declared",
918
+ via: { flow: "auth-revoke-sessions-on-password-change", step: "s1" },
919
+ },
920
+ ]);
921
+ expect(() => validateCatalog(catalog)).not.toThrow();
922
+ expect(problems(catalog)).toEqual(problems(raw));
923
+ });
924
+ });
925
+
926
+ // ---------------------------------------------------------------------------
927
+
928
+ describe("enrichCatalog: an event named by the name it travels under", () => {
929
+ /** The same estate, with a wire name on OrderPlaced. */
930
+ function wired(name = "oms.OrderPlaced"): Service {
931
+ const service = oms();
932
+ service.aggregates[0]!.events[0]!.wire = {
933
+ name,
934
+ channel: "shop.oms.order",
935
+ };
936
+ return service;
937
+ }
938
+
939
+ function stepOf(catalog: Catalog, slug: string) {
940
+ const found = catalog.flows.find((f) => f.slug === slug)!;
941
+ return found.steps[0] as Extract<FlowNode, { type: "step" }>;
942
+ }
943
+
944
+ it("resolves a step that names the wire name, and reads the consumer off it", () => {
945
+ // What an extractor can say about somebody else's message: the name on it.
946
+ const listens = flow("listens", [
947
+ step("bus", "shop.pricing", "event", {
948
+ status: "unresolved",
949
+ label: "oms.OrderPlaced",
950
+ }),
951
+ ]);
952
+ const { catalog } = enrichCatalog(estate([listens], [wired(), pricing()]));
953
+
954
+ const resolved = stepOf(catalog, "listens");
955
+ expect(resolved.ref).toBe(EVENT);
956
+ expect(resolved.status).toBe("declared");
957
+ expect(consumersOf(catalog)).toEqual([
958
+ {
959
+ service: "shop.pricing",
960
+ status: "declared",
961
+ via: { flow: "listens", step: "s1" },
962
+ },
963
+ ]);
964
+ });
965
+
966
+ it("resolves the last segment when one event travels under it", () => {
967
+ const listens = flow("listens", [
968
+ step("bus", "shop.pricing", "event", {
969
+ status: "unresolved",
970
+ label: "OrderPlaced",
971
+ }),
972
+ ]);
973
+ const { catalog } = enrichCatalog(estate([listens], [wired(), pricing()]));
974
+
975
+ expect(stepOf(catalog, "listens").ref).toBe(EVENT);
976
+ });
977
+
978
+ it("leaves a segment two events answer to alone", () => {
979
+ // Two publishers, one last segment: resolving it would put the listener on
980
+ // somebody else's event, which is worse than leaving the step unresolved.
981
+ const other = service("payments", "ledger", {
982
+ aggregates: [
983
+ {
984
+ id: "payments.ledger.payment",
985
+ slug: "payment",
986
+ name: "Payment",
987
+ readme: "",
988
+ root: "Payment",
989
+ entities: [],
990
+ valueObjects: [],
991
+ operations: [],
992
+ events: [
993
+ {
994
+ id: "payments.ledger.payment.OrderPlaced",
995
+ slug: "orderplaced",
996
+ name: "OrderPlaced",
997
+ versions: [
998
+ { version: "v1", doc: "", source: "x.java", fields: [] },
999
+ ],
1000
+ consumers: [],
1001
+ wire: { name: "ledger.OrderPlaced" },
1002
+ },
1003
+ ],
1004
+ },
1005
+ ],
1006
+ });
1007
+ const listens = flow("listens", [
1008
+ step("bus", "shop.pricing", "event", {
1009
+ status: "unresolved",
1010
+ label: "OrderPlaced",
1011
+ }),
1012
+ ]);
1013
+ const catalog = estate([listens], [wired(), pricing()]);
1014
+ catalog.contexts[1]!.services = [other];
1015
+
1016
+ const enriched = enrichCatalog(catalog).catalog;
1017
+ expect(stepOf(enriched, "listens").ref).toBeUndefined();
1018
+ expect(stepOf(enriched, "listens").status).toBe("unresolved");
1019
+ });
1020
+
1021
+ it("leaves a step that already resolves, and one nothing answers to", () => {
1022
+ const both = flow("both", [
1023
+ step("bus", "shop.pricing", "event", { ref: EVENT }),
1024
+ step("bus", "shop.pricing", "event", {
1025
+ status: "unresolved",
1026
+ label: "nothing.AtAll",
1027
+ }),
1028
+ ]);
1029
+ const { catalog } = enrichCatalog(estate([both], [wired(), pricing()]));
1030
+
1031
+ const steps = catalog.flows[0]!.steps as Extract<
1032
+ FlowNode,
1033
+ { type: "step" }
1034
+ >[];
1035
+ expect(steps[0]!.status).toBe("declared");
1036
+ expect(steps[1]!.ref).toBeUndefined();
1037
+ expect(steps[1]!.status).toBe("unresolved");
1038
+ });
1039
+
1040
+ it("sees a step inside a frame, and enriching twice changes nothing", () => {
1041
+ const nested = flow("nested", [
1042
+ {
1043
+ type: "alt",
1044
+ id: "alt1",
1045
+ branches: [
1046
+ {
1047
+ title: "the money arrived",
1048
+ steps: [
1049
+ step("bus", "shop.pricing", "event", {
1050
+ status: "unresolved",
1051
+ label: "oms.OrderPlaced",
1052
+ }),
1053
+ ],
1054
+ },
1055
+ { title: "otherwise", steps: [] },
1056
+ ],
1057
+ },
1058
+ ]);
1059
+ const once = enrichCatalog(estate([nested], [wired(), pricing()])).catalog;
1060
+ const twice = enrichCatalog(once).catalog;
1061
+
1062
+ const inside = (catalog: Catalog) =>
1063
+ (catalog.flows[0]!.steps[0] as Extract<FlowNode, { type: "alt" }>)
1064
+ .branches[0]!.steps[0] as Extract<FlowNode, { type: "step" }>;
1065
+ expect(inside(once).ref).toBe(EVENT);
1066
+ expect(twice).toEqual(once);
1067
+ });
1068
+ });
1069
+
1070
+ // ---------------------------------------------------------------------------
1071
+
1072
+ describe("enrichCatalog: a foreign key into another service's table", () => {
1073
+ function estateWithStores(): Catalog {
1074
+ const catalog = estate([]);
1075
+ catalog.stores = [
1076
+ {
1077
+ id: "delivery.core.pg",
1078
+ slug: "pg",
1079
+ name: "Delivery database",
1080
+ kind: "postgres",
1081
+ owner: "delivery.core",
1082
+ tables: [
1083
+ {
1084
+ id: "delivery.core.pg.packages",
1085
+ name: "packages",
1086
+ columns: [
1087
+ { name: "id", type: "text", nullable: false, pk: true },
1088
+ // What the extractor leaves when the table is not in its store.
1089
+ {
1090
+ name: "order_id",
1091
+ type: "text",
1092
+ nullable: false,
1093
+ fk: { table: "orders", column: "id" },
1094
+ },
1095
+ ],
1096
+ },
1097
+ ],
1098
+ },
1099
+ {
1100
+ id: "shop.oms.pg",
1101
+ slug: "pg",
1102
+ name: "Order database",
1103
+ kind: "postgres",
1104
+ owner: "shop.oms",
1105
+ tables: [
1106
+ {
1107
+ id: "shop.oms.pg.orders",
1108
+ name: "orders",
1109
+ columns: [{ name: "id", type: "text", nullable: false, pk: true }],
1110
+ },
1111
+ ],
1112
+ },
1113
+ ];
1114
+ return catalog;
1115
+ }
1116
+
1117
+ function keyOf(catalog: Catalog) {
1118
+ return catalog.stores![0]!.tables[0]!.columns[1]!.fk!.table;
1119
+ }
1120
+
1121
+ it("resolves a name exactly one table in the estate answers to", () => {
1122
+ expect(keyOf(enrichCatalog(estateWithStores()).catalog)).toBe(
1123
+ "shop.oms.pg.orders",
1124
+ );
1125
+ });
1126
+
1127
+ it("leaves a name two tables answer to alone", () => {
1128
+ const catalog = estateWithStores();
1129
+ catalog.stores!.push({
1130
+ id: "payments.ledger.pg",
1131
+ slug: "pg",
1132
+ name: "Ledger database",
1133
+ kind: "postgres",
1134
+ owner: "payments.ledger",
1135
+ tables: [
1136
+ { id: "payments.ledger.pg.orders", name: "orders", columns: [] },
1137
+ ],
1138
+ });
1139
+
1140
+ expect(keyOf(enrichCatalog(catalog).catalog)).toBe("orders");
1141
+ });
1142
+
1143
+ it("leaves a key that already resolves untouched, and enriching twice changes nothing", () => {
1144
+ const once = enrichCatalog(estateWithStores()).catalog;
1145
+ const twice = enrichCatalog(once).catalog;
1146
+
1147
+ expect(keyOf(twice)).toBe("shop.oms.pg.orders");
1148
+ expect(twice).toEqual(once);
1149
+ });
1150
+ });