@jmruthers/pace-core 0.5.186 → 0.5.188
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-IX2NBUTP.js → DataTable-GUFUNZ3N.js} +7 -7
- package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
- package/dist/{PublicPageProvider-DIzEzwKl.d.ts → PublicPageProvider-DrLDztHt.d.ts} +211 -106
- package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-643PUAIM.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-HGPQUCBC.js → chunk-2UUZZJFT.js} +3 -3
- package/dist/{chunk-445GEP27.js → chunk-3GOZZZYH.js} +33 -8
- package/dist/chunk-3GOZZZYH.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-DAGICKHT.js → chunk-DDM4CCYT.js} +3 -3
- package/dist/{chunk-XAUHJD3L.js → chunk-E7UAOUMY.js} +2 -2
- package/dist/{chunk-HDCUMOOI.js → chunk-EFCLXK7F.js} +792 -559
- package/dist/chunk-EFCLXK7F.js.map +1 -0
- package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
- package/dist/chunk-HEHYGYOX.js.map +1 -0
- package/dist/{chunk-GRIQLQ52.js → chunk-IM4QE42D.js} +27 -23
- package/dist/chunk-IM4QE42D.js.map +1 -0
- package/dist/{chunk-OALXJH4Y.js → chunk-IPCH26AG.js} +8 -8
- package/dist/chunk-IPCH26AG.js.map +1 -0
- package/dist/{chunk-UQWSHFVX.js → chunk-SAUPYVLF.js} +1 -1
- package/dist/{chunk-UQWSHFVX.js.map → chunk-SAUPYVLF.js.map} +1 -1
- package/dist/{chunk-TC7D3CR3.js → chunk-UNOTYLQF.js} +556 -101
- package/dist/chunk-UNOTYLQF.js.map +1 -0
- package/dist/{chunk-FXFJRTKI.js → chunk-VGZZXKBR.js} +5 -5
- package/dist/chunk-VGZZXKBR.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/{file-reference-PRTSLxKx.d.ts → file-reference-D037xOFK.d.ts} +0 -1
- package/dist/hooks.d.ts +221 -6
- package/dist/hooks.js +146 -49
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +24 -9
- package/dist/index.js +62 -28
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.d.ts +124 -7
- 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 +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-D71QLlg4.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +2 -2
- package/dist/utils.d.ts +213 -3
- 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 +5 -5
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- 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 +1 -1
- 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 +26 -3
- 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 +1 -1
- 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 +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- 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 +318 -59
- package/docs/best-practices/performance.md +11 -0
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/implementation-guides/file-upload-storage.md +29 -0
- package/docs/implementation-guides/public-pages.md +140 -1230
- 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 +14 -0
- package/docs/standards/07-rbac-and-rls-standard.md +356 -0
- package/package.json +1 -1
- package/src/__tests__/public-recipe-view.test.ts +199 -0
- package/src/__tests__/rls-policies.test.ts +333 -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/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 +6 -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 +20 -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/useQueryCache.ts +315 -0
- package/src/index.ts +2 -0
- package/src/providers/services/EventServiceProvider.tsx +4 -1
- 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/services/OrganisationService.ts +5 -4
- package/src/types/file-reference.ts +0 -1
- package/src/utils/file-reference/__tests__/file-reference.test.ts +31 -4
- package/src/utils/file-reference/index.ts +44 -15
- 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/storage/helpers.ts +143 -4
- package/dist/chunk-445GEP27.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-GRIQLQ52.js.map +0 -1
- package/dist/chunk-HDCUMOOI.js.map +0 -1
- package/dist/chunk-OALXJH4Y.js.map +0 -1
- package/dist/chunk-TC7D3CR3.js.map +0 -1
- package/dist/chunk-U6WNSFX5.js.map +0 -1
- /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-643PUAIM.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-2UUZZJFT.js.map} +0 -0
- /package/dist/{chunk-DAGICKHT.js.map → chunk-DDM4CCYT.js.map} +0 -0
- /package/dist/{chunk-XAUHJD3L.js.map → chunk-E7UAOUMY.js.map} +0 -0
|
@@ -73,6 +73,7 @@ import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
|
73
73
|
import { UUID, Permission, Scope } from '../types';
|
|
74
74
|
import { createScopeFromEvent } from '../utils/eventContext';
|
|
75
75
|
import { getRBACLogger } from '../config';
|
|
76
|
+
import { scopeEqual } from '../utils/deep-equal';
|
|
76
77
|
|
|
77
78
|
export interface PagePermissionGuardProps {
|
|
78
79
|
/** Name of the page being protected */
|
|
@@ -333,17 +334,24 @@ const PagePermissionGuardComponent = ({
|
|
|
333
334
|
|
|
334
335
|
// Create a stable scope that only includes valid values
|
|
335
336
|
// OrganisationId is required - use undefined if not available, useCan will handle loading state
|
|
337
|
+
// Use ref to track previous scope for deep equality comparison
|
|
338
|
+
const prevScopeRef = useRef<Scope | null>(null);
|
|
336
339
|
const stableScope = useMemo(() => {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
const newScope: Scope = resolvedScope && resolvedScope.organisationId
|
|
341
|
+
? {
|
|
342
|
+
organisationId: resolvedScope.organisationId,
|
|
343
|
+
appId: resolvedScope.appId || undefined,
|
|
344
|
+
eventId: resolvedScope.eventId || undefined
|
|
345
|
+
}
|
|
346
|
+
: { organisationId: undefined, appId: undefined, eventId: undefined };
|
|
347
|
+
|
|
348
|
+
// Only return new object if scope actually changed (deep equality check)
|
|
349
|
+
if (scopeEqual(prevScopeRef.current, newScope)) {
|
|
350
|
+
return prevScopeRef.current!;
|
|
343
351
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return
|
|
352
|
+
|
|
353
|
+
prevScopeRef.current = newScope;
|
|
354
|
+
return newScope;
|
|
347
355
|
}, [resolvedScope]);
|
|
348
356
|
|
|
349
357
|
// Check if user has permission - only call useCan when we have a resolved scope with valid organisationId
|
|
@@ -487,5 +495,6 @@ function DefaultLoading() {
|
|
|
487
495
|
);
|
|
488
496
|
};
|
|
489
497
|
|
|
490
|
-
|
|
498
|
+
// Memoize component to prevent unnecessary re-renders when props haven't changed
|
|
499
|
+
export const PagePermissionGuard = React.memo(PagePermissionGuardComponent);
|
|
491
500
|
export default PagePermissionGuard;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance tests for PagePermissionGuard
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RBAC/Components/PagePermissionGuard/Performance
|
|
5
|
+
* @since 2.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { PagePermissionGuard } from '../PagePermissionGuard';
|
|
12
|
+
import { useCan } from '../../hooks';
|
|
13
|
+
import { useUnifiedAuth } from '../../../providers/services/UnifiedAuthProvider';
|
|
14
|
+
import { clearInFlightRequests, getInFlightRequestCount } from '../../request-deduplication';
|
|
15
|
+
import { rbacCache } from '../../cache';
|
|
16
|
+
|
|
17
|
+
// Mock dependencies - use same path as component
|
|
18
|
+
vi.mock('../../hooks', () => ({
|
|
19
|
+
useCan: vi.fn()
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
// Mock the auth provider
|
|
23
|
+
const mockUseUnifiedAuthFn = vi.fn();
|
|
24
|
+
vi.mock('../../../providers/services/UnifiedAuthProvider', () => ({
|
|
25
|
+
useUnifiedAuth: () => mockUseUnifiedAuthFn(),
|
|
26
|
+
UnifiedAuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Mock the event context utility
|
|
30
|
+
vi.mock('../../utils/eventContext', () => ({
|
|
31
|
+
createScopeFromEvent: vi.fn()
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// Mock the app name resolver
|
|
35
|
+
vi.mock('../../../utils/app/appNameResolver', () => ({
|
|
36
|
+
getCurrentAppName: vi.fn(() => 'test-app')
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
import { createScopeFromEvent } from '../../utils/eventContext';
|
|
40
|
+
import { getCurrentAppName } from '../../../utils/app/appNameResolver';
|
|
41
|
+
|
|
42
|
+
const mockUseCan = vi.mocked(useCan);
|
|
43
|
+
const mockCreateScopeFromEvent = vi.mocked(createScopeFromEvent);
|
|
44
|
+
const mockGetCurrentAppName = vi.mocked(getCurrentAppName);
|
|
45
|
+
|
|
46
|
+
describe('PagePermissionGuard Performance', () => {
|
|
47
|
+
const mockUser = {
|
|
48
|
+
id: 'user-123',
|
|
49
|
+
email: 'test@example.com',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const mockOrganisation = {
|
|
53
|
+
id: 'org-123',
|
|
54
|
+
name: 'Test Org',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const mockEvent = {
|
|
58
|
+
event_id: 'event-123',
|
|
59
|
+
name: 'Test Event',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const mockScope = {
|
|
63
|
+
organisationId: 'org-123',
|
|
64
|
+
eventId: 'event-123',
|
|
65
|
+
appId: 'app-123',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
vi.clearAllMocks();
|
|
70
|
+
clearInFlightRequests();
|
|
71
|
+
rbacCache.clear();
|
|
72
|
+
|
|
73
|
+
// Set up mocks to match working test exactly
|
|
74
|
+
mockUseUnifiedAuthFn.mockReturnValue({
|
|
75
|
+
user: mockUser,
|
|
76
|
+
selectedOrganisation: { id: 'org-123' },
|
|
77
|
+
selectedEvent: { event_id: 'event-123' },
|
|
78
|
+
appId: 'app-123', // Required for scope resolution
|
|
79
|
+
supabase: {
|
|
80
|
+
from: vi.fn().mockReturnValue({
|
|
81
|
+
select: vi.fn().mockReturnValue({
|
|
82
|
+
eq: vi.fn().mockReturnValue({
|
|
83
|
+
eq: vi.fn().mockReturnValue({
|
|
84
|
+
single: vi.fn().mockResolvedValue({
|
|
85
|
+
data: { id: 'app-123', name: 'test-app', is_active: true },
|
|
86
|
+
error: null
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
} as any
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
mockGetCurrentAppName.mockReturnValue('test-app');
|
|
96
|
+
|
|
97
|
+
// Mock useCan to return permission granted
|
|
98
|
+
// Use mockImplementation to handle all calls, including initial calls with undefined scope
|
|
99
|
+
mockUseCan.mockImplementation(() => ({
|
|
100
|
+
can: true,
|
|
101
|
+
isLoading: false,
|
|
102
|
+
error: null,
|
|
103
|
+
refetch: vi.fn(),
|
|
104
|
+
}));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('Request Deduplication', () => {
|
|
108
|
+
it('should deduplicate requests when multiple instances check the same permission', async () => {
|
|
109
|
+
// Ensure useCan returns permission granted
|
|
110
|
+
mockUseCan.mockImplementation(() => ({
|
|
111
|
+
can: true,
|
|
112
|
+
isLoading: false,
|
|
113
|
+
error: null,
|
|
114
|
+
refetch: vi.fn(),
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
render(
|
|
118
|
+
<>
|
|
119
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
120
|
+
<div data-testid="content-1">Content 1</div>
|
|
121
|
+
</PagePermissionGuard>
|
|
122
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
123
|
+
<div data-testid="content-2">Content 2</div>
|
|
124
|
+
</PagePermissionGuard>
|
|
125
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
126
|
+
<div data-testid="content-3">Content 3</div>
|
|
127
|
+
</PagePermissionGuard>
|
|
128
|
+
</>
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Wait for scope resolution and permission check to complete
|
|
132
|
+
// The component resolves scope asynchronously, so we need to wait
|
|
133
|
+
await waitFor(() => {
|
|
134
|
+
expect(screen.getByTestId('content-1')).toBeInTheDocument();
|
|
135
|
+
expect(screen.getByTestId('content-2')).toBeInTheDocument();
|
|
136
|
+
expect(screen.getByTestId('content-3')).toBeInTheDocument();
|
|
137
|
+
}, { interval: 10 });
|
|
138
|
+
|
|
139
|
+
// Verify useCan was called (deduplication happens at API level, not hook level)
|
|
140
|
+
expect(mockUseCan).toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should track in-flight requests correctly', () => {
|
|
144
|
+
expect(getInFlightRequestCount()).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('Cache Performance', () => {
|
|
149
|
+
it('should use cached results on subsequent renders', async () => {
|
|
150
|
+
let checkCount = 0;
|
|
151
|
+
|
|
152
|
+
mockUseCan.mockImplementation(() => {
|
|
153
|
+
checkCount++;
|
|
154
|
+
return {
|
|
155
|
+
can: true,
|
|
156
|
+
isLoading: false,
|
|
157
|
+
error: null,
|
|
158
|
+
refetch: vi.fn(),
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const { rerender } = render(
|
|
163
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
164
|
+
<div data-testid="content">Content</div>
|
|
165
|
+
</PagePermissionGuard>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
await waitFor(() => {
|
|
169
|
+
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
170
|
+
}, { interval: 10 });
|
|
171
|
+
|
|
172
|
+
const initialCheckCount = checkCount;
|
|
173
|
+
|
|
174
|
+
// Rerender with same props
|
|
175
|
+
rerender(
|
|
176
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
177
|
+
<div data-testid="content">Content</div>
|
|
178
|
+
</PagePermissionGuard>
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
183
|
+
}, { interval: 10 });
|
|
184
|
+
|
|
185
|
+
// Should not increase check count significantly due to memoization
|
|
186
|
+
// Allow some margin for React's rendering behavior
|
|
187
|
+
expect(checkCount).toBeLessThanOrEqual(initialCheckCount + 2);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('Render Performance', () => {
|
|
192
|
+
it('should not cause excessive re-renders', () => {
|
|
193
|
+
let renderCount = 0;
|
|
194
|
+
|
|
195
|
+
const TestComponent = () => {
|
|
196
|
+
renderCount++;
|
|
197
|
+
return (
|
|
198
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
199
|
+
<div data-testid="content">Content</div>
|
|
200
|
+
</PagePermissionGuard>
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
render(<TestComponent />);
|
|
205
|
+
|
|
206
|
+
// Should not have excessive re-renders
|
|
207
|
+
expect(renderCount).toBeLessThan(10);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should memoize scope objects to prevent unnecessary re-renders', async () => {
|
|
211
|
+
// Ensure useCan returns permission granted
|
|
212
|
+
mockUseCan.mockImplementation(() => ({
|
|
213
|
+
can: true,
|
|
214
|
+
isLoading: false,
|
|
215
|
+
error: null,
|
|
216
|
+
refetch: vi.fn(),
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
const { rerender } = render(
|
|
220
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
221
|
+
<div data-testid="content">Content</div>
|
|
222
|
+
</PagePermissionGuard>
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
await waitFor(() => {
|
|
226
|
+
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
227
|
+
}, { interval: 10 });
|
|
228
|
+
|
|
229
|
+
const initialCallCount = mockUseCan.mock.calls.length;
|
|
230
|
+
|
|
231
|
+
// Rerender with same props
|
|
232
|
+
rerender(
|
|
233
|
+
<PagePermissionGuard pageName="dashboard" operation="read">
|
|
234
|
+
<div data-testid="content">Content</div>
|
|
235
|
+
</PagePermissionGuard>
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
await waitFor(() => {
|
|
239
|
+
expect(screen.getByTestId('content')).toBeInTheDocument();
|
|
240
|
+
}, { interval: 10 });
|
|
241
|
+
|
|
242
|
+
// useCan should be called, but scope memoization prevents excessive calls
|
|
243
|
+
// Allow some margin for React's rendering behavior
|
|
244
|
+
expect(mockUseCan.mock.calls.length).toBeLessThanOrEqual(initialCallCount + 2);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
package/src/rbac/config.ts
CHANGED
|
@@ -22,12 +22,21 @@ export interface RBACConfig {
|
|
|
22
22
|
cache?: {
|
|
23
23
|
ttl?: number;
|
|
24
24
|
enabled?: boolean;
|
|
25
|
+
sessionTtl?: number; // Session cache TTL in milliseconds (default: 5 minutes)
|
|
25
26
|
};
|
|
26
27
|
audit?: {
|
|
27
28
|
enabled?: boolean;
|
|
28
29
|
logLevel?: LogLevel;
|
|
30
|
+
batched?: boolean; // Enable batched audit logging (default: true)
|
|
31
|
+
batchWindow?: number; // Time window in milliseconds (default: 100ms)
|
|
32
|
+
batchSize?: number; // Maximum batch size (default: 10)
|
|
29
33
|
};
|
|
30
34
|
security?: Partial<RBACSecurityConfig>;
|
|
35
|
+
performance?: {
|
|
36
|
+
enableRequestDeduplication?: boolean; // Enable request deduplication (default: true)
|
|
37
|
+
enableBatchedAuditLogging?: boolean; // Enable batched audit logging (default: true)
|
|
38
|
+
enablePerformanceTracking?: boolean; // Enable performance tracking (default: false in production)
|
|
39
|
+
};
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
export interface RBACLogger {
|
package/src/rbac/engine.ts
CHANGED
|
@@ -156,27 +156,8 @@ export class RBACEngine {
|
|
|
156
156
|
cacheHit = true;
|
|
157
157
|
cacheSource = 'memory';
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// Audit cache hit (if organisation context exists)
|
|
162
|
-
if (scope.organisationId) {
|
|
163
|
-
const resolvedPageId = await this.resolvePageId(pageId, scope.appId);
|
|
164
|
-
await emitAuditEvent({
|
|
165
|
-
type: 'permission_check',
|
|
166
|
-
userId,
|
|
167
|
-
organisationId: scope.organisationId,
|
|
168
|
-
eventId: scope.eventId,
|
|
169
|
-
appId: scope.appId,
|
|
170
|
-
pageId: resolvedPageId,
|
|
171
|
-
permission,
|
|
172
|
-
decision: cached,
|
|
173
|
-
source: 'api',
|
|
174
|
-
duration_ms: duration,
|
|
175
|
-
cache_hit: true,
|
|
176
|
-
cache_source: 'memory',
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
159
|
+
// Skip audit logging for cached checks (only log on cache miss)
|
|
160
|
+
// This significantly reduces network requests
|
|
180
161
|
return cached;
|
|
181
162
|
}
|
|
182
163
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
isPermittedCached
|
|
23
23
|
} from '../api';
|
|
24
24
|
import { getRBACLogger } from '../config';
|
|
25
|
+
import { scopeEqual } from '../utils/deep-equal';
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Hook to get user's permissions in a scope
|
|
@@ -337,20 +338,35 @@ export function useCan(
|
|
|
337
338
|
const lastPageIdRef = useRef<UUID | undefined | null>(null);
|
|
338
339
|
const lastUseCacheRef = useRef<boolean | null>(null);
|
|
339
340
|
|
|
341
|
+
// Create a stable scope object for comparison
|
|
342
|
+
const stableScope = useMemo(() => {
|
|
343
|
+
if (!isValidScope) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
organisationId,
|
|
348
|
+
eventId,
|
|
349
|
+
appId,
|
|
350
|
+
};
|
|
351
|
+
}, [isValidScope, organisationId, eventId, appId]);
|
|
352
|
+
|
|
353
|
+
// Track previous scope for deep equality comparison
|
|
354
|
+
const prevScopeRef = useRef<Scope | null>(null);
|
|
355
|
+
|
|
340
356
|
useEffect(() => {
|
|
341
|
-
//
|
|
342
|
-
const
|
|
357
|
+
// Use deep equality check for scope to prevent unnecessary re-runs
|
|
358
|
+
const scopeChanged = !scopeEqual(prevScopeRef.current, stableScope);
|
|
343
359
|
|
|
344
360
|
// Only run if something has actually changed
|
|
345
361
|
if (
|
|
346
362
|
lastUserIdRef.current !== userId ||
|
|
347
|
-
|
|
363
|
+
scopeChanged ||
|
|
348
364
|
lastPermissionRef.current !== permission ||
|
|
349
365
|
lastPageIdRef.current !== pageId ||
|
|
350
366
|
lastUseCacheRef.current !== useCache
|
|
351
367
|
) {
|
|
352
368
|
lastUserIdRef.current = userId;
|
|
353
|
-
|
|
369
|
+
prevScopeRef.current = stableScope;
|
|
354
370
|
lastPermissionRef.current = permission;
|
|
355
371
|
lastPageIdRef.current = pageId;
|
|
356
372
|
lastUseCacheRef.current = useCache;
|
|
@@ -410,7 +426,7 @@ export function useCan(
|
|
|
410
426
|
|
|
411
427
|
checkPermission();
|
|
412
428
|
}
|
|
413
|
-
}, [userId,
|
|
429
|
+
}, [userId, stableScope, permission, pageId, useCache]);
|
|
414
430
|
|
|
415
431
|
const refetch = useCallback(async () => {
|
|
416
432
|
if (!userId) {
|
package/src/rbac/index.ts
CHANGED
|
@@ -59,6 +59,25 @@ export {
|
|
|
59
59
|
CACHE_PATTERNS,
|
|
60
60
|
} from './cache';
|
|
61
61
|
|
|
62
|
+
// Performance monitoring
|
|
63
|
+
export {
|
|
64
|
+
enablePerformanceMonitoring,
|
|
65
|
+
disablePerformanceMonitoring,
|
|
66
|
+
isPerformanceMonitoringEnabled,
|
|
67
|
+
recordPermissionCheck,
|
|
68
|
+
recordAuditEvent,
|
|
69
|
+
getPerformanceMetrics,
|
|
70
|
+
resetPerformanceMetrics,
|
|
71
|
+
getPerformanceSummary,
|
|
72
|
+
type RBACPerformanceMetrics,
|
|
73
|
+
} from './performance';
|
|
74
|
+
|
|
75
|
+
// Request deduplication
|
|
76
|
+
export {
|
|
77
|
+
clearInFlightRequests,
|
|
78
|
+
getInFlightRequestCount,
|
|
79
|
+
} from './request-deduplication';
|
|
80
|
+
|
|
62
81
|
// Audit
|
|
63
82
|
export {
|
|
64
83
|
RBACAuditManager,
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Monitoring for RBAC
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RBAC/Performance
|
|
5
|
+
* @since 2.0.0
|
|
6
|
+
*
|
|
7
|
+
* This module provides performance monitoring and metrics tracking for RBAC operations.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface RBACPerformanceMetrics {
|
|
11
|
+
/** Total number of permission checks */
|
|
12
|
+
totalChecks: number;
|
|
13
|
+
/** Number of cache hits */
|
|
14
|
+
cacheHits: number;
|
|
15
|
+
/** Number of cache misses */
|
|
16
|
+
cacheMisses: number;
|
|
17
|
+
/** Cache hit rate (0-1) */
|
|
18
|
+
cacheHitRate: number;
|
|
19
|
+
/** Number of deduplicated requests */
|
|
20
|
+
deduplicatedRequests: number;
|
|
21
|
+
/** Total number of network requests made */
|
|
22
|
+
networkRequests: number;
|
|
23
|
+
/** Average response time in milliseconds */
|
|
24
|
+
averageResponseTime: number;
|
|
25
|
+
/** Total response time in milliseconds */
|
|
26
|
+
totalResponseTime: number;
|
|
27
|
+
/** Number of batched audit events */
|
|
28
|
+
batchedAuditEvents: number;
|
|
29
|
+
/** Number of individual audit events */
|
|
30
|
+
individualAuditEvents: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class RBACPerformanceMonitor {
|
|
34
|
+
private metrics: RBACPerformanceMetrics = {
|
|
35
|
+
totalChecks: 0,
|
|
36
|
+
cacheHits: 0,
|
|
37
|
+
cacheMisses: 0,
|
|
38
|
+
cacheHitRate: 0,
|
|
39
|
+
deduplicatedRequests: 0,
|
|
40
|
+
networkRequests: 0,
|
|
41
|
+
averageResponseTime: 0,
|
|
42
|
+
totalResponseTime: 0,
|
|
43
|
+
batchedAuditEvents: 0,
|
|
44
|
+
individualAuditEvents: 0,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
private enabled: boolean = false;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Enable or disable performance monitoring
|
|
51
|
+
*/
|
|
52
|
+
setEnabled(enabled: boolean): void {
|
|
53
|
+
this.enabled = enabled;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if performance monitoring is enabled
|
|
58
|
+
*/
|
|
59
|
+
isEnabled(): boolean {
|
|
60
|
+
return this.enabled;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Record a permission check
|
|
65
|
+
*/
|
|
66
|
+
recordCheck(cacheHit: boolean, responseTime: number, wasDeduplicated: boolean = false): void {
|
|
67
|
+
if (!this.enabled) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.metrics.totalChecks++;
|
|
72
|
+
|
|
73
|
+
if (cacheHit) {
|
|
74
|
+
this.metrics.cacheHits++;
|
|
75
|
+
} else {
|
|
76
|
+
this.metrics.cacheMisses++;
|
|
77
|
+
this.metrics.networkRequests++;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (wasDeduplicated) {
|
|
81
|
+
this.metrics.deduplicatedRequests++;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.metrics.totalResponseTime += responseTime;
|
|
85
|
+
this.metrics.averageResponseTime = this.metrics.totalResponseTime / this.metrics.totalChecks;
|
|
86
|
+
this.metrics.cacheHitRate = this.metrics.cacheHits / this.metrics.totalChecks;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Record an audit event
|
|
91
|
+
*/
|
|
92
|
+
recordAuditEvent(batched: boolean): void {
|
|
93
|
+
if (!this.enabled) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (batched) {
|
|
98
|
+
this.metrics.batchedAuditEvents++;
|
|
99
|
+
} else {
|
|
100
|
+
this.metrics.individualAuditEvents++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get current metrics
|
|
106
|
+
*/
|
|
107
|
+
getMetrics(): RBACPerformanceMetrics {
|
|
108
|
+
return { ...this.metrics };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reset all metrics
|
|
113
|
+
*/
|
|
114
|
+
reset(): void {
|
|
115
|
+
this.metrics = {
|
|
116
|
+
totalChecks: 0,
|
|
117
|
+
cacheHits: 0,
|
|
118
|
+
cacheMisses: 0,
|
|
119
|
+
cacheHitRate: 0,
|
|
120
|
+
deduplicatedRequests: 0,
|
|
121
|
+
networkRequests: 0,
|
|
122
|
+
averageResponseTime: 0,
|
|
123
|
+
totalResponseTime: 0,
|
|
124
|
+
batchedAuditEvents: 0,
|
|
125
|
+
individualAuditEvents: 0,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get metrics summary as a formatted string
|
|
131
|
+
*/
|
|
132
|
+
getSummary(): string {
|
|
133
|
+
const m = this.metrics;
|
|
134
|
+
return `
|
|
135
|
+
RBAC Performance Metrics:
|
|
136
|
+
Total Checks: ${m.totalChecks}
|
|
137
|
+
Cache Hits: ${m.cacheHits} (${(m.cacheHitRate * 100).toFixed(1)}%)
|
|
138
|
+
Cache Misses: ${m.cacheMisses}
|
|
139
|
+
Deduplicated Requests: ${m.deduplicatedRequests}
|
|
140
|
+
Network Requests: ${m.networkRequests}
|
|
141
|
+
Average Response Time: ${m.averageResponseTime.toFixed(2)}ms
|
|
142
|
+
Batched Audit Events: ${m.batchedAuditEvents}
|
|
143
|
+
Individual Audit Events: ${m.individualAuditEvents}
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Global performance monitor instance
|
|
149
|
+
const performanceMonitor = new RBACPerformanceMonitor();
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Enable performance monitoring
|
|
153
|
+
*/
|
|
154
|
+
export function enablePerformanceMonitoring(): void {
|
|
155
|
+
performanceMonitor.setEnabled(true);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Disable performance monitoring
|
|
160
|
+
*/
|
|
161
|
+
export function disablePerformanceMonitoring(): void {
|
|
162
|
+
performanceMonitor.setEnabled(false);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if performance monitoring is enabled
|
|
167
|
+
*/
|
|
168
|
+
export function isPerformanceMonitoringEnabled(): boolean {
|
|
169
|
+
return performanceMonitor.isEnabled();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Record a permission check
|
|
174
|
+
*/
|
|
175
|
+
export function recordPermissionCheck(
|
|
176
|
+
cacheHit: boolean,
|
|
177
|
+
responseTime: number,
|
|
178
|
+
wasDeduplicated: boolean = false
|
|
179
|
+
): void {
|
|
180
|
+
performanceMonitor.recordCheck(cacheHit, responseTime, wasDeduplicated);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Record an audit event
|
|
185
|
+
*/
|
|
186
|
+
export function recordAuditEvent(batched: boolean): void {
|
|
187
|
+
performanceMonitor.recordAuditEvent(batched);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get current performance metrics
|
|
192
|
+
*/
|
|
193
|
+
export function getPerformanceMetrics(): RBACPerformanceMetrics {
|
|
194
|
+
return performanceMonitor.getMetrics();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Reset performance metrics
|
|
199
|
+
*/
|
|
200
|
+
export function resetPerformanceMetrics(): void {
|
|
201
|
+
performanceMonitor.reset();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get performance metrics summary
|
|
206
|
+
*/
|
|
207
|
+
export function getPerformanceSummary(): string {
|
|
208
|
+
return performanceMonitor.getSummary();
|
|
209
|
+
}
|
|
210
|
+
|