@starci/hfs 5.1.0 → 5.3.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 +54 -0
- package/README.md +11 -2
- package/cache/hfs-family.d.mts +2 -3
- package/cache/hfs-family.mjs +4 -9
- package/cache/secret-records.d.mts +7 -0
- package/cache/secret-records.mjs +12 -0
- package/cache/session.mjs +4 -2
- package/codemod/account-doors.d.mts +36 -0
- package/codemod/account-doors.mjs +128 -0
- package/codemod/main.mjs +31 -1
- package/codemod/module-definition.d.mts +52 -0
- package/codemod/module-definition.mjs +242 -0
- package/codemod/quality-binding.d.mts +33 -0
- package/codemod/quality-binding.mjs +38 -0
- package/codemod/secrets-cache.d.mts +35 -0
- package/codemod/secrets-cache.mjs +70 -0
- package/codemod/sops-rules.d.mts +34 -0
- package/codemod/sops-rules.mjs +69 -0
- package/codemod/theme-tokens.mjs +48 -2
- package/db/postgres.mjs +9 -1
- package/lint/run.d.mts +6 -0
- package/lint/run.mjs +63 -10
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +65 -11
- package/runtime/knowledge/hfs/fe-stack.yaml +9 -9
- package/runtime/knowledge/hfs/fork-images.yaml +7 -0
- package/runtime/knowledge/hfs/rules.yaml +67 -15
- package/runtime/knowledge/hfs/slots.yaml +174 -30
- package/runtime/modules/kernel/failure-codes.yaml +67 -4
- package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
- package/runtime/scripts/hfs/architecture/config.d.mts +4 -0
- package/runtime/scripts/hfs/architecture/config.mjs +25 -1
- package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
- package/runtime/scripts/hfs/architecture/module-per-transport.d.mts +1 -1
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +6 -1
- package/runtime/scripts/hfs/architecture/root-packages.d.mts +26 -0
- package/runtime/scripts/hfs/architecture/root-packages.mjs +78 -0
- package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
- package/runtime/scripts/hfs/architecture/type-context.d.mts +4 -1
- package/runtime/scripts/hfs/architecture/type-context.mjs +30 -0
- package/runtime/scripts/hfs/architecture/typescript.d.mts +7 -1
- package/runtime/scripts/hfs/architecture/typescript.mjs +21 -1
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +1 -1
- package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
- package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
- package/runtime/scripts/hfs/rules/database-access.mjs +39 -1
- package/runtime/scripts/hfs/rules/monorepo.d.mts +4 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +10 -5
- package/runtime/scripts/hfs/rules/secrets.mjs +11 -0
- package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
- package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
- package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
- package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
- package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
- package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
- package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
- package/runtime/scripts/hfs/secret-errors.d.mts +3 -0
- package/runtime/scripts/hfs/secret-errors.mjs +8 -0
- package/runtime/scripts/hfs/secret-init.d.mts +6 -0
- package/runtime/scripts/hfs/secret-init.mjs +23 -0
- package/runtime/scripts/hfs/secret-sops.mjs +9 -1
- package/runtime/scripts/hfs/secret-standard.d.mts +20 -0
- package/runtime/scripts/hfs/secret-standard.mjs +106 -0
- package/runtime/scripts/hfs/secret.mjs +15 -8
- package/runtime/scripts/hfs/slots.mjs +1 -1
- package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
- package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
- package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
- package/runtime/scripts/hfs/view.d.mts +23 -0
- package/runtime/scripts/hfs/view.mjs +68 -0
- package/runtime/scripts/lib/declaration-surface.d.mts +4 -0
- package/runtime/scripts/lib/declaration-surface.mjs +55 -1
- package/runtime/scripts/lib/secret-records.d.mts +13 -0
- package/runtime/scripts/lib/secret-records.mjs +83 -0
- package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
- package/runtime/scripts/lib/swarm-stack.mjs +2 -2
- package/scaffold/add-inbox-lite.mjs +2 -2
- package/scaffold/add-module.d.mts +16 -0
- package/scaffold/add-module.mjs +232 -0
- package/scaffold/add-queue-lite.mjs +50 -3
- package/scaffold/add.mjs +5 -0
- package/scaffold/app.d.mts +2 -2
- package/scaffold/app.mjs +16 -18
- package/scaffold/lite-exports.mjs +1 -15
- package/scaffold/module-registry.d.mts +19 -0
- package/scaffold/module-registry.mjs +84 -0
- package/scripts/sync-bundles.d.mts +8 -0
- package/src/main.mjs +23 -2
- package/stack/cli.mjs +6 -1
- package/stack/context.d.mts +15 -0
- package/stack/context.mjs +1 -0
- package/stack/images/openclaw-runtime/Dockerfile +18 -0
- package/stack/images/openclaw-runtime/entrypoint.sh +23 -0
- package/stack/images/openclaw-runtime/openclaw-init.mjs +101 -0
- package/stack/images/openclaw-runtime/openclaw-relay.mjs +25 -0
- package/stack/local-cli.d.mts +25 -0
- package/stack/local-cli.mjs +89 -0
- package/stack/local-config.d.mts +17 -0
- package/stack/local-config.mjs +37 -0
- package/stack/local-custody.d.mts +24 -0
- package/stack/local-custody.mjs +109 -0
- package/stack/local-doctor.d.mts +6 -0
- package/stack/local-doctor.mjs +49 -0
- package/stack/local-down.d.mts +8 -0
- package/stack/local-down.mjs +20 -0
- package/stack/local-host.d.mts +42 -0
- package/stack/local-host.mjs +163 -0
- package/stack/local-logs.d.mts +8 -0
- package/stack/local-logs.mjs +32 -0
- package/stack/local-ports.d.mts +7 -0
- package/stack/local-ports.mjs +39 -0
- package/stack/local-prepare.d.mts +22 -0
- package/stack/local-prepare.mjs +62 -0
- package/stack/local-run.d.mts +71 -0
- package/stack/local-run.mjs +200 -0
- package/stack/local-state.d.mts +35 -0
- package/stack/local-state.mjs +29 -0
- package/stack/local-status.d.mts +6 -0
- package/stack/local-status.mjs +36 -0
- package/stack/local-up.d.mts +11 -0
- package/stack/local-up.mjs +169 -0
- package/stack/local-verify.d.mts +6 -0
- package/stack/local-verify.mjs +21 -0
- package/stack/runtime-layers.d.mts +21 -0
- package/stack/runtime-layers.mjs +67 -0
- package/sync/hygiene.mjs +3 -1
- package/sync/index.d.mts +15 -12
- package/sync/index.mjs +114 -28
- package/templates/app/auth-doors.sql.partial +115 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -1
- package/templates/app/module/jest.config.js.tpl +8 -0
- package/templates/app/module/src/index.ts.tpl +2 -0
- package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
- package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
- package/templates/app/module/src/manifest/index.ts.tpl +1 -0
- package/templates/app/module/src/public/index.ts.tpl +2 -0
- package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
- package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
- package/templates/app/module/src/template/index.ts.tpl +1 -0
- package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
- package/templates/app/module/src/workbench/index.ts.tpl +2 -0
- package/templates/app/package-scripts/core.json +1 -1
- package/templates/app/package-scripts/package.json +1 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +85 -70
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +24 -12
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +43 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +21 -26
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +6 -1
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +76 -13
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +44 -0
- package/templates/be/auth-lite/src/features/api/auth/application/confirm.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/confirm.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/forgot.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/forgot.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/resend.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/resend.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/reset.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/reset.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
- package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +6 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +21 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
- package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +6 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +145 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/forgot.request.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/resend.request.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +18 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/token.request.ts +11 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +19 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +209 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +84 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.log-events.ts +6 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +233 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +176 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-sign-in.log-events.ts +6 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +98 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-email-token.entity.ts +34 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +10 -0
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
- package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
- package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +95 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +93 -0
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +40 -0
- package/templates/be/inbox/module-definition.ts.tpl +2 -9
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +2 -11
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +3 -9
- package/templates/be/platform/graphql/graphql.module-definition.ts +2 -9
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +2 -9
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +3 -13
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +4 -11
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +4 -9
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +5 -11
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +4 -11
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +2 -9
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +3 -0
- package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
- package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
- package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +12 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +40 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +22 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +50 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +3 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity-mail.messages.ts +65 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +28 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/index.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.config.ts +33 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.decorators.ts +14 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module-definition.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module.ts +27 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.options.ts +22 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +5 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
- package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
- package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
- package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/rate-limit.guard.spec.ts +11 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +22 -4
- package/templates/be/table/api/feature.module-definition.ts.tpl +3 -9
- package/templates/be/table/api/graphql.module-definition.ts.tpl +3 -9
- package/templates/be/table/module-definition.ts.tpl +2 -9
- package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
- package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -4
- package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
- package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +16 -0
- package/templates/fe/auth-lite/apps/web/src/modules/auth/index.ts +5 -0
- package/templates/fe/common/site-shell.tsx.partial +3 -3
- package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +15 -12
- package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +30 -67
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +6 -21
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +10 -16
- package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
- package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/confirm/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/forgot/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/resend/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/reset/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/signup/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +5 -9
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +51 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +84 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
- package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppConfirmPage/index.tsx +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppForgotPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +11 -3
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +4 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResendPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResetPage/index.tsx +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppSignupPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/emailAuth.shared.ts +12 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/index.ts +2 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/useEmailDoor.ts +32 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +50 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +50 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +7 -30
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +13 -14
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +5 -0
- package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +4 -17
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
- package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
- package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -41
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -40
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
- package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
- package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
- package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineConfigurableModule } from "@starci/nest-kit"
|
|
2
2
|
import type { {{Name}}ApiOptions } from "./{{name}}.options"
|
|
3
3
|
|
|
4
|
-
const built = new ConfigurableModuleBuilder<{{Name}}ApiOptions>().setClassMethodName("register").build()
|
|
5
|
-
type Host = ConfigurableModuleHost<{{Name}}ApiOptions, "register", "create", {}>
|
|
6
|
-
|
|
7
4
|
/** The configurable-module base of the {{name}} module. */
|
|
8
|
-
export const
|
|
9
|
-
|
|
10
|
-
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
11
|
-
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
-
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
5
|
+
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
|
|
6
|
+
defineConfigurableModule<{{Name}}ApiOptions>()
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineConfigurableModule } from "@starci/nest-kit"
|
|
2
2
|
import type { {{Name}}GraphqlOptions } from "./{{name}}-graphql.options"
|
|
3
3
|
|
|
4
|
-
const built = new ConfigurableModuleBuilder<{{Name}}GraphqlOptions>().setClassMethodName("register").build()
|
|
5
|
-
type Host = ConfigurableModuleHost<{{Name}}GraphqlOptions, "register", "create", {}>
|
|
6
|
-
|
|
7
4
|
/** The configurable-module base of the {{name}}-graphql module. */
|
|
8
|
-
export const
|
|
9
|
-
|
|
10
|
-
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
11
|
-
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
-
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
5
|
+
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
|
|
6
|
+
defineConfigurableModule<{{Name}}GraphqlOptions>()
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineCapabilityModule } from "@starci/nest-kit"
|
|
2
2
|
import type { {{Name}}Options } from "./{{name}}.options"
|
|
3
3
|
|
|
4
|
-
const built = new ConfigurableModuleBuilder<{{Name}}Options>()
|
|
5
|
-
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
6
|
-
.build()
|
|
7
|
-
type Host = ConfigurableModuleHost<{{Name}}Options, "register", "create", { isGlobal: boolean }>
|
|
8
|
-
|
|
9
4
|
/** The configurable-module base of the {{name}} capability. */
|
|
10
|
-
export const
|
|
11
|
-
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
-
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
5
|
+
export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<{{Name}}Options>()
|
|
@@ -10,23 +10,23 @@ describe("system-health: the {{app}} app serves once its database is migrated (e
|
|
|
10
10
|
const world = useTestWorld({ apps: ["{{app}}"] })
|
|
11
11
|
|
|
12
12
|
it("answers liveness and readiness over the migrated {{connection}} database", async () => {
|
|
13
|
-
const live = await world.apps{{appAccess}}
|
|
14
|
-
const ready = await world.apps{{appAccess}}
|
|
13
|
+
const live = await world.apps{{appAccess}}?.api.get("/health/live")
|
|
14
|
+
const ready = await world.apps{{appAccess}}?.api.get("/health/ready")
|
|
15
15
|
const applied = await appliedMigrations(world.db{{connectionAccess}}, {{migrationsConst}})
|
|
16
16
|
|
|
17
|
-
expect(live
|
|
18
|
-
expect(ready
|
|
19
|
-
expect(ready
|
|
17
|
+
expect(live?.status).toBe(200)
|
|
18
|
+
expect(ready?.status).toBe(200)
|
|
19
|
+
expect(ready?.body).toEqual({ ok: true })
|
|
20
20
|
expect(applied.length).toBeGreaterThan(0)
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
it("answers 503 on readiness while the database is cut, and liveness stays up", async () => {
|
|
24
24
|
await world.interruptDatabase(async () => {
|
|
25
|
-
const ready = await world.apps{{appAccess}}
|
|
26
|
-
const live = await world.apps{{appAccess}}
|
|
25
|
+
const ready = await world.apps{{appAccess}}?.api.get("/health/ready")
|
|
26
|
+
const live = await world.apps{{appAccess}}?.api.get("/health/live")
|
|
27
27
|
|
|
28
|
-
expect(ready
|
|
29
|
-
expect(live
|
|
28
|
+
expect(ready?.status).toBe(503)
|
|
29
|
+
expect(live?.status).toBe(200)
|
|
30
30
|
})
|
|
31
31
|
})
|
|
32
32
|
})
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { EntityManager } from "typeorm"
|
|
2
2
|
import { sql } from "@modules/platform/database"
|
|
3
|
-
import type { SqlText } from "@modules/platform/database"
|
|
4
3
|
|
|
5
4
|
/** One row of a connection's migration ledger: the name of a migration the cli migrate step applied. */
|
|
6
5
|
export interface AppliedMigrationRow {
|
|
@@ -8,8 +7,9 @@ export interface AppliedMigrationRow {
|
|
|
8
7
|
readonly name: string
|
|
9
8
|
}
|
|
10
9
|
{{migrationQueries}}
|
|
11
|
-
/** Reads the ledger of one connection with one of the queries above. */
|
|
10
|
+
/** Reads the ledger of one connection with one of the queries above; a connection the world did not wire fails the spec. */
|
|
12
11
|
export const appliedMigrations = (
|
|
13
|
-
manager: EntityManager,
|
|
12
|
+
manager: EntityManager | undefined,
|
|
14
13
|
query: ReturnType<typeof sql>,
|
|
15
|
-
): Promise<ReadonlyArray<AppliedMigrationRow>> =>
|
|
14
|
+
): Promise<ReadonlyArray<AppliedMigrationRow>> =>
|
|
15
|
+
manager === undefined ? Promise.reject(new Error("The test world wired no such connection")) : manager.query(query)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Managed by starci app sync (slot fe.modules.auth.client); do not edit: run "npx starci app sync --execute".
|
|
2
|
+
import { createAuthClient } from "@starci/auth/client"
|
|
3
|
+
import { apiUrl } from "@/modules/config"
|
|
4
|
+
import type { AuthClient, AuthFailure, AuthSession } from "./contracts"
|
|
5
|
+
|
|
6
|
+
/** The one instance of the front end: the doors of the app's own back end, the access token in its memory, one shared refresh. */
|
|
7
|
+
const auth = createAuthClient({ apiOrigin: () => apiUrl() })
|
|
8
|
+
|
|
9
|
+
const sessionOf = (user: { readonly id: string; readonly email: string } | null): AuthSession | undefined =>
|
|
10
|
+
user === null ? undefined : { userId: user.id, ...(user.email === "" ? {} : { email: user.email }) }
|
|
11
|
+
|
|
12
|
+
/** The lite session issuer's client: `@starci/auth/client` behind the interface every edition shares. */
|
|
13
|
+
export const authClient: AuthClient = {
|
|
14
|
+
session: async () => sessionOf(await auth.user()),
|
|
15
|
+
accessToken: () => auth.accessToken(),
|
|
16
|
+
refreshAccessToken: () => auth.renew(),
|
|
17
|
+
signInWithPassword: async (email, password): Promise<AuthFailure | undefined> => {
|
|
18
|
+
const outcome = await auth.signIn({ email, password })
|
|
19
|
+
return outcome.success ? undefined : { code: outcome.error, kind: outcome.message, message: "" }
|
|
20
|
+
},
|
|
21
|
+
signOut: async () => {
|
|
22
|
+
await auth.signOut()
|
|
23
|
+
return undefined
|
|
24
|
+
},
|
|
25
|
+
onChange: (listener) => auth.subscribe(() => listener(sessionOf(auth.getSnapshot().user))),
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Managed by starci app sync (slot fe.modules.auth.client); do not edit: run "npx starci app sync --execute".
|
|
2
|
+
import { createAuthClient } from "@starci/auth/client"
|
|
3
|
+
import { apiUrl } from "@/modules/config"
|
|
4
|
+
import type { AuthFailure } from "./contracts"
|
|
5
|
+
|
|
6
|
+
/** The email doors of the lite back end; the full edition's identity provider owns its own sign-up and recovery flows. */
|
|
7
|
+
export type EmailDoorName = "signup" | "resend" | "confirm" | "forgot" | "reset"
|
|
8
|
+
|
|
9
|
+
/** The anonymous doors need no session, so they use a client of their own over the same back-end origin. */
|
|
10
|
+
const doors = createAuthClient({ apiOrigin: () => apiUrl() })
|
|
11
|
+
|
|
12
|
+
/** Calls one email door with its posted fields; answers its failure (the wire code and its kind), or undefined when the back end accepted it. */
|
|
13
|
+
export const sendEmailDoor = async (door: EmailDoorName, body: Readonly<Record<string, string | undefined>>): Promise<AuthFailure | undefined> => {
|
|
14
|
+
const outcome = await doors.send(door, body)
|
|
15
|
+
return outcome.success ? undefined : { code: outcome.error, kind: outcome.message, message: "" }
|
|
16
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Managed by starci app sync (slot fe.modules.auth.client); do not edit: run "npx starci app sync --execute".
|
|
2
|
+
export { authClient } from "./client"
|
|
3
|
+
export { sendEmailDoor } from "./email-doors"
|
|
4
|
+
export type { EmailDoorName } from "./email-doors"
|
|
5
|
+
export type { AuthFailure, AuthSession } from "./contracts"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export const SiteShell = (props: SiteShellProps) => (
|
|
2
|
-
<GrammarRoot
|
|
2
|
+
<GrammarRoot>
|
|
3
3
|
<WorkspaceShell
|
|
4
4
|
viewport="flow"
|
|
5
5
|
primaryLabel={props.brand}
|
|
6
|
-
header={<TopBar brand={<
|
|
7
|
-
primary={
|
|
6
|
+
header={<TopBar brand={<Link href={props.homeHref}>{props.brand}</Link>} />}
|
|
7
|
+
primary={props.children}
|
|
8
8
|
/>
|
|
9
9
|
</GrammarRoot>
|
|
10
10
|
)
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { ToastProvider } from "@heroui/react"
|
|
4
|
+
import { viewerCacheKey, viewerSwrConfig } from "@starci/next-data"
|
|
5
|
+
import type { ViewerId } from "@starci/next-data"
|
|
4
6
|
import { ThemeProvider } from "next-themes"
|
|
5
7
|
import type { ReactNode } from "react"
|
|
6
|
-
import { SWRConfig } from "swr"
|
|
8
|
+
import { SWRConfig, useSWRConfig } from "swr"
|
|
9
|
+
import { useViewerId } from "@/hooks/auth"
|
|
7
10
|
import { useAuthSessionEffect } from "@/hooks/effects"
|
|
8
11
|
import { SwrSideEffects } from "@/hooks/swr"
|
|
12
|
+
import { viewerCache } from "@/modules/viewer-cache"
|
|
9
13
|
import { ReduxProvider } from "@/redux"
|
|
10
14
|
|
|
11
15
|
/** What the app providers wrap: the routed tree. */
|
|
@@ -25,25 +29,44 @@ const SWR_DEFAULTS = {
|
|
|
25
29
|
} as const
|
|
26
30
|
|
|
27
31
|
/** The app-wide session effect: modules/auth's session kept in redux. Renders nothing. */
|
|
28
|
-
const AuthSessionEffect = () => {
|
|
32
|
+
const AuthSessionEffect = (): null => {
|
|
29
33
|
useAuthSessionEffect()
|
|
30
34
|
return null
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
/** Points the writes of viewerCache at the SWR cache around it. Renders nothing. */
|
|
38
|
+
const ViewerCacheBinding = (): null => {
|
|
39
|
+
viewerCache.bind(useSWRConfig().mutate)
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Each viewer gets their own SWR cache (FE-BOUNDARY-3): a change of person remounts the config on a fresh Map, and
|
|
45
|
+
* `viewerSwrConfig` lists pollingRestart so a read whose refreshInterval stopped at 0 polls again for newer data.
|
|
46
|
+
*/
|
|
47
|
+
const renderViewerScope = (viewerId: ViewerId, children: ReactNode): ReactNode => (
|
|
48
|
+
<SWRConfig key={viewerCacheKey(viewerId)} value={viewerSwrConfig(SWR_DEFAULTS)}>
|
|
49
|
+
<ViewerCacheBinding />
|
|
50
|
+
<SwrSideEffects />
|
|
51
|
+
{children}
|
|
52
|
+
<ToastProvider />
|
|
53
|
+
</SWRConfig>
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
/** The SWR config of the viewer on screen around the routed tree. */
|
|
57
|
+
const ViewerSwrScope = (props: AppProvidersProps): ReactNode => renderViewerScope(useViewerId(), props.children)
|
|
58
|
+
|
|
33
59
|
/**
|
|
34
60
|
* The one client provider stack the locale layout mounts, outer to inner: the theme (light and dark through the `.light` /
|
|
35
|
-
* `.dark` class the grammar and the brand layer read, the system theme by default), the redux store, the
|
|
36
|
-
* app-wide effects and the HeroUI toast region modules/toast writes to. Each part is
|
|
61
|
+
* `.dark` class the grammar and the brand layer read, the system theme by default), the redux store, the session effect, the
|
|
62
|
+
* SWR config keyed by the viewer, the app-wide effects and the HeroUI toast region modules/toast writes to. Each part is
|
|
63
|
+
* composed here, in its one owner.
|
|
37
64
|
*/
|
|
38
65
|
export const AppProviders = (props: AppProvidersProps) => (
|
|
39
66
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
|
40
67
|
<ReduxProvider>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
<SwrSideEffects />
|
|
44
|
-
{props.children}
|
|
45
|
-
<ToastProvider />
|
|
46
|
-
</SWRConfig>
|
|
68
|
+
<AuthSessionEffect />
|
|
69
|
+
<ViewerSwrScope>{props.children}</ViewerSwrScope>
|
|
47
70
|
</ReduxProvider>
|
|
48
71
|
</ThemeProvider>
|
|
49
72
|
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type { ViewerId } from "@starci/next-data"
|
|
4
|
+
import { useAppSelector } from "@/redux"
|
|
5
|
+
|
|
6
|
+
/** The person the session belongs to: undefined until modules/auth answers, null when signed out (the viewer of @starci/next-data). */
|
|
7
|
+
export const useViewerId = (): ViewerId => {
|
|
8
|
+
const session = useAppSelector((state) => state.session)
|
|
9
|
+
if (session.status === "unknown") return undefined
|
|
10
|
+
return session.session?.userId ?? null
|
|
11
|
+
}
|
|
@@ -21,15 +21,18 @@ const INDICATOR: Record<"success" | "danger" | "warning" | "info", ReactNode> =
|
|
|
21
21
|
/** The app's toast: HeroUI's, with the four status methods wrapped. */
|
|
22
22
|
type AppToast = typeof heroToast
|
|
23
23
|
|
|
24
|
-
/** The app's toast: HeroUI's imperative toast with the status indicator
|
|
25
|
-
export const toast: AppToast =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
/** The app's toast: HeroUI's imperative toast, callable as it is, with the four status methods wrapped to inject the status indicator; the region is mounted by the providers. */
|
|
25
|
+
export const toast: AppToast = Object.assign(
|
|
26
|
+
(message: ReactNode, options?: Parameters<typeof heroToast>[1]) => heroToast(message, options),
|
|
27
|
+
heroToast,
|
|
28
|
+
{
|
|
29
|
+
success: (message: ReactNode, options?: StatusToastOptions) =>
|
|
30
|
+
heroToast.success(message, { indicator: INDICATOR.success, ...options }),
|
|
31
|
+
danger: (message: ReactNode, options?: StatusToastOptions) =>
|
|
32
|
+
heroToast.danger(message, { indicator: INDICATOR.danger, ...options }),
|
|
33
|
+
warning: (message: ReactNode, options?: StatusToastOptions) =>
|
|
34
|
+
heroToast.warning(message, { indicator: INDICATOR.warning, ...options }),
|
|
35
|
+
info: (message: ReactNode, options?: StatusToastOptions) =>
|
|
36
|
+
heroToast.info(message, { indicator: INDICATOR.info, ...options }),
|
|
37
|
+
},
|
|
38
|
+
)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { createViewerCache } from "@starci/next-data"
|
|
2
|
+
import type { ViewerCache } from "@starci/next-data"
|
|
3
|
+
|
|
4
|
+
/** The writes to the cache of the viewer on screen: the one instance of the app, bound by the client providers. */
|
|
5
|
+
export const viewerCache: ViewerCache = createViewerCache()
|
|
@@ -1,81 +1,44 @@
|
|
|
1
1
|
import "server-only"
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { createReadLocaleSegment } from "@starci/next-i18n/layout"
|
|
3
|
+
import type { ReadLocaleSegment } from "@starci/next-i18n/layout"
|
|
4
|
+
import { createI18nNavigation } from "@starci/next-i18n/navigation"
|
|
5
|
+
import type { I18nNavigation } from "@starci/next-i18n/navigation"
|
|
6
|
+
import { createRequestConfig } from "@starci/next-i18n/request"
|
|
7
|
+
import type { I18nRequestConfig } from "@starci/next-i18n/request"
|
|
8
|
+
import type { LoadCatalog } from "@starci/next-i18n"
|
|
7
9
|
import { locale as routeLocale } from "next/root-params"
|
|
8
|
-
import {
|
|
9
|
-
import type {
|
|
10
|
+
import { i18n } from "./routing"
|
|
11
|
+
import type { ProductI18n } from "./routing"
|
|
10
12
|
|
|
11
|
-
/**
|
|
12
|
-
type
|
|
13
|
+
/** The locales the product serves, as the catalog loader of an app receives them. */
|
|
14
|
+
type ProductLocale = ProductI18n["locales"][number]
|
|
13
15
|
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const messagesOf = (loaded: unknown): AbstractIntlMessages => {
|
|
21
|
-
const messages = typeof loaded === "object" && loaded !== null && "default" in loaded ? loaded.default : undefined
|
|
22
|
-
if (!isMessages(messages)) throw new TypeError("A message catalog module must export its messages as default")
|
|
23
|
-
return messages
|
|
16
|
+
/** What one app of the product reads of its next-intl stack. */
|
|
17
|
+
export interface AppI18n {
|
|
18
|
+
readonly navigation: I18nNavigation<ProductI18n["locales"], ProductI18n["localePrefix"]>
|
|
19
|
+
readonly requestConfig: I18nRequestConfig
|
|
20
|
+
readonly readLocaleSegment: ReadLocaleSegment<ProductLocale>
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
/** `next/root-params` is typed only after Next generates its types, so the reader is checked before it is called. */
|
|
27
24
|
const isLocaleReader = (value: unknown): value is () => Promise<unknown> => typeof value === "function"
|
|
28
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The `[locale]` route param, read through `next/root-params` (`experimental.rootParams` in the app's next.config.ts), never the
|
|
28
|
+
* deprecated `requestLocale`. `next/root-params` is importable only where Next bundles it, which is why this workspace package, not
|
|
29
|
+
* `@starci/next-i18n`, reads it.
|
|
30
|
+
*/
|
|
29
31
|
const readRouteLocale = async (): Promise<unknown> => (isLocaleReader(routeLocale) ? routeLocale() : undefined)
|
|
30
32
|
|
|
31
|
-
/** The language the address states, its catalog and the product time zone. */
|
|
32
|
-
interface LocaleSegment {
|
|
33
|
-
readonly locale: string
|
|
34
|
-
readonly messages: AbstractIntlMessages
|
|
35
|
-
readonly timeZone: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** The navigation helpers of the product's routing (links, redirects, the locale-aware router). */
|
|
39
|
-
type ProductNavigation = ReturnType<typeof createNavigation<ProductRouting["locales"], "as-needed">>
|
|
40
|
-
|
|
41
|
-
/** What one app of the product reads of its next-intl stack. */
|
|
42
|
-
export interface AppI18n {
|
|
43
|
-
readonly navigation: ProductNavigation
|
|
44
|
-
readonly requestConfig: ReturnType<typeof getRequestConfig>
|
|
45
|
-
readonly readLocaleSegment: (params: Promise<{ readonly locale: string }>) => Promise<LocaleSegment>
|
|
46
|
-
}
|
|
47
|
-
|
|
48
33
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
34
|
+
* What one app of the product calls from its `modules/i18n` (R59): the locale-aware navigation, the request config next-intl's
|
|
35
|
+
* plugin loads (the app's `modules/i18n/request.ts` default-exports it) and what the app's `[locale]` layout resolves before it
|
|
36
|
+
* mounts the shell, all built by `@starci/next-i18n` over the product's one description of its languages. `app` names the app in the
|
|
37
|
+
* text of a message its catalog lacks (`<app>:<namespace>.<key>`); `loadMessages` loads its own catalogs. A server module: a client
|
|
38
|
+
* component reads the default locale from `./routing`.
|
|
54
39
|
*/
|
|
55
|
-
export const createAppI18n = (app: string, loadMessages:
|
|
56
|
-
navigation:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* FE-I18N-1), never the deprecated `requestLocale`. An unrecognised locale resolves to the default before the loader runs, so a
|
|
60
|
-
* missing catalog file is never imported.
|
|
61
|
-
*/
|
|
62
|
-
requestConfig: getRequestConfig(async () => {
|
|
63
|
-
const requested = await readRouteLocale()
|
|
64
|
-
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale
|
|
65
|
-
return {
|
|
66
|
-
locale,
|
|
67
|
-
timeZone: PRODUCT_TIME_ZONE,
|
|
68
|
-
messages: messagesOf(await loadMessages(locale)),
|
|
69
|
-
getMessageFallback: ({ namespace, key }: MissingMessage) => `${app}:${namespace ?? ""}.${key}`,
|
|
70
|
-
}
|
|
71
|
-
}),
|
|
72
|
-
/**
|
|
73
|
-
* The language the address states, its catalog and the product time zone. An unknown segment is a route that does not exist,
|
|
74
|
-
* not a request to fall back silently.
|
|
75
|
-
*/
|
|
76
|
-
readLocaleSegment: async (params: Promise<{ readonly locale: string }>) => {
|
|
77
|
-
const { locale } = await params
|
|
78
|
-
if (!hasLocale(routing.locales, locale)) notFound()
|
|
79
|
-
return { locale, messages: await getMessages(), timeZone: PRODUCT_TIME_ZONE }
|
|
80
|
-
},
|
|
40
|
+
export const createAppI18n = (app: string, loadMessages: LoadCatalog<ProductLocale>): AppI18n => ({
|
|
41
|
+
navigation: createI18nNavigation(i18n),
|
|
42
|
+
requestConfig: createRequestConfig(i18n, { app, loadMessages, readRouteLocale }),
|
|
43
|
+
readLocaleSegment: createReadLocaleSegment(i18n),
|
|
81
44
|
})
|
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { routing } from "./routing"
|
|
5
|
-
|
|
6
|
-
/** The first path segments of the API, the health probe and the framework: never locale-negotiated. */
|
|
7
|
-
const UNNEGOTIATED_ROOTS: ReadonlySet<string> = new Set(["api", "health", "_next", "_vercel"])
|
|
8
|
-
|
|
9
|
-
/** Whether the path belongs to the API, the health probe, the framework or a file with an extension. */
|
|
10
|
-
const isUnnegotiated = (pathname: string): boolean => {
|
|
11
|
-
const segments = pathname.split("/")
|
|
12
|
-
const last = segments.at(-1) ?? ""
|
|
13
|
-
return UNNEGOTIATED_ROOTS.has(segments[1] ?? "") || (last.includes(".") && !last.endsWith("."))
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const negotiate = createMiddleware(routing)
|
|
1
|
+
import { createProxy } from "@starci/next-i18n/proxy"
|
|
2
|
+
import type { LocaleProxy } from "@starci/next-i18n/proxy"
|
|
3
|
+
import { i18n } from "./routing"
|
|
17
4
|
|
|
18
5
|
/**
|
|
19
|
-
* The one thing that runs before a route exists: which language it is in.
|
|
20
|
-
* the
|
|
21
|
-
* re-exports this.
|
|
6
|
+
* The one thing that runs before a route exists: which language it is in. The product's one description of its languages, read by
|
|
7
|
+
* the package's proxy; each app's `src/proxy.ts` re-exports this.
|
|
22
8
|
*/
|
|
23
|
-
export const proxy = (
|
|
24
|
-
isUnnegotiated(request.nextUrl.pathname) ? NextResponse.next() : negotiate(request)
|
|
9
|
+
export const proxy: LocaleProxy = createProxy(i18n)
|
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { defineI18n } from "@starci/next-i18n"
|
|
2
|
+
import type { I18n } from "@starci/next-i18n"
|
|
3
3
|
|
|
4
4
|
/** The locales every app of the product serves; every catalog of an app has the same keys in each. */
|
|
5
5
|
const LOCALES = ["vi", "en"] as const
|
|
6
6
|
|
|
7
|
-
/** The
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
/** The product's time zone: fixed, not inferred, so the server and the hydrated client format the same instant the same way. */
|
|
11
|
-
export const PRODUCT_TIME_ZONE = "Asia/Ho_Chi_Minh"
|
|
7
|
+
/** The description of the product's languages: the locales and the `as-needed` prefix mode that `defineI18n` types. */
|
|
8
|
+
export type ProductI18n = I18n<typeof LOCALES, "as-needed">
|
|
12
9
|
|
|
13
|
-
/** The
|
|
14
|
-
export
|
|
10
|
+
/** The locale served at the unprefixed path, and the language of a document no locale reached (the global error boundary). */
|
|
11
|
+
export const DEFAULT_LOCALE: ProductI18n["defaultLocale"] = "vi"
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
|
-
* The one
|
|
18
|
-
* only when the locale is not the default, so
|
|
14
|
+
* The one description of the product's languages, for the proxy, the navigation and the request config of every app: vi default,
|
|
15
|
+
* a prefix only when the locale is not the default, and a fixed time zone so the server and the hydrated client format the same
|
|
16
|
+
* instant the same way. The stack that reads it is `@starci/next-i18n`'s; this package only states the languages (FE-I18N-1).
|
|
19
17
|
*/
|
|
20
|
-
export const
|
|
21
|
-
locales: LOCALES,
|
|
22
|
-
defaultLocale: DEFAULT_LOCALE,
|
|
23
|
-
localePrefix: "as-needed",
|
|
24
|
-
})
|
|
18
|
+
export const i18n: ProductI18n = defineI18n({ locales: LOCALES, defaultLocale: DEFAULT_LOCALE, timeZone: "Asia/Ho_Chi_Minh" })
|
package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Link, Page } from "@starci/grammar/common"
|
|
2
2
|
|
|
3
3
|
/** The words of a page that does not exist and the way back. */
|
|
4
4
|
type NotFoundNoticeProps = {
|
|
@@ -10,10 +10,7 @@ type NotFoundNoticeProps = {
|
|
|
10
10
|
|
|
11
11
|
/** A missing page: the message and the way back to the app's front door. */
|
|
12
12
|
export const NotFoundNotice = (props: NotFoundNoticeProps) => (
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
{props.homeLabel}
|
|
17
|
-
</TextAction>
|
|
18
|
-
</>
|
|
13
|
+
<Page header={{ title: props.title }} measure="reading">
|
|
14
|
+
<Link href={props.homeHref}>{props.homeLabel}</Link>
|
|
15
|
+
</Page>
|
|
19
16
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
|
-
import { GrammarRoot,
|
|
3
|
-
import { siteShellClassNames } from "./classNames"
|
|
2
|
+
import { GrammarRoot, Link, TopBar, WorkspaceShell } from "@starci/grammar/common"
|
|
4
3
|
|
|
5
4
|
/** What an app's chrome hands the shell: the brand, its home link and the routed body. */
|
|
6
5
|
type SiteShellProps = {
|
|
@@ -14,6 +13,6 @@ type SiteShellProps = {
|
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* The document chrome both apps share: Grammar's Common boundary, the brand header (the top bar carrying the wordmark that links
|
|
17
|
-
* home), then the routed body in the shell's main landmark
|
|
16
|
+
* home), then the routed body in the shell's main landmark (each page draws its own measure).
|
|
18
17
|
*/
|
|
19
18
|
{{> fe/common/site-shell.tsx.partial}}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AppConfirmPage } from "@/features/pages/AppConfirmPage"
|
|
2
|
+
|
|
3
|
+
/** The document title of this page. */
|
|
4
|
+
export { appConfirmMetadata as generateMetadata } from "@/features/pages/AppConfirmPage"
|
|
5
|
+
|
|
6
|
+
/** The confirm page slot: Next hands the query over as a promise, which the page reads for the token of the mail link. */
|
|
7
|
+
const Page = (props: Parameters<typeof AppConfirmPage>[0]) => <AppConfirmPage {...props} />
|
|
8
|
+
|
|
9
|
+
export default Page
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AppForgotPage } from "@/features/pages/AppForgotPage"
|
|
2
|
+
|
|
3
|
+
/** The document title of this page. */
|
|
4
|
+
export { appForgotMetadata as generateMetadata } from "@/features/pages/AppForgotPage"
|
|
5
|
+
|
|
6
|
+
/** The forgot page slot. */
|
|
7
|
+
const Page = () => <AppForgotPage />
|
|
8
|
+
|
|
9
|
+
export default Page
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AppResendPage } from "@/features/pages/AppResendPage"
|
|
2
|
+
|
|
3
|
+
/** The document title of this page. */
|
|
4
|
+
export { appResendMetadata as generateMetadata } from "@/features/pages/AppResendPage"
|
|
5
|
+
|
|
6
|
+
/** The resend page slot. */
|
|
7
|
+
const Page = () => <AppResendPage />
|
|
8
|
+
|
|
9
|
+
export default Page
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AppResetPage } from "@/features/pages/AppResetPage"
|
|
2
|
+
|
|
3
|
+
/** The document title of this page. */
|
|
4
|
+
export { appResetMetadata as generateMetadata } from "@/features/pages/AppResetPage"
|
|
5
|
+
|
|
6
|
+
/** The reset page slot: Next hands the query over as a promise, which the page reads for the token of the mail link. */
|
|
7
|
+
const Page = (props: Parameters<typeof AppResetPage>[0]) => <AppResetPage {...props} />
|
|
8
|
+
|
|
9
|
+
export default Page
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AppSignupPage } from "@/features/pages/AppSignupPage"
|
|
2
|
+
|
|
3
|
+
/** The document title of this page. */
|
|
4
|
+
export { appSignupMetadata as generateMetadata } from "@/features/pages/AppSignupPage"
|
|
5
|
+
|
|
6
|
+
/** The signup page slot. */
|
|
7
|
+
const Page = () => <AppSignupPage />
|
|
8
|
+
|
|
9
|
+
export default Page
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getMessages } from "next-intl/server"
|
|
3
|
-
import { notFound } from "next/navigation"
|
|
1
|
+
import { createReadLocaleSegment } from "@starci/next-i18n/layout"
|
|
4
2
|
import type { Metadata } from "next"
|
|
5
3
|
import type { ReactElement, ReactNode } from "react"
|
|
6
4
|
import { AppLayout, appLayoutMetadata } from "@/features/layouts/AppLayout"
|
|
7
|
-
import {
|
|
5
|
+
import { i18n } from "@/modules/i18n"
|
|
8
6
|
import { Providers } from "./providers"
|
|
9
7
|
import "../globals.css"
|
|
10
8
|
import "@/modules/brand"
|
|
@@ -17,16 +15,14 @@ type LayoutProps = {
|
|
|
17
15
|
/** The localized document title of this route segment. */
|
|
18
16
|
export const generateMetadata = async (props: LayoutProps): Promise<Metadata> => appLayoutMetadata((await props.params).locale)
|
|
19
17
|
|
|
20
|
-
/** The locale segment's document, providers and app chrome. */
|
|
18
|
+
/** The locale segment's document, providers and app chrome; an unknown locale is a 404. */
|
|
21
19
|
const Layout = async (props: LayoutProps): Promise<ReactElement> => {
|
|
22
|
-
const { locale } = await props.params
|
|
23
|
-
if (!hasLocale(routing.locales, locale)) notFound()
|
|
24
|
-
const messages = await getMessages()
|
|
20
|
+
const { locale, messages, timeZone } = await createReadLocaleSegment(i18n)(props.params)
|
|
25
21
|
return (
|
|
26
22
|
<AppLayout
|
|
27
23
|
locale={locale}
|
|
28
24
|
content={
|
|
29
|
-
<Providers locale={locale} messages={messages} timeZone={
|
|
25
|
+
<Providers locale={locale} messages={messages} timeZone={timeZone}>
|
|
30
26
|
{props.children}
|
|
31
27
|
</Providers>
|
|
32
28
|
}
|