@starci/hfs 4.0.7 → 4.0.9
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/CHANGELOG.md +22 -0
- package/README.md +86 -43
- package/emit/contracts.mjs +56 -9
- package/emit/db-types.mjs +169 -0
- package/emit/events.mjs +41 -0
- package/emit/operations-worker.mjs +1 -1
- package/emit/operations.mjs +2 -2
- package/emit/schema-worker.mjs +1 -1
- package/lint/bound-sys.cjs +50 -0
- package/lint/run.mjs +54 -20
- package/package.json +14 -9
- package/report/sonar.mjs +8 -8
- package/runtime/engine/config.mjs +494 -0
- package/runtime/engine/invalid-config.mjs +15 -0
- package/runtime/engine/orca-config.mjs +27 -0
- package/runtime/engine/runtime-root.mjs +5 -0
- package/runtime/engine/yaml.mjs +3 -3
- package/runtime/knowledge/hfs/canon-pins.yaml +163 -32
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +2059 -0
- package/runtime/knowledge/hfs/slots.yaml +915 -123
- package/runtime/knowledge/patterns/be/api.yaml +369 -0
- package/runtime/knowledge/patterns/be/cli.yaml +297 -0
- package/runtime/knowledge/patterns/be/domain.yaml +113 -0
- package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
- package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
- package/runtime/knowledge/patterns/be/projections.yaml +178 -0
- package/runtime/knowledge/patterns/be/queues.yaml +236 -0
- package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
- package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
- package/runtime/knowledge/patterns/be/saga.yaml +248 -0
- package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
- package/runtime/knowledge/patterns/fe/folder.yaml +133 -27
- package/runtime/knowledge/sonar-gate.yaml +53 -19
- package/runtime/modules/kernel/failure-codes.yaml +580 -50
- package/runtime/modules/models/registry.yaml +591 -0
- package/runtime/modules/models/runtimes.yaml +701 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
- package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
- package/runtime/scripts/api/fs/lib.mjs +14 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +104 -0
- package/runtime/scripts/api/git/config-get.mjs +10 -0
- package/runtime/scripts/api/git/diff.mjs +5 -0
- package/runtime/scripts/api/git/lib.mjs +17 -27
- package/runtime/scripts/api/git/log.mjs +5 -0
- package/runtime/scripts/api/git/ls-files.mjs +6 -0
- package/runtime/scripts/api/git/ls-tree.mjs +5 -0
- package/runtime/scripts/api/git/merge-base.mjs +9 -0
- package/runtime/scripts/api/git/remote.mjs +5 -0
- package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
- package/runtime/scripts/api/git/show.mjs +6 -0
- package/runtime/scripts/api/sops/decrypt.mjs +10 -6
- package/runtime/scripts/api/sops/lib.mjs +35 -0
- package/runtime/scripts/api/sops/seal.mjs +18 -0
- package/runtime/scripts/{lib/hfs-allows.mjs → hfs/allows.mjs} +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
- package/runtime/scripts/{checks → hfs}/architecture/backend.mjs +17 -24
- package/runtime/scripts/{checks → hfs}/architecture/background-unowned.mjs +16 -34
- package/runtime/scripts/{checks → hfs}/architecture/client-reaches-server.mjs +3 -1
- package/runtime/scripts/{checks → hfs}/architecture/clones.mjs +4 -4
- package/runtime/scripts/{checks → hfs}/architecture/config.mjs +11 -8
- package/runtime/scripts/{checks → hfs}/architecture/connection-map.mjs +21 -9
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
- package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
- package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
- package/runtime/scripts/{checks → hfs}/architecture/contract-fixture-guard.mjs +5 -11
- package/runtime/scripts/{checks → hfs}/architecture/contracts.mjs +33 -162
- package/runtime/scripts/{checks → hfs}/architecture/dead-exports.mjs +58 -29
- package/runtime/scripts/{checks → hfs}/architecture/doc-language.mjs +1 -1
- package/runtime/scripts/{checks → hfs}/architecture/entrypoint.mjs +2 -1
- package/runtime/scripts/{checks → hfs}/architecture/fe-slot-allows.mjs +4 -4
- package/runtime/scripts/{checks → hfs}/architecture/feature-shape.mjs +6 -3
- package/runtime/scripts/{checks → hfs}/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/{checks → hfs}/architecture/frontend.mjs +17 -43
- package/runtime/scripts/{checks → hfs}/architecture/hfs-graph.mjs +1 -1
- package/runtime/scripts/{checks → hfs}/architecture/hfs.mjs +27 -42
- package/runtime/scripts/{checks → hfs}/architecture/i18n-keys.mjs +1 -1
- package/runtime/scripts/{checks → hfs}/architecture/index.mjs +17 -4
- package/runtime/scripts/{checks → hfs}/architecture/machine-ast.mjs +44 -11
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
- package/runtime/scripts/{checks → hfs}/architecture/module-per-transport.mjs +30 -6
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +96 -0
- package/runtime/scripts/{checks → hfs}/architecture/next-data.mjs +40 -168
- package/runtime/scripts/{checks → hfs}/architecture/owners.mjs +35 -15
- package/runtime/scripts/{checks → hfs}/architecture/register-once.mjs +2 -1
- package/runtime/scripts/{checks → hfs}/architecture/registration.mjs +23 -81
- package/runtime/scripts/{checks → hfs}/architecture/required-files.mjs +21 -13
- package/runtime/scripts/{checks → hfs}/architecture/schema-owner.mjs +17 -114
- package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
- package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
- package/runtime/scripts/{checks → hfs}/architecture/source-names.mjs +27 -147
- package/runtime/scripts/{checks → hfs}/architecture/sql-owner.mjs +59 -18
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
- package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
- package/runtime/scripts/{checks → hfs}/architecture/surface.mjs +10 -4
- package/runtime/scripts/{checks → hfs}/architecture/test-world-files.mjs +11 -21
- package/runtime/scripts/{checks → hfs}/architecture/tiers.mjs +47 -5
- package/runtime/scripts/{checks → hfs}/architecture/transport-owner.mjs +3 -3
- package/runtime/scripts/{checks → hfs}/architecture/typescript.mjs +13 -27
- package/runtime/scripts/{checks → hfs}/architecture.mjs +5 -5
- package/runtime/scripts/{lib/hfs-check.mjs → hfs/check.mjs} +95 -84
- package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
- package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
- package/runtime/scripts/hfs/edition-slots.mjs +66 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +247 -0
- package/runtime/scripts/hfs/param-names.mjs +18 -0
- package/runtime/scripts/{lib/hfs-path-findings.mjs → hfs/path-findings.mjs} +7 -7
- package/runtime/scripts/{lib → hfs}/repo-identity.mjs +9 -6
- package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
- package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
- package/runtime/scripts/hfs/rules/cli.mjs +37 -0
- package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/contract.mjs +11 -9
- package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
- package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
- package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
- package/runtime/scripts/hfs/rules/database.mjs +68 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/deps.mjs +1 -1
- package/runtime/scripts/hfs/rules/docker.mjs +259 -0
- package/runtime/scripts/hfs/rules/edition.mjs +111 -0
- package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +66 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/fe-no-tests.mjs +6 -5
- package/runtime/scripts/{lib/hfs-rules/frontend.mjs → hfs/rules/frontend-tree.mjs} +9 -9
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/integration-specs.mjs +3 -6
- package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/lint-suppression.mjs +1 -1
- package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/peer-integrations.mjs +3 -3
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/pipeline.mjs +11 -21
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/proof-commands.mjs +2 -2
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/read.mjs +1 -1
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/repo-local-checks.mjs +2 -2
- package/runtime/scripts/hfs/rules/saga.mjs +170 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/secrets.mjs +4 -4
- package/runtime/scripts/hfs/rules/services.mjs +220 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/spec-placement.mjs +1 -1
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/stacks.mjs +5 -5
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
- package/runtime/scripts/{lib/hfs-rules → hfs/rules}/test-topology.mjs +2 -2
- package/runtime/scripts/hfs/secret.mjs +175 -0
- package/runtime/scripts/{lib/hfs-slots.mjs → hfs/slots.mjs} +114 -150
- package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
- package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
- package/runtime/scripts/{lib/hfs-tree.mjs → hfs/tree.mjs} +6 -5
- package/runtime/scripts/{checks → hfs}/typescript-programs.mjs +0 -5
- package/runtime/scripts/{lib/hfs-view.mjs → hfs/view.mjs} +6 -5
- package/runtime/scripts/lib/dockerfile.mjs +124 -0
- package/runtime/scripts/lib/env.mjs +10 -0
- package/runtime/scripts/lib/event-contract.mjs +103 -0
- package/runtime/scripts/lib/git.mjs +36 -0
- package/runtime/scripts/lib/graphql-contract.mjs +429 -0
- package/runtime/scripts/lib/i18n.mjs +86 -0
- package/runtime/scripts/lib/is-main.mjs +13 -0
- package/runtime/scripts/lib/json.mjs +13 -3
- package/runtime/scripts/lib/language.mjs +26 -7
- package/runtime/scripts/lib/list.mjs +20 -0
- package/runtime/scripts/lib/package-at.mjs +37 -0
- package/runtime/scripts/lib/path-key.mjs +59 -2
- package/runtime/scripts/lib/read-text.mjs +34 -0
- package/runtime/scripts/lib/regex.mjs +4 -0
- package/runtime/scripts/lib/same-text.mjs +5 -0
- package/runtime/scripts/lib/secret-patterns.mjs +4 -3
- package/runtime/scripts/lib/sleep-sync.mjs +2 -1
- package/runtime/scripts/lib/sops-envelope.mjs +58 -0
- package/runtime/scripts/lib/stack-declaration.mjs +2 -2
- package/runtime/scripts/lib/stack-services.mjs +7 -31
- package/runtime/scripts/lib/ts-ast.mjs +35 -0
- package/runtime/scripts/{checks/common.mjs → lib/walk.mjs} +5 -15
- package/scaffold/add-app.mjs +174 -0
- package/scaffold/add-cli-lite.mjs +257 -0
- package/scaffold/add-inbox-lite.mjs +314 -0
- package/scaffold/add-table.mjs +344 -0
- package/scaffold/add.mjs +299 -0
- package/scaffold/app.mjs +359 -70
- package/scaffold/edition-gate.mjs +25 -0
- package/scaffold/image.mjs +19 -0
- package/scaffold/lite-exports.mjs +186 -0
- package/scaffold/service.mjs +33 -27
- package/scaffold/supabase-ports.mjs +26 -0
- package/src/main.mjs +327 -0
- package/sync/cli.mjs +6 -9
- package/sync/format.mjs +1 -1
- package/sync/hygiene.mjs +17 -17
- package/sync/index.mjs +171 -71
- package/sync/managed.mjs +19 -11
- package/sync/sonar-key.mjs +5 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
- package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
- package/templates/app/ci-workflows/sonar-steps.yml +9 -0
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
- package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
- package/templates/app/docker-ignore/dockerignore +50 -0
- package/templates/app/gitignore-lite +35 -0
- package/templates/app/hooks/husky/pre-commit +3 -8
- package/templates/app/hooks/husky/pre-push +45 -6
- package/templates/app/package-scripts/core.json +8 -0
- package/templates/app/package-scripts/package.json +3 -10
- package/templates/app/package-scripts-lite/package.json +15 -0
- package/templates/app/quality-config/codecov.yml +15 -3
- package/templates/app/quality-config/sonar-base.properties +4 -0
- package/templates/app/quality-config/sonar-project.properties +2 -5
- package/templates/app/quality-config-lite/sonar-project.properties +5 -0
- package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
- package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
- package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
- package/templates/app/skeleton/README.md +7 -5
- package/templates/app/skeleton-lite/.sops.yaml +3 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
- package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
- package/templates/app/skeleton-lite/README.md +40 -0
- package/templates/app/skeleton-lite/supabase/config.toml +41 -0
- package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
- package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
- package/templates/app/task-graph/turbo.json +23 -0
- package/templates/app/tool-config-lite/prettierignore +2 -0
- package/templates/app/tool-config-lite/prettierrc +1 -0
- package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
- package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
- package/templates/be/common/main-listen.ts.partial +14 -0
- package/templates/be/common/sql-ident.ts.partial +7 -0
- package/templates/be/common/sql-tag.ts.partial +5 -0
- package/templates/be/image/api/Dockerfile +38 -0
- package/templates/be/image/cli/Dockerfile +38 -0
- package/templates/be/image/worker/Dockerfile +36 -0
- package/templates/be/inbox/database-client.ts.tpl +50 -0
- package/templates/be/inbox/index.ts.tpl +2 -0
- package/templates/be/inbox/migration.sql.tpl +29 -0
- package/templates/be/inbox/module-definition.ts.tpl +6 -0
- package/templates/be/inbox/module.ts.tpl +14 -0
- package/templates/be/inbox/options.ts.tpl +2 -0
- package/templates/be/inbox/service.ts.tpl +33 -0
- package/templates/be/inbox/sql.ts.tpl +16 -0
- package/templates/be/inbox/webhook-door.ts.tpl +36 -0
- package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
- package/templates/be/patterns/api/application.command.ts.tpl +10 -0
- package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
- package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
- package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
- package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
- package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
- package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
- package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
- package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
- package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
- package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
- package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
- package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
- package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
- package/templates/be/patterns/api/http.module.ts.tpl +7 -0
- package/templates/be/patterns/api/http.request.ts.tpl +10 -0
- package/templates/be/patterns/api/http.response.ts.tpl +5 -0
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
- package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
- package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
- package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
- package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
- package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
- package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
- package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
- package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
- package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
- package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
- package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
- package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
- package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
- package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
- package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
- package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
- package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
- package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
- package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
- package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
- package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
- package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
- package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
- package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
- package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
- package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
- package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
- package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
- package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
- package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
- package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
- package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
- package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
- package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
- package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
- package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
- package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
- package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
- package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
- package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
- package/templates/be/patterns/projections/migration.ts.tpl +18 -0
- package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
- package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
- package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
- package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
- package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
- package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
- package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
- package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
- package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
- package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
- package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
- package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
- package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
- package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
- package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
- package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
- package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
- package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
- package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
- package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
- package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
- package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
- package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
- package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
- package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
- package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
- package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
- package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
- package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
- package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
- package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
- package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
- package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
- package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
- package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
- package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
- package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
- package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
- package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
- package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
- package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
- package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
- package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
- package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
- package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
- package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
- package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
- package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
- package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
- package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
- package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
- package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
- package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
- package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
- package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
- package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
- package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
- package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
- package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/index.ts +1 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
- package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
- package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
- package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
- package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
- package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
- package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
- package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
- package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
- package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
- package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
- package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
- package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
- package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
- package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
- package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
- package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
- package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
- package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
- package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
- package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
- package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
- package/templates/be/table/error.ts.tpl +16 -0
- package/templates/be/table/index.ts.tpl +2 -0
- package/templates/be/table/migration.sql.tpl +51 -0
- package/templates/be/table/module-definition.ts.tpl +7 -0
- package/templates/be/table/module.ts.tpl +14 -0
- package/templates/be/table/options.ts.tpl +2 -0
- package/templates/be/table/rows.ts.tpl +4 -0
- package/templates/be/table/service.ts.tpl +23 -0
- package/templates/be/table/sql.ts.tpl +4 -0
- package/templates/be/tool-config/jest.config.js +3 -1
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
- package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
- package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
- package/templates/common/gitignore.base +1 -2
- package/templates/fe/app/package.json.tpl +1 -0
- package/templates/fe/app/tsconfig.json +12 -0
- package/templates/fe/common/app-layout.tsx.partial +8 -0
- package/templates/fe/common/site-shell.tsx.partial +9 -0
- package/templates/fe/image/next/Dockerfile +42 -0
- package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
- package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +3 -3
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
- package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
- package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
- package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
- package/templates/fe/skeleton/apps/landing/next.config.ts +21 -0
- package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
- package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
- package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
- package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
- package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
- package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
- package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
- package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
- package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
- package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/modules/i18n/request.ts +4 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
- package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
- package/templates/fe/table/read.ts.tpl +16 -0
- package/templates/fe/table/write.ts.tpl +16 -0
- package/upgrade/check.mjs +137 -0
- package/upgrade/index.mjs +524 -0
- package/bin/hfs.mjs +0 -233
- package/runtime/engine/admission.mjs +0 -284
- package/runtime/engine/digest.mjs +0 -10
- package/runtime/engine/ledger-db.mjs +0 -1245
- package/runtime/engine/machine-db.mjs +0 -1565
- package/runtime/engine/migrations/machine/0001-init.sql +0 -887
- package/runtime/engine/migrations/machine/0002-worktrees-no-workflow-kind.sql +0 -13
- package/runtime/engine/migrations/machine/0003-worktrees-workflow-orca.sql +0 -21
- package/runtime/engine/migrations/runtime/0001-init.sql +0 -1072
- package/runtime/engine/migrations/runtime/0003-usage-unavailable.sql +0 -13
- package/runtime/engine/migrations/runtime/0004-attempt-why.sql +0 -43
- package/runtime/engine/migrations/runtime/0005-ended-workflow-views.sql +0 -93
- package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
- package/runtime/scripts/lib/artifact-hold.mjs +0 -89
- package/runtime/scripts/lib/artifact-store.mjs +0 -103
- package/runtime/scripts/lib/redact.mjs +0 -148
- package/runtime/scripts/lib/safe-remove.mjs +0 -275
- package/runtime/scripts/lib/test-secrets.mjs +0 -116
- package/templates/fe/skeleton/apps/__app__/next.config.ts +0 -11
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
- package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
- package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
- package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -21
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -17
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -26
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -15
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -25
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -27
- package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -32
- package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
- package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
- package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
- package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
- /package/runtime/scripts/{checks → hfs}/architecture/config-unread.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/constructor-deps.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/cross-app-duplicate.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/default-deny.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/error-codes.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/error-masked.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/hooks-are-hooks.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/injection-token-exported.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/package-shape.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/reachability.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/route-files-thin.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/sql-tokens.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/symbols.mjs +0 -0
- /package/runtime/scripts/{checks → hfs}/architecture/unit-spec-providers.mjs +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
- /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
- /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
- /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
- /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { contextModelOf } from './context-map.mjs';
|
|
3
|
+
import { machineKit } from './machine-ast.mjs';
|
|
4
|
+
import { entitiesOf } from './sql-owner.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* R175 `context-coupling` (BE_CONTEXT_COUPLING). Bounded contexts are coupled by events only, never by their schema or their code:
|
|
8
|
+
* - an entity relation (`OneToOne`, `OneToMany`, `ManyToOne`, `ManyToMany` imported from typeorm) whose target class is an entity of a capability
|
|
9
|
+
* of another context is refused;
|
|
10
|
+
* - a migration foreign key (`REFERENCES <table>` in a string of a migration file) onto a table whose entity belongs to another context is refused;
|
|
11
|
+
* - a domain or projection file imports no file of a capability of another context (its entities, services or SQL).
|
|
12
|
+
* A capability's context is the connection its persistence arrays are registered on (context-map.mjs). A file or table with no proven context
|
|
13
|
+
* (a platform capability, a capability registered nowhere) is never judged, and the table of a foreign key is looked up through the entities the
|
|
14
|
+
* program declares, never guessed from a name.
|
|
15
|
+
*/
|
|
16
|
+
export const CONTEXT_COUPLING_RULE_IDS = ['BE_CONTEXT_COUPLING'];
|
|
17
|
+
|
|
18
|
+
const RULE = 'BE_CONTEXT_COUPLING';
|
|
19
|
+
const TYPEORM = 'typeorm';
|
|
20
|
+
const RELATIONS = new Set(['OneToOne', 'OneToMany', 'ManyToOne', 'ManyToMany']);
|
|
21
|
+
// The table identifier after the SQL keyword REFERENCES, optionally schema-qualified and quoted.
|
|
22
|
+
const REFERENCES = /\bREFERENCES\s+(?:"?[A-Za-z0-9_]+"?\s*\.\s*)?"?([A-Za-z0-9_]+)"?/giu;
|
|
23
|
+
|
|
24
|
+
export function checkContextCoupling(input) {
|
|
25
|
+
const { graph } = input;
|
|
26
|
+
const kit = machineKit(input);
|
|
27
|
+
const { ts, resolver } = kit;
|
|
28
|
+
const model = contextModelOf(kit, graph);
|
|
29
|
+
const { tables } = entitiesOf(kit, graph);
|
|
30
|
+
const violations = [];
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const once = (key, violation) => { if (!seen.has(key)) { seen.add(key); violations.push({ ruleId: RULE, ...violation }); } };
|
|
33
|
+
const counts = { relations: 0, foreignKeys: 0, imports: 0 };
|
|
34
|
+
const covered = new Set(); // `${fromRel}|${targetRoot}` pairs a relation already reported, so the import edge is not a second finding
|
|
35
|
+
|
|
36
|
+
for (const file of graph.files.values()) {
|
|
37
|
+
if (file.slot !== 'be.persistence' || !file.owner) continue;
|
|
38
|
+
const base = path.posix.basename(file.rel);
|
|
39
|
+
const ownContext = model.contextOfCapability(file.owner.root);
|
|
40
|
+
if (!ownContext) continue;
|
|
41
|
+
const checker = kit.checkerOf(file.sourceFile);
|
|
42
|
+
if (base.endsWith('.entity.ts')) {
|
|
43
|
+
kit.walk(file.sourceFile, node => {
|
|
44
|
+
if (!ts.isDecorator(node) || !ts.isCallExpression(node.expression)) return true;
|
|
45
|
+
const binding = kit.importBinding(checker, node.expression.expression);
|
|
46
|
+
if (binding?.module !== TYPEORM || !RELATIONS.has(binding.name)) return true;
|
|
47
|
+
counts.relations += 1;
|
|
48
|
+
const argument = node.expression.arguments[0];
|
|
49
|
+
const target = argument && (ts.isArrowFunction(argument) || ts.isFunctionExpression(argument)) && !ts.isBlock(argument.body) ? model.unparen(argument.body) : null;
|
|
50
|
+
if (!target || !(ts.isIdentifier(target) || ts.isPropertyAccessExpression(target))) return true;
|
|
51
|
+
const home = kit.declarationsOf(checker, ts.isPropertyAccessExpression(target) ? target.name : target).map(kit.ownerOfDeclaration).find(Boolean);
|
|
52
|
+
const targetContext = home ? model.contextOfCapability(home.root) : null;
|
|
53
|
+
if (!home || !targetContext || targetContext === ownContext) return true;
|
|
54
|
+
covered.add(`${file.rel}|${home.root}`);
|
|
55
|
+
once(`${file.rel}|${node.getStart()}`, { ...kit.at(file.rel, file.sourceFile, node), message: `A ${binding.name} relation in ${file.owner.root} (context ${ownContext}) targets an entity of ${home.root} (context ${targetContext}); a relation never crosses a context. Keep the other context's id as a plain column and fill a local copy or a projection from its events.`, context: ownContext, targetContext });
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (file.rel.slice(file.owner.root.length + 1).startsWith('persistence/migrations/')) {
|
|
60
|
+
kit.walk(file.sourceFile, node => {
|
|
61
|
+
if (!(ts.isStringLiteralLike(node) || ts.isTemplateExpression(node))) return true;
|
|
62
|
+
const text = ts.isTemplateExpression(node) ? node.head.text + node.templateSpans.map(span => ` ${span.literal.text}`).join('') : node.text;
|
|
63
|
+
for (const match of text.matchAll(REFERENCES)) {
|
|
64
|
+
counts.foreignKeys += 1;
|
|
65
|
+
const entity = tables.get(match[1].toLowerCase());
|
|
66
|
+
const targetContext = entity ? model.contextOfCapability(entity.owner) : null;
|
|
67
|
+
if (!entity || !targetContext || targetContext === ownContext) continue;
|
|
68
|
+
once(`${file.rel}|${node.getStart()}|${match[1]}`, { ...kit.at(file.rel, file.sourceFile, node), message: `A foreign key in ${file.rel} references table ${entity.table} of ${entity.owner} (context ${targetContext}), but this migration belongs to context ${ownContext}; a foreign key never crosses a context. Keep the id as a plain column and validate it through the other context's API or a local copy fed by its events.`, context: ownContext, targetContext, table: entity.table });
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const edge of graph.edges) {
|
|
76
|
+
const from = graph.files.get(edge.from);
|
|
77
|
+
const to = graph.files.get(edge.to);
|
|
78
|
+
if (!from?.owner || !to?.owner || from.owner.root === to.owner.root) continue;
|
|
79
|
+
const fromContext = model.contextOfFile(edge.from);
|
|
80
|
+
const toContext = model.contextOfFile(edge.to);
|
|
81
|
+
if (!fromContext || !toContext || fromContext === toContext) continue;
|
|
82
|
+
counts.imports += 1;
|
|
83
|
+
if (covered.has(`${edge.from}|${to.owner.root}`)) continue;
|
|
84
|
+
once(`${edge.from}|${edge.to}|${edge.line}`, { path: edge.from, line: edge.line, column: edge.column, message: `${from.owner.root} (context ${fromContext}) imports ${to.owner.root} (context ${toContext}); a context never imports another context's entities, services or SQL. Keep a local copy fed by its events, or read a projection.`, context: fromContext, targetContext: toContext });
|
|
85
|
+
}
|
|
86
|
+
void resolver;
|
|
87
|
+
return { violations, coverage: { status: 'checked', ...counts } };
|
|
88
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pascal } from './machine-ast.mjs';
|
|
3
|
+
import { DATABASE_DIR } from './connection-map.mjs';
|
|
4
|
+
|
|
5
|
+
const camel = name => { const text = pascal(name); return text[0].toLowerCase() + text.slice(1); };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Where the persistence arrays of every capability are registered: the object literals (`DatabaseModule.register` connections and the
|
|
9
|
+
* migrate/cli app's) that list `<c>Entities` / `<c>Migrations` of a capability's `persistence/connection.ts` under a connection `name`.
|
|
10
|
+
* The one walk BE_SCHEMA_OWNER and the bounded-context rules (R174 to R178) share, so a capability's context is decided once, by origin.
|
|
11
|
+
*
|
|
12
|
+
* @param {{kit: object, graph: object, persistenceOf: (rel: string) => ({root: string, capability: string, folder: string, below: string[]} | null)}} input
|
|
13
|
+
* @returns {{registered: Map<string, {name: string, connections: Map<string, {file: object, node: object}>}>, registrations: number, all: Array<{file: object, node: object, connection: string | null}>}}
|
|
14
|
+
* `registered`: capability root -> its name and the connections it is registered on (first site each).
|
|
15
|
+
*/
|
|
16
|
+
export function collectRegistrations({ kit, graph, persistenceOf }) {
|
|
17
|
+
const { ts } = kit;
|
|
18
|
+
const unparen = node => { let current = node; while (current && (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression?.(current) || ts.isNonNullExpression(current))) current = current.expression; return current; };
|
|
19
|
+
const returned = declaration => {
|
|
20
|
+
const fn = ts.isVariableDeclaration(declaration) && declaration.initializer ? unparen(declaration.initializer) : declaration;
|
|
21
|
+
if (!(ts.isArrowFunction(fn) || ts.isFunctionDeclaration(fn) || ts.isFunctionExpression(fn) || ts.isMethodDeclaration(fn)) || !fn.body) return null;
|
|
22
|
+
if (!ts.isBlock(fn.body)) return fn.body;
|
|
23
|
+
const statements = fn.body.statements.filter(ts.isReturnStatement);
|
|
24
|
+
return statements.length === 1 ? statements[0].expression ?? null : null;
|
|
25
|
+
};
|
|
26
|
+
const propertySources = new Map(); // property name -> [{declaration, initializer, checker}] over the program
|
|
27
|
+
const assignmentsOf = name => {
|
|
28
|
+
if (propertySources.has(name)) return propertySources.get(name);
|
|
29
|
+
const list = [];
|
|
30
|
+
for (const file of graph.files.values()) {
|
|
31
|
+
if (!file.sourceFile.text.includes(name)) continue;
|
|
32
|
+
const checker = kit.checkerOf(file.sourceFile);
|
|
33
|
+
kit.walk(file.sourceFile, node => {
|
|
34
|
+
if (ts.isPropertyAssignment(node) && kit.propertyNameText(node.name) === name && ts.isObjectLiteralExpression(node.parent)) {
|
|
35
|
+
// the property of the type the literal is checked against, so a typed const and a typed return meet the same declaration
|
|
36
|
+
const contextual = checker.getContextualType(node.parent)?.getProperty(name);
|
|
37
|
+
const symbol = contextual ?? checker.getSymbolAtLocation(node.name);
|
|
38
|
+
list.push({ declaration: symbol?.declarations?.[0] ?? null, initializer: node.initializer, checker });
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
propertySources.set(name, list);
|
|
44
|
+
return list;
|
|
45
|
+
};
|
|
46
|
+
const nameOfLiteral = (checker, literal, depth) => {
|
|
47
|
+
const own = kit.propertyOf(literal, 'name');
|
|
48
|
+
if (own) return kit.stringValue(checker, kit.valueOfProperty(own));
|
|
49
|
+
for (const property of literal.properties) {
|
|
50
|
+
if (!ts.isSpreadAssignment(property)) continue;
|
|
51
|
+
const value = nameOfExpression(checker, property.expression, depth + 1);
|
|
52
|
+
if (value) return value;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
const nameOfExpression = (checker, expression, depth) => {
|
|
57
|
+
const node = unparen(expression);
|
|
58
|
+
if (!node || depth > 5) return null;
|
|
59
|
+
if (ts.isObjectLiteralExpression(node)) return nameOfLiteral(checker, node, depth);
|
|
60
|
+
if (ts.isCallExpression(node)) {
|
|
61
|
+
for (const declaration of kit.declarationsOf(checker, node.expression)) {
|
|
62
|
+
const body = returned(declaration);
|
|
63
|
+
const value = body ? nameOfExpression(kit.checkerOf(declaration.getSourceFile()), body, depth + 1) : null;
|
|
64
|
+
if (value) return value;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (ts.isIdentifier(node)) {
|
|
69
|
+
const declaration = kit.declarationsOf(checker, node)[0];
|
|
70
|
+
return declaration && ts.isVariableDeclaration(declaration) && declaration.initializer ? nameOfExpression(kit.checkerOf(declaration.getSourceFile()), declaration.initializer, depth + 1) : null;
|
|
71
|
+
}
|
|
72
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
73
|
+
const target = kit.declarationsOf(checker, node.name)[0];
|
|
74
|
+
if (!target) return null;
|
|
75
|
+
const names = new Set(assignmentsOf(node.name.text).filter(item => item.declaration === target).map(item => nameOfExpression(item.checker, item.initializer, depth + 1)).filter(Boolean));
|
|
76
|
+
return names.size === 1 ? [...names][0] : null;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
};
|
|
80
|
+
/** The capability arrays (`<c>Entities` / `<c>Migrations` of persistence/connection.ts) an expression lists. */
|
|
81
|
+
const arraysIn = (checker, expression, out, depth) => {
|
|
82
|
+
const node = unparen(expression);
|
|
83
|
+
if (!node || depth > 6) return;
|
|
84
|
+
if (ts.isArrayLiteralExpression(node)) { for (const element of node.elements) arraysIn(checker, ts.isSpreadElement(element) ? element.expression : element, out, depth + 1); return; }
|
|
85
|
+
if (!ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node)) return;
|
|
86
|
+
const declaration = kit.declarationsOf(checker, ts.isPropertyAccessExpression(node) ? node.name : node)[0];
|
|
87
|
+
if (!declaration || !ts.isVariableDeclaration(declaration) || !ts.isIdentifier(declaration.name)) return;
|
|
88
|
+
const rel = kit.graphPath(declaration);
|
|
89
|
+
const owner = rel ? persistenceOf(rel) : null;
|
|
90
|
+
if (owner && owner.below.length === 1 && owner.below[0] === 'connection.ts' && [`${camel(owner.capability)}Entities`, `${camel(owner.capability)}Migrations`].includes(declaration.name.text)) {
|
|
91
|
+
out.push({ key: path.posix.dirname(owner.root), name: owner.capability });
|
|
92
|
+
} else if (declaration.initializer) arraysIn(kit.checkerOf(declaration.getSourceFile()), declaration.initializer, out, depth + 1);
|
|
93
|
+
};
|
|
94
|
+
const all = []; // every registration literal: {file, node, connection | null when the name cannot be resolved}
|
|
95
|
+
const registered = new Map(); // capability root -> {name, connections: Map(connection -> first site)}
|
|
96
|
+
let registrations = 0;
|
|
97
|
+
for (const file of graph.files.values()) {
|
|
98
|
+
const checker = kit.checkerOf(file.sourceFile);
|
|
99
|
+
kit.walk(file.sourceFile, node => {
|
|
100
|
+
if (!ts.isObjectLiteralExpression(node)) return true;
|
|
101
|
+
const arrays = [];
|
|
102
|
+
for (const key of ['entities', 'migrations']) {
|
|
103
|
+
const property = kit.propertyOf(node, key);
|
|
104
|
+
if (property) arraysIn(checker, kit.valueOfProperty(property), arrays, 0);
|
|
105
|
+
}
|
|
106
|
+
if (!arrays.length) return true;
|
|
107
|
+
registrations += 1;
|
|
108
|
+
const connection = nameOfLiteral(checker, node, 0);
|
|
109
|
+
all.push({ file, node, connection });
|
|
110
|
+
if (connection === null) return true;
|
|
111
|
+
for (const { key, name } of new Map(arrays.map(item => [item.key, item])).values()) {
|
|
112
|
+
if (!registered.has(key)) registered.set(key, { name, connections: new Map() });
|
|
113
|
+
const sites = registered.get(key).connections;
|
|
114
|
+
if (!sites.has(connection)) sites.set(connection, { file, node });
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return { registered, registrations, all };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const PERSISTENCE_SLOT = 'be.persistence';
|
|
123
|
+
/** The slot tiers whose capabilities belong to a bounded context: the domain and the projections (a projection is owned by the context whose connection holds its tables). */
|
|
124
|
+
const CONTEXT_TIERS = new Set(['domain', 'projections']);
|
|
125
|
+
|
|
126
|
+
/** True for a platform capability the manifest lists as `perConnection` (its tables exist on every connection that uses it); `root` is the capability root. */
|
|
127
|
+
export const isPerConnection = (resolver, root, name) => root.split('/').includes('platform') && (resolver.slot(PERSISTENCE_SLOT)?.perConnection ?? []).includes(name);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* `persistenceOf(rel)`: {root, capability, folder, below} of a be.persistence file (`folder` the directory below persistence/, '' at its root),
|
|
131
|
+
* null for any other file. `root` is the persistence root, its dirname the capability root.
|
|
132
|
+
*/
|
|
133
|
+
export const persistenceOfFactory = resolver => rel => {
|
|
134
|
+
const classified = resolver.classifyPath(rel);
|
|
135
|
+
if (classified.slot !== PERSISTENCE_SLOT) return null;
|
|
136
|
+
const below = rel.slice(classified.root.length + 1).split('/');
|
|
137
|
+
return { root: classified.root, capability: path.posix.basename(path.posix.dirname(classified.root)), folder: below.length > 1 ? below[0] : '', below };
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The bounded-context model of a program: which context (declared connection) each capability belongs to, and which connection an expression's
|
|
142
|
+
* entity manager is. A context is a connection; a capability belongs to the one declared connection its persistence arrays are registered on
|
|
143
|
+
* (collectRegistrations), a platform capability named by the manifest's `perConnection` list to none (it is infrastructure on every connection).
|
|
144
|
+
* Everything is decided by declarations and origins: the registration literal, the `Inject<Conn>EntityManager` decorator's home file, the
|
|
145
|
+
* transaction callback parameter's receiver. Nothing is matched by a variable name.
|
|
146
|
+
*/
|
|
147
|
+
const MODELS = new WeakMap();
|
|
148
|
+
|
|
149
|
+
export function contextModelOf(kit, graph) {
|
|
150
|
+
if (MODELS.has(graph)) return MODELS.get(graph);
|
|
151
|
+
const { ts, resolver } = kit;
|
|
152
|
+
const persistenceOf = persistenceOfFactory(resolver);
|
|
153
|
+
const declared = new Map(resolver.repo.connections.map(connection => [connection.name, connection]));
|
|
154
|
+
const { registered, all } = collectRegistrations({ kit, graph, persistenceOf });
|
|
155
|
+
const perConnection = new Set();
|
|
156
|
+
const contextOfRoot = new Map(); // capability root -> declared connection name (single registration only)
|
|
157
|
+
for (const [root, { name, connections }] of registered) {
|
|
158
|
+
if (isPerConnection(resolver, root, name)) { perConnection.add(root); continue; }
|
|
159
|
+
const names = [...connections.keys()].filter(connection => declared.has(connection));
|
|
160
|
+
if (names.length === 1) contextOfRoot.set(root, names[0]);
|
|
161
|
+
}
|
|
162
|
+
/** The declared connection a capability root belongs to, or null (no registration, several, or a per-connection platform capability). */
|
|
163
|
+
const contextOfCapability = root => contextOfRoot.get(root) ?? null;
|
|
164
|
+
/** The context of the capability owning `rel` when that capability is of a context tier (domain, projections), else null: only these are bounded contexts. */
|
|
165
|
+
const contextOfFile = rel => {
|
|
166
|
+
const file = graph.files.get(rel);
|
|
167
|
+
if (!file?.owner || !CONTEXT_TIERS.has(file.tier)) return null;
|
|
168
|
+
return contextOfCapability(file.owner.root);
|
|
169
|
+
};
|
|
170
|
+
const unparen = node => { let current = node; while (current && (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isNonNullExpression(current))) current = current.expression; return current; };
|
|
171
|
+
/** The declared connection named by a decorator whose callee is declared in src/modules/platform/database/<conn>.decorators.ts, else null. */
|
|
172
|
+
const connectionOfDecorators = (checker, declaration) => {
|
|
173
|
+
for (const decorator of kit.decorators(declaration)) {
|
|
174
|
+
const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
|
|
175
|
+
for (const target of kit.declarationsOf(checker, callee)) {
|
|
176
|
+
const rel = kit.graphPath(target);
|
|
177
|
+
const name = rel ? [...declared.keys()].find(connection => rel === `${DATABASE_DIR}/${connection}.decorators.ts`) : null;
|
|
178
|
+
if (name) return name;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* The declared connection whose entity manager `expression` is: a property or parameter injected with `Inject<Conn>EntityManager`, or the
|
|
185
|
+
* manager parameter of the callback of a `.transaction(...)` on such a manager. Null when the origin cannot be proven (never guessed).
|
|
186
|
+
*/
|
|
187
|
+
const connectionOfManager = (checker, expression, depth = 0) => {
|
|
188
|
+
const node = unparen(expression);
|
|
189
|
+
if (!node || depth > 4) return null;
|
|
190
|
+
const target = ts.isPropertyAccessExpression(node) ? node.name : (ts.isIdentifier(node) ? node : null);
|
|
191
|
+
if (!target) return null;
|
|
192
|
+
const declaration = kit.declarationsOf(checker, target)[0];
|
|
193
|
+
if (!declaration) return null;
|
|
194
|
+
if (ts.isPropertyDeclaration(declaration) || ts.isParameter(declaration)) {
|
|
195
|
+
const direct = connectionOfDecorators(checker, declaration);
|
|
196
|
+
if (direct) return direct;
|
|
197
|
+
}
|
|
198
|
+
if (ts.isParameter(declaration) && (ts.isArrowFunction(declaration.parent) || ts.isFunctionExpression(declaration.parent)) && declaration.parent.parameters[0] === declaration) {
|
|
199
|
+
const call = declaration.parent.parent;
|
|
200
|
+
if (ts.isCallExpression(call) && call.arguments.includes(declaration.parent) && ts.isPropertyAccessExpression(call.expression) && call.expression.name.text === 'transaction') {
|
|
201
|
+
return connectionOfManager(checker, call.expression.expression, depth + 1);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
};
|
|
206
|
+
const model = { declared, registered, registrations: all, perConnection, contextOfCapability, contextOfFile, connectionOfManager, unparen };
|
|
207
|
+
MODELS.set(graph, model);
|
|
208
|
+
return model;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** The declared connection names the composition root of `app` passes to the database module registration, as [{connection, node}] in source order. */
|
|
212
|
+
export function connectionsComposedBy(kit, app) {
|
|
213
|
+
const { ts } = kit;
|
|
214
|
+
const root = kit.appRoot(app);
|
|
215
|
+
if (!root) return { root: null, items: [] };
|
|
216
|
+
const declared = new Set(kit.resolver.repo.connections.map(connection => connection.name));
|
|
217
|
+
const checker = kit.checkerOf(root.sourceFile);
|
|
218
|
+
const items = [];
|
|
219
|
+
kit.walk(root.sourceFile, node => {
|
|
220
|
+
if (!(ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'register')) return true;
|
|
221
|
+
const owner = kit.declarationsOf(checker, node.expression.expression).map(kit.ownerOfDeclaration).find(Boolean);
|
|
222
|
+
if (!owner || owner.tier !== 'platform' || owner.name !== 'database') return true;
|
|
223
|
+
for (const argument of node.arguments) {
|
|
224
|
+
kit.walk(argument, inner => {
|
|
225
|
+
if (ts.isPropertyAssignment(inner.parent ?? {}) && inner.parent.name === inner) return true;
|
|
226
|
+
if (ts.isPropertyAccessExpression(inner.parent ?? {}) && inner.parent.name === inner) return true;
|
|
227
|
+
if (!(ts.isIdentifier(inner) || ts.isStringLiteralLike(inner) || ts.isPropertyAccessExpression(inner))) return true;
|
|
228
|
+
const value = kit.stringValue(checker, inner);
|
|
229
|
+
if (value === null || !declared.has(value)) return true;
|
|
230
|
+
items.push({ connection: value, node: inner });
|
|
231
|
+
return false;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
});
|
|
236
|
+
return { root, items };
|
|
237
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { contextModelOf, connectionsComposedBy } from './context-map.mjs';
|
|
2
|
+
import { machineKit } from './machine-ast.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* R174 `context-owner` (BE_CONTEXT_OWNER). A connection of hfs.json IS a bounded context, and its `owner` is the one service app that composes it:
|
|
6
|
+
* - an api or worker app passes to the database module registration (or lists in its options) only the connections it owns (a monolith's one
|
|
7
|
+
* app owns them all);
|
|
8
|
+
* - an api or worker app imports no domain or projection capability of a context it does not own: it composes only its own contexts;
|
|
9
|
+
* - the cli app is the migration runner and composes every declared connection
|
|
10
|
+
* that holds registered tables (migrations run only through it, once per connection).
|
|
11
|
+
* Which connection an app composes is read from two typed origins in its own files: the connection passed to the database module registration
|
|
12
|
+
* and the connection literal that lists a capability's `<c>Entities` / `<c>Migrations`. Which context a capability belongs to is the connection
|
|
13
|
+
* its arrays are registered on (context-map.mjs); an import is judged by the owner graph edge and the target's slot tier, never by a path or
|
|
14
|
+
* name pattern. A runner app with a registration whose connection name cannot be resolved is not judged for missing connections.
|
|
15
|
+
*/
|
|
16
|
+
export const CONTEXT_OWNER_RULE_IDS = ['BE_CONTEXT_OWNER'];
|
|
17
|
+
|
|
18
|
+
const RULE = 'BE_CONTEXT_OWNER';
|
|
19
|
+
const SERVICE_KINDS = new Set(['api', 'worker']);
|
|
20
|
+
const RUNNER_KINDS = new Set(['migrate', 'cli']);
|
|
21
|
+
|
|
22
|
+
export function checkContextOwner(input) {
|
|
23
|
+
const { graph } = input;
|
|
24
|
+
const kit = machineKit(input);
|
|
25
|
+
const { resolver } = kit;
|
|
26
|
+
const model = contextModelOf(kit, graph);
|
|
27
|
+
const violations = [];
|
|
28
|
+
const owners = new Map(resolver.repo.connections.map(connection => [connection.name, connection.owner]));
|
|
29
|
+
let compositions = 0;
|
|
30
|
+
let imports = 0;
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const once = (key, violation) => { if (!seen.has(key)) { seen.add(key); violations.push({ ruleId: RULE, ...violation }); } };
|
|
33
|
+
const appOf = rel => { const classified = resolver.classifyPath(rel); return classified.slot?.startsWith('be.app.') ? classified.bindings?.app ?? null : null; };
|
|
34
|
+
|
|
35
|
+
// What each app composes: [{connection, file, node}] from the database module registration and from the registration literals under its files.
|
|
36
|
+
const composed = new Map(resolver.repo.apps.map(app => [app.name, { items: [], unresolved: false }]));
|
|
37
|
+
for (const app of resolver.repo.apps) {
|
|
38
|
+
const { root, items } = connectionsComposedBy(kit, app.name);
|
|
39
|
+
for (const item of items) composed.get(app.name).items.push({ ...item, file: root });
|
|
40
|
+
}
|
|
41
|
+
for (const site of model.registrations) {
|
|
42
|
+
const app = appOf(site.file.rel);
|
|
43
|
+
if (!app || !composed.has(app)) continue;
|
|
44
|
+
if (site.connection === null) composed.get(app).unresolved = true;
|
|
45
|
+
else composed.get(app).items.push({ connection: site.connection, file: site.file, node: site.node });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const app of resolver.repo.apps) {
|
|
49
|
+
const { items, unresolved } = composed.get(app.name);
|
|
50
|
+
compositions += items.length;
|
|
51
|
+
if (SERVICE_KINDS.has(app.kind)) {
|
|
52
|
+
for (const { connection, file, node } of items) {
|
|
53
|
+
const owner = owners.get(connection);
|
|
54
|
+
if (owner && owner !== app.name) {
|
|
55
|
+
once(`${app.name}|${connection}`, { ...kit.at(file.rel, file.sourceFile, node), message: `App ${app.name} composes connection ${connection}, the context owned by app ${owner}; an app composes only the contexts it owns. Reach that context through the API or the events of ${owner}.`, connection, app: app.name, owner });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} else if (RUNNER_KINDS.has(app.kind) && !unresolved) {
|
|
59
|
+
const root = kit.appRoot(app.name);
|
|
60
|
+
const present = new Set(items.map(item => item.connection));
|
|
61
|
+
const held = new Set([...model.registered.values()].flatMap(entry => [...entry.connections.keys()]));
|
|
62
|
+
for (const connection of owners.keys()) {
|
|
63
|
+
if (!root || present.has(connection) || !held.has(connection) || !items.length) continue;
|
|
64
|
+
once(`${app.name}|missing|${connection}`, { ...kit.at(root.rel, root.sourceFile, root.sourceFile), message: `The ${app.kind} app ${app.name} does not compose connection ${connection}; migrations run only through it, once per connection, so it registers every declared connection that holds tables.`, connection, app: app.name });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const kinds = new Map(resolver.repo.apps.map(app => [app.name, app.kind]));
|
|
70
|
+
for (const edge of graph.edges) {
|
|
71
|
+
const app = appOf(edge.from);
|
|
72
|
+
if (!app || !SERVICE_KINDS.has(kinds.get(app))) continue;
|
|
73
|
+
const context = model.contextOfFile(edge.to);
|
|
74
|
+
if (!context) continue;
|
|
75
|
+
imports += 1;
|
|
76
|
+
const owner = owners.get(context);
|
|
77
|
+
if (owner && owner !== app) {
|
|
78
|
+
once(`${edge.from}|${edge.to}`, { path: edge.from, line: edge.line, column: edge.column, message: `App ${app} imports ${graph.files.get(edge.to).owner.root}, a capability of context ${context} owned by app ${owner}; an app composes only the contexts it owns, so use the API or the events of ${owner} instead.`, connection: context, app, owner });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { violations, coverage: { status: 'checked', apps: resolver.repo.apps.length, compositions, contextImports: imports } };
|
|
82
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { contextModelOf } from './context-map.mjs';
|
|
2
|
+
import { machineKit } from './machine-ast.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* R178 `context-platform-tables` (BE_CONTEXT_PLATFORM_TABLES). A platform capability the manifest lists as `perConnection` (the event-bus outbox,
|
|
6
|
+
* the job table) keeps its tables on EVERY connection that uses it: when a call into the capability passes the entity manager of connection C
|
|
7
|
+
* (an `Inject<C>EntityManager` property, or the manager of a transaction on one), the capability's `<c>Entities` and `<c>Migrations` must be
|
|
8
|
+
* registered on C, or the row the call writes has no table in C's database. The caller's connection is proven by the manager's origin; a call
|
|
9
|
+
* whose manager origin cannot be proven is not judged.
|
|
10
|
+
*/
|
|
11
|
+
export const CONTEXT_PLATFORM_TABLES_RULE_IDS = ['BE_CONTEXT_PLATFORM_TABLES'];
|
|
12
|
+
|
|
13
|
+
const RULE = 'BE_CONTEXT_PLATFORM_TABLES';
|
|
14
|
+
|
|
15
|
+
export function checkContextPlatformTables(input) {
|
|
16
|
+
const { graph } = input;
|
|
17
|
+
const kit = machineKit(input);
|
|
18
|
+
const { ts } = kit;
|
|
19
|
+
const model = contextModelOf(kit, graph);
|
|
20
|
+
const violations = [];
|
|
21
|
+
let calls = 0;
|
|
22
|
+
let unproven = 0;
|
|
23
|
+
if (!model.perConnection.size) return { violations, coverage: { status: 'checked', perConnectionCapabilities: 0, calls, unproven } };
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const file of graph.files.values()) {
|
|
26
|
+
const checker = kit.checkerOf(file.sourceFile);
|
|
27
|
+
kit.walk(file.sourceFile, node => {
|
|
28
|
+
if (!ts.isCallExpression(node)) return true;
|
|
29
|
+
const callee = ts.isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression;
|
|
30
|
+
const home = kit.declarationsOf(checker, callee).map(kit.ownerOfDeclaration).find(Boolean);
|
|
31
|
+
if (!home || !model.perConnection.has(home.root) || file.owner?.root === home.root) return true;
|
|
32
|
+
calls += 1;
|
|
33
|
+
const connections = new Set(node.arguments.map(argument => model.connectionOfManager(checker, argument)).filter(Boolean));
|
|
34
|
+
if (!connections.size) { unproven += 1; return true; }
|
|
35
|
+
const registeredOn = model.registered.get(home.root)?.connections ?? new Map();
|
|
36
|
+
for (const connection of connections) {
|
|
37
|
+
if (registeredOn.has(connection)) continue;
|
|
38
|
+
const key = `${file.rel}|${node.getStart()}|${connection}`;
|
|
39
|
+
if (seen.has(key)) continue;
|
|
40
|
+
seen.add(key);
|
|
41
|
+
violations.push({ ruleId: RULE, ...kit.at(file.rel, file.sourceFile, node), message: `${file.rel} passes the entity manager of connection ${connection} to ${home.name}, but the tables of ${home.name} are not registered on ${connection} (registered: ${[...registeredOn.keys()].sort().join(', ') || 'none'}); register its entities and migrations on ${connection} too, so the table exists in the database of that context.`, connection, capability: home.name });
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return { violations, coverage: { status: 'checked', perConnectionCapabilities: model.perConnection.size, calls, unproven } };
|
|
47
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { contextModelOf } from './context-map.mjs';
|
|
2
|
+
import { machineKit } from './machine-ast.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* R176 `context-transaction` (BE_CONTEXT_TRANSACTION). One transaction touches one context's connection. In the callback of
|
|
6
|
+
* `<manager>.transaction(async (manager) => ...)`, where `<manager>` is proven to be the entity manager of connection C (an
|
|
7
|
+
* `Inject<C>EntityManager` property or parameter), the code never:
|
|
8
|
+
* - uses the entity manager of another declared connection (proven by its injector's home file); or
|
|
9
|
+
* - calls a method of a domain or projection capability of another context (that capability writes through its own connection).
|
|
10
|
+
* Work that spans contexts is a saga of one transaction per context, linked by events. A receiver whose connection cannot be proven is not judged.
|
|
11
|
+
*/
|
|
12
|
+
export const CONTEXT_TRANSACTION_RULE_IDS = ['BE_CONTEXT_TRANSACTION'];
|
|
13
|
+
|
|
14
|
+
const RULE = 'BE_CONTEXT_TRANSACTION';
|
|
15
|
+
|
|
16
|
+
export function checkContextTransaction(input) {
|
|
17
|
+
const { graph } = input;
|
|
18
|
+
const kit = machineKit(input);
|
|
19
|
+
const { ts } = kit;
|
|
20
|
+
const model = contextModelOf(kit, graph);
|
|
21
|
+
const violations = [];
|
|
22
|
+
let transactions = 0;
|
|
23
|
+
let unproven = 0;
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const report = (file, node, message, extra) => {
|
|
26
|
+
const key = `${file.rel}|${node.getStart()}|${message}`;
|
|
27
|
+
if (seen.has(key)) return;
|
|
28
|
+
seen.add(key);
|
|
29
|
+
violations.push({ ruleId: RULE, ...kit.at(file.rel, file.sourceFile, node), message, ...extra });
|
|
30
|
+
};
|
|
31
|
+
for (const file of graph.files.values()) {
|
|
32
|
+
const checker = kit.checkerOf(file.sourceFile);
|
|
33
|
+
kit.walk(file.sourceFile, node => {
|
|
34
|
+
if (!(ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'transaction')) return true;
|
|
35
|
+
const callback = node.arguments.find(argument => ts.isArrowFunction(argument) || ts.isFunctionExpression(argument));
|
|
36
|
+
if (!callback) return true;
|
|
37
|
+
const connection = model.connectionOfManager(checker, node.expression.expression);
|
|
38
|
+
if (!connection) { unproven += 1; return true; }
|
|
39
|
+
transactions += 1;
|
|
40
|
+
kit.walk(callback.body, inner => {
|
|
41
|
+
if (ts.isPropertyAccessExpression(inner)) {
|
|
42
|
+
const other = model.connectionOfManager(checker, inner);
|
|
43
|
+
if (other && other !== connection) report(file, inner, `A transaction on connection ${connection} uses the entity manager of connection ${other}; one transaction touches one context's connection. Span contexts with a saga: one transaction per context, linked by events.`, { connection, other });
|
|
44
|
+
}
|
|
45
|
+
if (ts.isCallExpression(inner)) {
|
|
46
|
+
const callee = ts.isPropertyAccessExpression(inner.expression) ? inner.expression.name : inner.expression;
|
|
47
|
+
const declaration = kit.declarationsOf(checker, callee)[0];
|
|
48
|
+
const rel = declaration ? kit.graphPath(declaration) : null;
|
|
49
|
+
const context = rel ? model.contextOfFile(rel) : null;
|
|
50
|
+
if (context && context !== connection) {
|
|
51
|
+
report(file, inner, `A transaction on connection ${connection} calls ${graph.files.get(rel).owner.root} of context ${context}; one transaction touches one context's connection. Span contexts with a saga: one transaction per context, linked by events.`, { connection, other: context });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return { violations, coverage: { status: 'checked', transactions, unproven } };
|
|
60
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import { allowsFile } from '
|
|
3
|
+
import { checkerScope } from './required-files.mjs';
|
|
4
|
+
import { allowsFile } from '../allows.mjs';
|
|
5
|
+
import { unwrapEach } from './ast-walks.mjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* R47 `contract-fixture-guard` (BE_CONTRACT_UNGUARDED). A fake at the network edge serves payload fixtures; a fixture drifts
|
|
@@ -28,11 +29,7 @@ const FAKES_DIRECTORY = 'fakes/';
|
|
|
28
29
|
const PAYLOADS = /^([^/]+)\/payloads\/[^/]+\.json$/u;
|
|
29
30
|
const SHAPE_MATCHERS = new Set(['toEqual', 'toStrictEqual']);
|
|
30
31
|
|
|
31
|
-
const unwrap = (ts, node) =>
|
|
32
|
-
let current = node;
|
|
33
|
-
while (current && (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isNonNullExpression(current) || ts.isAwaitExpression(current))) current = current.expression;
|
|
34
|
-
return current;
|
|
35
|
-
};
|
|
32
|
+
const unwrap = (ts, node) => unwrapEach(ts, node, [ts.isParenthesizedExpression, ts.isAsExpression, ts.isNonNullExpression, ts.isAwaitExpression]);
|
|
36
33
|
|
|
37
34
|
/** Every node under `root`, itself included. */
|
|
38
35
|
function walk(ts, root, visit) {
|
|
@@ -79,11 +76,8 @@ function readSpec(ts, sourceFile, helper, providers) {
|
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
export function checkContractFixtureGuard(input) {
|
|
82
|
-
const { config, graph,
|
|
83
|
-
const ts = context.ts;
|
|
84
|
-
const resolver = graph.resolver;
|
|
79
|
+
const { config, graph, ts, resolver, tree } = checkerScope(input);
|
|
85
80
|
const { contractShape } = resolver.ruleParams();
|
|
86
|
-
const tree = treeOf(config.root);
|
|
87
81
|
const violations = [];
|
|
88
82
|
const fixtures = new Map(); // provider -> first payload fixture file
|
|
89
83
|
const specs = new Map(); // provider -> [contract spec file]
|