@jmruthers/pace-core 0.5.193 → 0.6.2

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 (577) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +299 -0
  4. package/cursor-rules/01-standards-compliance.mdc +244 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +222 -0
  7. package/cursor-rules/04-testing-standards.mdc +268 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +309 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
  11. package/cursor-rules/08-markup-quality.mdc +452 -0
  12. package/cursor-rules/CHANGELOG.md +119 -0
  13. package/cursor-rules/README.md +192 -0
  14. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
  15. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
  16. package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
  17. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
  18. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
  19. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
  20. package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
  21. package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
  22. package/dist/chunk-24UVZUZG.js.map +1 -0
  23. package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
  24. package/dist/chunk-2UOI2FG5.js.map +1 -0
  25. package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
  26. package/dist/chunk-3XC4CPTD.js.map +1 -0
  27. package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
  28. package/dist/chunk-6J4GEEJR.js.map +1 -0
  29. package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
  30. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  31. package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
  32. package/dist/chunk-EHMR7VYL.js.map +1 -0
  33. package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
  34. package/dist/chunk-F2IMUDXZ.js.map +1 -0
  35. package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
  36. package/dist/chunk-FFQEQTNW.js.map +1 -0
  37. package/dist/chunk-FMUCXFII.js +76 -0
  38. package/dist/chunk-FMUCXFII.js.map +1 -0
  39. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  40. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  41. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  42. package/dist/chunk-L4OXEN46.js.map +1 -0
  43. package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
  44. package/dist/chunk-M43Y4SSO.js.map +1 -0
  45. package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
  46. package/dist/chunk-MMZ7JXPU.js.map +1 -0
  47. package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
  48. package/dist/chunk-NECFR5MM.js.map +1 -0
  49. package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
  50. package/dist/chunk-SFZUDBL5.js.map +1 -0
  51. package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
  52. package/dist/chunk-XWQCNGTQ.js.map +1 -0
  53. package/dist/components.d.ts +6 -6
  54. package/dist/components.js +15 -12
  55. package/dist/components.js.map +1 -1
  56. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  57. package/dist/hooks.d.ts +59 -126
  58. package/dist/hooks.js +19 -28
  59. package/dist/hooks.js.map +1 -1
  60. package/dist/index.d.ts +63 -16
  61. package/dist/index.js +23 -24
  62. package/dist/index.js.map +1 -1
  63. package/dist/providers.d.ts +21 -3
  64. package/dist/providers.js +2 -2
  65. package/dist/rbac/index.d.ts +146 -115
  66. package/dist/rbac/index.js +8 -11
  67. package/dist/theming/runtime.d.ts +1 -13
  68. package/dist/theming/runtime.js +1 -1
  69. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  70. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  71. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  72. package/dist/types.d.ts +2 -2
  73. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
  74. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  75. package/dist/utils.d.ts +4 -5
  76. package/dist/utils.js +15 -15
  77. package/dist/utils.js.map +1 -1
  78. package/docs/api/README.md +7 -1
  79. package/docs/api/classes/ColumnFactory.md +8 -8
  80. package/docs/api/classes/InvalidScopeError.md +4 -4
  81. package/docs/api/classes/Logger.md +1 -1
  82. package/docs/api/classes/MissingUserContextError.md +4 -4
  83. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  84. package/docs/api/classes/PermissionDeniedError.md +4 -4
  85. package/docs/api/classes/RBACAuditManager.md +1 -1
  86. package/docs/api/classes/RBACCache.md +1 -1
  87. package/docs/api/classes/RBACEngine.md +1 -1
  88. package/docs/api/classes/RBACError.md +4 -4
  89. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  90. package/docs/api/classes/SecureSupabaseClient.md +18 -15
  91. package/docs/api/classes/StorageUtils.md +1 -1
  92. package/docs/api/enums/FileCategory.md +1 -1
  93. package/docs/api/enums/LogLevel.md +1 -1
  94. package/docs/api/enums/RBACErrorCode.md +1 -1
  95. package/docs/api/enums/RPCFunction.md +1 -1
  96. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  97. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  98. package/docs/api/interfaces/AggregateConfig.md +4 -4
  99. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  100. package/docs/api/interfaces/AvatarProps.md +1 -1
  101. package/docs/api/interfaces/BadgeProps.md +9 -2
  102. package/docs/api/interfaces/ButtonProps.md +7 -4
  103. package/docs/api/interfaces/CalendarProps.md +8 -5
  104. package/docs/api/interfaces/CardProps.md +8 -5
  105. package/docs/api/interfaces/ColorPalette.md +1 -1
  106. package/docs/api/interfaces/ColorShade.md +1 -1
  107. package/docs/api/interfaces/ComplianceResult.md +1 -1
  108. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  109. package/docs/api/interfaces/DataRecord.md +1 -1
  110. package/docs/api/interfaces/DataTableAction.md +24 -21
  111. package/docs/api/interfaces/DataTableColumn.md +31 -31
  112. package/docs/api/interfaces/DataTableProps.md +1 -1
  113. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  114. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  115. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  116. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  117. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  118. package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
  119. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
  120. package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
  121. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  122. package/docs/api/interfaces/ExportColumn.md +1 -1
  123. package/docs/api/interfaces/ExportOptions.md +8 -8
  124. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  125. package/docs/api/interfaces/FileMetadata.md +1 -1
  126. package/docs/api/interfaces/FileReference.md +1 -1
  127. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  128. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  129. package/docs/api/interfaces/FileUploadProps.md +26 -23
  130. package/docs/api/interfaces/FooterProps.md +10 -8
  131. package/docs/api/interfaces/FormFieldProps.md +10 -10
  132. package/docs/api/interfaces/FormProps.md +1 -1
  133. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  134. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  135. package/docs/api/interfaces/InputProps.md +7 -4
  136. package/docs/api/interfaces/LabelProps.md +1 -1
  137. package/docs/api/interfaces/LoggerConfig.md +1 -1
  138. package/docs/api/interfaces/LoginFormProps.md +14 -11
  139. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  140. package/docs/api/interfaces/NavigationContextType.md +1 -1
  141. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  142. package/docs/api/interfaces/NavigationItem.md +11 -11
  143. package/docs/api/interfaces/NavigationMenuProps.md +15 -15
  144. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  145. package/docs/api/interfaces/Organisation.md +1 -1
  146. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  147. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  148. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  149. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  150. package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
  151. package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
  152. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  153. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  154. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  155. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  156. package/docs/api/interfaces/PaletteData.md +1 -1
  157. package/docs/api/interfaces/ParsedAddress.md +1 -1
  158. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  159. package/docs/api/interfaces/ProgressProps.md +1 -1
  160. package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
  161. package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
  162. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
  163. package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
  164. package/docs/api/interfaces/QuickFix.md +1 -1
  165. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  166. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  167. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  168. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  169. package/docs/api/interfaces/RBACConfig.md +1 -1
  170. package/docs/api/interfaces/RBACContext.md +1 -1
  171. package/docs/api/interfaces/RBACLogger.md +1 -1
  172. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  173. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  174. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  175. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  176. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  177. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  178. package/docs/api/interfaces/RBACResult.md +1 -1
  179. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  180. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  181. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  182. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  183. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  184. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  185. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  186. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  187. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  188. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  189. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  190. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  191. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  192. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  193. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  194. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  195. package/docs/api/interfaces/RouteConfig.md +1 -1
  196. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  197. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  198. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  199. package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
  200. package/docs/api/interfaces/SetupIssue.md +1 -1
  201. package/docs/api/interfaces/StorageConfig.md +1 -1
  202. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  203. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  204. package/docs/api/interfaces/StorageListOptions.md +1 -1
  205. package/docs/api/interfaces/StorageListResult.md +1 -1
  206. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  207. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  208. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  209. package/docs/api/interfaces/StyleImport.md +1 -1
  210. package/docs/api/interfaces/SwitchProps.md +1 -1
  211. package/docs/api/interfaces/TabsContentProps.md +1 -1
  212. package/docs/api/interfaces/TabsListProps.md +1 -1
  213. package/docs/api/interfaces/TabsProps.md +1 -1
  214. package/docs/api/interfaces/TabsTriggerProps.md +3 -3
  215. package/docs/api/interfaces/TextareaProps.md +1 -1
  216. package/docs/api/interfaces/ToastActionElement.md +4 -1
  217. package/docs/api/interfaces/ToastProps.md +1 -1
  218. package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
  219. package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
  220. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  221. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  222. package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
  223. package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
  224. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  225. package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
  226. package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
  227. package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
  228. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
  229. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
  230. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
  231. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  232. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  233. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  234. package/docs/api/interfaces/UserEventAccess.md +14 -11
  235. package/docs/api/interfaces/UserMenuProps.md +8 -6
  236. package/docs/api/interfaces/UserProfile.md +1 -1
  237. package/docs/api/modules.md +575 -634
  238. package/docs/architecture/database-schema-requirements.md +161 -0
  239. package/docs/core-concepts/rbac-system.md +3 -3
  240. package/docs/documentation-index.md +2 -4
  241. package/docs/getting-started/cursor-rules.md +263 -0
  242. package/docs/getting-started/installation-guide.md +6 -1
  243. package/docs/getting-started/quick-start.md +6 -1
  244. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  245. package/docs/migration/MIGRATION_GUIDE.md +6 -28
  246. package/docs/migration/README.md +52 -6
  247. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  248. package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
  249. package/docs/migration/database-changes-december-2025.md +3 -3
  250. package/docs/rbac/event-based-apps.md +1 -1
  251. package/docs/rbac/getting-started.md +1 -1
  252. package/docs/rbac/quick-start.md +1 -1
  253. package/docs/standards/README.md +40 -0
  254. package/docs/troubleshooting/migration.md +4 -4
  255. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  256. package/package.json +12 -6
  257. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  258. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  259. package/scripts/audit/core/checks/bundle.cjs +142 -0
  260. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
  261. package/scripts/audit/core/checks/config.cjs +54 -0
  262. package/scripts/audit/core/checks/coverage.cjs +84 -0
  263. package/scripts/audit/core/checks/dependencies.cjs +454 -0
  264. package/scripts/audit/core/checks/documentation.cjs +203 -0
  265. package/scripts/audit/core/checks/environment.cjs +128 -0
  266. package/scripts/audit/core/checks/error-handling.cjs +299 -0
  267. package/scripts/audit/core/checks/forms.cjs +172 -0
  268. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  269. package/scripts/audit/core/checks/hooks.cjs +334 -0
  270. package/scripts/audit/core/checks/imports.cjs +244 -0
  271. package/scripts/audit/core/checks/performance.cjs +325 -0
  272. package/scripts/audit/core/checks/routes.cjs +117 -0
  273. package/scripts/audit/core/checks/state.cjs +130 -0
  274. package/scripts/audit/core/checks/structure.cjs +65 -0
  275. package/scripts/audit/core/checks/style.cjs +584 -0
  276. package/scripts/audit/core/checks/testing.cjs +122 -0
  277. package/scripts/audit/core/checks/typescript.cjs +61 -0
  278. package/scripts/audit/core/scanner.cjs +199 -0
  279. package/scripts/audit/core/utils.cjs +137 -0
  280. package/scripts/audit/index.cjs +223 -0
  281. package/scripts/audit/reporters/console.cjs +151 -0
  282. package/scripts/audit/reporters/json.cjs +54 -0
  283. package/scripts/audit/reporters/markdown.cjs +124 -0
  284. package/scripts/audit-consuming-app.cjs +86 -0
  285. package/scripts/build-docs/build-decision.js +240 -0
  286. package/scripts/build-docs/cache-utils.js +105 -0
  287. package/scripts/build-docs/content-normalization.js +150 -0
  288. package/scripts/build-docs/file-utils.js +105 -0
  289. package/scripts/build-docs/git-utils.js +86 -0
  290. package/scripts/build-docs/hash-utils.js +116 -0
  291. package/scripts/build-docs/typedoc-runner.js +220 -0
  292. package/scripts/build-docs-incremental.js +77 -913
  293. package/scripts/install-cursor-rules.cjs +236 -0
  294. package/scripts/utils/command-runner.js +16 -11
  295. package/scripts/validate-formats.js +61 -56
  296. package/scripts/validate-master.js +74 -69
  297. package/scripts/validate-pre-publish.js +70 -65
  298. package/src/__tests__/helpers/test-providers.tsx +1 -1
  299. package/src/__tests__/helpers/test-utils.tsx +1 -1
  300. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  301. package/src/components/Alert/Alert.test.tsx +12 -18
  302. package/src/components/Alert/Alert.tsx +5 -7
  303. package/src/components/Avatar/Avatar.test.tsx +4 -4
  304. package/src/components/Badge/Badge.tsx +16 -4
  305. package/src/components/Button/Button.tsx +27 -4
  306. package/src/components/Calendar/Calendar.tsx +9 -3
  307. package/src/components/Card/Card.tsx +4 -0
  308. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  309. package/src/components/Checkbox/Checkbox.tsx +2 -2
  310. package/src/components/DataTable/DataTable.test.tsx +57 -93
  311. package/src/components/DataTable/DataTable.tsx +40 -6
  312. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  313. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
  314. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  315. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  316. package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
  317. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  318. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  319. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  320. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  321. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  322. package/src/components/DataTable/components/DataTableCore.tsx +200 -561
  323. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  324. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  325. package/src/components/DataTable/components/DataTableModals.tsx +9 -1
  326. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  327. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  328. package/src/components/DataTable/components/EditFields.tsx +307 -0
  329. package/src/components/DataTable/components/EditableRow.tsx +9 -1
  330. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  331. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  332. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  333. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  334. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  335. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  336. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  337. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  338. package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
  339. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  340. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  341. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  342. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  343. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  344. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  345. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  346. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  347. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  348. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  349. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  350. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  351. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  352. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  353. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  354. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  355. package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
  356. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  357. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  358. package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
  359. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  360. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  361. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  362. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  363. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  364. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  365. package/src/components/DataTable/styles.ts +6 -6
  366. package/src/components/DataTable/types.ts +6 -10
  367. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  368. package/src/components/DataTable/utils/debugTools.ts +18 -113
  369. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  370. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  371. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  372. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  373. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  374. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  375. package/src/components/Dialog/Dialog.tsx +8 -7
  376. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  377. package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
  378. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  379. package/src/components/ErrorBoundary/index.ts +27 -2
  380. package/src/components/EventSelector/EventSelector.tsx +4 -1
  381. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  382. package/src/components/FileDisplay/FileDisplay.tsx +32 -18
  383. package/src/components/FileUpload/FileUpload.tsx +22 -2
  384. package/src/components/Footer/Footer.test.tsx +16 -16
  385. package/src/components/Footer/Footer.tsx +15 -12
  386. package/src/components/Form/Form.test.tsx +36 -15
  387. package/src/components/Form/Form.tsx +31 -26
  388. package/src/components/Header/Header.tsx +22 -11
  389. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  390. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  391. package/src/components/Input/Input.test.tsx +2 -2
  392. package/src/components/Input/Input.tsx +36 -34
  393. package/src/components/Label/Label.tsx +1 -1
  394. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  395. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  396. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  397. package/src/components/LoginForm/LoginForm.tsx +12 -8
  398. package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
  399. package/src/components/NavigationMenu/types.ts +56 -0
  400. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  401. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
  402. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  403. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
  404. package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
  405. package/src/components/PaceAppLayout/README.md +1 -1
  406. package/src/components/PaceAppLayout/test-setup.tsx +1 -2
  407. package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
  408. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  409. package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
  410. package/src/components/Progress/Progress.tsx +1 -1
  411. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  412. package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
  413. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  414. package/src/components/Select/Select.tsx +95 -438
  415. package/src/components/Select/context.ts +23 -0
  416. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  417. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  418. package/src/components/Select/hooks/useSelectState.ts +104 -0
  419. package/src/components/Select/index.ts +9 -1
  420. package/src/components/Select/types.ts +123 -0
  421. package/src/components/Select/utils/text.ts +26 -0
  422. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
  423. package/src/components/Switch/Switch.tsx +4 -4
  424. package/src/components/Table/Table.tsx +1 -1
  425. package/src/components/Tabs/Tabs.tsx +1 -1
  426. package/src/components/Textarea/Textarea.tsx +27 -29
  427. package/src/components/Toast/Toast.tsx +5 -1
  428. package/src/components/Tooltip/Tooltip.tsx +3 -3
  429. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  430. package/src/components/UserMenu/UserMenu.tsx +22 -19
  431. package/src/components/index.ts +2 -2
  432. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  433. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  434. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  435. package/src/hooks/index.ts +1 -2
  436. package/src/hooks/public/usePublicEvent.ts +5 -1
  437. package/src/hooks/public/usePublicEventLogo.ts +5 -1
  438. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  439. package/src/hooks/public/usePublicRouteParams.ts +5 -1
  440. package/src/hooks/services/useAuth.ts +32 -0
  441. package/src/hooks/services/useCurrentEvent.ts +6 -0
  442. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  443. package/src/hooks/useDataTableState.ts +8 -18
  444. package/src/hooks/useDebounce.ts +9 -0
  445. package/src/hooks/useEventTheme.ts +6 -0
  446. package/src/hooks/useFileDisplay.ts +4 -0
  447. package/src/hooks/useFileReference.ts +25 -7
  448. package/src/hooks/useFileUrl.ts +11 -1
  449. package/src/hooks/useFocusManagement.ts +16 -2
  450. package/src/hooks/useFocusTrap.ts +7 -4
  451. package/src/hooks/useFormDialog.ts +8 -7
  452. package/src/hooks/useInactivityTracker.ts +4 -1
  453. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  454. package/src/hooks/useOrganisationPermissions.ts +4 -0
  455. package/src/hooks/useOrganisationSecurity.ts +4 -0
  456. package/src/hooks/usePerformanceMonitor.ts +4 -0
  457. package/src/hooks/usePermissionCache.ts +8 -1
  458. package/src/hooks/useQueryCache.ts +12 -1
  459. package/src/hooks/useSessionRestoration.ts +4 -0
  460. package/src/hooks/useStorage.ts +4 -0
  461. package/src/hooks/useToast.ts +3 -3
  462. package/src/index.ts +2 -1
  463. package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
  464. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  465. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  466. package/src/providers/services/AuthServiceProvider.tsx +18 -0
  467. package/src/providers/services/EventServiceProvider.tsx +18 -0
  468. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  469. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  470. package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
  471. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
  472. package/src/rbac/README.md +1 -1
  473. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
  474. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  475. package/src/rbac/adapters.tsx +14 -5
  476. package/src/rbac/api.ts +100 -67
  477. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  478. package/src/rbac/components/NavigationGuard.tsx +1 -1
  479. package/src/rbac/components/NavigationProvider.tsx +5 -2
  480. package/src/rbac/components/PagePermissionGuard.tsx +158 -18
  481. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  482. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  483. package/src/rbac/components/RoleBasedRouter.tsx +6 -2
  484. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  485. package/src/rbac/components/SecureDataProvider.tsx +21 -6
  486. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  487. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  488. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  489. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  490. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  491. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  492. package/src/rbac/engine.ts +38 -14
  493. package/src/rbac/hooks/permissions/index.ts +7 -0
  494. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  495. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  496. package/src/rbac/hooks/permissions/useCan.ts +347 -0
  497. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  498. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  499. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  500. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  501. package/src/rbac/hooks/useCan.test.ts +71 -64
  502. package/src/rbac/hooks/usePermissions.ts +14 -995
  503. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  504. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  505. package/src/rbac/hooks/useSecureSupabase.ts +33 -13
  506. package/src/rbac/permissions.ts +0 -30
  507. package/src/rbac/secureClient.ts +212 -61
  508. package/src/rbac/types.ts +8 -0
  509. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  510. package/src/theming/parseEventColours.ts +5 -19
  511. package/src/types/vitest-globals.d.ts +51 -26
  512. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  513. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  514. package/src/utils/__tests__/index.unit.test.ts +2 -2
  515. package/src/utils/audit/audit.ts +0 -3
  516. package/src/utils/core/cn.ts +1 -1
  517. package/src/utils/file-reference/index.ts +53 -1
  518. package/src/utils/formatting/formatting.ts +8 -18
  519. package/src/utils/index.ts +0 -1
  520. package/src/utils/security/secureDataAccess.test.ts +31 -20
  521. package/src/utils/security/secureDataAccess.ts +4 -3
  522. package/dist/chunk-6C4YBBJM.js +0 -628
  523. package/dist/chunk-6C4YBBJM.js.map +0 -1
  524. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  525. package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
  526. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  527. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  528. package/dist/chunk-7FLMSG37.js.map +0 -1
  529. package/dist/chunk-BC4IJKSL.js.map +0 -1
  530. package/dist/chunk-E3SPN4VZ.js +0 -12917
  531. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  532. package/dist/chunk-E66EQZE6 5.js +0 -37
  533. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  534. package/dist/chunk-HWIIPPNI.js.map +0 -1
  535. package/dist/chunk-I7PSE6JW 5.js +0 -191
  536. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  537. package/dist/chunk-I7PSE6JW.js.map +0 -1
  538. package/dist/chunk-IIELH4DL.js.map +0 -1
  539. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  540. package/dist/chunk-KNC55RTG.js.map +0 -1
  541. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  542. package/dist/chunk-LFNCN2SP.js +0 -412
  543. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  544. package/dist/chunk-LFNCN2SP.js.map +0 -1
  545. package/dist/chunk-LMC26NLJ 2.js +0 -84
  546. package/dist/chunk-NOAYCWCX.js +0 -4993
  547. package/dist/chunk-NOAYCWCX.js.map +0 -1
  548. package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
  549. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  550. package/dist/chunk-QXHPKYJV 3.js +0 -113
  551. package/dist/chunk-R77UEZ4E.js +0 -68
  552. package/dist/chunk-R77UEZ4E.js.map +0 -1
  553. package/dist/chunk-SQGMNID3.js.map +0 -1
  554. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  555. package/dist/chunk-XNXXZ43G.js.map +0 -1
  556. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  557. package/dist/components.js 5.map +0 -1
  558. package/dist/styles/index 2.js +0 -12
  559. package/dist/styles/index.js 5.map +0 -1
  560. package/dist/theming/runtime 5.js +0 -19
  561. package/dist/theming/runtime.js 5.map +0 -1
  562. package/docs/api/classes/ErrorBoundary.md +0 -144
  563. package/docs/migration/quick-migration-guide.md +0 -356
  564. package/docs/migration/service-architecture.md +0 -281
  565. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  566. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  567. package/src/hooks/useSecureDataAccess.ts +0 -666
  568. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
  569. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
  570. /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
  571. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  572. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  573. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  574. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  575. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  576. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  577. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -19,26 +19,44 @@ import { OrganisationContextRequiredError } from './types';
19
19
  * This client automatically injects organisation context into all requests
20
20
  * and prevents queries that don't have the required context.
21
21
  *
22
- * Note: Callers should derive organisationId from eventId before creating this client
23
- * if working with event-required apps. The client requires organisationId.
22
+ * Note: For non-super-admins, organisationId is required. Super-admins can operate
23
+ * without organisationId to access system-wide tables (like core_organisations).
24
+ * Callers should derive organisationId from eventId before creating this client
25
+ * if working with event-required apps.
24
26
  */
25
27
  export class SecureSupabaseClient {
26
28
  private supabase: SupabaseClient<Database>;
27
29
  private edgeFunctionClient: SupabaseClient<Database> | null = null;
28
30
  private supabaseUrl: string;
29
31
  private supabaseKey: string;
30
- private organisationId: UUID;
32
+ private organisationId: UUID | null;
31
33
  private eventId?: string;
32
34
  private appId?: UUID;
33
35
  private isSuperAdmin: boolean;
36
+ private usesExistingClient: boolean = false;
37
+
38
+ /**
39
+ * RPC functions that are safe to call without organisation context.
40
+ *
41
+ * These functions must:
42
+ * - rely on JWT context (auth.uid()) for authentication
43
+ * - not read or write organisation-scoped data
44
+ *
45
+ * This allowlist enables compliant consuming apps to use `secureSupabase.rpc(...)`
46
+ * even before an organisation is selected (common during initial page load/refresh).
47
+ */
48
+ private static readonly GLOBAL_RPC_ALLOWLIST = new Set<string>([
49
+ 'data_rbac_apps_list',
50
+ ]);
34
51
 
35
52
  constructor(
36
53
  supabaseUrl: string,
37
54
  supabaseKey: string,
38
- organisationId: UUID,
55
+ organisationId: UUID | null,
39
56
  eventId?: string,
40
57
  appId?: UUID,
41
- isSuperAdmin: boolean = false
58
+ isSuperAdmin: boolean = false,
59
+ existingClient?: SupabaseClient<Database>
42
60
  ) {
43
61
  this.supabaseUrl = supabaseUrl;
44
62
  this.supabaseKey = supabaseKey;
@@ -47,18 +65,25 @@ export class SecureSupabaseClient {
47
65
  this.appId = appId;
48
66
  this.isSuperAdmin = isSuperAdmin;
49
67
 
50
- // Create the base Supabase client with context headers
51
- // Note: We'll override functions.invoke to exclude headers for Edge Functions
52
- // as they may not have CORS configured to accept custom headers
53
- this.supabase = createClient<Database>(supabaseUrl, supabaseKey, {
54
- global: {
55
- headers: {
56
- 'x-organisation-id': organisationId,
57
- 'x-event-id': eventId || '',
58
- 'x-app-id': appId || '',
68
+ // Prefer reusing an existing authenticated client (avoids multiple GoTrue instances
69
+ // and ensures auth context is present for RLS policies).
70
+ if (existingClient) {
71
+ this.supabase = existingClient;
72
+ this.usesExistingClient = true;
73
+ } else {
74
+ // Create the base Supabase client with context headers
75
+ // Note: We'll override functions.invoke to exclude headers for Edge Functions
76
+ // as they may not have CORS configured to accept custom headers
77
+ this.supabase = createClient<Database>(supabaseUrl, supabaseKey, {
78
+ global: {
79
+ headers: {
80
+ 'x-organisation-id': organisationId || '',
81
+ 'x-event-id': eventId || '',
82
+ 'x-app-id': appId || '',
83
+ },
59
84
  },
60
- },
61
- });
85
+ });
86
+ }
62
87
 
63
88
  // Override the auth methods to inject context
64
89
  this.setupContextInjection();
@@ -75,8 +100,10 @@ export class SecureSupabaseClient {
75
100
  const originalFrom = this.supabase.from.bind(this.supabase);
76
101
 
77
102
  (this.supabase as any).from = (table: string): any => {
78
- // Validate context before allowing any database operations
79
- this.validateContext();
103
+ // Validate context before allowing database operations.
104
+ // Some tables are not organisation-scoped (e.g. rbac_apps) and must be queryable
105
+ // without organisation context for initial bootstrapping.
106
+ this.validateContextForTable(table);
80
107
 
81
108
  // Type assertion needed because table is a string but Supabase expects specific table names
82
109
  const query = originalFrom(table as any);
@@ -94,15 +121,25 @@ export class SecureSupabaseClient {
94
121
  // Type assertion needed because we're wrapping the generic rpc method
95
122
  // The fn parameter is typed as string to match Supabase's rpc signature
96
123
  (this.supabase as any).rpc = (fn: string, args?: any, options?: any): any => {
97
- // Validate context before allowing any RPC calls
98
- this.validateContext();
124
+ // Validate context before allowing RPC calls.
125
+ // Some RPCs are global (not organisation-scoped) but still require auth.uid() from JWT.
126
+ // Allow these even without organisation context.
127
+ this.validateContextForRpc(fn);
99
128
 
100
129
  // Inject context into RPC calls
130
+ // Only include organisation_id if it's available (super-admins may not have it)
131
+ // IMPORTANT:
132
+ // Do NOT overwrite explicitly provided RPC parameters.
133
+ // Some RPCs legitimately take `p_app_id`/`p_event_id` as the *target* entity,
134
+ // which may differ from the current RBAC scope app/event.
135
+ const safeArgs = (args ?? {}) as Record<string, unknown>;
101
136
  const contextArgs = {
102
- ...args,
103
- p_organisation_id: this.organisationId,
104
- p_event_id: this.eventId,
105
- p_app_id: this.appId,
137
+ ...safeArgs,
138
+ ...(this.organisationId && safeArgs.p_organisation_id === undefined
139
+ ? { p_organisation_id: this.organisationId }
140
+ : {}),
141
+ ...(this.eventId && safeArgs.p_event_id === undefined ? { p_event_id: this.eventId } : {}),
142
+ ...(this.appId && safeArgs.p_app_id === undefined ? { p_app_id: this.appId } : {}),
106
143
  };
107
144
 
108
145
  return originalRpc(fn as any, contextArgs, options);
@@ -119,10 +156,19 @@ export class SecureSupabaseClient {
119
156
  * This avoids interfering with the main client's operations.
120
157
  */
121
158
  private setupEdgeFunctionHandling() {
122
- // Create a separate client without custom headers for Edge Functions
123
- // This prevents CORS errors when Edge Functions don't accept custom headers
124
- // Store it as an instance variable to avoid creating multiple clients
125
- // We'll use this client directly for Edge Function calls instead of overriding
159
+ // IMPORTANT:
160
+ // Do not create a second Supabase client when we are already reusing an authenticated
161
+ // base client (this triggers "Multiple GoTrueClient instances" warnings and can cause
162
+ // session/auth desync that breaks RLS-protected reads).
163
+ //
164
+ // If we're using an existing client, just use it for Edge Functions too.
165
+ if (this.usesExistingClient) {
166
+ this.edgeFunctionClient = null;
167
+ return;
168
+ }
169
+
170
+ // Otherwise, create a separate client without the custom RBAC headers for Edge Functions.
171
+ // This prevents CORS errors when Edge Functions don't accept custom headers.
126
172
  this.edgeFunctionClient = createClient<Database>(this.supabaseUrl, this.supabaseKey);
127
173
  }
128
174
 
@@ -173,13 +219,28 @@ export class SecureSupabaseClient {
173
219
  return originalInsert(values);
174
220
  }
175
221
  // Non-super-admin: Add organisation_id as defense in depth
222
+ // organisationId should always be available for non-super-admins (validateContext ensures this)
223
+ if (!this.organisationId) {
224
+ throw new OrganisationContextRequiredError();
225
+ }
176
226
  const contextValues = Array.isArray(values)
177
227
  ? values.map(v => ({ ...v, organisation_id: this.organisationId }))
178
228
  : { ...values, organisation_id: this.organisationId };
179
229
  return originalInsert(contextValues);
180
230
  }
181
231
 
182
- // For other tables, always add organisation_id
232
+ // For other tables, add organisation_id if available
233
+ // Super-admins might not have organisationId set, so allow them to set it explicitly
234
+ if (this.isSuperAdmin && !this.organisationId) {
235
+ // Super admin without organisationId: Don't force it (can be set explicitly if needed)
236
+ return originalInsert(values);
237
+ }
238
+
239
+ // Non-super-admin or super-admin with organisationId: Add organisation_id
240
+ // organisationId should always be available for non-super-admins (validateContext ensures this)
241
+ if (!this.organisationId) {
242
+ throw new OrganisationContextRequiredError();
243
+ }
183
244
  const contextValues = Array.isArray(values)
184
245
  ? values.map(v => ({ ...v, organisation_id: this.organisationId }))
185
246
  : { ...values, organisation_id: this.organisationId };
@@ -213,6 +274,10 @@ export class SecureSupabaseClient {
213
274
  * - Super admins: No org filter (see all users) - RLS will allow access
214
275
  * - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
215
276
  *
277
+ * For system-wide tables (like core_organisations):
278
+ * - Super admins: No org filter (see all records) - RLS will allow access
279
+ * - Non-super-admins: Apply org filter as defense in depth - RLS will also filter
280
+ *
216
281
  * For other tables:
217
282
  * - Always apply org filter unless super admin bypasses it
218
283
  */
@@ -223,6 +288,18 @@ export class SecureSupabaseClient {
223
288
  'rbac_apps', // App configuration table - no organisation scope
224
289
  'rbac_app_pages', // Page configuration table - scoped by app_id, not organisation_id
225
290
  'rbac_global_roles', // Global roles - no organisation scope
291
+ // Person-scoped tables (organisation_id was removed in person-scoped profiles migration)
292
+ 'core_person', // Person records - person-scoped, no organisation_id
293
+ 'core_member', // Member profiles - person-scoped, no organisation_id
294
+ 'core_contact', // Contact profiles - person-scoped, no organisation_id
295
+ 'core_consent', // Consent records - person-scoped, no organisation_id
296
+ 'core_identification', // Identification records - person-scoped, no organisation_id
297
+ 'core_qualification', // Qualification records - person-scoped, no organisation_id
298
+ 'medi_profile', // Medical profiles - person-scoped, no organisation_id
299
+ 'medi_condition', // Medical conditions - person-scoped via medi_profile, no organisation_id
300
+ 'medi_diet', // Medical diets - person-scoped via medi_profile, no organisation_id
301
+ 'medi_action_plan', // Medical action plans - person-scoped via medi_profile, no organisation_id
302
+ 'medi_profile_versions', // Medical profile versions - person-scoped via medi_profile, no organisation_id
226
303
  ];
227
304
 
228
305
  // Skip organisation filter for tables that don't have organisation_id column
@@ -235,6 +312,17 @@ export class SecureSupabaseClient {
235
312
  return query;
236
313
  }
237
314
 
315
+ // System-wide tables that super-admins should be able to query without organisation filters
316
+ // These tables have organisation_id but super-admins need to see all records
317
+ const systemWideTablesForSuperAdmins = [
318
+ 'core_organisations', // Super-admins need to see all organisations
319
+ ];
320
+
321
+ // For system-wide tables, super-admins bypass organisation filter
322
+ if (systemWideTablesForSuperAdmins.includes(tableName) && this.isSuperAdmin) {
323
+ return query; // No filter - RLS handles access control
324
+ }
325
+
238
326
  // For rbac_user_profiles, use conditional filtering based on super admin status
239
327
  if (tableName === 'rbac_user_profiles') {
240
328
  // Super admins: No org filter (see all users via RLS)
@@ -261,17 +349,69 @@ export class SecureSupabaseClient {
261
349
 
262
350
  /**
263
351
  * Validate that required context is present
352
+ * Super-admins can operate without organisation context
264
353
  */
265
354
  private validateContext() {
355
+ // Super-admins can operate without organisation context
356
+ if (this.isSuperAdmin) {
357
+ return;
358
+ }
359
+
266
360
  if (!this.organisationId) {
267
361
  throw new OrganisationContextRequiredError();
268
362
  }
269
363
  }
270
364
 
365
+ /**
366
+ * Determine whether a table requires organisation context.
367
+ * Tables without an organisation_id column (or global configuration tables) are safe without org context.
368
+ */
369
+ private tableRequiresOrganisationContext(tableName: string): boolean {
370
+ // Keep this list aligned with the tables handled in `addOrganisationFilter` / `injectContext`.
371
+ const tablesWithoutOrganisationId = new Set<string>([
372
+ 'core_organisations',
373
+ 'rbac_apps',
374
+ 'rbac_app_pages',
375
+ 'rbac_global_roles',
376
+ 'core_person',
377
+ 'core_member',
378
+ 'core_contact',
379
+ 'core_consent',
380
+ 'core_identification',
381
+ 'core_qualification',
382
+ 'medi_profile',
383
+ 'medi_condition',
384
+ 'medi_diet',
385
+ 'medi_action_plan',
386
+ 'medi_profile_versions',
387
+ ]);
388
+
389
+ return !tablesWithoutOrganisationId.has(tableName);
390
+ }
391
+
392
+ /**
393
+ * Validate context for a specific table operation.
394
+ */
395
+ private validateContextForTable(tableName: string) {
396
+ if (this.isSuperAdmin) return;
397
+ if (!this.organisationId && this.tableRequiresOrganisationContext(tableName)) {
398
+ throw new OrganisationContextRequiredError();
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Validate context for a specific RPC call.
404
+ */
405
+ private validateContextForRpc(fn: string) {
406
+ if (this.isSuperAdmin) return;
407
+ if (SecureSupabaseClient.GLOBAL_RPC_ALLOWLIST.has(fn)) return;
408
+ this.validateContext();
409
+ }
410
+
271
411
  /**
272
412
  * Get the current organisation ID
273
413
  */
274
- getOrganisationId(): UUID {
414
+ getOrganisationId(): UUID | null {
275
415
  return this.organisationId;
276
416
  }
277
417
 
@@ -293,7 +433,7 @@ export class SecureSupabaseClient {
293
433
  * Create a new client with updated context
294
434
  */
295
435
  withContext(updates: {
296
- organisationId?: UUID;
436
+ organisationId?: UUID | null;
297
437
  eventId?: string;
298
438
  appId?: UUID;
299
439
  isSuperAdmin?: boolean;
@@ -301,7 +441,7 @@ export class SecureSupabaseClient {
301
441
  return new SecureSupabaseClient(
302
442
  this.supabaseUrl,
303
443
  this.supabaseKey,
304
- updates.organisationId || this.organisationId,
444
+ updates.organisationId !== undefined ? updates.organisationId : this.organisationId,
305
445
  updates.eventId !== undefined ? updates.eventId : this.eventId,
306
446
  updates.appId !== undefined ? updates.appId : this.appId,
307
447
  updates.isSuperAdmin !== undefined ? updates.isSuperAdmin : this.isSuperAdmin
@@ -329,33 +469,43 @@ export class SecureSupabaseClient {
329
469
  }
330
470
  }
331
471
 
332
- /**
333
- * Create a secure Supabase client with organisation context
334
- *
335
- * @param supabaseUrl - Supabase project URL
336
- * @param supabaseKey - Supabase publishable key or anon key (accepts both legacy anon keys and modern publishable keys)
337
- * @param organisationId - Required organisation ID
338
- * @param eventId - Optional event ID
339
- * @param appId - Optional app ID
340
- * @param isSuperAdmin - Optional super admin flag (defaults to false)
341
- * @returns SecureSupabaseClient instance
342
- *
343
- * @example
344
- * ```typescript
345
- * const client = createSecureClient(
346
- * 'https://your-project.supabase.co',
347
- * 'your-publishable-key-or-anon-key',
348
- * 'org-123',
349
- * 'event-456',
350
- * 'app-789',
351
- * false // isSuperAdmin
352
- * );
353
- * ```
354
- */
472
+ /**
473
+ * Create a secure Supabase client with organisation context
474
+ *
475
+ * @param supabaseUrl - Supabase project URL
476
+ * @param supabaseKey - Supabase publishable key or anon key (accepts both legacy anon keys and modern publishable keys)
477
+ * @param organisationId - Organisation ID (optional for super-admins)
478
+ * @param eventId - Optional event ID
479
+ * @param appId - Optional app ID
480
+ * @param isSuperAdmin - Optional super admin flag (defaults to false). When true, organisationId can be null.
481
+ * @returns SecureSupabaseClient instance
482
+ *
483
+ * @example
484
+ * ```typescript
485
+ * const client = createSecureClient(
486
+ * 'https://your-project.supabase.co',
487
+ * 'your-publishable-key-or-anon-key',
488
+ * 'org-123',
489
+ * 'event-456',
490
+ * 'app-789',
491
+ * false // isSuperAdmin
492
+ * );
493
+ *
494
+ * // For super-admins, organisationId can be null
495
+ * const superAdminClient = createSecureClient(
496
+ * 'https://your-project.supabase.co',
497
+ * 'your-publishable-key-or-anon-key',
498
+ * null, // organisationId not required for super-admins
499
+ * undefined,
500
+ * undefined,
501
+ * true // isSuperAdmin
502
+ * );
503
+ * ```
504
+ */
355
505
  export function createSecureClient(
356
506
  supabaseUrl: string,
357
507
  supabaseKey: string,
358
- organisationId: UUID,
508
+ organisationId: UUID | null,
359
509
  eventId?: string,
360
510
  appId?: UUID,
361
511
  isSuperAdmin: boolean = false
@@ -374,11 +524,12 @@ export function createSecureClient(
374
524
  */
375
525
  export function fromSupabaseClient(
376
526
  client: SupabaseClient<Database>,
377
- organisationId: UUID,
527
+ organisationId: UUID | null,
378
528
  eventId?: string,
379
- appId?: UUID
529
+ appId?: UUID,
530
+ isSuperAdmin: boolean = false
380
531
  ): SecureSupabaseClient {
381
- // We need the URL and key to create a new client, but they're not accessible
382
- // This function should be used with createSecureClient instead
383
- throw new Error('fromSupabaseClient is not supported. Use createSecureClient instead.');
532
+ // Wrap the existing client to reuse auth/session while enforcing organisation/event/app context.
533
+ // URL/key are unused in this mode.
534
+ return new SecureSupabaseClient('', '', organisationId, eventId, appId, isSuperAdmin, client);
384
535
  }
package/src/rbac/types.ts CHANGED
@@ -28,12 +28,20 @@ export type AccessLevel =
28
28
  | 'admin'
29
29
  | 'super';
30
30
 
31
+ /**
32
+ * Scope defines the context for permission checks.
33
+ * Can include organisation, event, and/or app identifiers.
34
+ */
31
35
  export type Scope = {
32
36
  organisationId?: UUID;
33
37
  eventId?: string; // event_id is text/varchar
34
38
  appId?: AppId | UUID;
35
39
  };
36
40
 
41
+ /**
42
+ * Permission check request parameters.
43
+ * Defines who (userId) is checking what permission in what context (scope).
44
+ */
37
45
  export type PermissionCheck = {
38
46
  userId: UUID;
39
47
  scope: Scope;
@@ -87,8 +87,8 @@ describe('parseAndNormalizeEventColours', () => {
87
87
  });
88
88
  });
89
89
 
90
- describe('Legacy format (ev-main/ev-sec/ev-acc keys)', () => {
91
- it('parses legacy format with ev-main, ev-sec, ev-acc keys', () => {
90
+ describe('Standard format only', () => {
91
+ it('returns null for legacy format with ev-main, ev-sec, ev-acc keys', () => {
92
92
  const input = {
93
93
  'ev-main': { 500: { L: 0.5, C: 0.2, H: 0 } },
94
94
  'ev-sec': { 500: { L: 0.5, C: 0.2, H: 120 } },
@@ -96,13 +96,11 @@ describe('parseAndNormalizeEventColours', () => {
96
96
  };
97
97
 
98
98
  const result = parseAndNormalizeEventColours(input);
99
- expect(result).not.toBeNull();
100
- expect(result?.main).toBeDefined();
101
- expect(result?.sec).toBeDefined();
102
- expect(result?.acc).toBeDefined();
99
+ // Legacy format is no longer supported - returns null
100
+ expect(result).toBeNull();
103
101
  });
104
102
 
105
- it('prefers standard keys over legacy keys when both exist', () => {
103
+ it('uses standard keys when provided', () => {
106
104
  const input = {
107
105
  main: { 500: { L: 0.5, C: 0.2, H: 0 } },
108
106
  'ev-main': { 500: { L: 0.7, C: 0.3, H: 10 } }
@@ -110,8 +108,7 @@ describe('parseAndNormalizeEventColours', () => {
110
108
 
111
109
  const result = parseAndNormalizeEventColours(input);
112
110
  expect(result).not.toBeNull();
113
- // Implementation prefers standard keys ('main') over legacy keys ('ev-main')
114
- // So when both exist, 'main' is used
111
+ // Only standard keys are used - legacy keys are ignored
115
112
  expect(result?.main[500]).toEqual({ L: 0.5, C: 0.2, H: 0 });
116
113
  });
117
114
  });
@@ -15,9 +15,8 @@ const log = createLogger('ParseEventColours');
15
15
  /**
16
16
  * Parse and normalize event_colours to PaletteData
17
17
  *
18
- * Supports multiple input formats:
18
+ * Supports input formats:
19
19
  * - Object with 'main', 'sec', 'acc' keys
20
- * - Object with 'ev-main', 'ev-sec', 'ev-acc' keys (legacy)
21
20
  * - JSON string that will be parsed
22
21
  *
23
22
  * Only includes explicitly defined color values. Does not fill
@@ -39,17 +38,6 @@ const log = createLogger('ParseEventColours');
39
38
  * // Returns: { main: { 500: {...}, raw: {...} }, sec: { 500: {...} }, acc: { 500: {...} } }
40
39
  * ```
41
40
  *
42
- * @example
43
- * ```ts
44
- * // Legacy format with ev-* keys
45
- * const colours = {
46
- * "ev-main": { 500: { L: 0.5, C: 0.2, H: 0 } },
47
- * "ev-sec": { 500: { L: 0.5, C: 0.2, H: 120 } },
48
- * "ev-acc": { 500: { L: 0.5, C: 0.2, H: 240 } }
49
- * };
50
- * const palette = parseAndNormalizeEventColours(colours);
51
- * // Returns normalized palette with only defined shades included
52
- * ```
53
41
  */
54
42
  export function parseAndNormalizeEventColours(input: unknown): { main: any; sec: any; acc: any } | null {
55
43
  try {
@@ -67,12 +55,10 @@ export function parseAndNormalizeEventColours(input: unknown): { main: any; sec:
67
55
  return null;
68
56
  }
69
57
 
70
- // Support both 'ev-main'/'ev-sec'/'ev-acc' (legacy) and 'main'/'sec'/'acc' (standard) keys
71
- // Prefer standard keys over legacy keys when both exist
72
- const pick = (o: any, standard: string, legacy: string) => (o?.[standard] ?? o?.[legacy]) || null;
73
- const main = pick(obj, 'main', 'ev-main');
74
- const sec = pick(obj, 'sec', 'ev-sec');
75
- const acc = pick(obj, 'acc', 'ev-acc');
58
+ // Use standard 'main'/'sec'/'acc' keys
59
+ const main = obj?.main || null;
60
+ const sec = obj?.sec || null;
61
+ const acc = obj?.acc || null;
76
62
 
77
63
  // If no palette data found, return null
78
64
  if (!main && !sec && !acc) return null;
@@ -1,33 +1,58 @@
1
1
 
2
2
  /// <reference types="vitest/globals" />
3
+ /// <reference types="@testing-library/jest-dom" />
3
4
 
4
- import * as matchers from '@testing-library/jest-dom/matchers';
5
-
6
- // Type definitions for @testing-library/jest-dom
7
- interface CustomMatchers<R = unknown> {
8
- toBeInTheDocument(): R;
9
- toBeVisible(): R;
10
- toBeDisabled(): R;
11
- toHaveAttribute(attr: string, value?: string): R;
12
- toHaveClass(...classNames: string[]): R;
13
- toHaveValue(value: string | string[] | number): R;
14
- toHaveTextContent(text: string | RegExp, options?: { normalizeWhitespace: boolean }): R;
15
- toBeChecked(): R;
16
- toBeEmpty(): R;
17
- toBeInvalid(): R;
18
- toBeRequired(): R;
19
- toBeValid(): R;
20
- toContainElement(element: HTMLElement | null): R;
21
- toContainHTML(htmlText: string): R;
22
- toHaveFocus(): R;
23
- toHaveFormValues(expectedValues: Record<string, unknown>): R;
24
- toHaveStyle(css: string | Record<string, unknown>): R;
25
- toBeEnabled(): R;
26
- }
27
-
5
+ // Type definitions for @testing-library/jest-dom matchers
6
+ // These extend Vitest's Assertion interface with jest-dom matchers
28
7
  declare module 'vitest' {
29
- interface Assertion<T = unknown> extends CustomMatchers<T> {}
30
- interface AsymmetricMatchersContaining extends CustomMatchers {}
8
+ interface Assertion<T = unknown> {
9
+ // DOM matchers
10
+ toBeInTheDocument(): void;
11
+ toBeVisible(): void;
12
+ toBeDisabled(): void;
13
+ toBeEnabled(): void;
14
+ toBeEmpty(): void;
15
+ toBeEmptyDOMElement(): void;
16
+ toBeInvalid(): void;
17
+ toBeValid(): void;
18
+ toBeRequired(): void;
19
+ toBeChecked(): void;
20
+ toHaveFocus(): void;
21
+
22
+ // Attribute and class matchers
23
+ toHaveAttribute(attr: string, value?: string): void;
24
+ toHaveClass(...classNames: string[]): void;
25
+ toHaveStyle(css: string | Record<string, unknown>): void;
26
+
27
+ // Content matchers
28
+ toHaveTextContent(text: string | RegExp, options?: { normalizeWhitespace?: boolean }): void;
29
+ toHaveValue(value: string | string[] | number): void;
30
+ toContainElement(element: HTMLElement | null): void;
31
+ toContainHTML(htmlText: string): void;
32
+ toHaveFormValues(expectedValues: Record<string, unknown>): void;
33
+ }
34
+
35
+ interface AsymmetricMatchersContaining {
36
+ toBeInTheDocument(): void;
37
+ toBeVisible(): void;
38
+ toBeDisabled(): void;
39
+ toBeEnabled(): void;
40
+ toBeEmpty(): void;
41
+ toBeEmptyDOMElement(): void;
42
+ toBeInvalid(): void;
43
+ toBeValid(): void;
44
+ toBeRequired(): void;
45
+ toBeChecked(): void;
46
+ toHaveFocus(): void;
47
+ toHaveAttribute(attr: string, value?: string): void;
48
+ toHaveClass(...classNames: string[]): void;
49
+ toHaveStyle(css: string | Record<string, unknown>): void;
50
+ toHaveTextContent(text: string | RegExp, options?: { normalizeWhitespace?: boolean }): void;
51
+ toHaveValue(value: string | string[] | number): void;
52
+ toContainElement(element: HTMLElement | null): void;
53
+ toContainHTML(htmlText: string): void;
54
+ toHaveFormValues(expectedValues: Record<string, unknown>): void;
55
+ }
31
56
  }
32
57
 
33
58
  // Ensure vitest globals are available
@@ -1,10 +1,8 @@
1
-
1
+ /// <reference types="vitest/globals" />
2
2
  /**
3
3
  * @file Supabase mock for testing
4
4
  */
5
5
 
6
- import { vi } from 'vitest';
7
-
8
6
  export function createMockSupabaseClient(overrides: Record<string, unknown> = {}) {
9
7
  const mockOrder = vi.fn().mockResolvedValue({
10
8
  data: [
@@ -35,7 +35,7 @@ describe('formatting utilities', () => {
35
35
  expect(formatPercent(0.5)).toBe('0.5%');
36
36
  });
37
37
  it('formats as percent with custom decimals', () => {
38
- expect(formatPercent(0.25, 'en-US', 2)).toBe('0.25%');
38
+ expect(formatPercent(0.25, 'en-US', { decimals: 2 })).toBe('0.25%');
39
39
  });
40
40
 
41
41
  // Tests for preserveDecimals functionality
@@ -65,9 +65,9 @@ describe('formatting utilities', () => {
65
65
  expect(formatPercent(0.81, 'en-US', { decimals: 3, preserveDecimals: false })).toBe('0.810%');
66
66
  });
67
67
 
68
- it('maintains backward compatibility with number parameter', () => {
69
- expect(formatPercent(0.81, 'en-US', 1)).toBe('0.8%');
70
- expect(formatPercent(0.25, 'en-US', 2)).toBe('0.25%');
68
+ it('works with explicit decimals option', () => {
69
+ expect(formatPercent(0.81, 'en-US', { decimals: 1 })).toBe('0.8%');
70
+ expect(formatPercent(0.25, 'en-US', { decimals: 2 })).toBe('0.25%');
71
71
  });
72
72
  });
73
73
  });
@@ -137,7 +137,7 @@ describe('utils index exports', () => {
137
137
 
138
138
  describe('Audit utilities', () => {
139
139
  it('should export audit functions', () => {
140
- expect(Utils).toHaveProperty('auditLog');
140
+ expect(Utils).toHaveProperty('auditLogger');
141
141
  expect(Utils).toHaveProperty('logAuditEvent');
142
142
  });
143
143
  });
@@ -237,7 +237,7 @@ describe('utils index exports', () => {
237
237
  'pickSchema', // Schema utilities
238
238
  'securityMonitor', // Security monitoring
239
239
  'useSessionTracking', // Session tracking
240
- 'auditLog', // Audit
240
+ 'auditLogger', // Audit
241
241
  'generateDeviceFingerprint', // Device fingerprinting
242
242
  'formatDate', // Formatting
243
243
  'setOrganisationContext' // Organisation context
@@ -84,9 +84,6 @@ class AuditLogger {
84
84
 
85
85
  export const auditLogger = new AuditLogger();
86
86
 
87
- // Alias for backward compatibility
88
- export const auditLog = auditLogger;
89
-
90
87
  export function logAuthEvent(action: string, user?: string, details?: Record<string, unknown>): void {
91
88
  auditLogger.log({
92
89
  type: 'auth',