@jmruthers/pace-core 0.5.186 → 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-DIzEzwKl.d.ts → PublicPageProvider-DrLDztHt.d.ts} +211 -106
- 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-OALXJH4Y.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-TC7D3CR3.js → chunk-C4OYJOV4.js} +556 -101
- 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-HDCUMOOI.js → chunk-LBBUPSSC.js} +792 -559
- package/dist/chunk-LBBUPSSC.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-GRIQLQ52.js → chunk-T6ZJVI3A.js} +27 -23
- package/dist/chunk-T6ZJVI3A.js.map +1 -0
- package/dist/{chunk-DAGICKHT.js → chunk-ULX5FYEM.js} +3 -3
- 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/{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/implementation-guides/file-upload-storage.md +29 -0
- 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/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/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-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
- /package/dist/{chunk-DAGICKHT.js.map → chunk-ULX5FYEM.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
|
+
|
|
@@ -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
|
/**
|
|
@@ -420,6 +420,145 @@ export async function getSignedUrl(
|
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
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
|
+
|
|
423
562
|
/**
|
|
424
563
|
* Delete a file from storage
|
|
425
564
|
* @param supabase - Supabase client instance
|