@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
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
# Operations Standards
|
|
2
|
+
|
|
3
|
+
**🤖 Cursor Rule**: See [09-operations.mdc](../../cursor-rules/09-operations.mdc) for AI-optimized directives that automatically enforce error handling, performance, and CI/CD best practices.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This standard defines error handling patterns, performance optimization strategies, and CI/CD integration patterns to ensure reliable, performant, and maintainable applications.
|
|
8
|
+
|
|
9
|
+
**Note:** RLS-specific performance requirements are covered in [Security & RBAC Standards](./6-security-rbac-standards.md). This document focuses on general application performance.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Error Handling Patterns
|
|
14
|
+
|
|
15
|
+
### Principles
|
|
16
|
+
|
|
17
|
+
1. **Never expose internal details** in user-facing error messages
|
|
18
|
+
2. **Always use type-safe error handling** (no `any` types)
|
|
19
|
+
3. **Log errors appropriately** (with context, without sensitive data)
|
|
20
|
+
4. **Provide recovery paths** when possible
|
|
21
|
+
5. **Use consistent error shapes** across the application
|
|
22
|
+
|
|
23
|
+
### Error Types
|
|
24
|
+
|
|
25
|
+
#### API Errors
|
|
26
|
+
|
|
27
|
+
**Shape:**
|
|
28
|
+
```typescript
|
|
29
|
+
type ApiError = {
|
|
30
|
+
ok: false;
|
|
31
|
+
error: {
|
|
32
|
+
code: string; // Machine-readable error code
|
|
33
|
+
message: string; // User-friendly message
|
|
34
|
+
details?: object; // Optional additional context (non-sensitive)
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Example:**
|
|
40
|
+
```typescript
|
|
41
|
+
// ✅ CORRECT
|
|
42
|
+
const result = await apiCall();
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
// result.error.message is user-friendly
|
|
45
|
+
toast.error(result.error.message);
|
|
46
|
+
logger.error('API call failed', { code: result.error.code, details: result.error.details });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ❌ WRONG - Exposing internal details
|
|
50
|
+
if (error.message.includes('SQL')) {
|
|
51
|
+
toast.error(error.message); // Exposes database internals
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Validation Errors
|
|
56
|
+
|
|
57
|
+
**Shape:**
|
|
58
|
+
```typescript
|
|
59
|
+
type ValidationError = {
|
|
60
|
+
field: string;
|
|
61
|
+
message: string;
|
|
62
|
+
code?: string;
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Example:**
|
|
67
|
+
```typescript
|
|
68
|
+
// ✅ CORRECT - Using Zod validation
|
|
69
|
+
const schema = z.object({
|
|
70
|
+
email: z.string().email('Please enter a valid email address'),
|
|
71
|
+
name: z.string().min(1, 'Name is required'),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const data = schema.parse(formData);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error instanceof z.ZodError) {
|
|
78
|
+
// User-friendly validation messages
|
|
79
|
+
error.errors.forEach(err => {
|
|
80
|
+
toast.error(err.message);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Network Errors
|
|
87
|
+
|
|
88
|
+
**Pattern:**
|
|
89
|
+
```typescript
|
|
90
|
+
// ✅ CORRECT - Handle network errors gracefully
|
|
91
|
+
try {
|
|
92
|
+
const data = await fetchData();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof TypeError && error.message.includes('fetch')) {
|
|
95
|
+
toast.error('Unable to connect. Please check your internet connection.');
|
|
96
|
+
logger.error('Network error', { error: error.message });
|
|
97
|
+
} else {
|
|
98
|
+
toast.error('An unexpected error occurred. Please try again.');
|
|
99
|
+
logger.error('Unexpected error', { error });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Error Handling Patterns
|
|
105
|
+
|
|
106
|
+
#### Pattern 1: Try-Catch with Type Guards
|
|
107
|
+
|
|
108
|
+
**Use for:** Known error types that need different handling
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// ✅ CORRECT
|
|
112
|
+
function isApiError(error: unknown): error is ApiError {
|
|
113
|
+
return (
|
|
114
|
+
typeof error === 'object' &&
|
|
115
|
+
error !== null &&
|
|
116
|
+
'ok' in error &&
|
|
117
|
+
(error as ApiError).ok === false &&
|
|
118
|
+
'error' in error
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const result = await apiCall();
|
|
124
|
+
if (!result.ok) {
|
|
125
|
+
handleApiError(result.error);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (isApiError(error)) {
|
|
129
|
+
handleApiError(error.error);
|
|
130
|
+
} else {
|
|
131
|
+
handleUnknownError(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Pattern 2: Result Types
|
|
137
|
+
|
|
138
|
+
**Use for:** Functions that can fail (preferred pattern)
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// ✅ CORRECT - Using Result type
|
|
142
|
+
type Result<T, E = ApiError> =
|
|
143
|
+
| { ok: true; data: T }
|
|
144
|
+
| { ok: false; error: E };
|
|
145
|
+
|
|
146
|
+
async function fetchUser(id: string): Promise<Result<User>> {
|
|
147
|
+
try {
|
|
148
|
+
const { data, error } = await supabase.from('users').select('*').eq('id', id).single();
|
|
149
|
+
if (error) {
|
|
150
|
+
return { ok: false, error: { code: 'USER_NOT_FOUND', message: 'User not found' } };
|
|
151
|
+
}
|
|
152
|
+
return { ok: true, data };
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
error: { code: 'UNKNOWN_ERROR', message: 'An unexpected error occurred' }
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Usage
|
|
162
|
+
const result = await fetchUser(userId);
|
|
163
|
+
if (result.ok) {
|
|
164
|
+
// TypeScript knows result.data exists
|
|
165
|
+
setUser(result.data);
|
|
166
|
+
} else {
|
|
167
|
+
// TypeScript knows result.error exists
|
|
168
|
+
toast.error(result.error.message);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Real-World Example: Form Submission with Validation**
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// ✅ CORRECT - Real-world form submission with Result type
|
|
176
|
+
async function submitEventForm(formData: EventFormData): Promise<Result<Event>> {
|
|
177
|
+
// 1. Validate input
|
|
178
|
+
const validation = eventFormSchema.safeParse(formData);
|
|
179
|
+
if (!validation.success) {
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
error: {
|
|
183
|
+
code: 'VALIDATION_ERROR',
|
|
184
|
+
message: 'Please check your input and try again',
|
|
185
|
+
details: validation.error.errors,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 2. Check permissions
|
|
191
|
+
const { canCreate, isLoading } = useResourcePermissions(RESOURCE_NAMES.EVENTS);
|
|
192
|
+
if (isLoading) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
error: {
|
|
196
|
+
code: 'PERMISSION_CHECK_IN_PROGRESS',
|
|
197
|
+
message: 'Please wait while we verify your permissions',
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (!canCreate({ organisationId: formData.organisationId })) {
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
error: {
|
|
205
|
+
code: 'PERMISSION_DENIED',
|
|
206
|
+
message: 'You do not have permission to create events',
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 3. Submit to API
|
|
212
|
+
try {
|
|
213
|
+
const { data, error } = await secureSupabase.rpc('app_events_create', {
|
|
214
|
+
p_name: formData.name,
|
|
215
|
+
p_date: formData.date,
|
|
216
|
+
p_organisation_id: formData.organisationId,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (error) {
|
|
220
|
+
// Map database errors to user-friendly messages
|
|
221
|
+
if (error.code === '23505') { // Unique constraint violation
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
error: {
|
|
225
|
+
code: 'DUPLICATE_EVENT',
|
|
226
|
+
message: 'An event with this name already exists',
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
error: {
|
|
233
|
+
code: 'CREATE_FAILED',
|
|
234
|
+
message: 'Unable to create event. Please try again.',
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { ok: true, data };
|
|
240
|
+
} catch (error) {
|
|
241
|
+
logger.error('Unexpected error creating event', { error, formData: { ...formData, password: '[REDACTED]' } });
|
|
242
|
+
return {
|
|
243
|
+
ok: false,
|
|
244
|
+
error: {
|
|
245
|
+
code: 'UNKNOWN_ERROR',
|
|
246
|
+
message: 'An unexpected error occurred. Please try again.',
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Usage in component
|
|
253
|
+
async function handleSubmit(formData: EventFormData) {
|
|
254
|
+
setIsSubmitting(true);
|
|
255
|
+
const result = await submitEventForm(formData);
|
|
256
|
+
|
|
257
|
+
if (result.ok) {
|
|
258
|
+
toast.success('Event created successfully');
|
|
259
|
+
navigate(`/events/${result.data.id}`);
|
|
260
|
+
} else {
|
|
261
|
+
// Handle different error types
|
|
262
|
+
if (result.error.code === 'VALIDATION_ERROR') {
|
|
263
|
+
// Show field-level errors
|
|
264
|
+
setFieldErrors(result.error.details);
|
|
265
|
+
} else {
|
|
266
|
+
// Show general error
|
|
267
|
+
toast.error(result.error.message);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
setIsSubmitting(false);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### Pattern 3: Error Boundaries (React)
|
|
276
|
+
|
|
277
|
+
**Use for:** Catching React component errors
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
// ✅ CORRECT - Error boundary component
|
|
281
|
+
import { ErrorBoundary } from '@jmruthers/pace-core';
|
|
282
|
+
|
|
283
|
+
function App() {
|
|
284
|
+
return (
|
|
285
|
+
<ErrorBoundary
|
|
286
|
+
fallback={<ErrorFallback />}
|
|
287
|
+
onError={(error, errorInfo) => {
|
|
288
|
+
logger.error('React error boundary caught error', { error, errorInfo });
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
<YourApp />
|
|
292
|
+
</ErrorBoundary>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Pattern 4: Async Error Handling
|
|
298
|
+
|
|
299
|
+
**Use for:** Async operations with proper error handling
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// ✅ CORRECT - Async with error handling
|
|
303
|
+
async function loadData() {
|
|
304
|
+
try {
|
|
305
|
+
setIsLoading(true);
|
|
306
|
+
const result = await fetchData();
|
|
307
|
+
if (!result.ok) {
|
|
308
|
+
throw new Error(result.error.message);
|
|
309
|
+
}
|
|
310
|
+
setData(result.data);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
handleError(error);
|
|
313
|
+
} finally {
|
|
314
|
+
setIsLoading(false);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Error Messages
|
|
320
|
+
|
|
321
|
+
#### User-Facing Messages
|
|
322
|
+
|
|
323
|
+
**MUST:**
|
|
324
|
+
- Be user-friendly and actionable
|
|
325
|
+
- Not expose internal details (SQL, stack traces, file paths)
|
|
326
|
+
- Provide context when helpful
|
|
327
|
+
- Be consistent in tone and style
|
|
328
|
+
|
|
329
|
+
**Examples:**
|
|
330
|
+
```typescript
|
|
331
|
+
// ✅ CORRECT - User-friendly
|
|
332
|
+
'Unable to save changes. Please try again.'
|
|
333
|
+
'This field is required.'
|
|
334
|
+
'Invalid email address format.'
|
|
335
|
+
|
|
336
|
+
// ❌ WRONG - Exposes internals
|
|
337
|
+
'SQLSTATE[23000]: Integrity constraint violation'
|
|
338
|
+
'Cannot read property "data" of undefined'
|
|
339
|
+
'/app/src/services/api.ts:123:45'
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
#### Logging Messages
|
|
343
|
+
|
|
344
|
+
**MUST:**
|
|
345
|
+
- Include error context (user ID, operation, etc.)
|
|
346
|
+
- Not log sensitive data (passwords, tokens, PII)
|
|
347
|
+
- Use structured logging when possible
|
|
348
|
+
- Include error codes for correlation
|
|
349
|
+
|
|
350
|
+
**Examples:**
|
|
351
|
+
```typescript
|
|
352
|
+
// ✅ CORRECT - Structured logging
|
|
353
|
+
logger.error('Failed to save user', {
|
|
354
|
+
userId: user.id,
|
|
355
|
+
operation: 'updateUser',
|
|
356
|
+
errorCode: error.code,
|
|
357
|
+
timestamp: new Date().toISOString(),
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// ❌ WRONG - Logs sensitive data
|
|
361
|
+
logger.error('Failed to save user', {
|
|
362
|
+
password: user.password, // NEVER log passwords
|
|
363
|
+
token: authToken, // NEVER log tokens
|
|
364
|
+
ssn: user.ssn, // NEVER log PII
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Error Recovery
|
|
369
|
+
|
|
370
|
+
#### Retry Logic
|
|
371
|
+
|
|
372
|
+
**Use for:** Transient errors (network, timeouts)
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// ✅ CORRECT - Retry with exponential backoff
|
|
376
|
+
async function fetchWithRetry<T>(
|
|
377
|
+
fn: () => Promise<Result<T>>,
|
|
378
|
+
maxRetries = 3
|
|
379
|
+
): Promise<Result<T>> {
|
|
380
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
381
|
+
const result = await fn();
|
|
382
|
+
if (result.ok) {
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Don't retry on client errors (4xx)
|
|
387
|
+
if (result.error.code?.startsWith('4')) {
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Exponential backoff
|
|
392
|
+
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return { ok: false, error: { code: 'MAX_RETRIES', message: 'Operation failed after retries' } };
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Real-World Example: File Upload with Retry**
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// ✅ CORRECT - File upload with retry and progress tracking
|
|
403
|
+
async function uploadFileWithRetry(
|
|
404
|
+
file: File,
|
|
405
|
+
onProgress?: (progress: number) => void
|
|
406
|
+
): Promise<Result<{ url: string }>> {
|
|
407
|
+
const maxRetries = 3;
|
|
408
|
+
|
|
409
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
410
|
+
try {
|
|
411
|
+
const formData = new FormData();
|
|
412
|
+
formData.append('file', file);
|
|
413
|
+
|
|
414
|
+
const xhr = new XMLHttpRequest();
|
|
415
|
+
|
|
416
|
+
// Track upload progress
|
|
417
|
+
xhr.upload.addEventListener('progress', (e) => {
|
|
418
|
+
if (e.lengthComputable && onProgress) {
|
|
419
|
+
const progress = (e.loaded / e.total) * 100;
|
|
420
|
+
onProgress(progress);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const result = await new Promise<Result<{ url: string }>>((resolve) => {
|
|
425
|
+
xhr.addEventListener('load', () => {
|
|
426
|
+
if (xhr.status === 200) {
|
|
427
|
+
resolve({ ok: true, data: JSON.parse(xhr.responseText) });
|
|
428
|
+
} else {
|
|
429
|
+
resolve({
|
|
430
|
+
ok: false,
|
|
431
|
+
error: {
|
|
432
|
+
code: `HTTP_${xhr.status}`,
|
|
433
|
+
message: 'File upload failed. Please try again.',
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
xhr.addEventListener('error', () => {
|
|
440
|
+
resolve({
|
|
441
|
+
ok: false,
|
|
442
|
+
error: {
|
|
443
|
+
code: 'NETWORK_ERROR',
|
|
444
|
+
message: 'Network error. Please check your connection.',
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
xhr.addEventListener('timeout', () => {
|
|
450
|
+
resolve({
|
|
451
|
+
ok: false,
|
|
452
|
+
error: {
|
|
453
|
+
code: 'TIMEOUT',
|
|
454
|
+
message: 'Upload timed out. Please try again.',
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
xhr.open('POST', '/api/upload');
|
|
460
|
+
xhr.send(formData);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
if (result.ok) {
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Don't retry on client errors (4xx) or permission errors
|
|
468
|
+
if (result.error.code?.startsWith('HTTP_4') || result.error.code === 'PERMISSION_DENIED') {
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Exponential backoff before retry
|
|
473
|
+
if (attempt < maxRetries - 1) {
|
|
474
|
+
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
|
|
475
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
476
|
+
}
|
|
477
|
+
} catch (error) {
|
|
478
|
+
logger.error('File upload error', { error, attempt, fileName: file.name });
|
|
479
|
+
|
|
480
|
+
if (attempt === maxRetries - 1) {
|
|
481
|
+
return {
|
|
482
|
+
ok: false,
|
|
483
|
+
error: {
|
|
484
|
+
code: 'UPLOAD_FAILED',
|
|
485
|
+
message: 'File upload failed after multiple attempts. Please try again later.',
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
ok: false,
|
|
494
|
+
error: {
|
|
495
|
+
code: 'MAX_RETRIES',
|
|
496
|
+
message: 'File upload failed after multiple attempts.',
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Usage in component
|
|
502
|
+
function FileUploader() {
|
|
503
|
+
const [uploadProgress, setUploadProgress] = useState(0);
|
|
504
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
505
|
+
|
|
506
|
+
async function handleFileSelect(file: File) {
|
|
507
|
+
setIsUploading(true);
|
|
508
|
+
setUploadProgress(0);
|
|
509
|
+
|
|
510
|
+
const result = await uploadFileWithRetry(file, (progress) => {
|
|
511
|
+
setUploadProgress(progress);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
if (result.ok) {
|
|
515
|
+
toast.success('File uploaded successfully');
|
|
516
|
+
setFileUrl(result.data.url);
|
|
517
|
+
} else {
|
|
518
|
+
toast.error(result.error.message);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
setIsUploading(false);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return (
|
|
525
|
+
<div>
|
|
526
|
+
<input type="file" onChange={(e) => e.target.files?.[0] && handleFileSelect(e.target.files[0])} />
|
|
527
|
+
{isUploading && <ProgressBar value={uploadProgress} />}
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
#### Fallback Values
|
|
534
|
+
|
|
535
|
+
**Use for:** Non-critical data that can have defaults
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
// ✅ CORRECT - Fallback to default
|
|
539
|
+
const userPreferences = await fetchUserPreferences().catch(() => ({
|
|
540
|
+
theme: 'light',
|
|
541
|
+
language: 'en',
|
|
542
|
+
}));
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
#### Graceful Degradation
|
|
546
|
+
|
|
547
|
+
**Use for:** Features that can work without optional data
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
// ✅ CORRECT - Graceful degradation
|
|
551
|
+
async function loadDashboard() {
|
|
552
|
+
const [events, stats, preferences] = await Promise.allSettled([
|
|
553
|
+
fetchEvents(),
|
|
554
|
+
fetchStats(),
|
|
555
|
+
fetchPreferences(),
|
|
556
|
+
]);
|
|
557
|
+
|
|
558
|
+
// Use data that loaded successfully
|
|
559
|
+
if (events.status === 'fulfilled') {
|
|
560
|
+
setEvents(events.value);
|
|
561
|
+
} else {
|
|
562
|
+
logger.warn('Events failed to load', events.reason);
|
|
563
|
+
setEvents([]); // Fallback to empty array
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Similar for stats and preferences
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Performance Optimization
|
|
573
|
+
|
|
574
|
+
### React Performance
|
|
575
|
+
|
|
576
|
+
#### Memoization
|
|
577
|
+
|
|
578
|
+
**Use `useMemo` for expensive computations:**
|
|
579
|
+
|
|
580
|
+
```tsx
|
|
581
|
+
// ✅ CORRECT - Memoize expensive computation
|
|
582
|
+
const expensiveValue = useMemo(() => {
|
|
583
|
+
return computeExpensiveValue(data);
|
|
584
|
+
}, [data]);
|
|
585
|
+
|
|
586
|
+
// ❌ WRONG - Recomputes on every render
|
|
587
|
+
const expensiveValue = computeExpensiveValue(data);
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**Use `useCallback` for stable function references:**
|
|
591
|
+
|
|
592
|
+
```tsx
|
|
593
|
+
// ✅ CORRECT - Stable callback reference
|
|
594
|
+
const handleClick = useCallback(() => {
|
|
595
|
+
doSomething(id);
|
|
596
|
+
}, [id]);
|
|
597
|
+
|
|
598
|
+
// ❌ WRONG - New function on every render
|
|
599
|
+
const handleClick = () => {
|
|
600
|
+
doSomething(id);
|
|
601
|
+
};
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Use `React.memo` for expensive components:**
|
|
605
|
+
|
|
606
|
+
```tsx
|
|
607
|
+
// ✅ CORRECT - Memoize component
|
|
608
|
+
const ExpensiveComponent = React.memo(({ data }) => {
|
|
609
|
+
return <div>{/* expensive rendering */}</div>;
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// ❌ WRONG - Re-renders unnecessarily
|
|
613
|
+
function ExpensiveComponent({ data }) {
|
|
614
|
+
return <div>{/* expensive rendering */}</div>;
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
#### Avoiding Unnecessary Re-renders
|
|
619
|
+
|
|
620
|
+
**Don't create new objects/arrays in render:**
|
|
621
|
+
|
|
622
|
+
```tsx
|
|
623
|
+
// ❌ WRONG - New object on every render
|
|
624
|
+
function Component({ items }) {
|
|
625
|
+
const config = { items, enabled: true };
|
|
626
|
+
return <Child config={config} />;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// ✅ CORRECT - Memoize object
|
|
630
|
+
function Component({ items }) {
|
|
631
|
+
const config = useMemo(() => ({ items, enabled: true }), [items]);
|
|
632
|
+
return <Child config={config} />;
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
#### Code Splitting
|
|
637
|
+
|
|
638
|
+
**Lazy load heavy components:**
|
|
639
|
+
|
|
640
|
+
```tsx
|
|
641
|
+
// ✅ CORRECT - Lazy load
|
|
642
|
+
import { lazy, Suspense } from 'react';
|
|
643
|
+
const HeavyComponent = lazy(() => import('./HeavyComponent'));
|
|
644
|
+
|
|
645
|
+
function App() {
|
|
646
|
+
return (
|
|
647
|
+
<Suspense fallback={<Loading />}>
|
|
648
|
+
<HeavyComponent />
|
|
649
|
+
</Suspense>
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Database Performance
|
|
655
|
+
|
|
656
|
+
#### Query Optimization
|
|
657
|
+
|
|
658
|
+
**Use indexes for frequently queried columns:**
|
|
659
|
+
|
|
660
|
+
```sql
|
|
661
|
+
-- ✅ CORRECT - Index on frequently queried column
|
|
662
|
+
CREATE INDEX idx_users_organisation_id ON users(organisation_id);
|
|
663
|
+
CREATE INDEX idx_events_organisation_id_event_id ON events(organisation_id, event_id);
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**Avoid N+1 queries:**
|
|
667
|
+
|
|
668
|
+
```tsx
|
|
669
|
+
// ❌ WRONG - N+1 queries
|
|
670
|
+
const events = await fetchEvents();
|
|
671
|
+
for (const event of events) {
|
|
672
|
+
const users = await fetchEventUsers(event.id); // N queries!
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ✅ CORRECT - Single query with join
|
|
676
|
+
const eventsWithUsers = await fetchEventsWithUsers(); // 1 query
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**Use RPC functions for complex queries:**
|
|
680
|
+
|
|
681
|
+
```tsx
|
|
682
|
+
// ✅ CORRECT - RPC for complex query
|
|
683
|
+
const { data } = await supabase.rpc('data_events_list', {
|
|
684
|
+
organisation_id: orgId
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// ❌ AVOID - Complex client-side query
|
|
688
|
+
const { data } = await supabase
|
|
689
|
+
.from('events')
|
|
690
|
+
.select('*, users(*), organisations(*)')
|
|
691
|
+
.eq('organisation_id', orgId);
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### Caching Strategies
|
|
695
|
+
|
|
696
|
+
#### TanStack Query Configuration
|
|
697
|
+
|
|
698
|
+
**Configure appropriate cache times:**
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
// ✅ CORRECT - Configure cache
|
|
702
|
+
const queryClient = new QueryClient({
|
|
703
|
+
defaultOptions: {
|
|
704
|
+
queries: {
|
|
705
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
706
|
+
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
|
|
707
|
+
retry: 1,
|
|
708
|
+
refetchOnWindowFocus: false, // Prevent unnecessary refetches
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
**Use query keys effectively:**
|
|
715
|
+
|
|
716
|
+
```tsx
|
|
717
|
+
// ✅ CORRECT - Specific query keys
|
|
718
|
+
const { data } = useQuery({
|
|
719
|
+
queryKey: ['events', organisationId, eventId],
|
|
720
|
+
queryFn: () => fetchEvent(organisationId, eventId),
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
// ❌ WRONG - Too generic
|
|
724
|
+
const { data } = useQuery({
|
|
725
|
+
queryKey: ['events'],
|
|
726
|
+
queryFn: () => fetchEvent(organisationId, eventId),
|
|
727
|
+
});
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
#### React Query Optimizations
|
|
731
|
+
|
|
732
|
+
**Use `keepPreviousData` for pagination:**
|
|
733
|
+
|
|
734
|
+
```tsx
|
|
735
|
+
// ✅ CORRECT - Keep previous data during pagination
|
|
736
|
+
const { data } = useQuery({
|
|
737
|
+
queryKey: ['events', page],
|
|
738
|
+
queryFn: () => fetchEvents(page),
|
|
739
|
+
keepPreviousData: true,
|
|
740
|
+
});
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
**Use `select` to transform data efficiently:**
|
|
744
|
+
|
|
745
|
+
```tsx
|
|
746
|
+
// ✅ CORRECT - Transform in select (only runs when data changes)
|
|
747
|
+
const { data: eventCount } = useQuery({
|
|
748
|
+
queryKey: ['events'],
|
|
749
|
+
queryFn: fetchEvents,
|
|
750
|
+
select: (data) => data.length,
|
|
751
|
+
});
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Bundle Size Optimization
|
|
755
|
+
|
|
756
|
+
#### Tree Shaking
|
|
757
|
+
|
|
758
|
+
**Use named imports:**
|
|
759
|
+
|
|
760
|
+
```tsx
|
|
761
|
+
// ✅ CORRECT - Tree-shakeable
|
|
762
|
+
import { Button, Card } from '@jmruthers/pace-core';
|
|
763
|
+
|
|
764
|
+
// ❌ WRONG - Imports entire library
|
|
765
|
+
import * as PaceCore from '@jmruthers/pace-core';
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
#### Dynamic Imports
|
|
769
|
+
|
|
770
|
+
**Lazy load heavy dependencies:**
|
|
771
|
+
|
|
772
|
+
```tsx
|
|
773
|
+
// ✅ CORRECT - Dynamic import
|
|
774
|
+
const HeavyLibrary = lazy(() => import('heavy-library'));
|
|
775
|
+
|
|
776
|
+
// ❌ WRONG - Eager import
|
|
777
|
+
import HeavyLibrary from 'heavy-library';
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### Performance Monitoring
|
|
781
|
+
|
|
782
|
+
#### Measuring Performance
|
|
783
|
+
|
|
784
|
+
**Use React DevTools Profiler:**
|
|
785
|
+
|
|
786
|
+
1. Open React DevTools
|
|
787
|
+
2. Go to Profiler tab
|
|
788
|
+
3. Record a session
|
|
789
|
+
4. Identify slow components
|
|
790
|
+
5. Optimize based on findings
|
|
791
|
+
|
|
792
|
+
**Use browser DevTools:**
|
|
793
|
+
|
|
794
|
+
1. Open Performance tab
|
|
795
|
+
2. Record page load
|
|
796
|
+
3. Identify bottlenecks
|
|
797
|
+
4. Optimize critical rendering path
|
|
798
|
+
|
|
799
|
+
#### Performance Metrics
|
|
800
|
+
|
|
801
|
+
**Track these metrics:**
|
|
802
|
+
|
|
803
|
+
- **Time to First Byte (TTFB)** - < 200ms
|
|
804
|
+
- **First Contentful Paint (FCP)** - < 1.8s
|
|
805
|
+
- **Largest Contentful Paint (LCP)** - < 2.5s
|
|
806
|
+
- **Time to Interactive (TTI)** - < 3.8s
|
|
807
|
+
- **Cumulative Layout Shift (CLS)** - < 0.1
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## CI/CD Integration
|
|
812
|
+
|
|
813
|
+
### Principles
|
|
814
|
+
|
|
815
|
+
1. **Automate everything** - Manual steps are error-prone
|
|
816
|
+
2. **Fail fast** - Catch issues early in the pipeline
|
|
817
|
+
3. **Test before deploy** - Never deploy untested code
|
|
818
|
+
4. **Security first** - Scan for vulnerabilities
|
|
819
|
+
5. **Consistent environments** - Dev, staging, production parity
|
|
820
|
+
|
|
821
|
+
### CI/CD Pipeline Structure
|
|
822
|
+
|
|
823
|
+
#### Standard Pipeline Stages
|
|
824
|
+
|
|
825
|
+
```
|
|
826
|
+
1. Lint & Format Check
|
|
827
|
+
2. Type Check
|
|
828
|
+
3. Unit Tests
|
|
829
|
+
4. Integration Tests
|
|
830
|
+
5. Build
|
|
831
|
+
6. Security Scan
|
|
832
|
+
7. Deploy (staging/production)
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### GitHub Actions Example
|
|
836
|
+
|
|
837
|
+
#### Basic Workflow
|
|
838
|
+
|
|
839
|
+
```yaml
|
|
840
|
+
# .github/workflows/ci.yml
|
|
841
|
+
name: CI
|
|
842
|
+
|
|
843
|
+
on:
|
|
844
|
+
push:
|
|
845
|
+
branches: [main, develop]
|
|
846
|
+
pull_request:
|
|
847
|
+
branches: [main, develop]
|
|
848
|
+
|
|
849
|
+
jobs:
|
|
850
|
+
quality-checks:
|
|
851
|
+
runs-on: ubuntu-latest
|
|
852
|
+
steps:
|
|
853
|
+
- uses: actions/checkout@v4
|
|
854
|
+
|
|
855
|
+
- name: Setup Node.js
|
|
856
|
+
uses: actions/setup-node@v4
|
|
857
|
+
with:
|
|
858
|
+
node-version: '20'
|
|
859
|
+
cache: 'npm'
|
|
860
|
+
|
|
861
|
+
- name: Install dependencies
|
|
862
|
+
run: npm ci
|
|
863
|
+
|
|
864
|
+
- name: Lint
|
|
865
|
+
run: npm run lint
|
|
866
|
+
|
|
867
|
+
- name: Type check
|
|
868
|
+
run: npm run type-check
|
|
869
|
+
|
|
870
|
+
- name: Format check
|
|
871
|
+
run: npm run format:check
|
|
872
|
+
|
|
873
|
+
- name: Run tests
|
|
874
|
+
run: npm run test
|
|
875
|
+
|
|
876
|
+
- name: Build
|
|
877
|
+
run: npm run build
|
|
878
|
+
|
|
879
|
+
- name: Security scan
|
|
880
|
+
run: npm audit --audit-level=moderate
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Required CI Checks
|
|
884
|
+
|
|
885
|
+
**Every CI pipeline MUST include:**
|
|
886
|
+
|
|
887
|
+
1. **Linting** - ESLint with pace-core rules
|
|
888
|
+
```yaml
|
|
889
|
+
- name: Lint
|
|
890
|
+
run: npm run lint
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
2. **Type Checking** - TypeScript compilation
|
|
894
|
+
```yaml
|
|
895
|
+
- name: Type check
|
|
896
|
+
run: npx tsc --noEmit
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
3. **Testing** - Unit and integration tests
|
|
900
|
+
```yaml
|
|
901
|
+
- name: Run tests
|
|
902
|
+
run: npm run test
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
4. **Build** - Verify build succeeds
|
|
906
|
+
```yaml
|
|
907
|
+
- name: Build
|
|
908
|
+
run: npm run build
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
### Package.json Scripts
|
|
912
|
+
|
|
913
|
+
**MUST have these scripts in `package.json`:**
|
|
914
|
+
|
|
915
|
+
```json
|
|
916
|
+
{
|
|
917
|
+
"scripts": {
|
|
918
|
+
"lint": "eslint .",
|
|
919
|
+
"lint:fix": "eslint . --fix",
|
|
920
|
+
"type-check": "tsc --noEmit",
|
|
921
|
+
"test": "vitest run",
|
|
922
|
+
"test:watch": "vitest",
|
|
923
|
+
"test:coverage": "vitest run --coverage",
|
|
924
|
+
"build": "vite build",
|
|
925
|
+
"format": "prettier --write .",
|
|
926
|
+
"format:check": "prettier --check"
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
### Environment Variables
|
|
932
|
+
|
|
933
|
+
#### CI/CD Secrets
|
|
934
|
+
|
|
935
|
+
**Store sensitive values as secrets:**
|
|
936
|
+
|
|
937
|
+
```yaml
|
|
938
|
+
# .github/workflows/deploy.yml
|
|
939
|
+
env:
|
|
940
|
+
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
|
|
941
|
+
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
|
|
942
|
+
VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
|
|
943
|
+
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
**Never commit secrets to repository:**
|
|
947
|
+
- Use GitHub Secrets
|
|
948
|
+
- Use environment-specific config files
|
|
949
|
+
- Use `.env.example` for documentation
|
|
950
|
+
|
|
951
|
+
### Database Migrations in CI/CD
|
|
952
|
+
|
|
953
|
+
**For Supabase migrations:**
|
|
954
|
+
|
|
955
|
+
```yaml
|
|
956
|
+
- name: Run migrations
|
|
957
|
+
run: |
|
|
958
|
+
npx supabase db push
|
|
959
|
+
# Or use Supabase CLI
|
|
960
|
+
supabase migration up
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
**Best Practices:**
|
|
964
|
+
- Run migrations in staging first
|
|
965
|
+
- Test migrations in CI
|
|
966
|
+
- Never run destructive migrations automatically
|
|
967
|
+
- Use migration review process
|
|
968
|
+
|
|
969
|
+
### Deployment Strategies
|
|
970
|
+
|
|
971
|
+
#### Staging Deployment
|
|
972
|
+
|
|
973
|
+
**Deploy to staging on every merge to `develop`:**
|
|
974
|
+
|
|
975
|
+
```yaml
|
|
976
|
+
deploy-staging:
|
|
977
|
+
if: github.ref == 'refs/heads/develop'
|
|
978
|
+
environment: staging
|
|
979
|
+
steps:
|
|
980
|
+
- name: Deploy to Staging
|
|
981
|
+
run: |
|
|
982
|
+
# Deploy to staging environment
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
#### Production Deployment
|
|
986
|
+
|
|
987
|
+
**Deploy to production only from `main` branch:**
|
|
988
|
+
|
|
989
|
+
```yaml
|
|
990
|
+
deploy-production:
|
|
991
|
+
if: github.ref == 'refs/heads/main'
|
|
992
|
+
environment: production
|
|
993
|
+
steps:
|
|
994
|
+
- name: Deploy to Production
|
|
995
|
+
run: |
|
|
996
|
+
# Deploy to production environment
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
---
|
|
1000
|
+
|
|
1001
|
+
## Checklists
|
|
1002
|
+
|
|
1003
|
+
### Error Handling Checklist
|
|
1004
|
+
|
|
1005
|
+
Before committing code with error handling, verify:
|
|
1006
|
+
|
|
1007
|
+
- [ ] User-facing error messages are friendly and actionable
|
|
1008
|
+
- [ ] No internal details exposed in user messages
|
|
1009
|
+
- [ ] Type-safe error handling (no `any` types)
|
|
1010
|
+
- [ ] Errors are logged with context (no sensitive data)
|
|
1011
|
+
- [ ] Recovery paths provided when possible
|
|
1012
|
+
- [ ] Error shapes are consistent
|
|
1013
|
+
- [ ] Error boundaries used for React components
|
|
1014
|
+
- [ ] Async operations have proper error handling
|
|
1015
|
+
- [ ] Validation errors use Zod or similar
|
|
1016
|
+
- [ ] Network errors handled gracefully
|
|
1017
|
+
|
|
1018
|
+
### Performance Checklist
|
|
1019
|
+
|
|
1020
|
+
Before committing performance-sensitive code, verify:
|
|
1021
|
+
|
|
1022
|
+
- [ ] Expensive computations memoized with `useMemo`
|
|
1023
|
+
- [ ] Callbacks stable with `useCallback`
|
|
1024
|
+
- [ ] Components memoized with `React.memo` when appropriate
|
|
1025
|
+
- [ ] No new objects/arrays created in render
|
|
1026
|
+
- [ ] Heavy components lazy loaded
|
|
1027
|
+
- [ ] Queries use indexes appropriately
|
|
1028
|
+
- [ ] No N+1 query patterns
|
|
1029
|
+
- [ ] TanStack Query configured with appropriate cache times
|
|
1030
|
+
- [ ] Bundle size optimized (tree shaking, code splitting)
|
|
1031
|
+
- [ ] Performance metrics measured and acceptable
|
|
1032
|
+
|
|
1033
|
+
### CI/CD Checklist
|
|
1034
|
+
|
|
1035
|
+
Before setting up CI/CD, verify:
|
|
1036
|
+
|
|
1037
|
+
- [ ] Lint check configured
|
|
1038
|
+
- [ ] Type check configured
|
|
1039
|
+
- [ ] Tests run in CI
|
|
1040
|
+
- [ ] Build succeeds in CI
|
|
1041
|
+
- [ ] Security scan configured
|
|
1042
|
+
- [ ] Environment variables set as secrets
|
|
1043
|
+
- [ ] Staging deployment configured
|
|
1044
|
+
- [ ] Production deployment configured
|
|
1045
|
+
- [ ] Migration strategy defined
|
|
1046
|
+
- [ ] Rollback plan documented
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
1050
|
+
## Common Mistakes to Avoid
|
|
1051
|
+
|
|
1052
|
+
### Error Handling
|
|
1053
|
+
|
|
1054
|
+
```typescript
|
|
1055
|
+
// ❌ WRONG - Expose internal details
|
|
1056
|
+
toast.error(error.message); // May contain SQL, stack traces, etc.
|
|
1057
|
+
|
|
1058
|
+
// ❌ WRONG - Use any for errors
|
|
1059
|
+
catch (error: any) {
|
|
1060
|
+
console.log(error.message);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// ❌ WRONG - Log sensitive data
|
|
1064
|
+
logger.error('Login failed', { password, token });
|
|
1065
|
+
|
|
1066
|
+
// ❌ WRONG - Ignore errors
|
|
1067
|
+
try {
|
|
1068
|
+
await riskyOperation();
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
// Silent failure
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
### Performance
|
|
1075
|
+
|
|
1076
|
+
```tsx
|
|
1077
|
+
// ❌ WRONG - Create new objects in render
|
|
1078
|
+
function Component({ items }) {
|
|
1079
|
+
return <Child config={{ items, enabled: true }} />;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// ❌ WRONG - Use inline functions
|
|
1083
|
+
<Button onClick={() => handleClick(id)}>Click</Button>
|
|
1084
|
+
|
|
1085
|
+
// ❌ WRONG - Over-memoize
|
|
1086
|
+
const simpleValue = useMemo(() => items.length, [items]); // Unnecessary
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
---
|
|
1090
|
+
|
|
1091
|
+
## Related Documentation
|
|
1092
|
+
|
|
1093
|
+
- [Standards Overview](./0-standards-overview.md) - Standards system overview
|
|
1094
|
+
- [Security & RBAC](./6-security-rbac-standards.md) - RLS performance requirements
|
|
1095
|
+
- [Code Quality](./4-code-quality-standards.md) - React performance patterns
|
|
1096
|
+
- [API & Tech Stack](./7-api-tech-stack-standards.md) - TanStack Query configuration
|
|
1097
|
+
|
|
1098
|
+
---
|
|
1099
|
+
|
|
1100
|
+
**Last Updated:** 2025-01-28
|
|
1101
|
+
**Version:** 2.0.0
|
|
1102
|
+
**Applies to:** All pace-core and consuming apps
|