@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.
- package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
- package/dist/{DataTable-IX2NBUTP.js → DataTable-K3RJRSOX.js} +7 -7
- package/dist/{PublicPageProvider-BABf6JCh.d.ts → PublicPageProvider-DrLDztHt.d.ts} +214 -107
- package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-B76OWOAT.js} +2 -2
- package/dist/{api-BMFCXVQX.js → api-YP7XD5L6.js} +3 -3
- package/dist/{audit-WRS3KJKI.js → audit-B5P6FFIR.js} +2 -2
- package/dist/{chunk-445GEP27.js → chunk-3IC5WCMO.js} +33 -8
- package/dist/chunk-3IC5WCMO.js.map +1 -0
- package/dist/{chunk-OKI34GZD.js → chunk-3NFNJOO7.js} +8 -8
- package/dist/chunk-3NFNJOO7.js.map +1 -0
- package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
- package/dist/chunk-63FOKYGO.js.map +1 -0
- package/dist/{chunk-MX3EIJGQ.js → chunk-C4OYJOV4.js} +631 -97
- package/dist/chunk-C4OYJOV4.js.map +1 -0
- package/dist/{chunk-HGPQUCBC.js → chunk-FMTK4XNN.js} +3 -3
- package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
- package/dist/chunk-HEHYGYOX.js.map +1 -0
- package/dist/{chunk-XAUHJD3L.js → chunk-K2JGDXGU.js} +2 -2
- package/dist/{chunk-HC67NW5K.js → chunk-LBBUPSSC.js} +863 -552
- package/dist/chunk-LBBUPSSC.js.map +1 -0
- package/dist/{chunk-IXSNYUCT.js → chunk-SAUPYVLF.js} +1 -1
- package/dist/chunk-SAUPYVLF.js.map +1 -0
- package/dist/{chunk-AISXLWGZ.js → chunk-T6ZJVI3A.js} +27 -23
- package/dist/chunk-T6ZJVI3A.js.map +1 -0
- package/dist/{chunk-STTZQK2I.js → chunk-ULX5FYEM.js} +9 -7
- package/dist/chunk-ULX5FYEM.js.map +1 -0
- package/dist/{chunk-FXFJRTKI.js → chunk-WK2Y6TGA.js} +3 -3
- package/dist/chunk-WK2Y6TGA.js.map +1 -0
- package/dist/chunk-YHCN776L.js +447 -0
- package/dist/chunk-YHCN776L.js.map +1 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +12 -10
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/{file-reference-BjR39ktt.d.ts → file-reference-D037xOFK.d.ts} +3 -1
- package/dist/hooks.d.ts +265 -6
- package/dist/hooks.js +148 -49
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +25 -10
- package/dist/index.js +65 -30
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.d.ts +125 -8
- package/dist/rbac/index.js +27 -7
- package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CvnC3d-e.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +3 -3
- package/dist/utils.d.ts +214 -4
- package/dist/utils.js +22 -2
- package/dist/utils.js.map +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +21 -17
- package/docs/api/classes/RBACCache.md +31 -23
- package/docs/api/classes/RBACEngine.md +6 -6
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +241 -0
- package/docs/api/interfaces/AddressFieldRef.md +94 -0
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +75 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +15 -15
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +33 -9
- package/docs/api/interfaces/FileUploadProps.md +36 -14
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +120 -0
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +27 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +5 -5
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +328 -69
- package/docs/api-reference/components.md +26 -12
- package/docs/best-practices/performance.md +11 -0
- package/docs/implementation-guides/file-reference-system.md +24 -2
- package/docs/implementation-guides/file-upload-storage.md +38 -1
- package/docs/rbac/README.md +2 -1
- package/docs/rbac/api-reference.md +11 -0
- package/docs/rbac/performance.md +320 -0
- package/docs/standards/01-architecture-standard.md +5 -0
- package/docs/standards/05-security-standard.md +12 -0
- package/package.json +1 -1
- package/scripts/check-pace-core-compliance.js +512 -0
- package/src/components/AddressField/AddressField.test.tsx +411 -0
- package/src/components/AddressField/AddressField.tsx +323 -0
- package/src/components/AddressField/README.md +336 -0
- package/src/components/AddressField/index.ts +10 -0
- package/src/components/AddressField/types.ts +65 -0
- package/src/components/FileDisplay/FileDisplay.test.tsx +454 -0
- package/src/components/FileDisplay/FileDisplay.tsx +28 -1
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +7 -1
- package/src/components/Header/Header.tsx +2 -5
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +134 -1
- package/src/components/index.ts +2 -0
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
- package/src/hooks/index.ts +9 -0
- package/src/hooks/public/usePublicFileDisplay.ts +8 -10
- package/src/hooks/useAddressAutocomplete.test.ts +318 -0
- package/src/hooks/useAddressAutocomplete.ts +268 -0
- package/src/hooks/useFileDisplay.ts +3 -15
- package/src/hooks/useFileReference.test.ts +21 -3
- package/src/hooks/useFileReference.ts +3 -24
- package/src/hooks/useFileUrlCache.ts +246 -0
- package/src/hooks/useInactivityTracker.ts +31 -20
- package/src/hooks/useOrganisationSecurity.test.ts +10 -7
- package/src/hooks/useOrganisationSecurity.ts +3 -3
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useQueryCache.ts +315 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +2 -0
- package/src/providers/services/EventServiceProvider.tsx +4 -1
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/api.test.ts +21 -6
- package/src/rbac/api.ts +32 -11
- package/src/rbac/audit-batched.ts +223 -0
- package/src/rbac/audit-enhanced.ts +2 -2
- package/src/rbac/audit.test.ts +6 -5
- package/src/rbac/audit.ts +34 -6
- package/src/rbac/cache-invalidation.ts +63 -12
- package/src/rbac/cache.test.ts +2 -2
- package/src/rbac/cache.ts +61 -14
- package/src/rbac/components/PagePermissionGuard.tsx +19 -10
- package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
- package/src/rbac/config.ts +9 -0
- package/src/rbac/engine.ts +2 -21
- package/src/rbac/hooks/usePermissions.ts +21 -5
- package/src/rbac/index.ts +19 -0
- package/src/rbac/performance.ts +210 -0
- package/src/rbac/request-deduplication.ts +87 -0
- package/src/rbac/utils/deep-equal.ts +93 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +3 -1
- package/src/utils/file-reference/__tests__/file-reference.test.ts +89 -8
- package/src/utils/file-reference/index.ts +56 -17
- package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
- package/src/utils/google-places/googlePlacesUtils.ts +475 -0
- package/src/utils/google-places/index.ts +26 -0
- package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
- package/src/utils/google-places/types.ts +94 -0
- package/src/utils/index.ts +23 -0
- package/src/utils/request-deduplication.ts +165 -0
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +211 -4
- package/dist/chunk-445GEP27.js.map +0 -1
- package/dist/chunk-AISXLWGZ.js.map +0 -1
- package/dist/chunk-FMUCXFII.js +0 -76
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-FSFQFJCU.js.map +0 -1
- package/dist/chunk-FXFJRTKI.js.map +0 -1
- package/dist/chunk-HC67NW5K.js.map +0 -1
- package/dist/chunk-IXSNYUCT.js.map +0 -1
- package/dist/chunk-MX3EIJGQ.js.map +0 -1
- package/dist/chunk-OKI34GZD.js.map +0 -1
- package/dist/chunk-STTZQK2I.js.map +0 -1
- package/dist/chunk-U6WNSFX5.js.map +0 -1
- /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-K3RJRSOX.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-B76OWOAT.js.map} +0 -0
- /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
- /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
- /package/dist/{chunk-HGPQUCBC.js.map → chunk-FMTK4XNN.js.map} +0 -0
- /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
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
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
|
|
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
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
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}/{
|
|
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/
|
|
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
|
```
|
package/docs/rbac/README.md
CHANGED
|
@@ -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
|
|
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**
|