@jmruthers/pace-core 0.5.190 → 0.5.193

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 (334) hide show
  1. package/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
  2. package/dist/{DataTable-ON3IXISJ.js → DataTable-5FU7IESH.js} +7 -6
  3. package/dist/{DataTable-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
  4. package/dist/{PublicPageProvider-C4uxosp6.d.ts → PublicPageProvider-C0Sm_e5k.d.ts} +4 -2
  5. package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
  6. package/dist/{UnifiedAuthProvider-X5NXANVI.js → UnifiedAuthProvider-RGJTDE2C.js} +3 -3
  7. package/dist/{api-I6UCQ5S6.js → api-N774RPUA.js} +2 -2
  8. package/dist/chunk-6C4YBBJM 5.js +628 -0
  9. package/dist/chunk-7D4SUZUM.js 2.map +1 -0
  10. package/dist/{chunk-73HSNNOQ.js → chunk-7EQTDTTJ.js} +47 -74
  11. package/dist/chunk-7EQTDTTJ.js 2.map +1 -0
  12. package/dist/chunk-7EQTDTTJ.js.map +1 -0
  13. package/dist/{chunk-J2XXC7R5.js → chunk-7FLMSG37.js} +409 -244
  14. package/dist/chunk-7FLMSG37.js 2.map +1 -0
  15. package/dist/chunk-7FLMSG37.js.map +1 -0
  16. package/dist/{chunk-NIU6J6OX.js → chunk-BC4IJKSL.js} +23 -32
  17. package/dist/chunk-BC4IJKSL.js.map +1 -0
  18. package/dist/{chunk-SDMHPX3X.js → chunk-E3SPN4VZ 5.js } +198 -53
  19. package/dist/chunk-E3SPN4VZ.js +12917 -0
  20. package/dist/{chunk-SDMHPX3X.js.map → chunk-E3SPN4VZ.js.map} +1 -1
  21. package/dist/chunk-E66EQZE6 5.js +37 -0
  22. package/dist/chunk-E66EQZE6.js 2.map +1 -0
  23. package/dist/{chunk-DZWK57KZ.js → chunk-G37KK66H.js} +1 -1
  24. package/dist/{chunk-DZWK57KZ.js.map → chunk-G37KK66H.js.map} +1 -1
  25. package/dist/{chunk-STYK4OH2.js → chunk-HWIIPPNI.js} +44 -225
  26. package/dist/chunk-HWIIPPNI.js.map +1 -0
  27. package/dist/chunk-I7PSE6JW 5.js +191 -0
  28. package/dist/chunk-I7PSE6JW.js 2.map +1 -0
  29. package/dist/{chunk-Y4BUBBHD.js → chunk-IIELH4DL.js} +211 -136
  30. package/dist/chunk-IIELH4DL.js.map +1 -0
  31. package/dist/{chunk-RUYZKXOD.js → chunk-KNC55RTG.js} +17 -5
  32. package/dist/chunk-KNC55RTG.js 5.map +1 -0
  33. package/dist/chunk-KNC55RTG.js.map +1 -0
  34. package/dist/chunk-KQCRWDSA.js 5.map +1 -0
  35. package/dist/{chunk-4QYC5L4K.js → chunk-LFNCN2SP.js} +26 -30
  36. package/dist/chunk-LFNCN2SP.js 2.map +1 -0
  37. package/dist/chunk-LFNCN2SP.js.map +1 -0
  38. package/dist/chunk-LMC26NLJ 2.js +84 -0
  39. package/dist/{chunk-VVBAW5A5.js → chunk-NOAYCWCX 5.js } +118 -110
  40. package/dist/chunk-NOAYCWCX.js +4993 -0
  41. package/dist/chunk-NOAYCWCX.js.map +1 -0
  42. package/dist/chunk-QWWZ5CAQ.js 3.map +1 -0
  43. package/dist/chunk-QXHPKYJV 3.js +113 -0
  44. package/dist/chunk-R77UEZ4E 3.js +68 -0
  45. package/dist/chunk-VBXEHIUJ.js 6.map +1 -0
  46. package/dist/{chunk-HQVPB5MZ.js → chunk-XNXXZ43G.js} +77 -33
  47. package/dist/chunk-XNXXZ43G.js.map +1 -0
  48. package/dist/chunk-ZSAAAMVR 6.js +25 -0
  49. package/dist/components.d.ts +4 -4
  50. package/dist/components.js +8 -8
  51. package/dist/components.js 5.map +1 -0
  52. package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
  53. package/dist/hooks.d.ts +12 -12
  54. package/dist/hooks.js +9 -9
  55. package/dist/index.d.ts +11 -11
  56. package/dist/index.js +20 -27
  57. package/dist/index.js.map +1 -1
  58. package/dist/providers.d.ts +3 -3
  59. package/dist/providers.js +2 -2
  60. package/dist/rbac/index.d.ts +2 -20
  61. package/dist/rbac/index.js +7 -9
  62. package/dist/styles/index 2.js +12 -0
  63. package/dist/styles/index.js 5.map +1 -0
  64. package/dist/theming/runtime 5.js +19 -0
  65. package/dist/theming/runtime.js 5.map +1 -0
  66. package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
  67. package/dist/types.d.ts +2 -2
  68. package/dist/{usePublicRouteParams-DxIDS4bC.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +1 -1
  69. package/dist/utils.d.ts +8 -8
  70. package/dist/utils.js +2 -2
  71. package/docs/api/classes/ColumnFactory.md +1 -1
  72. package/docs/api/classes/ErrorBoundary.md +1 -1
  73. package/docs/api/classes/InvalidScopeError.md +1 -1
  74. package/docs/api/classes/Logger.md +1 -1
  75. package/docs/api/classes/MissingUserContextError.md +1 -1
  76. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  77. package/docs/api/classes/PermissionDeniedError.md +2 -2
  78. package/docs/api/classes/RBACAuditManager.md +2 -2
  79. package/docs/api/classes/RBACCache.md +1 -1
  80. package/docs/api/classes/RBACEngine.md +2 -2
  81. package/docs/api/classes/RBACError.md +1 -1
  82. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  83. package/docs/api/classes/SecureSupabaseClient.md +10 -10
  84. package/docs/api/classes/StorageUtils.md +1 -1
  85. package/docs/api/enums/FileCategory.md +1 -1
  86. package/docs/api/enums/LogLevel.md +1 -1
  87. package/docs/api/enums/RBACErrorCode.md +1 -1
  88. package/docs/api/enums/RPCFunction.md +1 -1
  89. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  90. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  91. package/docs/api/interfaces/AggregateConfig.md +1 -1
  92. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  93. package/docs/api/interfaces/AvatarProps.md +1 -1
  94. package/docs/api/interfaces/BadgeProps.md +1 -1
  95. package/docs/api/interfaces/ButtonProps.md +1 -1
  96. package/docs/api/interfaces/CalendarProps.md +1 -1
  97. package/docs/api/interfaces/CardProps.md +1 -1
  98. package/docs/api/interfaces/ColorPalette.md +1 -1
  99. package/docs/api/interfaces/ColorShade.md +1 -1
  100. package/docs/api/interfaces/ComplianceResult.md +1 -1
  101. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  102. package/docs/api/interfaces/DataRecord.md +1 -1
  103. package/docs/api/interfaces/DataTableAction.md +1 -1
  104. package/docs/api/interfaces/DataTableColumn.md +1 -1
  105. package/docs/api/interfaces/DataTableProps.md +1 -1
  106. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  107. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  108. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  109. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  110. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  111. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  112. package/docs/api/interfaces/ExportColumn.md +1 -1
  113. package/docs/api/interfaces/ExportOptions.md +1 -1
  114. package/docs/api/interfaces/FileDisplayProps.md +24 -11
  115. package/docs/api/interfaces/FileMetadata.md +1 -1
  116. package/docs/api/interfaces/FileReference.md +1 -1
  117. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  118. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  119. package/docs/api/interfaces/FileUploadProps.md +1 -1
  120. package/docs/api/interfaces/FooterProps.md +1 -1
  121. package/docs/api/interfaces/FormFieldProps.md +1 -1
  122. package/docs/api/interfaces/FormProps.md +1 -1
  123. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  124. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  125. package/docs/api/interfaces/InputProps.md +1 -1
  126. package/docs/api/interfaces/LabelProps.md +1 -1
  127. package/docs/api/interfaces/LoggerConfig.md +1 -1
  128. package/docs/api/interfaces/LoginFormProps.md +1 -1
  129. package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
  130. package/docs/api/interfaces/NavigationContextType.md +1 -1
  131. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  132. package/docs/api/interfaces/NavigationItem.md +1 -1
  133. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  134. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  135. package/docs/api/interfaces/Organisation.md +1 -1
  136. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  137. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  138. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  139. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  140. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  141. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  142. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  143. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  144. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  145. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  146. package/docs/api/interfaces/PaletteData.md +1 -1
  147. package/docs/api/interfaces/ParsedAddress.md +2 -2
  148. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  149. package/docs/api/interfaces/ProgressProps.md +1 -1
  150. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  151. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  152. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  153. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  154. package/docs/api/interfaces/QuickFix.md +1 -1
  155. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  156. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  157. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  158. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  159. package/docs/api/interfaces/RBACConfig.md +2 -2
  160. package/docs/api/interfaces/RBACContext.md +1 -1
  161. package/docs/api/interfaces/RBACLogger.md +1 -1
  162. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  163. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  164. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  165. package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
  166. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  167. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  168. package/docs/api/interfaces/RBACResult.md +1 -1
  169. package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
  170. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  171. package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
  172. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  173. package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
  174. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  175. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  176. package/docs/api/interfaces/RBACRolesListResult.md +2 -2
  177. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  178. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  179. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  180. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  181. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  182. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  183. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  184. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  185. package/docs/api/interfaces/RouteConfig.md +2 -2
  186. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  187. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  188. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  189. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  190. package/docs/api/interfaces/SetupIssue.md +1 -1
  191. package/docs/api/interfaces/StorageConfig.md +1 -1
  192. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  193. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  194. package/docs/api/interfaces/StorageListOptions.md +1 -1
  195. package/docs/api/interfaces/StorageListResult.md +1 -1
  196. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  197. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  198. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  199. package/docs/api/interfaces/StyleImport.md +1 -1
  200. package/docs/api/interfaces/SwitchProps.md +1 -1
  201. package/docs/api/interfaces/TabsContentProps.md +1 -1
  202. package/docs/api/interfaces/TabsListProps.md +1 -1
  203. package/docs/api/interfaces/TabsProps.md +1 -1
  204. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  205. package/docs/api/interfaces/TextareaProps.md +1 -1
  206. package/docs/api/interfaces/ToastActionElement.md +1 -1
  207. package/docs/api/interfaces/ToastProps.md +1 -1
  208. package/docs/api/interfaces/UnifiedAuthContextType.md +60 -38
  209. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  210. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  211. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  212. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  213. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  214. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  215. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  216. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  217. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  218. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  219. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  220. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  221. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  222. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  223. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  224. package/docs/api/interfaces/UserEventAccess.md +1 -1
  225. package/docs/api/interfaces/UserMenuProps.md +1 -1
  226. package/docs/api/interfaces/UserProfile.md +1 -1
  227. package/docs/api/modules.md +202 -217
  228. package/docs/migration/README.md +18 -0
  229. package/docs/migration/database-changes-december-2025.md +768 -0
  230. package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
  231. package/docs/rbac/event-based-apps.md +124 -6
  232. package/package.json +1 -1
  233. package/scripts/check-pace-core-compliance.cjs +292 -57
  234. package/src/__tests__/public-recipe-view.test.ts +10 -10
  235. package/src/__tests__/rls-policies.test.ts +16 -14
  236. package/src/components/AddressField/README.md +6 -6
  237. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
  238. package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
  239. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
  240. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
  241. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
  242. package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
  243. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +75 -11
  244. package/src/components/DataTable/components/UnifiedTableBody.tsx +85 -14
  245. package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
  246. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -1
  247. package/src/components/FileDisplay/FileDisplay.tsx +16 -4
  248. package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
  249. package/src/components/NavigationMenu/NavigationMenu.tsx +1 -10
  250. package/src/components/OrganisationSelector/OrganisationSelector.tsx +35 -16
  251. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +25 -2
  252. package/src/components/PaceAppLayout/PaceAppLayout.tsx +97 -68
  253. package/src/components/PaceLoginPage/PaceLoginPage.tsx +0 -7
  254. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
  255. package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
  256. package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
  257. package/src/components/Select/Select.test.tsx +4 -1
  258. package/src/components/Select/Select.tsx +60 -15
  259. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +192 -0
  260. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +741 -0
  261. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +703 -0
  262. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +581 -0
  263. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +23 -15
  264. package/src/hooks/public/usePublicEvent.ts +8 -8
  265. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  266. package/src/hooks/services/useAuthService.ts +21 -3
  267. package/src/hooks/services/useEventService.ts +21 -3
  268. package/src/hooks/services/useInactivityService.ts +21 -3
  269. package/src/hooks/services/useOrganisationService.ts +21 -3
  270. package/src/hooks/useFileDisplay.ts +18 -26
  271. package/src/hooks/useQueryCache.ts +6 -6
  272. package/src/hooks/useSecureDataAccess.test.ts +24 -17
  273. package/src/hooks/useSecureDataAccess.ts +18 -13
  274. package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
  275. package/src/providers/services/EventServiceProvider.tsx +0 -8
  276. package/src/providers/services/UnifiedAuthProvider.tsx +174 -24
  277. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +10 -16
  278. package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
  279. package/src/rbac/adapters.tsx +3 -22
  280. package/src/rbac/api.test.ts +2 -2
  281. package/src/rbac/api.ts +7 -1
  282. package/src/rbac/components/EnhancedNavigationMenu.tsx +2 -15
  283. package/src/rbac/components/NavigationGuard.tsx +1 -10
  284. package/src/rbac/components/NavigationProvider.tsx +0 -1
  285. package/src/rbac/components/PermissionEnforcer.tsx +45 -12
  286. package/src/rbac/components/SecureDataProvider.tsx +0 -1
  287. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
  288. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
  289. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
  290. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
  291. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
  292. package/src/rbac/engine.ts +14 -2
  293. package/src/rbac/hooks/index.ts +0 -3
  294. package/src/rbac/hooks/usePermissions.ts +51 -11
  295. package/src/rbac/hooks/useRBAC.simple.test.ts +95 -0
  296. package/src/rbac/hooks/useRBAC.ts +3 -13
  297. package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
  298. package/src/rbac/hooks/useResolvedScope.ts +58 -33
  299. package/src/rbac/hooks/useSecureSupabase.ts +4 -9
  300. package/src/rbac/secureClient.ts +31 -0
  301. package/src/rbac/utils/__tests__/eventContext.test.ts +2 -2
  302. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +490 -0
  303. package/src/rbac/utils/eventContext.ts +5 -2
  304. package/src/services/AuthService.ts +37 -8
  305. package/src/services/EventService.ts +4 -57
  306. package/src/services/InactivityService.ts +127 -34
  307. package/src/services/OrganisationService.ts +160 -149
  308. package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
  309. package/src/services/__tests__/OrganisationService.test.ts +218 -86
  310. package/src/types/database.generated.ts +166 -201
  311. package/src/types/supabase.ts +2 -2
  312. package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
  313. package/src/utils/file-reference/index.ts +4 -4
  314. package/src/utils/google-places/googlePlacesUtils.ts +1 -1
  315. package/src/utils/google-places/types.ts +1 -1
  316. package/src/utils/request-deduplication.ts +4 -4
  317. package/src/utils/security/secureDataAccess.test.ts +1 -1
  318. package/src/utils/security/secureDataAccess.ts +7 -4
  319. package/src/utils/storage/README.md +1 -1
  320. package/dist/chunk-4QYC5L4K.js.map +0 -1
  321. package/dist/chunk-73HSNNOQ.js.map +0 -1
  322. package/dist/chunk-HQVPB5MZ.js.map +0 -1
  323. package/dist/chunk-J2XXC7R5.js.map +0 -1
  324. package/dist/chunk-NIU6J6OX.js.map +0 -1
  325. package/dist/chunk-RUYZKXOD.js.map +0 -1
  326. package/dist/chunk-STYK4OH2.js.map +0 -1
  327. package/dist/chunk-VVBAW5A5.js.map +0 -1
  328. package/dist/chunk-Y4BUBBHD.js.map +0 -1
  329. package/scripts/check-pace-core-compliance.js +0 -512
  330. package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
  331. package/src/utils/context/superAdminOverride.ts +0 -58
  332. /package/dist/{DataTable-ON3IXISJ.js.map → DataTable-5FU7IESH.js.map} +0 -0
  333. /package/dist/{UnifiedAuthProvider-X5NXANVI.js.map → UnifiedAuthProvider-RGJTDE2C.js.map} +0 -0
  334. /package/dist/{api-I6UCQ5S6.js.map → api-N774RPUA.js.map} +0 -0
@@ -144,49 +144,74 @@ export function useResolvedScope({
144
144
  let appConfig: AppConfig | null = null;
145
145
 
146
146
  // Try to resolve app config from database (with caching)
147
+ // Only query if user is authenticated (RLS policies require authentication)
147
148
  if (supabase && appName) {
148
149
  try {
149
- // Check cache first
150
- const cached = appConfigCache.get(appName);
151
- const now = Date.now();
152
- if (cached && (now - cached.timestamp) < CACHE_TTL) {
153
- appId = cached.appId;
154
- appConfig = cached.appConfig;
150
+ // Check if user is authenticated before querying (RLS requires auth)
151
+ // HTTP 406 errors are expected when not authenticated, so we skip the query
152
+ const { data: session } = await supabase.auth.getSession();
153
+ if (!session?.session) {
154
+ // User not authenticated - skip app resolution, will retry after login
155
+ // This is expected on login pages, so don't log as error
156
+ log.debug(`Skipping app resolution for "${appName}" - user not authenticated`);
155
157
  } else {
156
- // Cache miss or expired - fetch from database
157
- const { data: app, error } = await supabase
158
- .from('rbac_apps')
159
- .select('id, name, requires_event, is_active')
160
- .eq('name', appName)
161
- .eq('is_active', true)
162
- .single() as { data: { id: string; name: string; requires_event: boolean; is_active: boolean } | null; error: any };
163
-
164
- if (error) {
165
- // Check if app exists but is inactive
166
- const { data: inactiveApp } = await supabase
158
+ // Check cache first
159
+ const cached = appConfigCache.get(appName);
160
+ const now = Date.now();
161
+ if (cached && (now - cached.timestamp) < CACHE_TTL) {
162
+ appId = cached.appId;
163
+ appConfig = cached.appConfig;
164
+ } else {
165
+ // Cache miss or expired - fetch from database
166
+ const { data: app, error } = await supabase
167
167
  .from('rbac_apps')
168
- .select('id, name, is_active')
168
+ .select('id, name, requires_event, is_active')
169
169
  .eq('name', appName)
170
- .single() as { data: { id: string; name: string; is_active: boolean } | null };
170
+ .eq('is_active', true)
171
+ .single() as { data: { id: string; name: string; requires_event: boolean; is_active: boolean } | null; error: any };
171
172
 
172
- if (inactiveApp) {
173
- log.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
174
- // Don't cache inactive apps - set appId to undefined
175
- appId = undefined;
176
- } else {
177
- log.error(`App "${appName}" not found in rbac_apps table`);
178
- // Don't cache missing apps - set appId to undefined
179
- appId = undefined;
173
+ if (error) {
174
+ // HTTP 406 is expected when not authenticated (RLS blocks query)
175
+ // Don't log as error if it's a 406 - this is expected behavior
176
+ if (error.code === '406' || error.code === 'PGRST116' || error.message?.includes('406')) {
177
+ log.debug(`App resolution blocked by RLS for "${appName}" - user may not be authenticated`);
178
+ // Don't cache - will retry after authentication
179
+ appId = undefined;
180
+ } else {
181
+ // Check if app exists but is inactive
182
+ const { data: inactiveApp } = await supabase
183
+ .from('rbac_apps')
184
+ .select('id, name, is_active')
185
+ .eq('name', appName)
186
+ .single() as { data: { id: string; name: string; is_active: boolean } | null };
187
+
188
+ if (inactiveApp) {
189
+ log.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
190
+ // Don't cache inactive apps - set appId to undefined
191
+ appId = undefined;
192
+ } else {
193
+ log.error(`App "${appName}" not found in rbac_apps table`, { error });
194
+ // Don't cache missing apps - set appId to undefined
195
+ appId = undefined;
196
+ }
197
+ }
198
+ } else if (app) {
199
+ appId = app.id;
200
+ appConfig = { requires_event: app.requires_event ?? false };
201
+ // Only cache successful lookups of active apps
202
+ appConfigCache.set(appName, { appId, appConfig, timestamp: now });
180
203
  }
181
- } else if (app) {
182
- appId = app.id;
183
- appConfig = { requires_event: app.requires_event ?? false };
184
- // Only cache successful lookups of active apps
185
- appConfigCache.set(appName, { appId, appConfig, timestamp: now });
186
204
  }
187
205
  }
188
206
  } catch (error) {
189
- log.error('Unexpected error resolving app config:', error);
207
+ // Handle network errors or other unexpected errors gracefully
208
+ // Don't log 406 errors as they're expected when not authenticated
209
+ const errorMessage = error instanceof Error ? error.message : String(error);
210
+ if (!errorMessage.includes('406') && !errorMessage.includes('PGRST116')) {
211
+ log.error('Unexpected error resolving app config:', error);
212
+ } else {
213
+ log.debug('App resolution skipped - authentication required');
214
+ }
190
215
  }
191
216
  }
192
217
 
@@ -120,7 +120,7 @@ import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
120
120
  import { useOrganisations } from '../../hooks/useOrganisations';
121
121
  import { useEvents } from '../../hooks/useEvents';
122
122
  import { useResolvedScope } from './useResolvedScope';
123
- import { useSuperAdminBypass } from './useSuperAdminBypass';
123
+ import { useOrganisationSecurity } from '../../hooks/useOrganisationSecurity';
124
124
  import { createSecureClient, SecureSupabaseClient } from '../secureClient';
125
125
  import type { Database } from '../../types/database';
126
126
  import type { SupabaseClient } from '@supabase/supabase-js';
@@ -215,14 +215,9 @@ export function useSecureSupabase(
215
215
  const eventLoading = 'eventLoading' in eventsContext ? eventsContext.eventLoading : false;
216
216
 
217
217
  // Check super admin status for conditional filtering
218
- // Use both verified status and metadata hint to avoid race conditions
219
- // Strategy: Use verified status if available, otherwise use metadata hint during verification
220
- // This prevents creating clients with wrong super admin status before verification completes
221
- const { isSuperAdmin: verifiedIsSuperAdmin, isLoading: isVerifyingSuperAdmin } = useSuperAdminBypass();
222
- const metadataHint = Boolean(user?.app_metadata?.is_super_admin) || Boolean(user?.user_metadata?.is_super_admin);
223
- // If verified as super admin, use that. If verification in progress, use metadata hint optimistically.
224
- // Once verification completes and user is not super admin, verifiedIsSuperAdmin will be false.
225
- const isSuperAdmin = verifiedIsSuperAdmin || (isVerifyingSuperAdmin && metadataHint);
218
+ // Use verified status from useOrganisationSecurity which checks the database
219
+ const { superAdminContext } = useOrganisationSecurity();
220
+ const isSuperAdmin = superAdminContext.isSuperAdmin;
226
221
 
227
222
  // Resolve scope to get appId
228
223
  const { resolvedScope } = useResolvedScope({
@@ -152,6 +152,19 @@ export class SecureSupabaseClient {
152
152
 
153
153
  // Override insert to add organisation context
154
154
  query.insert = (values: any) => {
155
+ // Tables that don't have organisation_id column
156
+ const tablesWithoutOrganisationId = [
157
+ 'core_organisations', // Organisation table itself - uses 'id' as primary key
158
+ 'rbac_apps', // App configuration table - no organisation scope
159
+ 'rbac_app_pages', // Page configuration table - scoped by app_id, not organisation_id
160
+ 'rbac_global_roles', // Global roles - no organisation scope
161
+ ];
162
+
163
+ // Skip adding organisation_id for tables that don't have it
164
+ if (tablesWithoutOrganisationId.includes(tableName)) {
165
+ return originalInsert(values);
166
+ }
167
+
155
168
  // For rbac_user_profiles, only add organisation_id if not super admin
156
169
  // Super admins can create users in any org, non-super-admins are restricted
157
170
  if (tableName === 'rbac_user_profiles') {
@@ -204,6 +217,24 @@ export class SecureSupabaseClient {
204
217
  * - Always apply org filter unless super admin bypasses it
205
218
  */
206
219
  private addOrganisationFilter(query: any, tableName: string) {
220
+ // Tables that don't have organisation_id column - RLS policies handle access control
221
+ const tablesWithoutOrganisationId = [
222
+ 'core_organisations', // Organisation table itself - uses 'id' as primary key
223
+ 'rbac_apps', // App configuration table - no organisation scope
224
+ 'rbac_app_pages', // Page configuration table - scoped by app_id, not organisation_id
225
+ 'rbac_global_roles', // Global roles - no organisation scope
226
+ ];
227
+
228
+ // Skip organisation filter for tables that don't have organisation_id column
229
+ if (tablesWithoutOrganisationId.includes(tableName)) {
230
+ return query; // RLS policies handle access control for these tables
231
+ }
232
+
233
+ // If organisation context is not set, don't add a filter (e.g., super admin without selected org)
234
+ if (!this.organisationId) {
235
+ return query;
236
+ }
237
+
207
238
  // For rbac_user_profiles, use conditional filtering based on super admin status
208
239
  if (tableName === 'rbac_user_profiles') {
209
240
  // Super admins: No org filter (see all users via RLS)
@@ -41,7 +41,7 @@ describe('Event Context Utilities', () => {
41
41
  // Clear cache before each test to prevent test interference
42
42
  clearAllOrgDerivationCache();
43
43
  mockSupabase = createMockSupabaseClient();
44
- mockQuery = mockSupabase.from('event');
44
+ mockQuery = mockSupabase.from('core_events');
45
45
  });
46
46
 
47
47
  afterEach(() => {
@@ -63,7 +63,7 @@ describe('Event Context Utilities', () => {
63
63
  const result = await getOrganisationFromEvent(mockSupabase, eventId);
64
64
 
65
65
  expect(result).toBe(organisationId);
66
- expect(mockSupabase.from).toHaveBeenCalledWith('event');
66
+ expect(mockSupabase.from).toHaveBeenCalledWith('core_events');
67
67
  expect(mockQuery.select).toHaveBeenCalledWith('organisation_id');
68
68
  expect(mockQuery.eq).toHaveBeenCalledWith('event_id', eventId);
69
69
  expect(mockQuery.single).toHaveBeenCalled();
@@ -0,0 +1,490 @@
1
+ /**
2
+ * @file Event Context Utilities Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/EventContext/Tests
5
+ * @since 1.0.0
6
+ *
7
+ * Comprehensive tests for event context utilities in the RBAC system.
8
+ */
9
+
10
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
11
+ import { SupabaseClient } from '@supabase/supabase-js';
12
+ import { Database } from '../../../types/database';
13
+ import { UUID, Scope } from '../../types';
14
+ import {
15
+ getOrganisationFromEvent,
16
+ createScopeFromEvent,
17
+ isEventBasedScope,
18
+ isValidEventBasedScope,
19
+ clearAllOrgDerivationCache,
20
+ clearOrgDerivationCache
21
+ } from '../eventContext';
22
+
23
+ // Mock Supabase client
24
+ const createMockSupabaseClient = () => {
25
+ const mockQuery = {
26
+ select: vi.fn().mockReturnThis(),
27
+ eq: vi.fn().mockReturnThis(),
28
+ single: vi.fn()
29
+ };
30
+
31
+ const fromMock = vi.fn().mockReturnValue(mockQuery);
32
+
33
+ return {
34
+ from: fromMock,
35
+ query: mockQuery
36
+ } as unknown as SupabaseClient<Database>;
37
+ };
38
+
39
+ describe('Event Context Utilities', () => {
40
+ let mockSupabase: SupabaseClient<Database>;
41
+ let mockQuery: any;
42
+
43
+ beforeEach(() => {
44
+ // Clear cache before each test
45
+ clearAllOrgDerivationCache();
46
+
47
+ mockSupabase = createMockSupabaseClient();
48
+ // Reset mockQuery to get a fresh query builder for each test
49
+ mockQuery = {
50
+ select: vi.fn().mockReturnThis(),
51
+ eq: vi.fn().mockReturnThis(),
52
+ single: vi.fn()
53
+ };
54
+ (mockSupabase.from as any).mockReturnValue(mockQuery);
55
+ });
56
+
57
+ afterEach(() => {
58
+ vi.clearAllMocks();
59
+ // Clear cache after each test
60
+ clearAllOrgDerivationCache();
61
+ });
62
+
63
+ describe('getOrganisationFromEvent', () => {
64
+ it('should return organisation ID when event exists', async () => {
65
+ const eventId = 'event-123';
66
+ const organisationId = 'org-456';
67
+
68
+ mockQuery.single.mockResolvedValue({
69
+ data: { organisation_id: organisationId },
70
+ error: null
71
+ });
72
+
73
+ const result = await getOrganisationFromEvent(mockSupabase, eventId);
74
+
75
+ expect(result).toBe(organisationId);
76
+ expect(mockSupabase.from).toHaveBeenCalledWith('core_events');
77
+ expect(mockQuery.select).toHaveBeenCalledWith('organisation_id');
78
+ expect(mockQuery.eq).toHaveBeenCalledWith('event_id', eventId);
79
+ expect(mockQuery.single).toHaveBeenCalled();
80
+ });
81
+
82
+ it('should return null when event does not exist', async () => {
83
+ const eventId = 'nonexistent-event';
84
+
85
+ mockQuery.single.mockResolvedValue({
86
+ data: null,
87
+ error: { message: 'Event not found' }
88
+ });
89
+
90
+ const result = await getOrganisationFromEvent(mockSupabase, eventId);
91
+
92
+ expect(result).toBeNull();
93
+ });
94
+
95
+ it('should return null when data is null', async () => {
96
+ const eventId = 'event-123';
97
+
98
+ // Clear cache for this specific test
99
+ clearOrgDerivationCache(eventId);
100
+
101
+ mockQuery.single.mockResolvedValue({
102
+ data: null,
103
+ error: null
104
+ });
105
+
106
+ const result = await getOrganisationFromEvent(mockSupabase, eventId);
107
+
108
+ expect(result).toBeNull();
109
+ });
110
+
111
+ it('should handle database errors gracefully', async () => {
112
+ const eventId = 'event-123';
113
+ const dbError = new Error('Database connection failed');
114
+
115
+ // Clear cache for this specific test
116
+ clearOrgDerivationCache(eventId);
117
+
118
+ mockQuery.single.mockRejectedValue(dbError);
119
+
120
+ await expect(getOrganisationFromEvent(mockSupabase, eventId))
121
+ .rejects.toThrow('Database connection failed');
122
+ });
123
+
124
+ it('should handle empty organisation_id', async () => {
125
+ const eventId = 'event-123';
126
+
127
+ // Clear cache for this specific test
128
+ clearOrgDerivationCache(eventId);
129
+
130
+ mockQuery.single.mockResolvedValue({
131
+ data: { organisation_id: null },
132
+ error: null
133
+ });
134
+
135
+ const result = await getOrganisationFromEvent(mockSupabase, eventId);
136
+
137
+ expect(result).toBeNull();
138
+ });
139
+ });
140
+
141
+ describe('createScopeFromEvent', () => {
142
+ it('should create complete scope when event exists', async () => {
143
+ const eventId = 'event-123';
144
+ const organisationId = 'org-456';
145
+ const appId = 'app-789';
146
+
147
+ mockQuery.single.mockResolvedValue({
148
+ data: { organisation_id: organisationId },
149
+ error: null
150
+ });
151
+
152
+ const result = await createScopeFromEvent(mockSupabase, eventId, appId);
153
+
154
+ expect(result).toEqual({
155
+ organisationId,
156
+ eventId,
157
+ appId
158
+ });
159
+ });
160
+
161
+ it('should create scope without appId when not provided', async () => {
162
+ const eventId = 'event-123';
163
+ const organisationId = 'org-456';
164
+
165
+ mockQuery.single.mockResolvedValue({
166
+ data: { organisation_id: organisationId },
167
+ error: null
168
+ });
169
+
170
+ const result = await createScopeFromEvent(mockSupabase, eventId);
171
+
172
+ expect(result).toEqual({
173
+ organisationId,
174
+ eventId,
175
+ appId: undefined
176
+ });
177
+ });
178
+
179
+ it('should return null when event does not exist', async () => {
180
+ const eventId = 'nonexistent-event';
181
+
182
+ mockQuery.single.mockResolvedValue({
183
+ data: null,
184
+ error: { message: 'Event not found' }
185
+ });
186
+
187
+ const result = await createScopeFromEvent(mockSupabase, eventId);
188
+
189
+ expect(result).toBeNull();
190
+ });
191
+
192
+ it('should return null when organisation lookup fails', async () => {
193
+ const eventId = 'event-123';
194
+
195
+ // Clear cache for this specific test
196
+ clearOrgDerivationCache(eventId);
197
+
198
+ mockQuery.single.mockResolvedValue({
199
+ data: { organisation_id: null },
200
+ error: null
201
+ });
202
+
203
+ const result = await createScopeFromEvent(mockSupabase, eventId);
204
+
205
+ expect(result).toBeNull();
206
+ });
207
+
208
+ it('should handle database errors gracefully', async () => {
209
+ const eventId = 'event-123';
210
+ const dbError = new Error('Database connection failed');
211
+
212
+ // Clear cache for this specific test
213
+ clearOrgDerivationCache(eventId);
214
+
215
+ mockQuery.single.mockRejectedValue(dbError);
216
+
217
+ await expect(createScopeFromEvent(mockSupabase, eventId))
218
+ .rejects.toThrow('Database connection failed');
219
+ });
220
+ });
221
+
222
+ describe('isEventBasedScope', () => {
223
+ it('should return true for event-based scope (no organisationId, has eventId)', () => {
224
+ const scope: Scope = {
225
+ eventId: 'event-123',
226
+ appId: 'app-456'
227
+ };
228
+
229
+ expect(isEventBasedScope(scope)).toBe(true);
230
+ });
231
+
232
+ it('should return false when organisationId is present', () => {
233
+ const scope: Scope = {
234
+ organisationId: 'org-123',
235
+ eventId: 'event-123',
236
+ appId: 'app-456'
237
+ };
238
+
239
+ expect(isEventBasedScope(scope)).toBe(false);
240
+ });
241
+
242
+ it('should return false when eventId is missing', () => {
243
+ const scope: Scope = {
244
+ appId: 'app-456'
245
+ };
246
+
247
+ expect(isEventBasedScope(scope)).toBe(false);
248
+ });
249
+
250
+ it('should return false when both organisationId and eventId are missing', () => {
251
+ const scope: Scope = {
252
+ appId: 'app-456'
253
+ };
254
+
255
+ expect(isEventBasedScope(scope)).toBe(false);
256
+ });
257
+
258
+ it('should return false when eventId is null', () => {
259
+ const scope: Scope = {
260
+ eventId: null,
261
+ appId: 'app-456'
262
+ };
263
+
264
+ expect(isEventBasedScope(scope)).toBe(false);
265
+ });
266
+
267
+ it('should return false when eventId is undefined', () => {
268
+ const scope: Scope = {
269
+ eventId: undefined,
270
+ appId: 'app-456'
271
+ };
272
+
273
+ expect(isEventBasedScope(scope)).toBe(false);
274
+ });
275
+ });
276
+
277
+ describe('isValidEventBasedScope', () => {
278
+ it('should return true for valid event-based scope', () => {
279
+ const scope: Scope = {
280
+ eventId: 'event-123',
281
+ appId: 'app-456'
282
+ };
283
+
284
+ expect(isValidEventBasedScope(scope)).toBe(true);
285
+ });
286
+
287
+ it('should return false when eventId is missing', () => {
288
+ const scope: Scope = {
289
+ appId: 'app-456'
290
+ };
291
+
292
+ expect(isValidEventBasedScope(scope)).toBe(false);
293
+ });
294
+
295
+ it('should return false when eventId is null', () => {
296
+ const scope: Scope = {
297
+ eventId: null,
298
+ appId: 'app-456'
299
+ };
300
+
301
+ expect(isValidEventBasedScope(scope)).toBe(false);
302
+ });
303
+
304
+ it('should return false when eventId is undefined', () => {
305
+ const scope: Scope = {
306
+ eventId: undefined,
307
+ appId: 'app-456'
308
+ };
309
+
310
+ expect(isValidEventBasedScope(scope)).toBe(false);
311
+ });
312
+
313
+ it('should return false when organisationId is present (not event-based)', () => {
314
+ const scope: Scope = {
315
+ organisationId: 'org-123',
316
+ eventId: 'event-123',
317
+ appId: 'app-456'
318
+ };
319
+
320
+ expect(isValidEventBasedScope(scope)).toBe(false);
321
+ });
322
+
323
+ it('should return true for event-based scope without appId', () => {
324
+ const scope: Scope = {
325
+ eventId: 'event-123'
326
+ };
327
+
328
+ expect(isValidEventBasedScope(scope)).toBe(true);
329
+ });
330
+ });
331
+
332
+ describe('Edge Cases and Error Handling', () => {
333
+ it('should handle malformed event IDs', async () => {
334
+ const malformedEventId = '';
335
+
336
+ mockQuery.single.mockResolvedValue({
337
+ data: null,
338
+ error: { message: 'Invalid event ID' }
339
+ });
340
+
341
+ const result = await getOrganisationFromEvent(mockSupabase, malformedEventId);
342
+
343
+ expect(result).toBeNull();
344
+ });
345
+
346
+ it('should handle very long event IDs', async () => {
347
+ const longEventId = 'a'.repeat(1000);
348
+ const organisationId = 'org-456';
349
+
350
+ mockQuery.single.mockResolvedValue({
351
+ data: { organisation_id: organisationId },
352
+ error: null
353
+ });
354
+
355
+ const result = await getOrganisationFromEvent(mockSupabase, longEventId);
356
+
357
+ expect(result).toBe(organisationId);
358
+ expect(mockQuery.eq).toHaveBeenCalledWith('event_id', longEventId);
359
+ });
360
+
361
+ it('should handle special characters in event IDs', async () => {
362
+ const specialEventId = 'event-123!@#$%^&*()';
363
+ const organisationId = 'org-456';
364
+
365
+ mockQuery.single.mockResolvedValue({
366
+ data: { organisation_id: organisationId },
367
+ error: null
368
+ });
369
+
370
+ const result = await getOrganisationFromEvent(mockSupabase, specialEventId);
371
+
372
+ expect(result).toBe(organisationId);
373
+ expect(mockQuery.eq).toHaveBeenCalledWith('event_id', specialEventId);
374
+ });
375
+
376
+ it('should handle concurrent calls to getOrganisationFromEvent', async () => {
377
+ const eventId1 = 'event-123';
378
+ const eventId2 = 'event-456';
379
+ const organisationId1 = 'org-123';
380
+ const organisationId2 = 'org-456';
381
+
382
+ // Clear cache for these specific events
383
+ clearOrgDerivationCache(eventId1);
384
+ clearOrgDerivationCache(eventId2);
385
+
386
+ // Create separate query builders for concurrent calls
387
+ const mockQuery1 = {
388
+ select: vi.fn().mockReturnThis(),
389
+ eq: vi.fn().mockReturnThis(),
390
+ single: vi.fn().mockResolvedValue({
391
+ data: { organisation_id: organisationId1 },
392
+ error: null
393
+ })
394
+ };
395
+ const mockQuery2 = {
396
+ select: vi.fn().mockReturnThis(),
397
+ eq: vi.fn().mockReturnThis(),
398
+ single: vi.fn().mockResolvedValue({
399
+ data: { organisation_id: organisationId2 },
400
+ error: null
401
+ })
402
+ };
403
+
404
+ (mockSupabase.from as any)
405
+ .mockReturnValueOnce(mockQuery1)
406
+ .mockReturnValueOnce(mockQuery2);
407
+
408
+ const [result1, result2] = await Promise.all([
409
+ getOrganisationFromEvent(mockSupabase, eventId1),
410
+ getOrganisationFromEvent(mockSupabase, eventId2)
411
+ ]);
412
+
413
+ expect(result1).toBe(organisationId1);
414
+ expect(result2).toBe(organisationId2);
415
+ });
416
+
417
+ it('should handle concurrent calls to createScopeFromEvent', async () => {
418
+ const eventId1 = 'event-123';
419
+ const eventId2 = 'event-456';
420
+ const organisationId1 = 'org-123';
421
+ const organisationId2 = 'org-456';
422
+ const appId1 = 'app-123';
423
+ const appId2 = 'app-456';
424
+
425
+ // Clear cache for these specific events
426
+ clearOrgDerivationCache(eventId1);
427
+ clearOrgDerivationCache(eventId2);
428
+
429
+ // Create separate query builders for concurrent calls
430
+ const mockQuery1 = {
431
+ select: vi.fn().mockReturnThis(),
432
+ eq: vi.fn().mockReturnThis(),
433
+ single: vi.fn().mockResolvedValue({
434
+ data: { organisation_id: organisationId1 },
435
+ error: null
436
+ })
437
+ };
438
+ const mockQuery2 = {
439
+ select: vi.fn().mockReturnThis(),
440
+ eq: vi.fn().mockReturnThis(),
441
+ single: vi.fn().mockResolvedValue({
442
+ data: { organisation_id: organisationId2 },
443
+ error: null
444
+ })
445
+ };
446
+
447
+ (mockSupabase.from as any)
448
+ .mockReturnValueOnce(mockQuery1)
449
+ .mockReturnValueOnce(mockQuery2);
450
+
451
+ const [result1, result2] = await Promise.all([
452
+ createScopeFromEvent(mockSupabase, eventId1, appId1),
453
+ createScopeFromEvent(mockSupabase, eventId2, appId2)
454
+ ]);
455
+
456
+ expect(result1).toEqual({
457
+ organisationId: organisationId1,
458
+ eventId: eventId1,
459
+ appId: appId1
460
+ });
461
+ expect(result2).toEqual({
462
+ organisationId: organisationId2,
463
+ eventId: eventId2,
464
+ appId: appId2
465
+ });
466
+ });
467
+ });
468
+
469
+ describe('Type Safety', () => {
470
+ it('should handle UUID types correctly', () => {
471
+ const validUUID = '123e4567-e89b-12d3-a456-426614174000';
472
+ const scope: Scope = {
473
+ eventId: validUUID,
474
+ appId: validUUID
475
+ };
476
+
477
+ expect(isValidEventBasedScope(scope)).toBe(true);
478
+ });
479
+
480
+ it('should handle string types correctly', () => {
481
+ const stringId = 'event-123';
482
+ const scope: Scope = {
483
+ eventId: stringId,
484
+ appId: 'app-456'
485
+ };
486
+
487
+ expect(isValidEventBasedScope(scope)).toBe(true);
488
+ });
489
+ });
490
+ });