Async & Custom Functions
Declarative conditions are synchronous. For business logic that needs I/O (a DB
lookup, an external service), register a custom function and reference it
from a grant or gate as { fn, args } — the reference stays JSON‑serializable
(the function lives in code).
ac.defineCondition('ipAllowed', async (ctx, args) => isAllowed(ctx.ip, args.cidr));
ac.grant('admin') .where({ fn: 'ipAllowed', args: { cidr: '10.0.0.0/8' } }) .readAny('server');Resolving on the Async Path
Section titled “Resolving on the Async Path”A check that touches a custom function must use the async accessors:
const ok = await ac.can('admin', { ip }) .readAny('server').grantedAsync;
// one-shot formconst perm = await ac.checkAsync({ role: 'admin', resource: 'server', action: 'read:any', context: { ip }});perm.granted;Declarative Checks Stay Sync
Section titled “Declarative Checks Stay Sync”Only { fn } conditions require the async path. Purely declarative grants/gates
resolve synchronously even if you happen to await them, so you can use the
async accessors uniformly if you prefer.
ac.grant('user').where('$.order.value <= 100').updateAny('order');await ac.can('user', { order: { value: 50 } }) .updateAny('order').grantedAsync; // works (declarative, but awaitable)Caching Within a Permission
Section titled “Caching Within a Permission”Resolving a permission once memoizes its result: a sync resolve followed by an
await won’t re‑resolve (and won’t emit a second access event).