@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,783 @@
|
|
|
1
|
+
// Flows, read out of the same layout as everything else here, and the layout
|
|
2
|
+
// is again the claim.
|
|
3
|
+
//
|
|
4
|
+
// Two things start one. An endpoint is somebody calling in, and the handler
|
|
5
|
+
// says which use cases it runs and in what order. A policy is an event
|
|
6
|
+
// arriving, and the type or the name it tests for says which. Everything
|
|
7
|
+
// after that is the use case's own body: a constructor parameter is a port, a
|
|
8
|
+
// call on that port is a hop, and a value that a domain method handed back as
|
|
9
|
+
// an event is what puts the event on the bus when a port is given it.
|
|
10
|
+
//
|
|
11
|
+
// Statements are read in source order. An `if` or a `switch` becomes an alt
|
|
12
|
+
// only when something happens inside it, and its branch is terminal when the
|
|
13
|
+
// block ends in a return or a throw. A loop is a note on the steps inside it.
|
|
14
|
+
// Nothing is observed running, so every step is declared; a call whose peer
|
|
15
|
+
// the manifest does not name is unresolved.
|
|
16
|
+
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import type { Alt, AltBranch, Flow, FlowNode, Participant, RpcCall, Status, Step } from "../../src/catalog.ts";
|
|
19
|
+
import { adapterCalls, callsIn, type RpcHop } from "./clients.ts";
|
|
20
|
+
import { camel, eventID, slug } from "./ids.ts";
|
|
21
|
+
import { readSource, at, bareType, returnTypeText, sourceFiles, sourceNamed, text, type ClassInfo, type Source } from "./source.ts";
|
|
22
|
+
import { isArrayPattern, isBinary, isBlock, isCall, isForEach, isFor, isFunctionType, isIdent, isIf, isMember, isMethodSig, isPropertySig, isReturn, isSourceFile, isSpread, isString, isSwitch, isSwitchCase, isThis, isThrow, isTry, isVarDecl, isWhile, isAssign, keyName, memberName, paramIdent, thisMember, typeText, unwrap, walk, type CallExpression, type ForEachStatement, type IfStatement, type Node, type SwitchStatement } from "./ast.ts";
|
|
23
|
+
import type { UseCase } from "./operations.ts";
|
|
24
|
+
import { PORTS, type Binding } from "./wiring.ts";
|
|
25
|
+
import type { AggregateRead, WarningSink } from "./domain.ts";
|
|
26
|
+
import type { Endpoint } from "./transport.ts";
|
|
27
|
+
import type { Resolver } from "./graphql.ts";
|
|
28
|
+
|
|
29
|
+
export const LANE_CLIENT = "client";
|
|
30
|
+
export const LANE_BUS = "bus";
|
|
31
|
+
const MAX_INLINE = 2;
|
|
32
|
+
|
|
33
|
+
export interface FlowOptions {
|
|
34
|
+
context: string;
|
|
35
|
+
svcID: string;
|
|
36
|
+
service: string;
|
|
37
|
+
store: string;
|
|
38
|
+
peers: Record<string, string>;
|
|
39
|
+
/** Import specifier of another service's events → the aggregate id they belong to. */
|
|
40
|
+
events: Record<string, string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** What one use case body is being read against. */
|
|
44
|
+
interface Scope {
|
|
45
|
+
src: Source;
|
|
46
|
+
key: string;
|
|
47
|
+
cls: ClassInfo;
|
|
48
|
+
/**
|
|
49
|
+
* The identifier the ports arrive on, for a body that is not a method: a
|
|
50
|
+
* resolver is handed a context rather than holding fields, so `ctx.baskets`
|
|
51
|
+
* is what `this.baskets` is to a use case. Empty for a method.
|
|
52
|
+
*/
|
|
53
|
+
self?: string;
|
|
54
|
+
/** Port name → type as written. */
|
|
55
|
+
ports: Map<string, string>;
|
|
56
|
+
/** Local name → what it holds, when it is an event or a domain object. */
|
|
57
|
+
vars: Map<string, DomainRef>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface DomainRef {
|
|
61
|
+
/** The class name. */
|
|
62
|
+
name: string;
|
|
63
|
+
/** Its event id, when it is an event. */
|
|
64
|
+
event?: string;
|
|
65
|
+
/** The aggregate it belongs to, for a domain object whose methods hand back events. */
|
|
66
|
+
aggregate?: AggregateRead;
|
|
67
|
+
/** What a list the use case is collecting has been given, for `events.push(...)` handed on as `...events`. */
|
|
68
|
+
items?: DomainRef[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class Draft {
|
|
72
|
+
lanes: Participant[] = [];
|
|
73
|
+
steps: FlowNode[] = [];
|
|
74
|
+
private sinks: FlowNode[][] = [];
|
|
75
|
+
private n = 0;
|
|
76
|
+
readonly seen = new Set<string>();
|
|
77
|
+
private loops: string[] = [];
|
|
78
|
+
|
|
79
|
+
lane(p: Participant): string {
|
|
80
|
+
if (!this.lanes.some((l) => l.id === p.id)) this.lanes.push(p);
|
|
81
|
+
return p.id;
|
|
82
|
+
}
|
|
83
|
+
sink(): FlowNode[] {
|
|
84
|
+
return this.sinks[this.sinks.length - 1] ?? this.steps;
|
|
85
|
+
}
|
|
86
|
+
push(): void {
|
|
87
|
+
this.sinks.push([]);
|
|
88
|
+
}
|
|
89
|
+
pop(): FlowNode[] {
|
|
90
|
+
return this.sinks.pop() ?? [];
|
|
91
|
+
}
|
|
92
|
+
enter(loop: string): void {
|
|
93
|
+
this.loops.push(loop);
|
|
94
|
+
}
|
|
95
|
+
leave(): void {
|
|
96
|
+
this.loops.pop();
|
|
97
|
+
}
|
|
98
|
+
note(own: string): string {
|
|
99
|
+
const loops = [...new Set(this.loops.filter(Boolean))];
|
|
100
|
+
const prefix = loops.length ? `${loops.join(", ")}.` : "";
|
|
101
|
+
return `${prefix} ${own}`.trim();
|
|
102
|
+
}
|
|
103
|
+
add(step: Omit<Step, "type" | "id" | "status"> & { status?: Status }): void {
|
|
104
|
+
this.n += 1;
|
|
105
|
+
const note = this.note(step.note ?? "");
|
|
106
|
+
const out: Step = { type: "step", id: `s${this.n}`, from: step.from, to: step.to, kind: step.kind, label: step.label, status: step.status ?? "declared" };
|
|
107
|
+
if (step.ref) out.ref = step.ref;
|
|
108
|
+
if (note) out.note = note;
|
|
109
|
+
if (step.line) out.line = step.line;
|
|
110
|
+
this.sink().push(out);
|
|
111
|
+
}
|
|
112
|
+
addAlt(branches: AltBranch[]): void {
|
|
113
|
+
this.n += 1;
|
|
114
|
+
const alt: Alt = { type: "alt", id: `alt${this.n}`, branches };
|
|
115
|
+
this.sink().push(alt);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class FlowReader {
|
|
120
|
+
readonly calls = new Map<string, RpcCall>();
|
|
121
|
+
readonly referenced = new Set<string>();
|
|
122
|
+
private warnedStore = false;
|
|
123
|
+
private readonly warnedPeer = new Set<string>();
|
|
124
|
+
readonly opts: FlowOptions;
|
|
125
|
+
readonly useCases: Map<string, UseCase>;
|
|
126
|
+
readonly bindings: Map<string, Binding[]>;
|
|
127
|
+
readonly aggregates: AggregateRead[];
|
|
128
|
+
readonly rel: (abs: string) => string;
|
|
129
|
+
readonly b: WarningSink;
|
|
130
|
+
|
|
131
|
+
constructor(opts: FlowOptions, useCases: Map<string, UseCase>, bindings: Map<string, Binding[]>, aggregates: AggregateRead[], rel: (abs: string) => string, b: WarningSink) {
|
|
132
|
+
this.opts = opts;
|
|
133
|
+
this.useCases = useCases;
|
|
134
|
+
this.bindings = bindings;
|
|
135
|
+
this.aggregates = aggregates;
|
|
136
|
+
this.rel = rel;
|
|
137
|
+
this.b = b;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
serviceLane(): Participant {
|
|
141
|
+
return { id: this.opts.svcID, kind: "service", context: this.opts.context };
|
|
142
|
+
}
|
|
143
|
+
busLane(): Participant {
|
|
144
|
+
return { id: LANE_BUS, kind: "broker", context: null };
|
|
145
|
+
}
|
|
146
|
+
storeLane(d: Draft): string {
|
|
147
|
+
if (!this.opts.store) {
|
|
148
|
+
if (!this.warnedStore) {
|
|
149
|
+
this.b.warn(this.opts.svcID, "no store named in the options, so repository calls stay on the service's own lane");
|
|
150
|
+
this.warnedStore = true;
|
|
151
|
+
}
|
|
152
|
+
return this.opts.svcID;
|
|
153
|
+
}
|
|
154
|
+
return d.lane({ id: `${this.opts.service}-${this.opts.store}`, kind: "store", context: this.opts.context });
|
|
155
|
+
}
|
|
156
|
+
peerLane(d: Draft, pkg: string): { lane: string; peer: string; status: Status } {
|
|
157
|
+
const service = this.opts.peers[pkg];
|
|
158
|
+
if (service) {
|
|
159
|
+
const context = service.slice(0, service.indexOf("."));
|
|
160
|
+
return { lane: d.lane({ id: service, kind: "service", context }), peer: service, status: "declared" };
|
|
161
|
+
}
|
|
162
|
+
if (!this.warnedPeer.has(pkg)) {
|
|
163
|
+
this.warnedPeer.add(pkg);
|
|
164
|
+
this.b.warn(this.opts.svcID, `calls ${pkg} and the manifest names no peer for that package; add it under \`peers\` to say which service answers, until then the calls are unresolved`);
|
|
165
|
+
}
|
|
166
|
+
return { lane: d.lane({ id: pkg.replaceAll(".", "-"), kind: "unknown", context: null, label: pkg }), peer: pkg, status: "unresolved" };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
consumes(): RpcCall[] {
|
|
170
|
+
return [...this.calls.values()].sort((a, c) => a.id.localeCompare(c.id));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// --- the two openings -----------------------------------------------------
|
|
174
|
+
|
|
175
|
+
endpointFlow(endpoint: Endpoint): Flow | null {
|
|
176
|
+
const d = new Draft();
|
|
177
|
+
d.lane({ id: LANE_CLIENT, kind: "actor", context: null });
|
|
178
|
+
d.lane(this.serviceLane());
|
|
179
|
+
d.add({ from: LANE_CLIENT, to: this.opts.svcID, kind: "rpc", label: endpoint.id, line: endpoint.line });
|
|
180
|
+
for (const key of endpoint.useCases) this.walkUseCase(d, key, 0);
|
|
181
|
+
const last = endpoint.useCases[endpoint.useCases.length - 1];
|
|
182
|
+
const name = slug(endpoint.id);
|
|
183
|
+
const id = `${this.opts.service}-${name}`;
|
|
184
|
+
return {
|
|
185
|
+
id: `flow.${id}`,
|
|
186
|
+
slug: id,
|
|
187
|
+
name: sentence(name),
|
|
188
|
+
summary: last ? this.useCaseSummary(last) : "",
|
|
189
|
+
source: endpoint.source,
|
|
190
|
+
owner: this.opts.context,
|
|
191
|
+
participants: d.lanes,
|
|
192
|
+
steps: d.steps,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A field of the schema, opened by whoever asked for it.
|
|
198
|
+
*
|
|
199
|
+
* The counterpart of endpointFlow for a service whose way in is a graph:
|
|
200
|
+
* the step from the client is the field, named as the schema names it, and
|
|
201
|
+
* everything after it is the resolver's own body - a call on a port is a
|
|
202
|
+
* hop, exactly as it is inside a use case.
|
|
203
|
+
*/
|
|
204
|
+
resolverFlow(resolver: Resolver): Flow | null {
|
|
205
|
+
const d = new Draft();
|
|
206
|
+
d.lane({ id: LANE_CLIENT, kind: "actor", context: null });
|
|
207
|
+
d.lane(this.serviceLane());
|
|
208
|
+
d.add({ from: LANE_CLIENT, to: this.opts.svcID, kind: "rpc", label: resolver.id, line: resolver.line });
|
|
209
|
+
|
|
210
|
+
const cls: ClassInfo = { name: resolver.id, node: null as never, doc: "", exported: true, fields: [], params: [], methods: new Map(), nameLiteral: undefined };
|
|
211
|
+
const scope: Scope = { src: resolver.src, key: `resolver/${resolver.id}`, cls, ports: resolver.ports, vars: new Map() };
|
|
212
|
+
if (resolver.self) scope.self = resolver.self;
|
|
213
|
+
this.walkBody(d, scope, resolver.body, 0);
|
|
214
|
+
|
|
215
|
+
const name = slug(resolver.id);
|
|
216
|
+
const id = `${this.opts.service}-${name}`;
|
|
217
|
+
return {
|
|
218
|
+
id: `flow.${id}`,
|
|
219
|
+
slug: id,
|
|
220
|
+
name: sentence(name),
|
|
221
|
+
summary: resolver.doc.split(/\n\s*\n/)[0]?.replace(/\s+/g, " ") ?? "",
|
|
222
|
+
source: this.rel(resolver.src.path),
|
|
223
|
+
owner: this.opts.context,
|
|
224
|
+
participants: d.lanes,
|
|
225
|
+
steps: d.steps,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
policyFlows(policyDir: string): Flow[] {
|
|
230
|
+
const out: Flow[] = [];
|
|
231
|
+
let files: string[] = [];
|
|
232
|
+
try {
|
|
233
|
+
files = require("node:fs").readdirSync(policyDir).filter(isSourceFile).sort();
|
|
234
|
+
} catch {
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
237
|
+
for (const file of files) {
|
|
238
|
+
const src = readSource(join(policyDir, file));
|
|
239
|
+
if (!src) continue;
|
|
240
|
+
for (const cls of src.classes) {
|
|
241
|
+
const handle = cls.methods.get("handle");
|
|
242
|
+
if (!cls.exported || !handle?.body) continue;
|
|
243
|
+
const trigger = this.assertedEvent(src, handle.node);
|
|
244
|
+
if (!trigger) {
|
|
245
|
+
this.b.warn(cls.name, `${this.rel(src.path)}: ${cls.name}.handle tests for no event; the policy is not paired with what triggers it`);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const d = new Draft();
|
|
249
|
+
d.lane(this.busLane());
|
|
250
|
+
d.lane(this.serviceLane());
|
|
251
|
+
const line = at(src, handle.node, this.rel);
|
|
252
|
+
if (trigger.foreign) {
|
|
253
|
+
this.b.warn(cls.name, `${this.rel(src.path)}: ${cls.name}.handle reacts to ${trigger.name} from ${trigger.foreign}, an event this repository does not declare and the manifest's \`events\` does not place; the step is unresolved`);
|
|
254
|
+
d.add({ from: LANE_BUS, to: this.opts.svcID, kind: "event", label: trigger.name, status: "unresolved", note: `Reacts to \`${trigger.name}\` from \`${trigger.foreign}\`, which is not an event this repository declares.`, line });
|
|
255
|
+
} else {
|
|
256
|
+
d.add({ from: LANE_BUS, to: this.opts.svcID, kind: "event", label: trigger.name, ref: trigger.id, line });
|
|
257
|
+
this.referenced.add(trigger.id!);
|
|
258
|
+
}
|
|
259
|
+
this.walkBody(d, { src, key: `policy/${cls.name}`, cls, ports: new Map(cls.params.map((p) => [p.name, p.type])), vars: new Map([[paramIdent(handle.params[0]!)?.name ?? "event", { name: trigger.name, event: trigger.id }]]) }, handle.body, 0);
|
|
260
|
+
const id = `${this.opts.service}-${slug(cls.name)}`;
|
|
261
|
+
out.push({ id: `flow.${id}`, slug: id, name: sentence(slug(cls.name)), summary: cls.doc.split(/\n\s*\n/)[0]?.replace(/\s+/g, " ") ?? "", source: this.rel(src.path), owner: this.opts.context, participants: d.lanes, steps: d.steps });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** `event instanceof X`, `event.name === "…"`, or `switch (event.name) { case "…" }`. */
|
|
268
|
+
private assertedEvent(src: Source, fn: Node): { name: string; id?: string; foreign?: string } | undefined {
|
|
269
|
+
let found: { name: string; id?: string; foreign?: string } | undefined;
|
|
270
|
+
const byName = (wire: string): typeof found => {
|
|
271
|
+
for (const agg of this.aggregates) {
|
|
272
|
+
for (const [cls, id] of agg.events) {
|
|
273
|
+
const evSrc = readSource(sourceNamed(join(agg.dir, "events"), slug(cls))) ?? readSource(sourceNamed(join(agg.dir, "events"), cls));
|
|
274
|
+
const lit = evSrc?.classes.find((c) => c.name === cls)?.nameLiteral;
|
|
275
|
+
if (lit === wire) return { name: cls, id };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
};
|
|
280
|
+
const byClass = (local: string): typeof found => {
|
|
281
|
+
const imp = src.imports.find((i) => i.local === local);
|
|
282
|
+
for (const agg of this.aggregates) {
|
|
283
|
+
const id = agg.events.get(imp?.imported ?? local);
|
|
284
|
+
if (id && imp?.file && imp.file.startsWith(agg.dir)) return { name: imp.imported, id };
|
|
285
|
+
}
|
|
286
|
+
if (imp && !imp.file) {
|
|
287
|
+
const aggregate = this.opts.events[imp.specifier];
|
|
288
|
+
return aggregate ? { name: imp.imported, id: eventID(aggregate, imp.imported) } : { name: imp.imported, foreign: imp.specifier };
|
|
289
|
+
}
|
|
290
|
+
if (imp?.file) {
|
|
291
|
+
const aggregate = this.opts.events[imp.specifier];
|
|
292
|
+
return aggregate ? { name: imp.imported, id: eventID(aggregate, imp.imported) } : { name: imp.imported, foreign: imp.specifier };
|
|
293
|
+
}
|
|
294
|
+
return undefined;
|
|
295
|
+
};
|
|
296
|
+
walk(fn, (node) => {
|
|
297
|
+
if (found) return;
|
|
298
|
+
if (isBinary(node) && node.operator === "instanceof" && isIdent(node.right)) {
|
|
299
|
+
found = byClass(node.right.name);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (isBinary(node) && (node.operator === "===" || node.operator === "==")) {
|
|
303
|
+
const lit = [node.left, node.right].find((e) => isString(e));
|
|
304
|
+
if (isString(lit)) found = byName(lit.value);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (isSwitchCase(node) && isString(node.test)) found = byName(node.test.value);
|
|
308
|
+
});
|
|
309
|
+
return found;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// --- reading one use case ---------------------------------------------------
|
|
313
|
+
|
|
314
|
+
private useCaseSummary(key: string): string {
|
|
315
|
+
const uc = this.useCases.get(key);
|
|
316
|
+
if (!uc) return "";
|
|
317
|
+
return uc.cls.doc.split(/\n\s*\n/)[0]?.replace(/\s+/g, " ") ?? "";
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
walkUseCase(d: Draft, key: string, depth: number): void {
|
|
321
|
+
if (depth > MAX_INLINE || d.seen.has(key)) return;
|
|
322
|
+
d.seen.add(key);
|
|
323
|
+
const uc = this.useCases.get(key);
|
|
324
|
+
if (!uc) return;
|
|
325
|
+
const handle = uc.cls.methods.get("handle");
|
|
326
|
+
if (!handle) return;
|
|
327
|
+
this.walkBody(d, { src: uc.source, key, cls: uc.cls, ports: new Map(uc.cls.params.map((p) => [p.name, p.type])), vars: new Map() }, handle.body, depth);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private walkBody(d: Draft, s: Scope, body: Node | null, depth: number): void {
|
|
331
|
+
if (!body || !isBlock(body)) return;
|
|
332
|
+
this.walkStmts(d, s, body.body, depth);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private walkStmts(d: Draft, s: Scope, list: readonly Node[], depth: number): void {
|
|
336
|
+
for (const stmt of list) this.walkStmt(d, s, stmt, depth);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private walkStmt(d: Draft, s: Scope, stmt: Node, depth: number): void {
|
|
340
|
+
if (isIf(stmt)) return this.walkIf(d, s, stmt, depth);
|
|
341
|
+
if (isSwitch(stmt)) return this.walkSwitch(d, s, stmt, depth);
|
|
342
|
+
if (isForEach(stmt)) {
|
|
343
|
+
this.callsIn(d, s, stmt.right, depth);
|
|
344
|
+
this.bindElement(s, stmt);
|
|
345
|
+
d.enter(`inside a loop over \`${text(s.src, stmt.right)}\``);
|
|
346
|
+
this.walkBlock(d, s, stmt.body, depth);
|
|
347
|
+
d.leave();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (isFor(stmt) || isWhile(stmt)) {
|
|
351
|
+
const cond = stmt.test;
|
|
352
|
+
if (isFor(stmt) && stmt.init) this.callsIn(d, s, stmt.init, depth);
|
|
353
|
+
d.enter(cond ? `inside a loop, while \`${text(s.src, cond)}\`` : "inside a loop");
|
|
354
|
+
this.walkBlock(d, s, stmt.body, depth);
|
|
355
|
+
d.leave();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (isBlock(stmt)) return this.walkStmts(d, s, stmt.body, depth);
|
|
359
|
+
if (isTry(stmt)) {
|
|
360
|
+
this.walkStmts(d, s, stmt.block.body, depth);
|
|
361
|
+
if (stmt.handler) this.walkStmts(d, s, stmt.handler.body.body, depth);
|
|
362
|
+
if (stmt.finalizer) this.walkStmts(d, s, stmt.finalizer.body, depth);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.callsIn(d, s, stmt, depth);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private walkBlock(d: Draft, s: Scope, stmt: Node, depth: number): void {
|
|
369
|
+
if (isBlock(stmt)) this.walkStmts(d, s, stmt.body, depth);
|
|
370
|
+
else this.walkStmt(d, s, stmt, depth);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
private walkIf(d: Draft, s: Scope, stmt: IfStatement, depth: number): void {
|
|
374
|
+
const branches: AltBranch[] = [];
|
|
375
|
+
const titles = new Set<string>();
|
|
376
|
+
let drew = false;
|
|
377
|
+
let current: IfStatement | undefined = stmt;
|
|
378
|
+
while (current) {
|
|
379
|
+
this.callsIn(d, s, current.test, depth);
|
|
380
|
+
d.push();
|
|
381
|
+
this.walkBlock(d, s, current.consequent, depth);
|
|
382
|
+
const steps = d.pop();
|
|
383
|
+
drew ||= steps.length > 0;
|
|
384
|
+
branches.push({ title: unique(text(s.src, current.test), titles), steps, terminal: leaves(current.consequent) });
|
|
385
|
+
const els: Node | null = current.alternate;
|
|
386
|
+
if (!els) {
|
|
387
|
+
branches.push({ title: unique("otherwise", titles), steps: [] });
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
if (isIf(els)) {
|
|
391
|
+
current = els;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
d.push();
|
|
395
|
+
this.walkBlock(d, s, els, depth);
|
|
396
|
+
const elseSteps = d.pop();
|
|
397
|
+
drew ||= elseSteps.length > 0;
|
|
398
|
+
branches.push({ title: unique("otherwise", titles), steps: elseSteps, terminal: leaves(els) });
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
if (!drew) return;
|
|
402
|
+
d.addAlt(unmarkIfAllLeave(branches));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private walkSwitch(d: Draft, s: Scope, stmt: SwitchStatement, depth: number): void {
|
|
406
|
+
this.callsIn(d, s, stmt.discriminant, depth);
|
|
407
|
+
const subject = text(s.src, stmt.discriminant);
|
|
408
|
+
const branches: AltBranch[] = [];
|
|
409
|
+
const titles = new Set<string>();
|
|
410
|
+
let drew = false;
|
|
411
|
+
let sawDefault = false;
|
|
412
|
+
for (const clause of stmt.cases) {
|
|
413
|
+
if (clause.test) this.callsIn(d, s, clause.test, depth);
|
|
414
|
+
d.push();
|
|
415
|
+
this.walkStmts(d, s, clause.consequent, depth);
|
|
416
|
+
const steps = d.pop();
|
|
417
|
+
drew ||= steps.length > 0;
|
|
418
|
+
const title = clause.test ? `${subject} is ${text(s.src, clause.test)}` : "otherwise";
|
|
419
|
+
if (!clause.test) sawDefault = true;
|
|
420
|
+
const last = clause.consequent[clause.consequent.length - 1];
|
|
421
|
+
branches.push({ title: unique(title, titles), steps, terminal: last ? leaves(last) : false });
|
|
422
|
+
}
|
|
423
|
+
if (!drew) return;
|
|
424
|
+
if (!sawDefault) branches.push({ title: unique("otherwise", titles), steps: [] });
|
|
425
|
+
d.addAlt(unmarkIfAllLeave(branches));
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Every call under a node, in source order. */
|
|
429
|
+
private callsIn(d: Draft, s: Scope, node: Node, depth: number): void {
|
|
430
|
+
const assigned = new Map<CallExpression, Node>();
|
|
431
|
+
walk(node, (n) => {
|
|
432
|
+
if (isVarDecl(n)) {
|
|
433
|
+
for (const decl of n.declarations) {
|
|
434
|
+
const call = decl.init ? unwrap(decl.init) : undefined;
|
|
435
|
+
if (isCall(call)) assigned.set(call, decl.id);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
walk(node, (n) => {
|
|
440
|
+
if (isCall(n)) this.call(d, s, n, assigned.get(n), depth);
|
|
441
|
+
// `into = created`: a later assignment carries what the right side held.
|
|
442
|
+
if (isAssign(n) && n.operator === "=" && isIdent(n.left)) {
|
|
443
|
+
const r = this.resultsOfExpr(s, n.right)[0];
|
|
444
|
+
if (r?.name) s.vars.set(n.left.name, r);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private call(d: Draft, s: Scope, call: CallExpression, lhs: Node | undefined, depth: number): void {
|
|
450
|
+
const callee = call.callee;
|
|
451
|
+
if (!isMember(callee)) {
|
|
452
|
+
// A bare function: a domain constructor imported from the aggregate, `createBasket(...)`.
|
|
453
|
+
if (isIdent(callee)) this.bind(s, lhs, this.resultsOfFunction(s, callee.name));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const method = memberName(callee);
|
|
457
|
+
if (method === undefined) return;
|
|
458
|
+
const target = callee.object;
|
|
459
|
+
|
|
460
|
+
// events.push(into.addItem(...)) — a list the use case collects, to hand to a port later as `...events`.
|
|
461
|
+
if (method === "push" && isIdent(target)) {
|
|
462
|
+
const list = s.vars.get(target.name) ?? { name: target.name };
|
|
463
|
+
list.items ??= [];
|
|
464
|
+
for (const arg of call.arguments) for (const r of this.resultsOfExpr(s, arg)) if (r.event) list.items.push(r);
|
|
465
|
+
s.vars.set(target.name, list);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// this.<port>.<method>(...) — a hop. And `ctx.<port>.<method>(...)`, which
|
|
470
|
+
// is the same sentence in a resolver: the ports arrive on a parameter
|
|
471
|
+
// rather than on the instance.
|
|
472
|
+
const port = thisMember(target) ?? selfMember(s, target);
|
|
473
|
+
if (port !== undefined) {
|
|
474
|
+
const declared = s.ports.get(port);
|
|
475
|
+
if (declared !== undefined) this.portCall(d, s, port, declared, method, call, lhs, depth);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// this.<helper>(...) — the same use case, another method.
|
|
479
|
+
if (isThis(target)) {
|
|
480
|
+
const helper = s.cls.methods.get(method);
|
|
481
|
+
if (helper?.body) this.walkStmts(d, s, helper.body.body, depth);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
// Basket.create(...) — a static domain constructor; basket.addItem(...) — a method on something the domain handed over.
|
|
485
|
+
if (isIdent(target)) {
|
|
486
|
+
const held = s.vars.get(target.name);
|
|
487
|
+
if (held?.aggregate) {
|
|
488
|
+
this.bind(s, lhs, this.resultsOfMethod(held, method));
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
this.bind(s, lhs, this.resultsOfStatic(s, target.name, method));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private portCall(d: Draft, s: Scope, port: string, declared: string, method: string, call: CallExpression, lhs: Node | undefined, depth: number): void {
|
|
496
|
+
if (/^\(.*\)\s*=>/.test(declared) || declared === "") return; // a clock, an id generator
|
|
497
|
+
|
|
498
|
+
const src = s.src;
|
|
499
|
+
const line = at(src, call, this.rel);
|
|
500
|
+
const bare = bareType(declared);
|
|
501
|
+
|
|
502
|
+
// A port bound in assembly to another use case, or to an adapter over a
|
|
503
|
+
// client. It is looked up under the use case that declares it, which is
|
|
504
|
+
// not always this one: a use case may take a port another declared rather
|
|
505
|
+
// than say the same thing twice. And assembly may bind it more than once -
|
|
506
|
+
// the adapter over a peer when the peer is named, a stand-in when it is
|
|
507
|
+
// not - so the binding shown is the first that reaches a peer.
|
|
508
|
+
const bound = this.bindings.get(`${this.portOwner(src, bare) ?? s.key}.${bare}`) ?? this.bindings.get(`${PORTS}.${bare}`) ?? [];
|
|
509
|
+
const toUseCase = bound.find((b) => b.useCase)?.useCase;
|
|
510
|
+
if (toUseCase) {
|
|
511
|
+
this.useCaseHop(d, toUseCase, `Port \`${bare}\`, bound at assembly to the ${camel(toUseCase.split("/")[1] ?? "")} use case.`, line, depth);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const adapters = bound.flatMap((b) => (b.adapter ? [b.adapter] : []));
|
|
515
|
+
if (adapters.length > 0) {
|
|
516
|
+
for (const adapter of adapters) {
|
|
517
|
+
const adapterSrc = readSource(adapter.file);
|
|
518
|
+
if (!adapterSrc) continue;
|
|
519
|
+
const hops = adapterCalls(adapterSrc, adapter.cls, method, this.rel, this.b);
|
|
520
|
+
if (hops.length === 0) continue;
|
|
521
|
+
for (const hop of hops) this.rpcHop(d, hop, line);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
// An adapter over the bus calls nobody: it waits, and what it hears is
|
|
525
|
+
// said by the channel the service declares, not by a step in a flow.
|
|
526
|
+
// Every other adapter that reaches no peer is worth reporting.
|
|
527
|
+
if (!adapters.every((a) => isBusAdapter(a.file))) {
|
|
528
|
+
this.b.warn(s.key, `port \`${bare}\` is adapted by ${adapters.map((a) => `${a.cls}.${method}`).join(" and by ")}, which calls no peer; the call is left out of the flow`);
|
|
529
|
+
}
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// A port that is another use case outright.
|
|
534
|
+
const imp = src.imports.find((i) => i.local === bare);
|
|
535
|
+
if (imp?.file && imp.imported === "UseCase") {
|
|
536
|
+
const key = useCaseKeyFromFile(imp.file);
|
|
537
|
+
if (key) this.useCaseHop(d, key, "", line, depth);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// The generated client itself.
|
|
542
|
+
const direct = callsIn(src, call, this.rel, this.b);
|
|
543
|
+
if (direct.length > 0) {
|
|
544
|
+
for (const hop of direct) this.rpcHop(d, hop, line);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// A port of the domain: the store is at the other end of it.
|
|
549
|
+
const aggregate = this.domainPortAggregate(src, bare);
|
|
550
|
+
if (!aggregate) {
|
|
551
|
+
this.b.warn(s.key, `${this.rel(src.path)}: port \`${port}: ${declared}\` is neither a domain port, a use case nor a client; its calls are left out of the flow`);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
d.add({ from: this.opts.svcID, to: this.storeLane(d), kind: "call", label: method, line });
|
|
555
|
+
|
|
556
|
+
// An event handed to the port - the value itself, not a field read off
|
|
557
|
+
// one - is the event leaving for the bus. What carries it there, an
|
|
558
|
+
// outbox, a relay, is the adapter's business and not a step the source
|
|
559
|
+
// can show.
|
|
560
|
+
const handed = new Set<string>();
|
|
561
|
+
for (const arg of call.arguments) {
|
|
562
|
+
const inner = isSpread(arg) ? arg.argument! : arg;
|
|
563
|
+
if (!isIdent(inner)) continue;
|
|
564
|
+
const held = s.vars.get(inner.name);
|
|
565
|
+
for (const item of held?.items ?? (held ? [held] : [])) {
|
|
566
|
+
// A list handed over says which events leave, not how many times.
|
|
567
|
+
if (!item.event || handed.has(item.event)) continue;
|
|
568
|
+
handed.add(item.event);
|
|
569
|
+
d.add({ from: this.opts.svcID, to: d.lane(this.busLane()), kind: "event", ref: item.event, label: item.name, line });
|
|
570
|
+
this.referenced.add(item.event);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// What the port handed back: `const basket = await this.repo.byId(...)` holds a Basket.
|
|
575
|
+
this.bind(s, lhs, this.resultsOfPortMethod(src, aggregate, bare, method));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private useCaseHop(d: Draft, target: string, note: string, line: string, depth: number): void {
|
|
579
|
+
d.add({ from: this.opts.svcID, to: this.opts.svcID, kind: "call", label: camel(target.split("/")[1] ?? target), note, line });
|
|
580
|
+
this.walkUseCase(d, target, depth + 1);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private rpcHop(d: Draft, hop: RpcHop, line: string): void {
|
|
584
|
+
const { lane, peer, status } = this.peerLane(d, hop.pkg);
|
|
585
|
+
d.add({ from: this.opts.svcID, to: lane, kind: "rpc", ref: hop.id, label: hop.id.slice(hop.id.lastIndexOf("/") + 1), status, line });
|
|
586
|
+
if (!this.calls.has(hop.id)) this.calls.set(hop.id, { id: hop.id, peer, status, source: hop.source });
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// --- following a value back to its type ---------------------------------------
|
|
590
|
+
|
|
591
|
+
/** The use case whose file declares the port `bare`, when this one imports it from there. */
|
|
592
|
+
private portOwner(src: Source, bare: string): string | undefined {
|
|
593
|
+
const imp = src.imports.find((i) => i.local === bare);
|
|
594
|
+
return imp?.file ? useCaseKeyFromFile(imp.file) : undefined;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** What an expression holds, for a value read somewhere other than a declaration. */
|
|
598
|
+
private resultsOfExpr(s: Scope, expr: Node): DomainRef[] {
|
|
599
|
+
const e = unwrap(expr);
|
|
600
|
+
if (isIdent(e)) {
|
|
601
|
+
const held = s.vars.get(e.name);
|
|
602
|
+
return held ? [held] : [];
|
|
603
|
+
}
|
|
604
|
+
if (!isCall(e)) return [];
|
|
605
|
+
const callee = e.callee;
|
|
606
|
+
if (isIdent(callee)) return this.resultsOfFunction(s, callee.name);
|
|
607
|
+
if (isMember(callee) && isIdent(callee.object)) {
|
|
608
|
+
const method = memberName(callee);
|
|
609
|
+
if (method === undefined) return [];
|
|
610
|
+
const held = s.vars.get(callee.object.name);
|
|
611
|
+
if (held?.aggregate) return this.resultsOfMethod(held, method);
|
|
612
|
+
return this.resultsOfStatic(s, callee.object.name, method);
|
|
613
|
+
}
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** The aggregate whose port.ts declares `bare`, when the use case imports it from there. */
|
|
618
|
+
private domainPortAggregate(src: Source, bare: string): AggregateRead | undefined {
|
|
619
|
+
const imp = src.imports.find((i) => i.local === bare);
|
|
620
|
+
if (!imp?.file) return undefined;
|
|
621
|
+
return this.aggregates.find((a) => imp.file!.startsWith(a.dir));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private resultsOfPortMethod(src: Source, agg: AggregateRead, port: string, method: string): DomainRef[] {
|
|
625
|
+
const imp = src.imports.find((i) => i.local === port);
|
|
626
|
+
const portSrc = imp?.file ? readSource(imp.file) : undefined;
|
|
627
|
+
const iface = portSrc?.interfaces.get(imp?.imported ?? port);
|
|
628
|
+
if (!iface || !portSrc) return [];
|
|
629
|
+
for (const m of iface.node.body.body) {
|
|
630
|
+
if (isMethodSig(m) && keyName(m.key) === method) return this.refsOfType(agg, typeText(iface.p, m.returnType));
|
|
631
|
+
if (isPropertySig(m) && keyName(m.key) === method) {
|
|
632
|
+
const fn = m.typeAnnotation?.typeAnnotation;
|
|
633
|
+
return this.refsOfType(agg, isFunctionType(fn) ? typeText(iface.p, fn.returnType) : "");
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
private resultsOfFunction(s: Scope, name: string): DomainRef[] {
|
|
640
|
+
const imp = s.src.imports.find((i) => i.local === name);
|
|
641
|
+
if (!imp?.file) return [];
|
|
642
|
+
const fn = readSource(imp.file)?.functions.get(imp.imported);
|
|
643
|
+
if (!fn) return [];
|
|
644
|
+
// A domain constructor is read against its own aggregate; a helper kept
|
|
645
|
+
// elsewhere - `holderOf(repo, id, token)` under application/ - against
|
|
646
|
+
// whichever aggregate its return type names.
|
|
647
|
+
const own = this.aggregates.find((a) => imp.file!.startsWith(a.dir));
|
|
648
|
+
const type = returnTypeText(readSource(imp.file)!, fn);
|
|
649
|
+
for (const agg of own ? [own] : this.aggregates) {
|
|
650
|
+
const refs = this.refsOfType(agg, type);
|
|
651
|
+
if (refs.some((r) => r.name)) return refs;
|
|
652
|
+
}
|
|
653
|
+
return [];
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
private resultsOfStatic(s: Scope, cls: string, method: string): DomainRef[] {
|
|
657
|
+
const imp = s.src.imports.find((i) => i.local === cls);
|
|
658
|
+
if (!imp?.file) return [];
|
|
659
|
+
const agg = this.aggregates.find((a) => imp.file!.startsWith(a.dir));
|
|
660
|
+
const clsSrc = readSource(imp.file);
|
|
661
|
+
const m = clsSrc?.classes.find((c) => c.name === imp.imported)?.methods.get(method);
|
|
662
|
+
if (!agg || !m?.isStatic) return [];
|
|
663
|
+
return this.refsOfType(agg, m.returns);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
private resultsOfMethod(held: DomainRef, method: string): DomainRef[] {
|
|
667
|
+
const agg = held.aggregate!;
|
|
668
|
+
for (const file of sourceFiles(agg.dir)) {
|
|
669
|
+
const src = readSource(file);
|
|
670
|
+
const m = src?.classes.find((c) => c.name === held.name)?.methods.get(method);
|
|
671
|
+
if (m) return this.refsOfType(agg, m.returns);
|
|
672
|
+
}
|
|
673
|
+
return [];
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** `Promise<[Basket, BasketCreated]>` → a Basket and an event, by position. */
|
|
677
|
+
private refsOfType(agg: AggregateRead, type: string): DomainRef[] {
|
|
678
|
+
let t = bareType(type);
|
|
679
|
+
const tuple = /^\[(.*)\]$/s.exec(t);
|
|
680
|
+
const parts = tuple ? splitTop(tuple[1]!) : [t];
|
|
681
|
+
return parts.map((p) => {
|
|
682
|
+
const name = bareType(p).replace(/\[\]$/, "");
|
|
683
|
+
const event = agg.events.get(name);
|
|
684
|
+
if (event) return { name, event };
|
|
685
|
+
if (agg.own.has(name)) return { name, aggregate: agg };
|
|
686
|
+
return { name: "" };
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private bind(s: Scope, lhs: Node | undefined, results: DomainRef[]): void {
|
|
691
|
+
if (!lhs) return;
|
|
692
|
+
if (isIdent(lhs)) {
|
|
693
|
+
const r = results[0];
|
|
694
|
+
if (r?.name) s.vars.set(lhs.name, r);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (isArrayPattern(lhs)) {
|
|
698
|
+
lhs.elements.forEach((el, i) => {
|
|
699
|
+
const r = results[i];
|
|
700
|
+
if (isIdent(el) && r?.name) s.vars.set(el.name, r);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** `for (const basket of idle)`: the element holds what the list was read as holding. */
|
|
706
|
+
private bindElement(s: Scope, stmt: ForEachStatement): void {
|
|
707
|
+
if (stmt.type !== "ForOfStatement" || !isIdent(stmt.right) || !isVarDecl(stmt.left)) return;
|
|
708
|
+
const held = s.vars.get(stmt.right.name);
|
|
709
|
+
const decl = stmt.left.declarations[0];
|
|
710
|
+
if (held && !held.items && decl && isIdent(decl.id)) s.vars.set(decl.id.name, held);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function useCaseKeyFromFile(file: string): string | undefined {
|
|
715
|
+
const m = /[\\/]application[\\/]([^\\/]+)[\\/]usecases[\\/]([^\\/]+)[\\/]usecase\.[cm]?[jt]sx?$/.exec(file);
|
|
716
|
+
return m ? `${m[1]}/${m[2]}` : undefined;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** A block ends the path when its last statement returns or throws. */
|
|
720
|
+
function leaves(stmt: Node): boolean {
|
|
721
|
+
const last = isBlock(stmt) ? stmt.body[stmt.body.length - 1] : stmt;
|
|
722
|
+
return !!last && (isReturn(last) || isThrow(last));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function unmarkIfAllLeave(branches: AltBranch[]): AltBranch[] {
|
|
726
|
+
if (branches.every((b) => b.terminal)) return branches.map((b) => ({ ...b, terminal: false }));
|
|
727
|
+
return branches;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function unique(title: string, seen: Set<string>): string {
|
|
731
|
+
let out = title;
|
|
732
|
+
for (let n = 2; seen.has(out); n++) out = `${title} (${n})`;
|
|
733
|
+
seen.add(out);
|
|
734
|
+
return out;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function splitTop(s: string): string[] {
|
|
738
|
+
const out: string[] = [];
|
|
739
|
+
let depth = 0;
|
|
740
|
+
let cur = "";
|
|
741
|
+
for (const c of s) {
|
|
742
|
+
if (c === "<" || c === "[" || c === "(") depth++;
|
|
743
|
+
if (c === ">" || c === "]" || c === ")") depth--;
|
|
744
|
+
if (c === "," && depth === 0) {
|
|
745
|
+
out.push(cur);
|
|
746
|
+
cur = "";
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
cur += c;
|
|
750
|
+
}
|
|
751
|
+
if (cur.trim()) out.push(cur);
|
|
752
|
+
return out;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/** A slug written the way a person would: one capital at the front. */
|
|
756
|
+
export function sentence(s: string): string {
|
|
757
|
+
const words = s.split("-");
|
|
758
|
+
if (!words[0]) return s;
|
|
759
|
+
words[0] = words[0][0]!.toUpperCase() + words[0].slice(1);
|
|
760
|
+
return words.join(" ");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// `require` for the two places above that list a directory; the module is
|
|
764
|
+
// ESM, and this keeps the imports at the top to what the readers need.
|
|
765
|
+
import { createRequire } from "node:module";
|
|
766
|
+
const require = createRequire(import.meta.url);
|
|
767
|
+
export { dirname as _dirname };
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* `ctx.<port>` where `ctx` is what this body's ports arrive on.
|
|
771
|
+
*
|
|
772
|
+
* The resolver's counterpart of `this.<port>`. Nothing else on the context is
|
|
773
|
+
* a port, so a member that is not one is not found in the scope's map and the
|
|
774
|
+
* call is not a hop, exactly as it works for a use case's fields.
|
|
775
|
+
*/
|
|
776
|
+
function selfMember(s: Scope, n: Node): string | undefined {
|
|
777
|
+
return s.self && isMember(n) && isIdent(n.object) && n.object.name === s.self ? memberName(n) : undefined;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** The bus, by where it sits: `infrastructure/bus` beside the peers, or `pkg/messaging` beside the rest of the plumbing. */
|
|
781
|
+
function isBusAdapter(file: string): boolean {
|
|
782
|
+
return /[\\/](?:infrastructure[\\/]bus|pkg[\\/]messaging)[\\/]/.test(file);
|
|
783
|
+
}
|