Skip to content

API: Guantr.prototype.can.all ​

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

Use this for decisions that depend on multiple permissions (e.g. "should the user see the full management toolbar?").

Signature ​

ts
can.all(
  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 if every check is granted.
    • false as soon as any check is denied (short-circuits).
    • true for an empty array (vacuous truth — all zero checks 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 false. Remaining checks are not evaluated.
  5. Returns true if all checks passed.

Empty array behavior ​

An empty array of checks always returns true (vacuous truth):

ts
await guantr.can.all([]); // true

Context sharing ​

The context is resolved exactly once per can.all() call, not once per individual check. This is more efficient than calling can() in a loop.

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

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

Caching ​

Individual check results within a can.all call are not independently cached — _evaluateCheck is called directly without the per-check cache layer. However, subsequent can() calls for the same action/resource/context may hit the cache.

Short-circuit behavior ​

Because can.all stops on the first false:

  • Place the cheapest/fastest-to-evaluate checks first for best performance.
  • Checks that are most likely to fail should come early.
  • Later checks might never run — avoid side effects in check definitions.

Examples ​

Basic usage ​

ts
const post = { id: 1, title: 'Hello', status: 'draft', ownerId: 'user-1' };

// Check if user has full management access
const canManage = await guantr.can.all([
  ['read', ['post', post]],
  ['update', ['post', post]],
  ['delete', ['post', post]],
]);
// true only if ALL three actions are allowed

Mixed resource keys ​

ts
const result = await guantr.can.all([
  ['read', ['post', { id: 1 }]],
  ['read', ['comment', { id: 5, postId: 1 }]],
  ['read', ['user', { id: 1 }]],
]);

Short-circuit: first failing check ​

ts
// If 'delete' is denied, 'archive' is never evaluated
const result = await guantr.can.all([
  ['read', ['post', post]], // true → continue
  ['delete', ['post', post]], // false → short-circuit, return false
  ['archive', ['post', post]], // never evaluated
]);
// false

With shared context ​

ts
const guantr = await createGuantr<MyMeta>({
  context: () => ({ userId: getCurrentUserId() }),
  // Called once even though we check 3 permissions
});

await guantr.can.all([
  ['update', ['post', { authorId: 1 }]],
  ['delete', ['post', { authorId: 1 }]],
]);

When no rules exist ​

ts
await guantr.can.all([
  ['read', ['post', { id: 1 }]],
  ['edit', ['post', { id: 1 }]],
]);
// false — all checks fail (no allow rules, implicit deny)

See also ​

  • can.any — Check if any permission is 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.