@platformatic/kafka 1.34.0 → 1.35.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.
@@ -66,6 +66,7 @@ export declare class Base<OptionsType extends BaseOptions = BaseOptions, EventsT
66
66
  get closed(): boolean;
67
67
  get type(): ClientType;
68
68
  get context(): unknown;
69
+ get currentMetadata(): ClusterMetadata | undefined;
69
70
  get connections(): ConnectionPool;
70
71
  emitWithDebug(section: string | null, name: string, ...args: any[]): boolean;
71
72
  close(callback: CallbackWithPromise<void>): void;
@@ -90,6 +90,9 @@ export class Base extends TypedEventEmitter {
90
90
  get context() {
91
91
  return this[kContext];
92
92
  }
93
+ get currentMetadata() {
94
+ return this.#metadata;
95
+ }
93
96
  get connections() {
94
97
  return this[kConnections];
95
98
  }
@@ -172,6 +172,7 @@ export class Consumer extends Base {
172
172
  }
173
173
  options.autocommit ??= this[kOptions].autocommit ?? true;
174
174
  options.maxBytes ??= this[kOptions].maxBytes;
175
+ options.maxBytesPerPartition ??= this[kOptions].maxBytesPerPartition ?? options.maxBytes;
175
176
  options.highWaterMark ??= this[kOptions].highWaterMark;
176
177
  options.registry ??= this[kOptions].registry;
177
178
  options.beforeDeserialization ??= this[kOptions].beforeDeserialization;
@@ -618,7 +619,7 @@ export class Consumer extends Base {
618
619
  });
619
620
  });
620
621
  }
621
- #listCommittedOffsets(options, callback) {
622
+ #listCommittedOffsets(options, callback, staleMemberEpochRetries = 0) {
622
623
  const topics = [];
623
624
  for (const { topic: name, partitions } of options.topics) {
624
625
  topics.push({ name, partitionIndexes: partitions });
@@ -640,6 +641,18 @@ export class Consumer extends Base {
640
641
  });
641
642
  }, (error, response) => {
642
643
  if (error) {
644
+ if (this.#useConsumerGroupProtocol &&
645
+ staleMemberEpochRetries < this[kOptions].retries &&
646
+ error.findBy?.('apiId', 'STALE_MEMBER_EPOCH')) {
647
+ this.#consumerGroupHeartbeat(this[kOptions], heartbeatError => {
648
+ if (heartbeatError) {
649
+ callback(this.#handleMetadataError(heartbeatError));
650
+ return;
651
+ }
652
+ this.#listCommittedOffsets(options, callback, staleMemberEpochRetries + 1);
653
+ });
654
+ return;
655
+ }
643
656
  callback(this.#handleMetadataError(error));
644
657
  return;
645
658
  }
@@ -306,7 +306,16 @@ export class MessagesStream extends Readable {
306
306
  return super[Symbol.asyncIterator]();
307
307
  }
308
308
  _construct(callback) {
309
- this.#refreshOffsets(callback);
309
+ this.#refreshOffsetsInflight = true;
310
+ this.#refreshOffsets(error => {
311
+ const wasPending = this.#refreshOffsetsPending;
312
+ this.#refreshOffsetsInflight = false;
313
+ this.#refreshOffsetsPending = false;
314
+ if (!error && wasPending) {
315
+ this.#scheduleRefreshOffsetsAndFetch();
316
+ }
317
+ callback(error ?? undefined);
318
+ });
310
319
  }
311
320
  _destroy(error, callback) {
312
321
  if (this.#autocommitInterval) {
@@ -389,7 +398,7 @@ export class MessagesStream extends Readable {
389
398
  {
390
399
  partition,
391
400
  fetchOffset,
392
- partitionMaxBytes: this.#options.maxBytes,
401
+ partitionMaxBytes: this.#options.maxBytesPerPartition,
393
402
  currentLeaderEpoch: leaderEpoch,
394
403
  lastFetchedEpoch: leaderEpoch
395
404
  }
@@ -650,7 +659,10 @@ export class MessagesStream extends Readable {
650
659
  topics.push(assignment);
651
660
  }
652
661
  if (!topics.length) {
653
- this.#assignOffsets(offsets, new Map(), callback);
662
+ // No assignment yet: do not seed fallback offsets for the whole topic.
663
+ // consumer:group:join will refresh once partitions are assigned.
664
+ this.emit('offsets');
665
+ callback(null);
654
666
  return;
655
667
  }
656
668
  this.#consumer.listCommittedOffsets({ topics }, (error, commits) => {
@@ -677,6 +689,7 @@ export class MessagesStream extends Readable {
677
689
  this.#refreshOffsetsInflight = true;
678
690
  this.#refreshOffsets(error => {
679
691
  const shouldDestroyOnError = this.#refreshOffsetsDestroyOnError;
692
+ const wasPending = this.#refreshOffsetsPending;
680
693
  this.#refreshOffsetsInflight = false;
681
694
  this.#refreshOffsetsDestroyOnError = false;
682
695
  this.#refreshOffsetsPending = false;
@@ -688,8 +701,7 @@ export class MessagesStream extends Readable {
688
701
  return;
689
702
  }
690
703
  // A new one was scheduled while the previous one was inflight, we need to run it immediately
691
- /* c8 ignore next 4 - Hard to test */
692
- if (this.#refreshOffsetsPending) {
704
+ if (wasPending) {
693
705
  this.#scheduleRefreshOffsetsAndFetch(shouldDestroyOnError);
694
706
  return;
695
707
  }
@@ -97,6 +97,10 @@ export declare const consumeOptionsProperties: {
97
97
  type: string;
98
98
  minimum: number;
99
99
  };
100
+ maxBytesPerPartition: {
101
+ type: string;
102
+ minimum: number;
103
+ };
100
104
  maxWaitTime: {
101
105
  type: string;
102
106
  minimum: number;
@@ -217,6 +221,10 @@ export declare const consumeOptionsSchema: {
217
221
  type: string;
218
222
  minimum: number;
219
223
  };
224
+ maxBytesPerPartition: {
225
+ type: string;
226
+ minimum: number;
227
+ };
220
228
  maxWaitTime: {
221
229
  type: string;
222
230
  minimum: number;
@@ -379,6 +387,10 @@ export declare const consumerOptionsSchema: {
379
387
  type: string;
380
388
  minimum: number;
381
389
  };
390
+ maxBytesPerPartition: {
391
+ type: string;
392
+ minimum: number;
393
+ };
382
394
  maxWaitTime: {
383
395
  type: string;
384
396
  minimum: number;
@@ -502,6 +514,10 @@ export declare const fetchOptionsSchema: {
502
514
  type: string;
503
515
  minimum: number;
504
516
  };
517
+ maxBytesPerPartition: {
518
+ type: string;
519
+ minimum: number;
520
+ };
505
521
  maxWaitTime: {
506
522
  type: string;
507
523
  minimum: number;
@@ -58,6 +58,7 @@ export const consumeOptionsProperties = {
58
58
  autocommit: { oneOf: [{ type: 'boolean' }, { type: 'number', minimum: 100 }] },
59
59
  minBytes: { type: 'number', minimum: 0 },
60
60
  maxBytes: { type: 'number', minimum: 0 },
61
+ maxBytesPerPartition: { type: 'number', minimum: 0 },
61
62
  maxWaitTime: { type: 'number', minimum: 0 },
62
63
  isolationLevel: { type: 'number', enum: allowedFetchIsolationLevels },
63
64
  deserializers: serdeProperties,
@@ -65,6 +65,7 @@ export interface ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue> {
65
65
  autocommit?: boolean | number;
66
66
  minBytes?: number;
67
67
  maxBytes?: number;
68
+ maxBytesPerPartition?: number;
68
69
  maxWaitTime?: number;
69
70
  isolationLevel?: number;
70
71
  deserializers?: Partial<Deserializers<Key, Value, HeaderKey, HeaderValue>>;
@@ -838,7 +838,7 @@ export class Producer extends Base {
838
838
  let partition = 0;
839
839
  if (typeof message.partition !== 'number') {
840
840
  if (partitioner) {
841
- partition = partitioner(message, key);
841
+ partition = partitioner(message, key, { metadata: this.currentMetadata });
842
842
  }
843
843
  else if (key) {
844
844
  partition = defaultPartitioner(message, key);
@@ -1,7 +1,7 @@
1
1
  import { type CompressionAlgorithmValue } from '../../protocol/compression.ts';
2
2
  import { type MessageToProduce } from '../../protocol/records.ts';
3
3
  import { type SchemaRegistry } from '../../registries/abstract.ts';
4
- import { type BaseOptions, type TopicWithPartitionAndOffset } from '../base/types.ts';
4
+ import { type BaseOptions, type ClusterMetadata, type TopicWithPartitionAndOffset } from '../base/types.ts';
5
5
  import { type BeforeSerializationHook, type Serializers } from '../serde.ts';
6
6
  export interface ProducerInfo {
7
7
  producerId: bigint;
@@ -12,7 +12,10 @@ export interface ProduceResult {
12
12
  offsets?: TopicWithPartitionAndOffset[];
13
13
  unwritableNodes?: number[];
14
14
  }
15
- export type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, key?: Buffer | undefined) => number;
15
+ export interface PartitionerContext {
16
+ metadata: ClusterMetadata | undefined;
17
+ }
18
+ export type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, key: Buffer | undefined, context: PartitionerContext) => number;
16
19
  export interface ProducerStreamReport {
17
20
  batchId: number;
18
21
  count: number;
@@ -66,6 +66,7 @@ export declare class Base<OptionsType extends BaseOptions = BaseOptions, EventsT
66
66
  get closed(): boolean;
67
67
  get type(): ClientType;
68
68
  get context(): unknown;
69
+ get currentMetadata(): ClusterMetadata | undefined;
69
70
  get connections(): ConnectionPool;
70
71
  emitWithDebug(section: string | null, name: string, ...args: any[]): boolean;
71
72
  close(callback: CallbackWithPromise<void>): void;
@@ -97,6 +97,10 @@ export declare const consumeOptionsProperties: {
97
97
  type: string;
98
98
  minimum: number;
99
99
  };
100
+ maxBytesPerPartition: {
101
+ type: string;
102
+ minimum: number;
103
+ };
100
104
  maxWaitTime: {
101
105
  type: string;
102
106
  minimum: number;
@@ -217,6 +221,10 @@ export declare const consumeOptionsSchema: {
217
221
  type: string;
218
222
  minimum: number;
219
223
  };
224
+ maxBytesPerPartition: {
225
+ type: string;
226
+ minimum: number;
227
+ };
220
228
  maxWaitTime: {
221
229
  type: string;
222
230
  minimum: number;
@@ -379,6 +387,10 @@ export declare const consumerOptionsSchema: {
379
387
  type: string;
380
388
  minimum: number;
381
389
  };
390
+ maxBytesPerPartition: {
391
+ type: string;
392
+ minimum: number;
393
+ };
382
394
  maxWaitTime: {
383
395
  type: string;
384
396
  minimum: number;
@@ -502,6 +514,10 @@ export declare const fetchOptionsSchema: {
502
514
  type: string;
503
515
  minimum: number;
504
516
  };
517
+ maxBytesPerPartition: {
518
+ type: string;
519
+ minimum: number;
520
+ };
505
521
  maxWaitTime: {
506
522
  type: string;
507
523
  minimum: number;
@@ -65,6 +65,7 @@ export interface ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue> {
65
65
  autocommit?: boolean | number;
66
66
  minBytes?: number;
67
67
  maxBytes?: number;
68
+ maxBytesPerPartition?: number;
68
69
  maxWaitTime?: number;
69
70
  isolationLevel?: number;
70
71
  deserializers?: Partial<Deserializers<Key, Value, HeaderKey, HeaderValue>>;
@@ -1,7 +1,7 @@
1
1
  import { type CompressionAlgorithmValue } from "../../protocol/compression";
2
2
  import { type MessageToProduce } from "../../protocol/records";
3
3
  import { type SchemaRegistry } from "../../registries/abstract";
4
- import { type BaseOptions, type TopicWithPartitionAndOffset } from "../base/types";
4
+ import { type BaseOptions, type ClusterMetadata, type TopicWithPartitionAndOffset } from "../base/types";
5
5
  import { type BeforeSerializationHook, type Serializers } from "../serde";
6
6
  export interface ProducerInfo {
7
7
  producerId: bigint;
@@ -12,7 +12,10 @@ export interface ProduceResult {
12
12
  offsets?: TopicWithPartitionAndOffset[];
13
13
  unwritableNodes?: number[];
14
14
  }
15
- export type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, key?: Buffer | undefined) => number;
15
+ export interface PartitionerContext {
16
+ metadata: ClusterMetadata | undefined;
17
+ }
18
+ export type Partitioner<Key, Value, HeaderKey, HeaderValue> = (message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, key: Buffer | undefined, context: PartitionerContext) => number;
16
19
  export interface ProducerStreamReport {
17
20
  batchId: number;
18
21
  count: number;
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export const name = "@platformatic/kafka";
2
- export const version = "1.34.0";
2
+ export const version = "1.35.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/kafka",
3
- "version": "1.34.0",
3
+ "version": "1.35.0",
4
4
  "description": "Modern and performant client for Apache Kafka",
5
5
  "homepage": "https://github.com/platformatic/kafka",
6
6
  "author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
@@ -24,6 +24,24 @@
24
24
  "type": "module",
25
25
  "exports": "./dist/index.js",
26
26
  "types": "./dist/index.d.ts",
27
+ "scripts": {
28
+ "build": "rm -rf dist && tsc -p tsconfig.base.json",
29
+ "postbuild": "./scripts/node scripts/postbuild.ts",
30
+ "lint": "eslint --cache",
31
+ "typecheck": "tsc -p . --noEmit",
32
+ "format": "prettier -w benchmarks playground src test",
33
+ "test": "c8 -c test/config/c8-local.json ./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/*.test.ts' 'test/*/*.test.ts' 'test/*/*/*.test.ts'",
34
+ "test:ci": "c8 -c test/config/c8-ci.json ./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/*.test.ts' 'test/*/*.test.ts' 'test/*/*/*.test.ts'",
35
+ "test:memory": "NODE_OPTIONS='--expose-gc' ./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/**/*.memory-test.ts'",
36
+ "test:docker:up": "docker-compose up -d --wait",
37
+ "test:docker:down": "docker-compose down",
38
+ "ci": "npm run build && npm run lint && npm run test:ci",
39
+ "prepublishOnly": "npm run build && npm run lint",
40
+ "postpublish": "git push origin && git push origin -f --tags",
41
+ "generate:apis": "node scripts/generate-apis.ts",
42
+ "generate:errors": "node scripts/generate-errors.ts",
43
+ "create:api": "node scripts/create-api.ts"
44
+ },
27
45
  "dependencies": {
28
46
  "@platformatic/dynamic-buffer": "^0.3.1",
29
47
  "@platformatic/wasm-utils": "^0.2.1",
@@ -84,21 +102,5 @@
84
102
  "dist/typescript-4/*"
85
103
  ]
86
104
  }
87
- },
88
- "scripts": {
89
- "build": "rm -rf dist && tsc -p tsconfig.base.json",
90
- "postbuild": "./scripts/node scripts/postbuild.ts",
91
- "lint": "eslint --cache",
92
- "typecheck": "tsc -p . --noEmit",
93
- "format": "prettier -w benchmarks playground src test",
94
- "test": "c8 -c test/config/c8-local.json ./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/*.test.ts' 'test/*/*.test.ts' 'test/*/*/*.test.ts'",
95
- "test:ci": "c8 -c test/config/c8-ci.json ./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/*.test.ts' 'test/*/*.test.ts' 'test/*/*/*.test.ts'",
96
- "test:memory": "NODE_OPTIONS='--expose-gc' ./scripts/node --test --test-reporter=cleaner-spec-reporter 'test/**/*.memory-test.ts'",
97
- "test:docker:up": "docker-compose up -d --wait",
98
- "test:docker:down": "docker-compose down",
99
- "ci": "npm run build && npm run lint && npm run test:ci",
100
- "generate:apis": "node scripts/generate-apis.ts",
101
- "generate:errors": "node scripts/generate-errors.ts",
102
- "create:api": "node scripts/create-api.ts"
103
105
  }
104
106
  }