@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.
- package/LICENSE +21 -0
- package/README.md +387 -0
- package/catalog/enum_test.go +46 -0
- package/catalog/model.go +932 -0
- package/catalog/roundtrip_test.go +185 -0
- package/catalog/via_test.go +38 -0
- package/cli/portolan.mjs +368 -0
- package/go.mod +14 -0
- package/go.sum +14 -0
- package/index.html +13 -0
- package/internal/commands/cargo.go +154 -0
- package/internal/commands/commands.go +143 -0
- package/internal/commands/commands_test.go +323 -0
- package/internal/commands/gradle.go +77 -0
- package/internal/commands/justfile.go +82 -0
- package/internal/commands/makefile.go +106 -0
- package/internal/commands/maven.go +93 -0
- package/internal/commands/packagejson.go +159 -0
- package/internal/commands/pyproject.go +169 -0
- package/internal/commands/taskfile.go +101 -0
- package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
- package/internal/commands/testdata/estate/Makefile +33 -0
- package/internal/commands/testdata/estate/Taskfile.yml +21 -0
- package/internal/commands/testdata/estate/build.gradle.kts +21 -0
- package/internal/commands/testdata/estate/justfile +20 -0
- package/internal/commands/testdata/estate/package.json +13 -0
- package/internal/commands/testdata/estate/pom.xml +29 -0
- package/internal/commands/testdata/estate/pyproject.toml +27 -0
- package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
- package/internal/commands/testdata/golden/commands.json +305 -0
- package/internal/gohttp/analyze.go +2500 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +143 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +36 -0
- package/internal/goscan/tree.go +155 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +62 -0
- package/internal/wsdl/parse.go +920 -0
- package/internal/wsdl/parse_test.go +133 -0
- package/package.json +117 -0
- package/plugin/describe.go +107 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +117 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +1160 -0
- package/plugins/extract-adr/describe.go +19 -0
- package/plugins/extract-adr/describe_test.go +11 -0
- package/plugins/extract-adr/extract.go +153 -0
- package/plugins/extract-adr/extract_test.go +350 -0
- package/plugins/extract-adr/history.go +99 -0
- package/plugins/extract-adr/main.go +65 -0
- package/plugins/extract-adr/options.schema.json +26 -0
- package/plugins/extract-adr/parse.go +642 -0
- package/plugins/extract-adr/parse_test.go +405 -0
- package/plugins/extract-asyncapi/describe.go +19 -0
- package/plugins/extract-asyncapi/describe_test.go +11 -0
- package/plugins/extract-asyncapi/extract.go +315 -0
- package/plugins/extract-asyncapi/extract_test.go +197 -0
- package/plugins/extract-asyncapi/main.go +48 -0
- package/plugins/extract-asyncapi/options.schema.json +26 -0
- package/plugins/extract-asyncapi/spec.go +151 -0
- package/plugins/extract-celery/README.md +169 -0
- package/plugins/extract-celery/extract.py +346 -0
- package/plugins/extract-celery/extract_test.py +192 -0
- package/plugins/extract-celery/main.py +66 -0
- package/plugins/extract-celery/options.py +31 -0
- package/plugins/extract-celery/options.schema.json +31 -0
- package/plugins/extract-celery/producers.py +156 -0
- package/plugins/extract-celery/routes_test.py +106 -0
- package/plugins/extract-commands/describe.go +19 -0
- package/plugins/extract-commands/describe_test.go +11 -0
- package/plugins/extract-commands/extract.go +79 -0
- package/plugins/extract-commands/extract_test.go +92 -0
- package/plugins/extract-commands/main.go +55 -0
- package/plugins/extract-commands/options.schema.json +20 -0
- package/plugins/extract-csr/avro.go +258 -0
- package/plugins/extract-csr/describe.go +19 -0
- package/plugins/extract-csr/describe_test.go +11 -0
- package/plugins/extract-csr/extract.go +338 -0
- package/plugins/extract-csr/extract_test.go +374 -0
- package/plugins/extract-csr/jsonschema.go +343 -0
- package/plugins/extract-csr/lock.go +25 -0
- package/plugins/extract-csr/main.go +84 -0
- package/plugins/extract-csr/options.schema.json +43 -0
- package/plugins/extract-csr/subject.go +102 -0
- package/plugins/extract-django/README.md +386 -0
- package/plugins/extract-django/apps.py +100 -0
- package/plugins/extract-django/auth.py +302 -0
- package/plugins/extract-django/auth_test.py +188 -0
- package/plugins/extract-django/clients.py +121 -0
- package/plugins/extract-django/contracts.py +458 -0
- package/plugins/extract-django/database.py +119 -0
- package/plugins/extract-django/domain.py +318 -0
- package/plugins/extract-django/events.py +86 -0
- package/plugins/extract-django/extract.py +601 -0
- package/plugins/extract-django/extract_test.py +397 -0
- package/plugins/extract-django/flows.py +1278 -0
- package/plugins/extract-django/flows_test.py +338 -0
- package/plugins/extract-django/ids.py +42 -0
- package/plugins/extract-django/ids_test.py +30 -0
- package/plugins/extract-django/lifecycle.py +222 -0
- package/plugins/extract-django/lifecycle_test.py +76 -0
- package/plugins/extract-django/main.py +66 -0
- package/plugins/extract-django/openapi.py +229 -0
- package/plugins/extract-django/openapi_test.py +60 -0
- package/plugins/extract-django/operations.py +96 -0
- package/plugins/extract-django/options.py +61 -0
- package/plugins/extract-django/options.schema.json +126 -0
- package/plugins/extract-django/routing.py +214 -0
- package/plugins/extract-django/routing_test.py +198 -0
- package/plugins/extract-django/serializers.py +558 -0
- package/plugins/extract-django/store.py +434 -0
- package/plugins/extract-django/transport.py +242 -0
- package/plugins/extract-flows/describe.go +19 -0
- package/plugins/extract-flows/describe_test.go +11 -0
- package/plugins/extract-flows/extract.go +91 -0
- package/plugins/extract-flows/main.go +45 -0
- package/plugins/extract-flows/options.schema.json +16 -0
- package/plugins/extract-flows/parse.go +593 -0
- package/plugins/extract-flows/parse_test.go +204 -0
- package/plugins/extract-glossary/describe.go +19 -0
- package/plugins/extract-glossary/describe_test.go +11 -0
- package/plugins/extract-glossary/extract.go +115 -0
- package/plugins/extract-glossary/extract_test.go +220 -0
- package/plugins/extract-glossary/main.go +59 -0
- package/plugins/extract-glossary/options.schema.json +21 -0
- package/plugins/extract-glossary/parse.go +214 -0
- package/plugins/extract-glossary/parse_test.go +203 -0
- package/plugins/extract-go/aggregate.go +214 -0
- package/plugins/extract-go/client.go +409 -0
- package/plugins/extract-go/client_test.go +266 -0
- package/plugins/extract-go/describe.go +19 -0
- package/plugins/extract-go/describe_test.go +11 -0
- package/plugins/extract-go/enum.go +195 -0
- package/plugins/extract-go/enum_test.go +82 -0
- package/plugins/extract-go/event.go +99 -0
- package/plugins/extract-go/extract.go +191 -0
- package/plugins/extract-go/extract_test.go +261 -0
- package/plugins/extract-go/flow.go +1441 -0
- package/plugins/extract-go/flow_test.go +609 -0
- package/plugins/extract-go/httpclient.go +174 -0
- package/plugins/extract-go/httpclient_test.go +296 -0
- package/plugins/extract-go/ids.go +92 -0
- package/plugins/extract-go/layout.go +225 -0
- package/plugins/extract-go/layout_test.go +125 -0
- package/plugins/extract-go/lifecycle.go +324 -0
- package/plugins/extract-go/lifecycle_test.go +108 -0
- package/plugins/extract-go/main.go +79 -0
- package/plugins/extract-go/operation.go +157 -0
- package/plugins/extract-go/options.schema.json +66 -0
- package/plugins/extract-go/source.go +316 -0
- package/plugins/extract-go/transport.go +321 -0
- package/plugins/extract-go/transport_test.go +145 -0
- package/plugins/extract-go/wiring.go +434 -0
- package/plugins/extract-go-nats/describe.go +19 -0
- package/plugins/extract-go-nats/describe_test.go +11 -0
- package/plugins/extract-go-nats/extract.go +177 -0
- package/plugins/extract-go-nats/extract_test.go +389 -0
- package/plugins/extract-go-nats/index.go +379 -0
- package/plugins/extract-go-nats/main.go +42 -0
- package/plugins/extract-go-nats/options.schema.json +21 -0
- package/plugins/extract-go-nats/resolve.go +161 -0
- package/plugins/extract-go-nats/sites.go +224 -0
- package/plugins/extract-graphql/describe.go +19 -0
- package/plugins/extract-graphql/describe_test.go +11 -0
- package/plugins/extract-graphql/extract.go +433 -0
- package/plugins/extract-graphql/extract_test.go +256 -0
- package/plugins/extract-graphql/ids.go +49 -0
- package/plugins/extract-graphql/lex.go +237 -0
- package/plugins/extract-graphql/main.go +51 -0
- package/plugins/extract-graphql/options.schema.json +31 -0
- package/plugins/extract-graphql/parse.go +621 -0
- package/plugins/extract-graphql/parse_test.go +122 -0
- package/plugins/extract-http-clients/describe.go +19 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +705 -0
- package/plugins/extract-http-clients/extract_test.go +1263 -0
- package/plugins/extract-http-clients/main.go +42 -0
- package/plugins/extract-http-clients/options.schema.json +33 -0
- package/plugins/extract-java/README.md +190 -0
- package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
- package/plugins/extract-java/options.schema.json +70 -0
- package/plugins/extract-openapi/describe.go +19 -0
- package/plugins/extract-openapi/describe_test.go +11 -0
- package/plugins/extract-openapi/discover.go +243 -0
- package/plugins/extract-openapi/extract.go +526 -0
- package/plugins/extract-openapi/extract_test.go +545 -0
- package/plugins/extract-openapi/main.go +75 -0
- package/plugins/extract-openapi/options.schema.json +62 -0
- package/plugins/extract-openapi/spec.go +350 -0
- package/plugins/extract-project/README.md +22 -0
- package/plugins/extract-project/describe.go +19 -0
- package/plugins/extract-project/describe_test.go +11 -0
- package/plugins/extract-project/extract.go +221 -0
- package/plugins/extract-project/extract_test.go +109 -0
- package/plugins/extract-project/main.go +41 -0
- package/plugins/extract-project/options.schema.json +48 -0
- package/plugins/extract-proto/ast.go +125 -0
- package/plugins/extract-proto/consumes.go +77 -0
- package/plugins/extract-proto/describe.go +19 -0
- package/plugins/extract-proto/extract.go +293 -0
- package/plugins/extract-proto/extract_test.go +459 -0
- package/plugins/extract-proto/ids.go +89 -0
- package/plugins/extract-proto/ids_test.go +57 -0
- package/plugins/extract-proto/lex.go +285 -0
- package/plugins/extract-proto/main.go +100 -0
- package/plugins/extract-proto/module.go +120 -0
- package/plugins/extract-proto/options.schema.json +51 -0
- package/plugins/extract-proto/parse.go +720 -0
- package/plugins/extract-proto/parse_test.go +307 -0
- package/plugins/extract-proto/provides.go +236 -0
- package/plugins/extract-proto/resolve.go +222 -0
- package/plugins/extract-proto/resolve_test.go +119 -0
- package/plugins/extract-python-kafka/README.md +36 -0
- package/plugins/extract-python-kafka/extract.py +211 -0
- package/plugins/extract-python-kafka/extract_test.py +68 -0
- package/plugins/extract-python-kafka/main.py +59 -0
- package/plugins/extract-python-kafka/options.py +29 -0
- package/plugins/extract-python-kafka/options.schema.json +31 -0
- package/plugins/extract-redis/README.md +11 -0
- package/plugins/extract-redis/describe.go +19 -0
- package/plugins/extract-redis/describe_test.go +11 -0
- package/plugins/extract-redis/extract.go +183 -0
- package/plugins/extract-redis/extract_test.go +168 -0
- package/plugins/extract-redis/keyspaces.go +469 -0
- package/plugins/extract-redis/main.go +44 -0
- package/plugins/extract-redis/options.schema.json +30 -0
- package/plugins/extract-river/describe.go +19 -0
- package/plugins/extract-river/describe_test.go +11 -0
- package/plugins/extract-river/extract.go +507 -0
- package/plugins/extract-river/extract_test.go +132 -0
- package/plugins/extract-river/main.go +42 -0
- package/plugins/extract-river/options.schema.json +21 -0
- package/plugins/extract-rust/Cargo.lock +133 -0
- package/plugins/extract-rust/Cargo.toml +23 -0
- package/plugins/extract-rust/README.md +204 -0
- package/plugins/extract-rust/options.schema.json +65 -0
- package/plugins/extract-rust/rustfmt.toml +1 -0
- package/plugins/extract-sql/ddl.go +893 -0
- package/plugins/extract-sql/ddl_test.go +401 -0
- package/plugins/extract-sql/describe.go +19 -0
- package/plugins/extract-sql/describe_test.go +11 -0
- package/plugins/extract-sql/layout.go +221 -0
- package/plugins/extract-sql/layout_test.go +55 -0
- package/plugins/extract-sql/lineage.go +107 -0
- package/plugins/extract-sql/main.go +141 -0
- package/plugins/extract-sql/maps.go +564 -0
- package/plugins/extract-sql/maps_java.go +117 -0
- package/plugins/extract-sql/maps_rust.go +333 -0
- package/plugins/extract-sql/maps_rust_test.go +70 -0
- package/plugins/extract-sql/maps_test.go +204 -0
- package/plugins/extract-sql/maps_ts.go +398 -0
- package/plugins/extract-sql/maps_ts_test.go +136 -0
- package/plugins/extract-sql/options.schema.json +53 -0
- package/plugins/extract-sql/projection.go +70 -0
- package/plugins/extract-sql/projection_test.go +50 -0
- package/plugins/extract-sql/store.go +420 -0
- package/plugins/extract-sql/store_test.go +233 -0
- package/plugins/extract-sql/view.go +295 -0
- package/plugins/extract-ts/README.md +306 -0
- package/plugins/extract-ts/ast.test.ts +178 -0
- package/plugins/extract-ts/ast.ts +648 -0
- package/plugins/extract-ts/clients.ts +185 -0
- package/plugins/extract-ts/domain.ts +164 -0
- package/plugins/extract-ts/extract.test.ts +208 -0
- package/plugins/extract-ts/extract.ts +200 -0
- package/plugins/extract-ts/flows.ts +783 -0
- package/plugins/extract-ts/graphql.test.ts +73 -0
- package/plugins/extract-ts/graphql.ts +181 -0
- package/plugins/extract-ts/ids.test.ts +21 -0
- package/plugins/extract-ts/ids.ts +52 -0
- package/plugins/extract-ts/lifecycle.ts +138 -0
- package/plugins/extract-ts/main.ts +48 -0
- package/plugins/extract-ts/openapi.test.ts +64 -0
- package/plugins/extract-ts/openapi.ts +99 -0
- package/plugins/extract-ts/operations.ts +93 -0
- package/plugins/extract-ts/options.schema.json +96 -0
- package/plugins/extract-ts/source.test.ts +174 -0
- package/plugins/extract-ts/source.ts +465 -0
- package/plugins/extract-ts/transport.ts +147 -0
- package/plugins/extract-ts/tsconfig.json +13 -0
- package/plugins/extract-ts/wiring.ts +190 -0
- package/plugins/extract-watermill/describe.go +19 -0
- package/plugins/extract-watermill/describe_test.go +11 -0
- package/plugins/extract-watermill/extract.go +1228 -0
- package/plugins/extract-watermill/extract_test.go +234 -0
- package/plugins/extract-watermill/main.go +41 -0
- package/plugins/extract-watermill/options.schema.json +21 -0
- package/plugins/extract-wsdl/describe.go +19 -0
- package/plugins/extract-wsdl/describe_test.go +11 -0
- package/plugins/extract-wsdl/extract.go +148 -0
- package/plugins/extract-wsdl/extract_test.go +45 -0
- package/plugins/extract-wsdl/main.go +53 -0
- package/plugins/extract-wsdl/options.schema.json +59 -0
- package/plugins/fetch-bsr/auth.go +114 -0
- package/plugins/fetch-bsr/bsr.go +240 -0
- package/plugins/fetch-bsr/cache.go +66 -0
- package/plugins/fetch-bsr/describe.go +19 -0
- package/plugins/fetch-bsr/fetch.go +192 -0
- package/plugins/fetch-bsr/fetch_test.go +465 -0
- package/plugins/fetch-bsr/lock.go +71 -0
- package/plugins/fetch-bsr/main.go +117 -0
- package/plugins/fetch-bsr/options.schema.json +42 -0
- package/plugins/fetch-csr/auth.go +95 -0
- package/plugins/fetch-csr/cache.go +69 -0
- package/plugins/fetch-csr/describe.go +19 -0
- package/plugins/fetch-csr/describe_test.go +11 -0
- package/plugins/fetch-csr/fetch.go +254 -0
- package/plugins/fetch-csr/fetch_test.go +494 -0
- package/plugins/fetch-csr/lock.go +95 -0
- package/plugins/fetch-csr/main.go +122 -0
- package/plugins/fetch-csr/options.schema.json +42 -0
- package/plugins/fetch-csr/registry.go +209 -0
- package/plugins/fetch-git/cache.go +65 -0
- package/plugins/fetch-git/describe.go +19 -0
- package/plugins/fetch-git/describe_test.go +11 -0
- package/plugins/fetch-git/fetch.go +181 -0
- package/plugins/fetch-git/fetch_test.go +332 -0
- package/plugins/fetch-git/git.go +169 -0
- package/plugins/fetch-git/lock.go +72 -0
- package/plugins/fetch-git/main.go +127 -0
- package/plugins/fetch-git/offline.go +39 -0
- package/plugins/fetch-git/options.schema.json +42 -0
- package/plugins/fetch-git/pin.go +90 -0
- package/plugins/fetch-git/pin_test.go +74 -0
- package/plugins/gen-backstage/describe.go +17 -0
- package/plugins/gen-backstage/main.go +22 -0
- package/plugins/gen-backstage/options.schema.json +30 -0
- package/plugins/gen-backstage/plugin.go +473 -0
- package/plugins/gen-backstage/plugin_test.go +145 -0
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +162 -0
- package/plugins/gen-markdown/aggregate.go +362 -0
- package/plugins/gen-markdown/canonical.go +93 -0
- package/plugins/gen-markdown/context.go +90 -0
- package/plugins/gen-markdown/coverage_test.go +89 -0
- package/plugins/gen-markdown/describe.go +19 -0
- package/plugins/gen-markdown/describe_test.go +11 -0
- package/plugins/gen-markdown/external.go +71 -0
- package/plugins/gen-markdown/flow.go +277 -0
- package/plugins/gen-markdown/glossary.go +76 -0
- package/plugins/gen-markdown/glossary_test.go +148 -0
- package/plugins/gen-markdown/llms.go +302 -0
- package/plugins/gen-markdown/main.go +22 -0
- package/plugins/gen-markdown/markdown.go +254 -0
- package/plugins/gen-markdown/markdown_test.go +100 -0
- package/plugins/gen-markdown/module.go +107 -0
- package/plugins/gen-markdown/options.schema.json +16 -0
- package/plugins/gen-markdown/plugin.go +39 -0
- package/plugins/gen-markdown/quality_test.go +176 -0
- package/plugins/gen-markdown/redis_test.go +38 -0
- package/plugins/gen-markdown/render.go +344 -0
- package/plugins/gen-markdown/render_test.go +186 -0
- package/plugins/gen-markdown/service.go +456 -0
- package/plugins/gen-markdown/source.go +177 -0
- package/plugins/gen-markdown/store.go +201 -0
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +17 -0
- package/plugins/gen-mermaid/main.go +22 -0
- package/plugins/gen-mermaid/options.schema.json +10 -0
- package/plugins/gen-mermaid/plugin.go +104 -0
- package/plugins/gen-mermaid/plugin_test.go +33 -0
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/pyplugin/catalog.py +209 -0
- package/plugins/pyplugin/celery_conf.py +436 -0
- package/plugins/pyplugin/celery_tasks.py +76 -0
- package/plugins/pyplugin/kafka.py +570 -0
- package/plugins/pyplugin/names.py +38 -0
- package/plugins/pyplugin/protocol.py +79 -0
- package/plugins/pyplugin/source.py +285 -0
- package/plugins/verify-codeowners/describe.go +19 -0
- package/plugins/verify-codeowners/describe_test.go +11 -0
- package/plugins/verify-codeowners/main.go +75 -0
- package/plugins/verify-codeowners/match.go +85 -0
- package/plugins/verify-codeowners/match_test.go +47 -0
- package/plugins/verify-codeowners/options.schema.json +15 -0
- package/plugins/verify-codeowners/owners.go +164 -0
- package/plugins/verify-codeowners/owners_test.go +225 -0
- package/plugins/verify-codeowners/parse.go +90 -0
- package/plugins/verify-codeowners/parse_test.go +62 -0
- package/plugins/verify-otel/describe.go +19 -0
- package/plugins/verify-otel/describe_test.go +11 -0
- package/plugins/verify-otel/main.go +53 -0
- package/plugins/verify-otel/match.go +336 -0
- package/plugins/verify-otel/options.schema.json +30 -0
- package/plugins/verify-otel/otlp.go +200 -0
- package/plugins/verify-otel/verify.go +734 -0
- package/plugins/verify-otel/verify_test.go +460 -0
- package/portolan.json +1073 -0
- package/public/favicon.svg +12 -0
- package/schema/portolan.schema.json +2558 -0
- package/scripts/README.md +53 -0
- package/scripts/build-report.mjs +60 -0
- package/scripts/build-report.test.mjs +44 -0
- package/scripts/builtin-plugins.mjs +49 -0
- package/scripts/catalog-sources.mjs +67 -0
- package/scripts/diff.mjs +315 -0
- package/scripts/diff.test.mjs +151 -0
- package/scripts/forge-comment.mjs +144 -0
- package/scripts/forge-comment.test.mjs +151 -0
- package/scripts/forge-release.mjs +138 -0
- package/scripts/forge-release.test.mjs +106 -0
- package/scripts/forge.mjs +61 -0
- package/scripts/gen-likec4.mjs +854 -0
- package/scripts/gen-likec4.test.mjs +258 -0
- package/scripts/gen.mjs +603 -0
- package/scripts/generated-docs.test.mjs +66 -0
- package/scripts/local-api.mjs +1192 -0
- package/scripts/local-api.test.mjs +387 -0
- package/scripts/manifest.mjs +214 -0
- package/scripts/manifest.test.mjs +148 -0
- package/scripts/output-path.mjs +79 -0
- package/scripts/output-path.test.mjs +45 -0
- package/scripts/package-smoke.mjs +37 -0
- package/scripts/plugin-host.mjs +419 -0
- package/scripts/plugin-host.test.mjs +109 -0
- package/scripts/plugin-wasm-worker.mjs +34 -0
- package/scripts/run-builtin.mjs +81 -0
- package/scripts/schema.mjs +357 -0
- package/scripts/site-docs.mjs +295 -0
- package/scripts/site-docs.test.mjs +181 -0
- package/scripts/vendor-lock.mjs +58 -0
- package/scripts/vendor-lock.test.mjs +69 -0
- package/src/README.md +38 -0
- package/src/app/App.tsx +448 -0
- package/src/app/BranchPicker.test.ts +46 -0
- package/src/app/BranchPicker.tsx +153 -0
- package/src/app/Breadcrumbs.test.ts +38 -0
- package/src/app/Breadcrumbs.tsx +223 -0
- package/src/app/BuildStamp.tsx +146 -0
- package/src/app/CommandPalette.tsx +292 -0
- package/src/app/HashScroll.tsx +45 -0
- package/src/app/Sidebar.tsx +1742 -0
- package/src/app/TopBar.tsx +393 -0
- package/src/app/density.tsx +64 -0
- package/src/app/forge-access.test.ts +27 -0
- package/src/app/forge-access.tsx +80 -0
- package/src/app/panels.tsx +141 -0
- package/src/app/pins.tsx +242 -0
- package/src/app/query-client.ts +24 -0
- package/src/app/responsive.ts +55 -0
- package/src/app/shortcuts.test.ts +39 -0
- package/src/app/shortcuts.tsx +282 -0
- package/src/app/theme.tsx +51 -0
- package/src/app/title.test.ts +14 -0
- package/src/app/title.ts +30 -0
- package/src/app/toast.tsx +59 -0
- package/src/app/tree-keys.test.ts +80 -0
- package/src/app/tree-keys.ts +87 -0
- package/src/app/ui-store.ts +52 -0
- package/src/catalog-error.test.ts +105 -0
- package/src/catalog-profile.test.ts +59 -0
- package/src/catalog-profile.ts +101 -0
- package/src/catalog-stores.test.ts +431 -0
- package/src/catalog.test.ts +1215 -0
- package/src/catalog.ts +3125 -0
- package/src/chat/Cards.tsx +408 -0
- package/src/chat/ChatPanel.tsx +79 -0
- package/src/chat/ChatSettings.tsx +219 -0
- package/src/chat/Composer.tsx +99 -0
- package/src/chat/Conversation.tsx +281 -0
- package/src/chat/Header.tsx +46 -0
- package/src/chat/Message.tsx +49 -0
- package/src/chat/Notice.tsx +195 -0
- package/src/chat/Starter.tsx +67 -0
- package/src/chat/Steps.tsx +45 -0
- package/src/chat/Waiting.tsx +87 -0
- package/src/chat/flags.test.ts +59 -0
- package/src/chat/flags.ts +155 -0
- package/src/chat/linkify.test.ts +30 -0
- package/src/chat/linkify.ts +45 -0
- package/src/chat/prefs.ts +56 -0
- package/src/chat/prompt.test.ts +43 -0
- package/src/chat/prompt.ts +157 -0
- package/src/chat/store.ts +35 -0
- package/src/chat/tools.ts +58 -0
- package/src/chat/transport.ts +70 -0
- package/src/components/AdrRow.tsx +29 -0
- package/src/components/AnchorLink.tsx +67 -0
- package/src/components/ApiReference.tsx +134 -0
- package/src/components/AsyncApiReference.tsx +120 -0
- package/src/components/CatalogStamp.tsx +155 -0
- package/src/components/ChannelRows.tsx +132 -0
- package/src/components/CommandRows.tsx +139 -0
- package/src/components/DiagramSkeleton.tsx +71 -0
- package/src/components/EditorLink.tsx +85 -0
- package/src/components/FieldTree.tsx +305 -0
- package/src/components/FlowTrigger.tsx +34 -0
- package/src/components/Ident.tsx +89 -0
- package/src/components/Integrations.tsx +243 -0
- package/src/components/LifecycleDiagram.tsx +110 -0
- package/src/components/MachineDocs.tsx +42 -0
- package/src/components/Markdown.tsx +173 -0
- package/src/components/MarkdownTable.tsx +206 -0
- package/src/components/Mermaid.tsx +211 -0
- package/src/components/MethodRows.tsx +309 -0
- package/src/components/Overlay.tsx +125 -0
- package/src/components/PageHeader.tsx +189 -0
- package/src/components/RowActions.tsx +97 -0
- package/src/components/SchemaDocument.tsx +89 -0
- package/src/components/Select.tsx +126 -0
- package/src/components/ShapeRows.tsx +118 -0
- package/src/components/SourceDoc.tsx +82 -0
- package/src/components/SourcePreview.tsx +441 -0
- package/src/components/TabRow.tsx +182 -0
- package/src/components/TechIcon.tsx +34 -0
- package/src/components/Toc.tsx +132 -0
- package/src/components/WhatLinksHere.tsx +273 -0
- package/src/components/WsdlReference.tsx +84 -0
- package/src/components/ddd-icons.tsx +107 -0
- package/src/components/kind.tsx +157 -0
- package/src/components/logo.tsx +61 -0
- package/src/components/primitives.tsx +264 -0
- package/src/data.test.ts +18 -0
- package/src/data.ts +202 -0
- package/src/enrich.test.ts +1150 -0
- package/src/enrich.ts +672 -0
- package/src/er/ErCanvas.tsx +533 -0
- package/src/er/RedisSchema.tsx +74 -0
- package/src/er/RelationCard.tsx +267 -0
- package/src/er/StoreHeader.tsx +155 -0
- package/src/er/TableNode.tsx +84 -0
- package/src/er/ViewNode.tsx +83 -0
- package/src/er/layout.test.ts +115 -0
- package/src/er/layout.ts +77 -0
- package/src/er/lineage.test.ts +140 -0
- package/src/er/lineage.ts +107 -0
- package/src/er/markers.tsx +79 -0
- package/src/er/spec.test.ts +344 -0
- package/src/er/spec.ts +426 -0
- package/src/flow/ChainList.tsx +162 -0
- package/src/flow/FlowTable.tsx +123 -0
- package/src/flow/FlowToolbar.tsx +372 -0
- package/src/flow/StepDetail.tsx +380 -0
- package/src/flow/StepRail.tsx +428 -0
- package/src/flow/answers.test.ts +50 -0
- package/src/flow/answers.ts +58 -0
- package/src/flow/chain.test.ts +383 -0
- package/src/flow/chain.ts +353 -0
- package/src/flow/chapters.test.ts +185 -0
- package/src/flow/chapters.ts +225 -0
- package/src/flow/continues.test.ts +102 -0
- package/src/flow/continues.ts +65 -0
- package/src/flow/cross-context.test.ts +97 -0
- package/src/flow/cross-context.ts +35 -0
- package/src/flow/mermaid.test.ts +22 -0
- package/src/flow/mermaid.ts +141 -0
- package/src/flow/outline.test.ts +212 -0
- package/src/flow/outline.ts +170 -0
- package/src/flow/paths.test.ts +129 -0
- package/src/flow/paths.ts +147 -0
- package/src/flow/prefs.test.ts +80 -0
- package/src/flow/prefs.ts +104 -0
- package/src/graph/DependencyGraph.tsx +283 -0
- package/src/graph/DependencyNodes.tsx +162 -0
- package/src/graph/EdgeLabel.tsx +61 -0
- package/src/graph/FocusedEventGraph.tsx +211 -0
- package/src/graph/GraphToolbar.tsx +187 -0
- package/src/graph/RoutedEdge.tsx +80 -0
- package/src/graph/dependency-layout.test.ts +360 -0
- package/src/graph/dependency-layout.ts +444 -0
- package/src/graph/elk.test.ts +70 -0
- package/src/graph/elk.ts +287 -0
- package/src/graph/nodes.tsx +97 -0
- package/src/graph/theme.ts +81 -0
- package/src/graph/useElkFlow.ts +125 -0
- package/src/index.css +1547 -0
- package/src/language/InLanguage.tsx +40 -0
- package/src/language/TermCard.tsx +132 -0
- package/src/language/cards.test.ts +114 -0
- package/src/language/cards.ts +111 -0
- package/src/lib/admonition.test.ts +49 -0
- package/src/lib/admonition.ts +48 -0
- package/src/lib/adr.test.ts +131 -0
- package/src/lib/adr.ts +68 -0
- package/src/lib/api.test.ts +122 -0
- package/src/lib/api.ts +135 -0
- package/src/lib/backlinks.test.ts +330 -0
- package/src/lib/backlinks.ts +567 -0
- package/src/lib/branch-compare.ts +16 -0
- package/src/lib/build-info.test.ts +76 -0
- package/src/lib/build-info.ts +80 -0
- package/src/lib/catalog-diff.test.ts +275 -0
- package/src/lib/catalog-diff.ts +655 -0
- package/src/lib/clipboard.ts +43 -0
- package/src/lib/comparison-memory.test.ts +40 -0
- package/src/lib/comparison-memory.ts +39 -0
- package/src/lib/context-color.ts +27 -0
- package/src/lib/context-map.test.ts +148 -0
- package/src/lib/context-map.ts +399 -0
- package/src/lib/data-model.test.ts +245 -0
- package/src/lib/data-model.ts +399 -0
- package/src/lib/data-problems.test.ts +178 -0
- package/src/lib/data-problems.ts +314 -0
- package/src/lib/derive.test.ts +251 -0
- package/src/lib/derive.ts +515 -0
- package/src/lib/editor-link.test.ts +125 -0
- package/src/lib/editor-link.ts +96 -0
- package/src/lib/editor-prefs.ts +27 -0
- package/src/lib/event-graph.test.ts +237 -0
- package/src/lib/event-graph.ts +327 -0
- package/src/lib/export-canvas.ts +37 -0
- package/src/lib/export-file.test.ts +30 -0
- package/src/lib/export-file.ts +20 -0
- package/src/lib/flow-tree.test.ts +177 -0
- package/src/lib/flow-tree.ts +160 -0
- package/src/lib/forge-refs.test.ts +50 -0
- package/src/lib/forge-refs.ts +60 -0
- package/src/lib/format.test.ts +35 -0
- package/src/lib/format.ts +64 -0
- package/src/lib/github-catalog.test.ts +301 -0
- package/src/lib/github-catalog.ts +610 -0
- package/src/lib/integrations.test.ts +119 -0
- package/src/lib/integrations.ts +133 -0
- package/src/lib/kinds.test.ts +91 -0
- package/src/lib/kinds.ts +204 -0
- package/src/lib/lifecycle.test.ts +93 -0
- package/src/lib/lifecycle.ts +165 -0
- package/src/lib/local-api.ts +194 -0
- package/src/lib/motion-features.ts +11 -0
- package/src/lib/motion.test.ts +120 -0
- package/src/lib/motion.tsx +269 -0
- package/src/lib/palette-recent.test.ts +80 -0
- package/src/lib/palette-recent.ts +90 -0
- package/src/lib/palette.test.ts +365 -0
- package/src/lib/palette.ts +593 -0
- package/src/lib/pins.test.ts +139 -0
- package/src/lib/pins.ts +130 -0
- package/src/lib/problems.test.ts +112 -0
- package/src/lib/project-preview.ts +5 -0
- package/src/lib/proto-problems.test.ts +121 -0
- package/src/lib/proto-problems.ts +65 -0
- package/src/lib/queries.test.ts +113 -0
- package/src/lib/queries.ts +131 -0
- package/src/lib/registry.test.ts +219 -0
- package/src/lib/registry.ts +208 -0
- package/src/lib/scenarios.ts +547 -0
- package/src/lib/setup-info.test.ts +199 -0
- package/src/lib/setup-info.ts +415 -0
- package/src/lib/shape.test.ts +289 -0
- package/src/lib/shape.ts +454 -0
- package/src/lib/sidebar-prefs.test.ts +32 -0
- package/src/lib/sidebar-prefs.ts +75 -0
- package/src/lib/source-code.test.ts +159 -0
- package/src/lib/source-code.ts +286 -0
- package/src/lib/source-doc.test.ts +131 -0
- package/src/lib/source-doc.ts +103 -0
- package/src/lib/source-highlight.test.ts +29 -0
- package/src/lib/source-highlight.ts +24 -0
- package/src/lib/source-link.test.ts +153 -0
- package/src/lib/source-link.ts +201 -0
- package/src/lib/spec-files.test.ts +24 -0
- package/src/lib/spec-files.ts +23 -0
- package/src/lib/table-directive.test.ts +139 -0
- package/src/lib/table-directive.ts +106 -0
- package/src/lib/tech.test.ts +120 -0
- package/src/lib/tech.ts +421 -0
- package/src/lib/terms.test.ts +67 -0
- package/src/lib/terms.ts +179 -0
- package/src/lib/wire-problems.test.ts +263 -0
- package/src/lib/wire-problems.ts +281 -0
- package/src/likec4/C4View.tsx +55 -0
- package/src/likec4/CanvasBridge.tsx +187 -0
- package/src/likec4/FlowView.tsx +235 -0
- package/src/likec4/InteractiveView.tsx +144 -0
- package/src/likec4/canvas-viewport.test.ts +149 -0
- package/src/likec4/canvas-viewport.ts +135 -0
- package/src/likec4/flow-edges.test.ts +51 -0
- package/src/likec4/flow-edges.ts +89 -0
- package/src/likec4/frame-theme.test.ts +76 -0
- package/src/likec4/frame-theme.ts +101 -0
- package/src/likec4/highlight-css.test.ts +49 -0
- package/src/likec4/highlight-css.ts +52 -0
- package/src/likec4/ids.test.ts +151 -0
- package/src/likec4/ids.ts +115 -0
- package/src/likec4/levels.tsx +70 -0
- package/src/likec4/mapping.test.ts +66 -0
- package/src/likec4/mapping.ts +54 -0
- package/src/likec4/reserved.json +141 -0
- package/src/likec4/view-index.ts +78 -0
- package/src/main.tsx +13 -0
- package/src/map/ContextMapGraph.tsx +274 -0
- package/src/map/ContextNode.tsx +70 -0
- package/src/map/FloatingEdge.tsx +86 -0
- package/src/merge.test.ts +1038 -0
- package/src/merge.ts +1004 -0
- package/src/pages/AdrDetail.tsx +242 -0
- package/src/pages/AdrIndex.tsx +114 -0
- package/src/pages/AggregatePage.tsx +727 -0
- package/src/pages/BlockPage.tsx +478 -0
- package/src/pages/CatalogFailure.tsx +111 -0
- package/src/pages/Changes.tsx +313 -0
- package/src/pages/ContextMap.tsx +534 -0
- package/src/pages/ContextPage.tsx +346 -0
- package/src/pages/EnumPage.tsx +282 -0
- package/src/pages/EventPage.tsx +686 -0
- package/src/pages/ExternalPage.tsx +204 -0
- package/src/pages/FlowDetail.tsx +663 -0
- package/src/pages/FlowIndex.tsx +303 -0
- package/src/pages/GraphPage.tsx +217 -0
- package/src/pages/Language.tsx +257 -0
- package/src/pages/ModulePage.tsx +366 -0
- package/src/pages/NotFound.tsx +54 -0
- package/src/pages/Overview.tsx +344 -0
- package/src/pages/Problems.tsx +360 -0
- package/src/pages/RegistryIndex.tsx +118 -0
- package/src/pages/ServicePage.tsx +746 -0
- package/src/pages/Settings.tsx +975 -0
- package/src/pages/StorePage.tsx +114 -0
- package/src/peek/PeekCard.test.ts +35 -0
- package/src/peek/PeekCard.tsx +118 -0
- package/src/peek/PeekLayer.tsx +147 -0
- package/src/peek/model.test.ts +156 -0
- package/src/peek/model.ts +253 -0
- package/src/routes.test.ts +222 -0
- package/src/routes.ts +449 -0
- package/src/selection/DetailPanel.tsx +1407 -0
- package/src/selection/hash.test.ts +73 -0
- package/src/selection/hash.ts +58 -0
- package/src/selection/model.test.ts +199 -0
- package/src/selection/model.ts +473 -0
- package/src/selection/pages.test.ts +115 -0
- package/src/selection/pages.ts +268 -0
- package/src/selection/store.test.ts +62 -0
- package/src/selection/store.ts +46 -0
- package/src/selection/sync.tsx +99 -0
- package/src/table/ColumnsMenu.tsx +72 -0
- package/src/table/DataTable.tsx +780 -0
- package/src/table/Facets.tsx +54 -0
- package/src/table/GroupMenu.tsx +69 -0
- package/src/table/Toolbar.tsx +190 -0
- package/src/table/cells.tsx +117 -0
- package/src/table/compare.test.ts +117 -0
- package/src/table/compare.ts +196 -0
- package/src/table/export.test.ts +88 -0
- package/src/table/export.ts +66 -0
- package/src/table/facet-groups.test.ts +74 -0
- package/src/table/facet-groups.ts +56 -0
- package/src/table/grouping.test.ts +84 -0
- package/src/table/grouping.ts +82 -0
- package/src/table/infer.test.ts +57 -0
- package/src/table/infer.ts +50 -0
- package/src/table/persist.test.ts +76 -0
- package/src/table/persist.ts +112 -0
- package/src/table/sort-url.test.ts +75 -0
- package/src/table/sort-url.ts +52 -0
- package/src/table/types.ts +80 -0
- package/src/table/useTableState.ts +220 -0
- package/src/test-catalog.ts +7 -0
- package/src/testing/estate/data__catalog.json +2847 -0
- package/src/testing/estate/data__flows.json +1724 -0
- package/src/testing/estate/examples__auth__portolan__api.json +212 -0
- package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
- package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
- package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
- package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
- package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
- package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
- package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
- package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
- package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
- package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
- package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
- package/src/testing/estate.ts +49 -0
- package/src/trail/Trail.tsx +81 -0
- package/src/trail/model.test.ts +157 -0
- package/src/trail/model.ts +189 -0
- package/src/trail/record.tsx +32 -0
- package/src/trail/store.test.ts +66 -0
- package/src/trail/store.ts +102 -0
- package/tsconfig.json +35 -0
- package/vite.config.ts +171 -0
|
@@ -0,0 +1,1407 @@
|
|
|
1
|
+
// The right rail. One panel, one selection, every page that draws a diagram.
|
|
2
|
+
//
|
|
3
|
+
// It renders by kind rather than by page, so an event opened from a flow reads
|
|
4
|
+
// exactly the same as an event opened from the dependency graph. Nothing here
|
|
5
|
+
// navigates on its own: every route change is a link the reader chose.
|
|
6
|
+
|
|
7
|
+
import { Link } from "react-router";
|
|
8
|
+
import { X } from "lucide-react";
|
|
9
|
+
import { useEffect, type ReactNode } from "react";
|
|
10
|
+
import {
|
|
11
|
+
Panel,
|
|
12
|
+
ResizeHandle,
|
|
13
|
+
SavedGroup,
|
|
14
|
+
useCanvasResize,
|
|
15
|
+
usePanelRef,
|
|
16
|
+
} from "../app/panels";
|
|
17
|
+
import { SidePanel } from "../components/Overlay";
|
|
18
|
+
import { useNarrow } from "../app/responsive";
|
|
19
|
+
import { useUiStore } from "../app/ui-store";
|
|
20
|
+
import { catalog, index } from "../data";
|
|
21
|
+
import {
|
|
22
|
+
blockFields,
|
|
23
|
+
columnId,
|
|
24
|
+
columnNameOfId,
|
|
25
|
+
mapsBlockId,
|
|
26
|
+
mapsFieldPath,
|
|
27
|
+
relationOfColumnId,
|
|
28
|
+
storeViews,
|
|
29
|
+
viewReads,
|
|
30
|
+
} from "../catalog";
|
|
31
|
+
import { flowsForService, usesOfDef } from "../lib/derive";
|
|
32
|
+
import { KIND_LABEL } from "../lib/kinds";
|
|
33
|
+
import { eventScope, resolveShape } from "../lib/shape";
|
|
34
|
+
import { stepsInto } from "../lib/backlinks";
|
|
35
|
+
import { walkSteps } from "../catalog";
|
|
36
|
+
import { methodCount } from "../lib/api";
|
|
37
|
+
import {
|
|
38
|
+
consumersOf,
|
|
39
|
+
countsOf,
|
|
40
|
+
dependenciesOf,
|
|
41
|
+
registryUrl,
|
|
42
|
+
} from "../lib/registry";
|
|
43
|
+
import { typesDisagree } from "../lib/data-model";
|
|
44
|
+
import { STORE_KIND_LABEL, StoreKindMark } from "../er/StoreHeader";
|
|
45
|
+
import { upstreamOf } from "../er/lineage";
|
|
46
|
+
import type { LineageMaps } from "../er/lineage";
|
|
47
|
+
import { ctxStyle } from "../lib/context-color";
|
|
48
|
+
import { Ident } from "../components/Ident";
|
|
49
|
+
import { StatusChip } from "../components/primitives";
|
|
50
|
+
import { StepDetailBody } from "../flow/StepDetail";
|
|
51
|
+
import {
|
|
52
|
+
AGGREGATE_ANCHOR,
|
|
53
|
+
EVENT_ANCHOR,
|
|
54
|
+
SERVICE_ANCHOR,
|
|
55
|
+
eventPath as eventPathOf,
|
|
56
|
+
blockPath,
|
|
57
|
+
modulePath,
|
|
58
|
+
paths,
|
|
59
|
+
} from "../routes";
|
|
60
|
+
import { PinButton } from "../app/pins";
|
|
61
|
+
import type { PinKind } from "../lib/pins";
|
|
62
|
+
import type { Resolved, Selection } from "./model";
|
|
63
|
+
import { resolveSelection } from "./model";
|
|
64
|
+
import { selectionPath } from "./pages";
|
|
65
|
+
import { useSelectionStore } from "./store";
|
|
66
|
+
|
|
67
|
+
/** The catalog's lineage graph, read whenever a column is open. */
|
|
68
|
+
const LINEAGE: LineageMaps = {
|
|
69
|
+
from: index.lineageFrom,
|
|
70
|
+
into: index.lineageInto,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function Label({ children }: { children: ReactNode }) {
|
|
74
|
+
return <div className="label mt-3 mb-1">{children}</div>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function Row({ children }: { children: ReactNode }) {
|
|
78
|
+
return <div className="flex items-center gap-2 py-0.5">{children}</div>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The panel's own way of moving the selection, without leaving the page. */
|
|
82
|
+
function SelectLink({ id, children }: { id: string; children: ReactNode }) {
|
|
83
|
+
const select = useSelectionStore((s) => s.select);
|
|
84
|
+
return (
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
onClick={() => select(id, "panel")}
|
|
88
|
+
title={id}
|
|
89
|
+
data-peek={id}
|
|
90
|
+
className="mono trunc rounded-control text-left text-accent hover:underline"
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
</button>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Bodies
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Persistence bodies. A store, a table and a column are three zoom levels on
|
|
103
|
+
// one question — where does this live — so each one names the level above it
|
|
104
|
+
// and lists the level below.
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function StoreBody({
|
|
108
|
+
resolved,
|
|
109
|
+
}: {
|
|
110
|
+
resolved: Extract<Resolved, { kind: "store" }>;
|
|
111
|
+
}) {
|
|
112
|
+
const { store, service } = resolved;
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
<Label>Kind</Label>
|
|
116
|
+
<div className="mono flex items-center gap-1.5 text-muted">
|
|
117
|
+
<StoreKindMark kind={store.kind} />
|
|
118
|
+
{STORE_KIND_LABEL[store.kind]}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<Label>Owner</Label>
|
|
122
|
+
<SelectLink id={service.id}>{service.id}</SelectLink>
|
|
123
|
+
|
|
124
|
+
<Label>Tables</Label>
|
|
125
|
+
{store.tables.length === 0 && (store.keyspaces ?? []).length === 0 ? (
|
|
126
|
+
<div className="mono text-muted">no schema extracted</div>
|
|
127
|
+
) : null}
|
|
128
|
+
{store.tables.map((table) => (
|
|
129
|
+
<Row key={table.id}>
|
|
130
|
+
<SelectLink id={table.id}>{table.name}</SelectLink>
|
|
131
|
+
<span className="mono ml-auto shrink-0 text-muted">
|
|
132
|
+
{table.columns.length}
|
|
133
|
+
</span>
|
|
134
|
+
</Row>
|
|
135
|
+
))}
|
|
136
|
+
|
|
137
|
+
{(store.keyspaces ?? []).length > 0 ? (
|
|
138
|
+
<>
|
|
139
|
+
<Label>Key patterns</Label>
|
|
140
|
+
{(store.keyspaces ?? []).map((keyspace) => (
|
|
141
|
+
<Row key={keyspace.pattern}>
|
|
142
|
+
<span className="mono min-w-0 break-all">{keyspace.pattern}</span>
|
|
143
|
+
<span className="mono ml-auto shrink-0 text-muted">
|
|
144
|
+
{keyspace.operations.join("/")}
|
|
145
|
+
</span>
|
|
146
|
+
</Row>
|
|
147
|
+
))}
|
|
148
|
+
</>
|
|
149
|
+
) : null}
|
|
150
|
+
|
|
151
|
+
{storeViews(store).length > 0 ? (
|
|
152
|
+
<>
|
|
153
|
+
<Label>Views</Label>
|
|
154
|
+
{storeViews(store).map((view) => (
|
|
155
|
+
<Row key={view.id}>
|
|
156
|
+
<SelectLink id={view.id}>{view.name}</SelectLink>
|
|
157
|
+
<span className="mono ml-auto shrink-0 text-muted">
|
|
158
|
+
{view.materialized ? "matview" : "view"}
|
|
159
|
+
</span>
|
|
160
|
+
</Row>
|
|
161
|
+
))}
|
|
162
|
+
</>
|
|
163
|
+
) : null}
|
|
164
|
+
|
|
165
|
+
{store.source ? (
|
|
166
|
+
<>
|
|
167
|
+
<Label>Source</Label>
|
|
168
|
+
<Ident block value={store.source} className="text-muted" />
|
|
169
|
+
</>
|
|
170
|
+
) : null}
|
|
171
|
+
</>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Where a value goes and where it came from, as two lists of column links.
|
|
177
|
+
*
|
|
178
|
+
* Written once and used from both the table body and the column body, because
|
|
179
|
+
* "who reads this" is the same question at either zoom level and a reader who
|
|
180
|
+
* has learned to read it on a column should not have to learn it again.
|
|
181
|
+
*/
|
|
182
|
+
function LineageRows({ ids }: { ids: readonly string[] }) {
|
|
183
|
+
return (
|
|
184
|
+
<>
|
|
185
|
+
{ids.map((id) => (
|
|
186
|
+
<Row key={id}>
|
|
187
|
+
<SelectLink id={id}>
|
|
188
|
+
{relationOfColumnId(id).split(".").at(-1)}.{columnNameOfId(id)}
|
|
189
|
+
</SelectLink>
|
|
190
|
+
</Row>
|
|
191
|
+
))}
|
|
192
|
+
</>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function ViewBody({
|
|
197
|
+
resolved,
|
|
198
|
+
}: {
|
|
199
|
+
resolved: Extract<Resolved, { kind: "view" }>;
|
|
200
|
+
}) {
|
|
201
|
+
const { view, store } = resolved;
|
|
202
|
+
const aggregateId = view.persists?.aggregate;
|
|
203
|
+
const reads = viewReads(view);
|
|
204
|
+
// Everything computed from this view's columns, wherever it lives: a view
|
|
205
|
+
// read by another view is the case the canvas cannot draw in one hop.
|
|
206
|
+
const feeds = [
|
|
207
|
+
...new Set(
|
|
208
|
+
view.columns.flatMap(
|
|
209
|
+
(c) => index.lineageInto.get(columnId(view.id, c.name)) ?? [],
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<>
|
|
216
|
+
{view.doc ? <p className="mt-2 text-muted">{view.doc}</p> : null}
|
|
217
|
+
|
|
218
|
+
<Label>Store</Label>
|
|
219
|
+
<SelectLink id={store.id}>{store.id}</SelectLink>
|
|
220
|
+
|
|
221
|
+
<Label>Kind</Label>
|
|
222
|
+
<div className="mono text-muted">
|
|
223
|
+
{view.materialized
|
|
224
|
+
? "materialized view — the rows are kept, and can be stale"
|
|
225
|
+
: "view — the rows are computed on every read"}
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
{aggregateId ? (
|
|
229
|
+
<>
|
|
230
|
+
<Label>Presents</Label>
|
|
231
|
+
<SelectLink id={aggregateId}>{aggregateId}</SelectLink>
|
|
232
|
+
</>
|
|
233
|
+
) : null}
|
|
234
|
+
|
|
235
|
+
<Label>Reads</Label>
|
|
236
|
+
{reads.length === 0 ? (
|
|
237
|
+
<div className="mono text-muted">
|
|
238
|
+
nothing says what this view is computed from
|
|
239
|
+
</div>
|
|
240
|
+
) : null}
|
|
241
|
+
{reads.map((id) => (
|
|
242
|
+
<Row key={id}>
|
|
243
|
+
<SelectLink id={id}>{id.split(".").at(-1)}</SelectLink>
|
|
244
|
+
<span className="mono ml-auto shrink-0 text-muted">{id}</span>
|
|
245
|
+
</Row>
|
|
246
|
+
))}
|
|
247
|
+
|
|
248
|
+
<Label>Columns</Label>
|
|
249
|
+
<table className="w-full">
|
|
250
|
+
<tbody>
|
|
251
|
+
{view.columns.map((column) => (
|
|
252
|
+
<tr key={column.name} className="align-top">
|
|
253
|
+
<td className="mono py-0.5 pr-2 whitespace-nowrap">
|
|
254
|
+
<SelectLink id={columnId(view.id, column.name)}>
|
|
255
|
+
{column.name}
|
|
256
|
+
</SelectLink>
|
|
257
|
+
</td>
|
|
258
|
+
<td className="mono py-0.5 pr-2 text-muted">
|
|
259
|
+
{column.type}
|
|
260
|
+
{column.nullable ? "?" : ""}
|
|
261
|
+
</td>
|
|
262
|
+
<td className="mono py-0.5 text-muted">
|
|
263
|
+
{(column.from ?? []).length > 0 ? (
|
|
264
|
+
<span className="trunc" title={column.from?.join("\n")}>
|
|
265
|
+
← {columnNameOfId(column.from?.[0] ?? "")}
|
|
266
|
+
{(column.from?.length ?? 0) > 1
|
|
267
|
+
? ` +${(column.from?.length ?? 1) - 1}`
|
|
268
|
+
: ""}
|
|
269
|
+
</span>
|
|
270
|
+
) : null}
|
|
271
|
+
</td>
|
|
272
|
+
</tr>
|
|
273
|
+
))}
|
|
274
|
+
</tbody>
|
|
275
|
+
</table>
|
|
276
|
+
|
|
277
|
+
{feeds.length > 0 ? (
|
|
278
|
+
<>
|
|
279
|
+
<Label>Feeds</Label>
|
|
280
|
+
<LineageRows ids={feeds} />
|
|
281
|
+
</>
|
|
282
|
+
) : null}
|
|
283
|
+
|
|
284
|
+
{view.definition ? (
|
|
285
|
+
<>
|
|
286
|
+
<Label>Definition</Label>
|
|
287
|
+
<pre className="mono overflow-x-auto rounded-card border p-2 border-line bg-surface text-muted">
|
|
288
|
+
{view.definition}
|
|
289
|
+
</pre>
|
|
290
|
+
</>
|
|
291
|
+
) : null}
|
|
292
|
+
|
|
293
|
+
{view.source ? (
|
|
294
|
+
<>
|
|
295
|
+
<Label>Source</Label>
|
|
296
|
+
<Ident block value={view.source} className="text-muted" />
|
|
297
|
+
</>
|
|
298
|
+
) : null}
|
|
299
|
+
</>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function TableBody({
|
|
304
|
+
resolved,
|
|
305
|
+
}: {
|
|
306
|
+
resolved: Extract<Resolved, { kind: "table" }>;
|
|
307
|
+
}) {
|
|
308
|
+
const { table, store } = resolved;
|
|
309
|
+
const aggregateId = table.persists?.aggregate;
|
|
310
|
+
const into = index.fkIntoTable.get(table.id) ?? [];
|
|
311
|
+
const readers = index.viewsReading.get(table.id) ?? [];
|
|
312
|
+
const feeds = [
|
|
313
|
+
...new Set(
|
|
314
|
+
table.columns.flatMap(
|
|
315
|
+
(c) => index.lineageInto.get(columnId(table.id, c.name)) ?? [],
|
|
316
|
+
),
|
|
317
|
+
),
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<>
|
|
322
|
+
{table.doc ? <p className="mt-2 text-muted">{table.doc}</p> : null}
|
|
323
|
+
|
|
324
|
+
<Label>Store</Label>
|
|
325
|
+
<SelectLink id={store.id}>{store.id}</SelectLink>
|
|
326
|
+
|
|
327
|
+
{table.role ? (
|
|
328
|
+
<>
|
|
329
|
+
<Label>Role</Label>
|
|
330
|
+
<div className="mono text-muted">{table.role}</div>
|
|
331
|
+
</>
|
|
332
|
+
) : null}
|
|
333
|
+
|
|
334
|
+
{aggregateId ? (
|
|
335
|
+
<>
|
|
336
|
+
<Label>Persists</Label>
|
|
337
|
+
<SelectLink id={aggregateId}>{aggregateId}</SelectLink>
|
|
338
|
+
</>
|
|
339
|
+
) : null}
|
|
340
|
+
|
|
341
|
+
<Label>Columns</Label>
|
|
342
|
+
<table className="w-full">
|
|
343
|
+
<tbody>
|
|
344
|
+
{table.columns.map((column) => {
|
|
345
|
+
// A column carrying a domain field links to the block that
|
|
346
|
+
// declares it: that is the whole point of the `maps` metadata.
|
|
347
|
+
const blockId = mapsBlockId(
|
|
348
|
+
aggregateId ? index.aggregateById.get(aggregateId) : undefined,
|
|
349
|
+
column.maps,
|
|
350
|
+
);
|
|
351
|
+
const to = blockId ? blockPath(blockId) : null;
|
|
352
|
+
return (
|
|
353
|
+
<tr key={column.name} className="align-top">
|
|
354
|
+
<td className="mono py-0.5 pr-2 whitespace-nowrap">
|
|
355
|
+
<SelectLink id={columnId(table.id, column.name)}>
|
|
356
|
+
{column.pk ? "· " : ""}
|
|
357
|
+
{column.name}
|
|
358
|
+
</SelectLink>
|
|
359
|
+
</td>
|
|
360
|
+
<td className="mono py-0.5 pr-2 text-muted">
|
|
361
|
+
{column.type}
|
|
362
|
+
{column.nullable ? "?" : ""}
|
|
363
|
+
</td>
|
|
364
|
+
<td className="mono py-0.5 text-muted">
|
|
365
|
+
{column.maps ? (
|
|
366
|
+
to ? (
|
|
367
|
+
<Link
|
|
368
|
+
to={to}
|
|
369
|
+
className="trunc text-accent hover:underline"
|
|
370
|
+
>
|
|
371
|
+
{column.maps}
|
|
372
|
+
</Link>
|
|
373
|
+
) : (
|
|
374
|
+
column.maps
|
|
375
|
+
)
|
|
376
|
+
) : null}
|
|
377
|
+
</td>
|
|
378
|
+
</tr>
|
|
379
|
+
);
|
|
380
|
+
})}
|
|
381
|
+
</tbody>
|
|
382
|
+
</table>
|
|
383
|
+
|
|
384
|
+
{(table.indexes ?? []).length > 0 ? (
|
|
385
|
+
<>
|
|
386
|
+
<Label>Indexes</Label>
|
|
387
|
+
{(table.indexes ?? []).map((ix) => (
|
|
388
|
+
<Row key={ix.name}>
|
|
389
|
+
<span className="mono trunc" title={ix.name}>
|
|
390
|
+
{ix.columns.join(", ")}
|
|
391
|
+
</span>
|
|
392
|
+
{ix.unique ? (
|
|
393
|
+
<span className="chip ml-auto shrink-0">unique</span>
|
|
394
|
+
) : null}
|
|
395
|
+
</Row>
|
|
396
|
+
))}
|
|
397
|
+
</>
|
|
398
|
+
) : null}
|
|
399
|
+
|
|
400
|
+
<Label>Referenced by</Label>
|
|
401
|
+
{into.length === 0 ? (
|
|
402
|
+
<div className="mono text-muted">nothing points at this table</div>
|
|
403
|
+
) : null}
|
|
404
|
+
{into.map((owner) => (
|
|
405
|
+
<Row key={`${owner.table.id}.${owner.column.name}`}>
|
|
406
|
+
<SelectLink id={columnId(owner.table.id, owner.column.name)}>
|
|
407
|
+
{owner.table.name}.{owner.column.name}
|
|
408
|
+
</SelectLink>
|
|
409
|
+
</Row>
|
|
410
|
+
))}
|
|
411
|
+
|
|
412
|
+
{/* A view over this table is not a reference — nothing constrains
|
|
413
|
+
anything — but it is the same worry in a different shape: rename a
|
|
414
|
+
column here and the view breaks with no error until it is read. */}
|
|
415
|
+
{readers.length > 0 ? (
|
|
416
|
+
<>
|
|
417
|
+
<Label>Read by</Label>
|
|
418
|
+
{readers.map((view) => (
|
|
419
|
+
<Row key={view.id}>
|
|
420
|
+
<SelectLink id={view.id}>{view.name}</SelectLink>
|
|
421
|
+
<span className="mono ml-auto shrink-0 text-muted">
|
|
422
|
+
{view.materialized ? "matview" : "view"}
|
|
423
|
+
</span>
|
|
424
|
+
</Row>
|
|
425
|
+
))}
|
|
426
|
+
</>
|
|
427
|
+
) : null}
|
|
428
|
+
|
|
429
|
+
{feeds.length > 0 ? (
|
|
430
|
+
<>
|
|
431
|
+
<Label>Feeds</Label>
|
|
432
|
+
<LineageRows ids={feeds} />
|
|
433
|
+
</>
|
|
434
|
+
) : null}
|
|
435
|
+
</>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function ColumnBody({
|
|
440
|
+
resolved,
|
|
441
|
+
}: {
|
|
442
|
+
resolved: Extract<Resolved, { kind: "column" }>;
|
|
443
|
+
}) {
|
|
444
|
+
const { column, table, view } = resolved;
|
|
445
|
+
const aggregateId = (table ?? view)?.persists?.aggregate;
|
|
446
|
+
const aggregate = aggregateId
|
|
447
|
+
? index.aggregateById.get(aggregateId)
|
|
448
|
+
: undefined;
|
|
449
|
+
const from = index.lineageFrom.get(resolved.id) ?? [];
|
|
450
|
+
const into = index.lineageInto.get(resolved.id) ?? [];
|
|
451
|
+
// The far ends of the chain, not the next hop: a column three copies
|
|
452
|
+
// downstream of the truth is what a reader is trying to find out about.
|
|
453
|
+
const origins = [...upstreamOf(LINEAGE, resolved.id)].filter(
|
|
454
|
+
(id) => (index.lineageFrom.get(id)?.length ?? 0) === 0,
|
|
455
|
+
);
|
|
456
|
+
const blockId = mapsBlockId(aggregate, column.maps);
|
|
457
|
+
const block = blockId ? index.blockById.get(blockId) : undefined;
|
|
458
|
+
const to = blockId ? blockPath(blockId) : null;
|
|
459
|
+
const field = block
|
|
460
|
+
? blockFields(catalog, block.block).find(
|
|
461
|
+
(f) =>
|
|
462
|
+
f.name === (mapsFieldPath(column.maps ?? "").split(".")[0] ?? ""),
|
|
463
|
+
)
|
|
464
|
+
: undefined;
|
|
465
|
+
|
|
466
|
+
return (
|
|
467
|
+
<>
|
|
468
|
+
{column.doc ? <p className="mt-2 text-muted">{column.doc}</p> : null}
|
|
469
|
+
|
|
470
|
+
<Label>{view ? "View" : "Table"}</Label>
|
|
471
|
+
{view ? (
|
|
472
|
+
<SelectLink id={view.id}>{view.name}</SelectLink>
|
|
473
|
+
) : table ? (
|
|
474
|
+
<SelectLink id={table.id}>{table.name}</SelectLink>
|
|
475
|
+
) : null}
|
|
476
|
+
|
|
477
|
+
<Label>Type</Label>
|
|
478
|
+
<div className="mono text-muted">
|
|
479
|
+
{column.type}
|
|
480
|
+
{column.nullable ? " · nullable" : " · not null"}
|
|
481
|
+
{column.pk ? " · primary key" : ""}
|
|
482
|
+
</div>
|
|
483
|
+
|
|
484
|
+
{column.fk ? (
|
|
485
|
+
<>
|
|
486
|
+
<Label>References</Label>
|
|
487
|
+
<SelectLink id={column.fk.table}>
|
|
488
|
+
{column.fk.table}.{column.fk.column}
|
|
489
|
+
</SelectLink>
|
|
490
|
+
{column.fk.onDelete ? (
|
|
491
|
+
<div className="mono mt-1 text-muted">
|
|
492
|
+
on delete {column.fk.onDelete}
|
|
493
|
+
</div>
|
|
494
|
+
) : null}
|
|
495
|
+
</>
|
|
496
|
+
) : null}
|
|
497
|
+
|
|
498
|
+
{from.length > 0 ? (
|
|
499
|
+
<>
|
|
500
|
+
<Label>Computed from</Label>
|
|
501
|
+
<LineageRows ids={from} />
|
|
502
|
+
{/* Only worth saying when the chain is longer than one hop: with a
|
|
503
|
+
single source, the origin IS the source and printing it twice
|
|
504
|
+
says nothing. */}
|
|
505
|
+
{origins.length > 0 && !origins.every((id) => from.includes(id)) ? (
|
|
506
|
+
<>
|
|
507
|
+
<div className="mono mt-2 text-muted">originally</div>
|
|
508
|
+
<LineageRows ids={origins} />
|
|
509
|
+
</>
|
|
510
|
+
) : null}
|
|
511
|
+
</>
|
|
512
|
+
) : null}
|
|
513
|
+
|
|
514
|
+
{into.length > 0 ? (
|
|
515
|
+
<>
|
|
516
|
+
<Label>Feeds</Label>
|
|
517
|
+
<LineageRows ids={into} />
|
|
518
|
+
</>
|
|
519
|
+
) : null}
|
|
520
|
+
|
|
521
|
+
{column.maps ? (
|
|
522
|
+
<>
|
|
523
|
+
<Label>Carries</Label>
|
|
524
|
+
{to ? (
|
|
525
|
+
<Link
|
|
526
|
+
to={to}
|
|
527
|
+
className="mono rounded-control text-accent hover:underline"
|
|
528
|
+
>
|
|
529
|
+
{column.maps}
|
|
530
|
+
</Link>
|
|
531
|
+
) : (
|
|
532
|
+
<div className="mono text-muted">{column.maps}</div>
|
|
533
|
+
)}
|
|
534
|
+
{field ? (
|
|
535
|
+
<div className="mono mt-1 text-muted">
|
|
536
|
+
{field.type}
|
|
537
|
+
{typesDisagree(column.type, field.type) ? (
|
|
538
|
+
<span className="ml-2 text-declared">
|
|
539
|
+
· disagrees with {column.type}
|
|
540
|
+
</span>
|
|
541
|
+
) : null}
|
|
542
|
+
</div>
|
|
543
|
+
) : (
|
|
544
|
+
<div className="mono mt-1 text-unresolved">
|
|
545
|
+
no field of that name is declared
|
|
546
|
+
</div>
|
|
547
|
+
)}
|
|
548
|
+
</>
|
|
549
|
+
) : null}
|
|
550
|
+
</>
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function EventBody({
|
|
555
|
+
resolved,
|
|
556
|
+
}: {
|
|
557
|
+
resolved: Extract<Resolved, { kind: "event" }>;
|
|
558
|
+
}) {
|
|
559
|
+
const { event, service } = resolved;
|
|
560
|
+
const latest = event.versions[event.versions.length - 1];
|
|
561
|
+
const scope = eventScope(index, event);
|
|
562
|
+
// The steps that carry this event, not just the flows: a panel that says
|
|
563
|
+
// "checkout" sends the reader to the top of a forty-step rail, and one that
|
|
564
|
+
// says "checkout · step 14" opens on the step.
|
|
565
|
+
const steps = stepsInto(catalog, new Set([event.id]));
|
|
566
|
+
const decisions = index.adrsByEvent.get(event.id) ?? [];
|
|
567
|
+
|
|
568
|
+
return (
|
|
569
|
+
<>
|
|
570
|
+
<div className="mt-1.5 flex flex-wrap gap-1">
|
|
571
|
+
{event.versions.map((v) => (
|
|
572
|
+
<span
|
|
573
|
+
key={v.version}
|
|
574
|
+
className="mono rounded-control border px-1.5 py-px"
|
|
575
|
+
style={{
|
|
576
|
+
borderColor: v === latest ? "var(--accent)" : "var(--border)",
|
|
577
|
+
color: v === latest ? "var(--accent)" : "var(--fg-muted)",
|
|
578
|
+
}}
|
|
579
|
+
>
|
|
580
|
+
{v.version}
|
|
581
|
+
</span>
|
|
582
|
+
))}
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<Label>Producer</Label>
|
|
586
|
+
<SelectLink id={service.id}>{service.id}</SelectLink>
|
|
587
|
+
|
|
588
|
+
<Label>Schema · {latest?.version ?? "—"}</Label>
|
|
589
|
+
<table className="w-full">
|
|
590
|
+
<tbody>
|
|
591
|
+
{(latest?.fields ?? []).map((f) => {
|
|
592
|
+
// A shared def moves the selection; a block of the aggregate has
|
|
593
|
+
// a page and no selection, so it is a link.
|
|
594
|
+
const shape = f.ref ? null : resolveShape(catalog, f, scope);
|
|
595
|
+
const page =
|
|
596
|
+
shape && shape.kind !== "def" ? blockPath(shape.id) : null;
|
|
597
|
+
return (
|
|
598
|
+
<tr key={f.name} className="align-top">
|
|
599
|
+
<td className="mono py-0.5 pr-2 whitespace-nowrap">{f.name}</td>
|
|
600
|
+
<td className="mono py-0.5 text-muted">
|
|
601
|
+
{f.ref ? (
|
|
602
|
+
<SelectLink id={f.ref}>{f.type}</SelectLink>
|
|
603
|
+
) : page && shape ? (
|
|
604
|
+
<Link
|
|
605
|
+
to={page}
|
|
606
|
+
className="rounded-control text-accent hover:underline"
|
|
607
|
+
title={`open ${KIND_LABEL[shape.kind as "vo" | "entity"]} ${shape.name}`}
|
|
608
|
+
>
|
|
609
|
+
{f.type}
|
|
610
|
+
</Link>
|
|
611
|
+
) : (
|
|
612
|
+
f.type
|
|
613
|
+
)}
|
|
614
|
+
</td>
|
|
615
|
+
</tr>
|
|
616
|
+
);
|
|
617
|
+
})}
|
|
618
|
+
</tbody>
|
|
619
|
+
</table>
|
|
620
|
+
|
|
621
|
+
<Label>Consumers</Label>
|
|
622
|
+
{event.consumers.length === 0 ? (
|
|
623
|
+
<div className="mono text-muted">nothing consumes this event</div>
|
|
624
|
+
) : null}
|
|
625
|
+
{event.consumers.map((c) => (
|
|
626
|
+
<Row key={c.service}>
|
|
627
|
+
<SelectLink id={c.service}>{c.service}</SelectLink>
|
|
628
|
+
{c.via ? (
|
|
629
|
+
<Link
|
|
630
|
+
to={paths.flowStep(c.via.flow, c.via.step)}
|
|
631
|
+
className="mono shrink-0 text-muted hover:text-ink hover:underline"
|
|
632
|
+
title={`read from flow ${c.via.flow}, step ${c.via.step}; no source declares this consumer`}
|
|
633
|
+
>
|
|
634
|
+
via flow
|
|
635
|
+
</Link>
|
|
636
|
+
) : null}
|
|
637
|
+
<span className="ml-auto shrink-0">
|
|
638
|
+
<StatusChip status={c.status} title={c.note} />
|
|
639
|
+
</span>
|
|
640
|
+
</Row>
|
|
641
|
+
))}
|
|
642
|
+
|
|
643
|
+
<Label>Appears in flows</Label>
|
|
644
|
+
{steps.length === 0 ? (
|
|
645
|
+
<div className="mono text-muted">no flow references this event</div>
|
|
646
|
+
) : (
|
|
647
|
+
<div className="flex flex-col gap-1">
|
|
648
|
+
{steps.map((s) => (
|
|
649
|
+
<Link
|
|
650
|
+
key={`${s.flow.slug}:${s.stepId}`}
|
|
651
|
+
to={paths.flowStep(s.flow.slug, s.stepId)}
|
|
652
|
+
className="mono text-accent"
|
|
653
|
+
>
|
|
654
|
+
{s.flow.slug} · step {s.number} →
|
|
655
|
+
</Link>
|
|
656
|
+
))}
|
|
657
|
+
</div>
|
|
658
|
+
)}
|
|
659
|
+
|
|
660
|
+
{decisions.length > 0 ? (
|
|
661
|
+
<>
|
|
662
|
+
<Label>Decisions</Label>
|
|
663
|
+
<div className="flex flex-col gap-1">
|
|
664
|
+
{decisions.map((adr) => (
|
|
665
|
+
<Link
|
|
666
|
+
key={adr.id}
|
|
667
|
+
to={paths.adr(adr.slug)}
|
|
668
|
+
className="mono truncate text-accent"
|
|
669
|
+
title={adr.title}
|
|
670
|
+
>
|
|
671
|
+
{adr.id} · {adr.title}
|
|
672
|
+
</Link>
|
|
673
|
+
))}
|
|
674
|
+
</div>
|
|
675
|
+
</>
|
|
676
|
+
) : null}
|
|
677
|
+
|
|
678
|
+
<Label>Source</Label>
|
|
679
|
+
{latest?.source ? (
|
|
680
|
+
<Ident block value={latest.source} className="text-muted" />
|
|
681
|
+
) : (
|
|
682
|
+
<div className="mono text-muted">not recorded</div>
|
|
683
|
+
)}
|
|
684
|
+
</>
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* What the panel calls the thing it is showing.
|
|
690
|
+
*
|
|
691
|
+
* Most kinds are their id, which the line underneath already prints. These are
|
|
692
|
+
* the ones with a name a reader would say out loud instead - and it is a
|
|
693
|
+
* function rather than a ternary chain because it was nine levels deep before
|
|
694
|
+
* a tenth kind existed.
|
|
695
|
+
*/
|
|
696
|
+
function titleOf(resolved: Resolved | null, selection: Selection): string {
|
|
697
|
+
if (!resolved) return selection.id;
|
|
698
|
+
|
|
699
|
+
switch (resolved.kind) {
|
|
700
|
+
case "event":
|
|
701
|
+
return resolved.event.name;
|
|
702
|
+
case "table":
|
|
703
|
+
return resolved.table.name;
|
|
704
|
+
case "view":
|
|
705
|
+
return resolved.view.name;
|
|
706
|
+
case "column":
|
|
707
|
+
return `${resolved.view?.name ?? resolved.table?.name ?? resolved.store.slug}.${resolved.column.name}`;
|
|
708
|
+
case "flow-step":
|
|
709
|
+
return resolved.step.label ?? resolved.step.ref ?? resolved.step.kind;
|
|
710
|
+
case "bundle":
|
|
711
|
+
return `${resolved.bundle.from} → ${resolved.bundle.to}`;
|
|
712
|
+
// `acme/shop`, not the registry host as well: the host is the same for
|
|
713
|
+
// every module in almost every estate.
|
|
714
|
+
case "module":
|
|
715
|
+
return resolved.module.name;
|
|
716
|
+
default:
|
|
717
|
+
return selection.id;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function ModuleBody({
|
|
722
|
+
resolved,
|
|
723
|
+
}: {
|
|
724
|
+
resolved: Extract<Resolved, { kind: "module" }>;
|
|
725
|
+
}) {
|
|
726
|
+
const { module } = resolved;
|
|
727
|
+
const counts = countsOf(index, module);
|
|
728
|
+
const consumers = consumersOf(index, module);
|
|
729
|
+
const deps = dependenciesOf(index, module);
|
|
730
|
+
const owner = module.owner ? index.serviceById.get(module.owner) : undefined;
|
|
731
|
+
const url = registryUrl(module);
|
|
732
|
+
const to = modulePath(module.id);
|
|
733
|
+
|
|
734
|
+
return (
|
|
735
|
+
<>
|
|
736
|
+
{module.registry ? <Label>{module.registry}</Label> : null}
|
|
737
|
+
<Label>commit</Label>
|
|
738
|
+
<Row>
|
|
739
|
+
{module.commit ? (
|
|
740
|
+
<Ident value={module.commit.slice(0, 12)} />
|
|
741
|
+
) : (
|
|
742
|
+
/* Not pinned is a fact worth saying: it means two builds a day apart
|
|
743
|
+
can describe two different modules under one name. */
|
|
744
|
+
<span className="text-muted">not pinned</span>
|
|
745
|
+
)}
|
|
746
|
+
</Row>
|
|
747
|
+
|
|
748
|
+
<Label>holds</Label>
|
|
749
|
+
<div className="rows">
|
|
750
|
+
<div className="row">
|
|
751
|
+
<span className="flex-1">packages</span>
|
|
752
|
+
<span className="tnum text-muted">{counts.packages}</span>
|
|
753
|
+
</div>
|
|
754
|
+
<div className="row">
|
|
755
|
+
<span className="flex-1">interfaces</span>
|
|
756
|
+
<span className="tnum text-muted">{counts.interfaces}</span>
|
|
757
|
+
</div>
|
|
758
|
+
<div className="row">
|
|
759
|
+
<span className="flex-1">methods</span>
|
|
760
|
+
<span className="tnum text-muted">{counts.methods}</span>
|
|
761
|
+
</div>
|
|
762
|
+
<div className="row">
|
|
763
|
+
<span className="flex-1">messages</span>
|
|
764
|
+
<span className="tnum text-muted">{counts.messages}</span>
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
|
|
768
|
+
{/* Who publishes it, and - the more interesting half - who else reads it. */}
|
|
769
|
+
<Label>published by</Label>
|
|
770
|
+
{owner ? (
|
|
771
|
+
<SelectLink id={owner.id}>{owner.id}</SelectLink>
|
|
772
|
+
) : (
|
|
773
|
+
<p className="text-muted">
|
|
774
|
+
nobody in this catalog — the module is published elsewhere
|
|
775
|
+
</p>
|
|
776
|
+
)}
|
|
777
|
+
|
|
778
|
+
{consumers.length > 0 ? (
|
|
779
|
+
<>
|
|
780
|
+
<Label>read by</Label>
|
|
781
|
+
<div className="rows">
|
|
782
|
+
{consumers.map((service) => (
|
|
783
|
+
<SelectLink key={service.id} id={service.id}>
|
|
784
|
+
{service.id}
|
|
785
|
+
</SelectLink>
|
|
786
|
+
))}
|
|
787
|
+
</div>
|
|
788
|
+
</>
|
|
789
|
+
) : null}
|
|
790
|
+
|
|
791
|
+
{deps.length > 0 ? (
|
|
792
|
+
<>
|
|
793
|
+
<Label>depends on</Label>
|
|
794
|
+
<div className="rows">
|
|
795
|
+
{deps.map((dep) =>
|
|
796
|
+
dep.module ? (
|
|
797
|
+
<Link
|
|
798
|
+
key={dep.id}
|
|
799
|
+
to={paths.module(dep.module.slug)}
|
|
800
|
+
className="row mono hover:text-ink"
|
|
801
|
+
>
|
|
802
|
+
{dep.module.name}
|
|
803
|
+
</Link>
|
|
804
|
+
) : (
|
|
805
|
+
/* A module may depend on one the estate never vendored. Naming
|
|
806
|
+
it and saying so beats a link into nothing. */
|
|
807
|
+
<div key={dep.id} className="row mono text-muted">
|
|
808
|
+
{dep.id}
|
|
809
|
+
<span className="ml-auto">not in this catalog</span>
|
|
810
|
+
</div>
|
|
811
|
+
),
|
|
812
|
+
)}
|
|
813
|
+
</div>
|
|
814
|
+
</>
|
|
815
|
+
) : null}
|
|
816
|
+
|
|
817
|
+
{to ? (
|
|
818
|
+
<Link to={to} className="mt-3 block text-muted hover:text-ink">
|
|
819
|
+
open the module →
|
|
820
|
+
</Link>
|
|
821
|
+
) : null}
|
|
822
|
+
{url ? (
|
|
823
|
+
<a
|
|
824
|
+
href={url}
|
|
825
|
+
target="_blank"
|
|
826
|
+
rel="noreferrer"
|
|
827
|
+
className="mt-1 block text-muted hover:text-ink"
|
|
828
|
+
>
|
|
829
|
+
{module.name} on {module.registry} ↗
|
|
830
|
+
</a>
|
|
831
|
+
) : null}
|
|
832
|
+
</>
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function ServiceBody({
|
|
837
|
+
resolved,
|
|
838
|
+
}: {
|
|
839
|
+
resolved: Extract<Resolved, { kind: "service" }>;
|
|
840
|
+
}) {
|
|
841
|
+
const { service, context } = resolved;
|
|
842
|
+
const methods = methodCount(service);
|
|
843
|
+
const events = service.aggregates.flatMap((a) => a.events);
|
|
844
|
+
const unresolved = service.consumes.filter((c) => c.status === "unresolved");
|
|
845
|
+
// Each flow this service takes part in, opened on the first step it is
|
|
846
|
+
// on either end of - where it enters the story, not the top of the rail.
|
|
847
|
+
const appearances = flowsForService(catalog, service.id).map((flow) => {
|
|
848
|
+
const steps = walkSteps(flow.steps);
|
|
849
|
+
const at = steps.findIndex((s) => s.from === service.id || s.to === service.id);
|
|
850
|
+
return { flow, step: at >= 0 ? steps[at] : undefined, number: at + 1 };
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
return (
|
|
854
|
+
<>
|
|
855
|
+
<Ident
|
|
856
|
+
block
|
|
857
|
+
value={`${service.repo}/${service.path}`}
|
|
858
|
+
className="mt-1.5 text-muted"
|
|
859
|
+
/>
|
|
860
|
+
|
|
861
|
+
<Label>Provides</Label>
|
|
862
|
+
{/* The count opens the tab that lists the methods it counted. */}
|
|
863
|
+
<Link
|
|
864
|
+
to={`${paths.service(context.id, service.slug)}?tab=provides`}
|
|
865
|
+
className="mono rounded-control text-muted hover:text-ink"
|
|
866
|
+
>
|
|
867
|
+
<span className="tnum">{methods}</span> method
|
|
868
|
+
{methods === 1 ? "" : "s"} over{" "}
|
|
869
|
+
<span className="tnum">{service.provides.length}</span> service
|
|
870
|
+
{service.provides.length === 1 ? "" : "s"}
|
|
871
|
+
</Link>
|
|
872
|
+
{service.provides.map((p) => (
|
|
873
|
+
<Ident block key={p.id} value={p.id} className="text-muted" />
|
|
874
|
+
))}
|
|
875
|
+
|
|
876
|
+
<Label>Consumes</Label>
|
|
877
|
+
{service.consumes.length === 0 ? (
|
|
878
|
+
<div className="mono text-muted">this service calls nobody</div>
|
|
879
|
+
) : null}
|
|
880
|
+
{service.consumes.map((call) => (
|
|
881
|
+
<Row key={call.id}>
|
|
882
|
+
<Ident value={call.id} />
|
|
883
|
+
<span className="ml-auto shrink-0">
|
|
884
|
+
<StatusChip status={call.status} title={call.note} />
|
|
885
|
+
</span>
|
|
886
|
+
</Row>
|
|
887
|
+
))}
|
|
888
|
+
{unresolved.length > 0 ? (
|
|
889
|
+
<Link
|
|
890
|
+
to={paths.problems()}
|
|
891
|
+
className="mono mt-1 block rounded-control text-unresolved hover:underline"
|
|
892
|
+
>
|
|
893
|
+
<span className="tnum">{unresolved.length}</span> call
|
|
894
|
+
{unresolved.length === 1 ? "" : "s"} resolve to nothing in the catalog
|
|
895
|
+
→
|
|
896
|
+
</Link>
|
|
897
|
+
) : null}
|
|
898
|
+
|
|
899
|
+
<Label>Publishes</Label>
|
|
900
|
+
{events.length === 0 ? (
|
|
901
|
+
<div className="mono text-muted">no events</div>
|
|
902
|
+
) : null}
|
|
903
|
+
{events.map((e) => (
|
|
904
|
+
<Row key={e.id}>
|
|
905
|
+
<SelectLink id={e.id}>{e.name}</SelectLink>
|
|
906
|
+
<Link
|
|
907
|
+
to={`${eventPathOf(e.id) ?? paths.service(context.id, service.slug)}#${EVENT_ANCHOR.consumers}`}
|
|
908
|
+
title={`${e.consumers.length} consumers of ${e.name}`}
|
|
909
|
+
className="mono tnum ml-auto shrink-0 rounded-control text-muted hover:text-ink"
|
|
910
|
+
>
|
|
911
|
+
{e.consumers.length}
|
|
912
|
+
</Link>
|
|
913
|
+
</Row>
|
|
914
|
+
))}
|
|
915
|
+
|
|
916
|
+
<Label>Appears in flows</Label>
|
|
917
|
+
{appearances.length === 0 ? (
|
|
918
|
+
<div className="mono text-muted">appears in no flow</div>
|
|
919
|
+
) : (
|
|
920
|
+
<div className="flex flex-col gap-1">
|
|
921
|
+
{appearances.map(({ flow, step, number }) => (
|
|
922
|
+
<Link
|
|
923
|
+
key={flow.slug}
|
|
924
|
+
to={step ? paths.flowStep(flow.slug, step.id) : paths.flow(flow.slug)}
|
|
925
|
+
className="mono text-accent"
|
|
926
|
+
title={step ? `${flow.name}, from step ${number}` : flow.name}
|
|
927
|
+
>
|
|
928
|
+
{flow.slug}
|
|
929
|
+
{step ? ` · step ${number}` : ""} →
|
|
930
|
+
</Link>
|
|
931
|
+
))}
|
|
932
|
+
</div>
|
|
933
|
+
)}
|
|
934
|
+
|
|
935
|
+
<Label>Context</Label>
|
|
936
|
+
<SelectLink id={context.id}>{context.id}</SelectLink>
|
|
937
|
+
</>
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function AggregateBody({
|
|
942
|
+
resolved,
|
|
943
|
+
}: {
|
|
944
|
+
resolved: Extract<Resolved, { kind: "aggregate" }>;
|
|
945
|
+
}) {
|
|
946
|
+
const { aggregate, service, context } = resolved;
|
|
947
|
+
const commands = aggregate.operations.filter((o) => o.kind === "command");
|
|
948
|
+
const queries = aggregate.operations.filter((o) => o.kind === "query");
|
|
949
|
+
const aggregatePath = paths.aggregate(
|
|
950
|
+
context.id,
|
|
951
|
+
service.slug,
|
|
952
|
+
aggregate.slug,
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
return (
|
|
956
|
+
<>
|
|
957
|
+
<Label>Owner</Label>
|
|
958
|
+
<SelectLink id={service.id}>{service.id}</SelectLink>
|
|
959
|
+
|
|
960
|
+
<Label>Operations</Label>
|
|
961
|
+
<div className="mono flex gap-3 text-muted">
|
|
962
|
+
<Link
|
|
963
|
+
to={`${aggregatePath}#${AGGREGATE_ANCHOR.commands}`}
|
|
964
|
+
className="rounded-control hover:text-ink"
|
|
965
|
+
>
|
|
966
|
+
<span className="tnum">{commands.length}</span> command
|
|
967
|
+
{commands.length === 1 ? "" : "s"}
|
|
968
|
+
</Link>
|
|
969
|
+
<Link
|
|
970
|
+
to={`${aggregatePath}#${AGGREGATE_ANCHOR.queries}`}
|
|
971
|
+
className="rounded-control hover:text-ink"
|
|
972
|
+
>
|
|
973
|
+
<span className="tnum">{queries.length}</span> quer
|
|
974
|
+
{queries.length === 1 ? "y" : "ies"}
|
|
975
|
+
</Link>
|
|
976
|
+
</div>
|
|
977
|
+
|
|
978
|
+
<Label>Events</Label>
|
|
979
|
+
{aggregate.events.length === 0 ? (
|
|
980
|
+
<div className="mono text-muted">this aggregate publishes nothing</div>
|
|
981
|
+
) : null}
|
|
982
|
+
{aggregate.events.map((e) => (
|
|
983
|
+
<Row key={e.id}>
|
|
984
|
+
<SelectLink id={e.id}>{e.name}</SelectLink>
|
|
985
|
+
</Row>
|
|
986
|
+
))}
|
|
987
|
+
</>
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function ContextBody({
|
|
992
|
+
resolved,
|
|
993
|
+
}: {
|
|
994
|
+
resolved: Extract<Resolved, { kind: "context" }>;
|
|
995
|
+
}) {
|
|
996
|
+
const { context } = resolved;
|
|
997
|
+
return (
|
|
998
|
+
<>
|
|
999
|
+
<p className="mt-1.5 text-muted">{context.summary}</p>
|
|
1000
|
+
<Label>Services</Label>
|
|
1001
|
+
{context.services.map((s) => (
|
|
1002
|
+
<Row key={s.id}>
|
|
1003
|
+
<SelectLink id={s.id}>{s.slug}</SelectLink>
|
|
1004
|
+
<Link
|
|
1005
|
+
to={`${paths.service(context.id, s.slug)}#${SERVICE_ANCHOR.events}`}
|
|
1006
|
+
className="mono ml-auto shrink-0 rounded-control text-muted hover:text-ink"
|
|
1007
|
+
>
|
|
1008
|
+
<span className="tnum">
|
|
1009
|
+
{s.aggregates.reduce((n, a) => n + a.events.length, 0)}
|
|
1010
|
+
</span>{" "}
|
|
1011
|
+
events
|
|
1012
|
+
</Link>
|
|
1013
|
+
</Row>
|
|
1014
|
+
))}
|
|
1015
|
+
</>
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function ValueObjectBody({
|
|
1020
|
+
resolved,
|
|
1021
|
+
}: {
|
|
1022
|
+
resolved: Extract<Resolved, { kind: "value-object" }>;
|
|
1023
|
+
}) {
|
|
1024
|
+
const uses = usesOfDef(catalog, resolved.id);
|
|
1025
|
+
return (
|
|
1026
|
+
<>
|
|
1027
|
+
<Label>Fields</Label>
|
|
1028
|
+
<table className="w-full">
|
|
1029
|
+
<tbody>
|
|
1030
|
+
{resolved.def.fields.map((f) => (
|
|
1031
|
+
<tr key={f.name} className="align-top">
|
|
1032
|
+
<td className="mono py-0.5 pr-2 whitespace-nowrap">{f.name}</td>
|
|
1033
|
+
<td className="mono py-0.5 pr-2 text-muted">
|
|
1034
|
+
{f.ref ? <SelectLink id={f.ref}>{f.type}</SelectLink> : f.type}
|
|
1035
|
+
</td>
|
|
1036
|
+
<td className="py-0.5 text-muted">{f.doc}</td>
|
|
1037
|
+
</tr>
|
|
1038
|
+
))}
|
|
1039
|
+
</tbody>
|
|
1040
|
+
</table>
|
|
1041
|
+
|
|
1042
|
+
<Label>Used in</Label>
|
|
1043
|
+
{uses.events.length === 0 && uses.defs.length === 0 ? (
|
|
1044
|
+
<div className="mono text-muted">nothing carries this type</div>
|
|
1045
|
+
) : null}
|
|
1046
|
+
{uses.events.map((use) => (
|
|
1047
|
+
<Row key={use.eventId}>
|
|
1048
|
+
<SelectLink id={use.eventId}>{use.eventId}</SelectLink>
|
|
1049
|
+
<span className="mono ml-auto shrink-0 text-muted">
|
|
1050
|
+
{use.versions.join(" ")}
|
|
1051
|
+
</span>
|
|
1052
|
+
</Row>
|
|
1053
|
+
))}
|
|
1054
|
+
{uses.defs.map((id) => (
|
|
1055
|
+
<Row key={id}>
|
|
1056
|
+
<SelectLink id={id}>{id}</SelectLink>
|
|
1057
|
+
<span className="mono ml-auto shrink-0 text-muted">type</span>
|
|
1058
|
+
</Row>
|
|
1059
|
+
))}
|
|
1060
|
+
</>
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function FlowStepBody({
|
|
1065
|
+
resolved,
|
|
1066
|
+
}: {
|
|
1067
|
+
resolved: Extract<Resolved, { kind: "flow-step" }>;
|
|
1068
|
+
}) {
|
|
1069
|
+
return (
|
|
1070
|
+
<>
|
|
1071
|
+
<div className="mono mt-1 mb-2 flex items-center gap-2 text-muted">
|
|
1072
|
+
<Link to={paths.flow(resolved.flow.slug)} className="text-accent">
|
|
1073
|
+
{resolved.flow.slug}
|
|
1074
|
+
</Link>
|
|
1075
|
+
</div>
|
|
1076
|
+
<StepDetailBody step={resolved.step} flow={resolved.flow} />
|
|
1077
|
+
</>
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* One bundled edge, opened.
|
|
1083
|
+
*
|
|
1084
|
+
* Compact mode trades every event label for a number, and this is where the
|
|
1085
|
+
* number is spent: the count on the line is the length of this list, and the
|
|
1086
|
+
* list is the only place the reader can find out which events it stood for.
|
|
1087
|
+
*/
|
|
1088
|
+
function BundleBody({
|
|
1089
|
+
resolved,
|
|
1090
|
+
}: {
|
|
1091
|
+
resolved: Extract<Resolved, { kind: "bundle" }>;
|
|
1092
|
+
}) {
|
|
1093
|
+
const { bundle } = resolved;
|
|
1094
|
+
return (
|
|
1095
|
+
<>
|
|
1096
|
+
<Label>Publisher</Label>
|
|
1097
|
+
<SelectLink id={bundle.from}>{bundle.from}</SelectLink>
|
|
1098
|
+
|
|
1099
|
+
<Label>Consumer</Label>
|
|
1100
|
+
{resolved.to ? (
|
|
1101
|
+
<SelectLink id={bundle.to}>{bundle.to}</SelectLink>
|
|
1102
|
+
) : (
|
|
1103
|
+
<div className="mono text-muted" title="not in the catalog">
|
|
1104
|
+
{bundle.to} — not in catalog
|
|
1105
|
+
</div>
|
|
1106
|
+
)}
|
|
1107
|
+
|
|
1108
|
+
<Label>
|
|
1109
|
+
{bundle.events.length} {bundle.events.length === 1 ? "event" : "events"}
|
|
1110
|
+
</Label>
|
|
1111
|
+
{bundle.events.map((event) => (
|
|
1112
|
+
<Row key={event.id}>
|
|
1113
|
+
<SelectLink id={event.id}>{event.name}</SelectLink>
|
|
1114
|
+
<span className="ml-auto shrink-0">
|
|
1115
|
+
<StatusChip status={event.status} />
|
|
1116
|
+
</span>
|
|
1117
|
+
</Row>
|
|
1118
|
+
))}
|
|
1119
|
+
</>
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* A node the model draws but the catalog has never heard of — an external
|
|
1125
|
+
* participant, most often. Saying so beats saying nothing.
|
|
1126
|
+
*/
|
|
1127
|
+
function UnknownBody({ selection }: { selection: Selection }) {
|
|
1128
|
+
const consumers = catalog.contexts.flatMap((c) =>
|
|
1129
|
+
c.services.flatMap((s) =>
|
|
1130
|
+
s.aggregates.flatMap((a) =>
|
|
1131
|
+
a.events
|
|
1132
|
+
.filter((e) => e.consumers.some((x) => x.service === selection.id))
|
|
1133
|
+
.map((e) => e),
|
|
1134
|
+
),
|
|
1135
|
+
),
|
|
1136
|
+
);
|
|
1137
|
+
const flows = catalog.flows.filter((f) =>
|
|
1138
|
+
f.participants.some((p) => p.id === selection.id),
|
|
1139
|
+
);
|
|
1140
|
+
|
|
1141
|
+
return (
|
|
1142
|
+
<>
|
|
1143
|
+
<p className="meta mt-1.5 text-unresolved">
|
|
1144
|
+
nothing in the catalog owns this id
|
|
1145
|
+
</p>
|
|
1146
|
+
{consumers.length > 0 ? (
|
|
1147
|
+
<>
|
|
1148
|
+
<Label>Named as a consumer of</Label>
|
|
1149
|
+
{consumers.map((e) => (
|
|
1150
|
+
<Row key={e.id}>
|
|
1151
|
+
<SelectLink id={e.id}>{e.name}</SelectLink>
|
|
1152
|
+
</Row>
|
|
1153
|
+
))}
|
|
1154
|
+
</>
|
|
1155
|
+
) : null}
|
|
1156
|
+
{flows.length > 0 ? (
|
|
1157
|
+
<>
|
|
1158
|
+
<Label>Appears in flows</Label>
|
|
1159
|
+
{flows.map((f) => (
|
|
1160
|
+
<Link
|
|
1161
|
+
key={f.slug}
|
|
1162
|
+
to={paths.flow(f.slug)}
|
|
1163
|
+
className="mono block text-accent"
|
|
1164
|
+
>
|
|
1165
|
+
{f.slug} →
|
|
1166
|
+
</Link>
|
|
1167
|
+
))}
|
|
1168
|
+
</>
|
|
1169
|
+
) : null}
|
|
1170
|
+
{consumers.length === 0 && flows.length === 0 ? (
|
|
1171
|
+
<p className="meta mt-3">it is not referenced anywhere else either</p>
|
|
1172
|
+
) : null}
|
|
1173
|
+
</>
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// ---------------------------------------------------------------------------
|
|
1178
|
+
// Frame
|
|
1179
|
+
// ---------------------------------------------------------------------------
|
|
1180
|
+
|
|
1181
|
+
function kindLabel(selection: Selection, resolved: Resolved | null): string {
|
|
1182
|
+
if (!resolved) return "unknown";
|
|
1183
|
+
if (resolved.kind === "flow-step") return `step ${resolved.number}`;
|
|
1184
|
+
return selection.kind;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* What pinning the open selection would pin, or null when the selection is not
|
|
1189
|
+
* a thing a reader can come back to. A column, a view and a store are read
|
|
1190
|
+
* inside the canvas that holds them; a step is read inside its flow. Pinning
|
|
1191
|
+
* one of those would bookmark a scroll position rather than an entity.
|
|
1192
|
+
*/
|
|
1193
|
+
function pinFor(
|
|
1194
|
+
resolved: Resolved | null,
|
|
1195
|
+
): { kind: PinKind; id: string } | null {
|
|
1196
|
+
if (!resolved) return null;
|
|
1197
|
+
switch (resolved.kind) {
|
|
1198
|
+
case "event":
|
|
1199
|
+
return { kind: "event", id: resolved.event.id };
|
|
1200
|
+
case "service":
|
|
1201
|
+
return { kind: "service", id: resolved.service.id };
|
|
1202
|
+
case "aggregate":
|
|
1203
|
+
return { kind: "aggregate", id: resolved.aggregate.id };
|
|
1204
|
+
case "table":
|
|
1205
|
+
return { kind: "table", id: resolved.table.id };
|
|
1206
|
+
default:
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
export function DetailPanel() {
|
|
1212
|
+
const selection = useSelectionStore((s) => s.selection);
|
|
1213
|
+
const clear = useSelectionStore((s) => s.clear);
|
|
1214
|
+
if (!selection) return null;
|
|
1215
|
+
|
|
1216
|
+
const resolved = resolveSelection(selection.id);
|
|
1217
|
+
const page = selectionPath(selection);
|
|
1218
|
+
const pin = pinFor(resolved);
|
|
1219
|
+
|
|
1220
|
+
return (
|
|
1221
|
+
<aside
|
|
1222
|
+
/* Slides 16px and fades in when the panel appears - not on every change
|
|
1223
|
+
of selection, which would set the whole rail moving each time the
|
|
1224
|
+
reader clicked a step. There is no exit: it unmounts on clear. */
|
|
1225
|
+
className="panel-in pane flex h-full w-full flex-col border-l border-line bg-canvas"
|
|
1226
|
+
aria-label="Selection detail"
|
|
1227
|
+
>
|
|
1228
|
+
<div className="sticky-bar flex shrink-0 items-center gap-2 border-b px-4 py-2.5 border-line">
|
|
1229
|
+
<span className="label">{kindLabel(selection, resolved)}</span>
|
|
1230
|
+
{resolved?.kind === "flow-step" ? (
|
|
1231
|
+
<StatusChip status={resolved.step.status} />
|
|
1232
|
+
) : null}
|
|
1233
|
+
{/* Right-aligned beside the close: both are about the panel rather
|
|
1234
|
+
than about what is in it. */}
|
|
1235
|
+
{pin ? (
|
|
1236
|
+
<span className="ml-auto flex items-center">
|
|
1237
|
+
<PinButton kind={pin.kind} id={pin.id} size={14} />
|
|
1238
|
+
</span>
|
|
1239
|
+
) : null}
|
|
1240
|
+
<button
|
|
1241
|
+
type="button"
|
|
1242
|
+
onClick={() => clear("panel")}
|
|
1243
|
+
aria-label="Close selection detail (Esc)"
|
|
1244
|
+
title="Esc"
|
|
1245
|
+
className="rounded-control p-1 text-muted t-micro transition-colors hover:bg-surface hover:text-ink"
|
|
1246
|
+
>
|
|
1247
|
+
<X size={16} aria-hidden />
|
|
1248
|
+
</button>
|
|
1249
|
+
</div>
|
|
1250
|
+
|
|
1251
|
+
<div className="flex-1 overflow-y-auto p-4">
|
|
1252
|
+
<div className="flex flex-wrap items-baseline gap-x-2">
|
|
1253
|
+
<span className="mono break-all text-sm text-ink">
|
|
1254
|
+
{titleOf(resolved, selection)}
|
|
1255
|
+
</span>
|
|
1256
|
+
{resolved?.kind === "event" ||
|
|
1257
|
+
resolved?.kind === "service" ||
|
|
1258
|
+
resolved?.kind === "aggregate" ? (
|
|
1259
|
+
<span
|
|
1260
|
+
className="mono ctx"
|
|
1261
|
+
style={ctxStyle(
|
|
1262
|
+
"context" in resolved ? resolved.context.id : null,
|
|
1263
|
+
)}
|
|
1264
|
+
>
|
|
1265
|
+
{resolved.context.id}
|
|
1266
|
+
</span>
|
|
1267
|
+
) : null}
|
|
1268
|
+
</div>
|
|
1269
|
+
{/* A synthetic id is not something a reader can paste anywhere, so a
|
|
1270
|
+
step and a bundle keep theirs to themselves. */}
|
|
1271
|
+
{resolved !== null &&
|
|
1272
|
+
resolved.kind !== "flow-step" &&
|
|
1273
|
+
resolved.kind !== "bundle" ? (
|
|
1274
|
+
<Ident block value={selection.id} className="mt-0.5 text-muted" />
|
|
1275
|
+
) : null}
|
|
1276
|
+
|
|
1277
|
+
{page ? (
|
|
1278
|
+
<Link
|
|
1279
|
+
to={page}
|
|
1280
|
+
className="mono mt-3 inline-block rounded-control text-accent hover:underline"
|
|
1281
|
+
>
|
|
1282
|
+
open page →
|
|
1283
|
+
</Link>
|
|
1284
|
+
) : null}
|
|
1285
|
+
|
|
1286
|
+
{resolved === null ? (
|
|
1287
|
+
<UnknownBody selection={selection} />
|
|
1288
|
+
) : resolved.kind === "event" ? (
|
|
1289
|
+
<EventBody resolved={resolved} />
|
|
1290
|
+
) : resolved.kind === "service" ? (
|
|
1291
|
+
<ServiceBody resolved={resolved} />
|
|
1292
|
+
) : resolved.kind === "aggregate" ? (
|
|
1293
|
+
<AggregateBody resolved={resolved} />
|
|
1294
|
+
) : resolved.kind === "context" ? (
|
|
1295
|
+
<ContextBody resolved={resolved} />
|
|
1296
|
+
) : resolved.kind === "value-object" ? (
|
|
1297
|
+
<ValueObjectBody resolved={resolved} />
|
|
1298
|
+
) : resolved.kind === "store" ? (
|
|
1299
|
+
<StoreBody resolved={resolved} />
|
|
1300
|
+
) : resolved.kind === "table" ? (
|
|
1301
|
+
<TableBody resolved={resolved} />
|
|
1302
|
+
) : resolved.kind === "view" ? (
|
|
1303
|
+
<ViewBody resolved={resolved} />
|
|
1304
|
+
) : resolved.kind === "column" ? (
|
|
1305
|
+
<ColumnBody resolved={resolved} />
|
|
1306
|
+
) : resolved.kind === "bundle" ? (
|
|
1307
|
+
<BundleBody resolved={resolved} />
|
|
1308
|
+
) : resolved.kind === "module" ? (
|
|
1309
|
+
<ModuleBody resolved={resolved} />
|
|
1310
|
+
) : (
|
|
1311
|
+
<FlowStepBody resolved={resolved} />
|
|
1312
|
+
)}
|
|
1313
|
+
</div>
|
|
1314
|
+
</aside>
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Page shell for the routes that embed a diagram: the page on the left, the
|
|
1320
|
+
* selection detail on the right.
|
|
1321
|
+
*
|
|
1322
|
+
* The detail panel is not opened by dragging. It is collapsed to nothing while
|
|
1323
|
+
* there is no selection and expanded the moment there is one, driven from the
|
|
1324
|
+
* store rather than from the handle - the handle only decides how wide it is
|
|
1325
|
+
* once open, and that width is what gets remembered.
|
|
1326
|
+
*/
|
|
1327
|
+
export function WithDetail({
|
|
1328
|
+
id,
|
|
1329
|
+
children,
|
|
1330
|
+
}: {
|
|
1331
|
+
/** Page name for the persisted layout: "portolan:<page>". */
|
|
1332
|
+
id: string;
|
|
1333
|
+
children: ReactNode;
|
|
1334
|
+
}) {
|
|
1335
|
+
const selection = useSelectionStore((s) => s.selection);
|
|
1336
|
+
const clear = useSelectionStore((s) => s.clear);
|
|
1337
|
+
const hidden = useUiStore((s) => s.detailHidden);
|
|
1338
|
+
const setHidden = useUiStore((s) => s.setDetailHidden);
|
|
1339
|
+
const detailRef = usePanelRef();
|
|
1340
|
+
const settle = useCanvasResize();
|
|
1341
|
+
const narrow = useNarrow();
|
|
1342
|
+
const open = selection !== null && !hidden;
|
|
1343
|
+
|
|
1344
|
+
// Picking something new is a request to see it. "]" means "not now", not
|
|
1345
|
+
// "never again", so the next selection brings the rail back rather than
|
|
1346
|
+
// landing silently behind a panel the reader forgot they folded away.
|
|
1347
|
+
const selectedId = selection?.id ?? null;
|
|
1348
|
+
useEffect(() => {
|
|
1349
|
+
if (selectedId !== null) setHidden(false);
|
|
1350
|
+
}, [selectedId, setHidden]);
|
|
1351
|
+
|
|
1352
|
+
useEffect(() => {
|
|
1353
|
+
const panel = detailRef.current;
|
|
1354
|
+
if (!panel) return;
|
|
1355
|
+
if (open) panel.expand();
|
|
1356
|
+
else panel.collapse();
|
|
1357
|
+
// `narrow` is in the list because the Group unmounts across the
|
|
1358
|
+
// breakpoint: the panel that comes back has to be told again.
|
|
1359
|
+
}, [open, narrow, detailRef]);
|
|
1360
|
+
|
|
1361
|
+
// Below the breakpoint there is no room for a third pane, so the rail becomes
|
|
1362
|
+
// a sheet over the page. Esc is already the app's "clear the selection", and
|
|
1363
|
+
// clearing the selection is exactly what closes this.
|
|
1364
|
+
if (narrow) {
|
|
1365
|
+
return (
|
|
1366
|
+
<div className="relative h-full min-h-0">
|
|
1367
|
+
{children}
|
|
1368
|
+
<SidePanel
|
|
1369
|
+
open={open}
|
|
1370
|
+
onClose={() => clear("panel")}
|
|
1371
|
+
side="right"
|
|
1372
|
+
label="Selection detail"
|
|
1373
|
+
width="85%"
|
|
1374
|
+
>
|
|
1375
|
+
<DetailPanel />
|
|
1376
|
+
</SidePanel>
|
|
1377
|
+
</div>
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
return (
|
|
1382
|
+
<SavedGroup
|
|
1383
|
+
id={`portolan:${id}`}
|
|
1384
|
+
orientation="horizontal"
|
|
1385
|
+
className="h-full min-h-0"
|
|
1386
|
+
>
|
|
1387
|
+
<Panel id="content" className="h-full min-w-0" onResize={settle}>
|
|
1388
|
+
{children}
|
|
1389
|
+
</Panel>
|
|
1390
|
+
|
|
1391
|
+
<ResizeHandle id="detail" />
|
|
1392
|
+
|
|
1393
|
+
<Panel
|
|
1394
|
+
id="detail"
|
|
1395
|
+
defaultSize="24"
|
|
1396
|
+
minSize="16"
|
|
1397
|
+
collapsible
|
|
1398
|
+
collapsedSize="0"
|
|
1399
|
+
panelRef={detailRef}
|
|
1400
|
+
className="h-full"
|
|
1401
|
+
onResize={settle}
|
|
1402
|
+
>
|
|
1403
|
+
<DetailPanel />
|
|
1404
|
+
</Panel>
|
|
1405
|
+
</SavedGroup>
|
|
1406
|
+
);
|
|
1407
|
+
}
|