@platformatic/kafka 2.10.0-alpha.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import { consumerReceivesChannel, createDiagnosticContext, notifyCreation } from
5
5
  import { findErrorBy, protocolErrors, UserError } from "../../errors.js";
6
6
  import { IS_CONTROL } from "../../protocol/records.js";
7
7
  import { runAsyncSeries } from "../../registries/abstract.js";
8
- import { kAutocommit, kGetFetchNode, kInstance, kRefreshOffsetsAndFetch } from "../../symbols.js";
8
+ import { kAutocommit, kDeserializationError, kGetFetchNode, kInstance, kRefreshOffsetsAndFetch } from "../../symbols.js";
9
9
  import { kConnections, kCreateConnectionPool, kInspect, kPrometheus } from "../base/base.js";
10
10
  import { ensureMetric } from "../metrics.js";
11
11
  import { defaultConsumerOptions } from "./options.js";
@@ -24,11 +24,12 @@ function messageToJSON() {
24
24
  return {
25
25
  key: this.key,
26
26
  value: this.value,
27
- headers: Array.from(this.headers.entries()),
27
+ headers: this.headerEntries.map(([key, value]) => [key, value]),
28
28
  topic: this.topic,
29
29
  partition: this.partition,
30
30
  timestamp: this.timestamp.toString(),
31
31
  offset: this.offset.toString(),
32
+ leaderEpoch: this.leaderEpoch,
32
33
  metadata: this.metadata
33
34
  };
34
35
  }
@@ -309,7 +310,12 @@ export class MessagesStream extends Readable {
309
310
  _construct(callback) {
310
311
  this.#refreshOffsetsInflight = true;
311
312
  this.#refreshOffsets(error => {
313
+ const wasPending = this.#refreshOffsetsPending;
312
314
  this.#refreshOffsetsInflight = false;
315
+ this.#refreshOffsetsPending = false;
316
+ if (!error && wasPending) {
317
+ this.#scheduleRefreshOffsetsAndFetch();
318
+ }
313
319
  callback(error ?? undefined);
314
320
  });
315
321
  }
@@ -610,11 +616,20 @@ export class MessagesStream extends Readable {
610
616
  : this.#commit.bind(this, topic, partition, offset + 1n, leaderEpoch);
611
617
  try {
612
618
  const headers = new Map();
619
+ const headerEntries = [];
613
620
  let payloadType = 'headerKey';
614
621
  let deserializedKey;
615
622
  let deserializedValue;
616
623
  let deserializationError;
624
+ let hookFailed = false;
617
625
  try {
626
+ // A before-deserialization hook already failed for this message, report it as is
627
+ const hookError = messageToConsume[kDeserializationError];
628
+ if (hookError) {
629
+ hookFailed = true;
630
+ payloadType = hookError.payloadType;
631
+ throw hookError.error;
632
+ }
618
633
  for (const [headerKey, headerValue] of record.headers) {
619
634
  payloadType = 'headerKey';
620
635
  /* c8 ignore next - Hard to test */
@@ -623,6 +638,7 @@ export class MessagesStream extends Readable {
623
638
  /* c8 ignore next - Hard to test */
624
639
  const deserializedHeaderValue = headerValueDeserializer(headerValue ?? undefined, messageToConsume);
625
640
  headers.set(deserializedHeaderKey, deserializedHeaderValue);
641
+ headerEntries.push([deserializedHeaderKey, deserializedHeaderValue]);
626
642
  }
627
643
  payloadType = 'key';
628
644
  /* c8 ignore next - Hard to test */
@@ -659,11 +675,14 @@ export class MessagesStream extends Readable {
659
675
  */
660
676
  diagnosticContext.error = error;
661
677
  consumerReceivesChannel.error.publish(diagnosticContext);
662
- const headersFailed = payloadType === 'headerKey' || payloadType === 'headerValue';
678
+ // A hook failure happens before anything is deserialized, so nothing can be kept
679
+ const headersFailed = hookFailed || payloadType === 'headerKey' || payloadType === 'headerValue';
663
680
  if (headersFailed) {
664
681
  headers.clear();
682
+ headerEntries.length = 0;
665
683
  for (const [headerKey, headerValue] of record.headers) {
666
684
  headers.set(headerKey, headerValue);
685
+ headerEntries.push([headerKey, headerValue]);
667
686
  }
668
687
  }
669
688
  if (headersFailed || payloadType === 'key') {
@@ -678,7 +697,7 @@ export class MessagesStream extends Readable {
678
697
  else {
679
698
  diagnosticContext.error = error;
680
699
  consumerReceivesChannel.error.publish(diagnosticContext);
681
- this.destroy(new UserError('Failed to deserialize a message.', { cause: error }));
700
+ this.destroy(hookFailed ? error : new UserError('Failed to deserialize a message.', { cause: error }));
682
701
  return;
683
702
  }
684
703
  }
@@ -687,10 +706,14 @@ export class MessagesStream extends Readable {
687
706
  key: deserializedKey,
688
707
  value: deserializedValue,
689
708
  headers,
709
+ headerEntries,
690
710
  topic,
691
711
  partition,
692
712
  timestamp: firstTimestamp + record.timestampDelta,
693
713
  offset,
714
+ // The batch header epoch is the epoch of the record itself, which is what
715
+ // OffsetCommit expects as committedLeaderEpoch (KIP-320).
716
+ leaderEpoch: batch.partitionLeaderEpoch,
694
717
  commit,
695
718
  // Deserializers and hooks can enrich the metadata of the message they processed
696
719
  metadata: deserializationError
@@ -773,6 +796,16 @@ export class MessagesStream extends Readable {
773
796
  if (this.#offsetsToCommit.size === 0) {
774
797
  return;
775
798
  }
799
+ // Skip this tick while the consumer is mid-(re)join: a commit attempted here would fail
800
+ // with a rejoin-required error (ILLEGAL_GENERATION/UNKNOWN_MEMBER_ID/REBALANCE_IN_PROGRESS),
801
+ // and — since this runs on its own timer, independently of the group's own rejoin flow —
802
+ // each such failure calls joinGroup again. With a short autocommit interval and many
803
+ // consumers in the group, this can fire fresh JoinGroups fast enough that the group never
804
+ // completes a rebalance round. Leave the offsets queued; they are committed once the
805
+ // consumer becomes active again (next tick, or the final flush on close/destroy).
806
+ if (!this.#consumer.isActive()) {
807
+ return;
808
+ }
776
809
  this.#autocommitInflight = true;
777
810
  const offsets = Array.from(this.#offsetsToCommit.values());
778
811
  this.#offsetsToCommit.clear();
@@ -831,7 +864,10 @@ export class MessagesStream extends Readable {
831
864
  topics.push(assignment);
832
865
  }
833
866
  if (!topics.length) {
834
- this.#assignOffsets(offsets, new Map(), callback);
867
+ // No assignment yet: do not seed fallback offsets for the whole topic.
868
+ // consumer:group:join will refresh once partitions are assigned.
869
+ this.emit('offsets');
870
+ callback(null);
835
871
  return;
836
872
  }
837
873
  this.#consumer.listCommittedOffsets({ topics }, (error, commits) => {
@@ -858,6 +894,7 @@ export class MessagesStream extends Readable {
858
894
  this.#refreshOffsetsInflight = true;
859
895
  this.#refreshOffsets(error => {
860
896
  const shouldDestroyOnError = this.#refreshOffsetsDestroyOnError;
897
+ const wasPending = this.#refreshOffsetsPending;
861
898
  this.#refreshOffsetsInflight = false;
862
899
  this.#refreshOffsetsDestroyOnError = false;
863
900
  this.#refreshOffsetsPending = false;
@@ -869,8 +906,7 @@ export class MessagesStream extends Readable {
869
906
  return;
870
907
  }
871
908
  // A new one was scheduled while the previous one was inflight, we need to run it immediately
872
- /* c8 ignore next 4 - Hard to test */
873
- if (this.#refreshOffsetsPending) {
909
+ if (wasPending) {
874
910
  this.#scheduleRefreshOffsetsAndFetch(shouldDestroyOnError);
875
911
  return;
876
912
  }
@@ -972,9 +1008,23 @@ export class MessagesStream extends Readable {
972
1008
  }
973
1009
  runAsyncSeries((request, cb) => {
974
1010
  const [data, type, message] = request;
975
- const result = hook(data, type, message, cb);
1011
+ /*
1012
+ Without an error handler a failing hook destroys the stream, as it always did. With one,
1013
+ the failure is recorded on the message it belongs to and reported to the handler in
1014
+ #pushRecords, together with everything else which can fail while deserializing, so that
1015
+ the caller can skip or degrade that single message instead of losing the stream.
1016
+ */
1017
+ const onHookSettled = error => {
1018
+ if (error && this.#deserializationErrorHandler) {
1019
+ message[kDeserializationError] ??= { error, payloadType: type };
1020
+ cb(null);
1021
+ return;
1022
+ }
1023
+ cb(error);
1024
+ };
1025
+ const result = hook(data, type, message, onHookSettled);
976
1026
  if (typeof result?.then === 'function') {
977
- result.then(() => cb(null), cb);
1027
+ result.then(() => onHookSettled(null), onHookSettled);
978
1028
  }
979
1029
  }, requests, 0, error => {
980
1030
  if (error) {
@@ -15,6 +15,10 @@ export declare const groupOptionsProperties: {
15
15
  type: string;
16
16
  minimum: number;
17
17
  };
18
+ heartbeatStallTimeout: {
19
+ type: string;
20
+ minimum: number;
21
+ };
18
22
  groupProtocol: {
19
23
  type: string;
20
24
  enum: ("consumer" | "classic")[];
@@ -160,6 +164,10 @@ export declare const groupOptionsSchema: {
160
164
  type: string;
161
165
  minimum: number;
162
166
  };
167
+ heartbeatStallTimeout: {
168
+ type: string;
169
+ minimum: number;
170
+ };
163
171
  groupProtocol: {
164
172
  type: string;
165
173
  enum: ("consumer" | "classic")[];
@@ -283,6 +291,10 @@ export declare const consumeOptionsSchema: {
283
291
  type: string;
284
292
  minimum: number;
285
293
  };
294
+ heartbeatStallTimeout: {
295
+ type: string;
296
+ minimum: number;
297
+ };
286
298
  groupProtocol: {
287
299
  type: string;
288
300
  enum: ("consumer" | "classic")[];
@@ -455,6 +467,10 @@ export declare const consumerOptionsSchema: {
455
467
  type: string;
456
468
  minimum: number;
457
469
  };
470
+ heartbeatStallTimeout: {
471
+ type: string;
472
+ minimum: number;
473
+ };
458
474
  groupProtocol: {
459
475
  type: string;
460
476
  enum: ("consumer" | "classic")[];
@@ -585,6 +601,10 @@ export declare const fetchOptionsSchema: {
585
601
  type: string;
586
602
  minimum: number;
587
603
  };
604
+ heartbeatStallTimeout: {
605
+ type: string;
606
+ minimum: number;
607
+ };
588
608
  groupProtocol: {
589
609
  type: string;
590
610
  enum: ("consumer" | "classic")[];
@@ -8,6 +8,7 @@ export const groupOptionsProperties = {
8
8
  sessionTimeout: { type: 'number', minimum: 0 },
9
9
  rebalanceTimeout: { type: 'number', minimum: 0 },
10
10
  heartbeatInterval: { type: 'number', minimum: 0 },
11
+ heartbeatStallTimeout: { type: 'number', minimum: 0 },
11
12
  groupProtocol: { type: 'string', enum: allowedGroupProtocols },
12
13
  groupRemoteAssignor: { type: 'string' },
13
14
  protocols: {
@@ -82,6 +82,7 @@ export interface GroupOptions {
82
82
  sessionTimeout?: number;
83
83
  rebalanceTimeout?: number;
84
84
  heartbeatInterval?: number;
85
+ heartbeatStallTimeout?: number;
85
86
  protocols?: GroupProtocolSubscription[];
86
87
  protocolsMetadata?: GroupProtocolsMetadataCallback;
87
88
  partitionAssigner?: GroupPartitionsAssigner;
@@ -92,6 +93,7 @@ export interface ConsumerGroupOptions {
92
93
  groupInstanceId?: string;
93
94
  groupRemoteAssignor?: string;
94
95
  rebalanceTimeout?: number;
96
+ heartbeatStallTimeout?: number;
95
97
  assignmentUserData?: Buffer;
96
98
  }
97
99
  export interface ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue> {
@@ -169,3 +171,8 @@ export interface ConsumerHeartbeatPayload {
169
171
  export interface ConsumerHeartbeatErrorPayload extends ConsumerHeartbeatPayload {
170
172
  error: Error;
171
173
  }
174
+ export interface ConsumerHeartbeatStalledPayload {
175
+ lastError?: Error;
176
+ lastHeartbeat: Date | null;
177
+ stalledFor: number;
178
+ }
@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto';
2
2
  import { createPromisifiedCallback, kCallbackPromise, runConcurrentCallbacks } from "../../apis/callbacks.js";
3
3
  import { FindCoordinatorKeyTypes, ProduceAcks } from "../../apis/enumerations.js";
4
4
  import { createDiagnosticContext, producerInitIdempotentChannel, producerSendsChannel, producerTransactionsChannel } from "../../diagnostic.js";
5
- import { findErrorBy, UserError } from "../../errors.js";
5
+ import { findErrorBy, NetworkError, UserError } from "../../errors.js";
6
6
  import { runAsyncSeries } from "../../registries/abstract.js";
7
7
  import { kInstance, kTransaction, kTransactionAddOffsets, kTransactionAddPartitions, kTransactionCancel, kTransactionCommitOffset, kTransactionEnd, kTransactionFindCoordinator, kTransactionPrepare } from "../../symbols.js";
8
8
  import { emitExperimentalApiWarning, NumericMap } from "../../utils.js";
@@ -749,65 +749,71 @@ export class Producer extends Base {
749
749
  });
750
750
  }
751
751
  #performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, produceOptions, recoverOnProducerStateLoss, callback) {
752
- // Get the metadata with the topic/partitions informations
753
- this[kMetadata]({ topics, autocreateTopics }, (error, metadata) => {
754
- if (error) {
755
- callback(error);
756
- return;
757
- }
758
- const { topic, partition } = messages[0];
759
- const leader = metadata.topics.get(topic).partitions[partition].leader;
760
- this[kPerformWithRetry]('produce', retryCallback => {
752
+ const { topic, partition } = messages[0];
753
+ let refreshMetadata = false;
754
+ this[kPerformWithRetry]('produce', retryCallback => {
755
+ this[kMetadata]({ topics, autocreateTopics, forceUpdate: refreshMetadata }, (error, metadata) => {
756
+ if (error) {
757
+ refreshMetadata = NetworkError.isRetryable(error);
758
+ retryCallback(error);
759
+ return;
760
+ }
761
+ refreshMetadata = false;
762
+ const leader = metadata.topics.get(topic).partitions[partition].leader;
761
763
  this[kGetConnection](metadata.brokers.get(leader), (error, connection) => {
762
764
  if (error) {
765
+ refreshMetadata = NetworkError.isRetryable(error);
763
766
  retryCallback(error);
764
767
  return;
765
768
  }
766
769
  this[kGetApi]('Produce', (error, api) => {
767
770
  if (error) {
771
+ refreshMetadata = NetworkError.isRetryable(error);
768
772
  retryCallback(error);
769
773
  return;
770
774
  }
771
- api(connection, acks, timeout, messages, produceOptions, retryCallback);
775
+ api(connection, acks, timeout, messages, produceOptions, (error, response) => {
776
+ refreshMetadata = NetworkError.isRetryable(error);
777
+ retryCallback(error, response);
778
+ });
772
779
  });
773
780
  });
774
- }, (error, results) => {
775
- if (error) {
776
- // If the last error was due to stale metadata, we retry the operation with this set of messages
777
- // since the partition is already set, it should attempt on the new destination
778
- /* c8 ignore next - Hard to test */
779
- const hasStaleMetadata = findErrorBy(error, 'hasStaleMetadata', true);
780
- if (hasStaleMetadata && repeatOnStaleMetadata) {
781
- this.clearMetadata();
782
- this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, false, produceOptions, recoverOnProducerStateLoss, callback);
783
- return;
784
- }
785
- if (recoverOnProducerStateLoss && !this.#transaction && this.#isProducerStateLost(error)) {
786
- this.#recoverIdempotentProducer(messages, produceOptions, (error, recoveredProduceOptions) => {
787
- if (error) {
788
- callback(error);
789
- return;
790
- }
791
- this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, recoveredProduceOptions, false, callback);
792
- });
793
- return;
794
- }
795
- callback(error, results);
781
+ });
782
+ }, (error, results) => {
783
+ if (error) {
784
+ // If the last error was due to stale metadata, we retry the operation with this set of messages
785
+ // since the partition is already set, it should attempt on the new destination
786
+ /* c8 ignore next - Hard to test */
787
+ const hasStaleMetadata = findErrorBy(error, 'hasStaleMetadata', true);
788
+ if (hasStaleMetadata && repeatOnStaleMetadata) {
789
+ this.clearMetadata();
790
+ this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, false, produceOptions, recoverOnProducerStateLoss, callback);
796
791
  return;
797
792
  }
798
- callback(error, results);
799
- }, 0, [], error => {
800
- /* c8 ignore next 3 - Hard to test */
801
- if (!repeatOnStaleMetadata) {
802
- return false;
793
+ if (recoverOnProducerStateLoss && !this.#transaction && this.#isProducerStateLost(error)) {
794
+ this.#recoverIdempotentProducer(messages, produceOptions, (error, recoveredProduceOptions) => {
795
+ if (error) {
796
+ callback(error);
797
+ return;
798
+ }
799
+ this.#performSingleDestinationSend(topics, messages, timeout, acks, autocreateTopics, repeatOnStaleMetadata, recoveredProduceOptions, false, callback);
800
+ });
801
+ return;
803
802
  }
804
- return !!findErrorBy(error, 'hasStaleMetadata', true);
805
- });
803
+ callback(error, results);
804
+ return;
805
+ }
806
+ callback(error, results);
807
+ }, 0, [], error => {
808
+ /* c8 ignore next 3 - Hard to test */
809
+ if (!repeatOnStaleMetadata) {
810
+ return false;
811
+ }
812
+ return !!findErrorBy(error, 'hasStaleMetadata', true);
806
813
  });
807
814
  }
808
815
  #isProducerStateLost(error) {
809
- return !!(findErrorBy(error, 'apiId', 'UNKNOWN_PRODUCER_ID') ||
810
- findErrorBy(error, 'apiId', 'OUT_OF_ORDER_SEQUENCE_NUMBER'));
816
+ return !!(findErrorBy(error, 'apiId', 'UNKNOWN_PRODUCER_ID') || findErrorBy(error, 'apiId', 'OUT_OF_ORDER_SEQUENCE_NUMBER'));
811
817
  }
812
818
  #recoverIdempotentProducer(messages, produceOptions, callback) {
813
819
  this.#producerInfo = undefined;
package/dist/errors.d.ts CHANGED
@@ -36,6 +36,7 @@ export declare class AuthenticationError extends GenericError {
36
36
  }
37
37
  export declare class NetworkError extends GenericError {
38
38
  static code: ErrorCode;
39
+ static isRetryable(error: Error | null | undefined): boolean;
39
40
  constructor(message: string, properties?: ErrorProperties);
40
41
  }
41
42
  export declare class ProtocolError extends GenericError {
package/dist/errors.js CHANGED
@@ -119,6 +119,10 @@ export class AuthenticationError extends GenericError {
119
119
  }
120
120
  export class NetworkError extends GenericError {
121
121
  static code = 'PLT_KFK_NETWORK';
122
+ static isRetryable(error) {
123
+ const networkError = findErrorBy(error, 'code', NetworkError.code);
124
+ return networkError?.canRetry === true;
125
+ }
122
126
  constructor(message, properties = {}) {
123
127
  super(NetworkError.code, message, { canRetry: true, ...properties });
124
128
  }
@@ -1,12 +1,30 @@
1
+ const BRACKETED_BROKER_RE = /^\[([^\]]+)](?::(\d*))?$/;
1
2
  export function parseBroker(broker, defaultPort = 9092) {
2
- if (typeof broker === 'string') {
3
- if (broker.includes(':')) {
4
- const [host, port] = broker.split(':');
5
- return { host, port: Number(port) };
6
- }
7
- else {
8
- return { host: broker, port: defaultPort };
3
+ if (typeof broker !== 'string') {
4
+ const host = stripBrackets(broker.host);
5
+ if (host === broker.host) {
6
+ return broker;
9
7
  }
8
+ return { ...broker, host };
9
+ }
10
+ const bracketed = BRACKETED_BROKER_RE.exec(broker);
11
+ if (bracketed) {
12
+ return { host: bracketed[1], port: parsePort(bracketed[2], defaultPort) };
13
+ }
14
+ const separator = broker.lastIndexOf(':');
15
+ // Bare IPv6 addresses contain multiple colons and no port. The host:port form
16
+ // is unambiguous only with a single colon, or with brackets around the host.
17
+ if (separator === -1 || broker.indexOf(':') !== separator) {
18
+ return { host: broker, port: defaultPort };
19
+ }
20
+ return { host: broker.slice(0, separator), port: parsePort(broker.slice(separator + 1), defaultPort) };
21
+ }
22
+ function parsePort(port, defaultPort) {
23
+ return port === undefined || port.length === 0 ? defaultPort : Number(port);
24
+ }
25
+ function stripBrackets(host) {
26
+ if (host.startsWith('[') && host.endsWith(']')) {
27
+ return host.slice(1, -1);
10
28
  }
11
- return broker;
29
+ return host;
12
30
  }
@@ -33,11 +33,14 @@ export interface MessageJSON<Key = unknown, Value = unknown, HeaderKey = unknown
33
33
  partition: number;
34
34
  timestamp: string;
35
35
  offset: string;
36
+ leaderEpoch: number;
36
37
  metadata: Record<string, unknown>;
37
38
  }
38
39
  export interface Message<Key = Buffer, Value = Buffer, HeaderKey = Buffer, HeaderValue = Buffer> extends Required<MessageBase<Key, Value>> {
39
40
  headers: Map<HeaderKey, HeaderValue>;
41
+ headerEntries: Array<[HeaderKey, HeaderValue]>;
40
42
  offset: bigint;
43
+ leaderEpoch: number;
41
44
  metadata: Record<string, unknown>;
42
45
  commit(callback?: (error?: Error) => void): void | Promise<void>;
43
46
  toJSON(): MessageJSON<Key, Value, HeaderKey, HeaderValue>;
@@ -1,19 +1,38 @@
1
1
  export function runAsyncSeries(operation, collection, index, callback) {
2
- if (collection.length === 0 || index >= collection.length) {
2
+ const length = collection.length;
3
+ if (length === 0 || index >= length) {
3
4
  callback(null);
4
5
  return;
5
6
  }
6
- operation(collection[index], error => {
7
- if (error) {
8
- callback(error);
7
+ while (index < length) {
8
+ let invocationReturned = false;
9
+ let cbCalled = false;
10
+ let cbError;
11
+ operation(collection[index], error => {
12
+ if (invocationReturned) {
13
+ // The callback was invoked asynchronously, so continuing recursively does not grow the current stack.
14
+ if (error) {
15
+ callback(error);
16
+ return;
17
+ }
18
+ runAsyncSeries(operation, collection, index + 1, callback);
19
+ return;
20
+ }
21
+ cbCalled = true;
22
+ cbError = error;
23
+ });
24
+ invocationReturned = true;
25
+ if (!cbCalled) {
26
+ // Wait for the async callback to continue the series.
9
27
  return;
10
28
  }
11
- else if (index === collection.length - 1) {
12
- callback(null);
29
+ if (cbError) {
30
+ callback(cbError);
13
31
  return;
14
32
  }
15
- runAsyncSeries(operation, collection, index + 1, callback);
16
- });
33
+ index++;
34
+ }
35
+ callback(null);
17
36
  }
18
37
  /* c8 ignore start */
19
38
  export class AbstractSchemaRegistry {
@@ -1,3 +1,5 @@
1
+ import * as AjvDraft04Module from 'ajv-draft-04';
2
+ import draft06MetaSchema from 'ajv/dist/refs/json-schema-draft-06.json' with { type: 'json' };
1
3
  import { Ajv } from 'ajv';
2
4
  import { Ajv2020 } from 'ajv/dist/2020.js';
3
5
  import avro from 'avsc';
@@ -8,8 +10,7 @@ import { EMPTY_BUFFER, MultipleErrors, NetworkError, TimeoutError, UnsupportedFo
8
10
  import { getCredential } from "../protocol/sasl/utils.js";
9
11
  import { AbstractSchemaRegistry } from "./abstract.js";
10
12
  const require = createRequire(import.meta.url);
11
- const AjvDraft04 = require('ajv-draft-04');
12
- const draft06MetaSchema = require('ajv/dist/refs/json-schema-draft-06.json');
13
+ const AjvDraft04 = AjvDraft04Module.default;
13
14
  const undiciGlobalDispatcherSymbol = /^undici\.globalDispatcher\.(\d+)$/;
14
15
  export function createUndiciAgent(options) {
15
16
  /*
package/dist/symbols.d.ts CHANGED
@@ -2,6 +2,7 @@ export declare const kInstance: unique symbol;
2
2
  export declare const kRefreshOffsetsAndFetch: unique symbol;
3
3
  export declare const kAutocommit: unique symbol;
4
4
  export declare const kGetFetchNode: unique symbol;
5
+ export declare const kDeserializationError: unique symbol;
5
6
  export declare const kTransaction: unique symbol;
6
7
  export declare const kTransactionPrepare: unique symbol;
7
8
  export declare const kTransactionAddPartitions: unique symbol;
package/dist/symbols.js CHANGED
@@ -3,6 +3,7 @@ export const kInstance = Symbol('plt.kafka.base.instance');
3
3
  export const kRefreshOffsetsAndFetch = Symbol('plt.kafka.messagesStream.refreshOffsetsAndFetch');
4
4
  export const kAutocommit = Symbol('plt.kafka.messagesStream.autocommit');
5
5
  export const kGetFetchNode = Symbol('plt.kafka.consumer.getFetchNode');
6
+ export const kDeserializationError = Symbol('plt.kafka.messagesStream.deserializationError');
6
7
  export const kTransaction = Symbol('plt.kafka.producer.transactions');
7
8
  export const kTransactionPrepare = Symbol('plt.kafka.producer.transactions.prepare');
8
9
  export const kTransactionAddPartitions = Symbol('plt.kafka.producer.transactions.addPartitions');
@@ -14,8 +14,48 @@ export declare const createTopicOptionsSchema: {
14
14
  topics: {
15
15
  type: string;
16
16
  items: {
17
- type: string;
18
- pattern: string;
17
+ oneOf: ({
18
+ type: string;
19
+ pattern: string;
20
+ } | {
21
+ type: string;
22
+ properties: {
23
+ topic: {
24
+ type: string;
25
+ pattern: string;
26
+ };
27
+ partitions: {
28
+ type: string;
29
+ };
30
+ replicas: {
31
+ type: string;
32
+ };
33
+ assignments: {
34
+ type: string;
35
+ items: {
36
+ type: string;
37
+ properties: {
38
+ partition: {
39
+ type: string;
40
+ minimum: number;
41
+ };
42
+ brokers: {
43
+ type: string;
44
+ items: {
45
+ type: string;
46
+ };
47
+ minItems: number;
48
+ };
49
+ };
50
+ required: string[];
51
+ additionalProperties: boolean;
52
+ };
53
+ minItems: number;
54
+ };
55
+ };
56
+ required: string[];
57
+ additionalProperties: boolean;
58
+ })[];
19
59
  };
20
60
  };
21
61
  partitions: {
@@ -46,6 +86,22 @@ export declare const createTopicOptionsSchema: {
46
86
  };
47
87
  minItems: number;
48
88
  };
89
+ configs: {
90
+ type: string;
91
+ items: {
92
+ type: string;
93
+ properties: {
94
+ name: {
95
+ type: string;
96
+ };
97
+ value: {
98
+ type: string[];
99
+ };
100
+ };
101
+ required: string[];
102
+ additionalProperties: boolean;
103
+ };
104
+ };
49
105
  };
50
106
  required: string[];
51
107
  additionalProperties: boolean;
@@ -42,8 +42,14 @@ export interface Group extends Omit<GroupBase, 'groupType'> {
42
42
  authorizedOperations: number;
43
43
  }
44
44
  export interface AdminOptions extends BaseOptions {}
45
+ export interface CreateTopicsTopicOptions {
46
+ topic: string;
47
+ partitions?: number;
48
+ replicas?: number;
49
+ assignments?: BrokerAssignment[];
50
+ }
45
51
  export interface CreateTopicsOptions {
46
- topics: string[];
52
+ topics: (string | CreateTopicsTopicOptions)[];
47
53
  partitions?: number;
48
54
  replicas?: number;
49
55
  assignments?: BrokerAssignment[];
@@ -6,7 +6,7 @@ import { Base, type BaseEvents, kCreateConnectionPool } from "../base/base";
6
6
  import { type ClusterMetadata } from "../base/types";
7
7
  import { MessagesStream } from "./messages-stream";
8
8
  import { TopicsMap } from "./topics-map";
9
- import { type CommitOptions, type ConsumeOptions, type ConsumerGroupJoinPayload, type ConsumerGroupLeavePayload, type ConsumerGroupRebalancePayload, type ConsumerHeartbeatErrorPayload, type ConsumerHeartbeatPayload, type ConsumerOptions, type FetchOptions, type GetLagOptions, type GroupAssignment, type GroupOptions, type ListCommitsOptions, type ListOffsetsOptions, type Offsets, type OffsetsWithTimestamps } from "./types";
9
+ import { type CommitOptions, type ConsumeOptions, type ConsumerGroupJoinPayload, type ConsumerGroupLeavePayload, type ConsumerGroupRebalancePayload, type ConsumerHeartbeatErrorPayload, type ConsumerHeartbeatPayload, type ConsumerHeartbeatStalledPayload, type ConsumerOptions, type FetchOptions, type GetLagOptions, type GroupAssignment, type GroupOptions, type ListCommitsOptions, type ListOffsetsOptions, type Offsets, type OffsetsWithTimestamps } from "./types";
10
10
  export interface ConsumerEvents extends BaseEvents {
11
11
  'consumer:group:join': (payload: ConsumerGroupJoinPayload) => void;
12
12
  'consumer:group:leave': (payload: ConsumerGroupLeavePayload) => void;
@@ -16,6 +16,7 @@ export interface ConsumerEvents extends BaseEvents {
16
16
  'consumer:heartbeat:cancel': (payload: ConsumerHeartbeatPayload) => void;
17
17
  'consumer:heartbeat:end': (payload?: ConsumerHeartbeatPayload) => void;
18
18
  'consumer:heartbeat:error': (payload: ConsumerHeartbeatErrorPayload) => void;
19
+ 'consumer:heartbeat:stalled': (payload: ConsumerHeartbeatStalledPayload) => void;
19
20
  'consumer:lag': (lag: Offsets) => void;
20
21
  'consumer:lag:error': (error: Error) => void;
21
22
  }