Convex adapter
| This documentation is for an as-yet unreleased version of Cerbos. Choose 0.50.0 from the version picker at the top right or navigate to https://docs.cerbos.dev for the latest version. |
The @cerbos/orm-convex package converts a Cerbos PlanResources response into a Convex filter function. Authorization conditions are split between a database-level filter and an optional JavaScript post-filter for operators that Convex cannot express natively.
Supported operators
Database-level operators
| Category | Operators |
|---|---|
Logical |
|
Comparison |
|
Membership |
|
Existence |
|
Post-filter operators
The following operators cannot be expressed as Convex database filters. When encountered, the adapter returns a postFilter function that evaluates them in JavaScript:
| Category | Operators |
|---|---|
String |
|
Collection |
|
For and(…) expressions with mixed operator types, the adapter splits the tree: database-pushable children go to filter, the rest go to postFilter. For or(…) with any unsupported child, the entire expression goes to postFilter to avoid missing results.
allowPostFilter opt-in
By default, queryPlanToConvex throws when the query plan requires a postFilter. This is because post-filter operators cause data to be fetched before authorization filtering is fully applied. To opt in:
const { kind, filter, postFilter } = queryPlanToConvex({
queryPlan,
mapper,
allowPostFilter: true,
});
If your policies only use operators that Convex supports natively, filter alone enforces the full policy at the database level and this flag is not needed.
Usage
import { queryPlanToConvex, PlanKind } from "@cerbos/orm-convex";
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "document" },
action: "view",
});
const { kind, filter, postFilter } = queryPlanToConvex({
queryPlan,
mapper,
allowPostFilter: true,
});
if (kind === PlanKind.ALWAYS_DENIED) return [];
if (kind === PlanKind.ALWAYS_ALLOWED && !postFilter) {
return await ctx.db.query("documents").collect();
}
let query = ctx.db.query("documents");
if (filter) query = query.filter(filter);
let results = await query.collect();
if (postFilter) results = results.filter(postFilter);
return results;
Field mapper
const mapper = {
"request.resource.attr.title": { field: "title" },
"request.resource.attr.status": { field: "status" },
};
// Or as a function:
const mapper = (path) => ({
field: path.replace("request.resource.attr.", ""),
});
The field property rewrites a Cerbos path to a Convex document field. Dot notation is supported for nested fields. If the mapper is omitted, the adapter uses query plan paths as-is.
Limitations
-
String and collection operators are evaluated as a JavaScript
postFilterafter the database query returns. These conditions do not reduce the number of documents read from the database. -
For
or(…)expressions where any child uses an unsupported operator, the entire OR is evaluated viapostFilter. -
The
inoperator is composed as multipleeqcomparisons joined withor, which may be less efficient for large value lists.