rbac-manager 0.0.0-stage → 1.0.0

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 (67) hide show
  1. package/.github/workflows/publish.yml +29 -0
  2. package/README.md +317 -2
  3. package/package.json +45 -4
  4. package/scripts/postbuild.js +17 -0
  5. package/src/config/rbac.config.ts +206 -0
  6. package/src/constants/actions.constants.ts +21 -0
  7. package/src/constants/apiKeys.constants.ts +25 -0
  8. package/src/constants/auth.constants.ts +39 -0
  9. package/src/constants/endpoints.constants.ts +66 -0
  10. package/src/constants/i18n.constants.ts +82 -0
  11. package/src/constants/index.ts +70 -0
  12. package/src/constants/masters.constants.ts +68 -0
  13. package/src/constants/permissions/index.ts +44 -0
  14. package/src/constants/permissions/masterPermissions.constants.ts +44 -0
  15. package/src/constants/permissions/rolePermissions.constants.ts +18 -0
  16. package/src/constants/permissions.constants.ts +34 -0
  17. package/src/constants/roles.constants.ts +34 -0
  18. package/src/core/adapters/authAdapter.ts +89 -0
  19. package/src/core/adapters/index.ts +17 -0
  20. package/src/core/adapters/mastersAdapter.ts +132 -0
  21. package/src/core/adapters/permissionsAdapter.ts +90 -0
  22. package/src/core/adapters/rolesAdapter.ts +115 -0
  23. package/src/core/api/httpClient.ts +296 -0
  24. package/src/core/context/PermissionContext.tsx +232 -0
  25. package/src/core/context/RBACConfigContext.tsx +52 -0
  26. package/src/core/guards/Can.tsx +98 -0
  27. package/src/core/guards/RouteGuard.tsx +42 -0
  28. package/src/core/hooks/useMaster.ts +99 -0
  29. package/src/core/hooks/usePermissions.ts +114 -0
  30. package/src/core/hooks/useRoles.ts +120 -0
  31. package/src/core/types/adapter.types.ts +54 -0
  32. package/src/core/types/auth.types.ts +44 -0
  33. package/src/core/types/index.ts +7 -0
  34. package/src/core/types/masters.types.ts +63 -0
  35. package/src/core/types/pagination.types.ts +37 -0
  36. package/src/core/types/rbac.types.ts +69 -0
  37. package/src/core/types/router.types.ts +43 -0
  38. package/src/core/types/style.types.ts +53 -0
  39. package/src/core/utils/permissionResolver.ts +44 -0
  40. package/src/index.ts +44 -0
  41. package/src/modules/masters/DepartmentMasterView.tsx +126 -0
  42. package/src/modules/masters/DesignationMasterView.tsx +97 -0
  43. package/src/modules/masters/EmployeeMasterView.tsx +107 -0
  44. package/src/modules/masters/EntityMasterView.tsx +102 -0
  45. package/src/modules/masters/ModuleMasterView.tsx +104 -0
  46. package/src/modules/masters/SubModuleMasterView.tsx +106 -0
  47. package/src/modules/masters/components/MasterDataTable.tsx +684 -0
  48. package/src/modules/masters/index.ts +7 -0
  49. package/src/modules/roles-and-permissions/components/PermissionMatrix.tsx +289 -0
  50. package/src/modules/roles-and-permissions/components/PermissionRegistry.tsx +108 -0
  51. package/src/modules/roles-and-permissions/components/RoleListTable.tsx +568 -0
  52. package/src/modules/roles-and-permissions/components/RoleModal.tsx +213 -0
  53. package/src/modules/roles-and-permissions/index.ts +4 -0
  54. package/src/shared/components/Badge.tsx +24 -0
  55. package/src/shared/components/Button.tsx +75 -0
  56. package/src/shared/components/DynamicSidebar.tsx +181 -0
  57. package/src/shared/components/Input.tsx +48 -0
  58. package/src/shared/components/Modal.tsx +70 -0
  59. package/src/shared/components/Table.tsx +345 -0
  60. package/src/styles/standalone.css +51 -0
  61. package/src/theme/ThemeContext.tsx +74 -0
  62. package/src/theme/defaultTheme.ts +81 -0
  63. package/src/theme/index.ts +3 -0
  64. package/src/theme/theme.types.ts +106 -0
  65. package/tsconfig.cjs.json +10 -0
  66. package/tsconfig.esm.json +10 -0
  67. package/tsconfig.json +28 -0
@@ -0,0 +1,29 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 22
21
+ registry-url: https://registry.npmjs.org
22
+
23
+ # npm's trusted publishing requires a recent npm CLI.
24
+ - run: npm install --global npm@latest
25
+
26
+ - run: npm ci
27
+ - run: npm run typecheck
28
+ - run: npm run build
29
+ - run: npm publish --access public --provenance
package/README.md CHANGED
@@ -1,3 +1,318 @@
1
- # Temporary Holding Version
1
+ # Universal Enterprise RBAC & Masters Template
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A production-ready, highly modular, frontend-only RBAC (Role-Based Access Control) and Masters framework built for React and Next.js. Engineered to connect directly to any backend REST API with **zero hardcoded keys**, **dynamic permission naming & aliases**, **pluggable API adapters**, **granular action toggles**, **dynamic table schemas**, and **built-in solutions for all enterprise integration roadblocks**.
4
+
5
+ ---
6
+
7
+ ## 🌟 Core Highlights
8
+
9
+ - **Frontend-Only**: No database or backend code required. Directly interfaces with backend API endpoints.
10
+ - **Universal Framework Routing**: Native adapters for Next.js App Router (`useRouter`), React Router (`useNavigate`, `Link`), TanStack Router, and Remix—eliminating full-page browser reloads.
11
+ - **Zero FOUC (Flash of Unauthorized Content)**: Full SSR session hydration support (`initialUser`, `initialPermissions`) prevents permission flashing on server-rendered pages.
12
+ - **Silent Token Refresh & Request Replay**: Transparently catches 401s, calls your refresh token API, and replays failed requests without logging the user out.
13
+ - **Multi-Tenancy & Workspace Switching**: Built-in `activeTenantId` and `setTenantId()` automatically injects `X-Tenant-Id` headers and re-fetches permissions on the fly.
14
+ - **Flexible Pagination Engine**: Seamlessly adapts to 0-based (`page=0`), 1-based (`page=1`), or SQL offset/limit (`offset=0&limit=10`) backend contracts.
15
+ - **Cascading Form Dropdowns**: Dynamic select options that filter automatically based on parent fields (e.g. Department list dynamically filtered by selected Entity).
16
+ - **Zero Hardcoded Permission Strings**: All permission names are isolated in module-level constants (`src/constants/permissions/`).
17
+ - **Supports Any Permission Naming Convention**: Whether your backend sends `roles.add`, `roles.addition`, `roles:create`, or `ROLE_ADD`, it matches automatically via the dynamic permission resolver and alias map.
18
+ - **100% Complete Style & ClassName Overrides**: Strip default styles with `unstyled={true}` or inject custom Tailwind/CSS classes into any sub-element.
19
+ - **Dual ESM & CommonJS**: Ships with first-class ES modules (`dist/esm`) and CommonJS (`dist/cjs`) bundles with full TypeScript `.d.ts` declarations.
20
+
21
+ ---
22
+
23
+ ## 🛠️ Solutions for Enterprise Integration Roadblocks
24
+
25
+ ### 1. Framework Routing (Next.js & React Router)
26
+ Avoid full-page reloads by passing your framework's native router:
27
+
28
+ ```tsx
29
+ // Next.js App Router:
30
+ import { useRouter } from 'next/navigation';
31
+ import Link from 'next/link';
32
+ import { createNextRouterAdapter, createRBACConfig } from '@custom/rbac-template';
33
+
34
+ const rbacConfig = createRBACConfig({
35
+ apiBaseUrl: 'https://api.example.com',
36
+ router: createNextRouterAdapter(useRouter(), Link),
37
+ });
38
+
39
+ // React Router (Vite / CRA):
40
+ import { useNavigate, Link } from 'react-router-dom';
41
+ import { createReactRouterAdapter } from '@custom/rbac-template';
42
+
43
+ const rbacConfig = createRBACConfig({
44
+ router: createReactRouterAdapter(useNavigate(), Link),
45
+ });
46
+ ```
47
+
48
+ ---
49
+
50
+ ### 2. Eliminating SSR Flashing (Zero FOUC)
51
+ In Next.js, read your session on the server and pass it directly to `PermissionProvider`. The page renders with full permissions immediately upon hydration:
52
+
53
+ ```tsx
54
+ // app/layout.tsx (Server Component)
55
+ import { getServerSession } from '@/lib/auth';
56
+ import { ClientProviders } from './providers';
57
+
58
+ export default async function RootLayout({ children }) {
59
+ const session = await getServerSession();
60
+
61
+ return (
62
+ <html>
63
+ <body>
64
+ <ClientProviders
65
+ initialUser={session?.user}
66
+ initialPermissions={session?.permissions}
67
+ initialNavigation={session?.navigation}
68
+ >
69
+ {children}
70
+ </ClientProviders>
71
+ </body>
72
+ </html>
73
+ );
74
+ }
75
+ ```
76
+
77
+ ---
78
+
79
+ ### 3. Silent Token Refresh (401 Interception)
80
+ Prevent users from losing form progress due to expired access tokens:
81
+
82
+ ```typescript
83
+ const rbacConfig = createRBACConfig({
84
+ apiBaseUrl: 'https://api.example.com',
85
+ getToken: () => localStorage.getItem('accessToken'),
86
+ // Catches 401s, refreshes token, and replays failed requests transparently:
87
+ refreshToken: async () => {
88
+ const res = await fetch('/api/auth/refresh', { method: 'POST' });
89
+ const data = await res.json();
90
+ localStorage.setItem('accessToken', data.accessToken);
91
+ return data.accessToken;
92
+ },
93
+ onUnauthorized: () => {
94
+ window.location.href = '/login';
95
+ },
96
+ });
97
+ ```
98
+
99
+ ---
100
+
101
+ ### 4. Multi-Tenancy & Entity Switching
102
+ Switch active company/branch workspaces and automatically update permissions:
103
+
104
+ ```tsx
105
+ import { usePermission } from '@custom/rbac-template';
106
+
107
+ export function TenantSwitcher({ entities }) {
108
+ const { activeTenantId, setTenantId } = usePermission();
109
+
110
+ return (
111
+ <select
112
+ value={activeTenantId || ''}
113
+ onChange={(e) => setTenantId(e.target.value)} // Automatically refetches /auth/me & adds X-Tenant-Id header
114
+ >
115
+ {entities.map((e) => (
116
+ <option key={e.id} value={e.id}>
117
+ {e.name}
118
+ </option>
119
+ ))}
120
+ </select>
121
+ );
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ### 5. Backend Pagination Contract Adaptation
128
+ Configure 0-based vs 1-based indexing or offset/limit mode in 1 line:
129
+
130
+ ```typescript
131
+ const rbacConfig = createRBACConfig({
132
+ pagination: {
133
+ pageIndexOffset: 0, // For Spring Boot (0-based indexing: page=0)
134
+ pageParamName: 'pageIndex',
135
+ limitParamName: 'pageSize',
136
+ // Or for SQL offset/limit:
137
+ // useOffsetLimit: true,
138
+ // offsetParamName: 'offset',
139
+ },
140
+ });
141
+ ```
142
+
143
+ ---
144
+
145
+ ### 6. Cascading / Dependent Form Dropdowns
146
+ Configure form fields to dynamically filter based on parent selections:
147
+
148
+ ```tsx
149
+ <EmployeeMasterView
150
+ formFields={[
151
+ { name: 'firstName', label: 'First Name', required: true },
152
+ {
153
+ name: 'entityId',
154
+ label: 'Entity',
155
+ type: 'select',
156
+ options: entities.map((e) => ({ label: e.name, value: e.id })),
157
+ },
158
+ {
159
+ name: 'departmentId',
160
+ label: 'Department',
161
+ type: 'select',
162
+ dependsOn: ['entityId'], // Re-evaluates when entityId changes
163
+ options: (formData) =>
164
+ departments
165
+ .filter((d) => !formData.entityId || d.entityId === formData.entityId)
166
+ .map((d) => ({ label: d.name, value: d.id })),
167
+ disabled: (formData) => !formData.entityId, // Disabled until entity is chosen
168
+ },
169
+ ]}
170
+ />
171
+ ```
172
+
173
+ ---
174
+
175
+ ### 7. Dynamic Permission Names & Aliases (`roles.add` vs `roles.addition`)
176
+
177
+ ```typescript
178
+ const rbacConfig = createRBACConfig({
179
+ permissionAliases: {
180
+ 'roles.add': 'roles.addition', // Maps 'roles.add' -> 'roles.addition'
181
+ 'roles.edit': 'roles:modify', // Colon notation support
182
+ 'department_master.add': 'dept.create', // Custom department create slug
183
+ },
184
+ // Or global format resolver:
185
+ // permissionResolver: (module, action) => `${module}:${action}`,
186
+ });
187
+ ```
188
+
189
+ ---
190
+
191
+ ### 8. Field-Level Validation Error Mapping
192
+ When your backend validation fails (e.g. HTTP 400 or 422 with `{ errors: { name: "Name is already taken" } }` or `{ errors: [{ field: "code", message: "Duplicate" }] }`), `HttpClient` and `MasterDataTable` automatically normalize and highlight each field directly under its input element.
193
+
194
+ ---
195
+
196
+ ### 9. Toast & Notification Library Callbacks
197
+ Connect any UI toast library (Sonner, react-toastify, AntD, Chakra) with zero vendor lock-in:
198
+
199
+ ```typescript
200
+ import { toast } from 'sonner';
201
+
202
+ const rbacConfig = createRBACConfig({
203
+ onNotification: ({ type, title, message }) => {
204
+ if (type === 'success') toast.success(message);
205
+ else if (type === 'error') toast.error(message);
206
+ else toast(message);
207
+ },
208
+ });
209
+ ```
210
+
211
+ ---
212
+
213
+ ### 10. URL Query State Synchronization (Deep Linking)
214
+ Keep table filters, search keywords, page index, and sort order in sync with browser query params so users can bookmark and share URLs:
215
+
216
+ ```tsx
217
+ <DepartmentMasterView syncWithUrl={true} defaultSortBy="name" defaultSortOrder="asc" />
218
+ ```
219
+
220
+ ---
221
+
222
+ ### 11. Column Sorting (Ascending / Descending)
223
+ Mark any column as sortable with custom backend keys and direction toggle indicators:
224
+
225
+ ```tsx
226
+ <Table
227
+ columns={[
228
+ { key: 'name', header: 'Department Name', sortable: true, sortKey: 'dept_name' },
229
+ { key: 'createdAt', header: 'Created At', sortable: true },
230
+ ]}
231
+ sortBy={sortBy}
232
+ sortOrder={sortOrder}
233
+ onSortChange={(colKey, direction) => handleSort(colKey, direction)}
234
+ />
235
+ ```
236
+
237
+ ---
238
+
239
+ ### 12. Bulk Row Selection & Batch Actions
240
+ Opt-in multi-row select checkboxes with floating batch action toolbars:
241
+
242
+ ```tsx
243
+ <DepartmentMasterView
244
+ selectable={true}
245
+ batchActions={[
246
+ {
247
+ key: 'export',
248
+ label: 'Export Selected',
249
+ onClick: (selectedIds, selectedItems) => exportToCsv(selectedItems),
250
+ },
251
+ {
252
+ key: 'archive',
253
+ label: 'Archive',
254
+ variant: 'danger',
255
+ onClick: (selectedIds) => archiveDepartments(selectedIds),
256
+ },
257
+ ]}
258
+ />
259
+ ```
260
+
261
+ ---
262
+
263
+ ### 13. Multi-Language i18n Localization
264
+ Translate or customize all table buttons, modals, badges, and counters:
265
+
266
+ ```typescript
267
+ const rbacConfig = createRBACConfig({
268
+ i18n: {
269
+ addNew: '+ Añadir',
270
+ saveChanges: 'Guardar cambios',
271
+ cancel: 'Cancelar',
272
+ delete: 'Eliminar',
273
+ searchPlaceholder: 'Buscar...',
274
+ showingEntries: (from, to, total) => `Mostrando ${from} a ${to} de ${total} registros`,
275
+ },
276
+ });
277
+ ```
278
+
279
+ ---
280
+
281
+ ### 14. File & Multipart Uploads (`FormData`)
282
+ `HttpClient` automatically detects `FormData` instances, omitting default JSON headers so the browser dynamically injects the appropriate `multipart/form-data; boundary=...`:
283
+
284
+ ```typescript
285
+ const formData = new FormData();
286
+ formData.append('file', file);
287
+ await http.upload('/masters/departments/import', formData);
288
+ ```
289
+
290
+ ---
291
+
292
+ ## 🎨 Complete Style & ClassName Overrides
293
+
294
+ Pass custom CSS / Tailwind classes directly to any element of the module:
295
+ ```tsx
296
+ <DepartmentMasterView
297
+ classNames={{
298
+ root: 'p-6 bg-slate-900 text-white rounded-2xl',
299
+ header: {
300
+ title: 'text-2xl font-bold text-amber-400',
301
+ addButton: 'bg-amber-500 hover:bg-amber-600 text-black',
302
+ },
303
+ table: {
304
+ thead: 'bg-slate-800 text-slate-300 font-semibold',
305
+ th: 'px-6 py-4 uppercase tracking-wider text-xs',
306
+ tr: 'hover:bg-slate-800/50 border-b border-slate-800',
307
+ td: 'px-6 py-4 text-slate-300',
308
+ },
309
+ }}
310
+ />
311
+ ```
312
+
313
+ Or pass `unstyled={true}` to strip all default styling when using **shadcn/ui**, **MUI**, or **Bootstrap**.
314
+
315
+ ---
316
+
317
+ ## 📄 License
318
+ MIT
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "rbac-manager",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Enterprise-grade, highly customizable, frontend-only RBAC and Masters template with pluggable API adapters",
5
+ "main": "./dist/cjs/index.js",
6
+ "module": "./dist/esm/index.js",
7
+ "types": "./dist/esm/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/esm/index.d.ts",
11
+ "import": "./dist/esm/index.js",
12
+ "require": "./dist/cjs/index.js"
13
+ },
14
+ "./styles.css": "./src/styles/standalone.css"
15
+ },
16
+ "scripts": {
17
+ "build:esm": "tsc -p tsconfig.esm.json",
18
+ "build:cjs": "tsc -p tsconfig.cjs.json",
19
+ "build": "npm run build:esm && npm run build:cjs && node scripts/postbuild.js",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "keywords": [
23
+ "rbac",
24
+ "permission",
25
+ "roles",
26
+ "masters",
27
+ "access-control",
28
+ "react",
29
+ "frontend-template"
30
+ ],
31
+ "author": "",
32
+ "license": "MIT",
33
+ "peerDependencies": {
34
+ "react": ">=18.0.0",
35
+ "react-dom": ">=18.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.11.0",
39
+ "@types/react": "^18.3.31",
40
+ "@types/react-dom": "^18.3.7",
41
+ "typescript": "^5.9.3"
42
+ },
43
+ "dependencies": {
44
+ "clsx": "^2.1.0",
45
+ "tailwind-merge": "^2.2.0"
46
+ }
47
+ }
@@ -0,0 +1,17 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const cjsPackageJson = path.join(__dirname, '..', 'dist', 'cjs', 'package.json');
5
+ const esmPackageJson = path.join(__dirname, '..', 'dist', 'esm', 'package.json');
6
+
7
+ // Ensure dist/cjs exists
8
+ if (fs.existsSync(path.dirname(cjsPackageJson))) {
9
+ fs.writeFileSync(cjsPackageJson, JSON.stringify({ type: 'commonjs' }, null, 2));
10
+ }
11
+
12
+ // Ensure dist/esm exists
13
+ if (fs.existsSync(path.dirname(esmPackageJson))) {
14
+ fs.writeFileSync(esmPackageJson, JSON.stringify({ type: 'module' }, null, 2));
15
+ }
16
+
17
+ console.log('Postbuild: Successfully generated CJS and ESM package.json manifests.');
@@ -0,0 +1,206 @@
1
+ import { RBACAdapters } from '../core/types/adapter.types';
2
+ import { DEFAULT_ADAPTERS } from '../core/adapters';
3
+ import { RBAC_CONSTANTS, configureRBACConstants, RBACLocaleStrings } from '../constants';
4
+ import { configurePermissionKeys } from '../constants/permissions';
5
+ import { RBACTheme, defaultRBACTheme, mergeThemes } from '../theme';
6
+ import { PermissionKeyResolverFn, defaultPermissionResolver } from '../core/utils/permissionResolver';
7
+ import { RouterAdapter } from '../core/types/router.types';
8
+ import { PaginationConfig, defaultPaginationConfig } from '../core/types/pagination.types';
9
+
10
+ export interface RBACNotification {
11
+ type: 'success' | 'error' | 'info' | 'warning';
12
+ title?: string;
13
+ message: string;
14
+ }
15
+
16
+ export type RBACNotificationHandler = (notification: RBACNotification) => void;
17
+
18
+ export interface ModuleFeatureConfig {
19
+ enabled?: boolean;
20
+ allowAdd?: boolean;
21
+ allowEdit?: boolean;
22
+ allowDelete?: boolean;
23
+ allowView?: boolean;
24
+ }
25
+
26
+ export type FeatureToggle = boolean | ModuleFeatureConfig;
27
+
28
+ export interface RBACFeaturesConfig {
29
+ masters: {
30
+ entity: FeatureToggle;
31
+ department: FeatureToggle;
32
+ designation: FeatureToggle;
33
+ employee: FeatureToggle;
34
+ moduleMaster: FeatureToggle;
35
+ subModuleMaster: FeatureToggle;
36
+ [customMaster: string]: FeatureToggle | undefined;
37
+ };
38
+ rolesAndPermissions: {
39
+ roleCrud: FeatureToggle;
40
+ permissionAssignmentMatrix: boolean;
41
+ permissionRegistry: boolean;
42
+ };
43
+ }
44
+
45
+ export interface RBACConfig {
46
+ apiBaseUrl: string;
47
+ features: RBACFeaturesConfig;
48
+ endpoints: typeof RBAC_CONSTANTS.endpoints;
49
+ adapters: RBACAdapters;
50
+ theme: RBACTheme;
51
+ router?: RouterAdapter;
52
+ pagination: PaginationConfig;
53
+ permissionResolver: PermissionKeyResolverFn;
54
+ permissionAliases: Record<string, string>;
55
+ i18n?: Partial<RBACLocaleStrings>;
56
+ onNotification?: RBACNotificationHandler;
57
+ getToken?: () => string | null | undefined;
58
+ getTenantId?: () => string | number | null | undefined;
59
+ getCustomHeaders?: () => Record<string, string>;
60
+ refreshToken?: () => Promise<string | null | undefined>;
61
+ onUnauthorized?: () => void;
62
+ }
63
+
64
+ export const defaultRBACConfig: RBACConfig = {
65
+ apiBaseUrl: 'https://api.example.com',
66
+ features: {
67
+ masters: {
68
+ entity: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
69
+ department: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
70
+ designation: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
71
+ employee: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
72
+ moduleMaster: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
73
+ subModuleMaster: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
74
+ },
75
+ rolesAndPermissions: {
76
+ roleCrud: { enabled: true, allowAdd: true, allowEdit: true, allowDelete: true },
77
+ permissionAssignmentMatrix: true,
78
+ permissionRegistry: true,
79
+ },
80
+ },
81
+ endpoints: { ...RBAC_CONSTANTS.endpoints },
82
+ adapters: { ...DEFAULT_ADAPTERS },
83
+ theme: defaultRBACTheme,
84
+ pagination: defaultPaginationConfig,
85
+ permissionResolver: defaultPermissionResolver,
86
+ permissionAliases: {},
87
+ getToken: () => {
88
+ if (typeof window !== 'undefined') {
89
+ return localStorage.getItem('token') || undefined;
90
+ }
91
+ return undefined;
92
+ },
93
+ };
94
+
95
+ /**
96
+ * Check if a specific CRUD action is allowed for a given feature toggle
97
+ */
98
+ export function isActionAllowed(
99
+ configValue: FeatureToggle | undefined,
100
+ action: 'add' | 'edit' | 'delete' | 'view'
101
+ ): boolean {
102
+ if (configValue === undefined || configValue === false) return false;
103
+ if (configValue === true) return true;
104
+ if (configValue.enabled === false) return false;
105
+ if (action === 'add') return configValue.allowAdd ?? true;
106
+ if (action === 'edit') return configValue.allowEdit ?? true;
107
+ if (action === 'delete') return configValue.allowDelete ?? true;
108
+ if (action === 'view') return configValue.allowView ?? true;
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * Deep merge helper to create full config with user overrides
114
+ */
115
+ export function createRBACConfig(custom: Partial<{
116
+ apiBaseUrl: string;
117
+ features: Partial<{
118
+ masters: Partial<Record<string, FeatureToggle>>;
119
+ rolesAndPermissions: Partial<{
120
+ roleCrud: FeatureToggle;
121
+ permissionAssignmentMatrix: boolean;
122
+ permissionRegistry: boolean;
123
+ }>;
124
+ }>;
125
+ endpoints: Partial<typeof RBAC_CONSTANTS.endpoints>;
126
+ adapters: Partial<{
127
+ auth: Partial<RBACAdapters['auth']>;
128
+ roles: Partial<RBACAdapters['roles']>;
129
+ permissions: Partial<RBACAdapters['permissions']>;
130
+ masters: Partial<RBACAdapters['masters']>;
131
+ }>;
132
+ theme: Partial<RBACTheme>;
133
+ router: RouterAdapter;
134
+ pagination: Partial<PaginationConfig>;
135
+ permissionResolver: PermissionKeyResolverFn;
136
+ permissionAliases: Record<string, string>;
137
+ permissionKeys: Parameters<typeof configurePermissionKeys>[0];
138
+ constants: Parameters<typeof configureRBACConstants>[0];
139
+ i18n: Partial<RBACLocaleStrings>;
140
+ onNotification: RBACNotificationHandler;
141
+ getToken: () => string | null | undefined;
142
+ getTenantId: () => string | number | null | undefined;
143
+ getCustomHeaders: () => Record<string, string>;
144
+ refreshToken: () => Promise<string | null | undefined>;
145
+ onUnauthorized: () => void;
146
+ }> = {}): RBACConfig {
147
+ if (custom.constants) {
148
+ configureRBACConstants(custom.constants);
149
+ }
150
+
151
+ if (custom.permissionKeys) {
152
+ configurePermissionKeys(custom.permissionKeys);
153
+ }
154
+
155
+ if (custom.i18n) {
156
+ configureRBACConstants({ i18n: custom.i18n });
157
+ }
158
+
159
+ const features: RBACFeaturesConfig = {
160
+ masters: {
161
+ ...defaultRBACConfig.features.masters,
162
+ ...(custom.features?.masters || {}),
163
+ },
164
+ rolesAndPermissions: {
165
+ ...defaultRBACConfig.features.rolesAndPermissions,
166
+ ...(custom.features?.rolesAndPermissions || {}),
167
+ },
168
+ };
169
+
170
+ const endpoints = {
171
+ ...defaultRBACConfig.endpoints,
172
+ ...(custom.endpoints || {}),
173
+ };
174
+
175
+ const adapters: RBACAdapters = {
176
+ auth: { ...defaultRBACConfig.adapters.auth, ...(custom.adapters?.auth || {}) },
177
+ roles: { ...defaultRBACConfig.adapters.roles, ...(custom.adapters?.roles || {}) },
178
+ permissions: { ...defaultRBACConfig.adapters.permissions, ...(custom.adapters?.permissions || {}) },
179
+ masters: { ...defaultRBACConfig.adapters.masters, ...(custom.adapters?.masters || {}) },
180
+ };
181
+
182
+ const theme = mergeThemes(defaultRBACTheme, custom.theme);
183
+ const pagination = { ...defaultPaginationConfig, ...(custom.pagination || {}) };
184
+
185
+ return {
186
+ apiBaseUrl: custom.apiBaseUrl || defaultRBACConfig.apiBaseUrl,
187
+ features,
188
+ endpoints,
189
+ adapters,
190
+ theme,
191
+ router: custom.router || defaultRBACConfig.router,
192
+ pagination,
193
+ permissionResolver: custom.permissionResolver || defaultRBACConfig.permissionResolver,
194
+ permissionAliases: {
195
+ ...defaultRBACConfig.permissionAliases,
196
+ ...(custom.permissionAliases || {}),
197
+ },
198
+ i18n: custom.i18n,
199
+ onNotification: custom.onNotification,
200
+ getToken: custom.getToken || defaultRBACConfig.getToken,
201
+ getTenantId: custom.getTenantId || defaultRBACConfig.getTenantId,
202
+ getCustomHeaders: custom.getCustomHeaders || defaultRBACConfig.getCustomHeaders,
203
+ refreshToken: custom.refreshToken || defaultRBACConfig.refreshToken,
204
+ onUnauthorized: custom.onUnauthorized || defaultRBACConfig.onUnauthorized,
205
+ };
206
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Action keys, IDs, and human-readable labels.
3
+ * Used for the Permission Matrix and Permission Gates.
4
+ */
5
+ export const DEFAULT_ACTION_KEYS = {
6
+ VIEW: 'view',
7
+ ADD: 'add',
8
+ EDIT: 'edit',
9
+ DELETE: 'delete',
10
+ ASSIGN: 'assign',
11
+ } as const;
12
+
13
+ export const DEFAULT_ACTION_CONFIGS = [
14
+ { actionId: 1, action_key: DEFAULT_ACTION_KEYS.VIEW, action_name: 'View', sort_order: 1 },
15
+ { actionId: 2, action_key: DEFAULT_ACTION_KEYS.ADD, action_name: 'Add (Create)', sort_order: 2 },
16
+ { actionId: 3, action_key: DEFAULT_ACTION_KEYS.EDIT, action_name: 'Edit', sort_order: 3 },
17
+ { actionId: 4, action_key: DEFAULT_ACTION_KEYS.DELETE, action_name: 'Delete', sort_order: 4 },
18
+ { actionId: 5, action_key: DEFAULT_ACTION_KEYS.ASSIGN, action_name: 'Assign', sort_order: 5 },
19
+ ] as const;
20
+
21
+ export type ActionKey = (typeof DEFAULT_ACTION_KEYS)[keyof typeof DEFAULT_ACTION_KEYS] | string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Global API response and pagination field keys.
3
+ * If your backend wraps responses in different keys (e.g. `items` instead of `list`),
4
+ * change it here or pass overrides via RBACConfig.
5
+ */
6
+ export const DEFAULT_API_RESPONSE_KEYS = {
7
+ SUCCESS: 'success',
8
+ STATUS_CODE: 'statusCode',
9
+ MESSAGE: 'message',
10
+ DATA: 'data',
11
+ ERROR: 'error',
12
+ ERRORS: 'errors',
13
+ } as const;
14
+
15
+ export const DEFAULT_PAGINATION_KEYS = {
16
+ PAGE: 'page',
17
+ LIMIT: 'limit',
18
+ TOTAL: 'total',
19
+ TOTAL_PAGES: 'totalPages',
20
+ HAS_NEXT: 'hasNext',
21
+ ITEMS: 'list', // container API returns `list` or direct array
22
+ } as const;
23
+
24
+ export type ApiResponseKeys = typeof DEFAULT_API_RESPONSE_KEYS;
25
+ export type PaginationKeys = typeof DEFAULT_PAGINATION_KEYS;