@podosoft/podokit 0.3.0 → 0.5.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 (245) hide show
  1. package/README.md +2 -2
  2. package/dist/add.d.ts +2 -0
  3. package/dist/add.js +5 -1
  4. package/dist/assemble.d.ts +14 -0
  5. package/dist/assemble.js +60 -0
  6. package/dist/create.d.ts +2 -0
  7. package/dist/create.js +7 -0
  8. package/dist/eject.d.ts +11 -0
  9. package/dist/eject.js +27 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +122 -1
  12. package/dist/inspect.d.ts +43 -0
  13. package/dist/inspect.js +82 -0
  14. package/dist/lockfile.d.ts +80 -0
  15. package/dist/lockfile.js +193 -0
  16. package/dist/templates/base/dot-env.example +11 -6
  17. package/dist/templates/fullstack-nest-svelte/README.md +2 -2
  18. package/dist/templates/fullstack-nest-svelte/apps/api/package.json +21 -2
  19. package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +6 -3
  20. package/dist/templates/fullstack-nest-svelte/apps/api/src/common/app-exception.ts +4 -12
  21. package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +1 -1
  22. package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +1 -1
  23. package/dist/templates/fullstack-nest-svelte/apps/api/tsconfig.json +14 -5
  24. package/dist/templates/fullstack-nest-svelte/apps/web/package.json +4 -3
  25. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/api.ts +5 -0
  26. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +1 -1
  27. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +1 -1
  28. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/index.ts +7 -0
  29. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
  30. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +19 -0
  31. package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +44 -5
  32. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +8 -3
  33. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +12 -0
  34. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
  35. package/dist/templates/fullstack-nest-svelte/dot-env.example +10 -5
  36. package/dist/templates/fullstack-nest-svelte/dot-gitignore +5 -0
  37. package/dist/templates/fullstack-nest-svelte/infra/docker/docker-compose.yml +37 -0
  38. package/dist/templates/fullstack-nest-svelte/infra/docker/sms-sink.mjs +40 -0
  39. package/dist/templates/fullstack-nest-svelte/package.json +11 -3
  40. package/dist/templates/fullstack-nest-svelte/tests/README.md +15 -0
  41. package/dist/templates/fullstack-nest-svelte/tests/api/health.api.spec.ts +14 -0
  42. package/dist/templates/fullstack-nest-svelte/tests/package.json +15 -0
  43. package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +25 -0
  44. package/dist/templates/fullstack-nest-svelte/tests/ui/smoke.ui.spec.ts +8 -0
  45. package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +26 -0
  46. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +20 -0
  47. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +65 -0
  48. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +149 -0
  49. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/language-switch.svelte +33 -0
  50. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +44 -0
  51. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +39 -0
  52. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-toolbar.svelte +93 -0
  53. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +27 -0
  54. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
  55. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
  56. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
  57. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/index.ts +13 -0
  58. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/index.ts +31 -0
  59. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-content.svelte +20 -0
  60. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte +22 -0
  61. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-item.svelte +14 -0
  62. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-link.svelte +42 -0
  63. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte +31 -0
  64. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next.svelte +26 -0
  65. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte +31 -0
  66. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte +26 -0
  67. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination.svelte +28 -0
  68. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/index.ts +34 -0
  69. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
  70. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-content.svelte +55 -0
  71. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
  72. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
  73. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
  74. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte +17 -0
  75. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
  76. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
  77. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
  78. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
  79. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/constants.ts +6 -0
  80. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/context.svelte.ts +81 -0
  81. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/index.ts +75 -0
  82. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
  83. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
  84. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +33 -0
  85. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
  86. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +33 -0
  87. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
  88. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
  89. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
  90. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +20 -0
  91. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +37 -0
  92. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +24 -0
  93. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +102 -0
  94. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
  95. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
  96. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -0
  97. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
  98. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +21 -0
  99. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
  100. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
  101. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
  102. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
  103. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +36 -0
  104. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar.svelte +108 -0
  105. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte +12 -0
  106. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
  107. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +27 -0
  108. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +575 -0
  109. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.ts +22 -0
  110. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +30 -0
  111. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.svelte +50 -0
  112. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/accept-invitation/[id]/+page.svelte +36 -0
  113. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +45 -0
  114. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.server.ts +5 -0
  115. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +685 -0
  116. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +6 -0
  117. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +123 -0
  118. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.server.ts +6 -0
  119. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.svelte +264 -0
  120. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +7 -0
  121. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +156 -0
  122. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.server.ts +26 -0
  123. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +322 -0
  124. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +7 -0
  125. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +552 -0
  126. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +13 -0
  127. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +61 -0
  128. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.server.ts +14 -0
  129. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +181 -0
  130. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +51 -0
  131. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +76 -0
  132. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/verify-email/+page.svelte +63 -0
  133. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +22 -0
  134. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +20 -0
  135. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +22 -0
  136. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/oauth2/consent/+page.svelte +54 -0
  137. package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +97 -0
  138. package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +80 -0
  139. package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +306 -0
  140. package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +120 -0
  141. package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +134 -0
  142. package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +6 -0
  143. package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +9 -0
  144. package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +49 -0
  145. package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +35 -0
  146. package/dist/templates/modules/admin-dashboard/files/tests/helpers/totp.ts +9 -0
  147. package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +26 -0
  148. package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +40 -0
  149. package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +126 -0
  150. package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +16 -0
  151. package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +45 -0
  152. package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +25 -0
  153. package/dist/templates/modules/admin-dashboard/files/tests/ui/data-table.ui.spec.ts +32 -0
  154. package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +60 -0
  155. package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +42 -0
  156. package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +22 -0
  157. package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +13 -0
  158. package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +57 -0
  159. package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +52 -0
  160. package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +36 -0
  161. package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +22 -0
  162. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +34 -0
  163. package/dist/templates/modules/admin-dashboard/files/tests/ui/sidebar.ui.spec.ts +16 -0
  164. package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +32 -0
  165. package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +145 -0
  166. package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +51 -0
  167. package/dist/templates/modules/admin-dashboard/module.manifest.json +47 -0
  168. package/dist/templates/modules/api-key-auth/module.manifest.json +3 -3
  169. package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +59 -0
  170. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-enabled.ts +16 -0
  171. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +34 -0
  172. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +74 -0
  173. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +29 -8
  174. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +7 -2
  175. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.decorator.ts +23 -0
  176. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +38 -21
  177. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +2 -1
  178. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +36 -0
  179. package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +10 -4
  180. package/dist/templates/modules/audit-log/module.manifest.json +37 -8
  181. package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +85 -3
  182. package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +2 -1
  183. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +73 -0
  184. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +199 -31
  185. package/dist/templates/modules/auth/files/apps/api/src/auth/db.ts +12 -0
  186. package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +56 -0
  187. package/dist/templates/modules/auth/files/apps/api/src/auth/org-permissions.ts +22 -0
  188. package/dist/templates/modules/auth/files/apps/api/src/auth/permissions.ts +23 -0
  189. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.controller.ts +33 -0
  190. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.entity.ts +26 -0
  191. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.module.ts +12 -0
  192. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +118 -0
  193. package/dist/templates/modules/auth/files/apps/api/src/mail/mailer.ts +65 -0
  194. package/dist/templates/modules/auth/files/apps/api/src/migrations/1720400000000-InitAppSettings.ts +38 -0
  195. package/dist/templates/modules/auth/files/apps/api/src/migrations/1720500000000-InitAuthConfig.ts +27 -0
  196. package/dist/templates/modules/auth/files/apps/api/src/settings/app-setting.entity.ts +16 -0
  197. package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +25 -0
  198. package/dist/templates/modules/auth/files/apps/api/src/settings/settings.module.ts +11 -0
  199. package/dist/templates/modules/auth/files/apps/api/src/settings/settings.service.ts +57 -0
  200. package/dist/templates/modules/auth/files/apps/api/src/sms/sms.ts +23 -0
  201. package/dist/templates/modules/auth/module.manifest.json +54 -20
  202. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +6 -3
  203. package/dist/templates/modules/bullmq/module.manifest.json +4 -4
  204. package/dist/templates/modules/file-upload/module.manifest.json +3 -3
  205. package/dist/templates/modules/job-progress/module.manifest.json +9 -9
  206. package/dist/templates/modules/logging/module.manifest.json +3 -3
  207. package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -3
  208. package/dist/templates/modules/object-storage-s3/module.manifest.json +5 -5
  209. package/dist/templates/modules/rate-limit/module.manifest.json +2 -2
  210. package/dist/templates/modules/redis/module.manifest.json +4 -4
  211. package/dist/templates/modules/sse/module.manifest.json +4 -4
  212. package/dist/templates/todo/README.md +2 -2
  213. package/dist/templates/todo/apps/api/package.json +21 -2
  214. package/dist/templates/todo/apps/api/src/app.module.ts +6 -3
  215. package/dist/templates/todo/apps/api/src/common/app-exception.ts +4 -12
  216. package/dist/templates/todo/apps/api/src/config/env.validation.ts +1 -1
  217. package/dist/templates/todo/apps/api/src/main.ts +1 -1
  218. package/dist/templates/todo/apps/api/tsconfig.json +14 -5
  219. package/dist/templates/todo/apps/web/package.json +4 -3
  220. package/dist/templates/todo/apps/web/src/lib/api.ts +5 -0
  221. package/dist/templates/todo/apps/web/src/lib/components/ui/switch/index.ts +7 -0
  222. package/dist/templates/todo/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
  223. package/dist/templates/todo/apps/web/src/lib/server/api.ts +19 -0
  224. package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +44 -5
  225. package/dist/templates/todo/apps/web/src/routes/+page.svelte +19 -18
  226. package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +12 -0
  227. package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
  228. package/dist/templates/todo/dot-env.example +10 -5
  229. package/dist/templates/todo/dot-gitignore +5 -0
  230. package/dist/templates/todo/infra/docker/docker-compose.yml +4 -0
  231. package/dist/templates/todo/package.json +11 -3
  232. package/dist/templates/todo/tests/README.md +15 -0
  233. package/dist/templates/todo/tests/api/health.api.spec.ts +14 -0
  234. package/dist/templates/todo/tests/package.json +15 -0
  235. package/dist/templates/todo/tests/playwright.config.ts +25 -0
  236. package/dist/templates/todo/tests/ui/smoke.ui.spec.ts +8 -0
  237. package/dist/update.d.ts +49 -0
  238. package/dist/update.js +158 -0
  239. package/package.json +2 -2
  240. package/dist/templates/fullstack-nest-svelte/apps/api/test/health.e2e-spec.ts +0 -23
  241. package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/health/+server.ts +0 -12
  242. package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +0 -23
  243. package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +0 -12
  244. package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +0 -24
  245. package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +0 -24
@@ -1,25 +1,46 @@
1
- import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from "typeorm";
1
+ import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn } from "typeorm";
2
2
 
3
+ // An append-only audit trail. Fields follow the actor / action / target model:
4
+ // who did what, to which resource, when — with human-readable labels stored
5
+ // inline so entries stay meaningful even after users are renamed or deleted.
3
6
  @Entity("audit_logs")
4
7
  export class AuditLog {
5
8
  @PrimaryGeneratedColumn("uuid")
6
9
  id!: string;
7
10
 
11
+ // A stable, semantic action code, e.g. "user.create", "auth.login".
12
+ @Index()
13
+ @Column({ type: "varchar", length: 128 })
14
+ action!: string;
15
+
16
+ // Who performed it (denormalized display fields + id).
8
17
  @Column({ type: "varchar", nullable: true })
9
- userId!: string | null;
18
+ actorId!: string | null;
10
19
 
11
- @Column({ type: "varchar", length: 10 })
12
- method!: string;
20
+ @Column({ type: "varchar", nullable: true })
21
+ actorName!: string | null;
13
22
 
14
- @Column({ type: "varchar", length: 2048 })
15
- path!: string;
23
+ @Column({ type: "varchar", nullable: true })
24
+ actorEmail!: string | null;
16
25
 
17
- @Column({ type: "int" })
18
- statusCode!: number;
26
+ // What it acted on (optional).
27
+ @Column({ type: "varchar", nullable: true })
28
+ targetType!: string | null;
29
+
30
+ @Column({ type: "varchar", nullable: true })
31
+ targetId!: string | null;
32
+
33
+ @Column({ type: "varchar", nullable: true })
34
+ targetLabel!: string | null;
19
35
 
20
36
  @Column({ type: "varchar", nullable: true })
21
37
  ip!: string | null;
22
38
 
39
+ // Free-form context for custom events.
40
+ @Column({ type: "jsonb", nullable: true })
41
+ metadata!: Record<string, unknown> | null;
42
+
43
+ @Index()
23
44
  @CreateDateColumn({ type: "timestamptz" })
24
45
  createdAt!: Date;
25
46
  }
@@ -1,5 +1,6 @@
1
- import { Controller, Get } from "@nestjs/common";
1
+ import { Controller, ForbiddenException, Get } from "@nestjs/common";
2
2
  import { InjectRepository } from "@nestjs/typeorm";
3
+ import { Session, type UserSession } from "@thallesp/nestjs-better-auth";
3
4
  import { ApiTags } from "@nestjs/swagger";
4
5
  import { Repository } from "typeorm";
5
6
  import { AuditLog } from "./audit-log.entity";
@@ -9,8 +10,12 @@ import { AuditLog } from "./audit-log.entity";
9
10
  export class AuditController {
10
11
  constructor(@InjectRepository(AuditLog) private readonly logs: Repository<AuditLog>) {}
11
12
 
13
+ // Audit entries are sensitive — admins only.
12
14
  @Get()
13
- recent(): Promise<AuditLog[]> {
15
+ recent(@Session() session: UserSession): Promise<AuditLog[]> {
16
+ if ((session.user as { role?: string | null }).role !== "admin") {
17
+ throw new ForbiddenException("Admins only");
18
+ }
14
19
  return this.logs.find({ order: { createdAt: "DESC" }, take: 50 });
15
20
  }
16
21
  }
@@ -0,0 +1,23 @@
1
+ import { SetMetadata } from "@nestjs/common";
2
+ import type { Request } from "express";
3
+
4
+ export const AUDIT_KEY = "podokit:audit";
5
+
6
+ // Optionally derive the target (and extra metadata) from the request + handler
7
+ // result, e.g. (req, result) => ({ type: "todo", id: result.id, label: result.title }).
8
+ export type AuditTarget = {
9
+ type?: string;
10
+ id?: string;
11
+ label?: string;
12
+ metadata?: Record<string, unknown>;
13
+ };
14
+ export type AuditMeta = {
15
+ action: string;
16
+ resolve?: (req: Request, result: unknown) => AuditTarget | undefined;
17
+ };
18
+
19
+ // Mark a controller handler to be audited under a semantic action code:
20
+ // @Audit("todo.create", (_req, todo) => ({ type: "todo", id: todo.id, label: todo.title }))
21
+ export function Audit(action: string, resolve?: AuditMeta["resolve"]): MethodDecorator {
22
+ return SetMetadata(AUDIT_KEY, { action, resolve } satisfies AuditMeta);
23
+ }
@@ -1,39 +1,56 @@
1
1
  import { type CallHandler, type ExecutionContext, Injectable, type NestInterceptor } from "@nestjs/common";
2
- import { InjectRepository } from "@nestjs/typeorm";
2
+ import { Reflector } from "@nestjs/core";
3
3
  import { fromNodeHeaders } from "better-auth/node";
4
- import type { Request, Response } from "express";
4
+ import type { Request } from "express";
5
5
  import { type Observable, tap } from "rxjs";
6
- import { Repository } from "typeorm";
7
6
  import { auth } from "../auth/auth";
8
- import { AuditLog } from "./audit-log.entity";
7
+ import { AUDIT_KEY, type AuditMeta } from "./audit.decorator";
8
+ import { AuditService } from "./audit.service";
9
+ import { auditEnabled } from "./audit-enabled";
9
10
 
10
- const AUDITED_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
11
-
12
- // Records who performed each mutating request. Skips the auth endpoints.
11
+ // Records handlers explicitly marked with @Audit(...). Nothing is logged unless
12
+ // you opt a route in, so the trail stays meaningful (semantic actions, not raw
13
+ // HTTP). Auth/admin actions are recorded separately by the better-auth hook.
13
14
  @Injectable()
14
15
  export class AuditInterceptor implements NestInterceptor {
15
- constructor(@InjectRepository(AuditLog) private readonly logs: Repository<AuditLog>) {}
16
+ constructor(
17
+ private readonly reflector: Reflector,
18
+ private readonly audit: AuditService,
19
+ ) {}
16
20
 
17
21
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
22
+ const meta = this.reflector.get<AuditMeta | undefined>(AUDIT_KEY, context.getHandler());
23
+ if (!meta) return next.handle();
18
24
  const req = context.switchToHttp().getRequest<Request>();
19
- const path = req.originalUrl ?? req.url;
20
- if (!AUDITED_METHODS.has(req.method) || path.startsWith("/api/auth")) {
21
- return next.handle();
22
- }
23
- const res = context.switchToHttp().getResponse<Response>();
24
- return next.handle().pipe(tap(() => void this.record(req, res, path)));
25
+ return next.handle().pipe(tap((result) => void this.log(meta, req, result)));
25
26
  }
26
27
 
27
- private async record(req: Request, res: Response, path: string): Promise<void> {
28
- let userId: string | null = null;
28
+ private async log(meta: AuditMeta, req: Request, result: unknown): Promise<void> {
29
+ if (!(await auditEnabled())) return;
30
+ let actorId: string | null = null;
31
+ let actorName: string | null = null;
32
+ let actorEmail: string | null = null;
29
33
  try {
30
34
  const session = await auth.api.getSession({ headers: fromNodeHeaders(req.headers) });
31
- userId = session?.user?.id ?? null;
35
+ if (session?.user) {
36
+ actorId = session.user.id;
37
+ actorName = session.user.name ?? null;
38
+ actorEmail = session.user.email ?? null;
39
+ }
32
40
  } catch {
33
- userId = null;
41
+ /* unauthenticated */
34
42
  }
35
- await this.logs.save(
36
- this.logs.create({ userId, method: req.method, path, statusCode: res.statusCode, ip: req.ip ?? null }),
37
- );
43
+ const target = meta.resolve?.(req, result);
44
+ await this.audit.record({
45
+ action: meta.action,
46
+ actorId,
47
+ actorName,
48
+ actorEmail,
49
+ targetType: target?.type ?? null,
50
+ targetId: target?.id ?? null,
51
+ targetLabel: target?.label ?? null,
52
+ ip: req.ip ?? null,
53
+ metadata: target?.metadata ?? null,
54
+ });
38
55
  }
39
56
  }
@@ -3,11 +3,12 @@ import { APP_INTERCEPTOR } from "@nestjs/core";
3
3
  import { TypeOrmModule } from "@nestjs/typeorm";
4
4
  import { AuditController } from "./audit.controller";
5
5
  import { AuditInterceptor } from "./audit.interceptor";
6
+ import { AuditService } from "./audit.service";
6
7
  import { AuditLog } from "./audit-log.entity";
7
8
 
8
9
  @Module({
9
10
  imports: [TypeOrmModule.forFeature([AuditLog])],
10
11
  controllers: [AuditController],
11
- providers: [{ provide: APP_INTERCEPTOR, useClass: AuditInterceptor }],
12
+ providers: [AuditService, { provide: APP_INTERCEPTOR, useClass: AuditInterceptor }],
12
13
  })
13
14
  export class AuditModule {}
@@ -0,0 +1,36 @@
1
+ import { Injectable, type OnModuleInit } from "@nestjs/common";
2
+ import { InjectRepository } from "@nestjs/typeorm";
3
+ import { Repository } from "typeorm";
4
+ import { AuditLog } from "./audit-log.entity";
5
+ import { setAuditRecorder, type AuditEntry } from "./audit-events";
6
+
7
+ // The one place audit entries are written. Inject it to record from Nest code,
8
+ // or call recordAudit() from anywhere else — both end up here.
9
+ @Injectable()
10
+ export class AuditService implements OnModuleInit {
11
+ constructor(@InjectRepository(AuditLog) private readonly logs: Repository<AuditLog>) {}
12
+
13
+ onModuleInit(): void {
14
+ setAuditRecorder((entry) => this.record(entry));
15
+ }
16
+
17
+ async record(entry: AuditEntry): Promise<void> {
18
+ try {
19
+ await this.logs.save(
20
+ this.logs.create({
21
+ action: entry.action,
22
+ actorId: entry.actorId ?? null,
23
+ actorName: entry.actorName ?? null,
24
+ actorEmail: entry.actorEmail ?? null,
25
+ targetType: entry.targetType ?? null,
26
+ targetId: entry.targetId ?? null,
27
+ targetLabel: entry.targetLabel ?? null,
28
+ ip: entry.ip ?? null,
29
+ metadata: entry.metadata ?? null,
30
+ }),
31
+ );
32
+ } catch {
33
+ // An audit write must never break the request it describes.
34
+ }
35
+ }
36
+ }
@@ -8,15 +8,21 @@ export class InitAuditLogs1720300000000 implements MigrationInterface {
8
8
  await queryRunner.query(`
9
9
  CREATE TABLE "audit_logs" (
10
10
  "id" uuid NOT NULL DEFAULT gen_random_uuid(),
11
- "userId" character varying,
12
- "method" character varying(10) NOT NULL,
13
- "path" character varying(2048) NOT NULL,
14
- "statusCode" integer NOT NULL,
11
+ "action" character varying(128) NOT NULL,
12
+ "actorId" character varying,
13
+ "actorName" character varying,
14
+ "actorEmail" character varying,
15
+ "targetType" character varying,
16
+ "targetId" character varying,
17
+ "targetLabel" character varying,
15
18
  "ip" character varying,
19
+ "metadata" jsonb,
16
20
  "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
17
21
  CONSTRAINT "PK_audit_logs_id" PRIMARY KEY ("id")
18
22
  )
19
23
  `);
24
+ await queryRunner.query(`CREATE INDEX "IDX_audit_logs_action" ON "audit_logs" ("action")`);
25
+ await queryRunner.query(`CREATE INDEX "IDX_audit_logs_createdAt" ON "audit_logs" ("createdAt")`);
20
26
  }
21
27
 
22
28
  async down(queryRunner: QueryRunner): Promise<void> {
@@ -1,16 +1,45 @@
1
1
  {
2
2
  "name": "audit-log",
3
- "description": "Audit logging: a global interceptor records who did each mutating request (who/method/path/status) to an audit_logs table. Requires auth.",
4
- "requires": ["auth"],
3
+ "description": "Audit logging: records who did each mutating request (who/method/path/status) to an audit_logs table \u2014 a NestJS interceptor for your own API routes plus a better-auth hook for auth/admin actions (login, ban, role changes, ...). Requires auth.",
4
+ "requires": [
5
+ "auth"
6
+ ],
5
7
  "targetApp": "api",
6
- "env": [],
8
+ "env": [
9
+ "# audit-log: on/off is managed on the admin Settings page (stored in the DB,",
10
+ "# applied live, no restart). This env var is the fallback default used until an",
11
+ "# admin sets it \u2014 leave it 'true' so the trail is on out of the box; the DB value",
12
+ "# (Settings page) overrides it once set.",
13
+ "AUDIT_LOG_ENABLED=true"
14
+ ],
7
15
  "inject": [
8
- { "file": "apps/api/src/app.module.ts", "marker": "// podokit:imports", "text": "import { AuditModule } from \"./audit/audit.module\";" },
9
- { "file": "apps/api/src/app.module.ts", "marker": "// podokit:module-imports", "text": "AuditModule," }
16
+ {
17
+ "file": "apps/api/src/app.module.ts",
18
+ "marker": "// podokit:end:imports",
19
+ "text": "import { AuditModule } from \"./audit/audit.module\";"
20
+ },
21
+ {
22
+ "file": "apps/api/src/app.module.ts",
23
+ "marker": "// podokit:end:module-imports",
24
+ "text": "AuditModule,"
25
+ },
26
+ {
27
+ "file": "apps/api/src/auth/auth.ts",
28
+ "marker": "// podokit:end:auth-imports",
29
+ "text": "import { auditAfterHook } from \"../audit/audit-hook\";"
30
+ },
31
+ {
32
+ "file": "apps/api/src/auth/auth.ts",
33
+ "marker": "// podokit:end:auth-hooks",
34
+ "text": "hooks.after = auditAfterHook;"
35
+ }
10
36
  ],
11
37
  "instructions": [
12
38
  "Run migrations to create the audit_logs table: npm run migration:run -w <app>-api",
13
- "Mutating requests (POST/PUT/PATCH/DELETE) are recorded with the acting user.",
14
- "List recent entries (requires a session): curl -b cookies.txt localhost:3000/audit-logs"
15
- ]
39
+ "Auth/admin actions (login, ban, role changes, session revocation, user CRUD) are recorded via a better-auth hook; your own mutating NestJS routes are recorded by the interceptor.",
40
+ "List recent entries (admin session required): curl -b cookies.txt localhost:5002/audit-logs"
41
+ ],
42
+ "dependencies": {
43
+ "@podosoft/podokit-auth": "^0.1.0"
44
+ }
16
45
  }
@@ -1,13 +1,95 @@
1
- import { Controller, Get } from "@nestjs/common";
2
- import { Session, type UserSession } from "@thallesp/nestjs-better-auth";
1
+ import { Body, Controller, ForbiddenException, Get, Post, Put, Req } from "@nestjs/common";
2
+ import type { Request } from "express";
3
+ import { fromNodeHeaders } from "better-auth/node";
4
+ import { Public, Session, type UserSession } from "@thallesp/nestjs-better-auth";
3
5
  import { ApiTags } from "@nestjs/swagger";
6
+ import { FEATURE_FLAGS, SettingsService, type FeatureFlag } from "../settings/settings.service";
7
+ import { ROLE_NAMES } from "../auth/permissions";
8
+ import { auth } from "../auth/auth";
9
+ import { pool } from "../auth/db";
10
+ import { createConfigStore } from "@podosoft/podokit-auth";
11
+ import type { Capabilities } from "@podosoft/podokit-contracts";
12
+
13
+ // Reads OAuth/server-toggle state (DB-first, env fallback) for capabilities.
14
+ const configStore = createConfigStore(pool);
15
+
16
+ function isAdmin(session: UserSession): boolean {
17
+ const role = session.user?.role;
18
+ const roles = Array.isArray(role) ? role : (role ?? "").split(",").map((r: string) => r.trim());
19
+ return roles.includes("admin");
20
+ }
4
21
 
5
- // Protected by the global AuthGuard. Use @Session() to read the current user.
6
22
  @ApiTags("account")
7
23
  @Controller("account")
8
24
  export class AccountController {
25
+ constructor(private readonly settings: SettingsService) {}
26
+
9
27
  @Get("me")
10
28
  me(@Session() session: UserSession) {
11
29
  return session.user;
12
30
  }
31
+
32
+ // Which optional auth features are enabled, so the UI can show/hide sections.
33
+ // Public so the login page (unauthenticated) can offer available sign-in methods.
34
+ @Public()
35
+ @Get("capabilities")
36
+ async capabilities(): Promise<Capabilities> {
37
+ const flags = this.settings.flags();
38
+ // OAuth providers + server-enforced toggles are DB-backed (env fallback) and
39
+ // applied live, so read them from the config store rather than process.env.
40
+ const snapshot = await configStore.capabilitiesSnapshot();
41
+ return {
42
+ twoFactor: flags.twoFactor,
43
+ magicLink: flags.magicLink,
44
+ emailOtp: flags.emailOtp,
45
+ username: flags.username,
46
+ multiSession: flags.multiSession,
47
+ phoneNumber: flags.phoneNumber,
48
+ apiKey: flags.apiKey,
49
+ passkey: flags.passkey,
50
+ organization: flags.organization,
51
+ oidcProvider: flags.oidcProvider,
52
+ providers: snapshot.providers,
53
+ deleteAccount: snapshot.allowDelete,
54
+ emailVerification: snapshot.requireEmailVerification,
55
+ passwordBreachCheck: snapshot.passwordBreachCheck,
56
+ auditLog: snapshot.auditLog,
57
+ roles: ROLE_NAMES,
58
+ };
59
+ }
60
+
61
+ // Admin-only: toggle feature flags. Stored in the DB and reflected immediately in
62
+ // capabilities (UI). Server-enforced behaviours (email verification, breach check)
63
+ // are read by the auth server at startup, so those take effect on the next restart.
64
+ @Put("settings")
65
+ async updateSettings(
66
+ @Session() session: UserSession,
67
+ @Body() body: Partial<Record<FeatureFlag, boolean>>,
68
+ ): Promise<Record<FeatureFlag, boolean>> {
69
+ if (!isAdmin(session)) throw new ForbiddenException("Admins only");
70
+ const update: Partial<Record<FeatureFlag, boolean>> = {};
71
+ for (const flag of FEATURE_FLAGS) {
72
+ if (typeof body?.[flag] === "boolean") update[flag] = body[flag];
73
+ }
74
+ return this.settings.setMany(update);
75
+ }
76
+
77
+ // Add an existing user to an organization with a role (e.g. "manager"), so several
78
+ // members can hold it. better-auth's addMember is server-only; this exposes it and
79
+ // delegates org-level authorization to better-auth via the caller's session headers.
80
+ @Post("org-member")
81
+ async addOrgMember(
82
+ @Req() req: Request,
83
+ @Body() body: { organizationId: string; userId: string; role: string },
84
+ ): Promise<unknown> {
85
+ // addMember is a server-only better-auth endpoint (not on the typed public api);
86
+ // it exists at runtime, so reach it through a narrow cast.
87
+ const api = auth.api as unknown as {
88
+ addMember: (opts: { body: { organizationId: string; userId: string; role: string }; headers: Headers }) => Promise<unknown>;
89
+ };
90
+ return api.addMember({
91
+ body: { organizationId: body.organizationId, userId: body.userId, role: body.role },
92
+ headers: fromNodeHeaders(req.headers),
93
+ });
94
+ }
13
95
  }
@@ -1,5 +1,6 @@
1
1
  import { Module } from "@nestjs/common";
2
+ import { SettingsModule } from "../settings/settings.module";
2
3
  import { AccountController } from "./account.controller";
3
4
 
4
- @Module({ controllers: [AccountController] })
5
+ @Module({ imports: [SettingsModule], controllers: [AccountController] })
5
6
  export class AccountModule {}
@@ -0,0 +1,73 @@
1
+ import { auth as bootstrapAuth, buildAuth } from "./auth";
2
+ import { pool } from "./db";
3
+ import { createConfigStore } from "@podosoft/podokit-auth";
4
+
5
+ // Runtime auth instance that rebuilds when the DB config changes, so admin edits
6
+ // (OAuth credentials, SMTP, server-enforced toggles) apply WITHOUT a restart.
7
+ //
8
+ // @thallesp/nestjs-better-auth reads `auth.handler` exactly once (via toNodeHandler)
9
+ // and `auth.api` per request. We hand it a Proxy: `.handler` is a STABLE delegating
10
+ // function (so the adapter's captured reference stays valid) that redispatches each
11
+ // request to the currently-built instance; every other access forwards to the
12
+ // current instance too. The guard/session behavior is unchanged (session tokens are
13
+ // signed with the stable BETTER_AUTH_SECRET; trustedOrigins is baked into every build).
14
+
15
+ const store = createConfigStore(pool);
16
+ const TTL_MS = 3_000;
17
+
18
+ let current = bootstrapAuth;
19
+ let currentVersion = "env"; // matches envAuthConfig() bootstrap
20
+ let checkedAt = 0;
21
+ let inflight: Promise<void> | null = null;
22
+
23
+ export function getAuth(): typeof bootstrapAuth {
24
+ return current;
25
+ }
26
+
27
+ /** Rebuild the instance if the DB config version changed. Cheap and throttled to
28
+ * TTL; single-flight so concurrent requests trigger at most one rebuild; keeps the
29
+ * last-good instance if loading/building fails, so auth never goes down. */
30
+ export async function refreshIfStale(): Promise<void> {
31
+ if (checkedAt !== 0 && Date.now() - checkedAt < TTL_MS) return;
32
+ checkedAt = Date.now();
33
+ let latest: string;
34
+ try {
35
+ latest = await store.currentVersion();
36
+ } catch {
37
+ return; // DB blip — keep serving the last-good instance
38
+ }
39
+ if (latest === currentVersion) return;
40
+ if (inflight) return inflight;
41
+ inflight = (async () => {
42
+ try {
43
+ const config = await store.load();
44
+ current = buildAuth(config);
45
+ currentVersion = config.version;
46
+ } catch (err) {
47
+ console.error("[auth-config] rebuild failed; keeping last-good auth instance:", err instanceof Error ? err.message : err);
48
+ } finally {
49
+ inflight = null;
50
+ }
51
+ })();
52
+ return inflight;
53
+ }
54
+
55
+ // Stable handler reference the adapter captures once; redispatches per request.
56
+ const dynamicHandler: typeof bootstrapAuth.handler = async (request) => {
57
+ await refreshIfStale();
58
+ return current.handler(request);
59
+ };
60
+
61
+ export const authRuntime: typeof bootstrapAuth = new Proxy(bootstrapAuth, {
62
+ get(_target, prop) {
63
+ if (prop === "handler") return dynamicHandler;
64
+ const value = Reflect.get(current, prop, current);
65
+ return typeof value === "function" ? value.bind(current) : value;
66
+ },
67
+ });
68
+
69
+ /** Optional boot preload so the first request doesn't pay the initial DB read. */
70
+ export async function primeAuth(): Promise<void> {
71
+ checkedAt = 0;
72
+ await refreshIfStale();
73
+ }