@jmruthers/pace-core 0.6.5 → 0.6.7
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/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rbac/cache.ts","../src/rbac/cache-invalidation.ts","../src/rbac/errors.ts","../src/rbac/security.ts","../src/rbac/config.ts","../src/rbac/engine.ts","../src/rbac/performance.ts","../src/rbac/request-deduplication.ts","../src/rbac/api.ts"],"sourcesContent":["/**\n * RBAC Cache Implementation\n * @package @jmruthers/pace-core\n * @module RBAC/Cache\n * @since 1.0.0\n * \n * This module provides caching functionality for RBAC operations with TTL and invalidation.\n */\n\nimport { UUID } from './types';\nimport { CacheEntry, PermissionCacheKey } from './types';\n\n/**\n * In-memory cache for RBAC operations\n * \n * Provides two-tier caching:\n * - Short-term cache: 120 seconds for frequently changing permissions\n * - Session cache: 15 minutes for stable permissions (page-level checks)\n */\nexport class RBACCache {\n private cache = new Map<string, CacheEntry<any>>();\n private sessionCache = new Map<string, CacheEntry<any>>();\n private readonly TTL = 120 * 1000; // 120 seconds (short-term) - increased from 60s\n private readonly SESSION_TTL = 15 * 60 * 1000; // 15 minutes (session-level) - increased from 5min\n private invalidationCallbacks: Set<(pattern: string) => void> = new Set();\n\n /**\n * Get a value from the cache\n * \n * Checks both short-term cache and session cache.\n * \n * @param key - Cache key\n * @param useSessionCache - Whether to check session cache (default: true)\n * @returns Cached value or null if not found/expired\n */\n get<T>(key: string, useSessionCache: boolean = true): T | null {\n const now = Date.now();\n \n // Check short-term cache first\n const entry = this.cache.get(key);\n if (entry && now <= entry.expires) {\n return entry.data as T;\n }\n if (entry && now > entry.expires) {\n this.cache.delete(key);\n }\n \n // Check session cache if enabled\n if (useSessionCache) {\n const sessionEntry = this.sessionCache.get(key);\n if (sessionEntry && now <= sessionEntry.expires) {\n return sessionEntry.data as T;\n }\n if (sessionEntry && now > sessionEntry.expires) {\n this.sessionCache.delete(key);\n }\n }\n \n return null;\n }\n\n /**\n * Set a value in the cache\n * \n * @param key - Cache key\n * @param data - Data to cache\n * @param ttl - Time to live in milliseconds (defaults to 60s)\n * @param useSessionCache - Whether to also store in session cache (default: false for page-level checks)\n */\n set<T>(key: string, data: T, ttl: number = this.TTL, useSessionCache: boolean = false): void {\n const now = Date.now();\n // For zero or negative TTL, set expires to current time to make it immediately expired\n const expires = ttl <= 0 ? now - 1 : now + ttl;\n \n // Always store in short-term cache\n this.cache.set(key, {\n data,\n expires,\n });\n \n // Optionally store in session cache for page-level permissions\n if (useSessionCache) {\n const sessionExpires = ttl <= 0 ? now - 1 : now + this.SESSION_TTL;\n this.sessionCache.set(key, {\n data,\n expires: sessionExpires,\n });\n }\n }\n\n /**\n * Delete a specific key from the cache\n * \n * @param key - Cache key to delete\n */\n delete(key: string): void {\n this.cache.delete(key);\n this.sessionCache.delete(key);\n }\n\n /**\n * Invalidate cache entries matching a pattern\n * \n * @param pattern - Pattern to match against cache keys\n */\n invalidate(pattern: string): void {\n const trimmedPattern = pattern?.trim();\n\n if (!trimmedPattern) {\n return;\n }\n\n const matcher = this.createMatcher(trimmedPattern);\n const keysToDelete: string[] = [];\n\n // Invalidate from both caches\n for (const key of this.cache.keys()) {\n if (matcher(key)) {\n keysToDelete.push(key);\n }\n }\n \n for (const key of this.sessionCache.keys()) {\n if (matcher(key) && !keysToDelete.includes(key)) {\n keysToDelete.push(key);\n }\n }\n\n keysToDelete.forEach(key => {\n this.cache.delete(key);\n this.sessionCache.delete(key);\n });\n\n // Notify invalidation callbacks\n this.invalidationCallbacks.forEach(callback => callback(trimmedPattern));\n }\n\n private createMatcher(pattern: string): (key: string) => boolean {\n if (pattern.includes('*')) {\n const escapedSegments = pattern\n .split('*')\n .map(segment => segment.replace(/[|\\\\{}()[\\]^$+?.-]/g, '\\\\$&'));\n const regexPattern = escapedSegments.join('.*');\n const regex = new RegExp(regexPattern);\n return (key: string) => regex.test(key);\n }\n\n return (key: string) => key.includes(pattern);\n }\n\n /**\n * Clear all cache entries\n */\n clear(): void {\n this.cache.clear();\n this.sessionCache.clear();\n }\n\n /**\n * Get cache statistics\n */\n getStats(): {\n size: number;\n sessionSize: number;\n ttl: number;\n sessionTtl: number;\n keys: string[];\n } {\n return {\n size: this.cache.size,\n sessionSize: this.sessionCache.size,\n ttl: this.TTL,\n sessionTtl: this.SESSION_TTL,\n keys: Array.from(this.cache.keys()),\n };\n }\n\n /**\n * Add an invalidation callback\n * \n * @param callback - Function to call when cache is invalidated\n */\n onInvalidate(callback: (pattern: string) => void): () => void {\n this.invalidationCallbacks.add(callback);\n \n // Return unsubscribe function\n return () => {\n this.invalidationCallbacks.delete(callback);\n };\n }\n\n /**\n * Generate cache key for permission check (simplified signature)\n * \n * @param userId - User ID\n * @param permission - Permission string\n * @param organisationId - Organisation ID (optional)\n * @param eventId - Event ID (optional)\n * @param appId - App ID (optional)\n * @param pageId - Page ID (optional)\n * @returns String cache key\n */\n static generateKey(\n userId: UUID,\n permission: string,\n organisationId?: UUID,\n eventId?: string,\n appId?: UUID,\n pageId?: UUID | string\n ): string {\n const parts = [\n 'perm',\n userId,\n organisationId || 'null',\n eventId || 'null',\n appId || 'null',\n permission || 'null',\n pageId || 'null',\n ];\n \n return parts.join(':');\n }\n\n /**\n * Generate cache key for permission check (object signature)\n * \n * @param key - Permission cache key object\n * @returns String cache key\n */\n static generatePermissionKey(key: PermissionCacheKey): string {\n const parts = [\n 'perm',\n key.userId,\n key.organisationId || 'null',\n key.eventId || 'null',\n key.appId || 'null',\n key.permission || 'null',\n key.pageId || 'null',\n ];\n \n return parts.join(':');\n }\n\n /**\n * Generate cache key for access level\n * \n * @param userId - User ID\n * @param organisationId - Organisation ID\n * @param eventId - Event ID (optional)\n * @param appId - App ID (optional)\n * @returns String cache key\n */\n static generateAccessLevelKey(\n userId: UUID,\n organisationId: UUID,\n eventId?: string,\n appId?: UUID\n ): string {\n const parts = [\n 'access',\n userId,\n organisationId,\n eventId || 'null',\n appId || 'null',\n ];\n \n return parts.join(':');\n }\n\n /**\n * Generate cache key for permission map\n * \n * @param userId - User ID\n * @param organisationId - Organisation ID\n * @param eventId - Event ID (optional)\n * @param appId - App ID (optional)\n * @returns String cache key\n */\n static generatePermissionMapKey(\n userId: UUID,\n organisationId: UUID,\n eventId?: string,\n appId?: UUID\n ): string {\n const parts = [\n 'map',\n userId,\n organisationId,\n eventId || 'null',\n appId || 'null',\n ];\n \n return parts.join(':');\n }\n}\n\n/**\n * Global cache instance\n * \n * This is the default cache instance used by the RBAC system.\n * You can create additional instances if needed for different contexts.\n */\nexport const rbacCache = new RBACCache();\n\n/**\n * Cache key patterns for invalidation\n */\nexport const CACHE_PATTERNS = {\n USER: (userId: UUID) => `:${userId}:`,\n ORGANISATION: (organisationId: UUID) => `:${organisationId}:`,\n EVENT: (eventId: string) => `:${eventId}:`,\n APP: (appId: UUID) => `:${appId}`,\n PERMISSION: (userId: UUID, organisationId: UUID) => `perm:${userId}:${organisationId}:`,\n} as const;\n","/**\n * RBAC Cache Invalidation Service\n * @package @jmruthers/pace-core\n * @module RBAC/CacheInvalidation\n * @since 1.0.0\n * \n * This module provides automatic cache invalidation when RBAC data changes.\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../types/database';\nimport { rbacCache, CACHE_PATTERNS } from './cache';\nimport { emitAuditEvent } from './audit';\nimport { UUID } from './types';\nimport { createLogger } from '../utils/core/logger';\n\nconst log = createLogger('RBACCache');\n\n/**\n * Cache invalidation patterns for different RBAC changes\n */\nexport const INVALIDATION_PATTERNS = {\n // User-level invalidation\n USER_ROLES_CHANGED: (userId: UUID) => [\n CACHE_PATTERNS.USER(userId),\n `perm:${userId}:*`,\n `access:${userId}:*`,\n `map:${userId}:*`\n ],\n \n // Organisation-level invalidation\n ORGANISATION_PERMISSIONS_CHANGED: (organisationId: UUID) => [\n CACHE_PATTERNS.ORGANISATION(organisationId),\n `perm:*:${organisationId}:*`,\n `access:*:${organisationId}:*`,\n `map:*:${organisationId}:*`\n ],\n \n // Event-level invalidation\n EVENT_PERMISSIONS_CHANGED: (eventId: string) => [\n CACHE_PATTERNS.EVENT(eventId),\n `perm:*:*:${eventId}:*`,\n `access:*:*:${eventId}:*`,\n `map:*:*:${eventId}:*`\n ],\n \n // App-level invalidation\n APP_PERMISSIONS_CHANGED: (appId: UUID) => [\n CACHE_PATTERNS.APP(appId),\n `perm:*:*:*:${appId}:*`,\n `access:*:*:*:${appId}`,\n `map:*:*:*:${appId}`\n ],\n \n // Page-level invalidation\n PAGE_PERMISSIONS_CHANGED: (pageId: UUID) => [\n `perm:*:*:*:*:${pageId}`,\n `map:*:*:*:*`\n ]\n} as const;\n\n/**\n * RBAC Cache Invalidation Manager\n * \n * Handles automatic cache invalidation when RBAC data changes.\n */\nexport class RBACCacheInvalidationManager {\n private supabase: SupabaseClient<Database>;\n private invalidationCallbacks: Set<(pattern: string) => void> = new Set();\n private channels: Array<{ unsubscribe: () => void }> = [];\n\n constructor(supabase: SupabaseClient<Database>) {\n this.supabase = supabase;\n this.setupRealtimeSubscriptions();\n }\n\n /**\n * Add a callback for cache invalidation events\n * \n * @param callback - Function to call when cache is invalidated\n * @returns Unsubscribe function\n */\n onInvalidation(callback: (pattern: string) => void): () => void {\n this.invalidationCallbacks.add(callback);\n return () => this.invalidationCallbacks.delete(callback);\n }\n\n /**\n * Invalidate cache for a specific user\n * \n * @param userId - User ID\n * @param reason - Reason for invalidation\n */\n invalidateUser(userId: UUID, reason: string): void {\n const patterns = INVALIDATION_PATTERNS.USER_ROLES_CHANGED(userId);\n this.invalidatePatterns(patterns, reason);\n }\n\n /**\n * Invalidate cache for a specific organisation\n * \n * @param organisationId - Organisation ID\n * @param reason - Reason for invalidation\n */\n invalidateOrganisation(organisationId: UUID, reason: string): void {\n const patterns = INVALIDATION_PATTERNS.ORGANISATION_PERMISSIONS_CHANGED(organisationId);\n this.invalidatePatterns(patterns, reason);\n }\n\n /**\n * Invalidate cache for a specific event\n * \n * @param eventId - Event ID\n * @param reason - Reason for invalidation\n */\n invalidateEvent(eventId: string, reason: string): void {\n const patterns = INVALIDATION_PATTERNS.EVENT_PERMISSIONS_CHANGED(eventId);\n this.invalidatePatterns(patterns, reason);\n }\n\n /**\n * Invalidate cache for a specific app\n * \n * @param appId - App ID\n * @param reason - Reason for invalidation\n */\n invalidateApp(appId: UUID, reason: string): void {\n const patterns = INVALIDATION_PATTERNS.APP_PERMISSIONS_CHANGED(appId);\n this.invalidatePatterns(patterns, reason);\n }\n\n /**\n * Invalidate cache for a specific page\n * \n * @param pageId - Page ID\n * @param reason - Reason for invalidation\n */\n invalidatePage(pageId: UUID, reason: string): void {\n const patterns = INVALIDATION_PATTERNS.PAGE_PERMISSIONS_CHANGED(pageId);\n this.invalidatePatterns(patterns, reason);\n }\n\n /**\n * Invalidate cache patterns and notify callbacks\n * \n * @param patterns - Array of cache patterns to invalidate\n * @param reason - Reason for invalidation\n */\n private invalidatePatterns(patterns: string[], reason: string): void {\n log.debug(`Invalidating patterns: ${patterns.join(', ')} (${reason})`);\n \n patterns.forEach(pattern => {\n rbacCache.invalidate(pattern);\n });\n\n // Notify callbacks\n this.invalidationCallbacks.forEach(callback => {\n patterns.forEach(pattern => callback(pattern));\n });\n\n // Log audit event for cache invalidation\n emitAuditEvent({\n type: 'permission_check',\n userId: 'system' as UUID,\n organisationId: '00000000-0000-0000-0000-000000000000' as UUID,\n permission: 'cache:invalidate',\n decision: true,\n source: 'api',\n duration_ms: 0,\n metadata: {\n reason,\n patterns,\n timestamp: new Date().toISOString(),\n cache_invalidation: true\n }\n }).catch(error => {\n log.warn('Failed to log cache invalidation audit event:', error);\n });\n }\n\n /**\n * Cleanup subscriptions only (not all callbacks)\n * Used internally to cleanup before setting up new subscriptions\n */\n private cleanupSubscriptions(): void {\n this.channels.forEach(channel => {\n try {\n if (channel && typeof channel.unsubscribe === 'function') {\n channel.unsubscribe();\n }\n } catch (error) {\n log.warn('Failed to unsubscribe from channel:', error);\n }\n });\n this.channels = [];\n }\n\n /**\n * Setup realtime subscriptions for automatic cache invalidation\n * Always cleans up existing subscriptions before setting up new ones to prevent duplicates\n */\n private setupRealtimeSubscriptions(): void {\n // Always cleanup existing subscriptions first to prevent duplicates\n this.cleanupSubscriptions();\n \n // Check if realtime is available (skip in test environments)\n if (!this.supabase.channel || typeof this.supabase.channel !== 'function') {\n log.debug('Realtime not available, skipping subscriptions');\n return;\n }\n\n // Subscribe to organisation role changes\n const orgRolesChannel = this.supabase\n .channel('rbac_organisation_roles_changes')\n .on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'rbac_organisation_roles'\n }, (payload: any) => {\n const { organisation_id, user_id } = payload.new || payload.old || {};\n if (organisation_id) {\n this.invalidateOrganisation(organisation_id, `organisation_role_${payload.eventType}`);\n }\n if (user_id) {\n this.invalidateUser(user_id, `organisation_role_${payload.eventType}`);\n }\n });\n const orgRolesSubscription = orgRolesChannel.subscribe();\n this.channels.push(orgRolesSubscription);\n\n // Subscribe to event app role changes\n const eventAppRolesChannel = this.supabase\n .channel('rbac_event_app_roles_changes')\n .on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'rbac_event_app_roles'\n }, (payload: any) => {\n const { organisation_id, user_id, event_id, app_id } = payload.new || payload.old || {};\n if (organisation_id) {\n this.invalidateOrganisation(organisation_id, `event_app_role_${payload.eventType}`);\n }\n if (user_id) {\n this.invalidateUser(user_id, `event_app_role_${payload.eventType}`);\n }\n if (event_id) {\n this.invalidateEvent(event_id, `event_app_role_${payload.eventType}`);\n }\n if (app_id) {\n this.invalidateApp(app_id, `event_app_role_${payload.eventType}`);\n }\n });\n const eventAppRolesSubscription = eventAppRolesChannel.subscribe();\n this.channels.push(eventAppRolesSubscription);\n\n // Subscribe to global role changes\n const globalRolesChannel = this.supabase\n .channel('rbac_global_roles_changes')\n .on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'rbac_global_roles'\n }, (payload: any) => {\n const { user_id } = payload.new || payload.old || {};\n if (user_id) {\n this.invalidateUser(user_id, `global_role_${payload.eventType}`);\n }\n });\n const globalRolesSubscription = globalRolesChannel.subscribe();\n this.channels.push(globalRolesSubscription);\n\n // Subscribe to page permission changes\n const pagePermissionsChannel = this.supabase\n .channel('rbac_page_permissions_changes')\n .on('postgres_changes', {\n event: '*',\n schema: 'public',\n table: 'rbac_page_permissions'\n }, (payload: any) => {\n const { organisation_id, app_page_id, role_id } = payload.new || payload.old || {};\n if (organisation_id) {\n this.invalidateOrganisation(organisation_id, `page_permission_${payload.eventType}`);\n }\n if (app_page_id) {\n this.invalidatePage(app_page_id, `page_permission_${payload.eventType}`);\n }\n // Note: We can't easily get user_id from role_id without additional query\n // This is a limitation of the current schema design\n });\n const pagePermissionsSubscription = pagePermissionsChannel.subscribe();\n this.channels.push(pagePermissionsSubscription);\n }\n\n /**\n * Cleanup all realtime subscriptions\n * Call this when the manager is no longer needed to prevent memory leaks\n */\n cleanup(): void {\n // Unsubscribe from all channels\n this.channels.forEach(channel => {\n try {\n if (channel && typeof channel.unsubscribe === 'function') {\n channel.unsubscribe();\n }\n } catch (error) {\n log.warn('Failed to unsubscribe from channel:', error);\n }\n });\n this.channels = [];\n \n // Clear all callbacks\n this.invalidationCallbacks.clear();\n }\n\n /**\n * Manually trigger cache invalidation for all users in an organisation\n * \n * @param organisationId - Organisation ID\n * @param reason - Reason for invalidation\n */\n async invalidateAllUsersInOrganisation(organisationId: UUID, reason: string): Promise<void> {\n // Get all users in the organisation\n const { data: users } = await this.supabase\n .from('rbac_organisation_roles')\n .select('user_id')\n .eq('organisation_id', organisationId)\n .eq('is_active', true);\n\n if (users) {\n users.forEach(({ user_id }) => {\n this.invalidateUser(user_id, reason);\n });\n }\n }\n\n /**\n * Clear all cache entries\n */\n clearAllCache(): void {\n log.debug('Clearing all cache entries');\n rbacCache.clear();\n }\n}\n\n/**\n * Global cache invalidation manager instance\n */\nlet globalCacheInvalidationManager: RBACCacheInvalidationManager | null = null;\n\n/**\n * Initialize the global cache invalidation manager\n * Ensures only one instance exists per application lifecycle\n * \n * @param supabase - Supabase client\n * @returns Cache invalidation manager instance\n */\nexport function initializeCacheInvalidation(supabase: SupabaseClient<Database>): RBACCacheInvalidationManager {\n // Clean up existing manager if it exists (e.g., when switching Supabase clients)\n if (globalCacheInvalidationManager) {\n log.debug('Cleaning up existing cache invalidation manager before creating new one');\n globalCacheInvalidationManager.cleanup();\n }\n \n globalCacheInvalidationManager = new RBACCacheInvalidationManager(supabase);\n return globalCacheInvalidationManager;\n}\n\n/**\n * Get the global cache invalidation manager\n * \n * @returns Global cache invalidation manager or null if not initialized\n */\nexport function getCacheInvalidationManager(): RBACCacheInvalidationManager | null {\n return globalCacheInvalidationManager;\n}\n\n/**\n * Cleanup the global cache invalidation manager\n * Call this when the application is shutting down or when switching Supabase clients\n */\nexport function cleanupCacheInvalidation(): void {\n if (globalCacheInvalidationManager) {\n globalCacheInvalidationManager.cleanup();\n globalCacheInvalidationManager = null;\n }\n}\n","import {\n RBACError,\n PermissionDeniedError,\n OrganisationContextRequiredError,\n InvalidScopeError,\n MissingUserContextError,\n RBACNotInitializedError,\n} from './types';\n\nexport enum RBACErrorCategory {\n NETWORK = 'network_error',\n DATABASE = 'database_error',\n VALIDATION = 'validation_error',\n RATE_LIMIT = 'rate_limit_error',\n AUTHENTICATION = 'authentication_error',\n AUTHORIZATION = 'authorization_error',\n UNKNOWN = 'unknown_error',\n}\n\nconst RATE_LIMIT_STATUS_CODES = new Set([429]);\nconst AUTH_STATUS_CODES = new Set([401]);\nconst AUTHZ_STATUS_CODES = new Set([403]);\n\nfunction normalize(value: unknown): string {\n if (!value) {\n return '';\n }\n if (typeof value === 'string') {\n return value.toLowerCase();\n }\n if (value instanceof Error && typeof value.message === 'string') {\n return value.message.toLowerCase();\n }\n return String(value).toLowerCase();\n}\n\nexport function categorizeError(error: unknown): RBACErrorCategory {\n if (error instanceof PermissionDeniedError) {\n return RBACErrorCategory.AUTHORIZATION;\n }\n\n if (error instanceof OrganisationContextRequiredError || error instanceof InvalidScopeError) {\n return RBACErrorCategory.VALIDATION;\n }\n\n if (error instanceof MissingUserContextError) {\n return RBACErrorCategory.AUTHENTICATION;\n }\n\n if (error instanceof RBACError) {\n switch (error.code) {\n case 'PERMISSION_DENIED':\n return RBACErrorCategory.AUTHORIZATION;\n case 'ORGANISATION_CONTEXT_REQUIRED':\n case 'INVALID_SCOPE':\n return RBACErrorCategory.VALIDATION;\n case 'MISSING_USER_CONTEXT':\n return RBACErrorCategory.AUTHENTICATION;\n default:\n break;\n }\n }\n\n if (error && typeof error === 'object') {\n const status = (error as { status?: number }).status;\n if (typeof status === 'number') {\n if (RATE_LIMIT_STATUS_CODES.has(status)) {\n return RBACErrorCategory.RATE_LIMIT;\n }\n if (AUTH_STATUS_CODES.has(status)) {\n return RBACErrorCategory.AUTHENTICATION;\n }\n if (AUTHZ_STATUS_CODES.has(status)) {\n return RBACErrorCategory.AUTHORIZATION;\n }\n if (status >= 500) {\n return RBACErrorCategory.DATABASE;\n }\n }\n\n const codeValue = normalize((error as { code?: string }).code);\n if (codeValue) {\n if (codeValue.includes('network')) {\n return RBACErrorCategory.NETWORK;\n }\n if (codeValue.includes('postgres') || codeValue.includes('database') || codeValue.includes('db')) {\n return RBACErrorCategory.DATABASE;\n }\n if (codeValue.includes('rate')) {\n return RBACErrorCategory.RATE_LIMIT;\n }\n if (codeValue.includes('auth')) {\n return RBACErrorCategory.AUTHENTICATION;\n }\n if (codeValue.includes('permission')) {\n return RBACErrorCategory.AUTHORIZATION;\n }\n if (codeValue.includes('scope') || codeValue.includes('invalid')) {\n return RBACErrorCategory.VALIDATION;\n }\n }\n }\n\n const message = normalize(error);\n if (message.includes('timeout') || message.includes('network') || message.includes('fetch')) {\n return RBACErrorCategory.NETWORK;\n }\n if (message.includes('postgres') || message.includes('database') || message.includes('connection')) {\n return RBACErrorCategory.DATABASE;\n }\n if (message.includes('rate limit') || message.includes('too many requests')) {\n return RBACErrorCategory.RATE_LIMIT;\n }\n if (message.includes('permission') || message.includes('forbidden')) {\n return RBACErrorCategory.AUTHORIZATION;\n }\n if (message.includes('auth') || message.includes('token') || message.includes('session')) {\n return RBACErrorCategory.AUTHENTICATION;\n }\n if (message.includes('invalid') || message.includes('scope') || message.includes('validation')) {\n return RBACErrorCategory.VALIDATION;\n }\n\n return RBACErrorCategory.UNKNOWN;\n}\n\nexport type SecurityEventType =\n | 'permission_denied'\n | 'invalid_input'\n | 'rate_limit_exceeded'\n | 'suspicious_activity'\n | 'network_error'\n | 'database_error'\n | 'validation_error'\n | 'rate_limit_error'\n | 'authentication_error'\n | 'unknown_error';\n\nexport function mapErrorCategoryToSecurityEventType(category: RBACErrorCategory): SecurityEventType {\n switch (category) {\n case RBACErrorCategory.AUTHORIZATION:\n return 'permission_denied';\n case RBACErrorCategory.NETWORK:\n return 'network_error';\n case RBACErrorCategory.DATABASE:\n return 'database_error';\n case RBACErrorCategory.VALIDATION:\n return 'validation_error';\n case RBACErrorCategory.RATE_LIMIT:\n return 'rate_limit_error';\n case RBACErrorCategory.AUTHENTICATION:\n return 'authentication_error';\n case RBACErrorCategory.UNKNOWN:\n default:\n return 'unknown_error';\n }\n}\n\n// Re-export error classes for convenience\nexport {\n RBACError,\n PermissionDeniedError,\n OrganisationContextRequiredError,\n InvalidScopeError,\n MissingUserContextError,\n RBACNotInitializedError,\n};\n","/**\n * RBAC Security Enhancements\n * @package @jmruthers/pace-core\n * @module RBAC/Security\n * @since 1.0.0\n * \n * Additional security measures for the RBAC system\n */\n\nimport { UUID, Permission, Scope } from './types';\nimport { createLogger } from '../utils/core/logger';\n\nconst log = createLogger('RBACSecurity');\n\n/**\n * Security validation utilities for RBAC operations\n */\nexport class RBACSecurityValidator {\n /**\n * Validate permission string format\n * @param permission - Permission string to validate\n * @returns True if valid, false otherwise\n */\n static validatePermission(permission: string): boolean {\n if (typeof permission !== 'string' || permission.length === 0) {\n return false;\n }\n\n // Permission format: operation:resource[.subresource]\n // Only CRUD operations are allowed (read, create, update, delete)\n // The 'manage' operation has been removed for RBAC compliance\n const permissionRegex = /^(read|create|update|delete):[a-z0-9._-]+$/;\n return permissionRegex.test(permission);\n }\n\n /**\n * Validate UUID format\n * @param uuid - UUID string to validate\n * @returns True if valid, false otherwise\n */\n static validateUUID(uuid: string): boolean {\n if (typeof uuid !== 'string' || uuid.length === 0) {\n return false;\n }\n\n // More permissive UUID regex that allows all valid UUID versions\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n return uuidRegex.test(uuid);\n }\n\n /**\n * Validate scope object\n * @param scope - Scope object to validate\n * @returns True if valid, false otherwise\n */\n static validateScope(scope: Scope): boolean {\n if (!scope || typeof scope !== 'object') {\n return false;\n }\n\n // Organisation ID validation - reject empty strings\n if (scope.organisationId !== undefined) {\n // Reject empty strings - use undefined/null instead\n if (typeof scope.organisationId === 'string' && scope.organisationId.trim() === '') {\n return false;\n }\n if (scope.organisationId && !this.validateUUID(scope.organisationId)) {\n return false;\n }\n }\n\n // Event ID should be a string if provided\n if (scope.eventId && typeof scope.eventId !== 'string') {\n return false;\n }\n\n // App ID should be a valid UUID if provided\n if (scope.appId && !this.validateUUID(scope.appId)) {\n return false;\n }\n\n // At least one valid field must be present\n return !!(scope.organisationId || scope.eventId || scope.appId);\n }\n\n /**\n * Sanitize input string to prevent injection attacks\n * @param input - Input string to sanitize\n * @returns Sanitized string\n */\n static sanitizeInput(input: string): string {\n if (typeof input !== 'string') {\n return '';\n }\n\n // Remove potentially dangerous characters\n return input\n .replace(/<[^>]*>/g, '') // Remove HTML tags\n .replace(/[<>\\\"'&]/g, '') // Remove HTML/XML characters\n .replace(/[;()]/g, '') // Remove SQL injection characters\n .replace(/javascript:/gi, '') // Remove javascript: protocol\n .replace(/data:/gi, '') // Remove data: protocol\n .trim();\n }\n\n /**\n * Validate user ID format\n * @param userId - User ID to validate\n * @returns True if valid, false otherwise\n */\n static validateUserId(userId: UUID): boolean {\n return this.validateUUID(userId);\n }\n\n /**\n * Check if permission is a wildcard permission\n * @param permission - Permission string to check\n * @returns True if wildcard, false otherwise\n */\n static isWildcardPermission(permission: string): boolean {\n return permission.includes('*') || permission.endsWith(':*');\n }\n\n /**\n * Validate permission hierarchy\n * @param permission - Permission to validate\n * @param requiredOperation - Required operation\n * @returns True if permission matches or is higher in hierarchy\n */\n static validatePermissionHierarchy(permission: string, requiredOperation: string): boolean {\n if (!this.validatePermission(permission)) {\n return false;\n }\n\n const [operation] = permission.split(':');\n // Only CRUD operations - 'manage' has been removed\n const hierarchy = ['read', 'create', 'update', 'delete'];\n \n const permissionLevel = hierarchy.indexOf(operation);\n const requiredLevel = hierarchy.indexOf(requiredOperation);\n \n if (permissionLevel === -1 || requiredLevel === -1) {\n return false;\n }\n\n // Higher level permissions include lower level permissions\n return permissionLevel >= requiredLevel;\n }\n\n /**\n * Rate limiting check (placeholder for future implementation)\n * @param userId - User ID\n * @param operation - Operation being performed\n * @returns True if within rate limit, false otherwise\n */\n static async checkRateLimit(userId: UUID, operation: string): Promise<boolean> {\n // TODO: Implement actual rate limiting logic\n // This could use Redis, in-memory cache, or database-based rate limiting\n return true;\n }\n\n\n /**\n * Log security event for monitoring\n * @param event - Security event details\n */\n private static rateLimitWarningCount = new Map<UUID, { count: number; lastWarning: number }>();\n private static readonly RATE_LIMIT_WARNING_THROTTLE_MS = 5000; // Only warn once per 5 seconds per user\n \n static logSecurityEvent(event: {\n type:\n | 'permission_denied'\n | 'invalid_input'\n | 'rate_limit_exceeded'\n | 'suspicious_activity'\n | 'network_error'\n | 'database_error'\n | 'validation_error'\n | 'rate_limit_error'\n | 'authentication_error'\n | 'unknown_error';\n userId: UUID;\n details: Record<string, any>;\n timestamp?: Date;\n }): void {\n const securityEvent = {\n ...event,\n timestamp: event.timestamp || new Date(),\n severity: this.getEventSeverity(event.type),\n };\n\n // Throttle rate limit warnings to prevent console flooding\n if (event.type === 'rate_limit_exceeded') {\n const now = Date.now();\n const userWarning = this.rateLimitWarningCount.get(event.userId);\n \n if (userWarning) {\n const timeSinceLastWarning = now - userWarning.lastWarning;\n if (timeSinceLastWarning < this.RATE_LIMIT_WARNING_THROTTLE_MS) {\n // Still within throttle window - increment count but don't log\n userWarning.count++;\n this.rateLimitWarningCount.set(event.userId, userWarning);\n return; // Skip logging to prevent console flooding\n } else {\n // Throttle window expired - log with count and reset\n log.warn('Security event (throttled):', {\n ...securityEvent,\n details: {\n ...securityEvent.details,\n suppressedWarnings: userWarning.count,\n message: `Rate limit exceeded (${userWarning.count + 1} times in last ${Math.round(timeSinceLastWarning / 1000)}s)`\n }\n });\n this.rateLimitWarningCount.set(event.userId, { count: 0, lastWarning: now });\n return;\n }\n } else {\n // First warning for this user - log it\n this.rateLimitWarningCount.set(event.userId, { count: 0, lastWarning: now });\n log.warn('Security event:', securityEvent);\n return;\n }\n }\n\n // Log other security events normally\n log.warn('Security event:', securityEvent);\n\n // TODO: Send to security monitoring service\n }\n\n /**\n * Get severity level for security event\n * @param eventType - Type of security event\n * @returns Severity level\n */\n private static getEventSeverity(eventType: string): 'low' | 'medium' | 'high' | 'critical' {\n switch (eventType) {\n case 'permission_denied':\n return 'low';\n case 'invalid_input':\n case 'rate_limit_exceeded':\n case 'rate_limit_error':\n case 'network_error':\n return 'medium';\n case 'validation_error':\n return 'high';\n case 'authentication_error':\n case 'database_error':\n return 'critical';\n case 'suspicious_activity':\n case 'unknown_error':\n return 'high';\n default:\n return 'low';\n }\n }\n}\n\n/**\n * Security configuration for RBAC system\n */\nexport interface RBACSecurityConfig {\n enableInputValidation: boolean;\n enableRateLimiting: boolean;\n enableAuditLogging: boolean;\n maxPermissionChecksPerMinute: number;\n suspiciousActivityThreshold: number;\n}\n\n/**\n * Default security configuration\n */\nexport const DEFAULT_SECURITY_CONFIG: RBACSecurityConfig = {\n enableInputValidation: true,\n enableRateLimiting: true,\n enableAuditLogging: true,\n maxPermissionChecksPerMinute: 1000, // Increased from 100 to 1000 for normal app usage\n suspiciousActivityThreshold: 10,\n};\n\n/**\n * Security context for RBAC operations\n * \n * OrganisationId is required - it can always be derived from event context in event-based apps.\n * If organisation context is not available, the operation should fail rather than proceed without context.\n */\nexport interface SecurityContext {\n userId: UUID;\n organisationId: UUID | null; // Required for resource-level permissions, null for page-level permissions (database handles NULL)\n ipAddress?: string;\n userAgent?: string;\n timestamp: Date;\n}\n\n/**\n * Security middleware for RBAC operations\n */\nexport class RBACSecurityMiddleware {\n private config: RBACSecurityConfig;\n\n constructor(config: RBACSecurityConfig = DEFAULT_SECURITY_CONFIG) {\n this.config = config;\n this._startCleanupInterval();\n }\n\n /**\n * Start periodic cleanup of expired entries\n */\n private _startCleanupInterval(): void {\n // Clear expired entries every 5 minutes\n setInterval(() => {\n this.clearExpiredEntries();\n }, 5 * 60 * 1000);\n }\n\n /**\n * Validate input before processing\n * @param input - Input to validate\n * @param context - Security context\n * @returns Validation result\n */\n async validateInput(input: any, context: SecurityContext): Promise<{\n isValid: boolean;\n errors: string[];\n }> {\n const errors: string[] = [];\n\n // Core validations are always enforced regardless of configuration\n if (!RBACSecurityValidator.validateUserId(context.userId)) {\n errors.push('Invalid user ID format');\n }\n\n // For page-level permissions, organisationId can be null (database handles it)\n // For resource-level permissions, organisationId is required\n const isPagePermission = input.permission?.includes(':page.') || !!input.pageId;\n const requiresOrgId = !isPagePermission;\n\n // OrganisationId validation - only required for resource-level permissions\n if (requiresOrgId) {\n if (!context.organisationId) {\n errors.push('Organisation ID is required for resource-level permissions');\n } else if (!RBACSecurityValidator.validateUUID(context.organisationId)) {\n errors.push('Invalid organisation ID format');\n }\n } else {\n // For page-level permissions, organisationId can be null, but if provided, it must be valid\n if (context.organisationId && !RBACSecurityValidator.validateUUID(context.organisationId)) {\n errors.push('Invalid organisation ID format');\n }\n }\n\n if (input.permission && !RBACSecurityValidator.validatePermission(input.permission)) {\n errors.push('Invalid permission format');\n }\n\n if (input.scope && !RBACSecurityValidator.validateScope(input.scope)) {\n errors.push('Invalid scope format');\n }\n\n if (this.config.enableInputValidation) {\n if (context.ipAddress && typeof context.ipAddress !== 'string') {\n errors.push('Invalid IP address format');\n }\n\n if (context.userAgent && typeof context.userAgent !== 'string') {\n errors.push('Invalid user agent format');\n }\n }\n\n // Log suspicious activity\n if (errors.length > 0) {\n RBACSecurityValidator.logSecurityEvent({\n type: 'invalid_input',\n userId: context.userId,\n details: { errors, input: this.sanitizeInput(JSON.stringify(input)) },\n });\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n };\n }\n\n /**\n * Check rate limiting\n * @param context - Security context\n * @returns Rate limit check result\n */\n async checkRateLimit(context: SecurityContext): Promise<{\n isAllowed: boolean;\n remaining: number;\n }> {\n if (!this.config.enableRateLimiting) {\n return { isAllowed: true, remaining: this.config.maxPermissionChecksPerMinute };\n }\n\n // Implementation: In-memory rate limiting with sliding window\n // For production, consider using Redis or Supabase Edge Functions\n const isAllowed = await this._checkRateLimitInternal(context.userId);\n \n const remaining = isAllowed ? this.config.maxPermissionChecksPerMinute - this._getRequestCount(context.userId) : 0;\n\n return {\n isAllowed,\n remaining: Math.max(0, remaining),\n };\n }\n\n /**\n * In-memory rate limiting cache (sliding window)\n * Note: For production, this should use Redis or Supabase Edge Functions\n */\n private rateLimitCache = new Map<UUID, Array<{ timestamp: number }>>();\n\n private async _checkRateLimitInternal(userId: UUID): Promise<boolean> {\n const now = Date.now();\n const windowMs = 60 * 1000; // 1 minute window\n\n // Get or create rate limit entries for this user\n const entries = this.rateLimitCache.get(userId) || [];\n \n // Remove entries outside the time window\n const validEntries = entries.filter(entry => now - entry.timestamp < windowMs);\n \n // Check if user exceeded rate limit\n const requestCount = validEntries.length;\n const isAllowed = requestCount < this.config.maxPermissionChecksPerMinute;\n \n // If allowed, add current request\n if (isAllowed) {\n validEntries.push({ timestamp: now });\n }\n \n // Update cache\n this.rateLimitCache.set(userId, validEntries);\n \n return isAllowed;\n }\n\n private _getRequestCount(userId: UUID): number {\n const now = Date.now();\n const windowMs = 60 * 1000; // 1 minute window\n \n const entries = this.rateLimitCache.get(userId) || [];\n const validEntries = entries.filter(entry => now - entry.timestamp < windowMs);\n \n return validEntries.length;\n }\n\n /**\n * Clear old rate limit entries to prevent memory leaks\n * Should be called periodically (e.g., every 5 minutes)\n */\n private clearExpiredEntries(): void {\n const now = Date.now();\n const windowMs = 60 * 1000; // 1 minute window\n \n for (const [userId, entries] of this.rateLimitCache.entries()) {\n const validEntries = entries.filter(entry => now - entry.timestamp < windowMs);\n \n if (validEntries.length === 0) {\n this.rateLimitCache.delete(userId);\n } else {\n this.rateLimitCache.set(userId, validEntries);\n }\n }\n }\n\n /**\n * Sanitize input data\n * @param input - Input to sanitize\n * @returns Sanitized input\n */\n private sanitizeInput(input: string): string {\n return RBACSecurityValidator.sanitizeInput(input);\n }\n}\n","/**\n * RBAC Configuration\n * @package @jmruthers/pace-core\n * @module RBAC/Config\n * @since 1.0.0\n * \n * This module provides configuration options for the RBAC system.\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../types/database';\nimport { RBACSecurityConfig } from './security';\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug';\n\nexport interface RBACConfig {\n supabase: SupabaseClient<Database>;\n debug?: boolean;\n logLevel?: LogLevel;\n developmentMode?: boolean;\n mockPermissions?: Record<string, boolean>;\n cache?: {\n ttl?: number;\n enabled?: boolean;\n sessionTtl?: number; // Session cache TTL in milliseconds (default: 5 minutes)\n };\n audit?: {\n enabled?: boolean;\n logLevel?: LogLevel;\n batched?: boolean; // Enable batched audit logging (default: true)\n batchWindow?: number; // Time window in milliseconds (default: 100ms)\n batchSize?: number; // Maximum batch size (default: 10)\n };\n security?: Partial<RBACSecurityConfig>;\n performance?: {\n enableRequestDeduplication?: boolean; // Enable request deduplication (default: true)\n enableBatchedAuditLogging?: boolean; // Enable batched audit logging (default: true)\n enablePerformanceTracking?: boolean; // Enable performance tracking (default: false in production)\n };\n}\n\nexport interface RBACLogger {\n error: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n debug: (message: string, ...args: unknown[]) => void;\n}\n\nclass RBACConfigManager {\n private config: RBACConfig | null = null;\n private logger: RBACLogger | null = null;\n\n setConfig(config: RBACConfig): void {\n this.config = config;\n this.setupLogger();\n }\n\n getConfig(): RBACConfig | null {\n return this.config;\n }\n\n getLogger(): RBACLogger {\n if (!this.logger) {\n this.logger = this.createDefaultLogger();\n }\n return this.logger;\n }\n\n private setupLogger(): void {\n if (!this.config) return;\n\n const { debug = false, logLevel = 'warn' } = this.config;\n \n this.logger = {\n error: (message: string, ...args: unknown[]) => {\n console.error(`[RBAC ERROR] ${message}`, ...args);\n },\n warn: (message: string, ...args: unknown[]) => {\n if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug') {\n console.warn(`[RBAC WARN] ${message}`, ...args);\n }\n },\n info: (message: string, ...args: unknown[]) => {\n if (logLevel === 'info' || logLevel === 'debug') {\n console.info(`[RBAC INFO] ${message}`, ...args);\n }\n },\n debug: (message: string, ...args: unknown[]) => {\n if (debug && logLevel === 'debug') {\n console.debug(`[RBAC DEBUG] ${message}`, ...args);\n }\n },\n };\n }\n\n private createDefaultLogger(): RBACLogger {\n return {\n error: (message: string, ...args: unknown[]) => console.error(`[RBAC ERROR] ${message}`, ...args),\n warn: (message: string, ...args: unknown[]) => console.warn(`[RBAC WARN] ${message}`, ...args),\n info: (message: string, ...args: unknown[]) => console.info(`[RBAC INFO] ${message}`, ...args),\n debug: (message: string, ...args: unknown[]) => console.debug(`[RBAC DEBUG] ${message}`, ...args),\n };\n }\n\n isDebugMode(): boolean {\n return this.config?.debug ?? false;\n }\n\n isDevelopmentMode(): boolean {\n return this.config?.developmentMode ?? false;\n }\n\n getMockPermissions(): Record<string, boolean> | null {\n return this.config?.mockPermissions ?? null;\n }\n}\n\n// Global config manager instance\nconst configManager = new RBACConfigManager();\n\nexport function createRBACConfig(config: RBACConfig): RBACConfig {\n configManager.setConfig(config);\n return config;\n}\n\nexport function getRBACConfig(): RBACConfig | null {\n return configManager.getConfig();\n}\n\nexport function getRBACLogger(): RBACLogger {\n return configManager.getLogger();\n}\n\nexport function isDebugMode(): boolean {\n return configManager.isDebugMode();\n}\n\nexport function isDevelopmentMode(): boolean {\n return configManager.isDevelopmentMode();\n}\n\nexport function getMockPermissions(): Record<string, boolean> | null {\n return configManager.getMockPermissions();\n}\n","/**\n * RBAC Core Engine - Simplified Version\n * @package @jmruthers/pace-core\n * @module RBAC/Engine\n * @since 2.0.0\n * \n * This is a drastically simplified version that delegates permission checking to a single RPC function.\n * All the complex grant collection logic has been moved to the database for better performance and security.\n * \n * BREAKING CHANGES FROM v1:\n * - No more client-side grant collection\n * - No more complex permission resolution algorithm\n * - Single RPC call for all permission checks\n * - Caching is still supported for performance\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../types/database';\nimport {\n UUID,\n Permission,\n Scope,\n PermissionCheck,\n AccessLevel,\n PermissionMap,\n Operation,\n RBACAppContext,\n RBACRoleContext,\n RBACPermission,\n} from './types';\nimport { rbacCache, RBACCache } from './cache';\nimport { emitAuditEvent } from './audit';\nimport { initializeCacheInvalidation } from './cache-invalidation';\nimport { categorizeError, mapErrorCategoryToSecurityEventType } from './errors';\nimport { \n RBACSecurityValidator, \n RBACSecurityMiddleware, \n SecurityContext,\n DEFAULT_SECURITY_CONFIG,\n RBACSecurityConfig\n} from './security';\nimport { getRBACLogger } from './config';\n\n/**\n * Simplified RBAC Engine\n * \n * Delegates all permission checks to the database via a single RPC function.\n * This reduces complexity, improves performance, and enhances security.\n */\nexport class RBACEngine {\n private supabase: SupabaseClient<Database>;\n private securityMiddleware: RBACSecurityMiddleware;\n\n constructor(supabase: SupabaseClient<Database>, securityConfig?: Partial<RBACSecurityConfig>) {\n this.supabase = supabase;\n // Merge provided security config with defaults\n const mergedSecurityConfig: RBACSecurityConfig = {\n ...DEFAULT_SECURITY_CONFIG,\n ...securityConfig,\n };\n this.securityMiddleware = new RBACSecurityMiddleware(mergedSecurityConfig);\n \n // Initialize cache invalidation for automatic cache clearing\n initializeCacheInvalidation(supabase);\n }\n\n /**\n * Check if a user has a specific permission\n * \n * This method now delegates to the database RPC function for all the heavy lifting.\n * \n * @param input - Permission check input\n * @param securityContext - Security context for validation (required)\n * @returns Promise resolving to permission result\n */\n async isPermitted(input: PermissionCheck, securityContext: SecurityContext): Promise<boolean> {\n const startTime = Date.now();\n const { userId, permission, scope, pageId } = input;\n \n // Track cache usage for audit\n let cacheHit = false;\n let cacheSource: 'memory' | 'rpc' = 'rpc';\n\n try {\n // ========================================================================\n // STEP 1: Security Validation & Rate Limiting (MANDATORY)\n // ========================================================================\n \n // Validate input\n const validation = await this.securityMiddleware.validateInput(input, securityContext);\n if (!validation.isValid) {\n RBACSecurityValidator.logSecurityEvent({\n type: 'invalid_input',\n userId,\n details: { errors: validation.errors, input: JSON.stringify(input) },\n });\n return false;\n }\n\n // Check rate limits\n const rateLimit = await this.securityMiddleware.checkRateLimit(securityContext);\n if (!rateLimit.isAllowed) {\n RBACSecurityValidator.logSecurityEvent({\n type: 'rate_limit_exceeded',\n userId,\n details: { remaining: rateLimit.remaining },\n });\n return false;\n }\n\n // Validate user ID format\n if (!RBACSecurityValidator.validateUserId(userId)) {\n RBACSecurityValidator.logSecurityEvent({\n type: 'invalid_input',\n userId,\n details: { error: 'Invalid user ID format' },\n });\n return false;\n }\n\n // Validate permission format\n if (!RBACSecurityValidator.validatePermission(permission)) {\n RBACSecurityValidator.logSecurityEvent({\n type: 'invalid_input',\n userId,\n details: { error: 'Invalid permission format', permission },\n });\n return false;\n }\n\n // Validate scope format\n if (!RBACSecurityValidator.validateScope(scope)) {\n RBACSecurityValidator.logSecurityEvent({\n type: 'invalid_input',\n userId,\n details: { error: 'Invalid scope format', scope },\n });\n return false;\n }\n\n // ========================================================================\n // STEP 2: Check Cache (OPTIONAL - for performance)\n // ========================================================================\n \n const cacheKey = RBACCache.generateKey(\n userId,\n permission,\n scope.organisationId,\n scope.eventId,\n scope.appId,\n pageId\n );\n \n const cached = rbacCache.get<boolean>(cacheKey);\n if (cached !== null) {\n cacheHit = true;\n cacheSource = 'memory';\n \n // Skip audit logging for cached checks (only log on cache miss)\n // This significantly reduces network requests\n return cached;\n }\n\n // ========================================================================\n // STEP 3: Call Simplified RPC Function (SINGLE DATABASE CALL)\n // ========================================================================\n \n // This single RPC call replaces hundreds of lines of complex client-side logic:\n // - No more super admin checks here (RPC handles it)\n // - No more grant collection (RPC handles it)\n // - No more permission matching (RPC handles it)\n // - No more deny-override-allow logic (RPC handles it)\n \n const { data, error } = await (this.supabase as any).rpc('rbac_check_permission_simplified', {\n p_user_id: userId,\n p_permission: permission,\n p_organisation_id: scope.organisationId || undefined,\n p_event_id: scope.eventId || undefined,\n p_app_id: scope.appId || undefined,\n p_page_id: pageId || undefined,\n });\n\n if (error) {\n const logger = getRBACLogger();\n logger.error('RPC error:', error);\n\n const category = categorizeError(error);\n const eventType = mapErrorCategoryToSecurityEventType(category);\n const errorDetails = error as { message?: string; code?: string; hint?: string; details?: string };\n\n RBACSecurityValidator.logSecurityEvent({\n type: eventType,\n userId,\n details: {\n error: errorDetails?.message || 'RPC call failed',\n code: errorDetails?.code,\n hint: errorDetails?.hint,\n details: errorDetails?.details,\n permission,\n scope: JSON.stringify(scope),\n category,\n },\n });\n\n // Fail securely - deny on error\n return false;\n }\n\n const hasPermission = data === true;\n \n // ========================================================================\n // STEP 4: Cache Result & Audit (COMPLETION)\n // ========================================================================\n \n // Cache the result for 60 seconds\n rbacCache.set(cacheKey, hasPermission, 60000);\n \n const duration = Date.now() - startTime;\n \n // Emit audit event (if organisation context exists)\n if (scope.organisationId) {\n const resolvedPageId = await this.resolvePageId(pageId, scope.appId);\n await emitAuditEvent({\n type: hasPermission ? 'permission_check' : 'permission_denied',\n userId,\n organisationId: scope.organisationId,\n eventId: scope.eventId,\n appId: scope.appId,\n pageId: resolvedPageId,\n permission,\n decision: hasPermission,\n source: 'api',\n duration_ms: duration,\n cache_hit: cacheHit,\n cache_source: cacheSource,\n });\n }\n\n return hasPermission;\n \n } catch (error) {\n const category = categorizeError(error);\n const eventType = mapErrorCategoryToSecurityEventType(category);\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n\n RBACSecurityValidator.logSecurityEvent({\n type: eventType,\n userId,\n details: {\n error: errorMessage,\n permission,\n scope: JSON.stringify(scope),\n category,\n },\n });\n\n // Fail securely - deny access on error\n const logger = getRBACLogger();\n logger.error('Permission check failed:', error);\n return false;\n }\n }\n\n /**\n * Get user's access level in a scope\n * \n * This is derived from roles, not permissions.\n * \n * @param input - Access level input\n * @returns Promise resolving to access level\n */\n async getAccessLevel(input: { userId: UUID; scope: Scope }): Promise<AccessLevel> {\n const { userId, scope } = input;\n\n // Check cache first\n const cacheKey = RBACCache.generateAccessLevelKey(\n userId,\n scope.organisationId || '',\n scope.eventId,\n scope.appId\n );\n \n const cached = rbacCache.get<AccessLevel>(cacheKey);\n if (cached) {\n return cached;\n }\n\n const now = new Date().toISOString();\n\n // Check super admin\n const isSuperAdmin = await this.checkSuperAdmin(userId);\n if (isSuperAdmin) {\n rbacCache.set(cacheKey, 'super', 60000);\n return 'super';\n }\n\n // Check organisation role\n if (scope.organisationId) {\n const { data: orgRoles } = await this.supabase\n .from('rbac_organisation_roles')\n .select('role')\n .eq('user_id', userId)\n .eq('organisation_id', scope.organisationId)\n .eq('status', 'active')\n .is('revoked_at', null)\n .lte('valid_from', now)\n .or(`valid_to.is.null,valid_to.gte.${now}`)\n .limit(1) as { data: Array<{ role: string }> | null; error: any };\n\n const orgRole = orgRoles?.[0];\n\n if (orgRole?.role === 'org_admin') {\n rbacCache.set(cacheKey, 'admin', 60000);\n return 'admin';\n }\n }\n\n // Check event-app role\n if (scope.eventId && scope.appId) {\n const { data: eventRole } = await this.supabase\n .from('rbac_event_app_roles')\n .select('role')\n .eq('user_id', userId)\n .eq('event_id', scope.eventId)\n .eq('app_id', scope.appId)\n .eq('status', 'active')\n .lte('valid_from', now)\n .or(`valid_to.is.null,valid_to.gte.${now}`)\n .single() as { data: { role: string } | null; error: any };\n\n if (eventRole?.role === 'event_admin') {\n rbacCache.set(cacheKey, 'admin', 60000);\n return 'admin';\n }\n if (eventRole?.role === 'planner') {\n rbacCache.set(cacheKey, 'planner', 60000);\n return 'planner';\n }\n if (eventRole?.role === 'participant') {\n rbacCache.set(cacheKey, 'participant', 60000);\n return 'participant';\n }\n }\n\n // Default to viewer\n rbacCache.set(cacheKey, 'viewer', 60000);\n return 'viewer';\n }\n\n /**\n * Get user's permission map for a scope\n * \n * This builds a map of page IDs to allowed operations.\n * Uses the simplified RPC for each permission check.\n * \n * @param input - Permission map input\n * @returns Promise resolving to permission map\n */\n async getPermissionMap(input: { userId: UUID; scope: Scope }): Promise<PermissionMap> {\n const { userId, scope } = input;\n\n // Generate cache key early so it's available for super admin caching\n const cacheKey = RBACCache.generatePermissionMapKey(\n userId,\n scope.organisationId || '',\n scope.eventId,\n scope.appId\n );\n\n // Check super admin first - super admins have all permissions\n const isSuperAdmin = await this.checkSuperAdmin(userId);\n if (isSuperAdmin) {\n const wildcardMap: PermissionMap = { '*': true };\n rbacCache.set(cacheKey, wildcardMap, 60000);\n return wildcardMap;\n }\n\n // Validate scope\n if (!scope.organisationId) {\n return {}; // No permissions without valid context\n }\n\n // Check cache first\n \n const cached = rbacCache.get<PermissionMap>(cacheKey);\n if (cached) {\n return cached;\n }\n\n const permissionMap: PermissionMap = {};\n\n // Get all pages for the app\n if (scope.appId) {\n const { data: pages } = await this.supabase\n .from('rbac_app_pages')\n .select('id, page_name')\n .eq('app_id', scope.appId) as { data: Array<{ id: string; page_name: string }> | null };\n\n if (pages) {\n // OrganisationId is required for permission checks\n if (!scope.organisationId) {\n // Return empty permission map if no organisation context\n rbacCache.set(cacheKey, permissionMap, 60000);\n return permissionMap;\n }\n\n // Create a security context for permission checks\n const securityContext: SecurityContext = {\n userId,\n organisationId: scope.organisationId, // Required\n timestamp: new Date(),\n };\n\n for (const page of pages) {\n // Check each CRUD operation\n // Permission format: {operation}:page.{pageName} (e.g., read:page.meals)\n for (const operation of ['read', 'create', 'update', 'delete'] as Operation[]) {\n const permissionString = `${operation}:page.${page.page_name}`;\n const hasPermission = await this.isPermitted(\n {\n userId,\n scope,\n permission: permissionString as Permission,\n pageId: page.id,\n },\n securityContext\n );\n\n const permissionKey = permissionString as Permission;\n permissionMap[permissionKey] = hasPermission;\n }\n }\n }\n }\n\n rbacCache.set(cacheKey, permissionMap, 60000);\n return permissionMap;\n }\n\n async resolveAppContext(input: { userId: UUID; appName: string }): Promise<RBACAppContext | null> {\n try {\n const { userId, appName } = input;\n const { data, error } = await (this.supabase as any).rpc('util_app_resolve', {\n p_user_id: userId,\n p_app_name: appName,\n });\n\n if (error) {\n const logger = getRBACLogger();\n logger.error('Failed to resolve app context:', error);\n return null;\n }\n\n if (!data || data.length === 0) {\n return null;\n }\n\n const appData = data[0] as { app_id: UUID; has_access: boolean };\n if (!appData?.app_id) {\n return null;\n }\n\n return {\n appId: appData.app_id,\n hasAccess: appData.has_access !== false,\n };\n } catch (error) {\n const logger = getRBACLogger();\n logger.error('Unexpected error resolving app context:', error);\n return null;\n }\n }\n\n async getRoleContext(input: { userId: UUID; scope: Scope }): Promise<RBACRoleContext> {\n const result: RBACRoleContext = {\n globalRole: null,\n organisationRole: null,\n eventAppRole: null,\n };\n\n try {\n const { userId, scope } = input;\n // Call unified function (tech debt removed: consolidated from 2 overloaded versions)\n const { data, error } = await (this.supabase as any).rpc('rbac_permissions_get', {\n p_user_id: userId,\n p_organisation_id: scope.organisationId || null,\n p_event_id: scope.eventId || null,\n p_app_id: scope.appId || null,\n p_page_id: null, // Optional: can filter to specific page if needed\n });\n\n if (error) {\n const logger = getRBACLogger();\n logger.error('Failed to load role context:', error);\n return result;\n }\n\n if (!Array.isArray(data)) {\n return result;\n }\n\n for (const permission of data as RBACPermission[]) {\n if (permission.permission_type === 'all_permissions') {\n result.globalRole = 'super_admin';\n }\n\n if (permission.permission_type === 'organisation_access') {\n result.organisationRole = permission.role_name as any;\n }\n\n if (permission.permission_type === 'event_app_access') {\n result.eventAppRole = permission.role_name as any;\n }\n }\n\n return result;\n } catch (error) {\n const logger = getRBACLogger();\n logger.error('Unexpected error loading role context:', error);\n return result;\n }\n }\n\n /**\n * Check if user is super admin\n * \n * @param userId - User ID\n * @returns Promise resolving to super admin status\n */\n private async checkSuperAdmin(userId: UUID): Promise<boolean> {\n // Check cache first\n const cacheKey = `super_admin:${userId}`;\n const cached = rbacCache.get<boolean>(cacheKey);\n if (cached !== null) {\n return cached;\n }\n\n const startTime = Date.now();\n const now = new Date().toISOString();\n \n try {\n const { data, error } = await this.supabase\n .from('rbac_global_roles')\n .select('role')\n .eq('user_id', userId)\n .eq('role', 'super_admin')\n .lte('valid_from', now)\n .or(`valid_to.is.null,valid_to.gte.${now}`)\n .limit(1) as { data: Array<{ role: string }> | null; error: any };\n\n const elapsed = Date.now() - startTime;\n \n // Log warning if query takes too long\n if (elapsed > 2000) {\n console.warn('[RBACEngine] Super admin check took longer than expected', {\n userId,\n elapsedMs: elapsed,\n error: error?.message,\n });\n }\n\n const isSuperAdmin = !error && data && data.length > 0;\n \n // Cache for 60 seconds\n rbacCache.set(cacheKey, isSuperAdmin, 60000);\n \n return Boolean(isSuperAdmin);\n } catch (err) {\n const elapsed = Date.now() - startTime;\n console.error('[RBACEngine] Error checking super admin', {\n userId,\n error: err,\n elapsedMs: elapsed,\n });\n // Return false on error (fail secure)\n return false;\n }\n }\n\n /**\n * Resolve a page ID to UUID if it's a page name\n * \n * @param pageId - Page ID (UUID) or page name (string)\n * @param appId - App ID to look up the page\n * @returns Resolved page ID (UUID) or original pageId\n */\n private async resolvePageId(pageId?: UUID | string, appId?: UUID): Promise<UUID | string | undefined> {\n if (!pageId) {\n return undefined;\n }\n\n // Check if it's already a UUID\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (uuidRegex.test(pageId)) {\n return pageId as UUID;\n }\n\n // It's a page name, but we need appId to resolve it\n if (!appId) {\n return pageId;\n }\n\n // Resolve page name to UUID\n try {\n // Use maybeSingle() instead of single() to avoid 406 errors when page doesn't exist\n // This handles the case where the page might not exist gracefully\n const { data: page, error: pageError } = await this.supabase\n .from('rbac_app_pages')\n .select('id')\n .eq('app_id', appId)\n .eq('page_name', pageId)\n .maybeSingle() as { data: { id: UUID } | null; error: any };\n \n // If there's an error (including 406 Not Acceptable), log it but don't throw\n if (pageError) {\n const logger = getRBACLogger();\n // Only log if it's not a \"not found\" error (PGRST116)\n if (pageError.code !== 'PGRST116') {\n logger.warn('Failed to resolve page name to UUID:', { pageId, appId, error: pageError });\n }\n return pageId;\n }\n \n return page?.id || pageId;\n } catch (error) {\n const logger = getRBACLogger();\n logger.warn('Failed to resolve page name to UUID:', { pageId, appId, error });\n return pageId;\n }\n }\n}\n\n/**\n * Create an RBAC engine instance\n * \n * @param supabase - Supabase client\n * @param securityConfig - Optional security configuration\n * @returns RBACEngine instance\n */\nexport function createRBACEngine(\n supabase: SupabaseClient<Database>,\n securityConfig?: Partial<RBACSecurityConfig>\n): RBACEngine {\n return new RBACEngine(supabase, securityConfig);\n}\n\n","/**\n * Performance Monitoring for RBAC\n * @package @jmruthers/pace-core\n * @module RBAC/Performance\n * @since 2.0.0\n * \n * This module provides performance monitoring and metrics tracking for RBAC operations.\n */\n\nexport interface RBACPerformanceMetrics {\n /** Total number of permission checks */\n totalChecks: number;\n /** Number of cache hits */\n cacheHits: number;\n /** Number of cache misses */\n cacheMisses: number;\n /** Cache hit rate (0-1) */\n cacheHitRate: number;\n /** Number of deduplicated requests */\n deduplicatedRequests: number;\n /** Total number of network requests made */\n networkRequests: number;\n /** Average response time in milliseconds */\n averageResponseTime: number;\n /** Total response time in milliseconds */\n totalResponseTime: number;\n /** Number of batched audit events */\n batchedAuditEvents: number;\n /** Number of individual audit events */\n individualAuditEvents: number;\n}\n\nclass RBACPerformanceMonitor {\n private metrics: RBACPerformanceMetrics = {\n totalChecks: 0,\n cacheHits: 0,\n cacheMisses: 0,\n cacheHitRate: 0,\n deduplicatedRequests: 0,\n networkRequests: 0,\n averageResponseTime: 0,\n totalResponseTime: 0,\n batchedAuditEvents: 0,\n individualAuditEvents: 0,\n };\n\n private enabled: boolean = false;\n\n /**\n * Enable or disable performance monitoring\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /**\n * Check if performance monitoring is enabled\n */\n isEnabled(): boolean {\n return this.enabled;\n }\n\n /**\n * Record a permission check\n */\n recordCheck(cacheHit: boolean, responseTime: number, wasDeduplicated: boolean = false): void {\n if (!this.enabled) {\n return;\n }\n\n this.metrics.totalChecks++;\n \n if (cacheHit) {\n this.metrics.cacheHits++;\n } else {\n this.metrics.cacheMisses++;\n this.metrics.networkRequests++;\n }\n\n if (wasDeduplicated) {\n this.metrics.deduplicatedRequests++;\n }\n\n this.metrics.totalResponseTime += responseTime;\n this.metrics.averageResponseTime = this.metrics.totalResponseTime / this.metrics.totalChecks;\n this.metrics.cacheHitRate = this.metrics.cacheHits / this.metrics.totalChecks;\n }\n\n /**\n * Record an audit event\n */\n recordAuditEvent(batched: boolean): void {\n if (!this.enabled) {\n return;\n }\n\n if (batched) {\n this.metrics.batchedAuditEvents++;\n } else {\n this.metrics.individualAuditEvents++;\n }\n }\n\n /**\n * Get current metrics\n */\n getMetrics(): RBACPerformanceMetrics {\n return { ...this.metrics };\n }\n\n /**\n * Reset all metrics\n */\n reset(): void {\n this.metrics = {\n totalChecks: 0,\n cacheHits: 0,\n cacheMisses: 0,\n cacheHitRate: 0,\n deduplicatedRequests: 0,\n networkRequests: 0,\n averageResponseTime: 0,\n totalResponseTime: 0,\n batchedAuditEvents: 0,\n individualAuditEvents: 0,\n };\n }\n\n /**\n * Get metrics summary as a formatted string\n */\n getSummary(): string {\n const m = this.metrics;\n return `\nRBAC Performance Metrics:\n Total Checks: ${m.totalChecks}\n Cache Hits: ${m.cacheHits} (${(m.cacheHitRate * 100).toFixed(1)}%)\n Cache Misses: ${m.cacheMisses}\n Deduplicated Requests: ${m.deduplicatedRequests}\n Network Requests: ${m.networkRequests}\n Average Response Time: ${m.averageResponseTime.toFixed(2)}ms\n Batched Audit Events: ${m.batchedAuditEvents}\n Individual Audit Events: ${m.individualAuditEvents}\n`;\n }\n}\n\n// Global performance monitor instance\nconst performanceMonitor = new RBACPerformanceMonitor();\n\n/**\n * Enable performance monitoring\n */\nexport function enablePerformanceMonitoring(): void {\n performanceMonitor.setEnabled(true);\n}\n\n/**\n * Disable performance monitoring\n */\nexport function disablePerformanceMonitoring(): void {\n performanceMonitor.setEnabled(false);\n}\n\n/**\n * Check if performance monitoring is enabled\n */\nexport function isPerformanceMonitoringEnabled(): boolean {\n return performanceMonitor.isEnabled();\n}\n\n/**\n * Record a permission check\n */\nexport function recordPermissionCheck(\n cacheHit: boolean,\n responseTime: number,\n wasDeduplicated: boolean = false\n): void {\n performanceMonitor.recordCheck(cacheHit, responseTime, wasDeduplicated);\n}\n\n/**\n * Record an audit event\n */\nexport function recordAuditEvent(batched: boolean): void {\n performanceMonitor.recordAuditEvent(batched);\n}\n\n/**\n * Get current performance metrics\n */\nexport function getPerformanceMetrics(): RBACPerformanceMetrics {\n return performanceMonitor.getMetrics();\n}\n\n/**\n * Reset performance metrics\n */\nexport function resetPerformanceMetrics(): void {\n performanceMonitor.reset();\n}\n\n/**\n * Get performance metrics summary\n */\nexport function getPerformanceSummary(): string {\n return performanceMonitor.getSummary();\n}\n\n","/**\n * Request Deduplication for RBAC Permission Checks\n * @package @jmruthers/pace-core\n * @module RBAC/RequestDeduplication\n * @since 2.0.0\n * \n * This module provides request deduplication to prevent multiple identical\n * permission checks from being made simultaneously. When multiple components\n * request the same permission at the same time, they share the same promise.\n */\n\nimport { PermissionCheck } from './types';\nimport { RBACCache } from './cache';\n\n/**\n * Map of in-flight permission check requests\n * Key: cache key string, Value: Promise<boolean>\n */\nconst inFlightRequests = new Map<string, Promise<boolean>>();\n\n/**\n * Generate a deduplication key from permission check input\n * \n * @param input - Permission check input\n * @returns Deduplication key string\n */\nfunction generateDeduplicationKey(input: PermissionCheck): string {\n return RBACCache.generatePermissionKey({\n userId: input.userId,\n organisationId: input.scope.organisationId, // Can be undefined for page-level permissions\n eventId: input.scope.eventId,\n appId: input.scope.appId,\n permission: input.permission,\n pageId: input.pageId,\n });\n}\n\n/**\n * Get or create a deduplicated permission check request\n * \n * If a request for the same permission is already in-flight, returns the existing promise.\n * Otherwise, creates a new request and tracks it.\n * \n * @param input - Permission check input\n * @param checkFn - Function to perform the actual permission check\n * @returns Promise resolving to permission result\n */\nexport async function getOrCreateRequest(\n input: PermissionCheck,\n checkFn: (input: PermissionCheck) => Promise<boolean>\n): Promise<boolean> {\n const key = generateDeduplicationKey(input);\n \n // Check if request is already in-flight\n const existingRequest = inFlightRequests.get(key);\n if (existingRequest) {\n return existingRequest;\n }\n \n // Create new request\n const requestPromise = checkFn(input).finally(() => {\n // Clean up when request completes (success or failure)\n inFlightRequests.delete(key);\n });\n \n // Track the request\n inFlightRequests.set(key, requestPromise);\n \n return requestPromise;\n}\n\n/**\n * Clear all in-flight requests (useful for testing or cleanup)\n */\nexport function clearInFlightRequests(): void {\n inFlightRequests.clear();\n}\n\n/**\n * Get count of in-flight requests (useful for monitoring)\n * \n * @returns Number of in-flight requests\n */\nexport function getInFlightRequestCount(): number {\n return inFlightRequests.size;\n}\n\n","/**\n * RBAC Main API Functions\n * @package @jmruthers/pace-core\n * @module RBAC/API\n * @since 1.0.0\n * \n * This module provides the main API functions for the RBAC system.\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../types/database';\nimport {\n UUID,\n Scope,\n Permission,\n AccessLevel,\n PermissionMap,\n PermissionCheck,\n RBACNotInitializedError,\n OrganisationContextRequiredError,\n RBACAppContext,\n RBACRoleContext,\n} from './types';\nimport { createRBACEngine, RBACEngine } from './engine';\nimport { createAuditManager, setGlobalAuditManager } from './audit';\nimport { rbacCache, RBACCache, CACHE_PATTERNS } from './cache';\nimport { createRBACConfig, RBACConfig, getRBACLogger } from './config';\nimport { SecurityContext } from './security';\nimport { createLogger } from '../utils/core/logger';\nimport { enablePerformanceMonitoring } from './performance';\nimport { getOrCreateRequest } from './request-deduplication';\nimport { ContextValidator } from './utils/contextValidator';\n\nconst log = createLogger('RBACAPI');\n\n// Global engine instance\nlet globalEngine: RBACEngine | null = null;\n\n/**\n * Setup RBAC system\n * \n * @param supabase - Supabase client\n * @param config - Optional configuration\n */\nexport function setupRBAC(supabase: SupabaseClient<Database>, config?: Partial<RBACConfig>): void {\n const logger = getRBACLogger();\n \n // Create full config\n const isDevelopment = import.meta.env.MODE === 'development';\n const fullConfig: RBACConfig = {\n supabase,\n debug: isDevelopment,\n logLevel: 'warn',\n developmentMode: isDevelopment,\n ...config,\n };\n \n createRBACConfig(fullConfig);\n \n // Automatically disable rate limiting in development mode unless explicitly overridden\n // Apply default first, then let explicit config override it\n // Pass undefined if no config provided and not in development, otherwise create security config\n const securityConfig = \n config === undefined && !isDevelopment\n ? undefined\n : {\n // Default: disable rate limiting in development\n ...(isDevelopment && config?.security?.enableRateLimiting === undefined\n ? { enableRateLimiting: false }\n : {}),\n // Explicit config overrides defaults\n ...config?.security,\n };\n \n // Pass security config to engine\n globalEngine = createRBACEngine(supabase, securityConfig);\n \n // Setup audit manager with batching configuration\n const useBatchedAudit = config?.audit?.batched !== false && (config?.performance?.enableBatchedAuditLogging !== false);\n const batchConfig = useBatchedAudit ? {\n batchWindow: config?.audit?.batchWindow,\n batchSize: config?.audit?.batchSize,\n } : undefined;\n const auditManager = createAuditManager(supabase, useBatchedAudit, batchConfig);\n setGlobalAuditManager(auditManager);\n \n // Setup performance monitoring if enabled\n if (config?.performance?.enablePerformanceTracking) {\n enablePerformanceMonitoring();\n }\n \n}\n\n/**\n * Check if RBAC system is initialized\n * \n * @returns True if RBAC is initialized\n */\nexport function isRBACInitialized(): boolean {\n return globalEngine !== null;\n}\n\n/**\n * Get the global RBAC engine\n * \n * @returns Global RBAC engine\n * @throws Error if RBAC not initialized\n */\nfunction getEngine(): RBACEngine {\n if (!globalEngine) {\n throw new RBACNotInitializedError();\n }\n return globalEngine;\n}\n\n/**\n * Get user's access level in a scope\n * \n * @param input - Access level input\n * @param appConfig - Optional app configuration\n * @param appName - Optional app name\n * @returns Promise resolving to access level\n * \n * @example\n * ```typescript\n * const accessLevel = await getAccessLevel({\n * userId: 'user-123',\n * scope: { organisationId: 'org-456' }\n * });\n * ```\n */\nexport async function getAccessLevel(\n input: {\n userId: UUID;\n scope: Scope;\n },\n appName?: string\n): Promise<AccessLevel> {\n try {\n const engine = getEngine();\n \n // Check super admin status first - super admins bypass context requirements\n const isSuperAdminUser = await engine['checkSuperAdmin'](input.userId);\n if (isSuperAdminUser) {\n return 'super';\n }\n \n // For functions without pageId, default to organisation scope validation\n // This is a safe default - most operations require organisation context\n const validation = await ContextValidator.resolveScopeForPage(\n input.scope,\n 'organisation', // Default to organisation scope when no page context\n appName,\n engine['supabase']\n );\n \n if (!validation.isValid || !validation.resolvedScope) {\n throw validation.error || new OrganisationContextRequiredError();\n }\n \n // Use resolved scope\n return engine.getAccessLevel({\n ...input,\n scope: validation.resolvedScope\n });\n } catch (error) {\n // Re-throw the error - this is an API function that should propagate errors\n throw error;\n }\n}\n\n/**\n * Get user's permission map for a scope\n * \n * @param input - Permission map input\n * @param appConfig - Optional app configuration\n * @param appName - Optional app name\n * @returns Promise resolving to permission map\n * \n * @example\n * ```typescript\n * const permissions = await getPermissionMap({\n * userId: 'user-123',\n * scope: { \n * organisationId: 'org-456',\n * eventId: 'event-789',\n * appId: 'app-101'\n * }\n * });\n * ```\n */\nexport async function getPermissionMap(\n input: {\n userId: UUID;\n scope: Scope;\n },\n appName?: string\n): Promise<PermissionMap> {\n try {\n const engine = getEngine();\n \n // For functions without pageId, default to organisation scope validation\n // This is a safe default - most operations require organisation context\n const validation = await ContextValidator.resolveScopeForPage(\n input.scope,\n 'organisation', // Default to organisation scope when no page context\n appName,\n engine['supabase']\n );\n \n if (!validation.isValid || !validation.resolvedScope) {\n throw validation.error || new OrganisationContextRequiredError();\n }\n \n // Use resolved scope\n return engine.getPermissionMap({\n ...input,\n scope: validation.resolvedScope\n });\n } catch (error) {\n // Re-throw the error - this is an API function that should propagate errors\n throw error;\n }\n}\n\nexport async function resolveAppContext(input: {\n userId: UUID;\n appName: string;\n}): Promise<RBACAppContext | null> {\n try {\n const engine = getEngine();\n return await engine.resolveAppContext(input);\n } catch (error) {\n // Re-throw the error - this is an API function that should propagate errors\n throw error;\n }\n}\n\nexport async function getRoleContext(\n input: {\n userId: UUID;\n scope: Scope;\n },\n appName?: string\n): Promise<RBACRoleContext> {\n const engine = getEngine();\n \n // For functions without pageId, default to organisation scope validation\n const validation = await ContextValidator.resolveScopeForPage(\n input.scope,\n 'organisation', // Default to organisation scope when no page context\n appName,\n engine['supabase']\n );\n \n if (!validation.isValid || !validation.resolvedScope) {\n throw validation.error || new OrganisationContextRequiredError();\n }\n \n // Use resolved scope\n return engine.getRoleContext({\n ...input,\n scope: validation.resolvedScope\n });\n}\n\n/**\n * Check if user has a specific permission\n * \n * @param input - Permission check input\n * @param appConfig - Optional app configuration (if not provided, will be fetched)\n * @param appName - Optional app name (for PORTAL/ADMIN special case and config lookup)\n * @returns Promise resolving to permission result\n * \n * @example\n * ```typescript\n * const canManage = await isPermitted({\n * userId: 'user-123',\n * scope: { organisationId: 'org-456' },\n * permission: 'update:events',\n * pageId: 'page-789'\n * });\n * ```\n */\nexport async function isPermitted(\n input: PermissionCheck,\n appName?: string,\n /**\n * Pre-computed super admin status to avoid duplicate checks.\n * Pass null if not checked yet (will check), true if already checked and is super admin,\n * or false if already checked and is not super admin.\n * @default null\n */\n precomputedSuperAdmin: boolean | null = null\n): Promise<boolean> {\n const engine = getEngine();\n \n // Check super admin status first - super admins bypass context requirements\n // Super admins have access to all permissions regardless of organisation context\n // PERFORMANCE: Use precomputed value if provided to avoid duplicate checks\n if (precomputedSuperAdmin === true) {\n return true;\n }\n \n // If null, check super admin status\n if (precomputedSuperAdmin === null) {\n const isSuperAdminUser = await engine['checkSuperAdmin'](input.userId);\n if (isSuperAdminUser) {\n return true;\n }\n }\n // If precomputedSuperAdmin === false, skip check and proceed with permission check\n \n // Get app name if not provided (for PORTAL/ADMIN special case)\n let resolvedAppName = appName;\n if (!resolvedAppName && input.scope.appId) {\n try {\n const { data } = await engine['supabase']\n .from('rbac_apps')\n .select('name')\n .eq('id', input.scope.appId)\n .eq('is_active', true)\n .single() as { data: { name: string } | null };\n if (data) {\n resolvedAppName = data.name;\n }\n } catch (err) {\n // Ignore errors - appName is optional\n }\n }\n \n // Get page scope type (required for all permission checks)\n // All pages must have scope_type set - this is the single source of truth\n let pageScopeType: 'event' | 'organisation' | 'both';\n if (input.pageId) {\n try {\n const scopeType = await getPageScopeType(\n input.pageId,\n input.scope.appId,\n resolvedAppName\n );\n if (!scopeType) {\n throw new Error(`Page ${input.pageId} does not have scope_type set`);\n }\n pageScopeType = scopeType;\n } catch (err) {\n log.error('Failed to get page scope type:', err);\n throw new Error(`Failed to determine page scope type: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else {\n // No pageId provided - default to organisation scope\n // This should rarely happen, but provides a safe fallback\n pageScopeType = 'organisation';\n }\n \n // Validate context using page-level scope (single source of truth)\n const validation = await ContextValidator.resolveScopeForPage(\n input.scope,\n pageScopeType,\n resolvedAppName,\n engine['supabase']\n );\n \n if (!validation.isValid || !validation.resolvedScope) {\n throw validation.error || new OrganisationContextRequiredError();\n }\n \n // Use resolved scope for permission check\n const validatedScope = validation.resolvedScope;\n \n // Handle 'both' scope pages - check both scopes and return union\n if (pageScopeType === 'both' && input.pageId) {\n // Check permission with event scope\n const eventScope: Scope = {\n organisationId: validatedScope.organisationId, // Org derived from event\n eventId: validatedScope.eventId,\n appId: validatedScope.appId\n };\n \n // Check permission with organisation scope (if org is available separately)\n // For 'both' pages, we check the permission in both contexts and return the union\n // Higher permission wins (true > false, admin > user, etc.)\n \n const eventSecurityContext: SecurityContext = {\n userId: input.userId,\n organisationId: eventScope.organisationId || null,\n timestamp: new Date(),\n };\n \n const eventInput: PermissionCheck = {\n ...input,\n scope: eventScope\n };\n \n const hasEventPermission = await engine.isPermitted(eventInput, eventSecurityContext);\n \n // Also check with organisation scope if we have a separate org context\n // (This handles cases where user has org permissions separate from event)\n if (validatedScope.organisationId && validatedScope.eventId) {\n const orgScope: Scope = {\n organisationId: validatedScope.organisationId,\n eventId: undefined, // Clear event for org-only check\n appId: validatedScope.appId\n };\n \n const orgSecurityContext: SecurityContext = {\n userId: input.userId,\n organisationId: orgScope.organisationId || null,\n timestamp: new Date(),\n };\n \n const orgInput: PermissionCheck = {\n ...input,\n scope: orgScope\n };\n \n const hasOrgPermission = await engine.isPermitted(orgInput, orgSecurityContext);\n \n // Return union (true if either scope grants permission)\n // For access levels, the database function handles the \"higher wins\" logic\n return hasEventPermission || hasOrgPermission;\n }\n \n // If only event scope available, return event permission\n return hasEventPermission;\n }\n \n // Standard permission check for single-scope pages\n const securityContext: SecurityContext = {\n userId: input.userId,\n organisationId: validatedScope.organisationId || null,\n timestamp: new Date(),\n // Optional fields can be omitted\n };\n \n // Create new input with validated scope\n const validatedInput: PermissionCheck = {\n ...input,\n scope: validatedScope\n };\n \n return engine.isPermitted(validatedInput, securityContext);\n}\n\n/**\n * Check if user has a specific permission (cached version)\n * \n * Uses request deduplication to share in-flight requests across components\n * and checks cache before making new requests. Uses session cache for page-level checks.\n * \n * @param input - Permission check input\n * @param appName - Optional app name (for PORTAL/ADMIN special case)\n * @returns Promise resolving to permission result\n */\nexport async function isPermittedCached(\n input: PermissionCheck,\n appName?: string\n): Promise<boolean> {\n const { userId, scope, permission, pageId } = input;\n \n // Check cache first (checks both short-term and session cache)\n const cacheKey = RBACCache.generatePermissionKey({\n userId,\n organisationId: scope.organisationId,\n eventId: scope.eventId,\n appId: scope.appId,\n permission,\n pageId,\n });\n \n const cached = rbacCache.get<boolean>(cacheKey, true);\n if (cached !== null) {\n return cached;\n }\n\n // Use request deduplication - if same request is in-flight, share the promise\n return getOrCreateRequest(input, async (checkInput) => {\n // Check permission with context validation\n // Note: We can't pass precomputedSuperAdmin here because getOrCreateRequest doesn't support it\n // The super admin check in isPermitted will be cached, so it's not a huge performance hit\n const result = await isPermitted(checkInput, appName, null);\n \n // Determine if this is a page-level check (has pageId or permission contains 'page.')\n const isPageLevelCheck = !!pageId || permission.includes('page.');\n \n // Cache result - use session cache for page-level checks\n rbacCache.set(cacheKey, result, undefined, isPageLevelCheck);\n \n return result;\n });\n}\n\n/**\n * Check if a user has a specific permission (alias for isPermitted)\n * \n * @param input - Permission check parameters\n * @returns Promise<boolean> - True if user has permission\n */\nexport async function hasPermission(input: PermissionCheck): Promise<boolean> {\n return isPermitted(input);\n}\n\n/**\n * Check if user has any of the specified permissions\n * \n * @param input - Permission check input with array of permissions\n * @returns Promise resolving to true if user has any permission\n */\nexport async function hasAnyPermission(input: {\n userId: UUID;\n scope: Scope;\n permissions: Permission[];\n pageId?: UUID;\n}): Promise<boolean> {\n const { permissions, ...baseInput } = input;\n \n for (const permission of permissions) {\n const hasPermission = await isPermitted({\n ...baseInput,\n permission,\n });\n \n if (hasPermission) {\n return true;\n }\n }\n \n return false;\n}\n\n/**\n * Check if user has all of the specified permissions\n * \n * @param input - Permission check input with array of permissions\n * @returns Promise resolving to true if user has all permissions\n */\nexport async function hasAllPermissions(input: {\n userId: UUID;\n scope: Scope;\n permissions: Permission[];\n pageId?: UUID;\n}): Promise<boolean> {\n const { permissions, ...baseInput } = input;\n \n for (const permission of permissions) {\n const hasPermission = await isPermitted({\n ...baseInput,\n permission,\n });\n \n if (!hasPermission) {\n return false;\n }\n }\n \n return true;\n}\n\n/**\n * Check if user is super admin\n * \n * @param userId - User ID\n * @returns Promise resolving to super admin status\n */\nexport async function isSuperAdmin(userId: UUID): Promise<boolean> {\n const engine = getEngine();\n return engine['checkSuperAdmin'](userId);\n}\n\n\n/**\n * Get page scope type (effective scope for a page)\n * \n * Returns the scope type for a page. After migration, all pages have explicit scope_type set.\n * This is the single source of truth for page scoping.\n * \n * @param pageId - Page ID (UUID) or page name\n * @param appId - App ID (required if pageId is a page name)\n * @param appName - App name (alternative to appId, used to resolve appId)\n * @returns Promise resolving to page scope type: 'event', 'organisation', or 'both'\n */\nexport async function getPageScopeType(\n pageId: UUID | string,\n appId?: UUID,\n appName?: string\n): Promise<'event' | 'organisation' | 'both'> {\n const engine = getEngine();\n \n try {\n // Resolve appId if not provided\n let resolvedAppId = appId;\n if (!resolvedAppId && appName) {\n // Get appId directly from app name\n const { data: app } = await engine['supabase']\n .from('rbac_apps')\n .select('id')\n .eq('name', appName)\n .eq('is_active', true)\n .single() as { data: { id: UUID } | null; error: any };\n resolvedAppId = app?.id;\n }\n \n if (!resolvedAppId) {\n throw new Error(`Could not resolve appId for page ${pageId}`);\n }\n \n // Resolve pageId if it's a page name\n let resolvedPageId: UUID | string = pageId;\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(pageId)) {\n // It's a page name, resolve to UUID\n const { data: page } = await engine['supabase']\n .from('rbac_app_pages')\n .select('id')\n .eq('app_id', resolvedAppId)\n .eq('page_name', pageId)\n .maybeSingle() as { data: { id: UUID } | null; error: any };\n resolvedPageId = page?.id || pageId;\n }\n \n // If still not a UUID, can't proceed\n if (!uuidRegex.test(resolvedPageId)) {\n throw new Error(`Could not resolve pageId ${pageId} to a valid UUID`);\n }\n \n // Call the database function to get scope type (always returns a value)\n const { data, error } = await engine['supabase']\n .rpc('get_page_scope_type' as any, {\n p_page_id: resolvedPageId\n }) as { data: 'event' | 'organisation' | 'both' | null; error: any };\n \n if (error) {\n log.error('Error fetching page scope type:', { pageId, appId, error });\n throw new Error(`Failed to get page scope type: ${error.message}`);\n }\n \n if (!data) {\n throw new Error(`Page ${resolvedPageId} does not have scope_type set`);\n }\n \n return data;\n } catch (err) {\n log.error('Error fetching page scope type:', err);\n throw err instanceof Error ? err : new Error(`Failed to get page scope type: ${String(err)}`);\n }\n}\n\n/**\n * Check if user is organisation admin\n * \n * @param userId - User ID\n * @param organisationId - Organisation ID\n * @returns Promise resolving to organisation admin status\n */\nexport async function isOrganisationAdmin(userId: UUID, organisationId: UUID): Promise<boolean> {\n const accessLevel = await getAccessLevel({\n userId,\n scope: { organisationId },\n });\n \n return accessLevel === 'admin' || accessLevel === 'super';\n}\n\n/**\n * Check if user is event admin\n * \n * @param userId - User ID\n * @param scope - Permission scope with eventId and appId\n * @returns Promise resolving to event admin status\n */\nexport async function isEventAdmin(userId: UUID, scope: Scope): Promise<boolean> {\n if (!scope.eventId || !scope.appId) {\n return false;\n }\n \n const accessLevel = await getAccessLevel({ userId, scope });\n return accessLevel === 'admin' || accessLevel === 'super';\n}\n\n/**\n * Invalidate user's permission cache\n * \n * @param userId - User ID\n * @param organisationId - Organisation ID (optional)\n */\nexport function invalidateUserCache(userId: UUID, organisationId?: UUID): void {\n const patterns = organisationId\n ? [\n CACHE_PATTERNS.PERMISSION(userId, organisationId),\n `access:${userId}:${organisationId}:`,\n `map:${userId}:${organisationId}:`,\n ]\n : [\n `perm:${userId}:`,\n `access:${userId}:`,\n `map:${userId}:`,\n ];\n\n patterns.forEach(pattern => rbacCache.invalidate(pattern));\n}\n\n/**\n * Invalidate organisation's permission cache\n * \n * @param organisationId - Organisation ID\n */\nexport function invalidateOrganisationCache(organisationId: UUID): void {\n rbacCache.invalidate(CACHE_PATTERNS.ORGANISATION(organisationId));\n}\n\n/**\n * Invalidate event's permission cache\n * \n * @param eventId - Event ID\n */\nexport function invalidateEventCache(eventId: string): void {\n rbacCache.invalidate(CACHE_PATTERNS.EVENT(eventId));\n}\n\n/**\n * Invalidate app's permission cache\n * \n * @param appId - App ID\n */\nexport function invalidateAppCache(appId: UUID): void {\n rbacCache.invalidate(CACHE_PATTERNS.APP(appId));\n}\n\n/**\n * Clear all permission cache\n */\nexport function clearCache(): void {\n rbacCache.clear();\n}\n\n// Re-export OrganisationContextRequiredError for convenience\nexport { OrganisationContextRequiredError } from './types';\n"],"mappings":"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","names":["log","hasPermission","isSuperAdmin","log","hasPermission"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/context/sessionTracking.ts","../src/utils/validation/common.ts","../src/utils/validation/passwordSchema.ts","../src/utils/app/appConfig.ts","../src/utils/formatting/formatting.ts"],"sourcesContent":["import type { SupabaseClient } from '@supabase/supabase-js';\nimport { createLogger } from '../core/logger';\n\nconst log = createLogger('SessionTracking');\n\n// Define the tracking parameters locally since old RBAC types are removed\ninterface TrackUserSessionParams {\n p_session_type: 'event_switch' | 'session_expired';\n p_event_id?: string;\n p_app_id?: string;\n ip_address?: string;\n user_agent?: string;\n}\n\n/**\n * Hook for manual session tracking (event switches and session expiration).\n * \n * Note: Login and logout tracking is automatically handled by UnifiedAuthProvider.\n * You should only use this hook for tracking event switches or session expirations.\n * \n * @param supabaseClient - Supabase client instance\n * @param appName - Optional application name for tracking\n * @returns Object containing tracking functions for event switches and session expiration\n */\nexport function useSessionTracking(supabaseClient: SupabaseClient, appName?: string) {\n // Resolve app name to app_id\n const resolveAppId = async (): Promise<string | undefined> => {\n if (!appName) return undefined;\n \n try {\n const { data, error } = await supabaseClient\n .from('rbac_apps')\n .select('id')\n .eq('name', appName)\n .eq('is_active', true)\n .single();\n \n if (error || !data) {\n log.warn('App not found or inactive:', appName);\n return undefined;\n }\n \n return data.id;\n } catch (error) {\n log.error('Failed to resolve app ID:', error);\n return undefined;\n }\n };\n /**\n * Track an event switch\n * @param eventId - ID of the event being switched to\n */\n const trackEventSwitch = async (eventId: string) => {\n try {\n const { data: { user } } = await supabaseClient.auth.getUser();\n if (!user) {\n log.warn('No authenticated user found for session tracking');\n return;\n }\n\n const appId = await resolveAppId();\n\n const params: TrackUserSessionParams = {\n p_session_type: 'event_switch',\n p_event_id: eventId,\n p_app_id: appId\n };\n\n const { error } = await supabaseClient.rpc('rbac_session_track', {\n p_user_id: user?.id,\n p_session_type: params.p_session_type,\n p_event_id: params.p_event_id,\n p_app_id: params.p_app_id,\n p_ip_address: params.ip_address,\n p_user_agent: params.user_agent\n });\n \n if (error) {\n log.error('Failed to track event switch session:', error);\n }\n } catch (error) {\n log.error('Failed to track event switch:', error);\n }\n };\n\n /**\n * Track a session expiration\n */\n const trackSessionExpired = async () => {\n try {\n const { data: { user } } = await supabaseClient.auth.getUser();\n if (!user) {\n log.warn('No authenticated user found for session tracking');\n return;\n }\n\n const appId = await resolveAppId();\n\n const params: TrackUserSessionParams = {\n p_session_type: 'session_expired',\n p_app_id: appId\n };\n\n const { error } = await supabaseClient.rpc('rbac_session_track', {\n p_user_id: user?.id,\n p_session_type: params.p_session_type,\n p_event_id: params.p_event_id,\n p_app_id: params.p_app_id,\n p_ip_address: params.ip_address,\n p_user_agent: params.user_agent\n });\n \n if (error) {\n log.error('Failed to track session expiration:', error);\n }\n } catch (error) {\n log.error('Failed to track session expiration:', error);\n }\n };\n\n return {\n trackEventSwitch,\n trackSessionExpired\n };\n} ","\n/**\n * @file Common validation schemas\n * @description Reusable validation schemas for common data types\n */\n\nimport { z } from 'zod';\n\n/**\n * Email validation schema\n */\nexport const emailSchema = z\n .string()\n .min(1, 'Email is required')\n .email('Invalid email format')\n .max(254, 'Email too long');\n\n/**\n * Name validation schema\n */\nexport const nameSchema = z\n .string()\n .min(1, 'Name is required')\n .max(100, 'Name too long')\n .regex(/^[a-zA-Z\\s'-]+$/, 'Name contains invalid characters');\n\n/**\n * Phone number validation schema\n */\nexport const phoneSchema = z\n .string()\n .regex(/^\\+?[\\d\\s\\-\\(\\)]+$/, 'Invalid phone number format')\n .min(10, 'Phone number too short')\n .max(20, 'Phone number too long');\n\n/**\n * URL validation schema\n */\nexport const urlSchema = z\n .string()\n .url('Invalid URL format')\n .max(2048, 'URL too long');\n\n/**\n * Date validation schema\n */\nexport const dateSchema = z\n .string()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Date must be in YYYY-MM-DD format')\n .refine((date) => {\n const parsed = new Date(date);\n return !isNaN(parsed.getTime());\n }, 'Invalid date');\n","\n/**\n * @file Enhanced Password Schema with Security Validations\n * @description Comprehensive password validation with security checks\n */\n\nimport { z } from 'zod';\n\n// Common weak passwords to check against\nconst COMMON_PASSWORDS = new Set([\n 'password', '123456', '123456789', 'qwerty', 'abc123', 'password123',\n 'admin', 'letmein', 'welcome', 'monkey', '1234567890', 'password1'\n]);\n\n// Common password patterns to avoid\nconst WEAK_PATTERNS = [\n /^(.)\\1+$/, // All same character\n /^(012|123|234|345|456|567|678|789|890|987|876|765|654|543|432|321|210)+/, // Sequential numbers\n /^(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)+/i, // Sequential letters\n];\n\n/**\n * Enhanced password validation schema with security checks\n */\nexport const securePasswordSchema = z\n .string()\n .min(8, 'Password must be at least 8 characters long')\n .max(128, 'Password must not exceed 128 characters')\n .refine(\n (password) => /[a-z]/.test(password),\n 'Password must contain at least one lowercase letter'\n )\n .refine(\n (password) => /[A-Z]/.test(password),\n 'Password must contain at least one uppercase letter'\n )\n .refine(\n (password) => /\\d/.test(password),\n 'Password must contain at least one number'\n )\n .refine(\n (password) => /[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]/.test(password),\n 'Password must contain at least one special character'\n )\n .refine(\n (password) => !COMMON_PASSWORDS.has(password.toLowerCase()),\n 'Password is too common. Please choose a stronger password'\n )\n .refine(\n (password) => !WEAK_PATTERNS.some(pattern => pattern.test(password)),\n 'Password contains weak patterns. Please choose a more complex password'\n )\n .refine(\n (password) => {\n // Check for keyboard patterns (qwerty, asdf, etc.)\n const keyboardPatterns = ['qwerty', 'asdfgh', 'zxcvbn', '1234567890'];\n return !keyboardPatterns.some(pattern => \n password.toLowerCase().includes(pattern)\n );\n },\n 'Password contains keyboard patterns. Please choose a more secure password'\n );\n\n/**\n * Basic password schema for less strict requirements\n */\nexport const passwordSchema = z\n .string()\n .min(6, 'Password must be at least 6 characters long')\n .max(128, 'Password must not exceed 128 characters');\n\n/**\n * Password strength calculator\n */\nexport function calculatePasswordStrength(password: string): {\n score: number;\n feedback: string[];\n level: 'very-weak' | 'weak' | 'fair' | 'good' | 'strong';\n} {\n let score = 0;\n const feedback: string[] = [];\n\n // Length check\n if (password.length >= 8) score += 20;\n else if (password.length >= 6) score += 10;\n else feedback.push('Use at least 8 characters');\n\n // Character variety\n if (/[a-z]/.test(password)) score += 15;\n else feedback.push('Add lowercase letters');\n\n if (/[A-Z]/.test(password)) score += 15;\n else feedback.push('Add uppercase letters');\n\n if (/\\d/.test(password)) score += 15;\n else feedback.push('Add numbers');\n\n if (/[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]/.test(password)) score += 15;\n else feedback.push('Add special characters');\n\n // Additional complexity\n if (password.length >= 12) score += 10;\n if (/[^a-zA-Z0-9]/.test(password)) score += 10;\n\n // Penalties\n if (COMMON_PASSWORDS.has(password.toLowerCase())) {\n score -= 30;\n feedback.push('Avoid common passwords');\n }\n\n if (WEAK_PATTERNS.some(pattern => pattern.test(password))) {\n score -= 20;\n feedback.push('Avoid predictable patterns');\n }\n\n // Determine level\n let level: 'very-weak' | 'weak' | 'fair' | 'good' | 'strong';\n if (score < 30) level = 'very-weak';\n else if (score < 50) level = 'weak';\n else if (score < 70) level = 'fair';\n else if (score < 90) level = 'good';\n else level = 'strong';\n\n return { score: Math.max(0, Math.min(100, score)), feedback, level };\n}\n","\n/**\n * Application configuration utilities\n */\n\nexport interface AppConfig {\n appName: string;\n appId: string;\n}\n\nlet currentAppConfig: AppConfig | null = null;\n\n/**\n * Set the current application configuration\n */\nexport function setAppConfig(config: AppConfig) {\n currentAppConfig = config;\n}\n\n/**\n * Get the current application configuration\n */\nexport function getAppConfig(): AppConfig {\n if (!currentAppConfig) {\n // Fallback to environment or default\n const appName = import.meta.env.REACT_APP_NAME || 'PACE';\n return {\n appName,\n appId: appName\n };\n }\n return currentAppConfig;\n}\n\n/**\n * Get the current app name\n */\nexport function getCurrentAppName(): string {\n return getAppConfig().appName;\n}\n\n/**\n * Get the current app ID\n */\nexport function getCurrentAppId(): string {\n return getAppConfig().appId;\n}\n","/**\n * Utility functions for formatting data in the application\n */\n\nimport { parseISO, isValid } from 'date-fns';\nimport { formatInTimeZone, getTimezoneAbbreviation } from '../timezone';\n\n/**\n * Format a date as a readable string in \"dd mmm yyyy\" format (e.g., \"15 Jun 2024\")\n */\nexport function formatDate(date: Date | string | number): string {\n const dateObj = typeof date === 'string' || typeof date === 'number' \n ? new Date(date) \n : date;\n \n // Use 'en-GB' locale to ensure \"dd mmm yyyy\" format (e.g., \"15 Jun 2024\")\n return dateObj.toLocaleDateString('en-GB', {\n year: 'numeric',\n month: 'short',\n day: 'numeric'\n });\n}\n\n/**\n * Format a time as a readable string in \"HH:mm\" format (e.g., \"14:30\")\n * Uses 24-hour format for consistency across pace apps\n */\nexport function formatTime(date: Date | string | number): string {\n const dateObj = typeof date === 'string' || typeof date === 'number' \n ? new Date(date) \n : date;\n \n // Use 'en-GB' locale to ensure \"HH:mm\" format (24-hour format)\n return dateObj.toLocaleTimeString('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false\n });\n}\n\n/**\n * Format a date and time as a readable string in \"dd mmm yyyy, HH:mm\" format (e.g., \"15 Jun 2024, 14:30\")\n * Uses 24-hour format for consistency across pace apps\n */\nexport function formatDateTime(date: Date | string | number): string {\n const dateObj = typeof date === 'string' || typeof date === 'number' \n ? new Date(date) \n : date;\n \n // Use 'en-GB' locale to ensure consistent format (e.g., \"15 Jun 2024, 14:30\")\n return dateObj.toLocaleString('en-GB', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n hour12: false\n });\n}\n\n/**\n * Format a number as a currency\n */\nexport function formatCurrency(value: number, currencyCode = 'USD', locale = 'en-US'): string {\n return new Intl.NumberFormat(locale, {\n style: 'currency',\n currency: currencyCode,\n }).format(value);\n}\n\n/**\n * Format a number with custom options\n */\nexport function formatNumber(\n value: number,\n options: Intl.NumberFormatOptions = {},\n locale = 'en-US'\n): string {\n return new Intl.NumberFormat(locale, options).format(value);\n}\n\n/**\n * Format a number as a percentage.\n * \n * @param value - The percentage value as a decimal (e.g., 0.81 for 0.81%)\n * @param locale - The locale string (default: 'en-US')\n * @param options - Options object with:\n * - `decimals` - Fixed number of decimal places (default: 1)\n * - `preserveDecimals` - Auto-detect and preserve decimal places from the input value\n * - `maxDecimals` - Maximum decimal places when preserving (default: 10)\n * @returns Formatted percentage string (e.g., \"0.81%\", \"81%\")\n * \n * @example\n * ```ts\n * // Fixed decimals (default behavior)\n * formatPercent(0.5) // '0.5%'\n * formatPercent(0.81, 'en-US', { decimals: 1 }) // '0.8%' (loses precision)\n * \n * // Preserve decimal places dynamically\n * formatPercent(0.81, 'en-US', { preserveDecimals: true }) // '0.81%'\n * formatPercent(0.8123, 'en-US', { preserveDecimals: true, maxDecimals: 2 }) // '0.81%'\n * ```\n */\nexport function formatPercent(\n value: number,\n locale: string = 'en-US',\n options?: {\n decimals?: number;\n preserveDecimals?: boolean;\n maxDecimals?: number;\n }\n): string {\n let decimals: number;\n\n if (options && typeof options === 'object') {\n // Check if we should preserve decimals\n if (options.preserveDecimals) {\n const valueStr = value.toString();\n const decimalIndex = valueStr.indexOf('.');\n \n if (decimalIndex !== -1) {\n const detectedDecimals = valueStr.length - decimalIndex - 1;\n const maxDecimals = options.maxDecimals ?? 10;\n decimals = Math.min(detectedDecimals, maxDecimals);\n } else {\n decimals = 0;\n }\n } else {\n decimals = options.decimals ?? 1;\n }\n } else {\n decimals = 1;\n }\n\n return new Intl.NumberFormat(locale, {\n style: 'percent',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(value / 100);\n}\n\n/**\n * Format a large number with abbreviations (K, M, B)\n */\nexport function formatCompactNumber(value: number, locale = 'en-US'): string {\n return new Intl.NumberFormat(locale, {\n notation: 'compact',\n compactDisplay: 'short'\n }).format(value);\n}\n\n/**\n * Format a file size in bytes to a human-readable string\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 Bytes';\n \n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n}\n\n/**\n * Options for formatting date/time with timezone\n */\nexport interface DateTimeFormatOptions {\n /**\n * Include timezone abbreviation (default: true)\n */\n includeTimezone?: boolean;\n /**\n * Custom format string (default: 'MMM dd, yyyy HH:mm')\n */\n format?: string;\n}\n\n/**\n * Format a UTC date for display in a specific timezone\n *\n * @param utcDate - UTC date (ISO string, Date object, or undefined)\n * @param timezone - IANA timezone string (e.g., 'America/New_York')\n * @param options - Formatting options\n * @returns Formatted date string or empty string if invalid\n *\n * @example\n * ```ts\n * formatDateTimeForDisplay('2024-01-15T10:00:00Z', 'America/New_York');\n * // \"Jan 15, 2024 05:00 (EST)\"\n *\n * formatDateTimeForDisplay('2024-01-15T10:00:00Z', 'America/New_York', { includeTimezone: false });\n * // \"Jan 15, 2024 05:00\"\n * ```\n */\nexport function formatDateTimeForDisplay(\n utcDate: string | Date | undefined,\n timezone: string | undefined,\n options: DateTimeFormatOptions = {}\n): string {\n if (!utcDate) {\n return '';\n }\n\n if (!timezone) {\n return '';\n }\n\n try {\n const { includeTimezone = true, format: formatStr = 'MMM dd, yyyy HH:mm' } = options;\n\n let dateObj: Date;\n if (typeof utcDate === 'string') {\n dateObj = parseISO(utcDate);\n } else {\n dateObj = utcDate;\n }\n\n if (!isValid(dateObj)) {\n return '';\n }\n\n const formatted = formatInTimeZone(dateObj, timezone, formatStr);\n\n if (includeTimezone) {\n const tzAbbr = getTimezoneAbbreviation(dateObj, timezone);\n return `${formatted} (${tzAbbr})`;\n }\n\n return formatted;\n } catch {\n return '';\n }\n}\n\n/**\n * Format a UTC date for display (date only, no time)\n *\n * @param utcDate - UTC date (ISO string, Date object, or undefined)\n * @returns Formatted date string or empty string if invalid\n *\n * @example\n * ```ts\n * formatDateOnlyForDisplay('2024-01-15T10:00:00Z');\n * // \"15 January 2024\"\n * ```\n */\nexport function formatDateOnlyForDisplay(utcDate: string | Date | undefined): string {\n if (!utcDate) {\n return '';\n }\n\n try {\n let dateObj: Date;\n if (typeof utcDate === 'string') {\n dateObj = parseISO(utcDate);\n } else {\n dateObj = utcDate;\n }\n\n if (!isValid(dateObj)) {\n return '';\n }\n\n // Use 'en-GB' locale for \"dd mmm yyyy\" format\n return dateObj.toLocaleDateString('en-GB', {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n } catch {\n return '';\n }\n}\n\n/**\n * Format a UTC date for table display (compact format with timezone)\n *\n * @param utcDate - UTC date (ISO string, Date object, or undefined)\n * @param timezone - IANA timezone string\n * @returns Formatted date string or empty string if invalid\n *\n * @example\n * ```ts\n * formatDateTimeForTable('2024-01-15T10:00:00Z', 'America/New_York');\n * // \"Jan 15, 2024 05:00 (EST)\"\n * ```\n */\nexport function formatDateTimeForTable(\n utcDate: string | Date | undefined,\n timezone: string | undefined\n): string {\n return formatDateTimeForDisplay(utcDate, timezone, {\n includeTimezone: true,\n format: 'MMM dd, yyyy HH:mm'\n });\n}\n\n/**\n * Format a UTC date for map display (compact format)\n *\n * @param utcDate - UTC date (ISO string, Date object, or undefined)\n * @param timezone - IANA timezone string\n * @returns Formatted date string or empty string if invalid\n *\n * @example\n * ```ts\n * formatDateTimeForMap('2024-01-15T10:00:00Z', 'America/New_York');\n * // \"Jan 15, 05:00 EST\"\n * ```\n */\nexport function formatDateTimeForMap(\n utcDate: string | Date | undefined,\n timezone: string | undefined\n): string {\n if (!utcDate || !timezone) {\n return '';\n }\n\n try {\n let dateObj: Date;\n if (typeof utcDate === 'string') {\n dateObj = parseISO(utcDate);\n } else {\n dateObj = utcDate;\n }\n\n if (!isValid(dateObj)) {\n return '';\n }\n\n const formatted = formatInTimeZone(dateObj, timezone, 'MMM dd, HH:mm');\n const tzAbbr = getTimezoneAbbreviation(dateObj, timezone);\n\n return `${formatted} ${tzAbbr}`;\n } catch {\n return '';\n }\n}\n"],"mappings":"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","names":["z"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/performance/performanceBudgets.ts"],"sourcesContent":["\ninterface PerformanceBudget {\n metric: string;\n budget: number;\n actual: number;\n threshold: 'error' | 'warning' | 'info';\n}\n\ninterface PerformanceMetrics {\n bundleSize: number;\n chunkCount: number;\n treeshakingEffectiveness: number;\n dynamicImportUsage: number;\n}\n\ninterface MeasurementResult {\n passed: boolean;\n value: number;\n threshold: number;\n}\n\n// Performance budget thresholds with index signature\nexport const PERFORMANCE_BUDGETS: { [key: string]: { threshold: number } } = {\n COMPONENT_RENDER: { threshold: 50 },\n BUNDLE_SIZE: { threshold: 150000 },\n CHUNK_COUNT: { threshold: 10 },\n TREESHAKING_SCORE: { threshold: 70 },\n ERROR_BOUNDARY_TRIGGER: { threshold: 5 },\n MEMORY_INCREASE: { threshold: 1000 },\n LARGE_LIST_RENDER: { threshold: 500 },\n} as const;\n\nclass PerformanceBudgetMonitor {\n private metrics: Map<string, number> = new Map();\n private budgets: PerformanceBudget[] = [];\n\n measure(metric: string, value: number, metadata?: Record<string, unknown>): MeasurementResult {\n this.metrics.set(metric, value);\n \n // In production, this would send to a proper logging service\n // For now, we'll log to console for testing purposes\n if (import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test' || process.env.NODE_ENV === 'test') {\n console.log('📊 Performance Metric: ' + metric + ' = ' + value, metadata);\n }\n\n // Get threshold for this metric\n const budgetConfig = PERFORMANCE_BUDGETS[metric];\n const threshold = budgetConfig?.threshold || 100;\n const passed = value <= threshold;\n\n return {\n passed,\n value,\n threshold\n };\n }\n\n setBudget(metric: string, budget: number, threshold: 'error' | 'warning' | 'info' = 'warning'): void {\n this.budgets.push({ metric, budget, actual: 0, threshold });\n }\n\n checkBudgets(): PerformanceBudget[] {\n const violations: PerformanceBudget[] = [];\n \n for (const budget of this.budgets) {\n const actual = this.metrics.get(budget.metric) || 0;\n budget.actual = actual;\n \n if (actual > budget.budget) {\n violations.push(budget);\n \n // In production, this would send to a proper logging service\n // For now, we'll log to console for testing purposes\n if (import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test' || process.env.NODE_ENV === 'test') {\n if (budget.threshold === 'error') {\n console.error('❌ Performance budget exceeded: ' + budget.metric + ' (' + actual + ' > ' + budget.budget + ')');\n } else if (budget.threshold === 'warning') {\n console.warn('⚠️ Performance budget exceeded: ' + budget.metric + ' (' + actual + ' > ' + budget.budget + ')');\n } else {\n console.info('ℹ️ Performance budget exceeded: ' + budget.metric + ' (' + actual + ' > ' + budget.budget + ')');\n }\n }\n }\n }\n \n return violations;\n }\n\n getMetrics(): PerformanceMetrics {\n return {\n bundleSize: this.metrics.get('BUNDLE_SIZE') || 0,\n chunkCount: this.metrics.get('CHUNK_COUNT') || 0,\n treeshakingEffectiveness: this.metrics.get('TREESHAKING_SCORE') || 0,\n dynamicImportUsage: this.metrics.get('DYNAMIC_IMPORTS') || 0,\n };\n }\n\n reset(): void {\n this.metrics.clear();\n this.budgets.length = 0;\n }\n}\n\nexport const performanceBudgetMonitor = new PerformanceBudgetMonitor();\n\n// Set default performance budgets\nperformanceBudgetMonitor.setBudget('BUNDLE_SIZE', 150000, 'error'); // 150KB\nperformanceBudgetMonitor.setBudget('CHUNK_COUNT', 10, 'warning');\nperformanceBudgetMonitor.setBudget('TREESHAKING_SCORE', 70, 'warning');\nperformanceBudgetMonitor.setBudget('ERROR_BOUNDARY_TRIGGER', 5, 'error');\n"],"mappings":";AAsBO,IAAM,sBAAgE;AAAA,EAC3E,kBAAkB,EAAE,WAAW,GAAG;AAAA,EAClC,aAAa,EAAE,WAAW,KAAO;AAAA,EACjC,aAAa,EAAE,WAAW,GAAG;AAAA,EAC7B,mBAAmB,EAAE,WAAW,GAAG;AAAA,EACnC,wBAAwB,EAAE,WAAW,EAAE;AAAA,EACvC,iBAAiB,EAAE,WAAW,IAAK;AAAA,EACnC,mBAAmB,EAAE,WAAW,IAAI;AACtC;AAEA,IAAM,2BAAN,MAA+B;AAAA,EAA/B;AACE,SAAQ,UAA+B,oBAAI,IAAI;AAC/C,SAAQ,UAA+B,CAAC;AAAA;AAAA,EAExC,QAAQ,QAAgB,OAAe,UAAuD;AAC5F,SAAK,QAAQ,IAAI,QAAQ,KAAK;AAI9B,QAAI,YAAY,IAAI,SAAS,iBAAiB,YAAY,IAAI,SAAS,UAAU,OAAiC;AAChH,cAAQ,IAAI,mCAA4B,SAAS,QAAQ,OAAO,QAAQ;AAAA,IAC1E;AAGA,UAAM,eAAe,oBAAoB,MAAM;AAC/C,UAAM,YAAY,cAAc,aAAa;AAC7C,UAAM,SAAS,SAAS;AAExB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,QAAgB,QAAgB,YAA0C,WAAiB;AACnG,SAAK,QAAQ,KAAK,EAAE,QAAQ,QAAQ,QAAQ,GAAG,UAAU,CAAC;AAAA,EAC5D;AAAA,EAEA,eAAoC;AAClC,UAAM,aAAkC,CAAC;AAEzC,eAAW,UAAU,KAAK,SAAS;AACjC,YAAM,SAAS,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK;AAClD,aAAO,SAAS;AAEhB,UAAI,SAAS,OAAO,QAAQ;AAC1B,mBAAW,KAAK,MAAM;AAItB,YAAI,YAAY,IAAI,SAAS,iBAAiB,YAAY,IAAI,SAAS,UAAU,OAAiC;AAChH,cAAI,OAAO,cAAc,SAAS;AAChC,oBAAQ,MAAM,yCAAoC,OAAO,SAAS,OAAO,SAAS,QAAQ,OAAO,SAAS,GAAG;AAAA,UAC/G,WAAW,OAAO,cAAc,WAAW;AACzC,oBAAQ,KAAK,+CAAqC,OAAO,SAAS,OAAO,SAAS,QAAQ,OAAO,SAAS,GAAG;AAAA,UAC/G,OAAO;AACL,oBAAQ,KAAK,+CAAqC,OAAO,SAAS,OAAO,SAAS,QAAQ,OAAO,SAAS,GAAG;AAAA,UAC/G;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,aAAiC;AAC/B,WAAO;AAAA,MACL,YAAY,KAAK,QAAQ,IAAI,aAAa,KAAK;AAAA,MAC/C,YAAY,KAAK,QAAQ,IAAI,aAAa,KAAK;AAAA,MAC/C,0BAA0B,KAAK,QAAQ,IAAI,mBAAmB,KAAK;AAAA,MACnE,oBAAoB,KAAK,QAAQ,IAAI,iBAAiB,KAAK;AAAA,IAC7D;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,QAAQ,MAAM;AACnB,SAAK,QAAQ,SAAS;AAAA,EACxB;AACF;AAEO,IAAM,2BAA2B,IAAI,yBAAyB;AAGrE,yBAAyB,UAAU,eAAe,MAAQ,OAAO;AACjE,yBAAyB,UAAU,eAAe,IAAI,SAAS;AAC/D,yBAAyB,UAAU,qBAAqB,IAAI,SAAS;AACrE,yBAAyB,UAAU,0BAA0B,GAAG,OAAO;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/request-deduplication.ts","../src/utils/google-places/loadGoogleMapsScript.ts","../src/utils/google-places/googlePlacesUtils.ts"],"sourcesContent":["/**\n * Request Deduplication Utility\n * @package @jmruthers/pace-core\n * @module Utils/RequestDeduplication\n * @since 2.0.0\n * \n * Provides request deduplication to prevent duplicate in-flight requests.\n * When multiple components request the same data simultaneously, only one\n * request is made and all callers share the same promise.\n */\n\nimport { createLogger } from './core/logger';\n\nconst log = createLogger('request-deduplication');\n\n/**\n * In-flight request cache\n * Key: request identifier (e.g., \"GET:table:filter:value\")\n * Value: Promise that resolves to the request result\n */\nconst inFlightRequests = new Map<string, Promise<any>>();\n\n/**\n * Generate a request key from request parameters\n * \n * @param method - HTTP method (GET, POST, etc.)\n * @param table - Table name\n * @param filters - Filter object\n * @param select - Select columns\n * @returns Request key string\n */\nexport function generateRequestKey(\n method: string,\n table: string,\n filters?: Record<string, any>,\n select?: string\n): string {\n const filterStr = filters ? JSON.stringify(filters) : '';\n const selectStr = select || '*';\n return `${method}:${table}:${filterStr}:${selectStr}`;\n}\n\n/**\n * Get or create a request\n * \n * If a request with the same key is already in-flight, returns the existing promise.\n * Otherwise, creates a new request and stores it for deduplication.\n * \n * @param key - Request key\n * @param requestFn - Function that performs the actual request\n * @returns Promise that resolves to the request result\n * \n * @example\n * ```ts\n * const data = await getOrCreateRequest(\n * 'GET:core_person:{\"user_id\":\"123\"}',\n * async () => {\n * const { data } = await supabase\n * .from('core_person')\n * .select('id, first_name')\n * .eq('user_id', '123')\n * .single();\n * return data;\n * }\n * );\n * ```\n */\nexport async function getOrCreateRequest<T>(\n key: string,\n requestFn: () => Promise<T>\n): Promise<T> {\n // Check if request is already in-flight\n const existingRequest = inFlightRequests.get(key);\n if (existingRequest) {\n log.debug(`Request deduplication: reusing in-flight request for ${key}`);\n return existingRequest as Promise<T>;\n }\n \n // Create new request\n log.debug(`Creating new request for ${key}`);\n const requestPromise = requestFn()\n .then((result) => {\n // Remove from in-flight cache after completion\n inFlightRequests.delete(key);\n return result;\n })\n .catch((error) => {\n // Remove from in-flight cache on error\n inFlightRequests.delete(key);\n throw error;\n });\n \n // Store in-flight request\n inFlightRequests.set(key, requestPromise);\n \n return requestPromise;\n}\n\n/**\n * Clear all in-flight requests\n * \n * Useful for cleanup or testing.\n */\nexport function clearInFlightRequests(): void {\n const count = inFlightRequests.size;\n inFlightRequests.clear();\n log.debug(`Cleared ${count} in-flight requests`);\n}\n\n/**\n * Get statistics about in-flight requests\n * \n * @returns Statistics object\n */\nexport function getInFlightRequestStats(): {\n count: number;\n keys: string[];\n} {\n return {\n count: inFlightRequests.size,\n keys: Array.from(inFlightRequests.keys()),\n };\n}\n\n/**\n * Supabase query wrapper with automatic deduplication\n * \n * Wraps a Supabase query to automatically deduplicate identical requests.\n * \n * @param supabase - Supabase client\n * @param table - Table name\n * @param filters - Filter object (e.g., { user_id: '123' })\n * @param select - Select columns (default: '*')\n * @param requestFn - Function that performs the query\n * @returns Promise that resolves to query result\n * \n * @example\n * ```ts\n * const person = await deduplicatedQuery(\n * supabase,\n * 'core_person',\n * { user_id: userId },\n * 'id, first_name, last_name',\n * async () => {\n * const { data } = await supabase\n * .from('core_person')\n * .select('id, first_name, last_name')\n * .eq('user_id', userId)\n * .single();\n * return data;\n * }\n * );\n * ```\n */\nexport async function deduplicatedQuery<T>(\n supabase: any,\n table: string,\n filters: Record<string, any>,\n select: string,\n requestFn: () => Promise<T>\n): Promise<T> {\n const key = generateRequestKey('GET', table, filters, select);\n return getOrCreateRequest(key, requestFn);\n}\n\n","/**\n * @file Google Maps Script Loader\n * @package @jmruthers/pace-core\n * @module Utils/GooglePlaces\n * @since 0.1.0\n *\n * Utility to dynamically load the Google Maps JavaScript API.\n * This is required because the REST API doesn't support CORS from browsers.\n */\n\n// Type definitions for Google Maps (minimal, just what we need)\ndeclare global {\n interface Window {\n google?: {\n maps: {\n places: {\n // New AutocompleteSuggestion API (recommended)\n AutocompleteSuggestion: {\n fetchAutocompleteSuggestions: (\n request: {\n input: string;\n includedRegionCodes?: string[];\n locationBias?: {\n circle?: {\n center: { latitude: number; longitude: number };\n radius: number;\n };\n };\n includedPrimaryTypes?: string[];\n languageCode?: string;\n }\n ) => Promise<{\n suggestions: Array<{\n placePrediction?: {\n placeId: string;\n text: {\n text: string;\n matches: Array<{\n endOffset: number;\n startOffset: number;\n }>;\n };\n structuredFormat?: {\n mainText: { text: string };\n secondaryText: { text: string };\n };\n };\n }>;\n }>;\n };\n // Legacy AutocompleteService (deprecated but still works)\n AutocompleteService: new () => {\n getPlacePredictions: (\n request: {\n input: string;\n componentRestrictions?: { country: string | string[] };\n location?: { lat: () => number; lng: () => number };\n radius?: number;\n types?: string[];\n language?: string;\n },\n callback: (\n predictions: Array<{\n description: string;\n place_id: string;\n structured_formatting: {\n main_text: string;\n secondary_text: string;\n };\n }> | null,\n status: string\n ) => void\n ) => void;\n };\n PlacesService: new (element: HTMLElement) => {\n getDetails: (\n request: { placeId: string; fields?: string[] },\n callback: (\n place: {\n place_id: string;\n formatted_address?: string;\n address_components?: Array<{\n long_name: string;\n short_name: string;\n types: string[];\n }>;\n geometry?: {\n location?: {\n lat: () => number;\n lng: () => number;\n };\n };\n } | null,\n status: string\n ) => void\n ) => void;\n };\n PlacesServiceStatus: {\n OK: string;\n ZERO_RESULTS: string;\n NOT_FOUND: string;\n REQUEST_DENIED: string;\n INVALID_REQUEST: string;\n OVER_QUERY_LIMIT: string;\n };\n };\n LatLng: new (lat: number, lng: number) => { lat: () => number; lng: () => number };\n };\n };\n }\n}\n\n/**\n * Load Google Maps JavaScript API script\n * @param apiKey - Google Places API key\n * @param libraries - Comma-separated list of libraries to load (default: 'places')\n * @returns Promise that resolves when the script is loaded\n */\nexport function loadGoogleMapsScript(\n apiKey: string,\n libraries: string = 'places'\n): Promise<void> {\n return new Promise((resolve, reject) => {\n // Check if script is already loaded\n if (window.google && window.google.maps && window.google.maps.places) {\n resolve();\n return;\n }\n\n // Check if script is already being loaded\n const existingScript = document.querySelector(\n `script[src*=\"maps.googleapis.com/maps/api/js\"]`\n );\n if (existingScript) {\n // Wait for existing script to load\n existingScript.addEventListener('load', () => {\n // Wait for the library to initialize with multiple retries\n let attempts = 0;\n const maxAttempts = 20; // 2 seconds total\n \n const checkPlaces = () => {\n if (window.google?.maps?.places) {\n resolve();\n } else if (attempts < maxAttempts) {\n attempts++;\n setTimeout(checkPlaces, 100);\n } else {\n reject(new Error('Google Maps script loaded but Places library not available. Make sure the Places API is enabled in your Google Cloud Console.'));\n }\n };\n \n checkPlaces();\n });\n existingScript.addEventListener('error', () => {\n reject(new Error('Failed to load Google Maps script'));\n });\n return;\n }\n\n // Create and load script\n const script = document.createElement('script');\n script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=${libraries}&loading=async`;\n script.async = true;\n script.defer = true;\n\n script.onload = () => {\n // Wait for the library to initialize with multiple retries\n let attempts = 0;\n const maxAttempts = 20; // 2 seconds total (20 * 100ms)\n \n const checkPlaces = () => {\n if (window.google?.maps?.places) {\n resolve();\n } else if (attempts < maxAttempts) {\n attempts++;\n setTimeout(checkPlaces, 100);\n } else {\n // Check if google.maps exists but places doesn't\n if (window.google?.maps && !window.google.maps.places) {\n reject(new Error('Google Maps loaded but Places library not available. Make sure the Places API is enabled in your Google Cloud Console and the \"places\" library is included in the script URL.'));\n } else if (!window.google) {\n reject(new Error('Google Maps script loaded but google object not available. Check your API key and network connection.'));\n } else {\n reject(new Error('Google Maps script loaded but Places library not available after multiple attempts. Make sure the Places API is enabled in your Google Cloud Console.'));\n }\n }\n };\n \n // Start checking immediately, then retry if needed\n checkPlaces();\n };\n\n script.onerror = () => {\n reject(new Error('Failed to load Google Maps script'));\n };\n\n document.head.appendChild(script);\n });\n}\n\n/**\n * Check if Google Maps is already loaded\n */\nexport function isGoogleMapsLoaded(): boolean {\n return !!(window.google && window.google.maps && window.google.maps.places);\n}\n\n","/**\n * @file Google Places API Utilities\n * @package @jmruthers/pace-core\n * @module Utils/GooglePlaces\n * @since 0.1.0\n *\n * Utility functions for interacting with Google Places API.\n * Uses the Google Maps JavaScript API to avoid CORS issues.\n *\n * Features:\n * - Places Autocomplete Service integration\n * - Places Service integration\n * - Address component parsing\n * - Error handling\n * - Request deduplication\n */\n\nimport { getOrCreateRequest } from '../request-deduplication';\nimport { createLogger } from '../core/logger';\nimport { loadGoogleMapsScript, isGoogleMapsLoaded } from './loadGoogleMapsScript';\nimport type {\n GooglePlaceAutocompletePrediction,\n ParsedAddress,\n AutocompleteOptions,\n} from './types';\n\nconst log = createLogger('google-places');\n\n// Google Maps types are defined in loadGoogleMapsScript.ts\n\n/**\n * Fetch place autocomplete predictions using Google Maps JavaScript API\n *\n * @param query - Search query string\n * @param apiKey - Google Places API key\n * @param options - Optional autocomplete options\n * @returns Promise resolving to array of predictions\n *\n * @example\n * ```ts\n * const predictions = await fetchPlaceAutocomplete(\n * '123 Main St',\n * 'your-api-key',\n * { components: 'country:au' }\n * );\n * ```\n */\nexport async function fetchPlaceAutocomplete(\n query: string,\n apiKey: string,\n options?: AutocompleteOptions\n): Promise<GooglePlaceAutocompletePrediction[]> {\n if (!query.trim()) {\n return [];\n }\n\n if (!apiKey) {\n throw new Error('Google Places API key is required');\n }\n\n // Ensure Google Maps script is loaded\n if (!isGoogleMapsLoaded()) {\n await loadGoogleMapsScript(apiKey, 'places');\n }\n\n const requestKey = `google-places-autocomplete:${query}:${JSON.stringify(options || {})}`;\n\n return getOrCreateRequest(requestKey, async () => {\n try {\n log.debug(`Fetching autocomplete for query: ${query}`);\n\n if (!window.google?.maps?.places) {\n throw new Error('Google Maps Places library not available');\n }\n\n // Try to use the new AutocompleteSuggestion API first, fallback to legacy AutocompleteService\n const useNewAPI = window.google.maps.places.AutocompleteSuggestion !== undefined;\n \n if (useNewAPI) {\n // Use new AutocompleteSuggestion API\n const request: {\n input: string;\n includedRegionCodes?: string[];\n locationBias?: {\n circle?: {\n center: { latitude: number; longitude: number };\n radius: number;\n };\n };\n includedPrimaryTypes?: string[];\n languageCode?: string;\n } = {\n input: query.trim(),\n };\n\n // Parse components option (e.g., \"country:au\" -> [\"au\"])\n if (options?.components) {\n const componentParts = options.components.split('|');\n const countries: string[] = [];\n\n componentParts.forEach((part) => {\n const [key, value] = part.split(':');\n if (key === 'country') {\n countries.push(...value.split(',').map(c => c.toUpperCase()));\n }\n });\n\n if (countries.length > 0) {\n request.includedRegionCodes = countries;\n }\n }\n\n if (options?.location && options?.radius) {\n const [lat, lng] = options.location.split(',').map(Number);\n request.locationBias = {\n circle: {\n center: { latitude: lat, longitude: lng },\n radius: options.radius,\n },\n };\n }\n\n if (options?.language) {\n request.languageCode = options.language;\n }\n\n // Call new API\n return new Promise<GooglePlaceAutocompletePrediction[]>((resolve, reject) => {\n window.google!.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(request)\n .then((response) => {\n if (response.suggestions && response.suggestions.length > 0) {\n const result: GooglePlaceAutocompletePrediction[] = response.suggestions\n .filter(s => s.placePrediction)\n .map((s) => ({\n description: s.placePrediction!.text.text,\n place_id: s.placePrediction!.placeId,\n structured_formatting: {\n main_text: s.placePrediction!.structuredFormat?.mainText?.text || s.placePrediction!.text.text,\n secondary_text: s.placePrediction!.structuredFormat?.secondaryText?.text || '',\n },\n }));\n log.debug(`Received ${result.length} predictions (new API)`);\n resolve(result);\n } else {\n log.debug('No results found (new API)');\n resolve([]);\n }\n })\n .catch((error) => {\n log.error('Autocomplete fetch failed (new API):', error);\n reject(new Error(`Failed to fetch autocomplete predictions: ${error.message || 'Unknown error'}`));\n });\n });\n }\n\n // Fallback to legacy AutocompleteService\n const autocompleteService = new window.google.maps.places.AutocompleteService();\n\n // Build request\n const request: {\n input: string;\n componentRestrictions?: { country: string | string[] };\n location?: { lat: () => number; lng: () => number };\n radius?: number;\n types?: string[];\n language?: string;\n } = {\n input: query.trim(),\n };\n\n // Parse components option (e.g., \"country:au\" -> { country: \"AU\" })\n if (options?.components) {\n const componentParts = options.components.split('|');\n const restrictions: { country?: string[] } = {};\n const countries: string[] = [];\n\n componentParts.forEach((part) => {\n const [key, value] = part.split(':');\n if (key === 'country') {\n // Convert to uppercase for consistency with Google Maps API\n countries.push(...value.split(',').map(c => c.toUpperCase()));\n }\n });\n\n if (countries.length > 0) {\n request.componentRestrictions = { country: countries.length === 1 ? countries[0] : countries };\n }\n }\n\n if (options?.location) {\n const [lat, lng] = options.location.split(',').map(Number);\n if (window.google?.maps?.LatLng) {\n request.location = new window.google.maps.LatLng(lat, lng);\n }\n }\n\n if (options?.radius) {\n request.radius = options.radius;\n }\n\n if (options?.types) {\n request.types = [options.types];\n }\n\n if (options?.language) {\n request.language = options.language;\n }\n\n // Call AutocompleteService\n return new Promise<GooglePlaceAutocompletePrediction[]>((resolve, reject) => {\n autocompleteService.getPlacePredictions(request, (predictions, status) => {\n if (status === 'OK' && predictions) {\n log.debug(`Received ${predictions.length} predictions`);\n // Convert to our format\n const result: GooglePlaceAutocompletePrediction[] = predictions.map((pred) => ({\n description: pred.description,\n place_id: pred.place_id,\n structured_formatting: {\n main_text: pred.structured_formatting.main_text,\n secondary_text: pred.structured_formatting.secondary_text,\n },\n }));\n resolve(result);\n } else if (status === 'ZERO_RESULTS') {\n log.debug('No results found');\n resolve([]);\n } else {\n const errorMsg = `Google Places API error: ${status}`;\n log.error('Autocomplete fetch failed:', errorMsg);\n reject(new Error(errorMsg));\n }\n });\n });\n } catch (error) {\n if (error instanceof Error) {\n log.error('Autocomplete fetch failed:', error.message);\n throw error;\n }\n log.error('Autocomplete fetch failed: Unknown error');\n throw new Error('Failed to fetch autocomplete predictions');\n }\n });\n}\n\n/**\n * Fetch place details from Google Places API\n *\n * @param placeId - Google Place ID\n * @param apiKey - Google Places API key\n * @returns Promise resolving to place details\n *\n * @example\n * ```ts\n * const place = await fetchPlaceDetails('ChIJ...', 'your-api-key');\n * ```\n */\nexport async function fetchPlaceDetails(\n placeId: string,\n apiKey: string\n): Promise<{\n place_id: string;\n formatted_address?: string;\n address_components?: Array<{\n long_name: string;\n short_name: string;\n types: string[];\n }>;\n geometry?: {\n location?: {\n lat: () => number;\n lng: () => number;\n };\n };\n}> {\n if (!placeId) {\n throw new Error('Place ID is required');\n }\n\n if (!apiKey) {\n throw new Error('Google Places API key is required');\n }\n\n // Ensure Google Maps script is loaded\n if (!isGoogleMapsLoaded()) {\n await loadGoogleMapsScript(apiKey, 'places');\n }\n\n const requestKey = `google-places-details:${placeId}`;\n\n return getOrCreateRequest(requestKey, async () => {\n try {\n log.debug(`Fetching place details for place_id: ${placeId}`);\n\n if (!window.google?.maps?.places) {\n throw new Error('Google Maps Places library not available');\n }\n\n // Create a dummy element for PlacesService (it needs an element but we don't use it)\n const dummyElement = document.createElement('div');\n const placesService = new window.google.maps.places.PlacesService(dummyElement);\n\n // Call getDetails\n return new Promise<{\n place_id: string;\n formatted_address?: string;\n address_components?: Array<{\n long_name: string;\n short_name: string;\n types: string[];\n }>;\n geometry?: {\n location?: {\n lat: () => number;\n lng: () => number;\n };\n };\n }>((resolve, reject) => {\n placesService.getDetails(\n {\n placeId: placeId,\n fields: ['place_id', 'formatted_address', 'address_components', 'geometry'],\n },\n (place, status) => {\n if (status === 'OK' && place) {\n log.debug('Place details fetched successfully');\n resolve(place as any);\n } else if (status === 'NOT_FOUND') {\n log.error('Place not found:', placeId);\n reject(new Error('Place not found'));\n } else {\n const errorMsg = `Failed to fetch place details: ${status}`;\n log.error('Place details fetch failed:', errorMsg);\n reject(new Error(errorMsg));\n }\n }\n );\n });\n } catch (error) {\n if (error instanceof Error) {\n log.error('Place details fetch failed:', error.message);\n throw error;\n }\n log.error('Place details fetch failed: Unknown error');\n throw new Error('Failed to fetch place details');\n }\n });\n}\n\n/**\n * Parse address components from Google Places API response\n *\n * @param components - Array of address components from Google API\n * @returns Parsed address components\n */\nexport function parseAddressComponents(\n components: Array<{\n long_name: string;\n short_name: string;\n types: string[];\n }> | undefined\n): {\n street_number: string | null;\n route: string | null;\n suburb: string | null;\n state: string | null;\n postcode: string | null;\n country: string | null;\n} {\n if (!components || components.length === 0) {\n return {\n street_number: null,\n route: null,\n suburb: null,\n state: null,\n postcode: null,\n country: null,\n };\n }\n\n const result = {\n street_number: null as string | null,\n route: null as string | null,\n suburb: null as string | null,\n state: null as string | null,\n postcode: null as string | null,\n country: null as string | null,\n };\n\n for (const component of components) {\n const types = component.types || [];\n\n if (types.includes('street_number')) {\n result.street_number = component.long_name;\n } else if (types.includes('route')) {\n result.route = component.long_name;\n } else if (types.includes('locality') || types.includes('sublocality')) {\n result.suburb = component.long_name;\n } else if (types.includes('administrative_area_level_1')) {\n result.state = component.short_name;\n } else if (types.includes('postal_code')) {\n result.postcode = component.long_name;\n } else if (types.includes('country')) {\n result.country = component.short_name;\n }\n }\n\n return result;\n}\n\n/**\n * Create parsed address from Google Places API place result\n *\n * @param place - Place result from Google Places Details API\n * @returns Parsed address matching core_address table structure\n */\nexport function createAddressFromPlaceResult(\n place: {\n place_id: string;\n formatted_address?: string;\n address_components?: Array<{\n long_name: string;\n short_name: string;\n types: string[];\n }>;\n geometry?: {\n location?: {\n lat: () => number;\n lng: () => number;\n };\n };\n }\n): ParsedAddress {\n const components = parseAddressComponents(place.address_components || []);\n\n return {\n place_id: place.place_id,\n full_address: place.formatted_address || null,\n street_number: components.street_number,\n route: components.route,\n suburb: components.suburb,\n state: components.state,\n postcode: components.postcode,\n country: components.country,\n lat: place.geometry?.location?.lat() ?? null,\n lng: place.geometry?.location?.lng() ?? null,\n };\n}\n\n/**\n * Get full address details by place_id\n * Useful for retrieving address from stored place_id without autocomplete search\n *\n * @param placeId - Google Place ID\n * @param apiKey - Google Places API key\n * @returns Promise resolving to parsed address\n *\n * @example\n * ```ts\n * const address = await getAddressByPlaceId('ChIJ...', 'your-api-key');\n * // Returns: { place_id: 'ChIJ...', full_address: '...', ... }\n * ```\n */\nexport async function getAddressByPlaceId(\n placeId: string,\n apiKey: string\n): Promise<ParsedAddress | null> {\n try {\n const place = await fetchPlaceDetails(placeId, apiKey);\n return createAddressFromPlaceResult(place);\n } catch (error) {\n log.error('Failed to get address by place_id:', error);\n return null;\n }\n}\n\n"],"mappings":"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","names":["log","request"]}
|