@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
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Result Caching Hook
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Hooks/QueryCache
|
|
5
|
+
* @since 2.0.0
|
|
6
|
+
*
|
|
7
|
+
* Provides in-memory caching for frequently accessed data to eliminate duplicate queries.
|
|
8
|
+
* Useful for caching user profiles, app pages, and other relatively static data.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useCallback, useRef, useEffect } from 'react';
|
|
12
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
13
|
+
import { Database } from '../types/database';
|
|
14
|
+
import { createLogger } from '../utils/core/logger';
|
|
15
|
+
|
|
16
|
+
const log = createLogger('useQueryCache');
|
|
17
|
+
|
|
18
|
+
interface CachedQueryEntry<T> {
|
|
19
|
+
data: T;
|
|
20
|
+
expiresAt: number; // Unix timestamp
|
|
21
|
+
promise?: Promise<T>; // For in-flight requests
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* In-memory cache for query results
|
|
26
|
+
* Key format: `table:filterKey:filterValue`
|
|
27
|
+
*/
|
|
28
|
+
const queryCache = new Map<string, CachedQueryEntry<any>>();
|
|
29
|
+
|
|
30
|
+
// Cleanup interval (every 5 minutes)
|
|
31
|
+
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
|
|
32
|
+
let cleanupTimer: ReturnType<typeof setInterval> | null = null;
|
|
33
|
+
|
|
34
|
+
function runCacheCleanup() {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const expiredKeys: string[] = [];
|
|
37
|
+
|
|
38
|
+
queryCache.forEach((entry, key) => {
|
|
39
|
+
if (entry.expiresAt <= now) {
|
|
40
|
+
expiredKeys.push(key);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expiredKeys.forEach(key => {
|
|
45
|
+
queryCache.delete(key);
|
|
46
|
+
log.debug(`Removed expired query from cache: ${key}`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Initialize cleanup timer once
|
|
51
|
+
if (typeof window !== 'undefined' && !cleanupTimer) {
|
|
52
|
+
cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);
|
|
53
|
+
log.debug('Query cache cleanup initialized.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface UseQueryCacheOptions {
|
|
57
|
+
/** Time to live in seconds (default: 300 = 5 minutes) */
|
|
58
|
+
ttl?: number;
|
|
59
|
+
/** Whether to enable caching (default: true) */
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface UseQueryCacheReturn {
|
|
64
|
+
/** Get cached query result or fetch if not cached */
|
|
65
|
+
getCachedQuery: <T>(
|
|
66
|
+
table: string,
|
|
67
|
+
filterKey: string,
|
|
68
|
+
filterValue: string,
|
|
69
|
+
fetchFn: () => Promise<T>,
|
|
70
|
+
options?: UseQueryCacheOptions
|
|
71
|
+
) => Promise<T>;
|
|
72
|
+
/** Invalidate a specific cached query */
|
|
73
|
+
invalidateQuery: (table: string, filterKey: string, filterValue: string) => void;
|
|
74
|
+
/** Clear all cached queries */
|
|
75
|
+
clearCache: () => void;
|
|
76
|
+
/** Get cache statistics */
|
|
77
|
+
getCacheStats: () => { size: number; keys: string[] };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Hook for query result caching
|
|
82
|
+
*
|
|
83
|
+
* Provides caching for frequently accessed data to eliminate duplicate queries.
|
|
84
|
+
* Automatically handles cache expiration and cleanup.
|
|
85
|
+
*
|
|
86
|
+
* @param supabase - Supabase client (optional, can be passed in fetchFn)
|
|
87
|
+
* @returns Query cache utilities
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const { getCachedQuery } = useQueryCache(supabase);
|
|
92
|
+
*
|
|
93
|
+
* const person = await getCachedQuery(
|
|
94
|
+
* 'pace_person',
|
|
95
|
+
* 'user_id',
|
|
96
|
+
* userId,
|
|
97
|
+
* async () => {
|
|
98
|
+
* const { data } = await supabase
|
|
99
|
+
* .from('pace_person')
|
|
100
|
+
* .select('id, first_name, last_name, email')
|
|
101
|
+
* .eq('user_id', userId)
|
|
102
|
+
* .single();
|
|
103
|
+
* return data;
|
|
104
|
+
* },
|
|
105
|
+
* { ttl: 300 } // 5 minutes
|
|
106
|
+
* );
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export function useQueryCache(supabase?: SupabaseClient<Database>): UseQueryCacheReturn {
|
|
110
|
+
const getCachedQuery = useCallback(async <T,>(
|
|
111
|
+
table: string,
|
|
112
|
+
filterKey: string,
|
|
113
|
+
filterValue: string,
|
|
114
|
+
fetchFn: () => Promise<T>,
|
|
115
|
+
options: UseQueryCacheOptions = {}
|
|
116
|
+
): Promise<T> => {
|
|
117
|
+
const { ttl = 300, enabled = true } = options; // Default 5 minutes
|
|
118
|
+
const cacheKey = `${table}:${filterKey}:${filterValue}`;
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
|
|
121
|
+
if (!enabled) {
|
|
122
|
+
return fetchFn();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check cache
|
|
126
|
+
const cached = queryCache.get(cacheKey);
|
|
127
|
+
if (cached) {
|
|
128
|
+
// If data is still valid, return it
|
|
129
|
+
if (cached.expiresAt > now && cached.data !== undefined) {
|
|
130
|
+
log.debug(`Cache hit for query: ${cacheKey}`);
|
|
131
|
+
return cached.data as T;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// If there's an in-flight request, wait for it
|
|
135
|
+
if (cached.promise) {
|
|
136
|
+
log.debug(`Waiting for in-flight request: ${cacheKey}`);
|
|
137
|
+
return cached.promise as Promise<T>;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fetch data
|
|
142
|
+
log.debug(`Cache miss for query: ${cacheKey}, fetching...`);
|
|
143
|
+
const fetchPromise = fetchFn();
|
|
144
|
+
|
|
145
|
+
// Store promise for in-flight request deduplication
|
|
146
|
+
queryCache.set(cacheKey, {
|
|
147
|
+
data: undefined as any,
|
|
148
|
+
expiresAt: now + (ttl * 1000),
|
|
149
|
+
promise: fetchPromise,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const data = await fetchPromise;
|
|
154
|
+
|
|
155
|
+
// Update cache with actual data
|
|
156
|
+
queryCache.set(cacheKey, {
|
|
157
|
+
data,
|
|
158
|
+
expiresAt: now + (ttl * 1000),
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
log.debug(`Cached query result: ${cacheKey}, expires in ${ttl}s`);
|
|
162
|
+
return data;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// Remove failed request from cache
|
|
165
|
+
queryCache.delete(cacheKey);
|
|
166
|
+
log.error(`Query failed for ${cacheKey}:`, error);
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
const invalidateQuery = useCallback((table: string, filterKey: string, filterValue: string) => {
|
|
172
|
+
const cacheKey = `${table}:${filterKey}:${filterValue}`;
|
|
173
|
+
queryCache.delete(cacheKey);
|
|
174
|
+
log.debug(`Invalidated query cache: ${cacheKey}`);
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
const clearCache = useCallback(() => {
|
|
178
|
+
queryCache.clear();
|
|
179
|
+
log.debug('Cleared all query cache entries.');
|
|
180
|
+
}, []);
|
|
181
|
+
|
|
182
|
+
const getCacheStats = useCallback(() => {
|
|
183
|
+
return {
|
|
184
|
+
size: queryCache.size,
|
|
185
|
+
keys: Array.from(queryCache.keys()),
|
|
186
|
+
};
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
getCachedQuery,
|
|
191
|
+
invalidateQuery,
|
|
192
|
+
clearCache,
|
|
193
|
+
getCacheStats,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Pre-configured cache helpers for common queries
|
|
199
|
+
*/
|
|
200
|
+
export const queryCacheHelpers = {
|
|
201
|
+
/**
|
|
202
|
+
* Cache pace_person queries by user_id
|
|
203
|
+
* TTL: 5 minutes
|
|
204
|
+
*/
|
|
205
|
+
pacePersonByUserId: <T>(
|
|
206
|
+
supabase: SupabaseClient<Database>,
|
|
207
|
+
userId: string,
|
|
208
|
+
fetchFn: () => Promise<T>
|
|
209
|
+
): Promise<T> => {
|
|
210
|
+
const cacheKey = `pace_person:user_id:${userId}`;
|
|
211
|
+
const now = Date.now();
|
|
212
|
+
const ttl = 300 * 1000; // 5 minutes
|
|
213
|
+
|
|
214
|
+
const cached = queryCache.get(cacheKey);
|
|
215
|
+
if (cached && cached.expiresAt > now && cached.data !== undefined) {
|
|
216
|
+
return Promise.resolve(cached.data as T);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (cached?.promise) {
|
|
220
|
+
return cached.promise as Promise<T>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const promise = fetchFn();
|
|
224
|
+
queryCache.set(cacheKey, {
|
|
225
|
+
data: undefined as any,
|
|
226
|
+
expiresAt: now + ttl,
|
|
227
|
+
promise,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
promise.then(data => {
|
|
231
|
+
queryCache.set(cacheKey, { data, expiresAt: now + ttl });
|
|
232
|
+
}).catch(() => {
|
|
233
|
+
queryCache.delete(cacheKey);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return promise;
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Cache pace_member queries by person_id
|
|
241
|
+
* TTL: 5 minutes
|
|
242
|
+
*/
|
|
243
|
+
paceMemberByPersonId: <T>(
|
|
244
|
+
supabase: SupabaseClient<Database>,
|
|
245
|
+
personId: string,
|
|
246
|
+
fetchFn: () => Promise<T>
|
|
247
|
+
): Promise<T> => {
|
|
248
|
+
const cacheKey = `pace_member:person_id:${personId}`;
|
|
249
|
+
const now = Date.now();
|
|
250
|
+
const ttl = 300 * 1000; // 5 minutes
|
|
251
|
+
|
|
252
|
+
const cached = queryCache.get(cacheKey);
|
|
253
|
+
if (cached && cached.expiresAt > now && cached.data !== undefined) {
|
|
254
|
+
return Promise.resolve(cached.data as T);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (cached?.promise) {
|
|
258
|
+
return cached.promise as Promise<T>;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const promise = fetchFn();
|
|
262
|
+
queryCache.set(cacheKey, {
|
|
263
|
+
data: undefined as any,
|
|
264
|
+
expiresAt: now + ttl,
|
|
265
|
+
promise,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
promise.then(data => {
|
|
269
|
+
queryCache.set(cacheKey, { data, expiresAt: now + ttl });
|
|
270
|
+
}).catch(() => {
|
|
271
|
+
queryCache.delete(cacheKey);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return promise;
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Cache rbac_app_pages queries by app_id
|
|
279
|
+
* TTL: 15 minutes (app pages are relatively static)
|
|
280
|
+
*/
|
|
281
|
+
rbacAppPagesByAppId: <T>(
|
|
282
|
+
supabase: SupabaseClient<Database>,
|
|
283
|
+
appId: string,
|
|
284
|
+
fetchFn: () => Promise<T>
|
|
285
|
+
): Promise<T> => {
|
|
286
|
+
const cacheKey = `rbac_app_pages:app_id:${appId}`;
|
|
287
|
+
const now = Date.now();
|
|
288
|
+
const ttl = 15 * 60 * 1000; // 15 minutes
|
|
289
|
+
|
|
290
|
+
const cached = queryCache.get(cacheKey);
|
|
291
|
+
if (cached && cached.expiresAt > now && cached.data !== undefined) {
|
|
292
|
+
return Promise.resolve(cached.data as T);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (cached?.promise) {
|
|
296
|
+
return cached.promise as Promise<T>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const promise = fetchFn();
|
|
300
|
+
queryCache.set(cacheKey, {
|
|
301
|
+
data: undefined as any,
|
|
302
|
+
expiresAt: now + ttl,
|
|
303
|
+
promise,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
promise.then(data => {
|
|
307
|
+
queryCache.set(cacheKey, { data, expiresAt: now + ttl });
|
|
308
|
+
}).catch(() => {
|
|
309
|
+
queryCache.delete(cacheKey);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
return promise;
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
|
|
@@ -219,7 +219,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
219
219
|
'cake_meal', 'cake_mealtype', 'pace_person', 'pace_member',
|
|
220
220
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
221
221
|
'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
|
|
222
|
-
'pace_consent', 'pace_contact', '
|
|
222
|
+
'pace_consent', 'pace_contact', 'pace_identification', 'pace_identification_type', 'pace_qualification',
|
|
223
223
|
'form_responses', 'form_response_values', 'forms',
|
|
224
224
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
225
225
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|
|
@@ -379,7 +379,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
379
379
|
'cake_meal', 'cake_mealtype', 'pace_person', 'pace_member',
|
|
380
380
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
381
381
|
'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
|
|
382
|
-
'pace_consent', 'pace_contact', '
|
|
382
|
+
'pace_consent', 'pace_contact', 'pace_identification', 'pace_identification_type', 'pace_qualification',
|
|
383
383
|
'form_responses', 'form_response_values', 'forms',
|
|
384
384
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
385
385
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|
package/src/index.ts
CHANGED
|
@@ -76,6 +76,8 @@ export type { CardProps } from './components/Card/Card';
|
|
|
76
76
|
|
|
77
77
|
export { Input } from './components/Input/Input';
|
|
78
78
|
export type { InputProps } from './components/Input/Input';
|
|
79
|
+
export { AddressField } from './components/AddressField';
|
|
80
|
+
export type { AddressFieldProps, AddressFieldRef, ParsedAddress, AutocompleteOptions } from './components/AddressField';
|
|
79
81
|
export { Label } from './components/Label/Label';
|
|
80
82
|
export type { LabelProps } from './components/Label/Label';
|
|
81
83
|
|
|
@@ -50,6 +50,7 @@ export function EventServiceProvider({
|
|
|
50
50
|
const eventService = eventServiceRef.current;
|
|
51
51
|
|
|
52
52
|
// Update service dependencies and initialize when dependencies change
|
|
53
|
+
// Note: eventService is a ref and never changes, so we don't include it in dependencies
|
|
53
54
|
useEffect(() => {
|
|
54
55
|
let isMounted = true;
|
|
55
56
|
|
|
@@ -72,7 +73,9 @@ export function EventServiceProvider({
|
|
|
72
73
|
return () => {
|
|
73
74
|
isMounted = false;
|
|
74
75
|
};
|
|
75
|
-
|
|
76
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
|
+
// eventService is a ref and never changes, so we exclude it from dependencies
|
|
78
|
+
}, [supabaseClient, user, session, appName, selectedOrganisation, setSelectedEventId]);
|
|
76
79
|
|
|
77
80
|
// Cleanup service on unmount only
|
|
78
81
|
useEffect(() => {
|