@starci/hfs 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/cache/hfs-family.d.mts +2 -3
  4. package/cache/hfs-family.mjs +4 -9
  5. package/cache/secret-records.d.mts +7 -0
  6. package/cache/secret-records.mjs +12 -0
  7. package/cache/session.mjs +4 -2
  8. package/codemod/account-doors.d.mts +36 -0
  9. package/codemod/account-doors.mjs +128 -0
  10. package/codemod/main.mjs +31 -1
  11. package/codemod/module-definition.d.mts +52 -0
  12. package/codemod/module-definition.mjs +242 -0
  13. package/codemod/quality-binding.d.mts +33 -0
  14. package/codemod/quality-binding.mjs +38 -0
  15. package/codemod/secrets-cache.d.mts +35 -0
  16. package/codemod/secrets-cache.mjs +70 -0
  17. package/codemod/sops-rules.d.mts +34 -0
  18. package/codemod/sops-rules.mjs +69 -0
  19. package/codemod/theme-tokens.mjs +48 -2
  20. package/lint/run.d.mts +6 -0
  21. package/lint/run.mjs +63 -10
  22. package/package.json +1 -1
  23. package/runtime/knowledge/hfs/canon-pins.yaml +43 -10
  24. package/runtime/knowledge/hfs/fe-stack.yaml +3 -3
  25. package/runtime/knowledge/hfs/rules.yaml +13 -9
  26. package/runtime/knowledge/hfs/slots.yaml +105 -12
  27. package/runtime/modules/kernel/failure-codes.yaml +5 -4
  28. package/runtime/scripts/hfs/architecture/config.d.mts +4 -0
  29. package/runtime/scripts/hfs/architecture/config.mjs +25 -1
  30. package/runtime/scripts/hfs/architecture/module-per-transport.d.mts +1 -1
  31. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +6 -1
  32. package/runtime/scripts/hfs/architecture/root-packages.d.mts +26 -0
  33. package/runtime/scripts/hfs/architecture/root-packages.mjs +78 -0
  34. package/runtime/scripts/hfs/architecture/type-context.d.mts +4 -1
  35. package/runtime/scripts/hfs/architecture/type-context.mjs +30 -0
  36. package/runtime/scripts/hfs/architecture/typescript.d.mts +7 -1
  37. package/runtime/scripts/hfs/architecture/typescript.mjs +21 -1
  38. package/runtime/scripts/hfs/explicit-surface-rule.mjs +1 -1
  39. package/runtime/scripts/hfs/rules/database-access.mjs +39 -1
  40. package/runtime/scripts/hfs/rules/monorepo.d.mts +4 -0
  41. package/runtime/scripts/hfs/rules/monorepo.mjs +10 -5
  42. package/runtime/scripts/hfs/rules/secrets.mjs +11 -0
  43. package/runtime/scripts/hfs/secret-errors.d.mts +3 -0
  44. package/runtime/scripts/hfs/secret-errors.mjs +8 -0
  45. package/runtime/scripts/hfs/secret-init.d.mts +6 -0
  46. package/runtime/scripts/hfs/secret-init.mjs +23 -0
  47. package/runtime/scripts/hfs/secret-sops.mjs +9 -1
  48. package/runtime/scripts/hfs/secret-standard.d.mts +20 -0
  49. package/runtime/scripts/hfs/secret-standard.mjs +106 -0
  50. package/runtime/scripts/hfs/secret.mjs +5 -1
  51. package/runtime/scripts/hfs/view.d.mts +23 -0
  52. package/runtime/scripts/hfs/view.mjs +68 -0
  53. package/runtime/scripts/lib/declaration-surface.d.mts +4 -0
  54. package/runtime/scripts/lib/declaration-surface.mjs +55 -1
  55. package/runtime/scripts/lib/secret-records.d.mts +13 -0
  56. package/runtime/scripts/lib/secret-records.mjs +83 -0
  57. package/scaffold/add-inbox-lite.mjs +2 -2
  58. package/scaffold/add-queue-lite.mjs +1 -3
  59. package/scaffold/app.d.mts +2 -2
  60. package/scaffold/app.mjs +10 -12
  61. package/scaffold/lite-exports.mjs +1 -15
  62. package/scripts/sync-bundles.d.mts +8 -0
  63. package/stack/images/openclaw-runtime/Dockerfile +18 -0
  64. package/stack/images/openclaw-runtime/entrypoint.sh +23 -0
  65. package/stack/images/openclaw-runtime/openclaw-init.mjs +101 -0
  66. package/stack/images/openclaw-runtime/openclaw-relay.mjs +25 -0
  67. package/stack/local-up.mjs +3 -0
  68. package/stack/runtime-layers.d.mts +21 -0
  69. package/stack/runtime-layers.mjs +67 -0
  70. package/sync/hygiene.mjs +3 -1
  71. package/sync/index.d.mts +2 -0
  72. package/sync/index.mjs +33 -2
  73. package/templates/app/auth-doors.sql.partial +115 -0
  74. package/templates/app/ci-workflows/github/workflows/ci.yml +1 -1
  75. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -1
  76. package/templates/app/package-scripts/core.json +1 -1
  77. package/templates/app/package-scripts/package.json +1 -1
  78. package/templates/app/package-scripts-lite/package.json +1 -1
  79. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +9 -0
  80. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +3 -1
  81. package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +19 -1
  82. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +3 -0
  83. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +5 -0
  84. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +4 -0
  85. package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +24 -4
  86. package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +33 -0
  87. package/templates/be/auth-lite/src/features/api/auth/application/confirm.command.ts +11 -0
  88. package/templates/be/auth-lite/src/features/api/auth/application/confirm.handler.ts +22 -0
  89. package/templates/be/auth-lite/src/features/api/auth/application/forgot.command.ts +11 -0
  90. package/templates/be/auth-lite/src/features/api/auth/application/forgot.handler.ts +22 -0
  91. package/templates/be/auth-lite/src/features/api/auth/application/resend.command.ts +11 -0
  92. package/templates/be/auth-lite/src/features/api/auth/application/resend.handler.ts +22 -0
  93. package/templates/be/auth-lite/src/features/api/auth/application/reset.command.ts +11 -0
  94. package/templates/be/auth-lite/src/features/api/auth/application/reset.handler.ts +22 -0
  95. package/templates/be/auth-lite/src/features/api/auth/application/signup.command.ts +11 -0
  96. package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +22 -0
  97. package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +3 -10
  98. package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +6 -1
  99. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +3 -10
  100. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +56 -1
  101. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/forgot.request.ts +16 -0
  102. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/resend.request.ts +16 -0
  103. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +18 -0
  104. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +22 -0
  105. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/token.request.ts +11 -0
  106. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +30 -30
  107. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +7 -16
  108. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +113 -49
  109. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +42 -53
  110. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.log-events.ts +6 -0
  111. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +233 -0
  112. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +176 -0
  113. package/templates/be/auth-lite/src/modules/domain/identity/identity-sign-in.log-events.ts +6 -0
  114. package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +98 -0
  115. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-email-token.entity.ts +34 -0
  116. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +3 -10
  117. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +17 -0
  118. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +6 -0
  119. package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +29 -33
  120. package/templates/be/inbox/module-definition.ts.tpl +2 -9
  121. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +2 -11
  122. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +2 -9
  123. package/templates/be/patterns/api/feature.module-definition.ts.tpl +3 -9
  124. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +3 -9
  125. package/templates/be/patterns/cli/group.module-definition.ts.tpl +2 -9
  126. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +3 -10
  127. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +2 -9
  128. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +2 -9
  129. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +3 -9
  130. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +3 -10
  131. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +2 -9
  132. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +3 -10
  133. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +2 -9
  134. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +3 -9
  135. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +2 -9
  136. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +3 -9
  137. package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +3 -9
  138. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +3 -9
  139. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +3 -9
  140. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +2 -9
  141. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +3 -9
  142. package/templates/be/platform/graphql/graphql.module-definition.ts +2 -9
  143. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +3 -9
  144. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +3 -9
  145. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +2 -9
  146. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +3 -9
  147. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +2 -9
  148. package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +2 -9
  149. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +2 -9
  150. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +2 -9
  151. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +2 -9
  152. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +2 -0
  153. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +2 -9
  154. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +3 -13
  155. package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +4 -11
  156. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +4 -9
  157. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +5 -11
  158. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +4 -11
  159. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +2 -9
  160. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +2 -9
  161. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +2 -9
  162. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +4 -0
  163. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +3 -0
  164. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +3 -1
  165. package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +6 -0
  166. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +3 -0
  167. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +1 -0
  168. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity-mail.messages.ts +65 -0
  169. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +4 -0
  170. package/templates/be/skeleton-lite/src/modules/integrations/mail/index.ts +6 -0
  171. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.config.ts +33 -0
  172. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.decorators.ts +14 -0
  173. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module-definition.ts +12 -0
  174. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module.ts +27 -0
  175. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.options.ts +22 -0
  176. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +2 -9
  177. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +1 -1
  178. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  179. package/templates/be/skeleton-lite/src/modules/platform/http-security/rate-limit.guard.spec.ts +11 -0
  180. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +11 -0
  181. package/templates/be/table/api/feature.module-definition.ts.tpl +3 -9
  182. package/templates/be/table/api/graphql.module-definition.ts.tpl +3 -9
  183. package/templates/be/table/module-definition.ts.tpl +2 -9
  184. package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +16 -0
  185. package/templates/fe/auth-lite/apps/web/src/modules/auth/index.ts +5 -0
  186. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +30 -67
  187. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +6 -21
  188. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +10 -16
  189. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/confirm/page.tsx +9 -0
  190. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/forgot/page.tsx +9 -0
  191. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/resend/page.tsx +9 -0
  192. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/reset/page.tsx +9 -0
  193. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/signup/page.tsx +9 -0
  194. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +5 -9
  195. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +51 -0
  196. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +84 -0
  197. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppConfirmPage/index.tsx +25 -0
  198. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppForgotPage/index.tsx +21 -0
  199. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +11 -3
  200. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +4 -1
  201. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResendPage/index.tsx +21 -0
  202. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResetPage/index.tsx +25 -0
  203. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppSignupPage/index.tsx +21 -0
  204. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/emailAuth.shared.ts +12 -0
  205. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/index.ts +2 -0
  206. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/useEmailDoor.ts +32 -0
  207. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +2 -2
  208. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +50 -1
  209. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +50 -1
  210. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +7 -30
  211. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +13 -14
  212. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +5 -0
  213. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +4 -17
  214. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +0 -38
  215. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +0 -54
@@ -1,12 +1,6 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
2
2
  import type { @@Saga@@MessageOptions } from "./@@saga@@-message.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<@@Saga@@MessageOptions>().setClassMethodName("register").build()
5
- type Host = ConfigurableModuleHost<@@Saga@@MessageOptions, "register", "create", {}>
6
-
7
4
  /** The configurable-module base of the @@saga@@-message module. */
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
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ defineConfigurableModule<@@Saga@@MessageOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
2
2
  import type { @@Saga@@Options } from "./@@saga@@.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<@@Saga@@Options>().setClassMethodName("register").build()
5
- type Host = ConfigurableModuleHost<@@Saga@@Options, "register", "create", {}>
6
-
7
4
  /** The configurable-module base of the @@saga@@ module. */
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
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = defineConfigurableModule<@@Saga@@Options>()
@@ -1,12 +1,6 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
2
2
  import type { @@Provider@@HttpOptions } from "./@@provider@@-http.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<@@Provider@@HttpOptions>().setClassMethodName("register").build()
5
- type Host = ConfigurableModuleHost<@@Provider@@HttpOptions, "register", "create", {}>
6
-
7
4
  /** The configurable-module base of the @@provider@@-http module. */
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
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ defineConfigurableModule<@@Provider@@HttpOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { GraphqlOptions } from "./graphql.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<GraphqlOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<GraphqlOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** The configurable-module base of the graphql 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<GraphqlOptions>()
@@ -1,12 +1,6 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
2
2
  import type { SystemHealthOptions } from "./system-health.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<SystemHealthOptions>().setClassMethodName("register").build()
5
- type Host = ConfigurableModuleHost<SystemHealthOptions, "register", "create", {}>
6
-
7
4
  /** The configurable-module base of the system-health module. */
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
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ defineConfigurableModule<SystemHealthOptions>()
@@ -1,12 +1,6 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
2
2
  import type { SystemHealthHttpOptions } from "./system-health-http.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<SystemHealthHttpOptions>().setClassMethodName("register").build()
5
- type Host = ConfigurableModuleHost<SystemHealthHttpOptions, "register", "create", {}>
6
-
7
4
  /** The configurable-module base of the system-health-http module. */
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
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ defineConfigurableModule<SystemHealthHttpOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
2
2
  import type { CliOptions } from "./cli.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<CliOptions>().setClassMethodName("register").build()
5
- type Host = ConfigurableModuleHost<CliOptions, "register", "create", {}>
6
-
7
4
  /** The configurable-module base of the cli module. */
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
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = defineConfigurableModule<CliOptions>()
@@ -1,12 +1,6 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
2
2
  import type { MigrateOptions } from "./migrate.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<MigrateOptions>().setClassMethodName("register").build()
5
- type Host = ConfigurableModuleHost<MigrateOptions, "register", "create", {}>
6
-
7
4
  /** The configurable-module base of the migrate module. */
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
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ defineConfigurableModule<MigrateOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
2
2
  import type { SeedOptions } from "./seed.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<SeedOptions>().setClassMethodName("register").build()
5
- type Host = ConfigurableModuleHost<SeedOptions, "register", "create", {}>
6
-
7
4
  /** The configurable-module base of the seed module. */
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
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = defineConfigurableModule<SeedOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { IdentityOptions } from "./identity.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<IdentityOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<IdentityOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** The configurable-module base of the identity 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<IdentityOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { LivenessOptions } from "./liveness.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<LivenessOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<LivenessOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** The configurable-module base of the liveness 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<LivenessOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { NoteOptions } from "./note.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<NoteOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<NoteOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** The configurable-module base of the note 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<NoteOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { ClockOptions } from "./clock.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<ClockOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<ClockOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** The configurable-module base of the clock 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<ClockOptions>()
@@ -6,6 +6,8 @@ export interface Principal {
6
6
  readonly email?: string
7
7
  /** The authenticated provider role. */
8
8
  readonly role: "authenticated"
9
+ /** The device session the access token belongs to, when the token names one. */
10
+ readonly sessionId?: string
9
11
  }
10
12
 
11
13
  /** The params of an authenticated operation: its mapped request and verified caller. */
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { CqrsOptions } from "./cqrs.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<CqrsOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<CqrsOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** The configurable-module base of the cqrs 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<CqrsOptions>()
@@ -1,18 +1,8 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import { DATABASE_OPTIONS } from "./database.port"
3
3
  import type { DatabaseOptions } from "./database.options"
4
4
 
5
- const built = new ConfigurableModuleBuilder<DatabaseOptions>({
5
+ /** The configurable-module base of the database capability; `isGlobal` is decided by the app root. */
6
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<DatabaseOptions>({
6
7
  optionsInjectionToken: DATABASE_OPTIONS,
7
8
  })
8
- .setExtras({ isGlobal: false }, (definition, extras) => ({
9
- ...definition,
10
- global: extras.isGlobal,
11
- }))
12
- .build()
13
- type Host = ConfigurableModuleHost<DatabaseOptions, "register", "create", { isGlobal: boolean }>
14
-
15
- /** The configurable-module base of the database capability; `isGlobal` is decided by the app root. */
16
- export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
17
- /** The options of a synchronous registration, for the register signature. */
18
- export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,15 +1,8 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import { ERRORS_OPTIONS } from "./errors.decorators"
3
3
  import type { ErrorsOptions } from "./errors.options"
4
4
 
5
- const built = new ConfigurableModuleBuilder<ErrorsOptions>({ optionsInjectionToken: ERRORS_OPTIONS })
6
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
- .build()
8
- type Host = ConfigurableModuleHost<ErrorsOptions, "register", "create", { isGlobal: boolean }>
9
-
10
5
  /** The configurable-module base of the errors 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
6
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = defineCapabilityModule<ErrorsOptions>({
7
+ optionsInjectionToken: ERRORS_OPTIONS,
8
+ })
@@ -1,13 +1,8 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import { HTTP_OPTIONS } from "./http.decorators"
3
3
  import type { HttpOptions } from "./http.options"
4
4
 
5
- const built = new ConfigurableModuleBuilder<HttpOptions>({ optionsInjectionToken: HTTP_OPTIONS })
6
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
- .build()
8
- type Host = ConfigurableModuleHost<HttpOptions, "register", "create", { isGlobal: boolean }>
9
-
10
5
  /** The configurable-module base of the http capability; `isGlobal` is decided by the app root. */
11
- export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
12
- /** The options of a synchronous registration, for the register signature. */
13
- export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
6
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<HttpOptions>({
7
+ optionsInjectionToken: HTTP_OPTIONS,
8
+ })
@@ -1,15 +1,9 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import { HTTP_SECURITY_OPTIONS } from "./http-security.decorators"
3
3
  import type { HttpSecurityOptions } from "./http-security.options"
4
4
 
5
- const built = new ConfigurableModuleBuilder<HttpSecurityOptions>({ optionsInjectionToken: HTTP_SECURITY_OPTIONS })
6
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
- .build()
8
- type Host = ConfigurableModuleHost<HttpSecurityOptions, "register", "create", { isGlobal: boolean }>
9
-
10
5
  /** 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
6
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
7
+ defineCapabilityModule<HttpSecurityOptions>({
8
+ optionsInjectionToken: HTTP_SECURITY_OPTIONS,
9
+ })
@@ -1,15 +1,8 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import { I18N_OPTIONS } from "./i18n.decorators"
3
3
  import type { I18nOptions } from "./i18n.options"
4
4
 
5
- const built = new ConfigurableModuleBuilder<I18nOptions>({ optionsInjectionToken: I18N_OPTIONS })
6
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
- .build()
8
- type Host = ConfigurableModuleHost<I18nOptions, "register", "create", { isGlobal: boolean }>
9
-
10
5
  /** 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
6
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = defineCapabilityModule<I18nOptions>({
7
+ optionsInjectionToken: I18N_OPTIONS,
8
+ })
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { IdsOptions } from "./ids.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<IdsOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<IdsOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<IdsOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { LoggingOptions } from "./logging.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<LoggingOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<LoggingOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<LoggingOptions>()
@@ -1,12 +1,5 @@
1
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineCapabilityModule } from "@starci/nest-kit"
2
2
  import type { RetryOptions } from "./retry.options"
3
3
 
4
- const built = new ConfigurableModuleBuilder<RetryOptions>()
5
- .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
- .build()
7
- type Host = ConfigurableModuleHost<RetryOptions, "register", "create", { isGlobal: boolean }>
8
-
9
4
  /** 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
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<RetryOptions>()
@@ -4,6 +4,7 @@ import { parsePrimaryDatabaseConfig } from "@modules/platform/database"
4
4
  import { parseHttpConfig } from "@modules/platform/http"
5
5
  import { parseHttpSecurityConfig } from "@modules/platform/http-security"
6
6
  import { parsePassportConfig } from "@modules/integrations/passport"
7
+ import { parseMailConfig } from "@modules/integrations/mail"
7
8
  import { parseObjectStorageConfig } from "@modules/integrations/object-storage"
8
9
 
9
10
  /** The API environment values required before composition. */
@@ -20,6 +21,8 @@ export interface ApiEnv {
20
21
  readonly passport: ReturnType<typeof parsePassportConfig>
21
22
  /** S3-compatible object storage. */
22
23
  readonly objectStorage: ReturnType<typeof parseObjectStorageConfig>
24
+ /** Outgoing mail: the SMTP server and the app's own mail settings. */
25
+ readonly mail: ReturnType<typeof parseMailConfig>
23
26
  }
24
27
 
25
28
  /** Reads the API environment; missing or invalid values fail boot, and secrets use `_FILE` first. */
@@ -30,4 +33,5 @@ export const readApiEnv = (env: EnvSource): ApiEnv => ({
30
33
  database: parsePrimaryDatabaseConfig(env),
31
34
  passport: parsePassportConfig(env),
32
35
  objectStorage: parseObjectStorageConfig(env),
36
+ mail: parseMailConfig(env),
33
37
  })
@@ -1,4 +1,5 @@
1
1
  import type { PassportOptions } from "@modules/integrations/passport"
2
+ import type { MailOptions } from "@modules/integrations/mail"
2
3
  import type { ObjectStorageOptions } from "@modules/integrations/object-storage"
3
4
  import type { ServerOptions } from "@modules/platform/config"
4
5
  import type { DatabaseConnectionOptions } from "@modules/platform/database"
@@ -19,4 +20,6 @@ export interface ApiOptions {
19
20
  readonly passport: PassportOptions
20
21
  /** S3-compatible object storage. */
21
22
  readonly objectStorage: ObjectStorageOptions
23
+ /** Outgoing mail: the SMTP server and the app's own mail settings. */
24
+ readonly mail: MailOptions
22
25
  }
@@ -1,13 +1,15 @@
1
1
  import type { DynamicModule, Provider } from "@nestjs/common"
2
2
  import { OWN_TOKEN_VERIFIER, PASSPORT_OPTIONS, passportModule } from "@modules/integrations/passport"
3
3
  import type { VerifyAccessToken } from "@modules/integrations/passport"
4
+ import { MailModule } from "@modules/integrations/mail"
4
5
  import { ObjectStorageModule } from "@modules/integrations/object-storage"
5
6
  import type { ApiOptions } from "../api.options"
6
7
 
7
- /** The sign-in and object storage packages of the app, each registered once with the app's typed settings. */
8
+ /** The sign-in, object storage and mail packages of the app, each registered once with the app's typed settings. */
8
9
  export const integrationModules = (options: ApiOptions): Array<DynamicModule> => [
9
10
  passportModule(options.passport, { isGlobal: true }),
10
11
  ObjectStorageModule.register({ ...options.objectStorage, isGlobal: true }),
12
+ MailModule.register({ ...options.mail, isGlobal: true }),
11
13
  ]
12
14
 
13
15
  /** The passport integration ports bound once for this app. */
@@ -17,6 +17,10 @@ export enum IdentityErrorCode {
17
17
  PairRefused = "IDENTITY_INVALID_CREDENTIALS",
18
18
  /** The mailbox was never confirmed. */
19
19
  EmailNotConfirmed = "IDENTITY_EMAIL_NOT_CONFIRMED",
20
+ /** The new password does not fit the policy (at least 8 characters, at most 72 bytes). */
21
+ PasswordInvalid = "IDENTITY_PASSWORD_INVALID",
22
+ /** The confirmation or reset link is unknown, expired or already spent. */
23
+ ResetTokenInvalid = "IDENTITY_RESET_TOKEN_INVALID",
20
24
  }
21
25
 
22
26
  /** How each identity code travels. */
@@ -28,6 +32,8 @@ export const IDENTITY_ERROR_KINDS: Record<IdentityErrorCode, ErrorKind> = {
28
32
  [IdentityErrorCode.TooManyAttempts]: "rate-limited",
29
33
  [IdentityErrorCode.PairRefused]: "unauthenticated",
30
34
  [IdentityErrorCode.EmailNotConfirmed]: "forbidden",
35
+ [IdentityErrorCode.PasswordInvalid]: "invalid",
36
+ [IdentityErrorCode.ResetTokenInvalid]: "invalid",
31
37
  }
32
38
 
33
39
  /** The one error class of the identity capability. */
@@ -26,6 +26,7 @@ import {
26
26
  import { ConfigurableModuleClass, OPTIONS_TYPE } from "./identity.module-definition"
27
27
  import { IdentityRateCounterService } from "./rate-counter.service"
28
28
  import { IdentitySessionStoreService } from "./session-store.service"
29
+ import { IdentityEmailService } from "./identity-email.service"
29
30
  import { IdentitySignInService } from "./sign-in.service"
30
31
 
31
32
  const refusingAuthenticator: Provider = {
@@ -61,6 +62,7 @@ export class IdentityModule extends ConfigurableModuleClass {
61
62
  IdentityRateCounterService,
62
63
  IdentityAuthenticatorService,
63
64
  IdentitySignInService,
65
+ IdentityEmailService,
64
66
  { provide: IDENTITY_AUTH_DOORS, useExisting: PASSPORT_DOORS },
65
67
  { provide: IDENTITY_AUTH_SESSIONS, useExisting: PASSPORT_SESSIONS },
66
68
  { provide: IDENTITY_AUTH_PASSWORDS, useExisting: PASSPORT_PASSWORDS },
@@ -76,6 +78,7 @@ export class IdentityModule extends ConfigurableModuleClass {
76
78
  AuthGuard,
77
79
  ACCESS_TOKEN_VERIFIER,
78
80
  IdentitySignInService,
81
+ IdentityEmailService,
79
82
  PASSPORT_SESSION_STORE,
80
83
  PASSPORT_ACCOUNTS,
81
84
  PASSPORT_RATE_COUNTER,
@@ -6,4 +6,5 @@ export { PublicReason } from "./identity.contracts"
6
6
  export { ACCESS_TOKEN_VERIFIER, Public } from "./identity.decorators"
7
7
  export { IdentityModule } from "./identity.module"
8
8
  export { identityEntities } from "./persistence/entities"
9
+ export { IdentityEmailService } from "./identity-email.service"
9
10
  export { IdentitySignInService } from "./sign-in.service"
@@ -0,0 +1,65 @@
1
+ import { renderTransactional } from "@modules/integrations/mail"
2
+ import type { MailLanguage, RenderedMail } from "@modules/integrations/mail"
3
+
4
+ /** What a confirmation or reset mail is drawn from. */
5
+ export interface LinkMailInput {
6
+ /** The product name in the header. */
7
+ readonly brand: string
8
+ /** The absolute link the button opens. */
9
+ readonly url: string
10
+ /** How many hours the link lives. */
11
+ readonly hours: number
12
+ }
13
+
14
+ interface LinkCopy {
15
+ readonly subject: string
16
+ readonly paragraphs: (input: LinkMailInput) => readonly string[]
17
+ readonly button: string
18
+ readonly footer: string
19
+ }
20
+
21
+ const CONFIRM: Record<MailLanguage, LinkCopy> = {
22
+ vi: {
23
+ subject: "Xác nhận email của bạn",
24
+ paragraphs: ({ brand, hours }) => [`Cảm ơn bạn đã đăng ký ${brand}.`, `Nhấn nút bên dưới để xác nhận email. Liên kết có hiệu lực trong ${String(hours)} giờ và chỉ dùng được một lần.`],
25
+ button: "Xác nhận email",
26
+ footer: "Nếu bạn không đăng ký, hãy bỏ qua thư này.",
27
+ },
28
+ en: {
29
+ subject: "Confirm your email",
30
+ paragraphs: ({ brand, hours }) => [`Thanks for signing up to ${brand}.`, `Press the button below to confirm your email. The link works once and lasts ${String(hours)} hours.`],
31
+ button: "Confirm email",
32
+ footer: "If you did not sign up, ignore this message.",
33
+ },
34
+ }
35
+
36
+ const RESET: Record<MailLanguage, LinkCopy> = {
37
+ vi: {
38
+ subject: "Đặt lại mật khẩu",
39
+ paragraphs: ({ hours }) => ["Chúng tôi nhận được yêu cầu đặt lại mật khẩu cho tài khoản của bạn.", `Nhấn nút bên dưới để chọn mật khẩu mới. Liên kết có hiệu lực trong ${String(hours)} giờ và chỉ dùng được một lần.`],
40
+ button: "Đặt lại mật khẩu",
41
+ footer: "Nếu bạn không yêu cầu, hãy bỏ qua thư này; mật khẩu của bạn không thay đổi.",
42
+ },
43
+ en: {
44
+ subject: "Reset your password",
45
+ paragraphs: ({ hours }) => ["We received a request to reset the password of your account.", `Press the button below to choose a new password. The link works once and lasts ${String(hours)} hours.`],
46
+ button: "Reset password",
47
+ footer: "If you did not ask for this, ignore this message; your password stays as it is.",
48
+ },
49
+ }
50
+
51
+ const draw = (copy: LinkCopy, lang: MailLanguage, input: LinkMailInput): RenderedMail =>
52
+ renderTransactional({
53
+ lang,
54
+ subject: copy.subject,
55
+ brand: { name: input.brand },
56
+ paragraphs: copy.paragraphs(input),
57
+ action: { label: copy.button, url: input.url },
58
+ footer: copy.footer,
59
+ })
60
+
61
+ /** The mail that carries the link confirming a mailbox. */
62
+ export const confirmEmailMail = (lang: MailLanguage, input: LinkMailInput): RenderedMail => draw(CONFIRM[lang], lang, input)
63
+
64
+ /** The mail that carries the link choosing a new password. */
65
+ export const resetPasswordMail = (lang: MailLanguage, input: LinkMailInput): RenderedMail => draw(RESET[lang], lang, input)
@@ -11,6 +11,8 @@ export const IDENTITY_MESSAGES: MessageBundle = {
11
11
  "errors.IDENTITY_TOO_MANY_ATTEMPTS": "Quá nhiều lần thử. Hãy thử lại sau.",
12
12
  "errors.IDENTITY_INVALID_CREDENTIALS": "Email hoặc mật khẩu không đúng.",
13
13
  "errors.IDENTITY_EMAIL_NOT_CONFIRMED": "Hãy xác nhận email trước khi đăng nhập.",
14
+ "errors.IDENTITY_PASSWORD_INVALID": "Mật khẩu cần ít nhất 8 ký tự và tối đa 72 byte.",
15
+ "errors.IDENTITY_RESET_TOKEN_INVALID": "Liên kết không hợp lệ hoặc đã hết hạn.",
14
16
  },
15
17
  en: {
16
18
  "errors.IDENTITY_UNAUTHENTICATED": "You need to sign in to do this.",
@@ -20,5 +22,7 @@ export const IDENTITY_MESSAGES: MessageBundle = {
20
22
  "errors.IDENTITY_TOO_MANY_ATTEMPTS": "Too many attempts. Try again later.",
21
23
  "errors.IDENTITY_INVALID_CREDENTIALS": "The email or password is wrong.",
22
24
  "errors.IDENTITY_EMAIL_NOT_CONFIRMED": "Confirm your email before signing in.",
25
+ "errors.IDENTITY_PASSWORD_INVALID": "The password needs at least 8 characters and at most 72 bytes.",
26
+ "errors.IDENTITY_RESET_TOKEN_INVALID": "The link is invalid or has expired.",
23
27
  },
24
28
  }
@@ -0,0 +1,6 @@
1
+ export { MAILER, MailError, escapeHtml, html, renderTransactional } from "@starci/mail"
2
+ export type { Mailer, MailMessage, RenderedMail, SentMail } from "@starci/mail"
3
+ export { parseMailConfig } from "./mail.config"
4
+ export { InjectMailSettings, InjectMailer, MAIL_SETTINGS } from "./mail.decorators"
5
+ export { MailModule } from "./mail.module"
6
+ export type { MailLanguage, MailOptions } from "./mail.options"