@platformatic/kafka 1.27.0-alpha.2 → 1.27.0-alpha.3
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.
- package/README.md +0 -58
- package/dist/clients/base/base.js +6 -2
- package/dist/clients/base/options.d.ts +9 -2
- package/dist/clients/base/options.js +1 -1
- package/dist/clients/base/types.d.ts +2 -1
- package/dist/clients/consumer/consumer.js +1 -24
- package/dist/clients/consumer/messages-stream.js +10 -66
- package/dist/clients/consumer/options.d.ts +0 -24
- package/dist/clients/consumer/options.js +1 -3
- package/dist/clients/consumer/types.d.ts +1 -4
- package/dist/clients/producer/options.d.ts +18 -2
- package/dist/clients/producer/options.js +1 -3
- package/dist/clients/producer/producer.js +15 -75
- package/dist/clients/producer/types.d.ts +1 -4
- package/dist/clients/serde.d.ts +6 -11
- package/dist/errors.d.ts +1 -5
- package/dist/errors.js +0 -8
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/network/connection.d.ts +6 -7
- package/dist/protocol/definitions.js +1 -1
- package/dist/protocol/reader.js +1 -1
- package/dist/protocol/records.d.ts +18 -7
- package/dist/protocol/records.js +6 -2
- package/dist/protocol/sasl/oauth-bearer.d.ts +3 -3
- package/dist/protocol/sasl/plain.d.ts +3 -3
- package/dist/protocol/sasl/scram-sha.d.ts +3 -3
- package/dist/protocol/sasl/utils.d.ts +3 -3
- package/dist/protocol/writer.js +1 -1
- package/dist/typescript-4/dist/clients/base/options.d.ts +9 -2
- package/dist/typescript-4/dist/clients/base/types.d.ts +2 -1
- package/dist/typescript-4/dist/clients/consumer/options.d.ts +0 -24
- package/dist/typescript-4/dist/clients/consumer/types.d.ts +1 -4
- package/dist/typescript-4/dist/clients/producer/options.d.ts +18 -2
- package/dist/typescript-4/dist/clients/producer/types.d.ts +1 -4
- package/dist/typescript-4/dist/clients/serde.d.ts +6 -11
- package/dist/typescript-4/dist/errors.d.ts +1 -5
- package/dist/typescript-4/dist/index.d.ts +1 -2
- package/dist/typescript-4/dist/network/connection.d.ts +6 -7
- package/dist/typescript-4/dist/protocol/records.d.ts +18 -7
- package/dist/typescript-4/dist/protocol/sasl/oauth-bearer.d.ts +3 -3
- package/dist/typescript-4/dist/protocol/sasl/plain.d.ts +3 -3
- package/dist/typescript-4/dist/protocol/sasl/scram-sha.d.ts +3 -3
- package/dist/typescript-4/dist/protocol/sasl/utils.d.ts +3 -3
- package/dist/version.js +1 -1
- package/package.json +3 -4
- package/dist/registries/abstract.d.ts +0 -22
- package/dist/registries/abstract.js +0 -38
- package/dist/registries/confluent-schema-registry.d.ts +0 -41
- package/dist/registries/confluent-schema-registry.js +0 -222
- package/dist/registries/index.d.ts +0 -2
- package/dist/registries/index.js +0 -2
- package/dist/typescript-4/dist/registries/abstract.d.ts +0 -22
- package/dist/typescript-4/dist/registries/confluent-schema-registry.d.ts +0 -41
- package/dist/typescript-4/dist/registries/index.d.ts +0 -2
package/README.md
CHANGED
|
@@ -10,7 +10,6 @@ A modern, high-performance, pure TypeScript/JavaScript type safe client for Apac
|
|
|
10
10
|
- **Flexible API**: You can use promises or callbacks on all APIs.
|
|
11
11
|
- **Streaming or Event-based Consumers**: Thanks to Node.js streams, you can choose your preferred consuming method.
|
|
12
12
|
- **Flexible Serialisation**: Pluggable serialisers and deserialisers.
|
|
13
|
-
- **Schema Registry Support**: Built-in Confluent Schema Registry integration with AVRO, Protobuf, and JSON Schema support.
|
|
14
13
|
- **Connection Management**: Automatic connection pooling and recovery.
|
|
15
14
|
- **Low Dependencies**: Minimal external dependencies.
|
|
16
15
|
|
|
@@ -142,63 +141,6 @@ await admin.deleteTopics({ topics: ['my-topic'] })
|
|
|
142
141
|
await admin.close()
|
|
143
142
|
```
|
|
144
143
|
|
|
145
|
-
### Schema Registry
|
|
146
|
-
|
|
147
|
-
The library includes built-in support for Confluent Schema Registry with AVRO, Protocol Buffers, and JSON Schema:
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
import { Producer, Consumer } from '@platformatic/kafka'
|
|
151
|
-
import { ConfluentSchemaRegistry } from '@platformatic/kafka/registries'
|
|
152
|
-
|
|
153
|
-
// Create a schema registry instance
|
|
154
|
-
const registry = new ConfluentSchemaRegistry({
|
|
155
|
-
url: 'http://localhost:8081',
|
|
156
|
-
auth: {
|
|
157
|
-
username: 'user',
|
|
158
|
-
password: 'password'
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
// Producer with schema registry
|
|
163
|
-
const producer = new Producer({
|
|
164
|
-
clientId: 'schema-producer',
|
|
165
|
-
bootstrapBrokers: ['localhost:9092'],
|
|
166
|
-
registry // Automatic serialization with schemas
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
// Send messages with schema IDs
|
|
170
|
-
await producer.send({
|
|
171
|
-
messages: [{
|
|
172
|
-
topic: 'users',
|
|
173
|
-
value: { id: 1, name: 'Alice' },
|
|
174
|
-
metadata: {
|
|
175
|
-
schemas: {
|
|
176
|
-
value: 100 // Schema ID in the registry
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}]
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
// Consumer with schema registry
|
|
183
|
-
const consumer = new Consumer({
|
|
184
|
-
groupId: 'schema-consumers',
|
|
185
|
-
clientId: 'schema-consumer',
|
|
186
|
-
bootstrapBrokers: ['localhost:9092'],
|
|
187
|
-
registry // Automatic deserialization with schemas
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
const stream = await consumer.consume({
|
|
191
|
-
topics: ['users']
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
// Messages are automatically deserialized
|
|
195
|
-
for await (const message of stream) {
|
|
196
|
-
console.log('User:', message.value) // Typed object
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
For more details, see the [Confluent Schema Registry documentation](./docs/confluent-schema-registry.md).
|
|
201
|
-
|
|
202
144
|
## TLS and SASL
|
|
203
145
|
|
|
204
146
|
See the relevant sections in the the [Base Client](./docs/base.md) page.
|
|
@@ -230,16 +230,20 @@ export class Base extends TypedEventEmitter {
|
|
|
230
230
|
const retriable = !genericError.findBy?.('canRetry', false);
|
|
231
231
|
errors.push(error);
|
|
232
232
|
if (attempt < retries && retriable && !shouldSkipRetry?.(error)) {
|
|
233
|
-
this.emitWithDebug('client', 'performWithRetry:retry', operationId, attempt, retries);
|
|
234
233
|
function onClose() {
|
|
235
234
|
clearTimeout(timeout);
|
|
236
235
|
errors.push(new UserError(`Client closed while retrying ${operationId}.`));
|
|
237
236
|
callback(new MultipleErrors(`${operationId} failed ${attempt + 1} times.`, errors));
|
|
238
237
|
}
|
|
238
|
+
let delay = this[kOptions].retryDelay;
|
|
239
|
+
if (typeof delay === 'function') {
|
|
240
|
+
delay = delay(this, operationId, attempt + 1, retries, error);
|
|
241
|
+
}
|
|
242
|
+
this.emitWithDebug('client', 'performWithRetry:retry', operationId, attempt, retries, delay);
|
|
239
243
|
const timeout = setTimeout(() => {
|
|
240
244
|
this.removeListener('client:close', onClose);
|
|
241
245
|
this[kPerformWithRetry](operationId, operation, callback, attempt + 1, errors, shouldSkipRetry);
|
|
242
|
-
},
|
|
246
|
+
}, delay);
|
|
243
247
|
this.once('client:close', onClose);
|
|
244
248
|
}
|
|
245
249
|
else {
|
|
@@ -67,8 +67,15 @@ export declare const baseOptionsSchema: {
|
|
|
67
67
|
})[];
|
|
68
68
|
};
|
|
69
69
|
retryDelay: {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
oneOf: ({
|
|
71
|
+
type: string;
|
|
72
|
+
minimum: number;
|
|
73
|
+
function?: undefined;
|
|
74
|
+
} | {
|
|
75
|
+
function: boolean;
|
|
76
|
+
type?: undefined;
|
|
77
|
+
minimum?: undefined;
|
|
78
|
+
})[];
|
|
72
79
|
};
|
|
73
80
|
maxInflights: {
|
|
74
81
|
type: string;
|
|
@@ -29,7 +29,7 @@ export const baseOptionsSchema = {
|
|
|
29
29
|
timeout: { type: 'number', minimum: 0 },
|
|
30
30
|
connectTimeout: { type: 'number', minimum: 0 },
|
|
31
31
|
retries: { oneOf: [{ type: 'number', minimum: 0 }, { type: 'boolean' }] },
|
|
32
|
-
retryDelay: { type: 'number', minimum: 0 },
|
|
32
|
+
retryDelay: { oneOf: [{ type: 'number', minimum: 0 }, { function: true }] },
|
|
33
33
|
maxInflights: { type: 'number', minimum: 0 },
|
|
34
34
|
handleBackPressure: { type: 'boolean', default: false },
|
|
35
35
|
tls: { type: 'object', additionalProperties: true }, // No validation as they come from Node.js
|
|
@@ -29,12 +29,13 @@ export interface ClusterMetadata {
|
|
|
29
29
|
topics: Map<string, ClusterTopicMetadata>;
|
|
30
30
|
lastUpdate: number;
|
|
31
31
|
}
|
|
32
|
+
export type RetryDelayGetter<Owner = object> = (client: Owner, operationId: string, attempt: number, retries: number, error: Error) => number;
|
|
32
33
|
export interface BaseOptions extends ConnectionOptions {
|
|
33
34
|
clientId: string;
|
|
34
35
|
bootstrapBrokers: Broker[] | string[];
|
|
35
36
|
timeout?: number;
|
|
36
37
|
retries?: number | boolean;
|
|
37
|
-
retryDelay?: number;
|
|
38
|
+
retryDelay?: number | RetryDelayGetter;
|
|
38
39
|
metadataMaxAge?: number;
|
|
39
40
|
autocreateTopics?: boolean;
|
|
40
41
|
strict?: boolean;
|
|
@@ -40,15 +40,6 @@ export class Consumer extends Base {
|
|
|
40
40
|
constructor(options) {
|
|
41
41
|
super({ ...defaultConsumerOptions, ...options });
|
|
42
42
|
this[kValidateOptions](options, consumerOptionsValidator, '/options');
|
|
43
|
-
if (options.registry) {
|
|
44
|
-
if (options.beforeDeserialization) {
|
|
45
|
-
throw new UserError('/options/beforeDeserialization cannot be provided when /options/registry is provided.');
|
|
46
|
-
}
|
|
47
|
-
else if (options.deserializers) {
|
|
48
|
-
throw new UserError('/options/deserializers cannot be provided when /options/registry is provided.');
|
|
49
|
-
}
|
|
50
|
-
options.registry.getDeserializers();
|
|
51
|
-
}
|
|
52
43
|
this.groupId = options.groupId;
|
|
53
44
|
this.groupInstanceId = options.groupInstanceId ?? null;
|
|
54
45
|
this.generationId = 0;
|
|
@@ -159,22 +150,8 @@ export class Consumer extends Base {
|
|
|
159
150
|
}
|
|
160
151
|
options.autocommit ??= this[kOptions].autocommit ?? true;
|
|
161
152
|
options.maxBytes ??= this[kOptions].maxBytes;
|
|
153
|
+
options.deserializers = Object.assign({}, options.deserializers, this[kOptions].deserializers);
|
|
162
154
|
options.highWaterMark ??= this[kOptions].highWaterMark;
|
|
163
|
-
options.registry ??= this[kOptions].registry;
|
|
164
|
-
options.beforeDeserialization ??= this[kOptions].beforeDeserialization;
|
|
165
|
-
if (options.registry) {
|
|
166
|
-
if (options.beforeDeserialization) {
|
|
167
|
-
throw new UserError('/options/beforeDeserialization cannot be provided when /options/registry is provided.');
|
|
168
|
-
/* c8 ignore next - Hard to test */
|
|
169
|
-
}
|
|
170
|
-
else if (options.deserializers || this[kOptions].deserializers) {
|
|
171
|
-
throw new UserError('/options/deserializers cannot be provided when /options/registry is provided.');
|
|
172
|
-
}
|
|
173
|
-
options.deserializers = options.registry.getDeserializers();
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
options.deserializers = Object.assign({}, options.deserializers, this[kOptions].deserializers);
|
|
177
|
-
}
|
|
178
155
|
this.#consume(options, callback);
|
|
179
156
|
return callback[kCallbackPromise];
|
|
180
157
|
}
|
|
@@ -4,7 +4,6 @@ import { ListOffsetTimestamps } from "../../apis/enumerations.js";
|
|
|
4
4
|
import { consumerReceivesChannel, createDiagnosticContext, notifyCreation } from "../../diagnostic.js";
|
|
5
5
|
import { UserError } from "../../errors.js";
|
|
6
6
|
import { IS_CONTROL } from "../../protocol/records.js";
|
|
7
|
-
import { runAsyncSeries } from "../../registries/abstract.js";
|
|
8
7
|
import { kAutocommit, kInstance, kRefreshOffsetsAndFetch } from "../../symbols.js";
|
|
9
8
|
import { kConnections, kCreateConnectionPool, kInspect, kPrometheus } from "../base/base.js";
|
|
10
9
|
import { ensureMetric } from "../metrics.js";
|
|
@@ -57,7 +56,6 @@ export class MessagesStream extends Readable {
|
|
|
57
56
|
#closeCallbacks;
|
|
58
57
|
#metricsConsumedMessages;
|
|
59
58
|
#corruptedMessageHandler;
|
|
60
|
-
#pushRecordsOperation;
|
|
61
59
|
[kInstance];
|
|
62
60
|
/*
|
|
63
61
|
The following requests are blocking in Kafka:
|
|
@@ -76,7 +74,7 @@ export class MessagesStream extends Readable {
|
|
|
76
74
|
*/
|
|
77
75
|
[kConnections];
|
|
78
76
|
constructor(consumer, options) {
|
|
79
|
-
const { autocommit, mode, fallbackMode, maxFetches, offsets, deserializers, onCorruptedMessage,
|
|
77
|
+
const { autocommit, mode, fallbackMode, maxFetches, offsets, deserializers, onCorruptedMessage,
|
|
80
78
|
// The options below are only destructured to avoid being part of structuredClone below
|
|
81
79
|
partitionAssigner: _partitionAssigner, ...otherOptions } = options;
|
|
82
80
|
if (offsets && mode !== MessagesStreamModes.MANUAL) {
|
|
@@ -103,10 +101,8 @@ export class MessagesStream extends Readable {
|
|
|
103
101
|
this.#maxFetches = maxFetches ?? 0;
|
|
104
102
|
this.#topics = structuredClone(options.topics);
|
|
105
103
|
this.#inflightNodes = new Set();
|
|
106
|
-
this.#keyDeserializer =
|
|
107
|
-
|
|
108
|
-
this.#valueDeserializer =
|
|
109
|
-
deserializers?.value ?? noopDeserializer;
|
|
104
|
+
this.#keyDeserializer = deserializers?.key ?? noopDeserializer;
|
|
105
|
+
this.#valueDeserializer = deserializers?.value ?? noopDeserializer;
|
|
110
106
|
this.#headerKeyDeserializer = deserializers?.headerKey ?? noopDeserializer;
|
|
111
107
|
this.#headerValueDeserializer = deserializers?.headerValue ?? noopDeserializer;
|
|
112
108
|
this.#autocommitEnabled = !!options.autocommit;
|
|
@@ -114,15 +110,6 @@ export class MessagesStream extends Readable {
|
|
|
114
110
|
this.#closed = false;
|
|
115
111
|
this.#closeCallbacks = [];
|
|
116
112
|
this.#corruptedMessageHandler = onCorruptedMessage ?? defaultCorruptedMessageHandler;
|
|
117
|
-
if (registry) {
|
|
118
|
-
this.#pushRecordsOperation = this.#beforeDeserialization.bind(this, registry.getBeforeDeserializationHook());
|
|
119
|
-
}
|
|
120
|
-
else if (beforeDeserialization) {
|
|
121
|
-
this.#pushRecordsOperation = this.#beforeDeserialization.bind(this, beforeDeserialization);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
this.#pushRecordsOperation = this.#pushRecords.bind(this);
|
|
125
|
-
}
|
|
126
113
|
// Restore offsets
|
|
127
114
|
this.#offsetsToFetch = new Map();
|
|
128
115
|
if (offsets) {
|
|
@@ -375,7 +362,10 @@ export class MessagesStream extends Readable {
|
|
|
375
362
|
}
|
|
376
363
|
return;
|
|
377
364
|
}
|
|
378
|
-
this.#
|
|
365
|
+
this.#pushRecords(metadata, topicIds, response, requestedOffsets);
|
|
366
|
+
if (this.#maxFetches > 0 && ++this.#fetches >= this.#maxFetches) {
|
|
367
|
+
this.push(null);
|
|
368
|
+
}
|
|
379
369
|
});
|
|
380
370
|
}
|
|
381
371
|
});
|
|
@@ -429,7 +419,6 @@ export class MessagesStream extends Readable {
|
|
|
429
419
|
}
|
|
430
420
|
// Process messages
|
|
431
421
|
for (const record of batch.records) {
|
|
432
|
-
const messageToConsume = { ...record, topic, partition };
|
|
433
422
|
const offset = batch.firstOffset + BigInt(record.offsetDelta);
|
|
434
423
|
if (offset < requestedOffsets.get(`${topic}:${partition}`)) {
|
|
435
424
|
// Thi is a duplicate message, ignore it
|
|
@@ -448,10 +437,10 @@ export class MessagesStream extends Readable {
|
|
|
448
437
|
try {
|
|
449
438
|
const headers = new Map();
|
|
450
439
|
for (const [headerKey, headerValue] of record.headers) {
|
|
451
|
-
headers.set(headerKeyDeserializer(headerKey
|
|
440
|
+
headers.set(headerKeyDeserializer(headerKey), headerValueDeserializer(headerValue));
|
|
452
441
|
}
|
|
453
|
-
const key = keyDeserializer(record.key, headers
|
|
454
|
-
const value = valueDeserializer(record.value, headers
|
|
442
|
+
const key = keyDeserializer(record.key, headers);
|
|
443
|
+
const value = valueDeserializer(record.value, headers);
|
|
455
444
|
this.#metricsConsumedMessages?.inc();
|
|
456
445
|
const message = {
|
|
457
446
|
key,
|
|
@@ -494,9 +483,6 @@ export class MessagesStream extends Readable {
|
|
|
494
483
|
this.#fetch();
|
|
495
484
|
});
|
|
496
485
|
}
|
|
497
|
-
if (this.#maxFetches > 0 && ++this.#fetches >= this.#maxFetches) {
|
|
498
|
-
this.push(null);
|
|
499
|
-
}
|
|
500
486
|
}
|
|
501
487
|
#updateCommittedOffset(topic, partition, offset) {
|
|
502
488
|
const key = `${topic}:${partition}`;
|
|
@@ -651,46 +637,4 @@ export class MessagesStream extends Readable {
|
|
|
651
637
|
[kInspect](...args) {
|
|
652
638
|
this.#consumer[kInspect](...args);
|
|
653
639
|
}
|
|
654
|
-
#beforeDeserialization(hook, metadata, topicIds, response, requestedOffsets) {
|
|
655
|
-
const requests = [];
|
|
656
|
-
// Create the pre-deserialization requests
|
|
657
|
-
for (const topicResponse of response.responses) {
|
|
658
|
-
for (const { records: recordsBatches, partitionIndex: partition } of topicResponse.partitions) {
|
|
659
|
-
/* c8 ignore next 3 - Hard to test */
|
|
660
|
-
if (!recordsBatches) {
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
for (const batch of recordsBatches) {
|
|
664
|
-
// Filter control markers
|
|
665
|
-
/* c8 ignore next 3 - Hard to test */
|
|
666
|
-
if (batch.attributes & IS_CONTROL) {
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
for (const message of batch.records) {
|
|
670
|
-
message.topic = topicIds.get(topicResponse.topicId);
|
|
671
|
-
message.partition = partition;
|
|
672
|
-
requests.push([message.key, 'key', message]);
|
|
673
|
-
requests.push([message.value, 'value', message]);
|
|
674
|
-
for (const [headerKey, headerValue] of message.headers) {
|
|
675
|
-
requests.push([headerKey, 'headerKey', message]);
|
|
676
|
-
requests.push([headerValue, 'headerValue', message]);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
runAsyncSeries((request, cb) => {
|
|
683
|
-
const [data, type, message] = request;
|
|
684
|
-
const result = hook(data, type, message, cb);
|
|
685
|
-
if (typeof result?.then === 'function') {
|
|
686
|
-
result.then(() => cb(null), cb);
|
|
687
|
-
}
|
|
688
|
-
}, requests, 0, error => {
|
|
689
|
-
if (error) {
|
|
690
|
-
this.destroy(error);
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
this.#pushRecords(metadata, topicIds, response, requestedOffsets);
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
640
|
}
|
|
@@ -127,12 +127,6 @@ export declare const consumeOptionsProperties: {
|
|
|
127
127
|
type: string;
|
|
128
128
|
minimum: number;
|
|
129
129
|
};
|
|
130
|
-
beforeDeserialization: {
|
|
131
|
-
function: boolean;
|
|
132
|
-
};
|
|
133
|
-
registry: {
|
|
134
|
-
type: string;
|
|
135
|
-
};
|
|
136
130
|
};
|
|
137
131
|
export declare const groupOptionsSchema: {
|
|
138
132
|
type: string;
|
|
@@ -247,12 +241,6 @@ export declare const consumeOptionsSchema: {
|
|
|
247
241
|
type: string;
|
|
248
242
|
minimum: number;
|
|
249
243
|
};
|
|
250
|
-
beforeDeserialization: {
|
|
251
|
-
function: boolean;
|
|
252
|
-
};
|
|
253
|
-
registry: {
|
|
254
|
-
type: string;
|
|
255
|
-
};
|
|
256
244
|
groupInstanceId: {
|
|
257
245
|
type: string;
|
|
258
246
|
pattern: string;
|
|
@@ -408,12 +396,6 @@ export declare const consumerOptionsSchema: {
|
|
|
408
396
|
type: string;
|
|
409
397
|
minimum: number;
|
|
410
398
|
};
|
|
411
|
-
beforeDeserialization: {
|
|
412
|
-
function: boolean;
|
|
413
|
-
};
|
|
414
|
-
registry: {
|
|
415
|
-
type: string;
|
|
416
|
-
};
|
|
417
399
|
groupInstanceId: {
|
|
418
400
|
type: string;
|
|
419
401
|
pattern: string;
|
|
@@ -529,12 +511,6 @@ export declare const fetchOptionsSchema: {
|
|
|
529
511
|
type: string;
|
|
530
512
|
minimum: number;
|
|
531
513
|
};
|
|
532
|
-
beforeDeserialization: {
|
|
533
|
-
function: boolean;
|
|
534
|
-
};
|
|
535
|
-
registry: {
|
|
536
|
-
type: string;
|
|
537
|
-
};
|
|
538
514
|
groupInstanceId: {
|
|
539
515
|
type: string;
|
|
540
516
|
pattern: string;
|
|
@@ -61,9 +61,7 @@ export const consumeOptionsProperties = {
|
|
|
61
61
|
maxWaitTime: { type: 'number', minimum: 0 },
|
|
62
62
|
isolationLevel: { type: 'number', enum: allowedFetchIsolationLevels },
|
|
63
63
|
deserializers: serdeProperties,
|
|
64
|
-
highWaterMark: { type: 'number', minimum: 1 }
|
|
65
|
-
beforeDeserialization: { function: true },
|
|
66
|
-
registry: { type: 'object' }
|
|
64
|
+
highWaterMark: { type: 'number', minimum: 1 }
|
|
67
65
|
};
|
|
68
66
|
export const groupOptionsSchema = {
|
|
69
67
|
type: 'object',
|
|
@@ -2,9 +2,8 @@ import { type FetchRequestTopic } from '../../apis/consumer/fetch-v17.ts';
|
|
|
2
2
|
import { type GroupProtocols } from '../../apis/enumerations.ts';
|
|
3
3
|
import { type ConnectionPool } from '../../network/connection-pool.ts';
|
|
4
4
|
import { type KafkaRecord, type Message } from '../../protocol/records.ts';
|
|
5
|
-
import { type SchemaRegistry } from '../../registries/abstract.ts';
|
|
6
5
|
import { type BaseOptions, type ClusterMetadata, type TopicWithPartitionAndOffset } from '../base/types.ts';
|
|
7
|
-
import { type
|
|
6
|
+
import { type Deserializers } from '../serde.ts';
|
|
8
7
|
export interface GroupProtocolSubscription {
|
|
9
8
|
name: string;
|
|
10
9
|
version: number;
|
|
@@ -69,8 +68,6 @@ export interface ConsumeBaseOptions<Key, Value, HeaderKey, HeaderValue> {
|
|
|
69
68
|
isolationLevel?: number;
|
|
70
69
|
deserializers?: Partial<Deserializers<Key, Value, HeaderKey, HeaderValue>>;
|
|
71
70
|
highWaterMark?: number;
|
|
72
|
-
beforeDeserialization?: BeforeDeserializationHook;
|
|
73
|
-
registry?: SchemaRegistry<unknown, unknown, Key, Value, HeaderKey, HeaderValue>;
|
|
74
71
|
}
|
|
75
72
|
export interface StreamOptions {
|
|
76
73
|
topics: string[];
|
|
@@ -121,8 +121,24 @@ export declare const sendOptionsSchema: {
|
|
|
121
121
|
items: {
|
|
122
122
|
type: string;
|
|
123
123
|
properties: {
|
|
124
|
-
key:
|
|
125
|
-
|
|
124
|
+
key: {
|
|
125
|
+
oneOf: ({
|
|
126
|
+
type: string;
|
|
127
|
+
buffer?: undefined;
|
|
128
|
+
} | {
|
|
129
|
+
buffer: boolean;
|
|
130
|
+
type?: undefined;
|
|
131
|
+
})[];
|
|
132
|
+
};
|
|
133
|
+
value: {
|
|
134
|
+
oneOf: ({
|
|
135
|
+
type: string;
|
|
136
|
+
buffer?: undefined;
|
|
137
|
+
} | {
|
|
138
|
+
buffer: boolean;
|
|
139
|
+
type?: undefined;
|
|
140
|
+
})[];
|
|
141
|
+
};
|
|
126
142
|
headers: {
|
|
127
143
|
anyOf: ({
|
|
128
144
|
map: boolean;
|
|
@@ -36,9 +36,7 @@ export const producerOptionsValidator = ajv.compile({
|
|
|
36
36
|
type: 'object',
|
|
37
37
|
properties: {
|
|
38
38
|
...produceOptionsProperties,
|
|
39
|
-
serializers: serdeProperties
|
|
40
|
-
beforeSerialization: { function: true },
|
|
41
|
-
registry: { type: 'object' }
|
|
39
|
+
serializers: serdeProperties
|
|
42
40
|
},
|
|
43
41
|
additionalProperties: true
|
|
44
42
|
});
|
|
@@ -4,7 +4,6 @@ import { FindCoordinatorKeyTypes, ProduceAcks } from "../../apis/enumerations.js
|
|
|
4
4
|
import { createDiagnosticContext, producerInitIdempotentChannel, producerSendsChannel, producerTransactionsChannel } from "../../diagnostic.js";
|
|
5
5
|
import { UserError } from "../../errors.js";
|
|
6
6
|
import { murmur2 } from "../../protocol/murmur2.js";
|
|
7
|
-
import { runAsyncSeries } from "../../registries/abstract.js";
|
|
8
7
|
import { kInstance, kTransaction, kTransactionAddOffsets, kTransactionAddPartitions, kTransactionCancel, kTransactionCommitOffset, kTransactionEnd, kTransactionFindCoordinator, kTransactionPrepare } from "../../symbols.js";
|
|
9
8
|
import { NumericMap } from "../../utils.js";
|
|
10
9
|
import { Base, kAfterCreate, kCheckNotClosed, kClosed, kGetApi, kGetBootstrapConnection, kGetConnection, kMetadata, kOptions, kPerformDeduplicated, kPerformWithRetry, kPrometheus, kValidateOptions } from "../base/base.js";
|
|
@@ -29,7 +28,6 @@ export class Producer extends Base {
|
|
|
29
28
|
#metricsProducedMessages;
|
|
30
29
|
#coordinatorId;
|
|
31
30
|
#transaction;
|
|
32
|
-
#sendOperation;
|
|
33
31
|
constructor(options) {
|
|
34
32
|
if (options.idempotent) {
|
|
35
33
|
options.maxInflights = 1;
|
|
@@ -41,37 +39,18 @@ export class Producer extends Base {
|
|
|
41
39
|
}
|
|
42
40
|
options.repeatOnStaleMetadata ??= true;
|
|
43
41
|
super(options);
|
|
44
|
-
this[kValidateOptions](options, producerOptionsValidator, '/options');
|
|
45
|
-
let serializers = options.serializers;
|
|
46
|
-
if (options.registry) {
|
|
47
|
-
if (options.beforeSerialization) {
|
|
48
|
-
throw new UserError('/options/beforeSerialization cannot be provided when /options/registry is provided.');
|
|
49
|
-
}
|
|
50
|
-
else if (options.serializers) {
|
|
51
|
-
throw new UserError('/options/serializers cannot be provided when /options/registry is provided.');
|
|
52
|
-
}
|
|
53
|
-
serializers = options.registry.getSerializers();
|
|
54
|
-
}
|
|
55
42
|
this.#partitionsRoundRobin = new NumericMap();
|
|
56
43
|
this.#sequences = new NumericMap();
|
|
57
|
-
this.#keySerializer = serializers?.key ?? noopSerializer;
|
|
58
|
-
this.#valueSerializer = serializers?.value ?? noopSerializer;
|
|
59
|
-
this.#headerKeySerializer = serializers?.headerKey ?? noopSerializer;
|
|
60
|
-
this.#headerValueSerializer = serializers?.headerValue ?? noopSerializer;
|
|
44
|
+
this.#keySerializer = options.serializers?.key ?? noopSerializer;
|
|
45
|
+
this.#valueSerializer = options.serializers?.value ?? noopSerializer;
|
|
46
|
+
this.#headerKeySerializer = options.serializers?.headerKey ?? noopSerializer;
|
|
47
|
+
this.#headerValueSerializer = options.serializers?.headerValue ?? noopSerializer;
|
|
61
48
|
this[kOptions].transactionalId ??= randomUUID();
|
|
49
|
+
this[kValidateOptions](options, producerOptionsValidator, '/options');
|
|
62
50
|
if (this[kPrometheus]) {
|
|
63
51
|
ensureMetric(this[kPrometheus], 'Gauge', 'kafka_producers', 'Number of active Kafka producers').inc();
|
|
64
52
|
this.#metricsProducedMessages = ensureMetric(this[kPrometheus], 'Counter', 'kafka_produced_messages', 'Number of produced Kafka messages');
|
|
65
53
|
}
|
|
66
|
-
if (options.registry) {
|
|
67
|
-
this.#sendOperation = this.#beforeSerialization.bind(this, options.registry.getBeforeSerializationHook());
|
|
68
|
-
}
|
|
69
|
-
else if (options.beforeSerialization) {
|
|
70
|
-
this.#sendOperation = this.#beforeSerialization.bind(this, options.beforeSerialization);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
this.#sendOperation = this.#send.bind(this);
|
|
74
|
-
}
|
|
75
54
|
this[kAfterCreate]('producer');
|
|
76
55
|
}
|
|
77
56
|
get producerId() {
|
|
@@ -153,7 +132,7 @@ export class Producer extends Base {
|
|
|
153
132
|
}
|
|
154
133
|
}
|
|
155
134
|
options.acks ??= idempotent ? ProduceAcks.ALL : ProduceAcks.LEADER;
|
|
156
|
-
producerSendsChannel.traceCallback(this.#
|
|
135
|
+
producerSendsChannel.traceCallback(this.#send, 1, createDiagnosticContext({ client: this, operation: 'send', options }), this, options, callback);
|
|
157
136
|
return callback[kCallbackPromise];
|
|
158
137
|
}
|
|
159
138
|
beginTransaction(options, callback) {
|
|
@@ -451,28 +430,19 @@ export class Producer extends Base {
|
|
|
451
430
|
const messages = [];
|
|
452
431
|
for (const message of options.messages) {
|
|
453
432
|
const topic = message.topic;
|
|
454
|
-
let key;
|
|
455
|
-
let value;
|
|
456
433
|
let headers = new Map();
|
|
457
434
|
const serializedHeaders = new Map();
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
message.headers
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
for (const [key, value] of headers) {
|
|
466
|
-
serializedHeaders.set(this.#headerKeySerializer(key, metadata), this.#headerValueSerializer(value, metadata));
|
|
467
|
-
}
|
|
435
|
+
if (message.headers) {
|
|
436
|
+
headers =
|
|
437
|
+
message.headers instanceof Map
|
|
438
|
+
? message.headers
|
|
439
|
+
: new Map(Object.entries(message.headers));
|
|
440
|
+
for (const [key, value] of headers) {
|
|
441
|
+
serializedHeaders.set(this.#headerKeySerializer(key), this.#headerValueSerializer(value));
|
|
468
442
|
}
|
|
469
|
-
key = this.#keySerializer(message.key, headers, message);
|
|
470
|
-
value = this.#valueSerializer(message.value, headers, message);
|
|
471
|
-
}
|
|
472
|
-
catch (error) {
|
|
473
|
-
callback(new UserError('Failed to serialize a message.', { cause: error }));
|
|
474
|
-
return;
|
|
475
443
|
}
|
|
444
|
+
const key = this.#keySerializer(message.key, headers);
|
|
445
|
+
const value = this.#valueSerializer(message.value, headers);
|
|
476
446
|
let partition = 0;
|
|
477
447
|
if (typeof message.partition !== 'number') {
|
|
478
448
|
if (partitioner) {
|
|
@@ -652,34 +622,4 @@ export class Producer extends Base {
|
|
|
652
622
|
this.#transaction = undefined;
|
|
653
623
|
}
|
|
654
624
|
}
|
|
655
|
-
#beforeSerialization(hook, options, callback) {
|
|
656
|
-
// Create the pre-serialization requests
|
|
657
|
-
const requests = [];
|
|
658
|
-
for (const message of options.messages) {
|
|
659
|
-
requests.push([message.key, 'key', message]);
|
|
660
|
-
requests.push([message.value, 'value', message]);
|
|
661
|
-
if (typeof message.headers !== 'undefined') {
|
|
662
|
-
const headers = message.headers instanceof Map
|
|
663
|
-
? message.headers
|
|
664
|
-
: new Map(Object.entries(message.headers));
|
|
665
|
-
for (const [headerKey, headerValue] of headers) {
|
|
666
|
-
requests.push([headerKey, 'headerKey', message]);
|
|
667
|
-
requests.push([headerValue, 'headerValue', message]);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
runAsyncSeries((request, cb) => {
|
|
672
|
-
const [data, type, message] = request;
|
|
673
|
-
const result = hook(data, type, message, cb);
|
|
674
|
-
if (typeof result?.then === 'function') {
|
|
675
|
-
result.then(() => cb(null), cb);
|
|
676
|
-
}
|
|
677
|
-
}, requests, 0, error => {
|
|
678
|
-
if (error) {
|
|
679
|
-
callback(error);
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
this.#send(options, callback);
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
625
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type CompressionAlgorithmValue } from '../../protocol/compression.ts';
|
|
2
2
|
import { type MessageToProduce } from '../../protocol/records.ts';
|
|
3
|
-
import { type SchemaRegistry } from '../../registries/abstract.ts';
|
|
4
3
|
import { type BaseOptions, type TopicWithPartitionAndOffset } from '../base/types.ts';
|
|
5
|
-
import { type
|
|
4
|
+
import { type Serializers } from '../serde.ts';
|
|
6
5
|
export interface ProducerInfo {
|
|
7
6
|
producerId: bigint;
|
|
8
7
|
producerEpoch: number;
|
|
@@ -26,8 +25,6 @@ export interface ProduceOptions<Key, Value, HeaderKey, HeaderValue> {
|
|
|
26
25
|
export type ProducerOptions<Key, Value, HeaderKey, HeaderValue> = BaseOptions & ProduceOptions<Key, Value, HeaderKey, HeaderValue> & {
|
|
27
26
|
transactionalId?: string;
|
|
28
27
|
serializers?: Partial<Serializers<Key, Value, HeaderKey, HeaderValue>>;
|
|
29
|
-
beforeSerialization?: BeforeSerializationHook<Key, Value, HeaderKey, HeaderValue>;
|
|
30
|
-
registry?: SchemaRegistry<unknown, unknown, Key, Value, HeaderKey, HeaderValue>;
|
|
31
28
|
};
|
|
32
29
|
export type SendOptions<Key, Value, HeaderKey, HeaderValue> = {
|
|
33
30
|
messages: MessageToProduce<Key, Value, HeaderKey, HeaderValue>[];
|
package/dist/clients/serde.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export type
|
|
4
|
-
export type
|
|
5
|
-
export type Deserializer<OutputType = unknown> = (data?: Buffer, message?: MessageToConsume) => OutputType | undefined;
|
|
6
|
-
export type DeserializerWithHeaders<OutputType = unknown, HeaderKey = unknown, HeaderValue = unknown> = (data?: Buffer, headers?: Map<HeaderKey, HeaderValue>, message?: MessageToConsume) => OutputType | undefined;
|
|
1
|
+
export type Serializer<InputType = unknown> = (data?: InputType) => Buffer | undefined;
|
|
2
|
+
export type Deserializer<OutputType = unknown> = (data?: Buffer) => OutputType | undefined;
|
|
3
|
+
export type SerializerWithHeaders<InputType = unknown, HeaderKey = unknown, HeaderValue = unknown> = (data?: InputType, headers?: Map<HeaderKey, HeaderValue>) => Buffer | undefined;
|
|
4
|
+
export type DeserializerWithHeaders<OutputType = unknown, HeaderKey = unknown, HeaderValue = unknown> = (data?: Buffer, headers?: Map<HeaderKey, HeaderValue>) => OutputType | undefined;
|
|
7
5
|
export interface Serializers<Key, Value, HeaderKey, HeaderValue> {
|
|
8
6
|
key: SerializerWithHeaders<Key, HeaderKey, HeaderValue>;
|
|
9
7
|
value: SerializerWithHeaders<Value, HeaderKey, HeaderValue>;
|
|
@@ -11,14 +9,11 @@ export interface Serializers<Key, Value, HeaderKey, HeaderValue> {
|
|
|
11
9
|
headerValue: Serializer<HeaderValue>;
|
|
12
10
|
}
|
|
13
11
|
export interface Deserializers<Key, Value, HeaderKey, HeaderValue> {
|
|
14
|
-
key: DeserializerWithHeaders<Key
|
|
15
|
-
value: DeserializerWithHeaders<Value
|
|
12
|
+
key: DeserializerWithHeaders<Key>;
|
|
13
|
+
value: DeserializerWithHeaders<Value>;
|
|
16
14
|
headerKey: Deserializer<HeaderKey>;
|
|
17
15
|
headerValue: Deserializer<HeaderValue>;
|
|
18
16
|
}
|
|
19
|
-
export type BeforeHookPayloadType = 'key' | 'value' | 'headerKey' | 'headerValue';
|
|
20
|
-
export type BeforeDeserializationHook = (payload: Buffer, type: BeforeHookPayloadType, message: MessageToConsume, callback: Callback<void>) => void | Promise<void>;
|
|
21
|
-
export type BeforeSerializationHook<Key, Value, HeaderKey, HeaderValue> = (payload: unknown, type: BeforeHookPayloadType, message: MessageToProduce<Key, Value, HeaderKey, HeaderValue>, callback: Callback<void>) => void | Promise<void>;
|
|
22
17
|
export declare function stringSerializer(data?: string): Buffer | undefined;
|
|
23
18
|
export declare function stringDeserializer(data?: string | Buffer): string | undefined;
|
|
24
19
|
export declare function jsonSerializer<T = Record<string, any>>(data?: T): Buffer | undefined;
|