@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
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from "next/server";
2
- import { authenticateRequest, hasRequiredScope, canBypassTeamContext } from "../auth/dual-auth.js";
2
+ import { authenticateRequest, createAuthFailureResponse, canBypassTeamContext } from "../auth/dual-auth.js";
3
3
  import { resolveEntityFromUrl, validateEntityOperation } from "./resolver.js";
4
4
  import { generateEntitySchemas } from "../../entities/schema-generator.js";
5
5
  import { queryWithRLS, mutateWithRLS, queryOneWithRLS } from "../../db.js";
@@ -17,7 +17,8 @@ import {
17
17
  addCorsHeaders,
18
18
  handleCorsPreflightRequest
19
19
  } from "../helpers.js";
20
- import { afterEntityCreate, afterEntityUpdate, afterEntityDelete } from "../../entities/entity-hooks.js";
20
+ import { beforeEntityCreate, afterEntityCreate, beforeEntityUpdate, afterEntityUpdate, beforeEntityDelete, afterEntityDelete, beforeEntityRead, afterEntityRead } from "../../entities/entity-hooks.js";
21
+ import { logGenericHandlerUsage } from "./audit-log.js";
21
22
  import { checkPermission } from "../../permissions/check.js";
22
23
  import { extractPatternIds } from "../../blocks/pattern-resolver.js";
23
24
  import { isPatternReference } from "../../../types/pattern-reference.js";
@@ -169,6 +170,102 @@ function parseTaxonomyFilterParams(url, entityConfig) {
169
170
  function getTableName(entityConfig) {
170
171
  return entityConfig.tableName || entityConfig.slug;
171
172
  }
173
+ function isEntityField(entityConfig, fieldName) {
174
+ return entityConfig.fields.some((field) => field.name === fieldName);
175
+ }
176
+ const LIST_RESERVED_PARAMS = /* @__PURE__ */ new Set([
177
+ "page",
178
+ "limit",
179
+ "fields",
180
+ "distinct",
181
+ "ids",
182
+ "parentId",
183
+ "search",
184
+ "sortBy",
185
+ "sortOrder",
186
+ "sort",
187
+ "order",
188
+ "child",
189
+ "meta",
190
+ "metas",
191
+ "includeMeta",
192
+ "userId",
193
+ "from",
194
+ "to",
195
+ "dateField",
196
+ "userFiltered"
197
+ ]);
198
+ const TAXONOMY_FILTER_PARAMS = ["categoryId", "taxonomyId", "taxonomyType"];
199
+ const SEARCHABLE_FIELDS = ["name", "title", "slug", "content"];
200
+ const SORTABLE_BASE_FIELDS = ["id", "createdAt", "updatedAt", "teamId"];
201
+ const BARE_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
202
+ function omitHandlerManagedKeys(entityConfig, data) {
203
+ var _a;
204
+ const managed = /* @__PURE__ */ new Set(["userId", "teamId"]);
205
+ if ((_a = entityConfig.taxonomies) == null ? void 0 : _a.enabled) {
206
+ for (const taxonomyType of entityConfig.taxonomies.types ?? []) {
207
+ managed.add(taxonomyType.field);
208
+ }
209
+ }
210
+ return Object.fromEntries(Object.entries(data).filter(([key]) => !managed.has(key)));
211
+ }
212
+ function validationErrorMessage(issues) {
213
+ const unknownKeys = issues.filter((issue) => issue.code === "unrecognized_keys").flatMap((issue) => issue.keys ?? []);
214
+ return unknownKeys.length > 0 ? `Unknown field(s): ${unknownKeys.join(", ")}` : "Validation error";
215
+ }
216
+ function asPgError(error) {
217
+ if (error && typeof error === "object" && "code" in error && typeof error.code === "string") {
218
+ return error;
219
+ }
220
+ return null;
221
+ }
222
+ function mapConstraintViolation(error, operation) {
223
+ const pgError = asPgError(error);
224
+ if (!pgError) return null;
225
+ const { constraint, detail, table } = pgError;
226
+ switch (pgError.code) {
227
+ case "23505":
228
+ return createApiError(
229
+ "A record with this value already exists",
230
+ 409,
231
+ { constraint, detail },
232
+ "UNIQUE_CONSTRAINT_VIOLATION"
233
+ );
234
+ case "23514":
235
+ return createApiError(
236
+ constraint ? `Value rejected by check constraint "${constraint}"` : "Value rejected by a database check constraint",
237
+ 422,
238
+ { constraint, table },
239
+ "CHECK_CONSTRAINT_VIOLATION"
240
+ );
241
+ case "23503":
242
+ if (operation === "delete") {
243
+ return createApiError(
244
+ detail || "Cannot delete: this record is referenced by other records",
245
+ 409,
246
+ { constraint },
247
+ "FOREIGN_KEY_VIOLATION"
248
+ );
249
+ }
250
+ return createApiError(
251
+ detail ? `Referenced record does not exist: ${detail}` : "Referenced record does not exist",
252
+ 422,
253
+ { constraint, detail },
254
+ "FOREIGN_KEY_VIOLATION"
255
+ );
256
+ default:
257
+ return null;
258
+ }
259
+ }
260
+ function hookRejectionStatus(error) {
261
+ if (error && typeof error === "object") {
262
+ const candidate = error.status ?? error.statusCode;
263
+ if (typeof candidate === "number" && candidate >= 400 && candidate < 500) {
264
+ return candidate;
265
+ }
266
+ }
267
+ return 400;
268
+ }
172
269
  async function filterOrphanedPatternReferences(blocks, userId) {
173
270
  if (!Array.isArray(blocks) || blocks.length === 0) return blocks;
174
271
  const patternIds = extractPatternIds(blocks);
@@ -193,11 +290,11 @@ function getTeamIdFromRequest(request) {
193
290
  function isBuilderRequest(request) {
194
291
  return request.headers.get("x-builder-source") === "true";
195
292
  }
196
- async function resolveOwnershipFilter(entityConfig, userId, teamId, isBypass, authType) {
293
+ async function resolveOwnershipFilter(entityConfig, userId, teamId, isBypass) {
197
294
  var _a;
198
295
  const ownershipFilter = (_a = entityConfig.access) == null ? void 0 : _a.ownershipFilter;
199
296
  if (!ownershipFilter) return { applies: false };
200
- if (isBypass || authType === "api-key") return { applies: false };
297
+ if (isBypass) return { applies: false };
201
298
  const memberRow = await queryOneWithRLS(
202
299
  'SELECT role FROM "team_members" WHERE "teamId" = $1 AND "userId" = $2',
203
300
  [teamId, userId],
@@ -221,9 +318,9 @@ async function resolveOwnershipFilter(entityConfig, userId, teamId, isBypass, au
221
318
  const filterValue = linkedRow ? linkedRow[valueField] ?? null : null;
222
319
  return { applies: true, field: ownershipFilter.field, value: filterValue };
223
320
  }
224
- async function checkSessionPermission(authResult, entitySlug, action, teamId, request) {
321
+ async function checkAuthPermission(authResult, entitySlug, action, teamId, request) {
225
322
  var _a;
226
- if (authResult.type !== "session" || !((_a = authResult.user) == null ? void 0 : _a.id)) return null;
323
+ if (!((_a = authResult.user) == null ? void 0 : _a.id)) return null;
227
324
  const permission = `${entitySlug}.${action}`;
228
325
  try {
229
326
  const allowed = await checkPermission(authResult.user.id, teamId, permission);
@@ -291,7 +388,24 @@ async function validateTeamContextWithBypass(request, authResult, userId) {
291
388
  }
292
389
  return { valid: true, teamId, isBypass: false };
293
390
  }
391
+ async function runWithAuditLog(request, run) {
392
+ const startedAt = Date.now();
393
+ const audit = { auth: null };
394
+ let statusCode = 500;
395
+ try {
396
+ const response = await run(audit);
397
+ statusCode = response.status;
398
+ return response;
399
+ } finally {
400
+ logGenericHandlerUsage(audit.auth, request, statusCode, Date.now() - startedAt).catch((error) => {
401
+ console.error("[generic-handler:audit] unexpected audit failure:", error);
402
+ });
403
+ }
404
+ }
294
405
  async function handleGenericList(request) {
406
+ return runWithAuditLog(request, (audit) => handleGenericListImpl(request, audit));
407
+ }
408
+ async function handleGenericListImpl(request, audit) {
295
409
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
296
410
  try {
297
411
  const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
@@ -303,25 +417,22 @@ async function handleGenericList(request) {
303
417
  const response2 = createApiError("List operation not supported for this entity", 405);
304
418
  return addCorsHeaders(response2, request);
305
419
  }
306
- const authResult = await authenticateRequest(request);
420
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:read` });
421
+ audit.auth = authResult;
307
422
  let userId = null;
308
423
  let teamId = null;
309
424
  let isBypass = false;
425
+ if (!authResult.success && authResult.type === "api-key") {
426
+ return addCorsHeaders(createAuthFailureResponse(authResult), request);
427
+ }
310
428
  if (!authResult.success && ((_a = resolution.entityConfig.access) == null ? void 0 : _a.public)) {
311
429
  teamId = request.headers.get("x-team-id") ?? null;
312
430
  } else if (!authResult.success) {
313
- return NextResponse.json(
314
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
315
- { status: 401 }
316
- );
431
+ return createAuthFailureResponse(authResult);
317
432
  } else {
318
433
  if (authResult.rateLimitResponse) {
319
434
  return authResult.rateLimitResponse;
320
435
  }
321
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:read`)) {
322
- const response2 = createApiError("Insufficient permissions", 403);
323
- return addCorsHeaders(response2, request);
324
- }
325
436
  userId = authResult.user.id;
326
437
  const teamValidation = await validateTeamContextWithBypass(request, authResult, userId);
327
438
  if (!teamValidation.valid) {
@@ -330,7 +441,7 @@ async function handleGenericList(request) {
330
441
  teamId = teamValidation.teamId;
331
442
  isBypass = teamValidation.isBypass;
332
443
  if (!isBypass && teamId) {
333
- const permDenied = await checkSessionPermission(authResult, resolution.entityConfig.slug, "list", teamId, request);
444
+ const permDenied = await checkAuthPermission(authResult, resolution.entityConfig.slug, "list", teamId, request);
334
445
  if (permDenied) return permDenied;
335
446
  }
336
447
  }
@@ -341,10 +452,9 @@ async function handleGenericList(request) {
341
452
  const distinctParam = url.searchParams.get("distinct") === "true";
342
453
  const idsFromParams = url.searchParams.getAll("ids");
343
454
  const specificIds = idsFromParams.length > 0 ? idsFromParams.flatMap((param) => param.split(",").map((id) => id.trim())).filter(Boolean) : null;
344
- const knownParams = /* @__PURE__ */ new Set(["page", "limit", "fields", "distinct", "ids", "parentId", "search", "sortBy", "sortOrder", "child", "meta", "from", "to", "dateField"]);
345
455
  const customFilters = {};
346
456
  url.searchParams.forEach((value, key) => {
347
- if (!knownParams.has(key) && value) {
457
+ if (!LIST_RESERVED_PARAMS.has(key) && value) {
348
458
  if (!customFilters[key]) {
349
459
  customFilters[key] = [];
350
460
  }
@@ -353,9 +463,59 @@ async function handleGenericList(request) {
353
463
  }
354
464
  });
355
465
  const entityConfig = resolution.entityConfig;
466
+ const shapedQuery = await beforeEntityRead(entityConfig.slug, { ...customFilters }, userId ?? void 0);
467
+ for (const [key, value] of Object.entries(shapedQuery)) {
468
+ customFilters[key] = Array.isArray(value) ? value.map(String) : [String(value)];
469
+ }
470
+ const taxonomyFilterParams = ((_b = entityConfig.taxonomies) == null ? void 0 : _b.enabled) ? TAXONOMY_FILTER_PARAMS : [];
471
+ const invalidFilterKeys = Object.keys(customFilters).filter(
472
+ (key) => !isEntityField(entityConfig, key) && !taxonomyFilterParams.includes(key)
473
+ );
474
+ if (invalidFilterKeys.length > 0) {
475
+ const response2 = createApiError(
476
+ `Unknown filter parameter(s) for ${entityConfig.slug}: ${invalidFilterKeys.join(", ")}`,
477
+ 400,
478
+ {
479
+ invalidKeys: invalidFilterKeys,
480
+ allowedKeys: [...entityConfig.fields.map((f) => f.name), ...taxonomyFilterParams]
481
+ },
482
+ "INVALID_FILTER"
483
+ );
484
+ return addCorsHeaders(response2, request);
485
+ }
486
+ const searchParam = url.searchParams.get("search");
487
+ const searchableFields = SEARCHABLE_FIELDS.filter((name) => isEntityField(entityConfig, name));
488
+ if (searchParam && searchParam.trim() !== "" && searchableFields.length === 0) {
489
+ const response2 = createApiError(
490
+ `Entity ${entityConfig.slug} has no searchable field (${SEARCHABLE_FIELDS.join(", ")}); the search parameter is not supported`,
491
+ 400,
492
+ { searchableFields: SEARCHABLE_FIELDS },
493
+ "SEARCH_NOT_SUPPORTED"
494
+ );
495
+ return addCorsHeaders(response2, request);
496
+ }
497
+ const sortByParam = url.searchParams.get("sortBy") || url.searchParams.get("sort");
498
+ if (sortByParam && !isEntityField(entityConfig, sortByParam) && !SORTABLE_BASE_FIELDS.includes(sortByParam)) {
499
+ const response2 = createApiError(
500
+ `Invalid sortBy field for ${entityConfig.slug}: ${sortByParam}`,
501
+ 400,
502
+ { allowedFields: [...SORTABLE_BASE_FIELDS, ...entityConfig.fields.map((f) => f.name)] },
503
+ "INVALID_SORT_FIELD"
504
+ );
505
+ return addCorsHeaders(response2, request);
506
+ }
356
507
  let fields;
357
508
  if (fieldsParam && distinctParam) {
358
509
  const fieldName = fieldsParam;
510
+ if (!isEntityField(entityConfig, fieldName)) {
511
+ const response2 = createApiError(
512
+ `Invalid field for distinct query: ${fieldName}`,
513
+ 400,
514
+ void 0,
515
+ "INVALID_FIELD"
516
+ );
517
+ return addCorsHeaders(response2, request);
518
+ }
359
519
  const isJsonbField = fieldName === "contentLanguages" || fieldName === "brandValues" || fieldName === "hashtags";
360
520
  if (isJsonbField) {
361
521
  return handleDistinctJsonbField(entityConfig, fieldName, url, userId);
@@ -364,19 +524,17 @@ async function handleGenericList(request) {
364
524
  }
365
525
  } else if (fieldsParam) {
366
526
  const requestedFields = fieldsParam.split(",").map((f) => f.trim());
367
- const validFields = requestedFields.filter(
368
- (fieldName) => entityConfig.fields.some((field) => field.name === fieldName)
369
- );
527
+ const validFields = requestedFields.filter((fieldName) => isEntityField(entityConfig, fieldName));
370
528
  fields = ["id", ...validFields].map((fieldName) => {
371
529
  const columnName = /[A-Z]/.test(fieldName) ? `"${fieldName}"` : fieldName;
372
530
  return `t.${columnName}`;
373
531
  }).join(", ");
374
532
  } else {
375
533
  const systemFields = ["id", "userId", "teamId", "createdAt", "updatedAt"];
376
- if ((_b = entityConfig.builder) == null ? void 0 : _b.enabled) {
534
+ if ((_c = entityConfig.builder) == null ? void 0 : _c.enabled) {
377
535
  systemFields.push("blocks");
378
536
  }
379
- if ((_c = entityConfig.table) == null ? void 0 : _c.softDelete) {
537
+ if ((_d = entityConfig.table) == null ? void 0 : _d.softDelete) {
380
538
  systemFields.push("deletedAt", "deletedBy");
381
539
  }
382
540
  const configFields = entityConfig.fields.map((field) => {
@@ -395,12 +553,11 @@ async function handleGenericList(request) {
395
553
  let query;
396
554
  let queryParams;
397
555
  let paramIndex = 1;
398
- const searchParam = url.searchParams.get("search");
399
556
  const fromDate = url.searchParams.get("from");
400
557
  const toDate = url.searchParams.get("to");
401
558
  const dateFieldName = url.searchParams.get("dateField");
402
- const isPublicEntity = ((_d = entityConfig.access) == null ? void 0 : _d.public) === true;
403
- const requestingPublishedOnly = (_e = customFilters["status"]) == null ? void 0 : _e.includes("published");
559
+ const isPublicEntity = ((_e = entityConfig.access) == null ? void 0 : _e.public) === true;
560
+ const requestingPublishedOnly = (_f = customFilters["status"]) == null ? void 0 : _f.includes("published");
404
561
  const skipUserFilter = isPublicEntity && requestingPublishedOnly;
405
562
  if (skipUserFilter && process.env.NODE_ENV === "development") {
406
563
  console.log(`[GenericHandler] Public access mode: skipping userId filter for ${entityConfig.slug} (status=published)`);
@@ -413,8 +570,7 @@ async function handleGenericList(request) {
413
570
  entityConfig,
414
571
  userId,
415
572
  teamId,
416
- isBypass,
417
- authResult.type
573
+ isBypass
418
574
  );
419
575
  }
420
576
  if (ownershipFilter.applies && ownershipFilter.value === null) {
@@ -430,7 +586,7 @@ async function handleGenericList(request) {
430
586
  query += ` AND t."teamId" = $${paramIndex++}`;
431
587
  queryParams.push(teamId);
432
588
  }
433
- if (userId && !((_f = entityConfig.access) == null ? void 0 : _f.shared) && !skipUserFilter && !isBypass) {
589
+ if (userId && !((_g = entityConfig.access) == null ? void 0 : _g.shared) && !skipUserFilter && !isBypass) {
434
590
  query += ` AND t."userId" = $${paramIndex++}`;
435
591
  queryParams.push(userId);
436
592
  }
@@ -438,7 +594,7 @@ async function handleGenericList(request) {
438
594
  query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
439
595
  queryParams.push(ownershipFilter.value);
440
596
  }
441
- if (((_g = entityConfig.table) == null ? void 0 : _g.softDelete) && !isBypass) {
597
+ if (((_h = entityConfig.table) == null ? void 0 : _h.softDelete) && !isBypass) {
442
598
  query += ` AND t."deletedAt" IS NULL`;
443
599
  }
444
600
  } else if (fieldsParam && distinctParam) {
@@ -454,7 +610,7 @@ async function handleGenericList(request) {
454
610
  query += ` AND t."parentId" = $${paramIndex++}`;
455
611
  queryParams.push(parentId);
456
612
  }
457
- if (userId && !((_h = entityConfig.access) == null ? void 0 : _h.shared) && !skipUserFilter && !isBypass) {
613
+ if (userId && !((_i = entityConfig.access) == null ? void 0 : _i.shared) && !skipUserFilter && !isBypass) {
458
614
  query += ` AND t."userId" = $${paramIndex++}`;
459
615
  queryParams.push(userId);
460
616
  }
@@ -462,7 +618,7 @@ async function handleGenericList(request) {
462
618
  query += ` AND t."${ownershipFilter.field}" = $${paramIndex++}`;
463
619
  queryParams.push(ownershipFilter.value);
464
620
  }
465
- if (((_i = entityConfig.table) == null ? void 0 : _i.softDelete) && !isBypass) {
621
+ if (((_j = entityConfig.table) == null ? void 0 : _j.softDelete) && !isBypass) {
466
622
  query += ` AND t."deletedAt" IS NULL`;
467
623
  }
468
624
  query += ` ORDER BY "${fieldName}" ASC LIMIT $${paramIndex++}`;
@@ -475,7 +631,7 @@ async function handleGenericList(request) {
475
631
  whereConditions.push(`t."teamId" = $${paramIndex++}`);
476
632
  queryParams.push(teamId);
477
633
  }
478
- if (userId && !((_j = entityConfig.access) == null ? void 0 : _j.shared) && !skipUserFilter && !isBypass) {
634
+ if (userId && !((_k = entityConfig.access) == null ? void 0 : _k.shared) && !skipUserFilter && !isBypass) {
479
635
  whereConditions.push(`t."userId" = $${paramIndex++}`);
480
636
  queryParams.push(userId);
481
637
  }
@@ -483,33 +639,15 @@ async function handleGenericList(request) {
483
639
  whereConditions.push(`t."${ownershipFilter.field}" = $${paramIndex++}`);
484
640
  queryParams.push(ownershipFilter.value);
485
641
  }
486
- if (((_k = entityConfig.table) == null ? void 0 : _k.softDelete) && !isBypass) {
642
+ if (((_l = entityConfig.table) == null ? void 0 : _l.softDelete) && !isBypass) {
487
643
  whereConditions.push(`t."deletedAt" IS NULL`);
488
644
  }
489
- if (searchParam && searchParam.trim() !== "") {
645
+ if (searchParam && searchParam.trim() !== "" && searchableFields.length > 0) {
490
646
  const searchTerm = searchParam.trim();
491
- const hasName = entityConfig.fields.some((f) => f.name === "name");
492
- const hasTitle = entityConfig.fields.some((f) => f.name === "title");
493
- const hasSlug = entityConfig.fields.some((f) => f.name === "slug");
494
- const hasContent = entityConfig.fields.some((f) => f.name === "content");
495
- if (hasName || hasTitle || hasSlug || hasContent) {
496
- const searchConditions = [];
497
- if (hasName) {
498
- searchConditions.push(`t.name ILIKE $${paramIndex}`);
499
- }
500
- if (hasTitle) {
501
- searchConditions.push(`t.title ILIKE $${paramIndex}`);
502
- }
503
- if (hasSlug) {
504
- searchConditions.push(`t.slug ILIKE $${paramIndex}`);
505
- }
506
- if (hasContent) {
507
- searchConditions.push(`t.content ILIKE $${paramIndex}`);
508
- }
509
- whereConditions.push(`(${searchConditions.join(" OR ")})`);
510
- queryParams.push(`%${searchTerm}%`);
511
- paramIndex++;
512
- }
647
+ const searchConditions = searchableFields.map((name) => `t.${name} ILIKE $${paramIndex}`);
648
+ whereConditions.push(`(${searchConditions.join(" OR ")})`);
649
+ queryParams.push(`%${searchTerm}%`);
650
+ paramIndex++;
513
651
  }
514
652
  if (dateFieldName && (fromDate || toDate)) {
515
653
  const field = entityConfig.fields.find((f) => f.name === dateFieldName);
@@ -537,6 +675,18 @@ async function handleGenericList(request) {
537
675
  queryParams.push(JSON.stringify([value]));
538
676
  });
539
677
  whereConditions.push(`(${orConditions.join(" OR ")})`);
678
+ } else if (field.type === "date" || field.type === "datetime") {
679
+ const orConditions = values.map((value) => {
680
+ if (BARE_DATE_RE.test(value)) {
681
+ const lower = paramIndex++;
682
+ const upper = paramIndex++;
683
+ queryParams.push(value, value);
684
+ return `(t.${columnName} >= $${lower}::date AND t.${columnName} < $${upper}::date + 1)`;
685
+ }
686
+ queryParams.push(value);
687
+ return `t.${columnName} = $${paramIndex++}`;
688
+ });
689
+ whereConditions.push(`(${orConditions.join(" OR ")})`);
540
690
  } else {
541
691
  const orConditions = values.map(() => {
542
692
  const condition = `t.${columnName} = $${paramIndex++}`;
@@ -569,16 +719,9 @@ async function handleGenericList(request) {
569
719
  ) pu ON t.id = pu."patternId"
570
720
  `;
571
721
  }
572
- const sortByParam = url.searchParams.get("sortBy");
573
- const sortOrderParam = ((_l = url.searchParams.get("sortOrder")) == null ? void 0 : _l.toUpperCase()) === "ASC" ? "ASC" : "DESC";
574
- let orderByClause = 't."createdAt" DESC';
575
- if (sortByParam) {
576
- const baseFields = ["id", "createdAt", "updatedAt", "teamId"];
577
- const isValidField = entityConfig.fields.some((f) => f.name === sortByParam) || baseFields.includes(sortByParam);
578
- if (isValidField) {
579
- orderByClause = `t."${sortByParam}" ${sortOrderParam}`;
580
- }
581
- }
722
+ const sortOrderRaw = url.searchParams.get("sortOrder") || url.searchParams.get("order");
723
+ const sortOrderParam = (sortOrderRaw == null ? void 0 : sortOrderRaw.toUpperCase()) === "ASC" ? "ASC" : "DESC";
724
+ const orderByClause = sortByParam ? `t."${sortByParam}" ${sortOrderParam}` : 't."createdAt" DESC';
582
725
  query = `
583
726
  SELECT ${fields}, COUNT(*) OVER() as total_count
584
727
  FROM "${tableName}" t
@@ -625,7 +768,8 @@ async function handleGenericList(request) {
625
768
  );
626
769
  }
627
770
  const finalData = filterPublicFields(dataWithTaxonomies, entityConfig, userId);
628
- const response = createApiResponse(finalData, paginationMeta);
771
+ const shapedData = await afterEntityRead(entityConfig.slug, finalData, { ...customFilters }, userId ?? void 0);
772
+ const response = createApiResponse(shapedData, paginationMeta);
629
773
  return addCorsHeaders(response, request);
630
774
  } catch (error) {
631
775
  console.error("Error in generic list handler:", error);
@@ -634,6 +778,9 @@ async function handleGenericList(request) {
634
778
  }
635
779
  }
636
780
  async function handleGenericCreate(request) {
781
+ return runWithAuditLog(request, (audit) => handleGenericCreateImpl(request, audit));
782
+ }
783
+ async function handleGenericCreateImpl(request, audit) {
637
784
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
638
785
  try {
639
786
  const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
@@ -645,20 +792,14 @@ async function handleGenericCreate(request) {
645
792
  const response2 = createApiError("Create operation not supported for this entity", 405);
646
793
  return addCorsHeaders(response2, request);
647
794
  }
648
- const authResult = await authenticateRequest(request);
795
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
796
+ audit.auth = authResult;
649
797
  if (!authResult.success) {
650
- return NextResponse.json(
651
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
652
- { status: 401 }
653
- );
798
+ return createAuthFailureResponse(authResult);
654
799
  }
655
800
  if (authResult.rateLimitResponse) {
656
801
  return authResult.rateLimitResponse;
657
802
  }
658
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
659
- const response2 = createApiError("Insufficient permissions", 403);
660
- return addCorsHeaders(response2, request);
661
- }
662
803
  let body;
663
804
  try {
664
805
  body = await request.json();
@@ -671,16 +812,21 @@ async function handleGenericCreate(request) {
671
812
  const entityConfig = resolution.entityConfig;
672
813
  const tableName = getTableName(entityConfig);
673
814
  const schemas = generateEntitySchemas(entityConfig);
674
- const validation = schemas.create.safeParse(entityData);
815
+ const validation = schemas.create.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
675
816
  if (!validation.success) {
676
817
  console.error(`[${entityConfig.slug}] Validation failed:`, {
677
818
  entityData,
678
819
  errors: validation.error.issues
679
820
  });
680
- const response2 = createApiError("Validation error", 400, validation.error.issues, "VALIDATION_ERROR");
821
+ const response2 = createApiError(
822
+ validationErrorMessage(validation.error.issues),
823
+ 400,
824
+ validation.error.issues,
825
+ "VALIDATION_ERROR"
826
+ );
681
827
  return addCorsHeaders(response2, request);
682
828
  }
683
- const validatedData = validation.data;
829
+ let validatedData = validation.data;
684
830
  const xssField = checkNameFieldXss(entityConfig, validatedData);
685
831
  if (xssField) {
686
832
  const xssResponse = createApiError(
@@ -714,8 +860,24 @@ async function handleGenericCreate(request) {
714
860
  const response2 = createApiError("Team context required for create operations. Include x-team-id header.", 400, void 0, "TEAM_CONTEXT_REQUIRED");
715
861
  return addCorsHeaders(response2, request);
716
862
  }
717
- const permDenied = await checkSessionPermission(authResult, entityConfig.slug, "create", teamId, request);
863
+ const permDenied = await checkAuthPermission(authResult, entityConfig.slug, "create", teamId, request);
718
864
  if (permDenied) return permDenied;
865
+ try {
866
+ validatedData = await beforeEntityCreate(
867
+ entityConfig.slug,
868
+ { ...validatedData, userId: userIdToUse, teamId },
869
+ authResult.user.id
870
+ );
871
+ } catch (hookError) {
872
+ console.error(`[generic-handler] beforeEntityCreate rejected ${entityConfig.slug} create:`, hookError);
873
+ const response2 = createApiError(
874
+ hookError instanceof Error && hookError.message ? hookError.message : `Create rejected by ${entityConfig.slug} before_create hook`,
875
+ hookRejectionStatus(hookError),
876
+ void 0,
877
+ "BEFORE_CREATE_REJECTED"
878
+ );
879
+ return addCorsHeaders(response2, request);
880
+ }
719
881
  if (idStrategy === "serial") {
720
882
  insertFields = ['"userId"', '"teamId"'];
721
883
  placeholders = ["$1", "$2"];
@@ -801,6 +963,7 @@ async function handleGenericCreate(request) {
801
963
  if (!actionResult.allowed) {
802
964
  const statusCode = actionResult.reason === "quota_exceeded" ? 429 : 403;
803
965
  const response2 = createApiError(
966
+ // @ts-expect-error — pre-existing type error, tracked in https://github.com/NextSpark-js/nextspark/issues/131
804
967
  actionResult.message || `Quota exceeded for ${entityConfig.slug}`,
805
968
  statusCode,
806
969
  void 0,
@@ -883,22 +1046,18 @@ async function handleGenericCreate(request) {
883
1046
  return addCorsHeaders(response, request);
884
1047
  } catch (error) {
885
1048
  console.error("Error in generic create handler:", error);
886
- if (error && typeof error === "object" && "code" in error && error.code === "23505") {
887
- const detail = "detail" in error ? error.detail : void 0;
888
- const constraint = "constraint" in error ? error.constraint : void 0;
889
- const response2 = createApiError(
890
- "A record with this value already exists",
891
- 409,
892
- { constraint, detail },
893
- "UNIQUE_CONSTRAINT_VIOLATION"
894
- );
895
- return addCorsHeaders(response2, request);
1049
+ const constraintResponse = mapConstraintViolation(error, "create");
1050
+ if (constraintResponse) {
1051
+ return addCorsHeaders(constraintResponse, request);
896
1052
  }
897
1053
  const response = createApiError("Internal server error", 500);
898
1054
  return addCorsHeaders(response, request);
899
1055
  }
900
1056
  }
901
1057
  async function handleGenericRead(request, { params }) {
1058
+ return runWithAuditLog(request, (audit) => handleGenericReadImpl(request, audit, { params }));
1059
+ }
1060
+ async function handleGenericReadImpl(request, audit, { params }) {
902
1061
  var _a, _b, _c, _d, _e, _f;
903
1062
  try {
904
1063
  const { id } = await params;
@@ -911,25 +1070,22 @@ async function handleGenericRead(request, { params }) {
911
1070
  const response2 = createApiError("Read operation not supported for this entity", 405);
912
1071
  return addCorsHeaders(response2, request);
913
1072
  }
914
- const authResult = await authenticateRequest(request);
1073
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:read` });
1074
+ audit.auth = authResult;
915
1075
  let userId = null;
916
1076
  let teamId = null;
917
1077
  let isBypass = false;
1078
+ if (!authResult.success && authResult.type === "api-key") {
1079
+ return addCorsHeaders(createAuthFailureResponse(authResult), request);
1080
+ }
918
1081
  if (!authResult.success && ((_a = resolution.entityConfig.access) == null ? void 0 : _a.public)) {
919
1082
  console.log(`[GenericHandler] Public access allowed for ${resolution.entityConfig.slug} read`);
920
1083
  } else if (!authResult.success) {
921
- return NextResponse.json(
922
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
923
- { status: 401 }
924
- );
1084
+ return createAuthFailureResponse(authResult);
925
1085
  } else {
926
1086
  if (authResult.rateLimitResponse) {
927
1087
  return authResult.rateLimitResponse;
928
1088
  }
929
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:read`)) {
930
- const response2 = createApiError("Insufficient permissions", 403);
931
- return addCorsHeaders(response2, request);
932
- }
933
1089
  userId = authResult.user.id;
934
1090
  const teamValidation = await validateTeamContextWithBypass(request, authResult, userId);
935
1091
  if (!teamValidation.valid) {
@@ -938,7 +1094,7 @@ async function handleGenericRead(request, { params }) {
938
1094
  teamId = teamValidation.teamId;
939
1095
  isBypass = teamValidation.isBypass;
940
1096
  if (!isBypass && teamId) {
941
- const permDenied = await checkSessionPermission(authResult, resolution.entityConfig.slug, "read", teamId, request);
1097
+ const permDenied = await checkAuthPermission(authResult, resolution.entityConfig.slug, "read", teamId, request);
942
1098
  if (permDenied) return permDenied;
943
1099
  }
944
1100
  }
@@ -982,8 +1138,7 @@ async function handleGenericRead(request, { params }) {
982
1138
  entityConfig,
983
1139
  userId,
984
1140
  teamId,
985
- isBypass,
986
- authResult.type
1141
+ isBypass
987
1142
  );
988
1143
  if (readOwnershipFilter.applies) {
989
1144
  if (readOwnershipFilter.value === null) {
@@ -1036,7 +1191,8 @@ async function handleGenericRead(request, { params }) {
1036
1191
  itemWithTaxonomies = itemsWithTaxonomies[0] || itemWithChild;
1037
1192
  }
1038
1193
  const finalItem = filterPublicFields(itemWithTaxonomies, entityConfig, userId);
1039
- const response = createApiResponse(finalItem);
1194
+ const [shapedItem] = await afterEntityRead(entityConfig.slug, [finalItem], { id }, userId ?? void 0);
1195
+ const response = createApiResponse(shapedItem ?? finalItem);
1040
1196
  return addCorsHeaders(response, request);
1041
1197
  } catch (error) {
1042
1198
  console.error("Error in generic read handler:", error);
@@ -1045,7 +1201,10 @@ async function handleGenericRead(request, { params }) {
1045
1201
  }
1046
1202
  }
1047
1203
  async function handleGenericUpdate(request, { params }) {
1048
- var _a, _b, _c, _d, _e, _f;
1204
+ return runWithAuditLog(request, (audit) => handleGenericUpdateImpl(request, audit, { params }));
1205
+ }
1206
+ async function handleGenericUpdateImpl(request, audit, { params }) {
1207
+ var _a, _b, _c, _d, _e, _f, _g;
1049
1208
  try {
1050
1209
  const { id } = await params;
1051
1210
  const resolution = await resolveEntityFromUrl(request.nextUrl.pathname);
@@ -1057,20 +1216,14 @@ async function handleGenericUpdate(request, { params }) {
1057
1216
  const response2 = createApiError("Update operation not supported for this entity", 405);
1058
1217
  return addCorsHeaders(response2, request);
1059
1218
  }
1060
- const authResult = await authenticateRequest(request);
1219
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:write` });
1220
+ audit.auth = authResult;
1061
1221
  if (!authResult.success) {
1062
- return NextResponse.json(
1063
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
1064
- { status: 401 }
1065
- );
1222
+ return createAuthFailureResponse(authResult);
1066
1223
  }
1067
1224
  if (authResult.rateLimitResponse) {
1068
1225
  return authResult.rateLimitResponse;
1069
1226
  }
1070
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
1071
- const response2 = createApiError("Insufficient permissions", 403);
1072
- return addCorsHeaders(response2, request);
1073
- }
1074
1227
  const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
1075
1228
  if (!teamValidation.valid) {
1076
1229
  return teamValidation.error;
@@ -1080,7 +1233,7 @@ async function handleGenericUpdate(request, { params }) {
1080
1233
  const response2 = createApiError("Team context required for update operations. Include x-team-id header.", 400, void 0, "TEAM_CONTEXT_REQUIRED");
1081
1234
  return addCorsHeaders(response2, request);
1082
1235
  }
1083
- const permDenied = await checkSessionPermission(authResult, resolution.entityConfig.slug, "update", teamId, request);
1236
+ const permDenied = await checkAuthPermission(authResult, resolution.entityConfig.slug, "update", teamId, request);
1084
1237
  if (permDenied) return permDenied;
1085
1238
  let body;
1086
1239
  try {
@@ -1092,7 +1245,7 @@ async function handleGenericUpdate(request, { params }) {
1092
1245
  const { metas, ...entityData } = body;
1093
1246
  const entityConfig = resolution.entityConfig;
1094
1247
  const fieldGuards = (_b = (_a = entityConfig.access) == null ? void 0 : _a.ownershipFilter) == null ? void 0 : _b.fieldGuards;
1095
- if (fieldGuards && fieldGuards.length > 0 && authResult.type === "session") {
1248
+ if (fieldGuards && fieldGuards.length > 0 && ((_c = authResult.user) == null ? void 0 : _c.id)) {
1096
1249
  const guardMemberRow = await queryOneWithRLS(
1097
1250
  'SELECT role FROM "team_members" WHERE "teamId" = $1 AND "userId" = $2',
1098
1251
  [teamId, authResult.user.id],
@@ -1117,16 +1270,21 @@ async function handleGenericUpdate(request, { params }) {
1117
1270
  }
1118
1271
  const tableName = getTableName(entityConfig);
1119
1272
  const schemas = generateEntitySchemas(entityConfig);
1120
- const validation = schemas.update.safeParse(entityData);
1273
+ const validation = schemas.update.safeParse(omitHandlerManagedKeys(entityConfig, entityData));
1121
1274
  if (!validation.success) {
1122
1275
  console.log(`[${entityConfig.slug}] Validation failed:`, {
1123
1276
  entityData,
1124
1277
  errors: validation.error.issues
1125
1278
  });
1126
- const response2 = createApiError("Validation error", 400, validation.error.issues, "VALIDATION_ERROR");
1279
+ const response2 = createApiError(
1280
+ validationErrorMessage(validation.error.issues),
1281
+ 400,
1282
+ validation.error.issues,
1283
+ "VALIDATION_ERROR"
1284
+ );
1127
1285
  return addCorsHeaders(response2, request);
1128
1286
  }
1129
- const validatedData = validation.data;
1287
+ let validatedData = validation.data;
1130
1288
  const updateXssField = checkNameFieldXss(entityConfig, validatedData);
1131
1289
  if (updateXssField) {
1132
1290
  const updateXssResponse = createApiError(
@@ -1137,11 +1295,28 @@ async function handleGenericUpdate(request, { params }) {
1137
1295
  );
1138
1296
  return addCorsHeaders(updateXssResponse, request);
1139
1297
  }
1298
+ try {
1299
+ validatedData = await beforeEntityUpdate(
1300
+ entityConfig.slug,
1301
+ id,
1302
+ validatedData,
1303
+ authResult.user.id
1304
+ );
1305
+ } catch (hookError) {
1306
+ console.error(`[generic-handler] beforeEntityUpdate rejected ${entityConfig.slug} update:`, hookError);
1307
+ const response2 = createApiError(
1308
+ hookError instanceof Error && hookError.message ? hookError.message : `Update rejected by ${entityConfig.slug} before_update hook`,
1309
+ hookRejectionStatus(hookError),
1310
+ void 0,
1311
+ "BEFORE_UPDATE_REJECTED"
1312
+ );
1313
+ return addCorsHeaders(response2, request);
1314
+ }
1140
1315
  const updates = [];
1141
1316
  const values = [];
1142
1317
  let paramCount = 1;
1143
1318
  const builderRequest = isBuilderRequest(request);
1144
- const entityHasBuilder = ((_c = entityConfig.builder) == null ? void 0 : _c.enabled) === true;
1319
+ const entityHasBuilder = ((_d = entityConfig.builder) == null ? void 0 : _d.enabled) === true;
1145
1320
  if (builderRequest && entityHasBuilder && "blocks" in entityData) {
1146
1321
  let blocksData = entityData.blocks;
1147
1322
  blocksData = await filterOrphanedPatternReferences(blocksData, authResult.user.id);
@@ -1217,7 +1392,7 @@ async function handleGenericUpdate(request, { params }) {
1217
1392
  whereConditions.push(`"teamId" = $${paramCount++}`);
1218
1393
  values.push(teamId);
1219
1394
  }
1220
- if (!((_d = entityConfig.access) == null ? void 0 : _d.shared)) {
1395
+ if (!((_e = entityConfig.access) == null ? void 0 : _e.shared)) {
1221
1396
  whereConditions.push(`"userId" = $${paramCount++}`);
1222
1397
  values.push(authResult.user.id);
1223
1398
  }
@@ -1225,8 +1400,7 @@ async function handleGenericUpdate(request, { params }) {
1225
1400
  entityConfig,
1226
1401
  authResult.user.id,
1227
1402
  teamId,
1228
- teamValidation.isBypass,
1229
- authResult.type
1403
+ teamValidation.isBypass
1230
1404
  );
1231
1405
  if (updateOwnershipFilter.applies) {
1232
1406
  if (updateOwnershipFilter.value === null) {
@@ -1262,7 +1436,7 @@ async function handleGenericUpdate(request, { params }) {
1262
1436
  }
1263
1437
  updatedItem = result[0];
1264
1438
  }
1265
- if ((_e = entityConfig.taxonomies) == null ? void 0 : _e.enabled) {
1439
+ if ((_f = entityConfig.taxonomies) == null ? void 0 : _f.enabled) {
1266
1440
  await processTaxonomyRelations(entityConfig, id, body, authResult.user.id, true);
1267
1441
  }
1268
1442
  if (metadataWasProvided) {
@@ -1274,7 +1448,7 @@ async function handleGenericUpdate(request, { params }) {
1274
1448
  );
1275
1449
  }
1276
1450
  let responseItem = updatedItem;
1277
- if ((_f = entityConfig.taxonomies) == null ? void 0 : _f.enabled) {
1451
+ if ((_g = entityConfig.taxonomies) == null ? void 0 : _g.enabled) {
1278
1452
  const itemsWithTaxonomies = await includeTaxonomiesInData(
1279
1453
  entityConfig,
1280
1454
  [updatedItem],
@@ -1297,22 +1471,18 @@ async function handleGenericUpdate(request, { params }) {
1297
1471
  return addCorsHeaders(response, request);
1298
1472
  } catch (error) {
1299
1473
  console.error("Error in generic update handler:", error);
1300
- if (error && typeof error === "object" && "code" in error && error.code === "23505") {
1301
- const detail = "detail" in error ? error.detail : void 0;
1302
- const constraint = "constraint" in error ? error.constraint : void 0;
1303
- const response2 = createApiError(
1304
- "A record with this value already exists",
1305
- 409,
1306
- { constraint, detail },
1307
- "UNIQUE_CONSTRAINT_VIOLATION"
1308
- );
1309
- return addCorsHeaders(response2, request);
1474
+ const constraintResponse = mapConstraintViolation(error, "update");
1475
+ if (constraintResponse) {
1476
+ return addCorsHeaders(constraintResponse, request);
1310
1477
  }
1311
1478
  const response = createApiError("Internal server error", 500);
1312
1479
  return addCorsHeaders(response, request);
1313
1480
  }
1314
1481
  }
1315
1482
  async function handleGenericDelete(request, { params }) {
1483
+ return runWithAuditLog(request, (audit) => handleGenericDeleteImpl(request, audit, { params }));
1484
+ }
1485
+ async function handleGenericDeleteImpl(request, audit, { params }) {
1316
1486
  var _a, _b, _c, _d, _e;
1317
1487
  try {
1318
1488
  const { id } = await params;
@@ -1325,20 +1495,14 @@ async function handleGenericDelete(request, { params }) {
1325
1495
  const response2 = createApiError("Delete operation not supported for this entity", 405);
1326
1496
  return addCorsHeaders(response2, request);
1327
1497
  }
1328
- const authResult = await authenticateRequest(request);
1498
+ const authResult = await authenticateRequest(request, { requiredScope: `${resolution.entityConfig.slug}:delete` });
1499
+ audit.auth = authResult;
1329
1500
  if (!authResult.success) {
1330
- return NextResponse.json(
1331
- { success: false, error: "Authentication required", code: "AUTHENTICATION_FAILED" },
1332
- { status: 401 }
1333
- );
1501
+ return createAuthFailureResponse(authResult);
1334
1502
  }
1335
1503
  if (authResult.rateLimitResponse) {
1336
1504
  return authResult.rateLimitResponse;
1337
1505
  }
1338
- if (!hasRequiredScope(authResult, `${resolution.entityConfig.slug}:write`)) {
1339
- const response2 = createApiError("Insufficient permissions", 403);
1340
- return addCorsHeaders(response2, request);
1341
- }
1342
1506
  const teamValidation = await validateTeamContextWithBypass(request, authResult, authResult.user.id);
1343
1507
  if (!teamValidation.valid) {
1344
1508
  return teamValidation.error;
@@ -1348,10 +1512,31 @@ async function handleGenericDelete(request, { params }) {
1348
1512
  const response2 = createApiError("Team context required for delete operations. Include x-team-id header.", 400, void 0, "TEAM_CONTEXT_REQUIRED");
1349
1513
  return addCorsHeaders(response2, request);
1350
1514
  }
1351
- const permDenied = await checkSessionPermission(authResult, resolution.entityConfig.slug, "delete", teamId, request);
1515
+ const permDenied = await checkAuthPermission(authResult, resolution.entityConfig.slug, "delete", teamId, request);
1352
1516
  if (permDenied) return permDenied;
1353
1517
  const entityConfig = resolution.entityConfig;
1354
1518
  const tableName = getTableName(entityConfig);
1519
+ try {
1520
+ const allowDelete = await beforeEntityDelete(entityConfig.slug, id, authResult.user.id);
1521
+ if (!allowDelete) {
1522
+ const response2 = createApiError(
1523
+ `Delete rejected by ${entityConfig.slug} before_delete hook`,
1524
+ 403,
1525
+ void 0,
1526
+ "BEFORE_DELETE_REJECTED"
1527
+ );
1528
+ return addCorsHeaders(response2, request);
1529
+ }
1530
+ } catch (hookError) {
1531
+ console.error(`[generic-handler] beforeEntityDelete rejected ${entityConfig.slug} delete:`, hookError);
1532
+ const response2 = createApiError(
1533
+ hookError instanceof Error && hookError.message ? hookError.message : `Delete rejected by ${entityConfig.slug} before_delete hook`,
1534
+ hookRejectionStatus(hookError),
1535
+ void 0,
1536
+ "BEFORE_DELETE_REJECTED"
1537
+ );
1538
+ return addCorsHeaders(response2, request);
1539
+ }
1355
1540
  const whereConditions = ["id = $1"];
1356
1541
  const deleteParams = [id];
1357
1542
  let paramIndex = 2;
@@ -1408,16 +1593,9 @@ async function handleGenericDelete(request, { params }) {
1408
1593
  return addCorsHeaders(response, request);
1409
1594
  } catch (error) {
1410
1595
  console.error("Error in generic delete handler:", error);
1411
- if (error && typeof error === "object" && "code" in error && error.code === "23503") {
1412
- const detail = "detail" in error ? error.detail : void 0;
1413
- const constraint = "constraint" in error ? error.constraint : void 0;
1414
- const response2 = createApiError(
1415
- detail || "Cannot delete: this record is referenced by other records",
1416
- 409,
1417
- { constraint },
1418
- "FOREIGN_KEY_VIOLATION"
1419
- );
1420
- return addCorsHeaders(response2, request);
1596
+ const constraintResponse = mapConstraintViolation(error, "delete");
1597
+ if (constraintResponse) {
1598
+ return addCorsHeaders(constraintResponse, request);
1421
1599
  }
1422
1600
  const response = createApiError("Internal server error", 500);
1423
1601
  return addCorsHeaders(response, request);