@starci/hfs 5.0.8 → 5.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 +30 -0
- package/README.md +10 -1
- package/cache/import-graph.mjs +6 -61
- package/cache/lint-family.d.mts +10 -1
- package/cache/lint-family.mjs +12 -5
- package/codemod/directive-first.d.mts +48 -0
- package/codemod/directive-first.mjs +136 -0
- package/codemod/explicit-surface-edit.d.mts +14 -0
- package/codemod/explicit-surface-edit.mjs +57 -0
- package/codemod/explicit-surface-names.d.mts +30 -0
- package/codemod/explicit-surface-names.mjs +135 -0
- package/codemod/explicit-surface-render.d.mts +29 -0
- package/codemod/explicit-surface-render.mjs +197 -0
- package/codemod/explicit-surface.d.mts +50 -0
- package/codemod/explicit-surface.mjs +374 -0
- package/codemod/main.d.mts +7 -5
- package/codemod/main.mjs +37 -16
- package/codemod/source-tree.d.mts +8 -0
- package/codemod/source-tree.mjs +23 -0
- package/db/postgres.mjs +9 -1
- package/lint/eslint-list.cjs +29 -0
- package/lint/run.d.mts +19 -5
- package/lint/run.mjs +47 -14
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +31 -10
- package/runtime/knowledge/hfs/fe-stack.yaml +7 -7
- package/runtime/knowledge/hfs/fork-images.yaml +7 -0
- package/runtime/knowledge/hfs/rules.yaml +107 -8
- package/runtime/knowledge/hfs/slots.yaml +75 -24
- package/runtime/modules/kernel/failure-codes.yaml +62 -0
- package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
- package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
- package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
- 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/explicit-surface-rule.d.mts +58 -0
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
- 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/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.mjs +10 -7
- 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/lib/declaration-surface.d.mts +35 -0
- package/runtime/scripts/lib/declaration-surface.mjs +112 -0
- package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
- package/runtime/scripts/lib/swarm-stack.mjs +2 -2
- package/scaffold/add-module.d.mts +16 -0
- package/scaffold/add-module.mjs +232 -0
- package/scaffold/add-queue-lite.mjs +49 -0
- package/scaffold/add.mjs +5 -0
- package/scaffold/app.mjs +7 -7
- package/scaffold/module-registry.d.mts +19 -0
- package/scaffold/module-registry.mjs +84 -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/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 +166 -0
- package/stack/local-verify.d.mts +6 -0
- package/stack/local-verify.mjs +21 -0
- package/sync/index.d.mts +13 -12
- package/sync/index.mjs +83 -28
- 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/package.json +1 -1
- package/templates/app/package-scripts-lite/package.json +1 -1
- package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +76 -70
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +23 -13
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +25 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -26
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +1 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +2 -1
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +57 -14
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +11 -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/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/auth.module-definition.ts +13 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +16 -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 +13 -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 +90 -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/modules/domain/identity/account-directory.service.spec.ts +46 -0
- package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +28 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +145 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +95 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +38 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +54 -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-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 +17 -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 +78 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +87 -0
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +44 -0
- package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
- package/templates/be/inbox/entities.index.ts.tpl +1 -1
- package/templates/be/inbox/module-definition.ts.tpl +8 -2
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
- package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
- package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
- package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
- 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 +10 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +34 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +47 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +24 -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 +12 -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/database.port.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
- package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
- package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
- package/templates/be/table/entities.index.ts.tpl +1 -1
- package/templates/be/table/module-definition.ts.tpl +8 -3
- 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 -3
- package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
- package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
- package/templates/fe/common/app-layout.tsx.partial +1 -1
- 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/useSession.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
- package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +18 -12
- package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
- package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
- package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
- package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
- package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
- package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +20 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
- 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]/layout.tsx +4 -3
- 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/layouts/AppLayout/index.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
- package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
- package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
- package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
- package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
- package/templates/fe/units/page/index.tsx.tpl +3 -2
- package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
- 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 -37
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -36
- 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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AuthPrincipalVerifier, AuthTokens } from "@starci/auth/server"
|
|
2
|
+
import { ok, refused } from "@modules/platform/primitives"
|
|
3
|
+
import { PassportErrorCode } from "./errors/passport.error"
|
|
4
|
+
import type { PassportOptions, VerifyAccessToken } from "./passport.options"
|
|
5
|
+
|
|
6
|
+
/** The signer of the app's own access tokens (it also serves the JWKS) and the verifier `domain/identity` turns into a principal. */
|
|
7
|
+
export interface PassportKit {
|
|
8
|
+
/** Signs access tokens and publishes every key of the set. */
|
|
9
|
+
readonly tokens: AuthTokens
|
|
10
|
+
/** Verifies one bearer token against the app's own key set. */
|
|
11
|
+
readonly verify: VerifyAccessToken
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Builds the kit from the typed options. The key set is imported on the first use, so a bad key fails the first verification
|
|
16
|
+
* instead of hiding behind a default; the package verifies against its own key set only, never a remote one.
|
|
17
|
+
*/
|
|
18
|
+
export const passportKit = (options: PassportOptions): PassportKit => {
|
|
19
|
+
const tokens = new AuthTokens(options)
|
|
20
|
+
const verifier = new AuthPrincipalVerifier(tokens)
|
|
21
|
+
const verify: VerifyAccessToken = async (token) => {
|
|
22
|
+
const verified = await verifier.verify(token)
|
|
23
|
+
return verified.kind === "ok" ? ok(verified.value) : refused(PassportErrorCode.AccessTokenInvalid)
|
|
24
|
+
}
|
|
25
|
+
return { tokens, verify }
|
|
26
|
+
}
|
|
@@ -1,68 +1,39 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AuthOptions } from "@starci/auth/server"
|
|
2
2
|
import type { Outcome } from "@modules/platform/primitives"
|
|
3
3
|
import type { PassportErrorCode } from "./errors/passport.error"
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* The typed settings of the sign-in, parsed once at boot by `passport.config.ts` and handed to @starci/auth: the signing keys
|
|
7
|
+
* (the first with a private key signs; every key verifies and is published as the JWKS), the access and refresh lifetimes, the
|
|
8
|
+
* Google client, the refresh cookie and the sign-in budget. The stores the package needs (sessions, accounts, rate counter)
|
|
9
|
+
* are not settings: `domain/identity` provides them.
|
|
10
|
+
*/
|
|
11
|
+
export type PassportOptions = Pick<
|
|
12
|
+
AuthOptions,
|
|
13
|
+
| "issuer"
|
|
14
|
+
| "audience"
|
|
15
|
+
| "keys"
|
|
16
|
+
| "accessTtlSeconds"
|
|
17
|
+
| "refreshTtlSeconds"
|
|
18
|
+
| "reuseGraceMs"
|
|
19
|
+
| "bcryptCost"
|
|
20
|
+
| "returnOrigin"
|
|
21
|
+
| "google"
|
|
22
|
+
| "cookie"
|
|
23
|
+
| "budget"
|
|
24
|
+
>
|
|
25
|
+
|
|
5
26
|
/** The claims admitted from one of the app's own access tokens after every cryptographic and semantic check. */
|
|
6
27
|
export interface OwnPrincipal {
|
|
7
28
|
/** The user identifier from the verified `sub` claim (a row of auth.users). */
|
|
8
29
|
readonly id: string
|
|
9
30
|
/** The verified email claim when the user has one. */
|
|
10
31
|
readonly email?: string
|
|
32
|
+
/** The device session the token belongs to, when the token names one. */
|
|
33
|
+
readonly sessionId?: string
|
|
11
34
|
/** The role every access token the app issues carries; any other role is refused. */
|
|
12
35
|
readonly role: "authenticated"
|
|
13
36
|
}
|
|
14
37
|
|
|
15
38
|
/** Verifies one bearer token against the app's own key set and returns only claims that passed every check. */
|
|
16
39
|
export type VerifyAccessToken = (token: string) => Promise<Outcome<OwnPrincipal, PassportErrorCode>>
|
|
17
|
-
|
|
18
|
-
/** Issues one short-lived access token for a signed-in user. */
|
|
19
|
-
export type IssueAccessToken = (user: { readonly id: string; readonly email?: string }) => Promise<string>
|
|
20
|
-
|
|
21
|
-
/** Google OAuth 2.0 client configuration (authorization code with state and PKCE). */
|
|
22
|
-
export interface GoogleOptions {
|
|
23
|
-
/** The OAuth client id. */
|
|
24
|
-
readonly clientId: string
|
|
25
|
-
/** The OAuth client secret. */
|
|
26
|
-
readonly clientSecret: Secret
|
|
27
|
-
/** The absolute callback URL of the back end's Google callback door. */
|
|
28
|
-
readonly callbackUrl: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** The app's own asymmetric access tokens and the JWKS that publishes their public key. */
|
|
32
|
-
export interface TokenOptions {
|
|
33
|
-
/** The issuer (`iss`), the app's public API origin. */
|
|
34
|
-
readonly issuer: string
|
|
35
|
-
/** The audience (`aud`) every token names. */
|
|
36
|
-
readonly audience: string
|
|
37
|
-
/** The ES256 private key in PKCS#8 PEM; its public half is served as the JWKS. */
|
|
38
|
-
readonly privateKey: Secret
|
|
39
|
-
/** The key id (`kid`) of the signing key. */
|
|
40
|
-
readonly keyId: string
|
|
41
|
-
/** The access-token lifetime in seconds. */
|
|
42
|
-
readonly accessTtlSeconds: number
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** The rotating refresh token: an opaque value held only in an httpOnly, Secure, SameSite=Strict cookie. */
|
|
46
|
-
export interface RefreshCookieOptions {
|
|
47
|
-
/** The cookie name. */
|
|
48
|
-
readonly name: string
|
|
49
|
-
/** The path the browser sends the cookie to: the refresh door only. */
|
|
50
|
-
readonly path: string
|
|
51
|
-
/** The refresh-token lifetime in seconds. */
|
|
52
|
-
readonly ttlSeconds: number
|
|
53
|
-
/**
|
|
54
|
-
* The bare registrable domain every cookie passport sets and clears carries as `Domain` (`example.com`, no scheme,
|
|
55
|
-
* no port), so a web app on another subdomain can forward the cookie to this API. Unset keeps the cookies host-only.
|
|
56
|
-
*/
|
|
57
|
-
readonly cookieDomain?: string | undefined
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** The passport integration configuration parsed once at API boot. */
|
|
61
|
-
export interface PassportOptions {
|
|
62
|
-
/** Google OAuth 2.0. */
|
|
63
|
-
readonly google: GoogleOptions
|
|
64
|
-
/** The app's own access tokens. */
|
|
65
|
-
readonly tokens: TokenOptions
|
|
66
|
-
/** The refresh cookie. */
|
|
67
|
-
readonly refresh: RefreshCookieOptions
|
|
68
|
-
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AuthModule } from "@starci/auth/server"
|
|
2
|
+
import type { AccountDirectory, RateCounter, SessionStore } from "@starci/auth/server"
|
|
3
|
+
import type { DynamicModule } from "@nestjs/common"
|
|
4
|
+
import { CLOCK } from "@modules/platform/clock"
|
|
5
|
+
import type { Clock } from "@modules/platform/clock"
|
|
6
|
+
import { IDS } from "@modules/platform/ids"
|
|
7
|
+
import type { Ids } from "@modules/platform/ids"
|
|
8
|
+
import { PASSPORT_ACCOUNTS, PASSPORT_AUTHENTICATOR, PASSPORT_RATE_COUNTER, PASSPORT_SESSION_STORE } from "./passport.decorators"
|
|
9
|
+
import type { PassportOptions } from "./passport.options"
|
|
10
|
+
|
|
11
|
+
/** Whether the app root registers the sign-in package globally: the root decides, never the module. */
|
|
12
|
+
export interface PassportRegistration {
|
|
13
|
+
/** True when every module of the app may inject what the package provides, without importing it. */
|
|
14
|
+
readonly isGlobal: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The sign-in package as a Nest module: the typed options, the clock and ids of the app, and the stores the identity domain provides. */
|
|
18
|
+
export const passportModule = (options: PassportOptions, registration: PassportRegistration): DynamicModule =>
|
|
19
|
+
AuthModule.registerAsync({
|
|
20
|
+
isGlobal: registration.isGlobal,
|
|
21
|
+
authenticator: PASSPORT_AUTHENTICATOR,
|
|
22
|
+
inject: [PASSPORT_SESSION_STORE, PASSPORT_ACCOUNTS, PASSPORT_RATE_COUNTER, CLOCK, IDS],
|
|
23
|
+
useFactory: (sessions: SessionStore, accounts: AccountDirectory, rate: RateCounter, clock: Clock, ids: Ids) => ({
|
|
24
|
+
...options,
|
|
25
|
+
sessions,
|
|
26
|
+
accounts,
|
|
27
|
+
rate,
|
|
28
|
+
clock,
|
|
29
|
+
ids,
|
|
30
|
+
}),
|
|
31
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export type { Principal, PublicExecuteParams } from "./cqrs.contracts"
|
|
2
|
-
export { InjectQueryBus } from "./cqrs.decorators"
|
|
2
|
+
export { InjectCommandBus, InjectQueryBus } from "./cqrs.decorators"
|
|
3
3
|
export { ICQRSHandler } from "./cqrs.handler"
|
|
4
4
|
export { CqrsModule } from "./cqrs.module"
|
|
@@ -5,7 +5,7 @@ import type { DatabaseOptions } from "./database.options"
|
|
|
5
5
|
import { PRIMARY_CONNECTION } from "./primary.connection"
|
|
6
6
|
|
|
7
7
|
/** The primary pool's override token for infrastructure fixtures; TypeORM tokens stay owned by this capability. */
|
|
8
|
-
export const PRIMARY_DATA_SOURCE = getDataSourceToken(PRIMARY_CONNECTION)
|
|
8
|
+
export const PRIMARY_DATA_SOURCE: ReturnType<typeof getDataSourceToken> = getDataSourceToken(PRIMARY_CONNECTION)
|
|
9
9
|
|
|
10
10
|
/** Token of the options of the database capability. */
|
|
11
11
|
export const DATABASE_OPTIONS: unique symbol = Symbol("platform.database.options")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { identityEntities } from "@modules/domain/identity"
|
|
1
2
|
import { httpSecurityEntities } from "@modules/platform/http-security"
|
|
2
3
|
import type { EntityClass } from "./database.options"
|
|
3
4
|
|
|
@@ -7,4 +8,4 @@ import type { EntityClass } from "./database.options"
|
|
|
7
8
|
* file, from the deep path `@modules/platform/database/entities`; DatabaseModule and the database barrel never do, since the
|
|
8
9
|
* capability barrels import the database capability back.
|
|
9
10
|
*/
|
|
10
|
-
export const PRIMARY_ENTITIES: ReadonlyArray<EntityClass> = [...httpSecurityEntities]
|
|
11
|
+
export const PRIMARY_ENTITIES: ReadonlyArray<EntityClass> = [...httpSecurityEntities, ...identityEntities]
|
|
@@ -6,7 +6,7 @@ export type { HttpSecurityOptions } from "./http-security.options"
|
|
|
6
6
|
export { HTTP_SECURITY_MESSAGES } from "./messages/http-security.messages"
|
|
7
7
|
export { OriginGuard, ServerToServer } from "./origin.guard"
|
|
8
8
|
export { httpSecurityEntities } from "./persistence/entities"
|
|
9
|
-
export { RateLimitGuard } from "./rate-limit.guard"
|
|
9
|
+
export { RateLimit, RateLimitGuard, RateTier } from "./rate-limit.guard"
|
|
10
10
|
export { applyHttpSecurity } from "./edge-security.service"
|
|
11
11
|
export { ReadinessService } from "./readiness.service"
|
|
12
12
|
export { InjectReadiness } from "./readiness.decorators"
|
package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { RateWindowEntity } from "./rate-window.entity"
|
|
2
2
|
|
|
3
3
|
/** The entities of the http-security capability, named in PRIMARY_ENTITIES of platform/database/entities.ts. */
|
|
4
|
-
export const httpSecurityEntities = [RateWindowEntity]
|
|
4
|
+
export const httpSecurityEntities: ReadonlyArray<typeof RateWindowEntity> = [RateWindowEntity]
|
|
@@ -4,8 +4,14 @@ import { ACCESS_TOKEN_VERIFIER } from "@modules/domain/identity"
|
|
|
4
4
|
import { OWN_TOKEN_VERIFIER } from "@modules/integrations/passport"
|
|
5
5
|
import { OBJECT_STORAGE } from "@modules/integrations/object-storage"
|
|
6
6
|
|
|
7
|
+
/** What a wiring proof hands the compiled root: public options and the infrastructure doubles. */
|
|
8
|
+
export interface WiringProof {
|
|
9
|
+
readonly options: Readonly<Record<string, unknown>>
|
|
10
|
+
readonly overrides: ReadonlyArray<{ readonly token: unknown; readonly value: unknown }>
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
/** Compiles the real API root with public configuration and explicit infrastructure doubles. */
|
|
8
|
-
export const buildWiringProof = () => ({
|
|
14
|
+
export const buildWiringProof = (): WiringProof => ({
|
|
9
15
|
options: {
|
|
10
16
|
server: { port: 3000 },
|
|
11
17
|
httpSecurity: {
|
|
@@ -21,9 +27,16 @@ export const buildWiringProof = () => ({
|
|
|
21
27
|
schema: "public",
|
|
22
28
|
},
|
|
23
29
|
passport: {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
issuer: "https://api.fixture.example",
|
|
31
|
+
audience: "fixture",
|
|
32
|
+
keys: [{ privateKey: new Secret("fixture-only"), keyId: "k1" }],
|
|
33
|
+
accessTtlSeconds: 900,
|
|
34
|
+
refreshTtlSeconds: 2592000,
|
|
35
|
+
bcryptCost: 4,
|
|
36
|
+
returnOrigin: "https://app.fixture.example",
|
|
37
|
+
google: null,
|
|
38
|
+
cookie: { name: "refresh_token" },
|
|
39
|
+
budget: { loginAttempts: 10, resetAttempts: 5, windowMs: 900000 },
|
|
27
40
|
},
|
|
28
41
|
objectStorage: {
|
|
29
42
|
endpoint: "https://storage.fixture.example",
|
|
@@ -31,7 +44,7 @@ export const buildWiringProof = () => ({
|
|
|
31
44
|
bucket: "fixture",
|
|
32
45
|
accessKeyId: new Secret("fixture-only"),
|
|
33
46
|
secretAccessKey: new Secret("fixture-only"),
|
|
34
|
-
|
|
47
|
+
defaultExpiresIn: 300,
|
|
35
48
|
},
|
|
36
49
|
},
|
|
37
50
|
overrides: [
|
|
@@ -2,8 +2,14 @@ import { getDataSourceToken } from "@nestjs/typeorm"
|
|
|
2
2
|
import { Secret } from "@modules/platform/config"
|
|
3
3
|
import { PRIMARY_CONNECTION } from "@modules/platform/database"
|
|
4
4
|
|
|
5
|
+
/** What a wiring proof hands the compiled root: public options and the infrastructure doubles. */
|
|
6
|
+
export interface WiringProof {
|
|
7
|
+
readonly options: Readonly<Record<string, unknown>>
|
|
8
|
+
readonly overrides: ReadonlyArray<{ readonly token: unknown; readonly value: unknown }>
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
/** Public fixture options and the database double for compiling the real CLI AppModule without network access. */
|
|
6
|
-
export const buildWiringProof = () => ({
|
|
12
|
+
export const buildWiringProof = (): WiringProof => ({
|
|
7
13
|
options: {
|
|
8
14
|
connections: [{
|
|
9
15
|
name: PRIMARY_CONNECTION,
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
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
|
+
|
|
4
7
|
/** The configurable-module base of the {{name}} module. */
|
|
5
|
-
export const
|
|
6
|
-
|
|
8
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
9
|
+
/** The injection token of the registered options. */
|
|
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
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
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
|
+
|
|
4
7
|
/** The configurable-module base of the {{name}}-graphql module. */
|
|
5
|
-
export const
|
|
6
|
-
|
|
8
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
9
|
+
/** The injection token of the registered options. */
|
|
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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { {{Singular}}Entity } from "./{{name}}.entity"
|
|
2
2
|
|
|
3
3
|
/** The entities of the {{name}} capability, named in PRIMARY_ENTITIES of platform/database/entities.ts. */
|
|
4
|
-
export const {{nameCamel}}Entities = [{{Singular}}Entity]
|
|
4
|
+
export const {{nameCamel}}Entities: ReadonlyArray<typeof {{Singular}}Entity> = [{{Singular}}Entity]
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { {{Name}}Options } from "./{{name}}.options"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<{{Name}}Options>()
|
|
4
|
+
const built = new ConfigurableModuleBuilder<{{Name}}Options>()
|
|
6
5
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
7
6
|
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<{{Name}}Options, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module base of the {{name}} capability. */
|
|
10
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -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
|
})
|
|
@@ -7,8 +7,9 @@ export interface AppliedMigrationRow {
|
|
|
7
7
|
readonly name: string
|
|
8
8
|
}
|
|
9
9
|
{{migrationQueries}}
|
|
10
|
-
/** 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. */
|
|
11
11
|
export const appliedMigrations = (
|
|
12
|
-
manager: EntityManager,
|
|
12
|
+
manager: EntityManager | undefined,
|
|
13
13
|
query: ReturnType<typeof sql>,
|
|
14
|
-
): Promise<ReadonlyArray<AppliedMigrationRow>> =>
|
|
14
|
+
): Promise<ReadonlyArray<AppliedMigrationRow>> =>
|
|
15
|
+
manager === undefined ? Promise.reject(new Error("The test world wired no such connection")) : manager.query(query)
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { applyCorrelation } from "@modules/platform/logging"
|
|
7
7
|
import { defineTestWorld } from "@starci/test-world"
|
|
8
|
+
import type { TestWorldApi } from "@starci/test-world"
|
|
8
9
|
{{appModuleImports}}
|
|
9
10
|
import { {{optionsImports}} } from "./test-apps.options"
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const world: TestWorldApi = defineTestWorld({
|
|
12
13
|
stack: ".starcistacks/dev",
|
|
13
14
|
stacks: {
|
|
14
15
|
postgresql: { connections: [{{connectionDeclarations}}] },
|
|
@@ -19,3 +20,9 @@ export const { useTestWorld, useSandbox } = defineTestWorld({
|
|
|
19
20
|
migrate: { module: migrateCli, options: cliOptionsOf },
|
|
20
21
|
modules: { base: platformBase },
|
|
21
22
|
})
|
|
23
|
+
|
|
24
|
+
/** Registers the hooks that boot and close the world around a spec and answers its handle; call it inside `describe`. */
|
|
25
|
+
export const useTestWorld: TestWorldApi["useTestWorld"] = world.useTestWorld
|
|
26
|
+
|
|
27
|
+
/** The contract layer: the real integration client against a provider sandbox, in a `contract-spec`. */
|
|
28
|
+
export const useSandbox: TestWorldApi["useSandbox"] = world.useSandbox
|
|
@@ -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
|
+
}
|
|
@@ -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
|
)
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { useDebugValue } from "react"
|
|
4
4
|
import { useAppSelector } from "@/redux"
|
|
5
|
+
import type { SessionState } from "@/redux"
|
|
5
6
|
|
|
6
7
|
/** The reader's session as the app shares it (redux, kept by the auth session effect): unknown, signed in or signed out. */
|
|
7
|
-
export const useSession = () => {
|
|
8
|
+
export const useSession = (): SessionState => {
|
|
8
9
|
const session = useAppSelector((state) => state.session)
|
|
9
10
|
useDebugValue(session.status)
|
|
10
11
|
return session
|
|
@@ -12,18 +12,26 @@ const FAILURES: readonly SignInFailure[] = ["refused", "invalid", "unavailable"]
|
|
|
12
12
|
/** The failure of a sign-in's failure kind. */
|
|
13
13
|
const failureOf = (kind: string): SignInFailure => FAILURES.find((each) => each === kind) ?? "unavailable"
|
|
14
14
|
|
|
15
|
+
/** What the sign-in hook answers: whether a request runs, why the last one failed, the sign-in itself and `reject`. */
|
|
16
|
+
export interface SignIn {
|
|
17
|
+
readonly failure: SignInFailure | undefined
|
|
18
|
+
readonly pending: boolean
|
|
19
|
+
readonly signIn: (email: string, password: string) => void
|
|
20
|
+
readonly reject: () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
/**
|
|
16
24
|
* Signs the reader in with an email and a password through modules/auth: whether a request runs, why the last one failed,
|
|
17
25
|
* the sign-in itself and `reject` for input the form could not read. A success needs no state here: the session effect
|
|
18
26
|
* moves the reader to signed in.
|
|
19
27
|
*/
|
|
20
|
-
export const useSignIn = () => {
|
|
28
|
+
export const useSignIn = (): SignIn => {
|
|
21
29
|
const [failure, setFailure] = useState<SignInFailure>()
|
|
22
30
|
const [pending, startTransition] = useTransition()
|
|
23
|
-
const signIn = (email: string, password: string) =>
|
|
31
|
+
const signIn = (email: string, password: string): void =>
|
|
24
32
|
startTransition(async () => {
|
|
25
33
|
const failed = await authClient.signInWithPassword(email, password)
|
|
26
34
|
setFailure(failed === undefined ? undefined : failureOf(failed.kind))
|
|
27
35
|
})
|
|
28
|
-
return { failure, pending, signIn, reject: () => setFailure("invalid") }
|
|
36
|
+
return { failure, pending, signIn, reject: (): void => setFailure("invalid") }
|
|
29
37
|
}
|
|
@@ -11,14 +11,20 @@ const responseOf = (failure: AuthFailure | undefined) =>
|
|
|
11
11
|
? { success: true as const, data: null }
|
|
12
12
|
: { success: false as const, error: failure.code, kind: failure.kind, message: failure.message }
|
|
13
13
|
|
|
14
|
+
/** What the sign-out hook answers: whether the request runs and the sign-out itself. */
|
|
15
|
+
export interface SignOut {
|
|
16
|
+
readonly pending: boolean
|
|
17
|
+
readonly signOut: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Ends the reader's session through modules/auth: whether the request runs and the sign-out itself. A failure is reported by
|
|
16
22
|
* modules/toast; a success needs no toast, the session effect moves the reader to signed out.
|
|
17
23
|
*/
|
|
18
|
-
export const useSignOut = () => {
|
|
24
|
+
export const useSignOut = (): SignOut => {
|
|
19
25
|
const withToast = useGraphQLWithToast()
|
|
20
26
|
const [pending, startTransition] = useTransition()
|
|
21
|
-
const signOut = () =>
|
|
27
|
+
const signOut = (): void =>
|
|
22
28
|
startTransition(async () => {
|
|
23
29
|
await withToast(async () => responseOf(await authClient.signOut()), { showSuccessToast: false })
|
|
24
30
|
})
|
|
@@ -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
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { create } from "zustand"
|
|
4
|
+
import type { StoreApi, UseBoundStore } from "zustand"
|
|
4
5
|
|
|
5
6
|
/** The home guide's local UI state; it carries no session or server data. */
|
|
6
7
|
type AppGuideState = {
|
|
@@ -9,7 +10,7 @@ type AppGuideState = {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/** One feature-local store, shared by the guide trigger and its controlled dialog. */
|
|
12
|
-
export const useAppGuideStore = create<AppGuideState>()((set) => ({
|
|
13
|
+
export const useAppGuideStore: UseBoundStore<StoreApi<AppGuideState>> = create<AppGuideState>()((set) => ({
|
|
13
14
|
isOpen: false,
|
|
14
15
|
setOpen: (isOpen) => set({ isOpen }),
|
|
15
16
|
}))
|
|
@@ -23,7 +23,7 @@ const useToastMessages = (): ToastMessages => {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** A localized runGraphQLWithToast: every GraphQL write reports its outcome through it. */
|
|
26
|
-
export const useGraphQLWithToast = () => {
|
|
26
|
+
export const useGraphQLWithToast = (): (<T>(action: () => Promise<GraphQLResponse<T>>, options?: RunWithToastOptions) => Promise<GraphQLResponse<T>>) => {
|
|
27
27
|
const messages = useToastMessages()
|
|
28
28
|
return useCallback(
|
|
29
29
|
<T>(action: () => Promise<GraphQLResponse<T>>, options?: RunWithToastOptions) =>
|