@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,223 @@
1
+ /**
2
+ * Batched Audit Manager for RBAC
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/AuditBatched
5
+ * @since 2.0.0
6
+ *
7
+ * This module provides batched audit logging to reduce network requests
8
+ * by queuing events and sending them in batches.
9
+ */
10
+
11
+ import { SupabaseClient } from '@supabase/supabase-js';
12
+ import { Database } from '../types/database';
13
+ import { RBACAuditEvent, UUID } from './types';
14
+ import { AuditEventPayload } from './audit';
15
+ import { logger } from '../utils/core/logger';
16
+
17
+ export interface BatchedAuditConfig {
18
+ /** Enable batched audit logging (default: true) */
19
+ enabled: boolean;
20
+ /** Time window in milliseconds to wait before sending batch (default: 100ms) */
21
+ batchWindow: number;
22
+ /** Maximum batch size before forcing send (default: 10) */
23
+ batchSize: number;
24
+ }
25
+
26
+ const DEFAULT_CONFIG: BatchedAuditConfig = {
27
+ enabled: true,
28
+ batchWindow: 500, // 500ms - increased for better batching
29
+ batchSize: 20, // Increased from 10 to 20 for better efficiency
30
+ };
31
+
32
+ /**
33
+ * Batched Audit Manager
34
+ *
35
+ * Queues audit events and sends them in batches to reduce network requests.
36
+ */
37
+ export class BatchedAuditManager {
38
+ private supabase: SupabaseClient<Database>;
39
+ private config: BatchedAuditConfig;
40
+ private eventQueue: Array<Omit<RBACAuditEvent, 'id' | 'created_at'>> = [];
41
+ private flushTimer: ReturnType<typeof setTimeout> | null = null;
42
+ private isFlushing: boolean = false;
43
+
44
+ constructor(supabase: SupabaseClient<Database>, config: Partial<BatchedAuditConfig> = {}) {
45
+ this.supabase = supabase;
46
+ this.config = { ...DEFAULT_CONFIG, ...config };
47
+ }
48
+
49
+ /**
50
+ * Update configuration
51
+ */
52
+ updateConfig(config: Partial<BatchedAuditConfig>): void {
53
+ this.config = { ...this.config, ...config };
54
+ }
55
+
56
+ /**
57
+ * Queue an audit event for batching
58
+ *
59
+ * @param event - Audit event payload
60
+ */
61
+ async queueEvent(event: AuditEventPayload): Promise<void> {
62
+ if (!this.config.enabled) {
63
+ // If batching is disabled, send immediately
64
+ await this.sendEventImmediately(event);
65
+ return;
66
+ }
67
+
68
+ // Skip audit logging for cached checks (only log on cache miss)
69
+ if ('cache_hit' in event && event.cache_hit === true) {
70
+ return;
71
+ }
72
+
73
+ // Convert event payload to database format
74
+ const auditEvent = this.convertToAuditEvent(event);
75
+ if (!auditEvent) {
76
+ return;
77
+ }
78
+
79
+ // Add to queue
80
+ this.eventQueue.push(auditEvent);
81
+
82
+ // Check if we should flush immediately
83
+ if (this.eventQueue.length >= this.config.batchSize) {
84
+ await this.flush();
85
+ } else {
86
+ // Schedule flush after batch window
87
+ this.scheduleFlush();
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Convert audit event payload to database format
93
+ */
94
+ private convertToAuditEvent(event: AuditEventPayload): Omit<RBACAuditEvent, 'id' | 'created_at'> | null {
95
+ // Validate required fields
96
+ if (!event.userId) {
97
+ logger.error('RBAC Audit', 'Cannot queue audit event without userId:', {
98
+ eventType: event.type,
99
+ organisationId: event.organisationId
100
+ });
101
+ return null;
102
+ }
103
+
104
+ // Validate pageId: only include in page_id column if it's a valid UUID
105
+ const rawPageId = 'pageId' in event ? event.pageId : undefined;
106
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
107
+ const isValidPageIdUuid = rawPageId && uuidRegex.test(rawPageId);
108
+ const pageIdUuid: UUID | undefined = isValidPageIdUuid ? (rawPageId as UUID) : undefined;
109
+ const pageIdName: string | undefined = rawPageId && !isValidPageIdUuid ? rawPageId : undefined;
110
+
111
+ return {
112
+ event_type: event.type,
113
+ user_id: event.userId,
114
+ organisation_id: event.organisationId || null,
115
+ event_id: 'eventId' in event ? event.eventId : undefined,
116
+ app_id: 'appId' in event ? event.appId : undefined,
117
+ page_id: pageIdUuid,
118
+ permission: 'permission' in event ? event.permission : undefined,
119
+ decision: 'decision' in event ? event.decision : undefined,
120
+ source: 'source' in event ? event.source : 'api',
121
+ bypass: 'bypass' in event ? event.bypass : undefined,
122
+ duration_ms: 'duration_ms' in event ? event.duration_ms : undefined,
123
+ metadata: {
124
+ ...event.metadata,
125
+ cache_hit: 'cache_hit' in event ? event.cache_hit : undefined,
126
+ cache_source: 'cache_source' in event ? event.cache_source : undefined,
127
+ no_organisation_context: !event.organisationId,
128
+ page_name: pageIdName,
129
+ },
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Schedule a flush after the batch window
135
+ */
136
+ private scheduleFlush(): void {
137
+ if (this.flushTimer) {
138
+ return; // Already scheduled
139
+ }
140
+
141
+ this.flushTimer = setTimeout(() => {
142
+ this.flushTimer = null;
143
+ this.flush();
144
+ }, this.config.batchWindow);
145
+ }
146
+
147
+ /**
148
+ * Flush all queued events
149
+ */
150
+ async flush(): Promise<void> {
151
+ if (this.isFlushing || this.eventQueue.length === 0) {
152
+ return;
153
+ }
154
+
155
+ // Clear any pending timer
156
+ if (this.flushTimer) {
157
+ clearTimeout(this.flushTimer);
158
+ this.flushTimer = null;
159
+ }
160
+
161
+ this.isFlushing = true;
162
+
163
+ try {
164
+ // Get all events from queue
165
+ const eventsToSend = [...this.eventQueue];
166
+ this.eventQueue = [];
167
+
168
+ if (eventsToSend.length === 0) {
169
+ return;
170
+ }
171
+
172
+ // Send batch to database
173
+ const { error } = await (this.supabase as any)
174
+ .from('rbac_audit_events')
175
+ .insert(eventsToSend);
176
+
177
+ if (error) {
178
+ logger.warn('RBAC Audit', 'Failed to insert batched audit events:', {
179
+ error: error.message,
180
+ code: error.code,
181
+ batchSize: eventsToSend.length,
182
+ });
183
+ }
184
+ } catch (error) {
185
+ logger.error('RBAC Audit', 'Unexpected error during batched audit logging:', error);
186
+ } finally {
187
+ this.isFlushing = false;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Send event immediately (bypass batching)
193
+ */
194
+ private async sendEventImmediately(event: AuditEventPayload): Promise<void> {
195
+ const auditEvent = this.convertToAuditEvent(event);
196
+ if (!auditEvent) {
197
+ return;
198
+ }
199
+
200
+ try {
201
+ const { error } = await (this.supabase as any)
202
+ .from('rbac_audit_events')
203
+ .insert([auditEvent]);
204
+
205
+ if (error) {
206
+ logger.warn('RBAC Audit', 'Failed to insert audit event:', {
207
+ error: error.message,
208
+ code: error.code,
209
+ });
210
+ }
211
+ } catch (error) {
212
+ logger.error('RBAC Audit', 'Unexpected error during audit logging:', error);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Force flush and wait for completion
218
+ */
219
+ async forceFlush(): Promise<void> {
220
+ await this.flush();
221
+ }
222
+ }
223
+
@@ -294,7 +294,7 @@ export class EnhancedRBACAuditManager {
294
294
  async getUserAuditEvents(userId: UUID, limit: number = 100): Promise<RBACAuditEvent[]> {
295
295
  const { data, error } = await this.supabase
296
296
  .from('rbac_audit_events')
297
- .select('*')
297
+ .select('id, event_type, user_id, organisation_id, event_id, app_id, page_id, permission, decision, source, bypass, duration_ms, metadata, created_at')
298
298
  .eq('user_id', userId)
299
299
  .order('created_at', { ascending: false })
300
300
  .limit(limit);
@@ -338,7 +338,7 @@ export class EnhancedRBACAuditManager {
338
338
  async getOrganisationAuditEvents(organisationId: UUID, limit: number = 100): Promise<RBACAuditEvent[]> {
339
339
  const { data, error } = await this.supabase
340
340
  .from('rbac_audit_events')
341
- .select('*')
341
+ .select('id, event_type, user_id, organisation_id, event_id, app_id, page_id, permission, decision, source, bypass, duration_ms, metadata, created_at')
342
342
  .eq('organisation_id', organisationId)
343
343
  .order('created_at', { ascending: false })
344
344
  .limit(limit);
@@ -60,7 +60,8 @@ describe('RBACAuditManager', () => {
60
60
 
61
61
  beforeEach(() => {
62
62
  mockSupabase = createMockSupabaseClient();
63
- auditManager = new RBACAuditManager(mockSupabase as any);
63
+ // Disable batching in tests for predictable behavior
64
+ auditManager = new RBACAuditManager(mockSupabase as any, false);
64
65
  });
65
66
 
66
67
  afterEach(() => {
@@ -544,7 +545,7 @@ describe('RBACAuditManager', () => {
544
545
  source: 'ui' as AuditEventSource,
545
546
  bypass: true,
546
547
  duration_ms: 250,
547
- cache_hit: true,
548
+ cache_hit: false, // Set to false so event is actually logged (cached events are skipped)
548
549
  cache_source: 'memory',
549
550
  metadata: { test: 'data' }
550
551
  };
@@ -567,7 +568,7 @@ describe('RBACAuditManager', () => {
567
568
  source: 'ui',
568
569
  bypass: true,
569
570
  duration_ms: 250,
570
- cache_hit: true,
571
+ cache_hit: false,
571
572
  cache_source: 'memory',
572
573
  metadata: { test: 'data' }
573
574
  }),
@@ -648,7 +649,7 @@ describe('Global Audit Manager', () => {
648
649
  });
649
650
 
650
651
  it('creates and sets global audit manager', () => {
651
- const manager = createAuditManager(mockSupabase as any);
652
+ const manager = createAuditManager(mockSupabase as any, false);
652
653
  setGlobalAuditManager(manager);
653
654
 
654
655
  const globalManager = getGlobalAuditManager();
@@ -656,7 +657,7 @@ describe('Global Audit Manager', () => {
656
657
  });
657
658
 
658
659
  it('emits events through global manager', async () => {
659
- const manager = createAuditManager(mockSupabase as any);
660
+ const manager = createAuditManager(mockSupabase as any, false);
660
661
  setGlobalAuditManager(manager);
661
662
 
662
663
  const event: PermissionCheckAuditEvent = {
package/src/rbac/audit.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  } from './types';
17
17
  import type { AuditEventType } from './types/functions';
18
18
  import { logger } from '../utils/core/logger';
19
+ import { BatchedAuditManager } from './audit-batched';
19
20
 
20
21
  /**
21
22
  * Audit event payload for permission checks
@@ -111,9 +112,19 @@ export class RBACAuditManager {
111
112
  private supabase: SupabaseClient<Database>;
112
113
  private enabled: boolean = true;
113
114
  private fallbackEnabled: boolean = true;
114
-
115
- constructor(supabase: SupabaseClient<Database>) {
115
+ private batchedManager: BatchedAuditManager | null = null;
116
+ private useBatching: boolean = true;
117
+
118
+ constructor(
119
+ supabase: SupabaseClient<Database>,
120
+ useBatching: boolean = true,
121
+ batchConfig?: { batchWindow?: number; batchSize?: number }
122
+ ) {
116
123
  this.supabase = supabase;
124
+ this.useBatching = useBatching;
125
+ if (useBatching) {
126
+ this.batchedManager = new BatchedAuditManager(supabase, batchConfig);
127
+ }
117
128
  }
118
129
 
119
130
  /**
@@ -154,6 +165,17 @@ export class RBACAuditManager {
154
165
  return;
155
166
  }
156
167
 
168
+ // Skip audit logging for cached checks (only log on cache miss)
169
+ if ('cache_hit' in event && event.cache_hit === true) {
170
+ return;
171
+ }
172
+
173
+ // Use batched manager if enabled
174
+ if (this.useBatching && this.batchedManager) {
175
+ await this.batchedManager.queueEvent(event);
176
+ return;
177
+ }
178
+
157
179
  // Validate required fields before attempting to insert
158
180
  // MANDATORY: All audit events must have userId
159
181
  if (!event.userId) {
@@ -332,7 +354,7 @@ export class RBACAuditManager {
332
354
  async getUserAuditEvents(userId: UUID, limit: number = 100): Promise<RBACAuditEvent[]> {
333
355
  const { data, error } = await this.supabase
334
356
  .from('rbac_audit_events')
335
- .select('*')
357
+ .select('id, event_type, user_id, organisation_id, event_id, app_id, page_id, permission, decision, source, bypass, duration_ms, metadata, created_at')
336
358
  .eq('user_id', userId)
337
359
  .order('created_at', { ascending: false })
338
360
  .limit(limit);
@@ -376,7 +398,7 @@ export class RBACAuditManager {
376
398
  async getOrganisationAuditEvents(organisationId: UUID, limit: number = 100): Promise<RBACAuditEvent[]> {
377
399
  const { data, error } = await this.supabase
378
400
  .from('rbac_audit_events')
379
- .select('*')
401
+ .select('id, event_type, user_id, organisation_id, event_id, app_id, page_id, permission, decision, source, bypass, duration_ms, metadata, created_at')
380
402
  .eq('organisation_id', organisationId)
381
403
  .order('created_at', { ascending: false })
382
404
  .limit(limit);
@@ -415,10 +437,16 @@ export class RBACAuditManager {
415
437
  * Create an audit manager instance
416
438
  *
417
439
  * @param supabase - Supabase client
440
+ * @param useBatching - Whether to use batched audit logging (default: true)
441
+ * @param batchConfig - Optional batch configuration
418
442
  * @returns RBACAuditManager instance
419
443
  */
420
- export function createAuditManager(supabase: SupabaseClient<Database>): RBACAuditManager {
421
- return new RBACAuditManager(supabase);
444
+ export function createAuditManager(
445
+ supabase: SupabaseClient<Database>,
446
+ useBatching: boolean = true,
447
+ batchConfig?: { batchWindow?: number; batchSize?: number }
448
+ ): RBACAuditManager {
449
+ return new RBACAuditManager(supabase, useBatching, batchConfig);
422
450
  }
423
451
 
424
452
  /**
@@ -67,6 +67,7 @@ export const INVALIDATION_PATTERNS = {
67
67
  export class RBACCacheInvalidationManager {
68
68
  private supabase: SupabaseClient<Database>;
69
69
  private invalidationCallbacks: Set<(pattern: string) => void> = new Set();
70
+ private channels: Array<{ unsubscribe: () => void }> = [];
70
71
 
71
72
  constructor(supabase: SupabaseClient<Database>) {
72
73
  this.supabase = supabase;
@@ -179,6 +180,7 @@ export class RBACCacheInvalidationManager {
179
180
 
180
181
  /**
181
182
  * Setup realtime subscriptions for automatic cache invalidation
183
+ * Prevents duplicate subscriptions by checking if already set up
182
184
  */
183
185
  private setupRealtimeSubscriptions(): void {
184
186
  // Check if realtime is available (skip in test environments)
@@ -186,9 +188,15 @@ export class RBACCacheInvalidationManager {
186
188
  log.debug('Realtime not available, skipping subscriptions');
187
189
  return;
188
190
  }
191
+
192
+ // Prevent duplicate subscriptions - if channels already exist, skip setup
193
+ if (this.channels.length > 0) {
194
+ log.debug('Realtime subscriptions already set up, skipping duplicate setup');
195
+ return;
196
+ }
189
197
 
190
198
  // Subscribe to organisation role changes
191
- this.supabase
199
+ const orgRolesChannel = this.supabase
192
200
  .channel('rbac_organisation_roles_changes')
193
201
  .on('postgres_changes', {
194
202
  event: '*',
@@ -202,11 +210,12 @@ export class RBACCacheInvalidationManager {
202
210
  if (user_id) {
203
211
  this.invalidateUser(user_id, `organisation_role_${payload.eventType}`);
204
212
  }
205
- })
206
- .subscribe();
213
+ });
214
+ const orgRolesSubscription = orgRolesChannel.subscribe();
215
+ this.channels.push(orgRolesSubscription);
207
216
 
208
217
  // Subscribe to event app role changes
209
- this.supabase
218
+ const eventAppRolesChannel = this.supabase
210
219
  .channel('rbac_event_app_roles_changes')
211
220
  .on('postgres_changes', {
212
221
  event: '*',
@@ -226,11 +235,12 @@ export class RBACCacheInvalidationManager {
226
235
  if (app_id) {
227
236
  this.invalidateApp(app_id, `event_app_role_${payload.eventType}`);
228
237
  }
229
- })
230
- .subscribe();
238
+ });
239
+ const eventAppRolesSubscription = eventAppRolesChannel.subscribe();
240
+ this.channels.push(eventAppRolesSubscription);
231
241
 
232
242
  // Subscribe to global role changes
233
- this.supabase
243
+ const globalRolesChannel = this.supabase
234
244
  .channel('rbac_global_roles_changes')
235
245
  .on('postgres_changes', {
236
246
  event: '*',
@@ -241,11 +251,12 @@ export class RBACCacheInvalidationManager {
241
251
  if (user_id) {
242
252
  this.invalidateUser(user_id, `global_role_${payload.eventType}`);
243
253
  }
244
- })
245
- .subscribe();
254
+ });
255
+ const globalRolesSubscription = globalRolesChannel.subscribe();
256
+ this.channels.push(globalRolesSubscription);
246
257
 
247
258
  // Subscribe to page permission changes
248
- this.supabase
259
+ const pagePermissionsChannel = this.supabase
249
260
  .channel('rbac_page_permissions_changes')
250
261
  .on('postgres_changes', {
251
262
  event: '*',
@@ -261,8 +272,30 @@ export class RBACCacheInvalidationManager {
261
272
  }
262
273
  // Note: We can't easily get user_id from role_id without additional query
263
274
  // This is a limitation of the current schema design
264
- })
265
- .subscribe();
275
+ });
276
+ const pagePermissionsSubscription = pagePermissionsChannel.subscribe();
277
+ this.channels.push(pagePermissionsSubscription);
278
+ }
279
+
280
+ /**
281
+ * Cleanup all realtime subscriptions
282
+ * Call this when the manager is no longer needed to prevent memory leaks
283
+ */
284
+ cleanup(): void {
285
+ // Unsubscribe from all channels
286
+ this.channels.forEach(channel => {
287
+ try {
288
+ if (channel && typeof channel.unsubscribe === 'function') {
289
+ channel.unsubscribe();
290
+ }
291
+ } catch (error) {
292
+ log.warn('Failed to unsubscribe from channel:', error);
293
+ }
294
+ });
295
+ this.channels = [];
296
+
297
+ // Clear all callbacks
298
+ this.invalidationCallbacks.clear();
266
299
  }
267
300
 
268
301
  /**
@@ -302,11 +335,18 @@ let globalCacheInvalidationManager: RBACCacheInvalidationManager | null = null;
302
335
 
303
336
  /**
304
337
  * Initialize the global cache invalidation manager
338
+ * Ensures only one instance exists per application lifecycle
305
339
  *
306
340
  * @param supabase - Supabase client
307
341
  * @returns Cache invalidation manager instance
308
342
  */
309
343
  export function initializeCacheInvalidation(supabase: SupabaseClient<Database>): RBACCacheInvalidationManager {
344
+ // Clean up existing manager if it exists (e.g., when switching Supabase clients)
345
+ if (globalCacheInvalidationManager) {
346
+ log.debug('Cleaning up existing cache invalidation manager before creating new one');
347
+ globalCacheInvalidationManager.cleanup();
348
+ }
349
+
310
350
  globalCacheInvalidationManager = new RBACCacheInvalidationManager(supabase);
311
351
  return globalCacheInvalidationManager;
312
352
  }
@@ -319,3 +359,14 @@ export function initializeCacheInvalidation(supabase: SupabaseClient<Database>):
319
359
  export function getCacheInvalidationManager(): RBACCacheInvalidationManager | null {
320
360
  return globalCacheInvalidationManager;
321
361
  }
362
+
363
+ /**
364
+ * Cleanup the global cache invalidation manager
365
+ * Call this when the application is shutting down or when switching Supabase clients
366
+ */
367
+ export function cleanupCacheInvalidation(): void {
368
+ if (globalCacheInvalidationManager) {
369
+ globalCacheInvalidationManager.cleanup();
370
+ globalCacheInvalidationManager = null;
371
+ }
372
+ }
@@ -81,8 +81,8 @@ describe('RBACCache', () => {
81
81
  mockDateNow.mockReturnValue(startTime);
82
82
  testCache.set('test-key', 'test-value');
83
83
 
84
- // Advance time by 60 seconds (default TTL) + 1ms to ensure expiration
85
- mockDateNow.mockReturnValue(startTime + 60001);
84
+ // Advance time by 120 seconds (default TTL) + 1ms to ensure expiration
85
+ mockDateNow.mockReturnValue(startTime + 120001);
86
86
  expect(testCache.get('test-key')).toBeNull();
87
87
 
88
88
  mockDateNow.mockRestore();
package/src/rbac/cache.ts CHANGED
@@ -13,32 +13,50 @@ import { CacheEntry, PermissionCacheKey } from './types';
13
13
  /**
14
14
  * In-memory cache for RBAC operations
15
15
  *
16
- * Provides 60-second TTL and pattern-based invalidation for permission checks.
16
+ * Provides two-tier caching:
17
+ * - Short-term cache: 120 seconds for frequently changing permissions
18
+ * - Session cache: 15 minutes for stable permissions (page-level checks)
17
19
  */
18
20
  export class RBACCache {
19
21
  private cache = new Map<string, CacheEntry<any>>();
20
- private readonly TTL = 60 * 1000; // 60 seconds
22
+ private sessionCache = new Map<string, CacheEntry<any>>();
23
+ private readonly TTL = 120 * 1000; // 120 seconds (short-term) - increased from 60s
24
+ private readonly SESSION_TTL = 15 * 60 * 1000; // 15 minutes (session-level) - increased from 5min
21
25
  private invalidationCallbacks: Set<(pattern: string) => void> = new Set();
22
26
 
23
27
  /**
24
28
  * Get a value from the cache
25
29
  *
30
+ * Checks both short-term cache and session cache.
31
+ *
26
32
  * @param key - Cache key
33
+ * @param useSessionCache - Whether to check session cache (default: true)
27
34
  * @returns Cached value or null if not found/expired
28
35
  */
29
- get<T>(key: string): T | null {
30
- const entry = this.cache.get(key);
36
+ get<T>(key: string, useSessionCache: boolean = true): T | null {
37
+ const now = Date.now();
31
38
 
32
- if (!entry) {
33
- return null;
39
+ // Check short-term cache first
40
+ const entry = this.cache.get(key);
41
+ if (entry && now <= entry.expires) {
42
+ return entry.data as T;
34
43
  }
35
-
36
- if (Date.now() > entry.expires) {
44
+ if (entry && now > entry.expires) {
37
45
  this.cache.delete(key);
38
- return null;
39
46
  }
40
-
41
- return entry.data as T;
47
+
48
+ // Check session cache if enabled
49
+ if (useSessionCache) {
50
+ const sessionEntry = this.sessionCache.get(key);
51
+ if (sessionEntry && now <= sessionEntry.expires) {
52
+ return sessionEntry.data as T;
53
+ }
54
+ if (sessionEntry && now > sessionEntry.expires) {
55
+ this.sessionCache.delete(key);
56
+ }
57
+ }
58
+
59
+ return null;
42
60
  }
43
61
 
44
62
  /**
@@ -47,14 +65,27 @@ export class RBACCache {
47
65
  * @param key - Cache key
48
66
  * @param data - Data to cache
49
67
  * @param ttl - Time to live in milliseconds (defaults to 60s)
68
+ * @param useSessionCache - Whether to also store in session cache (default: false for page-level checks)
50
69
  */
51
- set<T>(key: string, data: T, ttl: number = this.TTL): void {
70
+ set<T>(key: string, data: T, ttl: number = this.TTL, useSessionCache: boolean = false): void {
71
+ const now = Date.now();
52
72
  // For zero or negative TTL, set expires to current time to make it immediately expired
53
- const expires = ttl <= 0 ? Date.now() - 1 : Date.now() + ttl;
73
+ const expires = ttl <= 0 ? now - 1 : now + ttl;
74
+
75
+ // Always store in short-term cache
54
76
  this.cache.set(key, {
55
77
  data,
56
78
  expires,
57
79
  });
80
+
81
+ // Optionally store in session cache for page-level permissions
82
+ if (useSessionCache) {
83
+ const sessionExpires = ttl <= 0 ? now - 1 : now + this.SESSION_TTL;
84
+ this.sessionCache.set(key, {
85
+ data,
86
+ expires: sessionExpires,
87
+ });
88
+ }
58
89
  }
59
90
 
60
91
  /**
@@ -64,6 +95,7 @@ export class RBACCache {
64
95
  */
65
96
  delete(key: string): void {
66
97
  this.cache.delete(key);
98
+ this.sessionCache.delete(key);
67
99
  }
68
100
 
69
101
  /**
@@ -81,13 +113,23 @@ export class RBACCache {
81
113
  const matcher = this.createMatcher(trimmedPattern);
82
114
  const keysToDelete: string[] = [];
83
115
 
116
+ // Invalidate from both caches
84
117
  for (const key of this.cache.keys()) {
85
118
  if (matcher(key)) {
86
119
  keysToDelete.push(key);
87
120
  }
88
121
  }
122
+
123
+ for (const key of this.sessionCache.keys()) {
124
+ if (matcher(key) && !keysToDelete.includes(key)) {
125
+ keysToDelete.push(key);
126
+ }
127
+ }
89
128
 
90
- keysToDelete.forEach(key => this.cache.delete(key));
129
+ keysToDelete.forEach(key => {
130
+ this.cache.delete(key);
131
+ this.sessionCache.delete(key);
132
+ });
91
133
 
92
134
  // Notify invalidation callbacks
93
135
  this.invalidationCallbacks.forEach(callback => callback(trimmedPattern));
@@ -111,6 +153,7 @@ export class RBACCache {
111
153
  */
112
154
  clear(): void {
113
155
  this.cache.clear();
156
+ this.sessionCache.clear();
114
157
  }
115
158
 
116
159
  /**
@@ -118,12 +161,16 @@ export class RBACCache {
118
161
  */
119
162
  getStats(): {
120
163
  size: number;
164
+ sessionSize: number;
121
165
  ttl: number;
166
+ sessionTtl: number;
122
167
  keys: string[];
123
168
  } {
124
169
  return {
125
170
  size: this.cache.size,
171
+ sessionSize: this.sessionCache.size,
126
172
  ttl: this.TTL,
173
+ sessionTtl: this.SESSION_TTL,
127
174
  keys: Array.from(this.cache.keys()),
128
175
  };
129
176
  }