@jmruthers/pace-core 0.5.186 → 0.5.188

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/dist/{DataTable-IX2NBUTP.js → DataTable-GUFUNZ3N.js} +7 -7
  2. package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
  3. package/dist/{PublicPageProvider-DIzEzwKl.d.ts → PublicPageProvider-DrLDztHt.d.ts} +211 -106
  4. package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-643PUAIM.js} +2 -2
  5. package/dist/{api-BMFCXVQX.js → api-YP7XD5L6.js} +3 -3
  6. package/dist/{audit-WRS3KJKI.js → audit-B5P6FFIR.js} +2 -2
  7. package/dist/{chunk-HGPQUCBC.js → chunk-2UUZZJFT.js} +3 -3
  8. package/dist/{chunk-445GEP27.js → chunk-3GOZZZYH.js} +33 -8
  9. package/dist/chunk-3GOZZZYH.js.map +1 -0
  10. package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
  11. package/dist/chunk-63FOKYGO.js.map +1 -0
  12. package/dist/{chunk-DAGICKHT.js → chunk-DDM4CCYT.js} +3 -3
  13. package/dist/{chunk-XAUHJD3L.js → chunk-E7UAOUMY.js} +2 -2
  14. package/dist/{chunk-HDCUMOOI.js → chunk-EFCLXK7F.js} +792 -559
  15. package/dist/chunk-EFCLXK7F.js.map +1 -0
  16. package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
  17. package/dist/chunk-HEHYGYOX.js.map +1 -0
  18. package/dist/{chunk-GRIQLQ52.js → chunk-IM4QE42D.js} +27 -23
  19. package/dist/chunk-IM4QE42D.js.map +1 -0
  20. package/dist/{chunk-OALXJH4Y.js → chunk-IPCH26AG.js} +8 -8
  21. package/dist/chunk-IPCH26AG.js.map +1 -0
  22. package/dist/{chunk-UQWSHFVX.js → chunk-SAUPYVLF.js} +1 -1
  23. package/dist/{chunk-UQWSHFVX.js.map → chunk-SAUPYVLF.js.map} +1 -1
  24. package/dist/{chunk-TC7D3CR3.js → chunk-UNOTYLQF.js} +556 -101
  25. package/dist/chunk-UNOTYLQF.js.map +1 -0
  26. package/dist/{chunk-FXFJRTKI.js → chunk-VGZZXKBR.js} +5 -5
  27. package/dist/chunk-VGZZXKBR.js.map +1 -0
  28. package/dist/chunk-YHCN776L.js +447 -0
  29. package/dist/chunk-YHCN776L.js.map +1 -0
  30. package/dist/components.d.ts +4 -4
  31. package/dist/components.js +12 -10
  32. package/dist/components.js.map +1 -1
  33. package/dist/{file-reference-PRTSLxKx.d.ts → file-reference-D037xOFK.d.ts} +0 -1
  34. package/dist/hooks.d.ts +221 -6
  35. package/dist/hooks.js +146 -49
  36. package/dist/hooks.js.map +1 -1
  37. package/dist/index.d.ts +24 -9
  38. package/dist/index.js +62 -28
  39. package/dist/index.js.map +1 -1
  40. package/dist/providers.js +1 -1
  41. package/dist/rbac/index.d.ts +124 -7
  42. package/dist/rbac/index.js +27 -7
  43. package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
  44. package/dist/types.d.ts +1 -1
  45. package/dist/types.js +1 -1
  46. package/dist/{usePublicRouteParams-D71QLlg4.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +2 -2
  47. package/dist/utils.d.ts +213 -3
  48. package/dist/utils.js +22 -2
  49. package/dist/utils.js.map +1 -1
  50. package/docs/api/classes/ColumnFactory.md +1 -1
  51. package/docs/api/classes/ErrorBoundary.md +1 -1
  52. package/docs/api/classes/InvalidScopeError.md +1 -1
  53. package/docs/api/classes/Logger.md +1 -1
  54. package/docs/api/classes/MissingUserContextError.md +1 -1
  55. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  56. package/docs/api/classes/PermissionDeniedError.md +1 -1
  57. package/docs/api/classes/RBACAuditManager.md +21 -17
  58. package/docs/api/classes/RBACCache.md +31 -23
  59. package/docs/api/classes/RBACEngine.md +5 -5
  60. package/docs/api/classes/RBACError.md +1 -1
  61. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  62. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  63. package/docs/api/classes/StorageUtils.md +1 -1
  64. package/docs/api/enums/FileCategory.md +1 -1
  65. package/docs/api/enums/LogLevel.md +1 -1
  66. package/docs/api/enums/RBACErrorCode.md +1 -1
  67. package/docs/api/enums/RPCFunction.md +1 -1
  68. package/docs/api/interfaces/AddressFieldProps.md +241 -0
  69. package/docs/api/interfaces/AddressFieldRef.md +94 -0
  70. package/docs/api/interfaces/AggregateConfig.md +1 -1
  71. package/docs/api/interfaces/AutocompleteOptions.md +75 -0
  72. package/docs/api/interfaces/BadgeProps.md +1 -1
  73. package/docs/api/interfaces/ButtonProps.md +1 -1
  74. package/docs/api/interfaces/CalendarProps.md +1 -1
  75. package/docs/api/interfaces/CardProps.md +1 -1
  76. package/docs/api/interfaces/ColorPalette.md +1 -1
  77. package/docs/api/interfaces/ColorShade.md +1 -1
  78. package/docs/api/interfaces/ComplianceResult.md +1 -1
  79. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  80. package/docs/api/interfaces/DataRecord.md +1 -1
  81. package/docs/api/interfaces/DataTableAction.md +1 -1
  82. package/docs/api/interfaces/DataTableColumn.md +1 -1
  83. package/docs/api/interfaces/DataTableProps.md +1 -1
  84. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  85. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  86. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  87. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  88. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  89. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  90. package/docs/api/interfaces/ExportColumn.md +1 -1
  91. package/docs/api/interfaces/ExportOptions.md +1 -1
  92. package/docs/api/interfaces/FileDisplayProps.md +15 -15
  93. package/docs/api/interfaces/FileMetadata.md +1 -1
  94. package/docs/api/interfaces/FileReference.md +1 -1
  95. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  96. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  97. package/docs/api/interfaces/FileUploadProps.md +1 -1
  98. package/docs/api/interfaces/FooterProps.md +1 -1
  99. package/docs/api/interfaces/FormFieldProps.md +1 -1
  100. package/docs/api/interfaces/FormProps.md +1 -1
  101. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoggerConfig.md +1 -1
  106. package/docs/api/interfaces/LoginFormProps.md +1 -1
  107. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  108. package/docs/api/interfaces/NavigationContextType.md +1 -1
  109. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  110. package/docs/api/interfaces/NavigationItem.md +1 -1
  111. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  112. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  113. package/docs/api/interfaces/Organisation.md +1 -1
  114. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  115. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  116. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  117. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  118. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  119. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  120. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  121. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  122. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  123. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  124. package/docs/api/interfaces/PaletteData.md +1 -1
  125. package/docs/api/interfaces/ParsedAddress.md +120 -0
  126. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  127. package/docs/api/interfaces/ProgressProps.md +1 -1
  128. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  131. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  132. package/docs/api/interfaces/QuickFix.md +1 -1
  133. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  134. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  135. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  136. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  137. package/docs/api/interfaces/RBACConfig.md +26 -3
  138. package/docs/api/interfaces/RBACContext.md +1 -1
  139. package/docs/api/interfaces/RBACLogger.md +5 -5
  140. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  141. package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
  142. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  143. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  144. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  146. package/docs/api/interfaces/RBACResult.md +1 -1
  147. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  148. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  149. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  150. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  151. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  152. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  153. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  154. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  155. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  156. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  157. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  158. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  159. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  160. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  161. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  162. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  163. package/docs/api/interfaces/RouteConfig.md +1 -1
  164. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  165. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  166. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  167. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  168. package/docs/api/interfaces/SetupIssue.md +1 -1
  169. package/docs/api/interfaces/StorageConfig.md +1 -1
  170. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  171. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  172. package/docs/api/interfaces/StorageListOptions.md +1 -1
  173. package/docs/api/interfaces/StorageListResult.md +1 -1
  174. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  175. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  176. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  177. package/docs/api/interfaces/StyleImport.md +1 -1
  178. package/docs/api/interfaces/SwitchProps.md +1 -1
  179. package/docs/api/interfaces/TabsContentProps.md +1 -1
  180. package/docs/api/interfaces/TabsListProps.md +1 -1
  181. package/docs/api/interfaces/TabsProps.md +1 -1
  182. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  183. package/docs/api/interfaces/TextareaProps.md +1 -1
  184. package/docs/api/interfaces/ToastActionElement.md +1 -1
  185. package/docs/api/interfaces/ToastProps.md +1 -1
  186. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  187. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  188. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  189. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  190. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  191. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  192. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  193. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  194. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  195. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  196. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  197. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  198. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  199. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  200. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  201. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  202. package/docs/api/interfaces/UserEventAccess.md +1 -1
  203. package/docs/api/interfaces/UserMenuProps.md +1 -1
  204. package/docs/api/interfaces/UserProfile.md +1 -1
  205. package/docs/api/modules.md +318 -59
  206. package/docs/best-practices/performance.md +11 -0
  207. package/docs/getting-started/examples/README.md +2 -2
  208. package/docs/implementation-guides/file-upload-storage.md +29 -0
  209. package/docs/implementation-guides/public-pages.md +140 -1230
  210. package/docs/rbac/README.md +2 -1
  211. package/docs/rbac/api-reference.md +11 -0
  212. package/docs/rbac/performance.md +320 -0
  213. package/docs/standards/01-architecture-standard.md +5 -0
  214. package/docs/standards/05-security-standard.md +14 -0
  215. package/docs/standards/07-rbac-and-rls-standard.md +356 -0
  216. package/package.json +1 -1
  217. package/src/__tests__/public-recipe-view.test.ts +199 -0
  218. package/src/__tests__/rls-policies.test.ts +333 -0
  219. package/src/components/AddressField/AddressField.test.tsx +411 -0
  220. package/src/components/AddressField/AddressField.tsx +323 -0
  221. package/src/components/AddressField/README.md +336 -0
  222. package/src/components/AddressField/index.ts +10 -0
  223. package/src/components/AddressField/types.ts +65 -0
  224. package/src/components/FileDisplay/FileDisplay.test.tsx +454 -0
  225. package/src/components/FileDisplay/FileDisplay.tsx +28 -1
  226. package/src/components/index.ts +2 -0
  227. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
  228. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
  229. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
  230. package/src/hooks/index.ts +6 -0
  231. package/src/hooks/public/usePublicFileDisplay.ts +8 -10
  232. package/src/hooks/useAddressAutocomplete.test.ts +318 -0
  233. package/src/hooks/useAddressAutocomplete.ts +268 -0
  234. package/src/hooks/useFileDisplay.ts +3 -15
  235. package/src/hooks/useFileReference.test.ts +20 -3
  236. package/src/hooks/useFileReference.ts +3 -24
  237. package/src/hooks/useFileUrlCache.ts +246 -0
  238. package/src/hooks/useInactivityTracker.ts +31 -20
  239. package/src/hooks/useOrganisationSecurity.test.ts +10 -7
  240. package/src/hooks/useOrganisationSecurity.ts +3 -3
  241. package/src/hooks/useQueryCache.ts +315 -0
  242. package/src/index.ts +2 -0
  243. package/src/providers/services/EventServiceProvider.tsx +4 -1
  244. package/src/rbac/api.test.ts +21 -6
  245. package/src/rbac/api.ts +32 -11
  246. package/src/rbac/audit-batched.ts +223 -0
  247. package/src/rbac/audit-enhanced.ts +2 -2
  248. package/src/rbac/audit.test.ts +6 -5
  249. package/src/rbac/audit.ts +34 -6
  250. package/src/rbac/cache-invalidation.ts +63 -12
  251. package/src/rbac/cache.test.ts +2 -2
  252. package/src/rbac/cache.ts +61 -14
  253. package/src/rbac/components/PagePermissionGuard.tsx +19 -10
  254. package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
  255. package/src/rbac/config.ts +9 -0
  256. package/src/rbac/engine.ts +2 -21
  257. package/src/rbac/hooks/usePermissions.ts +21 -5
  258. package/src/rbac/index.ts +19 -0
  259. package/src/rbac/performance.ts +210 -0
  260. package/src/rbac/request-deduplication.ts +87 -0
  261. package/src/rbac/utils/deep-equal.ts +93 -0
  262. package/src/services/OrganisationService.ts +5 -4
  263. package/src/types/file-reference.ts +0 -1
  264. package/src/utils/file-reference/__tests__/file-reference.test.ts +31 -4
  265. package/src/utils/file-reference/index.ts +44 -15
  266. package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
  267. package/src/utils/google-places/googlePlacesUtils.ts +475 -0
  268. package/src/utils/google-places/index.ts +26 -0
  269. package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
  270. package/src/utils/google-places/types.ts +94 -0
  271. package/src/utils/index.ts +23 -0
  272. package/src/utils/request-deduplication.ts +165 -0
  273. package/src/utils/storage/helpers.ts +143 -4
  274. package/dist/chunk-445GEP27.js.map +0 -1
  275. package/dist/chunk-FMUCXFII.js +0 -76
  276. package/dist/chunk-FMUCXFII.js.map +0 -1
  277. package/dist/chunk-FSFQFJCU.js.map +0 -1
  278. package/dist/chunk-FXFJRTKI.js.map +0 -1
  279. package/dist/chunk-GRIQLQ52.js.map +0 -1
  280. package/dist/chunk-HDCUMOOI.js.map +0 -1
  281. package/dist/chunk-OALXJH4Y.js.map +0 -1
  282. package/dist/chunk-TC7D3CR3.js.map +0 -1
  283. package/dist/chunk-U6WNSFX5.js.map +0 -1
  284. /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
  285. /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-643PUAIM.js.map} +0 -0
  286. /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
  287. /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
  288. /package/dist/{chunk-HGPQUCBC.js.map → chunk-2UUZZJFT.js.map} +0 -0
  289. /package/dist/{chunk-DAGICKHT.js.map → chunk-DDM4CCYT.js.map} +0 -0
  290. /package/dist/{chunk-XAUHJD3L.js.map → chunk-E7UAOUMY.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
+
@@ -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}/{category}/filename
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}/{category}/filename
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 pathCategory = customPath || 'files';
47
- return `${orgId}/${pathCategory}/${fileName}`;
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