Core Concepts

Architecture Overview

react-access-engine is built around a single <AccessProvider> that initializes several specialized engines:

┌─────────────────────────────────────────────────────┐
│                  AccessProvider                      │
│                                                     │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │
│  │   Role   │ │Permission│ │ Feature  │            │
│  │  Engine  │ │  Engine  │ │  Engine  │            │
│  └──────────┘ └──────────┘ └──────────┘            │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐            │
│  │  Policy  │ │   Plan   │ │Experiment│            │
│  │  Engine  │ │  Engine  │ │  Engine  │            │
│  └──────────┘ └──────────┘ └──────────┘            │
│  ┌──────────┐ ┌──────────┐                         │
│  │  Plugin  │ │  Debug   │                         │
│  │  Engine  │ │  Engine  │                         │
│  └──────────┘ └──────────┘                         │
└─────────────────────────────────────────────────────┘

Config Object

All access rules are defined in a single config object using defineAccess():

const config = defineAccess({
  roles: [...],           // Role definitions
  permissions: {...},     // Role → permission mapping
  features: {...},        // Feature flag definitions
  experiments: {...},     // A/B experiment definitions
  policies: [...],        // ABAC policy rules
  plans: [...],           // Plan hierarchy (low → high)
  environment: {...},     // Environment context
  plugins: [...],         // Plugin instances
  debug: true,            // Enable debug engine
});

User Context

The user object describes the current authenticated user:

const user = {
  id: "user-42", // Required: unique identifier
  roles: ["editor"] as const, // Required: assigned roles
  plan: "pro", // Optional: subscription plan
  attributes: {
    // Optional: ABAC attributes
    department: "engineering",
    country: "US",
    joinedAt: "2023-01-15",
  },
};

Permission Resolution

Permissions are resolved in this order:

  1. RBAC Check — Does the user's role grant this permission?
    • Exact match: posts:write matches posts:write
    • Namespace wildcard: posts:* matches posts:write
    • Global wildcard: * matches everything
  2. Policy Check — If ABAC policies exist, do they allow it?
    • Policies can override RBAC (a policy deny blocks even if RBAC allows)
    • Policy rules are sorted by priority (highest first), first match wins
    • Default: deny if policies exist but none match

Feature Evaluation

Features are evaluated with this precedence:

  1. Staticenabled: true/false
  2. EnvironmentallowedEnvironments: ['production']
  3. RoleallowedRoles: ['admin', 'beta-tester']
  4. PlanallowedPlans: ['pro', 'enterprise']
  5. Dependenciesdependencies: ['otherFeature'] (topologically resolved)
  6. RolloutrolloutPercentage: 25 (deterministic hash, SSR-safe)

If a feature has dependencies, they're resolved via topological sort. Circular dependencies result in the feature being disabled.

Hooks vs Components

react-access-engine provides both declarative components and imperative hooks:

Use CaseComponentHook
Show/hide UI by permission<Can>usePermission()
Feature flag gate<Feature>useFeature()
Multi-condition gate<AccessGate>useAccess()
Route protection<PermissionGuard>usePermission()
A/B variant rendering<Experiment>useExperiment()
Plan-gated UI<AccessGate plan="pro">usePlan()

Use components for simple show/hide logic. Use hooks when you need programmatic control (e.g., conditional API calls, analytics, styling).

Config Merging

Use mergeConfigs() to combine base configs with overrides:

import { defineAccess, mergeConfigs } from "react-access-engine";

const baseConfig = defineAccess({
  roles: ["admin", "user"] as const,
  permissions: { admin: ["*"], user: ["read"] },
});

const withFeatures = mergeConfigs(baseConfig, {
  features: { darkMode: { enabled: true } },
});

This is useful for layering environment-specific config on top of a base config, or for remote config merging.

Next Steps