# @ddd-ts-kit/openfga 中文文档

规范地址：https://npmcn.com/package/%40ddd-ts-kit/openfga/

版本：0\.106\.0
npm 元数据更新时间：2026-10-31T21:43:44.086Z
中文译文更新时间：2026-10-31T23:43:16.439Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40ddd-ts-kit/openfga
原文对照：https://npmcn.com/package/%40ddd-ts-kit/openfga/?lang=original
项目主页：https://gbourgeat.github.io/ddd-ts-kit/packages/openfga/
源码仓库：https://github.com/gbourgeat/ddd-ts-kit

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

- 包体积（解压后）：95.3 kB，68 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.12\.0
- 运行依赖：2 个
- peer 依赖：@openfga/sdk ^0\.9\.7
- 首次发布：2026-10-13T06:16:51.021Z
- 最新版本发布：2026-10-31T21:43:43.789Z
- 已发布版本数：55（近 12 个月 55 个）

## 中文简介

OpenFGA上的关系元组存储和@ddd-ts-kit/auth的授权器，以及从关系模式生成的授权模型。

## 安装

```sh
npm install @ddd-ts-kit/openfga
```

也可以使用：`pnpm add @ddd-ts-kit/openfga`、`yarn add @ddd-ts-kit/openfga`、`bun add @ddd-ts-kit/openfga`

## README

**英语** · [法语](https://npmcn.com/package/%40ddd-ts-kit/openfga/README.fr.md)

# @ddd-ts-kit / openfga

的关系 [`@ddd-ts-kit/auth`](https://npmcn.com/package/%40ddd-ts-kit/auth/README.md) 在 [OpenFGA](https://openfga.dev/)， CNCF在桑给巴尔的实施 `RelationTupleStore` 和 `Authorizer` 港口回答 由OpenFGA服务器提供，其授权模型由关系模式生成 项目已声明 `defineSchema`． 真理的一个来源：图式；模型是 从它派生并根据服务器持有的内容进行检查。

```bash
npm install @ddd-ts-kit/openfga @openfga/sdk
```

`@openfga/sdk` （0.9.7，官方客户端）是一个可选的对等体，由 `/sdk` 条目 只有。主条目接受任何合适的客户端 `OpenFgaApi`，官方的子集 客户机适配器调用。

![The three jobs of the package in an application: the use cases ask the Authorizer port, which OpenFgaAuthorizer implements with Check and ListObjects; a context adapter writes tuples after the commit through the RelationTupleStore port, which OpenFgaRelationTupleStore implements with Write and Read; the relationship schema is the one source of the model, which authorizationModelOf generates and ensureAuthorizationModel or modelDrift write or compare on the server](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/openfga-place-in-an-application.svg)

该包执行三个任务，每个任务针对OpenFGA服务器的一个存储：它决定 （`OpenFgaAuthorizer`)，它保存元组(`OpenFgaRelationTupleStore`)，并保留模型 等于架构(`authorizationModelOf`， `ensureAuthorizationModel`， `modelDrift`）. 处理程序永远不会导入它：它们依赖于的端口 `@ddd-ts-kit/auth`，和 平台将这些适配器绑定到它们，就像它绑定的那样 `RelationBasedAuthorizer` 一个元组 存储在您自己的数据库中。

## 模型，来自模式

模式是唯一的 `RelationBasedAuthorizer` 过程中评估：类型、关系a 元组可以包含，以及由它们派生的关系，其中以a命名的关系 许可。 `authorizationModelOf` 把它变成 的身体 `WriteAuthorizationModel` (schema 1.1)。这是一个简化的图式，本着 示例应用程序之一：

```ts
import { definePermissions } from '@ddd-ts-kit/auth';
import { defineSchema, via } from '@ddd-ts-kit/auth/rebac';
import { authorizationModelOf } from '@ddd-ts-kit/openfga';

export const schedulingPermissions = definePermissions('scheduling', {
  'appointment.cancel': { description: 'Cancel an appointment' },
});

// Who is what to which element: a practice has managers, a calendar its holder and assistants.
export const relationshipSchema = defineSchema({
  user: {},
  practice: { relations: { manager: ['user'] }, derived: { manage: 'manager' } },
  calendar: {
    relations: { holder: ['user'], assistant: ['user'], practice: ['practice'] },
    derived: { run: { anyOf: ['holder', 'assistant', via('practice', 'manage')] } },
  },
  appointment: {
    relations: { patient: ['user'], calendar: ['calendar'] },
    // To cancel an appointment is to hold `change` on it: the derived relation named after the permission grants it.
    derived: { change: { anyOf: ['patient', via('calendar', 'run')] }, [schedulingPermissions['appointment.cancel'].id]: 'change' },
  },
});

// { schema_version: '1.1', type_definitions: [user, practice, calendar, appointment] }
export const model = authorizationModelOf(relationshipSchema);
```

生成的模型决定如下，用OpenFGA建模语言编写 只读：包写入JSON表单，没有人手动编写此文本)：

```text
model
  schema 1.1

type user

type practice
  relations
    define manager: [user]
    define manage: manager

type calendar
  relations
    define holder: [user]
    define assistant: [user]
    define practice: [practice]
    define run: holder or assistant or manage from practice

type appointment
  relations
    define patient: [user]
    define calendar: [calendar]
    define change: patient or run from calendar
    define scheduling_appointment_cancel: change
```

以权限id命名的派生关系不能保留其名称：OpenFGA拒绝 `:` 在一个 关系的名字。 `relationNameOf` 写 `scheduling:appointment.cancel` 作为 `scheduling_appointment_cancel`，在模型中也是如此 `Check` 和 `ListObjects`;否 权限id持有 `_`所以两个永远不会成为一个名字。

| 模式的 `@ddd-ts-kit/auth/rebac` | OpenFGA |
| --- | --- |
| 关系：关系及其所列的主题： `editor: ['user', 'group#member']` | 直接可分配关系(`this`), `directly_related_user_types` |
| 诸道中的名字： `edit: 'owner'`， `anyOf: ['owner', 'editor']` | 计算用户集 |
| `anyOf`， `allOf` | `union`， `intersection` |
| `butNot` | `difference` |
| `via('parent', 'edit')` | 一个元组到用户集， `edit from parent` |
| 一个名为 `docs:document.edit` | 的关系 `docs_document_edit` |

`ensureAuthorizationModel(client, schema)` 对象中最新的对象写入生成的模型 商店不同，并回答有效的id和差异（OpenFGA保留每个模型；检查） 使用最新的，除非客户端固定一个)。在部署或启动应用程序的地方运行它：

```ts
import type { Schema } from '@ddd-ts-kit/auth/rebac';
import { ensureAuthorizationModel } from '@ddd-ts-kit/openfga';
import { openFgaClient } from '@ddd-ts-kit/openfga/sdk';

declare const relationshipSchema: Schema;
declare const config: { readonly openfga: { readonly url: string; readonly storeId: string } };

const client = openFgaClient({ apiUrl: config.openfga.url, storeId: config.openfga.storeId });

// The first run writes the model; the next ones write nothing until the schema changes.
const { modelId, changes } = await ensureAuthorizationModel(client, relationshipSchema);

console.log(`authorization model ${modelId}`, changes);
```

`modelDrift(authorizationModelOf(schema), served)` 只比较：每个类型或一行 `type#relation` 思念，多了还是变了，空的时候两个人决定一样。管道步骤如下 店里最新的模型在任何一条线上都失败了，所以模型从来没有手工编辑过 模式:

```ts
import type { Schema } from '@ddd-ts-kit/auth/rebac';
import { authorizationModelOf, modelDrift, type OpenFgaApi } from '@ddd-ts-kit/openfga';

declare const client: OpenFgaApi;
declare const relationshipSchema: Schema;

const { authorization_model: served } = await client.readLatestAuthorizationModel();
const drift = modelDrift(authorizationModelOf(relationshipSchema), served);

if (drift.length > 0) {
  console.error(drift.join('\n'));
  process.exitCode = 1;
}
```

运行在一个store上，它仍然保存着一个没有助手的模式的模型，它打印：

```text
calendar#assistant is in the schema, not in the store: this [user]
calendar#run differs: the schema says union(holder, assistant, from(practice, manage)) [], the store union(holder, from(practice, manage)) []
```

## 商店和授权者

在组合根中，一个绑定到一个存储的客户端为两个适配器提供服务。两人都读了 Schema：为用户集提供主题的存储，其关系为(`group:eng` 写 `group:eng#member`)，授权人请求许可作为以其命名的派生关系； 作为 `RelationBasedAuthorizer` :

```ts
import type { Schema } from '@ddd-ts-kit/auth/rebac';
import { OpenFgaAuthorizer, OpenFgaRelationTupleStore } from '@ddd-ts-kit/openfga';
import { openFgaClient } from '@ddd-ts-kit/openfga/sdk';

declare const relationshipSchema: Schema; // the one of the block above, with scheduling:appointment.cancel derived on an appointment
declare const config: { readonly openfga: { readonly url: string; readonly storeId: string } };

const client = openFgaClient({ apiUrl: config.openfga.url, storeId: config.openfga.storeId });

// The same two ports as the in-process authorizer and a tuple store in your database.
export const tuples = new OpenFgaRelationTupleStore(client, relationshipSchema);
export const authorizer = new OpenFgaAuthorizer({ client, schema: relationshipSchema, listObjectsLimit: 1000 });
```

-   `OpenFgaRelationTupleStore` 通过 `relationTupleStoreContract`，就像PostgreSQL存储一样： 两次写入元组并删除不存在的元组不会改变任何东西（冲突选项） （OpenFGA 1.10及更新版本），写入和删除每100次，读取每一个页面。的元组 这本书只给它的主题命名；存储写入模式给它的用户集 （`group:eng#member`)，然后自己再读一遍。按主题读取是按关系读取一次 模式接受主题的类型，因为OpenFGA在一个对象内按用户读取 类型。
-   `OpenFgaAuthorizer` 通过 `authorizerContract`的模式和拒绝代码 `RelationBasedAuthorizer` （`ANONYMOUS`， `RESOURCE_REQUIRED`， `UNMAPPED_PERMISSION`， `NO_RELATION`): `Check` 决定, `ListObjects` 给出搜索的范围(`{ kind: 'ids' }`）。 项目通过绑定在两者之间移动，其他的都不会改变。

![Left, check: an anonymous actor is denied ANONYMOUS, a check without a resource RESOURCE\_REQUIRED, an unmapped permission UNMAPPED\_PERMISSION, all before any call; otherwise Check on the server answers allowed or NO\_RELATION. Right, listAccessible: anonymous or unmapped gives the scope none; otherwise ListObjects, refused with ReverseExpansionLimitError when the answer reaches listObjectsLimit, else the scope ids](https://raw.githubusercontent.com/gbourgeat/ddd-ts-kit/main/docs/assets/diagrams/openfga-authorizer-decisions.svg)

的元组，授权中间件和搜索处理程序接收到什么 示例(`ada` 协助格雷斯博士的日程表，其中有预约；

```ts
import type { Permission } from '@ddd-ts-kit/auth';
import type { Actor } from '@ddd-ts-kit/kernel';
import type { OpenFgaAuthorizer, OpenFgaRelationTupleStore } from '@ddd-ts-kit/openfga';

declare const tuples: OpenFgaRelationTupleStore;
declare const authorizer: OpenFgaAuthorizer;
declare const cancel: Permission;

await tuples.write([
  { object: 'calendar:dr-grace', relation: 'assistant', subject: 'user:ada' },
  { object: 'appointment:42', relation: 'calendar', subject: 'calendar:dr-grace' },
]);

const ada: Actor = { id: 'ada', type: 'user', roles: [], attributes: {} };

// Check user:ada scheduling_appointment_cancel appointment:42: { allowed: true }
export const decision = await authorizer.check({
  actor: ada,
  permission: cancel,
  resource: { type: 'appointment', id: '42' },
});

// ListObjects user:ada scheduling_appointment_cancel appointment: { kind: 'ids', ids: ['42'] }, the ids the search may read
export const scope = await authorizer.listAccessible({ actor: ada, permission: cancel, resourceType: 'appointment' });
```

**的极限 `ListObjects`.** 服务器最多应答 `OPENFGA_LIST_OBJECTS_MAX_RESULTS` 对象（默认为1000），并在 `OPENFGA_LIST_OBJECTS_DEADLINE` （默认3秒）， 在这两种情况下都返回到目前为止找到的对象 **没有错误**． 搜索范围缩小了 删减列表会在沉默中丢失元素，所以 `OpenFgaAuthorizer` 拒绝回答，只要 `listObjectsLimit` （默认1000：将其设置为服务器的值） `ReverseExpansionLimitError`，因为进程内授权器超出了自己的限制。删减清单 截止日期不能从一个完整的答案中分辨出来：给服务器一个截止日期 在您所期望的最慢列表之上，并观察其延迟。当名单那么长， 首先缩小范围（可访问id的投影，如诊所等较粗糙的母体） 而不是提高限额。

**写操作是对服务的调用。** 它不加入工作单元的事务 聚合可以在没有元组的情况下提交，或者为其事务的聚合编写元组 回滚。在提交之后写元组，不要在命令的事务中写。 并决定当聚合存在时，请求在短窗口内看到什么 关系:

```ts
// Instead of: a handler of the default phase (before-commit) runs inside the transaction,
// so a rollback after it leaves on the server a tuple for an appointment that does not exist.
@DomainEventHandler(AppointmentBooked)
export class RelationshipsOnAppointmentBooked { /* ... */ }
```

```ts
import { NamedDomainEvent } from '@ddd-ts-kit/kernel';
import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';

export class AppointmentBooked extends NamedDomainEvent('scheduling.appointment.booked') {
  constructor(
    readonly aggregateId: string,
    readonly occurredAt: Date,
    readonly booking: { readonly calendarId: string; readonly patientId: string },
  ) {
    super();
  }
}

// The port of the context, in its own words; its adapter writes the tuples to OpenFgaRelationTupleStore.
export abstract class SchedulingRelationships {
  abstract recordAppointment(appointment: { appointmentId: string; calendarId: string; patientId: string }): Promise<void>;
}

// Write: after the commit, never when the transaction rolled back.
@DomainEventHandler(AppointmentBooked, { phase: 'after-commit' })
export class RelationshipsOnAppointmentBooked implements DomainEventHandler<AppointmentBooked> {
  constructor(private readonly relationships: SchedulingRelationships) {}

  async handle(event: AppointmentBooked): Promise<void> {
    await this.relationships.recordAppointment({ appointmentId: event.aggregateId, ...event.booking });
  }
}
```

相位处理程序 `after-commit` 运行 **最多一次**提交和运行之间的崩溃； 或者服务器没有响应，丢失元组，并且只报告其失败。一个元组 不能通过发件箱丢失：发布带有更改的集成事件，以及 从它的消费者写入元组，它至少被传递一次，然后重试，直到它被传递 成功。商店是幂等的，所以重复送货不会改变什么。

## 测试

该包的规范针对元组的内存双精度运行两个契约套件 端点，并针对一个真实的服务器时 `DDD_TS_KIT_OPENFGA_URL` 集成工作是什么 存储库启动 `openfga/openfga` V1.22.0，由摘要固定。每个项目都有自己的特色 适配器到相同的套件，每个测试在它自己的存储(`createStore`,然后 `ensureAuthorizationModel`）. 存储契约写入的元组是关于文档和组的 （`document:1#editor@group:eng`，由服务器持有为 `group:eng#member`, `document:1#parent@group:eng`)：它运行的存储保存了描述它们的模型。

```ts
import { OpenFgaClient } from '@openfga/sdk';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { defineSchema } from '@ddd-ts-kit/auth/rebac';
import { relationTupleStoreContract } from '@ddd-ts-kit/auth/testing';
import { ensureAuthorizationModel, OpenFgaRelationTupleStore, type OpenFgaApi } from '@ddd-ts-kit/openfga';
import { openFgaClient } from '@ddd-ts-kit/openfga/sdk';

const OPENFGA_URL = process.env['OPENFGA_TEST_URL'] ?? '';

// The types and relations of the tuples the contract writes.
const contractSchema = defineSchema({
  user: {},
  group: { relations: { member: ['user', 'group#member'] } },
  document: {
    relations: { editor: ['user', 'group#member'], viewer: ['user', 'group#member'], parent: ['group'] },
  },
});

let stores = 0;

// A store of its own per test, holding the model of the schema: no test sees the tuples of another.
async function freshStore(): Promise<OpenFgaApi> {
  const admin = new OpenFgaClient({ apiUrl: OPENFGA_URL });
  const { id: storeId } = await admin.createStore({ name: `contract-${process.pid}-${(stores += 1)}` });
  const client = openFgaClient({ apiUrl: OPENFGA_URL, storeId });

  await ensureAuthorizationModel(client, contractSchema);

  return client;
}

// Skipped without a server, as the specs of the package are.
relationTupleStoreContract(
  { describe: describe.runIf(OPENFGA_URL !== ''), it, expect, beforeEach, afterEach },
  {
    name: 'OpenFgaRelationTupleStore on an OpenFGA server',
    create: async () => new OpenFgaRelationTupleStore(await freshStore(), contractSchema),
  },
);
```

## 限制

-   不携带一致性令牌：检查使用服务器的默认优先级 （`MINIMIZE_LATENCY`)，因此，刚写入的元组可能不会被每个副本同时看到。
-   没有条件元组（OpenFGA条件）：工具包的模式也没有任何警告。
-   演员的主题是 `<type>:<id>`;持有OpenFGA拒绝的字符的id 对象id需求 `subjectOf`.
-   `explain` 的 `RelationBasedAuthorizer` 这里没有对应的词：用the `Expand` OpenFGA的API 用于调试，其中以权限命名的派生关系具有该名称 `relationNameOf` 给它(`docs_document_edit`）。

## 看到也

-   [授权引擎](https://npmcn.com/package/docs/tools/authorization-engines.md)： PostgreSQL元组存储 或OpenFGA，以及何时选择哪一个
-   [选择访问控制](https://npmcn.com/package/docs/guides/choosing-access-control.md)
-   [领域和集成事件](https://npmcn.com/package/docs/concepts/domain-and-integration-events.md): 域事件处理程序的阶段，以及至少一次到达另一个进程的内容

## 常见问题

### @ddd-ts-kit/openfga 是什么？

OpenFGA上的关系元组存储和@ddd-ts-kit/auth的授权器，以及从关系模式生成的授权模型。

### 如何安装 @ddd-ts-kit/openfga？

在项目目录运行 npm install @ddd-ts-kit/openfga，或使用 pnpm add / yarn add / bun add。

### @ddd-ts-kit/openfga 支持 TypeScript 吗？

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

### @ddd-ts-kit/openfga 支持 ESM 还是 CommonJS？

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

### @ddd-ts-kit/openfga 需要什么版本的 Node\.js？

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

### @ddd-ts-kit/openfga 可以商用吗？

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

### @ddd-ts-kit/openfga 还在维护吗？

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

