@eco-foundation/eco-message-contracts

中文文档
2.3.0发布于 2026/09/26MITTypeScriptESM + CJS零依赖近 30 天下载 2089

Message envelope, contract definitions, and payload schemas for Eco AMQP messaging

原始使用文档
文档目录7 节
  1. What a contract is
  2. The envelope
  3. Live contracts
  4. Breaking payload versions
  5. payloadDigest
  6. For non-TypeScript consumers
  7. Documentation

@eco-foundation/eco-message-contracts

The message envelope, defineContract(), and the live cross-service payload schemas for Eco's AMQP bus. Zero AMQP dependency — install this alone if you only need to define, publish-shape, or parse contracts. The transport lives in @eco-foundation/eco-messaging, which declares this package as a peer.

pnpm add @eco-foundation/eco-message-contracts

Requires zod (^3.25 || ^4) as a peer. Note the 3.25 floor: a service declaring an older range must raise the declared range even if its lockfile already resolves something newer.

What a contract is

A contract binds a payload schema to its routing, its deterministic identity, and its publication policy in one place, so those four facts cannot drift apart across the services that share the event.

import * as z from 'zod/v4';
import { defineContract } from '@eco-foundation/eco-message-contracts';

export const InventorySnapshot = defineContract({
  eventType: 'inventory.snapshot',
  routingKey: 'inventory.snapshot',
  bindingPattern: 'inventory.snapshot',
  schema: z.object({
    aggregateId: z.string().min(1),
    snapshotVersion: z.number().int().nonnegative(),
    actualBalance: z.string(), // decimal amount in base units, as a string wire type
  }),
  schemaVersion: 1,
  // Deterministic identity: the SAME logical event always resolves to the same messageId,
  // which is what makes an outbox retry safe rather than a duplicate.
  identity: (p) => `${p.aggregateId}:${p.snapshotVersion}`,
  // Optional: enforce apply-in-order-within-scope. A stale or out-of-order redelivery is
  // rejected before the handler runs, rather than being left to regress state.
  ordering: {
    field: 'snapshotVersion',
    version: (p) => p.snapshotVersion,
    scope: (p) => p.aggregateId,
  },
  delivery: 'required',
});

delivery may be static or payload-dependent, and resolvePublicationPolicy(validatedPayload) is the single admission decision derived from it. A required contract must also declare durability: 'caller-ledger' to be publishable directly — otherwise it is refused before it reaches the broker, because a persist-before-publish rule any call site can bypass silently is not a rule.

Declared field names are validated against the schema at definition time, so a typo in digestExcludes or an ordering.field that does not exist is a module-load failure rather than a silently-ineffective declaration.

Every static routing key is also exposed as routingKeyTemplate. For a payload-derived key, add an explicit template such as <chainId>.inventory.snapshot; the built-in catalog requires one for every contract and publishes it in the generated schema index for compatibility checks.

The envelope

buildEnvelope, readEnvelope, envelopeToHeaders, and the ENVELOPE_HEADERS constants are the canonical envelope surface. Every message carries a message ID, causation and correlation IDs, event type, schema version, source service, attempt count, and W3C trace context.

readEnvelope returns a discriminated result rather than throwing, so a malformed envelope is a value your consumer can park deliberately instead of an exception in a hot path.

Live contracts

Nine contracts ship with the package, exported as ALL_CONTRACTS and described by BALANCE_EVENTS_CATALOG (which records the producer and consumers of each, so the catalog answers "who breaks if I change this" without a code search):

Event typeBinding pattern
withdrawal.created*.withdrawal.created
withdrawal.resolved*.withdrawal.resolved
withdrawal.refunded*.withdrawal.refunded
gateway-deposit.created*.gateway-deposit.created
gateway-burn.mint-confirmed*.gateway-burn.mint-confirmed
gateway-burn.burn-observed*.gateway-burn.burn-observed
rebalance.snapshotrebalance.snapshot
solver.actual-balance.updatesolver.actual-balance.update
solver.settlement-risk.fulfilled-unprovensolver.settlement-risk.fulfilled-unproven

The settlement-risk contract signs an immutable observation; its versioned inner schema and identity semantics are included in the schema index and compatibility gate. Shared valid/invalid vectors are exported at @eco-foundation/eco-message-contracts/fixtures/fulfilled-unproven-v1.

Breaking payload versions

Breaking versions use real parsers, not a header allowlist. Define each historical schema with defineVersionAdapter(schema, upcast) under legacyVersions; consumers call parseVersion(envelope.schemaVersion, body) and handlers receive only the canonical current shape.

payloadDigest

payloadDigest(contract, payload) answers "does this publish carry anything a consumer can actually observe?" — for a producer whose version bump is unconditional and which would otherwise re-emit a terminal snapshot under a fresh version. Exclusions are declared per-contract via digestExcludes, apply at the root only, and arrays are never sorted (order is part of identity). Values JSON cannot faithfully represent are refused with the offending path named rather than silently mangled.

For non-TypeScript consumers

The tarball ships versioned, language-neutral JSON Schema artifacts, so a service in another language pins the published package (or vendors the files) instead of reimplementing the schemas:

  • @eco-foundation/eco-message-contracts/schema → schema/generated/index.json, the contract and envelope index with per-version schema paths.
  • @eco-foundation/eco-message-contracts/conformance → machine-readable conformance clauses, the AMQP wire mapping, and replay-verified delivery vectors.

The schemas deliberately do not reject unknown fields: a consumer must tolerate a field added by a newer producer, so validation is not a compatibility trap.

Documentation

These packages are the reference implementation of the standard, not its definition. The definition is two language-agnostic documents that a non-TypeScript service can implement against — read them before changing envelope headers, queue topology, settlement semantics, or the durable-publish rule:

Changes are tracked in CHANGELOG.md.

版本与发布记录

@eco-foundation/eco-message-contracts 共发布 8 个版本,首次发布于 2026/08/24;过去 12 个月发布了 8 个版本。

版本发布日期距今
2.3.0latest2026/09/261 个月前
2.2.12026/09/092 个月前
2.2.02026/09/092 个月前
2.1.02026/09/032 个月前
2.0.02026/09/012 个月前
1.3.02026/08/262 个月前

关于 @eco-foundation/eco-message-contracts 的常见问题

@eco-foundation/eco-message-contracts 是什么?

Message envelope, contract definitions, and payload schemas for Eco AMQP messaging

如何安装 @eco-foundation/eco-message-contracts?

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

@eco-foundation/eco-message-contracts 支持 TypeScript 吗?

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

@eco-foundation/eco-message-contracts 支持 ESM 还是 CommonJS?

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

@eco-foundation/eco-message-contracts 可以商用吗?

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

@eco-foundation/eco-message-contracts 的下载量有多少?

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

@eco-foundation/eco-message-contracts 还在维护吗?

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