@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,196 @@
|
|
|
1
|
+
// One comparator per column type.
|
|
2
|
+
//
|
|
3
|
+
// Every one of these returns an ASCENDING comparison and nothing else: the
|
|
4
|
+
// table reverses the result itself when a column is sorted descending, so a
|
|
5
|
+
// comparator that flips its own sign flips it twice. Empty cells never reach
|
|
6
|
+
// here — an empty cell is `undefined`, and the table places undefined last in
|
|
7
|
+
// both directions before it asks anyone to compare anything.
|
|
8
|
+
|
|
9
|
+
import type { Status } from "../catalog";
|
|
10
|
+
import type { Kind } from "../lib/kinds";
|
|
11
|
+
import type { CellValue, ColumnType } from "./types";
|
|
12
|
+
|
|
13
|
+
/** Ascending status order. The strongest claim about reality comes first. */
|
|
14
|
+
const STATUS_RANK: Record<Status, number> = {
|
|
15
|
+
verified: 0,
|
|
16
|
+
declared: 1,
|
|
17
|
+
unresolved: 2,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Ascending kind order: the five building blocks in the order the spec names
|
|
22
|
+
* them, then everything else in tree order, so a `kind` column of mixed rows
|
|
23
|
+
* still has one answer rather than an alphabetical accident.
|
|
24
|
+
*/
|
|
25
|
+
const KIND_RANK: Record<Kind, number> = {
|
|
26
|
+
event: 0,
|
|
27
|
+
command: 1,
|
|
28
|
+
query: 2,
|
|
29
|
+
entity: 3,
|
|
30
|
+
vo: 4,
|
|
31
|
+
enum: 5,
|
|
32
|
+
def: 5,
|
|
33
|
+
aggregate: 6,
|
|
34
|
+
// What the outside can call sits between the model and where it is kept: it
|
|
35
|
+
// is not one of the building blocks, and it is not infrastructure either.
|
|
36
|
+
endpoint: 7,
|
|
37
|
+
// The schema an interface was declared in, between what it offers and where
|
|
38
|
+
// its state is kept - the same place the palette ranks it.
|
|
39
|
+
module: 8,
|
|
40
|
+
// Where the model is kept comes after the model itself, and a store before
|
|
41
|
+
// the tables in it - the same order the palette ranks them in.
|
|
42
|
+
store: 9,
|
|
43
|
+
table: 10,
|
|
44
|
+
view: 11,
|
|
45
|
+
service: 12,
|
|
46
|
+
context: 13,
|
|
47
|
+
external: 13,
|
|
48
|
+
flow: 14,
|
|
49
|
+
adr: 15,
|
|
50
|
+
term: 16,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Case-insensitive, then case-sensitive, so the order is never arbitrary. */
|
|
54
|
+
export function compareText(a: string, b: string): number {
|
|
55
|
+
const folded = a.localeCompare(b, undefined, { sensitivity: "base" });
|
|
56
|
+
if (folded !== 0) return folded;
|
|
57
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function compareNumber(a: number, b: number): number {
|
|
61
|
+
return a - b;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const VERSION_RE = /^[vV]?\d+(\.\d+)*([-+].*)?$/;
|
|
65
|
+
|
|
66
|
+
/** Splits "v1.2.3-rc.1" into [1, 2, 3] and "rc.1". */
|
|
67
|
+
function parseVersion(
|
|
68
|
+
raw: string,
|
|
69
|
+
): { release: number[]; pre: string[] } | null {
|
|
70
|
+
if (!VERSION_RE.test(raw)) return null;
|
|
71
|
+
// Build metadata is not part of precedence; a "+build" suffix is dropped.
|
|
72
|
+
const body = raw.replace(/^[vV]/, "").split("+")[0] ?? "";
|
|
73
|
+
// Split at the FIRST hyphen only: "1.0.0-rc-2" has one prerelease, not two.
|
|
74
|
+
const dash = body.indexOf("-");
|
|
75
|
+
const core = dash === -1 ? body : body.slice(0, dash);
|
|
76
|
+
const prerelease = dash === -1 ? "" : body.slice(dash + 1);
|
|
77
|
+
return {
|
|
78
|
+
release: core.split(".").map((part) => Number(part)),
|
|
79
|
+
pre: prerelease === "" ? [] : prerelease.split("."),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Semver precedence, and the reason this file exists: 2 before 10, every time.
|
|
85
|
+
* Anything that is not shaped like a version falls back to text, so a column
|
|
86
|
+
* that turns out to hold "latest" still has a defined order.
|
|
87
|
+
*/
|
|
88
|
+
export function compareVersion(a: string, b: string): number {
|
|
89
|
+
const va = parseVersion(a);
|
|
90
|
+
const vb = parseVersion(b);
|
|
91
|
+
if (!va || !vb) {
|
|
92
|
+
// A version always precedes a word: "1.0" is a release, "latest" is a wish.
|
|
93
|
+
if (va) return -1;
|
|
94
|
+
if (vb) return 1;
|
|
95
|
+
return compareText(a, b);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const depth = Math.max(va.release.length, vb.release.length);
|
|
99
|
+
for (let i = 0; i < depth; i++) {
|
|
100
|
+
// A missing segment is zero: 1.2 and 1.2.0 are the same version.
|
|
101
|
+
const diff = (va.release[i] ?? 0) - (vb.release[i] ?? 0);
|
|
102
|
+
if (diff !== 0) return diff;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A prerelease precedes the release it leads to; 1.0.0-rc.1 < 1.0.0.
|
|
106
|
+
if (va.pre.length === 0 && vb.pre.length === 0) return 0;
|
|
107
|
+
if (va.pre.length === 0) return 1;
|
|
108
|
+
if (vb.pre.length === 0) return -1;
|
|
109
|
+
|
|
110
|
+
const parts = Math.max(va.pre.length, vb.pre.length);
|
|
111
|
+
for (let i = 0; i < parts; i++) {
|
|
112
|
+
const pa = va.pre[i];
|
|
113
|
+
const pb = vb.pre[i];
|
|
114
|
+
// The shorter prerelease wins when everything before it matched.
|
|
115
|
+
if (pa === undefined) return -1;
|
|
116
|
+
if (pb === undefined) return 1;
|
|
117
|
+
const na = /^\d+$/.test(pa) ? Number(pa) : null;
|
|
118
|
+
const nb = /^\d+$/.test(pb) ? Number(pb) : null;
|
|
119
|
+
if (na !== null && nb !== null) {
|
|
120
|
+
if (na !== nb) return na - nb;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
// Numeric identifiers rank below alphanumeric ones.
|
|
124
|
+
if (na !== null) return -1;
|
|
125
|
+
if (nb !== null) return 1;
|
|
126
|
+
const text = compareText(pa, pb);
|
|
127
|
+
if (text !== 0) return text;
|
|
128
|
+
}
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Oldest first. An unparseable date compares as the text it is. */
|
|
133
|
+
export function compareDate(a: string, b: string): number {
|
|
134
|
+
const ta = Date.parse(a);
|
|
135
|
+
const tb = Date.parse(b);
|
|
136
|
+
const okA = Number.isFinite(ta);
|
|
137
|
+
const okB = Number.isFinite(tb);
|
|
138
|
+
if (!okA || !okB) {
|
|
139
|
+
if (okA) return -1;
|
|
140
|
+
if (okB) return 1;
|
|
141
|
+
return compareText(a, b);
|
|
142
|
+
}
|
|
143
|
+
return ta - tb;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function compareStatus(a: string, b: string): number {
|
|
147
|
+
const ra = STATUS_RANK[a as Status];
|
|
148
|
+
const rb = STATUS_RANK[b as Status];
|
|
149
|
+
// A value outside the vocabulary sorts after every value inside it.
|
|
150
|
+
if (ra === undefined || rb === undefined) {
|
|
151
|
+
if (ra !== undefined) return -1;
|
|
152
|
+
if (rb !== undefined) return 1;
|
|
153
|
+
return compareText(a, b);
|
|
154
|
+
}
|
|
155
|
+
return ra - rb;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function compareKind(a: string, b: string): number {
|
|
159
|
+
const ra = KIND_RANK[a as Kind];
|
|
160
|
+
const rb = KIND_RANK[b as Kind];
|
|
161
|
+
if (ra === undefined || rb === undefined) {
|
|
162
|
+
if (ra !== undefined) return -1;
|
|
163
|
+
if (rb !== undefined) return 1;
|
|
164
|
+
return compareText(a, b);
|
|
165
|
+
}
|
|
166
|
+
return ra - rb;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The comparator a column of this type sorts by. */
|
|
170
|
+
export function comparatorFor(
|
|
171
|
+
type: ColumnType,
|
|
172
|
+
): (a: CellValue, b: CellValue) => number {
|
|
173
|
+
return (a, b) => {
|
|
174
|
+
// Defensive only: the table filters undefined out before it gets here.
|
|
175
|
+
if (a === undefined || b === undefined) {
|
|
176
|
+
if (a === b) return 0;
|
|
177
|
+
return a === undefined ? 1 : -1;
|
|
178
|
+
}
|
|
179
|
+
switch (type) {
|
|
180
|
+
case "number":
|
|
181
|
+
case "count":
|
|
182
|
+
return compareNumber(Number(a), Number(b));
|
|
183
|
+
case "version":
|
|
184
|
+
return compareVersion(String(a), String(b));
|
|
185
|
+
case "date":
|
|
186
|
+
return compareDate(String(a), String(b));
|
|
187
|
+
case "status":
|
|
188
|
+
return compareStatus(String(a), String(b));
|
|
189
|
+
case "kind":
|
|
190
|
+
return compareKind(String(a), String(b));
|
|
191
|
+
case "text":
|
|
192
|
+
case "mono":
|
|
193
|
+
return compareText(String(a), String(b));
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { csvFilename, toCsv, toExcelCsv, toMarkdown } from "./export";
|
|
3
|
+
|
|
4
|
+
const SHEET = {
|
|
5
|
+
headers: ["name", "consumers"],
|
|
6
|
+
rows: [
|
|
7
|
+
["order.placed", "3"],
|
|
8
|
+
["order.cancelled", "1"],
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe("toMarkdown", () => {
|
|
13
|
+
it("writes a header, a delimiter and one line per row", () => {
|
|
14
|
+
expect(toMarkdown(SHEET)).toBe(
|
|
15
|
+
[
|
|
16
|
+
"| name | consumers |",
|
|
17
|
+
"| --- | --- |",
|
|
18
|
+
"| order.placed | 3 |",
|
|
19
|
+
"| order.cancelled | 1 |",
|
|
20
|
+
].join("\n"),
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("escapes a pipe so the row keeps its width", () => {
|
|
25
|
+
const out = toMarkdown({ headers: ["a"], rows: [["x | y"]] });
|
|
26
|
+
expect(out.split("\n")[2]).toBe("| x \\| y |");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("flattens a newline rather than breaking the row", () => {
|
|
30
|
+
const out = toMarkdown({ headers: ["a"], rows: [["one\ntwo"]] });
|
|
31
|
+
expect(out.split("\n")).toHaveLength(3);
|
|
32
|
+
expect(out.split("\n")[2]).toBe("| one two |");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("pads a short row out to the header width", () => {
|
|
36
|
+
const out = toMarkdown({ headers: ["a", "b"], rows: [["x"]] });
|
|
37
|
+
expect(out.split("\n")[2]).toBe("| x | |");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("writes nothing when there are no columns", () => {
|
|
41
|
+
expect(toMarkdown({ headers: [], rows: [] })).toBe("");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("toCsv", () => {
|
|
46
|
+
it("writes CRLF rows", () => {
|
|
47
|
+
expect(toCsv(SHEET)).toBe(
|
|
48
|
+
"name,consumers\r\norder.placed,3\r\norder.cancelled,1",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("quotes a cell holding a comma", () => {
|
|
53
|
+
expect(toCsv({ headers: ["a"], rows: [["x,y"]] })).toBe('a\r\n"x,y"');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("doubles an embedded quote", () => {
|
|
57
|
+
expect(toCsv({ headers: ["a"], rows: [['say "hi"']] })).toBe(
|
|
58
|
+
'a\r\n"say ""hi"""',
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("keeps a newline inside its quotes", () => {
|
|
63
|
+
expect(toCsv({ headers: ["a"], rows: [["one\ntwo"]] })).toBe(
|
|
64
|
+
'a\r\n"one\ntwo"',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("neutralizes spreadsheet formulas without changing negative numbers", () => {
|
|
69
|
+
expect(toCsv({ headers: ["a"], rows: [["=CMD()"], ["@SUM(A:A)"], ["-42"], ["-SUM(A:A)"]] })).toBe(
|
|
70
|
+
"a\r\n'=CMD()\r\n'@SUM(A:A)\r\n-42\r\n'-SUM(A:A)",
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("adds a UTF-8 BOM only for the Excel download", () => {
|
|
75
|
+
expect(toExcelCsv(SHEET).charCodeAt(0)).toBe(0xfeff);
|
|
76
|
+
expect(toCsv(SHEET).charCodeAt(0)).not.toBe(0xfeff);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("csvFilename", () => {
|
|
81
|
+
it("slugs the table id", () => {
|
|
82
|
+
expect(csvFilename("adr.index")).toBe("adr-index.csv");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("falls back when the id slugs to nothing", () => {
|
|
86
|
+
expect(csvFilename("///")).toBe("table.csv");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// The table, taken elsewhere.
|
|
2
|
+
//
|
|
3
|
+
// What leaves is the CURRENT view: filtered, sorted, visible columns only, in
|
|
4
|
+
// the order on screen. That is the whole value of it - a reader who has just
|
|
5
|
+
// narrowed forty rows to the four that matter wants those four in the issue,
|
|
6
|
+
// not the forty they started with, and not in a different order.
|
|
7
|
+
|
|
8
|
+
/** A view, flattened: the header line and the body, already in display order. */
|
|
9
|
+
export interface Sheet {
|
|
10
|
+
headers: string[];
|
|
11
|
+
rows: string[][];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A cell that would break the row it is in, made safe for one line of GFM. */
|
|
15
|
+
function markdownCell(value: string): string {
|
|
16
|
+
return value
|
|
17
|
+
.replace(/\\/g, "\\\\")
|
|
18
|
+
.replace(/\|/g, "\\|")
|
|
19
|
+
// A GFM row is a line. A cell that contains a newline is not a cell.
|
|
20
|
+
.replace(/\r?\n/g, " ")
|
|
21
|
+
.trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Valid GFM: a header row, a delimiter row of the same width, and one line
|
|
26
|
+
* per row. Column widths are not padded - a diff of a padded table is a diff
|
|
27
|
+
* of every line, and this is written to be pasted into an issue.
|
|
28
|
+
*/
|
|
29
|
+
export function toMarkdown({ headers, rows }: Sheet): string {
|
|
30
|
+
if (headers.length === 0) return "";
|
|
31
|
+
const line = (cells: string[]) =>
|
|
32
|
+
`| ${headers.map((_, i) => markdownCell(cells[i] ?? "")).join(" | ")} |`;
|
|
33
|
+
return [
|
|
34
|
+
line(headers),
|
|
35
|
+
`| ${headers.map(() => "---").join(" | ")} |`,
|
|
36
|
+
...rows.map(line),
|
|
37
|
+
].join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** RFC 4180: quote when the cell holds a comma, a quote, or a line break. */
|
|
41
|
+
function csvCell(value: string): string {
|
|
42
|
+
// A CSV is often opened as a spreadsheet. Keep text that starts like a
|
|
43
|
+
// formula as text; negative numbers remain numbers.
|
|
44
|
+
if (/^[\t\r ]*[=+@]/.test(value) || /^[\t\r ]*-(?!\d+(?:[.,]\d+)?$)/.test(value)) {
|
|
45
|
+
value = `'${value}`;
|
|
46
|
+
}
|
|
47
|
+
if (!/[",\r\n]/.test(value)) return value;
|
|
48
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function toCsv({ headers, rows }: Sheet): string {
|
|
52
|
+
const line = (cells: string[]) =>
|
|
53
|
+
headers.map((_, i) => csvCell(cells[i] ?? "")).join(",");
|
|
54
|
+
return [line(headers), ...rows.map(line)].join("\r\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** UTF-8 CSV as Excel expects it when opened directly from a download. */
|
|
58
|
+
export function toExcelCsv(sheet: Sheet): string {
|
|
59
|
+
return `\uFEFF${toCsv(sheet)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A filename a download can carry without quoting anything. */
|
|
63
|
+
export function csvFilename(tableId: string): string {
|
|
64
|
+
const slug = tableId.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
65
|
+
return `${slug === "" ? "table" : slug}.csv`;
|
|
66
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { facetGroupFrom, facetValuesFrom } from "./facet-groups";
|
|
3
|
+
import type { ColumnSpec } from "./types";
|
|
4
|
+
|
|
5
|
+
type Row = { status: string; note?: string };
|
|
6
|
+
|
|
7
|
+
const status: ColumnSpec<Row> = {
|
|
8
|
+
id: "status",
|
|
9
|
+
header: "status",
|
|
10
|
+
type: "status",
|
|
11
|
+
value: (row) => row.status,
|
|
12
|
+
facet: true,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("facetValuesFrom", () => {
|
|
16
|
+
it("skips the empty cell and counts the rest", () => {
|
|
17
|
+
const counts = new Map<unknown, number>([
|
|
18
|
+
["declared", 2],
|
|
19
|
+
[undefined, 5],
|
|
20
|
+
["verified", 3],
|
|
21
|
+
]);
|
|
22
|
+
expect(facetValuesFrom(counts, "status")).toEqual([
|
|
23
|
+
{ value: "verified", count: 3 },
|
|
24
|
+
{ value: "declared", count: 2 },
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("is no facet at all when there is only one value", () => {
|
|
29
|
+
const counts = new Map<unknown, number>([["verified", 9], [undefined, 1]]);
|
|
30
|
+
expect(facetValuesFrom(counts, "status")).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("merges keys that read the same", () => {
|
|
34
|
+
const counts = new Map<unknown, number>([[1, 2], ["1", 3], ["2", 1]]);
|
|
35
|
+
expect(facetValuesFrom(counts, "text")).toEqual([
|
|
36
|
+
{ value: "1", count: 5 },
|
|
37
|
+
{ value: "2", count: 1 },
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("orders kinds by the taxonomy, not the alphabet", () => {
|
|
42
|
+
const counts = new Map<unknown, number>([["service", 1], ["event", 1], ["aggregate", 1]]);
|
|
43
|
+
expect(facetValuesFrom(counts, "kind").map((facet) => facet.value)).toEqual([
|
|
44
|
+
"event",
|
|
45
|
+
"aggregate",
|
|
46
|
+
"service",
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("facetGroupFrom", () => {
|
|
52
|
+
const counts = new Map<unknown, number>([["verified", 1], ["declared", 1]]);
|
|
53
|
+
|
|
54
|
+
it("builds the group the toolbar draws", () => {
|
|
55
|
+
expect(facetGroupFrom(status, counts, ["declared"])).toEqual({
|
|
56
|
+
columnId: "status",
|
|
57
|
+
label: "status",
|
|
58
|
+
values: [
|
|
59
|
+
{ value: "verified", count: 1 },
|
|
60
|
+
{ value: "declared", count: 1 },
|
|
61
|
+
],
|
|
62
|
+
selected: ["declared"],
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("offers nothing for a column that is not a facet, or cannot be one", () => {
|
|
67
|
+
expect(facetGroupFrom({ ...status, facet: false }, counts, [])).toBeNull();
|
|
68
|
+
expect(facetGroupFrom({ ...status, type: "number" }, counts, [])).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("offers nothing when the values would make a set of one", () => {
|
|
72
|
+
expect(facetGroupFrom(status, new Map([["verified", 4]]), [])).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// The chip-sets, read off the table's own model.
|
|
2
|
+
//
|
|
3
|
+
// TanStack counts the distinct values of a column once, memoised against the
|
|
4
|
+
// rows; this turns that count into what the toolbar draws. Nothing here walks
|
|
5
|
+
// the data a second time, and nothing here decides which rows are in - the
|
|
6
|
+
// counts are the whole table's, so the set never changes shape under the
|
|
7
|
+
// reader's hand.
|
|
8
|
+
|
|
9
|
+
import type { FacetValue } from "./Facets";
|
|
10
|
+
import { comparatorFor } from "./compare";
|
|
11
|
+
import type { ColumnSpec, ColumnType } from "./types";
|
|
12
|
+
import { canFacet, cellText } from "./types";
|
|
13
|
+
|
|
14
|
+
export interface FacetGroup {
|
|
15
|
+
columnId: string;
|
|
16
|
+
label: string;
|
|
17
|
+
values: FacetValue[];
|
|
18
|
+
selected: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The chips of one column, from TanStack's unique-value map. The empty cell is
|
|
23
|
+
* not a value to filter by; a set of one is not a filter at all. Values read
|
|
24
|
+
* in the column's own order, so a status set is not alphabetical and a kind
|
|
25
|
+
* set is not either.
|
|
26
|
+
*/
|
|
27
|
+
export function facetValuesFrom(
|
|
28
|
+
counts: ReadonlyMap<unknown, number>,
|
|
29
|
+
type: ColumnType,
|
|
30
|
+
): FacetValue[] {
|
|
31
|
+
const merged = new Map<string, number>();
|
|
32
|
+
for (const [key, count] of counts) {
|
|
33
|
+
// The map is keyed by what the accessor returned; two keys that read the
|
|
34
|
+
// same are one chip.
|
|
35
|
+
const value = cellText(key as string | number | undefined);
|
|
36
|
+
if (value === "") continue;
|
|
37
|
+
merged.set(value, (merged.get(value) ?? 0) + count);
|
|
38
|
+
}
|
|
39
|
+
if (merged.size < 2) return [];
|
|
40
|
+
const compare = comparatorFor(type);
|
|
41
|
+
return [...merged.entries()]
|
|
42
|
+
.sort(([a], [b]) => compare(a, b))
|
|
43
|
+
.map(([value, count]) => ({ value, count }));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One toolbar group, or nothing when the column does not offer one. */
|
|
47
|
+
export function facetGroupFrom<T>(
|
|
48
|
+
spec: ColumnSpec<T>,
|
|
49
|
+
counts: ReadonlyMap<unknown, number>,
|
|
50
|
+
selected: string[],
|
|
51
|
+
): FacetGroup | null {
|
|
52
|
+
if (!spec.facet || !canFacet(spec.type)) return null;
|
|
53
|
+
const values = facetValuesFrom(counts, spec.type);
|
|
54
|
+
if (values.length === 0) return null;
|
|
55
|
+
return { columnId: spec.id, label: spec.header, values, selected };
|
|
56
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
activeGroup,
|
|
4
|
+
groupOptions,
|
|
5
|
+
groupingSorting,
|
|
6
|
+
leafRows,
|
|
7
|
+
stripeParity,
|
|
8
|
+
} from "./grouping";
|
|
9
|
+
|
|
10
|
+
describe("activeGroup", () => {
|
|
11
|
+
it("keeps a remembered column this table may fold by", () => {
|
|
12
|
+
expect(activeGroup("scope", ["status", "scope"])).toBe("scope");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("drops a column the table lacks, or may not fold by", () => {
|
|
16
|
+
expect(activeGroup("date", ["status", "scope"])).toBeNull();
|
|
17
|
+
expect(activeGroup(null, ["status"])).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("groupingSorting", () => {
|
|
22
|
+
const readers = [{ id: "date", desc: true }, { id: "title", desc: false }];
|
|
23
|
+
|
|
24
|
+
it("hands back the reader's sort untouched when nothing is grouped", () => {
|
|
25
|
+
expect(groupingSorting(readers, null)).toBe(readers);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("puts the grouping column first, ascending, ahead of the reader's keys", () => {
|
|
29
|
+
expect(groupingSorting(readers, "scope")).toEqual([
|
|
30
|
+
{ id: "scope", desc: false },
|
|
31
|
+
{ id: "date", desc: true },
|
|
32
|
+
{ id: "title", desc: false },
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("keeps the reader's own direction on the grouping column, once", () => {
|
|
37
|
+
expect(groupingSorting([{ id: "title", desc: false }, { id: "scope", desc: true }], "scope")).toEqual([
|
|
38
|
+
{ id: "scope", desc: true },
|
|
39
|
+
{ id: "title", desc: false },
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("stripeParity", () => {
|
|
45
|
+
it("gives a header no parity and starts the count again beneath it", () => {
|
|
46
|
+
expect(stripeParity([true, false, false, false, true, false, false])).toEqual([
|
|
47
|
+
null, true, false, true, null, true, false,
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("is plain alternation without headers", () => {
|
|
52
|
+
expect(stripeParity([false, false, false])).toEqual([true, false, true]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("groupOptions", () => {
|
|
57
|
+
it("offers exactly the columns that have a chip-set", () => {
|
|
58
|
+
expect(
|
|
59
|
+
groupOptions([
|
|
60
|
+
{ columnId: "status", label: "status", values: [], selected: [] },
|
|
61
|
+
{ columnId: "scope", label: "scope", values: [], selected: [] },
|
|
62
|
+
]),
|
|
63
|
+
).toEqual([
|
|
64
|
+
{ id: "status", header: "status" },
|
|
65
|
+
{ id: "scope", header: "scope" },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("leafRows", () => {
|
|
71
|
+
type R = { id: string; subRows: R[]; getIsGrouped: () => boolean };
|
|
72
|
+
const leaf = (id: string): R => ({ id, subRows: [], getIsGrouped: () => false });
|
|
73
|
+
const group = (id: string, subRows: R[]): R => ({ id, subRows, getIsGrouped: () => true });
|
|
74
|
+
|
|
75
|
+
it("walks into every group, open or not, and keeps the display order", () => {
|
|
76
|
+
const rows = [group("g1", [leaf("a"), leaf("b")]), group("g2", [leaf("c")])];
|
|
77
|
+
expect(leafRows(rows).map((row) => row.id)).toEqual(["a", "b", "c"]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("is the identity on an ungrouped table", () => {
|
|
81
|
+
const rows = [leaf("a"), leaf("b")];
|
|
82
|
+
expect(leafRows(rows)).toEqual(rows);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Folding a table by one of its columns.
|
|
2
|
+
//
|
|
3
|
+
// Grouping is the reader's, one column at a time, chosen from the columns
|
|
4
|
+
// that already offer a chip-set: a column worth filtering by is a column
|
|
5
|
+
// worth folding by, and no other column is. TanStack builds the groups; what
|
|
6
|
+
// is decided here is which column may be one, how the groups are ordered, and
|
|
7
|
+
// how the stripes read once a header row sits between two leaves.
|
|
8
|
+
|
|
9
|
+
import type { FacetGroup } from "./facet-groups";
|
|
10
|
+
import type { SortEntry } from "./sort-url";
|
|
11
|
+
|
|
12
|
+
export interface GroupOption {
|
|
13
|
+
id: string;
|
|
14
|
+
header: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The remembered column, if this table still has it and may fold by it. */
|
|
18
|
+
export function activeGroup(
|
|
19
|
+
remembered: string | null,
|
|
20
|
+
eligible: readonly string[],
|
|
21
|
+
): string | null {
|
|
22
|
+
return remembered !== null && eligible.includes(remembered) ? remembered : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The sort the table runs when grouped: the grouping column first, so the
|
|
27
|
+
* groups follow that column's own order, then the reader's keys, which order
|
|
28
|
+
* the leaves inside each group. The reader's own direction on the grouping
|
|
29
|
+
* column is kept, so clicking its header turns the groups around.
|
|
30
|
+
*
|
|
31
|
+
* Derived, never written back: the URL and the page's memory still say only
|
|
32
|
+
* what the reader clicked.
|
|
33
|
+
*/
|
|
34
|
+
export function groupingSorting(
|
|
35
|
+
sorting: readonly SortEntry[],
|
|
36
|
+
groupBy: string | null,
|
|
37
|
+
): SortEntry[] {
|
|
38
|
+
if (groupBy === null) return sorting as SortEntry[];
|
|
39
|
+
const own = sorting.find((entry) => entry.id === groupBy);
|
|
40
|
+
return [
|
|
41
|
+
{ id: groupBy, desc: own?.desc ?? false },
|
|
42
|
+
...sorting.filter((entry) => entry.id !== groupBy),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Stripe parity per display row. A header row carries no parity, so the
|
|
48
|
+
* stripe rule cannot match it, and the count starts again beneath it: every
|
|
49
|
+
* group then opens on the same stripe, and a header with its first row always
|
|
50
|
+
* reads as the same pair.
|
|
51
|
+
*/
|
|
52
|
+
export function stripeParity(isGroup: readonly boolean[]): (boolean | null)[] {
|
|
53
|
+
let leaf = 0;
|
|
54
|
+
return isGroup.map((group) => {
|
|
55
|
+
if (group) {
|
|
56
|
+
leaf = 0;
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return leaf++ % 2 === 0;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The columns the picker offers: exactly the columns with a chip-set. */
|
|
64
|
+
export function groupOptions(groups: readonly FacetGroup[]): GroupOption[] {
|
|
65
|
+
return groups.map((group) => ({ id: group.columnId, header: group.label }));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The leaves under a list of rows, in display order, whether or not their
|
|
70
|
+
* group is open. An export is the view, and a folded group is still in it.
|
|
71
|
+
*/
|
|
72
|
+
export function leafRows<R extends { subRows: R[]; getIsGrouped: () => boolean }>(
|
|
73
|
+
rows: readonly R[],
|
|
74
|
+
): R[] {
|
|
75
|
+
const out: R[] = [];
|
|
76
|
+
const walk = (row: R) => {
|
|
77
|
+
if (row.getIsGrouped()) row.subRows.forEach(walk);
|
|
78
|
+
else out.push(row);
|
|
79
|
+
};
|
|
80
|
+
rows.forEach(walk);
|
|
81
|
+
return out;
|
|
82
|
+
}
|