@jmruthers/pace-core 0.6.6 → 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.
Files changed (246) hide show
  1. package/{scripts/audit/audit-dependencies.cjs → audit-tool/00-dependencies.cjs} +12 -13
  2. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  3. package/audit-tool/audits/02-project-structure.cjs +255 -0
  4. package/audit-tool/audits/03-architecture.cjs +196 -0
  5. package/audit-tool/audits/04-code-quality.cjs +149 -0
  6. package/audit-tool/audits/05-styling.cjs +224 -0
  7. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  8. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  9. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  10. package/audit-tool/audits/09-operations.cjs +208 -0
  11. package/audit-tool/index.cjs +291 -0
  12. package/audit-tool/utils/code-utils.cjs +218 -0
  13. package/audit-tool/utils/file-utils.cjs +230 -0
  14. package/audit-tool/utils/report-utils.cjs +241 -0
  15. package/cursor-rules/00-standards-overview.mdc +156 -0
  16. package/cursor-rules/{00-pace-core-compliance.mdc → 01-pace-core-compliance.mdc} +187 -34
  17. package/cursor-rules/02-project-structure.mdc +37 -5
  18. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +125 -11
  19. package/cursor-rules/04-code-quality.mdc +419 -0
  20. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +55 -10
  21. package/cursor-rules/{09-rbac-compliance.mdc → 06-security-rbac.mdc} +62 -6
  22. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  23. package/cursor-rules/08-testing-documentation.mdc +324 -0
  24. package/cursor-rules/09-operations.mdc +365 -0
  25. package/dist/DataTable-7PMH7XN7.js +15 -0
  26. package/dist/{DataTable-2N_tqbfq.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  27. package/dist/{PublicPageProvider-BBH6Vqg7.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +26 -16
  28. package/dist/{chunk-FENMYN2U.js → chunk-5X4QLXRG.js} +1 -3
  29. package/dist/{chunk-4T7OBVTU.js → chunk-6F3IILHI.js} +1 -1
  30. package/dist/{chunk-SD6WQY43.js → chunk-7ILTDCL2.js} +9 -1
  31. package/dist/{chunk-3QC3KRHK.js → chunk-A3W6LW53.js} +16 -1
  32. package/dist/{chunk-7TYHROIV.js → chunk-BM4CQ5P3.js} +50 -8
  33. package/dist/{chunk-2HGJFNAH.js → chunk-FEJLJNWA.js} +1 -15
  34. package/dist/{chunk-OHIK3MIO.js → chunk-GHYHJTYV.js} +2 -2
  35. package/dist/{chunk-UIYSCEV7.js → chunk-IUBRCBSY.js} +1 -1
  36. package/dist/{chunk-LAZMKTTF.js → chunk-JGWDVX64.js} +281 -347
  37. package/dist/{chunk-MAGBIDNS.js → chunk-L4XMVJKY.js} +2 -2
  38. package/dist/{chunk-A55DK444.js → chunk-OJ4SKRSV.js} +1 -7
  39. package/dist/{chunk-ZS5VO5JB.js → chunk-Q7Q7V5NV.js} +406 -451
  40. package/dist/{chunk-3O3WHILE.js → chunk-VBCS3DUA.js} +236 -60
  41. package/dist/{chunk-BVP2BCJF.js → chunk-ZKAWKYT4.js} +8 -8
  42. package/dist/components.d.ts +5 -4
  43. package/dist/components.js +27 -32
  44. package/dist/eslint-rules/index.cjs +22 -9
  45. package/{src/eslint-rules/rules/compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +184 -23
  46. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  47. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  48. package/dist/eslint-rules/rules/{rbac.cjs → 06-security-rbac.cjs} +26 -10
  49. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  50. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  51. package/dist/hooks.d.ts +5 -5
  52. package/dist/hooks.js +6 -6
  53. package/dist/index.d.ts +6 -6
  54. package/dist/index.js +18 -17
  55. package/dist/rbac/index.js +6 -6
  56. package/dist/theming/runtime.d.ts +14 -1
  57. package/dist/theming/runtime.js +1 -1
  58. package/dist/{types-B-K_5VnO.d.ts → types-DXstZpNI.d.ts} +0 -17
  59. package/dist/{usePublicRouteParams-COZ28Mvq.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +19 -19
  60. package/dist/utils.d.ts +2 -2
  61. package/dist/utils.js +8 -8
  62. package/docs/README.md +1 -1
  63. package/docs/api/modules.md +47 -31
  64. package/docs/api-reference/components.md +18 -20
  65. package/docs/api-reference/hooks.md +80 -80
  66. package/docs/api-reference/types.md +1 -1
  67. package/docs/api-reference/utilities.md +1 -1
  68. package/docs/architecture/README.md +1 -1
  69. package/docs/core-concepts/events.md +3 -3
  70. package/docs/core-concepts/organisations.md +6 -6
  71. package/docs/core-concepts/permissions.md +6 -6
  72. package/docs/documentation-index.md +12 -18
  73. package/docs/getting-started/documentation-index.md +1 -1
  74. package/docs/getting-started/examples/README.md +4 -4
  75. package/docs/getting-started/examples/full-featured-app.md +1 -1
  76. package/docs/getting-started/faq.md +2 -2
  77. package/docs/getting-started/quick-reference.md +4 -4
  78. package/docs/implementation-guides/authentication.md +15 -15
  79. package/docs/implementation-guides/component-styling.md +1 -1
  80. package/docs/implementation-guides/data-tables.md +126 -33
  81. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  82. package/docs/implementation-guides/dynamic-colors.md +3 -3
  83. package/docs/implementation-guides/file-upload-storage.md +2 -2
  84. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  85. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  86. package/docs/implementation-guides/large-datasets.md +3 -2
  87. package/docs/implementation-guides/organisation-security.md +2 -2
  88. package/docs/implementation-guides/performance.md +2 -2
  89. package/docs/implementation-guides/permission-enforcement.md +1 -1
  90. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  91. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  92. package/docs/rbac/README.md +5 -5
  93. package/docs/rbac/advanced-patterns.md +6 -6
  94. package/docs/rbac/api-reference.md +20 -20
  95. package/docs/rbac/event-based-apps.md +3 -3
  96. package/docs/rbac/examples.md +41 -41
  97. package/docs/rbac/getting-started.md +37 -37
  98. package/docs/rbac/performance.md +1 -1
  99. package/docs/rbac/quick-start.md +52 -52
  100. package/docs/rbac/secure-client-protection.md +1 -1
  101. package/docs/rbac/troubleshooting.md +1 -1
  102. package/docs/security/README.md +5 -5
  103. package/docs/standards/0-standards-overview.md +220 -0
  104. package/docs/standards/{00-pace-core-compliance.md → 1-pace-core-compliance-standards.md} +204 -185
  105. package/docs/standards/{02-project-structure.md → 2-project-structure-standards.md} +11 -47
  106. package/docs/standards/3-architecture-standards.md +606 -0
  107. package/docs/standards/4-code-quality-standards.md +728 -0
  108. package/docs/standards/{08-markup-quality.md → 5-styling-standards.md} +12 -9
  109. package/docs/standards/{09-rbac-compliance.md → 6-security-rbac-standards.md} +126 -18
  110. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  111. package/docs/standards/8-testing-documentation-standards.md +401 -0
  112. package/docs/standards/9-operations-standards.md +1102 -0
  113. package/docs/standards/README.md +203 -104
  114. package/docs/troubleshooting/README.md +4 -4
  115. package/docs/troubleshooting/common-issues.md +2 -2
  116. package/docs/troubleshooting/debugging.md +9 -9
  117. package/docs/troubleshooting/migration.md +4 -4
  118. package/eslint-config-pace-core.cjs +21 -10
  119. package/package.json +6 -5
  120. package/scripts/install-cursor-rules.cjs +11 -243
  121. package/scripts/install-eslint-config.cjs +284 -0
  122. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +2 -2
  123. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  124. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +10 -10
  125. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  126. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  127. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  128. package/src/__tests__/templates/component.test.template.tsx +18 -15
  129. package/src/components/Calendar/Calendar.tsx +201 -47
  130. package/src/components/ContextSelector/ContextSelector.tsx +137 -153
  131. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  132. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  133. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  134. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  135. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  136. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  137. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  138. package/src/components/DataTable/components/DataTableLayout.tsx +5 -16
  139. package/src/components/DataTable/components/EditableRow.tsx +5 -7
  140. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  141. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  142. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  143. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  144. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  145. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  146. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  147. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  148. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  149. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  150. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  151. package/src/components/DataTable/components/index.ts +2 -1
  152. package/src/components/DataTable/types.ts +0 -18
  153. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  154. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  155. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  156. package/src/components/DateTimeField/DateTimeField.tsx +7 -8
  157. package/src/components/Dialog/Dialog.test.tsx +1 -0
  158. package/src/components/Dialog/Dialog.tsx +25 -8
  159. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  160. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  161. package/src/components/FileUpload/FileUpload.tsx +112 -130
  162. package/src/components/Progress/Progress.tsx +2 -4
  163. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  164. package/src/components/Select/Select.tsx +86 -77
  165. package/src/components/Select/types.ts +3 -0
  166. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  167. package/src/hooks/__tests__/hooks.integration.test.tsx +49 -49
  168. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  169. package/src/hooks/public/usePublicEvent.ts +5 -5
  170. package/src/hooks/public/usePublicEventLogo.ts +5 -5
  171. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  172. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  173. package/src/hooks/useAppConfig.ts +2 -2
  174. package/src/hooks/useEventTheme.test.ts +7 -7
  175. package/src/hooks/useEventTheme.ts +1 -4
  176. package/src/hooks/useFileDisplay.ts +2 -2
  177. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  178. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  179. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  180. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  181. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  182. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  183. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  184. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  185. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +10 -10
  186. package/src/styles/core.css +7 -0
  187. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  188. package/src/theming/parseEventColours.ts +22 -10
  189. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  190. package/src/utils/storage/README.md +1 -1
  191. package/cursor-rules/01-standards-compliance.mdc +0 -285
  192. package/cursor-rules/04-testing-standards.mdc +0 -270
  193. package/cursor-rules/05-bug-reports-and-features.mdc +0 -248
  194. package/cursor-rules/06-code-quality.mdc +0 -311
  195. package/cursor-rules/07-tech-stack-compliance.mdc +0 -216
  196. package/cursor-rules/10-error-handling-patterns.mdc +0 -179
  197. package/cursor-rules/11-performance-optimization.mdc +0 -169
  198. package/cursor-rules/12-ci-cd-integration.mdc +0 -150
  199. package/dist/DataTable-LRJL4IRV.js +0 -15
  200. package/dist/eslint-rules/rules/compliance.cjs +0 -348
  201. package/dist/eslint-rules/rules/components.cjs +0 -113
  202. package/dist/eslint-rules/rules/imports.cjs +0 -102
  203. package/docs/best-practices/README.md +0 -472
  204. package/docs/best-practices/accessibility.md +0 -604
  205. package/docs/best-practices/common-patterns.md +0 -516
  206. package/docs/best-practices/deployment.md +0 -1103
  207. package/docs/best-practices/performance.md +0 -1328
  208. package/docs/best-practices/security.md +0 -940
  209. package/docs/best-practices/testing.md +0 -1034
  210. package/docs/rbac/compliance/compliance-guide.md +0 -544
  211. package/docs/standards/01-standards-compliance.md +0 -188
  212. package/docs/standards/03-solid-principles.md +0 -39
  213. package/docs/standards/04-testing-standards.md +0 -36
  214. package/docs/standards/05-bug-reports-and-features.md +0 -27
  215. package/docs/standards/06-code-quality.md +0 -34
  216. package/docs/standards/07-tech-stack-compliance.md +0 -30
  217. package/docs/standards/10-error-handling-patterns.md +0 -401
  218. package/docs/standards/11-performance-optimization.md +0 -348
  219. package/docs/standards/12-ci-cd-integration.md +0 -370
  220. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +0 -192
  221. package/scripts/audit/audit-compliance.cjs +0 -1295
  222. package/scripts/audit/audit-components.cjs +0 -260
  223. package/scripts/audit/audit-rbac.cjs +0 -954
  224. package/scripts/audit/audit-standards.cjs +0 -1268
  225. package/scripts/audit/index.cjs +0 -1927
  226. package/src/components/DataTable/components/DataTableBody.tsx +0 -478
  227. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  228. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  229. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  230. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  231. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  232. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  233. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  234. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  235. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  236. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  237. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  238. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  239. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  240. package/src/components/DataTable/utils/debugTools.ts +0 -514
  241. package/src/eslint-rules/index.cjs +0 -22
  242. package/src/eslint-rules/rules/components.cjs +0 -113
  243. package/src/eslint-rules/rules/imports.cjs +0 -102
  244. package/src/eslint-rules/rules/rbac.cjs +0 -790
  245. package/src/eslint-rules/utils/helpers.cjs +0 -42
  246. package/src/eslint-rules/utils/manifest-loader.cjs +0 -75
@@ -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