grantedScopes is the database-query primitive for permission-filtered lists. Instead of loading every resource and checking can per row is an O(N) pattern that defeats the purpose of database indexes. You call grantedScopes once, obtain the set of scope instance IDs where the principal holds the key, and push that filter into your query. Alfiz’s companion listing helpers turn those two sets into a query: planListing reduces them to a ListingPlan, one of { mode: "all" }, { mode: "none" }, { mode: "all_except", exclude }, or { mode: "scoped", include, exclude }, and matPathCondition, closureTableCondition, and prismaMatPathWhere build the actual predicate from it.
client.grantedScopes(principal, key)
PrincipalRef
required
Identifies who is being evaluated. Either
{ userId: string } for a human
user or { serviceId: string } for a machine principal.LooseKey<K>
required
A concrete permission key declared in your catalog. The key is verified
against the catalog before evaluation; an unknown key raises
UnknownPermissionError. Wildcard patterns are not valid here. This
method returns scope sets for one specific leaf.Return value
Set<ScopeId>
The scopes of the principal’s unexpired grant rows that match
key.
including grants conferred through a role and through wildcard patterns.
For an inactive principal, this set is always empty.These are the scopes the grants were made at, which in a hierarchical
deployment are usually ancestors such as docs.folder:42, not the docs.doc:*
ids on your rows. Match rows by ancestry (a closure table or a
materialized path), not by scope-id equality.Set<ScopeId>
The set of scope instance IDs at which the principal has a personal revoke
that suppresses
key. Use this set to exclude rows from queries: a scope
in revoked (or any descendant of it) should not appear in the results
even if the scope’s id appears in granted through an ancestor grant.How to use grantedScopes with a listing query
The pattern has three steps:
- Call
grantedScopesto obtain the granted and revoked scope sets. - Use the sets to build a SQL predicate that filters the resource table.
- Execute the query, and the database applies the permission filter, so you load only the rows the principal is allowed to see.
client.holds(principal, key)
true if the principal holds key at any scope: the single-key “does this button exist at all?” probe. Use holds for unscoped conditional UI where the concrete scope is not yet known: someone who holds publish_file on only a handful of folders should still see the publish button surface. The button’s server action still gates with can at its concrete scope.
PrincipalRef
required
The principal to check.
LooseKey<K>
required
A concrete permission key in the catalog.
Return value
Promise<boolean>
true when the principal holds an unexpired, non-globally-revoked grant
for key at any scope. false for inactive principals or when no grant
exists. Never a gate; use can for authorization.client.heldKeys(principal)
PrincipalRef
required
The principal to evaluate.
Return value
Promise<PermissionKey[]>
An array of every concrete key the principal holds at some scope. Empty for
inactive principals. Order matches the catalog’s key order (alphabetical).
O(catalog size), so call once per request and reuse;
snapshot(principal).heldKeys
does exactly that.