@amritk/asyncapi

中文文档
0.3.8发布于 2026/10/23MITTypeScriptES Module构建来源已验证近 30 天下载 5.9万

Extract JSON Schemas from AsyncAPI 2.x/3.0 documents for the mjst generators.

原始使用文档
文档目录6 节
  1. Overview
  2. Installation
  3. Usage
  4. Channel contracts
  5. API
  6. License

@amritk/asyncapi

Extract JSON Schemas — and @amritk/api channel contracts — from AsyncAPI 2.x/3.0 documents.

status  version  license  AsyncAPI  node  vibe coded


Overview

@amritk/asyncapi walks an AsyncAPI document — 2.0 through 2.6, or 3.0 — and pulls every message's payload and headers out as self-contained JSON Schema 2020-12 documents, ready for @amritk/validation, @amritk/generate-examples, or @amritk/runtime-validators. It is the extraction layer behind mjst --input asyncapi.

What "self-contained" buys: the AsyncAPI default schema dialect (a draft-07 superset) is upgraded to 2020-12 conventions, OpenAPI-format payloads get nullable folded into their type, and every $ref into the document's #/components/schemas/... is rebased into a local $defs with the referenced components copied in transitively — so each extracted schema stands alone as a generator input.

It also projects each channel onto a @amritk/api messages contract: the two directions AsyncAPI declares become clientToServer/serverToClient, each message's name becomes its wire discriminator value, and the discriminator property is stripped out of the payload — which is exactly the shape defineMessages takes. That projection is what mjst --input asyncapi --message-contracts writes to disk.

Both majors normalize into one 3.0-shaped model. Directions are named from the application's point of view (2.x publish → receive, subscribe → send), matching @amritk/api's message contracts. An Avro schemaFormat is converted, not skipped — @amritk/adapters already reads Avro, so those payloads reach the generators like any other. A payload in a language nothing here reads (Protobuf, RAML) is skipped per message with a recorded issue, and so is an Avro schema the converter rejects — one bad payload never costs the document's other messages.


Installation

npm install @amritk/asyncapi
# or
pnpm add @amritk/asyncapi
# or
yarn add @amritk/asyncapi
# or
bun add @amritk/asyncapi

Usage

import { extractAsyncApi, listMessageSchemas } from '@amritk/asyncapi'

// Parse the document yourself (JSON.parse, @amritk/yaml, ...) — this package
// takes the already-parsed value and never touches the filesystem or network.
const model = extractAsyncApi(document)

for (const issue of model.issues) {
  console.warn(`${issue.path}: ${issue.message}`)
}

for (const channel of model.channels) {
  for (const message of channel.messages) {
    // message.payload / message.headers are self-contained JSON Schema 2020-12
    console.log(channel.key, message.name, message.direction)
  }
}

// Flatten into generator inputs: one { subDir, rootTypeName, schema } per
// payload/headers, laid out as channels/<channel>/<message>[-headers].
const schemas = listMessageSchemas(model)

Channel contracts

import { buildChannelContract } from '@amritk/asyncapi'
import { defineMessages } from '@amritk/api'

for (const channel of model.channels) {
  const contract = buildChannelContract(channel)
  for (const issue of contract.issues) console.warn(`${issue.path}: ${issue.message}`)

  // The two maps are keyed by the value a frame carries on the wire, payloads
  // already stripped of the tag — hand them straight to defineMessages.
  const messages = defineMessages({
    discriminator: contract.discriminator,
    clientToServer: contract.clientToServer,
    serverToClient: contract.serverToClient,
  })
}

The discriminator is resolved in priority order: x-mjst: { discriminator } on the channel, then the discriminator option, then 'type' (matching @amritk/api's default). The document wins over the option deliberately — one option covers a whole run, and a run may span channels that disagree.

The key is the tag, not the name. A payload usually states its own tag — type: { const: 'bot_added' } is how a channel of alternatives says which message is which — and that value becomes the contract key, because it is what actually arrives on the wire. The AsyncAPI message name is only the fallback for a payload that pins nothing; it is a document-authoring handle, and 2.x messages inside a oneOf frequently have none at all. Slack's RTM document names a message botAdded and tags it bot_added; keying on the name would emit a contract listening for a frame that never comes.

Two messages that pin the same tag in the same direction are one frame shape with two descriptions, so the first wins and the second is reported (Slack declares two messages for its single bot_added event). A payload that constrains the tag without pinning it to one string — type: { type: 'string' }, a multi-member enum, a non-string const — names no message the runtime could select, and is skipped with an issue.

Headers are not part of a contract. listMessageSchemas emits a message's headers schema as its own generatable tree, but buildChannelContract projects payloads only: @amritk/api message contracts describe WebSocket frames, which carry no headers of their own. For a Kafka or MQTT document the headers types are still generated (under <message>-headers/) — they are simply yours to apply at the broker boundary, not something the socket runtime validates.

Cross-file and remote $refs are the loader's job: resolve them first (for example with @amritk/resolve-refs); a still-unresolved external reference is reported as an issue, never fetched.


API

  • extractAsyncApi(document, options?) → AsyncApiModel — the normalized document: version, major, title?, channels (each with key, address?, messages), and collected issues. Throws only when the input is not an AsyncAPI document at all. options.avroEncoding picks which JSON shape an Avro payload describes: 'json' (default) is the decoded object an application works with, 'avro-json' is the spec's JSON encoding as it travels on the wire.
  • listMessageSchemas(model, issues?) → ExtractedSchema[] — one { subDir, rootTypeName, schema } per generatable payload/headers, with deterministic collision-suffixed directory tokens; collision issues are appended to model.issues (or to the issues array you pass).
  • buildChannelContract(channel, options?) → ChannelContract — one channel as { exportName, discriminator, clientToServer, serverToClient, issues }, ready for defineMessages.
  • resolveDiscriminator(channel, override?) / DEFAULT_DISCRIMINATOR — the priority order above, and the 'type' fallback.
  • stripDiscriminator(payload, discriminator) → { schema, tag? } or { issue } — the payload with its tag removed plus the value it pinned the tag to, or why neither could be read.
  • sanitizeToken(value, fallback) — the filesystem- and import-safe token both output layouts name a channel or message with.
  • detectAsyncApiVersion(document) — the asyncapi version and its major, or undefined.
  • classifySchemaFormat(schemaFormat) — which schema language a schemaFormat names ('asyncapi' | 'draft-07' | '2020-12' | 'openapi' | 'avro'), or 'unsupported'.
  • mergeTraits(target, traits, precedence) — trait application as an RFC 7386 JSON Merge Patch (recursive, so nested contributions from both sides survive); precedence is 'trait' for 2.x (traits override the target) or 'target' for 3.0 (the target wins). Applied before schemaFormat is read.

License

MIT

版本与发布记录

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

版本发布日期距今
0.3.8latest2026/10/2314 天前
0.3.72026/10/1027 天前
0.3.62026/09/261 个月前
0.3.52026/09/241 个月前
0.3.42026/09/231 个月前
0.3.32026/09/221 个月前

关于 @amritk/asyncapi 的常见问题

@amritk/asyncapi 是什么?

Extract JSON Schemas from AsyncAPI 2.x/3.0 documents for the mjst generators.

如何安装 @amritk/asyncapi?

在项目目录运行 npm install @amritk/asyncapi,或使用 pnpm add / yarn add / bun add。

@amritk/asyncapi 支持 TypeScript 吗?

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

@amritk/asyncapi 支持 ESM 还是 CommonJS?

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

@amritk/asyncapi 需要什么版本的 Node.js?

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

@amritk/asyncapi 可以商用吗?

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

@amritk/asyncapi 的下载量有多少?

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

@amritk/asyncapi 还在维护吗?

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