@starci/hfs 4.0.8 → 4.0.10
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/LICENSE +21 -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 +131 -24
- package/package.json +16 -10
- package/report/sonar.mjs +8 -8
- package/runtime/engine/admission.mjs +308 -0
- package/runtime/engine/canonical-json.mjs +10 -0
- package/runtime/engine/config.mjs +486 -0
- package/runtime/engine/db/blob.mjs +315 -0
- package/runtime/engine/db/ledger-paths.mjs +83 -0
- package/runtime/engine/db/ledger.mjs +1118 -0
- package/runtime/engine/db/machine-connection.mjs +135 -0
- package/runtime/engine/db/machine-schema.mjs +84 -0
- package/runtime/engine/db/machine.mjs +1367 -0
- package/runtime/engine/db/migrations/machine/0001-init.sql +924 -0
- package/runtime/engine/db/migrations/runtime/0001-init.sql +1108 -0
- package/runtime/engine/db/provider-reservations.mjs +101 -0
- package/runtime/engine/digest.mjs +16 -0
- package/runtime/engine/invalid-config.mjs +15 -0
- package/runtime/engine/orca-config.mjs +27 -0
- package/runtime/engine/refuse.mjs +11 -0
- package/runtime/engine/secrets.mjs +130 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +162 -31
- 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 +611 -51
- package/runtime/modules/models/registry.yaml +553 -0
- package/runtime/modules/models/runtimes.yaml +695 -0
- package/runtime/modules/ops/_labels.yaml +53 -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 +16 -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 +18 -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/node/lib.mjs +14 -0
- package/runtime/scripts/api/node/spawn-node.mjs +6 -0
- package/runtime/scripts/api/process/lib.mjs +111 -0
- package/runtime/scripts/api/process/owned-process.mjs +78 -0
- package/runtime/scripts/api/process/resolve-real-tool.mjs +46 -0
- package/runtime/scripts/api/process/run-program.mjs +7 -0
- package/runtime/scripts/api/process/stop-owned-process.mjs +9 -0
- package/runtime/scripts/api/sops/decrypt.mjs +9 -12
- package/runtime/scripts/api/sops/lib.mjs +255 -0
- package/runtime/scripts/api/sops/seal.mjs +22 -0
- package/runtime/scripts/connectors/lib.mjs +488 -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 +2 -2
- 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 +209 -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/clip.mjs +19 -0
- package/runtime/scripts/lib/display-names.mjs +259 -0
- 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/example-refs.mjs +158 -0
- package/runtime/scripts/lib/fs-kind.mjs +14 -4
- 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-schema.mjs +52 -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/mutation-fence.mjs +15 -0
- package/runtime/scripts/lib/package-at.mjs +37 -0
- package/runtime/scripts/lib/path-key.mjs +57 -2
- package/runtime/scripts/lib/process-identity.mjs +6 -0
- package/runtime/scripts/lib/read-text.mjs +34 -0
- package/runtime/scripts/lib/read-yaml.mjs +18 -0
- package/runtime/scripts/lib/redact.mjs +161 -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/sleep.mjs +7 -0
- package/runtime/scripts/lib/sops-envelope.mjs +112 -0
- package/runtime/scripts/lib/source-phrases.mjs +34 -0
- package/runtime/scripts/lib/sqlite.mjs +21 -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
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { Test } from "@nestjs/testing"
|
|
2
|
+
import { FakeClock, mock } from "@starci/jest-preset"
|
|
3
|
+
import { CLOCK } from "@modules/platform/clock"
|
|
4
|
+
import { LOGGER } from "@modules/platform/logging"
|
|
5
|
+
import type { Logger } from "@modules/platform/logging"
|
|
6
|
+
import { BaseEvent } from "./event-bus.contracts"
|
|
7
|
+
import type { ParsedEvent } from "./event-bus.contracts"
|
|
8
|
+
import { EVENT_BUS_OPTIONS, EVENT_TRANSPORT } from "./event-bus.decorators"
|
|
9
|
+
import { EventBusLogEvent } from "./event-bus.log-events"
|
|
10
|
+
import type { EventBusOptions } from "./event-bus.options"
|
|
11
|
+
import type { EventConsumer } from "./event-bus.port"
|
|
12
|
+
import { readEnvelope } from "./event-envelope.policy"
|
|
13
|
+
import { EventRunnerService } from "./event-runner.service"
|
|
14
|
+
import type { EventSubscription, EventTransport, InboundMessage } from "./event-transport.port"
|
|
15
|
+
|
|
16
|
+
const AT = "2026-02-03T04:05:06.000Z"
|
|
17
|
+
const NOW = new Date(AT).getTime()
|
|
18
|
+
|
|
19
|
+
const options: EventBusOptions = {
|
|
20
|
+
brokers: ["localhost:9094"],
|
|
21
|
+
groupId: "app",
|
|
22
|
+
topicPrefix: "run-1.",
|
|
23
|
+
relayIntervalMs: 100,
|
|
24
|
+
relayBatch: 50,
|
|
25
|
+
timeoutMs: 3000,
|
|
26
|
+
connections: [],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PingPayload {
|
|
30
|
+
readonly ref: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class PingEvent extends BaseEvent {
|
|
34
|
+
static readonly eventName = "probe.ping"
|
|
35
|
+
static readonly version = 1
|
|
36
|
+
|
|
37
|
+
readonly eventName = PingEvent.eventName
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
readonly eventId: string,
|
|
41
|
+
readonly payload: PingPayload,
|
|
42
|
+
) {
|
|
43
|
+
super()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static parse(envelope: unknown): ParsedEvent<PingEvent> {
|
|
47
|
+
const read = readEnvelope(envelope)
|
|
48
|
+
return read !== null && typeof read.payload.ref === "string"
|
|
49
|
+
? new PingEvent(read.eventId, { ref: read.payload.ref })
|
|
50
|
+
: null
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const message = (overrides: Partial<InboundMessage> = {}): InboundMessage => ({
|
|
55
|
+
topic: "run-1.events.probe",
|
|
56
|
+
partition: 0,
|
|
57
|
+
offset: "4",
|
|
58
|
+
key: "o-1",
|
|
59
|
+
value: JSON.stringify({ eventId: "o-1", eventName: "probe.ping", payload: { ref: "o-1" } }),
|
|
60
|
+
headers: {},
|
|
61
|
+
...overrides,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const build = async (register = true) => {
|
|
65
|
+
const transport = mock<EventTransport>()
|
|
66
|
+
const logger = mock<Logger>()
|
|
67
|
+
const handle = jest.fn<Promise<void>, Array<never>>()
|
|
68
|
+
const subscriptions: Array<EventSubscription> = []
|
|
69
|
+
transport.subscribe.mockImplementation((subscription) => {
|
|
70
|
+
subscriptions.push(subscription)
|
|
71
|
+
return Promise.resolve()
|
|
72
|
+
})
|
|
73
|
+
const moduleRef = await Test.createTestingModule({
|
|
74
|
+
providers: [
|
|
75
|
+
EventRunnerService,
|
|
76
|
+
{ provide: EVENT_BUS_OPTIONS, useValue: options },
|
|
77
|
+
{ provide: EVENT_TRANSPORT, useValue: transport },
|
|
78
|
+
{ provide: CLOCK, useValue: new FakeClock(AT) },
|
|
79
|
+
{ provide: LOGGER, useValue: logger },
|
|
80
|
+
],
|
|
81
|
+
}).compile()
|
|
82
|
+
const runner = moduleRef.get(EventRunnerService)
|
|
83
|
+
if (register) {
|
|
84
|
+
const consumer: EventConsumer<PingEvent> = { event: PingEvent, handle }
|
|
85
|
+
runner.add(consumer)
|
|
86
|
+
}
|
|
87
|
+
return { runner, transport, logger, handle, subscriptions }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe("EventRunnerService", () => {
|
|
91
|
+
describe("onApplicationBootstrap", () => {
|
|
92
|
+
it("subscribes to the main and retry topic of every registered event", async () => {
|
|
93
|
+
const { runner, subscriptions } = await build()
|
|
94
|
+
|
|
95
|
+
await runner.onApplicationBootstrap()
|
|
96
|
+
|
|
97
|
+
expect(subscriptions.map((subscription) => subscription.topics)).toEqual([
|
|
98
|
+
["run-1.events.probe", "run-1.events.probe.retry"],
|
|
99
|
+
])
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it("subscribes to nothing when no consumer registered", async () => {
|
|
103
|
+
const { runner, transport } = await build(false)
|
|
104
|
+
|
|
105
|
+
await runner.onApplicationBootstrap()
|
|
106
|
+
|
|
107
|
+
expect(transport.subscribe).not.toHaveBeenCalled()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("hands what the transport delivers to receive", async () => {
|
|
111
|
+
const { runner, subscriptions, handle } = await build()
|
|
112
|
+
handle.mockResolvedValue(undefined)
|
|
113
|
+
await runner.onApplicationBootstrap()
|
|
114
|
+
|
|
115
|
+
await subscriptions[0]?.onMessage(message())
|
|
116
|
+
|
|
117
|
+
expect(handle).toHaveBeenCalledTimes(1)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe("receive", () => {
|
|
122
|
+
it("hands the parsed event to its consumer with the event id and the first attempt", async () => {
|
|
123
|
+
const { runner, handle, transport } = await build()
|
|
124
|
+
handle.mockResolvedValue(undefined)
|
|
125
|
+
|
|
126
|
+
await runner.receive(message())
|
|
127
|
+
|
|
128
|
+
expect(handle).toHaveBeenCalledWith({
|
|
129
|
+
eventId: "o-1",
|
|
130
|
+
event: new PingEvent("o-1", { ref: "o-1" }),
|
|
131
|
+
attempt: 1,
|
|
132
|
+
})
|
|
133
|
+
expect(transport.send).not.toHaveBeenCalled()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it("hands one event to every consumer registered for it and retries it once when any of them failed", async () => {
|
|
137
|
+
const { runner, handle, transport } = await build()
|
|
138
|
+
const second = jest.fn<Promise<void>, Array<never>>()
|
|
139
|
+
const third = jest.fn<Promise<void>, Array<never>>()
|
|
140
|
+
handle.mockRejectedValue(new Error("the first consumer failed"))
|
|
141
|
+
second.mockResolvedValue(undefined)
|
|
142
|
+
third.mockRejectedValue(new Error("the third consumer failed too"))
|
|
143
|
+
runner.add({ event: PingEvent, handle: second })
|
|
144
|
+
runner.add({ event: PingEvent, handle: third })
|
|
145
|
+
|
|
146
|
+
await runner.receive(message())
|
|
147
|
+
|
|
148
|
+
expect(second).toHaveBeenCalledTimes(1)
|
|
149
|
+
expect(third).toHaveBeenCalledTimes(1)
|
|
150
|
+
expect(transport.send).toHaveBeenCalledTimes(1)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it("leaves an event nobody here consumes alone", async () => {
|
|
154
|
+
const { runner, handle, transport, logger } = await build()
|
|
155
|
+
|
|
156
|
+
await runner.receive(
|
|
157
|
+
message({ value: JSON.stringify({ eventId: "x", eventName: "probe.other", payload: {} }) }),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
expect(handle).not.toHaveBeenCalled()
|
|
161
|
+
expect(transport.send).not.toHaveBeenCalled()
|
|
162
|
+
expect(logger.warn).not.toHaveBeenCalled()
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it("logs and skips a value that is not an envelope", async () => {
|
|
166
|
+
const { runner, handle, logger } = await build()
|
|
167
|
+
|
|
168
|
+
await runner.receive(message({ value: "not json" }))
|
|
169
|
+
await runner.receive(message({ value: JSON.stringify([1]) }))
|
|
170
|
+
|
|
171
|
+
expect(handle).not.toHaveBeenCalled()
|
|
172
|
+
expect(logger.error).toHaveBeenCalledWith(EventBusLogEvent.MessageSkipped, expect.any(SyntaxError), {
|
|
173
|
+
topic: "run-1.events.probe",
|
|
174
|
+
})
|
|
175
|
+
expect(logger.warn).toHaveBeenCalledTimes(1)
|
|
176
|
+
expect(logger.warn).toHaveBeenCalledWith(EventBusLogEvent.MessageSkipped, { topic: "run-1.events.probe" })
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it("buries an envelope the event class cannot read, without retrying it", async () => {
|
|
180
|
+
const { runner, handle, transport } = await build()
|
|
181
|
+
const value = JSON.stringify({ eventId: "o-2", eventName: "probe.ping", payload: {} })
|
|
182
|
+
|
|
183
|
+
await runner.receive(message({ key: "o-2", value }))
|
|
184
|
+
|
|
185
|
+
expect(handle).not.toHaveBeenCalled()
|
|
186
|
+
expect(transport.send).toHaveBeenCalledWith([
|
|
187
|
+
{
|
|
188
|
+
topic: "run-1.events.probe.dlq",
|
|
189
|
+
key: "o-2",
|
|
190
|
+
value,
|
|
191
|
+
headers: {
|
|
192
|
+
attempt: "1",
|
|
193
|
+
reason: "the envelope does not have the shape of the event",
|
|
194
|
+
"origin-topic": "run-1.events.probe",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
])
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it("puts a failed delivery on the retry topic with the next attempt and a doubled backoff", async () => {
|
|
201
|
+
const { runner, handle, transport, logger } = await build()
|
|
202
|
+
const failure = new Error("the database is down")
|
|
203
|
+
handle.mockRejectedValue(failure)
|
|
204
|
+
|
|
205
|
+
await runner.receive(message({ headers: { attempt: "2" } }))
|
|
206
|
+
|
|
207
|
+
expect(transport.send).toHaveBeenCalledWith([
|
|
208
|
+
{
|
|
209
|
+
topic: "run-1.events.probe.retry",
|
|
210
|
+
key: "o-1",
|
|
211
|
+
value: message().value,
|
|
212
|
+
headers: { attempt: "3", "not-before": String(NOW + 1000) },
|
|
213
|
+
},
|
|
214
|
+
])
|
|
215
|
+
expect(logger.error).toHaveBeenCalledWith(EventBusLogEvent.DeliveryFailed, failure, {
|
|
216
|
+
event: "probe.ping",
|
|
217
|
+
attempt: 2,
|
|
218
|
+
})
|
|
219
|
+
expect(logger.warn).toHaveBeenCalledWith(EventBusLogEvent.DeliveryRetried, {
|
|
220
|
+
event: "probe.ping",
|
|
221
|
+
attempt: 2,
|
|
222
|
+
reason: "the database is down",
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it("buries the delivery that ran out of attempts, naming the reason and the topic it came from", async () => {
|
|
227
|
+
const { runner, handle, transport, logger } = await build()
|
|
228
|
+
handle.mockRejectedValue("a text, not an error")
|
|
229
|
+
|
|
230
|
+
await runner.receive(message({ headers: { attempt: "5" } }))
|
|
231
|
+
|
|
232
|
+
expect(transport.send).toHaveBeenCalledWith([
|
|
233
|
+
{
|
|
234
|
+
topic: "run-1.events.probe.dlq",
|
|
235
|
+
key: "o-1",
|
|
236
|
+
value: message().value,
|
|
237
|
+
headers: { attempt: "5", reason: "a text, not an error", "origin-topic": "run-1.events.probe" },
|
|
238
|
+
},
|
|
239
|
+
])
|
|
240
|
+
expect(logger.warn).toHaveBeenCalledWith(EventBusLogEvent.DeliveryBuried, {
|
|
241
|
+
event: "probe.ping",
|
|
242
|
+
attempts: 5,
|
|
243
|
+
reason: "a text, not an error",
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it("waits for the backoff of a retry message before handing it over", async () => {
|
|
248
|
+
const { runner, handle, transport } = await build()
|
|
249
|
+
handle.mockResolvedValue(undefined)
|
|
250
|
+
|
|
251
|
+
await runner.receive(
|
|
252
|
+
message({
|
|
253
|
+
topic: "run-1.events.probe.retry",
|
|
254
|
+
headers: { attempt: "2", "not-before": String(NOW + 800) },
|
|
255
|
+
}),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
expect(transport.wait).toHaveBeenCalledWith(800)
|
|
259
|
+
expect(handle).toHaveBeenCalledWith(expect.objectContaining({ attempt: 2 }))
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it("does not wait for a retry message whose backoff has passed", async () => {
|
|
263
|
+
const { runner, handle, transport } = await build()
|
|
264
|
+
handle.mockResolvedValue(undefined)
|
|
265
|
+
|
|
266
|
+
await runner.receive(message({ headers: { attempt: "2", "not-before": String(NOW - 5) } }))
|
|
267
|
+
|
|
268
|
+
expect(transport.wait).not.toHaveBeenCalled()
|
|
269
|
+
expect(handle).toHaveBeenCalledTimes(1)
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
})
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common"
|
|
2
|
+
import type { OnApplicationBootstrap } from "@nestjs/common"
|
|
3
|
+
import { InjectClock } from "@modules/platform/clock"
|
|
4
|
+
import type { Clock } from "@modules/platform/clock"
|
|
5
|
+
import { InjectLogger } from "@modules/platform/logging"
|
|
6
|
+
import type { Logger } from "@modules/platform/logging"
|
|
7
|
+
import type { BaseEvent } from "./event-bus.contracts"
|
|
8
|
+
import { InjectEventBusOptions, InjectEventTransport } from "./event-bus.decorators"
|
|
9
|
+
import { EventBusLogEvent } from "./event-bus.log-events"
|
|
10
|
+
import type { EventBusOptions } from "./event-bus.options"
|
|
11
|
+
import type { EventConsumer, EventConsumerRegistry } from "./event-bus.port"
|
|
12
|
+
import { readEnvelopeText } from "./event-envelope.policy"
|
|
13
|
+
import type { EventTransport, InboundMessage } from "./event-transport.port"
|
|
14
|
+
import {
|
|
15
|
+
ATTEMPTS,
|
|
16
|
+
ATTEMPT_HEADER,
|
|
17
|
+
NOT_BEFORE_HEADER,
|
|
18
|
+
ORIGIN_HEADER,
|
|
19
|
+
REASON_HEADER,
|
|
20
|
+
backoffMs,
|
|
21
|
+
deadLetterTopicOf,
|
|
22
|
+
retryTopicOf,
|
|
23
|
+
topicOf,
|
|
24
|
+
} from "./event.policy"
|
|
25
|
+
|
|
26
|
+
@Injectable()
|
|
27
|
+
/**
|
|
28
|
+
* The consumer side of the event bus: the registry the message transport modules fill, and the read loop over the topics of the
|
|
29
|
+
* registered events. A delivery is at least once: the consumer acknowledges by returning; a throw puts the message on the retry
|
|
30
|
+
* topic with a doubling backoff, and after the last attempt on the dead-letter topic, where an operator reads and requeues it.
|
|
31
|
+
* The runner never throws for a failed delivery, so one poisoned event does not stop the partition behind it.
|
|
32
|
+
*/
|
|
33
|
+
export class EventRunnerService implements EventConsumerRegistry, OnApplicationBootstrap {
|
|
34
|
+
private readonly consumers = new Map<string, ReadonlyArray<EventConsumer<BaseEvent>>>()
|
|
35
|
+
|
|
36
|
+
constructor(
|
|
37
|
+
@InjectEventBusOptions() private readonly options: EventBusOptions,
|
|
38
|
+
@InjectEventTransport() private readonly transport: EventTransport,
|
|
39
|
+
@InjectClock() private readonly clock: Clock,
|
|
40
|
+
@InjectLogger() private readonly logger: Logger,
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
/** Registers a consumer of its event (several features may consume one event); the read loop starts when the app has registered every consumer. */
|
|
44
|
+
add<Event extends BaseEvent>(consumer: EventConsumer<Event>): void {
|
|
45
|
+
const name = consumer.event.eventName
|
|
46
|
+
this.consumers.set(name, [...(this.consumers.get(name) ?? []), consumer])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Starts reading the topics of the registered events and their retry topics. */
|
|
50
|
+
async onApplicationBootstrap(): Promise<void> {
|
|
51
|
+
const names = [...this.consumers.keys()]
|
|
52
|
+
if (names.length === 0) return
|
|
53
|
+
const prefix = this.options.topicPrefix
|
|
54
|
+
const topics = [...new Set(names.flatMap((name) => [topicOf(prefix, name), retryTopicOf(prefix, name)]))]
|
|
55
|
+
await this.transport.subscribe({ topics, onMessage: (message) => this.receive(message) })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Hands one message to every consumer of its event, in the order they registered; the events of the topic nobody here consumes
|
|
60
|
+
* are left alone. A failing consumer does not stop the others; the message goes once to the retry topic when any failed, and
|
|
61
|
+
* each consumer takes a redelivery through its own inbox, so one that already succeeded changes nothing.
|
|
62
|
+
*/
|
|
63
|
+
async receive(message: InboundMessage): Promise<void> {
|
|
64
|
+
const { eventName, envelope, cause: unreadable } = readEnvelopeText(message.value)
|
|
65
|
+
const consumers = this.consumers.get(eventName) ?? []
|
|
66
|
+
if (consumers.length === 0) {
|
|
67
|
+
if (unreadable !== null)
|
|
68
|
+
this.logger.error(EventBusLogEvent.MessageSkipped, unreadable, { topic: message.topic })
|
|
69
|
+
else if (eventName === "") this.logger.warn(EventBusLogEvent.MessageSkipped, { topic: message.topic })
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
const attempt = Number(message.headers[ATTEMPT_HEADER] ?? 1)
|
|
73
|
+
const notBefore = Number(message.headers[NOT_BEFORE_HEADER] ?? 0)
|
|
74
|
+
const pause = notBefore - this.clock.now().getTime()
|
|
75
|
+
if (pause > 0) await this.transport.wait(pause)
|
|
76
|
+
const failures: Array<unknown> = []
|
|
77
|
+
for (const consumer of consumers) {
|
|
78
|
+
const event = consumer.event.parse(envelope)
|
|
79
|
+
if (event === null) {
|
|
80
|
+
await this.bury(message, eventName, attempt, "the envelope does not have the shape of the event")
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await consumer.handle({ eventId: event.eventId, event, attempt })
|
|
85
|
+
} catch (cause) {
|
|
86
|
+
this.logger.error(EventBusLogEvent.DeliveryFailed, cause, { event: eventName, attempt })
|
|
87
|
+
failures.push(cause)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const [first] = failures
|
|
91
|
+
if (failures.length > 0) {
|
|
92
|
+
await this.failed(message, eventName, attempt, first instanceof Error ? first.message : String(first))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async failed(message: InboundMessage, eventName: string, attempt: number, reason: string): Promise<void> {
|
|
97
|
+
if (attempt >= ATTEMPTS) {
|
|
98
|
+
await this.bury(message, eventName, attempt, reason)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
const notBefore = this.clock.now().getTime() + backoffMs(attempt)
|
|
102
|
+
await this.transport.send([
|
|
103
|
+
{
|
|
104
|
+
topic: retryTopicOf(this.options.topicPrefix, eventName),
|
|
105
|
+
key: message.key,
|
|
106
|
+
value: message.value,
|
|
107
|
+
headers: { [ATTEMPT_HEADER]: String(attempt + 1), [NOT_BEFORE_HEADER]: String(notBefore) },
|
|
108
|
+
},
|
|
109
|
+
])
|
|
110
|
+
this.logger.warn(EventBusLogEvent.DeliveryRetried, { event: eventName, attempt, reason })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async bury(message: InboundMessage, eventName: string, attempt: number, reason: string): Promise<void> {
|
|
114
|
+
await this.transport.send([
|
|
115
|
+
{
|
|
116
|
+
topic: deadLetterTopicOf(this.options.topicPrefix, eventName),
|
|
117
|
+
key: message.key,
|
|
118
|
+
value: message.value,
|
|
119
|
+
headers: {
|
|
120
|
+
[ATTEMPT_HEADER]: String(attempt),
|
|
121
|
+
[REASON_HEADER]: reason,
|
|
122
|
+
[ORIGIN_HEADER]: topicOf(this.options.topicPrefix, eventName),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
])
|
|
126
|
+
this.logger.warn(EventBusLogEvent.DeliveryBuried, { event: eventName, attempts: attempt, reason })
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/** One message to put on a topic. */
|
|
2
|
+
export interface OutboundMessage {
|
|
3
|
+
/** The topic. */
|
|
4
|
+
readonly topic: string
|
|
5
|
+
/** The partition key: messages of one key keep their order. */
|
|
6
|
+
readonly key: string
|
|
7
|
+
/** The serialized envelope. */
|
|
8
|
+
readonly value: string
|
|
9
|
+
/** Text headers: the attempt, the earliest redelivery instant, the failure reason. */
|
|
10
|
+
readonly headers: Readonly<Record<string, string>>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** One message as the transport hands it to a subscriber, with its position on the topic. */
|
|
14
|
+
export interface InboundMessage extends OutboundMessage {
|
|
15
|
+
/** The partition. */
|
|
16
|
+
readonly partition: number
|
|
17
|
+
/** The offset on the partition, as text. */
|
|
18
|
+
readonly offset: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** What a subscriber of the transport asks for. */
|
|
22
|
+
export interface EventSubscription {
|
|
23
|
+
/** The topics to read. */
|
|
24
|
+
readonly topics: ReadonlyArray<string>
|
|
25
|
+
/** Handles one message; a return acknowledges it, a throw makes the transport deliver it again. */
|
|
26
|
+
onMessage(message: InboundMessage): Promise<void>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The broker as the event bus sees it: the one port the Kafka client implements. */
|
|
30
|
+
export interface EventTransport {
|
|
31
|
+
/** Appends the messages to their topics. */
|
|
32
|
+
send(messages: ReadonlyArray<OutboundMessage>): Promise<void>
|
|
33
|
+
/** Starts reading the topics for the consumer group of the service. */
|
|
34
|
+
subscribe(subscription: EventSubscription): Promise<void>
|
|
35
|
+
/** How many messages of the topic the consumer group of the service has not acknowledged yet. */
|
|
36
|
+
lag(topic: string): Promise<number>
|
|
37
|
+
/** Every message of the topic, oldest first. */
|
|
38
|
+
read(topic: string): Promise<ReadonlyArray<InboundMessage>>
|
|
39
|
+
/** Resolves after `ms` milliseconds: the only clock of the retry backoff and the relay loop. */
|
|
40
|
+
wait(ms: number): Promise<void>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A header value of a message as the driver hands it over: text or binary, one or a list per name. */
|
|
44
|
+
export type KafkaMessageHeaders = Readonly<Record<string, Buffer | string | Array<Buffer | string> | undefined>>
|
|
45
|
+
|
|
46
|
+
/** The settings the module hands the factory when it builds the driver of the app. */
|
|
47
|
+
export interface KafkaDriverConfig {
|
|
48
|
+
/** The client id the broker sees: the consumer group of the app. */
|
|
49
|
+
readonly clientId: string
|
|
50
|
+
/** The broker addresses. */
|
|
51
|
+
readonly brokers: ReadonlyArray<string>
|
|
52
|
+
/** How long connecting may take, in milliseconds. */
|
|
53
|
+
readonly connectionTimeout: number
|
|
54
|
+
/** How long a broker request may take, in milliseconds. */
|
|
55
|
+
readonly requestTimeout: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** One message of a batch the producer appends. */
|
|
59
|
+
export interface KafkaBatchMessage {
|
|
60
|
+
/** The partition key. */
|
|
61
|
+
readonly key?: Buffer | string | null
|
|
62
|
+
/** The serialized value. */
|
|
63
|
+
readonly value: Buffer | string | null
|
|
64
|
+
/** The headers. */
|
|
65
|
+
readonly headers?: KafkaMessageHeaders
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The producer as the transport uses it. */
|
|
69
|
+
export interface KafkaProducer {
|
|
70
|
+
/** Opens the connection to the brokers. */
|
|
71
|
+
connect(): Promise<void>
|
|
72
|
+
/** Appends the grouped messages to their topics in one batch. */
|
|
73
|
+
sendBatch(batch: { topicMessages: Array<{ topic: string; messages: Array<KafkaBatchMessage> }> }): Promise<unknown>
|
|
74
|
+
/** Closes the connection. */
|
|
75
|
+
disconnect(): Promise<void>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** What a consumer of a topic reads from; either one topic or a list of them. */
|
|
79
|
+
export type KafkaConsumerSubscription =
|
|
80
|
+
| { readonly topics: Array<string>; readonly fromBeginning?: boolean }
|
|
81
|
+
| { readonly topic: string; readonly fromBeginning?: boolean }
|
|
82
|
+
|
|
83
|
+
/** The record of a consumed message as the driver hands it over. */
|
|
84
|
+
export interface KafkaConsumedRecord {
|
|
85
|
+
/** The offset on the partition, as text. */
|
|
86
|
+
readonly offset: string
|
|
87
|
+
/** The partition key. */
|
|
88
|
+
readonly key: Buffer | null
|
|
89
|
+
/** The serialized value. */
|
|
90
|
+
readonly value: Buffer | null
|
|
91
|
+
/** The headers; a message of the old wire format has none. */
|
|
92
|
+
readonly headers?: KafkaMessageHeaders
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A message as the driver hands it to the eachMessage callback. */
|
|
96
|
+
export interface KafkaConsumedMessage {
|
|
97
|
+
/** The topic the message was read from. */
|
|
98
|
+
readonly topic: string
|
|
99
|
+
/** The partition. */
|
|
100
|
+
readonly partition: number
|
|
101
|
+
/** The record. */
|
|
102
|
+
readonly message: KafkaConsumedRecord
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The callbacks a consumer runs per message; the only one the transport sets. */
|
|
106
|
+
export interface KafkaConsumerRun {
|
|
107
|
+
/** Handles one message. */
|
|
108
|
+
eachMessage?(payload: KafkaConsumedMessage): Promise<void>
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The consumer as the transport uses it. */
|
|
112
|
+
export interface KafkaConsumer {
|
|
113
|
+
/** Opens the connection and joins the group. */
|
|
114
|
+
connect(): Promise<void>
|
|
115
|
+
/** Subscribes to the topics. */
|
|
116
|
+
subscribe(subscription: KafkaConsumerSubscription): Promise<void>
|
|
117
|
+
/** Starts delivering messages. */
|
|
118
|
+
run(config: KafkaConsumerRun): Promise<void>
|
|
119
|
+
/** Leaves the group and closes the connection. */
|
|
120
|
+
disconnect(): Promise<void>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The end offsets of one partition as the broker answers them. */
|
|
124
|
+
export interface KafkaPartitionOffsets {
|
|
125
|
+
/** The partition. */
|
|
126
|
+
readonly partition: number
|
|
127
|
+
/** The offset of the latest record, as text. */
|
|
128
|
+
readonly offset: string
|
|
129
|
+
/** The log start offset, as text. */
|
|
130
|
+
readonly low: string
|
|
131
|
+
/** The log end offset, as text. */
|
|
132
|
+
readonly high: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The offsets a consumer group committed on one topic. */
|
|
136
|
+
export interface KafkaGroupOffsets {
|
|
137
|
+
/** The topic. */
|
|
138
|
+
readonly topic: string
|
|
139
|
+
/** The committed offset per partition. */
|
|
140
|
+
readonly partitions: Array<{
|
|
141
|
+
readonly partition: number
|
|
142
|
+
readonly offset: string
|
|
143
|
+
readonly metadata: string | null
|
|
144
|
+
}>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The admin client as the transport uses it. */
|
|
148
|
+
export interface KafkaAdmin {
|
|
149
|
+
/** Opens the connection. */
|
|
150
|
+
connect(): Promise<void>
|
|
151
|
+
/** Closes the connection. */
|
|
152
|
+
disconnect(): Promise<void>
|
|
153
|
+
/** The topics the broker knows. */
|
|
154
|
+
listTopics(): Promise<Array<string>>
|
|
155
|
+
/** The end offsets of every partition of the topic. */
|
|
156
|
+
fetchTopicOffsets(topic: string): Promise<Array<KafkaPartitionOffsets>>
|
|
157
|
+
/** The offsets the group committed on the topics. */
|
|
158
|
+
fetchOffsets(options: {
|
|
159
|
+
readonly groupId: string
|
|
160
|
+
readonly topics?: Array<string>
|
|
161
|
+
}): Promise<Array<KafkaGroupOffsets>>
|
|
162
|
+
/** Deletes the consumer groups. */
|
|
163
|
+
deleteGroups(groupIds: Array<string>): Promise<Array<unknown>>
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The driver of the brokers: the minimal shape of the library object the client uses, so the client never imports it. */
|
|
167
|
+
export interface KafkaDriver {
|
|
168
|
+
/** The producer of the app. */
|
|
169
|
+
producer(options: { readonly allowAutoTopicCreation?: boolean }): KafkaProducer
|
|
170
|
+
/** A consumer of a group. */
|
|
171
|
+
consumer(options: { readonly groupId: string; readonly allowAutoTopicCreation?: boolean }): KafkaConsumer
|
|
172
|
+
/** The admin client. */
|
|
173
|
+
admin(): KafkaAdmin
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Builds the Kafka driver; the module provides it so a spec can double it through the injection token. */
|
|
177
|
+
export interface KafkaFactory {
|
|
178
|
+
/** The driver over the brokers the config names. */
|
|
179
|
+
create(config: KafkaDriverConfig): KafkaDriver
|
|
180
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ATTEMPTS,
|
|
3
|
+
ATTEMPT_HEADER,
|
|
4
|
+
BACKOFF_MS,
|
|
5
|
+
DEAD_LETTER_ID_SEPARATOR,
|
|
6
|
+
NOT_BEFORE_HEADER,
|
|
7
|
+
ORIGIN_HEADER,
|
|
8
|
+
REASON_HEADER,
|
|
9
|
+
REQUEUED_HEADER,
|
|
10
|
+
backoffMs,
|
|
11
|
+
deadLetterTopicOf,
|
|
12
|
+
retryTopicOf,
|
|
13
|
+
serviceOf,
|
|
14
|
+
topicOf,
|
|
15
|
+
} from "./event.policy"
|
|
16
|
+
|
|
17
|
+
describe("event policies", () => {
|
|
18
|
+
it("declares the delivery and wire constants", () => {
|
|
19
|
+
expect({
|
|
20
|
+
attempts: ATTEMPTS,
|
|
21
|
+
backoffMs: BACKOFF_MS,
|
|
22
|
+
attemptHeader: ATTEMPT_HEADER,
|
|
23
|
+
notBeforeHeader: NOT_BEFORE_HEADER,
|
|
24
|
+
reasonHeader: REASON_HEADER,
|
|
25
|
+
originHeader: ORIGIN_HEADER,
|
|
26
|
+
requeuedHeader: REQUEUED_HEADER,
|
|
27
|
+
deadLetterIdSeparator: DEAD_LETTER_ID_SEPARATOR,
|
|
28
|
+
}).toEqual({
|
|
29
|
+
attempts: 5,
|
|
30
|
+
backoffMs: 500,
|
|
31
|
+
attemptHeader: "attempt",
|
|
32
|
+
notBeforeHeader: "not-before",
|
|
33
|
+
reasonHeader: "reason",
|
|
34
|
+
originHeader: "origin-topic",
|
|
35
|
+
requeuedHeader: "requeued",
|
|
36
|
+
deadLetterIdSeparator: "|",
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it.each([
|
|
41
|
+
["order.created", "order"],
|
|
42
|
+
["order", "order"],
|
|
43
|
+
["", ""],
|
|
44
|
+
])("reads the service of %s", (eventName, service) => {
|
|
45
|
+
expect(serviceOf(eventName)).toBe(service)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it("builds the main topic from the prefix and event service", () => {
|
|
49
|
+
expect(topicOf("run-1.", "order.created")).toBe("run-1.events.order")
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("builds the retry topic from the main topic", () => {
|
|
53
|
+
expect(retryTopicOf("run-1.", "order.created")).toBe("run-1.events.order.retry")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("builds the dead-letter topic from the main topic", () => {
|
|
57
|
+
expect(deadLetterTopicOf("run-1.", "order.created")).toBe("run-1.events.order.dlq")
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it.each([
|
|
61
|
+
[1, 500],
|
|
62
|
+
[2, 1000],
|
|
63
|
+
[4, 4000],
|
|
64
|
+
])("doubles the backoff after delivery %i", (attempt, delay) => {
|
|
65
|
+
expect(backoffMs(attempt)).toBe(delay)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** How many deliveries an event gets before it is a dead letter. */
|
|
2
|
+
export const ATTEMPTS = 5
|
|
3
|
+
|
|
4
|
+
/** The pause after the first failed delivery; it doubles with every further failure. */
|
|
5
|
+
export const BACKOFF_MS = 500
|
|
6
|
+
|
|
7
|
+
/** The header that carries the number of the delivery a retry message stands for. */
|
|
8
|
+
export const ATTEMPT_HEADER = "attempt"
|
|
9
|
+
|
|
10
|
+
/** The header that carries the instant before which a retry message must not be delivered again. */
|
|
11
|
+
export const NOT_BEFORE_HEADER = "not-before"
|
|
12
|
+
|
|
13
|
+
/** The header that carries why a message was buried. */
|
|
14
|
+
export const REASON_HEADER = "reason"
|
|
15
|
+
|
|
16
|
+
/** The header of a dead-letter message that names the main topic its event travelled on. */
|
|
17
|
+
export const ORIGIN_HEADER = "origin-topic"
|
|
18
|
+
|
|
19
|
+
/** The header of a marker message that says which dead letter was requeued. */
|
|
20
|
+
export const REQUEUED_HEADER = "requeued"
|
|
21
|
+
|
|
22
|
+
/** What joins the topic, partition and offset in the id of a dead letter. */
|
|
23
|
+
export const DEAD_LETTER_ID_SEPARATOR = "|"
|
|
24
|
+
|
|
25
|
+
/** The service an event belongs to: the part of its name before the first dot. */
|
|
26
|
+
export const serviceOf = (eventName: string): string => eventName.replace(/\..*$/s, "")
|
|
27
|
+
|
|
28
|
+
/** The topic the events of a service travel on. */
|
|
29
|
+
export const topicOf = (prefix: string, eventName: string): string => `${prefix}events.${serviceOf(eventName)}`
|
|
30
|
+
|
|
31
|
+
/** The topic a failed delivery waits on until its backoff has passed. */
|
|
32
|
+
export const retryTopicOf = (prefix: string, eventName: string): string => `${topicOf(prefix, eventName)}.retry`
|
|
33
|
+
|
|
34
|
+
/** The topic an event goes to when it ran out of attempts. */
|
|
35
|
+
export const deadLetterTopicOf = (prefix: string, eventName: string): string => `${topicOf(prefix, eventName)}.dlq`
|
|
36
|
+
|
|
37
|
+
/** The pause before the delivery that follows the failed delivery number `attempt`: it doubles with every failure. */
|
|
38
|
+
export const backoffMs = (attempt: number): number => BACKOFF_MS * 2 ** (attempt - 1)
|