@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,40 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
forgetComparison,
|
|
4
|
+
rememberedComparison,
|
|
5
|
+
rememberComparison,
|
|
6
|
+
} from "./comparison-memory";
|
|
7
|
+
|
|
8
|
+
function memoryStorage() {
|
|
9
|
+
const values = new Map<string, string>();
|
|
10
|
+
return {
|
|
11
|
+
getItem: (key: string) => values.get(key) ?? null,
|
|
12
|
+
setItem: (key: string, value: string) => values.set(key, value),
|
|
13
|
+
removeItem: (key: string) => values.delete(key),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.stubGlobal("localStorage", memoryStorage());
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
22
|
+
|
|
23
|
+
describe("comparison memory", () => {
|
|
24
|
+
it("remembers and forgets a valid branch pair", () => {
|
|
25
|
+
rememberComparison("main", "feature/catalog");
|
|
26
|
+
expect(rememberedComparison()).toEqual({ base: "main", head: "feature/catalog" });
|
|
27
|
+
forgetComparison();
|
|
28
|
+
expect(rememberedComparison()).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("does not remember a comparison of a branch with itself", () => {
|
|
32
|
+
rememberComparison("main", "main");
|
|
33
|
+
expect(rememberedComparison()).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("ignores malformed persisted state", () => {
|
|
37
|
+
localStorage.setItem("portolan:last-comparison", "not json");
|
|
38
|
+
expect(rememberedComparison()).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const KEY = "portolan:last-comparison";
|
|
2
|
+
|
|
3
|
+
export type RememberedComparison = {
|
|
4
|
+
base: string;
|
|
5
|
+
head: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function rememberedComparison(): RememberedComparison | null {
|
|
9
|
+
try {
|
|
10
|
+
const value = JSON.parse(localStorage.getItem(KEY) ?? "null") as unknown;
|
|
11
|
+
if (!value || typeof value !== "object") return null;
|
|
12
|
+
const { base, head } = value as Partial<RememberedComparison>;
|
|
13
|
+
return typeof base === "string" && base && typeof head === "string" && head
|
|
14
|
+
? { base, head }
|
|
15
|
+
: null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function rememberComparison(base: string, head: string): void {
|
|
22
|
+
try {
|
|
23
|
+
if (base && head && base !== head) {
|
|
24
|
+
localStorage.setItem(KEY, JSON.stringify({ base, head }));
|
|
25
|
+
} else {
|
|
26
|
+
localStorage.removeItem(KEY);
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// The URL remains the source of truth where storage is unavailable.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function forgetComparison(): void {
|
|
34
|
+
try {
|
|
35
|
+
localStorage.removeItem(KEY);
|
|
36
|
+
} catch {
|
|
37
|
+
// Nothing to forget where storage is unavailable.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
import { catalog } from "../data";
|
|
3
|
+
|
|
4
|
+
const CTX_SLOTS = 6;
|
|
5
|
+
|
|
6
|
+
/** Context colour is assigned deterministically by index in catalog order. */
|
|
7
|
+
const order: string[] = catalog.contexts.map((c) => c.id);
|
|
8
|
+
|
|
9
|
+
export function contextVar(contextId: string | null | undefined): string {
|
|
10
|
+
if (!contextId) return "var(--fg-muted)";
|
|
11
|
+
const i = order.indexOf(contextId);
|
|
12
|
+
if (i < 0) return "var(--fg-muted)";
|
|
13
|
+
return `var(--ctx-${i % CTX_SLOTS})`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Feeds the `ctx` utility. Setting one custom property beats generating a
|
|
18
|
+
* colour utility per context, and keeps the value out of the class string.
|
|
19
|
+
*/
|
|
20
|
+
export function ctxStyle(contextId: string | null | undefined): CSSProperties {
|
|
21
|
+
return { "--ctx": contextVar(contextId) } as CSSProperties;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function contextName(contextId: string | null | undefined): string {
|
|
25
|
+
if (!contextId) return "—";
|
|
26
|
+
return catalog.contexts.find((c) => c.id === contextId)?.name ?? contextId;
|
|
27
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { catalog } from "../testing/estate";
|
|
3
|
+
import { contextMap } from "./context-map";
|
|
4
|
+
import type { ContextRelation, PatternName } from "./context-map";
|
|
5
|
+
|
|
6
|
+
const map = contextMap(catalog);
|
|
7
|
+
const find = (id: string): ContextRelation => {
|
|
8
|
+
const found = map.find((r) => r.id === id);
|
|
9
|
+
if (!found) throw new Error(`no relation ${id}`);
|
|
10
|
+
return found;
|
|
11
|
+
};
|
|
12
|
+
const names = (relation: ContextRelation): PatternName[] =>
|
|
13
|
+
relation.patterns.map((p) => p.name);
|
|
14
|
+
|
|
15
|
+
describe("shape", () => {
|
|
16
|
+
it("draws every pair of contexts exactly once, in catalog order", () => {
|
|
17
|
+
const n = catalog.contexts.length;
|
|
18
|
+
expect(map).toHaveLength((n * (n - 1)) / 2);
|
|
19
|
+
expect(new Set(map.map((r) => r.id)).size).toBe(map.length);
|
|
20
|
+
const rank = new Map(catalog.contexts.map((c, i) => [c.id, i]));
|
|
21
|
+
for (const relation of map) {
|
|
22
|
+
expect(rank.get(relation.a)).toBeLessThan(rank.get(relation.b) ?? -1);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("puts the pairs that are wired together above the pairs that are not", () => {
|
|
27
|
+
const weights = map.map(
|
|
28
|
+
(r) =>
|
|
29
|
+
r.dependencies.reduce((n, d) => n + d.links.length, 0) +
|
|
30
|
+
r.shared.length,
|
|
31
|
+
);
|
|
32
|
+
expect([...weights].sort((x, y) => y - x)).toEqual(weights);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("gives every relation at least one pattern to explain itself", () => {
|
|
36
|
+
for (const relation of map) {
|
|
37
|
+
expect(relation.patterns.length, relation.id).toBeGreaterThan(0);
|
|
38
|
+
for (const pattern of relation.patterns) {
|
|
39
|
+
expect(
|
|
40
|
+
pattern.why.length,
|
|
41
|
+
`${relation.id}/${pattern.name}`,
|
|
42
|
+
).toBeGreaterThan(0);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("direction", () => {
|
|
49
|
+
it("makes the publisher of an event upstream of everyone who consumes it", () => {
|
|
50
|
+
const shopPayments = find("shop~payments");
|
|
51
|
+
const shopUp = shopPayments.dependencies.find((d) => d.upstream === "shop");
|
|
52
|
+
expect(shopUp?.links.map((l) => l.label)).toContain("OrderPlaced");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("makes the ANSWERING side of a call upstream — the caller lives with its model", () => {
|
|
56
|
+
// payments.ledger calls shop.v1.Orders/GetOrder, which shop.oms answers.
|
|
57
|
+
const shopUp = find("shop~payments").dependencies.find(
|
|
58
|
+
(d) => d.upstream === "shop",
|
|
59
|
+
);
|
|
60
|
+
const call = shopUp?.links.find((l) => l.kind === "rpc");
|
|
61
|
+
expect(call?.id).toBe("shop.v1.Orders/GetOrder");
|
|
62
|
+
expect(call?.to).toBe("payments.ledger");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("ignores an edge that does not leave the context — a map draws boundaries", () => {
|
|
66
|
+
for (const relation of map) {
|
|
67
|
+
for (const dependency of relation.dependencies) {
|
|
68
|
+
expect(dependency.upstream).not.toBe(dependency.downstream);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("ignores a consumer that is not in the catalog — that is a Problem, not a peer", () => {
|
|
74
|
+
// shop.oms.order.OrderPlaced names `analytics-sink`, which owns no context.
|
|
75
|
+
const ids = map.flatMap((r) =>
|
|
76
|
+
r.dependencies.flatMap((d) => d.links.map((l) => l.to)),
|
|
77
|
+
);
|
|
78
|
+
expect(ids).not.toContain("analytics-sink");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("patterns that are counted", () => {
|
|
83
|
+
it("calls it a partnership when the arrows run both ways", () => {
|
|
84
|
+
const relation = find("shop~payments");
|
|
85
|
+
expect(relation.dependencies).toHaveLength(2);
|
|
86
|
+
expect(names(relation)).toContain("partnership");
|
|
87
|
+
expect(names(relation)).not.toContain("customer-supplier");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("names the supplier and the customer when they run one way", () => {
|
|
91
|
+
const relation = find("payments~delivery");
|
|
92
|
+
const pattern = relation.patterns.find(
|
|
93
|
+
(p) => p.name === "customer-supplier",
|
|
94
|
+
);
|
|
95
|
+
expect(pattern?.upstream).toBe("payments");
|
|
96
|
+
expect(pattern?.downstream).toBe("delivery");
|
|
97
|
+
expect(pattern?.basis).toBe("counted");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("finds a kernel wherever both domains name the same definition", () => {
|
|
101
|
+
const relation = find("shop~payments");
|
|
102
|
+
expect(relation.shared.map((s) => s.def)).toEqual(["Money"]);
|
|
103
|
+
// And says which shapes, so the claim can be checked rather than believed.
|
|
104
|
+
expect(relation.shared[0]?.blocks["shop"]?.length).toBeGreaterThan(0);
|
|
105
|
+
expect(relation.shared[0]?.blocks["payments"]?.length).toBeGreaterThan(0);
|
|
106
|
+
expect(names(relation)).toContain("shared-kernel");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("holds no kernel where the two name nothing in common", () => {
|
|
110
|
+
expect(find("payments~delivery").shared).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("patterns that are only read", () => {
|
|
115
|
+
it("marks conformity and translation as read, never as counted", () => {
|
|
116
|
+
for (const relation of map) {
|
|
117
|
+
for (const pattern of relation.patterns) {
|
|
118
|
+
const inferred =
|
|
119
|
+
pattern.name === "conformist" ||
|
|
120
|
+
pattern.name === "anticorruption-layer";
|
|
121
|
+
expect(pattern.basis, pattern.name).toBe(inferred ? "read" : "counted");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("reads conformity from the downstream naming what the upstream sends", () => {
|
|
127
|
+
const relation = find("shop~delivery");
|
|
128
|
+
const pattern = relation.patterns.find((p) => p.name === "conformist");
|
|
129
|
+
expect(pattern?.why).toContain("Address");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("reads a translation boundary from the downstream naming none of it", () => {
|
|
133
|
+
const relation = find("payments~delivery");
|
|
134
|
+
const pattern = relation.patterns.find(
|
|
135
|
+
(p) => p.name === "anticorruption-layer",
|
|
136
|
+
);
|
|
137
|
+
expect(pattern?.upstream).toBe("payments");
|
|
138
|
+
expect(pattern?.downstream).toBe("delivery");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("reads nothing at all where there is no dependency to read it from", () => {
|
|
142
|
+
for (const relation of map) {
|
|
143
|
+
if (relation.dependencies.length > 0) continue;
|
|
144
|
+
expect(names(relation)).not.toContain("conformist");
|
|
145
|
+
expect(names(relation)).not.toContain("anticorruption-layer");
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// The context map, derived.
|
|
2
|
+
//
|
|
3
|
+
// A bounded context in this catalog states no relationships to its neighbours
|
|
4
|
+
// (see BoundedContext in catalog.ts): who talks to whom is already written in
|
|
5
|
+
// the events and the calls, and asking an estate to also declare it is asking
|
|
6
|
+
// it to keep a second copy that will disagree with the first. So the map is
|
|
7
|
+
// computed here, from the same two facts the dependency graph is drawn from -
|
|
8
|
+
// an event with a consumer, and a call with a peer - plus a third the graph
|
|
9
|
+
// throws away: which shared types the two sides both name.
|
|
10
|
+
//
|
|
11
|
+
// What can be COUNTED and what can only be READ are kept apart, and the page
|
|
12
|
+
// says which is which.
|
|
13
|
+
//
|
|
14
|
+
// counted - follows from the graph and cannot be otherwise. If the arrows
|
|
15
|
+
// run both ways the two contexts are partners; if they run one
|
|
16
|
+
// way one is the supplier and the other the customer; if the same
|
|
17
|
+
// type definition is named by shapes on both sides there is a
|
|
18
|
+
// kernel between them, whether or not anyone meant there to be.
|
|
19
|
+
//
|
|
20
|
+
// read - the shapes hint at it and nothing states it. A downstream that
|
|
21
|
+
// names the upstream's types in its own model has conformed to
|
|
22
|
+
// that model; one that names none of them is translating at the
|
|
23
|
+
// boundary, which is what an anticorruption layer is for. Both
|
|
24
|
+
// are readings of an absence or a presence, not declarations, and
|
|
25
|
+
// the map must never print them as if the estate had said so.
|
|
26
|
+
//
|
|
27
|
+
// Nothing here invents a relationship. Two contexts that neither depend on
|
|
28
|
+
// each other nor share a type go their separate ways, and that is a finding.
|
|
29
|
+
|
|
30
|
+
import type { Catalog, Status } from "../catalog";
|
|
31
|
+
|
|
32
|
+
/** One measured dependency: something the downstream takes from the upstream. */
|
|
33
|
+
export interface MapLink {
|
|
34
|
+
kind: "event" | "rpc";
|
|
35
|
+
/** Event id, or rpc call id. */
|
|
36
|
+
id: string;
|
|
37
|
+
/** What to print - the event name, or the method. */
|
|
38
|
+
label: string;
|
|
39
|
+
/** The service on the upstream side: it publishes the event, answers the call. */
|
|
40
|
+
from: string;
|
|
41
|
+
/** The service on the downstream side: it consumes, it calls. */
|
|
42
|
+
to: string;
|
|
43
|
+
status: Status;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Everything one context takes from another, in one direction. */
|
|
47
|
+
export interface ContextDependency {
|
|
48
|
+
/** Depended ON - its events are consumed, its rpc answered. */
|
|
49
|
+
upstream: string;
|
|
50
|
+
/** Depends - it consumes, it calls. */
|
|
51
|
+
downstream: string;
|
|
52
|
+
links: MapLink[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A type definition named by shapes owned on both sides of a relationship. */
|
|
56
|
+
export interface SharedKernel {
|
|
57
|
+
/** Key into `catalog.defs`. */
|
|
58
|
+
def: string;
|
|
59
|
+
/** Block ids on each side that name it, keyed by context. */
|
|
60
|
+
blocks: Record<string, string[]>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type PatternName =
|
|
64
|
+
| "partnership"
|
|
65
|
+
| "customer-supplier"
|
|
66
|
+
| "shared-kernel"
|
|
67
|
+
| "conformist"
|
|
68
|
+
| "anticorruption-layer"
|
|
69
|
+
| "separate-ways";
|
|
70
|
+
|
|
71
|
+
export interface MapPattern {
|
|
72
|
+
name: PatternName;
|
|
73
|
+
/**
|
|
74
|
+
* `counted` follows from the graph. `read` is a reading of what the shapes
|
|
75
|
+
* name, and is drawn as such - it is a question worth asking of the team,
|
|
76
|
+
* not an answer the catalog gave.
|
|
77
|
+
*/
|
|
78
|
+
basis: "counted" | "read";
|
|
79
|
+
/** Why this pattern, in one line, naming the numbers it was read from. */
|
|
80
|
+
why: string;
|
|
81
|
+
/** Set on the directional patterns; absent on the symmetric ones. */
|
|
82
|
+
upstream?: string;
|
|
83
|
+
downstream?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** One pair of contexts, and everything the catalog says stands between them. */
|
|
87
|
+
export interface ContextRelation {
|
|
88
|
+
/** The two contexts, in catalog order, so a pair has exactly one identity. */
|
|
89
|
+
a: string;
|
|
90
|
+
b: string;
|
|
91
|
+
/** `${a}~${b}`. */
|
|
92
|
+
id: string;
|
|
93
|
+
/** At most two - one per direction that carries anything. */
|
|
94
|
+
dependencies: ContextDependency[];
|
|
95
|
+
shared: SharedKernel[];
|
|
96
|
+
patterns: MapPattern[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// What each context owns and names
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
interface ContextFacts {
|
|
104
|
+
/** Defs named by the value objects and entities this context owns. */
|
|
105
|
+
modelDefs: Map<string, string[]>;
|
|
106
|
+
/** Defs carried by the events this context publishes. */
|
|
107
|
+
eventDefs: Set<string>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function factsFor(catalog: Catalog): Map<string, ContextFacts> {
|
|
111
|
+
const out = new Map<string, ContextFacts>();
|
|
112
|
+
for (const context of catalog.contexts) {
|
|
113
|
+
const modelDefs = new Map<string, string[]>();
|
|
114
|
+
const eventDefs = new Set<string>();
|
|
115
|
+
const note = (def: string, blockId: string): void => {
|
|
116
|
+
const list = modelDefs.get(def) ?? [];
|
|
117
|
+
if (!list.includes(blockId)) list.push(blockId);
|
|
118
|
+
modelDefs.set(def, list);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
for (const service of context.services) {
|
|
122
|
+
for (const aggregate of service.aggregates) {
|
|
123
|
+
for (const block of [
|
|
124
|
+
...aggregate.valueObjects,
|
|
125
|
+
...aggregate.entities,
|
|
126
|
+
]) {
|
|
127
|
+
if (block.ref) note(block.ref, block.id);
|
|
128
|
+
for (const field of block.fields ?? []) {
|
|
129
|
+
if (field.ref) note(field.ref, block.id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const event of aggregate.events) {
|
|
133
|
+
for (const version of event.versions) {
|
|
134
|
+
for (const field of version.fields) {
|
|
135
|
+
if (field.ref) eventDefs.add(field.ref);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
out.set(context.id, { modelDefs, eventDefs });
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Service id -> context id, for both ends of every edge. */
|
|
147
|
+
function serviceContexts(catalog: Catalog): Map<string, string> {
|
|
148
|
+
const out = new Map<string, string>();
|
|
149
|
+
for (const context of catalog.contexts) {
|
|
150
|
+
for (const service of context.services) out.set(service.id, context.id);
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// The map
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
const pairId = (a: string, b: string): string => `${a}~${b}`;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Every pair of contexts the catalog has anything to say about, plus the pairs
|
|
163
|
+
* it has nothing to say about - which is the finding "separate ways", and the
|
|
164
|
+
* only way a map can show that two domains were never wired together.
|
|
165
|
+
*/
|
|
166
|
+
export function contextMap(catalog: Catalog): ContextRelation[] {
|
|
167
|
+
const order = catalog.contexts.map((c) => c.id);
|
|
168
|
+
const rank = new Map(order.map((id, i) => [id, i]));
|
|
169
|
+
const owner = serviceContexts(catalog);
|
|
170
|
+
const facts = factsFor(catalog);
|
|
171
|
+
|
|
172
|
+
/** upstream -> downstream -> the links that run that way. */
|
|
173
|
+
const byDirection = new Map<string, MapLink[]>();
|
|
174
|
+
const addLink = (up: string, down: string, link: MapLink): void => {
|
|
175
|
+
const key = pairId(up, down);
|
|
176
|
+
const list = byDirection.get(key) ?? [];
|
|
177
|
+
list.push(link);
|
|
178
|
+
byDirection.set(key, list);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
for (const context of catalog.contexts) {
|
|
182
|
+
for (const service of context.services) {
|
|
183
|
+
// An event crossing a context boundary makes its publisher upstream.
|
|
184
|
+
for (const aggregate of service.aggregates) {
|
|
185
|
+
for (const event of aggregate.events) {
|
|
186
|
+
for (const consumer of event.consumers) {
|
|
187
|
+
const down = owner.get(consumer.service);
|
|
188
|
+
// A consumer outside the catalog is a Problem, not a relationship:
|
|
189
|
+
// there is no second context for the line to reach.
|
|
190
|
+
if (!down || down === context.id) continue;
|
|
191
|
+
addLink(context.id, down, {
|
|
192
|
+
kind: "event",
|
|
193
|
+
id: event.id,
|
|
194
|
+
label: event.name,
|
|
195
|
+
from: service.id,
|
|
196
|
+
to: consumer.service,
|
|
197
|
+
status: consumer.status,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// A call crossing a boundary makes the ANSWERING context upstream: the
|
|
204
|
+
// caller is the one who has to live with the other's model.
|
|
205
|
+
for (const call of service.consumes) {
|
|
206
|
+
const up = owner.get(call.peer);
|
|
207
|
+
if (!up || up === context.id) continue;
|
|
208
|
+
addLink(up, context.id, {
|
|
209
|
+
kind: "rpc",
|
|
210
|
+
id: call.id,
|
|
211
|
+
label: call.id,
|
|
212
|
+
from: call.peer,
|
|
213
|
+
to: service.id,
|
|
214
|
+
status: call.status,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const relations: ContextRelation[] = [];
|
|
221
|
+
for (let i = 0; i < order.length; i += 1) {
|
|
222
|
+
for (let j = i + 1; j < order.length; j += 1) {
|
|
223
|
+
const a = order[i] as string;
|
|
224
|
+
const b = order[j] as string;
|
|
225
|
+
|
|
226
|
+
const dependencies: ContextDependency[] = [];
|
|
227
|
+
const aUp = byDirection.get(pairId(a, b));
|
|
228
|
+
const bUp = byDirection.get(pairId(b, a));
|
|
229
|
+
if (aUp?.length) {
|
|
230
|
+
dependencies.push({ upstream: a, downstream: b, links: aUp });
|
|
231
|
+
}
|
|
232
|
+
if (bUp?.length) {
|
|
233
|
+
dependencies.push({ upstream: b, downstream: a, links: bUp });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const shared = sharedKernel(facts, a, b);
|
|
237
|
+
relations.push({
|
|
238
|
+
a,
|
|
239
|
+
b,
|
|
240
|
+
id: pairId(a, b),
|
|
241
|
+
dependencies,
|
|
242
|
+
shared,
|
|
243
|
+
patterns: patternsFor(facts, dependencies, shared),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Loudest first: a pair that is wired together says more than one that is
|
|
249
|
+
// not, and the pairs that say nothing sink to the bottom where they read as
|
|
250
|
+
// the list of domains nobody has connected.
|
|
251
|
+
return relations.sort(
|
|
252
|
+
(x, y) =>
|
|
253
|
+
weight(y) - weight(x) ||
|
|
254
|
+
(rank.get(x.a) ?? 0) - (rank.get(y.a) ?? 0) ||
|
|
255
|
+
(rank.get(x.b) ?? 0) - (rank.get(y.b) ?? 0),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function weight(relation: ContextRelation): number {
|
|
260
|
+
return (
|
|
261
|
+
relation.dependencies.reduce((n, d) => n + d.links.length, 0) +
|
|
262
|
+
relation.shared.length
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Types both contexts name in shapes of their own. */
|
|
267
|
+
function sharedKernel(
|
|
268
|
+
facts: Map<string, ContextFacts>,
|
|
269
|
+
a: string,
|
|
270
|
+
b: string,
|
|
271
|
+
): SharedKernel[] {
|
|
272
|
+
const left = facts.get(a)?.modelDefs;
|
|
273
|
+
const right = facts.get(b)?.modelDefs;
|
|
274
|
+
if (!left || !right) return [];
|
|
275
|
+
const out: SharedKernel[] = [];
|
|
276
|
+
for (const [def, blocks] of left) {
|
|
277
|
+
const other = right.get(def);
|
|
278
|
+
if (!other) continue;
|
|
279
|
+
out.push({ def, blocks: { [a]: blocks, [b]: other } });
|
|
280
|
+
}
|
|
281
|
+
return out.sort((x, y) => x.def.localeCompare(y.def));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The patterns, in the order a reader wants them: what the two contexts ARE to
|
|
286
|
+
* each other first, then what is shared, then how the downstream is holding
|
|
287
|
+
* the upstream's model.
|
|
288
|
+
*/
|
|
289
|
+
function patternsFor(
|
|
290
|
+
facts: Map<string, ContextFacts>,
|
|
291
|
+
dependencies: ContextDependency[],
|
|
292
|
+
shared: SharedKernel[],
|
|
293
|
+
): MapPattern[] {
|
|
294
|
+
const out: MapPattern[] = [];
|
|
295
|
+
|
|
296
|
+
if (dependencies.length === 0 && shared.length === 0) {
|
|
297
|
+
return [
|
|
298
|
+
{
|
|
299
|
+
name: "separate-ways",
|
|
300
|
+
basis: "counted",
|
|
301
|
+
why: "no event, no call and no shared type joins these two domains",
|
|
302
|
+
},
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (dependencies.length === 2) {
|
|
307
|
+
out.push({
|
|
308
|
+
name: "partnership",
|
|
309
|
+
basis: "counted",
|
|
310
|
+
why: `each depends on the other — ${describe(dependencies[0])} and ${describe(dependencies[1])}`,
|
|
311
|
+
});
|
|
312
|
+
} else if (dependencies.length === 1) {
|
|
313
|
+
const only = dependencies[0] as ContextDependency;
|
|
314
|
+
out.push({
|
|
315
|
+
name: "customer-supplier",
|
|
316
|
+
basis: "counted",
|
|
317
|
+
why: `${only.downstream} depends on ${only.upstream} and not the other way — ${describe(only)}`,
|
|
318
|
+
upstream: only.upstream,
|
|
319
|
+
downstream: only.downstream,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (shared.length > 0) {
|
|
324
|
+
out.push({
|
|
325
|
+
name: "shared-kernel",
|
|
326
|
+
basis: "counted",
|
|
327
|
+
why: `${shared.length === 1 ? "one type is" : `${shared.length} types are`} named by shapes both domains own: ${shared.map((s) => s.def).join(", ")}`,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// How the downstream holds the upstream's model. Read per direction, and
|
|
332
|
+
// only where there is a dependency to read it from: with no arrow between
|
|
333
|
+
// them there is no model to conform to or translate.
|
|
334
|
+
for (const dependency of dependencies) {
|
|
335
|
+
const carried = facts.get(dependency.upstream)?.eventDefs;
|
|
336
|
+
const named = facts.get(dependency.downstream)?.modelDefs;
|
|
337
|
+
if (!carried || !named || carried.size === 0) continue;
|
|
338
|
+
const adopted = [...carried].filter((def) => named.has(def)).sort();
|
|
339
|
+
|
|
340
|
+
out.push(
|
|
341
|
+
adopted.length > 0
|
|
342
|
+
? {
|
|
343
|
+
name: "conformist",
|
|
344
|
+
basis: "read",
|
|
345
|
+
why: `${dependency.downstream} names ${adopted.join(", ")} in its own model — the same ${adopted.length === 1 ? "type" : "types"} ${dependency.upstream} puts on the wire`,
|
|
346
|
+
upstream: dependency.upstream,
|
|
347
|
+
downstream: dependency.downstream,
|
|
348
|
+
}
|
|
349
|
+
: {
|
|
350
|
+
name: "anticorruption-layer",
|
|
351
|
+
basis: "read",
|
|
352
|
+
why: `${dependency.downstream} names none of the ${carried.size === 1 ? "one type" : `${carried.size} types`} ${dependency.upstream} puts on the wire — something translates at the boundary`,
|
|
353
|
+
upstream: dependency.upstream,
|
|
354
|
+
downstream: dependency.downstream,
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function describe(dependency: ContextDependency | undefined): string {
|
|
363
|
+
if (!dependency) return "";
|
|
364
|
+
const events = dependency.links.filter((l) => l.kind === "event").length;
|
|
365
|
+
const calls = dependency.links.length - events;
|
|
366
|
+
const parts: string[] = [];
|
|
367
|
+
if (events > 0) parts.push(`${events} ${events === 1 ? "event" : "events"}`);
|
|
368
|
+
if (calls > 0) parts.push(`${calls} ${calls === 1 ? "call" : "calls"}`);
|
|
369
|
+
return `${dependency.downstream} takes ${parts.join(" and ")} from ${dependency.upstream}`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
// What the page prints
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
|
|
376
|
+
export const PATTERN_LABEL: Record<PatternName, string> = {
|
|
377
|
+
partnership: "partnership",
|
|
378
|
+
"customer-supplier": "customer / supplier",
|
|
379
|
+
"shared-kernel": "shared kernel",
|
|
380
|
+
conformist: "conformist",
|
|
381
|
+
"anticorruption-layer": "anticorruption layer",
|
|
382
|
+
"separate-ways": "separate ways",
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/** What the pattern means, for the reader who has not read Evans this month. */
|
|
386
|
+
export const PATTERN_MEANING: Record<PatternName, string> = {
|
|
387
|
+
partnership:
|
|
388
|
+
"Neither can move without the other. A change on either side is a change to both.",
|
|
389
|
+
"customer-supplier":
|
|
390
|
+
"One side is upstream: it can change and the other has to follow. Whose schedule wins is the question this raises.",
|
|
391
|
+
"shared-kernel":
|
|
392
|
+
"A piece of model both domains own. It cannot be changed by one of them alone.",
|
|
393
|
+
conformist:
|
|
394
|
+
"The downstream has taken the upstream's model as its own, translating nothing.",
|
|
395
|
+
"anticorruption-layer":
|
|
396
|
+
"The downstream keeps its own model and translates at the boundary.",
|
|
397
|
+
"separate-ways":
|
|
398
|
+
"Nothing joins them. Integrating them later is a decision nobody has taken yet.",
|
|
399
|
+
};
|