@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
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_OWNED_GLOBS = exports.LOCK_SCHEMA_VERSION = void 0;
4
+ exports.manifestPath = manifestPath;
5
+ exports.filesLockPath = filesLockPath;
6
+ exports.readManifest = readManifest;
7
+ exports.writeManifest = writeManifest;
8
+ exports.writeFilesLock = writeFilesLock;
9
+ exports.podokitVersion = podokitVersion;
10
+ exports.matchGlob = matchGlob;
11
+ exports.classifyTier = classifyTier;
12
+ exports.walkFiles = walkFiles;
13
+ exports.computeFilesLock = computeFilesLock;
14
+ exports.readFilesLock = readFilesLock;
15
+ exports.computeDrift = computeDrift;
16
+ exports.initLockfile = initLockfile;
17
+ exports.recordModules = recordModules;
18
+ const node_fs_1 = require("node:fs");
19
+ const node_path_1 = require("node:path");
20
+ const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
21
+ /**
22
+ * The generation lockfile (`.podokit/`) records how a project was assembled so
23
+ * `podo update` can rebuild the same inputs and detect user edits. See
24
+ * ADR-0009 (update mechanism) and ADR-0010 (ownership model).
25
+ */
26
+ exports.LOCK_SCHEMA_VERSION = 1;
27
+ const PODOKIT_DIR = ".podokit";
28
+ const MANIFEST_FILE = "manifest.json";
29
+ const FILES_LOCK_FILE = "files.lock";
30
+ /** Marker that flags a file as assembled from base + module injections. */
31
+ const INJECTION_MARKER = "// podokit:";
32
+ /** Directories that never belong in the lockfile (generated / vendored). */
33
+ const WALK_IGNORE = new Set([
34
+ "node_modules",
35
+ ".git",
36
+ PODOKIT_DIR,
37
+ "dist",
38
+ "build",
39
+ ".svelte-kit",
40
+ ".turbo",
41
+ "coverage",
42
+ ]);
43
+ /** Default user-owned globs seeded into a new project. See ADR-0010. */
44
+ exports.DEFAULT_OWNED_GLOBS = [
45
+ "apps/web/src/routes/**",
46
+ "apps/web/src/lib/components/ui/**",
47
+ ];
48
+ const TEXT_EXT = /\.(json|md|ts|tsx|js|mjs|cjs|css|html|yml|yaml|txt|env|svelte|gitignore|example)$/i;
49
+ function podokitDir(projectRoot) {
50
+ return (0, node_path_1.join)(projectRoot, PODOKIT_DIR);
51
+ }
52
+ function manifestPath(projectRoot) {
53
+ return (0, node_path_1.join)(podokitDir(projectRoot), MANIFEST_FILE);
54
+ }
55
+ function filesLockPath(projectRoot) {
56
+ return (0, node_path_1.join)(podokitDir(projectRoot), FILES_LOCK_FILE);
57
+ }
58
+ function readManifest(projectRoot) {
59
+ const file = manifestPath(projectRoot);
60
+ return (0, node_fs_1.existsSync)(file) ? JSON.parse((0, node_fs_1.readFileSync)(file, "utf8")) : null;
61
+ }
62
+ function writeManifest(projectRoot, manifest) {
63
+ (0, node_fs_1.mkdirSync)(podokitDir(projectRoot), { recursive: true });
64
+ (0, node_fs_1.writeFileSync)(manifestPath(projectRoot), `${JSON.stringify(manifest, null, 2)}\n`);
65
+ }
66
+ function writeFilesLock(projectRoot, lock) {
67
+ (0, node_fs_1.mkdirSync)(podokitDir(projectRoot), { recursive: true });
68
+ (0, node_fs_1.writeFileSync)(filesLockPath(projectRoot), `${JSON.stringify(lock, null, 2)}\n`);
69
+ }
70
+ /** Read the PodoKit CLI's own version to stamp into the manifest. */
71
+ function podokitVersion() {
72
+ try {
73
+ const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "..", "package.json"), "utf8"));
74
+ return pkg.version ?? "0.0.0";
75
+ }
76
+ catch {
77
+ return "0.0.0";
78
+ }
79
+ }
80
+ /** Match a POSIX relative path against a simple glob supporting `*` and `**`. */
81
+ function matchGlob(path, glob) {
82
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
83
+ // Single pass so replacement output is never re-scanned.
84
+ const pattern = escaped.replace(/\/\*\*$|\*\*\/|\*\*|\*/g, (token) => {
85
+ if (token === "/**")
86
+ return "(?:/.*)?"; // trailing `/**`: the dir itself or anything under it
87
+ if (token === "**/")
88
+ return "(?:.*/)?"; // `**/`: zero or more leading segments
89
+ if (token === "**")
90
+ return ".*"; // `**`: any characters
91
+ return "[^/]*"; // `*`: within a single segment
92
+ });
93
+ return new RegExp(`^${pattern}$`).test(path);
94
+ }
95
+ function isTextPath(relPath) {
96
+ return relPath.startsWith("dot-") || TEXT_EXT.test(relPath);
97
+ }
98
+ /** Classify a file's ownership tier. See ADR-0010 priority order. */
99
+ function classifyTier(relPath, content, ownedGlobs) {
100
+ if (ownedGlobs.some((glob) => matchGlob(relPath, glob)))
101
+ return "owned";
102
+ if (isTextPath(relPath) && content.toString("utf8").includes(INJECTION_MARKER))
103
+ return "assembled";
104
+ return "managed";
105
+ }
106
+ /** List project files as POSIX-relative paths, skipping generated/vendored dirs. */
107
+ function walkFiles(projectRoot, prefix = "") {
108
+ const out = [];
109
+ for (const entry of (0, node_fs_1.readdirSync)((0, node_path_1.join)(projectRoot, prefix))) {
110
+ if (WALK_IGNORE.has(entry))
111
+ continue;
112
+ const rel = prefix ? `${prefix}/${entry}` : entry;
113
+ if ((0, node_fs_1.statSync)((0, node_path_1.join)(projectRoot, rel)).isDirectory()) {
114
+ out.push(...walkFiles(projectRoot, rel));
115
+ }
116
+ else {
117
+ out.push(rel);
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+ /** Recompute the files.lock by hashing and classifying every project file. */
123
+ function computeFilesLock(projectRoot, ownedGlobs) {
124
+ const files = {};
125
+ for (const rel of walkFiles(projectRoot).sort()) {
126
+ const content = (0, node_fs_1.readFileSync)((0, node_path_1.join)(projectRoot, rel));
127
+ files[rel] = { tier: classifyTier(rel, content, ownedGlobs), outHash: (0, podokit_template_engine_1.hashContent)(content) };
128
+ }
129
+ return { schemaVersion: exports.LOCK_SCHEMA_VERSION, files };
130
+ }
131
+ function readFilesLock(projectRoot) {
132
+ const file = filesLockPath(projectRoot);
133
+ return (0, node_fs_1.existsSync)(file) ? JSON.parse((0, node_fs_1.readFileSync)(file, "utf8")) : null;
134
+ }
135
+ /**
136
+ * Compare the current disk against the lock: report managed/assembled files the
137
+ * user has edited (hash mismatch) or deleted. Owned files are never reported —
138
+ * they belong to the user. Returns empty drift when there is no lock.
139
+ */
140
+ function computeDrift(projectRoot) {
141
+ const lock = readFilesLock(projectRoot);
142
+ const drifted = [];
143
+ const missing = [];
144
+ if (!lock)
145
+ return { drifted, missing };
146
+ for (const [rel, entry] of Object.entries(lock.files)) {
147
+ if (entry.tier === "owned")
148
+ continue;
149
+ const abs = (0, node_path_1.join)(projectRoot, rel);
150
+ if (!(0, node_fs_1.existsSync)(abs)) {
151
+ missing.push(rel);
152
+ continue;
153
+ }
154
+ if ((0, podokit_template_engine_1.hashContent)((0, node_fs_1.readFileSync)(abs)) !== entry.outHash)
155
+ drifted.push(rel);
156
+ }
157
+ return { drifted: drifted.sort(), missing: missing.sort() };
158
+ }
159
+ /** Write the initial `.podokit/` lockfile for a freshly created project. */
160
+ function initLockfile(projectRoot, options) {
161
+ const ownedGlobs = options.ownedGlobs ?? exports.DEFAULT_OWNED_GLOBS;
162
+ const manifest = {
163
+ schemaVersion: exports.LOCK_SCHEMA_VERSION,
164
+ podokitVersion: options.version ?? podokitVersion(),
165
+ template: options.template,
166
+ packageManager: options.packageManager,
167
+ answers: options.answers,
168
+ modules: [],
169
+ ownedGlobs,
170
+ };
171
+ writeManifest(projectRoot, manifest);
172
+ writeFilesLock(projectRoot, computeFilesLock(projectRoot, ownedGlobs));
173
+ }
174
+ /**
175
+ * Record applied modules in the manifest and recompute the files.lock. Modules
176
+ * already present are left in place (idempotent); the lock always reflects disk.
177
+ * No-op on projects without a manifest (pre-lockfile projects; see `podo adopt`).
178
+ */
179
+ function recordModules(projectRoot, moduleNames, version) {
180
+ const manifest = readManifest(projectRoot);
181
+ if (!manifest)
182
+ return;
183
+ const stampedWith = version ?? podokitVersion();
184
+ const known = new Set(manifest.modules.map((m) => m.name));
185
+ for (const name of moduleNames) {
186
+ if (known.has(name))
187
+ continue;
188
+ manifest.modules.push({ name, order: manifest.modules.length, addedWith: stampedWith });
189
+ known.add(name);
190
+ }
191
+ writeManifest(projectRoot, manifest);
192
+ writeFilesLock(projectRoot, computeFilesLock(projectRoot, manifest.ownedGlobs));
193
+ }
@@ -1,15 +1,20 @@
1
1
  # Copy to .env and adjust. Never commit real secrets.
2
+
3
+ # ── Required (runtime) ───────────────────────────────────────────────
2
4
  POSTGRES_HOST=localhost
3
5
  POSTGRES_PORT=5432
4
6
  POSTGRES_USER=podokit
5
7
  POSTGRES_PASSWORD=podokit
6
8
  POSTGRES_DB=podokit
7
9
 
10
+ APP_BASE_URL=http://localhost:5001
11
+ API_BASE_URL=http://localhost:5002
12
+ BACKEND_INTERNAL_URL=http://localhost:5002
13
+ CORS_ORIGIN=http://localhost:5001
14
+ PORT=5002
15
+
16
+ # ── Optional / development ───────────────────────────────────────────
17
+ # Redis — only needed by cache/queue modules. Start it with:
18
+ # docker compose --profile cache up
8
19
  REDIS_HOST=localhost
9
20
  REDIS_PORT=6379
10
-
11
- APP_BASE_URL=http://localhost:5173
12
- API_BASE_URL=http://localhost:3000
13
- BACKEND_INTERNAL_URL=http://localhost:3000
14
- CORS_ORIGIN=http://localhost:5173
15
- PORT=3000
@@ -18,8 +18,8 @@ docker compose -f infra/docker/docker-compose.yml up -d
18
18
  {{packageManager}} run dev
19
19
  ```
20
20
 
21
- - API: http://localhost:3000 — health at `/health`, docs at `/api-docs`
22
- - Web: http://localhost:5173
21
+ - API: http://localhost:5002 — health at `/health`, docs at `/api-docs`
22
+ - Web: http://localhost:5001
23
23
 
24
24
  ## Database & migrations
25
25
 
@@ -7,13 +7,14 @@
7
7
  "build": "nest build",
8
8
  "start": "node dist/main",
9
9
  "lint": "tsc -p tsconfig.json --noEmit",
10
- "test": "jest",
10
+ "test": "jest --passWithNoTests",
11
11
  "typeorm": "typeorm-ts-node-commonjs -d src/database/data-source.ts",
12
12
  "migration:run": "npm run typeorm -- migration:run",
13
13
  "migration:revert": "npm run typeorm -- migration:revert",
14
14
  "migration:generate": "npm run typeorm -- migration:generate"
15
15
  },
16
16
  "dependencies": {
17
+ "@podosoft/podokit-contracts": "^0.1.0",
17
18
  "@nestjs/common": "^11.1.27",
18
19
  "@nestjs/config": "^4.0.4",
19
20
  "@nestjs/core": "^11.1.27",
@@ -39,6 +40,24 @@
39
40
  "supertest": "^7.0.0",
40
41
  "ts-jest": "^29.2.5",
41
42
  "ts-node": "^10.9.2",
42
- "typescript": "^5.6.3"
43
+ "typescript": "^6.0.3"
44
+ },
45
+ "jest": {
46
+ "moduleFileExtensions": [
47
+ "js",
48
+ "json",
49
+ "ts"
50
+ ],
51
+ "rootDir": "src",
52
+ "testRegex": ".*\\.spec\\.ts$",
53
+ "transform": {
54
+ "^.+\\.ts$": [
55
+ "ts-jest",
56
+ {
57
+ "tsconfig": "tsconfig.json"
58
+ }
59
+ ]
60
+ },
61
+ "testEnvironment": "node"
43
62
  }
44
63
  }
@@ -4,17 +4,20 @@ import { TypeOrmModule } from "@nestjs/typeorm";
4
4
  import { validateEnv } from "./config/env.validation";
5
5
  import { dataSourceOptions } from "./database/data-source";
6
6
  import { HealthModule } from "./health/health.module";
7
- // podokit:imports
7
+ // podokit:begin:imports
8
+ // podokit:end:imports
8
9
 
9
10
  @Module({
10
11
  imports: [
11
12
  ConfigModule.forRoot({ isGlobal: true, validate: validateEnv }),
12
13
  TypeOrmModule.forRoot(dataSourceOptions),
13
14
  HealthModule,
14
- // podokit:module-imports
15
+ // podokit:begin:module-imports
16
+ // podokit:end:module-imports
15
17
  ],
16
18
  providers: [
17
- // podokit:providers
19
+ // podokit:begin:providers
20
+ // podokit:end:providers
18
21
  ],
19
22
  })
20
23
  export class AppModule {}
@@ -1,12 +1,4 @@
1
- // Stable, language-independent error codes. The frontend branches on `code`,
2
- // not on the human-readable message.
3
- export class AppException extends Error {
4
- constructor(
5
- readonly code: string,
6
- message: string,
7
- readonly statusCode = 400,
8
- ) {
9
- super(message);
10
- this.name = "AppException";
11
- }
12
- }
1
+ // AppException is a shared contract (stable, language-independent error `code`
2
+ // the frontend branches on). Re-exported from @podosoft/podokit-contracts so the
3
+ // backend and any other layer reference one definition.
4
+ export { AppException } from "@podosoft/podokit-contracts";
@@ -3,7 +3,7 @@ import { z } from "zod";
3
3
  // Schema-validated environment. Fails fast at boot if something is wrong.
4
4
  const schema = z.object({
5
5
  NODE_ENV: z.string().default("development"),
6
- PORT: z.coerce.number().default(3000),
6
+ PORT: z.coerce.number().default(5002),
7
7
  CORS_ORIGIN: z.string().optional(),
8
8
  POSTGRES_HOST: z.string().default("localhost"),
9
9
  POSTGRES_PORT: z.coerce.number().default(5432),
@@ -22,7 +22,7 @@ async function bootstrap(): Promise<void> {
22
22
  .build();
23
23
  SwaggerModule.setup("api-docs", app, SwaggerModule.createDocument(app, config));
24
24
 
25
- const port = Number(process.env.PORT ?? 3000);
25
+ const port = Number(process.env.PORT ?? 5002);
26
26
  await app.listen(port);
27
27
  }
28
28
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "module": "CommonJS",
3
+ "module": "nodenext",
4
4
  "target": "ES2022",
5
- "moduleResolution": "Node",
5
+ "moduleResolution": "nodenext",
6
6
  "outDir": "dist",
7
7
  "rootDir": "src",
8
8
  "declaration": false,
@@ -14,8 +14,17 @@
14
14
  "noImplicitReturns": true,
15
15
  "esModuleInterop": true,
16
16
  "skipLibCheck": true,
17
- "forceConsistentCasingInFileNames": true
17
+ "forceConsistentCasingInFileNames": true,
18
+ "types": [
19
+ "node"
20
+ ]
18
21
  },
19
- "include": ["src/**/*.ts"],
20
- "exclude": ["node_modules", "dist", "test"]
22
+ "include": [
23
+ "src/**/*.ts"
24
+ ],
25
+ "exclude": [
26
+ "node_modules",
27
+ "dist",
28
+ "test"
29
+ ]
21
30
  }
@@ -4,15 +4,16 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "vite dev --port 5173",
7
+ "dev": "vite dev --port 5001",
8
8
  "build": "vite build",
9
9
  "preview": "vite preview",
10
10
  "lint": "svelte-check --tsconfig ./tsconfig.json",
11
- "test": "echo \"add Playwright tests here\" && exit 0"
11
+ "test": "echo \"unit: add vitest; e2e: npm run test:e2e (see tests/)\" && exit 0"
12
12
  },
13
13
  "dependencies": {
14
14
  "@internationalized/date": "^3.12.2",
15
15
  "@lucide/svelte": "^1.23.0",
16
+ "@podosoft/podokit-api-client": "^0.5.0",
16
17
  "bits-ui": "^2.18.1",
17
18
  "clsx": "^2.1.1",
18
19
  "mode-watcher": "^1.1.0",
@@ -29,7 +30,7 @@
29
30
  "svelte-check": "^4.0.0",
30
31
  "tailwindcss": "^4.0.0",
31
32
  "typesafe-i18n": "^5.26.2",
32
- "typescript": "^5.6.3",
33
+ "typescript": "^6.0.3",
33
34
  "vite": "^5.4.0",
34
35
  "tw-animate-css": "^1.4.0"
35
36
  }
@@ -0,0 +1,5 @@
1
+ import { createApiClient } from "@podosoft/podokit-api-client";
2
+
3
+ // Browser client: same-origin. REST calls go to /api/* and auth to /api/auth/*,
4
+ // both proxied to the backend by the SvelteKit server (see routes/api/**).
5
+ export const api = createApiClient();
@@ -27,7 +27,7 @@
27
27
  {preventScroll}
28
28
  data-slot="select-content"
29
29
  class={cn(
30
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-lg shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 overflow-x-hidden overflow-y-auto",
30
+ "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-lg p-1 shadow-md ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 relative isolate z-50 max-h-96 overflow-x-hidden overflow-y-auto",
31
31
  className
32
32
  )}
33
33
  {...restProps}
@@ -26,7 +26,7 @@
26
26
  {#snippet children({ selected, highlighted })}
27
27
  <span class="absolute end-2 flex size-3.5 items-center justify-center">
28
28
  {#if selected}
29
- <CheckIcon class="cn-select-item-indicator-icon" />
29
+ <CheckIcon class="size-3.5" />
30
30
  {/if}
31
31
  </span>
32
32
  <span class="flex flex-1 gap-2 shrink-0 whitespace-nowrap">
@@ -0,0 +1,7 @@
1
+ import Root from "./switch.svelte";
2
+
3
+ export {
4
+ Root,
5
+ //
6
+ Root as Switch,
7
+ };
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import { Switch as SwitchPrimitive } from "bits-ui";
3
+ import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ checked = $bindable(false),
8
+ class: className,
9
+ ...restProps
10
+ }: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props();
11
+ </script>
12
+
13
+ <SwitchPrimitive.Root
14
+ bind:ref
15
+ data-slot="switch"
16
+ class={cn(
17
+ "peer focus-visible:border-ring focus-visible:ring-ring/50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
18
+ className
19
+ )}
20
+ bind:checked
21
+ {...restProps}
22
+ >
23
+ <SwitchPrimitive.Thumb
24
+ data-slot="switch-thumb"
25
+ class={cn(
26
+ "bg-background pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
27
+ )}
28
+ />
29
+ </SwitchPrimitive.Root>
@@ -0,0 +1,19 @@
1
+ import type { RequestEvent } from "@sveltejs/kit";
2
+ import { createApiClient, type ApiClient } from "@podosoft/podokit-api-client";
3
+ import { backendBaseUrl } from "./backend-proxy";
4
+
5
+ // Server-side client for load functions and hooks: points at the internal
6
+ // backend URL and forwards the incoming request's cookies.
7
+ export function serverApiClient(event: RequestEvent): ApiClient {
8
+ const cookie = event.request.headers.get("cookie") ?? "";
9
+ const fetchWithCookies: typeof fetch = (input, init) => {
10
+ const headers = new Headers(init?.headers);
11
+ if (cookie) headers.set("cookie", cookie);
12
+ return event.fetch(input, { ...init, headers });
13
+ };
14
+ return createApiClient({
15
+ baseUrl: backendBaseUrl(),
16
+ apiBasePath: "",
17
+ fetch: fetchWithCookies,
18
+ });
19
+ }
@@ -1,16 +1,55 @@
1
1
  // Server-side proxy boundary. The browser never talks to the API directly:
2
- // only these allowlisted headers are forwarded to BACKEND_INTERNAL_URL.
3
- const FORWARDED_HEADERS = ["authorization", "cookie", "content-type"];
2
+ // requests go through SvelteKit server routes, which forward an allowlist of
3
+ // headers to BACKEND_INTERNAL_URL and relay the response (including Set-Cookie).
4
+ const FORWARDED_HEADERS = ["authorization", "cookie", "content-type", "accept", "origin", "referer"];
5
+ const RELAYED_RESPONSE_HEADERS = ["content-type", "location", "cache-control"];
4
6
 
5
7
  export function backendBaseUrl(): string {
6
- return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:3000";
8
+ return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:5002";
7
9
  }
8
10
 
9
- export function backendProxyHeaders(request: Request): Headers {
11
+ // Normalize the resolved client IP for the forwarded header. better-auth stores
12
+ // IPv4 as-is but anonymizes IPv6 (masks the host bits), so an IPv6 loopback would
13
+ // be recorded as "::" — map loopback to 127.0.0.1 for a clean local value, and
14
+ // unwrap IPv4-mapped addresses.
15
+ function normalizeClientIp(address: string | undefined): string | undefined {
16
+ if (!address) return undefined;
17
+ if (address === "::1" || address === "::") return "127.0.0.1";
18
+ if (address.startsWith("::ffff:")) return address.slice("::ffff:".length);
19
+ return address;
20
+ }
21
+
22
+ export async function proxyRequest(
23
+ request: Request,
24
+ targetUrl: string,
25
+ clientAddress?: string,
26
+ ): Promise<Response> {
10
27
  const headers = new Headers();
11
28
  for (const name of FORWARDED_HEADERS) {
12
29
  const value = request.headers.get(name);
13
30
  if (value) headers.set(name, value);
14
31
  }
15
- return headers;
32
+ // Forward the resolved client IP so the API (better-auth) can record it.
33
+ const forwardedIp = normalizeClientIp(clientAddress);
34
+ if (forwardedIp) headers.set("x-forwarded-for", forwardedIp);
35
+
36
+ const hasBody = request.method !== "GET" && request.method !== "HEAD";
37
+ const upstream = await fetch(targetUrl, {
38
+ method: request.method,
39
+ headers,
40
+ body: hasBody ? await request.text() : undefined,
41
+ redirect: "manual",
42
+ });
43
+
44
+ const responseHeaders = new Headers();
45
+ for (const name of RELAYED_RESPONSE_HEADERS) {
46
+ const value = upstream.headers.get(name);
47
+ if (value) responseHeaders.set(name, value);
48
+ }
49
+ // Relay Set-Cookie so auth session cookies reach the browser.
50
+ for (const cookie of upstream.headers.getSetCookie()) {
51
+ responseHeaders.append("set-cookie", cookie);
52
+ }
53
+
54
+ return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
16
55
  }
@@ -1,14 +1,19 @@
1
1
  <script lang="ts">
2
2
  import { Button } from "$lib/components/ui/button";
3
3
  import * as Card from "$lib/components/ui/card";
4
+ import { api } from "$lib/api";
5
+ import { ApiError } from "@podosoft/podokit-api-client";
4
6
 
5
7
  type Health = { status: string } | { error: string };
6
8
 
7
9
  let health = $state<Health | null>(null);
8
10
 
9
11
  async function check(): Promise<void> {
10
- const res = await fetch("/api/health");
11
- health = res.ok ? await res.json() : { error: `HTTP ${res.status}` };
12
+ try {
13
+ health = await api.get<{ status: string }>("/health");
14
+ } catch (err) {
15
+ health = { error: err instanceof ApiError ? `${err.code} (${err.statusCode})` : "Request failed" };
16
+ }
12
17
  }
13
18
  </script>
14
19
 
@@ -21,7 +26,7 @@
21
26
  <Card.Root>
22
27
  <Card.Header>
23
28
  <Card.Title>API health</Card.Title>
24
- <Card.Description>Check the NestJS API through the SvelteKit server proxy.</Card.Description>
29
+ <Card.Description>Check the NestJS API through the typed ApiClient.</Card.Description>
25
30
  </Card.Header>
26
31
  <Card.Content class="flex flex-col gap-4">
27
32
  <Button class="w-fit" onclick={check}>Check API health</Button>
@@ -0,0 +1,12 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
3
+
4
+ // Proxy the app's REST API, stripping the /api prefix (backend routes are at root).
5
+ const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
6
+ proxyRequest(request, `${backendBaseUrl()}/${params.path}${url.search}`, getClientAddress());
7
+
8
+ export const GET = handler;
9
+ export const POST = handler;
10
+ export const PUT = handler;
11
+ export const PATCH = handler;
12
+ export const DELETE = handler;
@@ -0,0 +1,12 @@
1
+ import type { RequestHandler } from "@sveltejs/kit";
2
+ import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
3
+
4
+ // Proxy the better-auth handler, keeping the /api/auth prefix.
5
+ const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
6
+ proxyRequest(request, `${backendBaseUrl()}/api/auth/${params.all}${url.search}`, getClientAddress());
7
+
8
+ export const GET = handler;
9
+ export const POST = handler;
10
+ export const PUT = handler;
11
+ export const PATCH = handler;
12
+ export const DELETE = handler;
@@ -1,6 +1,8 @@
1
1
  # Copy to .env and adjust. Never commit real secrets.
2
+
3
+ # ── Required (runtime) ───────────────────────────────────────────────
2
4
  NODE_ENV=development
3
- PORT=3000
5
+ PORT=5002
4
6
 
5
7
  POSTGRES_HOST=localhost
6
8
  POSTGRES_PORT=5432
@@ -8,9 +10,12 @@ POSTGRES_USER=podokit
8
10
  POSTGRES_PASSWORD=podokit
9
11
  POSTGRES_DB=podokit
10
12
 
13
+ # Web -> API boundary (server-side only; not exposed to the browser)
14
+ BACKEND_INTERNAL_URL=http://localhost:5002
15
+ CORS_ORIGIN=http://localhost:5001
16
+
17
+ # ── Optional / development ───────────────────────────────────────────
18
+ # Redis — only needed by cache/queue modules (redis, bullmq, rate-limit,
19
+ # job-progress, sse). Start it with: docker compose --profile cache up
11
20
  REDIS_HOST=localhost
12
21
  REDIS_PORT=6379
13
-
14
- # Web -> API boundary (server-side only; not exposed to the browser)
15
- BACKEND_INTERNAL_URL=http://localhost:3000
16
- CORS_ORIGIN=http://localhost:5173
@@ -7,3 +7,8 @@ build/
7
7
  !.env.example
8
8
  .DS_Store
9
9
  *.log
10
+
11
+ # e2e test artifacts
12
+ tests/playwright/
13
+ tests/playwright-report/
14
+ tests/test-results/