# @xndrjs/forma 中文文档

规范地址：https://npmcn.com/package/%40xndrjs/forma/

版本：0\.1\.0-alpha\.2
npm 元数据更新时间：2026-11-02T22:17:44.696Z
中文译文更新时间：2026-11-02T23:00:52.169Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40xndrjs/forma
原文对照：https://npmcn.com/package/%40xndrjs/forma/?lang=original
源码仓库：https://github.com/xndrjs/toolkit

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

- 包体积（解压后）：497 kB，26 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=24 \<26
- 运行依赖：0 个
- 首次发布：2026-10-26T21:39:43.540Z
- 最新版本发布：2026-11-02T22:17:44.507Z
- 已发布版本数：4（近 12 个月 4 个）

## 中文简介

TypeScript面向意图的组合：边界、意图、提供者和封闭组合图。

## 安装

```sh
npm install @xndrjs/forma
```

也可以使用：`pnpm add @xndrjs/forma`、`yarn add @xndrjs/forma`、`bun add @xndrjs/forma`

## README

# @xndrjs /形式

TypeScript面向意图的组合：declare **边界** 和 **意图**,附加 **供应商**，用 `compose`。作曲拥有公众 **表面**惰性物化和执行上下文生命周期。

这个包是一个概念的证明。词汇存在于 [GLOSSARY.md](https://npmcn.com/package/%40xndrjs/forma/GLOSSARY.md)； 设计记录 [文档/ adr /](https://npmcn.com/package/%40xndrjs/forma/docs/adr/) (边界的适配器对等体；统一 `wiring` 登记;Intent alias / Adapter passthrough默认；表面/`using` 关键卫生;设计C执行API；执行参数/ `AbortSignal`； 有效寿命推断；流意图模式/所有权 [美国存托凭证0012](https://npmcn.com/package/%40xndrjs/forma/docs/adr/0012-streaming-intents.md)； 入口边界和表面方向 [美国存托凭证0013](https://npmcn.com/package/%40xndrjs/forma/docs/adr/0013-entry-boundaries-and-surface-direction.md)； 名义提供程序和运行时品牌 [美国存托凭证0014](https://npmcn.com/package/%40xndrjs/forma/docs/adr/0014-nominal-providers-and-runtime-branding.md)； 边界误差累加- [美国存托凭证0015](https://npmcn.com/package/%40xndrjs/forma/docs/adr/0015-boundary-error-totalization.md);可选 `defineWiring` 拔线时)。

## 安装

```bash
npm install @xndrjs/forma
```

## 快速启动

### 边界，意图，契约

```ts
import { boundary } from "@xndrjs/forma";

type OrderId = { readonly id: string };
type Order = { readonly id: string; readonly total: number };
type FindError = { readonly kind: "not_found" };

const Orders = boundary("orders");

export const FindOrder = Orders.intent("find").contract<OrderId, Order, FindError>();
export const GetOrder = Orders.intent("get").contract<OrderId, Order, FindError>();
```

那就先说名字吧 `.contract<Input, Success, Err>()` TypeScript会以这种方式保留字面意思的Intent名称。 `boundary.intent` 总是 **一元** (一个终端 `InvocationOutcome`）. 对于基于拉的序列使用 `boundary.stream` (下图)。每个边界意图退出都是累加的：实现抛出/拒绝变成 `unexpected` 在原点([美国存托凭证0015](https://npmcn.com/package/%40xndrjs/forma/docs/adr/0015-boundary-error-totalization.md)）。

### 供应商

`.with` 接受一个工厂，它返回Intent操作作为 `function` (同步或 `async`）. 更喜欢 `ctx => function (…) { … }` 在嵌套箭头上。只有在使用上下文时才解构它。 `Lifetime.onDispose` 仅在物化尝试期间有效-在工厂返回后，手柄被密封；失败时，Forma立即回滚本地清理，并且不缓存该尝试。

生成的提供程序是一个不可变的标称描述符。它的公共元数据是 `intent`， `deps`， `origin`, `lifetimePolicy`； 实现和参数推理机制仍然在内部。 使用 `compose` 执行提供程序行为——提供程序工厂不是公共调用或公共调用 猴子补丁缝。

```ts
import { expectedError, success } from "@xndrjs/forma";

const FindProvider = FindOrder.provide.with(
  (_ctx) =>
    function (id) {
      return id.id === "42"
        ? success({ id: "42", total: 100 })
        : expectedError({ kind: "not_found" as const });
    }
);

type ConnHandle = { close(): void };
async function openConnection(opts: { signal: AbortSignal }): Promise<ConnHandle> {
  opts.signal.throwIfAborted();
  return { close() {} };
}

const Conn = Orders.intent("conn").contract<void, ConnHandle, never>();
const ConnProvider = Conn.provide.perContext().with(async ({ lifetime }) => {
  const conn = await openConnection({ signal: lifetime.signal });
  lifetime.onDispose(() => conn.close());
  return function () {
    return success(conn);
  };
});

type OrderParams = { readonly role: "admin" | "viewer" | "guest" };

const GetOrderProvider = GetOrder.provide
  .perContext<OrderParams>()
  .using({ find: FindOrder })
  .with(
    ({ deps: { find }, params }) =>
      function (id) {
        if (params.role === "guest") {
          return expectedError({ kind: "not_found" as const });
        }
        return find(id); // forwards success / error / unexpected / cancelled
      }
  );
```

生存期策略是局部的，而有效生存期是从解析后的图中得到的。没有修饰语的意思 `auto`：提供商由组合根共享，除非任何传递依赖是上下文所有的，在这种情况下，它被缩小到每个实例一个 `openExecutionContext()` / `withExecutionContext()`. `.perContext()` 强制上下文所有权； `.perComposition()` pin根所有权和make `compose()` 如果传递依赖项需要上下文，则依赖项路径失败。 `root.describePlan()` 报告申报和有效期限。根与上下文 `dispose()` 是终端状态机（并发调用者共享一个承诺）；进入 `disposing` 终止该所有者的 `AbortSignal` （`lifetime.signal` / `execution.signal`）。 `root.dispose()` 还关闭任何打开的执行上下文。更喜欢 `withExecutionContext` (或 `await using` 在打开句柄上)用于短词法范围；使用 `openExecutionContext` 当主机拥有整个挂载/卸载的生命周期时。

类型的执行 **参数个数**：声明一个普通的外部对象包上 `.perContext<ParamsReq>()`. `compose({ … })` **推断** 从所有此类声明中获取所需的宿主包（合并；键类型冲突导致组合失败）。打开/用然后要求 `{ params }` (共享 `ExecutionContextArgs`）. 在运行时，Forma只接受一个 `Object.prototype`/`null`\-原型包与自己的可枚举字符串数据属性，克隆它，并浅冻结副本；嵌套的值不会被验证或冻结。执行句柄和所有逐上下文工厂接收相同的副本。工厂阅读打字 `params` 在 `.with` Context - not on `Lifetime`. `compose<Explicit>()({ … })` 只是为了显式吗 **超集** 并应用相同的“尽力而为”静态形状检查。可选主机 `signal` 在这些参数上链接到上下文信号而不转移处理所有权。

`Intent.provide.using` 只接受相同边界的意图 **只有一次的** (不要用链条 `.using`)，并且需要字符串依赖键。表面别名同样是纯字符串的； `dispose` 和 `surface` 是否保留在合成表面上映射（操作下嵌套） `execution.surface`）. 每个提供程序都有一个必需的 `origin` 判别: `{ kind: "boundary", id }` 从 `Intent.provide…with`，或者 `{ kind: "adapter", id }` 从适配器 `.adapt…with`.

### 适配器

一个 **适配器** 是Boundary的对等体，作用域为一个目标Boundary。它拥有目标意图和可能的外部实现 `using` 来自任何边界的意图。 **默认为跨边界连接** -包括契约匹配-是一个适配器（或相同边界的提供者） **透传**，而不是一个身份别名。这使原点、图边、生命周期和政策空间保持明确。

```ts
import { adapter, expectedError, success } from "@xndrjs/forma";

type PgOrderId = { readonly order_id: string };
type PgOrder = { readonly order_id: string; readonly amount_cents: number };
type PgFindError = { readonly code: "NO_ROW" };

const Postgres = boundary("postgres");
const SelectOrder = Postgres.intent("selectOrder").contract<PgOrderId, PgOrder, PgFindError>();

const OrdersAdapter = adapter("orders-postgres", Orders);

export const FindOrderViaPostgresProvider = OrdersAdapter.adapt(FindOrder)
  .using({ select: SelectOrder })
  .with(
    ({ deps: { select } }) =>
      async function (id) {
        const row = await select({ order_id: id.id });
        switch (row.kind) {
          case "success":
            return success({
              id: row.value.order_id,
              total: row.value.amount_cents / 100,
            });
          case "error":
            return expectedError({ kind: "not_found" as const });
          case "unexpected":
          case "cancelled":
            return row; // pass through unchanged — never reinterpret as expected Err
        }
      }
  );
```

`.adapt` 拒绝边界id与适配器的目标（编译时）不匹配的intent。结果是一个可私有注册的Provider `wiring`, `origin: { kind: "adapter", id: "orders-postgres" }`。适配器提供程序不能是表面根。地图只 `success` / `error`;通过 `unexpected` / `cancelled` 通过。不封装Forma依赖调用吗 `try/catch` 实现失败——依赖意图已经被累加。

当形状已经匹配时，工厂可以转发而不需要映射(`return select(input)`）. 储备 `aliasIntent(required, satisfier)` 对于故意的同义词（rename / migration）。没有 `forward` 助手。

### 撰写和执行上下文

喜欢一个 **内联** `compose({ entryBoundaries, surface, wiring })` 文字。入口边界是准确的，非空的真实的集合 `boundary()` 值，其起源由Surface暴露。每个声明的入口边界必须拥有至少一个Surface别名，每个Surface根必须来自其中一个别名，适配器根将被拒绝。运行时标记验证协议的来源，而不是安全性。这使公共图的方向和提供程序的精度保持明确——你可以这样做 **不** 需要 `defineWiring` 在正常情况下。

```ts
import { compose, type InvocationOutcome } from "@xndrjs/forma";

const SelectOrderProvider = SelectOrder.provide.with(
  (_ctx) =>
    function (id) {
      return id.order_id === "42"
        ? success({ order_id: "42", amount_cents: 10000 })
        : expectedError({ code: "NO_ROW" as const });
    }
);

const root = compose({
  entryBoundaries: [Orders],
  surface: { getOrder: GetOrderProvider },
  wiring: [SelectOrderProvider, ConnProvider, FindOrderViaPostgresProvider],
  // Optional: only automatic throw→unexpected conversions (once at origin).
  onUnexpectedFailure(event) {
    console.error(event.failure.id, event.cause, event.intent, event.phase);
  },
});

const result: InvocationOutcome<Order, FindError> = await root.withExecutionContext(
  async (surface) => {
    return surface.getOrder({ id: "42" });
  }
);

await root.dispose();
```

`defineWiring` / `defineComposition` 只有当你存在 **提取** 将（或整个选项包）连接到一个变量或模块中 `const wiring = [A, B]` 扩大到 `Provider[]` 并且进行类型检查，就好像缺少私有提供者一样。内联(或 `as const` / `defineWiring`)保持元组精确。不要将具体的配置注释为 `ComposeOptions` -扩大入口边界，表面和布线，并防止准确的来源/图形检查。拒绝在一个表面或布线槽内的提供商连接。

主机拥有的生命周期（没有回调），参数(从 `GetOrderProvider`的ParamsReq;使用 `compose<Superset>()` 只是扩大)：

```ts
const rooted = compose({
  entryBoundaries: [Orders],
  surface: { getOrder: GetOrderProvider },
  wiring: [SelectOrderProvider, ConnProvider, FindOrderViaPostgresProvider],
});

await using execution = rooted.openExecutionContext({ params: { role: "viewer" } });
await execution.surface.getOrder({ id: "42" });
```

中声明的表面别名 `compose({ entryBoundaries, surface })` 是可调用的，嵌套的 `execution.surface` （生命周期停留在手柄上）。意图标识是结构化的对 `(boundaryId, intentName)` -同一对投掷在组合时间的竞争实现。该标识的重复声明必须在每次解析时具有完全相同的Input、Success/Item、Err和模态 `using` 优势;运行时还会在撰写期间拒绝具体化的模态不匹配。 `compose` 还会构建依赖计划(`using` + Intent-alias边)，拒绝全路径诊断的循环，需要一个封闭图，并在返回根之前导出有效生存期。 `describePlan()` 包括入口边界id、表面根来源、边界/适配器来源和嵌套 `lifetime` 每个提供商的解释。私人供应商和 `aliasIntent()` 条目共用一个 `wiring` 数组;跨界布线首选适配器直通。看到 `apps/forma-demo` 有关双适配器端到端示例(`postgres` | `memory`，参数策略； `WatchOrders` 委托给私有的入口流 `WatchOrderEvents` 接缝，异步连接，推断生命周期， `await using` 根)。

### 流的意图

声明一个 **流** 合同 `boundary.stream(name).contract<Input, Item, Err>()`。相同 `.provide` / `.adapt` 建造者;工厂返回一个 `StreamOperation` （`async function*` 或任何 `AsyncIterable` 生产者)接收 `(input, invocation)` 哪里 `invocation.signal` 每个调用都是，并且从属于执行上下文信号。

```ts
type OrderEvent = { readonly orderId: string; readonly status: string };
type WatchError = { readonly kind: "unavailable" };

const WatchOrders = Orders.stream("watchOrders").contract<OrderId, OrderEvent, WatchError>();

const WatchOrdersProvider = WatchOrders.provide.with(
  () =>
    async function* (id, invocation) {
      try {
        invocation.signal.throwIfAborted();
        yield success({ orderId: id.id, status: "open" });
        yield success({ orderId: id.id, status: "paid" });
      } finally {
        // runs on natural end, break, return(), AsyncDisposable, or context/root dispose
      }
    }
);

const streamingRoot = compose({
  entryBoundaries: [Orders],
  surface: { watchOrders: WatchOrdersProvider },
});

await using execution = streamingRoot.openExecutionContext();
for await (const event of execution.surface.watchOrders({ id: "42" })) {
  if (event.kind !== "success") break; // error / unexpected / cancelled are terminal
  // …
}
```

所有权和取消(见 [streaming-intents.md](https://npmcn.com/package/%40xndrjs/forma/docs/streaming-intents.md)， [美国存托凭证0012](https://npmcn.com/package/%40xndrjs/forma/docs/adr/0012-streaming-intents.md)):

-   每个表面调用返回a **冷,single-consumer** `AsyncIterable` （+ `[Symbol.asyncDispose]`）. 创建它并不能实现提供者；第一个 `next()` 所做的。
-   每个活动流调用都是 **由执行上下文拥有** （lease + registry），独立于提供商的有效生命周期——包括来自组合有效提供商的流。
-   第一个 `next()` 在调度源之前以事务方式获取注册中心/租赁所有权。如果接受失败，则不会启动源，并且通过正常的关闭/错误矩阵回滚部分所有权。
-   `break` / `return()` / `await using` 在可迭代对象上，context `dispose`，根 `dispose` 关闭活动流（中止每个流信号→） `return()` →等待 `finally` →释放一下)。取消一个流不会中止兄弟流或上下文。
-   使用已中止的主机信号打开立即失败。稍后的宿主中止拒绝新的一元工作 `signal.reason`，一股尚未开始的冷流在没有物化的情况下完成。已接受的工作配合观察取消。宿主中止 **不** 汽车- - - - - -`dispose()` 上下文（abort≠dispose）。
-   流清理失败由发起close的调用方观察到。上下文/根处理仍然尝试每个流和提供程序清理，并聚合多个失败。
-   适配器保留模态；一元流↔流Intent别名在编译时失败。 `using` 流意图解析为 `StreamOperation`，绝不是开放迭代器。 `describePlan()` 报告 `mode: "unary" | "stream"` 在每个已注册意图上。

### 验证发布的包

从存储库根目录运行 `pnpm validate:forma-package`。该命令构建Forma，创建发布到npm的相同的tarball形状，用 `publint` 和类型是错误的，将其安装到一个孤立的消费者中，并对该消费者进行类型检查 `skipLibCheck: false`，并运行一元/流/处置烟雾测试。它永远不会解决 `@xndrjs/forma` 通过工作区源别名。

## 状态

PoC (`0.1.0-alpha.0`)：封闭的作文； `.contract`边界的适配器对等体，罕见的结构 `aliasIntent`，图形导出有效寿命，统一 `.with({ deps?, lifetime })`设计C执行API，异步工厂，流意图模式，边界错误累加(`InvocationOutcome`),处理。不是通用的IoC容器。

## 常见问题

### @xndrjs/forma 是什么？

TypeScript面向意图的组合：边界、意图、提供者和封闭组合图。

### 如何安装 @xndrjs/forma？

在项目目录运行 npm install @xndrjs/forma，或使用 pnpm add / yarn add / bun add。

### @xndrjs/forma 支持 TypeScript 吗？

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

### @xndrjs/forma 支持 ESM 还是 CommonJS？

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

### @xndrjs/forma 需要什么版本的 Node\.js？

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

### @xndrjs/forma 可以商用吗？

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

### @xndrjs/forma 还在维护吗？

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

