@jmruthers/pace-core 0.6.10 → 0.6.11

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 (726) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/audit-tool/00-dependencies.cjs +46 -13
  3. package/audit-tool/audits/01-pace-core-compliance.cjs +96 -21
  4. package/audit-tool/audits/02-project-structure.cjs +13 -3
  5. package/audit-tool/audits/03-architecture.cjs +78 -4
  6. package/audit-tool/audits/04-code-quality.cjs +9 -2
  7. package/audit-tool/audits/05-styling.cjs +19 -7
  8. package/audit-tool/audits/06-security-rbac.cjs +105 -14
  9. package/audit-tool/audits/07-api-tech-stack.cjs +31 -15
  10. package/audit-tool/audits/08-testing-documentation.cjs +11 -3
  11. package/audit-tool/audits/09-operations.cjs +19 -7
  12. package/audit-tool/index.cjs +22 -11
  13. package/audit-tool/utils/report-utils.cjs +4 -0
  14. package/cursor-rules/01-pace-core-compliance.mdc +1 -0
  15. package/cursor-rules/02-project-structure.mdc +1 -0
  16. package/cursor-rules/03-architecture.mdc +3 -1
  17. package/cursor-rules/04-code-quality.mdc +1 -0
  18. package/cursor-rules/05-styling.mdc +41 -7
  19. package/cursor-rules/06-security-rbac.mdc +2 -1
  20. package/cursor-rules/07-api-tech-stack.mdc +1 -0
  21. package/cursor-rules/08-testing-documentation.mdc +1 -0
  22. package/cursor-rules/09-operations.mdc +1 -0
  23. package/dist/{DataTable-SAXFG4XI.js → DataTable-EFYP2QLE.js} +10 -7
  24. package/dist/{InactivityServiceProvider-DHryoh6K.d.ts → InactivityServiceProvider-BbxwwDz1.d.ts} +10 -1
  25. package/dist/{UnifiedAuthProvider-CiBAl9-s.d.ts → UnifiedAuthProvider-Bkt_tzdS.d.ts} +56 -24
  26. package/dist/{api-F47QJ7FX.js → api-BZR2CYXL.js} +3 -2
  27. package/dist/api-result-USV1Czr-.d.ts +51 -0
  28. package/dist/{audit-Z6ZZBWLU.js → audit-HI2DHUVU.js} +2 -1
  29. package/dist/{auth-BZOJqrdd.d.ts → auth-JvdRVaud.d.ts} +1 -1
  30. package/dist/{chunk-KSNLMI7N.js → chunk-2DL2WSOE.js} +1 -155
  31. package/dist/{chunk-MPY44PWB.js → chunk-2OEVOGGR.js} +4648 -3560
  32. package/dist/chunk-44CNXN4P.js +15 -0
  33. package/dist/{chunk-Y4PF6HIM.js → chunk-4R3T5ENU.js} +867 -786
  34. package/dist/{chunk-LNHFAF4X.js → chunk-7A6IMHH2.js} +289 -247
  35. package/dist/chunk-CU2BU2MQ.js +2 -0
  36. package/dist/{chunk-JJEYZ3DX.js → chunk-D6BMFMQZ.js} +37 -2
  37. package/dist/{chunk-BCTXBU6U.js → chunk-ENLXB7GP.js} +88 -71
  38. package/dist/{chunk-FBZ7U3ID.js → chunk-J2KQK6DG.js} +937 -987
  39. package/dist/{chunk-TFIPNIPE.js → chunk-KJXRL3XE.js} +3300 -2245
  40. package/dist/{chunk-3GWSPISD.js → chunk-L5LFKKLJ.js} +1 -1
  41. package/dist/{chunk-X5EAU5G7.js → chunk-PCSHBLPB.js} +132 -114
  42. package/dist/{chunk-NIU6DPQV.js → chunk-QRYSEPHB.js} +2 -0
  43. package/dist/{chunk-KYURMOQM.js → chunk-V7FTM2LU.js} +423 -320
  44. package/dist/chunk-WY6Y7KC3.js +264 -0
  45. package/dist/{chunk-FN52B75D.js → chunk-XOJME5T7.js} +176 -15
  46. package/dist/{chunk-7YDC7LMU.js → chunk-XPFVT3GN.js} +71 -66
  47. package/dist/{chunk-66R6RLUZ.js → chunk-YFTFFJIV.js} +3 -3
  48. package/dist/{chunk-W46INAVW.js → chunk-YYTWKVHO.js} +688 -570
  49. package/dist/components.d.ts +8 -7
  50. package/dist/components.js +17 -15
  51. package/dist/{database.generated-DT8JTZiP.d.ts → database.generated-qkdoiVrJ.d.ts} +45 -10
  52. package/dist/eslint-rules/index.cjs +3 -0
  53. package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
  54. package/dist/eslint-rules/rules/06-security-rbac.cjs +74 -0
  55. package/dist/{event-WTAQuGcq.d.ts → event-BfCox3N2.d.ts} +36 -10
  56. package/dist/{file-reference-BavO2eQj.d.ts → file-reference-DU1hcawx.d.ts} +29 -13
  57. package/dist/hooks.d.ts +22 -9
  58. package/dist/hooks.js +34 -25
  59. package/dist/icons/index.d.ts +1 -0
  60. package/dist/icons/index.js +1 -0
  61. package/dist/index.d.ts +66 -177
  62. package/dist/index.js +316 -340
  63. package/dist/pagination-BW1mqywp.d.ts +201 -0
  64. package/dist/providers.d.ts +6 -5
  65. package/dist/providers.js +5 -3
  66. package/dist/rbac/index.d.ts +123 -138
  67. package/dist/rbac/index.js +10 -8
  68. package/dist/theming/runtime.d.ts +19 -2
  69. package/dist/theming/runtime.js +1 -1
  70. package/dist/{timezone-K-ptz3HO.d.ts → timezone-BTWWXKVY.d.ts} +1 -1
  71. package/dist/types.d.ts +17 -10
  72. package/dist/types.js +1 -0
  73. package/dist/{usePublicPageContext-vxBlEHO9.d.ts → usePublicPageContext-B91dGYW1.d.ts} +433 -356
  74. package/dist/{usePublicRouteParams-G3Ks53mk.d.ts → usePublicRouteParams-BgV6VhMi.d.ts} +73 -4
  75. package/dist/utils.d.ts +163 -145
  76. package/dist/utils.js +42 -25
  77. package/docs/api/modules.md +782 -643
  78. package/docs/api-reference/rpc-functions.md +12 -3
  79. package/docs/core-concepts/rbac-system.md +8 -0
  80. package/docs/getting-started/cursor-rules.md +17 -20
  81. package/docs/getting-started/dependencies.md +1 -1
  82. package/docs/getting-started/setup.md +235 -0
  83. package/docs/implementation-guides/authentication.md +27 -0
  84. package/docs/implementation-guides/data-tables.md +176 -3
  85. package/docs/migration/ApiResult-migration.md +25 -0
  86. package/docs/rbac/api-reference.md +33 -31
  87. package/docs/standards/0-standards-overview.md +50 -15
  88. package/docs/standards/1-pace-core-compliance-standards.md +62 -57
  89. package/docs/standards/2-project-structure-standards.md +33 -16
  90. package/docs/standards/3-architecture-standards.md +41 -1
  91. package/docs/standards/4-code-quality-standards.md +26 -6
  92. package/docs/standards/5-styling-standards.md +35 -1
  93. package/docs/standards/6-security-rbac-standards.md +66 -0
  94. package/docs/standards/7-api-tech-stack-standards.md +25 -14
  95. package/docs/standards/8-testing-documentation-standards.md +31 -0
  96. package/docs/standards/9-operations-standards.md +19 -0
  97. package/docs/standards/README.md +20 -201
  98. package/docs/testing/test-setup-for-consumers.md +2 -0
  99. package/docs/troubleshooting/common-issues.md +17 -1
  100. package/docs/troubleshooting/organisation-context-setup.md +8 -0
  101. package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
  102. package/eslint-config-pace-core.cjs +20 -0
  103. package/package.json +14 -20
  104. package/scripts/{build-docs-incremental.js → build-docs.js} +3 -2
  105. package/scripts/setup.cjs +536 -0
  106. package/scripts/validate.cjs +480 -0
  107. package/src/__tests__/helpers/{__tests__/component-test-utils.test.tsx → component-test-utils.test.tsx} +3 -3
  108. package/src/__tests__/helpers/{__tests__/optimized-test-setup.test.ts → optimized-test-setup.test.ts} +2 -2
  109. package/src/__tests__/helpers/{__tests__/supabaseMock.test.ts → supabaseMock.test.ts} +2 -2
  110. package/src/__tests__/helpers/{__tests__/test-providers.test.tsx → test-providers.test.tsx} +1 -1
  111. package/src/__tests__/helpers/test-providers.tsx +37 -39
  112. package/src/__tests__/helpers/{__tests__/test-utils.test.tsx → test-utils.test.tsx} +4 -3
  113. package/src/__tests__/helpers/{__tests__/timer-utils.test.ts → timer-utils.test.ts} +2 -2
  114. package/src/assets/app-icons/index.test.ts +304 -0
  115. package/src/components/AddressField/AddressField.test.tsx +1 -1
  116. package/src/components/AddressField/AddressField.tsx +238 -212
  117. package/src/components/Button/Button.tsx +1 -1
  118. package/src/components/Card/Card.test.tsx +172 -17
  119. package/src/components/Card/Card.tsx +19 -10
  120. package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
  121. package/src/components/ContextSelector/{__tests__/ContextSelector.test.tsx → ContextSelector.test.tsx} +6 -6
  122. package/src/components/ContextSelector/ContextSelector.tsx +66 -280
  123. package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
  124. package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
  125. package/src/components/DataTable/AUDIT_REPORT.md +59 -44
  126. package/src/components/DataTable/{__tests__/DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx} +6 -6
  127. package/src/components/DataTable/{__tests__/DataTable.default-state.test.tsx → DataTable.default-state.test.tsx} +5 -5
  128. package/src/components/DataTable/{__tests__/DataTable.export.test.tsx → DataTable.export.test.tsx} +10 -10
  129. package/src/components/DataTable/{__tests__/DataTable.grouping-aggregation.test.tsx → DataTable.grouping-aggregation.test.tsx} +6 -6
  130. package/src/components/DataTable/{__tests__/DataTable.hooks.test.tsx → DataTable.hooks.test.tsx} +6 -6
  131. package/src/components/DataTable/{__tests__/DataTable.select-label-display.test.tsx → DataTable.select-label-display.test.tsx} +6 -6
  132. package/src/components/DataTable/DataTable.test.tsx +787 -416
  133. package/src/components/DataTable/DataTable.tsx +12 -12
  134. package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
  135. package/src/components/DataTable/{__tests__/DataTableCore.test-setup.ts → DataTableCore.test-setup.ts} +10 -9
  136. package/src/components/DataTable/{__tests__/DataTableCore.test.tsx → DataTableCore.test.tsx} +8 -8
  137. package/src/components/DataTable/{__tests__/README.md → README.md} +17 -7
  138. package/src/components/DataTable/TESTING.md +101 -0
  139. package/src/components/DataTable/{__tests__/a11y.basic.test.tsx → a11y.basic.test.tsx} +34 -34
  140. package/src/components/DataTable/components/DataTableCore.tsx +104 -864
  141. package/src/components/DataTable/components/{__tests__/GroupingDropdown.test.tsx → GroupingDropdown.test.tsx} +17 -8
  142. package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +61 -559
  144. package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
  145. package/src/components/DataTable/context/{__tests__/DataTableContext.test.tsx → DataTableContext.test.tsx} +2 -2
  146. package/src/components/DataTable/context/DataTableContext.tsx +7 -6
  147. package/src/components/DataTable/core/{__tests__/ColumnFactory.test.ts → ColumnFactory.test.ts} +2 -2
  148. package/src/components/DataTable/hooks/{__tests__/useColumnOrderPersistence.test.ts → useColumnOrderPersistence.test.ts} +2 -2
  149. package/src/components/DataTable/hooks/{__tests__/useColumnVisibilityPersistence.test.ts → useColumnVisibilityPersistence.test.ts} +2 -2
  150. package/src/components/DataTable/hooks/{__tests__/useDataTableConfiguration.test.ts → useDataTableConfiguration.test.ts} +3 -3
  151. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +14 -2
  152. package/src/components/DataTable/hooks/{__tests__/useDataTableDataPipeline.test.ts → useDataTableDataPipeline.test.ts} +6 -6
  153. package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
  154. package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
  155. package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
  156. package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +238 -0
  157. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
  158. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
  159. package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
  160. package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
  161. package/src/components/DataTable/hooks/{__tests__/useDataTablePermissions.test.ts → useDataTablePermissions.test.ts} +11 -11
  162. package/src/components/DataTable/hooks/useDataTablePermissions.ts +79 -247
  163. package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
  164. package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
  165. package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
  166. package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
  167. package/src/components/DataTable/hooks/useDataTableScope.test.ts +110 -0
  168. package/src/components/DataTable/hooks/useDataTableScope.ts +123 -0
  169. package/src/components/DataTable/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +47 -5
  170. package/src/components/DataTable/hooks/useDataTableState.ts +145 -94
  171. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
  172. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
  173. package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
  174. package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
  175. package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
  176. package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
  177. package/src/components/DataTable/hooks/{__tests__/useEffectiveColumnOrder.test.ts → useEffectiveColumnOrder.test.ts} +2 -2
  178. package/src/components/DataTable/hooks/{__tests__/useHierarchicalState.test.ts → useHierarchicalState.test.ts} +2 -2
  179. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.test.ts +3 -3
  180. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.ts +2 -2
  181. package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
  182. package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
  183. package/src/components/DataTable/hooks/{__tests__/useKeyboardNavigation.test.ts → useKeyboardNavigation.test.ts} +3 -3
  184. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +309 -269
  185. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.test.ts +3 -3
  186. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.ts +3 -3
  187. package/src/components/DataTable/hooks/{__tests__/useServerSideDataEffect.test.ts → useServerSideDataEffect.test.ts} +2 -2
  188. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +14 -3
  189. package/src/components/DataTable/hooks/{__tests__/useTableColumns.test.ts → useTableColumns.test.ts} +2 -2
  190. package/src/components/DataTable/hooks/{__tests__/useTableHandlers.test.ts → useTableHandlers.test.ts} +25 -4
  191. package/src/components/DataTable/hooks/useTableHandlers.ts +5 -2
  192. package/src/components/DataTable/index.ts +18 -17
  193. package/src/components/DataTable/{__tests__/keyboard.test.tsx → keyboard.test.tsx} +41 -63
  194. package/src/components/DataTable/{__tests__/mocks → mocks}/MockRBACProvider.tsx +1 -1
  195. package/src/components/DataTable/{__tests__/pagination.modes.test.tsx → pagination.modes.test.tsx} +6 -6
  196. package/src/components/DataTable/{__tests__/ssr.strict-mode.test.tsx → ssr.strict-mode.test.tsx} +2 -2
  197. package/src/components/DataTable/{__tests__/styles.test.ts → styles.test.ts} +1 -4
  198. package/src/components/DataTable/styles.ts +0 -1
  199. package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
  200. package/src/components/DataTable/{__tests__/test-utils → test-utils}/dataFactories.ts +2 -2
  201. package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
  202. package/src/components/DataTable/{__tests__/test-utils/sharedTestUtils.tsx → test-utils/sharedTestUtils.ts} +97 -66
  203. package/src/components/DataTable/{__tests__/test-utils.ts → test-utils.ts} +1 -1
  204. package/src/components/DataTable/types/actions.ts +71 -0
  205. package/src/components/DataTable/types/base.ts +39 -0
  206. package/src/components/DataTable/types/columns.ts +125 -0
  207. package/src/components/DataTable/types/export.ts +32 -0
  208. package/src/components/DataTable/types/features.ts +81 -0
  209. package/src/components/DataTable/types/hierarchical.ts +44 -0
  210. package/src/components/DataTable/types/index.ts +43 -0
  211. package/src/components/DataTable/types/pagination.ts +85 -0
  212. package/src/components/DataTable/types/performance.ts +47 -0
  213. package/src/components/DataTable/types/props.ts +62 -0
  214. package/src/components/DataTable/types/rbac.ts +45 -0
  215. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableCore.test.tsx +430 -28
  216. package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
  217. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableErrorBoundary.test.tsx +4 -4
  218. package/src/components/DataTable/{components → ui/layout}/DataTableErrorBoundary.tsx +7 -7
  219. package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
  220. package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
  221. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
  222. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
  223. package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
  224. package/src/components/DataTable/{components → ui/modals}/DataTableModals.tsx +36 -28
  225. package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
  226. package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
  227. package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
  228. package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
  229. package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
  230. package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
  231. package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
  232. package/src/components/DataTable/{components/__tests__ → ui/shared}/AccessDeniedPage.test.tsx +2 -2
  233. package/src/components/DataTable/{components → ui/shared}/AccessDeniedPage.tsx +2 -2
  234. package/src/components/DataTable/{components/__tests__ → ui/shared}/ActionButtons.test.tsx +6 -4
  235. package/src/components/DataTable/{components → ui/shared}/ActionButtons.tsx +4 -4
  236. package/src/components/DataTable/{components/__tests__ → ui/shared}/ColumnFilter.test.tsx +29 -16
  237. package/src/components/DataTable/{components → ui/shared}/ColumnFilter.tsx +4 -4
  238. package/src/components/DataTable/{components/__tests__ → ui/shared}/PaginationControls.test.tsx +38 -16
  239. package/src/components/DataTable/{components → ui/shared}/PaginationControls.tsx +21 -15
  240. package/src/components/DataTable/{components/__tests__ → ui/shared}/SortIndicator.test.tsx +2 -2
  241. package/src/components/DataTable/{components → ui/shared}/SortIndicator.tsx +1 -1
  242. package/src/components/DataTable/{components/__tests__ → ui/table}/EditFields.test.tsx +3 -3
  243. package/src/components/DataTable/{components → ui/table}/EditFields.tsx +138 -69
  244. package/src/components/DataTable/{components/__tests__ → ui/table}/EditableRow.test.tsx +36 -27
  245. package/src/components/DataTable/{components → ui/table}/EditableRow.tsx +86 -104
  246. package/src/components/DataTable/{components/__tests__ → ui/table}/EmptyState.test.tsx +2 -62
  247. package/src/components/DataTable/{components → ui/table}/EmptyState.tsx +7 -15
  248. package/src/components/DataTable/{components/__tests__ → ui/table}/FilterRow.test.tsx +5 -4
  249. package/src/components/DataTable/{components → ui/table}/FilterRow.tsx +3 -3
  250. package/src/components/DataTable/{components/__tests__ → ui/table}/LoadingState.test.tsx +6 -10
  251. package/src/components/DataTable/{components → ui/table}/LoadingState.tsx +4 -4
  252. package/src/components/DataTable/{components/__tests__ → ui/table}/RowComponent.test.tsx +412 -17
  253. package/src/components/DataTable/{components → ui/table}/RowComponent.tsx +183 -177
  254. package/src/components/DataTable/{components/__tests__ → ui/table}/UnifiedTableBody.test.tsx +425 -16
  255. package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
  256. package/src/components/DataTable/{components/__tests__ → ui/table}/cellValueUtils.test.ts +2 -2
  257. package/src/components/DataTable/{components → ui/table}/cellValueUtils.ts +1 -1
  258. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/BulkOperationsDropdown.test.tsx +12 -5
  259. package/src/components/DataTable/{components → ui/toolbar}/BulkOperationsDropdown.tsx +3 -3
  260. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/ColumnVisibilityDropdown.test.tsx +7 -4
  261. package/src/components/DataTable/{components → ui/toolbar}/ColumnVisibilityDropdown.tsx +7 -7
  262. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/DataTableToolbar.test.tsx +4 -4
  263. package/src/components/DataTable/{components → ui/toolbar}/DataTableToolbar.tsx +4 -4
  264. package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
  265. package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
  266. package/src/components/DataTable/utils/{__tests__/a11yUtils.test.ts → a11yUtils.test.ts} +2 -2
  267. package/src/components/DataTable/utils/{__tests__/aggregationUtils.test.ts → aggregationUtils.test.ts} +3 -3
  268. package/src/components/DataTable/utils/{__tests__/columnUtils.test.ts → columnUtils.test.ts} +2 -2
  269. package/src/components/DataTable/utils/csvParse.test.ts +74 -0
  270. package/src/components/DataTable/utils/csvParse.ts +65 -0
  271. package/src/components/DataTable/utils/{__tests__/errorHandling.test.ts → errorHandling.test.ts} +2 -2
  272. package/src/components/DataTable/utils/{__tests__/exportUtils.test.ts → exportUtils.test.ts} +3 -3
  273. package/src/components/DataTable/utils/{__tests__/flexibleImport.test.ts → flexibleImport.test.ts} +2 -2
  274. package/src/components/DataTable/utils/flexibleImport.ts +3 -186
  275. package/src/components/DataTable/utils/{__tests__/hierarchicalSorting.test.ts → hierarchicalSorting.test.ts} +3 -3
  276. package/src/components/DataTable/utils/{__tests__/hierarchicalUtils.test.ts → hierarchicalUtils.test.ts} +3 -3
  277. package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
  278. package/src/components/DataTable/utils/importDateParser.ts +114 -0
  279. package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
  280. package/src/components/DataTable/utils/importValueParser.ts +91 -0
  281. package/src/components/DataTable/utils/{__tests__/paginationUtils.test.ts → paginationUtils.test.ts} +2 -2
  282. package/src/components/DataTable/utils/paginationUtils.ts +6 -3
  283. package/src/components/DataTable/utils/{__tests__/performanceUtils.test.ts → performanceUtils.test.ts} +3 -3
  284. package/src/components/DataTable/utils/{__tests__/rowUtils.test.ts → rowUtils.test.ts} +3 -3
  285. package/src/components/DataTable/utils/{__tests__/selectFieldUtils.test.ts → selectFieldUtils.test.ts} +66 -3
  286. package/src/components/DataTable/utils/selectFieldUtils.ts +97 -60
  287. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -1
  288. package/src/components/DateTimeField/DateTimeField.test.tsx +1 -1
  289. package/src/components/Dialog/Dialog.test-utils.ts +49 -0
  290. package/src/components/Dialog/Dialog.test.tsx +896 -89
  291. package/src/components/Dialog/Dialog.tsx +174 -882
  292. package/src/components/Dialog/dialogLock.test.ts +238 -0
  293. package/src/components/Dialog/dialogLock.ts +98 -0
  294. package/src/components/Dialog/index.ts +2 -0
  295. package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
  296. package/src/components/Dialog/useDialogDimensions.ts +140 -0
  297. package/src/components/Dialog/useDialogLifecycle.test.ts +358 -0
  298. package/src/components/Dialog/useDialogLifecycle.ts +135 -0
  299. package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
  300. package/src/components/Dialog/useDialogPersistence.ts +357 -0
  301. package/src/components/FileDisplay/FileDisplay.test.tsx +40 -40
  302. package/src/components/FileDisplay/FileDisplay.tsx +24 -656
  303. package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
  304. package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
  305. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
  306. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
  307. package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
  308. package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
  309. package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
  310. package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
  311. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
  312. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
  313. package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
  314. package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
  315. package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
  316. package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
  317. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
  318. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
  319. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
  320. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
  321. package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
  322. package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
  323. package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
  324. package/src/components/FileDisplay/fallbackUtils.ts +44 -0
  325. package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
  326. package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
  327. package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
  328. package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
  329. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.test.ts +40 -42
  330. package/src/components/FileDisplay/useFileDisplay.ts +515 -0
  331. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.unit.test.ts +406 -77
  332. package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
  333. package/src/{hooks/public → components/FileDisplay}/usePublicFileDisplay.test.ts +94 -88
  334. package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
  335. package/src/components/FileUpload/FileUpload.test.tsx +16 -10
  336. package/src/components/FileUpload/FileUpload.tsx +107 -525
  337. package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
  338. package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
  339. package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
  340. package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
  341. package/src/components/FileUpload/useFileUploadManager.ts +454 -0
  342. package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
  343. package/src/components/FileUpload/useResolvedAppId.ts +77 -0
  344. package/src/components/Footer/Footer.test.tsx +6 -292
  345. package/src/components/Footer/Footer.tsx +8 -125
  346. package/src/components/Form/Form.test.tsx +44 -27
  347. package/src/components/Form/Form.tsx +64 -287
  348. package/src/components/Form/useFormPersistence.ts +257 -0
  349. package/src/components/Header/Header.test.tsx +17 -18
  350. package/src/components/Header/Header.tsx +10 -1
  351. package/src/components/Input/Input.tsx +1 -1
  352. package/src/components/Label/Label.test.tsx +1 -1
  353. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +1 -1
  354. package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
  355. package/src/components/NavigationMenu/NavigationMenu.test.tsx +1029 -26
  356. package/src/components/NavigationMenu/NavigationMenu.tsx +61 -361
  357. package/src/components/NavigationMenu/index.ts +6 -1
  358. package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
  359. package/src/components/NavigationMenu/{__tests__/useNavigationFiltering.test.ts → useNavigationFiltering.test.ts} +68 -53
  360. package/src/components/NavigationMenu/useNavigationFiltering.ts +197 -296
  361. package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
  362. package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +77 -62
  363. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +3 -3
  364. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +16 -19
  365. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +529 -5
  366. package/src/components/PaceAppLayout/PaceAppLayout.tsx +280 -756
  367. package/src/components/PaceAppLayout/useFilteredNavItems.ts +304 -0
  368. package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +142 -0
  369. package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
  370. package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
  371. package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +79 -0
  372. package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
  373. package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
  374. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +31 -25
  375. package/src/components/PaceLoginPage/PaceLoginPage.tsx +31 -122
  376. package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
  377. package/src/components/Progress/Progress.tsx +1 -2
  378. package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -235
  379. package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
  380. package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
  381. package/src/components/PublicLayout/PublicLayout.test.tsx +217 -36
  382. package/src/components/PublicLayout/PublicPageLayout.tsx +132 -73
  383. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -1
  384. package/src/components/Select/Select.test.tsx +1 -1
  385. package/src/components/Select/Select.tsx +28 -18
  386. package/src/components/Select/{__tests__/context.test.tsx → context.test.tsx} +3 -3
  387. package/src/components/Select/{utils/__tests__/text.test.tsx → text.test.tsx} +2 -2
  388. package/src/components/Select/{utils/text.ts → text.ts} +1 -1
  389. package/src/components/Select/{hooks/__tests__/useSelectEvents.test.ts → useSelectEvents.test.ts} +5 -5
  390. package/src/components/Select/{hooks/useSelectEvents.ts → useSelectEvents.ts} +2 -2
  391. package/src/components/Select/{hooks/__tests__/useSelectSearch.test.tsx → useSelectSearch.test.tsx} +7 -7
  392. package/src/components/Select/{hooks/useSelectSearch.ts → useSelectSearch.ts} +2 -2
  393. package/src/components/Select/{hooks/__tests__/useSelectState.test.ts → useSelectState.test.ts} +16 -2
  394. package/src/components/Select/{hooks/useSelectState.ts → useSelectState.ts} +3 -3
  395. package/src/components/Table/Table.test.tsx +348 -0
  396. package/src/components/Tabs/Tabs.test.tsx +270 -0
  397. package/src/components/Tabs/Tabs.tsx +1 -1
  398. package/src/components/Toast/Toast.test.tsx +420 -0
  399. package/src/components/{__tests__/index.test.ts → index.test.ts} +2 -2
  400. package/src/constants/{__tests__/performance.test.ts → performance.test.ts} +2 -2
  401. package/src/hooks/{__tests__/ServiceHooks.test.tsx → ServiceHooks.test.tsx} +8 -8
  402. package/src/hooks/{__tests__/hooks.integration.test.tsx → hooks.integration.test.tsx} +11 -11
  403. package/src/hooks/index.ts +7 -4
  404. package/src/hooks/{__tests__/index.unit.test.ts → index.unit.test.ts} +2 -2
  405. package/src/hooks/public/usePublicEvent.test.ts +1 -1
  406. package/src/hooks/public/usePublicEventLogo.test.ts +1 -1
  407. package/src/hooks/public/usePublicRouteParams.test.ts +1 -1
  408. package/src/hooks/services/useAuth.ts +9 -7
  409. package/src/hooks/useAddressAutocomplete.test.ts +22 -22
  410. package/src/hooks/useAddressAutocomplete.ts +90 -75
  411. package/src/hooks/{__tests__/useAppConfig.unit.test.ts → useAppConfig.unit.test.ts} +328 -22
  412. package/src/hooks/{__tests__/useComponentPerformance.unit.test.tsx → useComponentPerformance.unit.test.tsx} +27 -41
  413. package/src/hooks/useDataTablePerformance.ts +100 -120
  414. package/src/hooks/{__tests__/useDataTablePerformance.unit.test.ts → useDataTablePerformance.unit.test.ts} +5 -5
  415. package/src/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +2 -2
  416. package/src/hooks/{__tests__/useDebounce.unit.test.ts → useDebounce.unit.test.ts} +2 -2
  417. package/src/hooks/useEventTheme.test.ts +4 -1
  418. package/src/hooks/useEventTheme.ts +49 -21
  419. package/src/hooks/useEvents.ts +41 -1
  420. package/src/hooks/{__tests__/useEvents.unit.test.ts → useEvents.unit.test.ts} +5 -5
  421. package/src/hooks/useFileReference.test.ts +44 -41
  422. package/src/hooks/useFileReference.ts +182 -173
  423. package/src/hooks/useFileUrl.ts +1 -1
  424. package/src/hooks/{__tests__/useFileUrl.unit.test.ts → useFileUrl.unit.test.ts} +26 -36
  425. package/src/hooks/{__tests__/useFileUrlCache.test.ts → useFileUrlCache.test.ts} +8 -8
  426. package/src/hooks/useFileUrlCache.ts +1 -1
  427. package/src/hooks/{__tests__/useFocusManagement.unit.test.ts → useFocusManagement.unit.test.ts} +2 -2
  428. package/src/hooks/{__tests__/useFocusTrap.unit.test.tsx → useFocusTrap.unit.test.tsx} +2 -2
  429. package/src/hooks/{__tests__/useFormDialog.test.ts → useFormDialog.test.ts} +2 -2
  430. package/src/hooks/useInactivityTracker.ts +138 -131
  431. package/src/hooks/{__tests__/useInactivityTracker.unit.test.ts → useInactivityTracker.unit.test.ts} +3 -3
  432. package/src/hooks/{__tests__/useIsMobile.unit.test.ts → useIsMobile.unit.test.ts} +2 -2
  433. package/src/hooks/useIsPrint.ts +62 -0
  434. package/src/hooks/useIsPrint.unit.test.ts +545 -0
  435. package/src/hooks/{__tests__/useKeyboardShortcuts.unit.test.ts → useKeyboardShortcuts.unit.test.ts} +2 -2
  436. package/src/hooks/{__tests__/useOrganisationPermissions.unit.test.tsx → useOrganisationPermissions.unit.test.tsx} +4 -4
  437. package/src/hooks/useOrganisationSecurity.test.ts +3 -3
  438. package/src/hooks/useOrganisationSecurity.ts +190 -201
  439. package/src/hooks/{__tests__/useOrganisationSecurity.unit.test.tsx → useOrganisationSecurity.unit.test.tsx} +61 -63
  440. package/src/hooks/{__tests__/useOrganisations.unit.test.ts → useOrganisations.unit.test.ts} +5 -5
  441. package/src/hooks/{__tests__/usePerformanceMonitor.unit.test.ts → usePerformanceMonitor.unit.test.ts} +13 -14
  442. package/src/hooks/{__tests__/usePermissionCache.test.ts → usePermissionCache.test.ts} +26 -27
  443. package/src/hooks/usePermissionCache.ts +276 -271
  444. package/src/hooks/{__tests__/usePreventTabReload.test.ts → usePreventTabReload.test.ts} +2 -2
  445. package/src/hooks/{__tests__/usePublicEvent.simple.test.ts → usePublicEvent.simple.test.ts} +4 -4
  446. package/src/hooks/{__tests__/usePublicEvent.test.ts → usePublicEvent.test.ts} +4 -4
  447. package/src/hooks/{__tests__/usePublicEvent.unit.test.ts → usePublicEvent.unit.test.ts} +4 -4
  448. package/src/hooks/{__tests__/usePublicFileDisplay.test.ts → usePublicFileDisplay.test.ts} +12 -12
  449. package/src/hooks/{__tests__/usePublicRouteParams.unit.test.ts → usePublicRouteParams.unit.test.ts} +3 -3
  450. package/src/hooks/{__tests__/useQueryCache.test.ts → useQueryCache.test.ts} +2 -2
  451. package/src/hooks/useQueryCache.ts +0 -2
  452. package/src/hooks/{__tests__/useRBAC.unit.test.ts → useRBAC.unit.test.ts} +55 -38
  453. package/src/hooks/{__tests__/useSessionDraft.test.ts → useSessionDraft.test.ts} +2 -2
  454. package/src/hooks/{__tests__/useSessionRestoration.unit.test.tsx → useSessionRestoration.unit.test.tsx} +10 -19
  455. package/src/hooks/useStorage.ts +21 -16
  456. package/src/hooks/{__tests__/useStorage.unit.test.ts → useStorage.unit.test.ts} +38 -75
  457. package/src/hooks/{__tests__/useToast.test.ts → useToast.test.ts} +2 -2
  458. package/src/hooks/{__tests__/useToast.unit.test.tsx → useToast.unit.test.tsx} +2 -2
  459. package/src/hooks/{__tests__/useZodForm.unit.test.tsx → useZodForm.unit.test.tsx} +2 -2
  460. package/src/icons/{__tests__/index.test.ts → index.test.ts} +2 -2
  461. package/src/icons/index.ts +2 -0
  462. package/src/{__tests__/index.test.ts → index.test.ts} +3 -7
  463. package/src/index.ts +15 -7
  464. package/src/providers/{__tests__/AuthProvider.test.tsx → AuthProvider.test.tsx} +3 -3
  465. package/src/providers/{__tests__/EventProvider.test.tsx → EventProvider.test.tsx} +3 -3
  466. package/src/providers/InactivityProvider.test-helper.tsx +40 -0
  467. package/src/providers/{__tests__/InactivityProvider.test.tsx → InactivityProvider.test.tsx} +14 -21
  468. package/src/providers/{__tests__/ProviderLifecycle.test.tsx → ProviderLifecycle.test.tsx} +4 -4
  469. package/src/providers/{__tests__/UnifiedAuthProvider.test.tsx → UnifiedAuthProvider.test.tsx} +1 -1
  470. package/src/providers/{__tests__/index.test.ts → index.test.ts} +2 -2
  471. package/src/providers/services/{__tests__/AuthServiceProvider.integration.test.tsx → AuthServiceProvider.integration.test.tsx} +4 -4
  472. package/src/providers/services/{__tests__/AuthServiceProvider.test.tsx → AuthServiceProvider.test.tsx} +7 -7
  473. package/src/providers/services/{__tests__/EventServiceProvider.test.tsx → EventServiceProvider.test.tsx} +7 -7
  474. package/src/providers/services/{__tests__/InactivityServiceProvider.test.tsx → InactivityServiceProvider.test.tsx} +5 -5
  475. package/src/providers/services/{__tests__/OrganisationServiceProvider.test.tsx → OrganisationServiceProvider.test.tsx} +6 -6
  476. package/src/providers/services/UnifiedAuthContext.ts +30 -27
  477. package/src/providers/services/{__tests__/UnifiedAuthProvider.advanced.test.tsx → UnifiedAuthProvider.advanced.test.tsx} +8 -9
  478. package/src/providers/services/{__tests__/UnifiedAuthProvider.appId.test.tsx → UnifiedAuthProvider.appId.test.tsx} +25 -25
  479. package/src/providers/services/{__tests__/UnifiedAuthProvider.integration.test.tsx → UnifiedAuthProvider.integration.test.tsx} +14 -11
  480. package/src/providers/services/UnifiedAuthProvider.tsx +115 -360
  481. package/src/providers/services/{__tests__/contexts.test.tsx → contexts.test.tsx} +6 -6
  482. package/src/providers/services/{__tests__/useUnifiedAuth.test.tsx → useUnifiedAuth.test.tsx} +6 -6
  483. package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
  484. package/src/providers/useInactivity.test-helper.ts +27 -0
  485. package/src/rbac/{__tests__/adapters.comprehensive.test.tsx → adapters.comprehensive.test.tsx} +24 -24
  486. package/src/rbac/adapters.test.tsx +22 -22
  487. package/src/rbac/adapters.tsx +29 -29
  488. package/src/rbac/api.test.ts +973 -42
  489. package/src/rbac/api.ts +228 -253
  490. package/src/rbac/{__tests__/audit-batched.test.ts → audit-batched.test.ts} +6 -6
  491. package/src/rbac/audit.ts +4 -1
  492. package/src/rbac/{__tests__/auth-rbac-security.integration.test.tsx → auth-rbac-security.integration.test.tsx} +1 -1
  493. package/src/rbac/{__tests__/auth-rbac.e2e.test.tsx → auth-rbac.e2e.test.tsx} +27 -34
  494. package/src/rbac/cache-invalidation.test.ts +715 -0
  495. package/src/rbac/components/{__tests__/AccessDenied.test.tsx → AccessDenied.test.tsx} +3 -3
  496. package/src/rbac/components/{__tests__/NavigationGuard.test.tsx → NavigationGuard.test.tsx} +13 -11
  497. package/src/{__tests__/rbac/PagePermissionGuard.test.tsx → rbac/components/PagePermissionGuard.guard.test.tsx} +33 -19
  498. package/src/rbac/components/{__tests__/PagePermissionGuard.performance.test.tsx → PagePermissionGuard.performance.test.tsx} +30 -9
  499. package/src/rbac/components/{__tests__/PagePermissionGuard.race-condition.test.tsx → PagePermissionGuard.race-condition.test.tsx} +7 -7
  500. package/src/rbac/components/{__tests__/PagePermissionGuard.test.tsx → PagePermissionGuard.test.tsx} +10 -10
  501. package/src/rbac/components/PagePermissionGuard.tsx +177 -372
  502. package/src/rbac/components/{__tests__/PagePermissionGuard.verification.test.tsx → PagePermissionGuard.verification.test.tsx} +7 -7
  503. package/src/rbac/config.ts +58 -18
  504. package/src/rbac/{__tests__/engine.comprehensive.test.ts → engine.comprehensive.test.ts} +3 -3
  505. package/src/rbac/engine.test.ts +494 -0
  506. package/src/rbac/errors.ts +89 -55
  507. package/src/rbac/hooks/permissions/runPermissionCheck.ts +77 -0
  508. package/src/rbac/hooks/permissions/{__tests__/useAccessLevel.test.ts → useAccessLevel.test.ts} +40 -40
  509. package/src/rbac/hooks/permissions/useAccessLevel.ts +16 -6
  510. package/src/rbac/hooks/permissions/{__tests__/useCan.test.ts → useCan.test.ts} +41 -41
  511. package/src/rbac/hooks/permissions/useCan.ts +170 -252
  512. package/src/rbac/hooks/permissions/{__tests__/useMultiplePermissions.test.ts → useMultiplePermissions.test.ts} +49 -49
  513. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +6 -2
  514. package/src/rbac/hooks/permissions/{__tests__/usePermissions.test.ts → usePermissions.test.ts} +10 -12
  515. package/src/rbac/hooks/permissions/usePermissions.ts +36 -65
  516. package/src/rbac/hooks/useCan.test.ts +42 -42
  517. package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
  518. package/src/rbac/hooks/usePageGuardScope.ts +117 -0
  519. package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
  520. package/src/rbac/hooks/{__tests__/usePermissions.integration.test.ts → usePermissions.integration.test.ts} +9 -9
  521. package/src/{__tests__/hooks/usePermissions.test.ts → rbac/hooks/usePermissions.stability.test.ts} +18 -18
  522. package/src/rbac/hooks/usePermissions.test.ts +54 -54
  523. package/src/rbac/hooks/useRBAC.test.ts +313 -217
  524. package/src/rbac/hooks/useRBAC.ts +145 -81
  525. package/src/rbac/hooks/useResourcePermissions.test.ts +25 -25
  526. package/src/rbac/hooks/useResourcePermissions.ts +68 -134
  527. package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
  528. package/src/rbac/hooks/useRoleManagement.test.ts +27 -112
  529. package/src/rbac/hooks/useRoleManagement.ts +153 -585
  530. package/src/rbac/hooks/{__tests__/useSecureSupabase.test.ts → useSecureSupabase.test.ts} +17 -17
  531. package/src/rbac/hooks/useSecureSupabase.ts +10 -2
  532. package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
  533. package/src/rbac/{__tests__/performance.test.ts → performance.test.ts} +1 -1
  534. package/src/rbac/{__tests__/rbac-core.test.tsx → rbac-core.test.tsx} +3 -3
  535. package/src/rbac/{__tests__/rbac-engine-core-logic.test.ts → rbac-engine-core-logic.test.ts} +2 -2
  536. package/src/rbac/{__tests__/rbac-engine-simplified.test.ts → rbac-engine-simplified.test.ts} +3 -3
  537. package/src/rbac/{__tests__/rbac-functions.test.ts → rbac-functions.test.ts} +57 -0
  538. package/src/rbac/{__tests__/rbac-role-isolation.test.ts → rbac-role-isolation.test.ts} +2 -2
  539. package/src/rbac/request-deduplication.test.ts +14 -9
  540. package/src/rbac/request-deduplication.ts +5 -4
  541. package/src/rbac/{__tests__/scenarios.user-role.test.tsx → scenarios.user-role.test.tsx} +23 -23
  542. package/src/rbac/secureClient.test.ts +514 -83
  543. package/src/rbac/secureClient.ts +8 -2
  544. package/src/rbac/security.test.ts +323 -0
  545. package/src/rbac/types/roleManagement.ts +66 -0
  546. package/src/rbac/utils/{__tests__/clientSecurity.test.ts → clientSecurity.test.ts} +4 -4
  547. package/src/rbac/utils/{__tests__/contextValidator.test.ts → contextValidator.test.ts} +4 -4
  548. package/src/rbac/utils/contextValidator.ts +5 -1
  549. package/src/rbac/utils/{__tests__/deep-equal.test.ts → deep-equal.test.ts} +1 -1
  550. package/src/rbac/utils/{__tests__/eventContext.test.ts → eventContext.test.ts} +36 -21
  551. package/src/rbac/utils/eventContext.ts +37 -33
  552. package/src/rbac/utils/fetchPermissionMap.ts +13 -0
  553. package/src/rbac/utils/permissionMapHelpers.ts +34 -0
  554. package/src/rbac/utils/roleManagementRpc.ts +303 -0
  555. package/src/services/{__tests__/AuthService.edge-cases.test.ts → AuthService.edge-cases.test.ts} +19 -19
  556. package/src/services/{__tests__/AuthService.restoreSession.test.ts → AuthService.restoreSession.test.ts} +2 -2
  557. package/src/services/{__tests__/AuthService.test.ts → AuthService.test.ts} +89 -55
  558. package/src/services/AuthService.ts +184 -205
  559. package/src/services/{__tests__/BaseService.edge-cases.test.ts → BaseService.edge-cases.test.ts} +3 -3
  560. package/src/services/{__tests__/BaseService.test.ts → BaseService.test.ts} +2 -2
  561. package/src/services/{__tests__/EventService.edge-cases.test.ts → EventService.edge-cases.test.ts} +27 -24
  562. package/src/services/{__tests__/EventService.eventColours.test.ts → EventService.eventColours.test.ts} +1 -1
  563. package/src/services/{__tests__/EventService.test.ts → EventService.test.ts} +256 -24
  564. package/src/services/EventService.ts +242 -312
  565. package/src/services/{__tests__/InactivityService.edge-cases.test.ts → InactivityService.edge-cases.test.ts} +3 -3
  566. package/src/services/{__tests__/InactivityService.lifecycle.test.ts → InactivityService.lifecycle.test.ts} +2 -2
  567. package/src/services/{__tests__/InactivityService.test.ts → InactivityService.test.ts} +179 -4
  568. package/src/services/InactivityService.ts +172 -213
  569. package/src/services/{__tests__/OrganisationService.edge-cases.test.ts → OrganisationService.edge-cases.test.ts} +5 -5
  570. package/src/services/{__tests__/OrganisationService.pagination.test.ts → OrganisationService.pagination.test.ts} +4 -4
  571. package/src/services/{__tests__/OrganisationService.test.ts → OrganisationService.test.ts} +410 -7
  572. package/src/services/OrganisationService.ts +184 -238
  573. package/src/services/base/BaseService.test.ts +1 -1
  574. package/src/services/interfaces/{__tests__/IAuthService.test.ts → IAuthService.test.ts} +21 -27
  575. package/src/services/interfaces/IAuthService.ts +10 -9
  576. package/src/services/interfaces/{__tests__/IEventService.test.ts → IEventService.test.ts} +4 -4
  577. package/src/services/interfaces/{__tests__/IInactivityService.test.ts → IInactivityService.test.ts} +3 -3
  578. package/src/services/interfaces/{__tests__/IOrganisationService.test.ts → IOrganisationService.test.ts} +3 -3
  579. package/src/styles/core.css +243 -12
  580. package/src/theming/{__tests__/parseEventColours.test.ts → parseEventColours.test.ts} +1 -1
  581. package/src/theming/{__tests__/runtime.test.ts → runtime.test.ts} +8 -17
  582. package/src/theming/runtime.ts +71 -2
  583. package/src/types/api-result.ts +53 -0
  584. package/src/types/{__tests__/core.test.ts → core.test.ts} +2 -2
  585. package/src/types/{__tests__/database-generated.test.ts → database-generated.test.ts} +3 -3
  586. package/src/types/database.generated.ts +45 -10
  587. package/src/types/event.ts +38 -18
  588. package/src/types/{__tests__/file-reference.test.ts → file-reference.test.ts} +13 -13
  589. package/src/types/file-reference.ts +37 -12
  590. package/src/types/{__tests__/guards.test.ts → guards.test.ts} +2 -2
  591. package/src/types/{__tests__/index.test.ts → index.test.ts} +2 -2
  592. package/src/types/index.ts +3 -0
  593. package/src/types/{__tests__/organisation.roles.test.ts → organisation.roles.test.ts} +1 -1
  594. package/src/types/{__tests__/organisation.test.ts → organisation.test.ts} +3 -31
  595. package/src/types/organisation.ts +15 -15
  596. package/src/types/supabase.ts +13 -4
  597. package/src/types/{__tests__/theme.test.ts → theme.test.ts} +1 -1
  598. package/src/types/{__tests__/type-validation.test.ts → type-validation.test.ts} +1 -1
  599. package/src/types/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  600. package/src/utils/app/appIdResolver.test.ts +98 -71
  601. package/src/utils/app/appIdResolver.ts +31 -20
  602. package/src/utils/{__tests__/appConfig.unit.test.ts → appConfig.unit.test.ts} +1 -1
  603. package/src/utils/{__tests__/audit.unit.test.ts → audit.unit.test.ts} +1 -1
  604. package/src/utils/{__tests__/auth-utils.unit.test.ts → auth-utils.unit.test.ts} +16 -17
  605. package/src/utils/{__tests__/bundleAnalysis.unit.test.ts → bundleAnalysis.unit.test.ts} +35 -35
  606. package/src/utils/{__tests__/cn.unit.test.ts → cn.unit.test.ts} +1 -1
  607. package/src/utils/context/organisationContext.test.ts +105 -91
  608. package/src/utils/context/organisationContext.ts +29 -40
  609. package/src/utils/core/{__tests__/cn.test.ts → cn.test.ts} +3 -3
  610. package/src/utils/core/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +2 -2
  611. package/src/utils/core/{__tests__/logger.test.ts → logger.test.ts} +2 -2
  612. package/src/utils/core/mergeRefs.ts +24 -0
  613. package/src/utils/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +1 -1
  614. package/src/utils/{__tests__/deviceFingerprint.unit.test.ts → deviceFingerprint.unit.test.ts} +1 -1
  615. package/src/utils/dynamic/createLazyComponent.tsx +9 -1
  616. package/src/utils/dynamic/{__tests__/dynamicUtils.test.ts → dynamicUtils.test.ts} +2 -2
  617. package/src/utils/dynamic/{__tests__/lazyLoad.test.tsx → lazyLoad.test.tsx} +2 -2
  618. package/src/utils/{__tests__/dynamicUtils.unit.test.ts → dynamicUtils.unit.test.ts} +1 -1
  619. package/src/utils/file-reference/{__tests__/file-reference.test.ts → file-reference.test.ts} +214 -289
  620. package/src/utils/file-reference/index.ts +330 -347
  621. package/src/utils/{__tests__/formatDate.unit.test.ts → formatDate.unit.test.ts} +2 -2
  622. package/src/utils/formatting/formatDateTimeTimezone.test.ts +1 -1
  623. package/src/utils/formatting/formatNumber.test.ts +1 -1
  624. package/src/utils/{__tests__/formatting.unit.test.ts → formatting.unit.test.ts} +1 -1
  625. package/src/utils/google-places/googlePlacesUtils.test.ts +70 -48
  626. package/src/utils/google-places/googlePlacesUtils.ts +67 -99
  627. package/src/utils/google-places/loadGoogleMapsScript.test.ts +25 -22
  628. package/src/utils/google-places/loadGoogleMapsScript.ts +138 -117
  629. package/src/utils/{__tests__/index.unit.test.ts → index.unit.test.ts} +1 -1
  630. package/src/utils/{__tests__/lazyLoad.unit.test.tsx → lazyLoad.unit.test.tsx} +13 -14
  631. package/src/utils/location/location.test.ts +1 -1
  632. package/src/utils/{__tests__/logger.unit.test.ts → logger.unit.test.ts} +1 -1
  633. package/src/utils/{__tests__/organisationContext.unit.test.ts → organisationContext.unit.test.ts} +37 -48
  634. package/src/utils/performance/{__tests__/bundleAnalysis.test.ts → bundleAnalysis.test.ts} +2 -2
  635. package/src/utils/performance/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  636. package/src/utils/performance/{__tests__/performanceBudgets.test.ts → performanceBudgets.test.ts} +2 -2
  637. package/src/utils/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  638. package/src/utils/{__tests__/performanceBudgets.unit.test.ts → performanceBudgets.unit.test.ts} +2 -2
  639. package/src/utils/{__tests__/permissionTypes.unit.test.ts → permissionTypes.unit.test.ts} +1 -1
  640. package/src/utils/{__tests__/permissionUtils.unit.test.ts → permissionUtils.unit.test.ts} +1 -1
  641. package/src/utils/permissions/{__tests__/permissionTypes.test.ts → permissionTypes.test.ts} +2 -2
  642. package/src/utils/persistence/{__tests__/keyDerivation.test.ts → keyDerivation.test.ts} +2 -2
  643. package/src/utils/persistence/{__tests__/sensitiveFieldDetection.test.ts → sensitiveFieldDetection.test.ts} +2 -2
  644. package/src/utils/{__tests__/request-deduplication.test.ts → request-deduplication.test.ts} +2 -2
  645. package/src/utils/{__tests__/sanitization.unit.test.ts → sanitization.unit.test.ts} +1 -1
  646. package/src/utils/{__tests__/schemaUtils.unit.test.ts → schemaUtils.unit.test.ts} +1 -1
  647. package/src/utils/{__tests__/secureDataAccess.unit.test.ts → secureDataAccess.unit.test.ts} +2 -2
  648. package/src/utils/{__tests__/secureErrors.unit.test.ts → secureErrors.unit.test.ts} +4 -4
  649. package/src/utils/{__tests__/secureStorage.unit.test.ts → secureStorage.unit.test.ts} +1 -1
  650. package/src/utils/security/auth-utils.ts +34 -23
  651. package/src/utils/security/secureDataAccess.ts +241 -281
  652. package/src/utils/security/secureErrors.test.ts +1 -1
  653. package/src/utils/security/secureStorage.test.ts +1 -1
  654. package/src/utils/security/security.test.ts +25 -17
  655. package/src/utils/security/security.ts +15 -18
  656. package/src/utils/security/securityMonitor.test.ts +1 -1
  657. package/src/utils/{__tests__/security.unit.test.ts → security.unit.test.ts} +21 -15
  658. package/src/utils/{__tests__/securityMonitor.unit.test.ts → securityMonitor.unit.test.ts} +1 -1
  659. package/src/utils/{__tests__/sessionTracking.unit.test.ts → sessionTracking.unit.test.ts} +12 -12
  660. package/src/utils/storage/{__tests__/config.unit.test.ts → config.unit.test.ts} +2 -2
  661. package/src/utils/storage/helpers.test.ts +88 -102
  662. package/src/utils/storage/helpers.ts +173 -251
  663. package/src/utils/storage/{__tests__/index.unit.test.ts → index.unit.test.ts} +3 -3
  664. package/src/utils/storage/types.ts +7 -0
  665. package/src/utils/supabase/createBaseClient.test.ts +1 -1
  666. package/src/utils/timezone/timezone.test.ts +1 -1
  667. package/src/utils/{__tests__/timezone.test.ts → timezone.test.ts} +2 -2
  668. package/src/utils/validation/{__tests__/common.test.ts → common.test.ts} +2 -2
  669. package/src/utils/validation/{__tests__/csrf.test.ts → csrf.test.ts} +56 -28
  670. package/src/utils/validation/csrf.ts +42 -41
  671. package/src/utils/validation/{__tests__/htmlSanitization.unit.test.ts → htmlSanitization.unit.test.ts} +2 -2
  672. package/src/utils/validation/{__tests__/passwordSchema.test.ts → passwordSchema.test.ts} +2 -2
  673. package/src/utils/validation/{__tests__/schema.test.ts → schema.test.ts} +2 -2
  674. package/src/utils/validation/{__tests__/sqlInjectionProtection.test.ts → sqlInjectionProtection.test.ts} +2 -2
  675. package/src/utils/validation/{__tests__/user.test.ts → user.test.ts} +2 -2
  676. package/src/utils/validation/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  677. package/src/utils/validation/{__tests__/validationUtils.test.ts → validationUtils.test.ts} +2 -2
  678. package/src/utils/{__tests__/validation.unit.test.ts → validation.unit.test.ts} +1 -1
  679. package/src/utils/{__tests__/validationUtils.unit.test.ts → validationUtils.unit.test.ts} +5 -2
  680. package/dist/UnifiedAuthProvider-BBD2PS3Q.js +0 -7
  681. package/dist/chunk-KPYQWGFQ.js +0 -183
  682. package/dist/types-D05dCGma.d.ts +0 -521
  683. package/scripts/eslint-audit.cjs +0 -222
  684. package/scripts/generate-docs.js +0 -157
  685. package/scripts/install-cursor-rules.cjs +0 -255
  686. package/scripts/install-eslint-config.cjs +0 -349
  687. package/scripts/setup-build-cache.js +0 -73
  688. package/scripts/validate-pre-publish.js +0 -145
  689. package/src/__tests__/integration/UserProfile.test.tsx +0 -124
  690. package/src/__tests__/public-recipe-view.test.ts +0 -228
  691. package/src/__tests__/rls-policies.test.ts +0 -472
  692. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
  693. package/src/components/DataTable/components/DataTableLayout.tsx +0 -584
  694. package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -395
  695. package/src/components/DataTable/components/__tests__/DataTableLayout.test.tsx +0 -467
  696. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -358
  697. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -957
  698. package/src/components/DataTable/core/ActionManager.ts +0 -235
  699. package/src/components/DataTable/core/ColumnManager.ts +0 -204
  700. package/src/components/DataTable/core/DataManager.ts +0 -190
  701. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
  702. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  703. package/src/components/DataTable/core/StateManager.ts +0 -312
  704. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -235
  705. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -141
  706. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -178
  707. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -133
  708. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -142
  709. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -158
  710. package/src/components/DataTable/core/interfaces.ts +0 -338
  711. package/src/components/DataTable/types.ts +0 -764
  712. package/src/hooks/public/usePublicFileDisplay.ts +0 -534
  713. package/src/hooks/useFileDisplay.ts +0 -748
  714. package/src/providers/OrganisationProvider.test.tsx +0 -40
  715. package/src/providers/OrganisationProvider.tsx +0 -92
  716. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
  717. package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -616
  718. package/src/providers/__tests__/OrganisationProvider.wrapper.test.tsx +0 -591
  719. package/src/rbac/__tests__/cache-invalidation.test.ts +0 -393
  720. /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
  721. /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
  722. /package/src/hooks/{__tests__/useApiFetch.unit.test.ts → useApiFetch.unit.test.ts} +0 -0
  723. /package/src/providers/{__tests__/README.md → README.md} +0 -0
  724. /package/src/rbac/{__tests__/index.test.ts → index.test.ts} +0 -0
  725. /package/src/rbac/{__tests__/rbac-integration.test.ts → rbac-integration.test.ts} +0 -0
  726. /package/src/types/{__tests__/README.md → README.md} +0 -0
@@ -413,7 +413,7 @@ describe('RBAC API', () => {
413
413
  };
414
414
 
415
415
  const mockSupabaseForEngine = {
416
- from: vi.fn((table: string) => createQueryBuilder(table)),
416
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
417
417
  rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
418
418
  auth: mockSupabase.auth
419
419
  };
@@ -452,7 +452,8 @@ describe('RBAC API', () => {
452
452
  scope: { organisationId: 'org-456' }
453
453
  });
454
454
 
455
- expect(result).toBe(mockAccessLevel);
455
+ expect(result.ok).toBe(true);
456
+ expect(result.ok && result.data).toBe(mockAccessLevel);
456
457
  expect(mockEngine.getAccessLevel).toHaveBeenCalledWith({
457
458
  userId: 'user-123',
458
459
  scope: { organisationId: 'org-456' }
@@ -465,10 +466,12 @@ describe('RBAC API', () => {
465
466
  const error = new Error('Engine error');
466
467
  mockEngine.getAccessLevel.mockRejectedValue(error);
467
468
 
468
- await expect(getAccessLevel({
469
+ const result = await getAccessLevel({
469
470
  userId: 'user-123',
470
471
  scope: { organisationId: 'org-456' }
471
- })).rejects.toThrow('Engine error');
472
+ });
473
+ expect(result.ok).toBe(false);
474
+ expect(result.ok === false && result.error.message).toContain('Engine error');
472
475
  });
473
476
  });
474
477
 
@@ -487,7 +490,8 @@ describe('RBAC API', () => {
487
490
  scope: { organisationId: 'org-456' }
488
491
  });
489
492
 
490
- expect(result).toEqual(mockPermissionMap);
493
+ expect(result.ok).toBe(true);
494
+ expect(result.ok && result.data).toEqual(mockPermissionMap);
491
495
  expect(mockEngine.getPermissionMap).toHaveBeenCalledWith({
492
496
  userId: 'user-123',
493
497
  scope: { organisationId: 'org-456' }
@@ -500,10 +504,11 @@ describe('RBAC API', () => {
500
504
  const error = new Error('Engine error');
501
505
  mockEngine.getPermissionMap.mockRejectedValue(error);
502
506
 
503
- await expect(getPermissionMap({
507
+ const result = await getPermissionMap({
504
508
  userId: 'user-123',
505
509
  scope: { organisationId: 'org-456' }
506
- })).rejects.toThrow('Engine error');
510
+ });
511
+ expect(result.ok).toBe(false);
507
512
  });
508
513
  });
509
514
 
@@ -515,7 +520,8 @@ describe('RBAC API', () => {
515
520
 
516
521
  const result = await resolveAppContext({ userId: 'user-1', appName: 'test-app' });
517
522
 
518
- expect(result).toEqual(context);
523
+ expect(result.ok).toBe(true);
524
+ expect(result.ok && result.data).toEqual(context);
519
525
  expect(mockEngine.resolveAppContext).toHaveBeenCalledWith({ userId: 'user-1', appName: 'test-app' });
520
526
  });
521
527
 
@@ -525,7 +531,8 @@ describe('RBAC API', () => {
525
531
 
526
532
  const result = await resolveAppContext({ userId: 'user-1', appName: 'test-app' });
527
533
 
528
- expect(result).toBeNull();
534
+ expect(result.ok).toBe(true);
535
+ expect(result.ok && result.data).toBeNull();
529
536
  });
530
537
  });
531
538
 
@@ -545,7 +552,8 @@ describe('RBAC API', () => {
545
552
  scope: { organisationId: 'org-456' },
546
553
  });
547
554
 
548
- expect(result).toEqual(roleContext);
555
+ expect(result.ok).toBe(true);
556
+ expect(result.ok && result.data).toEqual(roleContext);
549
557
  expect(mockEngine.getRoleContext).toHaveBeenCalledWith({
550
558
  userId: 'user-123',
551
559
  scope: { organisationId: 'org-456' },
@@ -566,7 +574,7 @@ describe('RBAC API', () => {
566
574
  pageId: '123e4567-e89b-12d3-a456-426614174000' // Valid UUID format
567
575
  });
568
576
 
569
- expect(result).toBe(true);
577
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
570
578
  expect(mockEngine.isPermitted).toHaveBeenCalled();
571
579
  });
572
580
 
@@ -576,31 +584,35 @@ describe('RBAC API', () => {
576
584
  const error = new Error('Engine error');
577
585
  mockEngine.isPermitted.mockRejectedValue(error);
578
586
 
579
- await expect(isPermitted({
587
+ const result = await isPermitted({
580
588
  userId: 'user-123',
581
589
  scope: { organisationId: 'org-456' },
582
590
  permission: 'read:users'
583
- })).rejects.toThrow('Engine error');
591
+ });
592
+ expect(result.ok).toBe(false);
593
+ expect(result.ok === false && result.error.message).toContain('Engine error');
584
594
  });
585
595
 
586
596
  it('throws OrganisationContextRequiredError when organisationId is missing', async () => {
587
- const { isPermitted, OrganisationContextRequiredError } = await import('./api');
597
+ const { isPermitted } = await import('./api');
588
598
 
589
- await expect(isPermitted({
599
+ const result = await isPermitted({
590
600
  userId: 'user-123',
591
601
  scope: {}, // Missing organisationId
592
602
  permission: 'read:users'
593
- })).rejects.toThrow(OrganisationContextRequiredError);
603
+ });
604
+ expect(result.ok).toBe(false);
594
605
  });
595
606
 
596
607
  it('throws OrganisationContextRequiredError when organisationId is undefined', async () => {
597
- const { isPermitted, OrganisationContextRequiredError } = await import('./api');
608
+ const { isPermitted } = await import('./api');
598
609
 
599
- await expect(isPermitted({
610
+ const result = await isPermitted({
600
611
  userId: 'user-123',
601
612
  scope: { organisationId: undefined },
602
613
  permission: 'read:users'
603
- })).rejects.toThrow(OrganisationContextRequiredError);
614
+ });
615
+ expect(result.ok).toBe(false);
604
616
  });
605
617
  });
606
618
 
@@ -619,7 +631,7 @@ describe('RBAC API', () => {
619
631
  pageId: '123e4567-e89b-12d3-a456-426614174000' // Valid UUID format
620
632
  });
621
633
 
622
- expect(result).toBe(true);
634
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
623
635
  expect(rbacCache.get).toHaveBeenCalledWith(cacheKey, true);
624
636
  expect(mockEngine.isPermitted).not.toHaveBeenCalled();
625
637
  });
@@ -637,7 +649,7 @@ describe('RBAC API', () => {
637
649
  pageId: '123e4567-e89b-12d3-a456-426614174000' // Valid UUID format
638
650
  });
639
651
 
640
- expect(result).toBe(true);
652
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
641
653
  expect(mockEngine.isPermitted).toHaveBeenCalled();
642
654
  // Check that cache.set was called - pageId presence makes it a page-level check
643
655
  expect(rbacCache.set).toHaveBeenCalled();
@@ -665,7 +677,7 @@ describe('RBAC API', () => {
665
677
  permissions: ['read:users', 'write:users']
666
678
  });
667
679
 
668
- expect(result).toBe(true);
680
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
669
681
  expect(mockEngine.isPermitted).toHaveBeenCalledTimes(2);
670
682
  });
671
683
 
@@ -680,7 +692,7 @@ describe('RBAC API', () => {
680
692
  permissions: ['read:users', 'write:users']
681
693
  });
682
694
 
683
- expect(result).toBe(false);
695
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(false);
684
696
  expect(mockEngine.isPermitted).toHaveBeenCalledTimes(2);
685
697
  });
686
698
  });
@@ -697,7 +709,7 @@ describe('RBAC API', () => {
697
709
  permissions: ['read:users', 'write:users']
698
710
  });
699
711
 
700
- expect(result).toBe(true);
712
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
701
713
  expect(mockEngine.isPermitted).toHaveBeenCalledTimes(2);
702
714
  });
703
715
 
@@ -714,7 +726,7 @@ describe('RBAC API', () => {
714
726
  permissions: ['read:users', 'write:users']
715
727
  });
716
728
 
717
- expect(result).toBe(false);
729
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(false);
718
730
  expect(mockEngine.isPermitted).toHaveBeenCalledTimes(2);
719
731
  });
720
732
  });
@@ -727,7 +739,7 @@ describe('RBAC API', () => {
727
739
 
728
740
  const result = await isSuperAdmin('user-123');
729
741
 
730
- expect(result).toBe(true);
742
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
731
743
  expect(mockEngine.checkSuperAdmin).toHaveBeenCalledWith('user-123');
732
744
  });
733
745
 
@@ -737,7 +749,9 @@ describe('RBAC API', () => {
737
749
  const error = new Error('Engine error');
738
750
  mockEngine.checkSuperAdmin.mockRejectedValue(error);
739
751
 
740
- await expect(isSuperAdmin('user-123')).rejects.toThrow('Engine error');
752
+ const result = await isSuperAdmin('user-123');
753
+ expect(result.ok).toBe(false);
754
+ expect(result.ok === false && result.error.message).toContain('Engine error');
741
755
  });
742
756
  });
743
757
 
@@ -752,7 +766,7 @@ describe('RBAC API', () => {
752
766
 
753
767
  const result = await isOrganisationAdmin('user-123', 'org-456');
754
768
 
755
- expect(result).toBe(true);
769
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
756
770
  expect(mockEngine.getAccessLevel).toHaveBeenCalledWith({
757
771
  userId: 'user-123',
758
772
  scope: { organisationId: 'org-456' }
@@ -766,7 +780,7 @@ describe('RBAC API', () => {
766
780
 
767
781
  const result = await isOrganisationAdmin('user-123', 'org-456');
768
782
 
769
- expect(result).toBe(true);
783
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
770
784
  });
771
785
 
772
786
  it('returns false for other access levels', async () => {
@@ -776,7 +790,7 @@ describe('RBAC API', () => {
776
790
 
777
791
  const result = await isOrganisationAdmin('user-123', 'org-456');
778
792
 
779
- expect(result).toBe(false);
793
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(false);
780
794
  });
781
795
  });
782
796
 
@@ -792,7 +806,7 @@ describe('RBAC API', () => {
792
806
  appId: 'app-101'
793
807
  });
794
808
 
795
- expect(result).toBe(true);
809
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
796
810
  expect(mockEngine.getAccessLevel).toHaveBeenCalledWith({
797
811
  userId: 'user-123',
798
812
  scope: {
@@ -812,7 +826,7 @@ describe('RBAC API', () => {
812
826
  appId: 'app-101'
813
827
  });
814
828
 
815
- expect(result).toBe(false);
829
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(false);
816
830
  expect(mockEngine.getAccessLevel).not.toHaveBeenCalled();
817
831
  });
818
832
 
@@ -825,7 +839,7 @@ describe('RBAC API', () => {
825
839
  appId: undefined
826
840
  });
827
841
 
828
- expect(result).toBe(false);
842
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(false);
829
843
  expect(mockEngine.getAccessLevel).not.toHaveBeenCalled();
830
844
  });
831
845
  });
@@ -926,17 +940,934 @@ describe('RBAC API', () => {
926
940
  });
927
941
  });
928
942
 
929
- describe('Error Handling', () => {
930
- it('throws RBACNotInitializedError when engine not available', async () => {
931
- // Reset global engine by clearing the module cache
932
- vi.resetModules();
943
+ describe('getPageScopeType', () => {
944
+ let mockEngine: any;
945
+
946
+ beforeEach(() => {
947
+ const createQueryBuilder = (table: string, selectField?: string) => {
948
+ const builder: any = {
949
+ select: vi.fn().mockReturnThis(),
950
+ eq: vi.fn().mockReturnThis(),
951
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
952
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
953
+ };
954
+
955
+ if (table === 'rbac_apps') {
956
+ if (selectField === 'id, name, is_active') {
957
+ builder.maybeSingle = vi.fn().mockResolvedValue({
958
+ data: { id: 'app-123', name: 'test-app', is_active: true },
959
+ error: null
960
+ });
961
+ }
962
+ } else if (table === 'rbac_app_pages') {
963
+ if (selectField === 'id, page_name, app_id') {
964
+ builder.maybeSingle = vi.fn().mockResolvedValue({
965
+ data: { id: '123e4567-e89b-12d3-a456-426614174000', page_name: 'test-page', app_id: 'app-123' },
966
+ error: null
967
+ });
968
+ } else if (selectField === 'scope_type, page_name, app_id') {
969
+ builder.single = vi.fn().mockResolvedValue({
970
+ data: { scope_type: 'organisation', page_name: 'test-page', app_id: 'app-123' },
971
+ error: null
972
+ });
973
+ }
974
+ }
975
+
976
+ builder.select = vi.fn((fields: string) => {
977
+ return createQueryBuilder(table, fields);
978
+ });
979
+
980
+ return builder;
981
+ };
933
982
 
934
- const { getAccessLevel } = await import('./api');
983
+ const mockSupabaseForEngine = {
984
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
985
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
986
+ auth: mockSupabase.auth
987
+ };
988
+
989
+ mockEngine = {
990
+ getAccessLevel: vi.fn(),
991
+ getPermissionMap: vi.fn(),
992
+ isPermitted: vi.fn(),
993
+ resolveAppContext: vi.fn(),
994
+ getRoleContext: vi.fn(),
995
+ checkSuperAdmin: vi.fn(),
996
+ supabase: mockSupabaseForEngine as any
997
+ };
998
+
999
+ mockCreateRBACEngine.mockReturnValue(mockEngine);
1000
+ mockCreateAuditManager.mockReturnValue({} as any);
1001
+ mockGetRBACLogger.mockReturnValue({
1002
+ info: vi.fn(),
1003
+ warn: vi.fn(),
1004
+ error: vi.fn(),
1005
+ debug: vi.fn()
1006
+ });
1007
+
1008
+ setupRBAC(mockSupabase as any);
1009
+ });
1010
+
1011
+ it('returns scope type for UUID pageId', async () => {
1012
+ // Update mockEngine.supabase before importing (globalEngine references mockEngine)
1013
+ const mockSupabaseForQuery = {
1014
+ from: vi.fn((table: string) => {
1015
+ if (table === 'rbac_app_pages') {
1016
+ return {
1017
+ select: vi.fn().mockReturnThis(),
1018
+ eq: vi.fn().mockReturnThis(),
1019
+ single: vi.fn().mockResolvedValue({
1020
+ data: { scope_type: 'organisation', page_name: 'test-page', app_id: 'app-123' },
1021
+ error: null
1022
+ })
1023
+ };
1024
+ }
1025
+ return {
1026
+ select: vi.fn().mockReturnThis(),
1027
+ eq: vi.fn().mockReturnThis(),
1028
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1029
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1030
+ };
1031
+ }),
1032
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1033
+ auth: mockSupabase.auth
1034
+ };
935
1035
 
936
- await expect(getAccessLevel({
937
- userId: 'user-123',
938
- scope: { organisationId: 'org-456' }
939
- })).rejects.toThrow('RBAC system not initialized');
1036
+ // Update the mockEngine's supabase property (globalEngine references the same object)
1037
+ mockEngine.supabase = mockSupabaseForQuery as any;
1038
+
1039
+ const { getPageScopeType } = await import('./api');
1040
+
1041
+ const result = await getPageScopeType('123e4567-e89b-12d3-a456-426614174000', 'app-123');
1042
+
1043
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe('organisation');
1044
+ });
1045
+
1046
+ it('resolves page name to UUID and returns scope type', async () => {
1047
+ // Update mockEngine.supabase before importing (globalEngine references mockEngine)
1048
+ const createQueryBuilder = (table: string, selectField?: string) => {
1049
+ const builder: any = {
1050
+ select: vi.fn().mockReturnThis(),
1051
+ eq: vi.fn().mockReturnThis(),
1052
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1053
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1054
+ };
1055
+
1056
+ if (table === 'rbac_app_pages') {
1057
+ if (selectField === 'id, page_name, app_id') {
1058
+ builder.maybeSingle = vi.fn().mockResolvedValue({
1059
+ data: { id: '123e4567-e89b-12d3-a456-426614174000', page_name: 'test-page', app_id: 'app-123' },
1060
+ error: null
1061
+ });
1062
+ } else if (selectField === 'scope_type, page_name, app_id') {
1063
+ builder.single = vi.fn().mockResolvedValue({
1064
+ data: { scope_type: 'organisation', page_name: 'test-page', app_id: 'app-123' },
1065
+ error: null
1066
+ });
1067
+ }
1068
+ }
1069
+
1070
+ builder.select = vi.fn((fields: string) => {
1071
+ return createQueryBuilder(table, fields);
1072
+ });
1073
+
1074
+ return builder;
1075
+ };
1076
+
1077
+ const mockSupabaseForQuery = {
1078
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1079
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1080
+ auth: mockSupabase.auth
1081
+ };
1082
+
1083
+ // Update the mockEngine's supabase property (globalEngine references the same object)
1084
+ mockEngine.supabase = mockSupabaseForQuery as any;
1085
+
1086
+ const { getPageScopeType } = await import('./api');
1087
+
1088
+ const result = await getPageScopeType('test-page', 'app-123');
1089
+
1090
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe('organisation');
1091
+ });
1092
+
1093
+ it('resolves appId from appName', async () => {
1094
+ // Update mockEngine.supabase before importing (globalEngine references mockEngine)
1095
+ const createQueryBuilder = (table: string, selectField?: string) => {
1096
+ const builder: any = {
1097
+ select: vi.fn().mockReturnThis(),
1098
+ eq: vi.fn().mockReturnThis(),
1099
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1100
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1101
+ };
1102
+
1103
+ if (table === 'rbac_apps') {
1104
+ if (selectField === 'id, name, is_active') {
1105
+ builder.maybeSingle = vi.fn().mockResolvedValue({
1106
+ data: { id: 'app-123', name: 'test-app', is_active: true },
1107
+ error: null
1108
+ });
1109
+ }
1110
+ } else if (table === 'rbac_app_pages') {
1111
+ if (selectField === 'id, page_name, app_id') {
1112
+ builder.maybeSingle = vi.fn().mockResolvedValue({
1113
+ data: { id: '123e4567-e89b-12d3-a456-426614174000', page_name: 'test-page', app_id: 'app-123' },
1114
+ error: null
1115
+ });
1116
+ } else if (selectField === 'scope_type, page_name, app_id') {
1117
+ builder.single = vi.fn().mockResolvedValue({
1118
+ data: { scope_type: 'event', page_name: 'test-page', app_id: 'app-123' },
1119
+ error: null
1120
+ });
1121
+ }
1122
+ }
1123
+
1124
+ builder.select = vi.fn((fields: string) => {
1125
+ return createQueryBuilder(table, fields);
1126
+ });
1127
+
1128
+ return builder;
1129
+ };
1130
+
1131
+ const mockSupabaseForQuery = {
1132
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1133
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1134
+ auth: mockSupabase.auth
1135
+ };
1136
+
1137
+ // Update the mockEngine's supabase property (globalEngine references the same object)
1138
+ mockEngine.supabase = mockSupabaseForQuery as any;
1139
+
1140
+ const { getPageScopeType } = await import('./api');
1141
+
1142
+ const result = await getPageScopeType('test-page', undefined, 'test-app');
1143
+
1144
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe('event');
1145
+ });
1146
+
1147
+ it('handles errors when app not found', async () => {
1148
+ const createQueryBuilder = (table: string) => {
1149
+ const builder: any = {
1150
+ select: vi.fn().mockReturnThis(),
1151
+ eq: vi.fn().mockReturnThis(),
1152
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1153
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1154
+ };
1155
+
1156
+ if (table === 'rbac_apps') {
1157
+ builder.maybeSingle = vi.fn().mockResolvedValue({
1158
+ data: null,
1159
+ error: null
1160
+ });
1161
+ }
1162
+
1163
+ builder.select = vi.fn(() => builder);
1164
+
1165
+ return builder;
1166
+ };
1167
+
1168
+ const mockSupabaseForEngine = {
1169
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1170
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1171
+ auth: mockSupabase.auth
1172
+ };
1173
+
1174
+ // Update the existing mockEngine's supabase property (globalEngine references the same object)
1175
+ mockEngine.supabase = mockSupabaseForEngine as any;
1176
+
1177
+ const { getPageScopeType } = await import('./api');
1178
+
1179
+ const result = await getPageScopeType('test-page', undefined, 'nonexistent-app');
1180
+ expect(result.ok).toBe(false);
1181
+ });
1182
+
1183
+ it('handles errors when page not found', async () => {
1184
+ const createQueryBuilder = (table: string) => {
1185
+ const builder: any = {
1186
+ select: vi.fn().mockReturnThis(),
1187
+ eq: vi.fn().mockReturnThis(),
1188
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1189
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1190
+ };
1191
+
1192
+ if (table === 'rbac_app_pages') {
1193
+ builder.single = vi.fn().mockResolvedValue({
1194
+ data: null,
1195
+ error: { message: 'Page not found' }
1196
+ });
1197
+ }
1198
+
1199
+ builder.select = vi.fn(() => builder);
1200
+
1201
+ return builder;
1202
+ };
1203
+
1204
+ const mockSupabaseForEngine = {
1205
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1206
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1207
+ auth: mockSupabase.auth
1208
+ };
1209
+
1210
+ // Update the existing mockEngine's supabase property (globalEngine references the same object)
1211
+ mockEngine.supabase = mockSupabaseForEngine as any;
1212
+
1213
+ const { getPageScopeType } = await import('./api');
1214
+
1215
+ const result = await getPageScopeType('nonexistent-page', 'app-123');
1216
+ expect(result.ok).toBe(false);
1217
+ });
1218
+
1219
+ it('handles database errors gracefully', async () => {
1220
+ // Update mockEngine.supabase before importing (globalEngine references mockEngine)
1221
+ const createQueryBuilder = (_table: string) => {
1222
+ const builder: any = {
1223
+ select: vi.fn().mockReturnThis(),
1224
+ eq: vi.fn().mockReturnThis(),
1225
+ single: vi.fn().mockResolvedValue({
1226
+ data: null,
1227
+ error: { message: 'Database error', code: 'PGRST116' }
1228
+ })
1229
+ };
1230
+
1231
+ builder.select = vi.fn(() => builder);
1232
+
1233
+ return builder;
1234
+ };
1235
+
1236
+ const mockSupabaseForQuery = {
1237
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1238
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1239
+ auth: mockSupabase.auth
1240
+ };
1241
+
1242
+ // Update the mockEngine's supabase property (globalEngine references the same object)
1243
+ mockEngine.supabase = mockSupabaseForQuery as any;
1244
+
1245
+ const { getPageScopeType } = await import('./api');
1246
+
1247
+ const result = await getPageScopeType('123e4567-e89b-12d3-a456-426614174000', 'app-123');
1248
+ expect(result.ok).toBe(false);
1249
+ });
1250
+
1251
+ it('returns "both" scope type when page has both scope', async () => {
1252
+ // Update mockEngine.supabase before importing (globalEngine references mockEngine)
1253
+ const createQueryBuilder = (table: string, selectField?: string) => {
1254
+ const builder: any = {
1255
+ select: vi.fn().mockReturnThis(),
1256
+ eq: vi.fn().mockReturnThis(),
1257
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1258
+ };
1259
+
1260
+ if (table === 'rbac_app_pages' && selectField === 'scope_type, page_name, app_id') {
1261
+ builder.single = vi.fn().mockResolvedValue({
1262
+ data: { scope_type: 'both', page_name: 'test-page', app_id: 'app-123' },
1263
+ error: null
1264
+ });
1265
+ }
1266
+
1267
+ builder.select = vi.fn((fields: string) => {
1268
+ return createQueryBuilder(table, fields);
1269
+ });
1270
+
1271
+ return builder;
1272
+ };
1273
+
1274
+ const mockSupabaseForQuery = {
1275
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1276
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1277
+ auth: mockSupabase.auth
1278
+ };
1279
+
1280
+ // Update the mockEngine's supabase property (globalEngine references the same object)
1281
+ mockEngine.supabase = mockSupabaseForQuery as any;
1282
+
1283
+ const { getPageScopeType } = await import('./api');
1284
+
1285
+ const result = await getPageScopeType('123e4567-e89b-12d3-a456-426614174000', 'app-123');
1286
+
1287
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe('both');
1288
+ });
1289
+ });
1290
+
1291
+ describe('isPermitted - Advanced Cases', () => {
1292
+ let mockEngine: any;
1293
+
1294
+ beforeEach(() => {
1295
+ const createQueryBuilder = (table: string, selectField?: string) => {
1296
+ const builder: any = {
1297
+ select: vi.fn().mockReturnThis(),
1298
+ eq: vi.fn().mockReturnThis(),
1299
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1300
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1301
+ };
1302
+
1303
+ if (table === 'rbac_apps') {
1304
+ if (selectField === 'name') {
1305
+ builder.single = vi.fn().mockResolvedValue({ data: { name: 'test-app' }, error: null });
1306
+ }
1307
+ } else if (table === 'rbac_app_pages') {
1308
+ if (selectField === 'scope_type, page_name, app_id') {
1309
+ builder.single = vi.fn().mockResolvedValue({
1310
+ data: { scope_type: 'organisation', page_name: 'test-page', app_id: 'app-123' },
1311
+ error: null
1312
+ });
1313
+ }
1314
+ }
1315
+
1316
+ builder.select = vi.fn((fields: string) => {
1317
+ return createQueryBuilder(table, fields);
1318
+ });
1319
+
1320
+ return builder;
1321
+ };
1322
+
1323
+ const mockSupabaseForEngine = {
1324
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1325
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1326
+ auth: mockSupabase.auth
1327
+ };
1328
+
1329
+ mockEngine = {
1330
+ getAccessLevel: vi.fn(),
1331
+ getPermissionMap: vi.fn(),
1332
+ isPermitted: vi.fn(),
1333
+ resolveAppContext: vi.fn(),
1334
+ getRoleContext: vi.fn(),
1335
+ checkSuperAdmin: vi.fn(),
1336
+ supabase: mockSupabaseForEngine as any
1337
+ };
1338
+
1339
+ mockCreateRBACEngine.mockReturnValue(mockEngine);
1340
+ mockCreateAuditManager.mockReturnValue({} as any);
1341
+ mockGetRBACLogger.mockReturnValue({
1342
+ info: vi.fn(),
1343
+ warn: vi.fn(),
1344
+ error: vi.fn(),
1345
+ debug: vi.fn()
1346
+ });
1347
+
1348
+ setupRBAC(mockSupabase as any);
1349
+ });
1350
+
1351
+ it('uses precomputed super admin status when provided as true', async () => {
1352
+ const { isPermitted } = await import('./api');
1353
+
1354
+ mockEngine.isPermitted.mockResolvedValue(true);
1355
+
1356
+ const result = await isPermitted({
1357
+ userId: 'user-123',
1358
+ scope: { organisationId: 'org-456', appId: 'app-123' },
1359
+ permission: 'read:users',
1360
+ pageId: '123e4567-e89b-12d3-a456-426614174000'
1361
+ }, undefined, true);
1362
+
1363
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
1364
+ expect(mockEngine.checkSuperAdmin).not.toHaveBeenCalled();
1365
+ });
1366
+
1367
+ it('checks super admin when precomputed is null', async () => {
1368
+ const { isPermitted } = await import('./api');
1369
+
1370
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1371
+ mockEngine.isPermitted.mockResolvedValue(true);
1372
+
1373
+ const result = await isPermitted({
1374
+ userId: 'user-123',
1375
+ scope: { organisationId: 'org-456', appId: 'app-123' },
1376
+ permission: 'read:users',
1377
+ pageId: '123e4567-e89b-12d3-a456-426614174000'
1378
+ }, undefined, null);
1379
+
1380
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
1381
+ expect(mockEngine.checkSuperAdmin).toHaveBeenCalled();
1382
+ });
1383
+
1384
+ it('handles "both" scope type pages correctly', async () => {
1385
+ const createQueryBuilder = (table: string, selectField?: string) => {
1386
+ const builder: any = {
1387
+ select: vi.fn().mockReturnThis(),
1388
+ eq: vi.fn().mockReturnThis(),
1389
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1390
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1391
+ };
1392
+
1393
+ if (table === 'rbac_apps') {
1394
+ if (selectField === 'name') {
1395
+ builder.single = vi.fn().mockResolvedValue({ data: { name: 'test-app' }, error: null });
1396
+ }
1397
+ } else if (table === 'rbac_app_pages') {
1398
+ if (selectField === 'scope_type, page_name, app_id') {
1399
+ builder.single = vi.fn().mockResolvedValue({
1400
+ data: { scope_type: 'both', page_name: 'test-page', app_id: 'app-123' },
1401
+ error: null
1402
+ });
1403
+ }
1404
+ }
1405
+
1406
+ builder.select = vi.fn((fields: string) => {
1407
+ return createQueryBuilder(table, fields);
1408
+ });
1409
+
1410
+ return builder;
1411
+ };
1412
+
1413
+ const mockSupabaseForEngine = {
1414
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1415
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1416
+ auth: mockSupabase.auth
1417
+ };
1418
+
1419
+ // Update the mockEngine's supabase property (globalEngine references the same object)
1420
+ mockEngine.supabase = mockSupabaseForEngine as any;
1421
+
1422
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1423
+ mockEngine.isPermitted
1424
+ .mockResolvedValueOnce(true) // Event scope check
1425
+ .mockResolvedValueOnce(false); // Org scope check
1426
+
1427
+ const { isPermitted } = await import('./api');
1428
+
1429
+ const result = await isPermitted({
1430
+ userId: 'user-123',
1431
+ scope: { organisationId: 'org-456', eventId: 'event-789', appId: 'app-123' },
1432
+ permission: 'read:users',
1433
+ pageId: '123e4567-e89b-12d3-a456-426614174000'
1434
+ });
1435
+
1436
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true); // Union: true || false = true
1437
+ });
1438
+
1439
+ it('resolves appName from appId when not provided', async () => {
1440
+ const { isPermitted } = await import('./api');
1441
+
1442
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1443
+ mockEngine.isPermitted.mockResolvedValue(true);
1444
+
1445
+ const result = await isPermitted({
1446
+ userId: 'user-123',
1447
+ scope: { organisationId: 'org-456', appId: 'app-123' },
1448
+ permission: 'read:users',
1449
+ pageId: '123e4567-e89b-12d3-a456-426614174000'
1450
+ });
1451
+
1452
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
1453
+ });
1454
+
1455
+ it('handles errors in getPageScopeType gracefully', async () => {
1456
+ const createQueryBuilder = (_table: string) => {
1457
+ const builder: any = {
1458
+ select: vi.fn().mockReturnThis(),
1459
+ eq: vi.fn().mockReturnThis(),
1460
+ single: vi.fn().mockResolvedValue({
1461
+ data: null,
1462
+ error: { message: 'Database error' }
1463
+ })
1464
+ };
1465
+
1466
+ builder.select = vi.fn(() => builder);
1467
+
1468
+ return builder;
1469
+ };
1470
+
1471
+ const mockSupabaseForEngine = {
1472
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1473
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1474
+ auth: mockSupabase.auth
1475
+ };
1476
+
1477
+ // Update the mockEngine's supabase property (globalEngine references the same object)
1478
+ mockEngine.supabase = mockSupabaseForEngine as any;
1479
+
1480
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1481
+
1482
+ const { isPermitted } = await import('./api');
1483
+
1484
+ const result = await isPermitted({
1485
+ userId: 'user-123',
1486
+ scope: { organisationId: 'org-456', appId: 'app-123' },
1487
+ permission: 'read:users',
1488
+ pageId: '123e4567-e89b-12d3-a456-426614174000'
1489
+ });
1490
+ expect(result.ok).toBe(false);
1491
+ });
1492
+ });
1493
+
1494
+ describe('isPermittedCached - Edge Cases', () => {
1495
+ let mockEngine: any;
1496
+
1497
+ beforeEach(() => {
1498
+ const createQueryBuilder = (table: string, selectField?: string) => {
1499
+ const builder: any = {
1500
+ select: vi.fn().mockReturnThis(),
1501
+ eq: vi.fn().mockReturnThis(),
1502
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1503
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1504
+ };
1505
+
1506
+ if (table === 'rbac_apps') {
1507
+ if (selectField === 'name') {
1508
+ builder.single = vi.fn().mockResolvedValue({ data: { name: 'test-app' }, error: null });
1509
+ }
1510
+ } else if (table === 'rbac_app_pages') {
1511
+ if (selectField === 'scope_type, page_name, app_id') {
1512
+ builder.single = vi.fn().mockResolvedValue({
1513
+ data: { scope_type: 'organisation', page_name: 'test-page', app_id: 'app-123' },
1514
+ error: null
1515
+ });
1516
+ }
1517
+ }
1518
+
1519
+ builder.select = vi.fn((fields: string) => {
1520
+ return createQueryBuilder(table, fields);
1521
+ });
1522
+
1523
+ return builder;
1524
+ };
1525
+
1526
+ const mockSupabaseForEngine = {
1527
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1528
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1529
+ auth: mockSupabase.auth
1530
+ };
1531
+
1532
+ mockEngine = {
1533
+ getAccessLevel: vi.fn(),
1534
+ getPermissionMap: vi.fn(),
1535
+ isPermitted: vi.fn(),
1536
+ resolveAppContext: vi.fn(),
1537
+ getRoleContext: vi.fn(),
1538
+ checkSuperAdmin: vi.fn(),
1539
+ supabase: mockSupabaseForEngine as any
1540
+ };
1541
+
1542
+ mockCreateRBACEngine.mockReturnValue(mockEngine);
1543
+ mockCreateAuditManager.mockReturnValue({} as any);
1544
+ mockGetRBACLogger.mockReturnValue({
1545
+ info: vi.fn(),
1546
+ warn: vi.fn(),
1547
+ error: vi.fn(),
1548
+ debug: vi.fn()
1549
+ });
1550
+
1551
+ setupRBAC(mockSupabase as any);
1552
+ });
1553
+
1554
+ it('uses session cache for page-level checks', async () => {
1555
+ const { isPermittedCached } = await import('./api');
1556
+
1557
+ rbacCache.get.mockReturnValue(null);
1558
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1559
+ mockEngine.isPermitted.mockResolvedValue(true);
1560
+
1561
+ const result = await isPermittedCached({
1562
+ userId: 'user-123',
1563
+ scope: { organisationId: 'org-456', appId: 'app-123' },
1564
+ permission: 'read:users',
1565
+ pageId: '123e4567-e89b-12d3-a456-426614174000'
1566
+ });
1567
+
1568
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
1569
+ expect(rbacCache.set).toHaveBeenCalledWith(
1570
+ expect.any(String),
1571
+ true,
1572
+ undefined,
1573
+ true // useSessionCache = true for page-level checks
1574
+ );
1575
+ });
1576
+
1577
+ it('uses regular cache for non-page-level checks', async () => {
1578
+ const { isPermittedCached } = await import('./api');
1579
+
1580
+ rbacCache.get.mockReturnValue(null);
1581
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1582
+ mockEngine.isPermitted.mockResolvedValue(true);
1583
+
1584
+ const result = await isPermittedCached({
1585
+ userId: 'user-123',
1586
+ scope: { organisationId: 'org-456', appId: 'app-123' },
1587
+ permission: 'read:users'
1588
+ // No pageId
1589
+ });
1590
+
1591
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
1592
+ expect(rbacCache.set).toHaveBeenCalledWith(
1593
+ expect.any(String),
1594
+ true,
1595
+ undefined,
1596
+ false // useSessionCache = false for non-page-level checks
1597
+ );
1598
+ });
1599
+
1600
+ it('detects page-level check by permission pattern', async () => {
1601
+ const { isPermittedCached } = await import('./api');
1602
+
1603
+ rbacCache.get.mockReturnValue(null);
1604
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1605
+ mockEngine.isPermitted.mockResolvedValue(true);
1606
+
1607
+ const result = await isPermittedCached({
1608
+ userId: 'user-123',
1609
+ scope: { organisationId: 'org-456', appId: 'app-123' },
1610
+ permission: 'read:page.meals'
1611
+ // No pageId, but permission contains 'page.'
1612
+ });
1613
+
1614
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe(true);
1615
+ expect(rbacCache.set).toHaveBeenCalledWith(
1616
+ expect.any(String),
1617
+ true,
1618
+ undefined,
1619
+ true // useSessionCache = true for page-level checks
1620
+ );
1621
+ });
1622
+ });
1623
+
1624
+ describe('getAccessLevel - Super Admin Cases', () => {
1625
+ let mockEngine: any;
1626
+
1627
+ beforeEach(() => {
1628
+ const createQueryBuilder = (_table: string) => {
1629
+ const builder: any = {
1630
+ select: vi.fn().mockReturnThis(),
1631
+ eq: vi.fn().mockReturnThis(),
1632
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1633
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1634
+ };
1635
+
1636
+ builder.select = vi.fn(() => builder);
1637
+
1638
+ return builder;
1639
+ };
1640
+
1641
+ const mockSupabaseForEngine = {
1642
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1643
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1644
+ auth: mockSupabase.auth
1645
+ };
1646
+
1647
+ mockEngine = {
1648
+ getAccessLevel: vi.fn(),
1649
+ getPermissionMap: vi.fn(),
1650
+ isPermitted: vi.fn(),
1651
+ resolveAppContext: vi.fn(),
1652
+ getRoleContext: vi.fn(),
1653
+ checkSuperAdmin: vi.fn(),
1654
+ supabase: mockSupabaseForEngine as any
1655
+ };
1656
+
1657
+ mockCreateRBACEngine.mockReturnValue(mockEngine);
1658
+ mockCreateAuditManager.mockReturnValue({} as any);
1659
+ mockGetRBACLogger.mockReturnValue({
1660
+ info: vi.fn(),
1661
+ warn: vi.fn(),
1662
+ error: vi.fn(),
1663
+ debug: vi.fn()
1664
+ });
1665
+
1666
+ setupRBAC(mockSupabase as any);
1667
+ });
1668
+
1669
+ it('returns super for super admin users', async () => {
1670
+ const { getAccessLevel } = await import('./api');
1671
+
1672
+ mockEngine.checkSuperAdmin.mockResolvedValue(true);
1673
+
1674
+ const result = await getAccessLevel({
1675
+ userId: 'user-123',
1676
+ scope: { organisationId: 'org-456' }
1677
+ });
1678
+
1679
+ expect(result.ok).toBe(true); expect(result.ok && result.data).toBe('super');
1680
+ expect(mockEngine.getAccessLevel).not.toHaveBeenCalled();
1681
+ });
1682
+
1683
+ it('handles context validation errors', async () => {
1684
+ const { getAccessLevel } = await import('./api');
1685
+
1686
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1687
+ mockEngine.getAccessLevel.mockRejectedValue(new Error('Context validation failed'));
1688
+
1689
+ const result = await getAccessLevel({
1690
+ userId: 'user-123',
1691
+ scope: {} // Invalid scope
1692
+ });
1693
+ expect(result.ok).toBe(false);
1694
+ });
1695
+ });
1696
+
1697
+ describe('getPermissionMap - Error Cases', () => {
1698
+ let mockEngine: any;
1699
+
1700
+ beforeEach(() => {
1701
+ const createQueryBuilder = (_table: string) => {
1702
+ const builder: any = {
1703
+ select: vi.fn().mockReturnThis(),
1704
+ eq: vi.fn().mockReturnThis(),
1705
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1706
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1707
+ };
1708
+
1709
+ builder.select = vi.fn(() => builder);
1710
+
1711
+ return builder;
1712
+ };
1713
+
1714
+ const mockSupabaseForEngine = {
1715
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1716
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1717
+ auth: mockSupabase.auth
1718
+ };
1719
+
1720
+ mockEngine = {
1721
+ getAccessLevel: vi.fn(),
1722
+ getPermissionMap: vi.fn(),
1723
+ isPermitted: vi.fn(),
1724
+ resolveAppContext: vi.fn(),
1725
+ getRoleContext: vi.fn(),
1726
+ checkSuperAdmin: vi.fn(),
1727
+ supabase: mockSupabaseForEngine as any
1728
+ };
1729
+
1730
+ mockCreateRBACEngine.mockReturnValue(mockEngine);
1731
+ mockCreateAuditManager.mockReturnValue({} as any);
1732
+ mockGetRBACLogger.mockReturnValue({
1733
+ info: vi.fn(),
1734
+ warn: vi.fn(),
1735
+ error: vi.fn(),
1736
+ debug: vi.fn()
1737
+ });
1738
+
1739
+ setupRBAC(mockSupabase as any);
1740
+ });
1741
+
1742
+ it('handles context validation errors', async () => {
1743
+ const { getPermissionMap } = await import('./api');
1744
+
1745
+ mockEngine.checkSuperAdmin.mockResolvedValue(false);
1746
+ mockEngine.getPermissionMap.mockRejectedValue(new Error('Context validation failed'));
1747
+
1748
+ const result = await getPermissionMap({
1749
+ userId: 'user-123',
1750
+ scope: {} // Invalid scope
1751
+ });
1752
+ expect(result.ok).toBe(false);
1753
+ });
1754
+ });
1755
+
1756
+ describe('Error Handling', () => {
1757
+ let mockEngine: any;
1758
+
1759
+ beforeEach(() => {
1760
+ const createQueryBuilder = (_table: string) => {
1761
+ const builder: any = {
1762
+ select: vi.fn().mockReturnThis(),
1763
+ eq: vi.fn().mockReturnThis(),
1764
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1765
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1766
+ };
1767
+
1768
+ builder.select = vi.fn(() => builder);
1769
+
1770
+ return builder;
1771
+ };
1772
+
1773
+ const mockSupabaseForEngine = {
1774
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1775
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1776
+ auth: mockSupabase.auth
1777
+ };
1778
+
1779
+ mockEngine = {
1780
+ getAccessLevel: vi.fn(),
1781
+ getPermissionMap: vi.fn(),
1782
+ isPermitted: vi.fn(),
1783
+ resolveAppContext: vi.fn(),
1784
+ getRoleContext: vi.fn(),
1785
+ checkSuperAdmin: vi.fn(),
1786
+ supabase: mockSupabaseForEngine as any
1787
+ };
1788
+
1789
+ mockCreateRBACEngine.mockReturnValue(mockEngine);
1790
+ mockCreateAuditManager.mockReturnValue({} as any);
1791
+ mockGetRBACLogger.mockReturnValue({
1792
+ info: vi.fn(),
1793
+ warn: vi.fn(),
1794
+ error: vi.fn(),
1795
+ debug: vi.fn()
1796
+ });
1797
+
1798
+ setupRBAC(mockSupabase as any);
1799
+ });
1800
+
1801
+ it('throws RBACNotInitializedError when engine not available', async () => {
1802
+ // Reset global engine by clearing the module cache
1803
+ vi.resetModules();
1804
+
1805
+ const { getAccessLevel } = await import('./api');
1806
+
1807
+ const result = await getAccessLevel({
1808
+ userId: 'user-123',
1809
+ scope: { organisationId: 'org-456' }
1810
+ });
1811
+ expect(result.ok).toBe(false);
1812
+ expect(result.ok === false && result.error.code).toBe('RBAC_NOT_INITIALIZED');
1813
+ });
1814
+
1815
+ it('handles errors in resolveAppContext', async () => {
1816
+ // Re-initialize RBAC after potential resetModules() in previous test
1817
+ mockEngine.resolveAppContext.mockRejectedValue(new Error('Database error'));
1818
+
1819
+ // Re-import setupRBAC to get fresh reference after potential resetModules()
1820
+ const { setupRBAC: freshSetupRBAC } = await import('./api');
1821
+ freshSetupRBAC(mockSupabase as any);
1822
+
1823
+ // Dynamic import after setup
1824
+ const { resolveAppContext } = await import('./api');
1825
+
1826
+ const result = await resolveAppContext({
1827
+ userId: 'user-123',
1828
+ appName: 'test-app'
1829
+ });
1830
+ expect(result.ok).toBe(false);
1831
+ expect(result.ok === false && result.error.message).toContain('Database error');
1832
+ });
1833
+
1834
+ it('handles errors in getRoleContext', async () => {
1835
+ // Ensure engine is set up before importing
1836
+ // getRoleContext calls ContextValidator.resolveScopeForPage which needs engine.supabase
1837
+ const createQueryBuilder = (_table: string) => {
1838
+ const builder: any = {
1839
+ select: vi.fn().mockReturnThis(),
1840
+ eq: vi.fn().mockReturnThis(),
1841
+ maybeSingle: vi.fn().mockResolvedValue({ data: null, error: null }),
1842
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
1843
+ };
1844
+ builder.select = vi.fn(() => builder);
1845
+ return builder;
1846
+ };
1847
+
1848
+ const mockSupabaseForQuery = {
1849
+ from: vi.fn((_table: string) => createQueryBuilder(_table)),
1850
+ rpc: vi.fn().mockResolvedValue({ data: null, error: null }),
1851
+ auth: mockSupabase.auth
1852
+ };
1853
+
1854
+ // Update the mockEngine's supabase property (globalEngine references the same object)
1855
+ mockEngine.supabase = mockSupabaseForQuery as any;
1856
+ mockEngine.getRoleContext.mockRejectedValue(new Error('Database error'));
1857
+
1858
+ // Re-import setupRBAC to get fresh reference after potential resetModules()
1859
+ const { setupRBAC: freshSetupRBAC } = await import('./api');
1860
+ freshSetupRBAC(mockSupabase as any);
1861
+
1862
+ // Dynamic import after setup
1863
+ const { getRoleContext } = await import('./api');
1864
+
1865
+ const result = await getRoleContext({
1866
+ userId: 'user-123',
1867
+ scope: { organisationId: 'org-456' }
1868
+ });
1869
+ expect(result.ok).toBe(false);
1870
+ expect(result.ok === false && result.error.message).toContain('Database error');
940
1871
  });
941
1872
  });
942
1873
  });