@btravstack/amqp-worker

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

The AMQP starter for @btravstack/core: the handlers as a port, one unit per delivery, and a drain the kernel's deadline releases

原始使用文档
文档目录5 节
  1. A worked example
  2. Options
  3. What it decides, and what it does not
  4. License
  5. Broadcast subscriptions

@btravstack/amqp-worker

The consuming half of AMQP for @btravstack/core — publishing belongs to @amqp-contract/client, which the outbox publisher in examples/order-amqp-worker uses directly. An amqp-contract worker under the kernel's lifecycle — one unit per delivery, and a drain with exactly one deadline, the kernel's own.

📖 Documentation · Reference · API Reference

pnpm add @btravstack/amqp-worker @btravstack/core @btravstack/config @btravstack/di unthrown \
  @amqp-contract/worker@^3.0.0-beta.12 @opentelemetry/api

All six are peer dependencies — install them (@opentelemetry/api because @amqp-contract/worker itself peers on it). Node >=22.22.

A worked example

import { Env } from "@btravstack/config";
import { AmqpHandlers, AmqpModule } from "@btravstack/amqp-worker";
import { runMain, Logger } from "@btravstack/core";
import { ErrAsync, OkAsync, P } from "unthrown";

// The handlers, as a service: one entry per consumer the contract declares,
// built by di from the use cases it lists — no injected context — on the
// starter's own handlers port, typed by the contract (a consumer serves one
// handlers record, so there is nothing to name).
const orderHandlers = AmqpHandlers(orderContract)({
  inject: { placeOrder: PlaceOrder },
  sync: ({ placeOrder }) => ({
    orderNotifications: ({ input: message }) =>
      placeOrder
        .execute(
          TenantId(message.payload.tenantId),
          message.payload.id,
          message.payload.payload?.quantity ?? 0,
        )
        .map(() => undefined)
        // A modeled domain error is permanent: dead-letter it, no retry.
        .mapErrCases((matcher) =>
          matcher.with(
            P.tag("InvalidQuantity"),
            P.tag("InvalidOrderId"),
            P.tag("DuplicateOrder"),
            (error) => new NonRetryableError(error._tag, error),
          ),
        )
        // Infrastructure failing is not: recover the defect into a retry.
        .recoverDefect((cause) =>
          ErrAsync(new RetryableError("placing the order failed", cause)),
        ),
    // Every consumer the contract declares must be covered — an uncovered
    // key is refused at this call, not on the first delivery.
    orderAudit: () => OkAsync(undefined),
  }),
});

const Worker = AmqpModule("Worker")({
  needs: [Env],
  contract: orderContract,
  handlers: orderHandlers,
  imports: [AppModule],
});

await runMain(Worker);

AmqpModule imports the starter — AmqpRuntime, AmqpConfig bound from AMQP_URL (default amqp://127.0.0.1:5672) — provides the handlers and exports the runtime port; the handlers are checked against the contract at the call, not on the first delivery. runtimeInfo() reads { queues } back once consuming.

A worker with several consumers can be several slices instead of one record: AmqpHandler(contract, key)({ inject: { name: Dep }, unit, sync }) mints a provider for ONE consumer or rpc, typed by the key alone, and AmqpHandlers(contract)([...]) composes an array of them into the same handlers provider AmqpModule takes — the array must cover every key the contract declares, and each piece's own port must still be discharged (provides), since the composed provider's deps are the pieces' ports, not what they close over. A consumer belongs to exactly one slice, and the compiler holds only half of that: coverage is checked, injectivity is not, so two slices claiming one key type-check together. It is di's duplicate-provider defect at build once both pieces are discharged as providers in one graph — wire in only one and the other's handler is silently never registered.

A piece's unit names the ports its handler reads off context.unit, resolved out of the per-delivery fork of the module bound on unit.message — which is seeded with the validated message on AmqpMessage(contract), so a unit module derives a tenant from the delivery rather than from an ambient record. The record arm above takes the same unit: — one record for every entry in it — so a worker that has not outgrown one function reaches context.unit without slicing first. The root is checked against what was declared either way: bind a module that does not export one of those ports and AmqpModule refuses it, naming the port.

Its own test suite needs a Docker daemon: the specs run against the shared rabbitmq container internal/test-infra starts once per machine and every workspace reuses. Nothing else here does.

Options

AmqpModule(name)({...}) takes the starter's options plus handlers and the module lists (imports, provides, exports, needs). It is the only way to compose the starter, which is not exported:

OptionWhat it is
contractthe amqp-contract contract; the queues consumed are read off it
handlersthe handlers provider — what AmqpHandlers(contract)(...) returns for this contract
urlpins AMQP_URL (default amqp://127.0.0.1:5672) — a test's container
connectionOptionsAmqpConnectionOptions, the library's own connection tuning: heartbeat, reconnect interval, findServers, TLS
defaultConsumerOptionsthe library's ConsumerOptions, applied to every handler: prefetch (the throughput knob — unset is 10, "unbounded" is no cap), priority, …
connectTimeoutMspins AMQP_CONNECT_TIMEOUT_MS — how long startup waits before an unreachable broker is a RuntimeStartFailed (default 5000)
unit{ message } — the module the worker forks per delivery, seeded with the message on AmqpMessage(contract)

The full table — required/optional, defaults, and the reasoning — lives on the reference page, which is this list's one detailed home.

What it decides, and what it does not

Result → ack / retry / dead-letter is a three-way split and none of it is this package's: a RetryableError / NonRetryableError is routed by amqp-contract's own dispatch against the queue's retry config, and a Defect is nacked once, straight to the dead-letter queue, never touching that budget — which is why the handler above recovers its own defects into a RetryableError. The drain calls worker.close({ drainTimeoutMs: null }) raced against the kernel's deadline: the library is told to wait forever, so there is no second timeout in the process to keep in step. Each delivery is one unit, traceId the publisher's messageId. The rest is on the documentation site.

AmqpModule automatically exports publicly available Logger and HealthChecks ports, including whole-module re-exports and providers declared at the root. It keeps private infrastructure hidden and business exports explicit.

License

MIT © Benoit TRAVERS

Broadcast subscriptions

amqpSubscription(name, { contract, settings, inject?, sync }) consumes best-effort facts beside an HTTP runtime, with one exclusive ephemeral queue per replica. It reports failures and broker health and closes with the scope. InvalidAmqpSubscription names a refused configuration; SubscriptionHandlers<C, E> types the contract's handler record. See the subscription reference for the typed contract, settings port and delivery guarantees.

版本与发布记录

@btravstack/amqp-worker 共发布 62 个版本,首次发布于 2026/08/26;过去 12 个月发布了 62 个版本。最新版本附带 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/amqp-worker 的常见问题

@btravstack/amqp-worker 是什么?

The AMQP starter for @btravstack/core: the handlers as a port, one unit per delivery, and a drain the kernel's deadline releases

如何安装 @btravstack/amqp-worker?

在项目目录运行 npm install @btravstack/amqp-worker,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:unthrown(^5.11.0)、@btravstack/di(^0.58.0)、@btravstack/core(^0.58.0)、@btravstack/config(^0.58.0)、@opentelemetry/api(^1)、@amqp-contract/worker(^3.0.0-beta.12)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@btravstack/amqp-worker 支持 TypeScript 吗?

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

@btravstack/amqp-worker 支持 ESM 还是 CommonJS?

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

@btravstack/amqp-worker 需要什么版本的 Node.js?

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

@btravstack/amqp-worker 可以商用吗?

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

@btravstack/amqp-worker 的下载量有多少?

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

@btravstack/amqp-worker 还在维护吗?

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