@platformatic/kafka 1.15.1 → 1.17.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.
- package/dist/apis/consumer/fetch-v15.js +5 -9
- package/dist/apis/consumer/fetch-v16.js +5 -9
- package/dist/apis/consumer/fetch-v17.js +3 -7
- package/dist/clients/base/options.d.ts +4 -0
- package/dist/clients/base/options.js +1 -0
- package/dist/errors.d.ts +5 -1
- package/dist/errors.js +7 -0
- package/dist/network/connection.d.ts +4 -3
- package/dist/network/connection.js +53 -39
- package/dist/protocol/crc32c.d.ts +3 -1
- package/dist/protocol/crc32c.js +20 -1
- package/dist/protocol/dynamic-buffer.js +21 -15
- package/dist/protocol/reader.d.ts +17 -15
- package/dist/protocol/reader.js +36 -30
- package/dist/protocol/records.d.ts +1 -0
- package/dist/protocol/records.js +10 -0
- package/dist/protocol/writer.js +2 -2
- package/dist/version.js +1 -1
- package/package.json +4 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ResponseError } from "../../errors.js";
|
|
2
2
|
import { Reader } from "../../protocol/reader.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readRecordsBatches } from "../../protocol/records.js";
|
|
4
4
|
import { Writer } from "../../protocol/writer.js";
|
|
5
5
|
import { createAPI } from "../definitions.js";
|
|
6
6
|
/*
|
|
@@ -99,17 +99,13 @@ export function parseResponse(_correlationId, apiKey, apiVersion, reader) {
|
|
|
99
99
|
}),
|
|
100
100
|
preferredReadReplica: r.readInt32()
|
|
101
101
|
};
|
|
102
|
-
let recordsSize = r.readUnsignedVarInt();
|
|
103
102
|
if (partition.errorCode !== 0) {
|
|
104
103
|
errors.push([`/responses/${i}/partitions/${j}`, partition.errorCode]);
|
|
105
104
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
partition.records =
|
|
110
|
-
do {
|
|
111
|
-
partition.records.push(readRecordsBatch(recordsBatchesReader));
|
|
112
|
-
} while (recordsBatchesReader.position < recordsSize);
|
|
105
|
+
// We need to reduce the size by one to follow the COMPACT_RECORDS specification.
|
|
106
|
+
const recordsSize = r.readUnsignedVarInt() - 1;
|
|
107
|
+
if (recordsSize > 0) {
|
|
108
|
+
partition.records = readRecordsBatches(Reader.from(r.buffer.subarray(r.position, r.position + recordsSize)));
|
|
113
109
|
r.skip(recordsSize);
|
|
114
110
|
}
|
|
115
111
|
return partition;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ResponseError } from "../../errors.js";
|
|
2
2
|
import { Reader } from "../../protocol/reader.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readRecordsBatches } from "../../protocol/records.js";
|
|
4
4
|
import { Writer } from "../../protocol/writer.js";
|
|
5
5
|
import { createAPI } from "../definitions.js";
|
|
6
6
|
/*
|
|
@@ -99,17 +99,13 @@ export function parseResponse(_correlationId, apiKey, apiVersion, reader) {
|
|
|
99
99
|
}),
|
|
100
100
|
preferredReadReplica: r.readInt32()
|
|
101
101
|
};
|
|
102
|
-
let recordsSize = r.readUnsignedVarInt();
|
|
103
102
|
if (partition.errorCode !== 0) {
|
|
104
103
|
errors.push([`/responses/${i}/partitions/${j}`, partition.errorCode]);
|
|
105
104
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
partition.records =
|
|
110
|
-
do {
|
|
111
|
-
partition.records.push(readRecordsBatch(recordsBatchesReader));
|
|
112
|
-
} while (recordsBatchesReader.position < recordsSize);
|
|
105
|
+
// We need to reduce the size by one to follow the COMPACT_RECORDS specification.
|
|
106
|
+
const recordsSize = r.readUnsignedVarInt() - 1;
|
|
107
|
+
if (recordsSize > 0) {
|
|
108
|
+
partition.records = readRecordsBatches(Reader.from(r.buffer.subarray(r.position, r.position + recordsSize)));
|
|
113
109
|
r.skip(recordsSize);
|
|
114
110
|
}
|
|
115
111
|
return partition;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ResponseError } from "../../errors.js";
|
|
2
2
|
import { Reader } from "../../protocol/reader.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readRecordsBatches } from "../../protocol/records.js";
|
|
4
4
|
import { Writer } from "../../protocol/writer.js";
|
|
5
5
|
import { createAPI } from "../definitions.js";
|
|
6
6
|
/*
|
|
@@ -102,14 +102,10 @@ export function parseResponse(_correlationId, apiKey, apiVersion, reader) {
|
|
|
102
102
|
if (partition.errorCode !== 0) {
|
|
103
103
|
errors.push([`/responses/${i}/partitions/${j}`, partition.errorCode]);
|
|
104
104
|
}
|
|
105
|
-
// We need to reduce the size by one to follow the COMPACT_RECORDS specification
|
|
105
|
+
// We need to reduce the size by one to follow the COMPACT_RECORDS specification.
|
|
106
106
|
const recordsSize = r.readUnsignedVarInt() - 1;
|
|
107
107
|
if (recordsSize > 0) {
|
|
108
|
-
|
|
109
|
-
partition.records = [];
|
|
110
|
-
do {
|
|
111
|
-
partition.records.push(readRecordsBatch(recordsBatchesReader));
|
|
112
|
-
} while (recordsBatchesReader.position < recordsSize);
|
|
108
|
+
partition.records = readRecordsBatches(Reader.from(r.buffer.subarray(r.position, r.position + recordsSize)));
|
|
113
109
|
r.skip(recordsSize);
|
|
114
110
|
}
|
|
115
111
|
return partition;
|
|
@@ -31,6 +31,7 @@ export const baseOptionsSchema = {
|
|
|
31
31
|
retries: { oneOf: [{ type: 'number', minimum: 0 }, { type: 'boolean' }] },
|
|
32
32
|
retryDelay: { type: 'number', minimum: 0 },
|
|
33
33
|
maxInflights: { type: 'number', minimum: 0 },
|
|
34
|
+
handleBackPressure: { type: 'boolean', default: false },
|
|
34
35
|
tls: { type: 'object', additionalProperties: true }, // No validation as they come from Node.js
|
|
35
36
|
tlsServerName: { oneOf: [{ type: 'boolean' }, { type: 'string' }] },
|
|
36
37
|
sasl: {
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
declare const kGenericError: unique symbol;
|
|
2
2
|
declare const kMultipleErrors: unique symbol;
|
|
3
3
|
export declare const ERROR_PREFIX = "PLT_KFK_";
|
|
4
|
-
export declare const errorCodes: readonly ["PLT_KFK_AUTHENTICATION", "PLT_KFK_MULTIPLE", "PLT_KFK_NETWORK", "PLT_KFK_PROTOCOL", "PLT_KFK_RESPONSE", "PLT_KFK_TIMEOUT", "PLT_KFK_UNEXPECTED_CORRELATION_ID", "PLT_KFK_UNFINISHED_WRITE_BUFFER", "PLT_KFK_UNSUPPORTED_API", "PLT_KFK_UNSUPPORTED_COMPRESSION", "PLT_KFK_UNSUPPORTED", "PLT_KFK_USER"];
|
|
4
|
+
export declare const errorCodes: readonly ["PLT_KFK_AUTHENTICATION", "PLT_KFK_MULTIPLE", "PLT_KFK_NETWORK", "PLT_KFK_OUT_OF_BOUNDS", "PLT_KFK_PROTOCOL", "PLT_KFK_RESPONSE", "PLT_KFK_TIMEOUT", "PLT_KFK_UNEXPECTED_CORRELATION_ID", "PLT_KFK_UNFINISHED_WRITE_BUFFER", "PLT_KFK_UNSUPPORTED_API", "PLT_KFK_UNSUPPORTED_COMPRESSION", "PLT_KFK_UNSUPPORTED", "PLT_KFK_USER"];
|
|
5
5
|
export type ErrorCode = (typeof errorCodes)[number];
|
|
6
6
|
export type ErrorProperties = {
|
|
7
7
|
cause?: Error;
|
|
@@ -38,6 +38,10 @@ export declare class ProtocolError extends GenericError {
|
|
|
38
38
|
static code: ErrorCode;
|
|
39
39
|
constructor(codeOrId: string | number, properties?: ErrorProperties, response?: unknown);
|
|
40
40
|
}
|
|
41
|
+
export declare class OutOfBoundsError extends GenericError {
|
|
42
|
+
static code: ErrorCode;
|
|
43
|
+
constructor(message: string, properties?: ErrorProperties);
|
|
44
|
+
}
|
|
41
45
|
export declare class ResponseError extends MultipleErrors {
|
|
42
46
|
static code: ErrorCode;
|
|
43
47
|
constructor(apiName: number, apiVersion: number, errors: Record<string, number>, response: unknown, properties?: ErrorProperties);
|
package/dist/errors.js
CHANGED
|
@@ -7,6 +7,7 @@ export const errorCodes = [
|
|
|
7
7
|
'PLT_KFK_AUTHENTICATION',
|
|
8
8
|
'PLT_KFK_MULTIPLE',
|
|
9
9
|
'PLT_KFK_NETWORK',
|
|
10
|
+
'PLT_KFK_OUT_OF_BOUNDS',
|
|
10
11
|
'PLT_KFK_PROTOCOL',
|
|
11
12
|
'PLT_KFK_RESPONSE',
|
|
12
13
|
'PLT_KFK_TIMEOUT',
|
|
@@ -117,6 +118,12 @@ export class ProtocolError extends GenericError {
|
|
|
117
118
|
});
|
|
118
119
|
}
|
|
119
120
|
}
|
|
121
|
+
export class OutOfBoundsError extends GenericError {
|
|
122
|
+
static code = 'PLT_KFK_OUT_OF_BOUNDS';
|
|
123
|
+
constructor(message, properties = {}) {
|
|
124
|
+
super(OutOfBoundsError.code, message, { isOut: true, ...properties });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
120
127
|
export class ResponseError extends MultipleErrors {
|
|
121
128
|
static code = 'PLT_KFK_RESPONSE';
|
|
122
129
|
constructor(apiName, apiVersion, errors, response, properties = {}) {
|
|
@@ -24,14 +24,15 @@ export interface ConnectionOptions {
|
|
|
24
24
|
tlsServerName?: string | boolean;
|
|
25
25
|
sasl?: SASLOptions;
|
|
26
26
|
ownerId?: number;
|
|
27
|
+
handleBackPressure?: boolean;
|
|
27
28
|
}
|
|
28
29
|
export interface Request {
|
|
29
30
|
correlationId: number;
|
|
30
31
|
apiKey: number;
|
|
31
32
|
apiVersion: number;
|
|
32
|
-
hasRequestHeaderTaggedFields: boolean;
|
|
33
33
|
hasResponseHeaderTaggedFields: boolean;
|
|
34
|
-
|
|
34
|
+
noResponse: boolean;
|
|
35
|
+
payload: Buffer;
|
|
35
36
|
parser: ResponseParser<unknown>;
|
|
36
37
|
callback: Callback<any>;
|
|
37
38
|
diagnostic: Record<string, unknown>;
|
|
@@ -62,5 +63,5 @@ export declare class Connection extends EventEmitter {
|
|
|
62
63
|
ready(): Promise<void>;
|
|
63
64
|
close(callback: CallbackWithPromise<void>): void;
|
|
64
65
|
close(): Promise<void>;
|
|
65
|
-
send<ReturnType>(apiKey: number, apiVersion: number,
|
|
66
|
+
send<ReturnType>(apiKey: number, apiVersion: number, createPayload: () => Writer, responseParser: ResponseParser<ReturnType>, hasRequestHeaderTaggedFields: boolean, hasResponseHeaderTaggedFields: boolean, callback: Callback<ReturnType>): void;
|
|
66
67
|
}
|
|
@@ -38,6 +38,7 @@ export class Connection extends EventEmitter {
|
|
|
38
38
|
#clientId;
|
|
39
39
|
// @ts-ignore This is used just for debugging
|
|
40
40
|
#ownerId;
|
|
41
|
+
#handleBackPressure;
|
|
41
42
|
#correlationId;
|
|
42
43
|
#nextMessage;
|
|
43
44
|
#afterDrainRequests;
|
|
@@ -56,6 +57,7 @@ export class Connection extends EventEmitter {
|
|
|
56
57
|
this.#status = ConnectionStatuses.NONE;
|
|
57
58
|
this.#clientId = clientId;
|
|
58
59
|
this.#ownerId = options.ownerId;
|
|
60
|
+
this.#handleBackPressure = options.handleBackPressure ?? false;
|
|
59
61
|
this.#correlationId = 0;
|
|
60
62
|
this.#nextMessage = 0;
|
|
61
63
|
this.#afterDrainRequests = [];
|
|
@@ -134,7 +136,9 @@ export class Connection extends EventEmitter {
|
|
|
134
136
|
this.#socket.removeListener('error', connectionErrorHandler);
|
|
135
137
|
this.#socket.on('error', this.#onError.bind(this));
|
|
136
138
|
this.#socket.on('data', this.#onData.bind(this));
|
|
137
|
-
|
|
139
|
+
if (this.#handleBackPressure) {
|
|
140
|
+
this.#socket.on('drain', this.#onDrain.bind(this));
|
|
141
|
+
}
|
|
138
142
|
this.#socket.on('close', this.#onClose.bind(this));
|
|
139
143
|
this.#socket.setTimeout(0);
|
|
140
144
|
if (this.#options.sasl) {
|
|
@@ -205,26 +209,43 @@ export class Connection extends EventEmitter {
|
|
|
205
209
|
this.#socket.end();
|
|
206
210
|
return callback[kCallbackPromise];
|
|
207
211
|
}
|
|
208
|
-
send(apiKey, apiVersion,
|
|
212
|
+
send(apiKey, apiVersion, createPayload, responseParser, hasRequestHeaderTaggedFields, hasResponseHeaderTaggedFields, callback) {
|
|
213
|
+
const correlationId = ++this.#correlationId;
|
|
214
|
+
const diagnostic = createDiagnosticContext({
|
|
215
|
+
connection: this,
|
|
216
|
+
operation: 'send',
|
|
217
|
+
apiKey,
|
|
218
|
+
apiVersion,
|
|
219
|
+
correlationId
|
|
220
|
+
});
|
|
221
|
+
const writer = Writer.create();
|
|
222
|
+
writer.appendInt16(apiKey).appendInt16(apiVersion).appendInt32(correlationId).appendString(this.#clientId, false);
|
|
223
|
+
if (hasRequestHeaderTaggedFields) {
|
|
224
|
+
writer.appendTaggedFields();
|
|
225
|
+
}
|
|
226
|
+
let payload;
|
|
227
|
+
try {
|
|
228
|
+
payload = createPayload();
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
diagnostic.error = err;
|
|
232
|
+
connectionsApiChannel.error.publish(diagnostic);
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
writer.appendFrom(payload).prependLength();
|
|
236
|
+
const request = {
|
|
237
|
+
correlationId,
|
|
238
|
+
apiKey,
|
|
239
|
+
apiVersion,
|
|
240
|
+
parser: responseParser,
|
|
241
|
+
payload: writer.buffer,
|
|
242
|
+
callback: null, // Will be set later
|
|
243
|
+
hasResponseHeaderTaggedFields,
|
|
244
|
+
noResponse: payload.context.noResponse ?? false,
|
|
245
|
+
diagnostic
|
|
246
|
+
};
|
|
209
247
|
this.#requestsQueue.push(fastQueueCallback => {
|
|
210
|
-
|
|
211
|
-
const request = {
|
|
212
|
-
correlationId,
|
|
213
|
-
apiKey,
|
|
214
|
-
apiVersion,
|
|
215
|
-
hasRequestHeaderTaggedFields,
|
|
216
|
-
hasResponseHeaderTaggedFields,
|
|
217
|
-
parser: responseParser,
|
|
218
|
-
payload,
|
|
219
|
-
callback: fastQueueCallback,
|
|
220
|
-
diagnostic: createDiagnosticContext({
|
|
221
|
-
connection: this,
|
|
222
|
-
operation: 'send',
|
|
223
|
-
apiKey,
|
|
224
|
-
apiVersion,
|
|
225
|
-
correlationId
|
|
226
|
-
})
|
|
227
|
-
};
|
|
248
|
+
request.callback = fastQueueCallback;
|
|
228
249
|
if (this.#socketMustBeDrained) {
|
|
229
250
|
this.#afterDrainRequests.push(request);
|
|
230
251
|
return false;
|
|
@@ -274,37 +295,31 @@ export class Connection extends EventEmitter {
|
|
|
274
295
|
request.callback(new NetworkError('Connection closed'), undefined);
|
|
275
296
|
return false;
|
|
276
297
|
}
|
|
277
|
-
|
|
278
|
-
writer
|
|
279
|
-
.appendInt16(request.apiKey)
|
|
280
|
-
.appendInt16(request.apiVersion)
|
|
281
|
-
.appendInt32(request.correlationId)
|
|
282
|
-
.appendString(this.#clientId, false);
|
|
283
|
-
if (request.hasRequestHeaderTaggedFields) {
|
|
284
|
-
writer.appendTaggedFields();
|
|
285
|
-
}
|
|
286
|
-
const payload = request.payload();
|
|
287
|
-
writer.appendFrom(payload).prependLength();
|
|
288
|
-
const expectResponse = !payload.context.noResponse;
|
|
289
|
-
if (expectResponse)
|
|
298
|
+
if (!request.noResponse) {
|
|
290
299
|
this.#inflightRequests.set(request.correlationId, request);
|
|
291
|
-
|
|
292
|
-
|
|
300
|
+
}
|
|
301
|
+
let canWrite = this.#socket.write(request.payload);
|
|
302
|
+
if (!this.#handleBackPressure) {
|
|
303
|
+
canWrite = true;
|
|
304
|
+
}
|
|
305
|
+
if (!canWrite) {
|
|
293
306
|
this.#socketMustBeDrained = true;
|
|
294
|
-
|
|
307
|
+
}
|
|
308
|
+
if (request.noResponse) {
|
|
295
309
|
request.callback(null, canWrite);
|
|
310
|
+
}
|
|
296
311
|
loggers.protocol('Sending request.', {
|
|
297
312
|
apiKey: protocolAPIsById[request.apiKey],
|
|
298
313
|
correlationId: request.correlationId,
|
|
299
314
|
request
|
|
300
315
|
});
|
|
301
316
|
return canWrite;
|
|
317
|
+
/* c8 ignore next 8 - Hard to test */
|
|
302
318
|
}
|
|
303
319
|
catch (err) {
|
|
304
320
|
request.diagnostic.error = err;
|
|
305
321
|
connectionsApiChannel.error.publish(request.diagnostic);
|
|
306
322
|
throw err;
|
|
307
|
-
/* c8 ignore next 3 - Hard to test */
|
|
308
323
|
}
|
|
309
324
|
finally {
|
|
310
325
|
connectionsApiChannel.end.publish(request.diagnostic);
|
|
@@ -453,8 +468,7 @@ export class Connection extends EventEmitter {
|
|
|
453
468
|
this.emit('close');
|
|
454
469
|
const error = new NetworkError('Connection closed');
|
|
455
470
|
for (const request of this.#afterDrainRequests) {
|
|
456
|
-
|
|
457
|
-
if (!payload.context.noResponse) {
|
|
471
|
+
if (!request.noResponse) {
|
|
458
472
|
request.callback(error, undefined);
|
|
459
473
|
}
|
|
460
474
|
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { DynamicBuffer } from './dynamic-buffer.ts';
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function loadNativeCRC32C(): typeof jsCRC32C | null;
|
|
3
|
+
export declare function jsCRC32C(data: Buffer | Uint8Array | DynamicBuffer): number;
|
|
4
|
+
export declare const crc32c: typeof jsCRC32C;
|
package/dist/protocol/crc32c.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Based on the work from: https://github.com/tulios/kafkajs/blob/master/src/protocol/recordBatch/crc32C/crc32C.js
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { DynamicBuffer } from "./dynamic-buffer.js";
|
|
3
4
|
/* prettier-ignore */
|
|
4
5
|
const CRC = [
|
|
@@ -67,7 +68,23 @@ const CRC = [
|
|
|
67
68
|
0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,
|
|
68
69
|
0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351
|
|
69
70
|
];
|
|
70
|
-
export function
|
|
71
|
+
export function loadNativeCRC32C() {
|
|
72
|
+
try {
|
|
73
|
+
const require = createRequire(import.meta.url);
|
|
74
|
+
const { crc32c: nativeImplementation } = require('@node-rs/crc32');
|
|
75
|
+
return function nativeCRC32C(data) {
|
|
76
|
+
const bytes = DynamicBuffer.isDynamicBuffer(data)
|
|
77
|
+
? data.buffer
|
|
78
|
+
: new Uint8Array(data);
|
|
79
|
+
return nativeImplementation(bytes);
|
|
80
|
+
};
|
|
81
|
+
/* c8 ignore next 3 - Hard to test */
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function jsCRC32C(data) {
|
|
71
88
|
const bytes = DynamicBuffer.isDynamicBuffer(data)
|
|
72
89
|
? data.buffer
|
|
73
90
|
: new Uint8Array(data);
|
|
@@ -77,3 +94,5 @@ export function crc32c(data) {
|
|
|
77
94
|
}
|
|
78
95
|
return (crc ^ 0xffffffff) >>> 0;
|
|
79
96
|
}
|
|
97
|
+
/* c8 ignore next - Hard to test */
|
|
98
|
+
export const crc32c = loadNativeCRC32C() ?? jsCRC32C;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OutOfBoundsError } from "../errors.js";
|
|
2
2
|
import { EMPTY_BUFFER, INT16_SIZE, INT32_SIZE, INT64_SIZE, INT8_SIZE } from "./definitions.js";
|
|
3
3
|
import { BITS_8PLUS_MASK, BITS_8PLUS_MASK_64, int64ZigZagDecode, int64ZigZagEncode, intZigZagDecode, intZigZagEncode, LEAST_SIGNIFICANT_7_BITS, LEAST_SIGNIFICANT_7_BITS_64, MOST_SIGNIFICANT_BIT_FLAG, MOST_SIGNIFICANT_BIT_FLAG_64, sizeOfUnsignedVarInt, sizeOfUnsignedVarInt64 } from "./varint.js";
|
|
4
4
|
const instanceIdentifier = Symbol('plt.kafka.dynamicBuffer.instanceIdentifier');
|
|
@@ -48,12 +48,18 @@ export class DynamicBuffer {
|
|
|
48
48
|
return this;
|
|
49
49
|
}
|
|
50
50
|
appendFrom(DynamicBuffer) {
|
|
51
|
-
|
|
51
|
+
const buffers = DynamicBuffer.buffers;
|
|
52
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
53
|
+
this.buffers.push(buffers[i]);
|
|
54
|
+
}
|
|
52
55
|
this.length += DynamicBuffer.length;
|
|
53
56
|
return this;
|
|
54
57
|
}
|
|
55
58
|
prependFrom(DynamicBuffer) {
|
|
56
|
-
|
|
59
|
+
const buffers = DynamicBuffer.buffers;
|
|
60
|
+
for (let i = buffers.length - 1; i >= 0; i--) {
|
|
61
|
+
this.buffers.unshift(buffers[i]);
|
|
62
|
+
}
|
|
57
63
|
this.length += DynamicBuffer.length;
|
|
58
64
|
return this;
|
|
59
65
|
}
|
|
@@ -62,7 +68,7 @@ export class DynamicBuffer {
|
|
|
62
68
|
end = this.length;
|
|
63
69
|
}
|
|
64
70
|
if (start < 0 || start > this.length || end > this.length) {
|
|
65
|
-
throw new
|
|
71
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
66
72
|
}
|
|
67
73
|
if (this.buffers.length === 0) {
|
|
68
74
|
return new DynamicBuffer(EMPTY_BUFFER);
|
|
@@ -93,13 +99,13 @@ export class DynamicBuffer {
|
|
|
93
99
|
end = this.length;
|
|
94
100
|
}
|
|
95
101
|
if (start < 0 || start > this.length || end > this.length) {
|
|
96
|
-
throw new
|
|
102
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
97
103
|
}
|
|
98
104
|
if (this.buffers.length === 0) {
|
|
99
105
|
return EMPTY_BUFFER;
|
|
100
106
|
}
|
|
101
107
|
else if (this.buffers.length === 1) {
|
|
102
|
-
return this.buffers[0].
|
|
108
|
+
return this.buffers[0].subarray(start, end);
|
|
103
109
|
}
|
|
104
110
|
let position = 0;
|
|
105
111
|
let length = end - start;
|
|
@@ -133,7 +139,7 @@ export class DynamicBuffer {
|
|
|
133
139
|
}
|
|
134
140
|
consume(offset) {
|
|
135
141
|
if (offset < 0 || offset > this.length) {
|
|
136
|
-
throw new
|
|
142
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
137
143
|
}
|
|
138
144
|
if (offset === 0) {
|
|
139
145
|
return this;
|
|
@@ -159,14 +165,14 @@ export class DynamicBuffer {
|
|
|
159
165
|
}
|
|
160
166
|
get(offset) {
|
|
161
167
|
if (offset < 0 || offset >= this.length) {
|
|
162
|
-
throw new
|
|
168
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
163
169
|
}
|
|
164
170
|
const [finalIndex, current] = this.#findInitialBuffer(offset);
|
|
165
171
|
return this.buffers[current][finalIndex];
|
|
166
172
|
}
|
|
167
173
|
readUInt8(offset = 0) {
|
|
168
174
|
if (offset < 0 || offset >= this.length) {
|
|
169
|
-
throw new
|
|
175
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
170
176
|
}
|
|
171
177
|
const [finalIndex, current] = this.#findInitialBuffer(offset);
|
|
172
178
|
this.#readBuffer[0] = this.buffers[current][finalIndex];
|
|
@@ -202,7 +208,7 @@ export class DynamicBuffer {
|
|
|
202
208
|
let value = 0;
|
|
203
209
|
let read = 0;
|
|
204
210
|
if (offset < 0 || offset >= this.length) {
|
|
205
|
-
throw new
|
|
211
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
206
212
|
}
|
|
207
213
|
// Find the initial buffer
|
|
208
214
|
let [startOffset, current] = this.#findInitialBuffer(offset);
|
|
@@ -224,7 +230,7 @@ export class DynamicBuffer {
|
|
|
224
230
|
let value = 0n;
|
|
225
231
|
let read = 0;
|
|
226
232
|
if (offset < 0 || offset >= this.length) {
|
|
227
|
-
throw new
|
|
233
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
228
234
|
}
|
|
229
235
|
// Find the initial buffer
|
|
230
236
|
let [startOffset, current] = this.#findInitialBuffer(offset);
|
|
@@ -242,7 +248,7 @@ export class DynamicBuffer {
|
|
|
242
248
|
}
|
|
243
249
|
readInt8(offset = 0) {
|
|
244
250
|
if (offset < 0 || offset >= this.length) {
|
|
245
|
-
throw new
|
|
251
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
246
252
|
}
|
|
247
253
|
const [finalIndex, current] = this.#findInitialBuffer(offset);
|
|
248
254
|
this.#readBuffer[0] = this.buffers[current][finalIndex];
|
|
@@ -374,7 +380,7 @@ export class DynamicBuffer {
|
|
|
374
380
|
return this;
|
|
375
381
|
}
|
|
376
382
|
writeUnsignedVarInt(value, append = true) {
|
|
377
|
-
const buffer = Buffer.
|
|
383
|
+
const buffer = Buffer.allocUnsafe(sizeOfUnsignedVarInt(value));
|
|
378
384
|
let position = 0;
|
|
379
385
|
while ((value & BITS_8PLUS_MASK) !== 0) {
|
|
380
386
|
buffer.writeUInt8((value & LEAST_SIGNIFICANT_7_BITS) | MOST_SIGNIFICANT_BIT_FLAG, position);
|
|
@@ -390,7 +396,7 @@ export class DynamicBuffer {
|
|
|
390
396
|
}
|
|
391
397
|
}
|
|
392
398
|
writeUnsignedVarInt64(value, append = true) {
|
|
393
|
-
const buffer = Buffer.
|
|
399
|
+
const buffer = Buffer.allocUnsafe(sizeOfUnsignedVarInt64(value));
|
|
394
400
|
let position = 0;
|
|
395
401
|
while ((value & BITS_8PLUS_MASK_64) !== 0n) {
|
|
396
402
|
buffer.writeUInt8(Number((value & LEAST_SIGNIFICANT_7_BITS_64) | MOST_SIGNIFICANT_BIT_FLAG_64), position);
|
|
@@ -543,7 +549,7 @@ export class DynamicBuffer {
|
|
|
543
549
|
}
|
|
544
550
|
#readMultiple(index, length) {
|
|
545
551
|
if (index < 0 || index + length > this.length) {
|
|
546
|
-
throw new
|
|
552
|
+
throw new OutOfBoundsError('Out of bounds.');
|
|
547
553
|
}
|
|
548
554
|
let [startOffset, current] = this.#findInitialBuffer(index);
|
|
549
555
|
for (let i = 0; i < length; i++) {
|
|
@@ -5,28 +5,30 @@ declare const instanceIdentifier: unique symbol;
|
|
|
5
5
|
export declare class Reader {
|
|
6
6
|
buffer: DynamicBuffer;
|
|
7
7
|
position: number;
|
|
8
|
+
length: number;
|
|
8
9
|
[instanceIdentifier]: boolean;
|
|
9
10
|
static isReader(target: any): boolean;
|
|
10
11
|
static from(buffer: Buffer | DynamicBuffer | Writer): Reader;
|
|
11
12
|
constructor(buffer: DynamicBuffer);
|
|
13
|
+
get remaining(): number;
|
|
12
14
|
reset(buffer?: Buffer | DynamicBuffer): void;
|
|
13
15
|
inspect(): string;
|
|
14
16
|
skip(length: number): this;
|
|
15
|
-
peekUnsignedInt8(): number;
|
|
16
|
-
peekUnsignedInt16(): number;
|
|
17
|
-
peekUnsignedInt32(): number;
|
|
18
|
-
peekUnsignedInt64(): bigint;
|
|
19
|
-
peekUnsignedVarInt(): number;
|
|
20
|
-
peekUnsignedVarInt64(): bigint;
|
|
21
|
-
peekInt8(): number;
|
|
22
|
-
peekInt16(): number;
|
|
23
|
-
peekInt32(): number;
|
|
24
|
-
peekInt64(): bigint;
|
|
25
|
-
peekFloat64(): number;
|
|
26
|
-
peekVarInt(): number;
|
|
27
|
-
peekVarInt64(): bigint;
|
|
28
|
-
peekBoolean(): boolean;
|
|
29
|
-
peekUUID(): string;
|
|
17
|
+
peekUnsignedInt8(position?: number): number;
|
|
18
|
+
peekUnsignedInt16(position?: number): number;
|
|
19
|
+
peekUnsignedInt32(position?: number): number;
|
|
20
|
+
peekUnsignedInt64(position?: number): bigint;
|
|
21
|
+
peekUnsignedVarInt(position?: number): number;
|
|
22
|
+
peekUnsignedVarInt64(position?: number): bigint;
|
|
23
|
+
peekInt8(position?: number): number;
|
|
24
|
+
peekInt16(position?: number): number;
|
|
25
|
+
peekInt32(position?: number): number;
|
|
26
|
+
peekInt64(position?: number): bigint;
|
|
27
|
+
peekFloat64(position?: number): number;
|
|
28
|
+
peekVarInt(position?: number): number;
|
|
29
|
+
peekVarInt64(position?: number): bigint;
|
|
30
|
+
peekBoolean(position?: number): boolean;
|
|
31
|
+
peekUUID(position?: number): string;
|
|
30
32
|
readUnsignedInt8(): number;
|
|
31
33
|
readUnsignedInt16(): number;
|
|
32
34
|
readUnsignedInt32(): number;
|
package/dist/protocol/reader.js
CHANGED
|
@@ -5,6 +5,7 @@ const instanceIdentifier = Symbol('plt.kafka.reader.instanceIdentifier');
|
|
|
5
5
|
export class Reader {
|
|
6
6
|
buffer;
|
|
7
7
|
position;
|
|
8
|
+
length;
|
|
8
9
|
[instanceIdentifier];
|
|
9
10
|
static isReader(target) {
|
|
10
11
|
return target?.[instanceIdentifier] === true;
|
|
@@ -21,8 +22,12 @@ export class Reader {
|
|
|
21
22
|
constructor(buffer) {
|
|
22
23
|
this.buffer = buffer;
|
|
23
24
|
this.position = 0;
|
|
25
|
+
this.length = this.buffer.length;
|
|
24
26
|
this[instanceIdentifier] = true;
|
|
25
27
|
}
|
|
28
|
+
get remaining() {
|
|
29
|
+
return this.length - this.position;
|
|
30
|
+
}
|
|
26
31
|
reset(buffer) {
|
|
27
32
|
if (buffer) {
|
|
28
33
|
if (Buffer.isBuffer(buffer)) {
|
|
@@ -43,50 +48,51 @@ export class Reader {
|
|
|
43
48
|
this.position += length;
|
|
44
49
|
return this;
|
|
45
50
|
}
|
|
46
|
-
peekUnsignedInt8() {
|
|
47
|
-
return this.buffer.readUInt8(this.position);
|
|
51
|
+
peekUnsignedInt8(position) {
|
|
52
|
+
return this.buffer.readUInt8(position ?? this.position);
|
|
48
53
|
}
|
|
49
|
-
peekUnsignedInt16() {
|
|
50
|
-
return this.buffer.readUInt16BE(this.position);
|
|
54
|
+
peekUnsignedInt16(position) {
|
|
55
|
+
return this.buffer.readUInt16BE(position ?? this.position);
|
|
51
56
|
}
|
|
52
|
-
peekUnsignedInt32() {
|
|
53
|
-
return this.buffer.readUInt32BE(this.position);
|
|
57
|
+
peekUnsignedInt32(position) {
|
|
58
|
+
return this.buffer.readUInt32BE(position ?? this.position);
|
|
54
59
|
}
|
|
55
|
-
peekUnsignedInt64() {
|
|
56
|
-
return this.buffer.readBigUInt64BE(this.position);
|
|
60
|
+
peekUnsignedInt64(position) {
|
|
61
|
+
return this.buffer.readBigUInt64BE(position ?? this.position);
|
|
57
62
|
}
|
|
58
|
-
peekUnsignedVarInt() {
|
|
59
|
-
return this.buffer.readUnsignedVarInt(this.position)[0];
|
|
63
|
+
peekUnsignedVarInt(position) {
|
|
64
|
+
return this.buffer.readUnsignedVarInt(position ?? this.position)[0];
|
|
60
65
|
}
|
|
61
|
-
peekUnsignedVarInt64() {
|
|
62
|
-
return this.buffer.readUnsignedVarInt64(this.position)[0];
|
|
66
|
+
peekUnsignedVarInt64(position) {
|
|
67
|
+
return this.buffer.readUnsignedVarInt64(position ?? this.position)[0];
|
|
63
68
|
}
|
|
64
|
-
peekInt8() {
|
|
65
|
-
return this.buffer.readInt8(this.position);
|
|
69
|
+
peekInt8(position) {
|
|
70
|
+
return this.buffer.readInt8(position ?? this.position);
|
|
66
71
|
}
|
|
67
|
-
peekInt16() {
|
|
68
|
-
return this.buffer.readInt16BE(this.position);
|
|
72
|
+
peekInt16(position) {
|
|
73
|
+
return this.buffer.readInt16BE(position ?? this.position);
|
|
69
74
|
}
|
|
70
|
-
peekInt32() {
|
|
71
|
-
return this.buffer.readInt32BE(this.position);
|
|
75
|
+
peekInt32(position) {
|
|
76
|
+
return this.buffer.readInt32BE(position ?? this.position);
|
|
72
77
|
}
|
|
73
|
-
peekInt64() {
|
|
74
|
-
return this.buffer.readBigInt64BE(this.position);
|
|
78
|
+
peekInt64(position) {
|
|
79
|
+
return this.buffer.readBigInt64BE(position ?? this.position);
|
|
75
80
|
}
|
|
76
|
-
peekFloat64() {
|
|
77
|
-
return this.buffer.readDoubleBE(this.position);
|
|
81
|
+
peekFloat64(position) {
|
|
82
|
+
return this.buffer.readDoubleBE(position ?? this.position);
|
|
78
83
|
}
|
|
79
|
-
peekVarInt() {
|
|
80
|
-
return this.buffer.readVarInt(this.position)[0];
|
|
84
|
+
peekVarInt(position) {
|
|
85
|
+
return this.buffer.readVarInt(position ?? this.position)[0];
|
|
81
86
|
}
|
|
82
|
-
peekVarInt64() {
|
|
83
|
-
return this.buffer.readVarInt64(this.position)[0];
|
|
87
|
+
peekVarInt64(position) {
|
|
88
|
+
return this.buffer.readVarInt64(position ?? this.position)[0];
|
|
84
89
|
}
|
|
85
|
-
peekBoolean() {
|
|
86
|
-
return this.buffer.readInt8(this.position) === 1;
|
|
90
|
+
peekBoolean(position) {
|
|
91
|
+
return this.buffer.readInt8(position ?? this.position) === 1;
|
|
87
92
|
}
|
|
88
|
-
peekUUID() {
|
|
89
|
-
|
|
93
|
+
peekUUID(position) {
|
|
94
|
+
position ??= this.position;
|
|
95
|
+
return this.buffer.toString('hex', position, position + UUID_SIZE);
|
|
90
96
|
}
|
|
91
97
|
readUnsignedInt8() {
|
|
92
98
|
const value = this.peekUnsignedInt8();
|
|
@@ -108,3 +108,4 @@ export declare function createRecord(message: MessageRecord, offsetDelta: number
|
|
|
108
108
|
export declare function readRecord(reader: Reader): KafkaRecord;
|
|
109
109
|
export declare function createRecordsBatch(messages: MessageRecord[], options?: Partial<CreateRecordsBatchOptions>): Writer;
|
|
110
110
|
export declare function readRecordsBatch(reader: Reader): RecordsBatch;
|
|
111
|
+
export declare function readRecordsBatches(reader: Reader): RecordsBatch[];
|
package/dist/protocol/records.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Writer } from "./writer.js";
|
|
|
8
8
|
const CURRENT_RECORD_VERSION = 2;
|
|
9
9
|
const IS_TRANSACTIONAL = 0b10000; // Bit 4 set
|
|
10
10
|
const IS_COMPRESSED = 0b111; // Bits 0, 1 and/or 2 set
|
|
11
|
+
const BATCH_HEAD = INT64_SIZE + INT32_SIZE; // FirstOffset + Length
|
|
11
12
|
export const messageSchema = {
|
|
12
13
|
type: 'object',
|
|
13
14
|
properties: {
|
|
@@ -117,6 +118,7 @@ export function createRecordsBatch(messages, options = {}) {
|
|
|
117
118
|
// FirstOffset is 0
|
|
118
119
|
.appendInt64(0n, false));
|
|
119
120
|
}
|
|
121
|
+
// TODO: Early bail out if there are not enough bytes to read all the records as it might be truncated
|
|
120
122
|
export function readRecordsBatch(reader) {
|
|
121
123
|
const initialPosition = reader.position;
|
|
122
124
|
const batch = {
|
|
@@ -155,3 +157,11 @@ export function readRecordsBatch(reader) {
|
|
|
155
157
|
}
|
|
156
158
|
return batch;
|
|
157
159
|
}
|
|
160
|
+
export function readRecordsBatches(reader) {
|
|
161
|
+
const batches = [];
|
|
162
|
+
while (reader.remaining >= BATCH_HEAD &&
|
|
163
|
+
reader.remaining - BATCH_HEAD >= reader.peekInt32(reader.position + INT64_SIZE)) {
|
|
164
|
+
batches.push(readRecordsBatch(reader));
|
|
165
|
+
}
|
|
166
|
+
return batches;
|
|
167
|
+
}
|
package/dist/protocol/writer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { humanize } from "../utils.js";
|
|
2
|
-
import { EMPTY_UUID } from "./definitions.js";
|
|
2
|
+
import { EMPTY_TAGGED_FIELDS_BUFFER, EMPTY_UUID } from "./definitions.js";
|
|
3
3
|
import { DynamicBuffer } from "./dynamic-buffer.js";
|
|
4
4
|
const instanceIdentifier = Symbol('plt.kafka.writer.instanceIdentifier');
|
|
5
5
|
export class Writer {
|
|
@@ -212,7 +212,7 @@ export class Writer {
|
|
|
212
212
|
}
|
|
213
213
|
// TODO(ShogunPanda): Tagged fields are not supported yet
|
|
214
214
|
appendTaggedFields(_ = []) {
|
|
215
|
-
return this.
|
|
215
|
+
return this.append(EMPTY_TAGGED_FIELDS_BUFFER);
|
|
216
216
|
}
|
|
217
217
|
prependLength() {
|
|
218
218
|
return this.appendInt32(this.length, false);
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const name = "@platformatic/kafka";
|
|
2
|
-
export const version = "1.
|
|
2
|
+
export const version = "1.17.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/kafka",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.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)",
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
"scule": "^1.3.0"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
+
"@node-rs/crc32": "^1.10.6",
|
|
35
36
|
"lz4-napi": "^2.9.0",
|
|
36
37
|
"snappy": "^7.3.3"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
40
|
+
"@confluentinc/kafka-javascript": "^1.5.0",
|
|
39
41
|
"@platformatic/rdkafka": "^4.0.0",
|
|
40
42
|
"@types/debug": "^4.1.12",
|
|
41
43
|
"@types/node": "^22.18.5",
|
|
@@ -48,10 +50,9 @@
|
|
|
48
50
|
"eslint": "^9.35.0",
|
|
49
51
|
"fast-jwt": "^6.0.2",
|
|
50
52
|
"hwp": "^0.4.1",
|
|
51
|
-
"json5": "^2.2.3",
|
|
52
53
|
"kafkajs": "^2.2.4",
|
|
54
|
+
"json5": "^2.2.3",
|
|
53
55
|
"neostandard": "^0.12.2",
|
|
54
|
-
"node-rdkafka": "^3.5.0",
|
|
55
56
|
"parse5": "^7.3.0",
|
|
56
57
|
"prettier": "^3.6.2",
|
|
57
58
|
"prettier-plugin-space-before-function-paren": "^0.0.8",
|