# @12-apps/rbac 中文文档

规范地址：https://npmcn.com/package/%4012-apps/rbac/

版本：4\.29\.0
npm 元数据更新时间：2026-11-03T23:35:27.854Z
中文译文更新时间：2026-11-05T19:39:04.317Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%4012-apps/rbac
原文对照：https://npmcn.com/package/%4012-apps/rbac/?lang=original
项目主页：https://github.com/12-apps/shared-packages#readme
源码仓库：https://github.com/12-apps/shared-packages

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：2.1 MB，179 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.0\.0
- 运行依赖：5 个
- peer 依赖：hono \>=4\.0\.0，react \>=19\.0\.0，react-dom \>=19\.0\.0，playwright-bdd \>=8\.3\.0，@12-apps/wiring \>=1\.3\.0，@playwright/test \>=1\.55\.0，react-router-dom \>=7\.0\.0
- 首次发布：2026-08-05T02:17:34.595Z
- 最新版本发布：2026-11-03T23:35:27.541Z
- 近 30 天下载量：33887（2026-10-04 至 2026-11-02，npm 官方下载统计）
- 已发布版本数：76（近 12 个月 76 个）

## 中文简介

通用的,可移植的角色访问控制. 无框架核心(引擎,注册,备注,模板,治理),主机挂载后端表面(./服务器:角色 CRUD,权限,团队名册,守护助手,鸭型Prisma接合器),Hono适配器(./hono)

## 安装

```sh
npm install @12-apps/rbac
```

也可以使用：`pnpm add @12-apps/rbac`、`yarn add @12-apps/rbac`、`bun add @12-apps/rbac`

## README

# @ 12- apps/ rbac

普通,便携式, **引擎不可识别** 授权。 无框架核心加 可选反应和框架中性服务器适配器. **零应用程序对接**页:1 核心对原主机——权限,角色,范围以及角色都一无所知 所有主机提供的数据.

> **在另一个项目中采纳这一点?** 此 README 是 API 的参考文献 。 见 **[`RBAC.md`](https://npmcn.com/package/%4012-apps/rbac/RBAC.md)** 用于渐进式集成游戏本—— DB 解析器、卫士、CI覆盖门、DB-自定义角色以及角色 管理面板+成员分配模式(与gotchas).

## 子路径导出

| 导入 | 目录 |
| --- | --- |
| `@12-apps/rbac` | 核心+组成`composePermissions`\+ 治理 + 本套件本身 `RBAC_PERMISSIONS` |
| `@12-apps/rbac/react` | `RbacProvider`, (中文(简体) ), `useCan`, (中文(简体) ), `<Can>` |
| `@12-apps/rbac/next` | `createRbacGuards` (框架中立服务器守卫) |

出口地图位于 `./src` 直接——在 运行时间.

## 交接的PDP后面的两个接合

所有授权通过 a **政策决定点** - 那个 `AuthzAdapter` 接口 —— 完全有两个接合。 呼叫网站的使用 `can()` 财务报告和审定财务报表 `visibleResources()` **仅限**他们永远不知道哪个引擎会回答.

```ts
interface AuthzAdapter {
  // POINT seam: may this subject do this action to this exact resource?
  check(subject, action, resource: string | null, context): boolean | Promise<boolean>;
  // LIST seam: which instances of resourceType may this subject act on?
  visible(subject, action, resourceType, context): VisibleResult | Promise<VisibleResult>;
}

type VisibleResult =
  | { kind: 'all' }                       // hold the class permission
  | { kind: 'none' }                      // denied
  | { kind: 'ids'; ids: string[] }        // exactly these instances
  | { kind: 'predicate'; predicate: unknown }; // host turns this into a WHERE clause
```

-   `check` 答题 **点** (一行,或集体诉讼 `resource` 实值 `null`) (中文(简体) ).
-   `visible` 这是 **列表** 缝合——永远不要取出当时的过滤器. 问PDP是什么 可见,然后按 `all` 编号 : `none` 编号 : `ids` 编号 : `predicate` 下到您的询问.

### 默认适配器( SQL / 在进程)

船舶内置. 这是经典角色数据引擎 加上实体门:

-   **类许可** - 仅由中非区域局决定. `visible` =: `all` 被扣押时.
-   **实例许可** - 非洲区域局大门 **接下来** 实体门: 所有权或 转让关系或说明. `visible` = 工会 `ids` 编号 : `predicate`。 。 。 .

### 在 OpenFGA (后期) 中抓取

OpenFGA 适配器执行 **不变** `AuthzAdapter`数字 : `check` + 《联邦宪章》 `Check` 皇家警察部队, `visible` → `ListObjects`通过它作为 `config.adapter` 每一个呼叫网站都维持不变.

```ts
createRbac({ ...config, adapter: myOpenFgaAdapter });
```

## 类对实例权限

声明权限 `definePermissions`。要么落后的兼容性 字符串tuple( 所有默认值为 `'class'`或一个 **对象映射** 旗帜 每个权限的范围类型 :

```ts
import { definePermissions } from '@12-apps/rbac';

const PERMISSIONS = definePermissions({
  'products:write': 'class',       // RBAC alone
  'orders:read:own': 'instance',   // RBAC gate THEN entity gate
} as const);

PERMISSIONS.kind('orders:read:own'); // 'instance'
PERMISSIONS.kind('products:write');  // 'class'
PERMISSIONS.kind('unknown');         // 'class' (default)
```

## 作为数据的作用(附有说明)

```ts
const ROLES: readonly RoleDef<AppPermission>[] = [
  { name: 'OWNER', permissions: '*' },
  { name: 'STAFF', permissions: ['products:read:all', 'orders:create'] },
  {
    name: 'JUNIOR_FINANCE',
    permissions: [
      // ABAC caveat: refunds only under a threshold in the host context
      { permission: 'orders:refund', caveat: (ctx) => Number(ctx.amount) < 100 },
    ],
  },
];
```

`'*'` 给予一切. `inherits: ['OTHER']` 工会的另一角色权限 (复取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相取相相取相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相.

## ABAC 说明+上下文

角色许可条目可能包含 `caveat: (context) => boolean`编辑 此外,阶级/戒备门还需要通过警告. `context` 实值 主机群和线程通过 `can()` 编号 : `visibleResources()` (作为最后一次) 参数). **缺少上下文否认** ——无法找到其数据的提醒 需要必须返回 `false`没有沉默的允许.

```ts
await rbac.can(userId, 'orders:refund', orderId, { scope: tenantId, amount: 50 });  // true
await rbac.can(userId, 'orders:refund', orderId, { scope: tenantId, amount: 500 }); // false
await rbac.can(userId, 'orders:refund', orderId, { scope: tenantId });              // false (no amount)
```

三个条件使设计名称船成为现成的工厂 (单位:千美元)`src/core/caveats.ts`),每个否定缺失/错误上下文:

```ts
import { amountAtMost, withinShift, notExpired, allOf } from '@12-apps/rbac';

{ permission: 'orders:refund', caveat: amountAtMost(5000) }        // comp/refund cap
{ permission: 'payments:take', caveat: withinShift() }             // ctx.onShift === true
{ permission: 'config:write',  caveat: notExpired() }              // time-boxed elevation
{ permission: 'orders:refund', caveat: allOf(amountAtMost(5000), notExpired()) }
```

`isActiveAssignment({ validFrom, validTo }, now)` 是进程内镜像 sQL 时间上游(`valid_to IS NULL OR valid_to > now()`用于 内部主机、解析器和测试.

## 范围 父行(链)

多层次的瞄准镜(org- 餐厅)通过下列方式支持: `scopeParent`补助金 (单位:千美元) **父目录** 范围满足对任何后裔的检查;子女补助金从未发放 到达父母或兄弟。 出行被循环守护.

```ts
createRbac({
  ...config,
  scopeParent: (scope) => parentOf[scope] ?? null, // return null at the root
});
// OWNER@org-1 can config:write on restaurant-1 (child)   -> true
// OWNER@restaurant-1 cannot reach org-1 (parent)          -> false
```

## 创建引擎

```ts
import { createRbac } from '@12-apps/rbac';

export const rbac = createRbac<AppPermission>({
  permissions: PERMISSIONS,
  roles: ROLES,
  resolver,                 // actorId -> RoleAssignment[]
  globalScope: 'GLOBAL',    // assignments here satisfy any scope
  scopeParent,              // optional: org -> restaurant chains
  ownership,                // optional entity gate: (subject, type) -> OwnershipPredicate | null
  assignmentResolver,       // optional entity gate: (subject, type, ctx) -> string[] (the RELATION)
  adapter,                  // optional: swap in OpenFGA etc.
});

// converged call sites:
await rbac.can(userId, 'products:write', null, { scope: tenantId });        // Promise<boolean>
await rbac.can(userId, 'orders:read:own', orderId, { scope: tenantId });    // instance point check
await rbac.visibleResources(userId, 'orders:read:own', 'orders', { scope: tenantId }); // LIST seam
await rbac.requirePermission(userId, 'orders:void', orderId, { scope: tenantId });      // throws
```

这个 **实体门** (单位:千美元)`ownership` + 键 `assignmentResolver`仅供参考 实例权限。 任务总是 **关系** (许多身份),从来没有 单列,和主机时间过滤器。 组成:实例检查 如果中非区域局批准此项行动,则通过 **财务报告和审定财务报表** (所有权持有) **或者说** 编号在 任务集 **或者说** a 备注出入证).

## 治理验证器

`validateGrant` 是一个 **纯无框架** 保护角色管理 表面(谁可以创建/指定哪个角色,哪个范围)。 这不是一个 授权检查——它决定是否是 _允许生产_。 。 。 .

```ts
import { validateGrant } from '@12-apps/rbac';

const verdict = validateGrant({
  granterPermissions: myPermissions, // the granter's ceiling ('*' allowed)
  roleBeingGranted: 'MANAGER',        // a catalog role name or an inline RoleDef
  targetScope: { isLeaf: true },      // leaf (a single site) vs org/parent
  catalog: CATALOG.governance,        // from your composed catalog (see above)
});
// { ok: true }
// | { ok: false, reason: 'ESCALATION: ...', missingPermissions: ['a:write', …] }
// | { ok: false, reason: 'SCOPE_CEILING: ...' }
```

`missingPermissions` 显示于 **`ESCALATION` 仅限** ——每个被授予者 不只是第一个发现的 它是如此的宿主 给一个人取名 没必要把它们重新分析出来 `reason`; 数据 服务器表面已经将它们附加到拒绝副本中(封顶为10个,有一个 其余部分则计算在内).

强制执行规则(稳定 `reason` 前缀 :

-   **`OWNER_PROTECTED`** ——所有者角色/所有者-标记权限/通配符角色 永远不能通过自定义的角色授予.
-   **`ESCALATION`** ——赠与人只能授予其持有的许可. 唯一一个 包含的代码 `missingPermissions`,并且是唯一一个其用户化 复制名称 ids: 集为调用者自给的权限从角色中减去 他们已经可以看见了,所以它揭示了他们无法计算的东西.
-   **`SCOPE_CEILING`** ——只允许叶子角色和实例权限 划入叶片范围,从未划入卵巢/母体范围.
-   **`SEPARATION_OF_DUTIES`** - 任何一种作用都不得同时兼收并蓄 配置了相互排斥的对(例如). `purchasing:write` + 键 `purchasing:approve`) (中文(简体) ).

## 编译目录( 主机是组装器)

货船 **无应用程序目录**。它宣布三个权限 保护自己的屏幕和终点—— `roles:manage`, (中文(简体) ), `team:read`, (中文(简体) ), `team:manage`,导出为 `RBAC_PERMISSIONS` 以主者发誓 每一个所有者的贡献.

```ts
import {
  composePermissions,
  definePermissionContribution,
  RBAC_PERMISSIONS,
  type PermissionOf,
} from '@12-apps/rbac';
import { LIFECYCLE_PERMISSIONS } from '@12-apps/entity-lifecycle';

/** Your domain declares its own ids, with everything true about each. */
export const SHOP_PERMISSIONS = definePermissionContribution({
  source: 'shop',
  permissions: {
    'products:write': { kind: 'class' },
    'orders:read:own': { kind: 'instance' },
    'payouts:manage': { kind: 'class', ownerMarker: true },
  },
  labels: { domains: { products: 'Produtos' }, actions: { write: 'Editar' } },
});

export const CATALOG = composePermissions(
  RBAC_PERMISSIONS,        // this package's own surfaces
  LIFECYCLE_PERMISSIONS,   // another package's surfaces
  SHOP_PERMISSIONS,        // your domain
).withRoles({
  roles: SHOP_ROLES,       // typed against the composed union
  ownerRoles: ['OWNER'],
  leafOnlyRoles: ['MANAGER'],
  platformOnlyRoles: ['SUPERADMIN'],
  roleLabels: { OWNER: 'Proprietário' },
});

/** The union your guards are checked against — it survives composition. */
export type ShopPermission = PermissionOf<typeof CATALOG>;
```

`CATALOG` 是一个对象 `createApiRbac` 编号 : `createWebRbac` 拿着 它携带 登记册、角色模板、治理目录、租户种子 行和合并的标签,所以一个主机不能从一个地方连接注册 和别人的治理,并使他们彼此分歧.

**一种贡献是一个连贯的单位.** 每一个ID都带着它的范围 (单位:千美元)`class` 编号 : `instance`),其所有者-标志性旗帜,分出责任 对口单位及其标签。 注册的ID没有其范围类型 `class` 并跳过实体门——即无法打开的半振荡门——所以 不是声明的形状 省略它。 TYPE说,当一个目录 被写; 组成在集合时再次说, 因为目录是 生成、 从配置中读取或从 JSON 中解析出 一次 一次吗 一个Spec 谁 `kind` 没有 `class`编号 :`instance`,其 `ownerMarker` 没有 布尔人,或 `separateFrom` 不是一个 ids 的列表 `RbacCatalogError` (单位:千美元)`INVALID_PERMISSION_SPEC`)命名id及其来源.

**碰撞声很大.** 两个来源提供同样的ID投出 `RbacCatalogError` (单位:千美元)`PERMISSION_COLLISION`)以任一顺序命名两者. 那边 不是一个最后的写出胜负: 一个默默地覆盖着一个包 `instance` 变成别人的 `class`。组成也拒绝对等软件 贡献,赋予未知身份的角色,以及指定未知角色的政策.

**一对可能跨越源.** `separateFrom` 解决整个问题 组装目录,所以批准包可以声明 `'products:approve': { separateFrom: ['products:write'] }` 不拥有 `products:write`。 。 。 .

## 服务器守卫

```ts
import { createRbacGuards } from '@12-apps/rbac/next';
import { PermissionDeniedError } from '@12-apps/rbac';

const guards = createRbacGuards(rbac, async () => (await auth())?.user?.id ?? null);

export async function refundOrder(tenantId: string) {
  try {
    await guards.requirePermission('orders:refund', tenantId);
  } catch (e) {
    if (e instanceof PermissionDeniedError) return { status: 403 };
    throw e;
  }
  // ...perform the refund
}
```

## 反应 : `useCan` 编号 : `<Can>`

供货商拿一个 **已解决** 权限设置( 解决它 ) 用于当前范围的服务器侧并传递到下方). 客户从来不碰 解析器或 DB.

```tsx
import { RbacProvider, useCan, Can } from '@12-apps/rbac/react';

const permissions = await rbac.getPermissions(userId, tenantId);

<RbacProvider permissions={permissions}>
  <Can permission="orders:refund" fallback={null}>
    <RefundButton />
  </Can>
</RbacProvider>;
```

## 移动:玩具第二主机(博客)

相同的核心赋予完全不同的域和一个 OpenFGA 适配器 可以在不触碰任何一个的情况下替换默认引擎. 不只是说: `src/__tests__/portability.test.ts` 是这个主机的可运行版本——它的 本身的目录、作用、范围链、所有权、时间审查任务和 治理政策——加上声称PACKAGE包含的内容的绊线, 这就是这里“便携式”的意思,没有检查:

```ts
import { definePermissions, createRbac, type RoleDef } from '@12-apps/rbac';

const BLOG = definePermissions({
  'posts:read': 'class',
  'posts:write:own': 'instance', // authors edit only their own drafts
  'posts:publish': 'class',
} as const);
type BlogPerm = (typeof BLOG.list)[number];

const ROLES: readonly RoleDef<BlogPerm>[] = [
  { name: 'READER', permissions: ['posts:read'] },
  { name: 'AUTHOR', permissions: ['posts:read', 'posts:write:own'] },
  { name: 'EDITOR', permissions: '*' },
];

const blogRbac = createRbac<BlogPerm>({
  permissions: BLOG,
  roles: ROLES,
  resolver: () => [{ role: 'AUTHOR', scope: 'blog-1' }],
  ownership: (subject, type) =>
    type === 'posts' ? { kind: 'predicate', test: (id) => id.startsWith(subject) } : null,
});

await blogRbac.can('u1', 'posts:read', null, { scope: 'blog-1' });        // true (class)
await blogRbac.can('u1', 'posts:write:own', 'u1-draft', { scope: 'blog-1' }); // true (owns it)
await blogRbac.can('u1', 'posts:write:own', 'u2-draft', { scope: 'blog-1' }); // false (not owner)
await blogRbac.can('u1', 'posts:publish', null, { scope: 'blog-1' });     // false (author lacks it)
```

## 常见问题

### @12-apps/rbac 是什么？

通用的,可移植的角色访问控制\. 无框架核心\(引擎,注册,备注,模板,治理\),主机挂载后端表面\(\./服务器:角色 CRUD,权限,团队名册,守护助手,鸭型Prisma接合器\),Hono适配器\(\./hono\)

### 如何安装 @12-apps/rbac？

在项目目录运行 npm install @12-apps/rbac，或使用 pnpm add / yarn add / bun add。

### @12-apps/rbac 支持 TypeScript 吗？

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

### @12-apps/rbac 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @12-apps/rbac 需要什么版本的 Node\.js？

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

### @12-apps/rbac 可以商用吗？

@12-apps/rbac 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @12-apps/rbac 的下载量有多少？

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

### @12-apps/rbac 还在维护吗？

最新版本 4\.29\.0 发布于 2026/11/04（2 天前）；至今共发布 76 个版本，其中过去 12 个月发布了 76 个；首次发布于 2026/08/05。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

