Pre-release quality gates for
react-access-enginev0.1.0
| Area | File | Tests | Coverage |
|---|---|---|---|
| Role engine | role-engine.test.ts |
8 | hasRole, hasAnyRole, hasAllRoles, getPermissionsForUser (dedup) |
| Permission engine | permission-engine.test.ts |
8 | hasPermission, wildcards (*, ns:*), hasAny/hasAll |
| Policy engine | policy-engine.test.ts |
9 | ABAC conditions, priority, scopes (role/plan/env), error handling |
| Feature engine | feature-engine.test.ts |
14 | Boolean, rollout %, role/plan/env gating, dependencies, circular deps |
| Experiment engine | experiment-engine.test.ts |
4 | Deterministic assignment, allocation distribution |
| Plan engine | plan-engine.test.ts |
7 | Tier comparison, unknown plan, no hierarchy |
| Hash utility | hash.test.ts |
4 | Deterministic, non-negative, distribution |
| Condition engine | condition-engine.test.ts |
21 | All 10 operators, AND/OR/nested groups, custom operators, unknown ops |
| Plugin engine | plugin-engine.test.ts |
14 | All 6 event types, error isolation, collectOperators |
| Debug engine | debug-engine.test.ts |
17 | Recording, MAX_ENTRIES cap, subscribe/unsubscribe, clear |
| Remote config | remote-config-engine.test.ts |
12 | Load, stale-while-revalidate, polling, refresh, destroy |
| resolve-path | resolve-path.test.ts |
9 | Nested paths, arrays, null/undefined, empty path |
| Built-in plugins | plugins.test.ts |
13 | Audit logger, analytics adapter, operator plugin |
| Components | components.test.tsx |
34 | Can, Feature, AccessGate, PermissionGuard, Experiment, error boundary |
| Hooks | hooks.test.tsx |
20 | useAccess, usePermission, useRole, useFeature, usePolicy, useExperiment, usePlan, useAccessDebug |
| Integration | integration.test.tsx |
8 | Nested providers, mergeConfigs, SSR safety, plugin events |
| Total | 16 files | 202 |
- RBAC: Role → permission mapping, wildcard matching, namespace wildcards
- ABAC: Condition engine with all 10 built-in operators (equals, notEquals, in, notIn, includes, greaterThan, lessThan, gte, lte, exists), AND/OR condition groups, nested groups, custom operators via plugins
- Feature flags: Boolean, percentage rollout (deterministic hash), role/plan/environment gating, dependency resolution, circular dependency detection
- Experiments: Deterministic variant assignment via DJB2 hash, custom allocation percentages, inactive experiments
- Policy engine: Priority-based rule evaluation, ABAC conditions, scope matching (role, plan, environment), error isolation in condition functions
- Components: All 7 components (AccessProvider, Can, Feature, AccessGate, PermissionGuard, FeatureToggle, Experiment) with fallback rendering, mode=all/any
- Hooks: All 9 public hooks with type safety
- Plugin system: 6 event types with error isolation, operator collection, 3 built-in plugins
- Debug system: Event recording, 100-entry cap, push-based subscriptions, clear
- Remote config: Custom loader, polling, stale-while-revalidate, error handling
- Integration: Nested providers, config merging, SSR safety, plugin event flow through components
| Technique | Where | Why |
|---|---|---|
useMemo on userPermissions |
AccessProvider | Recomputed only when roles or permissions config change |
useMemo on featureResults |
AccessProvider | Recomputed only when userId, roles, plan, features, or environment change |
useStableValue for roles array |
AccessProvider | Prevents spurious recomputation when roles array is recreated with same values |
useCallback on checkPermission, checkFeature, getExperiment |
AccessProvider | Stable function references across renders |
useMemo on context value |
AccessProvider | Prevents unnecessary context propagation |
- Child components only re-render when the access context actually changes (new user, new config, or new role/plan).
usePermission('articles:read')returns a memoized boolean — it won't cause re-renders on its own.- Feature evaluation is done once per provider render (all features pre-computed), not per
useFeature()call. - The
useStableValuehook ensures that['editor']!==['editor'](referential inequality from parent re-renders) doesn't cascade.
- Add
React.memowrappers toCan,Feature,AccessGatecomponents for cases where parent re-renders frequently. - Consider
useSyncExternalStorefor the debug engine subscription (already used in devtools package). - Benchmark with React DevTools Profiler to validate real-world rerender counts.
- Zero runtime dependencies — only React as a peer dependency.
- Dual ESM/CJS output via tsup with tree-shaking and code splitting.
sideEffects: falsein package.json — enables dead code elimination.- Separate engine modules — each engine is tree-shakeable. If you only use roles/permissions, the experiment/policy/remote-config engines are eliminated.
- Dev-only warnings —
warnOnceandwarnare tree-shaken in production (predicated onprocess.env.NODE_ENV). 'use client'directives — on all components and hooks for RSC compatibility.
| Package | ESM | CJS | DTS |
|---|---|---|---|
react-access-engine |
~15 KB | ~17 KB | ✅ |
react-access-engine-devtools |
~30 KB | ~32 KB | ✅ |
Sizes are uncompressed. Gzipped sizes are typically 40-60% smaller.
- Add
size-limitCI check to prevent regressions (target: <5 KB gzipped for core). - Consider separate entry points for advanced features (
/policies,/experiments) if core bundle grows.
| Requirement | Status |
|---|---|
No window access in engines |
✅ |
No document access in engines |
✅ |
No Math.random() — deterministic hashing |
✅ DJB2 hash |
'use client' on all components/hooks |
✅ |
| Server-side rendering works in jsdom | ✅ Tested |
| Next.js App Router compatible | ✅ Example exists |
defineAccess()infers literal types for roles, permissions, features, plans, and experiments viaconstgenerics.InferRoles<T>,InferPermissions<T>, etc. extract types from a config object.- All hooks and components accept the inferred types — IDE autocomplete works out of the box.
- Missing provider:
useAccess()outside<AccessProvider>throws a clear[react-access-engine]prefixed error. - Empty roles: Warning when
AccessProviderreceives an empty roles array. - Nested providers: Warning when a nested
<AccessProvider>is detected, suggestingmergeConfigs(). - Unknown features: Warning when
useFeature()orcheckFeature()is called with a feature name not in the config. - Unknown experiments: Warning when
getExperiment()is called with an unknown experiment ID. - Tree-shaken in production: All warnings use
process.env.NODE_ENVguards.
- 3 built-in plugins ready to use: audit logger, analytics adapter, custom operators.
- Plugin errors never break the application — all plugin calls are wrapped in try/catch.
- Custom operators extend the condition engine without modifying core code.
- All 202 tests pass (
vitest run) - TypeScript strict mode passes (
tsc --noEmit) - Build succeeds for all packages (
turbo run build) - Dual ESM/CJS output verified
- DTS (declaration files) generated
-
sideEffects: falsein package.json -
'use client'directives on all components/hooks - No runtime dependencies beyond React peer dep
- Examples compile and render correctly
- Playground app works with all control combinations
- Add
size-limitCI check - Add GitHub Actions CI workflow (test + build + typecheck)
- Generate API documentation from TSDoc comments
- Add CONTRIBUTING.md
- Add CHANGELOG.md via changesets
- Publish
0.1.0-beta.1to npm for validation - Test with React 18 (not just React 19)
- Add
eslint-plugin-react-hooksexhaustive-deps audit - Add integration test with Next.js build
- Benchmark suite (rerender counts, provider mount time)
- Storybook integration for component documentation
- Visual regression tests for devtools overlay
- WebSocket-based remote config transport
- React Native compatibility testing
| Example | Directory | What it demonstrates |
|---|---|---|
| Basic RBAC | examples/basic/ |
Role → permission mapping, Can, Feature, hooks |
| SaaS Dashboard | examples/saas-dashboard/ |
Multi-role, plan hierarchy, plan-gated features, role/plan switcher |
| Feature Rollout | examples/feature-rollout/ |
Percentage rollout, role/plan gating, user ID cohort switching |
| A/B Experiments | examples/experiments/ |
Multi-variant experiments, deterministic assignment, inactive experiments |
| ABAC Policies | examples/abac-policies/ |
Owner-only editing, department scoping, archived document deny, policy evaluation results |
| Next.js SSR | examples/nextjs/ |
App Router integration, Server/Client Components, 'use client' pattern |
| Playground | apps/playground/ |
Full interactive explorer with all tabs: identity, permissions, features, experiments, gates, policies, debug trace |
| Decision | Rationale |
|---|---|
| DJB2 hash for rollout/experiments | Deterministic, SSR-safe, no crypto dependency, good distribution |
| Single context provider | Simplicity over micro-contexts; memoization prevents unnecessary rerenders |
| Policy engine separate from RBAC | Policies are opt-in; basic RBAC works without them |
| Plugin errors silenced | Library must never crash the host application |
| 100-event debug cap | Prevent memory leaks in long-running apps |
'use client' on all React exports |
Required for Next.js RSC compatibility |
as const in defineAccess() |
Enables literal type inference without extra generics |