@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,105 @@
1
+ // The failure path is a feature, so it is tested like one: a bad catalog must
2
+ // fail with a message a reader can act on AND a path that names where to look.
3
+
4
+ import { describe, expect, it } from "vitest";
5
+ import { rawCatalog } from "./test-catalog";
6
+ import { CatalogError, validateCatalog } from "./catalog";
7
+ import type { Catalog } from "./catalog";
8
+
9
+ const clone = (): Catalog =>
10
+ JSON.parse(JSON.stringify(rawCatalog)) as unknown as Catalog;
11
+
12
+ /** Runs the validator and returns the error, failing the test if there is none. */
13
+ function failureOf(catalog: Catalog): CatalogError {
14
+ try {
15
+ validateCatalog(catalog);
16
+ } catch (e) {
17
+ expect(e).toBeInstanceOf(CatalogError);
18
+ return e as CatalogError;
19
+ }
20
+ throw new Error("expected the catalog to fail validation");
21
+ }
22
+
23
+ describe("CatalogError.path", () => {
24
+ it("names the flow and the step for a step that points at a missing lane", () => {
25
+ const bad = clone();
26
+ const flow = bad.flows[0];
27
+ if (!flow) throw new Error("fixture has no flows");
28
+ const step = flow.steps.find((n) => n.type === "step");
29
+ if (!step || step.type !== "step")
30
+ throw new Error("fixture flow has no plain step");
31
+ step.to = "nobody.at.all";
32
+
33
+ const error = failureOf(bad);
34
+ expect(error.message).toContain(step.id);
35
+ expect(error.path).toBe(`flow ${flow.id} / step ${step.id}`);
36
+ });
37
+
38
+ it("names the aggregate for a root that is not one of its entities", () => {
39
+ const bad = clone();
40
+ const aggregate = bad.contexts[0]?.services[0]?.aggregates[0];
41
+ if (!aggregate) throw new Error("fixture has no aggregates");
42
+ aggregate.root = "NotAnEntity";
43
+
44
+ const error = failureOf(bad);
45
+ expect(error.path).toBe(`aggregate ${aggregate.id}`);
46
+ });
47
+
48
+ it("names the event and version for a field pointing at a missing def", () => {
49
+ const bad = clone();
50
+ const event = bad.contexts
51
+ .flatMap((c) => c.services)
52
+ .flatMap((s) => s.aggregates)
53
+ .flatMap((a) => a.events)
54
+ .find((e) => (e.versions[0]?.fields.length ?? 0) > 0);
55
+ const version = event?.versions[0];
56
+ const field = version?.fields[0];
57
+ if (!event || !version || !field)
58
+ throw new Error("fixture has no event fields");
59
+ field.ref = "no.such.def";
60
+
61
+ const error = failureOf(bad);
62
+ expect(error.path).toBe(
63
+ `event ${event.id}@${version.version} / field ${field.name}`,
64
+ );
65
+ });
66
+
67
+ it("leaves the message untouched - the page prints it verbatim", () => {
68
+ const bad = clone();
69
+ bad.commit = "";
70
+ const error = failureOf(bad);
71
+ expect(error.message).toBe("catalog.commit is missing");
72
+ expect(error.path).toBe("catalog");
73
+ });
74
+
75
+ it("passes the real catalog, path and all", () => {
76
+ expect(() => validateCatalog(clone())).not.toThrow();
77
+ });
78
+
79
+ it("refuses a flow that names no owner", () => {
80
+ // Whatever derived the flow knew whose tree it was reading, so a missing
81
+ // owner is a broken extraction - and without it the tree has to guess,
82
+ // which is a fact nobody wrote down.
83
+ const bad = clone();
84
+ const flow = bad.flows[0];
85
+ if (!flow) throw new Error("fixture has no flows");
86
+ delete (flow as { owner?: string }).owner;
87
+
88
+ const error = failureOf(bad);
89
+ expect(error.message).toContain("names no owner");
90
+ expect(error.path).toBe(`flow ${flow.id}`);
91
+ });
92
+
93
+ it("refuses an owner that is not a top-level group", () => {
94
+ const bad = clone();
95
+ const flow = bad.flows[0];
96
+ if (!flow) throw new Error("fixture has no flows");
97
+ flow.owner = "not-a-context";
98
+
99
+ const error = failureOf(bad);
100
+ expect(error.message).toContain(
101
+ 'names owner "not-a-context", which is not a top-level group',
102
+ );
103
+ expect(error.path).toBe(`flow ${flow.id}`);
104
+ });
105
+ });
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import type { Catalog } from "./catalog";
4
+ import {
5
+ catalogProfileNamed,
6
+ filterCatalogForProfile,
7
+ globToRegExp,
8
+ } from "./catalog-profile";
9
+
10
+ describe("catalog profiles", () => {
11
+ it("selects a named profile and falls back to the declared default", () => {
12
+ const manifest = {
13
+ defaultCatalog: "avia",
14
+ catalogs: [
15
+ { id: "example", title: "Example", sources: ["examples/**/*.json"], contexts: ["shop"], projects: [] },
16
+ { id: "avia", title: "Avia", sources: ["external/repos/avia/**/*.json"], contexts: ["avia"], projects: [] },
17
+ ],
18
+ };
19
+ expect(catalogProfileNamed(manifest, "example").id).toBe("example");
20
+ expect(catalogProfileNamed(manifest, "missing").id).toBe("avia");
21
+ });
22
+
23
+ it("matches one and two-star source globs", () => {
24
+ expect(globToRegExp("examples/*/portolan/*.json").test("examples/auth/portolan/api.json")).toBe(true);
25
+ expect(globToRegExp("examples/*/portolan/*.json").test("examples/shop/cart/portolan/api.json")).toBe(false);
26
+ expect(globToRegExp("external/repos/avia/**/portolan/*.json").test("external/repos/avia/core/portolan/api.json")).toBe(true);
27
+ });
28
+
29
+ it("removes foreign contexts and their profile-owned facts", () => {
30
+ const catalog = {
31
+ generatedAt: "",
32
+ commit: "",
33
+ defs: {},
34
+ contexts: [
35
+ { id: "avia", slug: "avia", name: "Avia", summary: "", services: [{ id: "avia.core", slug: "core", name: "Core", repo: "", path: "", readme: "", provides: [], consumes: [], aggregates: [] }] },
36
+ { id: "shop", slug: "shop", name: "Shop", summary: "", services: [{ id: "shop.cart", slug: "cart", name: "Cart", repo: "", path: "", readme: "", provides: [], consumes: [], aggregates: [] }] },
37
+ ],
38
+ flows: [
39
+ { id: "flow.avia", slug: "avia", name: "Avia", summary: "", owner: "avia", participants: [], steps: [] },
40
+ { id: "flow.shop", slug: "shop", name: "Shop", summary: "", owner: "shop", participants: [], steps: [] },
41
+ ],
42
+ adrs: [],
43
+ stores: [
44
+ { id: "avia.core.redis", slug: "redis", name: "Redis", kind: "redis", owner: "avia.core", tables: [] },
45
+ { id: "shop.cart.pg", slug: "pg", name: "PG", kind: "postgres", owner: "shop.cart", tables: [] },
46
+ ],
47
+ } satisfies Catalog;
48
+ const filtered = filterCatalogForProfile(catalog, {
49
+ id: "avia",
50
+ title: "Avia",
51
+ sources: [],
52
+ contexts: ["avia"],
53
+ projects: [],
54
+ });
55
+ expect(filtered.contexts.map((context) => context.id)).toEqual(["avia"]);
56
+ expect(filtered.flows.map((flow) => flow.id)).toEqual(["flow.avia"]);
57
+ expect(filtered.stores?.map((store) => store.id)).toEqual(["avia.core.redis"]);
58
+ });
59
+ });
@@ -0,0 +1,101 @@
1
+ import type { Catalog } from "./catalog";
2
+
3
+ export interface CatalogProfile {
4
+ id: string;
5
+ title: string;
6
+ /** Catalog fragments merged for this profile. */
7
+ sources: string[];
8
+ /** Top-level groups retained after the profile sources are merged. */
9
+ contexts: string[];
10
+ /** Projects shown as belonging to this profile in setup UI. */
11
+ projects: string[];
12
+ }
13
+
14
+ export interface CatalogProfileManifest {
15
+ defaultCatalog?: string;
16
+ catalogs?: CatalogProfile[];
17
+ }
18
+
19
+ /** The historical single-estate manifest is one implicit catalog. */
20
+ export function catalogProfiles(
21
+ manifest: CatalogProfileManifest & { sources?: string[] },
22
+ ): CatalogProfile[] {
23
+ if (manifest.catalogs?.length) return manifest.catalogs;
24
+ return [
25
+ {
26
+ id: "default",
27
+ title: "Catalog",
28
+ sources: manifest.sources ?? [],
29
+ contexts: [],
30
+ projects: [],
31
+ },
32
+ ];
33
+ }
34
+
35
+ export function defaultCatalogProfile(
36
+ manifest: CatalogProfileManifest & { sources?: string[] },
37
+ ): CatalogProfile {
38
+ const profiles = catalogProfiles(manifest);
39
+ return (
40
+ profiles.find((profile) => profile.id === manifest.defaultCatalog) ??
41
+ profiles[0]!
42
+ );
43
+ }
44
+
45
+ export function catalogProfileNamed(
46
+ manifest: CatalogProfileManifest & { sources?: string[] },
47
+ id: string | null | undefined,
48
+ ): CatalogProfile {
49
+ const profiles = catalogProfiles(manifest);
50
+ return profiles.find((profile) => profile.id === id) ?? defaultCatalogProfile(manifest);
51
+ }
52
+
53
+ /** Match the source globs used by the manifest without bringing a Node globber into the browser. */
54
+ export function globToRegExp(glob: string): RegExp {
55
+ let source = "^";
56
+ for (let i = 0; i < glob.length; i++) {
57
+ const char = glob[i]!;
58
+ if (char !== "*") {
59
+ source += /[\\^$.*+?()[\]{}|]/.test(char) ? `\\${char}` : char;
60
+ continue;
61
+ }
62
+ if (glob[i + 1] === "*") {
63
+ i++;
64
+ source += glob[i + 1] === "/" ? "(?:.*/)?" : ".*";
65
+ if (glob[i + 1] === "/") i++;
66
+ } else {
67
+ source += "[^/]*";
68
+ }
69
+ }
70
+ return new RegExp(`${source}$`);
71
+ }
72
+
73
+ export function profileIncludesSource(profile: CatalogProfile, path: string): boolean {
74
+ return profile.sources.some((pattern) => globToRegExp(pattern).test(path));
75
+ }
76
+
77
+ /**
78
+ * A source such as a shared CODEOWNERS overlay can describe more than one
79
+ * estate. Source selection removes almost all foreign facts; this final cut
80
+ * keeps such overlays from reintroducing another profile's top-level group.
81
+ */
82
+ export function filterCatalogForProfile(catalog: Catalog, profile: CatalogProfile): Catalog {
83
+ if (profile.contexts.length === 0) return catalog;
84
+ const contexts = new Set(profile.contexts);
85
+ const selectedContexts = catalog.contexts.filter((context) => contexts.has(context.id));
86
+ const services = new Set(selectedContexts.flatMap((context) => context.services.map((service) => service.id)));
87
+
88
+ return {
89
+ ...catalog,
90
+ contexts: selectedContexts,
91
+ flows: catalog.flows.filter((flow) => contexts.has(flow.owner)),
92
+ adrs: catalog.adrs.filter((adr) => {
93
+ if (adr.scope.kind === "org") return true;
94
+ if (adr.scope.kind === "context") return contexts.has(adr.scope.context);
95
+ return services.has(adr.scope.service);
96
+ }),
97
+ stores: (catalog.stores ?? []).filter((store) => services.has(store.owner)),
98
+ terms: (catalog.terms ?? []).filter((term) => contexts.has(term.context)),
99
+ modules: (catalog.modules ?? []).filter((module) => !module.owner || services.has(module.owner)),
100
+ };
101
+ }
@@ -0,0 +1,431 @@
1
+ // The persistence half of the contract.
2
+ //
3
+ // A store points into the domain tree in three places — the service that owns
4
+ // it, the aggregate a table persists, the block a column maps to — and a
5
+ // foreign key points at another table. Every one of those is a link that can
6
+ // dangle, and a dangling link on an ER canvas is an edge drawn into nothing.
7
+ // So each is checked here, along with the one thing the validator deliberately
8
+ // does NOT enforce: whether an outbox has a payload.
9
+
10
+ import { describe, expect, it } from "vitest";
11
+ import { rawCatalog } from "./test-catalog";
12
+ import {
13
+ CatalogError,
14
+ buildIndex,
15
+ columnId,
16
+ keyColumns,
17
+ mapsBlockId,
18
+ mapsFieldPath,
19
+ validateCatalog,
20
+ } from "./catalog";
21
+ import type { Catalog, Store } from "./catalog";
22
+
23
+ const clone = (): Catalog =>
24
+ JSON.parse(JSON.stringify(rawCatalog)) as unknown as Catalog;
25
+
26
+ function failureOf(catalog: Catalog): CatalogError {
27
+ try {
28
+ validateCatalog(catalog);
29
+ } catch (e) {
30
+ expect(e).toBeInstanceOf(CatalogError);
31
+ return e as CatalogError;
32
+ }
33
+ throw new Error("expected the catalog to fail validation");
34
+ }
35
+
36
+ /** The sample's OMS database, which every case below starts from. */
37
+ function omsStore(catalog: Catalog): Store {
38
+ const store = (catalog.stores ?? []).find((s) => s.id === "shop.oms.pg");
39
+ if (!store) throw new Error("fixture has no shop.oms.pg store");
40
+ return store;
41
+ }
42
+
43
+ describe("store validation", () => {
44
+ it("accepts the sample catalog", () => {
45
+ expect(() => validateCatalog(clone())).not.toThrow();
46
+ });
47
+
48
+ it("rejects a foreign key into a table nobody declared", () => {
49
+ const bad = clone();
50
+ const table = omsStore(bad).tables.find((t) => t.name === "order_items");
51
+ const column = table?.columns.find((c) => c.name === "order_id");
52
+ if (!table || !column?.fk) throw new Error("fixture has no fk column");
53
+ column.fk.table = "shop.oms.pg.nowhere";
54
+
55
+ const error = failureOf(bad);
56
+ expect(error.message).toContain("shop.oms.pg.nowhere");
57
+ expect(error.path).toBe(
58
+ "store shop.oms.pg / table order_items / column order_id",
59
+ );
60
+ });
61
+
62
+ it("rejects a foreign key into a column the target table does not have", () => {
63
+ const bad = clone();
64
+ const column = omsStore(bad)
65
+ .tables.find((t) => t.name === "order_items")
66
+ ?.columns.find((c) => c.name === "order_id");
67
+ if (!column?.fk) throw new Error("fixture has no fk column");
68
+ column.fk.column = "not_a_column";
69
+
70
+ expect(failureOf(bad).message).toContain("not_a_column");
71
+ });
72
+
73
+ it("rejects a table that persists an aggregate the catalog has never heard of", () => {
74
+ const bad = clone();
75
+ const table = omsStore(bad).tables.find((t) => t.name === "orders");
76
+ if (!table?.persists) throw new Error("fixture has no persisting table");
77
+ table.persists.aggregate = "shop.oms.ghost";
78
+
79
+ expect(failureOf(bad).message).toContain("shop.oms.ghost");
80
+ });
81
+
82
+ it("rejects a block that does not belong to the aggregate the table persists", () => {
83
+ const bad = clone();
84
+ const table = omsStore(bad).tables.find((t) => t.name === "order_items");
85
+ if (!table?.persists) throw new Error("fixture has no child table");
86
+ table.persists.block = "shop.pricing.quote.quote";
87
+
88
+ expect(failureOf(bad).message).toContain("shop.pricing.quote.quote");
89
+ });
90
+
91
+ it("rejects a store owned by a service that is not in the catalog", () => {
92
+ const bad = clone();
93
+ omsStore(bad).owner = "shop.gone";
94
+
95
+ // The id check fires first, and says the same thing in more detail.
96
+ expect(failureOf(bad).message).toContain("shop.gone");
97
+ });
98
+
99
+ it("rejects a store whose id is not owner plus slug", () => {
100
+ const bad = clone();
101
+ omsStore(bad).slug = "postgres";
102
+
103
+ const error = failureOf(bad);
104
+ expect(error.message).toContain("shop.oms.postgres");
105
+ });
106
+
107
+ it("rejects a store whose slug is an aggregate's, since the model draws both inside the service", () => {
108
+ const bad = clone();
109
+ const store = omsStore(bad);
110
+ store.slug = "order";
111
+ store.id = "shop.oms.order";
112
+
113
+ expect(failureOf(bad).message).toContain("aggregate");
114
+ });
115
+
116
+ it("rejects an index over a column the table does not have", () => {
117
+ const bad = clone();
118
+ const table = omsStore(bad).tables.find((t) => t.name === "orders");
119
+ const index = table?.indexes?.[0];
120
+ if (!index) throw new Error("fixture has no indexes");
121
+ index.columns = ["not_a_column"];
122
+
123
+ expect(failureOf(bad).message).toContain("not_a_column");
124
+ });
125
+
126
+ it("rejects two tables with the same id", () => {
127
+ const bad = clone();
128
+ const store = omsStore(bad);
129
+ const first = store.tables[0];
130
+ if (!first) throw new Error("fixture store has no tables");
131
+ store.tables.push({ ...first });
132
+
133
+ expect(failureOf(bad).message).toContain(first.id);
134
+ });
135
+
136
+ it("rejects a service listing a store that does not exist", () => {
137
+ const bad = clone();
138
+ const service = bad.contexts[0]?.services[0];
139
+ if (!service) throw new Error("fixture has no services");
140
+ service.stores = ["nowhere.at.all"];
141
+
142
+ expect(failureOf(bad).message).toContain("nowhere.at.all");
143
+ });
144
+
145
+ it("does NOT reject an outbox with no payload column", () => {
146
+ // This is the one rule the spec calls a warning. A catalog mid-migration
147
+ // must still render; the Problems page is where it is said out loud.
148
+ const bad = clone();
149
+ const outbox = omsStore(bad).tables.find((t) => t.role === "outbox");
150
+ if (!outbox) throw new Error("fixture has no outbox");
151
+ outbox.columns = outbox.columns.filter((c) => c.type !== "jsonb");
152
+
153
+ expect(() => validateCatalog(bad)).not.toThrow();
154
+ });
155
+
156
+ it("accepts a catalog with no stores at all", () => {
157
+ const bare = clone();
158
+ delete bare.stores;
159
+ for (const context of bare.contexts) {
160
+ for (const service of context.services) delete service.stores;
161
+ }
162
+ expect(() => validateCatalog(bare)).not.toThrow();
163
+ });
164
+
165
+ it("accepts source-backed Redis key patterns", () => {
166
+ const redis = clone();
167
+ const store = omsStore(redis);
168
+ store.kind = "redis";
169
+ store.keyspaces = [
170
+ {
171
+ pattern: "session:{id}",
172
+ operations: ["read", "write", "delete"],
173
+ ttl: "24h",
174
+ value: "Session",
175
+ source: "cache.go:20",
176
+ },
177
+ ];
178
+ expect(() => validateCatalog(redis)).not.toThrow();
179
+ });
180
+
181
+ it("rejects Redis key patterns on a non-Redis store", () => {
182
+ const bad = clone();
183
+ omsStore(bad).keyspaces = [
184
+ { pattern: "session:{id}", operations: ["read"] },
185
+ ];
186
+ expect(failureOf(bad).message).toContain("has kind \"postgres\"");
187
+ });
188
+
189
+ it("rejects duplicate key patterns and unknown operations", () => {
190
+ const duplicate = clone();
191
+ const store = omsStore(duplicate);
192
+ store.kind = "redis";
193
+ store.keyspaces = [
194
+ { pattern: "session:{id}", operations: ["read"] },
195
+ { pattern: "session:{id}", operations: ["write"] },
196
+ ];
197
+ expect(failureOf(duplicate).message).toContain("repeats Redis key pattern");
198
+
199
+ const unknown = clone();
200
+ const redis = omsStore(unknown);
201
+ redis.kind = "redis";
202
+ redis.keyspaces = [
203
+ {
204
+ pattern: "session:{id}",
205
+ operations: ["rename" as "read"],
206
+ },
207
+ ];
208
+ expect(failureOf(unknown).message).toContain("rename");
209
+ });
210
+ });
211
+
212
+ describe("view validation", () => {
213
+ /** The sample's two OMS views, which every case below starts from. */
214
+ function views(catalog: Catalog) {
215
+ const found = omsStore(catalog).views;
216
+ if (!found || found.length < 2) throw new Error("fixture has no views");
217
+ return found;
218
+ }
219
+
220
+ it("rejects a view whose id does not spell out its store and name", () => {
221
+ const bad = clone();
222
+ (views(bad)[0] as { id: string }).id = "shop.oms.pg.something_else";
223
+ expect(failureOf(bad).message).toContain("must have id");
224
+ });
225
+
226
+ it("rejects a view that collides with a table", () => {
227
+ const bad = clone();
228
+ const view = views(bad)[0];
229
+ if (!view) throw new Error("fixture has no view");
230
+ view.id = "shop.oms.pg.orders";
231
+ view.name = "orders";
232
+ expect(failureOf(bad).message).toContain("collides");
233
+ });
234
+
235
+ it("rejects a view reading something the catalog does not have", () => {
236
+ const bad = clone();
237
+ const view = views(bad)[0];
238
+ if (!view) throw new Error("fixture has no view");
239
+ view.reads = ["shop.oms.pg.nowhere"];
240
+ const error = failureOf(bad);
241
+ expect(error.message).toContain("shop.oms.pg.nowhere");
242
+ expect(error.path).toBe("store shop.oms.pg / view v_open_orders");
243
+ });
244
+
245
+ it("rejects a key on a view, which cannot enforce one", () => {
246
+ const bad = clone();
247
+ const column = views(bad)[0]?.columns[0];
248
+ if (!column) throw new Error("fixture has no view column");
249
+ column.pk = true;
250
+ expect(failureOf(bad).message).toContain("has no key of its own");
251
+ });
252
+
253
+ it("rejects a foreign key on a view", () => {
254
+ const bad = clone();
255
+ const column = views(bad)[0]?.columns[0];
256
+ if (!column) throw new Error("fixture has no view column");
257
+ column.fk = { table: "shop.oms.pg.orders", column: "id" };
258
+ expect(failureOf(bad).message).toContain("declares a foreign key");
259
+ });
260
+
261
+ it("rejects lineage into a column that is not there", () => {
262
+ const bad = clone();
263
+ const column = views(bad)[0]?.columns[0];
264
+ if (!column) throw new Error("fixture has no view column");
265
+ column.from = ["shop.oms.pg.orders.no_such_column"];
266
+ expect(failureOf(bad).message).toContain("has no column");
267
+ });
268
+
269
+ it("rejects lineage into a relation that is not there", () => {
270
+ const bad = clone();
271
+ const column = views(bad)[0]?.columns[0];
272
+ if (!column) throw new Error("fixture has no view column");
273
+ column.from = ["shop.oms.pg.nowhere.id"];
274
+ expect(failureOf(bad).message).toContain("is not a table or view");
275
+ });
276
+
277
+ it("rejects a column derived from itself", () => {
278
+ const bad = clone();
279
+ const table = omsStore(bad).tables.find((t) => t.name === "outbox");
280
+ const column = table?.columns.find((c) => c.name === "aggregate_id");
281
+ if (!column) throw new Error("fixture has no outbox column");
282
+ column.from = ["shop.oms.pg.outbox.aggregate_id"];
283
+ expect(failureOf(bad).message).toContain("derived from itself");
284
+ });
285
+
286
+ it("accepts a store with no views at all", () => {
287
+ const bare = clone();
288
+ for (const store of bare.stores ?? []) delete store.views;
289
+ expect(() => validateCatalog(bare)).not.toThrow();
290
+ });
291
+ });
292
+
293
+ describe("store indexes", () => {
294
+ const catalog = validateCatalog(clone());
295
+ const index = buildIndex(catalog);
296
+
297
+ it("resolves a table id to the store holding it", () => {
298
+ expect(index.tableById.get("shop.oms.pg.orders")?.store.id).toBe(
299
+ "shop.oms.pg",
300
+ );
301
+ });
302
+
303
+ it("resolves a column id", () => {
304
+ const id = columnId("shop.oms.pg.orders", "status");
305
+ expect(index.columnById.get(id)?.column.type).toBe("text");
306
+ });
307
+
308
+ it("lists the stores a service owns", () => {
309
+ expect(index.storesOwnedBy.get("shop.oms")?.map((s) => s.id)).toEqual([
310
+ "shop.oms.pg",
311
+ ]);
312
+ });
313
+
314
+ it("lists the tables that persist an aggregate", () => {
315
+ expect(
316
+ index.tablesByAggregate.get("shop.oms.order")?.map((t) => t.name),
317
+ ).toEqual(["orders", "order_items"]);
318
+ });
319
+
320
+ it("finds a child table through the block it persists, with no aggregate named", () => {
321
+ // The extractor is allowed to name only the block; the aggregate that owns
322
+ // that block still has to list the table.
323
+ const catalog2 = validateCatalog(clone());
324
+ const store = (catalog2.stores ?? []).find((s) => s.id === "shop.oms.pg");
325
+ const items = store?.tables.find((t) => t.name === "order_items");
326
+ if (!items?.persists) throw new Error("fixture has no child table");
327
+ delete items.persists.aggregate;
328
+
329
+ const rebuilt = buildIndex(catalog2);
330
+ expect(
331
+ rebuilt.tablesByAggregate.get("shop.oms.order")?.map((t) => t.name),
332
+ ).toContain("order_items");
333
+ });
334
+
335
+ it("indexes the columns that carry a block's fields", () => {
336
+ const columns = index.columnsByBlock.get("shop.oms.order.order") ?? [];
337
+ expect(columns.map((c) => c.column.name)).toContain("status");
338
+ // A column with no `maps` is not a column that carries a field.
339
+ expect(columns.map((c) => c.column.name)).not.toContain("placed_at");
340
+ });
341
+
342
+ it("resolves a view id to the store declaring it", () => {
343
+ expect(index.viewById.get("shop.oms.pg.v_open_orders")?.store.id).toBe(
344
+ "shop.oms.pg",
345
+ );
346
+ });
347
+
348
+ it("resolves a view's column apart from a table's", () => {
349
+ const id = columnId("shop.oms.pg.v_open_orders", "line_count");
350
+ expect(index.viewColumnById.get(id)?.column.type).toBe("bigint");
351
+ // The two namespaces do not leak into one another: a view column is not a
352
+ // table column, and a caller that wants either has to ask for both.
353
+ expect(index.columnById.has(id)).toBe(false);
354
+ });
355
+
356
+ it("lists the views reading a table, and a view read by another view", () => {
357
+ expect(
358
+ index.viewsReading.get("shop.oms.pg.orders")?.map((v) => v.name),
359
+ ).toEqual(["v_open_orders"]);
360
+ expect(
361
+ index.viewsReading.get("shop.oms.pg.v_open_orders")?.map((v) => v.name),
362
+ ).toEqual(["mv_orders_daily"]);
363
+ });
364
+
365
+ it("lists the views presenting an aggregate", () => {
366
+ expect(
367
+ index.viewsByAggregate.get("shop.oms.order")?.map((v) => v.name),
368
+ ).toEqual(["v_open_orders"]);
369
+ });
370
+
371
+ it("indexes lineage from both ends", () => {
372
+ const derived = columnId("shop.oms.pg.v_open_orders", "total_minor");
373
+ expect(index.lineageFrom.get(derived)).toEqual([
374
+ "shop.oms.pg.orders.total_minor",
375
+ ]);
376
+ expect(index.lineageInto.get("shop.oms.pg.orders.total_minor")).toEqual([
377
+ derived,
378
+ ]);
379
+ });
380
+
381
+ it("indexes lineage declared on a table, not only on a view", () => {
382
+ expect(index.lineageFrom.get("shop.oms.pg.outbox.aggregate_id")).toEqual([
383
+ "shop.oms.pg.orders.id",
384
+ ]);
385
+ });
386
+
387
+ it("indexes what points at a table", () => {
388
+ const into = index.fkIntoTable.get("shop.oms.pg.orders") ?? [];
389
+ expect(into.map((o) => `${o.table.name}.${o.column.name}`)).toEqual([
390
+ "order_items.order_id",
391
+ "packages.order_id",
392
+ ]);
393
+ });
394
+ });
395
+
396
+ describe("maps paths", () => {
397
+ const catalog = validateCatalog(clone());
398
+ const index = buildIndex(catalog);
399
+ const order = index.aggregateById.get("shop.oms.order");
400
+
401
+ it("resolves the type name against the aggregate the table persists", () => {
402
+ expect(mapsBlockId(order, "OrderLine.sku")).toBe(
403
+ "shop.oms.order.order-line",
404
+ );
405
+ });
406
+
407
+ it("resolves nothing for a type the aggregate does not declare", () => {
408
+ expect(mapsBlockId(order, "Basket.id")).toBeNull();
409
+ expect(mapsBlockId(undefined, "Order.id")).toBeNull();
410
+ });
411
+
412
+ it("keeps everything after the type name as the field path", () => {
413
+ expect(mapsFieldPath("Order.total.amountMinor")).toBe("total.amountMinor");
414
+ expect(mapsFieldPath("id")).toBe("id");
415
+ });
416
+ });
417
+
418
+ describe("keyColumns", () => {
419
+ const catalog = validateCatalog(clone());
420
+
421
+ it("is the primary key and every foreign key, in declaration order", () => {
422
+ const items = (catalog.stores ?? [])
423
+ .find((s) => s.id === "shop.oms.pg")
424
+ ?.tables.find((t) => t.name === "order_items");
425
+ if (!items) throw new Error("fixture has no order_items");
426
+ expect(keyColumns(items).map((c) => c.name)).toEqual([
427
+ "order_id",
428
+ "line_no",
429
+ ]);
430
+ });
431
+ });