@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,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Google Places API Types
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/GooglePlaces
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Google Places Autocomplete API response prediction
|
|
10
|
+
*/
|
|
11
|
+
export interface GooglePlaceAutocompletePrediction {
|
|
12
|
+
description: string;
|
|
13
|
+
place_id: string;
|
|
14
|
+
structured_formatting?: {
|
|
15
|
+
main_text: string;
|
|
16
|
+
secondary_text: string;
|
|
17
|
+
};
|
|
18
|
+
types?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Google Places Autocomplete API response
|
|
23
|
+
*/
|
|
24
|
+
export interface GooglePlaceAutocompleteResponse {
|
|
25
|
+
predictions: GooglePlaceAutocompletePrediction[];
|
|
26
|
+
status: string;
|
|
27
|
+
error_message?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Google Places Details API address component
|
|
32
|
+
*/
|
|
33
|
+
export interface GoogleAddressComponent {
|
|
34
|
+
long_name: string;
|
|
35
|
+
short_name: string;
|
|
36
|
+
types: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Google Places Details API geometry
|
|
41
|
+
*/
|
|
42
|
+
export interface GoogleGeometry {
|
|
43
|
+
location: {
|
|
44
|
+
lat: number;
|
|
45
|
+
lng: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Google Places Details API response
|
|
51
|
+
*/
|
|
52
|
+
export interface GooglePlaceDetailsResponse {
|
|
53
|
+
result: {
|
|
54
|
+
place_id: string;
|
|
55
|
+
formatted_address: string;
|
|
56
|
+
address_components: GoogleAddressComponent[];
|
|
57
|
+
geometry: GoogleGeometry;
|
|
58
|
+
};
|
|
59
|
+
status: string;
|
|
60
|
+
error_message?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parsed address matching pace_address table structure
|
|
65
|
+
*/
|
|
66
|
+
export interface ParsedAddress {
|
|
67
|
+
place_id: string;
|
|
68
|
+
full_address: string | null;
|
|
69
|
+
street_number: string | null;
|
|
70
|
+
route: string | null;
|
|
71
|
+
suburb: string | null;
|
|
72
|
+
state: string | null;
|
|
73
|
+
postcode: string | null;
|
|
74
|
+
country: string | null;
|
|
75
|
+
lat: number | null;
|
|
76
|
+
lng: number | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Options for Google Places Autocomplete API
|
|
81
|
+
*/
|
|
82
|
+
export interface AutocompleteOptions {
|
|
83
|
+
/** Restrict results to specific countries (ISO 3166-1 Alpha-2 country codes) */
|
|
84
|
+
components?: string;
|
|
85
|
+
/** Location bias (lat,lng) */
|
|
86
|
+
location?: string;
|
|
87
|
+
/** Radius in meters for location bias */
|
|
88
|
+
radius?: number;
|
|
89
|
+
/** Restrict results to specific place types */
|
|
90
|
+
types?: string;
|
|
91
|
+
/** Language code for results */
|
|
92
|
+
language?: string;
|
|
93
|
+
}
|
|
94
|
+
|
package/src/utils/index.ts
CHANGED
|
@@ -156,3 +156,26 @@ export {
|
|
|
156
156
|
getGoogleMapsUrl
|
|
157
157
|
} from './location';
|
|
158
158
|
export type { Coordinates } from './location';
|
|
159
|
+
|
|
160
|
+
// Google Places utilities
|
|
161
|
+
export {
|
|
162
|
+
fetchPlaceAutocomplete,
|
|
163
|
+
fetchPlaceDetails,
|
|
164
|
+
parseAddressComponents,
|
|
165
|
+
createAddressFromPlaceResult,
|
|
166
|
+
getAddressByPlaceId,
|
|
167
|
+
} from './google-places';
|
|
168
|
+
export type {
|
|
169
|
+
GooglePlaceAutocompletePrediction,
|
|
170
|
+
ParsedAddress,
|
|
171
|
+
AutocompleteOptions,
|
|
172
|
+
} from './google-places';
|
|
173
|
+
|
|
174
|
+
// Request deduplication utilities
|
|
175
|
+
export {
|
|
176
|
+
generateRequestKey,
|
|
177
|
+
getOrCreateRequest,
|
|
178
|
+
clearInFlightRequests,
|
|
179
|
+
getInFlightRequestStats,
|
|
180
|
+
deduplicatedQuery
|
|
181
|
+
} from './request-deduplication';
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Deduplication Utility
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/RequestDeduplication
|
|
5
|
+
* @since 2.0.0
|
|
6
|
+
*
|
|
7
|
+
* Provides request deduplication to prevent duplicate in-flight requests.
|
|
8
|
+
* When multiple components request the same data simultaneously, only one
|
|
9
|
+
* request is made and all callers share the same promise.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createLogger } from './core/logger';
|
|
13
|
+
|
|
14
|
+
const log = createLogger('request-deduplication');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* In-flight request cache
|
|
18
|
+
* Key: request identifier (e.g., "GET:table:filter:value")
|
|
19
|
+
* Value: Promise that resolves to the request result
|
|
20
|
+
*/
|
|
21
|
+
const inFlightRequests = new Map<string, Promise<any>>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate a request key from request parameters
|
|
25
|
+
*
|
|
26
|
+
* @param method - HTTP method (GET, POST, etc.)
|
|
27
|
+
* @param table - Table name
|
|
28
|
+
* @param filters - Filter object
|
|
29
|
+
* @param select - Select columns
|
|
30
|
+
* @returns Request key string
|
|
31
|
+
*/
|
|
32
|
+
export function generateRequestKey(
|
|
33
|
+
method: string,
|
|
34
|
+
table: string,
|
|
35
|
+
filters?: Record<string, any>,
|
|
36
|
+
select?: string
|
|
37
|
+
): string {
|
|
38
|
+
const filterStr = filters ? JSON.stringify(filters) : '';
|
|
39
|
+
const selectStr = select || '*';
|
|
40
|
+
return `${method}:${table}:${filterStr}:${selectStr}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get or create a request
|
|
45
|
+
*
|
|
46
|
+
* If a request with the same key is already in-flight, returns the existing promise.
|
|
47
|
+
* Otherwise, creates a new request and stores it for deduplication.
|
|
48
|
+
*
|
|
49
|
+
* @param key - Request key
|
|
50
|
+
* @param requestFn - Function that performs the actual request
|
|
51
|
+
* @returns Promise that resolves to the request result
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const data = await getOrCreateRequest(
|
|
56
|
+
* 'GET:pace_person:{"user_id":"123"}',
|
|
57
|
+
* async () => {
|
|
58
|
+
* const { data } = await supabase
|
|
59
|
+
* .from('pace_person')
|
|
60
|
+
* .select('id, first_name')
|
|
61
|
+
* .eq('user_id', '123')
|
|
62
|
+
* .single();
|
|
63
|
+
* return data;
|
|
64
|
+
* }
|
|
65
|
+
* );
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export async function getOrCreateRequest<T>(
|
|
69
|
+
key: string,
|
|
70
|
+
requestFn: () => Promise<T>
|
|
71
|
+
): Promise<T> {
|
|
72
|
+
// Check if request is already in-flight
|
|
73
|
+
const existingRequest = inFlightRequests.get(key);
|
|
74
|
+
if (existingRequest) {
|
|
75
|
+
log.debug(`Request deduplication: reusing in-flight request for ${key}`);
|
|
76
|
+
return existingRequest as Promise<T>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create new request
|
|
80
|
+
log.debug(`Creating new request for ${key}`);
|
|
81
|
+
const requestPromise = requestFn()
|
|
82
|
+
.then((result) => {
|
|
83
|
+
// Remove from in-flight cache after completion
|
|
84
|
+
inFlightRequests.delete(key);
|
|
85
|
+
return result;
|
|
86
|
+
})
|
|
87
|
+
.catch((error) => {
|
|
88
|
+
// Remove from in-flight cache on error
|
|
89
|
+
inFlightRequests.delete(key);
|
|
90
|
+
throw error;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Store in-flight request
|
|
94
|
+
inFlightRequests.set(key, requestPromise);
|
|
95
|
+
|
|
96
|
+
return requestPromise;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Clear all in-flight requests
|
|
101
|
+
*
|
|
102
|
+
* Useful for cleanup or testing.
|
|
103
|
+
*/
|
|
104
|
+
export function clearInFlightRequests(): void {
|
|
105
|
+
const count = inFlightRequests.size;
|
|
106
|
+
inFlightRequests.clear();
|
|
107
|
+
log.debug(`Cleared ${count} in-flight requests`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get statistics about in-flight requests
|
|
112
|
+
*
|
|
113
|
+
* @returns Statistics object
|
|
114
|
+
*/
|
|
115
|
+
export function getInFlightRequestStats(): {
|
|
116
|
+
count: number;
|
|
117
|
+
keys: string[];
|
|
118
|
+
} {
|
|
119
|
+
return {
|
|
120
|
+
count: inFlightRequests.size,
|
|
121
|
+
keys: Array.from(inFlightRequests.keys()),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Supabase query wrapper with automatic deduplication
|
|
127
|
+
*
|
|
128
|
+
* Wraps a Supabase query to automatically deduplicate identical requests.
|
|
129
|
+
*
|
|
130
|
+
* @param supabase - Supabase client
|
|
131
|
+
* @param table - Table name
|
|
132
|
+
* @param filters - Filter object (e.g., { user_id: '123' })
|
|
133
|
+
* @param select - Select columns (default: '*')
|
|
134
|
+
* @param requestFn - Function that performs the query
|
|
135
|
+
* @returns Promise that resolves to query result
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* const person = await deduplicatedQuery(
|
|
140
|
+
* supabase,
|
|
141
|
+
* 'pace_person',
|
|
142
|
+
* { user_id: userId },
|
|
143
|
+
* 'id, first_name, last_name',
|
|
144
|
+
* async () => {
|
|
145
|
+
* const { data } = await supabase
|
|
146
|
+
* .from('pace_person')
|
|
147
|
+
* .select('id, first_name, last_name')
|
|
148
|
+
* .eq('user_id', userId)
|
|
149
|
+
* .single();
|
|
150
|
+
* return data;
|
|
151
|
+
* }
|
|
152
|
+
* );
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export async function deduplicatedQuery<T>(
|
|
156
|
+
supabase: any,
|
|
157
|
+
table: string,
|
|
158
|
+
filters: Record<string, any>,
|
|
159
|
+
select: string,
|
|
160
|
+
requestFn: () => Promise<T>
|
|
161
|
+
): Promise<T> {
|
|
162
|
+
const key = generateRequestKey('GET', table, filters, select);
|
|
163
|
+
return getOrCreateRequest(key, requestFn);
|
|
164
|
+
}
|
|
165
|
+
|
|
@@ -102,7 +102,7 @@ export const createSecureDataAccess = (
|
|
|
102
102
|
'cake_meal', 'cake_mealtype', 'pace_person', 'pace_member',
|
|
103
103
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
104
104
|
'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
|
|
105
|
-
'pace_consent', 'pace_contact', '
|
|
105
|
+
'pace_consent', 'pace_contact', 'pace_identification', 'pace_identification_type', 'pace_qualification',
|
|
106
106
|
'form_responses', 'form_response_values', 'forms',
|
|
107
107
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
108
108
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|
|
@@ -30,21 +30,21 @@ export function generateFilePath(options: StorageUploadOptions, fileName: string
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
if (isPublic) {
|
|
33
|
-
// Public files go to {orgId}/{
|
|
33
|
+
// Public files go to {orgId}/{folder}/filename
|
|
34
34
|
if (customPath) {
|
|
35
35
|
return `${orgId}/${customPath}/${fileName}`;
|
|
36
36
|
}
|
|
37
37
|
return `${orgId}/public/${fileName}`;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// Organization-first structure: {orgId}/{
|
|
40
|
+
// Organization-first structure: {orgId}/{folder}/filename
|
|
41
41
|
if (customPath) {
|
|
42
42
|
return `${orgId}/${customPath}/${fileName}`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// Use customPath if available, otherwise default to files
|
|
46
|
-
const
|
|
47
|
-
return `${orgId}/${
|
|
46
|
+
const pathFolder = customPath || 'files';
|
|
47
|
+
return `${orgId}/${pathFolder}/${fileName}`;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -153,6 +153,66 @@ async function generateFileHash(file: File): Promise<string> {
|
|
|
153
153
|
return `sha256:${hashHex}`;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Ensure a folder exists in the storage bucket
|
|
158
|
+
* In Supabase storage, folders are created automatically when files are uploaded,
|
|
159
|
+
* but this function explicitly creates the folder structure by uploading a placeholder
|
|
160
|
+
* file if the folder doesn't exist
|
|
161
|
+
* @param supabase - Supabase client instance
|
|
162
|
+
* @param folderPath - Folder path to ensure exists (e.g., 'orgId/folder')
|
|
163
|
+
* @param bucketName - Bucket name
|
|
164
|
+
* @returns True if folder exists or was created, false on error
|
|
165
|
+
*/
|
|
166
|
+
async function ensureFolderExists(
|
|
167
|
+
supabase: SupabaseClient,
|
|
168
|
+
folderPath: string,
|
|
169
|
+
bucketName: string
|
|
170
|
+
): Promise<boolean> {
|
|
171
|
+
try {
|
|
172
|
+
// Check if folder exists by trying to list it
|
|
173
|
+
const { data, error } = await supabase.storage
|
|
174
|
+
.from(bucketName)
|
|
175
|
+
.list(folderPath, {
|
|
176
|
+
limit: 1
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// If listing succeeds (even with empty data), the folder exists
|
|
180
|
+
if (!error) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// If we get a "not found" error, the folder doesn't exist yet
|
|
185
|
+
// Create it by uploading a placeholder file
|
|
186
|
+
// Supabase storage doesn't support empty folders, so we create a .keep file
|
|
187
|
+
const placeholderPath = `${folderPath}/.keep`;
|
|
188
|
+
const placeholderBlob = new Blob([''], { type: 'text/plain' });
|
|
189
|
+
const placeholderFile = new File([placeholderBlob], '.keep', { type: 'text/plain' });
|
|
190
|
+
|
|
191
|
+
const { error: uploadError } = await supabase.storage
|
|
192
|
+
.from(bucketName)
|
|
193
|
+
.upload(placeholderPath, placeholderFile, {
|
|
194
|
+
cacheControl: '3600',
|
|
195
|
+
upsert: true, // Use upsert to avoid errors if file already exists
|
|
196
|
+
contentType: 'text/plain'
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (uploadError) {
|
|
200
|
+
// If we can't create the placeholder, log it but don't fail
|
|
201
|
+
// The folder will be created automatically when we upload the actual file
|
|
202
|
+
log.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
|
|
203
|
+
return true; // Still return true - folder will be created on actual file upload
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Folder structure now exists
|
|
207
|
+
return true;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
// If there's an exception, log it but proceed anyway
|
|
210
|
+
// The folder structure will be created when we upload the actual file
|
|
211
|
+
log.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
212
|
+
return true; // Return true to proceed - folder will be created on actual file upload
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
156
216
|
/**
|
|
157
217
|
* Upload a file to Supabase storage with app segregation
|
|
158
218
|
*/
|
|
@@ -175,13 +235,21 @@ export async function uploadFile(
|
|
|
175
235
|
const uniqueFileName = generateUniqueFileName(file.name);
|
|
176
236
|
const filePath = generateFilePath(options, uniqueFileName);
|
|
177
237
|
|
|
238
|
+
// Extract folder path from file path (everything except the filename)
|
|
239
|
+
const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
240
|
+
|
|
178
241
|
// Extract metadata
|
|
179
242
|
const metadata = await extractFileMetadata(file, options, 'current-user'); // TODO: Get actual user ID
|
|
180
243
|
|
|
181
244
|
// Select bucket based on isPublic flag
|
|
182
245
|
const bucketName = getBucketName(options.isPublic || false);
|
|
183
246
|
|
|
247
|
+
// Ensure folder exists (Supabase creates folders automatically on upload,
|
|
248
|
+
// but we verify the path is accessible)
|
|
249
|
+
await ensureFolderExists(supabase, folderPath, bucketName);
|
|
250
|
+
|
|
184
251
|
// Upload file to Supabase
|
|
252
|
+
// Note: Supabase will automatically create the folder structure if it doesn't exist
|
|
185
253
|
const { data, error } = await supabase.storage
|
|
186
254
|
.from(bucketName)
|
|
187
255
|
.upload(filePath, file, {
|
|
@@ -352,6 +420,145 @@ export async function getSignedUrl(
|
|
|
352
420
|
}
|
|
353
421
|
}
|
|
354
422
|
|
|
423
|
+
// Global URL cache for batch operations (shared with useFileUrlCache)
|
|
424
|
+
const globalUrlCache = new Map<string, { url: string; expiresAt: number }>();
|
|
425
|
+
const MAX_CACHE_SIZE = 500;
|
|
426
|
+
const DEFAULT_TTL_MS = 3600 * 1000;
|
|
427
|
+
|
|
428
|
+
function getCacheKey(fileId: string, filePath: string, isPublic: boolean): string {
|
|
429
|
+
return `file-url:${fileId}:${isPublic ? 'public' : 'private'}`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function cleanupUrlCache(): void {
|
|
433
|
+
const now = Date.now();
|
|
434
|
+
|
|
435
|
+
// Remove expired entries
|
|
436
|
+
for (const [key, value] of globalUrlCache.entries()) {
|
|
437
|
+
if (value.expiresAt < now) {
|
|
438
|
+
globalUrlCache.delete(key);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Enforce size limit by removing oldest entries
|
|
443
|
+
if (globalUrlCache.size > MAX_CACHE_SIZE) {
|
|
444
|
+
const entries = Array.from(globalUrlCache.entries());
|
|
445
|
+
entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
446
|
+
|
|
447
|
+
const toRemove = Math.floor(MAX_CACHE_SIZE * 0.2);
|
|
448
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
449
|
+
globalUrlCache.delete(entries[i][0]);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Generate URLs for multiple file references in parallel
|
|
456
|
+
* This batches URL generation to reduce sequential requests and uses caching
|
|
457
|
+
* @param supabase - Supabase client instance
|
|
458
|
+
* @param fileReferences - Array of file references to generate URLs for
|
|
459
|
+
* @param options - URL options including expiry time and organisation ID
|
|
460
|
+
* @returns Map of file ID to URL string (only includes successful URL generations)
|
|
461
|
+
*/
|
|
462
|
+
export async function generateFileUrlsBatch(
|
|
463
|
+
supabase: SupabaseClient,
|
|
464
|
+
fileReferences: Array<{ id: string; file_path: string; is_public: boolean }>,
|
|
465
|
+
options: StorageUrlOptions & { orgId?: string }
|
|
466
|
+
): Promise<Map<string, string>> {
|
|
467
|
+
const urlMap = new Map<string, string>();
|
|
468
|
+
|
|
469
|
+
if (fileReferences.length === 0) {
|
|
470
|
+
return urlMap;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const now = Date.now();
|
|
474
|
+
const ttl = (options.expiresIn || 3600) * 1000; // Convert seconds to milliseconds
|
|
475
|
+
|
|
476
|
+
// Separate files into cached, public, and private
|
|
477
|
+
const publicFiles: Array<{ id: string; file_path: string }> = [];
|
|
478
|
+
const privateFiles: Array<{ id: string; file_path: string }> = [];
|
|
479
|
+
const uncachedFiles: Array<{ id: string; file_path: string; is_public: boolean }> = [];
|
|
480
|
+
|
|
481
|
+
for (const fileRef of fileReferences) {
|
|
482
|
+
const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);
|
|
483
|
+
const cached = globalUrlCache.get(cacheKey);
|
|
484
|
+
|
|
485
|
+
// Use cached URL if still valid
|
|
486
|
+
if (cached && cached.expiresAt > now) {
|
|
487
|
+
urlMap.set(fileRef.id, cached.url);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Add to processing queue
|
|
492
|
+
if (fileRef.is_public) {
|
|
493
|
+
publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
|
|
494
|
+
} else {
|
|
495
|
+
privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
|
|
496
|
+
}
|
|
497
|
+
uncachedFiles.push(fileRef);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Generate public URLs synchronously (they're just string concatenation)
|
|
501
|
+
for (const file of publicFiles) {
|
|
502
|
+
try {
|
|
503
|
+
const url = getPublicUrl(supabase, file.file_path, true);
|
|
504
|
+
if (url) {
|
|
505
|
+
urlMap.set(file.id, url);
|
|
506
|
+
// Cache the URL
|
|
507
|
+
const cacheKey = getCacheKey(file.id, file.file_path, true);
|
|
508
|
+
globalUrlCache.set(cacheKey, {
|
|
509
|
+
url,
|
|
510
|
+
expiresAt: now + ttl
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
} catch (err) {
|
|
514
|
+
log.error(`Failed to generate public URL for file ${file.id}:`, err);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Generate signed URLs in parallel using Promise.all
|
|
519
|
+
if (privateFiles.length > 0) {
|
|
520
|
+
const signedUrlPromises = privateFiles.map(async (file) => {
|
|
521
|
+
try {
|
|
522
|
+
const signedUrlResult = await getSignedUrl(supabase, file.file_path, {
|
|
523
|
+
appName: options.appName || 'pace-core',
|
|
524
|
+
orgId: options.orgId,
|
|
525
|
+
expiresIn: options.expiresIn || 3600
|
|
526
|
+
});
|
|
527
|
+
const url = signedUrlResult?.url || null;
|
|
528
|
+
|
|
529
|
+
// Cache the URL if generated successfully
|
|
530
|
+
if (url) {
|
|
531
|
+
const cacheKey = getCacheKey(file.id, file.file_path, false);
|
|
532
|
+
globalUrlCache.set(cacheKey, {
|
|
533
|
+
url,
|
|
534
|
+
expiresAt: now + ttl
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return { id: file.id, url };
|
|
539
|
+
} catch (err) {
|
|
540
|
+
log.error(`Failed to generate signed URL for file ${file.id}:`, err);
|
|
541
|
+
return { id: file.id, url: null };
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
const signedUrlResults = await Promise.all(signedUrlPromises);
|
|
546
|
+
|
|
547
|
+
for (const result of signedUrlResults) {
|
|
548
|
+
if (result.url) {
|
|
549
|
+
urlMap.set(result.id, result.url);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Clean up cache after adding new entries
|
|
555
|
+
if (uncachedFiles.length > 0) {
|
|
556
|
+
cleanupUrlCache();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return urlMap;
|
|
560
|
+
}
|
|
561
|
+
|
|
355
562
|
/**
|
|
356
563
|
* Delete a file from storage
|
|
357
564
|
* @param supabase - Supabase client instance
|