@starci/hfs 5.0.8 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +10 -1
  3. package/cache/import-graph.mjs +6 -61
  4. package/cache/lint-family.d.mts +10 -1
  5. package/cache/lint-family.mjs +12 -5
  6. package/codemod/directive-first.d.mts +48 -0
  7. package/codemod/directive-first.mjs +136 -0
  8. package/codemod/explicit-surface-edit.d.mts +14 -0
  9. package/codemod/explicit-surface-edit.mjs +57 -0
  10. package/codemod/explicit-surface-names.d.mts +30 -0
  11. package/codemod/explicit-surface-names.mjs +135 -0
  12. package/codemod/explicit-surface-render.d.mts +29 -0
  13. package/codemod/explicit-surface-render.mjs +197 -0
  14. package/codemod/explicit-surface.d.mts +50 -0
  15. package/codemod/explicit-surface.mjs +374 -0
  16. package/codemod/main.d.mts +7 -5
  17. package/codemod/main.mjs +37 -16
  18. package/codemod/source-tree.d.mts +8 -0
  19. package/codemod/source-tree.mjs +23 -0
  20. package/db/postgres.mjs +9 -1
  21. package/lint/eslint-list.cjs +29 -0
  22. package/lint/run.d.mts +19 -5
  23. package/lint/run.mjs +47 -14
  24. package/package.json +1 -1
  25. package/runtime/knowledge/hfs/canon-pins.yaml +31 -10
  26. package/runtime/knowledge/hfs/fe-stack.yaml +7 -7
  27. package/runtime/knowledge/hfs/fork-images.yaml +7 -0
  28. package/runtime/knowledge/hfs/rules.yaml +107 -8
  29. package/runtime/knowledge/hfs/slots.yaml +75 -24
  30. package/runtime/modules/kernel/failure-codes.yaml +62 -0
  31. package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
  32. package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
  33. package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
  34. package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
  35. package/runtime/scripts/hfs/architecture/transport-owner.d.mts +1 -2
  36. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -6
  37. package/runtime/scripts/hfs/explicit-surface-rule.d.mts +58 -0
  38. package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
  39. package/runtime/scripts/hfs/manifest-shape.d.mts +1 -0
  40. package/runtime/scripts/hfs/manifest-shape.mjs +3 -1
  41. package/runtime/scripts/hfs/rules/a15-findings.mjs +5 -0
  42. package/runtime/scripts/hfs/rules/stacks-local.d.mts +7 -0
  43. package/runtime/scripts/hfs/rules/stacks-local.mjs +71 -0
  44. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +6 -1
  45. package/runtime/scripts/hfs/rules/stacks-service-images.d.mts +12 -0
  46. package/runtime/scripts/hfs/rules/stacks-service-images.mjs +120 -0
  47. package/runtime/scripts/hfs/secret-dev-init.d.mts +11 -0
  48. package/runtime/scripts/hfs/secret-dev-init.mjs +79 -0
  49. package/runtime/scripts/hfs/secret.mjs +10 -7
  50. package/runtime/scripts/hfs/slots.mjs +1 -1
  51. package/runtime/scripts/hfs/stack-local-config.d.mts +101 -0
  52. package/runtime/scripts/hfs/stack-local-config.mjs +320 -0
  53. package/runtime/scripts/hfs/stacks-gate.mjs +1 -1
  54. package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
  55. package/runtime/scripts/lib/declaration-surface.mjs +112 -0
  56. package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
  57. package/runtime/scripts/lib/swarm-stack.mjs +2 -2
  58. package/scaffold/add-module.d.mts +16 -0
  59. package/scaffold/add-module.mjs +232 -0
  60. package/scaffold/add-queue-lite.mjs +49 -0
  61. package/scaffold/add.mjs +5 -0
  62. package/scaffold/app.mjs +7 -7
  63. package/scaffold/module-registry.d.mts +19 -0
  64. package/scaffold/module-registry.mjs +84 -0
  65. package/src/main.mjs +23 -2
  66. package/stack/cli.mjs +6 -1
  67. package/stack/context.d.mts +15 -0
  68. package/stack/context.mjs +1 -0
  69. package/stack/local-cli.d.mts +25 -0
  70. package/stack/local-cli.mjs +89 -0
  71. package/stack/local-config.d.mts +17 -0
  72. package/stack/local-config.mjs +37 -0
  73. package/stack/local-custody.d.mts +24 -0
  74. package/stack/local-custody.mjs +109 -0
  75. package/stack/local-doctor.d.mts +6 -0
  76. package/stack/local-doctor.mjs +49 -0
  77. package/stack/local-down.d.mts +8 -0
  78. package/stack/local-down.mjs +20 -0
  79. package/stack/local-host.d.mts +42 -0
  80. package/stack/local-host.mjs +163 -0
  81. package/stack/local-logs.d.mts +8 -0
  82. package/stack/local-logs.mjs +32 -0
  83. package/stack/local-ports.d.mts +7 -0
  84. package/stack/local-ports.mjs +39 -0
  85. package/stack/local-prepare.d.mts +22 -0
  86. package/stack/local-prepare.mjs +62 -0
  87. package/stack/local-run.d.mts +71 -0
  88. package/stack/local-run.mjs +200 -0
  89. package/stack/local-state.d.mts +35 -0
  90. package/stack/local-state.mjs +29 -0
  91. package/stack/local-status.d.mts +6 -0
  92. package/stack/local-status.mjs +36 -0
  93. package/stack/local-up.d.mts +11 -0
  94. package/stack/local-up.mjs +166 -0
  95. package/stack/local-verify.d.mts +6 -0
  96. package/stack/local-verify.mjs +21 -0
  97. package/sync/index.d.mts +13 -12
  98. package/sync/index.mjs +83 -28
  99. package/templates/app/module/jest.config.js.tpl +8 -0
  100. package/templates/app/module/src/index.ts.tpl +2 -0
  101. package/templates/app/module/src/manifest/declaration.spec.ts.tpl +11 -0
  102. package/templates/app/module/src/manifest/declaration.ts.tpl +13 -0
  103. package/templates/app/module/src/manifest/index.ts.tpl +1 -0
  104. package/templates/app/module/src/public/index.ts.tpl +2 -0
  105. package/templates/app/module/src/template/__name__.template.spec.ts.tpl +13 -0
  106. package/templates/app/module/src/template/__name__.template.ts.tpl +41 -0
  107. package/templates/app/module/src/template/index.ts.tpl +1 -0
  108. package/templates/app/module/src/workbench/components/__pascal__WorkbenchView.tsx.tpl +16 -0
  109. package/templates/app/module/src/workbench/index.ts.tpl +2 -0
  110. package/templates/app/package-scripts/package.json +1 -1
  111. package/templates/app/package-scripts-lite/package.json +1 -1
  112. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +76 -70
  113. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +23 -13
  114. package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +25 -0
  115. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -26
  116. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +1 -0
  117. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +2 -1
  118. package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +57 -14
  119. package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +11 -0
  120. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.handler.ts +23 -0
  121. package/templates/be/auth-lite/src/features/api/auth/application/get-jwks.query.ts +12 -0
  122. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.command.ts +11 -0
  123. package/templates/be/auth-lite/src/features/api/auth/application/google-begin.handler.ts +22 -0
  124. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.command.ts +12 -0
  125. package/templates/be/auth-lite/src/features/api/auth/application/google-complete.handler.ts +23 -0
  126. package/templates/be/auth-lite/src/features/api/auth/application/login.command.ts +12 -0
  127. package/templates/be/auth-lite/src/features/api/auth/application/login.handler.ts +23 -0
  128. package/templates/be/auth-lite/src/features/api/auth/application/logout.command.ts +11 -0
  129. package/templates/be/auth-lite/src/features/api/auth/application/logout.handler.ts +22 -0
  130. package/templates/be/auth-lite/src/features/api/auth/application/refresh.command.ts +12 -0
  131. package/templates/be/auth-lite/src/features/api/auth/application/refresh.handler.ts +23 -0
  132. package/templates/be/auth-lite/src/features/api/auth/application/session.command.ts +12 -0
  133. package/templates/be/auth-lite/src/features/api/auth/application/session.handler.ts +23 -0
  134. package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +13 -0
  135. package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +16 -0
  136. package/templates/be/auth-lite/src/features/api/auth/auth.options.ts +3 -0
  137. package/templates/be/auth-lite/src/features/api/auth/index.ts +2 -0
  138. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +13 -0
  139. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module.ts +9 -0
  140. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.options.ts +3 -0
  141. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +90 -0
  142. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/google-start.request.ts +17 -0
  143. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/login.request.ts +16 -0
  144. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +46 -0
  145. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +28 -0
  146. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +145 -0
  147. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +95 -0
  148. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +38 -0
  149. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +54 -0
  150. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-attempt.entity.ts +18 -0
  151. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-refresh-token.entity.ts +34 -0
  152. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-session.entity.ts +30 -0
  153. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +17 -0
  154. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.spec.ts +86 -0
  155. package/templates/be/auth-lite/src/modules/domain/identity/rate-counter.service.ts +45 -0
  156. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.spec.ts +97 -0
  157. package/templates/be/auth-lite/src/modules/domain/identity/session-store.service.ts +81 -0
  158. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +78 -0
  159. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +87 -0
  160. package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +44 -0
  161. package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
  162. package/templates/be/inbox/entities.index.ts.tpl +1 -1
  163. package/templates/be/inbox/module-definition.ts.tpl +8 -2
  164. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
  165. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
  166. package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
  167. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
  168. package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
  169. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
  170. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
  171. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
  172. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
  173. package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
  174. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
  175. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
  176. package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
  177. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
  178. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
  179. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
  180. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
  181. package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
  182. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
  183. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
  184. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
  185. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
  186. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
  187. package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
  188. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
  189. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
  190. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
  191. package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
  192. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
  193. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
  194. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
  195. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
  196. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
  197. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
  198. package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
  199. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
  200. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
  201. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
  202. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
  203. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
  204. package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
  205. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
  206. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
  207. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
  208. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
  209. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
  210. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
  211. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
  212. package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
  213. package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
  214. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
  215. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
  216. package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
  217. package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
  218. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
  219. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
  220. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
  221. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
  222. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +6 -5
  223. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +3 -2
  224. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +10 -5
  225. package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +34 -0
  226. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -2
  227. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +47 -2
  228. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -0
  229. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +24 -0
  230. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +3 -3
  231. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +3 -1
  232. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +8 -4
  233. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +12 -0
  234. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts +29 -0
  235. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +6 -31
  236. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +18 -9
  237. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +35 -11
  238. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.spec.ts +44 -8
  239. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +40 -18
  240. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +18 -6
  241. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.spec.ts +73 -0
  242. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.kit.ts +26 -0
  243. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +24 -53
  244. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.registration.ts +31 -0
  245. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +1 -1
  246. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +1 -1
  247. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +2 -1
  248. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
  249. package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
  250. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
  251. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
  252. package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
  253. package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
  254. package/templates/be/table/entities.index.ts.tpl +1 -1
  255. package/templates/be/table/module-definition.ts.tpl +8 -3
  256. package/templates/be/test-world/e2e/serves-once-migrated.e2e-spec.ts.tpl +9 -9
  257. package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +4 -3
  258. package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
  259. package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
  260. package/templates/fe/common/app-layout.tsx.partial +1 -1
  261. package/templates/fe/common/site-shell.tsx.partial +3 -3
  262. package/templates/fe/skeleton/apps/__app__/src/components/providers/AppProviders/index.tsx +33 -10
  263. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/index.ts +1 -0
  264. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +2 -1
  265. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
  266. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
  267. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useViewerId.ts +11 -0
  268. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
  269. package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
  270. package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +18 -12
  271. package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
  272. package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
  273. package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
  274. package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
  275. package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
  276. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
  277. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
  278. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
  279. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
  280. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
  281. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
  282. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
  283. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
  284. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
  285. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
  286. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
  287. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
  288. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
  289. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
  290. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +20 -2
  291. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
  292. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
  293. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +4 -7
  294. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/index.tsx +2 -3
  295. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
  296. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +4 -7
  297. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/index.tsx +1 -2
  298. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +2 -2
  299. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
  300. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
  301. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
  302. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
  303. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
  304. package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
  305. package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
  306. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  307. package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
  308. package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
  309. package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
  310. package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
  311. package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
  312. package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
  313. package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
  314. package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
  315. package/templates/fe/units/page/index.tsx.tpl +3 -2
  316. package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
  317. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +0 -16
  318. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +0 -11
  319. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +0 -7
  320. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +0 -6
  321. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +0 -8
  322. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +0 -6
  323. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +0 -186
  324. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +0 -22
  325. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +0 -25
  326. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.spec.ts +0 -104
  327. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +0 -34
  328. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.spec.ts +0 -28
  329. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +0 -11
  330. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +0 -88
  331. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +0 -69
  332. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +0 -37
  333. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -36
  334. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +0 -63
  335. package/templates/fe/skeleton/packages/__project__-ui/src/frames/SiteShell/classNames.ts +0 -4
  336. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +0 -30
  337. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +0 -50
  338. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +0 -31
  339. package/templates/fe/skeleton-lite/apps/web/src/components/frames/SiteShell/classNames.ts +0 -4
@@ -18,15 +18,21 @@ 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 imperative toast with the status indicator injected; the region is mounted by the providers. */
22
- export const toast = {
23
- ...heroToast,
24
- success: (message: ReactNode, options?: StatusToastOptions) =>
25
- heroToast.success(message, { indicator: INDICATOR.success, ...options }),
26
- danger: (message: ReactNode, options?: StatusToastOptions) =>
27
- heroToast.danger(message, { indicator: INDICATOR.danger, ...options }),
28
- warning: (message: ReactNode, options?: StatusToastOptions) =>
29
- heroToast.warning(message, { indicator: INDICATOR.warning, ...options }),
30
- info: (message: ReactNode, options?: StatusToastOptions) =>
31
- heroToast.info(message, { indicator: INDICATOR.info, ...options }),
32
- }
21
+ /** The app's toast: HeroUI's, with the four status methods wrapped. */
22
+ type AppToast = typeof heroToast
23
+
24
+ /** The app's toast: HeroUI's imperative toast, callable as it is, with the four status methods wrapped to inject the status indicator; the region is mounted by the providers. */
25
+ export const toast: AppToast = Object.assign(
26
+ (message: ReactNode, options?: Parameters<typeof heroToast>[1]) => heroToast(message, options),
27
+ heroToast,
28
+ {
29
+ success: (message: ReactNode, options?: StatusToastOptions) =>
30
+ heroToast.success(message, { indicator: INDICATOR.success, ...options }),
31
+ danger: (message: ReactNode, options?: StatusToastOptions) =>
32
+ heroToast.danger(message, { indicator: INDICATOR.danger, ...options }),
33
+ warning: (message: ReactNode, options?: StatusToastOptions) =>
34
+ heroToast.warning(message, { indicator: INDICATOR.warning, ...options }),
35
+ info: (message: ReactNode, options?: StatusToastOptions) =>
36
+ heroToast.info(message, { indicator: INDICATOR.info, ...options }),
37
+ },
38
+ )
@@ -0,0 +1,5 @@
1
+ import { createViewerCache } from "@starci/next-data"
2
+ import type { ViewerCache } from "@starci/next-data"
3
+
4
+ /** The writes to the cache of the viewer on screen: the one instance of the app, bound by the client providers. */
5
+ export const viewerCache: ViewerCache = createViewerCache()
@@ -1,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,3 +1,4 @@
1
1
  export { useAppDispatch, useAppSelector } from "./hooks"
2
2
  export { ReduxProvider } from "./ReduxProvider"
3
3
  export { sessionChanged } from "./slices/session"
4
+ export type { SessionState } from "./slices/session"
@@ -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
+ type 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 { sessionChanged } = sessionSlice.actions
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 { ReactNode } from "react"
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
- export default appI18n.requestConfig
5
+ const requestConfig: AppI18n["requestConfig"] = appI18n.requestConfig
6
+
7
+ export default requestConfig
@@ -1,4 +1,5 @@
1
- import type { ReactNode } from "react"
1
+ import type { Metadata } from "next"
2
+ import type { ReactElement, ReactNode } from "react"
2
3
  import { LocaleShell } from "@{{project}}/ui"
3
4
  import { LandingLayout, landingLayoutMetadata } from "@/features/layouts/LandingLayout"
4
5
  import { landingI18n } from "@/modules/i18n"
@@ -10,10 +11,10 @@ type LayoutProps = {
10
11
  }
11
12
 
12
13
  /** The localized document title and description of the landing. */
13
- export const generateMetadata = async (props: LayoutProps) => landingLayoutMetadata((await props.params).locale)
14
+ export const generateMetadata = async (props: LayoutProps): Promise<Metadata> => landingLayoutMetadata((await props.params).locale)
14
15
 
15
16
  /** The locale segment's layout slot: the document of the address's language around the landing's chrome. */
16
- const Layout = async (props: LayoutProps) => (
17
+ const Layout = async (props: LayoutProps): Promise<ReactElement> => (
17
18
  <LocaleShell {...await landingI18n.readLocaleSegment(props.params)} content={<LandingLayout content={props.children} />} />
18
19
  )
19
20
 
@@ -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 { LANDING_ROUTES } from "@/modules/routes"
6
6
 
@@ -14,7 +14,7 @@ export const landingLayoutMetadata = async (locale: string): Promise<Metadata> =
14
14
  }
15
15
 
16
16
  /** The landing's chrome, resolved on the server: the shared brand shell with the wordmark linking home, around the routed body. */
17
- export const LandingLayout = async (props: LandingLayoutProps) => {
17
+ export const LandingLayout = async (props: LandingLayoutProps): Promise<ReactElement> => {
18
18
  const t = await getTranslations("landing.shell")
19
19
  return (
20
20
  <SiteShell brand={t("brand")} homeHref={LANDING_ROUTES.home}>
@@ -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 landing resolves. */
5
- export const LandingLoadingPage = async () => {
6
+ export const LandingLoadingPage = async (): Promise<ReactElement> => {
6
7
  const t = await getTranslations("landing.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 { LANDING_ROUTES } from "@/modules/routes"
4
5
 
5
6
  /** Shown when a route of the landing calls `notFound()`. */
6
- export const LandingNotFoundPage = async () => {
7
+ export const LandingNotFoundPage = async (): Promise<ReactElement> => {
7
8
  const t = await getTranslations("landing.notFound")
8
9
  return <NotFoundNotice title={t("title")} homeLabel={t("home")} homeHref={LANDING_ROUTES.home} />
9
10
  }
@@ -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 { appUrl } from "@/modules/config"
@@ -10,7 +11,7 @@ export const landingMetadata = async (): Promise<Metadata> => {
10
11
  }
11
12
 
12
13
  /** The landing page, resolved on the server: its copy from the catalog and the product app's origin from the config module. */
13
- export const LandingPage = async () => {
14
+ export const LandingPage = async (): Promise<ReactElement> => {
14
15
  const t = await getTranslations("landing.home")
15
16
  return <LandingPageBase props={{ title: t("title"), lede: t("lede"), openApp: t("openApp"), appHref: appUrl() }} />
16
17
  }
@@ -1,4 +1,5 @@
1
1
  import { createAppI18n } from "@{{project}}/i18n"
2
+ import type { AppI18n } from "@{{project}}/i18n"
2
3
 
3
4
  /** The landing's i18n: the product's one next-intl stack (the i18n package), with this app's own catalogs under `messages/`. */
4
- export const landingI18n = createAppI18n("landing", (locale) => import(`./messages/${locale}.json`))
5
+ export const landingI18n: AppI18n = createAppI18n("landing", (locale) => import(`./messages/${locale}.json`))
@@ -1,4 +1,7 @@
1
1
  import { landingI18n } 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 the landing's catalogs. */
4
- export default landingI18n.requestConfig
5
+ const requestConfig: AppI18n["requestConfig"] = landingI18n.requestConfig
6
+
7
+ export default requestConfig
@@ -6,6 +6,7 @@ import { getMessages, getRequestConfig } from "next-intl/server"
6
6
  import { notFound } from "next/navigation"
7
7
  import { locale as routeLocale } from "next/root-params"
8
8
  import { PRODUCT_TIME_ZONE, routing } from "./routing"
9
+ import type { ProductRouting } from "./routing"
9
10
 
10
11
  /** Loads one app's own catalog for a served locale. */
11
12
  type LoadMessages = (locale: string) => Promise<unknown>
@@ -27,6 +28,23 @@ const isLocaleReader = (value: unknown): value is () => Promise<unknown> => type
27
28
 
28
29
  const readRouteLocale = async (): Promise<unknown> => (isLocaleReader(routeLocale) ? routeLocale() : undefined)
29
30
 
31
+ /** The language the address states, its catalog and the product time zone. */
32
+ interface LocaleSegment {
33
+ readonly locale: string
34
+ readonly messages: AbstractIntlMessages
35
+ readonly timeZone: string
36
+ }
37
+
38
+ /** The navigation helpers of the product's routing (links, redirects, the locale-aware router). */
39
+ type ProductNavigation = ReturnType<typeof createNavigation<ProductRouting["locales"], "as-needed">>
40
+
41
+ /** What one app of the product reads of its next-intl stack. */
42
+ export interface AppI18n {
43
+ readonly navigation: ProductNavigation
44
+ readonly requestConfig: ReturnType<typeof getRequestConfig>
45
+ readonly readLocaleSegment: (params: Promise<{ readonly locale: string }>) => Promise<LocaleSegment>
46
+ }
47
+
30
48
  /**
31
49
  * The next-intl stack of one app of the product, written once here and called once by each app's `modules/i18n` (R59): the
32
50
  * product's one routing with its locale-aware navigation, the request config next-intl's plugin loads (the app's
@@ -34,8 +52,8 @@ const readRouteLocale = async (): Promise<unknown> => (isLocaleReader(routeLocal
34
52
  * the app in the text of a message its catalog lacks (`<app>:<namespace>.<key>`); `loadMessages` loads its own catalogs. A server
35
53
  * module: a client component reads the default locale from `./routing`.
36
54
  */
37
- export const createAppI18n = (app: string, loadMessages: LoadMessages) => ({
38
- navigation: createNavigation(routing),
55
+ export const createAppI18n = (app: string, loadMessages: LoadMessages): AppI18n => ({
56
+ navigation: createNavigation<ProductRouting["locales"], "as-needed">(routing),
39
57
  /**
40
58
  * The locale is the `[locale]` route param read through `next/root-params` (`experimental.rootParams` in the app's next.config.ts,
41
59
  * FE-I18N-1), never the deprecated `requestLocale`. An unrecognised locale resolves to the default before the loader runs, so a
@@ -20,5 +20,5 @@ const negotiate = createMiddleware(routing)
20
20
  * the default and serves the default locale (vi) without a prefix. One routing table for every app; each app's `src/proxy.ts`
21
21
  * re-exports this.
22
22
  */
23
- export const proxy = (request: NextRequest) =>
23
+ export const proxy = (request: NextRequest): NextResponse =>
24
24
  isUnnegotiated(request.nextUrl.pathname) ? NextResponse.next() : negotiate(request)
@@ -1,4 +1,5 @@
1
1
  import { defineRouting } from "next-intl/routing"
2
+ import type { RoutingConfig } from "next-intl/routing"
2
3
 
3
4
  /** The locales every app of the product serves; every catalog of an app has the same keys in each. */
4
5
  const LOCALES = ["vi", "en"] as const
@@ -9,11 +10,14 @@ export const DEFAULT_LOCALE = "vi"
9
10
  /** The product's time zone: fixed, not inferred, so the server and the hydrated client format the same instant the same way. */
10
11
  export const PRODUCT_TIME_ZONE = "Asia/Ho_Chi_Minh"
11
12
 
13
+ /** The type of the product's routing table: the served locales, no locale prefix for the default one, no localized pathnames or domains. */
14
+ export type ProductRouting = RoutingConfig<typeof LOCALES, "as-needed", never, never>
15
+
12
16
  /**
13
17
  * The one routing table of the product, for the proxy, the navigation and the request config of every app: vi default, a prefix
14
18
  * only when the locale is not the default, so `/vi` means the same thing on every app.
15
19
  */
16
- export const routing = defineRouting({
20
+ export const routing: ProductRouting = defineRouting({
17
21
  locales: LOCALES,
18
22
  defaultLocale: DEFAULT_LOCALE,
19
23
  localePrefix: "as-needed",
@@ -1,4 +1,4 @@
1
- import { Heading, TextAction } from "@starci/grammar/common"
1
+ import { Link, Page } from "@starci/grammar/common"
2
2
 
3
3
  /** The words of a page that does not exist and the way back. */
4
4
  type NotFoundNoticeProps = {
@@ -10,10 +10,7 @@ type NotFoundNoticeProps = {
10
10
 
11
11
  /** A missing page: the message and the way back to the app's front door. */
12
12
  export const NotFoundNotice = (props: NotFoundNoticeProps) => (
13
- <>
14
- <Heading level={1}>{props.title}</Heading>
15
- <TextAction appearance="inline" href={props.homeHref}>
16
- {props.homeLabel}
17
- </TextAction>
18
- </>
13
+ <Page header={{ title: props.title }} measure="reading">
14
+ <Link href={props.homeHref}>{props.homeLabel}</Link>
15
+ </Page>
19
16
  )
@@ -1,6 +1,5 @@
1
1
  import type { ReactNode } from "react"
2
- import { GrammarRoot, PageContainer, TextAction, TopBar, WorkspaceShell } from "@starci/grammar/common"
3
- import { siteShellClassNames } from "./classNames"
2
+ import { GrammarRoot, Link, TopBar, WorkspaceShell } from "@starci/grammar/common"
4
3
 
5
4
  /** What an app's chrome hands the shell: the brand, its home link and the routed body. */
6
5
  type SiteShellProps = {
@@ -14,6 +13,6 @@ type SiteShellProps = {
14
13
 
15
14
  /**
16
15
  * The document chrome both apps share: Grammar's Common boundary, the brand header (the top bar carrying the wordmark that links
17
- * home), then the routed body in the shell's main landmark at reading measure.
16
+ * home), then the routed body in the shell's main landmark (each page draws its own measure).
18
17
  */
19
18
  {{> fe/common/site-shell.tsx.partial}}
@@ -1,7 +1,8 @@
1
1
  import { hasLocale } from "next-intl"
2
2
  import { getMessages } from "next-intl/server"
3
3
  import { notFound } from "next/navigation"
4
- import type { ReactNode } from "react"
4
+ import type { Metadata } from "next"
5
+ import type { ReactElement, ReactNode } from "react"
5
6
  import { AppLayout, appLayoutMetadata } from "@/features/layouts/AppLayout"
6
7
  import { PRODUCT_TIME_ZONE, routing } from "@/modules/i18n"
7
8
  import { Providers } from "./providers"
@@ -14,10 +15,10 @@ type LayoutProps = {
14
15
  }
15
16
 
16
17
  /** The localized document title of this route segment. */
17
- export const generateMetadata = async (props: LayoutProps) => appLayoutMetadata((await props.params).locale)
18
+ export const generateMetadata = async (props: LayoutProps): Promise<Metadata> => appLayoutMetadata((await props.params).locale)
18
19
 
19
20
  /** The locale segment's document, providers and app chrome. */
20
- const Layout = async (props: LayoutProps) => {
21
+ const Layout = async (props: LayoutProps): Promise<ReactElement> => {
21
22
  const { locale } = await props.params
22
23
  if (!hasLocale(routing.locales, locale)) notFound()
23
24
  const messages = await getMessages()
@@ -1,4 +1,4 @@
1
- import { Heading, TextAction } from "@starci/grammar/common"
1
+ import { Link, Page } from "@starci/grammar/common"
2
2
 
3
3
  /** The words of a missing page and the way home. */
4
4
  type NotFoundNoticeProps = {
@@ -9,10 +9,7 @@ type NotFoundNoticeProps = {
9
9
 
10
10
  /** A missing page with one destination back to the app's front door. */
11
11
  export const NotFoundNotice = (props: NotFoundNoticeProps) => (
12
- <>
13
- <Heading level={1}>{props.title}</Heading>
14
- <TextAction appearance="inline" href={props.homeHref}>
15
- {props.homeLabel}
16
- </TextAction>
17
- </>
12
+ <Page header={{ title: props.title }} measure="reading">
13
+ <Link href={props.homeHref}>{props.homeLabel}</Link>
14
+ </Page>
18
15
  )
@@ -1,6 +1,5 @@
1
1
  import type { ReactNode } from "react"
2
- import { GrammarRoot, PageContainer, TextAction, TopBar, WorkspaceShell } from "@starci/grammar/common"
3
- import { siteShellClassNames } from "./classNames"
2
+ import { GrammarRoot, Link, TopBar, WorkspaceShell } from "@starci/grammar/common"
4
3
 
5
4
  /** What the app chrome hands the shell. */
6
5
  type SiteShellProps = {
@@ -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 "@/components/frames/SiteShell"
5
5
  import { siteUrl } from "@/modules/config"
6
6
  import { APP_ROUTES } from "@/modules/routes"
@@ -17,7 +17,7 @@ export const appLayoutMetadata = async (locale: string): Promise<Metadata> => {
17
17
  }
18
18
 
19
19
  /** The server-resolved app chrome around the routed body. */
20
- export const AppLayout = async (props: AppLayoutProps) => {
20
+ export const AppLayout = async (props: AppLayoutProps): Promise<ReactElement> => {
21
21
  const t = await getTranslations("app.shell")
22
22
  return (
23
23
  <html lang={props.locale}>
@@ -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 front door (the session is client-side, drawn by the blocks the page composes). */
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 "@/components/composites/LoadingNotice"
3
4
 
4
5
  /** Shown while a route 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 "@/components/composites/NotFoundNotice"
3
4
  import { APP_ROUTES } from "@/modules/routes"
4
5
 
5
6
  /** Shown when a route does not exist. */
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
  }
@@ -21,7 +21,7 @@ const LOCALE_HEADER = "x-next-intl-locale"
21
21
  * The request config next-intl's plugin loads. The locale is the one the middleware negotiated: this module is reachable from the proxy
22
22
  * through the navigation, and `next/root-params` can only be imported inside the app directory.
23
23
  */
24
- export default getRequestConfig(async () => {
24
+ const requestConfig: ReturnType<typeof getRequestConfig> = getRequestConfig(async () => {
25
25
  const requested = (await headers()).get(LOCALE_HEADER)
26
26
  const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale
27
27
  return {
@@ -31,3 +31,5 @@ export default getRequestConfig(async () => {
31
31
  getMessageFallback: ({ namespace, key }) => `web:${namespace ?? ""}.${key}`,
32
32
  }
33
33
  })
34
+
35
+ export default requestConfig
@@ -7,7 +7,11 @@ export const DEFAULT_LOCALE = "vi" as const
7
7
  export const PRODUCT_TIME_ZONE = "Asia/Ho_Chi_Minh"
8
8
 
9
9
  /** The one locale-routing table of the web app. */
10
- export const routing = {
10
+ export const routing: {
11
+ readonly locales: typeof LOCALES
12
+ readonly defaultLocale: typeof DEFAULT_LOCALE
13
+ readonly localePrefix: "as-needed"
14
+ } = {
11
15
  locales: LOCALES,
12
16
  defaultLocale: DEFAULT_LOCALE,
13
17
  localePrefix: "as-needed" as const,
@@ -1,5 +1,6 @@
1
1
  import { useSWRConfig } from "swr"
2
2
  import useSWRMutation from "swr/mutation"
3
+ import type { SWRMutationResponse } from "swr/mutation"
3
4
  import type { GraphQLResponse } from "@/modules/api"
4
5
  import { mutateCreate{{Singular}} } from "@/modules/api/graphql/mutations/mutation-create-{{singular}}"
5
6
  import type { MutateCreate{{Singular}}Data } from "@/modules/api/graphql/mutations/types/create-{{singular}}"
@@ -12,7 +13,7 @@ export const MUTATE_CREATE_{{singularUpper}}_SWR_KEY = "MUTATE_CREATE_{{singular
12
13
  * Creates one {{singular}} row; answers the GraphQLResponse so the caller reports it through modules/toast
13
14
  * (useGraphQLWithToast). A success primes the {{singular}} query of the new row with that response.
14
15
  */
15
- export const useMutateCreate{{Singular}}Swr = () => {
16
+ export const useMutateCreate{{Singular}}Swr = (): SWRMutationResponse<GraphQLResponse<MutateCreate{{Singular}}Data>, Error, string, never> => {
16
17
  const { mutate } = useSWRConfig()
17
18
  return useSWRMutation(MUTATE_CREATE_{{singularUpper}}_SWR_KEY, async () => {
18
19
  const response: GraphQLResponse<MutateCreate{{Singular}}Data> = await mutateCreate{{Singular}}({ request: {} })
@@ -1,4 +1,5 @@
1
1
  import useSWR from "swr"
2
+ import type { SWRResponse } from "swr"
2
3
  import type { GraphQLResponse } from "@/modules/api"
3
4
  import { query{{Singular}} } from "@/modules/api/graphql/queries/query-{{singular}}"
4
5
  import type { Query{{Singular}}Data } from "@/modules/api/graphql/queries/types/{{singular}}"
@@ -7,7 +8,7 @@ import type { Query{{Singular}}Data } from "@/modules/api/graphql/queries/types/
7
8
  export const QUERY_{{singularUpper}}_SWR_KEY = "QUERY_{{singularUpper}}_SWR"
8
9
 
9
10
  /** One {{singular}} row of the signed-in reader by id, as its GraphQLResponse; no id, no request. */
10
- export const useQuery{{Singular}}Swr = (id: string | undefined) =>
11
+ export const useQuery{{Singular}}Swr = (id: string | undefined): SWRResponse<GraphQLResponse<Query{{Singular}}Data>, Error> =>
11
12
  useSWR(
12
13
  id === undefined ? null : ([QUERY_{{singularUpper}}_SWR_KEY, id] as const),
13
14
  ([, rowId]): Promise<GraphQLResponse<Query{{Singular}}Data>> => query{{Singular}}({ request: { id: rowId } }),
@@ -23,7 +23,7 @@ export interface CreateAuthApolloClientOptions {
23
23
  * retry (transient network failures) -> error (report) -> timeout -> refresh (one token refresh and replay on
24
24
  * UNAUTHENTICATED) -> locale (`Accept-Language`) -> access token (`Authorization: Bearer`, from modules/auth) -> http (terminal).
25
25
  */
26
- export const createAuthApolloClient = ({ signal, report }: CreateAuthApolloClientOptions) =>
26
+ export const createAuthApolloClient = ({ signal, report }: CreateAuthApolloClientOptions): ApolloClient =>
27
27
  new ApolloClient({
28
28
  link: ApolloLink.from([
29
29
  createRetryLink(),
@@ -19,7 +19,7 @@ export interface CreateNoAuthApolloClientOptions {
19
19
  * The anonymous Apollo client for public operations: retry -> error -> timeout -> locale -> http. It never attaches a
20
20
  * token, so a public operation stays public even for a signed-in reader.
21
21
  */
22
- export const createNoAuthApolloClient = ({ signal, report }: CreateNoAuthApolloClientOptions) =>
22
+ export const createNoAuthApolloClient = ({ signal, report }: CreateNoAuthApolloClientOptions): ApolloClient =>
23
23
  new ApolloClient({
24
24
  link: ApolloLink.from([
25
25
  createRetryLink(),