@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,662 @@
|
|
|
1
|
+
# API & Tech Stack Standards
|
|
2
|
+
|
|
3
|
+
**🤖 Cursor Rule**: See [07-api-tech-stack.mdc](../../cursor-rules/07-api-tech-stack.mdc) for AI-optimized directives that automatically enforce tech stack compliance.
|
|
4
|
+
|
|
5
|
+
**🔧 ESLint Rules**: See [07-api-tech-stack.cjs](../../eslint-rules/rules/07-api-tech-stack.cjs) for mechanically checkable API and tech stack rules.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
This standard defines the required technology stack, API design patterns, and RPC conventions to ensure consistency, type safety, and maintainability across pace-core and consuming apps.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Required Tech Stack
|
|
14
|
+
|
|
15
|
+
### Core Technologies
|
|
16
|
+
|
|
17
|
+
**MUST** use these technologies and versions:
|
|
18
|
+
|
|
19
|
+
- **React 19+** - Functional components only, no class components
|
|
20
|
+
- **TypeScript 5+** - With `strict` mode enabled
|
|
21
|
+
- **Vite** - For tooling; use `import.meta.env` for environment variables
|
|
22
|
+
- **Tailwind v4** - CSS-first with `app.css` scaffold (see [Styling Standards](./5-styling-standards.md))
|
|
23
|
+
- **Supabase** - Via secure clients/hooks (`useSecureSupabase`); never bypass RLS
|
|
24
|
+
- **TanStack Query** - For server state management
|
|
25
|
+
- **React Hook Form + Zod** - Prefer `useZodForm` from pace-core for forms
|
|
26
|
+
|
|
27
|
+
### Version Requirements
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"react": "^19.0.0",
|
|
33
|
+
"react-dom": "^19.0.0",
|
|
34
|
+
"@types/react": "^19.0.0",
|
|
35
|
+
"@types/react-dom": "^19.0.0",
|
|
36
|
+
"typescript": "^5.0.0",
|
|
37
|
+
"@supabase/supabase-js": "^2.0.0",
|
|
38
|
+
"@tanstack/react-query": "^5.0.0",
|
|
39
|
+
"react-hook-form": "^7.0.0",
|
|
40
|
+
"zod": "^3.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"vite": "^5.0.0",
|
|
44
|
+
"@vitejs/plugin-react": "^4.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Environment Variables
|
|
50
|
+
|
|
51
|
+
**MUST** use `import.meta.env` (Vite) for environment variables:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// ✅ CORRECT - Vite environment variables
|
|
55
|
+
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
|
|
56
|
+
const supabaseKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY;
|
|
57
|
+
|
|
58
|
+
// ❌ WRONG - Node.js process.env
|
|
59
|
+
const supabaseUrl = process.env.VITE_SUPABASE_URL;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## API & RPC Naming Conventions
|
|
65
|
+
|
|
66
|
+
### RPC Naming Pattern
|
|
67
|
+
|
|
68
|
+
**MUST** follow this naming pattern: `<family>_<domain>_<verb>`
|
|
69
|
+
|
|
70
|
+
- **Family**: `data` (read operations), `app` (write operations)
|
|
71
|
+
- **Domain**: Feature/domain name (e.g., `events`, `users`, `cake_dish`)
|
|
72
|
+
- **Verb**: Operation name (e.g., `list`, `get`, `create`, `update`, `delete`)
|
|
73
|
+
|
|
74
|
+
**Examples:**
|
|
75
|
+
```typescript
|
|
76
|
+
// Read operations (data_*)
|
|
77
|
+
data_events_list
|
|
78
|
+
data_events_get
|
|
79
|
+
data_users_list
|
|
80
|
+
data_cake_dish_list
|
|
81
|
+
|
|
82
|
+
// Write operations (app_*)
|
|
83
|
+
app_events_create
|
|
84
|
+
app_events_update
|
|
85
|
+
app_events_delete
|
|
86
|
+
app_cake_dish_create
|
|
87
|
+
|
|
88
|
+
// Bulk operations (use _bulk_ prefix)
|
|
89
|
+
app_events_bulk_create
|
|
90
|
+
app_cake_dish_bulk_update
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Naming Rules
|
|
94
|
+
|
|
95
|
+
1. **Use snake_case** for all RPC names
|
|
96
|
+
2. **Read operations** start with `data_`
|
|
97
|
+
3. **Write operations** start with `app_`
|
|
98
|
+
4. **Bulk operations** use `_bulk_` prefix (e.g., `app_events_bulk_create`)
|
|
99
|
+
5. **Domain names** should match table names where applicable
|
|
100
|
+
6. **Verb names** should be clear and consistent (`list`, `get`, `create`, `update`, `delete`)
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## API Result Shape
|
|
105
|
+
|
|
106
|
+
### Standard Result Type
|
|
107
|
+
|
|
108
|
+
**MUST** use this result type for all APIs:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
type ApiResult<T> =
|
|
112
|
+
| { ok: true; data: T }
|
|
113
|
+
| { ok: false; error: ApiError };
|
|
114
|
+
|
|
115
|
+
type ApiError = {
|
|
116
|
+
code: string; // Machine-readable error code
|
|
117
|
+
message: string; // User-friendly message
|
|
118
|
+
details?: object; // Optional additional context (non-sensitive)
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Example Usage
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// ✅ CORRECT - Using ApiResult type
|
|
126
|
+
async function fetchEvent(id: string): Promise<ApiResult<Event>> {
|
|
127
|
+
try {
|
|
128
|
+
const { data, error } = await supabase
|
|
129
|
+
.from('events')
|
|
130
|
+
.select('*')
|
|
131
|
+
.eq('id', id)
|
|
132
|
+
.single();
|
|
133
|
+
|
|
134
|
+
if (error) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
error: {
|
|
138
|
+
code: 'EVENT_NOT_FOUND',
|
|
139
|
+
message: 'Event not found',
|
|
140
|
+
details: { eventId: id },
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { ok: true, data };
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
error: {
|
|
150
|
+
code: 'UNKNOWN_ERROR',
|
|
151
|
+
message: 'An unexpected error occurred',
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Usage
|
|
158
|
+
const result = await fetchEvent(eventId);
|
|
159
|
+
if (result.ok) {
|
|
160
|
+
// TypeScript knows result.data exists
|
|
161
|
+
console.log(result.data.name);
|
|
162
|
+
} else {
|
|
163
|
+
// TypeScript knows result.error exists
|
|
164
|
+
toast.error(result.error.message);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### RPC Result Shape
|
|
169
|
+
|
|
170
|
+
**RPCs MUST** return data in a format that can be wrapped in `ApiResult`:
|
|
171
|
+
|
|
172
|
+
```sql
|
|
173
|
+
-- ✅ CORRECT - RPC returns data directly
|
|
174
|
+
CREATE OR REPLACE FUNCTION data_events_list(
|
|
175
|
+
p_organisation_id UUID
|
|
176
|
+
)
|
|
177
|
+
RETURNS TABLE (
|
|
178
|
+
id UUID,
|
|
179
|
+
name TEXT,
|
|
180
|
+
date TIMESTAMPTZ
|
|
181
|
+
)
|
|
182
|
+
LANGUAGE plpgsql
|
|
183
|
+
STABLE
|
|
184
|
+
SECURITY DEFINER
|
|
185
|
+
SET search_path TO public
|
|
186
|
+
AS $$
|
|
187
|
+
BEGIN
|
|
188
|
+
RETURN QUERY
|
|
189
|
+
SELECT e.id, e.name, e.date
|
|
190
|
+
FROM events e
|
|
191
|
+
WHERE e.organisation_id = p_organisation_id;
|
|
192
|
+
END;
|
|
193
|
+
$$;
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Client-side wrapper:**
|
|
197
|
+
```typescript
|
|
198
|
+
async function listEvents(organisationId: string): Promise<ApiResult<Event[]>> {
|
|
199
|
+
const { data, error } = await supabase.rpc('data_events_list', {
|
|
200
|
+
p_organisation_id: organisationId,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (error) {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
error: {
|
|
207
|
+
code: 'LIST_FAILED',
|
|
208
|
+
message: 'Failed to list events',
|
|
209
|
+
details: { error: error.message },
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { ok: true, data: data ?? [] };
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## RPC Rules
|
|
221
|
+
|
|
222
|
+
### Read RPCs Never Mutate
|
|
223
|
+
|
|
224
|
+
**Read operations MUST NOT have side effects.**
|
|
225
|
+
|
|
226
|
+
```sql
|
|
227
|
+
-- ❌ WRONG - Read operation with side effect
|
|
228
|
+
CREATE OR REPLACE FUNCTION data_events_get(p_event_id UUID)
|
|
229
|
+
RETURNS TABLE (...)
|
|
230
|
+
AS $$
|
|
231
|
+
BEGIN
|
|
232
|
+
-- Side effect: updates last_accessed
|
|
233
|
+
UPDATE events SET last_accessed = NOW() WHERE id = p_event_id;
|
|
234
|
+
RETURN QUERY SELECT * FROM events WHERE id = p_event_id;
|
|
235
|
+
END;
|
|
236
|
+
$$;
|
|
237
|
+
|
|
238
|
+
-- ✅ CORRECT - Pure read operation
|
|
239
|
+
CREATE OR REPLACE FUNCTION data_events_get(p_event_id UUID)
|
|
240
|
+
RETURNS TABLE (...)
|
|
241
|
+
LANGUAGE plpgsql
|
|
242
|
+
STABLE -- ✅ STABLE for read operations
|
|
243
|
+
SECURITY DEFINER
|
|
244
|
+
SET search_path TO public
|
|
245
|
+
AS $$
|
|
246
|
+
BEGIN
|
|
247
|
+
RETURN QUERY
|
|
248
|
+
SELECT * FROM events WHERE id = p_event_id;
|
|
249
|
+
END;
|
|
250
|
+
$$;
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Write RPCs Should Be Idempotent
|
|
254
|
+
|
|
255
|
+
**Write operations SHOULD be idempotent when possible.**
|
|
256
|
+
|
|
257
|
+
```sql
|
|
258
|
+
-- ✅ CORRECT - Idempotent upsert
|
|
259
|
+
CREATE OR REPLACE FUNCTION app_events_upsert(
|
|
260
|
+
p_id UUID,
|
|
261
|
+
p_name TEXT,
|
|
262
|
+
p_date TIMESTAMPTZ
|
|
263
|
+
)
|
|
264
|
+
RETURNS UUID
|
|
265
|
+
LANGUAGE plpgsql
|
|
266
|
+
SECURITY DEFINER
|
|
267
|
+
SET search_path TO public
|
|
268
|
+
AS $$
|
|
269
|
+
DECLARE
|
|
270
|
+
v_event_id UUID;
|
|
271
|
+
BEGIN
|
|
272
|
+
INSERT INTO events (id, name, date, organisation_id)
|
|
273
|
+
VALUES (p_id, p_name, p_date, get_organisation_context())
|
|
274
|
+
ON CONFLICT (id) DO UPDATE
|
|
275
|
+
SET name = EXCLUDED.name, date = EXCLUDED.date
|
|
276
|
+
RETURNING id INTO v_event_id;
|
|
277
|
+
|
|
278
|
+
RETURN v_event_id;
|
|
279
|
+
END;
|
|
280
|
+
$$;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Never Accept Dynamic SQL
|
|
284
|
+
|
|
285
|
+
**NEVER accept SQL strings as parameters. Use parameterized queries or RPCs.**
|
|
286
|
+
|
|
287
|
+
```sql
|
|
288
|
+
-- ❌ WRONG - Dynamic SQL injection risk
|
|
289
|
+
CREATE OR REPLACE FUNCTION execute_query(p_sql TEXT)
|
|
290
|
+
RETURNS TABLE (...)
|
|
291
|
+
AS $$
|
|
292
|
+
BEGIN
|
|
293
|
+
RETURN QUERY EXECUTE p_sql; -- DANGEROUS!
|
|
294
|
+
END;
|
|
295
|
+
$$;
|
|
296
|
+
|
|
297
|
+
-- ✅ CORRECT - Parameterized query
|
|
298
|
+
CREATE OR REPLACE FUNCTION data_events_list(
|
|
299
|
+
p_organisation_id UUID,
|
|
300
|
+
p_status TEXT DEFAULT NULL,
|
|
301
|
+
p_limit INTEGER DEFAULT 100
|
|
302
|
+
)
|
|
303
|
+
RETURNS TABLE (...)
|
|
304
|
+
AS $$
|
|
305
|
+
BEGIN
|
|
306
|
+
RETURN QUERY
|
|
307
|
+
SELECT * FROM events
|
|
308
|
+
WHERE organisation_id = p_organisation_id
|
|
309
|
+
AND (p_status IS NULL OR status = p_status)
|
|
310
|
+
LIMIT p_limit;
|
|
311
|
+
END;
|
|
312
|
+
$$;
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Enforce RLS + Tenant Boundaries
|
|
316
|
+
|
|
317
|
+
**RPCs MUST enforce RLS and tenant boundaries. Never bypass security checks.**
|
|
318
|
+
|
|
319
|
+
```sql
|
|
320
|
+
-- ✅ CORRECT - RLS enforced via helper functions
|
|
321
|
+
CREATE OR REPLACE FUNCTION data_events_list(p_organisation_id UUID)
|
|
322
|
+
RETURNS TABLE (...)
|
|
323
|
+
LANGUAGE plpgsql
|
|
324
|
+
STABLE
|
|
325
|
+
SECURITY DEFINER
|
|
326
|
+
SET search_path TO public
|
|
327
|
+
AS $$
|
|
328
|
+
BEGIN
|
|
329
|
+
-- Check organisation access
|
|
330
|
+
IF NOT check_user_organisation_access(p_organisation_id) THEN
|
|
331
|
+
RAISE EXCEPTION 'Access denied';
|
|
332
|
+
END IF;
|
|
333
|
+
|
|
334
|
+
RETURN QUERY
|
|
335
|
+
SELECT * FROM events
|
|
336
|
+
WHERE organisation_id = p_organisation_id;
|
|
337
|
+
END;
|
|
338
|
+
$$;
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Real-World Example: Complex RPC with Multiple Security Checks**
|
|
342
|
+
|
|
343
|
+
```sql
|
|
344
|
+
-- Real-world example: Creating an event with attendees and permissions
|
|
345
|
+
CREATE OR REPLACE FUNCTION app_events_create_with_attendees(
|
|
346
|
+
p_name TEXT,
|
|
347
|
+
p_date TIMESTAMPTZ,
|
|
348
|
+
p_organisation_id UUID,
|
|
349
|
+
p_attendee_ids UUID[]
|
|
350
|
+
)
|
|
351
|
+
RETURNS UUID
|
|
352
|
+
LANGUAGE plpgsql
|
|
353
|
+
SECURITY DEFINER
|
|
354
|
+
SET search_path TO public
|
|
355
|
+
AS $$
|
|
356
|
+
DECLARE
|
|
357
|
+
v_event_id UUID;
|
|
358
|
+
v_user_id UUID;
|
|
359
|
+
v_has_permission BOOLEAN;
|
|
360
|
+
BEGIN
|
|
361
|
+
-- 1. Get current user ID
|
|
362
|
+
v_user_id := safe_get_user_id_for_rls();
|
|
363
|
+
|
|
364
|
+
-- 2. Check organisation access
|
|
365
|
+
IF NOT check_user_organisation_access(p_organisation_id) THEN
|
|
366
|
+
RAISE EXCEPTION 'You do not have access to this organisation';
|
|
367
|
+
END IF;
|
|
368
|
+
|
|
369
|
+
-- 3. Check create permission using RBAC
|
|
370
|
+
v_has_permission := check_rbac_permission_with_context(
|
|
371
|
+
'create:page.events',
|
|
372
|
+
'events',
|
|
373
|
+
p_organisation_id,
|
|
374
|
+
NULL,
|
|
375
|
+
get_app_id('PACE')
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
IF NOT v_has_permission THEN
|
|
379
|
+
RAISE EXCEPTION 'You do not have permission to create events';
|
|
380
|
+
END IF;
|
|
381
|
+
|
|
382
|
+
-- 4. Validate attendee IDs belong to the same organisation
|
|
383
|
+
IF array_length(p_attendee_ids, 1) > 0 THEN
|
|
384
|
+
IF EXISTS (
|
|
385
|
+
SELECT 1 FROM users
|
|
386
|
+
WHERE id = ANY(p_attendee_ids)
|
|
387
|
+
AND organisation_id != p_organisation_id
|
|
388
|
+
) THEN
|
|
389
|
+
RAISE EXCEPTION 'All attendees must belong to the same organisation';
|
|
390
|
+
END IF;
|
|
391
|
+
END IF;
|
|
392
|
+
|
|
393
|
+
-- 5. Create event
|
|
394
|
+
INSERT INTO events (name, date, organisation_id, created_by)
|
|
395
|
+
VALUES (p_name, p_date, p_organisation_id, v_user_id)
|
|
396
|
+
RETURNING id INTO v_event_id;
|
|
397
|
+
|
|
398
|
+
-- 6. Create attendee records
|
|
399
|
+
IF array_length(p_attendee_ids, 1) > 0 THEN
|
|
400
|
+
INSERT INTO event_attendees (event_id, user_id, organisation_id)
|
|
401
|
+
SELECT v_event_id, unnest(p_attendee_ids), p_organisation_id;
|
|
402
|
+
END IF;
|
|
403
|
+
|
|
404
|
+
RETURN v_event_id;
|
|
405
|
+
END;
|
|
406
|
+
$$;
|
|
407
|
+
|
|
408
|
+
-- Usage in TypeScript
|
|
409
|
+
async function createEventWithAttendees(
|
|
410
|
+
name: string,
|
|
411
|
+
date: Date,
|
|
412
|
+
organisationId: string,
|
|
413
|
+
attendeeIds: string[]
|
|
414
|
+
): Promise<ApiResult<{ eventId: string }>> {
|
|
415
|
+
const { data, error } = await secureSupabase.rpc('app_events_create_with_attendees', {
|
|
416
|
+
p_name: name,
|
|
417
|
+
p_date: date.toISOString(),
|
|
418
|
+
p_organisation_id: organisationId,
|
|
419
|
+
p_attendee_ids: attendeeIds,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
if (error) {
|
|
423
|
+
// Map database errors to user-friendly messages
|
|
424
|
+
if (error.message.includes('access to this organisation')) {
|
|
425
|
+
return {
|
|
426
|
+
ok: false,
|
|
427
|
+
error: {
|
|
428
|
+
code: 'ORGANISATION_ACCESS_DENIED',
|
|
429
|
+
message: 'You do not have access to this organisation',
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (error.message.includes('permission to create events')) {
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
error: {
|
|
437
|
+
code: 'PERMISSION_DENIED',
|
|
438
|
+
message: 'You do not have permission to create events',
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
if (error.message.includes('same organisation')) {
|
|
443
|
+
return {
|
|
444
|
+
ok: false,
|
|
445
|
+
error: {
|
|
446
|
+
code: 'INVALID_ATTENDEES',
|
|
447
|
+
message: 'All attendees must belong to the same organisation',
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
ok: false,
|
|
454
|
+
error: {
|
|
455
|
+
code: 'CREATE_FAILED',
|
|
456
|
+
message: 'Unable to create event. Please try again.',
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return { ok: true, data: { eventId: data } };
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### User-Safe Error Messages
|
|
466
|
+
|
|
467
|
+
**Errors MUST be user-friendly and not expose internal details.**
|
|
468
|
+
|
|
469
|
+
```sql
|
|
470
|
+
-- ❌ WRONG - Exposes internal details
|
|
471
|
+
CREATE OR REPLACE FUNCTION app_events_create(...)
|
|
472
|
+
AS $$
|
|
473
|
+
BEGIN
|
|
474
|
+
IF some_condition THEN
|
|
475
|
+
RAISE EXCEPTION 'SQLSTATE[23000]: Integrity constraint violation: duplicate key';
|
|
476
|
+
END IF;
|
|
477
|
+
END;
|
|
478
|
+
$$;
|
|
479
|
+
|
|
480
|
+
-- ✅ CORRECT - User-friendly error
|
|
481
|
+
CREATE OR REPLACE FUNCTION app_events_create(...)
|
|
482
|
+
AS $$
|
|
483
|
+
BEGIN
|
|
484
|
+
IF some_condition THEN
|
|
485
|
+
RAISE EXCEPTION 'An event with this name already exists';
|
|
486
|
+
END IF;
|
|
487
|
+
END;
|
|
488
|
+
$$;
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Deprecation Policy
|
|
494
|
+
|
|
495
|
+
### Deprecation Process
|
|
496
|
+
|
|
497
|
+
**When deprecating APIs or RPCs:**
|
|
498
|
+
|
|
499
|
+
1. **Mark with `@deprecated`** JSDoc comment
|
|
500
|
+
2. **Add migration notes** in documentation
|
|
501
|
+
3. **Retirement window** = 2 stable releases
|
|
502
|
+
4. **Remove after retirement window** expires
|
|
503
|
+
|
|
504
|
+
### Example Deprecation
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
/**
|
|
508
|
+
* @deprecated Use `data_events_list` instead. This function will be removed in v2.0.0.
|
|
509
|
+
*
|
|
510
|
+
* Migration:
|
|
511
|
+
* ```typescript
|
|
512
|
+
* // Old
|
|
513
|
+
* const events = await getEvents(orgId);
|
|
514
|
+
*
|
|
515
|
+
* // New
|
|
516
|
+
* const result = await listEvents(orgId);
|
|
517
|
+
* if (result.ok) {
|
|
518
|
+
* const events = result.data;
|
|
519
|
+
* }
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
async function getEvents(organisationId: string): Promise<Event[]> {
|
|
523
|
+
// Implementation
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Tech Stack Configuration
|
|
530
|
+
|
|
531
|
+
### TypeScript Configuration
|
|
532
|
+
|
|
533
|
+
**MUST** enable strict mode:
|
|
534
|
+
|
|
535
|
+
```json
|
|
536
|
+
{
|
|
537
|
+
"compilerOptions": {
|
|
538
|
+
"strict": true,
|
|
539
|
+
"noImplicitAny": true,
|
|
540
|
+
"strictNullChecks": true,
|
|
541
|
+
"strictFunctionTypes": true,
|
|
542
|
+
"strictBindCallApply": true,
|
|
543
|
+
"strictPropertyInitialization": true,
|
|
544
|
+
"noImplicitThis": true,
|
|
545
|
+
"alwaysStrict": true
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Vite Configuration
|
|
551
|
+
|
|
552
|
+
**MUST** configure path aliases, React plugin, and dependency optimization:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// vite.config.ts
|
|
556
|
+
import { defineConfig } from 'vite';
|
|
557
|
+
import react from '@vitejs/plugin-react';
|
|
558
|
+
import path from 'path';
|
|
559
|
+
|
|
560
|
+
export default defineConfig({
|
|
561
|
+
plugins: [react()],
|
|
562
|
+
resolve: {
|
|
563
|
+
alias: {
|
|
564
|
+
'@': path.resolve(__dirname, './src'),
|
|
565
|
+
},
|
|
566
|
+
// CRITICAL: Dedupe React dependencies to prevent context mismatches
|
|
567
|
+
dedupe: ['react', 'react-dom', 'react-router-dom'],
|
|
568
|
+
},
|
|
569
|
+
optimizeDeps: {
|
|
570
|
+
include: [
|
|
571
|
+
'react',
|
|
572
|
+
'react-dom',
|
|
573
|
+
'react/jsx-runtime',
|
|
574
|
+
],
|
|
575
|
+
// CRITICAL: Exclude pace-core and react-router-dom to prevent React context mismatches
|
|
576
|
+
exclude: ['@jmruthers/pace-core', 'react-router-dom'],
|
|
577
|
+
},
|
|
578
|
+
envPrefix: 'VITE_', // Only expose VITE_* env vars
|
|
579
|
+
});
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
**Why this configuration is required:**
|
|
583
|
+
- Pre-bundling `@jmruthers/pace-core` creates separate React instances, causing "useUnifiedAuth must be used within a UnifiedAuthProvider" errors
|
|
584
|
+
- Excluding it ensures pace-core uses the same React instance as your app
|
|
585
|
+
- `react-router-dom` must also be excluded and deduped to prevent Router context errors
|
|
586
|
+
|
|
587
|
+
**If you encounter context errors:**
|
|
588
|
+
1. Verify `@jmruthers/pace-core` is in `optimizeDeps.exclude`
|
|
589
|
+
2. Verify `react-router-dom` is in both `resolve.dedupe` and `optimizeDeps.exclude`
|
|
590
|
+
3. Clear Vite cache: `rm -rf node_modules/.vite`
|
|
591
|
+
4. Restart dev server
|
|
592
|
+
|
|
593
|
+
### TanStack Query Configuration
|
|
594
|
+
|
|
595
|
+
**MUST** configure appropriate cache times:
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
599
|
+
|
|
600
|
+
export const queryClient = new QueryClient({
|
|
601
|
+
defaultOptions: {
|
|
602
|
+
queries: {
|
|
603
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
604
|
+
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
|
|
605
|
+
retry: 1,
|
|
606
|
+
refetchOnWindowFocus: false,
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## API Design Checklist
|
|
615
|
+
|
|
616
|
+
Before creating or updating an API/RPC, verify:
|
|
617
|
+
|
|
618
|
+
- [ ] RPC name follows naming convention (`data_*` or `app_*`)
|
|
619
|
+
- [ ] Uses `ApiResult<T>` type for return values
|
|
620
|
+
- [ ] Read RPCs are `STABLE` and have no side effects
|
|
621
|
+
- [ ] Write RPCs are idempotent when possible
|
|
622
|
+
- [ ] No dynamic SQL accepted as parameters
|
|
623
|
+
- [ ] RLS and tenant boundaries enforced
|
|
624
|
+
- [ ] Error messages are user-friendly
|
|
625
|
+
- [ ] TypeScript types are complete and accurate
|
|
626
|
+
- [ ] Deprecation process followed if removing/changing APIs
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## ESLint Rules
|
|
631
|
+
|
|
632
|
+
The following ESLint rules enforce API and tech stack standards:
|
|
633
|
+
|
|
634
|
+
### RPC Naming
|
|
635
|
+
|
|
636
|
+
- **`rpc-naming-pattern`** - Enforces `data_*` prefix for read operations and `app_*` prefix for write operations
|
|
637
|
+
|
|
638
|
+
### React 19+ Patterns
|
|
639
|
+
|
|
640
|
+
- **`no-class-components`** - Disallows React class components (functional components only)
|
|
641
|
+
|
|
642
|
+
### Environment Variables
|
|
643
|
+
|
|
644
|
+
- **`prefer-import-meta-env`** - Enforces `import.meta.env` (Vite) instead of `process.env` in client code
|
|
645
|
+
|
|
646
|
+
These rules are part of the `pace-core-compliance` plugin and are automatically enabled when you extend `@jmruthers/pace-core/eslint-config`.
|
|
647
|
+
|
|
648
|
+
**Setup**: Run `node node_modules/@jmruthers/pace-core/scripts/install-eslint-config.cjs` to configure ESLint in your consuming app.
|
|
649
|
+
|
|
650
|
+
## Related Documentation
|
|
651
|
+
|
|
652
|
+
- [Standards Overview](./0-standards-overview.md) - Standards system overview
|
|
653
|
+
- [Architecture](./3-architecture-standards.md) - API design principles
|
|
654
|
+
- [Code Quality](./4-code-quality-standards.md) - TypeScript standards
|
|
655
|
+
- [Security & RBAC](./6-security-rbac-standards.md) - RLS and security requirements
|
|
656
|
+
- [Operations](./9-operations-standards.md) - Error handling patterns
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
**Last Updated:** 2025-01-28
|
|
661
|
+
**Version:** 2.0.0
|
|
662
|
+
**Applies to:** All pace-core and consuming apps
|