@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,50 @@
1
+ package main
2
+
3
+ import (
4
+ "reflect"
5
+ "testing"
6
+ )
7
+
8
+ // The note names the table it stands above, and only that one. A bare slug is
9
+ // an aggregate of the store's owner; a dotted name is somebody's full id.
10
+ func TestAggregateNoteBindsToTheNextCreateTable(t *testing.T) {
11
+ got := readProjected(`
12
+ -- Planned stops, rebuilt from RoutePlanned.
13
+ -- aggregate: shop.oms.order
14
+ CREATE TABLE route_stops (
15
+ route_id text NOT NULL
16
+ );
17
+
18
+ -- aggregate: price_list
19
+ CREATE TABLE IF NOT EXISTS board.prices (
20
+ id text PRIMARY KEY
21
+ );
22
+
23
+ CREATE TABLE seen (
24
+ id text PRIMARY KEY
25
+ );`, "shop.delivery")
26
+
27
+ want := map[string]string{
28
+ "route_stops": "shop.oms.order",
29
+ "prices": "shop.delivery.price-list",
30
+ }
31
+ if !reflect.DeepEqual(got, want) {
32
+ t.Errorf("projected = %v, want %v", got, want)
33
+ }
34
+ }
35
+
36
+ // A note that a statement gets in front of was not about the table after it,
37
+ // and a note nothing follows says nothing.
38
+ func TestAggregateNoteDoesNotReachPastAStatement(t *testing.T) {
39
+ got := readProjected(`
40
+ -- aggregate: shop.oms.order
41
+ CREATE INDEX route_stops_by_route ON route_stops (route_id);
42
+ CREATE TABLE route_stops_copy (
43
+ route_id text NOT NULL
44
+ );
45
+ -- aggregate: shop.oms.order`, "shop.delivery")
46
+
47
+ if len(got) != 0 {
48
+ t.Errorf("no table should carry the note, got %v", got)
49
+ }
50
+ }
@@ -0,0 +1,420 @@
1
+ package main
2
+
3
+ import (
4
+ "go/parser"
5
+ "go/token"
6
+ "os"
7
+ "path"
8
+ "path/filepath"
9
+ "regexp"
10
+ "sort"
11
+ "strings"
12
+
13
+ "github.com/shortlink-org/portolan/catalog"
14
+ "github.com/shortlink-org/portolan/plugin"
15
+ )
16
+
17
+ // readStore builds one store out of every migration in the infrastructure
18
+ // layer: the repository packages, and the projector packages beside them.
19
+ //
20
+ // One store, not one per aggregate: the migrations are numbered inside their
21
+ // own package and each keeps its own schema_migrations table, but they are all
22
+ // applied to the same database. The numbering is about who waits for whom, not
23
+ // about where the rows live.
24
+ func readStore(root string, layout storageLayout, storeID, owner string, b *plugin.Builder) ([]catalog.Table, []catalog.View) {
25
+ tables := []catalog.Table{}
26
+ views := []catalog.View{}
27
+
28
+ for _, repository := range layout.repositories {
29
+ aggregate := repository.name
30
+ dir := repository.migrations
31
+
32
+ state, copies, _, ok := readMigrations(root, dir, storeID, owner, b)
33
+ if !ok {
34
+ // A repository package with no migrations of its own is normal:
35
+ // not every adapter keeps rows.
36
+ continue
37
+ }
38
+
39
+ // The directory names the aggregate, and an id spells it the way every
40
+ // extractor spells one: price_list is price-list.
41
+ aggregateID := owner + "." + slug(aggregate)
42
+ // Which column carries which field, read from the statements that
43
+ // write the rows rather than from the column names.
44
+ mapped := readMaps(root, repository.dir, aggregate, b)
45
+ for _, more := range []map[string]map[string]string{
46
+ readMapsTS(root, repository.dir, aggregate, b),
47
+ readMapsRust(root, repository.dir, aggregate, b),
48
+ readMapsJava(root, repository.dir, aggregate, b),
49
+ } {
50
+ for table, columns := range more {
51
+ if _, ok := mapped[table]; !ok {
52
+ mapped[table] = columns
53
+ }
54
+ }
55
+ }
56
+
57
+ first := true
58
+ for _, relation := range state.relations {
59
+ table := finishTable(relation, state, storeID)
60
+ // An outbox holds messages on their way out, not the
61
+ // aggregate: it is created beside the aggregate because the
62
+ // repository writes both in one transaction, and that is all
63
+ // the layout says about it.
64
+ if isOutbox(lastNamePart(table.Name)) {
65
+ table.Role = catalog.TableRoleOutbox
66
+ tables = append(tables, table)
67
+
68
+ continue
69
+ }
70
+
71
+ // The layout is the claim: these rows exist because this
72
+ // aggregate exists, and its schema lives beside the code that
73
+ // reads it.
74
+ table.Persists = &catalog.Persists{Aggregate: aggregateID}
75
+
76
+ mapping := forTable(mapped, table.Name)
77
+ for i := range table.Columns {
78
+ if field, ok := mapping[table.Columns[i].Name]; ok {
79
+ table.Columns[i].Maps = field
80
+ }
81
+ }
82
+ applyCopies(&table, copies)
83
+
84
+ // The first table an aggregate creates holds the aggregate
85
+ // itself; anything it creates afterwards hangs off it.
86
+ if first {
87
+ table.Role = catalog.TableRoleAggregateRoot
88
+ first = false
89
+ } else {
90
+ table.Role = catalog.TableRoleChild
91
+ }
92
+
93
+ tables = append(tables, table)
94
+ }
95
+ for _, declared := range state.views {
96
+ views = append(views, declared.asCatalog(storeID, declared.source, tables))
97
+ }
98
+ }
99
+
100
+ // A projector package holds one projection: rows assembled from events,
101
+ // kept by a subscriber that writes and nothing else. The layout says the
102
+ // role. It does not say whose rows these are a picture of - a projection
103
+ // of another service's aggregate lives here too, and its name is not in
104
+ // any directory of this tree - so the link is taken from the migration
105
+ // when it is written there (`-- aggregate:`) and left out when it is not.
106
+ for _, projector := range layout.projectors {
107
+ dir := projector.migrations
108
+
109
+ state, copies, projected, ok := readMigrations(root, dir, storeID, owner, b)
110
+ if !ok {
111
+ continue
112
+ }
113
+
114
+ for _, relation := range state.relations {
115
+ table := finishTable(relation, state, storeID)
116
+ table.Role = catalog.TableRoleProjection
117
+ if aggregate := forTable(projected, table.Name); aggregate != "" {
118
+ table.Persists = &catalog.Persists{Aggregate: aggregate}
119
+ }
120
+ // No `maps`: the upsert in a projector writes what an event
121
+ // carries, and an event's field is not a field of the aggregate
122
+ // the way a repository's insert argument is. Where a value came
123
+ // from is said by `-- from:`, which is read.
124
+ applyCopies(&table, copies)
125
+
126
+ tables = append(tables, table)
127
+ }
128
+ for _, declared := range state.views {
129
+ views = append(views, declared.asCatalog(storeID, declared.source, tables))
130
+ }
131
+ }
132
+
133
+ // A view is read where it is written, which may be before the tables it
134
+ // reads are: the second pass is what lets a column take its type from the
135
+ // column it comes from wherever that was created.
136
+ for i := range views {
137
+ views[i] = resolveView(views[i], storeID, tables)
138
+ }
139
+
140
+ return tables, views
141
+ }
142
+
143
+ // readMigrations applies every up migration under dir, in name order, and
144
+ // reads the comments the grammar drops: which columns are copies (`-- from:`)
145
+ // and which aggregate a table is a picture of (`-- aggregate:`). ok is false
146
+ // when there is no such directory.
147
+ func readMigrations(root, dir, storeID, owner string, b *plugin.Builder) (*ddlState, map[string]map[string][]string, map[string]string, bool) {
148
+ files, err := os.ReadDir(filepath.Join(root, filepath.FromSlash(dir)))
149
+ if err != nil {
150
+ return nil, nil, nil, false
151
+ }
152
+
153
+ names := make([]string, 0, len(files))
154
+ for _, file := range files {
155
+ // Only the up direction describes the schema. A down migration
156
+ // says how to lose it; a migration with no direction in its name
157
+ // only goes up.
158
+ if !file.IsDir() && strings.HasSuffix(file.Name(), ".sql") && !strings.HasSuffix(file.Name(), ".down.sql") {
159
+ names = append(names, file.Name())
160
+ }
161
+ }
162
+ // Applied in name order, so read in name order: a later migration is
163
+ // allowed to know about an earlier one.
164
+ sort.Strings(names)
165
+
166
+ state := newDDLState()
167
+ copies := map[string]map[string][]string{}
168
+ projected := map[string]string{}
169
+
170
+ for _, name := range names {
171
+ source := path.Join(dir, name)
172
+
173
+ sql, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(source)))
174
+ if err != nil {
175
+ b.Warn(storeID, source+" could not be read: "+err.Error())
176
+
177
+ continue
178
+ }
179
+
180
+ for table, columns := range readCopies(string(sql), storeID) {
181
+ if copies[table] == nil {
182
+ copies[table] = map[string][]string{}
183
+ }
184
+ for column, from := range columns {
185
+ copies[table][column] = from
186
+ }
187
+ }
188
+ for table, aggregate := range readProjected(string(sql), owner) {
189
+ projected[table] = aggregate
190
+ }
191
+ unread, err := state.apply(string(sql), source)
192
+ if err != nil {
193
+ b.Warn(storeID, "could not parse "+source+": "+err.Error())
194
+
195
+ continue
196
+ }
197
+ for _, note := range unread {
198
+ b.Warn(storeID, source+": "+note)
199
+ }
200
+ }
201
+
202
+ return state, copies, projected, true
203
+ }
204
+
205
+ // finishTable gives a relation its place in the catalog: an id under the
206
+ // store, its indexes, and its column types spelled the way the migration
207
+ // spelled them.
208
+ func finishTable(relation relation, state *ddlState, storeID string) catalog.Table {
209
+ table := relation.table
210
+ table.ID = storeID + "." + table.Name
211
+ table.Indexes = relation.indexes
212
+ for i := range table.Columns {
213
+ table.Columns[i].Type = state.renderType(table.Columns[i].Type)
214
+ }
215
+
216
+ return table
217
+ }
218
+
219
+ // applyCopies writes onto each column where its value came from, when the
220
+ // migration says. A table cannot show a copy the way a view shows a select,
221
+ // so the copy is declared beside the column it lands in.
222
+ func applyCopies(table *catalog.Table, copies map[string]map[string][]string) {
223
+ copying := forTable(copies, table.Name)
224
+ for i := range table.Columns {
225
+ if from, ok := copying[table.Columns[i].Name]; ok {
226
+ table.Columns[i].From = from
227
+ }
228
+ }
229
+ }
230
+
231
+ // forTable looks a table up by the name the migration used, then by its
232
+ // unqualified name: a comment says `orders`, the grammar says `sales.orders`.
233
+ func forTable[T any](byName map[string]T, name string) T {
234
+ if held, ok := byName[name]; ok {
235
+ return held
236
+ }
237
+
238
+ return byName[lastNamePart(name)]
239
+ }
240
+
241
+ // resolveView fills in what a view could not know when it was read: the type,
242
+ // nullability and mapping of every column that comes from exactly one place.
243
+ func resolveView(held catalog.View, storeID string, tables []catalog.Table) catalog.View {
244
+ for i := range held.Columns {
245
+ column := &held.Columns[i]
246
+ if column.Type != "" || len(column.From) != 1 {
247
+ continue
248
+ }
249
+ at := strings.LastIndex(column.From[0], ".")
250
+ if at < 0 {
251
+ continue
252
+ }
253
+ if source := columnOf(tables, column.From[0][:at], column.From[0][at+1:]); source != nil {
254
+ column.Type = source.Type
255
+ column.Nullable = source.Nullable
256
+ column.Maps = source.Maps
257
+ }
258
+ }
259
+ if held.Persists == nil {
260
+ persists := ""
261
+ for _, read := range held.Reads {
262
+ table := tableOf(tables, read)
263
+ if table == nil || table.Persists == nil {
264
+ continue
265
+ }
266
+ if persists == "" {
267
+ persists = table.Persists.Aggregate
268
+
269
+ continue
270
+ }
271
+ if persists != table.Persists.Aggregate {
272
+ persists = ""
273
+
274
+ break
275
+ }
276
+ }
277
+ if persists != "" {
278
+ held.Persists = &catalog.Persists{Aggregate: persists}
279
+ }
280
+ }
281
+
282
+ return held
283
+ }
284
+
285
+ // isOutbox says whether a table is the outbox by its name, which is the one
286
+ // convention every outbox library shares.
287
+ func isOutbox(name string) bool {
288
+ return name == "outbox" || strings.HasSuffix(name, "_outbox")
289
+ }
290
+
291
+ // resolveForeignKeys turns the table names the grammar gives into table ids the
292
+ // catalog can follow. A key pointing outside this store keeps the raw name and
293
+ // is reported: it crosses a boundary this extractor cannot see the far side of.
294
+ func resolveForeignKeys(storeID string, tables []catalog.Table, b *plugin.Builder) {
295
+ known := map[string]bool{}
296
+ for i := range tables {
297
+ known[tables[i].Name] = true
298
+ }
299
+
300
+ for i := range tables {
301
+ for j := range tables[i].Columns {
302
+ fk := tables[i].Columns[j].FK
303
+ if fk == nil {
304
+ continue
305
+ }
306
+ if !known[fk.Table] {
307
+ b.Warn(tables[i].ID, "column "+tables[i].Columns[j].Name+" references "+fk.Table+", which no migration here creates")
308
+
309
+ continue
310
+ }
311
+ fk.Table = storeID + "." + fk.Table
312
+ }
313
+ }
314
+ }
315
+
316
+ // foreignSchemas reports migrations applied from outside this module.
317
+ //
318
+ // A service can bring a table with a dependency - an outbox published by an
319
+ // SDK, say - and its DDL is then in that module rather than in this tree. The
320
+ // table is real, the catalog cannot describe it, and silence would read as
321
+ // "there is no such table".
322
+ //
323
+ // What is checked is the IMPORT PATH, never the name a file refers to the
324
+ // package by. `userrepo.Migrations` and `sdkoutbox.Migrations` look equally
325
+ // foreign as identifiers, and only the path says that one of them is the
326
+ // repository package next door.
327
+ func foreignSchemas(root, module string, b *plugin.Builder, storeID string) {
328
+ if module == "" {
329
+ return
330
+ }
331
+
332
+ reported := map[string]bool{}
333
+ fset := token.NewFileSet()
334
+
335
+ _ = filepath.WalkDir(filepath.Join(root, "internal"), func(p string, entry os.DirEntry, err error) error {
336
+ if err != nil || entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
337
+ return nil
338
+ }
339
+ if strings.HasSuffix(entry.Name(), "_test.go") {
340
+ // A test standing up its own schema says nothing about what the
341
+ // service deploys.
342
+ return nil
343
+ }
344
+
345
+ file, err := parser.ParseFile(fset, p, nil, parser.ImportsOnly|parser.SkipObjectResolution)
346
+ if err != nil {
347
+ return nil
348
+ }
349
+
350
+ paths := map[string]string{}
351
+ for _, spec := range file.Imports {
352
+ importPath := strings.Trim(spec.Path.Value, `"`)
353
+ name := path.Base(importPath)
354
+ if spec.Name != nil {
355
+ name = spec.Name.Name
356
+ }
357
+ paths[name] = importPath
358
+ }
359
+
360
+ // Imports only got parsed above, so the bodies are gone; the file is
361
+ // read again for the one selector that matters.
362
+ source, err := os.ReadFile(p)
363
+ if err != nil {
364
+ return nil
365
+ }
366
+
367
+ for _, match := range migrationsRef.FindAllStringSubmatch(string(source), -1) {
368
+ importPath, known := paths[match[1]]
369
+ if !known || reported[importPath] {
370
+ continue
371
+ }
372
+ if strings.HasPrefix(importPath, module) {
373
+ continue
374
+ }
375
+
376
+ reported[importPath] = true
377
+ b.Warn(storeID, "migrations are applied from "+importPath+", whose schema is not in this tree; the tables it creates are missing from this store")
378
+ }
379
+
380
+ return nil
381
+ })
382
+ }
383
+
384
+ // migrationsRef finds `<pkg>.Migrations`, which is how a migration set is
385
+ // handed to the migrator. A regular expression rather than a walk of the
386
+ // bodies: this produces a diagnostic and never a fact, and the day it misses
387
+ // one nothing in the catalog is wrong - there is only one fewer warning.
388
+ var migrationsRef = regexp.MustCompile(`(\w+)\.Migrations\b`)
389
+
390
+ // modulePath is the module line of go.mod: what "inside this tree" means.
391
+ func modulePath(root string) string {
392
+ source, err := os.ReadFile(filepath.Join(root, "go.mod"))
393
+ if err != nil {
394
+ return ""
395
+ }
396
+
397
+ for _, line := range strings.Split(string(source), "\n") {
398
+ if rest, ok := strings.CutPrefix(strings.TrimSpace(line), "module "); ok {
399
+ return strings.TrimSpace(rest)
400
+ }
401
+ }
402
+
403
+ return ""
404
+ }
405
+
406
+ func subdirs(root, rel string) []string {
407
+ entries, err := os.ReadDir(filepath.Join(root, filepath.FromSlash(rel)))
408
+ if err != nil {
409
+ return nil
410
+ }
411
+
412
+ var out []string
413
+ for _, entry := range entries {
414
+ if entry.IsDir() {
415
+ out = append(out, entry.Name())
416
+ }
417
+ }
418
+
419
+ return out
420
+ }
@@ -0,0 +1,233 @@
1
+ package main
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+
10
+ "github.com/shortlink-org/portolan/catalog"
11
+ "github.com/shortlink-org/portolan/plugin"
12
+ )
13
+
14
+ func fragment(t *testing.T) (catalog.Catalog, []plugin.Warning) {
15
+ t.Helper()
16
+
17
+ resp := extract(
18
+ plugin.Input{Root: "../../examples/auth", Commit: "abc1234", GeneratedAt: "2026-01-01T00:00:00Z"},
19
+ Options{Context: "auth", Service: "auth", Store: "pg", Name: "Auth database"},
20
+ )
21
+ if len(resp.Files) != 1 {
22
+ t.Fatalf("expected one fragment, got %d files", len(resp.Files))
23
+ }
24
+
25
+ var out catalog.Catalog
26
+ if err := json.Unmarshal([]byte(resp.Files[0].Contents), &out); err != nil {
27
+ t.Fatal(err)
28
+ }
29
+
30
+ return out, resp.Warnings()
31
+ }
32
+
33
+ // One store, not one per aggregate: the migrations are numbered inside their
34
+ // own package but they are all applied to the same database.
35
+ func TestOneStoreHoldsEveryAggregatesTables(t *testing.T) {
36
+ cat, _ := fragment(t)
37
+
38
+ if len(cat.Stores) != 1 {
39
+ t.Fatalf("expected one store, got %d", len(cat.Stores))
40
+ }
41
+
42
+ store := cat.Stores[0]
43
+ if store.ID != "auth.auth.pg" || store.Owner != "auth.auth" {
44
+ t.Errorf("store = %s owned by %s", store.ID, store.Owner)
45
+ }
46
+
47
+ persists := map[string]string{}
48
+ for _, table := range store.Tables {
49
+ persists[table.Name] = table.Persists.Aggregate
50
+ }
51
+ if persists["users"] != "auth.auth.user" || persists["sessions"] != "auth.auth.session" || persists["lockouts"] != "auth.auth.lockout" {
52
+ t.Errorf("tables are linked to the wrong aggregates: %v", persists)
53
+ }
54
+ if store.Source != "../../examples/auth/internal/*/infrastructure/repository" {
55
+ t.Errorf("feature repository source = %q", store.Source)
56
+ }
57
+ }
58
+
59
+ // The service half of the fragment says only which store it touches. What it
60
+ // is called and what it holds belong to the extractors that read those.
61
+ func TestFragmentClaimsOnlyTheLink(t *testing.T) {
62
+ cat, _ := fragment(t)
63
+
64
+ service := cat.Contexts[0].Services[0]
65
+ if len(service.Stores) != 1 || service.Stores[0] != "auth.auth.pg" {
66
+ t.Errorf("stores = %v", service.Stores)
67
+ }
68
+ if service.Name != "" || service.Readme != "" || len(service.Aggregates) != 0 {
69
+ t.Errorf("the sql fragment should not describe the service: %+v", service)
70
+ }
71
+ }
72
+
73
+ // A table brought by a dependency is real, and its DDL is not here. Silence
74
+ // would read as "there is no such table".
75
+ func TestSchemaAppliedFromOutsideTheTreeIsReported(t *testing.T) {
76
+ _, diagnostics := fragment(t)
77
+
78
+ var found string
79
+ for _, d := range diagnostics {
80
+ if strings.Contains(d.Message, "not in this tree") {
81
+ found = d.Message
82
+ }
83
+ }
84
+
85
+ if !strings.Contains(found, "go-sdk/outbox") {
86
+ t.Errorf("the outbox schema should be reported by import path, got %q", found)
87
+ }
88
+ // The repository packages next door are imported under aliases that look
89
+ // just as foreign as the SDK's. Only the path tells them apart.
90
+ for _, d := range diagnostics {
91
+ if strings.Contains(d.Message, "repo") && strings.Contains(d.Message, "not in this tree") {
92
+ t.Errorf("a package from this module was called foreign: %q", d.Message)
93
+ }
94
+ }
95
+ }
96
+
97
+ // writeTree lays out a service under a temporary root: one file per path.
98
+ func writeTree(t *testing.T, files map[string]string) string {
99
+ t.Helper()
100
+
101
+ root := t.TempDir()
102
+ for rel, contents := range files {
103
+ full := filepath.Join(root, filepath.FromSlash(rel))
104
+ if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
105
+ t.Fatal(err)
106
+ }
107
+ if err := os.WriteFile(full, []byte(contents), 0o644); err != nil {
108
+ t.Fatal(err)
109
+ }
110
+ }
111
+
112
+ return root
113
+ }
114
+
115
+ // A projector package is the layout's word for a projection: every table it
116
+ // creates has that role. Whose rows it pictures is not in the layout, so it is
117
+ // taken from the migration when written there and left out when not - a
118
+ // projection of another service's aggregate has no directory here to be named
119
+ // by. The repository packages beside it are read exactly as before.
120
+ func TestProjectorPackagesAreProjections(t *testing.T) {
121
+ root := writeTree(t, map[string]string{
122
+ "internal/infrastructure/repository/route/migrations/0001_routes.sql": `
123
+ CREATE TABLE routes (
124
+ id text PRIMARY KEY,
125
+ version bigint NOT NULL
126
+ );
127
+ CREATE TABLE route_legs (
128
+ route_id text NOT NULL REFERENCES routes (id),
129
+ seq integer NOT NULL
130
+ );`,
131
+ "internal/infrastructure/projector/route_board/migrations/0001_route_board.sql": `
132
+ -- Stops with the address to print, rebuilt from OrderPlaced.
133
+ -- aggregate: shop.oms.order
134
+ CREATE TABLE route_board (
135
+ order_id text PRIMARY KEY,
136
+ -- from: shop.oms.pg.orders.ship_to
137
+ address text NOT NULL,
138
+ projected_at timestamptz NOT NULL
139
+ );
140
+
141
+ CREATE TABLE route_board_cursor (
142
+ topic text PRIMARY KEY,
143
+ -- from: routes.version
144
+ position bigint NOT NULL
145
+ );`,
146
+ })
147
+
148
+ resp := extract(plugin.Input{Root: root}, Options{Context: "shop", Service: "delivery"})
149
+ if len(resp.Files) != 1 {
150
+ t.Fatalf("expected one fragment, got %d files", len(resp.Files))
151
+ }
152
+ var cat catalog.Catalog
153
+ if err := json.Unmarshal([]byte(resp.Files[0].Contents), &cat); err != nil {
154
+ t.Fatal(err)
155
+ }
156
+ if len(cat.Stores) != 1 {
157
+ t.Fatalf("expected one store, got %d", len(cat.Stores))
158
+ }
159
+
160
+ byName := map[string]catalog.Table{}
161
+ for _, table := range cat.Stores[0].Tables {
162
+ byName[table.Name] = table
163
+ }
164
+ if len(byName) != 4 {
165
+ t.Fatalf("expected four tables, got %v", byName)
166
+ }
167
+
168
+ // The repository side is unchanged.
169
+ if byName["routes"].Role != catalog.TableRoleAggregateRoot || byName["routes"].Persists.Aggregate != "shop.delivery.route" {
170
+ t.Errorf("routes = %+v", byName["routes"])
171
+ }
172
+ if byName["route_legs"].Role != catalog.TableRoleChild || byName["route_legs"].Persists.Aggregate != "shop.delivery.route" {
173
+ t.Errorf("route_legs = %+v", byName["route_legs"])
174
+ }
175
+
176
+ board := byName["route_board"]
177
+ if board.ID != "shop.delivery.pg.route_board" || board.Role != catalog.TableRoleProjection {
178
+ t.Errorf("route_board = %+v", board)
179
+ }
180
+ if board.Persists == nil || board.Persists.Aggregate != "shop.oms.order" {
181
+ t.Errorf("the migration says whose picture this is: %+v", board.Persists)
182
+ }
183
+ for _, column := range board.Columns {
184
+ if column.Name != "address" {
185
+ continue
186
+ }
187
+ if len(column.From) != 1 || column.From[0] != "shop.oms.pg.orders.ship_to" {
188
+ t.Errorf("address.from = %v", column.From)
189
+ }
190
+ if column.Maps != "" {
191
+ t.Errorf("a projection column maps to no aggregate field, got %q", column.Maps)
192
+ }
193
+ }
194
+
195
+ cursor := byName["route_board_cursor"]
196
+ if cursor.Role != catalog.TableRoleProjection {
197
+ t.Errorf("every table a projector creates is a projection, got %q", cursor.Role)
198
+ }
199
+ if cursor.Persists != nil {
200
+ t.Errorf("no note, no link: %+v", cursor.Persists)
201
+ }
202
+ for _, column := range cursor.Columns {
203
+ if column.Name == "position" && (len(column.From) != 1 || column.From[0] != "shop.delivery.pg.routes.version") {
204
+ t.Errorf("a copy inside the store gets the store put back on, got %v", column.From)
205
+ }
206
+ }
207
+
208
+ for _, d := range resp.Warnings() {
209
+ if strings.Contains(d.Message, "no migrations") {
210
+ t.Errorf("the store has tables, yet: %q", d.Message)
211
+ }
212
+ }
213
+ }
214
+
215
+ // A service with projectors and no repositories still keeps state.
216
+ func TestAStoreOfOnlyProjectionsIsStillAStore(t *testing.T) {
217
+ root := writeTree(t, map[string]string{
218
+ "internal/infrastructure/projector/user_directory/migrations/0001_user_directory.sql": `
219
+ CREATE TABLE user_directory (
220
+ user_id text PRIMARY KEY,
221
+ email text NOT NULL
222
+ );`,
223
+ })
224
+
225
+ resp := extract(plugin.Input{Root: root}, Options{Context: "shop", Service: "bff"})
226
+ var cat catalog.Catalog
227
+ if err := json.Unmarshal([]byte(resp.Files[0].Contents), &cat); err != nil {
228
+ t.Fatal(err)
229
+ }
230
+ if len(cat.Stores) != 1 || len(cat.Stores[0].Tables) != 1 || cat.Stores[0].Tables[0].Role != catalog.TableRoleProjection {
231
+ t.Errorf("stores = %+v", cat.Stores)
232
+ }
233
+ }