ワンクリックで
herald-internals
How to develop, maintain, and extend the @warlock.js/herald package — architecture, conventions, and driver authoring guide.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
How to develop, maintain, and extend the @warlock.js/herald package — architecture, conventions, and driver authoring guide.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | herald-internals |
| description | How to develop, maintain, and extend the @warlock.js/herald package — architecture, conventions, and driver authoring guide. |
This skill is for agents modifying the Herald package itself (fixing bugs, adding drivers, refactoring internals). For using Herald in an application, see the project-level herald-usage skill instead.
@warlock.js/herald/src/
├── index.ts # Public API — re-exports everything
├── types/ # Shared TypeScript types (split by domain)
│ ├── index.ts # Re-exports all types (backward-compat barrel)
│ ├── message.types.ts # Message, MessageMetadata, MessageContext, MessageHandler, Subscription
│ ├── publish.types.ts # PublishOptions, RequestOptions
│ ├── subscribe.types.ts # RetryOptions, DeadLetterOptions, SubscribeOptions
│ ├── channel.types.ts # ChannelOptions, ChannelStats
│ ├── driver.types.ts # BrokerDriverType, BrokerEvent, BrokerEventListener, HealthCheckResult
│ ├── connection.types.ts # RabbitMQ/Kafka connection options, BrokerConfigurations
│ └── registry.types.ts # BrokerRegistryEvent, BrokerRegistryListener
├── contracts/
│ ├── channel.contract.ts # ChannelContract<TPayload> — all channel drivers implement this
│ ├── broker-driver.contract.ts # BrokerDriverContract — all drivers implement this
│ └── index.ts
├── communicators/ # NOTE: directory still named "communicators" (legacy)
│ ├── broker.ts # Broker class (wraps driver + name + isDefault)
│ ├── broker-registry.ts # BrokerRegistry singleton + MissingBrokerError
│ └── index.ts
├── drivers/
│ └── rabbitmq/
│ ├── rabbitmq-driver.ts # RabbitMQDriver implements BrokerDriverContract
│ ├── rabbitmq-channel.ts # RabbitMQChannel implements ChannelContract
│ └── index.ts
├── message-managers/
│ ├── event-message.ts # EventMessage base class + defineEvent() factory
│ ├── event-consumer.ts # EventConsumer base class + defineConsumer() factory
│ ├── prepare-consumer-subscription.ts # Bridges EventConsumer → MessageHandler
│ ├── types.ts # ConsumedEventMessage, EventConsumerClass
│ └── index.ts
├── decorators/
│ ├── consumable.ts # @Consumable() decorator + pendingSubscribers queue
│ └── index.ts
└── utils/
├── connect-to-broker.ts # connectToBroker(), herald(), heraldChannel(), publishEvent(), subscribeConsumer()
└── index.ts
User code
│
├─ connectToBroker(options) ← creates driver + Broker, registers in BrokerRegistry
│
├─ herald() ← returns Broker from BrokerRegistry.get()
│ │
│ ├─ .channel("name") ← returns ChannelContract (lazy-created, cached per Broker)
│ │ ├─ .publish()
│ │ ├─ .subscribe()
│ │ └─ .stopConsuming()
│ │
│ ├─ .subscribe(Consumer) ← uses prepareConsumerSubscription to bridge
│ └─ .publish(EventMessage) ← delegates to driver.publish()
│
└─ @Consumable() ← defers subscription until "connected" event fires
│
└─ brokerRegistry.on("connected", ...)
Registry pattern — BrokerRegistry is a singleton that holds all named brokers. herald() is a convenience accessor that calls brokerRegistry.get().
Lazy channel creation — Calling broker.channel("x") creates and caches a RabbitMQChannel. Subsequent calls return the same instance. This is why publish() must use this.channel() and never look up the Map directly.
Dynamic driver imports — connectToBroker() uses await import(...) to load RabbitMQDriver on demand. This avoids requiring amqplib for users who don't use RabbitMQ.
Error propagation via events — Consumer errors are emitted as "error" events on the driver (not logged to console). The prepareConsumerSubscription function accepts an onError callback for this.
Boot-order handling — @Consumable() stores pending consumers and subscribes them when brokerRegistry.on("connected") fires. This solves the race condition where consumer classes are imported before the broker connects.
consumerId lazy static getter — Each EventConsumer subclass gets a unique UUID generated on first access via a static getter. This avoids the old collision bug where all subclasses shared the base class's UUID.
console.log/error/warntypes/ — don't dump everything in one filestopConsuming() to any new channel implementation — it's part of ChannelContractthis.channel() inside drivers for channel access — never access the channels Map directlyyarn tsc after every changeconsole.* calls anywhere in the packageEventConsumer subclasses — use lazy gettersamqplib or kafkajs at the top level — always use dynamic import()File: src/drivers/kafka/kafka-driver.ts
Must implement BrokerDriverContract:
import type { BrokerDriverContract } from "../../contracts";
export class KafkaDriver implements BrokerDriverContract {
readonly name = "kafka" as const;
// Implement all methods from BrokerDriverContract
}
Required methods (from BrokerDriverContract):
connect() / disconnect() — lifecycleon() / off() — event listeners (connected, disconnected, error, reconnecting)subscribe(Consumer) / unsubscribe(Consumer) — EventConsumer registrationpublish(event) — must use this.channel() for auto-creationchannel(name, options?) — lazy-create and cache KafkaChannel instancesstartConsuming() / stopConsuming() — batch lifecyclehealthCheck() — returns { healthy, latency?, error? }getChannelNames() / closeChannel(name) — channel managementFile: src/drivers/kafka/kafka-channel.ts
Must implement ChannelContract<TPayload>:
import type { ChannelContract } from "../../contracts";
export class KafkaChannel<TPayload> implements ChannelContract<TPayload> {
// Implement all methods from ChannelContract
}
Required methods:
publish() / publishBatch() — message sendingsubscribe() — returns Subscription with unsubscribe()unsubscribeById() — cancel specific consumerrequest() / respond() — RPC patternstats() / purge() / exists() / delete() / assert() — channel managementstopConsuming() — cancel all subscriptions gracefullysrc/drivers/kafka/index.tssrc/drivers/index.ts"kafka" to BrokerDriverType union in types/driver.types.tscase "kafka": block in connect-to-broker.ts (use dynamic import like RabbitMQ does)yarn tsc # must pass with zero errors
brokerRegistry.getAll() — list all registered brokersbroker.driver.getChannelNames() — list cached channelsbroker.driver.on("error", console.error) — catch consumer errors (in app code only)pendingSubscribers set (exported from decorators/consumable.ts) — are consumers stuck waiting?