@nextsparkjs/core 0.1.0-beta.185 → 0.1.0-beta.186

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 (386) hide show
  1. package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
  2. package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
  3. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicFooter.js +2 -2
  5. package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
  6. package/dist/components/app/layouts/PublicNavbar.js +2 -2
  7. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  8. package/dist/components/app/misc/ThemeToggle.js +4 -1
  9. package/dist/components/auth/DevKeyring.d.ts.map +1 -1
  10. package/dist/components/auth/DevKeyring.js +10 -2
  11. package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
  12. package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
  13. package/dist/components/auth/SessionCookieRefresher.js +11 -0
  14. package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
  15. package/dist/components/auth/forms/LoginForm.js +300 -6
  16. package/dist/components/auth/index.d.ts +1 -0
  17. package/dist/components/auth/index.d.ts.map +1 -1
  18. package/dist/components/auth/index.js +2 -0
  19. package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
  20. package/dist/components/billing/InvoicesTable.js +4 -3
  21. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  22. package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
  23. package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
  24. package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
  25. package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
  26. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
  27. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
  28. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
  29. package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
  30. package/dist/components/dashboard/layouts/Sidebar.js +2 -3
  31. package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
  32. package/dist/components/dashboard/layouts/index.d.ts +1 -0
  33. package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
  34. package/dist/components/dashboard/layouts/index.js +2 -0
  35. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  36. package/dist/components/entities/EntityFieldRenderer.js +3 -0
  37. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  38. package/dist/components/entities/EntityTable.js +2 -1
  39. package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
  40. package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
  41. package/dist/components/ui/combobox.d.ts +8 -1
  42. package/dist/components/ui/combobox.d.ts.map +1 -1
  43. package/dist/components/ui/combobox.js +37 -44
  44. package/dist/components/ui/country-select.d.ts +3 -1
  45. package/dist/components/ui/country-select.d.ts.map +1 -1
  46. package/dist/components/ui/country-select.js +3 -1
  47. package/dist/components/ui/currency-select.d.ts +3 -1
  48. package/dist/components/ui/currency-select.d.ts.map +1 -1
  49. package/dist/components/ui/currency-select.js +3 -1
  50. package/dist/components/ui/sheet.d.ts +5 -0
  51. package/dist/components/ui/sheet.d.ts.map +1 -1
  52. package/dist/components/ui/sheet.js +46 -26
  53. package/dist/components/ui/timezone-select.d.ts +3 -1
  54. package/dist/components/ui/timezone-select.d.ts.map +1 -1
  55. package/dist/components/ui/timezone-select.js +3 -1
  56. package/dist/contexts/TeamContext.d.ts +6 -0
  57. package/dist/contexts/TeamContext.d.ts.map +1 -1
  58. package/dist/contexts/TeamContext.js +8 -1
  59. package/dist/emails/otp-verification.d.ts.map +1 -1
  60. package/dist/emails/reset-password.d.ts.map +1 -1
  61. package/dist/emails/team-invitation.d.ts.map +1 -1
  62. package/dist/emails/team-invitation.js +3 -2
  63. package/dist/emails/verify-email.d.ts.map +1 -1
  64. package/dist/hooks/index.d.ts +1 -0
  65. package/dist/hooks/index.d.ts.map +1 -1
  66. package/dist/hooks/index.js +1 -0
  67. package/dist/hooks/useAuth.d.ts +29 -0
  68. package/dist/hooks/useAuth.d.ts.map +1 -1
  69. package/dist/hooks/useAuth.js +24 -0
  70. package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
  71. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  72. package/dist/hooks/useEnsureUserMetadata.js +18 -5
  73. package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
  74. package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
  75. package/dist/hooks/useSessionCookieRefresh.js +49 -0
  76. package/dist/lib/api/api-error.d.ts +3 -1
  77. package/dist/lib/api/api-error.d.ts.map +1 -1
  78. package/dist/lib/api/api-error.js +4 -2
  79. package/dist/lib/api/auth/dual-auth.d.ts +38 -3
  80. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  81. package/dist/lib/api/auth/dual-auth.js +42 -7
  82. package/dist/lib/api/auth/permissions.d.ts +7 -2
  83. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  84. package/dist/lib/api/auth/permissions.js +2 -2
  85. package/dist/lib/api/auth/scope-policy.d.ts +71 -0
  86. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
  87. package/dist/lib/api/auth/scope-policy.js +53 -0
  88. package/dist/lib/api/auth.d.ts +15 -0
  89. package/dist/lib/api/auth.d.ts.map +1 -1
  90. package/dist/lib/api/auth.js +17 -4
  91. package/dist/lib/api/entity/audit-log.d.ts +35 -0
  92. package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
  93. package/dist/lib/api/entity/audit-log.js +35 -0
  94. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  95. package/dist/lib/api/entity/generic-handler.js +315 -134
  96. package/dist/lib/api/helpers.d.ts +29 -4
  97. package/dist/lib/api/helpers.d.ts.map +1 -1
  98. package/dist/lib/api/helpers.js +30 -6
  99. package/dist/lib/api/keys.d.ts +16 -0
  100. package/dist/lib/api/keys.d.ts.map +1 -1
  101. package/dist/lib/api/keys.js +19 -1
  102. package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
  103. package/dist/lib/api/routes/patterns-usages.js +3 -11
  104. package/dist/lib/auth/auth-methods.d.ts +52 -0
  105. package/dist/lib/auth/auth-methods.d.ts.map +1 -0
  106. package/dist/lib/auth/auth-methods.js +63 -0
  107. package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
  108. package/dist/lib/auth/registration-helpers.d.ts +9 -0
  109. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  110. package/dist/lib/auth/registration-helpers.js +8 -2
  111. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
  112. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
  113. package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
  114. package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
  115. package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
  116. package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
  117. package/dist/lib/auth/security-notifications/index.d.ts +20 -0
  118. package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
  119. package/dist/lib/auth/security-notifications/index.js +22 -0
  120. package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
  121. package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
  122. package/dist/lib/auth/security-notifications/registry.js +26 -0
  123. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
  124. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
  125. package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
  126. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
  127. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
  128. package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
  129. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
  130. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
  131. package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
  132. package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
  133. package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
  134. package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
  135. package/dist/lib/auth/security-notifications/types.d.ts +74 -0
  136. package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
  137. package/dist/lib/auth/security-notifications/types.js +0 -0
  138. package/dist/lib/auth/session-config.d.ts +37 -0
  139. package/dist/lib/auth/session-config.d.ts.map +1 -0
  140. package/dist/lib/auth/session-config.js +59 -0
  141. package/dist/lib/auth/session-refresh.d.ts +46 -0
  142. package/dist/lib/auth/session-refresh.d.ts.map +1 -0
  143. package/dist/lib/auth/session-refresh.js +11 -0
  144. package/dist/lib/auth-client.d.ts +1639 -0
  145. package/dist/lib/auth-client.d.ts.map +1 -0
  146. package/dist/lib/auth-client.js +4 -0
  147. package/dist/lib/auth.d.ts.map +1 -1
  148. package/dist/lib/auth.js +21 -8
  149. package/dist/lib/billing/gateways/factory.d.ts +8 -3
  150. package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
  151. package/dist/lib/billing/gateways/factory.js +37 -23
  152. package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
  153. package/dist/lib/billing/stripe-webhook.js +2 -1
  154. package/dist/lib/config/app.config.d.ts.map +1 -1
  155. package/dist/lib/config/app.config.js +71 -0
  156. package/dist/lib/config/config-sync.d.ts +1 -4
  157. package/dist/lib/config/config-sync.d.ts.map +1 -1
  158. package/dist/lib/config/config-sync.js +6 -2
  159. package/dist/lib/config/types.d.ts +130 -0
  160. package/dist/lib/config/types.d.ts.map +1 -1
  161. package/dist/lib/entities/entity-hooks.d.ts +9 -1
  162. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  163. package/dist/lib/entities/entity-hooks.js +77 -4
  164. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  165. package/dist/lib/entities/schema-generator.js +20 -4
  166. package/dist/lib/entities/serialization.d.ts +2 -2
  167. package/dist/lib/entities/serialization.d.ts.map +1 -1
  168. package/dist/lib/entities/serialization.js +3 -2
  169. package/dist/lib/entities/types.d.ts +8 -0
  170. package/dist/lib/entities/types.d.ts.map +1 -1
  171. package/dist/lib/locale.d.ts.map +1 -1
  172. package/dist/lib/locale.js +4 -1
  173. package/dist/lib/mcp/audit.d.ts +8 -4
  174. package/dist/lib/mcp/audit.d.ts.map +1 -1
  175. package/dist/lib/plugins/hook-system.d.ts +13 -0
  176. package/dist/lib/plugins/hook-system.d.ts.map +1 -1
  177. package/dist/lib/plugins/hook-system.js +32 -2
  178. package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
  179. package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
  180. package/dist/lib/scheduled-actions/handlers/index.js +3 -1
  181. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
  182. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
  183. package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
  184. package/dist/lib/selectors/core-selectors.d.ts +12 -0
  185. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  186. package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
  187. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  188. package/dist/lib/selectors/domains/auth.selectors.js +14 -1
  189. package/dist/lib/selectors/selectors.d.ts +24 -0
  190. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  191. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  192. package/dist/lib/services/generic-entity.service.js +16 -32
  193. package/dist/lib/services/membership.service.d.ts +10 -2
  194. package/dist/lib/services/membership.service.d.ts.map +1 -1
  195. package/dist/lib/services/membership.service.js +37 -16
  196. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  197. package/dist/lib/services/subscription.service.js +2 -1
  198. package/dist/lib/services/team-member.service.d.ts +15 -3
  199. package/dist/lib/services/team-member.service.d.ts.map +1 -1
  200. package/dist/lib/services/team-member.service.js +27 -6
  201. package/dist/lib/services/team.service.d.ts.map +1 -1
  202. package/dist/lib/teams/permissions.d.ts +3 -1
  203. package/dist/lib/teams/permissions.d.ts.map +1 -1
  204. package/dist/lib/teams/permissions.js +6 -0
  205. package/dist/lib/teams/schema.d.ts.map +1 -1
  206. package/dist/lib/teams/schema.js +3 -0
  207. package/dist/lib/theme/forced-theme.d.ts +34 -0
  208. package/dist/lib/theme/forced-theme.d.ts.map +1 -0
  209. package/dist/lib/theme/forced-theme.js +27 -0
  210. package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
  211. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  212. package/dist/lib/theme/get-default-theme-mode.js +5 -3
  213. package/dist/messages/de/auth.json +25 -2
  214. package/dist/messages/de/index.d.ts +81 -0
  215. package/dist/messages/de/index.d.ts.map +1 -1
  216. package/dist/messages/de/index.js +2 -0
  217. package/dist/messages/de/index.ts +2 -0
  218. package/dist/messages/en/auth.json +25 -2
  219. package/dist/messages/en/index.d.ts +81 -0
  220. package/dist/messages/en/index.d.ts.map +1 -1
  221. package/dist/messages/en/index.js +2 -0
  222. package/dist/messages/en/index.ts +2 -0
  223. package/dist/messages/es/auth.json +25 -2
  224. package/dist/messages/es/index.d.ts +81 -0
  225. package/dist/messages/es/index.d.ts.map +1 -1
  226. package/dist/messages/es/index.js +2 -0
  227. package/dist/messages/es/index.ts +2 -0
  228. package/dist/messages/fr/auth.json +25 -2
  229. package/dist/messages/fr/index.d.ts +81 -0
  230. package/dist/messages/fr/index.d.ts.map +1 -1
  231. package/dist/messages/fr/index.js +2 -0
  232. package/dist/messages/fr/index.ts +2 -0
  233. package/dist/messages/it/auth.json +25 -2
  234. package/dist/messages/it/index.d.ts +81 -0
  235. package/dist/messages/it/index.d.ts.map +1 -1
  236. package/dist/messages/it/index.js +2 -0
  237. package/dist/messages/it/index.ts +2 -0
  238. package/dist/messages/pt/auth.json +25 -2
  239. package/dist/messages/pt/index.d.ts +81 -0
  240. package/dist/messages/pt/index.d.ts.map +1 -1
  241. package/dist/messages/pt/index.js +2 -0
  242. package/dist/messages/pt/index.ts +2 -0
  243. package/dist/migrations/005_api_audit_log.sql +2 -1
  244. package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  245. package/dist/migrations/026_login_events.sql +91 -0
  246. package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  247. package/dist/providers/DashboardProviders.d.ts +12 -3
  248. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  249. package/dist/providers/DashboardProviders.js +6 -2
  250. package/dist/providers/theme-provider.d.ts +19 -2
  251. package/dist/providers/theme-provider.d.ts.map +1 -1
  252. package/dist/providers/theme-provider.js +6 -2
  253. package/dist/templates/app/(auth)/signup/page.tsx +8 -0
  254. package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
  255. package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
  256. package/dist/templates/app/api/health/route.ts +4 -2
  257. package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
  258. package/dist/templates/app/api/mcp/route.ts +4 -1
  259. package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  260. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  261. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  262. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  263. package/dist/templates/app/api/v1/api-keys/route.ts +21 -43
  264. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  265. package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
  266. package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  267. package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
  268. package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
  269. package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
  270. package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
  271. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  272. package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  273. package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
  274. package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
  275. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  276. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  277. package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
  278. package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
  279. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  280. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  281. package/dist/templates/app/api/v1/media/route.ts +5 -9
  282. package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
  283. package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
  284. package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  285. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  286. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  287. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  288. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  289. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  290. package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
  291. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  292. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  293. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  294. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  295. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  296. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  297. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  298. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  299. package/dist/templates/app/api/v1/teams/route.ts +33 -27
  300. package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
  301. package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  302. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  303. package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
  304. package/dist/templates/app/api/v1/users/route.ts +21 -25
  305. package/dist/templates/app/dashboard/layout.tsx +2 -6
  306. package/dist/templates/app/layout.ppr.tsx +9 -14
  307. package/dist/templates/app/layout.tsx +37 -20
  308. package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
  309. package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
  310. package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
  311. package/dist/templates/proxy.ts +46 -10
  312. package/dist/templates/tsconfig.json +4 -0
  313. package/dist/types/theme.d.ts +9 -0
  314. package/dist/types/theme.d.ts.map +1 -1
  315. package/migrations/005_api_audit_log.sql +2 -1
  316. package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  317. package/migrations/026_login_events.sql +91 -0
  318. package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  319. package/package.json +14 -11
  320. package/scripts/build/registry/__tests__/scope-registry.test.mjs +18 -0
  321. package/scripts/build/registry/generators/scope-registry.mjs +9 -2
  322. package/templates/app/(auth)/signup/page.tsx +8 -0
  323. package/templates/app/(public)/[...slug]/page.tsx +9 -14
  324. package/templates/app/api/auth/[...all]/route.ts +23 -1
  325. package/templates/app/api/health/route.ts +4 -2
  326. package/templates/app/api/internal/user-metadata/route.ts +5 -3
  327. package/templates/app/api/mcp/route.ts +4 -1
  328. package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  329. package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  330. package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  331. package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  332. package/templates/app/api/v1/api-keys/route.ts +21 -43
  333. package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  334. package/templates/app/api/v1/billing/cancel/route.ts +7 -6
  335. package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  336. package/templates/app/api/v1/billing/check-action/route.ts +6 -4
  337. package/templates/app/api/v1/billing/checkout/route.ts +7 -5
  338. package/templates/app/api/v1/billing/plans/route.ts +8 -12
  339. package/templates/app/api/v1/billing/portal/route.ts +7 -5
  340. package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  341. package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  342. package/templates/app/api/v1/devtools/features/route.ts +5 -4
  343. package/templates/app/api/v1/devtools/flows/route.ts +5 -4
  344. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  345. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  346. package/templates/app/api/v1/devtools/testing/route.ts +5 -4
  347. package/templates/app/api/v1/media/[id]/route.ts +13 -25
  348. package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  349. package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  350. package/templates/app/api/v1/media/route.ts +5 -9
  351. package/templates/app/api/v1/media/upload/route.ts +9 -21
  352. package/templates/app/api/v1/media-tags/route.ts +9 -13
  353. package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  354. package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  355. package/templates/app/api/v1/post-categories/route.ts +5 -4
  356. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  357. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  358. package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  359. package/templates/app/api/v1/team-invitations/route.ts +9 -11
  360. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  361. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  362. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  363. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  364. package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  365. package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  366. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  367. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  368. package/templates/app/api/v1/teams/route.ts +33 -27
  369. package/templates/app/api/v1/teams/switch/route.ts +11 -13
  370. package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  371. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  372. package/templates/app/api/v1/users/[id]/route.ts +35 -68
  373. package/templates/app/api/v1/users/route.ts +21 -25
  374. package/templates/app/dashboard/layout.tsx +2 -6
  375. package/templates/app/layout.ppr.tsx +9 -14
  376. package/templates/app/layout.tsx +37 -20
  377. package/templates/contents/themes/starter/config/app.config.ts +19 -0
  378. package/templates/contents/themes/starter/config/theme.config.ts +6 -0
  379. package/templates/contents/themes/starter/styles/globals.css +6 -2
  380. package/templates/proxy.ts +46 -10
  381. package/templates/tsconfig.json +4 -0
  382. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
  383. package/tests/jest/__mocks__/next-server.js +24 -0
  384. package/dist/styles/classes.json +0 -1093
  385. package/dist/styles/ui.css +0 -2
  386. package/scripts/build/build-ui-css.mjs +0 -351
@@ -14,7 +14,7 @@ import {
14
14
  handleEntityMetadataInResponse,
15
15
  processEntityMetadata
16
16
  } from '@nextsparkjs/core/lib/api/helpers';
17
- import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth';
17
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth';
18
18
  import { hasAdminPermission } from '@nextsparkjs/core/lib/api/auth/permissions';
19
19
  import { z } from 'zod';
20
20
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit';
@@ -34,21 +34,19 @@ const createUserSchema = z.object({
34
34
 
35
35
 
36
36
  // Handle CORS preflight
37
- export async function OPTIONS() {
38
- return handleCorsPreflightRequest();
37
+ export async function OPTIONS(request: NextRequest) {
38
+ return handleCorsPreflightRequest(request);
39
39
  }
40
40
 
41
41
  // GET /api/v1/users - List users with dual auth
42
42
  export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
43
43
  try {
44
- // Authenticate using dual auth
45
- const authResult = await authenticateRequest(req);
46
-
44
+ // Authenticate using dual auth; the API-key scope is declared at the entry
45
+ // point, which fails closed for keys that lack it (#93).
46
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:read' });
47
+
47
48
  if (!authResult.success) {
48
- return NextResponse.json(
49
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
50
- { status: 401 }
51
- );
49
+ return createAuthFailureResponse(authResult);
52
50
  }
53
51
 
54
52
  if (authResult.rateLimitResponse) {
@@ -58,7 +56,7 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
58
56
  // SECURITY: Only superadmins can list all users
59
57
  if (!hasAdminPermission(authResult, 'users:read')) {
60
58
  const response = createApiError('Insufficient permissions. Superadmin access required.', 403);
61
- return addCorsHeaders(response);
59
+ return addCorsHeaders(response, req);
62
60
  }
63
61
 
64
62
  const metaParams = parseMetaParams(req);
@@ -103,25 +101,23 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
103
101
  const usersWithMeta = await includeEntityMetadata('user', users as { id: string }[], metaParams, authResult.user!.id);
104
102
 
105
103
  const response = createApiResponse(usersWithMeta, paginationMeta);
106
- return addCorsHeaders(response);
104
+ return addCorsHeaders(response, req);
107
105
  } catch (error) {
108
106
  console.error('Error fetching users:', error);
109
107
  const response = createApiError('Internal server error', 500);
110
- return addCorsHeaders(response);
108
+ return addCorsHeaders(response, req);
111
109
  }
112
110
  }), 'read');
113
111
 
114
112
  // POST /api/v1/users - Create user with dual auth
115
113
  export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
116
114
  try {
117
- // Authenticate using dual auth
118
- const authResult = await authenticateRequest(req);
119
-
115
+ // Authenticate using dual auth; the API-key scope is declared at the entry
116
+ // point, which fails closed for keys that lack it (#93).
117
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:write' });
118
+
120
119
  if (!authResult.success) {
121
- return NextResponse.json(
122
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
123
- { status: 401 }
124
- );
120
+ return createAuthFailureResponse(authResult);
125
121
  }
126
122
 
127
123
  if (authResult.rateLimitResponse) {
@@ -131,7 +127,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
131
127
  // SECURITY: Only superadmins can create users
132
128
  if (!hasAdminPermission(authResult, 'users:write')) {
133
129
  const response = createApiError('Insufficient permissions. Superadmin access required.', 403);
134
- return addCorsHeaders(response);
130
+ return addCorsHeaders(response, req);
135
131
  }
136
132
 
137
133
  const body = await req.json();
@@ -147,7 +143,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
147
143
 
148
144
  if (existingUser.length > 0) {
149
145
  const response = createApiError('Email already exists', 409, null, 'EMAIL_EXISTS');
150
- return addCorsHeaders(response);
146
+ return addCorsHeaders(response, req);
151
147
  }
152
148
 
153
149
  const newUserId = globalThis.crypto.randomUUID();
@@ -183,16 +179,16 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
183
179
  const responseData = await handleEntityMetadataInResponse('user', createdUser as { id: string }, metadataWasProvided, authResult.user!.id);
184
180
 
185
181
  const response = createApiResponse(responseData, { created: true }, 201);
186
- return addCorsHeaders(response);
182
+ return addCorsHeaders(response, req);
187
183
  } catch (error) {
188
184
  if (error instanceof z.ZodError) {
189
185
  const response = createApiError('Validation error', 400, error.issues, 'VALIDATION_ERROR');
190
- return addCorsHeaders(response);
186
+ return addCorsHeaders(response, req);
191
187
  }
192
188
 
193
189
  console.error('Error creating user:', error);
194
190
  const response = createApiError('Internal server error', 500);
195
- return addCorsHeaders(response);
191
+ return addCorsHeaders(response, req);
196
192
  }
197
193
  }), 'write');
198
194
 
@@ -3,7 +3,7 @@
3
3
  import { useAuth } from '@nextsparkjs/core/hooks/useAuth'
4
4
  import { useRouter } from 'next/navigation'
5
5
  import { useEffect, Suspense } from 'react'
6
- import { Loader2 } from 'lucide-react'
6
+ import { DashboardAuthSkeleton } from '@nextsparkjs/core/components/dashboard/layouts/DashboardAuthSkeleton'
7
7
  import { DashboardProviders } from '@nextsparkjs/core/providers/DashboardProviders'
8
8
  import { DashboardTranslationPreloader } from '@nextsparkjs/core/lib/i18n/DashboardTranslationPreloader'
9
9
  import { TranslationDebugger } from '@nextsparkjs/core/utils/dev/TranslationDebugger'
@@ -35,11 +35,7 @@ function DashboardLayoutContent({ children }: { children: React.ReactNode }) {
35
35
  }, [user, isLoading, router])
36
36
 
37
37
  if (isLoading) {
38
- return (
39
- <div className="min-h-screen flex items-center justify-center">
40
- <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
41
- </div>
42
- )
38
+ return <DashboardAuthSkeleton />
43
39
  }
44
40
 
45
41
  if (!user) {
@@ -55,27 +55,22 @@ export const metadata: Metadata = getMetadataOrDefault(
55
55
  defaultMetadata
56
56
  )
57
57
 
58
- export default function RootLayout({
58
+ export default async function RootLayout({
59
59
  children,
60
60
  }: Readonly<{
61
61
  children: React.ReactNode
62
62
  }>) {
63
+ const billingHints = await getBillingResourceHints()
64
+
63
65
  return (
64
66
  <html lang={DEFAULT_LOCALE} suppressHydrationWarning>
65
67
  <head>
66
- {(() => {
67
- const hints = getBillingResourceHints()
68
- return (
69
- <>
70
- {hints.preconnect.map((domain) => (
71
- <link key={`pre-${domain}`} rel="preconnect" href={domain} />
72
- ))}
73
- {[...hints.preconnect, ...hints.dnsPrefetch].map((domain) => (
74
- <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
75
- ))}
76
- </>
77
- )
78
- })()}
68
+ {billingHints.preconnect.map((domain) => (
69
+ <link key={`pre-${domain}`} rel="preconnect" href={domain} />
70
+ ))}
71
+ {[...billingHints.preconnect, ...billingHints.dnsPrefetch].map((domain) => (
72
+ <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
73
+ ))}
79
74
  </head>
80
75
  <body
81
76
  className={`${geistSans.variable} ${geistMono.variable} antialiased`}
@@ -15,11 +15,15 @@ import { getBillingResourceHints } from "@nextsparkjs/core/lib/billing/gateways/
15
15
  import { ThemeProvider as NextThemeProvider } from "@nextsparkjs/core/providers/theme-provider"
16
16
  import { ThemeProvider as CustomThemeProvider } from "@nextsparkjs/core/lib/theme/ThemeProvider"
17
17
  import { Toaster } from "@nextsparkjs/core/components/ui/sonner"
18
+ import { QueryProvider } from "@nextsparkjs/core/providers/query-provider"
19
+ import { TeamProvider } from "@nextsparkjs/core/contexts/TeamContext"
20
+ import { SubscriptionProvider } from "@nextsparkjs/core/contexts/SubscriptionContext"
18
21
  import { getUserLocale } from '@nextsparkjs/core/lib/locale'
19
22
  import { TranslationContextManager } from "@nextsparkjs/core/providers/TranslationContextManager"
23
+ import { SessionCookieRefresher } from "@nextsparkjs/core/components/auth/SessionCookieRefresher"
20
24
  import { PluginService } from '@nextsparkjs/core/lib/services'
21
25
  import { getMetadataOrDefault } from '@nextsparkjs/core/lib/template-resolver'
22
- import { getDefaultThemeMode } from '@nextsparkjs/core/lib/theme/get-default-theme-mode'
26
+ import { getThemeSettings } from '@nextsparkjs/core/lib/theme/get-default-theme-mode'
23
27
 
24
28
  const geistSans = Geist({
25
29
  variable: "--font-geist-sans",
@@ -55,24 +59,18 @@ export default async function RootLayout({
55
59
 
56
60
  const locale = await getUserLocale()
57
61
  const messages = await getMessages({ locale })
58
- const defaultTheme = await getDefaultThemeMode()
62
+ const { defaultMode, allowUserToggle, forcedThemeRoutes } = await getThemeSettings()
63
+ const billingHints = await getBillingResourceHints()
59
64
 
60
65
  return (
61
66
  <html lang={locale} suppressHydrationWarning>
62
67
  <head>
63
- {(() => {
64
- const hints = getBillingResourceHints()
65
- return (
66
- <>
67
- {hints.preconnect.map((domain) => (
68
- <link key={`pre-${domain}`} rel="preconnect" href={domain} />
69
- ))}
70
- {[...hints.preconnect, ...hints.dnsPrefetch].map((domain) => (
71
- <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
72
- ))}
73
- </>
74
- )
75
- })()}
68
+ {billingHints.preconnect.map((domain) => (
69
+ <link key={`pre-${domain}`} rel="preconnect" href={domain} />
70
+ ))}
71
+ {[...billingHints.preconnect, ...billingHints.dnsPrefetch].map((domain) => (
72
+ <link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
73
+ ))}
76
74
  </head>
77
75
  <body
78
76
  className={`${geistSans.variable} ${geistMono.variable} antialiased`}
@@ -80,14 +78,33 @@ export default async function RootLayout({
80
78
  <NextIntlClientProvider locale={locale} messages={messages}>
81
79
  <NextThemeProvider
82
80
  attribute="class"
83
- defaultTheme={defaultTheme}
84
- enableSystem
81
+ defaultTheme={defaultMode}
82
+ // When allowUserToggle is false, force the theme and ignore localStorage/system
83
+ forcedTheme={!allowUserToggle ? defaultMode : undefined}
84
+ // Only detect OS preference when theme configures defaultMode: 'system' AND user can toggle
85
+ enableSystem={allowUserToggle && defaultMode === 'system'}
86
+ // Force a theme on the routes declared in theme.config.ts (forcedThemeRoutes)
87
+ forcedThemeRoutes={forcedThemeRoutes}
85
88
  disableTransitionOnChange
86
89
  >
87
90
  <CustomThemeProvider>
88
- <TranslationContextManager />
89
- <main>{children}</main>
90
- <Suspense><Toaster position="bottom-left" /></Suspense>
91
+ {/* Team providers live here (not only in DashboardProviders) so the activeTeamId
92
+ cookie is synced from any route, matching layout.ppr.tsx — see #115.
93
+ DashboardProviders skips re-mounting them when already present. */}
94
+ <QueryProvider>
95
+ <TeamProvider>
96
+ <SubscriptionProvider>
97
+ <TranslationContextManager />
98
+ {/* Real session-cookie renewal for installed PWAs: the render-time
99
+ session reads above (getUserLocale/getThemeSettings) cannot
100
+ write cookies, so the rolling refresh is triggered from the
101
+ client through the auth Route Handler instead. */}
102
+ <SessionCookieRefresher />
103
+ <main>{children}</main>
104
+ <Suspense><Toaster position="bottom-left" /></Suspense>
105
+ </SubscriptionProvider>
106
+ </TeamProvider>
107
+ </QueryProvider>
91
108
  </CustomThemeProvider>
92
109
  </NextThemeProvider>
93
110
  </NextIntlClientProvider>
@@ -115,6 +115,25 @@ export const APP_CONFIG_OVERRIDES = {
115
115
  enabled: false,
116
116
  },
117
117
  },
118
+ /**
119
+ * Login methods, in priority order. Core default = passwordless preset:
120
+ * a one-time code by email + Google, no password field (needs an email
121
+ * provider — RESEND_API_KEY — and GOOGLE_CLIENT_ID/SECRET for Google).
122
+ *
123
+ * Uncomment to switch to the classic email + password preset, or list both
124
+ * (the first email method is the form shown first):
125
+ */
126
+ // methods: ['email-password', 'google'],
127
+ // methods: ['email-otp', 'email-password', 'google'],
128
+
129
+ /**
130
+ * Session duration / renewal in seconds (defaults: 7 days, renewed daily).
131
+ * Long-lived sessions for an installed PWA, for example:
132
+ */
133
+ // session: {
134
+ // expiresIn: 60 * 60 * 24 * 90, // 90 days
135
+ // updateAge: 60 * 60 * 24 * 7, // renew weekly
136
+ // },
118
137
  /**
119
138
  * Whether Better Auth automatically sends the verification email on signup.
120
139
  *
@@ -22,6 +22,12 @@ export const starterThemeConfig: ThemeConfig = {
22
22
  // ==========================================
23
23
  plugins: [],
24
24
 
25
+ // ==========================================
26
+ // THEME MODE (Optional)
27
+ // ==========================================
28
+ // defaultMode: 'system', // 'light' | 'dark' | 'system'
29
+ // forcedThemeRoutes: { '/login': 'light' }, // force a theme on route prefixes (longest wins)
30
+
25
31
  // ==========================================
26
32
  // STYLES CONFIGURATION
27
33
  // ==========================================
@@ -200,8 +200,12 @@
200
200
  /* =============================================
201
201
  BASE STYLES
202
202
  ============================================= */
203
- * {
204
- border-color: var(--border);
203
+ /* Must live in @layer base: un-layered rules beat every Tailwind v4 utility
204
+ (border-primary, border-destructive, ...) regardless of specificity. */
205
+ @layer base {
206
+ * {
207
+ border-color: var(--border);
208
+ }
205
209
  }
206
210
 
207
211
  body {
@@ -64,9 +64,48 @@ function isPublicPath(pathname: string): boolean {
64
64
  )
65
65
  }
66
66
 
67
+ /**
68
+ * Identity headers this proxy injects for downstream server code.
69
+ *
70
+ * SECURITY (#87): these headers are trusted by RSC layouts and permission
71
+ * checks, so an inbound value must NEVER reach the app. They are stripped from
72
+ * every request before any path branching and re-added only from the verified
73
+ * session. Gating the strip on protected prefixes is not enough: Next.js
74
+ * dispatches Server Actions by the `Next-Action` header, not by the URL, so an
75
+ * action can be POSTed to a public path with a forged `x-user-id`.
76
+ */
77
+ const TRUSTED_IDENTITY_HEADERS = ['x-user-id', 'x-user-email', 'x-pathname'] as const
78
+
79
+ /**
80
+ * Build the request headers forwarded to the app: inbound copy minus every
81
+ * trusted identity header, plus the real pathname.
82
+ */
83
+ function sanitizeRequestHeaders(request: NextRequest): Headers {
84
+ const headers = new Headers(request.headers)
85
+ for (const name of TRUSTED_IDENTITY_HEADERS) {
86
+ headers.delete(name)
87
+ }
88
+ headers.set('x-pathname', request.nextUrl.pathname)
89
+ return headers
90
+ }
91
+
92
+ /**
93
+ * Continue to the app with the (sanitized) request headers.
94
+ * Every pass-through in this proxy MUST go through here so the strip applies
95
+ * to public paths, /api/v1 and unmatched routes alike.
96
+ */
97
+ function passThrough(requestHeaders: Headers): NextResponse {
98
+ return NextResponse.next({
99
+ request: { headers: requestHeaders },
100
+ })
101
+ }
102
+
67
103
  export async function proxy(request: NextRequest) {
68
104
  const { pathname } = request.nextUrl
69
105
 
106
+ // 0. Strip forgeable identity headers before ANY branching (see #87)
107
+ const requestHeaders = sanitizeRequestHeaders(request)
108
+
70
109
  // 1. Check for theme middleware override
71
110
  const activeTheme = process.env.NEXT_PUBLIC_ACTIVE_THEME
72
111
  if (activeTheme && hasThemeMiddleware(activeTheme)) {
@@ -114,17 +153,17 @@ export async function proxy(request: NextRequest) {
114
153
  return NextResponse.redirect(loginUrl)
115
154
  }
116
155
  }
117
- return NextResponse.next()
156
+ return passThrough(requestHeaders)
118
157
  }
119
158
 
120
159
  // 4. Allow public paths
121
160
  if (isPublicPath(pathname)) {
122
- return NextResponse.next()
161
+ return passThrough(requestHeaders)
123
162
  }
124
163
 
125
164
  // 5. API v1 routes handle their own dual authentication
126
165
  if (pathname.startsWith('/api/v1')) {
127
- return NextResponse.next()
166
+ return passThrough(requestHeaders)
128
167
  }
129
168
 
130
169
  // 6. Protected routes - require authentication and inject user headers
@@ -162,20 +201,17 @@ export async function proxy(request: NextRequest) {
162
201
  }
163
202
  }
164
203
 
165
- // Inject user headers for downstream use
204
+ // Inject user headers for downstream use, ONLY from the verified session
205
+ // (requestHeaders already had any inbound values stripped).
166
206
  // IMPORTANT: EntityPermissionLayout depends on these headers
167
- const requestHeaders = new Headers(request.headers)
168
207
  if (session.user?.id) {
169
208
  requestHeaders.set('x-user-id', session.user.id)
170
209
  }
171
210
  if (session.user?.email) {
172
211
  requestHeaders.set('x-user-email', session.user.email)
173
212
  }
174
- requestHeaders.set('x-pathname', pathname)
175
213
 
176
- return NextResponse.next({
177
- request: { headers: requestHeaders },
178
- })
214
+ return passThrough(requestHeaders)
179
215
  } catch (error) {
180
216
  console.error('Proxy error:', error)
181
217
  const loginUrl = new URL('/login', request.url)
@@ -184,7 +220,7 @@ export async function proxy(request: NextRequest) {
184
220
  }
185
221
  }
186
222
 
187
- return NextResponse.next()
223
+ return passThrough(requestHeaders)
188
224
  }
189
225
 
190
226
  export const config = {
@@ -56,6 +56,10 @@
56
56
  ],
57
57
  "exclude": [
58
58
  "node_modules",
59
+ // PPR template variants (layout.ppr.tsx) are copied over layout.tsx when a project
60
+ // enables cacheComponents; in place they import registry exports that only exist
61
+ // in PPR mode, so they are never type-checked as-is.
62
+ "**/*.ppr.tsx",
59
63
  "**/*.test.ts",
60
64
  "**/*.test.tsx",
61
65
  "**/*.spec.ts",
@@ -36,3 +36,26 @@ export const TEAM_PERMISSIONS_BY_ROLE: Record<string, string[]> = {
36
36
  member: ['team.view', 'team.members.view'],
37
37
  viewer: ['team.view'],
38
38
  }
39
+
40
+ // Entity permissions used by service tests (mirrors the default theme's customers entity)
41
+ const ENTITY_PERMISSIONS_BY_ROLE: Record<string, string[]> = {
42
+ owner: ['customers.create', 'customers.read', 'customers.list', 'customers.update', 'customers.delete'],
43
+ admin: ['customers.create', 'customers.read', 'customers.list', 'customers.update'],
44
+ editor: ['customers.read', 'customers.list'],
45
+ member: ['customers.read', 'customers.list'],
46
+ viewer: [],
47
+ }
48
+
49
+ export const ROLE_PERMISSIONS_ARRAY: Record<string, string[]> = Object.fromEntries(
50
+ AVAILABLE_ROLES.map((role) => [
51
+ role,
52
+ [...(TEAM_PERMISSIONS_BY_ROLE[role] ?? []), ...(ENTITY_PERMISSIONS_BY_ROLE[role] ?? [])],
53
+ ])
54
+ )
55
+
56
+ export const PERMISSIONS_BY_ROLE: Record<string, Set<string>> = Object.fromEntries(
57
+ Object.entries(ROLE_PERMISSIONS_ARRAY).map(([role, perms]) => [role, new Set(perms)])
58
+ )
59
+
60
+ export const ALL_PERMISSIONS: string[] = Array.from(new Set(Object.values(ROLE_PERMISSIONS_ARRAY).flat()))
61
+ export const ALL_PERMISSIONS_SET: Set<string> = new Set(ALL_PERMISSIONS)
@@ -9,6 +9,14 @@ class MockNextRequest {
9
9
  this.method = options.method || 'GET'
10
10
  this.headers = new Map(Object.entries(options.headers || {}))
11
11
  this._body = options.body
12
+ // Minimal nextUrl (pathname/origin/searchParams/clone) for proxy/middleware tests
13
+ try {
14
+ const parsed = new URL(url, 'http://localhost')
15
+ parsed.clone = () => new URL(parsed.toString())
16
+ this.nextUrl = parsed
17
+ } catch {
18
+ this.nextUrl = undefined
19
+ }
12
20
  }
13
21
 
14
22
  async json() {
@@ -48,6 +56,22 @@ class MockNextResponse {
48
56
  headers: { 'Content-Type': 'application/json', ...options.headers }
49
57
  })
50
58
  }
59
+
60
+ // Pass-through response. Keeps the forwarded request headers (if any) so
61
+ // middleware/proxy tests can assert what reaches the app.
62
+ static next(init = {}) {
63
+ const response = new MockNextResponse(null, { status: 200, headers: init.headers })
64
+ response.type = 'next'
65
+ response.requestHeaders = init.request && init.request.headers ? init.request.headers : null
66
+ return response
67
+ }
68
+
69
+ static redirect(url, status = 307) {
70
+ const response = new MockNextResponse(null, { status, headers: { Location: String(url) } })
71
+ response.type = 'redirect'
72
+ response.redirectUrl = String(url)
73
+ return response
74
+ }
51
75
  }
52
76
 
53
77
  module.exports = {