@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
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Tests for the scope-registry generator.
3
+ *
4
+ * The role→scope computation no longer happens at generation time (see the
5
+ * rationale comment at the top of `generators/scope-registry.mjs`) — it's
6
+ * emitted as TypeScript that computes `SCOPE_CONFIG.roles` at IMPORT time,
7
+ * from the sibling generated `entity-registry.ts`/`permissions-registry.ts`.
8
+ * So this generator-level test can only assert on the EMITTED source: the
9
+ * correct static imports, the defensive entity-registry guard, the
10
+ * role/permission-derived scope logic, and the untouched flag/restriction
11
+ * literals. The actual `SCOPE_CONFIG.roles` VALUES for a real role are
12
+ * covered by `packages/core/tests/jest/services/scope.service.test.ts`
13
+ * (against a realistic mock) and by manual inspection of the real generated
14
+ * output after `node packages/core/scripts/build/registry.mjs`.
15
+ *
16
+ * Run: node --test packages/core/scripts/build/registry/__tests__/scope-registry.test.mjs
17
+ */
18
+
19
+ import { test } from 'node:test'
20
+ import assert from 'node:assert/strict'
21
+
22
+ import { generateScopeRegistry } from '../generators/scope-registry.mjs'
23
+
24
+ test('imports ENTITY_REGISTRY and AVAILABLE_ROLES/PERMISSIONS_BY_ROLE from sibling generated registries', () => {
25
+ const out = generateScopeRegistry([], {})
26
+ assert.match(out, /import \{ ENTITY_REGISTRY \} from '\.\/entity-registry'/)
27
+ assert.match(out, /import \{ AVAILABLE_ROLES, PERMISSIONS_BY_ROLE \} from '\.\/permissions-registry'/)
28
+ })
29
+
30
+ test('does not reintroduce the old generation-time entities/config computation', () => {
31
+ const out = generateScopeRegistry([{ name: 'ignored', api: { endpoints: { create: true } } }], { some: 'config' })
32
+ // The old bug read entity.api.endpoints/entity.features/entity.fields directly —
33
+ // none of those property paths should appear in the emitted output at all,
34
+ // since the real computation now reads ENTITY_REGISTRY's real EntityConfig.
35
+ assert.doesNotMatch(out, /entity\.api\.endpoints/)
36
+ assert.doesNotMatch(out, /entity\.features/)
37
+ })
38
+
39
+ test('base scopes are empty — nothing granted irrespective of role', () => {
40
+ const out = generateScopeRegistry([], {})
41
+ assert.match(out, /base:\s*\[\]/)
42
+ })
43
+
44
+ test('role scopes are computed from PERMISSIONS_BY_ROLE per AVAILABLE_ROLES, not hardcoded role names', () => {
45
+ const out = generateScopeRegistry([], {})
46
+ assert.match(out, /for \(const role of AVAILABLE_ROLES\)/)
47
+ assert.match(out, /const perms = PERMISSIONS_BY_ROLE\[role\]/)
48
+ // The old hardcoded roleScopes literal (superadmin/admin/manager/member keys,
49
+ // referencing a nonexistent "products" entity) must be gone.
50
+ assert.doesNotMatch(out, /'manager'/)
51
+ assert.doesNotMatch(out, /products:write/)
52
+ assert.doesNotMatch(out, /products:delete/)
53
+ })
54
+
55
+ test('read/write/delete scopes are derived from list|read / create|update / delete permissions', () => {
56
+ const out = generateScopeRegistry([], {})
57
+ assert.match(out, /\$\{slug\}\.list.*Permission.*\|\|.*perms\?\.has\(`\$\{slug\}\.read`/)
58
+ assert.match(out, /\$\{slug\}\.create.*Permission.*\|\|.*perms\?\.has\(`\$\{slug\}\.update`/)
59
+ assert.match(out, /\$\{slug\}\.delete.*Permission/)
60
+ assert.match(out, /scopes\.push\(`\$\{slug\}:read`\)/)
61
+ assert.match(out, /scopes\.push\(`\$\{slug\}:write`\)/)
62
+ assert.match(out, /scopes\.push\(`\$\{slug\}:delete`\)/)
63
+ })
64
+
65
+ test('guards against ChildEntityDefinition entries (no access/slug) in ENTITY_REGISTRY', () => {
66
+ const out = generateScopeRegistry([], {})
67
+ assert.match(out, /if \(!cfg\?\.access\?\.api \|\| !cfg\.slug\) return null/)
68
+ })
69
+
70
+ test('flag scopes and restrictions are preserved unchanged', () => {
71
+ const out = generateScopeRegistry([], {})
72
+ assert.match(out, /beta_tester/)
73
+ assert.match(out, /vip:features/)
74
+ assert.match(out, /"remove": \[\s*"delete",\s*"admin"\s*\]/)
75
+ assert.match(out, /"allow_only": \[\s*"read",\s*"tasks:write"\s*\]/)
76
+ })
77
+
78
+ test('superadmin is not hardcoded into SCOPE_CONFIG.roles — handled as a global-role bypass elsewhere', () => {
79
+ const out = generateScopeRegistry([], {})
80
+ assert.doesNotMatch(out, /'superadmin':\s*\[/)
81
+ assert.match(out, /Superadmin is a GLOBAL role/)
82
+ })
83
+
84
+ test('is a pure function of its output shape — always emits both SCOPE_CONFIG and API_CONFIG exports', () => {
85
+ const out = generateScopeRegistry([], {})
86
+ assert.match(out, /export const SCOPE_CONFIG: ScopeConfig = \{/)
87
+ assert.match(out, /export const API_CONFIG: ApiConfig = \{/)
88
+ })
89
+
90
+ // Regression for the npm-mode bug: this generator hardcoded the two `@/core/*`
91
+ // type-only imports instead of routing them through convertCorePath() like
92
+ // every sibling generator does, so a scaffolded (non-monorepo) app's tsconfig
93
+ // — which maps `@/*` to its own `./*`, not to core — failed `tsc --noEmit`
94
+ // with TS2307 on both. See https://github.com/NextSpark-js/nextspark/issues/133
95
+ test('monorepo mode (isNpmMode: false or unset): @/core/* type imports are left as-is', () => {
96
+ const out = generateScopeRegistry([], { isNpmMode: false })
97
+ assert.match(out, /import type \{ Permission \} from '@\/core\/lib\/permissions\/types'/)
98
+ assert.match(out, /import type \{ EntityConfig \} from '@\/core\/lib\/entities\/types'/)
99
+ })
100
+
101
+ test('npm mode: @/core/* type imports are converted to @nextsparkjs/core/*, like every sibling generator', () => {
102
+ const out = generateScopeRegistry([], { isNpmMode: true, outputDir: '/project/.nextspark/registries' })
103
+ assert.match(out, /import type \{ Permission \} from '@nextsparkjs\/core\/lib\/permissions\/types'/)
104
+ assert.match(out, /import type \{ EntityConfig \} from '@nextsparkjs\/core\/lib\/entities\/types'/)
105
+ assert.doesNotMatch(out, /from '@\/core\//)
106
+ })
@@ -0,0 +1,72 @@
1
+ /**
2
+ * MCP Override Discovery
3
+ *
4
+ * Discovers `entities/<slug>/mcp.ts` files for the active theme — the
5
+ * extension point a theme uses to customize how one entity is exposed by the
6
+ * MCP engine (@nextsparkjs/core/lib/mcp): exclude it, exclude specific
7
+ * operations, relax required fields, transform input/output, or add extra
8
+ * (non-CRUD) tools.
9
+ *
10
+ * Mirrors discoverApiPresets's discoverEntityFolders (same
11
+ * `{theme}/entities/<slug>/` scan, same theme-only scope) with one
12
+ * difference: `mcp.ts` sits at the entity folder root (a sibling of `api/`,
13
+ * `migrations/`, `messages/`), not nested under `api/` like `presets.ts`.
14
+ *
15
+ * Unlike the presets discovery, this module does NOT parse file contents —
16
+ * an McpEntityOverride exports real functions (transformInput, extraTools),
17
+ * which can't be recovered by regex/brace-counting. The generator instead
18
+ * emits a literal `import` statement per discovered file (same approach as
19
+ * entity-registry.mjs), and the override object itself is resolved by the
20
+ * TypeScript compiler/bundler at build time, not by this script.
21
+ *
22
+ * @module core/scripts/build/registry/discovery/mcp-overrides
23
+ */
24
+
25
+ import { readdir } from 'fs/promises'
26
+ import { join } from 'path'
27
+ import { existsSync } from 'fs'
28
+
29
+ import { log, verbose } from '../../../utils/index.mjs'
30
+
31
+ /**
32
+ * Discover MCP entity overrides for the active theme.
33
+ * @param {object} config - Configuration object from getConfig()
34
+ * @returns {Promise<Array<{slug: string, importPath: string, themeName: string}>>}
35
+ */
36
+ export async function discoverMcpOverrides(config) {
37
+ const overrides = []
38
+ const themeName = config.activeTheme
39
+
40
+ if (!themeName) {
41
+ verbose('[mcp-overrides] No active theme configured — skipping MCP override discovery')
42
+ return overrides
43
+ }
44
+
45
+ const entitiesDir = join(config.themesDir, themeName, 'entities')
46
+ if (!existsSync(entitiesDir)) {
47
+ verbose(`[mcp-overrides] No entities directory found for theme "${themeName}"`)
48
+ return overrides
49
+ }
50
+
51
+ try {
52
+ const entries = await readdir(entitiesDir, { withFileTypes: true })
53
+ const entityDirs = entries.filter((entry) => entry.isDirectory())
54
+
55
+ for (const dir of entityDirs) {
56
+ const entityName = dir.name
57
+ const mcpPath = join(entitiesDir, entityName, 'mcp.ts')
58
+ if (!existsSync(mcpPath)) continue
59
+
60
+ overrides.push({
61
+ slug: entityName,
62
+ importPath: `@/contents/themes/${themeName}/entities/${entityName}/mcp`,
63
+ themeName,
64
+ })
65
+ verbose(`[mcp-overrides] MCP override discovered: ${entityName} -> ${mcpPath}`)
66
+ }
67
+ } catch (error) {
68
+ log(`[mcp-overrides] Error discovering MCP overrides: ${error.message}`, 'error')
69
+ }
70
+
71
+ return overrides
72
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Tests for discoverMcpOverrides.
3
+ *
4
+ * Run: node --test packages/core/scripts/build/registry/discovery/mcp-overrides.test.mjs
5
+ */
6
+
7
+ import { test } from 'node:test'
8
+ import assert from 'node:assert/strict'
9
+ import { mkdtemp, mkdir, writeFile, rm } from 'fs/promises'
10
+ import { tmpdir } from 'os'
11
+ import { join } from 'path'
12
+
13
+ import { discoverMcpOverrides } from './mcp-overrides.mjs'
14
+
15
+ async function makeThemeFixture(entities) {
16
+ const root = await mkdtemp(join(tmpdir(), 'mcp-overrides-test-'))
17
+ const themesDir = join(root, 'themes')
18
+ const themeName = 'testtheme'
19
+ const entitiesDir = join(themesDir, themeName, 'entities')
20
+
21
+ for (const [slug, files] of Object.entries(entities)) {
22
+ const entityDir = join(entitiesDir, slug)
23
+ await mkdir(entityDir, { recursive: true })
24
+ for (const file of files) {
25
+ await writeFile(join(entityDir, file), '// fixture\n')
26
+ }
27
+ }
28
+
29
+ return { root, themesDir, themeName }
30
+ }
31
+
32
+ test('returns an empty array when there is no active theme', async () => {
33
+ const result = await discoverMcpOverrides({ activeTheme: undefined, themesDir: '/nonexistent' })
34
+ assert.deepEqual(result, [])
35
+ })
36
+
37
+ test('returns an empty array when the theme has no entities directory', async () => {
38
+ const root = await mkdtemp(join(tmpdir(), 'mcp-overrides-test-'))
39
+ const result = await discoverMcpOverrides({ activeTheme: 'ghost', themesDir: join(root, 'themes') })
40
+ assert.deepEqual(result, [])
41
+ await rm(root, { recursive: true, force: true })
42
+ })
43
+
44
+ test('finds mcp.ts at the entity folder root, not nested under api/', async () => {
45
+ const { root, themesDir, themeName } = await makeThemeFixture({
46
+ customers: ['customers.config.ts', 'mcp.ts'],
47
+ })
48
+ const result = await discoverMcpOverrides({ activeTheme: themeName, themesDir })
49
+ assert.equal(result.length, 1)
50
+ assert.equal(result[0].slug, 'customers')
51
+ assert.equal(result[0].importPath, `@/contents/themes/${themeName}/entities/customers/mcp`)
52
+ await rm(root, { recursive: true, force: true })
53
+ })
54
+
55
+ test('skips entities that have no mcp.ts', async () => {
56
+ const { root, themesDir, themeName } = await makeThemeFixture({
57
+ customers: ['customers.config.ts', 'mcp.ts'],
58
+ tasks: ['tasks.config.ts'],
59
+ })
60
+ const result = await discoverMcpOverrides({ activeTheme: themeName, themesDir })
61
+ assert.deepEqual(result.map((r) => r.slug), ['customers'])
62
+ await rm(root, { recursive: true, force: true })
63
+ })
64
+
65
+ test('does NOT match a preset-style entities/<slug>/api/mcp.ts (wrong location)', async () => {
66
+ const root = await mkdtemp(join(tmpdir(), 'mcp-overrides-test-'))
67
+ const themesDir = join(root, 'themes')
68
+ const themeName = 'testtheme'
69
+ await mkdir(join(themesDir, themeName, 'entities', 'customers', 'api'), { recursive: true })
70
+ await writeFile(join(themesDir, themeName, 'entities', 'customers', 'api', 'mcp.ts'), '// wrong spot\n')
71
+
72
+ const result = await discoverMcpOverrides({ activeTheme: themeName, themesDir })
73
+ assert.deepEqual(result, [])
74
+ await rm(root, { recursive: true, force: true })
75
+ })
76
+
77
+ test('discovers multiple entity overrides in the same theme', async () => {
78
+ const { root, themesDir, themeName } = await makeThemeFixture({
79
+ customers: ['mcp.ts'],
80
+ tasks: ['mcp.ts'],
81
+ posts: [],
82
+ })
83
+ const result = await discoverMcpOverrides({ activeTheme: themeName, themesDir })
84
+ assert.deepEqual(result.map((r) => r.slug).sort(), ['customers', 'tasks'])
85
+ await rm(root, { recursive: true, force: true })
86
+ })
@@ -0,0 +1,61 @@
1
+ /**
2
+ * MCP Override Registry Generator
3
+ *
4
+ * Generates mcp-registry.ts: a literal-import map of discovered
5
+ * `entities/<slug>/mcp.ts` files (see discovery/mcp-overrides.mjs), consumed
6
+ * by the default `/api/mcp` route template to build the `overrides` option
7
+ * passed to `createMcpEngine`. Follows entity-registry.mjs's literal-import
8
+ * pattern (not the regex-parsing approach used for api-presets.ts) because
9
+ * an McpEntityOverride exports real functions — transformInput, extraTools —
10
+ * that can't be recovered from source text.
11
+ *
12
+ * @module core/scripts/build/registry/generators/mcp-registry
13
+ */
14
+
15
+ import { join } from 'path'
16
+ import { convertCorePath } from '../config.mjs'
17
+
18
+ /**
19
+ * Generate the MCP override registry file.
20
+ * @param {Array<{slug: string, importPath: string, themeName: string}>} mcpOverrides - Discovered overrides
21
+ * @param {object} config - Configuration object from getConfig()
22
+ * @returns {string} Generated TypeScript content
23
+ */
24
+ export function generateMcpRegistry(mcpOverrides, config) {
25
+ const outputFilePath = join(config.outputDir, 'mcp-registry.ts')
26
+
27
+ const importName = (slug) => `${toPascalCase(slug)}McpOverride`
28
+
29
+ const imports = mcpOverrides
30
+ .map((entry) => `import ${importName(entry.slug)} from '${entry.importPath}'`)
31
+ .join('\n')
32
+
33
+ const entries = mcpOverrides
34
+ .map((entry) => ` '${entry.slug}': ${importName(entry.slug)},`)
35
+ .join('\n')
36
+
37
+ return `/**
38
+ * Auto-generated MCP Override Registry
39
+ *
40
+ * Generated at: ${new Date().toISOString()}
41
+ * MCP overrides discovered: ${mcpOverrides.length}
42
+ *
43
+ * DO NOT EDIT - This file is auto-generated by scripts/build/registry.mjs
44
+ */
45
+
46
+ ${imports}
47
+ import type { McpEntityOverride } from '${convertCorePath('@/core/lib/mcp', outputFilePath, config)}'
48
+
49
+ export const MCP_OVERRIDES: Record<string, McpEntityOverride> = {
50
+ ${entries}
51
+ }
52
+ `
53
+ }
54
+
55
+ function toPascalCase(slug) {
56
+ return slug
57
+ .split(/[-_/]+/)
58
+ .filter(Boolean)
59
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
60
+ .join('')
61
+ }
@@ -2,113 +2,90 @@
2
2
  * Scope Registry Generator
3
3
  *
4
4
  * Generates scope-registry.ts
5
+ */
6
+
7
+ import { join } from 'path'
8
+ import { convertCorePath } from '../config.mjs'
9
+
10
+ /**
11
+ *
12
+ * IMPORTANT: unlike most registry generators, the role→scope computation does
13
+ * NOT happen here at generation time. It happens at IMPORT TIME inside the
14
+ * emitted TypeScript, by statically importing the sibling generated
15
+ * `entity-registry.ts` (for which entities are API-exposed, via
16
+ * `access.api`) and `permissions-registry.ts` (for which team roles can
17
+ * perform which action on each entity, via `PERMISSIONS_BY_ROLE`).
18
+ *
19
+ * Why: this generator previously computed scopes from the raw `entities`
20
+ * discovery array (filesystem-scan metadata only — `{name, exportName,
21
+ * configPath, ...}`), which never carried `.api`/`.features`/`.fields`
22
+ * properties. Those properties don't exist on that shape, nor on the real
23
+ * `EntityConfig` type, so the computation silently produced `base: []` and a
24
+ * hardcoded, wrong `roles` object every time. Reading the REAL entity config
25
+ * (`access.api`, `slug`, `fields`) and the REAL per-role permission matrix
26
+ * requires resolving imports, which can only happen once the generated files
27
+ * are imported by app code — not synchronously inside this generation
28
+ * script. Doing the computation at import time also guarantees scope
29
+ * minting and scope enforcement can never diverge again: both read the same
30
+ * `PERMISSIONS_BY_ROLE` source of truth `checkPermission()` uses at request
31
+ * time.
5
32
  *
6
33
  * @module core/scripts/build/registry/generators/scope-registry
7
34
  */
8
35
 
36
+ // Flag-based scopes and restriction rules are orthogonal to team roles/entity
37
+ // permissions (they're driven by user flags like `beta_tester`/`vip`, not by
38
+ // what a role can do to which entity) — kept as static literals, unrelated to
39
+ // the role/entity computation this generator used to get wrong.
40
+ const FLAG_SCOPES = {
41
+ 'beta_tester': ['beta:features'],
42
+ 'vip': ['vip:features', 'advanced:export'],
43
+ 'early_adopter': ['early:features'],
44
+ 'power_user': ['advanced:features', 'bulk:operations']
45
+ }
46
+
47
+ const SCOPE_RESTRICTIONS = {
48
+ 'restricted': {
49
+ remove: ['delete', 'admin']
50
+ },
51
+ 'limited_access': {
52
+ allow_only: ['read', 'tasks:write']
53
+ }
54
+ }
55
+
9
56
  /**
10
57
  * Generate the scope registry file
11
58
  *
12
- * @param {Array} entities - Discovered entities
59
+ * @param {Array} entities - Discovered entities (unused for scope computation
60
+ * today — kept in the signature for call-site consistency with the other
61
+ * generators; the real per-entity data used below comes from the generated
62
+ * `entity-registry.ts` this file imports, not from this parameter)
13
63
  * @param {object} config - Configuration object from getConfig()
14
64
  * @returns {string} Generated TypeScript content
15
65
  */
16
66
  export function generateScopeRegistry(entities, config) {
17
- // Generate base scopes from entities
18
- const baseScopes = []
19
- const entityPaths = []
20
-
21
- for (const entity of entities) {
22
- if (entity.api && entity.features?.enabled) {
23
- const entityName = entity.name
24
- baseScopes.push(`${entityName}:read`)
25
-
26
- // Add write scope if entity supports creation
27
- if (entity.api.endpoints && entity.api.endpoints.create) {
28
- baseScopes.push(`${entityName}:write`)
29
- }
30
-
31
- // Add delete scope for entities that support deletion
32
- if (entity.api.endpoints && entity.api.endpoints.delete) {
33
- baseScopes.push(`${entityName}:delete`)
34
- }
35
-
36
- // Collect entity paths for routing
37
- if (entity.api.apiPath) {
38
- entityPaths.push(entity.api.apiPath)
39
- }
40
- }
41
- }
42
-
43
- // Define role-based scopes
44
- const roleScopes = {
45
- 'superadmin': ['admin:users', 'tasks:delete', 'products:write', 'products:delete', ...baseScopes.filter(s => s.includes(':delete'))],
46
- 'admin': ['admin:users', 'tasks:delete', 'products:write', 'products:delete', ...baseScopes.filter(s => s.includes(':delete'))],
47
- 'manager': baseScopes.filter(s => s.includes(':write') || s.includes(':read')),
48
- 'member': baseScopes.filter(s => s.includes(':read') || s === 'tasks:write')
49
- }
50
-
51
- // Define flag-based scopes
52
- const flagScopes = {
53
- 'beta_tester': ['beta:features'],
54
- 'vip': ['vip:features', 'advanced:export'],
55
- 'early_adopter': ['early:features'],
56
- 'power_user': ['advanced:features', 'bulk:operations']
57
- }
58
-
59
- // Define allowed filters based on entities
60
- const allowedFilters = ['status', 'role', 'completed', 'userId']
61
-
62
- // Add entity-specific filters
63
- for (const entity of entities) {
64
- if (entity.api && entity.features?.enabled) {
65
- allowedFilters.push(`${entity.name}Id`)
66
-
67
- // Add common entity filters
68
- if (entity.fields) {
69
- const fields = Object.keys(entity.fields)
70
- if (fields.includes('status')) allowedFilters.push(`${entity.name}Status`)
71
- if (fields.includes('type')) allowedFilters.push(`${entity.name}Type`)
72
- if (fields.includes('category')) allowedFilters.push(`${entity.name}Category`)
73
- }
74
- }
75
- }
76
-
77
- const scopeConfig = {
78
- base: [...new Set(baseScopes)],
79
- roles: roleScopes,
80
- flags: flagScopes,
81
- restrictions: {
82
- 'restricted': {
83
- remove: ['delete', 'admin']
84
- },
85
- 'limited_access': {
86
- allow_only: ['read', 'tasks:write']
87
- }
88
- }
89
- }
90
-
91
- const apiConfig = {
92
- filters: {
93
- allowed: [...new Set(allowedFilters)]
94
- },
95
- entityPaths: [...new Set(entityPaths)]
96
- }
97
-
67
+ const outputFilePath = config.outputDir ? join(config.outputDir, 'scope-registry.ts') : 'scope-registry.ts'
98
68
  return `/**
99
69
  * Auto-generated API Scope Registry
100
70
  *
101
71
  * Generated at: ${new Date().toISOString()}
102
- * Base scopes: ${scopeConfig.base.length}
103
- * Role configurations: ${Object.keys(scopeConfig.roles).length}
104
- * Flag configurations: ${Object.keys(scopeConfig.flags).length}
105
- * Allowed filters: ${apiConfig.filters.allowed.length}
72
+ *
73
+ * Role→scope mapping is computed below at import time from
74
+ * \`entity-registry.ts\` (which entities are API-exposed) and
75
+ * \`permissions-registry.ts\` (which team roles can do what to each entity),
76
+ * so scope MINTING (this file) and scope ENFORCEMENT
77
+ * (\`checkPermission\`/\`hasRequiredScope\`) can never drift apart.
106
78
  *
107
79
  * This file replaces hardcoded scope configurations in helpers.ts
108
80
  *
109
81
  * DO NOT EDIT - This file is auto-generated by scripts/build-registry.mjs
110
82
  */
111
83
 
84
+ import { ENTITY_REGISTRY } from './entity-registry'
85
+ import { AVAILABLE_ROLES, PERMISSIONS_BY_ROLE } from './permissions-registry'
86
+ import type { Permission } from '${convertCorePath('@/core/lib/permissions/types', outputFilePath, config)}'
87
+ import type { EntityConfig } from '${convertCorePath('@/core/lib/entities/types', outputFilePath, config)}'
88
+
112
89
  export interface ScopeConfig {
113
90
  base: string[]
114
91
  roles: Record<string, string[]>
@@ -123,9 +100,84 @@ export interface ApiConfig {
123
100
  entityPaths: string[]
124
101
  }
125
102
 
126
- export const SCOPE_CONFIG: ScopeConfig = ${JSON.stringify(scopeConfig, null, 2)}
103
+ interface ScopeApiEntity {
104
+ slug: string
105
+ apiPath: string
106
+ fieldNames: string[]
107
+ }
127
108
 
128
- export const API_CONFIG: ApiConfig = ${JSON.stringify(apiConfig, null, 2)}
109
+ // Entities exposed via the external API (access.api === true). ENTITY_REGISTRY
110
+ // entries can be a full EntityConfig or a ChildEntityDefinition (which has no
111
+ // \`access\`/\`slug\` at all) — guard defensively rather than assume every
112
+ // registry entry is a root entity.
113
+ const SCOPE_API_ENTITIES: ScopeApiEntity[] = Object.values(ENTITY_REGISTRY)
114
+ .map((entry) => {
115
+ const cfg = entry.config as Partial<EntityConfig>
116
+ if (!cfg?.access?.api || !cfg.slug) return null
117
+ return {
118
+ slug: cfg.slug,
119
+ apiPath: cfg.access.basePath || \`/api/v1/\${cfg.slug}\`,
120
+ fieldNames: Array.isArray(cfg.fields) ? cfg.fields.map((field) => field.name) : []
121
+ }
122
+ })
123
+ .filter((entity): entity is ScopeApiEntity => entity !== null)
124
+
125
+ // For each API-exposed entity, grant \`<slug>:read/write/delete\` to any team
126
+ // role whose PERMISSIONS_BY_ROLE set includes the corresponding
127
+ // \`<slug>.<action>\` permission — the SAME set checkAuthPermission() consults
128
+ // at request time. Superadmin is a GLOBAL role (never in AVAILABLE_ROLES) and
129
+ // is intentionally NOT represented here; it's handled as an explicit bypass
130
+ // in validateScopesForUser().
131
+ function computeRoleScopes(): Record<string, string[]> {
132
+ const roleScopes: Record<string, string[]> = {}
133
+ for (const role of AVAILABLE_ROLES) {
134
+ const perms = PERMISSIONS_BY_ROLE[role]
135
+ const scopes: string[] = []
136
+ for (const { slug } of SCOPE_API_ENTITIES) {
137
+ if (perms?.has(\`\${slug}.list\` as Permission) || perms?.has(\`\${slug}.read\` as Permission)) {
138
+ scopes.push(\`\${slug}:read\`)
139
+ }
140
+ if (perms?.has(\`\${slug}.create\` as Permission) || perms?.has(\`\${slug}.update\` as Permission)) {
141
+ scopes.push(\`\${slug}:write\`)
142
+ }
143
+ if (perms?.has(\`\${slug}.delete\` as Permission)) {
144
+ scopes.push(\`\${slug}:delete\`)
145
+ }
146
+ }
147
+ roleScopes[role] = [...new Set(scopes)]
148
+ }
149
+ return roleScopes
150
+ }
151
+
152
+ function computeAllowedFilters(): string[] {
153
+ const allowedFilters = new Set<string>(['status', 'role', 'completed', 'userId'])
154
+ for (const { slug, fieldNames } of SCOPE_API_ENTITIES) {
155
+ allowedFilters.add(\`\${slug}Id\`)
156
+ if (fieldNames.includes('status')) allowedFilters.add(\`\${slug}Status\`)
157
+ if (fieldNames.includes('type')) allowedFilters.add(\`\${slug}Type\`)
158
+ if (fieldNames.includes('category')) allowedFilters.add(\`\${slug}Category\`)
159
+ }
160
+ return [...allowedFilters]
161
+ }
162
+
163
+ export const SCOPE_CONFIG: ScopeConfig = {
164
+ // Nothing is granted irrespective of role — every entity scope flows
165
+ // through computeRoleScopes() above, sourced from the real permission
166
+ // matrix. Kept as an empty array (rather than removed) so a future,
167
+ // deliberate "these scopes belong to every authenticated key" need has a
168
+ // place to live without re-deriving this decision.
169
+ base: [],
170
+ roles: computeRoleScopes(),
171
+ flags: ${JSON.stringify(FLAG_SCOPES, null, 2).split('\n').join('\n ')},
172
+ restrictions: ${JSON.stringify(SCOPE_RESTRICTIONS, null, 2).split('\n').join('\n ')}
173
+ }
174
+
175
+ export const API_CONFIG: ApiConfig = {
176
+ filters: {
177
+ allowed: computeAllowedFilters()
178
+ },
179
+ entityPaths: SCOPE_API_ENTITIES.map((entity) => entity.apiPath)
180
+ }
129
181
 
130
182
  // ==================== Service Layer ====================
131
183
  // Query functions have been moved to: @nextsparkjs/core/lib/services/scope.service
@@ -55,6 +55,7 @@ import { discoverEmails } from './registry/discovery/emails.mjs'
55
55
  import { discoverBlocks } from './registry/discovery/blocks.mjs'
56
56
  import { discoverCoreRoutes } from './registry/discovery/core-routes.mjs'
57
57
  import { discoverApiPresets } from './registry/discovery/api-presets.mjs'
58
+ import { discoverMcpOverrides } from './registry/discovery/mcp-overrides.mjs'
58
59
  import { validateEntityConfigurations } from './registry/validation/entity-validator.mjs'
59
60
  import { generatePluginRegistry, generatePluginRegistryClient } from './registry/generators/plugin-registry.mjs'
60
61
  import { generateEntityRegistry, generateEntityRegistryClient } from './registry/generators/entity-registry.mjs'
@@ -76,6 +77,7 @@ import { generatePermissionsRegistry } from './registry/generators/permissions-r
76
77
  import { generateScheduledActionsRegistry } from './registry/generators/scheduled-actions-registry.mjs'
77
78
  import { generateDocsRegistry } from './registry/generators/docs-registry.mjs'
78
79
  import { generateApiPresetsRegistry, generateApiDocsRegistry } from './registry/generators/api-presets-registry.mjs'
80
+ import { generateMcpRegistry } from './registry/generators/mcp-registry.mjs'
79
81
  import {
80
82
  generateMissingPages,
81
83
  displayTreeStructure,
@@ -89,7 +91,7 @@ import { syncAppGlobalsCss } from './theme.mjs'
89
91
 
90
92
  // ==================== Registry File Generation ====================
91
93
 
92
- async function generateRegistryFiles(CONFIG, plugins, entities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails) {
94
+ async function generateRegistryFiles(CONFIG, plugins, entities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails, mcpOverridesData) {
93
95
  log('Generating registry files...', 'build')
94
96
 
95
97
  try {
@@ -122,6 +124,7 @@ async function generateRegistryFiles(CONFIG, plugins, entities, themes, template
122
124
  { name: 'docs-registry.ts', content: generateDocsRegistry() },
123
125
  { name: 'api-presets-registry.ts', content: generateApiPresetsRegistry(apiPresetsData, CONFIG) },
124
126
  { name: 'api-docs-registry.ts', content: generateApiDocsRegistry(apiPresetsData, CONFIG) },
127
+ { name: 'mcp-registry.ts', content: generateMcpRegistry(mcpOverridesData, CONFIG) },
125
128
  { name: 'index.ts', content: generateUnifiedRegistry(plugins, entities, themes, templates, middlewares, CONFIG) }
126
129
  ]
127
130
 
@@ -191,7 +194,7 @@ export async function buildRegistries(projectRoot = null) {
191
194
  await discoverParentChildRelations(CONFIG)
192
195
 
193
196
  // Discover all content types in parallel (pass CONFIG to each)
194
- const [plugins, coreEntities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails] = await Promise.all([
197
+ const [plugins, coreEntities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails, mcpOverridesData] = await Promise.all([
195
198
  discoverPlugins(CONFIG),
196
199
  discoverCoreEntities(CONFIG),
197
200
  discoverThemes(CONFIG),
@@ -201,7 +204,8 @@ export async function buildRegistries(projectRoot = null) {
201
204
  discoverPermissionsConfig(CONFIG),
202
205
  discoverCoreRoutes(CONFIG),
203
206
  discoverApiPresets(CONFIG),
204
- discoverEmails(CONFIG)
207
+ discoverEmails(CONFIG),
208
+ discoverMcpOverrides(CONFIG)
205
209
  ])
206
210
 
207
211
  // Aggregate all entities with proper priority: plugin < core < theme
@@ -260,7 +264,7 @@ export async function buildRegistries(projectRoot = null) {
260
264
 
261
265
  // Hoist plugin dependencies to root workspace for proper resolution
262
266
  // Generate all registry files (use aggregated entities for entity registry + blocks)
263
- await generateRegistryFiles(CONFIG, plugins, allEntities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails)
267
+ await generateRegistryFiles(CONFIG, plugins, allEntities, themes, templates, middlewares, blocks, permissionsConfig, coreRoutes, apiPresetsData, emails, mcpOverridesData)
264
268
 
265
269
  // Generate missing pages for templates that don't have core app pages
266
270
  await generateMissingPages(templates, CONFIG)
@@ -4,6 +4,7 @@ import { redirect } from 'next/navigation'
4
4
  import { SignupForm } from '@nextsparkjs/core/components/auth/forms/SignupForm'
5
5
  import { getTemplateOrDefault, getMetadataOrDefault } from '@nextsparkjs/core/lib/template-resolver'
6
6
  import { AUTH_CONFIG } from '@nextsparkjs/core/lib/config'
7
+ import { resolveAuthMethods } from '@nextsparkjs/core/lib/auth/auth-methods'
7
8
  import { TeamService } from '@nextsparkjs/core/lib/services'
8
9
 
9
10
  // This page reads DB state (TeamService.hasGlobal) and redirect()s during render,
@@ -23,6 +24,13 @@ export const metadata: Metadata = getMetadataOrDefault(
23
24
  async function SignupPageContent() {
24
25
  const registrationMode = AUTH_CONFIG?.registration?.mode ?? 'open'
25
26
 
27
+ // Passwordless preset (no 'email-password' in auth.methods): the account is
28
+ // created by the first one-time-code sign-in, so there is no password signup
29
+ // form to show — send people to /login instead.
30
+ if (!resolveAuthMethods(AUTH_CONFIG).includes('email-password')) {
31
+ redirect('/login')
32
+ }
33
+
26
34
  // In invitation-only mode, allow the first user to register
27
35
  // (when no global team exists yet). Subsequent users need invitations.
28
36
  if (registrationMode === 'invitation-only') {