@cerbos/orm-drizzle

中文文档
0.1.0发布于 2026/02/03Apache-2.0TypeScriptCommonJS近 30 天下载 4257

Drizzle ORM adapter for Cerbos query plans

原始使用文档
文档目录10 节
  1. Features
  2. How it works
  3. Installation
  4. Usage
  5. Handling different plan kinds
  6. Mapper options
  7. Attribute references and functions
  8. Mapping relations
  9. Working with collections
  10. Testing

Cerbos + Drizzle ORM Adapter

An adapter library that takes a Cerbos Query Plan (PlanResources API) response and converts it into a Drizzle ORM SQL expression. This allows you to use Cerbos query plans directly inside your Drizzle queries.

Features

  • Supports logical operators: and, or, not
  • Supports comparison operators: eq, ne, lt, gt, le, ge, in
  • Supports string operators: contains, startsWith, endsWith
  • Supports nullability checks via the isSet operator
  • Supports set-aware operators such as hasIntersection, exists, exists_one, and all
  • Supports relation-aware mappings, including nested relations and many-to-many joins
  • Works with Drizzle SQLite, PostgreSQL, MySQL and PlanetScale drivers

How it works

Cerbos can respond to a PlanResources request with one of three plan kinds. The adapter mirrors that API:

  • PlanKind.ALWAYS_ALLOWED: The user can access the resource without any extra filtering.
  • PlanKind.ALWAYS_DENIED: The user cannot access the resource at all.
  • PlanKind.CONDITIONAL: Cerbos returns an expression tree that must be applied when reading data. The adapter converts this expression into a Drizzle SQL filter.

queryPlanToDrizzle walks the Cerbos expression, resolves every attribute reference through the mapper, and produces a Drizzle SQL fragment. That fragment can then be composed with the rest of your query builder chain (db.select().from(table).where(result.filter)).

Installation

npm install @cerbos/orm-drizzle

Usage

import { queryPlanToDrizzle, PlanKind } from "@cerbos/orm-drizzle";
import { eq, and } from "drizzle-orm";
import { resources } from "./schema";

const plan = await cerbos.planResources({
  principal,
  resource,
  action,
});

const result = queryPlanToDrizzle({
  queryPlan: plan,
  mapper: {
    "request.resource.attr.status": resources.status,
    "request.resource.attr.owner": resources.ownerId,
  },
});

if (result.kind === PlanKind.CONDITIONAL) {
  const rows = await db
    .select()
    .from(resources)
    .where(and(eq(resources.deleted, false), result.filter));
}

Handling different plan kinds

const evaluation = queryPlanToDrizzle({ queryPlan: plan, mapper });

switch (evaluation.kind) {
  case PlanKind.ALWAYS_ALLOWED:
    // run the query without extra filters
    break;
  case PlanKind.ALWAYS_DENIED:
    // return an empty result immediately
    break;
  case PlanKind.CONDITIONAL:
    const rows = await db
      .select()
      .from(resources)
      .where(evaluation.filter);
    break;
}

Cerbos plans reference both resources (request.resource.attr.*) and principals (request.principal.attr.*), so include the paths your policies emit in the mapper.

Mapper options

The mapper associates Cerbos attribute references with Drizzle columns. It can be:

  • A plain object where keys are Cerbos attribute references and values are Drizzle columns or SQL expressions.
  • A function receiving the attribute reference and returning the column/expression.
  • An object with a column property and an optional transform function to customize how operator/value pairs are converted into SQL.
const result = queryPlanToDrizzle({
  queryPlan,
  mapper: {
    "request.resource.attr.custom": {
      column: sql`lower(${resources.title})`,
      transform: ({ operator, value }) => {
        if (operator !== "eq") throw new Error("Unsupported");
        return eq(sql`lower(${resources.title})`, value.toLowerCase());
      },
    },
  },
});

Attribute references and functions

  • Plain values: map request.resource.attr.field to a column (resources.field).
  • Nested attributes: map longer paths such as request.resource.attr.owner.email.
  • Principal attributes: map request.principal.attr.role or similar paths when policies check the caller.
  • Dynamic resolution: pass a mapper function (reference) => ... to compute mappings at runtime.

Every mapper entry can be:

  • A column or SQL fragment.
  • An object with column and/or transform to customize how each operator is translated.
  • A relation mapping (described below) for nested resource structures.

Mapping relations

Relations can be described using the relation option, mirroring the structure of the Prisma adapter. The adapter will wrap comparisons in EXISTS subqueries and automatically infer relation fields when they match the column names on the related table.

const result = queryPlanToDrizzle({
  queryPlan,
  mapper: {
    "request.resource.attr.owner": {
      relation: {
        type: "one",
        table: owners,
        sourceColumn: resources.ownerId,
        targetColumn: owners.id,
        fields: {
          email: owners.email,
        },
      },
    },
    "request.resource.attr.tags": {
      relation: {
        type: "many",
        table: resourceTags,
        sourceColumn: resources.id,
        targetColumn: resourceTags.resourceId,
        fields: {
          name: {
            relation: {
              type: "one",
              table: tags,
              sourceColumn: resourceTags.tagId,
              targetColumn: tags.id,
              field: tags.name,
            },
          },
        },
      },
    },
  },
});

With the above mapper, query plan references such as request.resource.attr.owner.email and request.resource.attr.tags.name are translated into EXISTS expressions that join the owners and tags tables respectively.

Working with collections

  • hasIntersection: Use for multi-valued attributes such as tags. When Cerbos emits hasIntersection(map(resource.tags, lambda t => t.name), ["tag"]), the mapper looks up the nested field and the adapter converts it into a column IN (...) condition.
  • exists, exists_one, and all: When policies reference array attributes (e.g., request.resource.attr.tags), mark the mapper entry as a relation. The adapter scopes the lambda variable, generates the EXISTS subquery, and correlates it with the parent table automatically.
  • filter: Cerbos uses filter during plan construction. The adapter discards those lambdas because the entire filter is rerun in Drizzle land.

Testing

The project ships with a comprehensive test suite that exercises all supported operators using an in-memory SQLite database and the official Drizzle ORM query builder.

版本与发布记录

@cerbos/orm-drizzle 共发布 1 个版本,首次发布于 2026/02/03;过去 12 个月发布了 1 个版本。

版本发布日期距今
0.1.0latest2026/02/039 个月前

关于 @cerbos/orm-drizzle 的常见问题

@cerbos/orm-drizzle 是什么?

Drizzle ORM adapter for Cerbos query plans

如何安装 @cerbos/orm-drizzle?

在项目目录运行 npm install @cerbos/orm-drizzle,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:drizzle-orm(^0.44.0 || ^0.45.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@cerbos/orm-drizzle 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cerbos/orm-drizzle 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@cerbos/orm-drizzle 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cerbos/orm-drizzle 可以商用吗?

@cerbos/orm-drizzle 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@cerbos/orm-drizzle 的下载量有多少?

根据 npm 官方下载统计,@cerbos/orm-drizzle 在 2026/10/10 – 2026/11/08 这 30 天内被下载 4,257 次,日均约 142 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cerbos/orm-drizzle 还在维护吗?

最新版本 0.1.0 发布于 2026/02/03(9 个月前);至今共发布 1 个版本,其中过去 12 个月发布了 1 个;首次发布于 2026/02/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。