@btravstack/entity

中文文档
0.58.0发布于 2026/11/08MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 8426

Zod v4 上的域实体构建器: 有品牌的字段, 不可改变的数据, 密封的构造, 以及结果而不是抛出

中文使用文档
文档目录4 节
  1. 合计数
  2. 事件驱动的根源
  3. 文档
  4. 许可证

@btravstack/实体

一个域实体构建者 类型脚本打开 佐德语 v4——有品牌的田地,不可改变的数据,密封的构造,以及 Result 而不是抛出.

一份声明给了你一个类型, 四个衍生的计划 来构建合同从, 行为,以及本身是zod schema的类——所以实体互相筑巢 而不失去他们的力量 无所投出: 每一次都是假的 操作返回一个 unthrown Result。 。 。 .

pnpm add @btravstack/entity zod unthrown @unthrown/standard-schema

zod, (中文(简体) ), unthrown 财务报告和审定财务报表 @unthrown/standard-schema 是同伴的依赖关系。 这个 zod 范围为 ^4.3.0 ——地表被测出,不是被猜出:地表完整 4.3.0 类型核对、申报和通过运行时间断言. 这里不需要一个后来的未成年人, 和单簧管通常把一Zod钉在了过去 每一个包,所以范围保持了同真实一样宽.

// vocabulary.ts — each brand declared once, imported by every entity
import { z } from "zod";

export const OrgId = z.uuid().brand("OrgId");
export const Slug = z.string().min(1).brand("Slug");
export const Instant = z.iso.datetime().brand("Instant");

一个实体从该模块中进口其品牌; Entity.field 只添加旗帜 到一个。 自由文本字段没有什么可混淆的,所以它选择与 unbranded: true 而不是给一个没人需要的品牌做点什么.

import { z } from "zod";
import { Entity } from "@btravstack/entity";
import { Instant, OrgId, Slug } from "./vocabulary.js";

class Organization extends Entity("Organization")({
  id: Entity.field(OrgId, { generated: true, immutable: true }),
  slug: Entity.field(Slug, { immutable: true }),
  name: Entity.field(z.string().min(1), { unbranded: true }), // free text
  createdAt: Entity.field(Instant, { generated: true, immutable: true }),
}) {
  get greeting(): string {
    return `Welcome, ${this.name}`;
  }
}

// Bind the effect sources once, at your composition root.
const createOrganization = Organization.factory({
  id: () => ids.next(),
  createdAt: () => clock.now(),
});

const org = createOrganization({ slug, name }).getOrThrow();
await db.insert(org.toJSON()); // the stored shape — never `_tag`
const loaded = Organization.make(row).getOrThrow(); // rows, imports, event folds
const renamed = loaded.update({ name: next }).getOrThrow(); // a NEW entity
Schema 成员对于
input所有的东西 make() 接受
output存储状态, 内部字段包括: 通过允许列表从中选择响应
createInput域名允许创建集的内容—— input 减去 generated 字段
updateInput域允许改变什么.. input 减去 immutable 字段, 部分
类解析实例; 作为字段有效

四是构件,不是公共API. 一条路从他们那里取出 允许列表,所以一个内部域保持内部,一个新的域保持到 有人补充道: 显示 HTTP 合同。 。 。 .

generated, (中文(简体) ), immutable, (中文(简体) ), identity 财务报告和审定财务报表 unbranded 为: 旗帜 字段,写为 Entity.field(schema, flags); 一个字段里没有字段是 赤裸的图谋. computed 财务报告和审定财务报表 invariants 是两个声明选项.

一个实体是: 最终几个实体共享的领域和行为 根号, Entity.abstract(name)(fields),并在那里延长寿命 实体是您命名的值 :

abstract class AccountBase extends Entity.abstract("Account")({
  id: AccountId,
  label: DisplayName,
}) {
  abstract describe(): string; // every variant owes this — the compiler checks
}

class Personal extends AccountBase.extend("Personal")({
  kind: z.literal("personal"),
}) {
  override describe(): string {
    return `personal ${this.label}`;
  }
}

// `Business` is declared the same way, on the same root
export const Account = Entity.union("kind", [Personal, Business]);
export type Account = Entity.Instance<typeof Account>;

Account.make(row); // Result<Personal | Business, InvalidEntity>

一个变体是它根的真正例子,所以 instanceof 狭入了它,和 Account 作为类型 Personal | Business。没有类表:设置 工会在基层地位是: TS2507 在宣言中,因为 类的实例类型完全不能是联合体( c)TS2509) (中文(简体) ).

合计数

集合根是实体. 它拥有其边界内的实体,并且 每一个变化都要通过一个方法 在根上命名商业意图. 该方法检查其规则并返回一个新的根,当其他部分 系统应该作出反应, 它产生的事件旁边。 事件是可选的 : 命令中没有要宣布的单独返回实体.

class Invoice extends Entity("Invoice")({
  id: Entity.field(InvoiceId, { identity: true, immutable: true }),
  status: InvoiceStatus, // "DRAFT" | "ISSUED" | "VOID"
  total: Money,
}) {
  void(): Result<
    { readonly entity: Invoice; readonly events: readonly [InvoiceVoided] },
    InvoiceNotVoidable | Entity.InvalidEntity
  > {
    if (this.status !== "ISSUED") {
      return Err(new InvoiceNotVoidable({ invoiceId: this.id }));
    }
    return this.update({ status: "VOID" }).map((entity) => ({
      entity,
      events: [{ type: "InvoiceVoided", invoiceId: entity.id }] as const,
    }));
  }
}

const { entity, events } = invoice.void().getOrThrow();
// one transaction: the new row, and one outbox row per event

事件不应用回根;它们告诉其他模块什么 所以会计部门可以对 InvoiceVoided 没有 发票知道它的存在. update() 是公开的,所以导出命令 域模块而不是要补丁的实体。 见 型号 A 合计 财务报告和审定财务报表 写入命令和 事件。 。 。 .

事件驱动的根源

当根的状态应该改变 仅仅通过它宣布的事件, 声明一下 Entity.aggregate 换句话说 它宣布事件和一 每次处理器, 它没有 update(),每个成功的命令返回 a 秘密决定。 您仍然选择存储 : 或事件流本身.

class Subscription extends Entity.aggregate("Subscription")({
  id: Entity.field(SubscriptionId, { identity: true }), // a root needs an identity
  seats: Seats,
  status: z.enum(["ACTIVE", "CANCELLED"]),
})({
  events: SubscriptionEvent, // a zod discriminated union on `type`
  opens: {
    SubscriptionStarted: (e) => ({
      id: e.subscriptionId,
      seats: e.seats,
      status: "ACTIVE",
    }),
  },
  evolve: {
    // one handler per event, or it does not compile
    SeatsChanged: (r, e) => ({ ...r, seats: e.seats }),
    SubscriptionCancelled: (r) => ({ ...r, status: "CANCELLED" }),
  },
}) {
  changeSeats(seats: number) {
    if (this.status === "CANCELLED") return Err(new SubscriptionIsCancelled());
    return this.emit({ type: "SeatsChanged", seats }); // fold, verify once, decide
  }
}

const decision = subscription.changeSeats(5).getOrThrow();
decision.events; // every event since the load
decision.expectedVersion; // the version the store must still be at
repository.save(decision); // a state row and an outbox, or an event stream

仅 emit 财务报告和审定财务报表 start 构建一个决定, 所以一个寄存器只能被移交 事件被折叠 并检查每个变化。 装入 make(row, { version }) 或者说 replay(stream); 与 或作为事件而不触及其声明。 只有当 您想要这个约束; a Entity root 是默认值。 见 模拟事件驱动 合计。 。 。 .

文档

实体指南

许可证

麻省理工学院 贝诺伊特旅行社

版本与发布记录

@btravstack/entity 共发布 56 个版本,首次发布于 2026/08/07;过去 12 个月发布了 56 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.58.0latest2026/11/08今天
0.57.02026/11/071 天前
0.56.12026/11/062 天前
0.56.02026/11/062 天前
0.55.12026/11/035 天前
0.55.02026/11/035 天前

关于 @btravstack/entity 的常见问题

@btravstack/entity 是什么?

Zod v4 上的域实体构建器: 有品牌的字段, 不可改变的数据, 密封的构造, 以及结果而不是抛出

如何安装 @btravstack/entity?

在项目目录运行 npm install @btravstack/entity,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:zod(^4.3.0)、unthrown(^5.0.0)、@unthrown/standard-schema(^5.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@btravstack/entity 支持 TypeScript 吗?

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

@btravstack/entity 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@btravstack/entity 需要什么版本的 Node.js?

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

@btravstack/entity 可以商用吗?

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

@btravstack/entity 的下载量有多少?

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

@btravstack/entity 还在维护吗?

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