@starci/hfs 4.0.10 → 4.2.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 +5 -1
- package/emit/operations.mjs +1 -1
- package/emit/type-schema.mjs +2 -1
- package/lint/run.mjs +65 -24
- package/package.json +1 -1
- package/report/order.mjs +2 -0
- package/runtime/config.example.yaml +194 -0
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +222 -239
- package/runtime/engine/invalid-config.mjs +5 -5
- package/runtime/engine/model-config.mjs +90 -0
- package/runtime/engine/orca-config.mjs +3 -1
- package/runtime/engine/release-config.mjs +28 -0
- package/runtime/engine/removed-vocabulary.mjs +28 -0
- package/runtime/engine/resources-config.mjs +52 -0
- package/runtime/engine/runtime-root.mjs +17 -0
- package/runtime/engine/secrets.mjs +35 -31
- package/runtime/engine/sonar-config.mjs +21 -0
- package/runtime/engine/temp-root.mjs +32 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
- package/runtime/knowledge/hfs/rules.yaml +161 -65
- package/runtime/knowledge/hfs/slots.yaml +11 -14
- package/runtime/knowledge/patterns/be/api.yaml +7 -24
- package/runtime/knowledge/patterns/be/cli.yaml +3 -12
- package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
- package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
- package/runtime/modules/kernel/failure-codes.yaml +1 -41
- package/runtime/modules/kernel/removed-vocabulary.yaml +268 -0
- package/runtime/modules/models/registry.yaml +25 -359
- package/runtime/modules/models/runtimes.yaml +126 -265
- package/runtime/modules/models/tiers.yaml +66 -0
- package/runtime/scripts/api/fs/claim-file.mjs +30 -0
- package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
- package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
- package/runtime/scripts/api/git/lib.mjs +34 -1
- package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
- package/runtime/scripts/api/process/run-program.mjs +2 -1
- package/runtime/scripts/api/sops/lib.mjs +198 -120
- package/runtime/scripts/hfs/allows.mjs +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
- package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
- package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
- package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
- package/runtime/scripts/hfs/architecture/config.mjs +95 -80
- package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
- package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
- package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
- package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
- package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
- package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
- package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
- package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
- package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
- package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
- package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
- package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
- package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
- package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
- package/runtime/scripts/hfs/architecture/index.mjs +164 -104
- package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
- package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
- package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
- package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
- package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
- package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
- package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
- package/runtime/scripts/hfs/architecture/required-files.mjs +121 -70
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
- package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
- package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
- package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
- package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
- package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
- package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
- package/runtime/scripts/hfs/architecture.mjs +3 -4
- package/runtime/scripts/hfs/catalog-once.mjs +15 -0
- package/runtime/scripts/hfs/check.mjs +63 -71
- package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
- package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
- package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
- package/runtime/scripts/hfs/edition-slots.mjs +5 -3
- package/runtime/scripts/hfs/linear-text.mjs +30 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
- package/runtime/scripts/hfs/path-findings.mjs +58 -38
- package/runtime/scripts/hfs/pin-findings.mjs +31 -0
- package/runtime/scripts/hfs/repo-identity.mjs +21 -4
- package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
- package/runtime/scripts/hfs/rules/cli.mjs +5 -1
- package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
- package/runtime/scripts/hfs/rules/contract.mjs +35 -27
- package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
- package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
- package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
- package/runtime/scripts/hfs/rules/database.mjs +36 -23
- package/runtime/scripts/hfs/rules/deps.mjs +64 -32
- package/runtime/scripts/hfs/rules/docker.mjs +113 -65
- package/runtime/scripts/hfs/rules/edition.mjs +118 -111
- package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
- package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
- package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
- package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
- package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
- package/runtime/scripts/hfs/rules/saga.mjs +128 -61
- package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
- package/runtime/scripts/hfs/rules/services.mjs +34 -16
- package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
- package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
- package/runtime/scripts/hfs/secret.mjs +59 -48
- package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
- package/runtime/scripts/hfs/slot-classify.mjs +65 -0
- package/runtime/scripts/hfs/slot-errors.mjs +11 -0
- package/runtime/scripts/hfs/slot-imports.mjs +76 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
- package/runtime/scripts/hfs/slot-match.mjs +153 -0
- package/runtime/scripts/hfs/slot-path.mjs +8 -0
- package/runtime/scripts/hfs/slot-required.mjs +76 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
- package/runtime/scripts/hfs/slots.mjs +74 -695
- package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
- package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
- package/runtime/scripts/hfs/tree.mjs +19 -14
- package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
- package/runtime/scripts/hfs/view.mjs +1 -1
- package/runtime/scripts/lib/dockerfile.mjs +56 -33
- package/runtime/scripts/lib/env.mjs +10 -0
- package/runtime/scripts/lib/event-contract.mjs +17 -13
- package/runtime/scripts/lib/git.mjs +2 -2
- package/runtime/scripts/lib/graphql-contract.mjs +158 -322
- package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
- package/runtime/scripts/lib/i18n.mjs +3 -2
- package/runtime/scripts/lib/in-order.mjs +71 -0
- package/runtime/scripts/lib/language.mjs +3 -3
- package/runtime/scripts/lib/list.mjs +8 -1
- package/runtime/scripts/lib/path-key.mjs +38 -5
- package/runtime/scripts/lib/pid-alive.mjs +7 -0
- package/runtime/scripts/lib/regex.mjs +1 -1
- package/runtime/scripts/lib/same-text.mjs +1 -1
- package/runtime/scripts/lib/secret-patterns.mjs +6 -2
- package/runtime/scripts/lib/sleep-sync.mjs +2 -2
- package/runtime/scripts/lib/sops-envelope.mjs +95 -48
- package/runtime/scripts/lib/stack-services.mjs +1 -2
- package/runtime/scripts/lib/ts-ast.mjs +9 -0
- package/runtime/scripts/lib/walk.mjs +6 -1
- package/runtime/scripts/lib/yaml-cached.mjs +14 -0
- package/scaffold/add-cli-lite.mjs +2 -1
- package/scaffold/add-table.mjs +2 -2
- package/scaffold/add.mjs +3 -2
- package/scaffold/app.mjs +4 -2
- package/scaffold/edition-gate.mjs +25 -25
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +5 -4
- package/sync/hygiene.mjs +15 -9
- package/sync/index.mjs +7 -3
- package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
- package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
- package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
- package/templates/app/ci-workflows/sonar-steps.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
- package/templates/app/starciwork.gitignore +1 -1
- package/templates/be/image/api/Dockerfile +1 -1
- package/templates/be/image/cli/Dockerfile +1 -1
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
- package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/fe/image/next/Dockerfile +2 -1
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
- package/upgrade/index.mjs +9 -2
- package/runtime/engine/admission.mjs +0 -308
- package/runtime/engine/canonical-json.mjs +0 -10
- package/runtime/engine/db/blob.mjs +0 -315
- package/runtime/engine/db/ledger-paths.mjs +0 -83
- package/runtime/engine/db/ledger.mjs +0 -1118
- package/runtime/engine/db/machine-connection.mjs +0 -135
- package/runtime/engine/db/machine-schema.mjs +0 -84
- package/runtime/engine/db/machine.mjs +0 -1367
- package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
- package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
- package/runtime/engine/db/provider-reservations.mjs +0 -101
- package/runtime/engine/digest.mjs +0 -16
- package/runtime/engine/refuse.mjs +0 -11
- package/runtime/modules/ops/_labels.yaml +0 -53
- package/runtime/scripts/api/node/lib.mjs +0 -14
- package/runtime/scripts/api/node/spawn-node.mjs +0 -6
- package/runtime/scripts/api/process/lib.mjs +0 -111
- package/runtime/scripts/api/process/owned-process.mjs +0 -78
- package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
- package/runtime/scripts/connectors/lib.mjs +0 -488
- package/runtime/scripts/lib/clip.mjs +0 -19
- package/runtime/scripts/lib/display-names.mjs +0 -259
- package/runtime/scripts/lib/example-refs.mjs +0 -158
- package/runtime/scripts/lib/json-schema.mjs +0 -52
- package/runtime/scripts/lib/process-identity.mjs +0 -6
- package/runtime/scripts/lib/read-yaml.mjs +0 -18
- package/runtime/scripts/lib/redact.mjs +0 -161
- package/runtime/scripts/lib/sleep.mjs +0 -7
- package/runtime/scripts/lib/source-phrases.mjs +0 -34
- package/runtime/scripts/lib/sqlite.mjs +0 -21
|
@@ -20,12 +20,12 @@ jobs:
|
|
|
20
20
|
env:
|
|
21
21
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
22
22
|
steps:
|
|
23
|
-
- uses: actions/checkout@v4
|
|
24
|
-
- uses: actions/setup-node@v4
|
|
23
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
24
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
25
25
|
with:
|
|
26
26
|
node-version: {{nodeMajor}}
|
|
27
27
|
cache: npm
|
|
28
|
-
- run: npm ci
|
|
28
|
+
- run: npm ci --ignore-scripts
|
|
29
29
|
- name: lint
|
|
30
30
|
if: env.SONAR_TOKEN == ''
|
|
31
31
|
run: npm run lint
|
|
@@ -47,12 +47,12 @@ jobs:
|
|
|
47
47
|
db-types:
|
|
48
48
|
runs-on: ubuntu-latest
|
|
49
49
|
steps:
|
|
50
|
-
- uses: actions/checkout@v4
|
|
51
|
-
- uses: actions/setup-node@v4
|
|
50
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
51
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
52
52
|
with:
|
|
53
53
|
node-version: {{nodeMajor}}
|
|
54
54
|
cache: npm
|
|
55
|
-
- run: npm ci
|
|
55
|
+
- run: npm ci --ignore-scripts
|
|
56
56
|
- name: start Supabase
|
|
57
57
|
run: npm run db:start
|
|
58
58
|
- name: apply migrations
|
|
@@ -21,11 +21,11 @@ jobs:
|
|
|
21
21
|
SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }}
|
|
22
22
|
SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}
|
|
23
23
|
steps:
|
|
24
|
-
- uses: actions/checkout@v4
|
|
25
|
-
- uses: actions/setup-node@v4
|
|
24
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
25
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
26
26
|
with:
|
|
27
27
|
node-version: {{nodeMajor}}
|
|
28
28
|
cache: npm
|
|
29
|
-
- run: npm ci
|
|
29
|
+
- run: npm ci --ignore-scripts
|
|
30
30
|
- name: push migrations
|
|
31
31
|
run: npm run db:push
|
|
@@ -25,9 +25,9 @@ jobs:
|
|
|
25
25
|
include:
|
|
26
26
|
{{imageMatrix}}
|
|
27
27
|
steps:
|
|
28
|
-
- uses: actions/checkout@v4
|
|
29
|
-
- uses: docker/setup-buildx-action@v3
|
|
30
|
-
- uses: docker/build-push-action@v6
|
|
28
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
29
|
+
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
|
30
|
+
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
|
31
31
|
with:
|
|
32
32
|
context: .
|
|
33
33
|
file: ${{ matrix.file }}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# StarCi .starciwork: product content only (
|
|
1
|
+
# StarCi .starciwork: product content only (work-layout.yaml shape.productPaths).
|
|
2
2
|
# Agent output (reports, checks, captures, draw rounds, UAT runs, logs, ledgers, worktrees) lives in
|
|
3
3
|
# runtime.sqlite rows and blobs outside the repository.
|
|
4
4
|
/*
|
|
@@ -19,7 +19,7 @@ RUN npm ci --ignore-scripts
|
|
|
19
19
|
|
|
20
20
|
COPY be/tsconfig.json be/tsconfig.build.json be/
|
|
21
21
|
COPY be/src be/src
|
|
22
|
-
COPY be/apps/{{app}} be/apps/{{app}}
|
|
22
|
+
{{copyBeInputs}}COPY be/apps/{{app}} be/apps/{{app}}
|
|
23
23
|
RUN npm run build:be
|
|
24
24
|
|
|
25
25
|
FROM {{nodeImage}} AS runtime
|
|
@@ -20,7 +20,7 @@ RUN npm ci --ignore-scripts
|
|
|
20
20
|
|
|
21
21
|
COPY be/tsconfig.json be/tsconfig.build.json be/
|
|
22
22
|
COPY be/src be/src
|
|
23
|
-
COPY be/apps/{{app}} be/apps/{{app}}
|
|
23
|
+
{{copyBeInputs}}COPY be/apps/{{app}} be/apps/{{app}}
|
|
24
24
|
RUN npm run build:be
|
|
25
25
|
|
|
26
26
|
FROM {{nodeImage}} AS runtime
|
|
@@ -19,7 +19,7 @@ RUN npm ci --ignore-scripts
|
|
|
19
19
|
|
|
20
20
|
COPY be/tsconfig.json be/tsconfig.build.json be/
|
|
21
21
|
COPY be/src be/src
|
|
22
|
-
COPY be/apps/{{app}} be/apps/{{app}}
|
|
22
|
+
{{copyBeInputs}}COPY be/apps/{{app}} be/apps/{{app}}
|
|
23
23
|
RUN npm run build:be
|
|
24
24
|
|
|
25
25
|
FROM {{nodeImage}} AS runtime
|
|
@@ -8,7 +8,7 @@ describe("@@Group@@Cli", () => {
|
|
|
8
8
|
const help = jest.fn()
|
|
9
9
|
Object.defineProperty(group, "command", { value: { help } })
|
|
10
10
|
|
|
11
|
-
await expect(group.run()).resolves.toBeUndefined()
|
|
11
|
+
await expect(Promise.resolve(group.run())).resolves.toBeUndefined()
|
|
12
12
|
|
|
13
13
|
expect(help).toHaveBeenCalledTimes(1)
|
|
14
14
|
})
|
|
@@ -5,7 +5,7 @@ import { RunCli } from "./subs/run.cli"
|
|
|
5
5
|
/** `cli @@group@@`: the @@group@@ group; its commands are the sub-commands (`cli @@group@@ run`). */
|
|
6
6
|
export class @@Group@@Cli extends CommandRunner {
|
|
7
7
|
/** Without a sub-command the group shows its help. */
|
|
8
|
-
|
|
9
|
-
this.command.help()
|
|
8
|
+
run(): Promise<void> {
|
|
9
|
+
return this.command.help()
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -197,6 +197,19 @@ describe("EventRunnerService", () => {
|
|
|
197
197
|
])
|
|
198
198
|
})
|
|
199
199
|
|
|
200
|
+
it("buries once and runs no later consumer of the event when an envelope cannot be read", async () => {
|
|
201
|
+
const { runner, handle, transport } = await build()
|
|
202
|
+
const later = jest.fn<Promise<void>, Array<never>>()
|
|
203
|
+
runner.add({ event: PingEvent, handle: later })
|
|
204
|
+
const value = JSON.stringify({ eventId: "o-2", eventName: "probe.ping", payload: {} })
|
|
205
|
+
|
|
206
|
+
await runner.receive(message({ key: "o-2", value }))
|
|
207
|
+
|
|
208
|
+
expect(handle).not.toHaveBeenCalled()
|
|
209
|
+
expect(later).not.toHaveBeenCalled()
|
|
210
|
+
expect(transport.send).toHaveBeenCalledTimes(1)
|
|
211
|
+
})
|
|
212
|
+
|
|
200
213
|
it("puts a failed delivery on the retry topic with the next attempt and a doubled backoff", async () => {
|
|
201
214
|
const { runner, handle, transport, logger } = await build()
|
|
202
215
|
const failure = new Error("the database is down")
|
|
@@ -4,6 +4,7 @@ import { InjectClock } from "@modules/platform/clock"
|
|
|
4
4
|
import type { Clock } from "@modules/platform/clock"
|
|
5
5
|
import { InjectLogger } from "@modules/platform/logging"
|
|
6
6
|
import type { Logger } from "@modules/platform/logging"
|
|
7
|
+
import { eachInOrder } from "@modules/platform/primitives"
|
|
7
8
|
import type { BaseEvent } from "./event-bus.contracts"
|
|
8
9
|
import { InjectEventBusOptions, InjectEventTransport } from "./event-bus.decorators"
|
|
9
10
|
import { EventBusLogEvent } from "./event-bus.log-events"
|
|
@@ -74,9 +75,12 @@ export class EventRunnerService implements EventConsumerRegistry, OnApplicationB
|
|
|
74
75
|
const pause = notBefore - this.clock.now().getTime()
|
|
75
76
|
if (pause > 0) await this.transport.wait(pause)
|
|
76
77
|
const failures: Array<unknown> = []
|
|
77
|
-
|
|
78
|
+
let buried = false
|
|
79
|
+
await eachInOrder(consumers, async (consumer) => {
|
|
80
|
+
if (buried) return
|
|
78
81
|
const event = consumer.event.parse(envelope)
|
|
79
82
|
if (event === null) {
|
|
83
|
+
buried = true
|
|
80
84
|
await this.bury(message, eventName, attempt, "the envelope does not have the shape of the event")
|
|
81
85
|
return
|
|
82
86
|
}
|
|
@@ -86,7 +90,8 @@ export class EventRunnerService implements EventConsumerRegistry, OnApplicationB
|
|
|
86
90
|
this.logger.error(EventBusLogEvent.DeliveryFailed, cause, { event: eventName, attempt })
|
|
87
91
|
failures.push(cause)
|
|
88
92
|
}
|
|
89
|
-
}
|
|
93
|
+
})
|
|
94
|
+
if (buried) return
|
|
90
95
|
const [first] = failures
|
|
91
96
|
if (failures.length > 0) {
|
|
92
97
|
await this.failed(message, eventName, attempt, first instanceof Error ? first.message : String(first))
|
|
@@ -23,7 +23,10 @@ export const REQUEUED_HEADER = "requeued"
|
|
|
23
23
|
export const DEAD_LETTER_ID_SEPARATOR = "|"
|
|
24
24
|
|
|
25
25
|
/** The service an event belongs to: the part of its name before the first dot. */
|
|
26
|
-
export const serviceOf = (eventName: string): string =>
|
|
26
|
+
export const serviceOf = (eventName: string): string => {
|
|
27
|
+
const dot = eventName.indexOf(".")
|
|
28
|
+
return dot === -1 ? eventName : eventName.slice(0, dot)
|
|
29
|
+
}
|
|
27
30
|
|
|
28
31
|
/** The topic the events of a service travel on. */
|
|
29
32
|
export const topicOf = (prefix: string, eventName: string): string => `${prefix}events.${serviceOf(eventName)}`
|
|
@@ -2,6 +2,7 @@ import { Injectable } from "@nestjs/common"
|
|
|
2
2
|
import type { OnApplicationShutdown } from "@nestjs/common"
|
|
3
3
|
import { InjectIds } from "@modules/platform/ids"
|
|
4
4
|
import type { Ids } from "@modules/platform/ids"
|
|
5
|
+
import { eachInOrder } from "@modules/platform/primitives"
|
|
5
6
|
import type { Probe } from "@modules/platform/probes"
|
|
6
7
|
import { EventBusError, EventBusErrorCode } from "./errors/event-bus.error"
|
|
7
8
|
import { InjectEventBusOptions, InjectKafkaFactory } from "./event-bus.decorators"
|
|
@@ -181,7 +182,7 @@ export class KafkaEventTransportClient implements EventTransport, Probe, OnAppli
|
|
|
181
182
|
|
|
182
183
|
/** Disconnects the producer, the consumer and the admin client. */
|
|
183
184
|
async onApplicationShutdown(): Promise<void> {
|
|
184
|
-
|
|
185
|
+
await eachInOrder([this.producer, this.consumer, this.admin], (client) => this.close(client))
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
/** The end offsets of the topic; a topic that does not exist yet answers no partitions and the cause. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OnApplicationBootstrap, OnApplicationShutdown } from "@nestjs/common"
|
|
2
2
|
import type { EntityManager } from "typeorm"
|
|
3
3
|
import type { Logger } from "@modules/platform/logging"
|
|
4
|
+
import { eachInOrder } from "@modules/platform/primitives"
|
|
4
5
|
import { OutboxLogEvent } from "./outbox.log-events"
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -28,7 +29,9 @@ export abstract class OutboxRelayPolicy<Row extends { readonly id: string }>
|
|
|
28
29
|
/** One pass over every connection: relays the oldest waiting rows of each and answers how many it relayed. */
|
|
29
30
|
async relay(): Promise<number> {
|
|
30
31
|
let relayed = 0
|
|
31
|
-
|
|
32
|
+
await eachInOrder(this.managers, async (manager) => {
|
|
33
|
+
relayed += await this.relayOf(manager)
|
|
34
|
+
})
|
|
32
35
|
return relayed
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -66,13 +69,25 @@ export abstract class OutboxRelayPolicy<Row extends { readonly id: string }>
|
|
|
66
69
|
})
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
/** One pass, then the next, until the app stops: each pass starts from the settled promise of the last, so the loop holds no growing chain. */
|
|
73
|
+
private run(): Promise<void> {
|
|
74
|
+
return new Promise<void>((resolve, reject) => {
|
|
75
|
+
const next = (): void => {
|
|
76
|
+
if (!this.running) {
|
|
77
|
+
resolve()
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
void this.pass().then(next, reject)
|
|
81
|
+
}
|
|
82
|
+
next()
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private async pass(): Promise<void> {
|
|
87
|
+
const relayed = await this.relay().catch((cause: unknown) => {
|
|
88
|
+
this.logger.error(OutboxLogEvent.RelayFailed, cause, { outbox: this.outbox })
|
|
89
|
+
return 0
|
|
90
|
+
})
|
|
91
|
+
if (relayed === 0 && this.running) await this.idle()
|
|
77
92
|
}
|
|
78
93
|
}
|
|
@@ -4,6 +4,7 @@ import { InjectClock } from "@modules/platform/clock"
|
|
|
4
4
|
import type { Clock } from "@modules/platform/clock"
|
|
5
5
|
import { InjectLogger } from "@modules/platform/logging"
|
|
6
6
|
import type { Logger } from "@modules/platform/logging"
|
|
7
|
+
import { eachInOrder } from "@modules/platform/primitives"
|
|
7
8
|
import { OutboxRelayPolicy } from "@modules/platform/outbox"
|
|
8
9
|
import { InjectQueueOptions, InjectQueueRelayManagers, InjectQueueTransport } from "./queue.decorators"
|
|
9
10
|
import type { QueueOptions } from "./queue.options"
|
|
@@ -34,8 +35,8 @@ export class QueueRelayService extends OutboxRelayPolicy<QueueRow> {
|
|
|
34
35
|
return tx.query(SELECT_WAITING_ROWS, [this.options.relayBatch])
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
protected
|
|
38
|
-
|
|
38
|
+
protected deliver(rows: ReadonlyArray<QueueRow>): Promise<void> {
|
|
39
|
+
return eachInOrder(rows, (row) => this.transport.add(row.queue, row.id, row.payload))
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
protected async mark(tx: EntityManager, rows: ReadonlyArray<QueueRow>): Promise<void> {
|
|
@@ -2,6 +2,7 @@ import { Injectable } from "@nestjs/common"
|
|
|
2
2
|
import type { OnApplicationBootstrap, OnApplicationShutdown } from "@nestjs/common"
|
|
3
3
|
import { InjectLogger } from "@modules/platform/logging"
|
|
4
4
|
import type { Logger } from "@modules/platform/logging"
|
|
5
|
+
import { eachInOrder } from "@modules/platform/primitives"
|
|
5
6
|
import type { QueueHandler } from "./queue.contracts"
|
|
6
7
|
import { InjectQueueOptions, InjectQueueTransport } from "./queue.decorators"
|
|
7
8
|
import { QueueLogEvent } from "./queue.log-events"
|
|
@@ -30,12 +31,14 @@ export class QueueWorkerService implements QueueWorkerRegistry, OnApplicationBoo
|
|
|
30
31
|
|
|
31
32
|
/** Starts one worker per registered queue, then registers the schedulers of the options. */
|
|
32
33
|
async onApplicationBootstrap(): Promise<void> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
await eachInOrder([...this.handlers.values()], (handler) =>
|
|
35
|
+
this.transport.work(handler, this.options.concurrency),
|
|
36
|
+
)
|
|
37
|
+
await eachInOrder(this.options.schedulers, (scheduler) =>
|
|
38
|
+
this.transport.upsertScheduler(scheduler).catch((cause: unknown) => {
|
|
36
39
|
this.logger.error(QueueLogEvent.SchedulerFailed, cause, { scheduler: scheduler.id })
|
|
37
|
-
})
|
|
38
|
-
|
|
40
|
+
}),
|
|
41
|
+
)
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/** Closes the workers and the queues. */
|
|
@@ -10,7 +10,7 @@ describe("MigrateCli", () => {
|
|
|
10
10
|
const help = jest.fn()
|
|
11
11
|
Object.defineProperty(group, "command", { value: { help } })
|
|
12
12
|
|
|
13
|
-
await expect(group.run()).resolves.toBeUndefined()
|
|
13
|
+
await expect(Promise.resolve(group.run())).resolves.toBeUndefined()
|
|
14
14
|
|
|
15
15
|
expect(help).toHaveBeenCalledTimes(1)
|
|
16
16
|
})
|
|
@@ -9,7 +9,7 @@ import { RunCli } from "./subs/run.cli"
|
|
|
9
9
|
/** `cli migrate`: the migration group; its commands are the sub-commands (`cli migrate run`). */
|
|
10
10
|
export class MigrateCli extends CommandRunner {
|
|
11
11
|
/** Without a sub-command the group shows its help. */
|
|
12
|
-
|
|
13
|
-
this.command.help()
|
|
12
|
+
run(): Promise<void> {
|
|
13
|
+
return this.command.help()
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -10,7 +10,7 @@ describe("SeedCli", () => {
|
|
|
10
10
|
const help = jest.fn()
|
|
11
11
|
Object.defineProperty(group, "command", { value: { help } })
|
|
12
12
|
|
|
13
|
-
await expect(group.run()).resolves.toBeUndefined()
|
|
13
|
+
await expect(Promise.resolve(group.run())).resolves.toBeUndefined()
|
|
14
14
|
|
|
15
15
|
expect(help).toHaveBeenCalledTimes(1)
|
|
16
16
|
})
|
|
@@ -9,7 +9,7 @@ import { RunSeedsCli } from "./subs/run.cli"
|
|
|
9
9
|
/** `cli seed`: the seed group; its commands are the sub-commands (`cli seed run`). */
|
|
10
10
|
export class SeedCli extends CommandRunner {
|
|
11
11
|
/** Without a sub-command the group shows its help. */
|
|
12
|
-
|
|
13
|
-
this.command.help()
|
|
12
|
+
run(): Promise<void> {
|
|
13
|
+
return this.command.help()
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { eachInOrder } from "@modules/platform/primitives"
|
|
1
2
|
import type { DatabaseConnectionOptions } from "./database.options"
|
|
2
3
|
import type { ConnectionOpener } from "./database.port"
|
|
3
4
|
|
|
@@ -16,7 +17,7 @@ export async function migrateConnections(
|
|
|
16
17
|
opener: ConnectionOpener,
|
|
17
18
|
): Promise<AppliedMigrations> {
|
|
18
19
|
const applied: Record<string, ReadonlyArray<string>> = {}
|
|
19
|
-
|
|
20
|
+
await eachInOrder(connections, async (connection) => {
|
|
20
21
|
const source = opener.open(connection)
|
|
21
22
|
await source.initialize()
|
|
22
23
|
try {
|
|
@@ -25,6 +26,6 @@ export async function migrateConnections(
|
|
|
25
26
|
} finally {
|
|
26
27
|
await source.destroy()
|
|
27
28
|
}
|
|
28
|
-
}
|
|
29
|
+
})
|
|
29
30
|
return applied
|
|
30
31
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, readdir } from "node:fs/promises"
|
|
2
2
|
import { join } from "node:path"
|
|
3
|
+
import { eachInOrder } from "@modules/platform/primitives"
|
|
3
4
|
import { seedText } from "./database.sql"
|
|
4
5
|
import type { SqlText } from "./database.sql"
|
|
5
6
|
import type { DatabaseConnectionOptions } from "./database.options"
|
|
@@ -56,18 +57,18 @@ export async function seedConnections(
|
|
|
56
57
|
opener: ConnectionOpener,
|
|
57
58
|
): Promise<SeedReport> {
|
|
58
59
|
const applied: Record<string, ReadonlyArray<string>> = {}
|
|
59
|
-
|
|
60
|
+
await eachInOrder(connections, async (connection) => {
|
|
60
61
|
const own = files.filter((file) => seeds(file, connection))
|
|
61
|
-
if (own.length === 0)
|
|
62
|
+
if (own.length === 0) return
|
|
62
63
|
const source = opener.open(connection)
|
|
63
64
|
await source.initialize()
|
|
64
65
|
try {
|
|
65
|
-
|
|
66
|
+
await eachInOrder(own, (file) => source.query(file.text))
|
|
66
67
|
} finally {
|
|
67
68
|
await source.destroy()
|
|
68
69
|
}
|
|
69
70
|
applied[connection.name] = own.map((file) => file.name)
|
|
70
|
-
}
|
|
71
|
+
})
|
|
71
72
|
const unmatched = files
|
|
72
73
|
.filter((file) => !connections.some((connection) => seeds(file, connection)))
|
|
73
74
|
.map((file) => file.name)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DomainError } from "@modules/platform/errors"
|
|
2
|
+
import { eachInOrder } from "./sequence.policy"
|
|
3
|
+
|
|
4
|
+
type SequenceErrorCode = "STEP_FAILED"
|
|
5
|
+
|
|
6
|
+
class SequenceError extends DomainError<SequenceErrorCode> {}
|
|
7
|
+
|
|
8
|
+
const pause = (): Promise<void> => new Promise((resolve) => setImmediate(resolve))
|
|
9
|
+
|
|
10
|
+
describe("eachInOrder", () => {
|
|
11
|
+
it("starts an item only after the one before it settled, in item order", async () => {
|
|
12
|
+
const events: Array<string> = []
|
|
13
|
+
|
|
14
|
+
await eachInOrder(new Set(["a", "b", "c"]), async (item, index) => {
|
|
15
|
+
events.push(`start ${item} ${index}`)
|
|
16
|
+
await pause()
|
|
17
|
+
events.push(`end ${item}`)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
expect(events).toEqual(["start a 0", "end a", "start b 1", "end b", "start c 2", "end c"])
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("answers nothing for an empty list", async () => {
|
|
24
|
+
await expect(eachInOrder([], () => Promise.resolve())).resolves.toBeUndefined()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("rejects with the first failure and never starts the items after it", async () => {
|
|
28
|
+
const started: Array<number> = []
|
|
29
|
+
|
|
30
|
+
const run = eachInOrder([1, 2, 3], async (item) => {
|
|
31
|
+
started.push(item)
|
|
32
|
+
await pause()
|
|
33
|
+
if (item === 2) throw new SequenceError({ code: "STEP_FAILED" })
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
await expect(run).rejects.toThrow(expect.objectContaining({ code: "STEP_FAILED" }))
|
|
37
|
+
expect(started).toEqual([1, 2])
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("rejects when a step throws before it returns a promise", async () => {
|
|
41
|
+
const run = eachInOrder([1], (): Promise<void> => {
|
|
42
|
+
throw new SequenceError({ code: "STEP_FAILED" })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
await expect(run).rejects.toThrow(expect.objectContaining({ code: "STEP_FAILED" }))
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** One step of a sequence: it receives the item and its index and answers a promise that settles when the step is done. */
|
|
2
|
+
type SequenceStep<Item> = (item: Item, index: number) => Promise<unknown>
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runs `step` over `items` one at a time: a step starts only after the one before it settled, so the order the caller relies on
|
|
6
|
+
* (ordered writes, one connection at a time, stop at the first failure) is the order in the code. The first rejection (or throw)
|
|
7
|
+
* rejects the answer and the items after it never start.
|
|
8
|
+
*/
|
|
9
|
+
export const eachInOrder = <Item>(items: Iterable<Item>, step: SequenceStep<Item>): Promise<void> =>
|
|
10
|
+
[...items].reduce<Promise<void>>(
|
|
11
|
+
(done, item, index) =>
|
|
12
|
+
done.then(async () => {
|
|
13
|
+
await step(item, index)
|
|
14
|
+
}),
|
|
15
|
+
Promise.resolve(),
|
|
16
|
+
)
|
|
@@ -23,8 +23,9 @@ COPY turbo.json ./
|
|
|
23
23
|
COPY scripts scripts
|
|
24
24
|
COPY fe/tsconfig.json fe/tsconfig.json
|
|
25
25
|
{{copyInputs}}COPY fe/apps/{{app}} fe/apps/{{app}}
|
|
26
|
+
RUN mkdir -p fe/apps/{{app}}/public
|
|
26
27
|
RUN npm run codegen --silent
|
|
27
|
-
RUN
|
|
28
|
+
RUN node_modules/.bin/turbo run build --filter=@{{project}}/{{app}}
|
|
28
29
|
|
|
29
30
|
FROM {{nodeImage}} AS runtime
|
|
30
31
|
WORKDIR /app
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AppHomePage
|
|
1
|
+
import { AppHomePage } from "@/features/pages/AppHomePage"
|
|
2
2
|
|
|
3
3
|
/** The document title of this page, from the catalog of the requested locale. */
|
|
4
|
-
export
|
|
4
|
+
export { appHomeMetadata as generateMetadata } from "@/features/pages/AppHomePage"
|
|
5
5
|
|
|
6
6
|
/** The locale root's page slot: it mounts the product app's home page and nothing else. */
|
|
7
7
|
const Page = () => <AppHomePage />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { LandingPage
|
|
1
|
+
import { LandingPage } from "@/features/pages/LandingPage"
|
|
2
2
|
|
|
3
3
|
/** The document title of this page, from the catalog of the requested locale. */
|
|
4
|
-
export
|
|
4
|
+
export { landingMetadata as generateMetadata } from "@/features/pages/LandingPage"
|
|
5
5
|
|
|
6
6
|
/** The locale root's page slot: it mounts the landing page and nothing else. */
|
|
7
7
|
const Page = () => <LandingPage />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "server-only"
|
|
2
2
|
import { hasLocale } from "next-intl"
|
|
3
3
|
import { createNavigation } from "next-intl/navigation"
|
|
4
|
-
import { getMessages, getRequestConfig
|
|
4
|
+
import { getMessages, getRequestConfig } from "next-intl/server"
|
|
5
5
|
import { notFound } from "next/navigation"
|
|
6
6
|
import { locale as routeLocale } from "next/root-params"
|
|
7
7
|
import { PRODUCT_TIME_ZONE, routing } from "./routing"
|
|
@@ -43,7 +43,6 @@ export const createAppI18n = (app: string, loadMessages: LoadMessages) => ({
|
|
|
43
43
|
readLocaleSegment: async (params: Promise<{ readonly locale: string }>) => {
|
|
44
44
|
const { locale } = await params
|
|
45
45
|
if (!hasLocale(routing.locales, locale)) notFound()
|
|
46
|
-
setRequestLocale(locale)
|
|
47
46
|
return { locale, messages: await getMessages(), timeZone: PRODUCT_TIME_ZONE }
|
|
48
47
|
},
|
|
49
48
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hasLocale } from "next-intl"
|
|
2
|
-
import { getMessages
|
|
2
|
+
import { getMessages } from "next-intl/server"
|
|
3
3
|
import { notFound } from "next/navigation"
|
|
4
4
|
import type { ReactNode } from "react"
|
|
5
5
|
import { AppLayout, appLayoutMetadata } from "@/features/layouts/AppLayout"
|
|
@@ -20,7 +20,6 @@ export const generateMetadata = async (props: LayoutProps) => appLayoutMetadata(
|
|
|
20
20
|
const Layout = async (props: LayoutProps) => {
|
|
21
21
|
const { locale } = await props.params
|
|
22
22
|
if (!hasLocale(routing.locales, locale)) notFound()
|
|
23
|
-
setRequestLocale(locale)
|
|
24
23
|
const messages = await getMessages()
|
|
25
24
|
return (
|
|
26
25
|
<AppLayout
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AppHomePage
|
|
1
|
+
import { AppHomePage } from "@/features/pages/AppHomePage"
|
|
2
2
|
|
|
3
3
|
/** The document title of this page. */
|
|
4
|
-
export
|
|
4
|
+
export { appHomeMetadata as generateMetadata } from "@/features/pages/AppHomePage"
|
|
5
5
|
|
|
6
6
|
/** The locale root's page slot. */
|
|
7
7
|
const Page = () => <AppHomePage />
|
|
@@ -8,6 +8,12 @@ import { SignInFormBase } from "./component"
|
|
|
8
8
|
|
|
9
9
|
type SignInFailure = "refused" | "not-found" | "invalid" | "unavailable"
|
|
10
10
|
|
|
11
|
+
/** The state the form shows: working while a request runs, failed after a refusal, else ready. */
|
|
12
|
+
const stateOf = (pending: boolean, failure: SignInFailure | undefined): "working" | "ready" | "failed" => {
|
|
13
|
+
if (pending) return "working"
|
|
14
|
+
return failure === undefined ? "ready" : "failed"
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
/** The interactive sign-in form wired to the schema-checked Server Action. */
|
|
12
18
|
export const SignInForm = () => {
|
|
13
19
|
const router = navigation.useRouter()
|
|
@@ -27,7 +33,7 @@ export const SignInForm = () => {
|
|
|
27
33
|
}
|
|
28
34
|
return (
|
|
29
35
|
<SignInFormBase
|
|
30
|
-
state={pending
|
|
36
|
+
state={stateOf(pending, failure)}
|
|
31
37
|
props={{
|
|
32
38
|
title: t("title"),
|
|
33
39
|
emailLabel: t("email"),
|
|
@@ -9,9 +9,9 @@ import type { DbOutcome } from "../outcome"
|
|
|
9
9
|
/** Ends the cookie-backed Supabase session, including an already-anonymous one. */
|
|
10
10
|
export const writeSignOut = async (): Promise<DbOutcome<true>> => {
|
|
11
11
|
const principal = await getPrincipal()
|
|
12
|
-
void principal
|
|
13
12
|
const client = await createServerDbClient()
|
|
14
13
|
const result = await client.auth.signOut()
|
|
15
14
|
if (result.error === null) return dbOk(true)
|
|
15
|
+
if (principal.kind !== "ok" && result.error.status === 401) return dbOk(true)
|
|
16
16
|
return dbFailure(result.error.status === 401 ? "refused" : "unavailable", result.error.code ?? "sign-out")
|
|
17
17
|
}
|
|
@@ -12,7 +12,15 @@ interface SignInInput {
|
|
|
12
12
|
type SignInParse = { readonly success: true; readonly data: SignInInput } | { readonly success: false }
|
|
13
13
|
|
|
14
14
|
const UUID = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/u
|
|
15
|
-
const
|
|
15
|
+
const ADDRESS = /^[^\s@]+@([^\s@]+)$/u
|
|
16
|
+
|
|
17
|
+
/** Whether the text is one local part, one `@` and a domain with a dot that has a character on each side; linear in the length. */
|
|
18
|
+
const isEmail = (value: string): boolean => {
|
|
19
|
+
const domain = ADDRESS.exec(value)?.[1]
|
|
20
|
+
if (domain === undefined) return false
|
|
21
|
+
const dot = domain.indexOf(".", 1)
|
|
22
|
+
return dot !== -1 && dot < domain.length - 1
|
|
23
|
+
}
|
|
16
24
|
|
|
17
25
|
/** The bounded UUID schema shared by generated table Server Actions. */
|
|
18
26
|
export const rowSchema = {
|
|
@@ -38,7 +46,7 @@ export const signInInputSchema = {
|
|
|
38
46
|
if (
|
|
39
47
|
typeof email !== "string" ||
|
|
40
48
|
email.length > 254 ||
|
|
41
|
-
!
|
|
49
|
+
!isEmail(email) ||
|
|
42
50
|
typeof password !== "string" ||
|
|
43
51
|
password.length < 8 ||
|
|
44
52
|
password.length > 256
|