@starci/hfs 5.0.8 → 5.1.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 +10 -0
- 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/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 +7 -7
- package/runtime/knowledge/hfs/rules.yaml +52 -1
- 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/explicit-surface-rule.d.mts +58 -0
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
- package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
- package/runtime/scripts/lib/declaration-surface.mjs +112 -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/src/modules/domain/identity/identity.decorators.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +5 -1
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +5 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/database.port.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 +7 -1
- 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/fixtures/migrations.sql.ts.tpl +1 -0
- package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
- package/templates/fe/common/app-layout.tsx.partial +1 -1
- 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/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 +4 -1
- 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 +19 -1
- 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-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
- 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/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import { HTTP_SECURITY_OPTIONS } from "./http-security.decorators"
|
|
3
3
|
import type { HttpSecurityOptions } from "./http-security.options"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
|
|
7
|
-
new ConfigurableModuleBuilder<HttpSecurityOptions>({ optionsInjectionToken: HTTP_SECURITY_OPTIONS })
|
|
5
|
+
const built = new ConfigurableModuleBuilder<HttpSecurityOptions>({ optionsInjectionToken: HTTP_SECURITY_OPTIONS })
|
|
8
6
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
9
7
|
.build()
|
|
8
|
+
type Host = ConfigurableModuleHost<HttpSecurityOptions, "register", "create", { isGlobal: boolean }>
|
|
9
|
+
|
|
10
|
+
/** The configurable-module base of the http-security capability; `isGlobal` is decided by the app root. */
|
|
11
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
12
|
+
/** The injection token of the registered options. */
|
|
13
|
+
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
14
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
15
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { DatabaseConnectionOptions } from "@modules/platform/database"
|
|
1
2
|
import { RateWindowEntity } from "./entities/rate-window.entity"
|
|
2
3
|
import { CreateRateWindows1789900000000 } from "./migrations/1789900000000-create-rate-windows"
|
|
3
4
|
|
|
4
5
|
/** The entities of the http-security capability, for the primary connection its rate limit counts in. */
|
|
5
|
-
export const httpSecurityEntities = [RateWindowEntity]
|
|
6
|
+
export const httpSecurityEntities: DatabaseConnectionOptions["entities"] = [RateWindowEntity]
|
|
6
7
|
|
|
7
8
|
/** The migrations of the http-security capability, in the order they run. */
|
|
8
|
-
export const httpSecurityMigrations = [CreateRateWindows1789900000000]
|
|
9
|
+
export const httpSecurityMigrations: DatabaseConnectionOptions["migrations"] = [CreateRateWindows1789900000000]
|
package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { sql } from "@modules/platform/database"
|
|
2
|
+
import type { SqlText } from "@modules/platform/database"
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Counts one hit in the caller's window and answers the hits of that window: a missing or expired window opens afresh at one
|
|
5
6
|
* hit and ends at $3 ($1 tier and address key, $2 instant, $3 end of a fresh window).
|
|
6
7
|
*/
|
|
7
|
-
export const HIT_RATE_WINDOW = sql`INSERT INTO http_security_rate_windows (key, hits, reset_at)
|
|
8
|
+
export const HIT_RATE_WINDOW: SqlText = sql`INSERT INTO http_security_rate_windows (key, hits, reset_at)
|
|
8
9
|
VALUES ($1::varchar, 1, $3::timestamptz)
|
|
9
10
|
ON CONFLICT (key) DO UPDATE SET
|
|
10
11
|
hits = CASE WHEN http_security_rate_windows.reset_at <= $2::timestamptz THEN 1
|
|
@@ -14,7 +15,7 @@ export const HIT_RATE_WINDOW = sql`INSERT INTO http_security_rate_windows (key,
|
|
|
14
15
|
RETURNING hits`
|
|
15
16
|
|
|
16
17
|
/** Deletes a bounded batch of the windows that ended by $1; a second replica skips the rows this one holds. */
|
|
17
|
-
export const PRUNE_RATE_WINDOWS = sql`DELETE FROM http_security_rate_windows WHERE key IN (
|
|
18
|
+
export const PRUNE_RATE_WINDOWS: SqlText = sql`DELETE FROM http_security_rate_windows WHERE key IN (
|
|
18
19
|
SELECT key FROM http_security_rate_windows WHERE reset_at <= $1::timestamptz
|
|
19
20
|
LIMIT 100 FOR UPDATE SKIP LOCKED
|
|
20
21
|
)`
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import { I18N_OPTIONS } from "./i18n.decorators"
|
|
3
3
|
import type { I18nOptions } from "./i18n.options"
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
|
|
7
|
-
new ConfigurableModuleBuilder<I18nOptions>({ optionsInjectionToken: I18N_OPTIONS })
|
|
5
|
+
const built = new ConfigurableModuleBuilder<I18nOptions>({ optionsInjectionToken: I18N_OPTIONS })
|
|
8
6
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
9
7
|
.build()
|
|
8
|
+
type Host = ConfigurableModuleHost<I18nOptions, "register", "create", { isGlobal: boolean }>
|
|
9
|
+
|
|
10
|
+
/** The configurable-module base of the i18n capability; `isGlobal` is decided by the app root. */
|
|
11
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
12
|
+
/** The injection token of the registered options. */
|
|
13
|
+
export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
|
|
14
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
15
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { IdsOptions } from "./ids.options"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<IdsOptions>()
|
|
4
|
+
const built = new ConfigurableModuleBuilder<IdsOptions>()
|
|
6
5
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
7
6
|
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<IdsOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module base of the ids capability; `isGlobal` is decided by the app root. */
|
|
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
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { LoggingOptions } from "./logging.options"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<LoggingOptions>()
|
|
4
|
+
const built = new ConfigurableModuleBuilder<LoggingOptions>()
|
|
6
5
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
7
6
|
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<LoggingOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module base of the logging capability; `isGlobal` is decided by the app root. */
|
|
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
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { ConfigurableModuleBuilder } from "@nestjs/common"
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
2
|
import type { RetryOptions } from "./retry.options"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<RetryOptions>()
|
|
4
|
+
const built = new ConfigurableModuleBuilder<RetryOptions>()
|
|
6
5
|
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
7
6
|
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<RetryOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module base of the retry capability; the app root decides its global scope. */
|
|
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
|
|
@@ -47,7 +47,7 @@ export const Credential = (metadata: CredentialMetadata): DoorDecorator =>
|
|
|
47
47
|
applyDecorators(SetMetadata(CREDENTIAL_KEY, metadata), ServerToServer())
|
|
48
48
|
|
|
49
49
|
/** The authenticated caller established by the default-deny guard. */
|
|
50
|
-
export const CurrentPrincipal = createParamDecorator((_data: unknown, context: ExecutionContext): Principal => {
|
|
50
|
+
export const CurrentPrincipal: ReturnType<typeof createParamDecorator> = createParamDecorator((_data: unknown, context: ExecutionContext): Principal => {
|
|
51
51
|
const principal = requestOf(context).principal
|
|
52
52
|
if (!principal) throw new IdentityError({ code: IdentityErrorCode.Unauthenticated })
|
|
53
53
|
return principal
|
package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Injectable } from "@nestjs/common"
|
|
2
|
+
import type { Type } from "@nestjs/common"
|
|
2
3
|
import { PassportStrategy } from "@nestjs/passport"
|
|
3
4
|
import { Strategy } from "passport-google-oauth20"
|
|
4
5
|
import type { Profile } from "passport-google-oauth20"
|
|
@@ -14,9 +15,12 @@ export interface GoogleIdentity {
|
|
|
14
15
|
readonly email?: string
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/** The Passport strategy base of the google scheme, typed as the class the strategy extends. */
|
|
19
|
+
const GoogleStrategyBase: Type<object> = PassportStrategy(Strategy, "google")
|
|
20
|
+
|
|
17
21
|
@Injectable()
|
|
18
22
|
/** Google OAuth 2.0 (authorization code with state and PKCE): it proves the Google identity and never signs the user in itself. */
|
|
19
|
-
export class GoogleStrategy extends
|
|
23
|
+
export class GoogleStrategy extends GoogleStrategyBase {
|
|
20
24
|
constructor(@InjectPassportOptions() options: PassportOptions) {
|
|
21
25
|
super({
|
|
22
26
|
clientID: options.google.clientId,
|
package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Injectable } from "@nestjs/common"
|
|
2
|
+
import type { Type } from "@nestjs/common"
|
|
2
3
|
import { PassportStrategy } from "@nestjs/passport"
|
|
3
4
|
import { Strategy } from "passport-local"
|
|
4
5
|
import { PassportError, PassportErrorCode } from "../errors/passport.error"
|
|
@@ -18,9 +19,12 @@ export interface PasswordAccount {
|
|
|
18
19
|
/** The lookup the identity domain provides: the account of one email, or undefined. */
|
|
19
20
|
export type FindPasswordAccount = (email: string) => Promise<PasswordAccount | undefined>
|
|
20
21
|
|
|
22
|
+
/** The Passport strategy base of the local scheme, typed as the class the strategy extends. */
|
|
23
|
+
const LocalStrategyBase: Type<object> = PassportStrategy(Strategy, "local")
|
|
24
|
+
|
|
21
25
|
@Injectable()
|
|
22
26
|
/** Email and password sign-in: bcrypt against the stored hash; a miss and a wrong password fail alike. */
|
|
23
|
-
export class LocalStrategy extends
|
|
27
|
+
export class LocalStrategy extends LocalStrategyBase {
|
|
24
28
|
constructor(@InjectPasswordAccounts() private readonly findAccount: FindPasswordAccount) {
|
|
25
29
|
super({ usernameField: "email", passwordField: "password" })
|
|
26
30
|
}
|
|
@@ -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")
|
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: {
|
|
@@ -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
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EntityManager } from "typeorm"
|
|
2
2
|
import { sql } from "@modules/platform/database"
|
|
3
|
+
import type { SqlText } from "@modules/platform/database"
|
|
3
4
|
|
|
4
5
|
/** One row of a connection's migration ledger: the name of a migration the cli migrate step applied. */
|
|
5
6
|
export interface AppliedMigrationRow {
|
|
@@ -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
|
|
@@ -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
|
})
|
|
@@ -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) =>
|
|
@@ -18,8 +18,11 @@ const INDICATOR: Record<"success" | "danger" | "warning" | "info", ReactNode> =
|
|
|
18
18
|
info: <InformationCircleIcon className="size-6" />,
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** The app's toast: HeroUI's, with the four status methods wrapped. */
|
|
22
|
+
type AppToast = typeof heroToast
|
|
23
|
+
|
|
21
24
|
/** The app's toast: HeroUI's imperative toast with the status indicator injected; the region is mounted by the providers. */
|
|
22
|
-
export const toast = {
|
|
25
|
+
export const toast: AppToast = {
|
|
23
26
|
...heroToast,
|
|
24
27
|
success: (message: ReactNode, options?: StatusToastOptions) =>
|
|
25
28
|
heroToast.success(message, { indicator: INDICATOR.success, ...options }),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useDispatch, useSelector } from "react-redux"
|
|
2
|
+
import type { UseDispatch, UseSelector } from "react-redux"
|
|
2
3
|
import type { AppDispatch, RootState } from "./store"
|
|
3
4
|
|
|
4
5
|
/** The typed dispatch of the app's store. */
|
|
5
|
-
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
|
|
6
|
+
export const useAppDispatch: UseDispatch<AppDispatch> = useDispatch.withTypes<AppDispatch>()
|
|
6
7
|
|
|
7
8
|
/** The typed selector of the app's store. */
|
|
8
|
-
export const useAppSelector = useSelector.withTypes<RootState>()
|
|
9
|
+
export const useAppSelector: UseSelector<RootState> = useSelector.withTypes<RootState>()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSlice } from "@reduxjs/toolkit"
|
|
2
|
-
import type { PayloadAction } from "@reduxjs/toolkit"
|
|
2
|
+
import type { CaseReducer, PayloadAction, Reducer, Slice } from "@reduxjs/toolkit"
|
|
3
3
|
import type { AuthSession } from "@/modules/auth"
|
|
4
4
|
|
|
5
5
|
/** The reader's session as the app shares it: unknown until modules/auth answers, then signed in or signed out. */
|
|
@@ -8,10 +8,18 @@ export interface SessionState {
|
|
|
8
8
|
readonly session?: AuthSession
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/** The reducers of the session slice. */
|
|
12
|
+
interface SessionReducers {
|
|
13
|
+
sessionChanged: CaseReducer<SessionState, PayloadAction<AuthSession | undefined>>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The session slice as the store and the effects see it. */
|
|
17
|
+
type SessionSlice = Slice<SessionState, SessionReducers, "session">
|
|
18
|
+
|
|
11
19
|
const initialState: SessionState = { status: "unknown" }
|
|
12
20
|
|
|
13
21
|
/** The session slice: written only by the auth session effect (hooks/effects), read by any component through useAppSelector. */
|
|
14
|
-
export const sessionSlice = createSlice({
|
|
22
|
+
export const sessionSlice: SessionSlice = createSlice({
|
|
15
23
|
name: "session",
|
|
16
24
|
initialState,
|
|
17
25
|
reducers: {
|
|
@@ -21,6 +29,6 @@ export const sessionSlice = createSlice({
|
|
|
21
29
|
})
|
|
22
30
|
|
|
23
31
|
/** The one action of the session slice: modules/auth reported a session (or none). */
|
|
24
|
-
export const
|
|
32
|
+
export const sessionChanged: SessionSlice["actions"]["sessionChanged"] = sessionSlice.actions.sessionChanged
|
|
25
33
|
/** The reducer the store mounts under `session`. */
|
|
26
|
-
export const sessionReducer = sessionSlice.reducer
|
|
34
|
+
export const sessionReducer: Reducer<SessionState> = sessionSlice.reducer
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { configureStore } from "@reduxjs/toolkit"
|
|
2
|
+
import type { EnhancedStore } from "@reduxjs/toolkit"
|
|
2
3
|
import { sessionReducer } from "./slices/session"
|
|
4
|
+
import type { SessionState } from "./slices/session"
|
|
5
|
+
|
|
6
|
+
/** The state of the store, for useAppSelector: one slice per key of the root reducer. */
|
|
7
|
+
export interface RootState {
|
|
8
|
+
readonly session: SessionState
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
/** The one store of the app: app-wide shared state only (feature-local state lives in hooks/zustand). */
|
|
5
|
-
export const store = configureStore({
|
|
12
|
+
export const store: EnhancedStore<RootState> = configureStore({
|
|
6
13
|
reducer: {
|
|
7
14
|
session: sessionReducer,
|
|
8
15
|
},
|
|
9
16
|
})
|
|
10
17
|
|
|
11
|
-
/** The state of the store, for useAppSelector. */
|
|
12
|
-
export type RootState = ReturnType<typeof store.getState>
|
|
13
18
|
/** The dispatch of the store, for useAppDispatch. */
|
|
14
19
|
export type AppDispatch = typeof store.dispatch
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Metadata } from "next"
|
|
2
|
+
import type { ReactElement, ReactNode } from "react"
|
|
2
3
|
import { LocaleShell } from "@{{project}}/ui"
|
|
3
4
|
import { AppProviders } from "@/components/providers/AppProviders"
|
|
4
5
|
import { AppLayout, appLayoutMetadata } from "@/features/layouts/AppLayout"
|
|
@@ -11,10 +12,10 @@ type LayoutProps = {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/** The localized document title of this route segment. */
|
|
14
|
-
export const generateMetadata = async (props: LayoutProps) => appLayoutMetadata((await props.params).locale)
|
|
15
|
+
export const generateMetadata = async (props: LayoutProps): Promise<Metadata> => appLayoutMetadata((await props.params).locale)
|
|
15
16
|
|
|
16
17
|
/** The locale segment's layout slot: the document of the address's language, the app's client providers and its chrome. */
|
|
17
|
-
const Layout = async (props: LayoutProps) => (
|
|
18
|
+
const Layout = async (props: LayoutProps): Promise<ReactElement> => (
|
|
18
19
|
<LocaleShell
|
|
19
20
|
{...await appI18n.readLocaleSegment(props.params)}
|
|
20
21
|
content={
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Metadata } from "next"
|
|
2
2
|
import { getTranslations } from "next-intl/server"
|
|
3
|
-
import type { ReactNode } from "react"
|
|
3
|
+
import type { ReactElement, ReactNode } from "react"
|
|
4
4
|
import { SiteShell } from "@{{project}}/ui"
|
|
5
5
|
import { siteUrl } from "@/modules/config"
|
|
6
6
|
import { APP_ROUTES } from "@/modules/routes"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactElement } from "react"
|
|
1
2
|
import type { Metadata } from "next"
|
|
2
3
|
import { getTranslations } from "next-intl/server"
|
|
3
4
|
import { AppGuideOverlay } from "@/features/overlays/AppGuideOverlay"
|
|
@@ -10,7 +11,7 @@ export const appHomeMetadata = async (): Promise<Metadata> => {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/** The first page of the product app: a server component; the session surface it draws runs on the client. */
|
|
13
|
-
export const AppHomePage = async () => {
|
|
14
|
+
export const AppHomePage = async (): Promise<ReactElement> => {
|
|
14
15
|
const t = await getTranslations("app.home")
|
|
15
16
|
return (
|
|
16
17
|
<>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { ReactElement } from "react"
|
|
1
2
|
import { getTranslations } from "next-intl/server"
|
|
2
3
|
import { LoadingNotice } from "@{{project}}/ui"
|
|
3
4
|
|
|
4
5
|
/** Shown while a route of the product app resolves. */
|
|
5
|
-
export const AppLoadingPage = async () => {
|
|
6
|
+
export const AppLoadingPage = async (): Promise<ReactElement> => {
|
|
6
7
|
const t = await getTranslations("app.loading")
|
|
7
8
|
return <LoadingNotice label={t("message")} />
|
|
8
9
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { ReactElement } from "react"
|
|
1
2
|
import { getTranslations } from "next-intl/server"
|
|
2
3
|
import { NotFoundNotice } from "@{{project}}/ui"
|
|
3
4
|
import { APP_ROUTES } from "@/modules/routes"
|
|
4
5
|
|
|
5
6
|
/** Shown when a route of the product app calls `notFound()`. */
|
|
6
|
-
export const AppNotFoundPage = async () => {
|
|
7
|
+
export const AppNotFoundPage = async (): Promise<ReactElement> => {
|
|
7
8
|
const t = await getTranslations("app.notFound")
|
|
8
9
|
return <NotFoundNotice title={t("title")} homeLabel={t("home")} homeHref={APP_ROUTES.home} />
|
|
9
10
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createAppI18n } from "@{{project}}/i18n"
|
|
2
|
+
import type { AppI18n } from "@{{project}}/i18n"
|
|
2
3
|
|
|
3
4
|
/** The product app's i18n: the product's one next-intl stack (the i18n package), with this app's own catalogs under `messages/`. */
|
|
4
|
-
export const appI18n = createAppI18n("app", (locale) => import(`./messages/${locale}.json`))
|
|
5
|
+
export const appI18n: AppI18n = createAppI18n("app", (locale) => import(`./messages/${locale}.json`))
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { appI18n } from "./index"
|
|
2
|
+
import type { AppI18n } from "@{{project}}/i18n"
|
|
2
3
|
|
|
3
4
|
/** The request config next-intl's plugin loads (next.config.ts): the product stack over this app's catalogs. */
|
|
4
|
-
|
|
5
|
+
const requestConfig: AppI18n["requestConfig"] = appI18n.requestConfig
|
|
6
|
+
|
|
7
|
+
export default requestConfig
|