@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,80 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
SEQUENCE_MAX_STEPS,
|
|
4
|
+
defaultVariant,
|
|
5
|
+
readPrefs,
|
|
6
|
+
writePrefs,
|
|
7
|
+
} from "./prefs";
|
|
8
|
+
|
|
9
|
+
/** The node test environment has no localStorage; this is the whole of one. */
|
|
10
|
+
function stubStorage(): Map<string, string> {
|
|
11
|
+
const store = new Map<string, string>();
|
|
12
|
+
vi.stubGlobal("localStorage", {
|
|
13
|
+
getItem: (key: string) => store.get(key) ?? null,
|
|
14
|
+
setItem: (key: string, value: string) => void store.set(key, value),
|
|
15
|
+
removeItem: (key: string) => void store.delete(key),
|
|
16
|
+
});
|
|
17
|
+
return store;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("defaultVariant", () => {
|
|
21
|
+
it("keeps a short flow as a sequence, where order is position", () => {
|
|
22
|
+
expect(defaultVariant(1)).toBe("sequence");
|
|
23
|
+
expect(defaultVariant(SEQUENCE_MAX_STEPS)).toBe("sequence");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("folds a long flow onto the diagram, which fits on a screen", () => {
|
|
27
|
+
expect(defaultVariant(SEQUENCE_MAX_STEPS + 1)).toBe("diagram");
|
|
28
|
+
expect(defaultVariant(45)).toBe("diagram");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("readPrefs", () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
stubStorage();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("falls back to the caller's default when nothing was remembered", () => {
|
|
38
|
+
expect(readPrefs("checkout", "diagram")).toEqual({
|
|
39
|
+
variant: "diagram",
|
|
40
|
+
expanded: false,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("remembers per flow rather than globally", () => {
|
|
45
|
+
writePrefs("checkout", { variant: "sequence", expanded: true });
|
|
46
|
+
expect(readPrefs("checkout", "diagram").variant).toBe("sequence");
|
|
47
|
+
expect(readPrefs("refund-requested", "diagram").variant).toBe("diagram");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("ignores a stored value that is not a variant", () => {
|
|
51
|
+
localStorage.setItem(
|
|
52
|
+
"portolan.flow.checkout",
|
|
53
|
+
JSON.stringify({ variant: "isometric", expanded: true }),
|
|
54
|
+
);
|
|
55
|
+
const prefs = readPrefs("checkout", "sequence");
|
|
56
|
+
expect(prefs.variant).toBe("sequence");
|
|
57
|
+
// The half of the record that parsed is still honoured: one bad field is
|
|
58
|
+
// not a reason to forget the other one.
|
|
59
|
+
expect(prefs.expanded).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("survives storage that is broken rather than merely empty", () => {
|
|
63
|
+
localStorage.setItem("portolan.flow.checkout", "{not json");
|
|
64
|
+
expect(readPrefs("checkout", "sequence")).toEqual({
|
|
65
|
+
variant: "sequence",
|
|
66
|
+
expanded: false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
vi.stubGlobal("localStorage", {
|
|
70
|
+
getItem: () => {
|
|
71
|
+
throw new Error("private mode");
|
|
72
|
+
},
|
|
73
|
+
setItem: () => {
|
|
74
|
+
throw new Error("private mode");
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
expect(readPrefs("checkout", "diagram").variant).toBe("diagram");
|
|
78
|
+
expect(() => writePrefs("checkout", { variant: "sequence", expanded: false })).not.toThrow();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// What the flow page remembers, per flow.
|
|
2
|
+
//
|
|
3
|
+
// Two settings, and both are per-flow rather than global on purpose. Which
|
|
4
|
+
// variant reads better depends on how long the sequence is, and whether the
|
|
5
|
+
// summary is worth two lines or ten depends on what the summary says — neither
|
|
6
|
+
// is a statement about how this reader likes flow pages in general, so neither
|
|
7
|
+
// belongs in a global preference.
|
|
8
|
+
|
|
9
|
+
import { useCallback, useState } from "react";
|
|
10
|
+
|
|
11
|
+
export type Variant = "diagram" | "sequence";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Above this many steps a sequence rendered at a readable zoom is longer than
|
|
15
|
+
* any screen, and the diagram — which folds the same steps onto the lanes that
|
|
16
|
+
* repeat — is the one that fits. Below it the sequence is strictly better,
|
|
17
|
+
* because the order is the whole point of a flow and only the sequence draws
|
|
18
|
+
* order as position.
|
|
19
|
+
*/
|
|
20
|
+
export const SEQUENCE_MAX_STEPS = 12;
|
|
21
|
+
|
|
22
|
+
export function defaultVariant(stepCount: number): Variant {
|
|
23
|
+
return stepCount > SEQUENCE_MAX_STEPS ? "diagram" : "sequence";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface FlowPrefs {
|
|
27
|
+
variant: Variant;
|
|
28
|
+
/** The summary is shown in full rather than clamped to two lines. */
|
|
29
|
+
expanded: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const key = (slug: string): string => `portolan.flow.${slug}`;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* localStorage, but a private-mode failure is not worth a crash: a preference
|
|
36
|
+
* that cannot be remembered is a preference that starts at its default.
|
|
37
|
+
*/
|
|
38
|
+
export function readPrefs(slug: string, fallback: Variant): FlowPrefs {
|
|
39
|
+
const defaults: FlowPrefs = { variant: fallback, expanded: false };
|
|
40
|
+
try {
|
|
41
|
+
const raw = localStorage.getItem(key(slug));
|
|
42
|
+
if (!raw) return defaults;
|
|
43
|
+
const parsed: unknown = JSON.parse(raw);
|
|
44
|
+
if (typeof parsed !== "object" || parsed === null) return defaults;
|
|
45
|
+
const value = parsed as Partial<FlowPrefs>;
|
|
46
|
+
return {
|
|
47
|
+
variant:
|
|
48
|
+
value.variant === "diagram" || value.variant === "sequence"
|
|
49
|
+
? value.variant
|
|
50
|
+
: fallback,
|
|
51
|
+
expanded: value.expanded === true,
|
|
52
|
+
};
|
|
53
|
+
} catch {
|
|
54
|
+
return defaults;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function writePrefs(slug: string, prefs: FlowPrefs): void {
|
|
59
|
+
try {
|
|
60
|
+
localStorage.setItem(key(slug), JSON.stringify(prefs));
|
|
61
|
+
} catch {
|
|
62
|
+
/* private mode: this session still switches, it just does not persist */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The remembered settings for one flow, and one setter for changing part of
|
|
68
|
+
* them. Re-reads when the slug changes, so walking from flow to flow picks up
|
|
69
|
+
* each one's own choice rather than carrying the last one along.
|
|
70
|
+
*/
|
|
71
|
+
export function useFlowPrefs(
|
|
72
|
+
slug: string,
|
|
73
|
+
stepCount: number,
|
|
74
|
+
): [FlowPrefs, (patch: Partial<FlowPrefs>) => void] {
|
|
75
|
+
const fallback = defaultVariant(stepCount);
|
|
76
|
+
const [state, setState] = useState<{ slug: string; prefs: FlowPrefs }>(() => ({
|
|
77
|
+
slug,
|
|
78
|
+
prefs: readPrefs(slug, fallback),
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
// Derived during render rather than in an effect: an effect would draw one
|
|
82
|
+
// frame of the previous flow's settings before correcting itself, and on
|
|
83
|
+
// this page that frame is a whole re-layout of the canvas.
|
|
84
|
+
const prefs =
|
|
85
|
+
state.slug === slug ? state.prefs : readPrefs(slug, defaultVariant(stepCount));
|
|
86
|
+
if (state.slug !== slug) setState({ slug, prefs });
|
|
87
|
+
|
|
88
|
+
const update = useCallback(
|
|
89
|
+
(patch: Partial<FlowPrefs>) => {
|
|
90
|
+
setState((current) => {
|
|
91
|
+
const base =
|
|
92
|
+
current.slug === slug
|
|
93
|
+
? current.prefs
|
|
94
|
+
: readPrefs(slug, defaultVariant(stepCount));
|
|
95
|
+
const next = { ...base, ...patch };
|
|
96
|
+
writePrefs(slug, next);
|
|
97
|
+
return { slug, prefs: next };
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
[slug, stepCount],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return [prefs, update];
|
|
104
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// The dependency graph, drawn.
|
|
2
|
+
//
|
|
3
|
+
// Events are nodes here, not edge labels. The old canvas wrote an event's name
|
|
4
|
+
// once per consumer and hung it on a line, so the sample's nine events became
|
|
5
|
+
// twenty-one pieces of text lying across the densest part of the picture. A
|
|
6
|
+
// pill written once, with bare lines into and out of it, says the same thing
|
|
7
|
+
// with a third of the ink and gives the reader something to click.
|
|
8
|
+
//
|
|
9
|
+
// Everything geometric is elk's: the positions AND the routes. Nothing here
|
|
10
|
+
// invents a curve.
|
|
11
|
+
|
|
12
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
13
|
+
import { useNavigate } from "react-router";
|
|
14
|
+
import {
|
|
15
|
+
Background,
|
|
16
|
+
MiniMap,
|
|
17
|
+
ReactFlow,
|
|
18
|
+
ReactFlowProvider,
|
|
19
|
+
useStore,
|
|
20
|
+
} from "@xyflow/react";
|
|
21
|
+
import type { Edge, Node } from "@xyflow/react";
|
|
22
|
+
import "@xyflow/react/dist/style.css";
|
|
23
|
+
import { DiagramSkeleton } from "../components/DiagramSkeleton";
|
|
24
|
+
import { contextVar } from "../lib/context-color";
|
|
25
|
+
import type { EventGraph } from "../lib/event-graph";
|
|
26
|
+
import { bundles } from "../lib/event-graph";
|
|
27
|
+
import { servicePath } from "../routes";
|
|
28
|
+
import { useSelectionStore } from "../selection/store";
|
|
29
|
+
import { dependencyNodeTypes, TinyZoom } from "./DependencyNodes";
|
|
30
|
+
import type { DependencyNode } from "./DependencyNodes";
|
|
31
|
+
import { catalogIdOf, EVENT_NODE, layoutDependencyGraph } from "./dependency-layout";
|
|
32
|
+
import type { GraphMode, Layout } from "./dependency-layout";
|
|
33
|
+
import { dependencyEdgeTypes } from "./RoutedEdge";
|
|
34
|
+
import { FIT_OPTIONS, GraphToolbar } from "./GraphToolbar";
|
|
35
|
+
import { EDGE_W, EDGE_W_LIT, LEGIBLE_ZOOM } from "./theme";
|
|
36
|
+
|
|
37
|
+
/** How far out of the way everything more than one hop from the subject goes. */
|
|
38
|
+
const DIM_EDGE = 0.1;
|
|
39
|
+
const DIM_NODE = 0.2;
|
|
40
|
+
|
|
41
|
+
/** Past this many boxes the canvas is bigger than the frame; below it, it is not. */
|
|
42
|
+
const MINIMAP_FROM = 12;
|
|
43
|
+
|
|
44
|
+
export interface DependencyGraphProps {
|
|
45
|
+
graph: EventGraph;
|
|
46
|
+
mode: GraphMode;
|
|
47
|
+
onMode: (mode: GraphMode) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Changes whenever the filters do. Remounting the flow on a new value is
|
|
50
|
+
* what makes fitView measure the layout that just arrived rather than the
|
|
51
|
+
* one it replaced.
|
|
52
|
+
*/
|
|
53
|
+
fitKey: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function DependencyGraph({
|
|
57
|
+
graph,
|
|
58
|
+
mode,
|
|
59
|
+
onMode,
|
|
60
|
+
fitKey,
|
|
61
|
+
}: DependencyGraphProps) {
|
|
62
|
+
const navigate = useNavigate();
|
|
63
|
+
const [layout, setLayout] = useState<Layout | null>(null);
|
|
64
|
+
const [focusing, setFocusing] = useState(false);
|
|
65
|
+
const [focused, setFocused] = useState<string | null>(null);
|
|
66
|
+
|
|
67
|
+
const selectionId = useSelectionStore((s) => s.selection?.id ?? null);
|
|
68
|
+
const select = useSelectionStore((s) => s.select);
|
|
69
|
+
const clear = useSelectionStore((s) => s.clear);
|
|
70
|
+
|
|
71
|
+
// Below the legible zoom an 11px label is a smudge, so pills give up their
|
|
72
|
+
// name and keep their icon. A boolean, not the zoom: a pinch that stays on
|
|
73
|
+
// one side of the threshold re-renders nothing.
|
|
74
|
+
const tiny = useStore((s) => s.transform[2] < LEGIBLE_ZOOM);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
let cancelled = false;
|
|
78
|
+
setLayout(null);
|
|
79
|
+
void layoutDependencyGraph(graph, mode).then((next) => {
|
|
80
|
+
if (!cancelled) setLayout(next);
|
|
81
|
+
});
|
|
82
|
+
return () => {
|
|
83
|
+
cancelled = true;
|
|
84
|
+
};
|
|
85
|
+
}, [graph, mode]);
|
|
86
|
+
|
|
87
|
+
// --- focus -------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
// Esc unwinds the innermost thing first. It is caught on the way DOWN so it
|
|
90
|
+
// gets ahead of the shell's "Esc clears the selection": a reader who has
|
|
91
|
+
// dimmed the canvas means the dimming when they press it.
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!focused) return;
|
|
94
|
+
const onKey = (e: KeyboardEvent) => {
|
|
95
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
setFocused(null);
|
|
98
|
+
};
|
|
99
|
+
window.addEventListener("keydown", onKey, true);
|
|
100
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
101
|
+
}, [focused]);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* One hop, in the sense a reader means it.
|
|
105
|
+
*
|
|
106
|
+
* In events mode the graph is literally bipartite, so one hop from a service
|
|
107
|
+
* reaches only pills - focusing would dim every other service to nothing and
|
|
108
|
+
* answer no question at all. The hop that is being counted is the dependency:
|
|
109
|
+
* this service, the events on either side of it, and whoever is at the far
|
|
110
|
+
* end of those events.
|
|
111
|
+
*/
|
|
112
|
+
const neighbourhood = useMemo(() => {
|
|
113
|
+
if (!focused) return null;
|
|
114
|
+
const nodes = new Set<string>([focused]);
|
|
115
|
+
const edges = new Set<string>();
|
|
116
|
+
if (mode === "compact") {
|
|
117
|
+
for (const bundle of bundles(graph)) {
|
|
118
|
+
if (bundle.from !== focused && bundle.to !== focused) continue;
|
|
119
|
+
nodes.add(bundle.from);
|
|
120
|
+
nodes.add(bundle.to);
|
|
121
|
+
edges.add(bundle.id);
|
|
122
|
+
}
|
|
123
|
+
return { nodes, edges };
|
|
124
|
+
}
|
|
125
|
+
for (const event of graph.events) {
|
|
126
|
+
const touches =
|
|
127
|
+
event.publisher === focused ||
|
|
128
|
+
event.consumers.some((c) => c.service === focused);
|
|
129
|
+
if (!touches) continue;
|
|
130
|
+
nodes.add(EVENT_NODE(event.id));
|
|
131
|
+
nodes.add(event.publisher);
|
|
132
|
+
edges.add(`pub:${event.id}`);
|
|
133
|
+
for (const consumer of event.consumers) {
|
|
134
|
+
if (consumer.self) continue;
|
|
135
|
+
nodes.add(consumer.service);
|
|
136
|
+
edges.add(`con:${event.id}->${consumer.service}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { nodes, edges };
|
|
140
|
+
}, [focused, graph, mode]);
|
|
141
|
+
|
|
142
|
+
// --- what actually gets handed to React Flow ---------------------------
|
|
143
|
+
|
|
144
|
+
const nodes: DependencyNode[] = useMemo(() => {
|
|
145
|
+
const built = layout?.nodes ?? [];
|
|
146
|
+
return built.map((node) => ({
|
|
147
|
+
...node,
|
|
148
|
+
selected:
|
|
149
|
+
node.id === selectionId ||
|
|
150
|
+
(node.type === "event" && node.id === EVENT_NODE(selectionId ?? "")),
|
|
151
|
+
style: {
|
|
152
|
+
...node.style,
|
|
153
|
+
opacity:
|
|
154
|
+
neighbourhood && !neighbourhood.nodes.has(node.id) ? DIM_NODE : 1,
|
|
155
|
+
},
|
|
156
|
+
}));
|
|
157
|
+
}, [layout, selectionId, neighbourhood]);
|
|
158
|
+
|
|
159
|
+
const edges: Edge[] = useMemo(() => {
|
|
160
|
+
const built = layout?.edges ?? [];
|
|
161
|
+
return built.map((edge) => {
|
|
162
|
+
const dimmed = neighbourhood ? !neighbourhood.edges.has(edge.id) : false;
|
|
163
|
+
// A selected event lights the lines that carry it. Selection thickens;
|
|
164
|
+
// focus dims. They are different questions and they read differently.
|
|
165
|
+
const lit =
|
|
166
|
+
selectionId !== null &&
|
|
167
|
+
(edge.data?.["eventId"] === selectionId ||
|
|
168
|
+
edge.data?.["bundleId"] === selectionId);
|
|
169
|
+
return {
|
|
170
|
+
...edge,
|
|
171
|
+
selected: edge.id === selectionId,
|
|
172
|
+
style: {
|
|
173
|
+
...edge.style,
|
|
174
|
+
opacity: dimmed ? DIM_EDGE : 1,
|
|
175
|
+
strokeWidth: lit ? EDGE_W_LIT : (edge.style?.strokeWidth ?? EDGE_W),
|
|
176
|
+
},
|
|
177
|
+
zIndex: lit ? 10 : 0,
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
}, [layout, neighbourhood, selectionId]);
|
|
181
|
+
|
|
182
|
+
// --- interaction -------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
const onNodeClick = useCallback(
|
|
185
|
+
(_: React.MouseEvent, node: Node) => {
|
|
186
|
+
if (focusing && node.type === "service") setFocused(node.id);
|
|
187
|
+
select(catalogIdOf(node.id), "diagram");
|
|
188
|
+
},
|
|
189
|
+
[focusing, select],
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const onNodeDoubleClick = useCallback(
|
|
193
|
+
(_: React.MouseEvent, node: Node) => {
|
|
194
|
+
if (node.type !== "service") return;
|
|
195
|
+
const to = servicePath(node.id);
|
|
196
|
+
if (to) navigate(to);
|
|
197
|
+
},
|
|
198
|
+
[navigate],
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const onEdgeClick = useCallback(
|
|
202
|
+
(_: React.MouseEvent, edge: Edge) => {
|
|
203
|
+
const bundleId = edge.data?.["bundleId"];
|
|
204
|
+
if (typeof bundleId === "string") {
|
|
205
|
+
select(bundleId, "diagram");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const eventId = edge.data?.["eventId"];
|
|
209
|
+
if (typeof eventId === "string") select(eventId, "diagram");
|
|
210
|
+
},
|
|
211
|
+
[select],
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const ready = layout !== null;
|
|
215
|
+
const minimap = nodes.length > MINIMAP_FROM;
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
/* The box is the box whether elk has answered or not: the skeleton is
|
|
219
|
+
laid over it, never in place of it, so nothing reflows on arrival. */
|
|
220
|
+
<div className="relative h-full w-full">
|
|
221
|
+
{ready ? null : <DiagramSkeleton />}
|
|
222
|
+
<TinyZoom.Provider value={tiny}>
|
|
223
|
+
<ReactFlow
|
|
224
|
+
nodes={nodes}
|
|
225
|
+
edges={edges}
|
|
226
|
+
nodeTypes={dependencyNodeTypes}
|
|
227
|
+
edgeTypes={dependencyEdgeTypes}
|
|
228
|
+
onNodeClick={onNodeClick}
|
|
229
|
+
onNodeDoubleClick={onNodeDoubleClick}
|
|
230
|
+
onEdgeClick={onEdgeClick}
|
|
231
|
+
onPaneClick={() => {
|
|
232
|
+
setFocused(null);
|
|
233
|
+
clear("diagram");
|
|
234
|
+
}}
|
|
235
|
+
nodesDraggable={false}
|
|
236
|
+
nodesConnectable={false}
|
|
237
|
+
elementsSelectable
|
|
238
|
+
proOptions={{ hideAttribution: true }}
|
|
239
|
+
fitView
|
|
240
|
+
fitViewOptions={FIT_OPTIONS}
|
|
241
|
+
minZoom={0.08}
|
|
242
|
+
maxZoom={2}
|
|
243
|
+
key={ready ? `fit-${fitKey}-${nodes.length}-${edges.length}` : "pending"}
|
|
244
|
+
>
|
|
245
|
+
<Background gap={20} size={2} />
|
|
246
|
+
<GraphToolbar
|
|
247
|
+
mode={mode}
|
|
248
|
+
onMode={onMode}
|
|
249
|
+
focusing={focusing}
|
|
250
|
+
onFocusing={(on) => {
|
|
251
|
+
setFocusing(on);
|
|
252
|
+
if (!on) setFocused(null);
|
|
253
|
+
}}
|
|
254
|
+
focused={focused}
|
|
255
|
+
/>
|
|
256
|
+
{minimap ? (
|
|
257
|
+
<MiniMap
|
|
258
|
+
position="bottom-right"
|
|
259
|
+
pannable
|
|
260
|
+
zoomable
|
|
261
|
+
ariaLabel="Graph overview"
|
|
262
|
+
nodeBorderRadius={4}
|
|
263
|
+
nodeColor={(node) =>
|
|
264
|
+
contextVar(
|
|
265
|
+
(node.data as { context?: string | null }).context ?? null,
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
nodeStrokeWidth={0}
|
|
269
|
+
/>
|
|
270
|
+
) : null}
|
|
271
|
+
</ReactFlow>
|
|
272
|
+
</TinyZoom.Provider>
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function DependencyGraphPane(props: DependencyGraphProps) {
|
|
278
|
+
return (
|
|
279
|
+
<ReactFlowProvider>
|
|
280
|
+
<DependencyGraph {...props} />
|
|
281
|
+
</ReactFlowProvider>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// The dependency graph's two node kinds.
|
|
2
|
+
//
|
|
3
|
+
// A service is a card and an event is a pill, and the difference in
|
|
4
|
+
// silhouette is doing most of the work: a reader scanning the canvas can tell
|
|
5
|
+
// which of the two rows they are looking at without reading a word of either.
|
|
6
|
+
|
|
7
|
+
import { createContext, useContext } from "react";
|
|
8
|
+
import { Handle, Position } from "@xyflow/react";
|
|
9
|
+
import type { Node, NodeProps } from "@xyflow/react";
|
|
10
|
+
import { Server, Zap } from "lucide-react";
|
|
11
|
+
import { contextVar } from "../lib/context-color";
|
|
12
|
+
import { EVENT_ICON_W } from "./theme";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* True when the viewport is zoomed out past the point where an 11px label is
|
|
16
|
+
* a word. It is a context rather than node data so that crossing the threshold
|
|
17
|
+
* re-renders the pills without rebuilding every node object - and it is a
|
|
18
|
+
* boolean rather than the zoom, so a pinch that stays on one side of the line
|
|
19
|
+
* costs nothing at all.
|
|
20
|
+
*/
|
|
21
|
+
export const TinyZoom = createContext(false);
|
|
22
|
+
|
|
23
|
+
export interface ServiceNodeData {
|
|
24
|
+
kind: "service";
|
|
25
|
+
label: string;
|
|
26
|
+
context: string | null;
|
|
27
|
+
ghost: boolean;
|
|
28
|
+
publishes: number;
|
|
29
|
+
consumes: number;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface EventNodeData {
|
|
34
|
+
kind: "event";
|
|
35
|
+
label: string;
|
|
36
|
+
/** the publishing service's context; the pill borrows its colour */
|
|
37
|
+
context: string | null;
|
|
38
|
+
eventId: string;
|
|
39
|
+
publisher: string;
|
|
40
|
+
/** the publisher consumes it too */
|
|
41
|
+
self: boolean;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type ServiceNodeType = Node<ServiceNodeData, "service">;
|
|
46
|
+
export type EventNodeType = Node<EventNodeData, "event">;
|
|
47
|
+
export type DependencyNode = ServiceNodeType | EventNodeType;
|
|
48
|
+
|
|
49
|
+
const HANDLE = { opacity: 0, width: 1, height: 1 } as const;
|
|
50
|
+
|
|
51
|
+
export function ServiceNode({ data }: NodeProps<ServiceNodeType>) {
|
|
52
|
+
const accent = data.ghost ? "var(--status-unresolved)" : contextVar(data.context);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className={`flex h-full w-full items-center gap-2 px-2.5 ${
|
|
57
|
+
data.ghost ? "flow-card-ghost" : "flow-card"
|
|
58
|
+
}`}
|
|
59
|
+
style={{ color: data.ghost ? "var(--status-unresolved)" : "var(--fg)" }}
|
|
60
|
+
title={data.ghost ? `${data.label} — not in the catalog` : data.label}
|
|
61
|
+
>
|
|
62
|
+
<span className="flow-tile" style={{ color: accent }}>
|
|
63
|
+
<Server size={12} aria-hidden />
|
|
64
|
+
</span>
|
|
65
|
+
<div className="flex min-w-0 flex-1 flex-col justify-center gap-0.5">
|
|
66
|
+
<span className="mono truncate" style={{ fontSize: 11, lineHeight: 1.3 }}>
|
|
67
|
+
{data.label}
|
|
68
|
+
</span>
|
|
69
|
+
{data.ghost ? (
|
|
70
|
+
<span className="truncate" style={{ fontSize: 10, opacity: 0.8 }}>
|
|
71
|
+
not in catalog
|
|
72
|
+
</span>
|
|
73
|
+
) : (
|
|
74
|
+
/* Two numbers, one line: what it emits and what it listens to. The
|
|
75
|
+
arrows carry the direction so the words do not have to shout it. */
|
|
76
|
+
<span
|
|
77
|
+
className="tnum truncate text-muted"
|
|
78
|
+
style={{ fontSize: 10, lineHeight: 1.4 }}
|
|
79
|
+
>
|
|
80
|
+
↑{data.publishes} publishes · ↓{data.consumes} consumes
|
|
81
|
+
</span>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
<Handle type="target" position={Position.Left} style={HANDLE} />
|
|
85
|
+
<Handle type="source" position={Position.Right} style={HANDLE} />
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The pill. A tinted badge in the publisher's colour, the way a status badge
|
|
92
|
+
* is tinted: the tint is the context, the bolt is the kind, and neither needs
|
|
93
|
+
* a border to say so. Selection draws its own ring here because the pill is
|
|
94
|
+
* not the shape of its box - see the rule on `.react-flow__node.selected`.
|
|
95
|
+
*/
|
|
96
|
+
export function EventNode({ data, selected }: NodeProps<EventNodeType>) {
|
|
97
|
+
const accent = contextVar(data.context);
|
|
98
|
+
const tiny = useContext(TinyZoom);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className="flex h-full w-full items-center">
|
|
102
|
+
<div
|
|
103
|
+
className="flex h-full items-center gap-1.5 overflow-hidden px-2"
|
|
104
|
+
style={{
|
|
105
|
+
width: tiny ? EVENT_ICON_W : "100%",
|
|
106
|
+
background: `color-mix(in srgb, ${accent} 10%, var(--flow-card))`,
|
|
107
|
+
// A pill, not a box: the shape is the kind.
|
|
108
|
+
borderRadius: 999,
|
|
109
|
+
boxShadow: selected ? "var(--ring)" : "var(--shadow-card)",
|
|
110
|
+
justifyContent: tiny ? "center" : undefined,
|
|
111
|
+
paddingInline: tiny ? 0 : undefined,
|
|
112
|
+
transition: "box-shadow var(--dur-micro) var(--ease-out)",
|
|
113
|
+
}}
|
|
114
|
+
title={
|
|
115
|
+
data.self
|
|
116
|
+
? `${data.label} — published and consumed by ${data.publisher}`
|
|
117
|
+
: `${data.label} — published by ${data.publisher}`
|
|
118
|
+
}
|
|
119
|
+
>
|
|
120
|
+
<Zap
|
|
121
|
+
size={11}
|
|
122
|
+
aria-hidden
|
|
123
|
+
className="shrink-0"
|
|
124
|
+
style={{ color: accent }}
|
|
125
|
+
fill="currentColor"
|
|
126
|
+
/>
|
|
127
|
+
{tiny ? null : (
|
|
128
|
+
<>
|
|
129
|
+
<span
|
|
130
|
+
className="mono truncate"
|
|
131
|
+
style={{ fontSize: 11, lineHeight: 1.2, color: "var(--fg)" }}
|
|
132
|
+
>
|
|
133
|
+
{data.label}
|
|
134
|
+
</span>
|
|
135
|
+
{/* A service listening to its own event is a fact about the pill,
|
|
136
|
+
not a line: a loop edge would have to circle the node it leaves
|
|
137
|
+
and arrives at, and there is nothing between the two ends of it
|
|
138
|
+
for a reader to follow. */}
|
|
139
|
+
{data.self ? (
|
|
140
|
+
<span
|
|
141
|
+
className="ml-auto shrink-0 rounded-full px-1.5"
|
|
142
|
+
style={{
|
|
143
|
+
fontSize: 9,
|
|
144
|
+
lineHeight: "14px",
|
|
145
|
+
fontWeight: 600,
|
|
146
|
+
background: `color-mix(in srgb, ${accent} 16%, transparent)`,
|
|
147
|
+
color: accent,
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
self
|
|
151
|
+
</span>
|
|
152
|
+
) : null}
|
|
153
|
+
</>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
<Handle type="target" position={Position.Left} style={HANDLE} />
|
|
157
|
+
<Handle type="source" position={Position.Right} style={HANDLE} />
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const dependencyNodeTypes = { service: ServiceNode, event: EventNode };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The pill that sits on a line.
|
|
2
|
+
//
|
|
3
|
+
// A word printed straight onto a line is a word with a line through it, so
|
|
4
|
+
// the label carries a surface of its own - the card colour and the card's
|
|
5
|
+
// ring - and lives in React Flow's label layer, which is above the nodes: on
|
|
6
|
+
// a short line between two neighbours the pill is wider than the gap, and a
|
|
7
|
+
// pill laid over a corner beats half a pill. The bundled edges of the
|
|
8
|
+
// dependency graph and the relation labels of the context map both draw this
|
|
9
|
+
// one; they used to draw two copies of it.
|
|
10
|
+
|
|
11
|
+
import { EdgeLabelRenderer } from "@xyflow/react";
|
|
12
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
13
|
+
import type { Point } from "./elk";
|
|
14
|
+
|
|
15
|
+
export function EdgeLabel({
|
|
16
|
+
at,
|
|
17
|
+
opacity,
|
|
18
|
+
title,
|
|
19
|
+
onClick,
|
|
20
|
+
children,
|
|
21
|
+
}: {
|
|
22
|
+
at: Point;
|
|
23
|
+
/** Follows the line's own opacity, so a dimmed line cannot have a lit label. */
|
|
24
|
+
opacity: number;
|
|
25
|
+
title?: string | undefined;
|
|
26
|
+
/** When given the pill is a button and takes the pointer; otherwise it lets clicks through to the line. */
|
|
27
|
+
onClick?: (() => void) | undefined;
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
}) {
|
|
30
|
+
const style: CSSProperties = {
|
|
31
|
+
position: "absolute",
|
|
32
|
+
transform: `translate(-50%, -50%) translate(${at.x}px, ${at.y}px)`,
|
|
33
|
+
background: "var(--flow-card)",
|
|
34
|
+
color: "var(--fg-muted)",
|
|
35
|
+
boxShadow: "var(--shadow-card)",
|
|
36
|
+
borderRadius: 999,
|
|
37
|
+
padding: "0 6px",
|
|
38
|
+
fontSize: 10,
|
|
39
|
+
lineHeight: "16px",
|
|
40
|
+
whiteSpace: "nowrap",
|
|
41
|
+
opacity,
|
|
42
|
+
zIndex: 5,
|
|
43
|
+
pointerEvents: onClick ? "all" : "none",
|
|
44
|
+
cursor: onClick ? "pointer" : undefined,
|
|
45
|
+
};
|
|
46
|
+
const className = "mono nodrag nopan tnum";
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<EdgeLabelRenderer>
|
|
50
|
+
{onClick ? (
|
|
51
|
+
<button type="button" onClick={onClick} className={className} title={title} style={style}>
|
|
52
|
+
{children}
|
|
53
|
+
</button>
|
|
54
|
+
) : (
|
|
55
|
+
<div className={className} title={title} style={style}>
|
|
56
|
+
{children}
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</EdgeLabelRenderer>
|
|
60
|
+
);
|
|
61
|
+
}
|