@starci/hfs 4.0.8 → 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 +9 -0
- package/README.md +81 -39
- 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 +53 -19
- 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/knowledge/hfs/canon-pins.yaml +160 -29
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +2059 -0
- package/runtime/knowledge/hfs/slots.yaml +912 -120
- 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 +131 -25
- package/runtime/knowledge/sonar-gate.yaml +52 -18
- package/runtime/modules/kernel/failure-codes.yaml +571 -51
- 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 +10 -2
- package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
- 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 +16 -26
- 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 +9 -5
- package/runtime/scripts/api/sops/lib.mjs +35 -0
- package/runtime/scripts/api/sops/seal.mjs +18 -0
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
- package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
- package/runtime/scripts/hfs/architecture/config.mjs +9 -6
- package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
- 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/hfs/architecture/contract-fixture-guard.mjs +4 -10
- package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
- package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
- package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
- package/runtime/scripts/hfs/architecture/index.mjs +17 -4
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
- package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
- package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
- package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
- package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
- package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
- package/runtime/scripts/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/hfs/architecture/source-names.mjs +27 -147
- package/runtime/scripts/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/hfs/architecture/surface.mjs +9 -3
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
- package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
- package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
- package/runtime/scripts/hfs/architecture.mjs +4 -4
- package/runtime/scripts/hfs/check.mjs +75 -72
- 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 +109 -32
- package/runtime/scripts/hfs/param-names.mjs +18 -0
- package/runtime/scripts/hfs/path-findings.mjs +1 -1
- package/runtime/scripts/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/hfs/rules/contract.mjs +10 -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/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 +1 -1
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
- package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
- package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
- package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
- package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
- package/runtime/scripts/hfs/rules/read.mjs +1 -1
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
- package/runtime/scripts/hfs/rules/saga.mjs +170 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
- package/runtime/scripts/hfs/rules/services.mjs +220 -0
- package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
- package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
- package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
- package/runtime/scripts/hfs/secret.mjs +175 -0
- package/runtime/scripts/hfs/slots.mjs +78 -108
- package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
- package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
- package/runtime/scripts/hfs/tree.mjs +5 -4
- package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
- package/runtime/scripts/hfs/view.mjs +3 -2
- 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 +1 -1
- package/runtime/scripts/lib/i18n.mjs +86 -0
- package/runtime/scripts/lib/json.mjs +13 -3
- package/runtime/scripts/lib/language.mjs +24 -5
- package/runtime/scripts/lib/list.mjs +20 -0
- package/runtime/scripts/lib/package-at.mjs +37 -0
- package/runtime/scripts/lib/path-key.mjs +57 -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-services.mjs +7 -31
- package/runtime/scripts/lib/ts-ast.mjs +35 -0
- package/runtime/scripts/lib/walk.mjs +4 -3
- 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 +358 -69
- package/scaffold/edition-gate.mjs +25 -0
- package/scaffold/image.mjs +19 -0
- package/scaffold/lite-exports.mjs +186 -0
- package/scaffold/service.mjs +32 -26
- 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 +10 -10
- package/sync/index.mjs +167 -104
- 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 +1 -4
- 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/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 +2 -2
- 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/{__app__ → landing}/next.config.ts +4 -2
- 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-lite/apps/web/src/modules/i18n/request.ts +16 -0
- 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/scripts/api/fs/rmdir-link.mjs +0 -12
- package/runtime/scripts/lib/test-secrets.mjs +0 -116
- 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 -19
- package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
- package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
- package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
- 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 -23
- 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 -28
- 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/request.ts +0 -19
- 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/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
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { machineKit, pascal } from './machine-ast.mjs';
|
|
3
3
|
import { treeOf } from './required-files.mjs';
|
|
4
4
|
import { DATABASE_DIR } from './connection-map.mjs';
|
|
5
|
+
import { collectRegistrations, isPerConnection, persistenceOfFactory } from './context-map.mjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* R35 `schema-owner` (BE_SCHEMA_OWNER, BE-CONVENTION 1.4.6). Schema lives with the capability that owns the table:
|
|
@@ -11,7 +12,7 @@ import { DATABASE_DIR } from './connection-map.mjs';
|
|
|
11
12
|
* folder named `entities` or `migrations` outside be.persistence is refused too;
|
|
12
13
|
* - `persistence/connection.ts` declares `<c>Entities` / `<c>Migrations`, the arrays the apps register; a capability has no
|
|
13
14
|
* `CONNECTION` alias (C0 lead decision 2026-09-30): its connection is the one those arrays are REGISTERED on, in the
|
|
14
|
-
* `entities` / `migrations` of a connection literal of `DatabaseModule.register` and of the
|
|
15
|
+
* `entities` / `migrations` of a connection literal of `DatabaseModule.register` and of the cli app. The arrays are
|
|
15
16
|
* registered under exactly one connection across all apps, that connection is declared in hfs.json, and every
|
|
16
17
|
* `Inject<Conn>EntityManager()` used inside the capability is that same connection;
|
|
17
18
|
* - the capability's `index.ts` exports `<c>Entities` when it has entities and `<c>Migrations` when it has migrations
|
|
@@ -36,13 +37,7 @@ export function checkSchemaOwner(input) {
|
|
|
36
37
|
const report = (file, node, message, extra = {}) => violations.push({ ruleId: RULE, path: file.rel, ...kit.at(file.rel, file.sourceFile, node), message, ...extra });
|
|
37
38
|
const plain = (rel, message, extra = {}) => violations.push({ ruleId: RULE, path: rel, line: 1, column: 1, message, ...extra });
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
const persistenceOf = rel => {
|
|
41
|
-
const classified = resolver.classifyPath(rel);
|
|
42
|
-
if (classified.slot !== PERSISTENCE_SLOT) return null;
|
|
43
|
-
const below = rel.slice(classified.root.length + 1).split('/');
|
|
44
|
-
return { root: classified.root, capability: path.posix.basename(path.posix.dirname(classified.root)), folder: below.length > 1 ? below[0] : '', below };
|
|
45
|
-
};
|
|
40
|
+
const persistenceOf = persistenceOfFactory(resolver);
|
|
46
41
|
|
|
47
42
|
let entities = 0;
|
|
48
43
|
let migrations = 0;
|
|
@@ -80,7 +75,10 @@ export function checkSchemaOwner(input) {
|
|
|
80
75
|
const isMigration = heritage(statement).some(expression => kit.isImportOf(checker, expression, 'MigrationInterface', 'typeorm'));
|
|
81
76
|
if (isEntity) {
|
|
82
77
|
entities += 1;
|
|
83
|
-
|
|
78
|
+
// The table of a projection belongs to the projection that alone writes it: `<name>.projection-entity.ts` of slot be.projections.
|
|
79
|
+
const ownProjectionTable = resolver.classifyPath(file.rel).slot === 'be.projections' && file.rel.endsWith('.projection-entity.ts');
|
|
80
|
+
if (ownProjectionTable) note({ root: path.posix.join(path.posix.dirname(file.rel), 'persistence'), capability: path.posix.basename(path.posix.dirname(file.rel)) }, 'entities');
|
|
81
|
+
else if (persistence?.folder !== 'entities') report(file, statement.name, `Entity ${statement.name.text} is declared outside persistence/entities/ of a domain or platform capability; an entity lives with the capability that owns its table, in src/modules/{domain,platform}/<capability>/persistence/entities/<table>.entity.ts.`, { class: statement.name.text });
|
|
84
82
|
else note(persistence, 'entities');
|
|
85
83
|
}
|
|
86
84
|
if (!isMigration) continue;
|
|
@@ -100,124 +98,28 @@ export function checkSchemaOwner(input) {
|
|
|
100
98
|
}
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
const returned = declaration => {
|
|
106
|
-
const fn = ts.isVariableDeclaration(declaration) && declaration.initializer ? unparen(declaration.initializer) : declaration;
|
|
107
|
-
if (!(ts.isArrowFunction(fn) || ts.isFunctionDeclaration(fn) || ts.isFunctionExpression(fn) || ts.isMethodDeclaration(fn)) || !fn.body) return null;
|
|
108
|
-
if (!ts.isBlock(fn.body)) return fn.body;
|
|
109
|
-
const statements = fn.body.statements.filter(ts.isReturnStatement);
|
|
110
|
-
return statements.length === 1 ? statements[0].expression ?? null : null;
|
|
111
|
-
};
|
|
112
|
-
const propertySources = new Map(); // property name -> [{declaration, initializer, checker}] over the program
|
|
113
|
-
const assignmentsOf = name => {
|
|
114
|
-
if (propertySources.has(name)) return propertySources.get(name);
|
|
115
|
-
const list = [];
|
|
116
|
-
for (const file of graph.files.values()) {
|
|
117
|
-
if (!file.sourceFile.text.includes(name)) continue;
|
|
118
|
-
const checker = kit.checkerOf(file.sourceFile);
|
|
119
|
-
kit.walk(file.sourceFile, node => {
|
|
120
|
-
if (ts.isPropertyAssignment(node) && kit.propertyNameText(node.name) === name && ts.isObjectLiteralExpression(node.parent)) {
|
|
121
|
-
// the property of the type the literal is checked against, so a typed const and a typed return meet the same declaration
|
|
122
|
-
const contextual = checker.getContextualType(node.parent)?.getProperty(name);
|
|
123
|
-
const symbol = contextual ?? checker.getSymbolAtLocation(node.name);
|
|
124
|
-
list.push({ declaration: symbol?.declarations?.[0] ?? null, initializer: node.initializer, checker });
|
|
125
|
-
}
|
|
126
|
-
return true;
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
propertySources.set(name, list);
|
|
130
|
-
return list;
|
|
131
|
-
};
|
|
132
|
-
const nameOfLiteral = (checker, literal, depth) => {
|
|
133
|
-
const own = kit.propertyOf(literal, 'name');
|
|
134
|
-
if (own) return kit.stringValue(checker, kit.valueOfProperty(own));
|
|
135
|
-
for (const property of literal.properties) {
|
|
136
|
-
if (!ts.isSpreadAssignment(property)) continue;
|
|
137
|
-
const value = nameOfExpression(checker, property.expression, depth + 1);
|
|
138
|
-
if (value) return value;
|
|
139
|
-
}
|
|
140
|
-
return null;
|
|
141
|
-
};
|
|
142
|
-
const nameOfExpression = (checker, expression, depth) => {
|
|
143
|
-
const node = unparen(expression);
|
|
144
|
-
if (!node || depth > 5) return null;
|
|
145
|
-
if (ts.isObjectLiteralExpression(node)) return nameOfLiteral(checker, node, depth);
|
|
146
|
-
if (ts.isCallExpression(node)) {
|
|
147
|
-
for (const declaration of kit.declarationsOf(checker, node.expression)) {
|
|
148
|
-
const body = returned(declaration);
|
|
149
|
-
const value = body ? nameOfExpression(kit.checkerOf(declaration.getSourceFile()), body, depth + 1) : null;
|
|
150
|
-
if (value) return value;
|
|
151
|
-
}
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
if (ts.isIdentifier(node)) {
|
|
155
|
-
const declaration = kit.declarationsOf(checker, node)[0];
|
|
156
|
-
return declaration && ts.isVariableDeclaration(declaration) && declaration.initializer ? nameOfExpression(kit.checkerOf(declaration.getSourceFile()), declaration.initializer, depth + 1) : null;
|
|
157
|
-
}
|
|
158
|
-
if (ts.isPropertyAccessExpression(node)) {
|
|
159
|
-
const target = kit.declarationsOf(checker, node.name)[0];
|
|
160
|
-
if (!target) return null;
|
|
161
|
-
const names = new Set(assignmentsOf(node.name.text).filter(item => item.declaration === target).map(item => nameOfExpression(item.checker, item.initializer, depth + 1)).filter(Boolean));
|
|
162
|
-
return names.size === 1 ? [...names][0] : null;
|
|
163
|
-
}
|
|
164
|
-
return null;
|
|
165
|
-
};
|
|
166
|
-
/** The capability arrays (`<c>Entities` / `<c>Migrations` of persistence/connection.ts) an expression lists. */
|
|
167
|
-
const arraysIn = (checker, expression, out, depth) => {
|
|
168
|
-
const node = unparen(expression);
|
|
169
|
-
if (!node || depth > 6) return;
|
|
170
|
-
if (ts.isArrayLiteralExpression(node)) { for (const element of node.elements) arraysIn(checker, ts.isSpreadElement(element) ? element.expression : element, out, depth + 1); return; }
|
|
171
|
-
if (!ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node)) return;
|
|
172
|
-
const declaration = kit.declarationsOf(checker, ts.isPropertyAccessExpression(node) ? node.name : node)[0];
|
|
173
|
-
if (!declaration || !ts.isVariableDeclaration(declaration) || !ts.isIdentifier(declaration.name)) return;
|
|
174
|
-
const rel = kit.graphPath(declaration);
|
|
175
|
-
const owner = rel ? persistenceOf(rel) : null;
|
|
176
|
-
if (owner && owner.below.length === 1 && owner.below[0] === 'connection.ts' && [`${camel(owner.capability)}Entities`, `${camel(owner.capability)}Migrations`].includes(declaration.name.text)) {
|
|
177
|
-
out.push({ key: path.posix.dirname(owner.root), name: owner.capability });
|
|
178
|
-
} else if (declaration.initializer) arraysIn(kit.checkerOf(declaration.getSourceFile()), declaration.initializer, out, depth + 1);
|
|
179
|
-
};
|
|
180
|
-
const registered = new Map(); // capability root -> {name, connections: Map(connection -> first site)}
|
|
181
|
-
let registrations = 0;
|
|
182
|
-
for (const file of graph.files.values()) {
|
|
183
|
-
const checker = kit.checkerOf(file.sourceFile);
|
|
184
|
-
kit.walk(file.sourceFile, node => {
|
|
185
|
-
if (!ts.isObjectLiteralExpression(node)) return true;
|
|
186
|
-
const arrays = [];
|
|
187
|
-
for (const key of ['entities', 'migrations']) {
|
|
188
|
-
const property = kit.propertyOf(node, key);
|
|
189
|
-
if (property) arraysIn(checker, kit.valueOfProperty(property), arrays, 0);
|
|
190
|
-
}
|
|
191
|
-
if (!arrays.length) return true;
|
|
192
|
-
registrations += 1;
|
|
193
|
-
const connection = nameOfLiteral(checker, node, 0);
|
|
194
|
-
if (connection === null) return true;
|
|
195
|
-
for (const { key, name } of new Map(arrays.map(item => [item.key, item])).values()) {
|
|
196
|
-
if (!registered.has(key)) registered.set(key, { name, connections: new Map() });
|
|
197
|
-
const sites = registered.get(key).connections;
|
|
198
|
-
if (!sites.has(connection)) sites.set(connection, { file, node });
|
|
199
|
-
}
|
|
200
|
-
return true;
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
for (const { name, connections: sites } of registered.values()) {
|
|
101
|
+
const { registered, registrations } = collectRegistrations({ kit, graph, persistenceOf });
|
|
102
|
+
for (const [key, { name, connections: sites }] of registered) {
|
|
204
103
|
for (const [connection, site] of sites) {
|
|
205
104
|
if (!connections.has(connection)) report(site.file, site.node, `The ${name} capability is registered on connection "${connection}", which hfs.json does not declare (declared: ${[...connections].sort().join(', ') || 'none'}); the tables of a capability live on one declared connection.`, { connection, capability: name });
|
|
206
105
|
}
|
|
207
|
-
if (sites.size > 1) {
|
|
106
|
+
if (sites.size > 1 && !isPerConnection(resolver, key, name)) {
|
|
208
107
|
const all = [...sites.keys()].sort().join(' and ');
|
|
209
108
|
for (const [connection, site] of [...sites].slice(1)) report(site.file, site.node, `The ${name} capability's entities and migrations are registered on connections ${all}; a capability's tables live on exactly one connection, so register its arrays under one connection across every app.`, { connection, capability: name });
|
|
210
109
|
}
|
|
211
110
|
}
|
|
212
111
|
for (const [key, kinds] of found) {
|
|
213
|
-
if (!registered.get(key)?.connections.size) plain(`${key}/persistence/connection.ts`, `The ${kinds.name} capability has entities or migrations but no app registers them; add ${camel(kinds.name)}Entities and ${camel(kinds.name)}Migrations to a connection of DatabaseModule.register and of the
|
|
112
|
+
if (!registered.get(key)?.connections.size) plain(`${key}/persistence/connection.ts`, `The ${kinds.name} capability has entities or migrations but no app registers them; add ${camel(kinds.name)}Entities and ${camel(kinds.name)}Migrations to a connection of DatabaseModule.register and of the cli app.`, { capability: kinds.name });
|
|
214
113
|
const connectionFile = graph.files.get(`${key}/persistence/connection.ts`);
|
|
215
114
|
const declared = new Set((connectionFile?.sourceFile.statements.filter(ts.isVariableStatement).flatMap(statement => statement.declarationList.declarations) ?? []).map(item => item.name.text));
|
|
216
115
|
for (const [kind, has] of [['Entities', kinds.entities], ['Migrations', kinds.migrations]]) {
|
|
217
116
|
if (has && !declared.has(`${camel(kinds.name)}${kind}`)) plain(`${key}/persistence/connection.ts`, `${key}/persistence/connection.ts must export ${camel(kinds.name)}${kind}, the ${kind.toLowerCase()} of the ${kinds.name} capability, the one file that lists them for the apps.`, { capability: kinds.name });
|
|
218
117
|
}
|
|
219
118
|
const only = registered.get(key)?.connections;
|
|
220
|
-
if (!only || only.size
|
|
119
|
+
if (!only || only.size < 1) continue;
|
|
120
|
+
// A per-connection platform capability (event-bus, jobs) lives on every connection that uses it; any other on exactly one (reported above).
|
|
121
|
+
const perConnection = only.size > 1 && isPerConnection(resolver, key, kinds.name);
|
|
122
|
+
if (only.size > 1 && !perConnection) continue;
|
|
221
123
|
const home = [...only.keys()][0];
|
|
222
124
|
for (const file of graph.files.values()) {
|
|
223
125
|
if (!file.rel.startsWith(`${key}/`)) continue;
|
|
@@ -227,7 +129,8 @@ export function checkSchemaOwner(input) {
|
|
|
227
129
|
const declaration = kit.declarationsOf(checker, node.expression)[0];
|
|
228
130
|
const rel = declaration ? kit.graphPath(declaration) : null;
|
|
229
131
|
const used = rel ? [...connections].find(name => rel === `${DATABASE_DIR}/${name}.decorators.ts`) : null;
|
|
230
|
-
if (used &&
|
|
132
|
+
if (used && perConnection && !only.has(used)) report(file, node, `${file.rel} injects the entity manager of connection ${used}, but the ${kinds.name} capability's tables are registered on ${[...only.keys()].sort().join(' and ')}; register the capability on ${used} too or inject one of its connections.`, { connection: used, expected: [...only.keys()].sort().join(','), capability: kinds.name });
|
|
133
|
+
else if (used && !perConnection && used !== home) report(file, node, `${file.rel} injects the entity manager of connection ${used}, but the ${kinds.name} capability's tables are registered on ${home}; a capability reads only its own database, so use Inject${pascal(home)}EntityManager or move the tables.`, { connection: used, expected: home, capability: kinds.name });
|
|
231
134
|
return true;
|
|
232
135
|
});
|
|
233
136
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The value of a directive-prologue statement, or null once the prologue has ended. */
|
|
2
|
+
function directiveValue(ts, sourceFile, statement) {
|
|
3
|
+
const expressionStatement = ts?.isExpressionStatement
|
|
4
|
+
? ts.isExpressionStatement(statement)
|
|
5
|
+
: Boolean(statement?.expression);
|
|
6
|
+
if (!expressionStatement) return null;
|
|
7
|
+
const expression = statement.expression;
|
|
8
|
+
if (ts?.isStringLiteral) return ts.isStringLiteral(expression) ? expression.text : null;
|
|
9
|
+
const raw = expression?.getText?.(sourceFile);
|
|
10
|
+
const quote = raw?.[0];
|
|
11
|
+
return typeof expression?.text === 'string' && (quote === "'" || quote === '"') && raw.at(-1) === quote
|
|
12
|
+
? expression.text
|
|
13
|
+
: null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** True when a TypeScript source file declares `"use server"` in its directive prologue. */
|
|
17
|
+
export function isServerActionModule(ts, sourceFile) {
|
|
18
|
+
for (const statement of sourceFile?.statements ?? []) {
|
|
19
|
+
const value = directiveValue(ts, sourceFile, statement);
|
|
20
|
+
if (value === null) return false;
|
|
21
|
+
if (value === 'use server') return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// side-boundary.mjs - what counts as an import that leaves a side of an app (ARCH_INTERNAL_IMPORT_OUTSIDE).
|
|
2
|
+
// A side (be/ or fe/) imports nothing of the app outside itself: the root holds no source and the other side is another program; a
|
|
3
|
+
// declared read of the OTHER side (be/contracts/) is codegen input, never an import. A declared read of a tree the APP ROOT owns
|
|
4
|
+
// (supabase/types/, sides.<side>.reads of the slot manifest) is the one thing both sides do import: the generated database types.
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { isInside, slash } from './config.mjs';
|
|
7
|
+
|
|
8
|
+
const OTHER_SIDE = /^(be|fe)\//;
|
|
9
|
+
|
|
10
|
+
/** True when `target` (an absolute path) lies in a tree of the app root that the side declares it reads (`reads` of the side declaration). */
|
|
11
|
+
const readsAppTree = (config, target) => (config.hfs?.repo?.reads ?? [])
|
|
12
|
+
.some((read) => !OTHER_SIDE.test(read) && isInside(path.join(config.packageRoot, read), target));
|
|
13
|
+
|
|
14
|
+
/** Whether an import of `target` leaves the side the machine judges (config.root) for the app (config.packageRoot), declared app-root reads excepted. */
|
|
15
|
+
export const crossesSide = (config, target) => config.packageRoot !== undefined && config.packageRoot !== config.root
|
|
16
|
+
&& isInside(config.packageRoot, target) && !isInside(config.root, target) && !slash(target).includes('/node_modules/') && !readsAppTree(config, target);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { isInside } from './config.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { relativePath, UNPROVEN_FRAMEWORK, unwrapExpression } from './typescript.mjs';
|
|
4
|
+
import { commonJsRequireReasons, constructedDecoratorKind, decoratorCallee, moduleExportsOf, mutableDecoratorKind, nodeDecorators, normalizedSymbol, normalizedSymbolValue, programSourcesOf, returnedExpressions, selectedNode, tracedFrameworkKinds, valueSymbol, violation } from './ast-walks.mjs';
|
|
5
|
+
import { sourceLocation } from '../../lib/ts-ast.mjs';
|
|
4
6
|
|
|
5
7
|
const SOURCE_LAYOUT_RULE_ID = 'BE_FEATURE_LAYOUT_INVALID';
|
|
6
8
|
const SOURCE_NAME_RULE_ID = 'BE_SOURCE_FORM';
|
|
@@ -9,16 +11,16 @@ const FRAMEWORK_EXPORTS = new Map([
|
|
|
9
11
|
['typeorm', new Set(['Entity', 'EntitySchema', 'MigrationInterface', 'ViewEntity'])],
|
|
10
12
|
]);
|
|
11
13
|
const APPLICATION_ROLES = new Set(['command', 'contracts', 'handler', 'query']);
|
|
12
|
-
const TRANSPORT_ROLES = new Set(['args', 'cli', 'consumer', 'controller', 'gateway', 'input', '
|
|
14
|
+
const TRANSPORT_ROLES = new Set(['args', 'cli', 'consumer', 'controller', 'gateway', 'input', 'processor', 'request', 'resolver', 'response', 'type']);
|
|
13
15
|
const APPLICATION_LAYER_ROLES = new Set([...APPLICATION_ROLES, 'mapper']);
|
|
14
16
|
const TRANSPORT_LAYER_ROLES = new Set([...TRANSPORT_ROLES, 'enum', 'filter', 'guard', 'interceptor', 'mapper']);
|
|
15
17
|
// Class-bearing roles of the CLOSED suffix list of BE-CONVENTION 1.15 (class name = PascalCase of file + role). A role that is
|
|
16
|
-
// not in that list (adapter, exception,
|
|
18
|
+
// not in that list (adapter, exception, provider, repository, strategy, use-case) is never an allowed role here.
|
|
17
19
|
const CLASS_ROLE_SUFFIX = new Map([
|
|
18
20
|
['args', 'Args'], ['cli', 'Cli'], ['client', 'Client'], ['command', 'Command'], ['consumer', 'Consumer'],
|
|
19
21
|
['controller', 'Controller'], ['entity', 'Entity'], ['error', 'Error'], ['filter', 'Filter'], ['gateway', 'Gateway'],
|
|
20
|
-
['guard', 'Guard'], ['handler', 'Handler'], ['input', 'Input'], ['interceptor', 'Interceptor'], ['
|
|
21
|
-
['mapper', 'Mapper'], ['module', 'Module'], ['module-definition', 'Module'], ['policy', 'Policy'], ['query', 'Query'],
|
|
22
|
+
['guard', 'Guard'], ['handler', 'Handler'], ['input', 'Input'], ['interceptor', 'Interceptor'], ['processor', 'Processor'], ['event', 'Event'], ['queue', 'Queue'],
|
|
23
|
+
['projection', 'Projection'], ['projection-entity', 'ProjectionEntity'], ['step', 'Step'], ['mapper', 'Mapper'], ['module', 'Module'], ['module-definition', 'Module'], ['policy', 'Policy'], ['query', 'Query'],
|
|
22
24
|
['request', 'Request'], ['resolver', 'Resolver'], ['response', 'Response'], ['service', 'Service'], ['type', 'Type'],
|
|
23
25
|
]);
|
|
24
26
|
// Transport object contracts (interfaces and object aliases) named by their file role: <Action>Input in *.input.ts,
|
|
@@ -41,162 +43,40 @@ function canonical(file) {
|
|
|
41
43
|
return path.resolve(file);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
function normalizedSymbolValue(ts, checker, value) {
|
|
45
|
-
let symbol = value ?? null;
|
|
46
|
-
const seen = new Set();
|
|
47
|
-
while (symbol && (symbol.flags & ts.SymbolFlags.Alias) && !seen.has(symbol)) {
|
|
48
|
-
seen.add(symbol);
|
|
49
|
-
const target = checker.getAliasedSymbol(symbol);
|
|
50
|
-
if (!target || target === symbol) break;
|
|
51
|
-
symbol = target;
|
|
52
|
-
}
|
|
53
|
-
return symbol;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function normalizedSymbol(ts, checker, node) {
|
|
57
|
-
return normalizedSymbolValue(ts, checker, checker?.getSymbolAtLocation(node) ?? null);
|
|
58
|
-
}
|
|
59
46
|
|
|
60
|
-
function selectedNode(ts, expression) {
|
|
61
|
-
expression = unwrapExpression(ts, expression);
|
|
62
|
-
if (ts.isIdentifier(expression)) return expression;
|
|
63
|
-
if (ts.isPropertyAccessExpression(expression)) return expression.name;
|
|
64
|
-
if (ts.isElementAccessExpression(expression) && ts.isStringLiteralLike(expression.argumentExpression)) return expression.argumentExpression;
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function valueSymbol(ts, checker, node, seen = new Set()) {
|
|
69
|
-
const selected = selectedNode(ts, node) ?? node;
|
|
70
|
-
const symbol = normalizedSymbol(ts, checker, selected);
|
|
71
|
-
if (!symbol || seen.has(symbol)) return symbol;
|
|
72
|
-
seen.add(symbol);
|
|
73
|
-
const declarations = symbol.getDeclarations?.() ?? [];
|
|
74
|
-
if (declarations.length === 1 && ts.isVariableDeclaration(declarations[0]) && declarations[0].initializer
|
|
75
|
-
&& (ts.getCombinedNodeFlags(declarations[0].parent) & ts.NodeFlags.Const)) {
|
|
76
|
-
return valueSymbol(ts, checker, declarations[0].initializer, seen);
|
|
77
|
-
}
|
|
78
|
-
return symbol;
|
|
79
|
-
}
|
|
80
47
|
|
|
81
48
|
function frameworkTargets(config, context, checker, localFiles) {
|
|
82
|
-
const bySymbol = new Map();
|
|
83
49
|
const reasons = [];
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
50
|
+
const bySymbol = new Map();
|
|
51
|
+
const files = programSourcesOf(context, checker, localFiles);
|
|
52
|
+
if (!files) return { bySymbol, reasons: ['a TypeScript program checker could not be associated with its source'] };
|
|
53
|
+
for (const sourceFile of files) {
|
|
88
54
|
for (const statement of sourceFile.statements) {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
if (!moduleSymbol) {
|
|
95
|
-
reasons.push(`${relativePath(config.root, sourceFile.fileName)} cannot resolve ${specifier}`);
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
const selected = FRAMEWORK_EXPORTS.get(specifier);
|
|
99
|
-
for (const exported of checker.getExportsOfModule(moduleSymbol)) if (selected.has(exported.getName())) {
|
|
100
|
-
const target = normalizedSymbolValue(context.ts, checker, exported);
|
|
101
|
-
if (target) bySymbol.set(target, exported.getName());
|
|
102
|
-
}
|
|
55
|
+
const bound = moduleExportsOf(context.ts, checker, statement);
|
|
56
|
+
if (!bound || !FRAMEWORK_EXPORTS.has(bound.specifier)) continue;
|
|
57
|
+
if (!bound.symbol) { reasons.push(`${relativePath(config.root, sourceFile.fileName)} cannot resolve ${bound.specifier}`); continue; }
|
|
58
|
+
const selected = FRAMEWORK_EXPORTS.get(bound.specifier);
|
|
59
|
+
for (const [name, target] of bound.exports) if (selected.has(name) && target) bySymbol.set(target, name);
|
|
103
60
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
&& node.arguments.length === 1 && context.ts.isStringLiteralLike(node.arguments[0]) && FRAMEWORK_EXPORTS.has(node.arguments[0].text)) {
|
|
107
|
-
reasons.push(`${relativePath(config.root, sourceFile.fileName)} uses a CommonJS ${node.arguments[0].text} binding whose source role cannot be proved`);
|
|
108
|
-
}
|
|
109
|
-
context.ts.forEachChild(node, visit);
|
|
110
|
-
};
|
|
111
|
-
visit(sourceFile);
|
|
61
|
+
commonJsRequireReasons(context.ts, checker, sourceFile, (specifier) => FRAMEWORK_EXPORTS.has(specifier),
|
|
62
|
+
reasons, relativePath(config.root, sourceFile.fileName), 'binding whose source role cannot be proved');
|
|
112
63
|
}
|
|
113
64
|
return { bySymbol, reasons };
|
|
114
65
|
}
|
|
115
66
|
|
|
116
|
-
function calledExpression(ts, decorator) {
|
|
117
|
-
return ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
67
|
function decoratorKind(ts, checker, decorator, targets) {
|
|
121
|
-
const selected = selectedNode(ts,
|
|
68
|
+
const selected = selectedNode(ts, decoratorCallee(ts, decorator));
|
|
122
69
|
return selected ? targets.get(valueSymbol(ts, checker, selected)) ?? null : null;
|
|
123
70
|
}
|
|
124
71
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
|
|
128
|
-
const declarations = symbol?.getDeclarations?.() ?? [];
|
|
129
|
-
if (declarations.length !== 1 || !ts.isVariableDeclaration(declarations[0]) || !declarations[0].initializer
|
|
130
|
-
|| (ts.getCombinedNodeFlags(declarations[0].parent) & ts.NodeFlags.Const)) return null;
|
|
131
|
-
return targets.get(valueSymbol(ts, checker, declarations[0].initializer)) ?? null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function returnedExpressions(ts, declaration) {
|
|
135
|
-
if ((ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) && !ts.isBlock(declaration.body)) return [declaration.body];
|
|
136
|
-
const body = ts.isFunctionDeclaration(declaration) || ts.isMethodDeclaration(declaration)
|
|
137
|
-
|| ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration) ? declaration.body : null;
|
|
138
|
-
if (!body || !ts.isBlock(body)) return [];
|
|
139
|
-
const returned = [];
|
|
140
|
-
const visit = node => {
|
|
141
|
-
if (node !== body && (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node))) return;
|
|
142
|
-
if (ts.isReturnStatement(node) && node.expression) returned.push(node.expression);
|
|
143
|
-
else ts.forEachChild(node, visit);
|
|
144
|
-
};
|
|
145
|
-
visit(body);
|
|
146
|
-
return returned;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function tracedFrameworkKinds(ts, checker, expression, targets, seen = new Set(), depth = 0) {
|
|
150
|
-
if (!expression) return new Set();
|
|
151
|
-
if (depth > 10) return new Set([UNPROVEN_FRAMEWORK]);
|
|
152
|
-
expression = unwrapExpression(ts, expression);
|
|
153
|
-
const selected = selectedNode(ts, expression);
|
|
154
|
-
const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
|
|
155
|
-
const direct = symbol ? targets.get(symbol) : null;
|
|
156
|
-
if (direct) return new Set([direct]);
|
|
157
|
-
if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
|
|
158
|
-
const kinds = new Set();
|
|
159
|
-
for (const returned of returnedExpressions(ts, expression)) {
|
|
160
|
-
for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, seen, depth + 1)) kinds.add(kind);
|
|
161
|
-
}
|
|
162
|
-
return kinds;
|
|
163
|
-
}
|
|
164
|
-
if (ts.isCallExpression(expression)) {
|
|
165
|
-
const kinds = tracedFrameworkKinds(ts, checker, expression.expression, targets, seen, depth + 1);
|
|
166
|
-
if (kinds.size) return kinds;
|
|
167
|
-
}
|
|
168
|
-
if (ts.isNewExpression(expression)) return tracedFrameworkKinds(ts, checker, expression.expression, targets, seen, depth + 1);
|
|
169
|
-
if (!symbol || seen.has(symbol)) return new Set();
|
|
170
|
-
const nextSeen = new Set(seen).add(symbol);
|
|
171
|
-
const kinds = new Set();
|
|
172
|
-
for (const declaration of symbol.getDeclarations?.() ?? []) {
|
|
173
|
-
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
174
|
-
for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, nextSeen, depth + 1)) kinds.add(kind);
|
|
175
|
-
}
|
|
176
|
-
for (const returned of returnedExpressions(ts, declaration)) {
|
|
177
|
-
for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, nextSeen, depth + 1)) kinds.add(kind);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return kinds;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function constructedDecoratorKind(ts, checker, decorator, targets) {
|
|
184
|
-
const kinds = tracedFrameworkKinds(ts, checker, calledExpression(ts, decorator), targets);
|
|
185
|
-
if (kinds.has(UNPROVEN_FRAMEWORK)) return 'unproven framework';
|
|
186
|
-
return kinds.size === 1 ? [...kinds][0] : kinds.size ? 'multiple framework' : null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function decorators(ts, node) {
|
|
190
|
-
return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : node.decorators ?? [];
|
|
191
|
-
}
|
|
72
|
+
/* This module's trace descends `new X()` callees too; the framework map keys symbols directly. */
|
|
73
|
+
const TRACE_NEW = { newExpression: true };
|
|
192
74
|
|
|
193
75
|
function hasModifier(ts, node, kind) {
|
|
194
76
|
return (ts.canHaveModifiers(node) ? ts.getModifiers(node) ?? [] : node.modifiers ?? []).some(modifier => modifier.kind === kind);
|
|
195
77
|
}
|
|
196
78
|
|
|
197
|
-
|
|
198
|
-
return { ruleId, path: relativePath(config.root, sourceFile.fileName), ...sourceLocation(sourceFile, node), message, ...extra };
|
|
199
|
-
}
|
|
79
|
+
|
|
200
80
|
|
|
201
81
|
function sourceRole(ts, sourceFile) {
|
|
202
82
|
const file = path.basename(sourceFile.fileName).replace(SOURCE_EXTENSION, '');
|
|
@@ -304,7 +184,7 @@ function companionInterfaceAllowed(ts, sourceFile, name) {
|
|
|
304
184
|
if (!/^I[A-Z]/.test(name)) return false;
|
|
305
185
|
const companion = name.slice(1);
|
|
306
186
|
return sourceFile.statements.some(statement => ts.isClassDeclaration(statement) && statement.name?.text === companion
|
|
307
|
-
&&
|
|
187
|
+
&& nodeDecorators(ts, statement).length > 0);
|
|
308
188
|
}
|
|
309
189
|
|
|
310
190
|
function camelCase(value) {
|
|
@@ -485,7 +365,7 @@ export function checkBackendSourceShape(config, context) {
|
|
|
485
365
|
|
|
486
366
|
const classDeclarations = sourceFile.statements.filter(statement => ts.isClassDeclaration(statement));
|
|
487
367
|
for (const declaration of classDeclarations) {
|
|
488
|
-
const declarationDecorators =
|
|
368
|
+
const declarationDecorators = nodeDecorators(ts, declaration);
|
|
489
369
|
const kinds = declarationDecorators.map(decorator => decoratorKind(ts, checker, decorator, framework.bySymbol)).filter(Boolean);
|
|
490
370
|
const graphQlDto = kinds.some(kind => kind === 'ArgsType' || kind === 'InputType' || kind === 'ObjectType');
|
|
491
371
|
if (graphQlDto && (!featureRoot || !inTransport || directories[transportIndex + 1] !== 'graphql')) {
|
|
@@ -551,11 +431,11 @@ export function checkBackendSourceShape(config, context) {
|
|
|
551
431
|
}
|
|
552
432
|
|
|
553
433
|
const visit = node => {
|
|
554
|
-
for (const decorator of
|
|
434
|
+
for (const decorator of nodeDecorators(ts, node)) {
|
|
555
435
|
const kind = decoratorKind(ts, checker, decorator, framework.bySymbol);
|
|
556
436
|
if (!kind) {
|
|
557
437
|
const dynamic = mutableDecoratorKind(ts, checker, decorator, framework.bySymbol)
|
|
558
|
-
?? constructedDecoratorKind(ts, checker, decorator, framework.bySymbol);
|
|
438
|
+
?? constructedDecoratorKind(ts, checker, decorator, framework.bySymbol, TRACE_NEW);
|
|
559
439
|
if (dynamic) {
|
|
560
440
|
const detail = `${relative} uses a mutable or constructed ${dynamic} decorator identity`;
|
|
561
441
|
if (['Entity', 'ViewEntity', 'ArgsType', 'InputType', 'ObjectType', 'multiple framework', 'unproven framework'].includes(dynamic)) layoutReasons.push(detail);
|
|
@@ -574,7 +454,7 @@ export function checkBackendSourceShape(config, context) {
|
|
|
574
454
|
}
|
|
575
455
|
const exportedFactoryCall = ts.isCallExpression(node) && ts.isVariableDeclaration(node.parent) && publicDeclarations.has(node.parent);
|
|
576
456
|
if ((ts.isNewExpression(node) || exportedFactoryCall) && featureRoot) {
|
|
577
|
-
const constructed = tracedFrameworkKinds(ts, checker, node, framework.bySymbol);
|
|
457
|
+
const constructed = tracedFrameworkKinds(ts, checker, node, framework.bySymbol, TRACE_NEW);
|
|
578
458
|
if (constructed.has('EntitySchema')) violations.push(violation(config, sourceFile, node.expression, SOURCE_LAYOUT_RULE_ID,
|
|
579
459
|
'TypeORM EntitySchema cannot be owned by a feature; place schema under its declared persistence module.'));
|
|
580
460
|
if (constructed.has(UNPROVEN_FRAMEWORK)) layoutReasons.push(`${relative} has a constructed provider identity beyond the bounded static trace`);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { analyzeSql, HOLE } from './sql-tokens.mjs';
|
|
3
3
|
import { machineKit } from './machine-ast.mjs';
|
|
4
|
+
import { contextModelOf } from './context-map.mjs';
|
|
5
|
+
import { supabaseTablesOf } from './supabase-tables.mjs';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* R86 `sql-owner` (BE_SQL_TABLE_OWNER). Every `sql` tagged template (the tag declared in platform/database) of a
|
|
7
9
|
* `<name>.sql.ts` file in a capability's `persistence/` is read with the tokenizer of sql-tokens.mjs and judged against the
|
|
8
|
-
*
|
|
9
|
-
* - it writes (INSERT INTO, UPDATE, DELETE FROM, MERGE INTO, TRUNCATE) only tables the file's own capability declares
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - every table it names is declared by some entity
|
|
10
|
+
* schema authority of the whole program:
|
|
11
|
+
* - it writes (INSERT INTO, UPDATE, DELETE FROM, MERGE INTO, TRUNCATE) only tables the file's own capability declares;
|
|
12
|
+
* - it reads (FROM, JOIN, USING) only tables of its own CONTEXT (the connection its capability's arrays are registered on): its own
|
|
13
|
+
* capability's, or those of a same-context capability its owner may import (the tier matrix, through `resolver.importAllowed`
|
|
14
|
+
* to the table owner's public entry); a table of another context is refused (cross-context data comes only by events);
|
|
15
|
+
* - every table it names is declared by some entity under TypeORM authority, or by the generated
|
|
16
|
+
* `Database['public']['Tables']` contract under Supabase authority;
|
|
14
17
|
* - a SELECT carries LIMIT, or constrains the primary key or a unique key of its first table with `=`, or selects only
|
|
15
18
|
* aggregates without GROUP BY.
|
|
16
19
|
* Table positions that hold a `${...}` substitution (SqlIdent) are dynamic; they are counted in the coverage and judged by
|
|
@@ -28,7 +31,7 @@ const literalStrings = (ts, node) => {
|
|
|
28
31
|
return null;
|
|
29
32
|
};
|
|
30
33
|
|
|
31
|
-
function entitiesOf(kit, graph) {
|
|
34
|
+
export function entitiesOf(kit, graph) {
|
|
32
35
|
const { ts } = kit;
|
|
33
36
|
const tables = new Map();
|
|
34
37
|
let unreadable = 0;
|
|
@@ -90,11 +93,29 @@ function entitiesOf(kit, graph) {
|
|
|
90
93
|
return { tables, unreadable };
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
const capabilityTable = root => path.posix.basename(root).replaceAll('-', '_').toLowerCase();
|
|
97
|
+
|
|
98
|
+
/** Supabase declarations, with an owner when the generated table name matches a capability's persistence folder. */
|
|
99
|
+
function supabaseDeclarations(graph, appRoot, ts) {
|
|
100
|
+
const generated = supabaseTablesOf(appRoot, ts);
|
|
101
|
+
const tables = new Map([...generated].map(table => [table, { table, owner: null, rel: null, uniqueSets: [] }]));
|
|
102
|
+
for (const file of graph.files.values()) {
|
|
103
|
+
if (file.slot !== 'be.persistence' || !file.owner) continue;
|
|
104
|
+
const key = capabilityTable(file.owner.root);
|
|
105
|
+
const declaration = tables.get(key);
|
|
106
|
+
if (declaration && declaration.owner === null) tables.set(key, { ...declaration, owner: file.owner.root, rel: file.rel });
|
|
107
|
+
}
|
|
108
|
+
return tables;
|
|
109
|
+
}
|
|
110
|
+
|
|
93
111
|
export function checkSqlOwner(input) {
|
|
94
|
-
const { graph } = input;
|
|
112
|
+
const { config, graph } = input;
|
|
95
113
|
const kit = machineKit(input);
|
|
96
114
|
const { ts, resolver } = kit;
|
|
97
|
-
const
|
|
115
|
+
const supabase = resolver.ruleParams().schemaAuthority === 'supabase';
|
|
116
|
+
const entityDeclarations = supabase ? { tables: new Map(), unreadable: 0 } : entitiesOf(kit, graph);
|
|
117
|
+
const tables = supabase ? supabaseDeclarations(graph, config.packageRoot, ts) : entityDeclarations.tables;
|
|
118
|
+
const model = contextModelOf(kit, graph);
|
|
98
119
|
const violations = [];
|
|
99
120
|
let templates = 0;
|
|
100
121
|
let dynamic = 0;
|
|
@@ -119,17 +140,36 @@ export function checkSqlOwner(input) {
|
|
|
119
140
|
};
|
|
120
141
|
const result = analyzeSql(text, { uniqueSetsOf: table => tables.get(table.toLowerCase())?.uniqueSets ?? null });
|
|
121
142
|
dynamic += result.dynamic;
|
|
143
|
+
const ownTable = capabilityTable(file.owner.root);
|
|
122
144
|
for (const write of result.writes) {
|
|
123
|
-
const
|
|
124
|
-
if (!
|
|
125
|
-
|
|
145
|
+
const declaration = tables.get(write.table.toLowerCase());
|
|
146
|
+
if (!declaration) {
|
|
147
|
+
report(supabase
|
|
148
|
+
? `SQL writes table ${write.table}, which Database['public']['Tables'] does not declare; regenerate supabase/types/database.types.ts from the migrations.`
|
|
149
|
+
: `SQL writes table ${write.table}, which no @Entity declares; declare the entity in the owning capability's persistence/entities.`, { table: write.table });
|
|
150
|
+
} else if (supabase && write.table.toLowerCase() !== ownTable) {
|
|
151
|
+
report(`SQL in ${file.owner.root} writes table ${write.table}${declaration.owner ? `, owned by ${declaration.owner}` : ''}; under Supabase schema authority a capability writes only its own table (${ownTable}), named for that capability.`, { table: write.table, ...(declaration.owner ? { tableOwner: declaration.owner } : {}) });
|
|
152
|
+
} else if (!supabase && declaration.owner !== file.owner.root) {
|
|
153
|
+
report(`SQL in ${file.owner.root} writes table ${write.table}, owned by ${declaration.owner}; a capability writes only the tables of its own entities, call the owner's public API instead.`, { table: write.table, tableOwner: declaration.owner });
|
|
154
|
+
}
|
|
126
155
|
}
|
|
127
156
|
for (const read of result.reads) {
|
|
128
|
-
const
|
|
129
|
-
if (!
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
const declaration = tables.get(read.table.toLowerCase());
|
|
158
|
+
if (!declaration) {
|
|
159
|
+
report(supabase
|
|
160
|
+
? `SQL reads table ${read.table}, which Database['public']['Tables'] does not declare; regenerate supabase/types/database.types.ts from the migrations.`
|
|
161
|
+
: `SQL reads table ${read.table}, which no @Entity declares; declare the entity in the owning capability's persistence/entities.`, { table: read.table });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!declaration.owner || declaration.owner === file.owner.root) continue;
|
|
165
|
+
const ownContext = model.contextOfCapability(file.owner.root);
|
|
166
|
+
const tableContext = model.contextOfCapability(declaration.owner);
|
|
167
|
+
if (ownContext && tableContext && ownContext !== tableContext) {
|
|
168
|
+
report(`SQL in ${file.owner.root} (context ${ownContext}) reads table ${read.table}, owned by ${declaration.owner} (context ${tableContext}); a context reads and writes only its own context's tables, so a cross-context JOIN or subquery is refused. Keep a local copy fed by the other context's events (a reactor), or read a projection.`, { table: read.table, tableOwner: declaration.owner, context: ownContext, tableContext });
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const verdict = resolver.importAllowed(file.rel, `${declaration.owner}/index.ts`);
|
|
172
|
+
if (!verdict.allowed) report(`SQL in ${file.owner.root} reads table ${read.table}, owned by ${declaration.owner}, which ${file.owner.root} may not import (${verdict.reason}); read it through the owner's public API.`, { table: read.table, tableOwner: declaration.owner, reason: verdict.reason });
|
|
133
173
|
}
|
|
134
174
|
for (const select of result.selects) {
|
|
135
175
|
if (select.bounded || (select.table && !tables.has(select.table.toLowerCase()))) continue;
|
|
@@ -138,5 +178,6 @@ export function checkSqlOwner(input) {
|
|
|
138
178
|
return true;
|
|
139
179
|
});
|
|
140
180
|
}
|
|
141
|
-
return { violations, coverage: { status: 'checked', entities: tables.size, unreadableEntities: unreadable,
|
|
181
|
+
return { violations, coverage: { status: 'checked', entities: entityDeclarations.tables.size, unreadableEntities: entityDeclarations.unreadable,
|
|
182
|
+
...(supabase ? { supabaseTables: tables.size } : {}), templates, dynamicIdentifiers: dynamic, foreignTags } };
|
|
142
183
|
}
|