@jmruthers/pace-core 0.5.185 → 0.5.187

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 (300) hide show
  1. package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
  2. package/dist/{DataTable-IX2NBUTP.js → DataTable-K3RJRSOX.js} +7 -7
  3. package/dist/{PublicPageProvider-BABf6JCh.d.ts → PublicPageProvider-DrLDztHt.d.ts} +214 -107
  4. package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-B76OWOAT.js} +2 -2
  5. package/dist/{api-BMFCXVQX.js → api-YP7XD5L6.js} +3 -3
  6. package/dist/{audit-WRS3KJKI.js → audit-B5P6FFIR.js} +2 -2
  7. package/dist/{chunk-445GEP27.js → chunk-3IC5WCMO.js} +33 -8
  8. package/dist/chunk-3IC5WCMO.js.map +1 -0
  9. package/dist/{chunk-OKI34GZD.js → chunk-3NFNJOO7.js} +8 -8
  10. package/dist/chunk-3NFNJOO7.js.map +1 -0
  11. package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
  12. package/dist/chunk-63FOKYGO.js.map +1 -0
  13. package/dist/{chunk-MX3EIJGQ.js → chunk-C4OYJOV4.js} +631 -97
  14. package/dist/chunk-C4OYJOV4.js.map +1 -0
  15. package/dist/{chunk-HGPQUCBC.js → chunk-FMTK4XNN.js} +3 -3
  16. package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
  17. package/dist/chunk-HEHYGYOX.js.map +1 -0
  18. package/dist/{chunk-XAUHJD3L.js → chunk-K2JGDXGU.js} +2 -2
  19. package/dist/{chunk-HC67NW5K.js → chunk-LBBUPSSC.js} +863 -552
  20. package/dist/chunk-LBBUPSSC.js.map +1 -0
  21. package/dist/{chunk-IXSNYUCT.js → chunk-SAUPYVLF.js} +1 -1
  22. package/dist/chunk-SAUPYVLF.js.map +1 -0
  23. package/dist/{chunk-AISXLWGZ.js → chunk-T6ZJVI3A.js} +27 -23
  24. package/dist/chunk-T6ZJVI3A.js.map +1 -0
  25. package/dist/{chunk-STTZQK2I.js → chunk-ULX5FYEM.js} +9 -7
  26. package/dist/chunk-ULX5FYEM.js.map +1 -0
  27. package/dist/{chunk-FXFJRTKI.js → chunk-WK2Y6TGA.js} +3 -3
  28. package/dist/chunk-WK2Y6TGA.js.map +1 -0
  29. package/dist/chunk-YHCN776L.js +447 -0
  30. package/dist/chunk-YHCN776L.js.map +1 -0
  31. package/dist/components.d.ts +4 -4
  32. package/dist/components.js +12 -10
  33. package/dist/components.js.map +1 -1
  34. package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
  35. package/dist/{file-reference-BjR39ktt.d.ts → file-reference-D037xOFK.d.ts} +3 -1
  36. package/dist/hooks.d.ts +265 -6
  37. package/dist/hooks.js +148 -49
  38. package/dist/hooks.js.map +1 -1
  39. package/dist/index.d.ts +25 -10
  40. package/dist/index.js +65 -30
  41. package/dist/index.js.map +1 -1
  42. package/dist/providers.js +1 -1
  43. package/dist/rbac/index.d.ts +125 -8
  44. package/dist/rbac/index.js +27 -7
  45. package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
  46. package/dist/types.d.ts +2 -2
  47. package/dist/types.js +1 -1
  48. package/dist/{usePublicRouteParams-CvnC3d-e.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +3 -3
  49. package/dist/utils.d.ts +214 -4
  50. package/dist/utils.js +22 -2
  51. package/dist/utils.js.map +1 -1
  52. package/docs/api/classes/ColumnFactory.md +1 -1
  53. package/docs/api/classes/ErrorBoundary.md +1 -1
  54. package/docs/api/classes/InvalidScopeError.md +1 -1
  55. package/docs/api/classes/Logger.md +1 -1
  56. package/docs/api/classes/MissingUserContextError.md +1 -1
  57. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  58. package/docs/api/classes/PermissionDeniedError.md +1 -1
  59. package/docs/api/classes/RBACAuditManager.md +21 -17
  60. package/docs/api/classes/RBACCache.md +31 -23
  61. package/docs/api/classes/RBACEngine.md +6 -6
  62. package/docs/api/classes/RBACError.md +1 -1
  63. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  64. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  65. package/docs/api/classes/StorageUtils.md +1 -1
  66. package/docs/api/enums/FileCategory.md +1 -1
  67. package/docs/api/enums/LogLevel.md +1 -1
  68. package/docs/api/enums/RBACErrorCode.md +1 -1
  69. package/docs/api/enums/RPCFunction.md +1 -1
  70. package/docs/api/interfaces/AddressFieldProps.md +241 -0
  71. package/docs/api/interfaces/AddressFieldRef.md +94 -0
  72. package/docs/api/interfaces/AggregateConfig.md +1 -1
  73. package/docs/api/interfaces/AutocompleteOptions.md +75 -0
  74. package/docs/api/interfaces/BadgeProps.md +1 -1
  75. package/docs/api/interfaces/ButtonProps.md +1 -1
  76. package/docs/api/interfaces/CalendarProps.md +1 -1
  77. package/docs/api/interfaces/CardProps.md +1 -1
  78. package/docs/api/interfaces/ColorPalette.md +1 -1
  79. package/docs/api/interfaces/ColorShade.md +1 -1
  80. package/docs/api/interfaces/ComplianceResult.md +1 -1
  81. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  82. package/docs/api/interfaces/DataRecord.md +1 -1
  83. package/docs/api/interfaces/DataTableAction.md +1 -1
  84. package/docs/api/interfaces/DataTableColumn.md +1 -1
  85. package/docs/api/interfaces/DataTableProps.md +1 -1
  86. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  87. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  88. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  89. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  90. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  91. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  92. package/docs/api/interfaces/ExportColumn.md +1 -1
  93. package/docs/api/interfaces/ExportOptions.md +1 -1
  94. package/docs/api/interfaces/FileDisplayProps.md +15 -15
  95. package/docs/api/interfaces/FileMetadata.md +1 -1
  96. package/docs/api/interfaces/FileReference.md +1 -1
  97. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  98. package/docs/api/interfaces/FileUploadOptions.md +33 -9
  99. package/docs/api/interfaces/FileUploadProps.md +36 -14
  100. package/docs/api/interfaces/FooterProps.md +1 -1
  101. package/docs/api/interfaces/FormFieldProps.md +1 -1
  102. package/docs/api/interfaces/FormProps.md +1 -1
  103. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  104. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  105. package/docs/api/interfaces/InputProps.md +1 -1
  106. package/docs/api/interfaces/LabelProps.md +1 -1
  107. package/docs/api/interfaces/LoggerConfig.md +1 -1
  108. package/docs/api/interfaces/LoginFormProps.md +1 -1
  109. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  110. package/docs/api/interfaces/NavigationContextType.md +1 -1
  111. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  112. package/docs/api/interfaces/NavigationItem.md +1 -1
  113. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  114. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  115. package/docs/api/interfaces/Organisation.md +1 -1
  116. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  117. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  118. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  119. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  120. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  121. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  122. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  123. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  124. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  125. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  126. package/docs/api/interfaces/PaletteData.md +1 -1
  127. package/docs/api/interfaces/ParsedAddress.md +120 -0
  128. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  129. package/docs/api/interfaces/ProgressProps.md +1 -1
  130. package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
  131. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  132. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  133. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  134. package/docs/api/interfaces/QuickFix.md +1 -1
  135. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  136. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  137. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  138. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  139. package/docs/api/interfaces/RBACConfig.md +27 -4
  140. package/docs/api/interfaces/RBACContext.md +1 -1
  141. package/docs/api/interfaces/RBACLogger.md +5 -5
  142. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  143. package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
  144. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  146. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  148. package/docs/api/interfaces/RBACResult.md +1 -1
  149. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  150. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  151. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  152. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  153. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  154. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  155. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  156. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  157. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  158. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  159. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  160. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  161. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  162. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  163. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  164. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  165. package/docs/api/interfaces/RouteConfig.md +1 -1
  166. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  167. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  168. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  169. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  170. package/docs/api/interfaces/SetupIssue.md +1 -1
  171. package/docs/api/interfaces/StorageConfig.md +1 -1
  172. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  173. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  174. package/docs/api/interfaces/StorageListOptions.md +1 -1
  175. package/docs/api/interfaces/StorageListResult.md +1 -1
  176. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  177. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  178. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  179. package/docs/api/interfaces/StyleImport.md +1 -1
  180. package/docs/api/interfaces/SwitchProps.md +1 -1
  181. package/docs/api/interfaces/TabsContentProps.md +1 -1
  182. package/docs/api/interfaces/TabsListProps.md +1 -1
  183. package/docs/api/interfaces/TabsProps.md +1 -1
  184. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  185. package/docs/api/interfaces/TextareaProps.md +1 -1
  186. package/docs/api/interfaces/ToastActionElement.md +1 -1
  187. package/docs/api/interfaces/ToastProps.md +1 -1
  188. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  189. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  190. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  191. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  192. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  193. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  194. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  195. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  196. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  198. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  199. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  201. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  202. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  203. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  204. package/docs/api/interfaces/UserEventAccess.md +1 -1
  205. package/docs/api/interfaces/UserMenuProps.md +1 -1
  206. package/docs/api/interfaces/UserProfile.md +1 -1
  207. package/docs/api/modules.md +328 -69
  208. package/docs/api-reference/components.md +26 -12
  209. package/docs/best-practices/performance.md +11 -0
  210. package/docs/implementation-guides/file-reference-system.md +24 -2
  211. package/docs/implementation-guides/file-upload-storage.md +38 -1
  212. package/docs/rbac/README.md +2 -1
  213. package/docs/rbac/api-reference.md +11 -0
  214. package/docs/rbac/performance.md +320 -0
  215. package/docs/standards/01-architecture-standard.md +5 -0
  216. package/docs/standards/05-security-standard.md +12 -0
  217. package/package.json +1 -1
  218. package/scripts/check-pace-core-compliance.js +512 -0
  219. package/src/components/AddressField/AddressField.test.tsx +411 -0
  220. package/src/components/AddressField/AddressField.tsx +323 -0
  221. package/src/components/AddressField/README.md +336 -0
  222. package/src/components/AddressField/index.ts +10 -0
  223. package/src/components/AddressField/types.ts +65 -0
  224. package/src/components/FileDisplay/FileDisplay.test.tsx +454 -0
  225. package/src/components/FileDisplay/FileDisplay.tsx +28 -1
  226. package/src/components/FileUpload/FileUpload.test.tsx +2 -0
  227. package/src/components/FileUpload/FileUpload.tsx +7 -1
  228. package/src/components/Header/Header.tsx +2 -5
  229. package/src/components/ProtectedRoute/ProtectedRoute.tsx +134 -1
  230. package/src/components/index.ts +2 -0
  231. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
  232. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
  233. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
  234. package/src/hooks/index.ts +9 -0
  235. package/src/hooks/public/usePublicFileDisplay.ts +8 -10
  236. package/src/hooks/useAddressAutocomplete.test.ts +318 -0
  237. package/src/hooks/useAddressAutocomplete.ts +268 -0
  238. package/src/hooks/useFileDisplay.ts +3 -15
  239. package/src/hooks/useFileReference.test.ts +21 -3
  240. package/src/hooks/useFileReference.ts +3 -24
  241. package/src/hooks/useFileUrlCache.ts +246 -0
  242. package/src/hooks/useInactivityTracker.ts +31 -20
  243. package/src/hooks/useOrganisationSecurity.test.ts +10 -7
  244. package/src/hooks/useOrganisationSecurity.ts +3 -3
  245. package/src/hooks/usePreventTabReload.ts +106 -0
  246. package/src/hooks/useQueryCache.ts +315 -0
  247. package/src/hooks/useSecureDataAccess.ts +2 -2
  248. package/src/index.ts +2 -0
  249. package/src/providers/services/EventServiceProvider.tsx +4 -1
  250. package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
  251. package/src/rbac/api.test.ts +21 -6
  252. package/src/rbac/api.ts +32 -11
  253. package/src/rbac/audit-batched.ts +223 -0
  254. package/src/rbac/audit-enhanced.ts +2 -2
  255. package/src/rbac/audit.test.ts +6 -5
  256. package/src/rbac/audit.ts +34 -6
  257. package/src/rbac/cache-invalidation.ts +63 -12
  258. package/src/rbac/cache.test.ts +2 -2
  259. package/src/rbac/cache.ts +61 -14
  260. package/src/rbac/components/PagePermissionGuard.tsx +19 -10
  261. package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
  262. package/src/rbac/config.ts +9 -0
  263. package/src/rbac/engine.ts +2 -21
  264. package/src/rbac/hooks/usePermissions.ts +21 -5
  265. package/src/rbac/index.ts +19 -0
  266. package/src/rbac/performance.ts +210 -0
  267. package/src/rbac/request-deduplication.ts +87 -0
  268. package/src/rbac/utils/deep-equal.ts +93 -0
  269. package/src/styles/core.css +5 -5
  270. package/src/types/database.generated.ts +63 -9
  271. package/src/types/file-reference.ts +3 -1
  272. package/src/utils/file-reference/__tests__/file-reference.test.ts +89 -8
  273. package/src/utils/file-reference/index.ts +56 -17
  274. package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
  275. package/src/utils/google-places/googlePlacesUtils.ts +475 -0
  276. package/src/utils/google-places/index.ts +26 -0
  277. package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
  278. package/src/utils/google-places/types.ts +94 -0
  279. package/src/utils/index.ts +23 -0
  280. package/src/utils/request-deduplication.ts +165 -0
  281. package/src/utils/security/secureDataAccess.ts +1 -1
  282. package/src/utils/storage/helpers.ts +211 -4
  283. package/dist/chunk-445GEP27.js.map +0 -1
  284. package/dist/chunk-AISXLWGZ.js.map +0 -1
  285. package/dist/chunk-FMUCXFII.js +0 -76
  286. package/dist/chunk-FMUCXFII.js.map +0 -1
  287. package/dist/chunk-FSFQFJCU.js.map +0 -1
  288. package/dist/chunk-FXFJRTKI.js.map +0 -1
  289. package/dist/chunk-HC67NW5K.js.map +0 -1
  290. package/dist/chunk-IXSNYUCT.js.map +0 -1
  291. package/dist/chunk-MX3EIJGQ.js.map +0 -1
  292. package/dist/chunk-OKI34GZD.js.map +0 -1
  293. package/dist/chunk-STTZQK2I.js.map +0 -1
  294. package/dist/chunk-U6WNSFX5.js.map +0 -1
  295. /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-K3RJRSOX.js.map} +0 -0
  296. /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-B76OWOAT.js.map} +0 -0
  297. /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
  298. /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
  299. /package/dist/{chunk-HGPQUCBC.js.map → chunk-FMTK4XNN.js.map} +0 -0
  300. /package/dist/{chunk-XAUHJD3L.js.map → chunk-K2JGDXGU.js.map} +0 -0
@@ -1829,37 +1829,51 @@ A comprehensive file upload component with drag-and-drop support, validation, an
1829
1829
  ```typescript
1830
1830
  interface FileUploadProps {
1831
1831
  supabase: SupabaseClient;
1832
- appName: string;
1833
- orgId: string;
1834
- onUploadComplete?: (fileRef: FileReference) => void;
1835
- onUploadStart?: (file: File) => void;
1832
+ table_name: string;
1833
+ record_id: string;
1834
+ organisation_id: string;
1835
+ app_id?: string; // Optional - will be resolved from app name if not provided
1836
+ category: FileCategory; // File category for metadata (stored in file_metadata JSONB field)
1837
+ folder: string; // Folder name in storage bucket (e.g., 'profile_photos', 'documents')
1838
+ pageContext: string; // The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
1836
1839
  accept?: string;
1837
1840
  maxSize?: number;
1838
1841
  multiple?: boolean;
1839
1842
  disabled?: boolean;
1843
+ isPublic?: boolean; // Whether files should be uploaded to public-files bucket
1840
1844
  className?: string;
1845
+ showPreview?: boolean; // Show image preview for accepted files
1846
+ showProgress?: boolean; // Show upload progress bar
1847
+ onUploadSuccess?: (result: FileUploadResult) => void;
1848
+ onUploadError?: (error: string, file?: File) => void;
1849
+ onProgress?: (progress: UploadProgress) => void;
1841
1850
  children?: React.ReactNode;
1842
1851
  }
1843
1852
  ```
1844
1853
 
1854
+ **Note:** The `category` prop is used for metadata purposes (stored in the `file_metadata` JSONB field), while the `folder` prop determines the actual storage path: `{orgId}/{folder}/{timestamp-uuid-filename}`. You can use the same value for both (e.g., `category={FileCategory.PROFILE_PHOTOS}` and `folder="profile_photos"`), or use different values if needed.
1855
+
1845
1856
  #### Usage
1846
1857
 
1847
1858
  ```tsx
1848
- import { FileUpload, FileCategory, logger } from '@jmruthers/pace-core';
1859
+ import { FileUpload, FileCategory } from '@jmruthers/pace-core';
1849
1860
 
1850
1861
  function MyFileUpload() {
1851
- const handleUpload = (fileRef: FileReference) => {
1852
- logger.debug('FileUpload', 'File uploaded:', { fileId: fileRef.id, fileName: fileRef.name });
1853
- };
1854
-
1855
1862
  return (
1856
1863
  <FileUpload
1857
1864
  supabase={supabase}
1858
- appName="my-app"
1859
- orgId={organisationId}
1860
- onUploadComplete={handleUpload}
1865
+ table_name="pace_person"
1866
+ record_id={personId}
1867
+ organisation_id={organisationId}
1868
+ category={FileCategory.PROFILE_PHOTOS}
1869
+ folder="profile_photos"
1870
+ pageContext="configuration"
1861
1871
  accept="image/*"
1862
1872
  maxSize={2 * 1024 * 1024} // 2MB
1873
+ onUploadSuccess={(result) => {
1874
+ console.log('Uploaded:', result.file_reference);
1875
+ console.log('URL:', result.file_url);
1876
+ }}
1863
1877
  >
1864
1878
  <div className="border-2 border-dashed border-main-300 rounded-lg p-8 text-center">
1865
1879
  <p>Drag and drop files here or click to browse</p>
@@ -490,6 +490,17 @@ function VirtualizedEventList() {
490
490
 
491
491
  ## Network Optimization
492
492
 
493
+ ### RBAC Performance Optimizations
494
+
495
+ The RBAC system includes built-in performance optimizations that significantly reduce network requests:
496
+
497
+ - **Request Deduplication**: Multiple components checking the same permission share network requests
498
+ - **Enhanced Caching**: Two-tier caching (60s short-term + 5min session cache)
499
+ - **Batched Audit Logging**: Audit events are batched to reduce network requests
500
+ - **Memoization**: Components are memoized to prevent unnecessary re-renders
501
+
502
+ See the [RBAC Performance Guide](../rbac/performance.md) for detailed information.
503
+
493
504
  ### 1. Request Deduplication
494
505
 
495
506
  ```typescript
@@ -45,14 +45,16 @@ CREATE TABLE file_references (
45
45
 
46
46
  **Organisation-First Structure:**
47
47
  ```
48
- {bucket}/{orgId}/{category}/{filename}
48
+ {bucket}/{orgId}/{folder}/{timestamp-uuid-filename}
49
49
 
50
50
  Examples:
51
51
  - files/org-123/profile_photos/timestamp-uuid-photo.jpg (private)
52
- - files/org-123/id_documents/timestamp-uuid-passport.pdf (private)
52
+ - files/org-123/documents/timestamp-uuid-passport.pdf (private)
53
53
  - public-files/org-123/event_logos/timestamp-uuid-logo.png (public)
54
54
  ```
55
55
 
56
+ **Note:** The `folder` prop determines the storage path, while `category` is stored in the `file_metadata` JSONB field for filtering and metadata purposes. You can use the same value for both (e.g., `category={FileCategory.PROFILE_PHOTOS}` and `folder="profile_photos"`), or use different values if needed.
57
+
56
58
  ### Bucket Selection
57
59
 
58
60
  The system uses two Supabase storage buckets:
@@ -77,7 +79,10 @@ await service.createFileReference({
77
79
  table_name: 'pace_person',
78
80
  record_id: personId,
79
81
  organisation_id: orgId,
82
+ app_id: 'your-app-id',
80
83
  category: FileCategory.PROFILE_PHOTOS,
84
+ folder: 'profile_photos',
85
+ pageContext: 'configuration',
81
86
  is_public: false // Uses 'files' bucket
82
87
  }, file);
83
88
 
@@ -86,7 +91,10 @@ await service.createFileReference({
86
91
  table_name: 'event',
87
92
  record_id: eventId,
88
93
  organisation_id: orgId,
94
+ app_id: 'your-app-id',
89
95
  category: FileCategory.EVENT_LOGOS,
96
+ folder: 'event_logos',
97
+ pageContext: 'configuration',
90
98
  is_public: true // Uses 'public-files' bucket
91
99
  }, file);
92
100
  ```
@@ -136,6 +144,8 @@ import { FileUpload, FileCategory } from '@jmruthers/pace-core';
136
144
  organisation_id={orgId}
137
145
  app_id="your-app-id" // Optional - auto-resolved from app name if not provided
138
146
  category={FileCategory.PROFILE_PHOTOS}
147
+ folder="profile_photos"
148
+ pageContext="configuration"
139
149
  accept="image/*"
140
150
  maxSize={5 * 1024 * 1024}
141
151
  onUploadSuccess={(result) => {
@@ -157,6 +167,8 @@ import { FileUpload, FileCategory } from '@jmruthers/pace-core';
157
167
  organisation_id={orgId}
158
168
  // app_id omitted - will be auto-resolved from app name
159
169
  category={FileCategory.PROFILE_PHOTOS}
170
+ folder="profile_photos"
171
+ pageContext="configuration"
160
172
  accept="image/*"
161
173
  maxSize={5 * 1024 * 1024}
162
174
  showProgress={true}
@@ -191,6 +203,8 @@ import { FileUpload, FileCategory } from '@jmruthers/pace-core';
191
203
  organisation_id={orgId}
192
204
  app_id="your-app-id"
193
205
  category={FileCategory.EVENT_LOGOS}
206
+ folder="event_logos"
207
+ pageContext="configuration"
194
208
  accept="image/*"
195
209
  isPublic={true} // Uploads to public-files bucket
196
210
  showPreview={true}
@@ -210,6 +224,8 @@ import { FileUpload, FileCategory } from '@jmruthers/pace-core';
210
224
  organisation_id={orgId}
211
225
  app_id="your-app-id"
212
226
  category={FileCategory.PROFILE_PHOTOS}
227
+ folder="profile_photos"
228
+ pageContext="configuration"
213
229
  >
214
230
  <div className="custom-upload-ui">
215
231
  <p>Click to upload profile photo</p>
@@ -237,6 +253,8 @@ import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
237
253
  organisation_id={orgId}
238
254
  app_id="your-app-id"
239
255
  category={FileCategory.PROFILE_PHOTOS}
256
+ folder="profile_photos"
257
+ pageContext="configuration"
240
258
  />
241
259
  </FileDisplay>
242
260
  ```
@@ -331,6 +349,8 @@ const result = await uploadFile({
331
349
  organisation_id: orgId,
332
350
  app_id: 'your-app-id',
333
351
  category: FileCategory.PROFILE_PHOTOS,
352
+ folder: 'profile_photos',
353
+ pageContext: 'configuration',
334
354
  is_public: false // Uses 'files' bucket
335
355
  }, file);
336
356
 
@@ -341,6 +361,8 @@ const publicResult = await uploadFile({
341
361
  organisation_id: orgId,
342
362
  app_id: 'your-app-id',
343
363
  category: FileCategory.EVENT_LOGOS,
364
+ folder: 'event_logos',
365
+ pageContext: 'configuration',
344
366
  is_public: true // Uses 'public-files' bucket
345
367
  }, logoFile);
346
368
 
@@ -48,6 +48,7 @@ function MyFileUpload() {
48
48
  organisation_id="org-123"
49
49
  // app_id auto-resolved from app name
50
50
  category={FileCategory.GENERAL_DOCUMENTS}
51
+ folder="documents"
51
52
  pageContext="configuration"
52
53
  accept=".pdf,.doc,.docx"
53
54
  maxSize={5 * 1024 * 1024} // 5MB
@@ -103,6 +104,8 @@ function UserProfile({ userId }: { userId: string }) {
103
104
  record_id={userId}
104
105
  organisation_id="org-123"
105
106
  category={FileCategory.GENERAL_DOCUMENTS}
107
+ folder="documents"
108
+ pageContext="configuration"
106
109
  accept=".pdf,.doc,.docx"
107
110
  maxSize={10 * 1024 * 1024}
108
111
  />
@@ -127,7 +130,8 @@ interface FileUploadProps {
127
130
  record_id: string;
128
131
  organisation_id: string;
129
132
  app_id?: string; // Optional - will be resolved from app name if not provided
130
- category: FileCategory;
133
+ category: FileCategory; // File category for metadata (stored in file_metadata JSONB field)
134
+ folder: string; // Folder name in storage bucket (e.g., 'profile_photos', 'documents')
131
135
  pageContext: string; // The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
132
136
  accept?: string;
133
137
  maxSize?: number;
@@ -144,6 +148,8 @@ interface FileUploadProps {
144
148
  }
145
149
  ```
146
150
 
151
+ **Note:** The `category` prop is used for metadata purposes (stored in the `file_metadata` JSONB field), while the `folder` prop determines the actual storage path: `{orgId}/{folder}/{timestamp-uuid-filename}`. You can use the same value for both (e.g., `category={FileCategory.PROFILE_PHOTOS}` and `folder="profile_photos"`), or use different values if needed.
152
+
147
153
  #### Usage Examples
148
154
 
149
155
  **Basic Upload:**
@@ -154,6 +160,7 @@ interface FileUploadProps {
154
160
  record_id="person-123"
155
161
  organisation_id={organisationId}
156
162
  category={FileCategory.PROFILE_PHOTOS}
163
+ folder="profile_photos"
157
164
  pageContext="configuration"
158
165
  accept="image/*"
159
166
  maxSize={2 * 1024 * 1024} // 2MB
@@ -171,6 +178,7 @@ interface FileUploadProps {
171
178
  record_id="person-123"
172
179
  organisation_id={organisationId}
173
180
  category={FileCategory.GENERAL_DOCUMENTS}
181
+ folder="documents"
174
182
  pageContext="forms"
175
183
  multiple={true}
176
184
  accept=".pdf,.doc,.docx"
@@ -273,6 +281,8 @@ interface FileDisplayProps {
273
281
  record_id={personId}
274
282
  organisation_id={organisationId}
275
283
  category={FileCategory.IMAGES}
284
+ folder="images"
285
+ pageContext="configuration"
276
286
  accept="image/*"
277
287
  showPreview={true}
278
288
  />
@@ -294,6 +304,29 @@ interface FileDisplayProps {
294
304
  // without wrapper divs, borders, or metadata. Prefers image files over other types.
295
305
  ```
296
306
 
307
+ **Document Display (Clickable Links):**
308
+ ```tsx
309
+ // Display a PDF or other document as a clickable link
310
+ <FileDisplay
311
+ supabase={supabase}
312
+ table_name="medi_action_plan"
313
+ record_id={planId}
314
+ organisation_id={organisationId}
315
+ category={FileCategory.MEDICAL_DOCUMENTS}
316
+ displayOnly={true}
317
+ showFallback={true}
318
+ fallbackText="View Action Plan"
319
+ />
320
+ // When displayOnly is true and the file is NOT an image (e.g., PDF, Word doc, Excel),
321
+ // renders as a clickable link that opens in a new tab. The link includes:
322
+ // - Document icon (FileText) on the left
323
+ // - Filename in the center
324
+ // - External link icon on the right
325
+ // - Proper security attributes (rel="noopener noreferrer")
326
+ // - Full accessibility support (ARIA labels, keyboard navigation, focus states)
327
+ // If the file URL is unavailable, falls back to the fallback UI.
328
+ ```
329
+
297
330
  ## Hooks
298
331
 
299
332
  ### useFileReference
@@ -451,6 +484,8 @@ await uploadFile({
451
484
  organisation_id: 'org-123',
452
485
  app_id: 'app-123',
453
486
  category: FileCategory.GENERAL_DOCUMENTS,
487
+ folder: 'documents',
488
+ pageContext: 'configuration',
454
489
  is_public: false
455
490
  }, pdfFile);
456
491
 
@@ -461,6 +496,8 @@ await uploadFile({
461
496
  organisation_id: 'org-123',
462
497
  app_id: 'app-123',
463
498
  category: FileCategory.PROFILE_PHOTOS,
499
+ folder: 'profile_photos',
500
+ pageContext: 'configuration',
464
501
  is_public: false
465
502
  }, imageFile);
466
503
  ```
@@ -96,7 +96,7 @@ The RBAC system uses a **database-first architecture** where all permission logi
96
96
  ## 🎯 Key Features
97
97
 
98
98
  - **🔐 Secure by Default** - Organisation context enforced, RLS integrated, database-first API
99
- - **⚡ High Performance** - Intelligent caching with 60s TTL, optimized RPC calls
99
+ - **⚡ High Performance** - Intelligent two-tier caching (60s + 5min), request deduplication, batched audit logging, optimized RPC calls
100
100
  - **🎨 React Integration** - Hooks and components for easy UI integration
101
101
  - **🔄 Real-time Updates** - Automatic cache invalidation on permission changes
102
102
  - **📊 Audit Logging** - Complete audit trail for all permission checks
@@ -422,6 +422,7 @@ When database connection is lost:
422
422
  - **[API Reference](./api-reference.md)** - Explore all available APIs
423
423
  - **[Examples](./examples.md)** - See real-world usage patterns
424
424
  - **[Advanced Patterns](./advanced-patterns.md)** - Learn optimization techniques
425
+ - **[Performance Guide](./performance.md)** - Performance optimizations and configuration
425
426
 
426
427
  ## 🆘 Need Help?
427
428
 
@@ -331,6 +331,17 @@ function UsersPage() {
331
331
 
332
332
  4. **Error Handling**: If permission check fails, the component shows the `fallback`. Check browser console for `[PagePermissionGuard]` logs to debug issues.
333
333
 
334
+ #### Performance Optimizations
335
+
336
+ `PagePermissionGuard` includes built-in performance optimizations:
337
+
338
+ - **Request Deduplication**: Multiple instances checking the same permission share network requests
339
+ - **Enhanced Caching**: Two-tier caching (60s short-term + 5min session cache for page-level checks)
340
+ - **Batched Audit Logging**: Audit events are automatically batched to reduce network requests
341
+ - **Memoization**: Component is memoized with deep equality checks to prevent unnecessary re-renders
342
+
343
+ These optimizations reduce network requests from 1,200+ to <50 per page load. See [RBAC Performance Guide](../rbac/performance.md) for details.
344
+
334
345
  ### NavigationGuard
335
346
 
336
347
  Conditionally render navigation items based on permissions.
@@ -0,0 +1,320 @@
1
+ ---
2
+ lastUpdated: 2025-01-26T00:00:00+11:00
3
+ version: 0.5.186
4
+ reviewedBy: documentation-standards-audit
5
+ ---
6
+
7
+ # RBAC Performance Optimizations
8
+
9
+ > **📚 RBAC Performance Guide** | [← Back to RBAC Documentation](./README.md) | [API Reference](./api-reference.md)
10
+
11
+ The RBAC system includes comprehensive performance optimizations to minimize network requests and improve page load times. This guide explains how these optimizations work and how to configure them.
12
+
13
+ ## Overview
14
+
15
+ The RBAC performance optimizations address the common issue of excessive network requests when using `PagePermissionGuard` and other RBAC components. Without optimizations, a single page load could generate 1,200+ network requests. With optimizations enabled, this is reduced to <50 requests per page load.
16
+
17
+ ## Performance Features
18
+
19
+ ### 1. Request Deduplication
20
+
21
+ **Problem**: Multiple components checking the same permission simultaneously make separate network requests.
22
+
23
+ **Solution**: Request deduplication shares in-flight permission check requests across all components. When multiple `PagePermissionGuard` instances check the same permission at the same time, they share a single network request.
24
+
25
+ **How it works**:
26
+ - When a permission check is requested, the system checks if an identical request is already in-flight
27
+ - If found, the existing promise is returned instead of making a new request
28
+ - The deduplication map is automatically cleaned up when requests complete
29
+
30
+ **Example**:
31
+ ```tsx
32
+ // Three components checking the same permission
33
+ <PagePermissionGuard pageName="dashboard" operation="read">
34
+ <Component1 />
35
+ </PagePermissionGuard>
36
+ <PagePermissionGuard pageName="dashboard" operation="read">
37
+ <Component2 />
38
+ </PagePermissionGuard>
39
+ <PagePermissionGuard pageName="dashboard" operation="read">
40
+ <Component3 />
41
+ </PagePermissionGuard>
42
+ // Result: Only 1 network request instead of 3
43
+ ```
44
+
45
+ **Configuration**: Enabled by default. Can be disabled via configuration:
46
+ ```typescript
47
+ setupRBAC(supabase, {
48
+ performance: {
49
+ enableRequestDeduplication: false, // Disable deduplication
50
+ },
51
+ });
52
+ ```
53
+
54
+ ### 2. Enhanced Caching
55
+
56
+ **Problem**: Short cache TTL (60 seconds) causes frequent re-checks, especially for stable page-level permissions.
57
+
58
+ **Solution**: Two-tier caching strategy with session-level cache for page-level permissions.
59
+
60
+ **Cache Tiers**:
61
+ - **Short-term cache**: 60 seconds for frequently changing permissions
62
+ - **Session cache**: 5 minutes for stable page-level permissions
63
+
64
+ **How it works**:
65
+ - Permission checks first look in the short-term cache
66
+ - If not found, check the session cache (for page-level checks)
67
+ - Page-level permission checks automatically use session cache
68
+ - Cache is invalidated on user/org/event changes
69
+
70
+ **Example**:
71
+ ```typescript
72
+ // First check: Network request + cache result
73
+ await isPermittedCached({ userId, scope, permission: 'read:page.dashboard' });
74
+
75
+ // Subsequent checks within 5 minutes: Cache hit (no network request)
76
+ await isPermittedCached({ userId, scope, permission: 'read:page.dashboard' });
77
+ ```
78
+
79
+ **Configuration**:
80
+ ```typescript
81
+ setupRBAC(supabase, {
82
+ cache: {
83
+ ttl: 60000, // Short-term cache TTL (default: 60s)
84
+ sessionTtl: 300000, // Session cache TTL (default: 5min)
85
+ },
86
+ });
87
+ ```
88
+
89
+ ### 3. Batched Audit Logging
90
+
91
+ **Problem**: Every permission check generates a separate audit log request, even for cached checks.
92
+
93
+ **Solution**: Batched audit logging queues events and sends them in batches, and skips logging for cached checks.
94
+
95
+ **How it works**:
96
+ - Audit events are queued instead of being sent immediately
97
+ - Events are batched by time window (default: 100ms) or batch size (default: 10 events)
98
+ - Cached permission checks skip audit logging entirely (only cache misses are logged)
99
+ - Batches are automatically flushed when the window expires or batch size is reached
100
+
101
+ **Example**:
102
+ ```typescript
103
+ // 10 permission checks in quick succession
104
+ // Without batching: 10 separate audit log requests
105
+ // With batching: 1 batched request with 10 events
106
+ ```
107
+
108
+ **Configuration**:
109
+ ```typescript
110
+ setupRBAC(supabase, {
111
+ audit: {
112
+ batched: true, // Enable batched logging (default: true)
113
+ batchWindow: 100, // Time window in ms (default: 100ms)
114
+ batchSize: 10, // Maximum batch size (default: 10)
115
+ },
116
+ performance: {
117
+ enableBatchedAuditLogging: true, // Enable/disable (default: true)
118
+ },
119
+ });
120
+ ```
121
+
122
+ ### 4. Improved Memoization
123
+
124
+ **Problem**: Scope objects are recreated on every render, causing unnecessary permission re-checks.
125
+
126
+ **Solution**: Deep equality checks for scope objects and React.memo for components.
127
+
128
+ **How it works**:
129
+ - `PagePermissionGuard` uses `React.memo` to prevent unnecessary re-renders
130
+ - Scope objects are compared using deep equality instead of reference equality
131
+ - Permission strings are memoized to prevent recreation
132
+ - Only re-check permissions when dependencies actually change
133
+
134
+ **Example**:
135
+ ```tsx
136
+ // Component re-renders with same scope object
137
+ // Without memoization: Permission re-checked
138
+ // With memoization: Cached result used, no re-check
139
+ ```
140
+
141
+ **Configuration**: Automatic, no configuration needed.
142
+
143
+ ### 5. Performance Monitoring
144
+
145
+ **Problem**: No visibility into performance metrics and optimization effectiveness.
146
+
147
+ **Solution**: Built-in performance monitoring tracks key metrics.
148
+
149
+ **Metrics Tracked**:
150
+ - Total permission checks
151
+ - Cache hit rate
152
+ - Request deduplication rate
153
+ - Network request count
154
+ - Average response time
155
+ - Batched vs individual audit events
156
+
157
+ **Usage**:
158
+ ```typescript
159
+ import {
160
+ enablePerformanceMonitoring,
161
+ getPerformanceMetrics,
162
+ getPerformanceSummary
163
+ } from '@jmruthers/pace-core/rbac';
164
+
165
+ // Enable monitoring
166
+ enablePerformanceMonitoring();
167
+
168
+ // Get metrics
169
+ const metrics = getPerformanceMetrics();
170
+ console.log('Cache hit rate:', metrics.cacheHitRate);
171
+ console.log('Network requests:', metrics.networkRequests);
172
+
173
+ // Get formatted summary
174
+ console.log(getPerformanceSummary());
175
+ ```
176
+
177
+ **Configuration**:
178
+ ```typescript
179
+ setupRBAC(supabase, {
180
+ performance: {
181
+ enablePerformanceTracking: true, // Enable in development
182
+ },
183
+ });
184
+ ```
185
+
186
+ ## Configuration
187
+
188
+ All performance optimizations can be configured when setting up RBAC:
189
+
190
+ ```typescript
191
+ import { setupRBAC } from '@jmruthers/pace-core/rbac';
192
+
193
+ setupRBAC(supabase, {
194
+ // Cache configuration
195
+ cache: {
196
+ ttl: 60000, // Short-term cache TTL (default: 60s)
197
+ sessionTtl: 300000, // Session cache TTL (default: 5min)
198
+ enabled: true, // Enable caching (default: true)
199
+ },
200
+
201
+ // Audit configuration
202
+ audit: {
203
+ enabled: true, // Enable audit logging (default: true)
204
+ batched: true, // Enable batched logging (default: true)
205
+ batchWindow: 100, // Batch time window in ms (default: 100ms)
206
+ batchSize: 10, // Maximum batch size (default: 10)
207
+ },
208
+
209
+ // Performance configuration
210
+ performance: {
211
+ enableRequestDeduplication: true, // Enable deduplication (default: true)
212
+ enableBatchedAuditLogging: true, // Enable batched audit (default: true)
213
+ enablePerformanceTracking: false, // Enable monitoring (default: false in production)
214
+ },
215
+ });
216
+ ```
217
+
218
+ ## Expected Performance Improvements
219
+
220
+ With all optimizations enabled:
221
+
222
+ | Metric | Before | After | Improvement |
223
+ |--------|--------|-------|-------------|
224
+ | Network Requests | 1,200+ | <50 | 96% reduction |
225
+ | Page Load Time | 1+ minute | <5 seconds | 92% reduction |
226
+ | Bandwidth | 13+ MB | <1 MB | 92% reduction |
227
+ | Cache Hit Rate | N/A | >90% | - |
228
+ | Audit Log Requests | 1,200+ | <50 | 96% reduction |
229
+
230
+ ## Best Practices
231
+
232
+ ### 1. Enable All Optimizations
233
+
234
+ All optimizations are enabled by default. Only disable them if you have a specific reason:
235
+
236
+ ```typescript
237
+ // ✅ Good: Use defaults
238
+ setupRBAC(supabase);
239
+
240
+ // ❌ Avoid: Disabling optimizations without reason
241
+ setupRBAC(supabase, {
242
+ performance: {
243
+ enableRequestDeduplication: false, // Only if you have a specific need
244
+ },
245
+ });
246
+ ```
247
+
248
+ ### 2. Monitor Performance in Development
249
+
250
+ Enable performance tracking during development to identify bottlenecks:
251
+
252
+ ```typescript
253
+ setupRBAC(supabase, {
254
+ performance: {
255
+ enablePerformanceTracking: import.meta.env.MODE === 'development',
256
+ },
257
+ });
258
+ ```
259
+
260
+ ### 3. Adjust Cache TTL Based on Use Case
261
+
262
+ For applications with frequently changing permissions, reduce cache TTL:
263
+
264
+ ```typescript
265
+ setupRBAC(supabase, {
266
+ cache: {
267
+ ttl: 30000, // 30 seconds for frequently changing permissions
268
+ sessionTtl: 180000, // 3 minutes for page-level checks
269
+ },
270
+ });
271
+ ```
272
+
273
+ ### 4. Tune Batch Settings for High-Volume Apps
274
+
275
+ For applications with many permission checks, adjust batch settings:
276
+
277
+ ```typescript
278
+ setupRBAC(supabase, {
279
+ audit: {
280
+ batchWindow: 50, // Shorter window for faster batching
281
+ batchSize: 20, // Larger batches for high volume
282
+ },
283
+ });
284
+ ```
285
+
286
+ ## Troubleshooting
287
+
288
+ ### High Network Request Count
289
+
290
+ If you're still seeing high request counts:
291
+
292
+ 1. **Check cache hit rate**: Use performance monitoring to verify cache is working
293
+ 2. **Verify deduplication**: Ensure multiple components aren't bypassing deduplication
294
+ 3. **Check audit batching**: Verify batched audit logging is enabled
295
+ 4. **Review scope changes**: Ensure scope objects aren't being recreated unnecessarily
296
+
297
+ ### Low Cache Hit Rate
298
+
299
+ If cache hit rate is low:
300
+
301
+ 1. **Check cache TTL**: Verify cache TTL settings are appropriate
302
+ 2. **Review cache invalidation**: Ensure cache isn't being invalidated too frequently
303
+ 3. **Verify session cache**: Check that page-level checks are using session cache
304
+ 4. **Monitor scope changes**: Frequent scope changes will reduce cache effectiveness
305
+
306
+ ### Performance Monitoring Not Working
307
+
308
+ If performance monitoring isn't showing data:
309
+
310
+ 1. **Verify it's enabled**: Check `enablePerformanceTracking` is set to `true`
311
+ 2. **Check timing**: Metrics accumulate over time, check after multiple permission checks
312
+ 3. **Review console**: Check for any errors in the console
313
+
314
+ ## Related Documentation
315
+
316
+ - [PagePermissionGuard API Reference](./api-reference.md#pagepermissionguard) - Component API
317
+ - [RBAC Configuration](./api-reference.md#configuration) - Configuration options
318
+ - [Performance Best Practices](../best-practices/performance.md) - General performance guide
319
+ - [RBAC Getting Started](./getting-started.md) - Getting started guide
320
+
@@ -11,6 +11,11 @@ Define the core architectural principles for pace-core so that components, APIs,
11
11
  - Secure by default
12
12
  - Performance-conscious
13
13
 
14
+ ## Performance Requirements
15
+ - **RLS policies must use helper functions** - Never use subqueries in RLS policies as they cause N+1 query patterns
16
+ - **Database migrations must be tested** - Verify migrations don't cause query timeouts or performance degradation
17
+ - **Monitor query performance** - Use EXPLAIN ANALYZE to verify RLS policies don't create expensive execution plans
18
+
14
19
  ## Boundaries
15
20
  ### In scope
16
21
  - UI primitives
@@ -13,6 +13,16 @@
13
13
  - Never store secrets in code
14
14
  - Use safe error messaging
15
15
 
16
+ ## RLS Policy Performance Rules
17
+ - **NEVER use subqueries in RLS policies** - They execute for every row check and cause severe performance degradation
18
+ - **ALWAYS use helper functions** - Create STABLE SECURITY DEFINER functions for lookups (e.g., `get_base_app_id()`, `get_form_event_id()`)
19
+ - **Helper functions must be**:
20
+ - `STABLE` - Results are consistent within a transaction
21
+ - `SECURITY DEFINER` - Bypass RLS to avoid recursion
22
+ - `SET search_path TO 'public'` - Prevent search path injection
23
+ - **Test RLS policies** - Verify they don't cause query timeouts or N+1 query patterns
24
+ - **Monitor performance** - Watch for slow queries after RLS policy changes
25
+
16
26
  ## Logging Rules
17
27
  Allowed:
18
28
  - IDs
@@ -28,3 +38,5 @@ Forbidden:
28
38
  - Ensure no sensitive logs
29
39
  - Replace raw errors with ApiError
30
40
  - Validate input shapes
41
+ - **RLS policies use helper functions, not subqueries**
42
+ - **Helper functions are STABLE and SECURITY DEFINER**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmruthers/pace-core",
3
- "version": "0.5.185",
3
+ "version": "0.5.187",
4
4
  "description": "Clean, modern React component library with Tailwind v4 styling and native utilities",
5
5
  "private": false,
6
6
  "publishConfig": {