Skip to content

API: Guantr.prototype.can.any ​

The can.any sub-method performs a batch permission check — it returns true if any check in the array is granted. It resolves the evaluation context once and shares it across all checks, and short-circuits on the first true result.

Use this for decisions that require at least one permission (e.g. "should we show the interaction bar?").

Signature ​

ts
can.any(
  checks: Array<[action: string, resource: [resourceKey: string, resourceInstance: object]]>,
): Promise<boolean>;

Parameters ​

  • checks: An array of check tuples. Each tuple is [action, [resourceKey, resourceInstance]]:
    • action: (string) The action being checked.
    • resource: A tuple [string, object] of resource key and resource instance.

Returns ​

  • Promise<boolean>:
    • true as soon as any check is granted (short-circuits).
    • false if no check is granted.
    • false for an empty array (vacuous false — no check in an empty set can pass).

How it works ​

  1. Resolves context once. The context option is resolved once and its result is shared across every check in the array.
  2. Iterates through the checks in order.
  3. For each check, performs the same evaluation as can(): queries relevant rules, evaluates conditions against the resource instance and shared context.
  4. Short-circuits on the first true. Remaining checks are not evaluated.
  5. Returns false if no check passed.

Empty array behavior ​

An empty array of checks always returns false (vacuous false):

ts
await guantr.can.any([]); // false

Context sharing ​

The context is resolved exactly once per can.any() call, not once per individual check.

ts
const guantr = await createGuantr({
  context: async () => {
    console.log('context called');
    return { userId: 1 };
  },
});

await guantr.can.any([
  ['read', ['post', { id: 1 }]],
  ['comment', ['post', { id: 1 }]],
]);
// "context called" logged only once

Caching ​

Individual check results within a can.any call are not independently cached — the raw evaluation path is called directly without consulting the cache. Subsequent individual can() calls may still hit the cache.

Short-circuit behavior ​

Because can.any stops on the first true:

  • Place the most likely-to-pass checks first for best performance.
  • If you expect most checks to be denied, expect near-full iteration.
  • Later checks might never run — avoid side effects in check definitions.

Examples ​

Basic usage ​

ts
const post = { id: 1, title: 'Hello', status: 'published' };

// Check if user can interact with this post in any way
const canInteract = await guantr.can.any([
  ['read', ['post', post]],
  ['comment', ['post', post]],
  ['share', ['post', post]],
]);
// true if at least ONE action is allowed

Short-circuit: first passing check ​

ts
// 'read' is allowed, so 'delete' and 'archive' are never evaluated
const result = await guantr.can.any([
  ['delete', ['post', post]], // false → continue
  ['read', ['post', post]], // true → short-circuit, return true
  ['archive', ['post', post]], // never evaluated
]);
// true

When no rules exist ​

ts
await guantr.can.any([
  ['read', ['post', { id: 1 }]],
  ['comment', ['post', { id: 1 }]],
]);
// false — no rules exist, all checks fail

With overlapping allow/deny ​

ts
// Even if 'delete' is denied, 'read' may still be allowed
const result = await guantr.can.any([
  ['delete', ['post', { status: 'published' }]], // denied
  ['read', ['post', { status: 'published' }]], // allowed → true
]);
// true

Mixed resource keys ​

ts
const result = await guantr.can.any([
  ['read', ['post', { id: 1 }]],
  ['read', ['document', { id: 5 }]],
]);

See also ​

  • can.all — Check if all permissions are granted.
  • cannot.all — Check if all permissions are denied (!can.any()).
  • cannot.any — Check if any permission is denied (!can.all()).
  • can — Single permission check.