@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,245 @@
1
+ // The join between a schema and a model, asserted.
2
+ //
3
+ // Most of what matters here is what does NOT get flagged. A type comparison
4
+ // that shouts about every named domain shape is worse than no comparison at
5
+ // all, so the negative cases below carry as much weight as the positive ones.
6
+
7
+ import { describe, expect, it } from "vitest";
8
+ import { rawCatalog } from "../test-catalog";
9
+ import { buildIndex, validateCatalog } from "../catalog";
10
+ import type { Catalog } from "../catalog";
11
+ import {
12
+ columnsOfBlock,
13
+ dbClass,
14
+ domainClass,
15
+ outboxOfService,
16
+ payloadColumn,
17
+ readersOfStore,
18
+ storeColumnCount,
19
+ storedFields,
20
+ storesOfService,
21
+ tablesPersisting,
22
+ typesDisagree,
23
+ unmappedFields,
24
+ } from "./data-model";
25
+
26
+ const catalog = validateCatalog(
27
+ JSON.parse(JSON.stringify(rawCatalog)) as unknown as Catalog,
28
+ );
29
+ const index = buildIndex(catalog);
30
+
31
+ /**
32
+ * The catalog with one table's columns pointing at fields the aggregate no
33
+ * longer declares - the shape a schema takes after a rename that never
34
+ * reached the migrations, and the thing the drift warning is about.
35
+ */
36
+ function drifted(): { catalog: Catalog; index: ReturnType<typeof buildIndex> } {
37
+ const copy = JSON.parse(JSON.stringify(rawCatalog)) as unknown as Catalog;
38
+ const orders = (copy.stores ?? [])
39
+ .find((s) => s.id === "shop.oms.pg")
40
+ ?.tables.find((t) => t.name === "orders");
41
+ if (!orders) throw new Error("fixture has no orders table");
42
+ for (const column of orders.columns) if (column.maps) column.maps = `Order.${column.name}Gone`;
43
+ const validated = validateCatalog(copy);
44
+ return { catalog: validated, index: buildIndex(validated) };
45
+ }
46
+
47
+ describe("type classes", () => {
48
+ it("reads postgres spellings, parameters and all", () => {
49
+ expect(dbClass("varchar(64)")).toBe("text");
50
+ expect(dbClass("TIMESTAMPTZ")).toBe("time");
51
+ expect(dbClass("timestamp with time zone")).toBe("time");
52
+ expect(dbClass("bigserial")).toBe("int64");
53
+ expect(dbClass("jsonb")).toBe("json");
54
+ });
55
+
56
+ it("reads go spellings, pointers and slices and all", () => {
57
+ expect(domainClass("*string")).toBe("text");
58
+ expect(domainClass("[]byte")).toBe("bytes");
59
+ expect(domainClass("time.Time")).toBe("time");
60
+ expect(domainClass("int32")).toBe("int32");
61
+ });
62
+
63
+ it("reads java and typescript spellings", () => {
64
+ expect(domainClass("long")).toBe("int64");
65
+ expect(domainClass("Long")).toBe("int64");
66
+ expect(domainClass("Integer")).toBe("int32");
67
+ expect(domainClass("String")).toBe("text");
68
+ expect(domainClass("BigDecimal")).toBe("decimal");
69
+ expect(domainClass("Instant")).toBe("time");
70
+ expect(domainClass("Date")).toBe("time");
71
+ });
72
+
73
+ it("gives a width-less int its own class", () => {
74
+ expect(domainClass("int")).toBe("int");
75
+ expect(domainClass("*int")).toBe("int");
76
+ });
77
+
78
+ it("knows nothing about a named domain shape, and says so", () => {
79
+ expect(domainClass("Money")).toBe("unknown");
80
+ expect(domainClass("GatewayRef")).toBe("unknown");
81
+ expect(domainClass("number")).toBe("unknown");
82
+ });
83
+ });
84
+
85
+ describe("typesDisagree", () => {
86
+ it("flags the two disagreements the spec names", () => {
87
+ expect(typesDisagree("uuid", "string")).toBe(true);
88
+ expect(typesDisagree("bigint", "int32")).toBe(true);
89
+ });
90
+
91
+ it("accepts the same shape spelled differently", () => {
92
+ expect(typesDisagree("text", "string")).toBe(false);
93
+ expect(typesDisagree("timestamptz", "time.Time")).toBe(false);
94
+ expect(typesDisagree("integer", "int32")).toBe(false);
95
+ expect(typesDisagree("bigserial", "long")).toBe(false);
96
+ expect(typesDisagree("timestamptz", "Instant")).toBe(false);
97
+ });
98
+
99
+ it("lets a width-less int sit on any integer column, and nothing else", () => {
100
+ // Go's `int` is whatever the platform says; Postgres `integer` under it
101
+ // is a choice, not a disagreement. Only a non-integer column is.
102
+ expect(typesDisagree("integer", "int")).toBe(false);
103
+ expect(typesDisagree("bigint", "int")).toBe(false);
104
+ expect(typesDisagree("smallint", "int")).toBe(false);
105
+ expect(typesDisagree("uuid", "int")).toBe(true);
106
+ expect(typesDisagree("text", "int")).toBe(true);
107
+ expect(typesDisagree("numeric", "int")).toBe(true);
108
+ });
109
+
110
+ it("stays quiet when either side is a shape it does not know", () => {
111
+ // Half the domain types in a catalog are named shapes; guessing at them
112
+ // would bury the two real disagreements above.
113
+ expect(typesDisagree("bigint", "Money")).toBe(false);
114
+ expect(typesDisagree("some_extension_type", "string")).toBe(false);
115
+ });
116
+ });
117
+
118
+ describe("storesOfService", () => {
119
+ it("lists owned stores first, then the ones only read", () => {
120
+ const stores = storesOfService(index, "delivery.core");
121
+ expect(stores.map((s) => [s.store.id, s.access])).toEqual([
122
+ ["delivery.core.pg", "owns"],
123
+ ["shop.oms.pg", "reads"],
124
+ ]);
125
+ });
126
+
127
+ it("does not call a service a reader of its own store", () => {
128
+ const stores = storesOfService(index, "shop.oms");
129
+ expect(stores.filter((s) => s.access === "reads")).toEqual([]);
130
+ });
131
+
132
+ it("finds the readers of a store from the other side", () => {
133
+ expect(
134
+ readersOfStore(catalog, "shop.oms.pg", "shop.oms").map((s) => s.id),
135
+ ).toEqual(["shop.pricing", "delivery.core"]);
136
+ });
137
+ });
138
+
139
+ describe("tablesPersisting", () => {
140
+ it("lists every table holding an aggregate, with its store", () => {
141
+ const tables = tablesPersisting(index, "shop.oms.order");
142
+ expect(tables.map((t) => t.table.name)).toEqual(["orders", "order_items"]);
143
+ expect(tables[0]?.store.id).toBe("shop.oms.pg");
144
+ });
145
+
146
+ it("is empty for an aggregate nothing stores", () => {
147
+ expect(tablesPersisting(index, "shop.pricing.price-list")).toEqual([]);
148
+ });
149
+ });
150
+
151
+ describe("outboxOfService", () => {
152
+ it("finds the outbox in a service's own store", () => {
153
+ expect(outboxOfService(index, "shop.oms")?.table.name).toBe("outbox");
154
+ });
155
+
156
+ it("is null for a service that publishes without one", () => {
157
+ expect(outboxOfService(index, "payments.ledger")).toBeNull();
158
+ });
159
+
160
+ it("calls the first json column the payload", () => {
161
+ const outbox = outboxOfService(index, "shop.oms");
162
+ if (!outbox) throw new Error("fixture has no outbox");
163
+ expect(payloadColumn(outbox.table)?.name).toBe("payload");
164
+ });
165
+ });
166
+
167
+ describe("storedFields", () => {
168
+ const stored = storedFields(catalog, index, "shop.oms.order.order");
169
+
170
+ it("joins each column to the field it maps to", () => {
171
+ const status = stored.find((s) => s.owner.column.name === "status");
172
+ expect(status?.path).toBe("status");
173
+ expect(status?.field?.type).toBe("Status");
174
+ expect(status?.mismatch).toBe(false);
175
+ });
176
+
177
+ it("marks the uuid column against a string field", () => {
178
+ const id = stored.find((s) => s.owner.column.name === "id");
179
+ expect(id?.mismatch).toBe(true);
180
+ });
181
+
182
+ it("leaves a named shape unmarked", () => {
183
+ const total = stored.find((s) => s.owner.column.name === "total_minor");
184
+ expect(total?.field?.type).toBe("Money");
185
+ expect(total?.mismatch).toBe(false);
186
+ });
187
+
188
+ it("keeps a column whose field no longer exists, with no field", () => {
189
+ const d = drifted();
190
+ const stored = storedFields(d.catalog, d.index, "shop.oms.order.order");
191
+ expect(stored.length).toBeGreaterThan(0);
192
+ expect(stored.every((s) => s.field === null)).toBe(true);
193
+ });
194
+
195
+ it("resolves a nested path against the field the block declares", () => {
196
+ // "Order.total.amountMinor" is a fact about Money, and Money is read on
197
+ // its own page; here it is still Order.total.
198
+ const line = storedFields(catalog, index, "shop.oms.order.order-line");
199
+ const price = line.find(
200
+ (s) => s.owner.column.name === "unit_price_minor",
201
+ );
202
+ expect(price?.field?.name).toBe("unitPrice");
203
+ });
204
+
205
+ it("is empty for a block nothing stores", () => {
206
+ expect(storedFields(catalog, index, "shop.oms.order.risk-decision")).toEqual(
207
+ [],
208
+ );
209
+ });
210
+ });
211
+
212
+ describe("columnsOfBlock", () => {
213
+ it("finds the columns pointing into a child block", () => {
214
+ const columns = columnsOfBlock(index, "shop.oms.order.order-line");
215
+ expect(columns.map((c) => c.column.name)).toEqual([
216
+ "line_no",
217
+ "sku",
218
+ "quantity",
219
+ "unit_price_minor",
220
+ ]);
221
+ });
222
+ });
223
+
224
+ describe("counts", () => {
225
+ it("counts every column in a store", () => {
226
+ const store = (catalog.stores ?? []).find((s) => s.id === "delivery.core.pg");
227
+ if (!store) throw new Error("fixture has no delivery store");
228
+ expect(storeColumnCount(store)).toBe(
229
+ store.tables.reduce((n, t) => n + t.columns.length, 0),
230
+ );
231
+ });
232
+
233
+ it("counts the fields of an aggregate no column carries", () => {
234
+ const order = index.aggregateById.get("shop.oms.order");
235
+ if (!order) throw new Error("fixture has no order");
236
+ const d = drifted();
237
+ const drift = d.index.aggregateById.get("shop.oms.order");
238
+ if (!drift) throw new Error("drifted fixture has no order");
239
+ // More once the columns point at names the aggregate no longer declares:
240
+ // that gap is exactly what the drift warning counts.
241
+ expect(unmappedFields(d.catalog, d.index, drift)).toBeGreaterThan(
242
+ unmappedFields(catalog, index, order),
243
+ );
244
+ });
245
+ });
@@ -0,0 +1,399 @@
1
+ // The persistence axis, derived. Pure over a catalog and its index, like
2
+ // derive.ts and backlinks.ts: no DOM, no router, so every number a Data tab or
3
+ // a Persistence section shows can be asserted in a test.
4
+ //
5
+ // The interesting work here is the join between two vocabularies. A schema
6
+ // speaks in `uuid` and `timestamptz`; a domain model speaks in `string` and
7
+ // `time.Time`. Neither is authoritative and neither is wrong, so nothing is
8
+ // rewritten into the other — the two are put side by side and the places where
9
+ // they disagree are marked.
10
+
11
+ import type {
12
+ Aggregate,
13
+ Catalog,
14
+ CatalogIndex,
15
+ Column,
16
+ ColumnOwner,
17
+ Field,
18
+ Service,
19
+ Store,
20
+ Table,
21
+ View,
22
+ } from "../catalog";
23
+ import {
24
+ aggregateBlocks,
25
+ blockFields,
26
+ mapsFieldPath,
27
+ storeViews,
28
+ } from "../catalog";
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Stores and services
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /**
35
+ * How a service touches a store. Ownership is the store's own claim, so a
36
+ * service that lists a store it does not own is reading it — which is a fact
37
+ * the canvas draws (ghosted) rather than an error.
38
+ */
39
+ export type StoreAccess = "owns" | "reads";
40
+
41
+ export interface ServiceStore {
42
+ store: Store;
43
+ access: StoreAccess;
44
+ }
45
+
46
+ /**
47
+ * Every store a service touches: the ones it owns first, in catalog order,
48
+ * then the ones it only reads. A store it owns but forgot to list is still
49
+ * owned — the list is a claim about reading, never about ownership.
50
+ */
51
+ export function storesOfService(
52
+ index: CatalogIndex,
53
+ serviceId: string,
54
+ ): ServiceStore[] {
55
+ const owned = index.storesOwnedBy.get(serviceId) ?? [];
56
+ const out: ServiceStore[] = owned.map((store) => ({
57
+ store,
58
+ access: "owns" as const,
59
+ }));
60
+
61
+ const service = index.serviceById.get(serviceId);
62
+ for (const storeId of service?.stores ?? []) {
63
+ const store = index.storeById.get(storeId);
64
+ if (!store || store.owner === serviceId) continue;
65
+ out.push({ store, access: "reads" });
66
+ }
67
+ return out;
68
+ }
69
+
70
+ /** Services that list a store they do not own — its readers, in catalog order. */
71
+ export function readersOfStore(
72
+ catalog: Catalog,
73
+ storeId: string,
74
+ ownerId: string,
75
+ ): Service[] {
76
+ const out: Service[] = [];
77
+ for (const context of catalog.contexts) {
78
+ for (const service of context.services) {
79
+ if (service.id === ownerId) continue;
80
+ if ((service.stores ?? []).includes(storeId)) out.push(service);
81
+ }
82
+ }
83
+ return out;
84
+ }
85
+
86
+ /**
87
+ * How many columns a store holds, for the header line above an ER canvas.
88
+ * A view's columns are counted apart: they are not storage, and adding them to
89
+ * the total would inflate "how big is this schema" with rows that do not exist.
90
+ */
91
+ export function storeColumnCount(store: Store): number {
92
+ return store.tables.reduce((n, t) => n + t.columns.length, 0);
93
+ }
94
+
95
+ /** How many views a store declares. */
96
+ export function storeViewCount(store: Store): number {
97
+ return storeViews(store).length;
98
+ }
99
+
100
+ /**
101
+ * The views presenting an aggregate, alongside the tables that hold it. A
102
+ * report a service reads its own aggregate through belongs on that aggregate's
103
+ * page: it is one of the ways the model is actually queried.
104
+ */
105
+ export interface PresentedView {
106
+ view: View;
107
+ store: Store;
108
+ }
109
+
110
+ export function viewsPresenting(
111
+ index: CatalogIndex,
112
+ aggregateId: string,
113
+ ): PresentedView[] {
114
+ const out: PresentedView[] = [];
115
+ for (const view of index.viewsByAggregate.get(aggregateId) ?? []) {
116
+ const held = index.viewById.get(view.id);
117
+ if (held) out.push({ view, store: held.store });
118
+ }
119
+ return out;
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Aggregates and blocks
124
+ // ---------------------------------------------------------------------------
125
+
126
+ export interface PersistedTable {
127
+ table: Table;
128
+ store: Store;
129
+ }
130
+
131
+ /**
132
+ * The tables that hold an aggregate — those naming it in `persists`, plus the
133
+ * ones naming one of its blocks. A child table pointing at a value object of
134
+ * the aggregate is still where that aggregate lives.
135
+ */
136
+ export function tablesPersisting(
137
+ index: CatalogIndex,
138
+ aggregateId: string,
139
+ ): PersistedTable[] {
140
+ const out: PersistedTable[] = [];
141
+ const seen = new Set<string>();
142
+ for (const table of index.tablesByAggregate.get(aggregateId) ?? []) {
143
+ const held = index.tableById.get(table.id);
144
+ if (!held || seen.has(table.id)) continue;
145
+ seen.add(table.id);
146
+ out.push({ table, store: held.store });
147
+ }
148
+ return out;
149
+ }
150
+
151
+ /** Columns whose `maps` lands in a block, in table then column order. */
152
+ export function columnsOfBlock(
153
+ index: CatalogIndex,
154
+ blockId: string,
155
+ ): ColumnOwner[] {
156
+ return index.columnsByBlock.get(blockId) ?? [];
157
+ }
158
+
159
+ /**
160
+ * The outbox a service publishes through, if it has one. An event's delivery
161
+ * is a fact about the service that announces it, not about the event, so this
162
+ * is asked once per publisher rather than once per event.
163
+ */
164
+ export function outboxOfService(
165
+ index: CatalogIndex,
166
+ serviceId: string,
167
+ ): PersistedTable | null {
168
+ for (const store of index.storesOwnedBy.get(serviceId) ?? []) {
169
+ for (const table of store.tables) {
170
+ if (table.role === "outbox") return { table, store };
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+
176
+ /** A table's payload column: the first `jsonb`/`json` it holds, if any. */
177
+ export function payloadColumn(table: Table): Column | null {
178
+ return table.columns.find((c) => dbClass(c.type) === "json") ?? null;
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Types. Two vocabularies, one comparison.
183
+ // ---------------------------------------------------------------------------
184
+
185
+ /**
186
+ * The shape a type has, once the spelling is set aside. Widths are kept apart
187
+ * on purpose: `int32` in the model against `bigint` in the column is exactly
188
+ * the kind of quiet disagreement this whole comparison exists to surface.
189
+ *
190
+ * `int` is the one width that is no width: Go's `int` is whatever the platform
191
+ * says, Java's is what the schema author was not thinking about. Either agrees
192
+ * with any integer column - a `bigint` under a Go `int` is a choice, not a bug.
193
+ */
194
+ export type TypeClass =
195
+ | "uuid"
196
+ | "text"
197
+ | "int"
198
+ | "int16"
199
+ | "int32"
200
+ | "int64"
201
+ | "float"
202
+ | "decimal"
203
+ | "bool"
204
+ | "time"
205
+ | "json"
206
+ | "bytes"
207
+ | "unknown";
208
+
209
+ /** Strips `[]`, pointers, parameters: "varchar(64)" and "*string" are still one type. */
210
+ function bare(type: string): string {
211
+ return type
212
+ .trim()
213
+ .replace(/^\*+/, "")
214
+ .replace(/^\[\]/, "")
215
+ .replace(/\(.*\)$/, "")
216
+ .replace(/\s+/g, " ")
217
+ .toLowerCase();
218
+ }
219
+
220
+ const DB_CLASSES: Record<string, TypeClass> = {
221
+ uuid: "uuid",
222
+ text: "text",
223
+ varchar: "text",
224
+ "character varying": "text",
225
+ character: "text",
226
+ char: "text",
227
+ citext: "text",
228
+ smallint: "int16",
229
+ int2: "int16",
230
+ smallserial: "int16",
231
+ integer: "int32",
232
+ int: "int32",
233
+ int4: "int32",
234
+ serial: "int32",
235
+ bigint: "int64",
236
+ int8: "int64",
237
+ bigserial: "int64",
238
+ numeric: "decimal",
239
+ decimal: "decimal",
240
+ money: "decimal",
241
+ real: "float",
242
+ float4: "float",
243
+ "double precision": "float",
244
+ float8: "float",
245
+ boolean: "bool",
246
+ bool: "bool",
247
+ timestamptz: "time",
248
+ "timestamp with time zone": "time",
249
+ timestamp: "time",
250
+ "timestamp without time zone": "time",
251
+ date: "time",
252
+ time: "time",
253
+ timetz: "time",
254
+ jsonb: "json",
255
+ json: "json",
256
+ bytea: "bytes",
257
+ };
258
+
259
+ const DOMAIN_CLASSES: Record<string, TypeClass> = {
260
+ string: "text",
261
+ "uuid.uuid": "uuid",
262
+ uuid: "uuid",
263
+ int16: "int16",
264
+ int32: "int32",
265
+ rune: "int32",
266
+ int64: "int64",
267
+ int: "int",
268
+ uint64: "int64",
269
+ uint32: "int32",
270
+ float32: "float",
271
+ float64: "float",
272
+ bool: "bool",
273
+ "time.time": "time",
274
+ "time.duration": "int64",
275
+ "decimal.decimal": "decimal",
276
+ "json.rawmessage": "json",
277
+ byte: "bytes",
278
+ // Java. `bare` lowercases, so `Integer` and `Long` land here too.
279
+ long: "int64",
280
+ integer: "int32",
281
+ short: "int16",
282
+ double: "float",
283
+ boolean: "bool",
284
+ bigdecimal: "decimal",
285
+ instant: "time",
286
+ offsetdatetime: "time",
287
+ localdatetime: "time",
288
+ localdate: "time",
289
+ // TypeScript. `string`, `boolean` above; `number` is deliberately unknown.
290
+ date: "time",
291
+ };
292
+
293
+ const INTEGER_CLASSES: ReadonlySet<TypeClass> = new Set([
294
+ "int",
295
+ "int16",
296
+ "int32",
297
+ "int64",
298
+ ]);
299
+
300
+ export function dbClass(type: string): TypeClass {
301
+ return DB_CLASSES[bare(type)] ?? "unknown";
302
+ }
303
+
304
+ export function domainClass(type: string): TypeClass {
305
+ return DOMAIN_CLASSES[bare(type)] ?? "unknown";
306
+ }
307
+
308
+ /**
309
+ * True when a column and the field it maps to disagree about what they hold.
310
+ *
311
+ * A type either side does not recognise is NOT a mismatch. Half the domain
312
+ * types in a catalog are named shapes — Money, GatewayRef — and calling every
313
+ * one of them a disagreement with `numeric` would bury the two or three real
314
+ * ones under a page of amber dots.
315
+ */
316
+ export function typesDisagree(dbType: string, domainType: string): boolean {
317
+ const a = dbClass(dbType);
318
+ const b = domainClass(domainType);
319
+ if (a === "unknown" || b === "unknown") return false;
320
+ if (a === b) return false;
321
+ // A width-less integer fits any integer column.
322
+ return !(b === "int" && INTEGER_CLASSES.has(a));
323
+ }
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // The join: a column, the field it maps to, and whether they agree.
327
+ // ---------------------------------------------------------------------------
328
+
329
+ export interface StoredField {
330
+ owner: ColumnOwner;
331
+ /** The `maps` path as written, e.g. "Order.CustomerID". */
332
+ maps: string;
333
+ /** The field half of that path. */
334
+ path: string;
335
+ /** The domain field, when the block actually declares one by that name. */
336
+ field: Field | null;
337
+ /** Set when both types are recognised and they are not the same shape. */
338
+ mismatch: boolean;
339
+ }
340
+
341
+ /**
342
+ * Every column stored against a block, joined to the field it claims to carry.
343
+ * A `maps` naming a field the block does not declare resolves to `field: null`
344
+ * — the column is still shown, because a column pointing at a field that is no
345
+ * longer there is precisely what a reader needs to see.
346
+ */
347
+ export function storedFields(
348
+ catalog: Catalog,
349
+ index: CatalogIndex,
350
+ blockId: string,
351
+ ): StoredField[] {
352
+ const owner = index.blockById.get(blockId);
353
+ if (!owner) return [];
354
+ const fields = blockFields(catalog, owner.block);
355
+
356
+ return columnsOfBlock(index, blockId).map((columnOwner) => {
357
+ const maps = columnOwner.column.maps ?? "";
358
+ const path = mapsFieldPath(maps);
359
+ // One level only: "Total.Amount" is matched on "Total", the field the
360
+ // block itself declares, and the nested shape is read on its own page.
361
+ const head = path.split(".")[0] ?? "";
362
+ const field = fields.find((f) => f.name === head) ?? null;
363
+ return {
364
+ owner: columnOwner,
365
+ maps,
366
+ path,
367
+ field,
368
+ mismatch: field
369
+ ? typesDisagree(columnOwner.column.type, field.type)
370
+ : false,
371
+ };
372
+ });
373
+ }
374
+
375
+ /**
376
+ * Fields of an aggregate's blocks that no column maps to. This is the evidence
377
+ * behind "persistence drift": a table claiming to persist an aggregate whose
378
+ * fields it does not carry is either mid-migration or mis-labelled, and either
379
+ * way the claim is worth less than it looks.
380
+ */
381
+ export function unmappedFields(
382
+ catalog: Catalog,
383
+ index: CatalogIndex,
384
+ aggregate: Aggregate,
385
+ ): number {
386
+ let total = 0;
387
+ let mapped = 0;
388
+ for (const { block } of aggregateBlocks(aggregate)) {
389
+ const fields = blockFields(catalog, block);
390
+ total += fields.length;
391
+ const paths = new Set(
392
+ columnsOfBlock(index, block.id).map(
393
+ (o) => mapsFieldPath(o.column.maps ?? "").split(".")[0] ?? "",
394
+ ),
395
+ );
396
+ mapped += fields.filter((f) => paths.has(f.name)).length;
397
+ }
398
+ return total - mapped;
399
+ }