Tutorial: RBACClient without a framework
Every frontend adapter — React, Vue, Angular, Svelte — is a thin wrapper around RBACClient. This tutorial skips the framework layer entirely, so the underlying model is clear before you add one back.
Build a snapshot (normally your backend does this)
const snapshot = {
user: { id: 'u1' },
permissions: [{ resource: 'invoice', actions: ['approve'] }],
conditions: [{ resource: 'expense-report', actions: ['approve'], when: 'owner_id == user.id' }],
};In a real app, this comes from a backend endpoint (GET /me/permissions) that already ran the Node RBAC.can()-equivalent resolution server-side and returned just the flattened result — the browser never sees raw role definitions.
Construct the client and check permissions
import { RBACClient } from 'rbac-fs/client';
const client = new RBACClient(snapshot);
client.can('invoice', 'approve'); // true
client.can('expense-report', 'approve', { owner_id: 'u1' }); // true — matches user.id
client.can('expense-report', 'approve', { owner_id: 'someone-else' }); // falseWire it into plain DOM manipulation
function renderApproveButton(container) {
if (client.can('invoice', 'approve')) {
const btn = document.createElement('button');
btn.textContent = 'Approve invoice';
container.appendChild(btn);
} else {
container.textContent = "You can't approve invoices";
}
}This is exactly the pattern rbac-fs/react's <Can> component wraps for you declaratively — worth seeing the manual version once so the framework adapter later feels like a convenience, not magic.
Custom operators work here too
const client = new RBACClient(snapshot, {
operators: {
withinBusinessHours: ({ context }) => {
const hour = new Date(context.now).getHours();
return hour >= 9 && hour < 17;
},
},
});Same operators option as the Node RBAC constructor — register a real function, referenced by name from a condition's { op: 'custom', name: 'withinBusinessHours' }. This is how you'd express a rule like "can only approve during business hours" entirely client-side, without a network round-trip per check.
Verified against README.md's browser-usage documentation and docs/PLAN.md §7's snapshot API specification — the same public surface every framework adapter builds on.