Consumer API
interface Consumer {
connect(options?: ConnectOptions): Promise<void>;
disconnect(options?: ConnectOptions): Promise<void>;
subscribe(subscription: ConsumerSubscribeTopics | ConsumerSubscribeTopic): Promise<void>;
assign(topicPartitions: readonly { topic: string; partition: number }[]): Promise<void>;
run(config?: ConsumerRunConfig): Promise<void>;
stream(config?: Omit<ConsumerRunConfig, 'eachBatch' | 'eachMessage'>): AsyncIterableIterator<Batch>;
stop(): Promise<void>;
pause(topics: readonly { topic: string; partitions?: number[] }[]): void;
resume(topics: readonly { topic: string; partitions?: number[] }[]): void;
paused(): TopicPartitions[];
seek(topicPartitionOffset: { topic: string; partition: number; offset: bigint | number | string }): void;
commitOffsets(topicPartitions: readonly TopicPartitionOffsetAndMetadata[]): Promise<void>;
describeGroup(): Promise<GroupDescription>;
committed(topicPartitions: readonly TopicPartition[]): Promise<TopicPartitionOffsetAndMetadata[]>;
position(topicPartition: TopicPartition): bigint | null;
currentLag(topicPartition: TopicPartition): bigint | null;
listTopics(): Promise<string[]>;
partitionsFor(topic: string): Promise<TopicPartitionInfo[]>;
clientInstanceId(): Buffer | null;
logger(): Logger;
on(eventName: string, listener: (event: unknown) => void | Promise<void>): () => void;
readonly events: Record<string, string>;
[Symbol.asyncDispose](): Promise<void>;
}
Source: consumer/index.ts.
Payload types: consumer/types.ts.
Guide: Consumer. Config:
ConsumerConfig.
Apache: consumer configs.
subscribe
await consumer.subscribe({ topics: ['events'], fromBeginning: true });
await consumer.subscribe({ topic: /^events\./, autoOffsetReset: 'none' });
await consumer.subscribe({ topics: ['events'], autoOffsetReset: 'by_duration:PT1H' });
autoOffsetReset wins over fromBeginning when both are set. 'by_duration:PT1H'
(KIP-1106) starts at the first offset at or after now minus that ISO-8601 duration.
assign
await consumer.assign([
{ topic: 'events', partition: 0 },
{ topic: 'events', partition: 1 },
]);
Fetches exactly these partitions with no group membership (no JoinGroup/SyncGroup, no
ConsumerGroupHeartbeat, no rebalancing). Mutually exclusive with subscribe on the same
consumer - calling one after the other throws, and so does run/stream if neither was called.
groupId is optional on kafka.consumer(...) in this mode; it is required only to call
commitOffsets. Guide: Assign mode.
run / stream
| Option | Default | Notes |
|---|---|---|
eachMessage |
Per-record handler | |
eachBatch |
Per-batch handler | |
autoCommit |
true |
Commit after processing |
autoCommitInterval |
ms. When both interval and threshold are unset, commit after each processed batch | |
autoCommitThreshold |
messages | |
partitionsConsumedConcurrently |
1 |
Parallel partitions |
signal |
Abort to stop | |
onPartitionsRevoked |
Partitions given up this rebalance, before fetching the new assignment | |
onPartitionsAssigned |
Partitions newly gained this rebalance | |
onPartitionsLost |
Fires instead of onPartitionsRevoked when the assignment was lost, not revoked |
stream() cannot run alongside run(). See
Rebalance callbacks for the
revoked-vs-lost distinction and error-handling policy.
eachBatch plus partitionsConsumedConcurrently is the heavy-load consume API.
The default concurrency is 1. Spread throughputPreset().consumer into run()
for concurrency 4. See Throughput.
committed / position / currentLag
const committed = await consumer.committed([{ topic: 'events', partition: 0 }]);
// [{ topic: 'events', partition: 0, offset: 41n, metadata: null }]
const position = consumer.position({ topic: 'events', partition: 0 }); // 42n | null
const lag = consumer.currentLag({ topic: 'events', partition: 0 }); // bigint | null
committed reads offsets from the group coordinator (OffsetFetch) and works
whether or not run()/stream() has started - it queries the broker
directly, the same way admin.fetchOffsets does. A partition with no
committed offset comes back as offset: -1n, metadata: null (Kafka’s wire
convention for “none”).
position is the next fetch offset for a partition currently assigned to
this consumer. It returns null, rather than throwing, when the partition
isn’t currently assigned - a rebalance can move it away between fetches, for
example. currentLag is highWatermark - position and returns null under
the same condition, or when no Fetch response has landed yet for that
partition. Both throw if the group/assignment hasn’t started yet.
listTopics / partitionsFor
Same Metadata helpers as on the producer — no Admin client required:
await consumer.connect();
const topics = await consumer.listTopics();
const partitions = await consumer.partitionsFor('events');
clientInstanceId
Same KIP-714 UUID as on the producer (Buffer | null until the broker assigns
one). See Observability.
KafkaMessage
| Field | Type | Notes |
|---|---|---|
offset |
bigint |
Always bigint, including MessageSet on 0.10 |
timestamp |
bigint |
|
key / value |
Buffer | null |
|
headers |
RecordHeaders |
Empty on MessageSet |
isControlRecord |
boolean |
Transaction markers |
magicByte |
number |
0/1 MessageSet, 2 RecordBatch |
seek / commitOffsets still accept number and string at runtime; prefer
bigint.
ShareConsumer
Returned by kafka.shareConsumer({ groupId }) (KIP-932). Subscribe is
synchronous. Successful eachMessage / auto-acked eachBatch calls are
acknowledged as ACCEPT; handler failures RELEASE the acquired range so
another member can retry. Share fetch runs per assigned node in parallel, with
the same partitionsConsumedConcurrently / prefetch knobs as the classic
consumer.
interface ShareConsumer {
connect(options?: ConnectOptions): Promise<void>;
disconnect(options?: ConnectOptions): Promise<void>;
subscribe(subscription: { topics: readonly string[] }): void;
run(config: {
eachMessage?: EachMessageHandler | null;
eachBatch?: EachShareBatchHandler | null;
eachBatchAutoAck?: boolean;
partitionsConsumedConcurrently?: number;
prefetchMaxBatches?: number;
prefetchMaxBytes?: number;
}): Promise<void>;
stop(): Promise<void>;
clientInstanceId(): Buffer | null;
on(eventName: string, listener: (event: unknown) => void | Promise<void>): () => void;
readonly events: Record<string, string>;
logger(): Logger;
[Symbol.asyncDispose](): Promise<void>;
}
SHARE_ACKNOWLEDGE_TYPE: GAP 0, ACCEPT 1, RELEASE 2, REJECT 3, RENEW 4.
SHARE_ACQUIRE_MODE: BATCH_OPTIMIZED 0, RECORD_LIMIT 1 (ShareFetch v2 / KIP-1206).
Guide: Share groups,
Acknowledgement: implicit vs explicit,
Instrumentation events.
ShareConsumer events
Namespaced under share_consumer.*; source:
share-consumer/instrumentation-events.ts.
| Event | Payload |
|---|---|
FETCH_START |
{ nodeId } |
FETCH |
{ nodeId, numberOfBatches, duration } |
ACKNOWLEDGE |
{ groupId, memberId, nodeId, topics: [{ topic, partitions: [{ partition, firstOffset, lastOffset, acknowledgeType }] }] } |
REQUEST |
Same shape as the classic consumer’s REQUEST (NetworkRequestEvent) |
REQUEST_TIMEOUT |
Same shape as the classic consumer’s REQUEST_TIMEOUT |
REQUEST_QUEUE_SIZE |
Same shape as the classic consumer’s REQUEST_QUEUE_SIZE |