@starci/hfs 4.2.17 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +7 -0
- package/db/migrate.mjs +361 -0
- package/db/post-cutover.mjs +136 -0
- package/emit/db-types.mjs +10 -1
- package/lint/run.mjs +1 -1
- package/package.json +4 -1
- package/release/gate.mjs +298 -0
- package/release/index.mjs +103 -0
- package/release/push.mjs +110 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
- package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
- package/runtime/knowledge/hfs/rules.yaml +3437 -160
- package/runtime/knowledge/hfs/slots.yaml +430 -92
- package/runtime/knowledge/patterns/be/api.yaml +92 -2
- package/runtime/knowledge/patterns/be/cli.yaml +37 -9
- package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
- package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
- package/runtime/knowledge/patterns/be/projections.yaml +4 -2
- package/runtime/knowledge/patterns/be/queues.yaml +11 -3
- package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
- package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
- package/runtime/knowledge/patterns/be/saga.yaml +20 -3
- package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
- package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
- package/runtime/knowledge/sonar-gate.yaml +1 -1
- package/runtime/modules/kernel/failure-codes.yaml +3128 -47
- package/runtime/modules/models/runtimes.yaml +1 -1
- package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
- package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
- package/runtime/packages/cli/src/validate-args.mjs +213 -0
- package/runtime/scripts/api/git/add.mjs +5 -0
- package/runtime/scripts/api/git/cat-file.mjs +5 -0
- package/runtime/scripts/api/git/init.mjs +7 -0
- package/runtime/scripts/api/git/lib.mjs +3 -0
- package/runtime/scripts/api/git/tag.mjs +5 -0
- package/runtime/scripts/api/node/lib.mjs +20 -0
- package/runtime/scripts/api/node/run-node.mjs +6 -0
- package/runtime/scripts/api/sops/lib.mjs +41 -0
- package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
- package/runtime/scripts/api/sops/seal.mjs +1 -1
- package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
- package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
- package/runtime/scripts/api/supabase/database-query.mjs +5 -0
- package/runtime/scripts/api/supabase/lib.mjs +44 -0
- package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
- package/runtime/scripts/hfs/app-wiring.mjs +201 -0
- package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
- package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
- package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
- package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
- package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
- package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
- package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
- package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
- package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
- package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
- package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
- package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
- package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
- package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
- package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
- package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
- package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
- package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
- package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
- package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
- package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
- package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
- package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
- package/runtime/scripts/hfs/check-results.mjs +28 -0
- package/runtime/scripts/hfs/check.mjs +63 -72
- package/runtime/scripts/hfs/coverage-from.mjs +70 -0
- package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
- package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
- package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
- package/runtime/scripts/hfs/doc-commands.mjs +367 -0
- package/runtime/scripts/hfs/edition-slots.mjs +2 -2
- package/runtime/scripts/hfs/example-secrets.mjs +158 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
- package/runtime/scripts/hfs/path-findings.mjs +32 -2
- package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
- package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
- package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
- package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
- package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
- package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
- package/runtime/scripts/hfs/rules/assets.mjs +79 -0
- package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
- package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
- package/runtime/scripts/hfs/rules/contract.mjs +66 -5
- package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
- package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
- package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
- package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
- package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
- package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
- package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
- package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
- package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
- package/runtime/scripts/hfs/rules/database.mjs +11 -2
- package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
- package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
- package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
- package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
- package/runtime/scripts/hfs/rules/docker.mjs +24 -7
- package/runtime/scripts/hfs/rules/edition.mjs +11 -10
- package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
- package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
- package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
- package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
- package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
- package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
- package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
- package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
- package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
- package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
- package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
- package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
- package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
- package/runtime/scripts/hfs/rules/saga.mjs +8 -16
- package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
- package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
- package/runtime/scripts/hfs/rules/services.mjs +4 -4
- package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
- package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
- package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
- package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
- package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
- package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
- package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
- package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
- package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
- package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
- package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
- package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
- package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
- package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
- package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
- package/runtime/scripts/hfs/sealed-members.mjs +101 -0
- package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
- package/runtime/scripts/hfs/secret-document.mjs +97 -0
- package/runtime/scripts/hfs/secret-edit.mjs +170 -0
- package/runtime/scripts/hfs/secret-errors.mjs +20 -0
- package/runtime/scripts/hfs/secret-files.mjs +124 -0
- package/runtime/scripts/hfs/secret-init.mjs +300 -0
- package/runtime/scripts/hfs/secret-output.mjs +100 -0
- package/runtime/scripts/hfs/secret-sops.mjs +130 -0
- package/runtime/scripts/hfs/secret-store.mjs +163 -0
- package/runtime/scripts/hfs/secret.mjs +162 -193
- package/runtime/scripts/hfs/slot-imports.mjs +18 -3
- package/runtime/scripts/hfs/slot-required.mjs +13 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
- package/runtime/scripts/hfs/slots.mjs +1 -0
- package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
- package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
- package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
- package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
- package/runtime/scripts/hfs/tree-findings.mjs +22 -0
- package/runtime/scripts/lib/command-tokens.mjs +126 -0
- package/runtime/scripts/lib/compose-values.mjs +11 -0
- package/runtime/scripts/lib/deployment-findings.mjs +7 -0
- package/runtime/scripts/lib/json-clone.mjs +61 -0
- package/runtime/scripts/lib/list.mjs +4 -0
- package/runtime/scripts/lib/roots.mjs +59 -0
- package/runtime/scripts/lib/runtime-example.mjs +13 -0
- package/runtime/scripts/lib/secret-patterns.mjs +60 -5
- package/runtime/scripts/lib/sql-ast.mjs +32 -0
- package/runtime/scripts/lib/stack-services.mjs +1 -1
- package/runtime/scripts/lib/swarm-stack.mjs +126 -0
- package/runtime/scripts/lib/trim-end.mjs +8 -0
- package/runtime/scripts/lib/ts-analysis.mjs +23 -0
- package/runtime/scripts/lib/ts-ast.mjs +27 -2
- package/runtime/scripts/lib/workflow-events.mjs +8 -0
- package/scaffold/add-app.mjs +77 -59
- package/scaffold/add-cli-lite.mjs +24 -17
- package/scaffold/add-compose.mjs +79 -0
- package/scaffold/add-database-lite.mjs +14 -0
- package/scaffold/add-fe-overlay.mjs +122 -0
- package/scaffold/add-fe-page.mjs +269 -0
- package/scaffold/add-inbox-lite.mjs +87 -124
- package/scaffold/add-plan.mjs +183 -0
- package/scaffold/add-table.mjs +132 -70
- package/scaffold/add.mjs +186 -204
- package/scaffold/app.mjs +128 -43
- package/scaffold/edition-gate.mjs +3 -3
- package/scaffold/image.mjs +2 -2
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +2 -2
- package/src/main.mjs +238 -119
- package/stack/age-install.mjs +37 -0
- package/stack/backup.mjs +100 -0
- package/stack/build-args.mjs +42 -0
- package/stack/build.mjs +173 -0
- package/stack/cli.mjs +130 -0
- package/stack/config.mjs +57 -0
- package/stack/context.mjs +57 -0
- package/stack/converge.mjs +107 -0
- package/stack/deploy.mjs +301 -0
- package/stack/digest.mjs +16 -0
- package/stack/docker-transport.mjs +53 -0
- package/stack/edge-alias.mjs +118 -0
- package/stack/errors.mjs +21 -0
- package/stack/image-scan.mjs +152 -0
- package/stack/ingress-create.mjs +114 -0
- package/stack/ingress-publish.mjs +273 -0
- package/stack/ingress.mjs +706 -0
- package/stack/jobs.mjs +51 -0
- package/stack/live-restore.mjs +177 -0
- package/stack/prepare.mjs +266 -0
- package/stack/private-file.mjs +22 -0
- package/stack/provision.mjs +90 -0
- package/stack/prune.mjs +46 -0
- package/stack/release-store.mjs +87 -0
- package/stack/remote.mjs +205 -0
- package/stack/render.mjs +149 -0
- package/stack/retire.mjs +47 -0
- package/stack/sealed.mjs +149 -0
- package/stack/task.mjs +42 -0
- package/stack/verify.mjs +99 -0
- package/sync/ci-commands.mjs +13 -0
- package/sync/index.mjs +127 -64
- package/sync/stack-images.mjs +127 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
- package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
- package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
- package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
- package/templates/app/codegen-graphql.mjs.partial +13 -2
- package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
- package/templates/app/codegen-prelude.mjs.partial +8 -0
- package/templates/app/gitignore +4 -0
- package/templates/app/gitignore-lite +4 -0
- package/templates/app/hooks/husky/pre-commit +7 -12
- package/templates/app/package-scripts/package.json +1 -0
- package/templates/app/package-scripts/supabase.json +7 -0
- package/templates/app/package-scripts-lite/package.json +1 -7
- package/templates/app/skeleton/.editorconfig +1 -0
- package/templates/app/skeleton/.gitattributes +7 -1
- package/templates/app/skeleton/.github/dependabot.yml +17 -0
- package/templates/app/skeleton/.nvmrc +1 -1
- package/templates/app/skeleton/.sops.yaml +1 -1
- package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
- package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
- package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
- package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
- package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
- package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
- package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
- package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
- package/templates/app/skeleton/README.md +6 -6
- package/templates/app/skeleton/scripts/codegen.mjs +1 -2
- package/templates/app/skeleton-lite/.editorconfig +16 -0
- package/templates/app/skeleton-lite/.gitattributes +8 -0
- package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
- package/templates/app/skeleton-lite/.nvmrc +1 -0
- package/templates/app/skeleton-lite/.sops.yaml +3 -2
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
- package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
- package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
- package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
- package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
- package/templates/app/skeleton-lite/README.md +8 -4
- package/templates/app/skeleton-lite/supabase/config.toml +8 -1
- package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
- package/templates/app/starciwork.gitignore-lite +6 -0
- package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
- package/templates/be/inbox/database-client.ts.tpl +14 -28
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
- package/templates/be/inbox/event-index.ts.tpl +1 -0
- package/templates/be/inbox/events.json.tpl +10 -0
- package/templates/be/inbox/migration.sql.tpl +1 -0
- package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
- package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
- package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
- package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
- package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
- package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
- package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
- package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
- package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
- package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
- package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
- package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
- package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
- package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
- package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
- package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
- package/templates/be/inbox/platform/migration.sql.tpl +48 -0
- package/templates/be/inbox/service.spec.ts.tpl +49 -20
- package/templates/be/inbox/service.ts.tpl +27 -16
- package/templates/be/inbox/sql.ts.tpl +2 -2
- package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
- package/templates/be/inbox/webhook-door.ts.tpl +4 -3
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
- package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
- package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
- package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
- package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
- package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
- package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
- package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
- package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
- package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
- package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
- package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
- package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
- package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
- package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
- package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
- package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
- package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
- package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
- package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
- package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
- package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
- package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
- package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
- package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
- package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
- package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
- package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
- package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
- package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
- package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
- package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
- package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
- package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
- package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
- package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
- package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
- package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
- package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
- package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
- package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
- package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
- package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
- package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
- package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
- package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
- package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
- package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
- package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
- package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
- package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
- package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
- package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
- package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
- package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
- package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
- package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
- package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
- package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
- package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
- package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
- package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
- package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
- package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
- package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
- package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
- package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
- package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
- package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
- package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
- package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
- package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
- package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
- package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
- package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
- package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
- package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
- package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
- package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
- package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
- package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
- package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
- package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
- package/templates/be/table/api/feature.module.ts.tpl +2 -1
- package/templates/be/table/api/feature.options.ts.tpl +2 -0
- package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
- package/templates/be/table/api/graphql.module.ts.tpl +3 -2
- package/templates/be/table/api/graphql.options.ts.tpl +2 -0
- package/templates/be/table/migration.sql.tpl +1 -0
- package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
- package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
- package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
- package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
- package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
- package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
- package/templates/fe/auth/supabase-client.ts.tpl +34 -0
- package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
- package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
- package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
- package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
- package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
- package/upgrade/check.mjs +3 -1
- package/upgrade/file-state.mjs +68 -0
- package/upgrade/index.mjs +58 -94
- package/runtime/scripts/api/git/log.mjs +0 -5
- package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createSlotResolver, loadSlotManifest, readRepoDeclaration, ruleParams } from '../runtime/scripts/hfs/slots.mjs';
|
|
4
|
+
import { parseYaml } from '../runtime/engine/yaml.mjs';
|
|
5
|
+
import { TEMPLATES_DIR } from '../sync/index.mjs';
|
|
6
|
+
import { ScaffoldError, pascalOf } from './service.mjs';
|
|
7
|
+
import { refuseFullAdapterInLite, refuseInEdition } from './edition-gate.mjs';
|
|
8
|
+
import { selectLiteWebhookEntries } from './add-inbox-lite.mjs';
|
|
9
|
+
import { ensureLiteCli } from './add-cli-lite.mjs';
|
|
10
|
+
|
|
11
|
+
const KEBAB = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
12
|
+
const VARIABLE = /<([A-Za-z][A-Za-z0-9]*)>/g;
|
|
13
|
+
const PLACEHOLDER = /@@([A-Za-z][A-Za-z0-9]*)@@/g;
|
|
14
|
+
const PLATFORM_PREFIX = 'src/modules/platform/';
|
|
15
|
+
const TOPICS_DIR = path.join(import.meta.dirname, '..', 'runtime', 'knowledge', 'patterns', 'be');
|
|
16
|
+
const PATTERN_TEMPLATES = path.join(TEMPLATES_DIR, 'be', 'patterns');
|
|
17
|
+
const camelOf = (name) => { const pascal = pascalOf(name); return pascal[0].toLowerCase() + pascal.slice(1); };
|
|
18
|
+
|
|
19
|
+
/** The noun's app declaration and the ruleParams entry that supplies its topic and options. */
|
|
20
|
+
export function readAddContext(input) {
|
|
21
|
+
const manifest = loadSlotManifest();
|
|
22
|
+
const params = ruleParams(manifest, 'be');
|
|
23
|
+
const spec = params.addKinds[input.noun];
|
|
24
|
+
if (!spec) throw new ScaffoldError('HFS_ADD_NOUN_UNKNOWN', `starci app add knows ${Object.keys(params.addKinds).join(', ')}; ${input.noun} is none of them`);
|
|
25
|
+
const declarationFile = path.join(input.repoRoot, 'hfs.json');
|
|
26
|
+
if (!fs.existsSync(declarationFile)) throw new ScaffoldError('HFS_ADD_NOT_AN_APP', 'starci app add runs at the app root (the folder of hfs.json)');
|
|
27
|
+
const repo = readRepoDeclaration(manifest, input.repoRoot);
|
|
28
|
+
if (!repo.sides?.be) throw new ScaffoldError('HFS_ADD_NO_BACK_END', 'this app has no back-end side');
|
|
29
|
+
return { ...input, manifest, params, spec, declarationFile, repo };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Lite's built-in migrate and seed groups come from its one CLI bootstrap, without a second instance tree. */
|
|
33
|
+
export function addBuiltinLiteCli({ noun, name, repo, repoRoot, manifest }) {
|
|
34
|
+
if (noun !== 'cli' || repo.edition !== 'lite' || !['migrate', 'seed'].includes(name)) return null;
|
|
35
|
+
if (repo.sides.be.apps.some(app => app.kind === 'cli')) throw new ScaffoldError('HFS_ADD_EXISTS', `the lite cli already carries its built-in ${name} group`);
|
|
36
|
+
return { created: ensureLiteCli({ root: repoRoot, manifest, repo }), registered: { patterns: [], kinds: ['cli'] } };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const readTopic = (topic) => ({ id: topic, doc: parseYaml(fs.readFileSync(path.join(TOPICS_DIR, `${topic}.yaml`), 'utf8')) });
|
|
40
|
+
|
|
41
|
+
function readTree(topic) {
|
|
42
|
+
const file = path.join(TOPICS_DIR, `${topic}.yaml`);
|
|
43
|
+
if (!fs.existsSync(file)) throw new ScaffoldError('HFS_ADD_TOPIC_MISSING', `the pattern topic ${topic} (knowledge/patterns/be/${topic}.yaml) is not carried by this package`);
|
|
44
|
+
const doc = parseYaml(fs.readFileSync(file, 'utf8'));
|
|
45
|
+
if (!Array.isArray(doc?.files)) throw new ScaffoldError('HFS_ADD_TREE_MISSING', `knowledge/patterns/be/${topic}.yaml has no files tree`);
|
|
46
|
+
return doc.files;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const selectLiteApiEntries = entries => entries.map(entry => {
|
|
50
|
+
if (entry.path === 'src/features/api/<feature>/application/<action>.command.ts') return { ...entry, template: 'api/application.command.principal.ts.tpl' };
|
|
51
|
+
if (entry.path === 'src/features/api/<feature>/application/<action>.handler.ts') return { ...entry, template: 'api/application.handler.principal.ts.tpl' };
|
|
52
|
+
return entry;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/** Extra lite api templates stay in the same template set as the pattern topics. */
|
|
56
|
+
export const liteApiTemplates = () => selectLiteApiEntries(readTree('api')).map(entry => entry.template).filter(Boolean);
|
|
57
|
+
|
|
58
|
+
function topicsOfPatterns(patterns) {
|
|
59
|
+
const topics = new Set();
|
|
60
|
+
for (const file of fs.readdirSync(TOPICS_DIR).filter((entry) => entry.endsWith('.yaml'))) {
|
|
61
|
+
const doc = parseYaml(fs.readFileSync(path.join(TOPICS_DIR, file), 'utf8'));
|
|
62
|
+
if (patterns.includes(doc?.pattern)) topics.add(file.slice(0, -'.yaml'.length));
|
|
63
|
+
}
|
|
64
|
+
return topics;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function requiredOption(need, value, noun) {
|
|
68
|
+
if (typeof value !== 'string' || !value) throw new ScaffoldError('HFS_ADD_OPTION_MISSING', `starci app add ${noun} needs --${need}`);
|
|
69
|
+
if (need === 'service') {
|
|
70
|
+
const [service, serviceModule] = value.split('=');
|
|
71
|
+
if (!service || !serviceModule) throw new ScaffoldError('HFS_ADD_OPTION_INVALID', '--service takes <ServiceClass>=<module specifier>');
|
|
72
|
+
return { service, serviceModule };
|
|
73
|
+
}
|
|
74
|
+
if (!KEBAB.test(value)) throw new ScaffoldError('HFS_ADD_OPTION_INVALID', `--${need} ${value} must be kebab-case`);
|
|
75
|
+
return { [need]: value };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function variablesOf({ spec, noun, name, options }) {
|
|
79
|
+
if (!KEBAB.test(name)) throw new ScaffoldError('HFS_ADD_NAME_INVALID', `${noun} name ${name} must be kebab-case (letters, digits and single dashes)`);
|
|
80
|
+
const values = { [spec.variable]: name };
|
|
81
|
+
for (const need of spec.needs ?? []) Object.assign(values, requiredOption(need, options[need], noun));
|
|
82
|
+
for (const [key, rule] of Object.entries(spec.defaults ?? {})) {
|
|
83
|
+
if (values[key] === undefined) values[key] = rule === '@name' ? name : String(rule);
|
|
84
|
+
}
|
|
85
|
+
return values;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function formsOf(values) {
|
|
89
|
+
const forms = {};
|
|
90
|
+
for (const [name, value] of Object.entries(values)) {
|
|
91
|
+
const kebab = KEBAB.test(value);
|
|
92
|
+
forms[name] = value;
|
|
93
|
+
forms[name[0].toUpperCase() + name.slice(1)] = kebab ? pascalOf(value) : value[0].toUpperCase() + value.slice(1);
|
|
94
|
+
forms[`${name}Camel`] = kebab ? camelOf(value) : value[0].toLowerCase() + value.slice(1);
|
|
95
|
+
forms[`${name}Upper`] = kebab ? value.replaceAll('-', '_').toUpperCase() : value;
|
|
96
|
+
forms[`${name}Snake`] = kebab ? value.replaceAll('-', '_') : value;
|
|
97
|
+
}
|
|
98
|
+
return forms;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const fillPath = (text, values) => {
|
|
102
|
+
let missing = false;
|
|
103
|
+
const filled = text.replace(VARIABLE, (whole, name) => (values[name] === undefined ? ((missing = true), whole) : values[name]));
|
|
104
|
+
return missing ? null : filled;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const renderBody = (text, forms, template) => text.replace(PLACEHOLDER, (whole, name) => {
|
|
108
|
+
if (forms[name] === undefined) throw new ScaffoldError('HFS_ADD_PLACEHOLDER_UNKNOWN', `template ${template} uses ${whole}, which no variable of this noun provides`);
|
|
109
|
+
return forms[name];
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
/** Wiring may leave a placeholder unchanged; template bodies use the strict rendering refusal above. */
|
|
113
|
+
export const fillKnownForms = (text, forms) => text.replace(PLACEHOLDER, (whole, name) => forms[name] ?? whole);
|
|
114
|
+
|
|
115
|
+
function treesOf({ topics, repo, noun, spec }) {
|
|
116
|
+
const liteApi = noun === 'api' && repo.edition === 'lite';
|
|
117
|
+
return new Map([...topics].map((topic) => {
|
|
118
|
+
const entries = repo.edition === 'lite' ? selectLiteWebhookEntries(readTree(topic)) : readTree(topic);
|
|
119
|
+
return [topic, liteApi && topic === spec.topic ? selectLiteApiEntries(entries) : entries];
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function collectSlots(entries, platform, slots) {
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
if (entry.path.startsWith(PLATFORM_PREFIX) === platform && entry.optional !== true && entry.slot) slots.add(entry.slot);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Optional entries gate nothing; the generated instance and platform slots decide the edition. */
|
|
130
|
+
function validateWrittenSlots({ specs, topics, trees, manifest, repo, noun }) {
|
|
131
|
+
const slots = new Set();
|
|
132
|
+
for (const [, spec] of specs) collectSlots(trees.get(spec.topic), false, slots);
|
|
133
|
+
for (const topic of topics) collectSlots(trees.get(topic), true, slots);
|
|
134
|
+
refuseInEdition({ resolver: createSlotResolver(manifest, repo), slotIds: [...slots], command: `add ${noun}` });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function planEntry(entry, { topic, instance, shared, beRoot, withStamp, forms }) {
|
|
138
|
+
if (entry.optional === true) return null;
|
|
139
|
+
const target = fillPath(entry.path, withStamp);
|
|
140
|
+
if (target === null) return null;
|
|
141
|
+
const absolute = path.join(beRoot, ...target.split('/'));
|
|
142
|
+
// A platform capability is whole or absent: its index prevents regeneration of all its files.
|
|
143
|
+
if (!instance && fs.existsSync(path.join(beRoot, ...target.split('/').slice(0, 4), 'index.ts'))) return null;
|
|
144
|
+
if ((!instance || shared) && fs.existsSync(absolute)) return null;
|
|
145
|
+
if (!entry.template) throw new ScaffoldError('HFS_ADD_TEMPLATE_MISSING', `${topic}.yaml lists ${entry.path} without a template, so it cannot be generated`);
|
|
146
|
+
const templateFile = path.join(PATTERN_TEMPLATES, ...entry.template.split('/'));
|
|
147
|
+
if (!fs.existsSync(templateFile)) throw new ScaffoldError('HFS_ADD_TEMPLATE_MISSING', `${topic}.yaml names template ${entry.template} for ${entry.path}, which the package does not carry`);
|
|
148
|
+
return { target, absolute, body: renderBody(fs.readFileSync(templateFile, 'utf8'), forms, entry.template), instance };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function appendEntries(entries, context, planned) {
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
const file = planEntry(entry, context);
|
|
154
|
+
if (file) planned.push(file);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Renders all topic-owned paths and bodies before any instance file is written. */
|
|
159
|
+
export function planKind(context) {
|
|
160
|
+
const { noun, name, spec, params, repo, options, now } = context;
|
|
161
|
+
const specs = [noun, ...(spec.also ?? [])].map((each) => [each, params.addKinds[each]]);
|
|
162
|
+
const patterns = [...new Set(specs.flatMap(([, eachSpec]) => eachSpec.patterns))];
|
|
163
|
+
const topics = new Set([...specs.map(([, eachSpec]) => eachSpec.topic), ...topicsOfPatterns(patterns)]);
|
|
164
|
+
refuseFullAdapterInLite({ edition: repo.edition, topics: [...topics].map(readTopic), command: `add ${noun}` });
|
|
165
|
+
const trees = treesOf({ topics, repo, noun, spec });
|
|
166
|
+
validateWrittenSlots({ ...context, specs, topics, trees });
|
|
167
|
+
const values = Object.assign({}, ...specs.map(([each, eachSpec]) => variablesOf({ spec: eachSpec, noun: each, name, options })), variablesOf({ spec, noun, name, options }));
|
|
168
|
+
const withStamp = { ...values, epochMs13: String(now()).padStart(13, '0').slice(0, 13) };
|
|
169
|
+
const forms = formsOf(withStamp);
|
|
170
|
+
const beRoot = path.join(context.repoRoot, 'be');
|
|
171
|
+
const planned = [];
|
|
172
|
+
for (const [each, eachSpec] of specs) {
|
|
173
|
+
const entries = trees.get(eachSpec.topic).filter(entry => !entry.path.startsWith(PLATFORM_PREFIX));
|
|
174
|
+
appendEntries(entries, { topic: eachSpec.topic, instance: true, shared: each !== noun, beRoot, withStamp, forms }, planned);
|
|
175
|
+
}
|
|
176
|
+
for (const topic of topics) {
|
|
177
|
+
const entries = trees.get(topic).filter(entry => entry.path.startsWith(PLATFORM_PREFIX));
|
|
178
|
+
appendEntries(entries, { topic, instance: false, shared: false, beRoot, withStamp, forms }, planned);
|
|
179
|
+
}
|
|
180
|
+
const clash = planned.filter(file => file.instance && fs.existsSync(file.absolute)).map(file => file.target);
|
|
181
|
+
if (clash.length) throw new ScaffoldError('HFS_ADD_EXISTS', `${noun} ${name} already exists: ${clash.join(', ')}`);
|
|
182
|
+
return { values, forms, beRoot, patterns, planned };
|
|
183
|
+
}
|
package/scaffold/add-table.mjs
CHANGED
|
@@ -10,6 +10,8 @@ import { ScaffoldError, pascalOf } from "./service.mjs";
|
|
|
10
10
|
import { filesBelow, registerLiteDomainService, registerLiteExports, registrationFiles } from "./lite-exports.mjs";
|
|
11
11
|
import { byCodeUnit } from "../report/order.mjs";
|
|
12
12
|
import { coreFeApps } from "./app.mjs";
|
|
13
|
+
import { addModuleImport, planComposeGroup } from "./add-compose.mjs";
|
|
14
|
+
import { withSqlIdentifierError } from "./add-database-lite.mjs";
|
|
13
15
|
|
|
14
16
|
const NAME = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
|
|
15
17
|
const MIGRATION = /^(\d{14})_[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.sql$/;
|
|
@@ -163,53 +165,53 @@ function removeEmptyParents(file, stop) {
|
|
|
163
165
|
/** The app-root relative path of a file under the app (posix). */
|
|
164
166
|
const at = (root, relative) => path.join(root, ...relative.split("/"));
|
|
165
167
|
|
|
168
|
+
/** A line that opens the error kinds array of the errors registration. */
|
|
169
|
+
const KINDS_LINE = /^\s*kinds: \[$/;
|
|
170
|
+
|
|
166
171
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
172
|
+
* The composition file of an api app that holds its error kinds array: the root `app.module.ts`, or else the composition group
|
|
173
|
+
* `compose/<area>.ts` of the same app that holds it (the platform group of a scaffolded app). Repository-relative.
|
|
169
174
|
*/
|
|
170
|
-
function
|
|
175
|
+
function kindsHomeOf(root, appRelative) {
|
|
176
|
+
const holdsKinds = (relative) => fs.readFileSync(at(root, relative), "utf8").replace(/\r\n/g, "\n").split("\n").some((line) => KINDS_LINE.test(line));
|
|
177
|
+
if (holdsKinds(appRelative)) return appRelative;
|
|
178
|
+
const composeRelative = `${path.posix.dirname(appRelative)}/compose`;
|
|
179
|
+
const groups = fs.existsSync(at(root, composeRelative)) ? fs.readdirSync(at(root, composeRelative)).filter((file) => file.endsWith(".ts")).sort(byCodeUnit) : [];
|
|
180
|
+
const home = groups.map((file) => `${composeRelative}/${file}`).find(holdsKinds);
|
|
181
|
+
if (!home) throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `add table could not find the error kinds array in ${appRelative} or a group of its compose/`);
|
|
182
|
+
return home;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** A composition group after `kinds` is listed first in its error kinds array and imported from `specifier`. Returns the new text. */
|
|
186
|
+
function wireKinds(text, relative, { kinds, specifier }) {
|
|
171
187
|
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
172
|
-
if (lines.some((line) => line.startsWith("import ") && new RegExp(
|
|
188
|
+
if (lines.some((line) => line.startsWith("import ") && new RegExp(String.raw`\b${kinds}\b`).test(line))) return text;
|
|
173
189
|
const lastImport = lines.reduce((last, line, index) => (line.startsWith("import ") ? index : last), -1);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
lines.
|
|
180
|
-
if (kinds !== undefined) {
|
|
181
|
-
const kindsAt = lines.findIndex((line) => /^\s*kinds: \[$/.test(line));
|
|
182
|
-
if (kindsAt < 0) throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `add table could not find the error kinds array in ${relative}`);
|
|
183
|
-
const kindsIndent = /^(\s*)/.exec(lines[kindsAt])?.[1] ?? "";
|
|
184
|
-
lines.splice(kindsAt + 1, 0, `${kindsIndent} ${kinds},`);
|
|
185
|
-
}
|
|
186
|
-
return `${lines.join("\n").replace(/\n+$/, "")}\n`;
|
|
190
|
+
const kindsAt = lines.findIndex((line) => KINDS_LINE.test(line));
|
|
191
|
+
if (lastImport < 0 || kindsAt < 0) throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `add table could not find the error kinds array in ${relative}`);
|
|
192
|
+
const kindsIndent = /^(\s*)/.exec(lines[kindsAt])?.[1] ?? "";
|
|
193
|
+
lines.splice(kindsAt + 1, 0, `${kindsIndent} ${kinds},`);
|
|
194
|
+
lines.splice(lastImport + 1, 0, `import { ${kinds} } from "${specifier}"`);
|
|
195
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
187
196
|
}
|
|
188
197
|
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
/** Plans the domain and API area groups of the connection owner, keeping registrations out of app.module.ts. */
|
|
199
|
+
function tableComposition({ root, repo, owner, feature, fe, ownerRelative }) {
|
|
200
|
+
const domain = { importLine: `import { ${pascalOf(feature)}Module } from "@modules/domain/${feature}"`, entry: `${pascalOf(feature)}Module.register({ isGlobal: true })` };
|
|
201
|
+
const groups = [['domain', [domain]]];
|
|
202
|
+
if (fe) groups.push(['api', [
|
|
203
|
+
{ importLine: 'import { GraphqlModule } from "@modules/platform/graphql"', entry: 'GraphqlModule.register({ isGlobal: true })' },
|
|
204
|
+
{ importLine: `import { ${pascalOf(feature)}GraphqlModule } from "@features/api/${feature}"`, entry: `${pascalOf(feature)}GraphqlModule.register({})` },
|
|
205
|
+
]]);
|
|
206
|
+
const lines = fs.readFileSync(at(root, ownerRelative), 'utf8').replace(/\r\n/g, '\n').split('\n');
|
|
207
|
+
const patches = groups.map(([area, entries]) => {
|
|
208
|
+
const argument = area === 'domain' && repo.edition === 'lite' ? 'verifyAccessToken' : '';
|
|
209
|
+
addModuleImport(lines, { importLine: `import { ${area}Modules } from "./compose/${area}"`, entry: `...${area}Modules(${argument})`, relative: ownerRelative, noun: 'table' });
|
|
210
|
+
return planComposeGroup({ repoRoot: root, repo, app: owner.name, area, noun: 'table', entries });
|
|
196
211
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
wireImport(
|
|
201
|
-
wireImport(text, relative, {
|
|
202
|
-
importLine: 'import { GraphqlModule } from "@modules/platform/graphql"',
|
|
203
|
-
symbol: "GraphqlModule",
|
|
204
|
-
entry: "GraphqlModule.register({ isGlobal: true })",
|
|
205
|
-
}),
|
|
206
|
-
relative,
|
|
207
|
-
{
|
|
208
|
-
importLine: `import { ${pascalOf(name)}GraphqlModule } from "@features/api/${name}"`,
|
|
209
|
-
symbol: `${pascalOf(name)}GraphqlModule`,
|
|
210
|
-
entry: `${pascalOf(name)}GraphqlModule`,
|
|
211
|
-
},
|
|
212
|
-
);
|
|
212
|
+
patches.push({ relative: ownerRelative, text: lines.join('\n') });
|
|
213
|
+
return patches;
|
|
214
|
+
}
|
|
213
215
|
|
|
214
216
|
/** The platform GraphQL server capability (templates/be/platform/graphql), whole or absent: written once, by the first GraphQL feature. */
|
|
215
217
|
const GRAPHQL_PLATFORM = "be/src/modules/platform/graphql";
|
|
@@ -252,7 +254,7 @@ const GRAPHQL_URL_READER = [
|
|
|
252
254
|
|
|
253
255
|
/** An app's modules/config text with graphqlUrl appended once. */
|
|
254
256
|
const withGraphqlUrl = (text) =>
|
|
255
|
-
/^export const graphqlUrl\b/m.test(text) ? text : `${text.
|
|
257
|
+
/^export const graphqlUrl\b/m.test(text) ? text : `${text.trimEnd()}\n${GRAPHQL_URL_READER}\n`;
|
|
256
258
|
|
|
257
259
|
/** The be api feature of a table (owner ruling 2026-10-13, lite = full): get-<singular> and create-<singular> over GraphQL. */
|
|
258
260
|
const apiFeatureFiles = (values) => {
|
|
@@ -261,6 +263,8 @@ const apiFeatureFiles = (values) => {
|
|
|
261
263
|
const create = `create-${values.singular}`;
|
|
262
264
|
return [
|
|
263
265
|
[`${base}/index.ts`, "feature.index.ts.tpl"],
|
|
266
|
+
[`${base}/${values.name}.options.ts`, "feature.options.ts.tpl"],
|
|
267
|
+
[`${base}/${values.name}.module-definition.ts`, "feature.module-definition.ts.tpl"],
|
|
264
268
|
[`${base}/${values.name}.module.ts`, "feature.module.ts.tpl"],
|
|
265
269
|
[`${base}/application/${get}.query.ts`, "get.query.ts.tpl"],
|
|
266
270
|
[`${base}/application/${get}.handler.ts`, "get.handler.ts.tpl"],
|
|
@@ -275,6 +279,8 @@ const apiFeatureFiles = (values) => {
|
|
|
275
279
|
[`${base}/transport/graphql/${create}.resolver.ts`, "create.resolver.ts.tpl"],
|
|
276
280
|
[`${base}/transport/graphql/${create}.mapper.ts`, "create.mapper.ts.tpl"],
|
|
277
281
|
[`${base}/transport/graphql/dto/${create}.type.ts`, "create.type.ts.tpl"],
|
|
282
|
+
[`${base}/transport/graphql/${values.name}-graphql.options.ts`, "graphql.options.ts.tpl"],
|
|
283
|
+
[`${base}/transport/graphql/${values.name}-graphql.module-definition.ts`, "graphql.module-definition.ts.tpl"],
|
|
278
284
|
[`${base}/transport/graphql/${values.name}-graphql.module.ts`, "graphql.module.ts.tpl"],
|
|
279
285
|
].map(([relative, file]) => ({ relative, body: template(`be/table/api/${file}`, values) }));
|
|
280
286
|
};
|
|
@@ -316,7 +322,7 @@ const feBarrels = (app, values) => {
|
|
|
316
322
|
const withExport = (text, line) => {
|
|
317
323
|
const lines = text.replace(/\r\n/g, "\n").split("\n").filter(Boolean);
|
|
318
324
|
if (lines.includes(line)) return text;
|
|
319
|
-
return `${[...lines, line].sort(
|
|
325
|
+
return `${[...lines, line].sort(byCodeUnit).join("\n")}\n`;
|
|
320
326
|
};
|
|
321
327
|
|
|
322
328
|
/**
|
|
@@ -324,13 +330,7 @@ const withExport = (text, line) => {
|
|
|
324
330
|
* server capability on first use) and, in every product Next app, its operation files and SWR hooks.
|
|
325
331
|
* `emitTypes` returns the generated database.types.ts text; false is the explicit `--no-types` seam.
|
|
326
332
|
*/
|
|
327
|
-
|
|
328
|
-
root,
|
|
329
|
-
name,
|
|
330
|
-
fe = false,
|
|
331
|
-
emitTypes = emitDbTypes,
|
|
332
|
-
now = Date.now,
|
|
333
|
-
}) {
|
|
333
|
+
function tableContext({ root, name }) {
|
|
334
334
|
const requestedName = String(name);
|
|
335
335
|
if (!NAME.test(requestedName))
|
|
336
336
|
throw new ScaffoldError(
|
|
@@ -349,12 +349,16 @@ export function addTable({
|
|
|
349
349
|
);
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
return { root, name, requestedName, feature, table, repo, connection };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function nextMigration({ root, feature, requestedName, now }) {
|
|
352
356
|
const migrationsDir = path.join(root, "supabase", "migrations");
|
|
353
357
|
const migrations = fs.existsSync(migrationsDir)
|
|
354
358
|
? fs
|
|
355
359
|
.readdirSync(migrationsDir)
|
|
356
360
|
.filter((file) => MIGRATION.test(file))
|
|
357
|
-
.sort()
|
|
361
|
+
.sort(byCodeUnit)
|
|
358
362
|
: [];
|
|
359
363
|
if (migrations.some((file) => file.endsWith(`_${feature}.sql`)))
|
|
360
364
|
throw new ScaffoldError(
|
|
@@ -369,6 +373,10 @@ export function addTable({
|
|
|
369
373
|
`the current UTC timestamp ${stamp} is not later than the latest migration ${latest}; fix the clock or wait for the next second`,
|
|
370
374
|
);
|
|
371
375
|
|
|
376
|
+
return stamp;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function tableValues({ feature, table }) {
|
|
372
380
|
const singular = singularOf(feature);
|
|
373
381
|
const values = {
|
|
374
382
|
name: feature,
|
|
@@ -382,12 +390,20 @@ export function addTable({
|
|
|
382
390
|
singularCamel: camelOf(singular),
|
|
383
391
|
singularUpper: snakeOf(singular).toUpperCase(),
|
|
384
392
|
};
|
|
393
|
+
return values;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function productApps(repo, fe) {
|
|
385
397
|
const apps = fe ? coreFeApps(repo.sides?.fe?.apps ?? []) : [];
|
|
386
398
|
if (fe && !apps.length)
|
|
387
399
|
throw new ScaffoldError(
|
|
388
400
|
"HFS_ADD_NO_FRONT_END",
|
|
389
401
|
"add table --fe needs at least one declared product Next app",
|
|
390
402
|
);
|
|
403
|
+
return apps;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function tableFiles({ root, repo, feature, stamp, values, fe, apps }) {
|
|
391
407
|
const api = fe ? ".api" : "";
|
|
392
408
|
const domain = `be/src/modules/domain/${feature}`;
|
|
393
409
|
const planned = [
|
|
@@ -420,6 +436,10 @@ export function addTable({
|
|
|
420
436
|
}
|
|
421
437
|
}
|
|
422
438
|
|
|
439
|
+
return planned;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function tableOwner({ root, repo, connection, name, requestedName, planned }) {
|
|
423
443
|
const clash = planned
|
|
424
444
|
.filter((file) => fs.existsSync(at(root, file.relative)))
|
|
425
445
|
.map((file) => file.relative);
|
|
@@ -435,8 +455,10 @@ export function addTable({
|
|
|
435
455
|
if (!fs.existsSync(at(root, ownerRelative)))
|
|
436
456
|
throw new ScaffoldError("HFS_ADD_WIRE_MISSING", `add table registers ${name} in ${ownerRelative}, which does not exist`);
|
|
437
457
|
|
|
438
|
-
|
|
439
|
-
|
|
458
|
+
return { owner, ownerRelative };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function tableEditor(root, touched) {
|
|
440
462
|
const edit = (relative, change) => {
|
|
441
463
|
const file = at(root, relative);
|
|
442
464
|
if (!touched.has(file)) touched.set(file, fs.existsSync(file) ? fs.readFileSync(file) : null);
|
|
@@ -446,34 +468,51 @@ export function addTable({
|
|
|
446
468
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
447
469
|
fs.writeFileSync(file, after);
|
|
448
470
|
};
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
461
|
-
edit(
|
|
471
|
+
return edit;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function writeTableFiles({ root, planned, emitTypes, edit }, created) {
|
|
475
|
+
for (const file of planned) {
|
|
476
|
+
const target = at(root, file.relative);
|
|
477
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
478
|
+
fs.writeFileSync(target, file.body);
|
|
479
|
+
created.push(target);
|
|
480
|
+
}
|
|
481
|
+
if (typeof emitTypes === "function") {
|
|
482
|
+
const generated = emitTypes({ root });
|
|
483
|
+
if (typeof generated === "string") edit(dbTypesPath, () => generated);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function tableRegistrations({ root, repo, fe, edit }) {
|
|
489
|
+
const errorFile = "be/src/modules/platform/database/errors/database.error.ts";
|
|
490
|
+
if (repo.edition === "lite" && fs.existsSync(at(root, errorFile))) edit(errorFile, withSqlIdentifierError);
|
|
462
491
|
const registrations = [
|
|
463
492
|
...(repo.edition === "lite" ? ["table"] : []),
|
|
464
493
|
...(fe ? ["api", "graphql"] : []),
|
|
465
494
|
];
|
|
466
495
|
for (const generator of registrations) for (const relative of registrationFiles(generator)) edit(relative, (text) => text);
|
|
467
496
|
for (const generator of registrations) registerLiteExports({ root, generator });
|
|
468
|
-
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function wireTableFrontends({ root, feature, values, apps, edit }) {
|
|
469
500
|
registerLiteDomainService({ root, moduleSpecifier: `@modules/domain/${feature}`, service: `${values.Name}Service` });
|
|
470
|
-
edit(ownerRelative, (text) => wireGraphqlFeature(text, ownerRelative, feature));
|
|
471
501
|
for (const app of apps) {
|
|
472
502
|
for (const [relative, line] of feBarrels(app.name, values)) edit(relative, (text) => withExport(text, line));
|
|
473
503
|
edit(`fe/apps/${app.name}/src/modules/config/index.ts`, withGraphqlUrl);
|
|
474
504
|
}
|
|
475
|
-
|
|
476
|
-
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function wireTable({ root, repo, feature, values, ownerRelative, composition, fe, apps, edit }) {
|
|
508
|
+
const kindsHome = kindsHomeOf(root, ownerRelative);
|
|
509
|
+
for (const patch of composition) edit(patch.relative, () => patch.text);
|
|
510
|
+
edit(kindsHome, (text) => wireKinds(text, kindsHome, { kinds: `${snakeOf(feature).toUpperCase()}_ERROR_KINDS`, specifier: `@modules/domain/${feature}` }));
|
|
511
|
+
tableRegistrations({ root, repo, fe, edit });
|
|
512
|
+
if (fe) wireTableFrontends({ root, feature, values, apps, edit });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function rollbackTable(root, touched, created) {
|
|
477
516
|
for (const [file, before] of touched) {
|
|
478
517
|
if (before === null) fs.rmSync(file, { force: true });
|
|
479
518
|
else fs.writeFileSync(file, before);
|
|
@@ -483,10 +522,33 @@ export function addTable({
|
|
|
483
522
|
removeEmptyParents(file, root);
|
|
484
523
|
}
|
|
485
524
|
for (const [file, before] of touched) if (before === null) removeEmptyParents(file, root);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function applyTable(context) {
|
|
528
|
+
const touched = new Map();
|
|
529
|
+
const created = [];
|
|
530
|
+
const edit = tableEditor(context.root, touched);
|
|
531
|
+
try {
|
|
532
|
+
writeTableFiles({ ...context, edit }, created);
|
|
533
|
+
wireTable({ ...context, edit });
|
|
534
|
+
} catch (error) {
|
|
535
|
+
rollbackTable(context.root, touched, created);
|
|
486
536
|
throw error;
|
|
487
537
|
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Adds one table through typed domain/API groups and restores every touched byte on failure. */
|
|
541
|
+
export function addTable({ root, name, fe = false, emitTypes = emitDbTypes, now = Date.now }) {
|
|
542
|
+
const context = tableContext({ root, name });
|
|
543
|
+
const stamp = nextMigration({ ...context, now });
|
|
544
|
+
const values = tableValues(context);
|
|
545
|
+
const apps = productApps(context.repo, fe);
|
|
546
|
+
const planned = tableFiles({ ...context, stamp, values, apps, fe });
|
|
547
|
+
const owner = tableOwner({ ...context, planned });
|
|
548
|
+
const composition = tableComposition({ ...context, ...owner, fe });
|
|
549
|
+
applyTable({ ...context, ...owner, planned, composition, apps, values, fe, emitTypes });
|
|
488
550
|
return {
|
|
489
|
-
created: planned.map((file) => file.relative),
|
|
551
|
+
created: [...planned.map((file) => file.relative), ...composition.filter((patch) => patch.exists === false).map((patch) => patch.relative)],
|
|
490
552
|
types: typeof emitTypes === "function" ? dbTypesPath : "skipped",
|
|
491
553
|
};
|
|
492
554
|
}
|