@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
@@ -1,88 +0,0 @@
1
- import { Test } from "@nestjs/testing"
2
- import { exportPKCS8, generateKeyPair, SignJWT } from "jose"
3
- import { Secret } from "@modules/platform/config"
4
- import { PassportErrorCode } from "./errors/passport.error"
5
- import { createTokenKit, tokenKit } from "./passport.tokens"
6
-
7
- const TOKENS: unique symbol = Symbol("passport.tokens")
8
-
9
- type SigningKey = Awaited<ReturnType<typeof generateKeyPair>>["privateKey"]
10
-
11
- const ISSUER = "https://api.example.test"
12
- const AUDIENCE = "app"
13
-
14
- const build = async () => {
15
- const moduleRef = await Test.createTestingModule({
16
- providers: [{ provide: TOKENS, useValue: { createTokenKit, tokenKit } }],
17
- }).compile()
18
- return moduleRef.get<{ createTokenKit: typeof createTokenKit; tokenKit: typeof tokenKit }>(TOKENS)
19
- }
20
-
21
- const keyPair = async () => {
22
- const { privateKey } = await generateKeyPair("ES256", { extractable: true })
23
- return { privateKey, options: { issuer: ISSUER, audience: AUDIENCE, privateKey: new Secret(await exportPKCS8(privateKey)), keyId: "k1", accessTtlSeconds: 60 } }
24
- }
25
-
26
- /** A token the kit's own key signs with the given claims, so only the claim checks can refuse it. */
27
- const ownSigned = (privateKey: SigningKey, claims: Record<string, unknown>, { subject = "user-1", expires = true } = {}) => {
28
- const jwt = new SignJWT(claims).setProtectedHeader({ alg: "ES256", kid: "k1" }).setIssuer(ISSUER).setAudience(AUDIENCE).setIssuedAt()
29
- if (subject !== "") jwt.setSubject(subject)
30
- if (expires) jwt.setExpirationTime("60s")
31
- return jwt.sign(privateKey)
32
- }
33
-
34
- describe("createTokenKit", () => {
35
- it("verifies its own token with an email against its own key set and publishes only the public key", async () => {
36
- const subject = await build()
37
- const { options } = await keyPair()
38
- const kit = await subject.createTokenKit(options)
39
-
40
- expect(await kit.verify(await kit.issue({ id: "user-1", email: "a@example.test" }))).toEqual({ kind: "ok", value: { id: "user-1", role: "authenticated", email: "a@example.test" } })
41
- expect(kit.jwks.keys).toHaveLength(1)
42
- expect(kit.jwks.keys[0]).toMatchObject({ kid: "k1", alg: "ES256", use: "sig", kty: "EC", crv: "P-256" })
43
- expect(kit.jwks.keys[0]).not.toHaveProperty("d")
44
- })
45
-
46
- it("issues a token without an email claim for a user who has none", async () => {
47
- const subject = await build()
48
- const { options } = await keyPair()
49
- const kit = await subject.createTokenKit(options)
50
-
51
- expect(await kit.verify(await kit.issue({ id: "user-2" }))).toEqual({ kind: "ok", value: { id: "user-2", role: "authenticated" } })
52
- })
53
-
54
- it("refuses a token another key signed, naming the failed signature", async () => {
55
- const subject = await build()
56
- const kit = await subject.createTokenKit((await keyPair()).options)
57
- const other = await subject.createTokenKit((await keyPair()).options)
58
-
59
- expect(await kit.verify(await other.issue({ id: "user-1" }))).toEqual({
60
- kind: "refused",
61
- code: PassportErrorCode.AccessTokenInvalid,
62
- params: { cause: "JWSSignatureVerificationFailed: signature verification failed" },
63
- })
64
- })
65
-
66
- it("refuses a correctly signed token whose subject, expiry or role is not the one it issues", async () => {
67
- const subject = await build()
68
- const { privateKey, options } = await keyPair()
69
- const kit = await subject.createTokenKit(options)
70
- const invalid = { kind: "refused", code: PassportErrorCode.AccessTokenInvalid }
71
-
72
- expect(await kit.verify(await ownSigned(privateKey, { role: "authenticated" }, { subject: "" }))).toEqual(invalid)
73
- expect(await kit.verify(await ownSigned(privateKey, { role: "authenticated" }, { expires: false }))).toEqual(invalid)
74
- expect(await kit.verify(await ownSigned(privateKey, { role: "service_role" }))).toEqual(invalid)
75
- expect(await kit.verify(await ownSigned(privateKey, { role: "authenticated", email: 7 }))).toEqual({ kind: "ok", value: { id: "user-1", role: "authenticated" } })
76
- })
77
- })
78
-
79
- describe("tokenKit", () => {
80
- it("signs, verifies and publishes through the one key import it starts", async () => {
81
- const subject = await build()
82
- const { options } = await keyPair()
83
- const kit = subject.tokenKit(options)
84
-
85
- expect(await kit.verify(await kit.issue({ id: "user-3", email: "c@example.test" }))).toEqual({ kind: "ok", value: { id: "user-3", role: "authenticated", email: "c@example.test" } })
86
- expect((await kit.jwks()).keys.map((key) => key.kid)).toEqual(["k1"])
87
- })
88
- })
@@ -1,69 +0,0 @@
1
- import { createLocalJWKSet, exportJWK, importPKCS8, jwtVerify, SignJWT } from "jose"
2
- import type { JSONWebKeySet } from "jose"
3
- import { ok, refused } from "@modules/platform/primitives"
4
- import { PassportErrorCode } from "./errors/passport.error"
5
- import type { IssueAccessToken, OwnPrincipal, TokenOptions, VerifyAccessToken } from "./passport.options"
6
-
7
- const ALGORITHM = "ES256"
8
- const ROLE = "authenticated"
9
-
10
- /** The app's own token kit: the signer, the verifier over its own key set, and the public JWKS the API serves. */
11
- export interface TokenKit {
12
- /** Signs one access token. */
13
- readonly issue: IssueAccessToken
14
- /** Verifies one access token against the app's own keys. */
15
- readonly verify: VerifyAccessToken
16
- /** The public key set served at `/.well-known/jwks.json`. */
17
- readonly jwks: JSONWebKeySet
18
- }
19
-
20
- /** Builds the token kit from the ES256 private key; the verifier checks the app's own key set, never a remote one. */
21
- export const createTokenKit = async (options: TokenOptions): Promise<TokenKit> => {
22
- const privateKey = await importPKCS8(options.privateKey.reveal(), ALGORITHM, { extractable: true })
23
- const { d: _private, ...publicJwk } = await exportJWK(privateKey)
24
- const jwks: JSONWebKeySet = { keys: [{ ...publicJwk, kid: options.keyId, alg: ALGORITHM, use: "sig" }] }
25
- const keys = createLocalJWKSet(jwks)
26
- const issue: IssueAccessToken = (user) =>
27
- new SignJWT({ role: ROLE, ...(user.email === undefined ? {} : { email: user.email }) })
28
- .setProtectedHeader({ alg: ALGORITHM, kid: options.keyId })
29
- .setSubject(user.id)
30
- .setIssuer(options.issuer)
31
- .setAudience(options.audience)
32
- .setIssuedAt()
33
- .setExpirationTime(`${String(options.accessTtlSeconds)}s`)
34
- .sign(privateKey)
35
- const verify: VerifyAccessToken = async (token) => {
36
- try {
37
- const { payload } = await jwtVerify(token, keys, {
38
- algorithms: ["ES256"],
39
- audience: options.audience,
40
- issuer: options.issuer,
41
- })
42
- if (typeof payload.sub !== "string" || typeof payload.exp !== "number" || payload.role !== ROLE) {
43
- return refused(PassportErrorCode.AccessTokenInvalid)
44
- }
45
- const principal: OwnPrincipal = {
46
- id: payload.sub,
47
- role: ROLE,
48
- ...(typeof payload.email === "string" ? { email: payload.email } : {}),
49
- }
50
- return ok(principal)
51
- } catch (cause) {
52
- return refused(PassportErrorCode.AccessTokenInvalid, { cause: String(cause) })
53
- }
54
- }
55
- return { issue, verify, jwks }
56
- }
57
-
58
- /**
59
- * The token kit of a composition root that registers synchronously: each call awaits the one key import started here, so a
60
- * bad key fails the first verification instead of hiding behind a default.
61
- */
62
- export const tokenKit = (options: TokenOptions): { readonly issue: IssueAccessToken; readonly verify: VerifyAccessToken; readonly jwks: () => Promise<JSONWebKeySet> } => {
63
- const kit = createTokenKit(options)
64
- return {
65
- issue: async (user) => (await kit).issue(user),
66
- verify: async (token) => (await kit).verify(token),
67
- jwks: async () => (await kit).jwks,
68
- }
69
- }
@@ -1,37 +0,0 @@
1
- import { Injectable } from "@nestjs/common"
2
- import { PassportStrategy } from "@nestjs/passport"
3
- import { Strategy } from "passport-google-oauth20"
4
- import type { Profile } from "passport-google-oauth20"
5
- import { InjectPassportOptions } from "../passport.decorators"
6
- import type { PassportOptions } from "../passport.options"
7
- import { CookieStateStore } from "./oauth-state.store"
8
-
9
- /** The Google identity a callback proves: the provider subject and the verified email when Google states one. */
10
- export interface GoogleIdentity {
11
- /** Google's stable subject id (auth.identities.provider_id). */
12
- readonly providerId: string
13
- /** The verified email, when present. */
14
- readonly email?: string
15
- }
16
-
17
- @Injectable()
18
- /** Google OAuth 2.0 (authorization code with state and PKCE): it proves the Google identity and never signs the user in itself. */
19
- export class GoogleStrategy extends PassportStrategy(Strategy, "google") {
20
- constructor(@InjectPassportOptions() options: PassportOptions) {
21
- super({
22
- clientID: options.google.clientId,
23
- clientSecret: options.google.clientSecret.reveal(),
24
- callbackURL: options.google.callbackUrl,
25
- scope: ["openid", "email"],
26
- pkce: true,
27
- state: true,
28
- store: new CookieStateStore(new URL(options.google.callbackUrl).pathname, options.refresh.cookieDomain),
29
- })
30
- }
31
-
32
- /** The identity of the callback; the identity domain links it to a user and issues the session. */
33
- validate(_accessToken: string, _refreshToken: string, profile: Profile): GoogleIdentity {
34
- const email = profile.emails?.find((entry) => entry.verified)?.value
35
- return { providerId: profile.id, ...(email === undefined ? {} : { email }) }
36
- }
37
- }
@@ -1,36 +0,0 @@
1
- import { Injectable } from "@nestjs/common"
2
- import { PassportStrategy } from "@nestjs/passport"
3
- import { Strategy } from "passport-local"
4
- import { PassportError, PassportErrorCode } from "../errors/passport.error"
5
- import { InjectPasswordAccounts } from "../passport.decorators"
6
- import { verifyPassword } from "../passport.password"
7
-
8
- /** The account a password sign-in proves. */
9
- export interface PasswordAccount {
10
- /** The user id (auth.users.id). */
11
- readonly id: string
12
- /** The account email. */
13
- readonly email: string
14
- /** The stored bcrypt hash (auth.users.encrypted_password), null for an account without a password. */
15
- readonly passwordHash: string | null
16
- }
17
-
18
- /** The lookup the identity domain provides: the account of one email, or undefined. */
19
- export type FindPasswordAccount = (email: string) => Promise<PasswordAccount | undefined>
20
-
21
- @Injectable()
22
- /** Email and password sign-in: bcrypt against the stored hash; a miss and a wrong password fail alike. */
23
- export class LocalStrategy extends PassportStrategy(Strategy, "local") {
24
- constructor(@InjectPasswordAccounts() private readonly findAccount: FindPasswordAccount) {
25
- super({ usernameField: "email", passwordField: "password" })
26
- }
27
-
28
- /** The proven account; every refusal is the same error so an email's existence never leaks. */
29
- async validate(email: string, password: string): Promise<{ readonly id: string; readonly email: string }> {
30
- const account = await this.findAccount(email.trim().toLowerCase())
31
- if (!(await verifyPassword(password, account?.passwordHash ?? null)) || account === undefined) {
32
- throw new PassportError({ code: PassportErrorCode.CredentialsRefused })
33
- }
34
- return { id: account.id, email: account.email }
35
- }
36
- }
@@ -1,63 +0,0 @@
1
- import { randomBytes, timingSafeEqual } from "node:crypto"
2
- import type { Request, Response } from "express"
3
- import { domainAttribute } from "../passport.cookie"
4
-
5
- const COOKIE = "oauth_state"
6
- const MAX_AGE_MS = 600_000
7
-
8
- type Stored = { readonly state: string; readonly verifier: string }
9
- type StoreCallback = (error: Error | null, state?: string) => void
10
- type VerifyCallback = (error: Error | null, verifier?: string | false, state?: string) => void
11
-
12
- /** The response of the request a passport-oauth2 store is called with: passport keeps it on `req.res`. */
13
- const responseOf = (request: Request): Response | undefined => request.res
14
-
15
- /** The value of the state cookie in the request's Cookie header (no cookie middleware is installed). */
16
- const cookieOf = (request: Request): string | undefined =>
17
- request.headers.cookie
18
- ?.split(";")
19
- .map((part) => part.trim())
20
- .find((part) => part.startsWith(`${COOKIE}=`))
21
- ?.slice(COOKIE.length + 1)
22
-
23
- const readStored = (request: Request): Stored | undefined => {
24
- const raw = cookieOf(request)
25
- if (raw === undefined) return undefined
26
- const [state, verifier, extra] = raw.split(".")
27
- return state && verifier && extra === undefined ? { state, verifier } : undefined
28
- }
29
-
30
- const sameText = (left: string, right: string): boolean => {
31
- const a = Buffer.from(left)
32
- const b = Buffer.from(right)
33
- return a.length === b.length && timingSafeEqual(a, b)
34
- }
35
-
36
- /**
37
- * The OAuth state and PKCE verifier store of the Google strategy without a server session: both live in one short-lived
38
- * httpOnly, Secure, SameSite=Lax cookie scoped to the callback path, and the callback must present the same state.
39
- */
40
- export class CookieStateStore {
41
- constructor(
42
- private readonly callbackPath: string,
43
- private readonly cookieDomain?: string,
44
- ) {}
45
-
46
- /** Stores a fresh state with the PKCE verifier and hands the state to the authorization redirect. */
47
- store(request: Request, verifier: string, _state: unknown, _meta: unknown, callback: StoreCallback): void {
48
- const state = randomBytes(24).toString("base64url")
49
- responseOf(request)?.cookie(COOKIE, `${state}.${verifier}`, { httpOnly: true, secure: true, sameSite: "lax", path: this.callbackPath, maxAge: MAX_AGE_MS, ...domainAttribute(this.cookieDomain) })
50
- callback(null, state)
51
- }
52
-
53
- /** Answers the stored verifier only when the callback's state equals the stored one; the cookie is cleared either way. */
54
- verify(request: Request, provided: string, callback: VerifyCallback): void {
55
- const stored = readStored(request)
56
- responseOf(request)?.clearCookie(COOKIE, { httpOnly: true, secure: true, sameSite: "lax", path: this.callbackPath, ...domainAttribute(this.cookieDomain) })
57
- if (stored === undefined || !sameText(stored.state, provided)) {
58
- callback(null, false)
59
- return
60
- }
61
- callback(null, stored.verifier, stored.state)
62
- }
63
- }
@@ -1,4 +0,0 @@
1
- /** The shell's frame: a full-height column; colours come from Grammar's root. */
2
- export const siteShellClassNames = {
3
- frame: "flex min-h-screen flex-col",
4
- } as const
@@ -1,30 +0,0 @@
1
- import axios from "axios"
2
- import { apiUrl } from "@/modules/config"
3
-
4
- /** What one auth door answered: its status and its body, unread (the client narrows it). */
5
- export interface DoorAnswer {
6
- readonly status: number
7
- readonly body: unknown
8
- }
9
-
10
- /** The auth doors of the app's own back end; the refresh token rides in their httpOnly cookie, never in a body. */
11
- export type AuthDoor = "sign-in" | "sign-up" | "refresh" | "sign-out" | "password-reset" | "resend-verification"
12
-
13
- const TIMEOUT_MS = 10_000
14
-
15
- /**
16
- * The one auth door of the front end: a POST to `<api>/auth/<door>` with the cookies attached. A network failure answers
17
- * status 0, so the client maps it to "unavailable" instead of throwing.
18
- */
19
- export const callAuthDoor = async (door: AuthDoor, body?: Readonly<Record<string, string>>): Promise<DoorAnswer> => {
20
- try {
21
- const response = await axios.post<unknown>(`${apiUrl()}/auth/${door}`, body, {
22
- withCredentials: true,
23
- validateStatus: () => true,
24
- signal: AbortSignal.timeout(TIMEOUT_MS),
25
- })
26
- return { status: response.status, body: response.data }
27
- } catch {
28
- return { status: 0, body: undefined }
29
- }
30
- }
@@ -1,50 +0,0 @@
1
- import { callAuthDoor } from "./auth-door"
2
- import type { DoorAnswer } from "./auth-door"
3
- import type { AuthClient, AuthFailure, AuthSession } from "./contracts"
4
- import { heldSession, heldToken, hold, listen } from "./token-store"
5
-
6
- const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> => typeof value === "object" && value !== null
7
-
8
- /** The token and session an auth door issued, narrowed from its unread body; undefined when the body is not one. */
9
- const issuedOf = (body: unknown): { readonly token: string; readonly session: AuthSession } | undefined => {
10
- if (!isRecord(body) || typeof body.accessToken !== "string" || !isRecord(body.user) || typeof body.user.id !== "string") return undefined
11
- const email = typeof body.user.email === "string" ? body.user.email : undefined
12
- return { token: body.accessToken, session: { userId: body.user.id, ...(email === undefined ? {} : { email }) } }
13
- }
14
-
15
- /** The failure kind of a door's status: refused credentials, rejected input, or the API unavailable. */
16
- const kindOf = (status: number): string => {
17
- if (status === 400 || status === 401 || status === 403) return "refused"
18
- return status === 422 ? "invalid" : "unavailable"
19
- }
20
-
21
- const failureOf = (answer: DoorAnswer): AuthFailure => ({ code: `AUTH_${String(answer.status)}`, kind: kindOf(answer.status), message: "" })
22
-
23
- const ok = (answer: DoorAnswer): boolean => answer.status >= 200 && answer.status < 300
24
-
25
- /** Rotates the refresh cookie and holds the new access token; signs out when the API refuses the cookie. */
26
- const refresh = async (): Promise<string | undefined> => {
27
- const answer = await callAuthDoor("refresh")
28
- hold(ok(answer) ? issuedOf(answer.body) : undefined)
29
- return heldToken()
30
- }
31
-
32
- /** The lite session issuer's client: the app's own auth doors through `auth-door.ts`, the token in `token-store.ts`. */
33
- export const authClient: AuthClient = {
34
- session: async () => heldSession() ?? ((await refresh()) === undefined ? undefined : heldSession()),
35
- accessToken: async () => heldToken() ?? refresh(),
36
- refreshAccessToken: refresh,
37
- signInWithPassword: async (email, password) => {
38
- const answer = await callAuthDoor("sign-in", { email, password })
39
- const issued = ok(answer) ? issuedOf(answer.body) : undefined
40
- if (issued === undefined) return failureOf(answer)
41
- hold(issued)
42
- return undefined
43
- },
44
- signOut: async () => {
45
- const answer = await callAuthDoor("sign-out")
46
- hold(undefined)
47
- return ok(answer) ? undefined : failureOf(answer)
48
- },
49
- onChange: listen,
50
- }
@@ -1,31 +0,0 @@
1
- import type { AuthSession } from "./contracts"
2
-
3
- /** The signed-in reader and the access token, in memory only; the refresh token never reaches the browser's script. */
4
- interface Held {
5
- readonly token: string
6
- readonly session: AuthSession
7
- }
8
-
9
- let held: Held | undefined
10
- const listeners = new Set<(session: AuthSession | undefined) => void>()
11
-
12
- /** The held access token, or undefined when signed out. */
13
- export const heldToken = (): string | undefined => held?.token
14
-
15
- /** The held session, or undefined when signed out. */
16
- export const heldSession = (): AuthSession | undefined => held?.session
17
-
18
- /** Replaces the held token and session (undefined signs out) and tells every listener. */
19
- export const hold = (next: Held | undefined): void => {
20
- held = next
21
- for (const listener of listeners) listener(held?.session)
22
- }
23
-
24
- /** Subscribes to session changes; the listener hears the current session first. */
25
- export const listen = (listener: (session: AuthSession | undefined) => void): (() => void) => {
26
- listeners.add(listener)
27
- listener(held?.session)
28
- return () => {
29
- listeners.delete(listener)
30
- }
31
- }
@@ -1,4 +0,0 @@
1
- /** The shell's frame: a full-height column; colours come from Grammar's root. */
2
- export const siteShellClassNames = {
3
- frame: "flex min-h-screen flex-col",
4
- } as const