@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,157 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { sameVisit, visitFor, visitSubject, visitTo } from "./model";
|
|
3
|
+
|
|
4
|
+
const subject = (path: string, id?: string) =>
|
|
5
|
+
visitSubject({
|
|
6
|
+
path,
|
|
7
|
+
selection: id ? { kind: "unknown", id } : null,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe("visitFor", () => {
|
|
11
|
+
it("records entity pages", () => {
|
|
12
|
+
expect(visitFor("/c/shop/oms", null)).toEqual({
|
|
13
|
+
path: "/c/shop/oms",
|
|
14
|
+
selection: null,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The overview, the two index lists and the two whole-estate views are one
|
|
20
|
+
* click away from the chrome that is always on screen. A trail that spends
|
|
21
|
+
* its slots on them has fewer left for the leaves nobody can get back to.
|
|
22
|
+
*/
|
|
23
|
+
it("ignores the pages the chrome already reaches", () => {
|
|
24
|
+
for (const path of [
|
|
25
|
+
"/",
|
|
26
|
+
"/flows",
|
|
27
|
+
"/adrs",
|
|
28
|
+
"/graph",
|
|
29
|
+
"/map",
|
|
30
|
+
"/problems",
|
|
31
|
+
]) {
|
|
32
|
+
expect(visitFor(path, null)).toBeNull();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("ignores a page the catalog does not know", () => {
|
|
37
|
+
expect(visitFor("/c/shop/nope", null)).toBeNull();
|
|
38
|
+
expect(visitFor("/adrs/not-a-decision", null)).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("visitSubject from the path", () => {
|
|
43
|
+
it("names each level of the catalog", () => {
|
|
44
|
+
expect(subject("/c/shop")).toEqual({
|
|
45
|
+
kind: "context",
|
|
46
|
+
label: "shop",
|
|
47
|
+
contextId: "shop",
|
|
48
|
+
});
|
|
49
|
+
expect(subject("/c/shop/oms")).toEqual({ kind: "service", label: "oms" });
|
|
50
|
+
expect(subject("/c/shop/oms/order")).toEqual({
|
|
51
|
+
kind: "aggregate",
|
|
52
|
+
label: "order",
|
|
53
|
+
});
|
|
54
|
+
expect(subject("/c/shop/oms/order/order-placed")).toEqual({
|
|
55
|
+
kind: "event",
|
|
56
|
+
label: "OrderPlaced",
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("reads the block literals rather than the slug behind them", () => {
|
|
61
|
+
expect(subject("/c/shop/oms/order/vo/money")).toEqual({
|
|
62
|
+
kind: "vo",
|
|
63
|
+
label: "Money",
|
|
64
|
+
});
|
|
65
|
+
expect(subject("/c/shop/oms/order/entity/order")).toEqual({
|
|
66
|
+
kind: "entity",
|
|
67
|
+
label: "Order",
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("names flows and decisions, which have no selection of their own", () => {
|
|
72
|
+
expect(subject("/flows/cart-checkout")).toEqual({
|
|
73
|
+
kind: "flow",
|
|
74
|
+
label: "cart-checkout",
|
|
75
|
+
});
|
|
76
|
+
expect(subject("/adrs/payments-0004-idempotent-journal-entries")).toEqual({
|
|
77
|
+
kind: "adr",
|
|
78
|
+
label: "ADR-0004",
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("visitSubject from the selection", () => {
|
|
84
|
+
/**
|
|
85
|
+
* An event picked on a service diagram is what the reader was looking at,
|
|
86
|
+
* even though the route still says the service. The chip says so, and going
|
|
87
|
+
* back to it puts the event under them again.
|
|
88
|
+
*/
|
|
89
|
+
it("prefers what was selected over what the route says", () => {
|
|
90
|
+
expect(subject("/c/shop/oms", "shop.oms.order.OrderPlaced")).toEqual({
|
|
91
|
+
kind: "event",
|
|
92
|
+
label: "OrderPlaced",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("carries the step number for a flow", () => {
|
|
97
|
+
expect(subject("/flows/cart-checkout", "cart-checkout/s1")).toEqual({
|
|
98
|
+
kind: "flow",
|
|
99
|
+
label: "cart-checkout · 1",
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("falls back to the page when the selection is gone", () => {
|
|
104
|
+
expect(subject("/c/shop/oms", "shop.oms.order.Deleted")).toEqual({
|
|
105
|
+
kind: "service",
|
|
106
|
+
label: "oms",
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("is null when neither the selection nor the page survives", () => {
|
|
111
|
+
expect(subject("/c/gone/nowhere", "nothing.at.all")).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("identity", () => {
|
|
116
|
+
/**
|
|
117
|
+
* Thirteen steps of one flow are thirteen selections on one page. They share
|
|
118
|
+
* a slot, or a single flow fills the whole trail.
|
|
119
|
+
*/
|
|
120
|
+
it("is the page, so steps of one flow collapse", () => {
|
|
121
|
+
const a = { path: "/flows/cart-checkout", selection: null };
|
|
122
|
+
const b = {
|
|
123
|
+
path: "/flows/cart-checkout",
|
|
124
|
+
selection: { kind: "flow-step" as const, id: "cart-checkout/s1" },
|
|
125
|
+
};
|
|
126
|
+
expect(sameVisit(a, b)).toBe(true);
|
|
127
|
+
expect(
|
|
128
|
+
sameVisit(a, { path: "/flows/auth-login", selection: null }),
|
|
129
|
+
).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("goes back to the selection it was left on", () => {
|
|
133
|
+
expect(
|
|
134
|
+
visitTo({
|
|
135
|
+
path: "/c/shop/oms",
|
|
136
|
+
selection: { kind: "event", id: "shop.oms.order.OrderPlaced" },
|
|
137
|
+
}),
|
|
138
|
+
).toBe("/c/shop/oms#sel=event:shop.oms.order.OrderPlaced");
|
|
139
|
+
expect(visitTo({ path: "/c/shop/oms", selection: null })).toBe(
|
|
140
|
+
"/c/shop/oms",
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("a module in the trail", () => {
|
|
146
|
+
// The registry index is chrome, not an entity: a reader gets back to it from
|
|
147
|
+
// the sidebar, which is the rule the whole trail is built on.
|
|
148
|
+
it("does not record the registry index as a visit", () => {
|
|
149
|
+
expect(visitFor("/registry", null)).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Every lookup goes to the catalog, so a slug it does not hold is null
|
|
153
|
+
// rather than a chip labelled with a URL fragment.
|
|
154
|
+
it("does not record a module page for a slug the catalog does not hold", () => {
|
|
155
|
+
expect(visitFor("/registry/acme-shop", null)).toBeNull();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// One visit in the reader's journal.
|
|
2
|
+
//
|
|
3
|
+
// The breadcrumb answers "where does this thing sit" — it walks the ancestors
|
|
4
|
+
// of whatever is selected. This answers a different question, "where have I
|
|
5
|
+
// been", and the two are not the same shape: an ADR and a flow have no
|
|
6
|
+
// ancestors in the model at all, and they are exactly the pages a reader loses
|
|
7
|
+
// the thread of after five moves.
|
|
8
|
+
//
|
|
9
|
+
// So a visit is a route plus whatever was selected on it, and nothing is
|
|
10
|
+
// stored that the catalog could contradict later: the icon and the label are
|
|
11
|
+
// derived at render time, so a renamed event is renamed in the trail too, and
|
|
12
|
+
// an entity the catalog no longer has drops out of it rather than pointing at
|
|
13
|
+
// a page that is gone.
|
|
14
|
+
|
|
15
|
+
import { catalog, index } from "../data";
|
|
16
|
+
import { adrNumber } from "../lib/adr";
|
|
17
|
+
import type { Kind } from "../lib/kinds";
|
|
18
|
+
import { selectionHash } from "../selection/hash";
|
|
19
|
+
import type { Selection } from "../selection/model";
|
|
20
|
+
import { resolveSelection } from "../selection/model";
|
|
21
|
+
|
|
22
|
+
export interface Visit {
|
|
23
|
+
/** The route, with no hash and no query. */
|
|
24
|
+
path: string;
|
|
25
|
+
/** What was selected on it, or null when the page is the whole answer. */
|
|
26
|
+
selection: Selection | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** What a chip draws: one icon, one name. */
|
|
30
|
+
export interface VisitSubject {
|
|
31
|
+
kind: Kind;
|
|
32
|
+
label: string;
|
|
33
|
+
/** Only ever set for `context`, which is painted in its own colour. */
|
|
34
|
+
contextId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A visit worth recording, or null for a page that is not an entity — the
|
|
39
|
+
* overview, the two index lists, the map, the graph. Those are places the
|
|
40
|
+
* reader gets back to from the chrome that is already on screen; the trail is
|
|
41
|
+
* for the leaves, which are not reachable in one move from anywhere.
|
|
42
|
+
*/
|
|
43
|
+
export function visitFor(
|
|
44
|
+
pathname: string,
|
|
45
|
+
selection: Selection | null,
|
|
46
|
+
): Visit | null {
|
|
47
|
+
if (!subjectOfPath(pathname)) return null;
|
|
48
|
+
return { path: pathname, selection };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Identity is the path, not the pair.
|
|
53
|
+
*
|
|
54
|
+
* That one line is what collapses a flow: thirteen steps of `checkout` are
|
|
55
|
+
* thirteen selections on one page, and a journal of seven that spends all of
|
|
56
|
+
* itself on one flow has forgotten the six other things the reader came from.
|
|
57
|
+
* The newest selection wins, so the chip still points at the step they left.
|
|
58
|
+
*/
|
|
59
|
+
export function sameVisit(a: Visit, b: Visit): boolean {
|
|
60
|
+
return a.path === b.path;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Where a chip goes back to: the page, and the selection it was left on. */
|
|
64
|
+
export function visitTo(visit: Visit): string {
|
|
65
|
+
return `${visit.path}${selectionHash(visit.selection)}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The chip's icon and name, or null when nothing in the catalog answers to it
|
|
70
|
+
* any more. A trail restored from a previous session can name an event that
|
|
71
|
+
* has since been deleted, and a chip that navigates to a 404 is worse than no
|
|
72
|
+
* chip at all.
|
|
73
|
+
*
|
|
74
|
+
* The selection is asked first, and it is allowed to disagree with the page:
|
|
75
|
+
* an event picked on a service diagram was what the reader was looking at,
|
|
76
|
+
* even though the route still says the service. Going back to that route with
|
|
77
|
+
* that selection puts them back in front of it.
|
|
78
|
+
*/
|
|
79
|
+
export function visitSubject(visit: Visit): VisitSubject | null {
|
|
80
|
+
const selected = visit.selection ? subjectOfSelection(visit.selection) : null;
|
|
81
|
+
return selected ?? subjectOfPath(visit.path);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function subjectOfSelection(selection: Selection): VisitSubject | null {
|
|
85
|
+
const resolved = resolveSelection(selection.id);
|
|
86
|
+
if (!resolved) return null;
|
|
87
|
+
switch (resolved.kind) {
|
|
88
|
+
case "context":
|
|
89
|
+
return {
|
|
90
|
+
kind: "context",
|
|
91
|
+
label: resolved.context.id,
|
|
92
|
+
contextId: resolved.context.id,
|
|
93
|
+
};
|
|
94
|
+
case "service":
|
|
95
|
+
return { kind: "service", label: resolved.service.slug };
|
|
96
|
+
case "aggregate":
|
|
97
|
+
return { kind: "aggregate", label: resolved.aggregate.slug };
|
|
98
|
+
case "event":
|
|
99
|
+
return { kind: "event", label: resolved.event.name };
|
|
100
|
+
case "value-object":
|
|
101
|
+
return { kind: "def", label: resolved.id };
|
|
102
|
+
case "store":
|
|
103
|
+
return { kind: "store", label: resolved.store.slug };
|
|
104
|
+
// A table and a column both take the reader back to the same canvas, so
|
|
105
|
+
// the chip names the table either way; a column rides along in the label.
|
|
106
|
+
case "table":
|
|
107
|
+
return { kind: "table", label: resolved.table.name };
|
|
108
|
+
case "view":
|
|
109
|
+
return { kind: "view", label: resolved.view.name };
|
|
110
|
+
case "column":
|
|
111
|
+
return {
|
|
112
|
+
kind: resolved.view ? "view" : "table",
|
|
113
|
+
label: `${resolved.view?.name ?? resolved.table?.name ?? ""}.${resolved.column.name}`,
|
|
114
|
+
};
|
|
115
|
+
// The step number rides along in the label rather than in a chip of its
|
|
116
|
+
// own: one flow, one slot, and the number says how far in they had got.
|
|
117
|
+
case "flow-step":
|
|
118
|
+
return {
|
|
119
|
+
kind: "flow",
|
|
120
|
+
label: `${resolved.flow.slug} · ${resolved.number}`,
|
|
121
|
+
};
|
|
122
|
+
// A bundle is a line on one canvas and nothing anywhere else. There is no
|
|
123
|
+
// route that puts a reader back in front of it, so it earns no chip.
|
|
124
|
+
case "bundle":
|
|
125
|
+
return null;
|
|
126
|
+
case "module":
|
|
127
|
+
return { kind: "module", label: resolved.module.name };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The entity a route is about, read off the segments. Every lookup goes to the
|
|
133
|
+
* catalog, so an unrecognised slug is null rather than a chip labelled with a
|
|
134
|
+
* URL fragment.
|
|
135
|
+
*/
|
|
136
|
+
function subjectOfPath(pathname: string): VisitSubject | null {
|
|
137
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
138
|
+
const head = parts[0];
|
|
139
|
+
|
|
140
|
+
if (head === "flows") {
|
|
141
|
+
const slug = parts[1];
|
|
142
|
+
const flow = slug ? index.flowBySlug.get(slug) : undefined;
|
|
143
|
+
return flow ? { kind: "flow", label: flow.slug } : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (head === "adrs") {
|
|
147
|
+
const slug = parts[1];
|
|
148
|
+
const adr = slug ? index.adrBySlug.get(slug) : undefined;
|
|
149
|
+
return adr ? { kind: "adr", label: adrNumber(adr) } : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (head === "registry") {
|
|
153
|
+
const slug = parts[1];
|
|
154
|
+
// The index itself is chrome, not an entity: a reader gets back to it from
|
|
155
|
+
// the sidebar, which is the rule the whole trail is built on.
|
|
156
|
+
const module = slug ? index.moduleBySlug.get(slug) : undefined;
|
|
157
|
+
return module ? { kind: "module", label: module.name } : null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (head !== "c") return null;
|
|
161
|
+
|
|
162
|
+
const [, contextId, serviceSlug, aggregateSlug, fourth, fifth] = parts;
|
|
163
|
+
const context = contextId
|
|
164
|
+
? catalog.contexts.find((c) => c.id === contextId)
|
|
165
|
+
: undefined;
|
|
166
|
+
if (!context) return null;
|
|
167
|
+
if (!serviceSlug) {
|
|
168
|
+
return { kind: "context", label: context.id, contextId: context.id };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const service = context.services.find((s) => s.slug === serviceSlug);
|
|
172
|
+
if (!service) return null;
|
|
173
|
+
if (!aggregateSlug) return { kind: "service", label: service.slug };
|
|
174
|
+
|
|
175
|
+
const aggregate = service.aggregates.find((a) => a.slug === aggregateSlug);
|
|
176
|
+
if (!aggregate) return null;
|
|
177
|
+
if (!fourth) return { kind: "aggregate", label: aggregate.slug };
|
|
178
|
+
|
|
179
|
+
// "vo" and "entity" are literals with a slug behind them, which is what
|
|
180
|
+
// keeps an event whose slug is "vo" from being read as a block page.
|
|
181
|
+
if (fourth === "vo" || fourth === "entity") {
|
|
182
|
+
const list = fourth === "vo" ? aggregate.valueObjects : aggregate.entities;
|
|
183
|
+
const block = fifth ? list.find((b) => b.slug === fifth) : undefined;
|
|
184
|
+
return block ? { kind: fourth, label: block.name } : null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const event = aggregate.events.find((e) => e.slug === fourth);
|
|
188
|
+
return event ? { kind: "event", label: event.name } : null;
|
|
189
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// What counts as a visit.
|
|
2
|
+
//
|
|
3
|
+
// Not every render, and not every URL the router passes through. A reader
|
|
4
|
+
// holding the back button, or stepping through a flow with the arrow keys,
|
|
5
|
+
// crosses a dozen states in a second; a journal of seven that recorded all of
|
|
6
|
+
// them would be full of places nobody stopped at. So a page has to be stood on
|
|
7
|
+
// before it is written down.
|
|
8
|
+
|
|
9
|
+
import { useEffect } from "react";
|
|
10
|
+
import { useLocation } from "react-router";
|
|
11
|
+
import { useSelection } from "../selection/store";
|
|
12
|
+
import { visitFor } from "./model";
|
|
13
|
+
import { useTrailStore } from "./store";
|
|
14
|
+
|
|
15
|
+
/** Long enough to skip a page passed through, short enough to be there on
|
|
16
|
+
* arrival — the trail is read after a move, not during one. */
|
|
17
|
+
const VISIT_SETTLE_MS = 400;
|
|
18
|
+
|
|
19
|
+
export function TrailRecorder() {
|
|
20
|
+
const { pathname } = useLocation();
|
|
21
|
+
const selection = useSelection();
|
|
22
|
+
const record = useTrailStore((s) => s.record);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const visit = visitFor(pathname, selection);
|
|
26
|
+
if (!visit) return;
|
|
27
|
+
const timer = setTimeout(() => record(visit), VISIT_SETTLE_MS);
|
|
28
|
+
return () => clearTimeout(timer);
|
|
29
|
+
}, [pathname, selection, record]);
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import type { Visit } from "./model";
|
|
3
|
+
import { MAX_VISITS, useTrailStore } from "./store";
|
|
4
|
+
|
|
5
|
+
const store = () => useTrailStore.getState();
|
|
6
|
+
const paths = () => store().visits.map((v) => v.path);
|
|
7
|
+
|
|
8
|
+
const visit = (path: string, id?: string): Visit => ({
|
|
9
|
+
path,
|
|
10
|
+
selection: id ? { kind: "event", id } : null,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
useTrailStore.setState({ visits: [] });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("trail store", () => {
|
|
18
|
+
it("keeps the most recent first", () => {
|
|
19
|
+
store().record(visit("/c/shop"));
|
|
20
|
+
store().record(visit("/c/shop/oms"));
|
|
21
|
+
expect(paths()).toEqual(["/c/shop/oms", "/c/shop"]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A reader who goes back to an event they were on an hour ago has just made
|
|
26
|
+
* it the freshest thing in their head, not a second copy of it.
|
|
27
|
+
*/
|
|
28
|
+
it("moves a page already in the trail to the front", () => {
|
|
29
|
+
store().record(visit("/c/shop"));
|
|
30
|
+
store().record(visit("/c/shop/oms"));
|
|
31
|
+
store().record(visit("/c/shop"));
|
|
32
|
+
expect(paths()).toEqual(["/c/shop", "/c/shop/oms"]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("keeps the newest selection for a page it already holds", () => {
|
|
36
|
+
store().record(visit("/flows/checkout"));
|
|
37
|
+
store().record(visit("/flows/checkout", "checkout/a1"));
|
|
38
|
+
expect(store().visits).toHaveLength(1);
|
|
39
|
+
expect(store().visits[0]?.selection?.id).toBe("checkout/a1");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("holds no more than the strip can be glanced at", () => {
|
|
43
|
+
for (let i = 0; i < MAX_VISITS + 3; i++) store().record(visit(`/c/c${i}`));
|
|
44
|
+
expect(store().visits).toHaveLength(MAX_VISITS);
|
|
45
|
+
expect(paths()[0]).toBe(`/c/c${MAX_VISITS + 2}`);
|
|
46
|
+
expect(paths().at(-1)).toBe("/c/c3");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The recorder fires on every settled render, so the common case is a write
|
|
51
|
+
* that changes nothing. It must not notify, or the strip re-renders behind
|
|
52
|
+
* a reader who has not moved.
|
|
53
|
+
*/
|
|
54
|
+
it("ignores a write that repeats the head", () => {
|
|
55
|
+
store().record(visit("/c/shop/oms", "shop.oms.order.OrderPlaced"));
|
|
56
|
+
const before = useTrailStore.getState();
|
|
57
|
+
store().record(visit("/c/shop/oms", "shop.oms.order.OrderPlaced"));
|
|
58
|
+
expect(useTrailStore.getState()).toBe(before);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("clears", () => {
|
|
62
|
+
store().record(visit("/c/shop"));
|
|
63
|
+
store().clear();
|
|
64
|
+
expect(store().visits).toEqual([]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// The journal itself: the last few entities the reader stood in front of.
|
|
2
|
+
//
|
|
3
|
+
// Most recent first, one slot per page, seven slots. Seven is not a round
|
|
4
|
+
// number picked for looks — it is about as many names as a reader can scan
|
|
5
|
+
// without reading, and a strip longer than that stops being a glance and
|
|
6
|
+
// becomes another list to search.
|
|
7
|
+
|
|
8
|
+
import { create } from "zustand";
|
|
9
|
+
import { sameSelection, selectionFor } from "../selection/model";
|
|
10
|
+
import type { Selection } from "../selection/model";
|
|
11
|
+
import type { Visit } from "./model";
|
|
12
|
+
import { sameVisit } from "./model";
|
|
13
|
+
|
|
14
|
+
export const MAX_VISITS = 7;
|
|
15
|
+
|
|
16
|
+
const KEY = "portolan.trail";
|
|
17
|
+
|
|
18
|
+
interface TrailState {
|
|
19
|
+
/** Newest first. */
|
|
20
|
+
visits: Visit[];
|
|
21
|
+
record: (visit: Visit) => void;
|
|
22
|
+
clear: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useTrailStore = create<TrailState>()((set) => ({
|
|
26
|
+
visits: load(),
|
|
27
|
+
record: (visit) =>
|
|
28
|
+
set((state) => {
|
|
29
|
+
const head = state.visits[0];
|
|
30
|
+
// Nothing moved: the same page with the same thing selected on it. The
|
|
31
|
+
// whole state object is returned so zustand notifies nobody.
|
|
32
|
+
if (
|
|
33
|
+
head &&
|
|
34
|
+
sameVisit(head, visit) &&
|
|
35
|
+
sameSelection(head.selection, visit.selection)
|
|
36
|
+
) {
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
visits: [
|
|
41
|
+
visit,
|
|
42
|
+
...state.visits.filter((v) => !sameVisit(v, visit)),
|
|
43
|
+
].slice(0, MAX_VISITS),
|
|
44
|
+
};
|
|
45
|
+
}),
|
|
46
|
+
clear: () => set({ visits: [] }),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The trail is per tab and per session: a reader who opens a second tab to
|
|
51
|
+
* compare two contexts is on a second thread of thought, and neither tab
|
|
52
|
+
* should be writing into the other's journal. sessionStorage is exactly that
|
|
53
|
+
* scope, and it is also why a reload keeps the trail while tomorrow does not.
|
|
54
|
+
*/
|
|
55
|
+
useTrailStore.subscribe((state) => save(state.visits));
|
|
56
|
+
|
|
57
|
+
function load(): Visit[] {
|
|
58
|
+
const raw = read();
|
|
59
|
+
if (!raw) return [];
|
|
60
|
+
try {
|
|
61
|
+
const parsed: unknown = JSON.parse(raw);
|
|
62
|
+
if (!Array.isArray(parsed)) return [];
|
|
63
|
+
return parsed.flatMap(toVisit).slice(0, MAX_VISITS);
|
|
64
|
+
} catch {
|
|
65
|
+
return []; // hand-edited or half-written: start empty, say nothing
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Storage is a text field anyone can edit, so every field is checked. The
|
|
71
|
+
* selection's kind is re-derived rather than trusted: a stored `event` whose
|
|
72
|
+
* id is now a value object would otherwise put the wrong icon on the chip.
|
|
73
|
+
*/
|
|
74
|
+
function toVisit(value: unknown): Visit[] {
|
|
75
|
+
if (typeof value !== "object" || value === null) return [];
|
|
76
|
+
const { path, selection } = value as { path?: unknown; selection?: unknown };
|
|
77
|
+
if (typeof path !== "string" || !path.startsWith("/")) return [];
|
|
78
|
+
return [{ path, selection: toSelection(selection) }];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function toSelection(value: unknown): Selection | null {
|
|
82
|
+
if (typeof value !== "object" || value === null) return null;
|
|
83
|
+
const { id } = value as { id?: unknown };
|
|
84
|
+
if (typeof id !== "string" || !id) return null;
|
|
85
|
+
return selectionFor(id);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function read(): string | null {
|
|
89
|
+
try {
|
|
90
|
+
return sessionStorage.getItem(KEY);
|
|
91
|
+
} catch {
|
|
92
|
+
return null; // no storage, or a private mode that refuses it
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function save(visits: Visit[]): void {
|
|
97
|
+
try {
|
|
98
|
+
sessionStorage.setItem(KEY, JSON.stringify(visits));
|
|
99
|
+
} catch {
|
|
100
|
+
/* this session still keeps its trail, it just does not survive a reload */
|
|
101
|
+
}
|
|
102
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2022",
|
|
7
|
+
"DOM",
|
|
8
|
+
"DOM.Iterable"
|
|
9
|
+
],
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"moduleDetection": "force",
|
|
17
|
+
"noEmit": true,
|
|
18
|
+
"jsx": "react-jsx",
|
|
19
|
+
"strict": true,
|
|
20
|
+
"noUnusedLocals": true,
|
|
21
|
+
"noUnusedParameters": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUncheckedIndexedAccess": true,
|
|
24
|
+
"exactOptionalPropertyTypes": false,
|
|
25
|
+
"types": [
|
|
26
|
+
"vite/client",
|
|
27
|
+
"node"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"include": [
|
|
31
|
+
"src",
|
|
32
|
+
"vite.config.ts",
|
|
33
|
+
"vitest.config.ts"
|
|
34
|
+
]
|
|
35
|
+
}
|