@nextsparkjs/core 0.1.0-beta.184 → 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 (420) 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 +103 -25
  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/hooks/useValidationSchemas.d.ts +7 -7
  77. package/dist/lib/api/api-error.d.ts +3 -1
  78. package/dist/lib/api/api-error.d.ts.map +1 -1
  79. package/dist/lib/api/api-error.js +4 -2
  80. package/dist/lib/api/auth/dual-auth.d.ts +45 -3
  81. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  82. package/dist/lib/api/auth/dual-auth.js +42 -7
  83. package/dist/lib/api/auth/permissions.d.ts +7 -2
  84. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  85. package/dist/lib/api/auth/permissions.js +2 -2
  86. package/dist/lib/api/auth/scope-policy.d.ts +71 -0
  87. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
  88. package/dist/lib/api/auth/scope-policy.js +53 -0
  89. package/dist/lib/api/auth.d.ts +35 -3
  90. package/dist/lib/api/auth.d.ts.map +1 -1
  91. package/dist/lib/api/auth.js +54 -23
  92. package/dist/lib/api/entity/audit-log.d.ts +35 -0
  93. package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
  94. package/dist/lib/api/entity/audit-log.js +35 -0
  95. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  96. package/dist/lib/api/entity/generic-handler.js +333 -155
  97. package/dist/lib/api/helpers.d.ts +29 -4
  98. package/dist/lib/api/helpers.d.ts.map +1 -1
  99. package/dist/lib/api/helpers.js +30 -6
  100. package/dist/lib/api/keys.d.ts +16 -0
  101. package/dist/lib/api/keys.d.ts.map +1 -1
  102. package/dist/lib/api/keys.js +19 -1
  103. package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
  104. package/dist/lib/api/routes/patterns-usages.js +3 -11
  105. package/dist/lib/auth/auth-methods.d.ts +52 -0
  106. package/dist/lib/auth/auth-methods.d.ts.map +1 -0
  107. package/dist/lib/auth/auth-methods.js +63 -0
  108. package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
  109. package/dist/lib/auth/registration-helpers.d.ts +9 -0
  110. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  111. package/dist/lib/auth/registration-helpers.js +8 -2
  112. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
  113. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
  114. package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
  115. package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
  116. package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
  117. package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
  118. package/dist/lib/auth/security-notifications/index.d.ts +20 -0
  119. package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
  120. package/dist/lib/auth/security-notifications/index.js +22 -0
  121. package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
  122. package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
  123. package/dist/lib/auth/security-notifications/registry.js +26 -0
  124. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
  125. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
  126. package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
  127. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
  128. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
  129. package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
  130. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
  131. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
  132. package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
  133. package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
  134. package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
  135. package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
  136. package/dist/lib/auth/security-notifications/types.d.ts +74 -0
  137. package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
  138. package/dist/lib/auth/security-notifications/types.js +0 -0
  139. package/dist/lib/auth/session-config.d.ts +37 -0
  140. package/dist/lib/auth/session-config.d.ts.map +1 -0
  141. package/dist/lib/auth/session-config.js +59 -0
  142. package/dist/lib/auth/session-refresh.d.ts +46 -0
  143. package/dist/lib/auth/session-refresh.d.ts.map +1 -0
  144. package/dist/lib/auth/session-refresh.js +11 -0
  145. package/dist/lib/auth-client.d.ts +1489 -3445
  146. package/dist/lib/auth-client.d.ts.map +1 -1
  147. package/dist/lib/auth-client.js +4 -0
  148. package/dist/lib/auth.d.ts +1 -3057
  149. package/dist/lib/auth.d.ts.map +1 -1
  150. package/dist/lib/auth.js +21 -8
  151. package/dist/lib/billing/gateways/factory.d.ts +8 -3
  152. package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
  153. package/dist/lib/billing/gateways/factory.js +37 -23
  154. package/dist/lib/billing/schema.d.ts +1 -1
  155. package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
  156. package/dist/lib/billing/stripe-webhook.js +2 -1
  157. package/dist/lib/config/app.config.d.ts.map +1 -1
  158. package/dist/lib/config/app.config.js +71 -0
  159. package/dist/lib/config/config-sync.d.ts +1 -4
  160. package/dist/lib/config/config-sync.d.ts.map +1 -1
  161. package/dist/lib/config/config-sync.js +6 -2
  162. package/dist/lib/config/types.d.ts +130 -0
  163. package/dist/lib/config/types.d.ts.map +1 -1
  164. package/dist/lib/entities/entity-hooks.d.ts +9 -1
  165. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  166. package/dist/lib/entities/entity-hooks.js +77 -4
  167. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  168. package/dist/lib/entities/schema-generator.js +20 -4
  169. package/dist/lib/entities/serialization.d.ts +2 -2
  170. package/dist/lib/entities/serialization.d.ts.map +1 -1
  171. package/dist/lib/entities/serialization.js +3 -2
  172. package/dist/lib/entities/types.d.ts +8 -0
  173. package/dist/lib/entities/types.d.ts.map +1 -1
  174. package/dist/lib/locale.d.ts.map +1 -1
  175. package/dist/lib/locale.js +4 -1
  176. package/dist/lib/mcp/audit.d.ts +21 -0
  177. package/dist/lib/mcp/audit.d.ts.map +1 -0
  178. package/dist/lib/mcp/audit.js +48 -0
  179. package/dist/lib/mcp/engine.d.ts +28 -0
  180. package/dist/lib/mcp/engine.d.ts.map +1 -0
  181. package/dist/lib/mcp/engine.js +158 -0
  182. package/dist/lib/mcp/errors.d.ts +16 -0
  183. package/dist/lib/mcp/errors.d.ts.map +1 -0
  184. package/dist/lib/mcp/errors.js +66 -0
  185. package/dist/lib/mcp/executor.d.ts +15 -0
  186. package/dist/lib/mcp/executor.d.ts.map +1 -0
  187. package/dist/lib/mcp/executor.js +63 -0
  188. package/dist/lib/mcp/index.d.ts +9 -0
  189. package/dist/lib/mcp/index.d.ts.map +1 -0
  190. package/dist/lib/mcp/index.js +32 -0
  191. package/dist/lib/mcp/schema-builder.d.ts +45 -0
  192. package/dist/lib/mcp/schema-builder.d.ts.map +1 -0
  193. package/dist/lib/mcp/schema-builder.js +212 -0
  194. package/dist/lib/mcp/tool-generator.d.ts +31 -0
  195. package/dist/lib/mcp/tool-generator.d.ts.map +1 -0
  196. package/dist/lib/mcp/tool-generator.js +271 -0
  197. package/dist/lib/mcp/transport.d.ts +31 -0
  198. package/dist/lib/mcp/transport.d.ts.map +1 -0
  199. package/dist/lib/mcp/transport.js +58 -0
  200. package/dist/lib/mcp/types.d.ts +184 -0
  201. package/dist/lib/mcp/types.d.ts.map +1 -0
  202. package/dist/lib/mcp/types.js +11 -0
  203. package/dist/lib/plugins/hook-system.d.ts +13 -0
  204. package/dist/lib/plugins/hook-system.d.ts.map +1 -1
  205. package/dist/lib/plugins/hook-system.js +32 -2
  206. package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
  207. package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
  208. package/dist/lib/scheduled-actions/handlers/index.js +3 -1
  209. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
  210. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
  211. package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
  212. package/dist/lib/selectors/core-selectors.d.ts +12 -0
  213. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  214. package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
  215. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  216. package/dist/lib/selectors/domains/auth.selectors.js +14 -1
  217. package/dist/lib/selectors/selectors.d.ts +24 -0
  218. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  219. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  220. package/dist/lib/services/generic-entity.service.js +16 -32
  221. package/dist/lib/services/membership.service.d.ts +10 -2
  222. package/dist/lib/services/membership.service.d.ts.map +1 -1
  223. package/dist/lib/services/membership.service.js +37 -16
  224. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  225. package/dist/lib/services/subscription.service.js +2 -1
  226. package/dist/lib/services/team-member.service.d.ts +15 -3
  227. package/dist/lib/services/team-member.service.d.ts.map +1 -1
  228. package/dist/lib/services/team-member.service.js +27 -6
  229. package/dist/lib/services/team.service.d.ts.map +1 -1
  230. package/dist/lib/teams/permissions.d.ts +3 -1
  231. package/dist/lib/teams/permissions.d.ts.map +1 -1
  232. package/dist/lib/teams/permissions.js +6 -0
  233. package/dist/lib/teams/schema.d.ts.map +1 -1
  234. package/dist/lib/teams/schema.js +3 -0
  235. package/dist/lib/theme/forced-theme.d.ts +34 -0
  236. package/dist/lib/theme/forced-theme.d.ts.map +1 -0
  237. package/dist/lib/theme/forced-theme.js +27 -0
  238. package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
  239. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  240. package/dist/lib/theme/get-default-theme-mode.js +5 -3
  241. package/dist/messages/de/auth.json +25 -2
  242. package/dist/messages/de/index.d.ts +81 -0
  243. package/dist/messages/de/index.d.ts.map +1 -1
  244. package/dist/messages/de/index.js +2 -0
  245. package/dist/messages/de/index.ts +2 -0
  246. package/dist/messages/en/auth.json +25 -2
  247. package/dist/messages/en/index.d.ts +81 -0
  248. package/dist/messages/en/index.d.ts.map +1 -1
  249. package/dist/messages/en/index.js +2 -0
  250. package/dist/messages/en/index.ts +2 -0
  251. package/dist/messages/es/auth.json +25 -2
  252. package/dist/messages/es/index.d.ts +81 -0
  253. package/dist/messages/es/index.d.ts.map +1 -1
  254. package/dist/messages/es/index.js +2 -0
  255. package/dist/messages/es/index.ts +2 -0
  256. package/dist/messages/fr/auth.json +25 -2
  257. package/dist/messages/fr/index.d.ts +81 -0
  258. package/dist/messages/fr/index.d.ts.map +1 -1
  259. package/dist/messages/fr/index.js +2 -0
  260. package/dist/messages/fr/index.ts +2 -0
  261. package/dist/messages/it/auth.json +25 -2
  262. package/dist/messages/it/index.d.ts +81 -0
  263. package/dist/messages/it/index.d.ts.map +1 -1
  264. package/dist/messages/it/index.js +2 -0
  265. package/dist/messages/it/index.ts +2 -0
  266. package/dist/messages/pt/auth.json +25 -2
  267. package/dist/messages/pt/index.d.ts +81 -0
  268. package/dist/messages/pt/index.d.ts.map +1 -1
  269. package/dist/messages/pt/index.js +2 -0
  270. package/dist/messages/pt/index.ts +2 -0
  271. package/dist/migrations/005_api_audit_log.sql +2 -1
  272. package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  273. package/dist/migrations/026_login_events.sql +91 -0
  274. package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  275. package/dist/providers/DashboardProviders.d.ts +12 -3
  276. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  277. package/dist/providers/DashboardProviders.js +6 -2
  278. package/dist/providers/theme-provider.d.ts +19 -2
  279. package/dist/providers/theme-provider.d.ts.map +1 -1
  280. package/dist/providers/theme-provider.js +6 -2
  281. package/dist/templates/app/(auth)/signup/page.tsx +8 -0
  282. package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
  283. package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
  284. package/dist/templates/app/api/health/route.ts +4 -2
  285. package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
  286. package/dist/templates/app/api/mcp/route.ts +176 -0
  287. package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  288. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  289. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  290. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  291. package/dist/templates/app/api/v1/api-keys/route.ts +35 -45
  292. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  293. package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
  294. package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  295. package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
  296. package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
  297. package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
  298. package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
  299. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  300. package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  301. package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
  302. package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
  303. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  304. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  305. package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
  306. package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
  307. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  308. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  309. package/dist/templates/app/api/v1/media/route.ts +5 -9
  310. package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
  311. package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
  312. package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  313. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  314. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  315. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  316. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  317. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  318. package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
  319. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  320. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  321. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  322. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  323. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  324. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  325. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  326. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  327. package/dist/templates/app/api/v1/teams/route.ts +33 -27
  328. package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
  329. package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  330. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  331. package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
  332. package/dist/templates/app/api/v1/users/route.ts +21 -25
  333. package/dist/templates/app/dashboard/layout.tsx +2 -6
  334. package/dist/templates/app/layout.ppr.tsx +9 -14
  335. package/dist/templates/app/layout.tsx +39 -19
  336. package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
  337. package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
  338. package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
  339. package/dist/templates/proxy.ts +46 -10
  340. package/dist/templates/tsconfig.json +4 -0
  341. package/dist/types/theme.d.ts +9 -0
  342. package/dist/types/theme.d.ts.map +1 -1
  343. package/migrations/005_api_audit_log.sql +2 -1
  344. package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  345. package/migrations/026_login_events.sql +91 -0
  346. package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  347. package/package.json +20 -12
  348. package/scripts/build/registry/__tests__/mcp-registry.test.mjs +64 -0
  349. package/scripts/build/registry/__tests__/scope-registry.test.mjs +106 -0
  350. package/scripts/build/registry/discovery/mcp-overrides.mjs +72 -0
  351. package/scripts/build/registry/discovery/mcp-overrides.test.mjs +86 -0
  352. package/scripts/build/registry/generators/mcp-registry.mjs +61 -0
  353. package/scripts/build/registry/generators/scope-registry.mjs +140 -88
  354. package/scripts/build/registry.mjs +8 -4
  355. package/templates/app/(auth)/signup/page.tsx +8 -0
  356. package/templates/app/(public)/[...slug]/page.tsx +9 -14
  357. package/templates/app/api/auth/[...all]/route.ts +23 -1
  358. package/templates/app/api/health/route.ts +4 -2
  359. package/templates/app/api/internal/user-metadata/route.ts +5 -3
  360. package/templates/app/api/mcp/route.ts +176 -0
  361. package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  362. package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  363. package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  364. package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  365. package/templates/app/api/v1/api-keys/route.ts +35 -45
  366. package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  367. package/templates/app/api/v1/billing/cancel/route.ts +7 -6
  368. package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  369. package/templates/app/api/v1/billing/check-action/route.ts +6 -4
  370. package/templates/app/api/v1/billing/checkout/route.ts +7 -5
  371. package/templates/app/api/v1/billing/plans/route.ts +8 -12
  372. package/templates/app/api/v1/billing/portal/route.ts +7 -5
  373. package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  374. package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  375. package/templates/app/api/v1/devtools/features/route.ts +5 -4
  376. package/templates/app/api/v1/devtools/flows/route.ts +5 -4
  377. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  378. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  379. package/templates/app/api/v1/devtools/testing/route.ts +5 -4
  380. package/templates/app/api/v1/media/[id]/route.ts +13 -25
  381. package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  382. package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  383. package/templates/app/api/v1/media/route.ts +5 -9
  384. package/templates/app/api/v1/media/upload/route.ts +9 -21
  385. package/templates/app/api/v1/media-tags/route.ts +9 -13
  386. package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  387. package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  388. package/templates/app/api/v1/post-categories/route.ts +5 -4
  389. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  390. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  391. package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  392. package/templates/app/api/v1/team-invitations/route.ts +9 -11
  393. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  394. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  395. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  396. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  397. package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  398. package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  399. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  400. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  401. package/templates/app/api/v1/teams/route.ts +33 -27
  402. package/templates/app/api/v1/teams/switch/route.ts +11 -13
  403. package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  404. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  405. package/templates/app/api/v1/users/[id]/route.ts +35 -68
  406. package/templates/app/api/v1/users/route.ts +21 -25
  407. package/templates/app/dashboard/layout.tsx +2 -6
  408. package/templates/app/layout.ppr.tsx +9 -14
  409. package/templates/app/layout.tsx +39 -19
  410. package/templates/contents/themes/starter/config/app.config.ts +19 -0
  411. package/templates/contents/themes/starter/config/theme.config.ts +6 -0
  412. package/templates/contents/themes/starter/styles/globals.css +6 -2
  413. package/templates/proxy.ts +46 -10
  414. package/templates/tsconfig.json +4 -0
  415. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
  416. package/tests/jest/__mocks__/@nextsparkjs/registries/scope-registry.ts +56 -6
  417. package/tests/jest/__mocks__/next-server.js +24 -0
  418. package/dist/styles/classes.json +0 -1093
  419. package/dist/styles/ui.css +0 -2
  420. package/scripts/build/build-ui-css.mjs +0 -351
@@ -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",
@@ -17,6 +17,15 @@ export interface ThemeConfig {
17
17
  enabled?: boolean;
18
18
  /** Default theme mode - controls next-themes behavior */
19
19
  defaultMode?: 'light' | 'dark' | 'system';
20
+ /**
21
+ * Force a theme on specific routes, ignoring user and system preference.
22
+ * Keys are route prefixes matched at segment boundaries (the longest match
23
+ * wins); values are the theme to force. The root ThemeProvider passes the
24
+ * resolved value to next-themes' `forcedTheme`, so the route renders with
25
+ * that theme from the first paint and on client-side navigations.
26
+ * @example { '/login': 'light', '/signup': 'light', '/embed': 'dark' }
27
+ */
28
+ forcedThemeRoutes?: Record<string, 'light' | 'dark'>;
20
29
  dependencies?: string[];
21
30
  plugins?: string[];
22
31
  styles?: {
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/types/theme.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/types/theme.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,8 @@
3
3
 
4
4
  CREATE TABLE IF NOT EXISTS "api_audit_log" (
5
5
  id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
6
- "apiKeyId" TEXT NOT NULL REFERENCES public."api_key"(id) ON DELETE CASCADE,
6
+ -- NULL for session-authenticated requests (see 025_api_audit_log_nullable_api_key.sql)
7
+ "apiKeyId" TEXT REFERENCES public."api_key"(id) ON DELETE CASCADE,
7
8
  "userId" TEXT NOT NULL REFERENCES public."users"(id) ON DELETE CASCADE,
8
9
  endpoint TEXT NOT NULL,
9
10
  method TEXT NOT NULL,
@@ -0,0 +1,12 @@
1
+ -- Migration: 025_api_audit_log_nullable_api_key.sql
2
+ -- Description: Allow api_audit_log rows without an API key (#105)
3
+ --
4
+ -- The generic entity routes (/api/v1/[entity], /api/v1/[entity]/[id]) now
5
+ -- write an audit row for every authenticated request, whether it was
6
+ -- authenticated with an API key or with a browser session. Session requests
7
+ -- have no key, so "apiKeyId" must accept NULL. The foreign key stays as-is:
8
+ -- a FK only constrains non-null values.
9
+ --
10
+ -- Idempotent: DROP NOT NULL on an already-nullable column is a no-op.
11
+
12
+ ALTER TABLE "api_audit_log" ALTER COLUMN "apiKeyId" DROP NOT NULL;
@@ -0,0 +1,91 @@
1
+ -- Migration: 026_login_events.sql
2
+ -- Description: login_events — per-user device/login history for built-in
3
+ -- security email notifications (new-device login alerts).
4
+ -- Date: 2026-09-01
5
+ -- Issue: #75
6
+ --
7
+ -- Why 026 (and not 022 as the issue text proposed, nor 025):
8
+ -- The issue was written when the migration sequence ended earlier. By the
9
+ -- time this branch was authored the sequence already went through
10
+ -- 024_teams_soft_delete.sql (022 and 023 are taken by rls_runtime_roles and
11
+ -- the invitations index), so this migration was originally 025. It was
12
+ -- renumbered to 026 when merging to main, because 025 had in the meantime
13
+ -- been taken by 025_api_audit_log_nullable_api_key.sql (issue #105).
14
+ --
15
+ -- What this backs:
16
+ -- The security-notifications dispatcher records one row per successful sign-in
17
+ -- with a device fingerprint = sha256(normalizedUA | ip/24). A sign-in whose
18
+ -- (userId, deviceFingerprint) pair has never been seen is flagged isNew=true
19
+ -- and triggers a "new device" email via the auth:security-notification
20
+ -- scheduled action. Known fingerprints are still logged (isNew=false) but send
21
+ -- no email. The table is also the substrate for a future login-history UI
22
+ -- (out of scope here — see issue #75 "Out of scope").
23
+ --
24
+ -- Append-only: rows are never updated, so there is no "updatedAt"/set_updated_at
25
+ -- trigger (unlike mutable entity tables). city/country are nullable for a future
26
+ -- geolocation enhancement (also out of scope).
27
+
28
+ -- ============================================
29
+ -- TABLE
30
+ -- ============================================
31
+ DROP TABLE IF EXISTS public."login_events" CASCADE;
32
+
33
+ CREATE TABLE IF NOT EXISTS public."login_events" (
34
+ -- Primary Key (TEXT not UUID — Better Auth convention)
35
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
36
+
37
+ -- Relational Fields
38
+ "userId" TEXT NOT NULL REFERENCES public."users"(id) ON DELETE CASCADE,
39
+
40
+ -- Entity-specific fields
41
+ "ipAddress" TEXT,
42
+ "userAgent" TEXT,
43
+ "deviceFingerprint" TEXT NOT NULL,
44
+ city TEXT,
45
+ country TEXT,
46
+ "isNew" BOOLEAN NOT NULL DEFAULT false,
47
+
48
+ -- System fields (append-only: created only, never updated)
49
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now()
50
+ );
51
+
52
+ COMMENT ON TABLE public."login_events" IS 'Per-user login/device history backing built-in new-device security email notifications (issue #75).';
53
+ COMMENT ON COLUMN public."login_events"."userId" IS 'Owner of the login event.';
54
+ COMMENT ON COLUMN public."login_events"."ipAddress" IS 'Best-effort client IP captured at sign-in (may be a proxy address).';
55
+ COMMENT ON COLUMN public."login_events"."userAgent" IS 'Raw User-Agent header captured at sign-in.';
56
+ COMMENT ON COLUMN public."login_events"."deviceFingerprint" IS 'sha256(normalizedUA | ip/24). Stable across patch-version UA churn and within a /24 network so routine re-logins from the same device do not re-alert.';
57
+ COMMENT ON COLUMN public."login_events".city IS 'Reserved for future geolocation enrichment (nullable, unused today).';
58
+ COMMENT ON COLUMN public."login_events".country IS 'Reserved for future geolocation enrichment (nullable, unused today).';
59
+ COMMENT ON COLUMN public."login_events"."isNew" IS 'True when this (userId, deviceFingerprint) pair had not been seen before this row → a new-device email was queued.';
60
+
61
+ -- ============================================
62
+ -- INDEXES
63
+ -- ============================================
64
+ -- Most-recent-first history lookups per user (login-history UI, TTL checks).
65
+ CREATE INDEX IF NOT EXISTS idx_login_events_user_created
66
+ ON public."login_events" ("userId", "createdAt" DESC);
67
+
68
+ -- "Have we seen this fingerprint for this user?" — the new-device decision.
69
+ CREATE INDEX IF NOT EXISTS idx_login_events_user_fingerprint
70
+ ON public."login_events" ("userId", "deviceFingerprint");
71
+
72
+ -- ============================================
73
+ -- RLS
74
+ -- ============================================
75
+ -- A user may read ONLY their own login events; superadmin/developer bypass via
76
+ -- can_bypass_rls() (defined in 001). Writes are service-only: the dispatcher
77
+ -- inserts with no user GUC (service/bypass pool), matching how session/account
78
+ -- rows are written in 002. Mirrors the "self read + service write" pattern.
79
+ ALTER TABLE public."login_events" ENABLE ROW LEVEL SECURITY;
80
+
81
+ DROP POLICY IF EXISTS "Login events self read" ON public."login_events";
82
+ DROP POLICY IF EXISTS "Login events service write" ON public."login_events";
83
+
84
+ CREATE POLICY "Login events self read" ON public."login_events"
85
+ FOR SELECT TO authenticated
86
+ USING (public.can_bypass_rls() OR "userId" = public.get_auth_user_id());
87
+
88
+ CREATE POLICY "Login events service write" ON public."login_events"
89
+ FOR ALL TO authenticated
90
+ USING (public.can_bypass_rls())
91
+ WITH CHECK (public.can_bypass_rls());
@@ -0,0 +1,197 @@
1
+ -- Migration: 027_rls_bypass_developer_and_system_admin_team.sql
2
+ -- Description: Resolve the System Admin Team by a metadata flag instead of a
3
+ -- hardcoded team id, auto-enroll/unenroll developer/superadmin
4
+ -- users via a trigger, and fix 4 RLS policies that hardcoded
5
+ -- their own role check instead of calling can_bypass_rls().
6
+ -- Date: 2026-09-03
7
+ -- Issues: #108, #102
8
+ --
9
+ -- Why both issues in one migration: #102's fix (make a policy call
10
+ -- can_bypass_rls()) only makes 'developer' bypass actually reachable in a
11
+ -- real (non-seeded) deployment once #108's enrollment mechanism exists —
12
+ -- fixing one without the other leaves the combination still broken.
13
+ --
14
+ -- #108 — System Admin Team enrollment:
15
+ -- can_bypass_rls() grants 'developer' a conditional bypass: membership in
16
+ -- the System Admin Team. Nothing ever enrolled anyone into it outside of
17
+ -- 090_sample_data.sql's explicit INSERTs, and the team id was a literal
18
+ -- ('team-nextspark-001') duplicated in this function AND in
19
+ -- dual-auth.ts's SYSTEM_ADMIN_TEAM_ID constant. This migration:
20
+ -- 1. Resolves the System Admin Team by `teams.metadata.systemAdmin =
21
+ -- true` instead of a literal id — 090_sample_data.sql's team already
22
+ -- carries this flag, so seeded deployments keep working unchanged;
23
+ -- a real deployment flags its own team, no code change needed.
24
+ -- 2. Adds a trigger on users(role) that auto-enrolls a user into that
25
+ -- team (as 'owner' for superadmin, 'admin' for developer) when their
26
+ -- role becomes bypass-eligible, and removes the membership when it
27
+ -- stops being eligible. Framework-level (a Postgres trigger fires
28
+ -- regardless of which application code performed the UPDATE) rather
29
+ -- than an app-level hook, because no Server Action in core actually
30
+ -- assigns users.role today — the only code that does lives in
31
+ -- apps/dev, reimplementable by any consumer of this framework.
32
+ -- 3. Backfills existing developer/superadmin users into the team, for
33
+ -- deployments that already have such users predating this trigger.
34
+ -- Deliberately NOT included here (separate, larger concern — see #108):
35
+ -- POST /api/v1/users creating a "half" user (no account/team/subscription
36
+ -- row). That depends on patterns from the not-yet-approved #98 MCP work.
37
+ --
38
+ -- #102 (+ 3 more found while auditing the same pattern) — RLS policies that
39
+ -- hardcoded `role IN ('admin','superadmin')` or `role = 'superadmin'`
40
+ -- instead of calling can_bypass_rls() have drifted from it in both
41
+ -- directions. 'admin' is NOT a valid users.role value (see check_users_role
42
+ -- in 002_auth_tables.sql: only 'member' | 'superadmin' | 'developer'), so
43
+ -- that branch never matched any real row — removing it changes nothing for
44
+ -- existing behavior. All four also silently excluded 'developer', who
45
+ -- can_bypass_rls() already grants conditional bypass everywhere else.
46
+ -- Switching to can_bypass_rls() is a pure widening for 'developer' and an
47
+ -- exact match for 'superadmin' — no regression, only fixes a gap:
48
+ -- - users_metas_owner_or_admin_all (003_user_metas.sql)
49
+ -- - "API key owner or admin can do all" (004_api_key.sql)
50
+ -- - "API logs superadmin can select" (005_api_audit_log.sql)
51
+ -- - "Plans superadmin write" (012_billing_plans.sql)
52
+
53
+ -- ============================================
54
+ -- #108.1 — can_bypass_rls(): resolve System Admin Team by metadata flag
55
+ -- ============================================
56
+
57
+ CREATE OR REPLACE FUNCTION public.can_bypass_rls()
58
+ RETURNS BOOLEAN AS $$
59
+ DECLARE
60
+ current_user_id TEXT;
61
+ user_role TEXT;
62
+ admin_team_id TEXT;
63
+ is_system_admin_member BOOLEAN;
64
+ BEGIN
65
+ current_user_id := public.get_auth_user_id();
66
+
67
+ SELECT role INTO user_role
68
+ FROM public."users"
69
+ WHERE id = current_user_id;
70
+
71
+ IF user_role = 'superadmin' THEN
72
+ RETURN TRUE;
73
+ END IF;
74
+
75
+ IF user_role = 'developer' THEN
76
+ -- The System Admin Team is whichever team has metadata.systemAdmin =
77
+ -- true, not a hardcoded id — each deployment can flag its own team.
78
+ SELECT id INTO admin_team_id
79
+ FROM public."teams"
80
+ WHERE (metadata->>'systemAdmin')::boolean IS TRUE
81
+ LIMIT 1;
82
+
83
+ IF admin_team_id IS NULL THEN
84
+ RETURN FALSE;
85
+ END IF;
86
+
87
+ SELECT EXISTS(
88
+ SELECT 1 FROM public."team_members"
89
+ WHERE "userId" = current_user_id
90
+ AND "teamId" = admin_team_id
91
+ ) INTO is_system_admin_member;
92
+
93
+ RETURN is_system_admin_member;
94
+ END IF;
95
+
96
+ RETURN FALSE;
97
+ END;
98
+ $$ LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = public;
99
+
100
+ -- ============================================
101
+ -- #108.2 — auto-enroll/unenroll on role change
102
+ -- ============================================
103
+
104
+ CREATE OR REPLACE FUNCTION public.sync_system_admin_team_membership()
105
+ RETURNS TRIGGER
106
+ LANGUAGE plpgsql
107
+ SECURITY DEFINER
108
+ SET search_path = public
109
+ AS $$
110
+ DECLARE
111
+ admin_team_id TEXT;
112
+ new_team_role TEXT;
113
+ BEGIN
114
+ SELECT id INTO admin_team_id
115
+ FROM public."teams"
116
+ WHERE (metadata->>'systemAdmin')::boolean IS TRUE
117
+ LIMIT 1;
118
+
119
+ -- No team flagged as the System Admin Team in this deployment yet —
120
+ -- nothing to enroll into. Not an error: same as no bypass being possible.
121
+ IF admin_team_id IS NULL THEN
122
+ RETURN NEW;
123
+ END IF;
124
+
125
+ IF NEW.role IN ('developer', 'superadmin') THEN
126
+ new_team_role := CASE WHEN NEW.role = 'superadmin' THEN 'owner' ELSE 'admin' END;
127
+
128
+ INSERT INTO public."team_members" ("teamId", "userId", role)
129
+ VALUES (admin_team_id, NEW.id, new_team_role)
130
+ ON CONFLICT ("teamId", "userId") DO UPDATE SET role = EXCLUDED.role;
131
+
132
+ ELSIF TG_OP = 'UPDATE' AND OLD.role IN ('developer', 'superadmin') THEN
133
+ -- Role changed away from a bypass-eligible tier: revoke membership.
134
+ DELETE FROM public."team_members"
135
+ WHERE "teamId" = admin_team_id AND "userId" = NEW.id;
136
+ END IF;
137
+
138
+ RETURN NEW;
139
+ END;
140
+ $$;
141
+
142
+ DROP TRIGGER IF EXISTS trg_sync_system_admin_team_membership ON public."users";
143
+ CREATE TRIGGER trg_sync_system_admin_team_membership
144
+ AFTER INSERT OR UPDATE OF role ON public."users"
145
+ FOR EACH ROW
146
+ EXECUTE FUNCTION public.sync_system_admin_team_membership();
147
+
148
+ -- ============================================
149
+ -- #108.3 — backfill existing developer/superadmin users
150
+ -- ============================================
151
+
152
+ DO $$
153
+ DECLARE
154
+ admin_team_id TEXT;
155
+ BEGIN
156
+ SELECT id INTO admin_team_id
157
+ FROM public."teams"
158
+ WHERE (metadata->>'systemAdmin')::boolean IS TRUE
159
+ LIMIT 1;
160
+
161
+ IF admin_team_id IS NOT NULL THEN
162
+ INSERT INTO public."team_members" ("teamId", "userId", role)
163
+ SELECT admin_team_id, u.id, CASE WHEN u.role = 'superadmin' THEN 'owner' ELSE 'admin' END
164
+ FROM public."users" u
165
+ WHERE u.role IN ('developer', 'superadmin')
166
+ ON CONFLICT ("teamId", "userId") DO UPDATE SET role = EXCLUDED.role;
167
+ END IF;
168
+ END $$;
169
+
170
+ -- ============================================
171
+ -- #102 + siblings — switch drifted policies to can_bypass_rls()
172
+ -- ============================================
173
+
174
+ DROP POLICY IF EXISTS "users_metas_owner_or_admin_all" ON "users_metas";
175
+ CREATE POLICY "users_metas_owner_or_admin_all" ON "users_metas"
176
+ FOR ALL
177
+ TO authenticated
178
+ USING ("userId" = public.get_auth_user_id() OR public.can_bypass_rls())
179
+ WITH CHECK ("userId" = public.get_auth_user_id() OR public.can_bypass_rls());
180
+
181
+ DROP POLICY IF EXISTS "API key owner or admin can do all" ON "api_key";
182
+ CREATE POLICY "API key owner or admin can do all" ON "api_key"
183
+ FOR ALL TO authenticated
184
+ USING ("userId" = public.get_auth_user_id() OR public.can_bypass_rls())
185
+ WITH CHECK ("userId" = public.get_auth_user_id() OR public.can_bypass_rls());
186
+
187
+ DROP POLICY IF EXISTS "API logs superadmin can select" ON "api_audit_log";
188
+ CREATE POLICY "API logs superadmin can select" ON "api_audit_log"
189
+ FOR SELECT TO authenticated
190
+ USING (public.can_bypass_rls());
191
+
192
+ DROP POLICY IF EXISTS "Plans superadmin write" ON public."plans";
193
+ CREATE POLICY "Plans superadmin write"
194
+ ON public."plans"
195
+ FOR ALL TO authenticated
196
+ USING (public.can_bypass_rls())
197
+ WITH CHECK (public.can_bypass_rls());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextsparkjs/core",
3
- "version": "0.1.0-beta.184",
3
+ "version": "0.1.0-beta.186",
4
4
  "description": "NextSpark - The complete SaaS framework for Next.js",
5
5
  "license": "MIT",
6
6
  "author": "NextSpark <hello@nextspark.dev>",
@@ -50,10 +50,6 @@
50
50
  },
51
51
  "./globals.css": "./globals.css",
52
52
  "./theme-styles.css": "./theme-styles.css",
53
- "./styles/ui.css": {
54
- "style": "./dist/styles/ui.css",
55
- "default": "./dist/styles/ui.css"
56
- },
57
53
  "./styles/utilities.css": "./styles/utilities.css",
58
54
  "./styles/docs.css": "./styles/docs.css",
59
55
  "./lib/middleware": {
@@ -64,6 +60,10 @@
64
60
  "types": "./dist/lib/services/index.d.ts",
65
61
  "import": "./dist/lib/services/index.js"
66
62
  },
63
+ "./lib/mcp": {
64
+ "types": "./dist/lib/mcp/index.d.ts",
65
+ "import": "./dist/lib/mcp/index.js"
66
+ },
67
67
  "./lib/config": {
68
68
  "types": "./dist/lib/config/index.d.ts",
69
69
  "import": "./dist/lib/config/index.js"
@@ -108,6 +108,14 @@
108
108
  "types": "./dist/lib/scheduled-actions/index.d.ts",
109
109
  "import": "./dist/lib/scheduled-actions/index.js"
110
110
  },
111
+ "./lib/scheduled-actions/handlers": {
112
+ "types": "./dist/lib/scheduled-actions/handlers/index.d.ts",
113
+ "import": "./dist/lib/scheduled-actions/handlers/index.js"
114
+ },
115
+ "./lib/auth/security-notifications": {
116
+ "types": "./dist/lib/auth/security-notifications/index.d.ts",
117
+ "import": "./dist/lib/auth/security-notifications/index.js"
118
+ },
111
119
  "./lib/permissions": {
112
120
  "types": "./dist/lib/permissions/index.d.ts",
113
121
  "import": "./dist/lib/permissions/index.js"
@@ -401,8 +409,9 @@
401
409
  "@dnd-kit/core": "^6.3.1",
402
410
  "@dnd-kit/sortable": "^10.0.0",
403
411
  "@dnd-kit/utilities": "^3.2.2",
404
- "@hookform/resolvers": "^5.2.1",
412
+ "@hookform/resolvers": "^5.9.1",
405
413
  "@inquirer/prompts": "^7.2.0",
414
+ "@modelcontextprotocol/sdk": "^1.30.0",
406
415
  "@nextsparkjs/ui": "^0.1.0-beta.2",
407
416
  "@polar-sh/sdk": "^0.43.1",
408
417
  "@radix-ui/react-accordion": "^1.2.12",
@@ -437,7 +446,7 @@
437
446
  "@upstash/ratelimit": "^2.0.3",
438
447
  "@upstash/redis": "^1.20.6",
439
448
  "@vercel/blob": "^2.0.0",
440
- "better-auth": "^1.3.5",
449
+ "better-auth": "~1.6.30",
441
450
  "chalk": "^5.4.1",
442
451
  "jiti": "^2.7.0",
443
452
  "kysely": "0.28.17",
@@ -463,21 +472,20 @@
463
472
  "remark-gfm": "^4.0.1",
464
473
  "remark-rehype": "^11.1.2",
465
474
  "resend": "^5.0.0",
466
- "sharp": "^0.33.5",
475
+ "sharp": "^0.34.3 || ^0.35.3",
467
476
  "shiki": "^3.20.0",
468
477
  "sonner": "^2.0.7",
469
478
  "stripe": "^18.5.0",
470
479
  "tailwind-merge": "^3.3.1",
471
480
  "uuid": "^13.0.0",
472
- "zod": "^4.1.5",
473
- "@nextsparkjs/testing": "0.1.0-beta.184"
481
+ "zod": "^4.3.6",
482
+ "@nextsparkjs/testing": "0.1.0-beta.186"
474
483
  },
475
484
  "scripts": {
476
485
  "postinstall": "node scripts/postinstall.mjs || true",
477
- "build": "tsup && pnpm build:dts && pnpm build:ui-css",
486
+ "build": "tsup && pnpm build:dts",
478
487
  "build:js": "tsup",
479
488
  "build:dts": "tsc -p tsconfig.dts.json || echo '⚠️ DTS generation completed with some type errors (partial coverage)'",
480
- "build:ui-css": "node scripts/build/build-ui-css.mjs",
481
489
  "dev": "tsup --watch",
482
490
  "test": "jest",
483
491
  "test:watch": "jest --watch",
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Tests for the mcp-registry generator.
3
+ *
4
+ * Run: node --test packages/core/scripts/build/registry/__tests__/mcp-registry.test.mjs
5
+ */
6
+
7
+ import { test } from 'node:test'
8
+ import assert from 'node:assert/strict'
9
+
10
+ import { generateMcpRegistry } from '../generators/mcp-registry.mjs'
11
+
12
+ const CONFIG = { outputDir: '/project/.nextspark/registries', isNpmMode: false }
13
+
14
+ test('emits an empty MCP_OVERRIDES map when no overrides were discovered', () => {
15
+ const out = generateMcpRegistry([], CONFIG)
16
+ assert.match(out, /export const MCP_OVERRIDES: Record<string, McpEntityOverride> = \{\s*\}/)
17
+ assert.doesNotMatch(out, /^import .+McpOverride/m)
18
+ })
19
+
20
+ test('emits one literal import + map entry per discovered override', () => {
21
+ const out = generateMcpRegistry(
22
+ [{ slug: 'customers', importPath: '@/contents/themes/default/entities/customers/mcp', themeName: 'default' }],
23
+ CONFIG
24
+ )
25
+ assert.match(out, /import CustomersMcpOverride from '@\/contents\/themes\/default\/entities\/customers\/mcp'/)
26
+ assert.match(out, /'customers': CustomersMcpOverride,/)
27
+ })
28
+
29
+ test('imports the McpEntityOverride type from @/core/lib/mcp in monorepo mode', () => {
30
+ const out = generateMcpRegistry([], CONFIG)
31
+ assert.match(out, /import type \{ McpEntityOverride \} from '@\/core\/lib\/mcp'/)
32
+ })
33
+
34
+ test('converts @/core/lib/mcp to @nextsparkjs/core/lib/mcp in npm mode', () => {
35
+ const out = generateMcpRegistry([], { ...CONFIG, isNpmMode: true })
36
+ assert.match(out, /import type \{ McpEntityOverride \} from '@nextsparkjs\/core\/lib\/mcp'/)
37
+ })
38
+
39
+ test('PascalCases hyphenated/underscored/nested slugs into a valid identifier', () => {
40
+ const out = generateMcpRegistry(
41
+ [
42
+ { slug: 'meal-plans', importPath: '@/contents/themes/default/entities/meal-plans/mcp' },
43
+ { slug: 'pantry_items', importPath: '@/contents/themes/default/entities/pantry_items/mcp' },
44
+ ],
45
+ CONFIG
46
+ )
47
+ assert.match(out, /import MealPlansMcpOverride from/)
48
+ assert.match(out, /import PantryItemsMcpOverride from/)
49
+ assert.match(out, /'meal-plans': MealPlansMcpOverride,/)
50
+ assert.match(out, /'pantry_items': PantryItemsMcpOverride,/)
51
+ })
52
+
53
+ test('handles multiple overrides, one import + entry each, in discovery order', () => {
54
+ const out = generateMcpRegistry(
55
+ [
56
+ { slug: 'customers', importPath: '@/contents/themes/default/entities/customers/mcp' },
57
+ { slug: 'tasks', importPath: '@/contents/themes/default/entities/tasks/mcp' },
58
+ ],
59
+ CONFIG
60
+ )
61
+ const importLines = out.match(/^import \w+McpOverride from .+$/gm) ?? []
62
+ assert.equal(importLines.length, 2)
63
+ assert.match(out, /'customers': CustomersMcpOverride,[\s\S]*'tasks': TasksMcpOverride,/)
64
+ })