@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
package/src/utils/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export * from './validation';
|
|
|
21
21
|
// Explicitly re-export commonly used validation utilities to ensure they're available
|
|
22
22
|
// Import from source modules to avoid re-export issues
|
|
23
23
|
export { validateUserInput, usernameSchema } from './validation/validationUtils';
|
|
24
|
-
export { sanitizeUserInput, sanitizeFormData } from './validation/sanitization';
|
|
24
|
+
export { sanitizeUserInput, sanitizeFormData, sanitizeHtml } from './validation/sanitization';
|
|
25
25
|
export { emailSchema, nameSchema, phoneSchema, urlSchema } from './validation/common';
|
|
26
26
|
export { passwordSchema } from './validation/passwordSchema';
|
|
27
27
|
export { pickSchema, combineSchemas } from './validation/schema';
|
|
@@ -178,3 +178,6 @@ export {
|
|
|
178
178
|
getInFlightRequestStats,
|
|
179
179
|
deduplicatedQuery
|
|
180
180
|
} from './request-deduplication';
|
|
181
|
+
|
|
182
|
+
// Supabase client creation (restricted wrapper)
|
|
183
|
+
export { createBaseClient } from './supabase/createBaseClient';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Key Derivation Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Persistence/__tests__
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
deriveDataTableKey,
|
|
10
|
+
deriveDialogKey,
|
|
11
|
+
deriveFormKey,
|
|
12
|
+
hashStableFingerprint,
|
|
13
|
+
} from '../keyDerivation';
|
|
14
|
+
|
|
15
|
+
describe('keyDerivation', () => {
|
|
16
|
+
describe('deriveDataTableKey', () => {
|
|
17
|
+
it('should use rbacPageId as primary key', () => {
|
|
18
|
+
const key = deriveDataTableKey({
|
|
19
|
+
rbacPageId: 'user-management',
|
|
20
|
+
});
|
|
21
|
+
expect(key).toBe('datatable:user-management');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should fall back to title when rbacPageId not available', () => {
|
|
25
|
+
const key = deriveDataTableKey({
|
|
26
|
+
title: 'Users Table',
|
|
27
|
+
});
|
|
28
|
+
expect(key).toBe('datatable:users-table');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should use route pathname as fallback', () => {
|
|
32
|
+
const key = deriveDataTableKey(
|
|
33
|
+
{},
|
|
34
|
+
{ pathname: '/users' }
|
|
35
|
+
);
|
|
36
|
+
expect(key).toBe('datatable:/users');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should use column IDs hash as last resort', () => {
|
|
40
|
+
const key = deriveDataTableKey({
|
|
41
|
+
columnIds: ['id', 'name', 'email'],
|
|
42
|
+
});
|
|
43
|
+
expect(key).toMatch(/^datatable:[a-f0-9]+$/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return null if no stable key can be determined', () => {
|
|
47
|
+
const key = deriveDataTableKey({});
|
|
48
|
+
expect(key).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('deriveDialogKey', () => {
|
|
53
|
+
it('should use title as primary key', () => {
|
|
54
|
+
const key = deriveDialogKey({
|
|
55
|
+
title: 'Edit User',
|
|
56
|
+
});
|
|
57
|
+
expect(key).toBe('dialog:edit-user');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should use route pathname as fallback', () => {
|
|
61
|
+
const key = deriveDialogKey(
|
|
62
|
+
{},
|
|
63
|
+
{ pathname: '/users/edit' }
|
|
64
|
+
);
|
|
65
|
+
expect(key).toBe('dialog:/users/edit');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return null if no stable key can be determined', () => {
|
|
69
|
+
const key = deriveDialogKey({});
|
|
70
|
+
expect(key).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('deriveFormKey', () => {
|
|
75
|
+
it('should use parent dialog title when available', () => {
|
|
76
|
+
const key = deriveFormKey(
|
|
77
|
+
{},
|
|
78
|
+
{ dialogTitle: 'Edit User' }
|
|
79
|
+
);
|
|
80
|
+
expect(key).toBe('form:edit-user');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should use route + field names hash as fallback', () => {
|
|
84
|
+
const key = deriveFormKey(
|
|
85
|
+
{
|
|
86
|
+
fieldNames: ['name', 'email'],
|
|
87
|
+
},
|
|
88
|
+
null,
|
|
89
|
+
{ pathname: '/users' }
|
|
90
|
+
);
|
|
91
|
+
expect(key).toMatch(/^form:\/users:[a-f0-9]+$/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should use route pathname as last resort', () => {
|
|
95
|
+
const key = deriveFormKey(
|
|
96
|
+
{},
|
|
97
|
+
null,
|
|
98
|
+
{ pathname: '/users' }
|
|
99
|
+
);
|
|
100
|
+
expect(key).toBe('form:/users');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should return null if no stable key can be determined', () => {
|
|
104
|
+
const key = deriveFormKey({});
|
|
105
|
+
expect(key).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('hashStableFingerprint', () => {
|
|
110
|
+
it('should create stable hash from array of strings', () => {
|
|
111
|
+
const hash1 = hashStableFingerprint(['a', 'b', 'c']);
|
|
112
|
+
const hash2 = hashStableFingerprint(['a', 'b', 'c']);
|
|
113
|
+
expect(hash1).toBe(hash2);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should be order-independent', () => {
|
|
117
|
+
const hash1 = hashStableFingerprint(['a', 'b', 'c']);
|
|
118
|
+
const hash2 = hashStableFingerprint(['c', 'b', 'a']);
|
|
119
|
+
expect(hash1).toBe(hash2);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should normalize strings (lowercase, trim)', () => {
|
|
123
|
+
const hash1 = hashStableFingerprint(['A', ' B ', 'C']);
|
|
124
|
+
const hash2 = hashStableFingerprint(['a', 'b', 'c']);
|
|
125
|
+
expect(hash1).toBe(hash2);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should filter out empty values', () => {
|
|
129
|
+
const hash1 = hashStableFingerprint(['a', '', 'b', 'c']);
|
|
130
|
+
const hash2 = hashStableFingerprint(['a', 'b', 'c']);
|
|
131
|
+
expect(hash1).toBe(hash2);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Sensitive Field Detection Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Persistence/__tests__
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
isSensitiveField,
|
|
10
|
+
filterSensitiveFields,
|
|
11
|
+
getSensitiveFieldNames,
|
|
12
|
+
} from '../sensitiveFieldDetection';
|
|
13
|
+
|
|
14
|
+
describe('sensitiveFieldDetection', () => {
|
|
15
|
+
describe('isSensitiveField', () => {
|
|
16
|
+
it('should detect password input type', () => {
|
|
17
|
+
expect(isSensitiveField('password', 'password')).toBe(true);
|
|
18
|
+
expect(isSensitiveField('userPassword', 'password')).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should detect hidden input type', () => {
|
|
22
|
+
expect(isSensitiveField('token', 'hidden')).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should detect sensitive field names by pattern', () => {
|
|
26
|
+
expect(isSensitiveField('password', 'text')).toBe(true);
|
|
27
|
+
expect(isSensitiveField('user_password', 'text')).toBe(true);
|
|
28
|
+
expect(isSensitiveField('api_key', 'text')).toBe(true);
|
|
29
|
+
expect(isSensitiveField('secretToken', 'text')).toBe(true);
|
|
30
|
+
expect(isSensitiveField('credit_card', 'text')).toBe(true);
|
|
31
|
+
expect(isSensitiveField('ssn', 'text')).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should not detect non-sensitive fields', () => {
|
|
35
|
+
expect(isSensitiveField('name', 'text')).toBe(false);
|
|
36
|
+
expect(isSensitiveField('email', 'email')).toBe(false);
|
|
37
|
+
expect(isSensitiveField('age', 'number')).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should be case-insensitive', () => {
|
|
41
|
+
expect(isSensitiveField('PASSWORD', 'text')).toBe(true);
|
|
42
|
+
expect(isSensitiveField('Api_Key', 'text')).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('filterSensitiveFields', () => {
|
|
47
|
+
it('should filter out sensitive fields', () => {
|
|
48
|
+
const data = {
|
|
49
|
+
name: 'John',
|
|
50
|
+
email: 'john@example.com',
|
|
51
|
+
password: 'secret123',
|
|
52
|
+
api_key: 'key123',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const filtered = filterSensitiveFields(data, ['name', 'email', 'password', 'api_key']);
|
|
56
|
+
|
|
57
|
+
expect(filtered).toEqual({
|
|
58
|
+
name: 'John',
|
|
59
|
+
email: 'john@example.com',
|
|
60
|
+
});
|
|
61
|
+
expect(filtered).not.toHaveProperty('password');
|
|
62
|
+
expect(filtered).not.toHaveProperty('api_key');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should filter by input type', () => {
|
|
66
|
+
const data = {
|
|
67
|
+
name: 'John',
|
|
68
|
+
password: 'secret123',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const filtered = filterSensitiveFields(data, ['name', 'password'], {
|
|
72
|
+
password: 'password',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(filtered).toEqual({
|
|
76
|
+
name: 'John',
|
|
77
|
+
});
|
|
78
|
+
expect(filtered).not.toHaveProperty('password');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should return empty object if all fields are sensitive', () => {
|
|
82
|
+
const data = {
|
|
83
|
+
password: 'secret123',
|
|
84
|
+
api_key: 'key123',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const filtered = filterSensitiveFields(data, ['password', 'api_key']);
|
|
88
|
+
|
|
89
|
+
expect(filtered).toEqual({});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return all fields if none are sensitive', () => {
|
|
93
|
+
const data = {
|
|
94
|
+
name: 'John',
|
|
95
|
+
email: 'john@example.com',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const filtered = filterSensitiveFields(data, ['name', 'email']);
|
|
99
|
+
|
|
100
|
+
expect(filtered).toEqual(data);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('getSensitiveFieldNames', () => {
|
|
105
|
+
it('should return list of sensitive field names', () => {
|
|
106
|
+
const sensitive = getSensitiveFieldNames(
|
|
107
|
+
['name', 'email', 'password', 'api_key'],
|
|
108
|
+
{
|
|
109
|
+
password: 'password',
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(sensitive).toEqual(['password', 'api_key']);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should return empty array if no sensitive fields', () => {
|
|
117
|
+
const sensitive = getSensitiveFieldNames(['name', 'email']);
|
|
118
|
+
|
|
119
|
+
expect(sensitive).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Key Derivation Utilities
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Persistence
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* Utilities for automatically deriving stable persistence keys for components.
|
|
8
|
+
* Provides deterministic key generation based on component props, route, and context.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Automatic key derivation from component props
|
|
12
|
+
* - Route-based fallbacks when props unavailable
|
|
13
|
+
* - Stable hashing for fingerprint-based keys
|
|
14
|
+
* - Support for nested component contexts
|
|
15
|
+
*
|
|
16
|
+
* @dependencies
|
|
17
|
+
* - React Router (optional) - For route-based keys
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Simple hash function for creating stable fingerprints
|
|
22
|
+
* Uses djb2 algorithm for fast, stable hashing
|
|
23
|
+
*/
|
|
24
|
+
function hashString(str: string): string {
|
|
25
|
+
let hash = 5381;
|
|
26
|
+
for (let i = 0; i < str.length; i++) {
|
|
27
|
+
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
28
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
29
|
+
}
|
|
30
|
+
// Convert to positive hex string
|
|
31
|
+
return Math.abs(hash).toString(16);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a stable hash from an array of strings
|
|
36
|
+
*/
|
|
37
|
+
function hashStableFingerprint(values: string[]): string {
|
|
38
|
+
const normalized = values
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((v) => String(v).toLowerCase().trim())
|
|
41
|
+
.sort()
|
|
42
|
+
.join('|');
|
|
43
|
+
|
|
44
|
+
return hashString(normalized);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Normalize a string for use in storage keys
|
|
49
|
+
* Removes special characters and converts to lowercase
|
|
50
|
+
* Preserves path structure by keeping slashes
|
|
51
|
+
*/
|
|
52
|
+
function normalizeKey(str: string, preserveSlashes = false): string {
|
|
53
|
+
if (preserveSlashes) {
|
|
54
|
+
// For pathnames, preserve slashes but normalize other characters
|
|
55
|
+
const trimmed = str.toLowerCase().trim();
|
|
56
|
+
const hasLeadingSlash = trimmed.startsWith('/');
|
|
57
|
+
const parts = trimmed.split('/').map(segment => segment.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')).filter(Boolean);
|
|
58
|
+
const normalized = parts.join('/');
|
|
59
|
+
return hasLeadingSlash ? `/${normalized}` : normalized;
|
|
60
|
+
}
|
|
61
|
+
return str
|
|
62
|
+
.toLowerCase()
|
|
63
|
+
.trim()
|
|
64
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
65
|
+
.replace(/^-+|-+$/g, '');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get route pathname if React Router is available
|
|
70
|
+
* Returns null if router is not available
|
|
71
|
+
*/
|
|
72
|
+
function getRoutePathname(): string | null {
|
|
73
|
+
if (typeof window === 'undefined') {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Try to use React Router's useLocation if available
|
|
79
|
+
// Since we can't use hooks here, we'll use window.location as fallback
|
|
80
|
+
// Components using this should pass location from useLocation() hook
|
|
81
|
+
return window.location.pathname;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Props for DataTable key derivation
|
|
89
|
+
*/
|
|
90
|
+
export interface DataTableKeyProps {
|
|
91
|
+
/** RBAC page ID (mandatory, always available) */
|
|
92
|
+
rbacPageId?: string;
|
|
93
|
+
/** Table title */
|
|
94
|
+
title?: string;
|
|
95
|
+
/** Column IDs for fingerprint fallback */
|
|
96
|
+
columnIds?: string[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Derive a persistence key for DataTable component
|
|
101
|
+
*
|
|
102
|
+
* Priority order:
|
|
103
|
+
* 1. rbac.pageId (mandatory prop, always available)
|
|
104
|
+
* 2. title prop (normalized)
|
|
105
|
+
* 3. Hashed fingerprint of column IDs
|
|
106
|
+
* 4. Route pathname + component type (only if not root path)
|
|
107
|
+
*
|
|
108
|
+
* @param props - DataTable props
|
|
109
|
+
* @param location - Optional location from useLocation() hook
|
|
110
|
+
* @param userId - Optional user ID to scope persistence by user
|
|
111
|
+
* @returns Derived key or null if no stable key can be determined
|
|
112
|
+
*/
|
|
113
|
+
export function deriveDataTableKey(
|
|
114
|
+
props: DataTableKeyProps,
|
|
115
|
+
location?: { pathname: string } | null,
|
|
116
|
+
userId?: string | null
|
|
117
|
+
): string | null {
|
|
118
|
+
let baseKey: string | null = null;
|
|
119
|
+
|
|
120
|
+
// Priority 1: rbac.pageId (mandatory, always available)
|
|
121
|
+
if (props.rbacPageId) {
|
|
122
|
+
baseKey = `datatable:${normalizeKey(props.rbacPageId)}`;
|
|
123
|
+
}
|
|
124
|
+
// Priority 2: title prop
|
|
125
|
+
else if (props.title) {
|
|
126
|
+
baseKey = `datatable:${normalizeKey(props.title)}`;
|
|
127
|
+
}
|
|
128
|
+
// Priority 3: Hashed fingerprint of column IDs (before pathname fallback)
|
|
129
|
+
else if (props.columnIds && props.columnIds.length > 0) {
|
|
130
|
+
const fingerprint = hashStableFingerprint(props.columnIds);
|
|
131
|
+
if (fingerprint) {
|
|
132
|
+
baseKey = `datatable:${fingerprint}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Priority 4: Route pathname + component type (only if not root path)
|
|
136
|
+
else {
|
|
137
|
+
const pathname = location?.pathname || getRoutePathname();
|
|
138
|
+
if (pathname && pathname !== '/') {
|
|
139
|
+
// Preserve path structure with slashes
|
|
140
|
+
const normalized = normalizeKey(pathname, true);
|
|
141
|
+
baseKey = `datatable:${normalized}`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// No stable key can be determined
|
|
146
|
+
if (!baseKey) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Scope by user ID if provided (prevents data leakage between users)
|
|
151
|
+
if (userId) {
|
|
152
|
+
return `${baseKey}:user:${userId}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return baseKey;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Props for Dialog key derivation
|
|
160
|
+
*/
|
|
161
|
+
export interface DialogKeyProps {
|
|
162
|
+
/** Dialog title */
|
|
163
|
+
title?: string;
|
|
164
|
+
/** Dialog description (for fingerprint fallback) */
|
|
165
|
+
description?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Derive a persistence key for Dialog component
|
|
170
|
+
*
|
|
171
|
+
* Priority order:
|
|
172
|
+
* 1. title prop (if stable and provided)
|
|
173
|
+
* 2. Route pathname + component type
|
|
174
|
+
* 3. Hashed fingerprint of dialog content structure
|
|
175
|
+
*
|
|
176
|
+
* @param props - Dialog props
|
|
177
|
+
* @param location - Optional location from useLocation() hook
|
|
178
|
+
* @param userId - Optional user ID to scope persistence by user
|
|
179
|
+
* @returns Derived key or null if no stable key can be determined
|
|
180
|
+
*/
|
|
181
|
+
export function deriveDialogKey(
|
|
182
|
+
props: DialogKeyProps,
|
|
183
|
+
location?: { pathname: string } | null,
|
|
184
|
+
userId?: string | null
|
|
185
|
+
): string | null {
|
|
186
|
+
let baseKey: string | null = null;
|
|
187
|
+
|
|
188
|
+
// Priority 1: title prop
|
|
189
|
+
if (props.title) {
|
|
190
|
+
baseKey = `dialog:${normalizeKey(props.title)}`;
|
|
191
|
+
}
|
|
192
|
+
// Priority 2: Route pathname + component type
|
|
193
|
+
else {
|
|
194
|
+
const pathname = location?.pathname || getRoutePathname();
|
|
195
|
+
if (pathname && pathname !== '/') {
|
|
196
|
+
// Preserve path structure with slashes
|
|
197
|
+
const normalized = normalizeKey(pathname, true);
|
|
198
|
+
baseKey = `dialog:${normalized}`;
|
|
199
|
+
}
|
|
200
|
+
// Priority 3: Hashed fingerprint of dialog structure
|
|
201
|
+
else {
|
|
202
|
+
const fingerprintParts: string[] = [];
|
|
203
|
+
if (props.description) {
|
|
204
|
+
fingerprintParts.push(props.description);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (fingerprintParts.length > 0) {
|
|
208
|
+
const fingerprint = hashStableFingerprint(fingerprintParts);
|
|
209
|
+
baseKey = `dialog:${fingerprint}`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// No stable key can be determined
|
|
215
|
+
if (!baseKey) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Scope by user ID if provided (prevents data leakage between users)
|
|
220
|
+
if (userId) {
|
|
221
|
+
return `${baseKey}:user:${userId}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return baseKey;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Props for Form key derivation
|
|
229
|
+
*/
|
|
230
|
+
export interface FormKeyProps {
|
|
231
|
+
/** Form field names (for fingerprint fallback) */
|
|
232
|
+
fieldNames?: string[];
|
|
233
|
+
/** Parent dialog title (if form is inside dialog) */
|
|
234
|
+
parentDialogTitle?: string;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Derive a persistence key for Form component
|
|
239
|
+
*
|
|
240
|
+
* Priority order:
|
|
241
|
+
* 1. Parent Dialog's title (if inside Dialog)
|
|
242
|
+
* 2. Route pathname + form field names hash
|
|
243
|
+
* 3. Route pathname + component type
|
|
244
|
+
*
|
|
245
|
+
* @param props - Form props
|
|
246
|
+
* @param parentContext - Optional parent context (e.g., Dialog title)
|
|
247
|
+
* @param location - Optional location from useLocation() hook
|
|
248
|
+
* @param userId - Optional user ID to scope persistence by user
|
|
249
|
+
* @returns Derived key or null if no stable key can be determined
|
|
250
|
+
*/
|
|
251
|
+
export function deriveFormKey(
|
|
252
|
+
props: FormKeyProps = {},
|
|
253
|
+
parentContext?: { dialogTitle?: string } | null,
|
|
254
|
+
location?: { pathname: string } | null,
|
|
255
|
+
userId?: string | null
|
|
256
|
+
): string | null {
|
|
257
|
+
let baseKey: string | null = null;
|
|
258
|
+
|
|
259
|
+
// Priority 1: Parent Dialog's title
|
|
260
|
+
const dialogTitle = parentContext?.dialogTitle || props.parentDialogTitle;
|
|
261
|
+
if (dialogTitle) {
|
|
262
|
+
baseKey = `form:${normalizeKey(dialogTitle)}`;
|
|
263
|
+
}
|
|
264
|
+
// Priority 2: Route pathname + form field names hash
|
|
265
|
+
else {
|
|
266
|
+
const pathname = location?.pathname || getRoutePathname();
|
|
267
|
+
if (pathname && pathname !== '/' && props.fieldNames && props.fieldNames.length > 0) {
|
|
268
|
+
const fieldHash = hashStableFingerprint(props.fieldNames);
|
|
269
|
+
if (fieldHash) {
|
|
270
|
+
// Preserve path structure with slashes
|
|
271
|
+
const normalized = normalizeKey(pathname, true);
|
|
272
|
+
baseKey = `form:${normalized}:${fieldHash}`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Priority 3: Route pathname + component type
|
|
276
|
+
else if (pathname && pathname !== '/') {
|
|
277
|
+
// Preserve path structure with slashes
|
|
278
|
+
const normalized = normalizeKey(pathname, true);
|
|
279
|
+
baseKey = `form:${normalized}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// No stable key can be determined
|
|
284
|
+
if (!baseKey) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Scope by user ID if provided (prevents data leakage between users)
|
|
289
|
+
if (userId) {
|
|
290
|
+
return `${baseKey}:user:${userId}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return baseKey;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Create a stable hash from an array of values
|
|
298
|
+
* Exported for use in components that need custom fingerprinting
|
|
299
|
+
*
|
|
300
|
+
* @param values - Array of string values to hash
|
|
301
|
+
* @returns Stable hash string
|
|
302
|
+
*/
|
|
303
|
+
export { hashStableFingerprint };
|
|
304
|
+
|