apache-iggy 0.10.0-edge.3 → 0.10.0-edge.4
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/client/client.connection.js +2 -2
- package/dist/client/client.connection.test.js +2 -2
- package/dist/client/client.frame.test.js +2 -2
- package/dist/client/client.socket.test.js +3 -3
- package/dist/wire/command.code.d.ts +0 -2
- package/dist/wire/command.code.js +0 -2
- package/dist/wire/error.code.js +88 -13
- package/dist/wire/error.code.test.js +7 -7
- package/dist/wire/message/iggy-header.utils.d.ts +95 -24
- package/dist/wire/message/iggy-header.utils.js +90 -43
- package/dist/wire/message/message-batch.test.d.ts +2 -0
- package/dist/wire/message/message-batch.test.js +173 -0
- package/dist/wire/message/message.utils.d.ts +25 -22
- package/dist/wire/message/message.utils.js +81 -50
- package/dist/wire/message/poll-messages.command.test.js +1 -1
- package/dist/wire/message/poll.utils.d.ts +36 -3
- package/dist/wire/message/poll.utils.js +62 -27
- package/dist/wire/message/send-messages.command.test.js +3 -1
- package/dist/wire/offset/delete-offset.command.js +2 -2
- package/dist/wire/offset/offset.utils.d.ts +10 -0
- package/dist/wire/offset/offset.utils.js +17 -1
- package/dist/wire/vsr/header.d.ts +2 -2
- package/dist/wire/vsr/header.js +3 -3
- package/dist/wire/vsr/header.test.js +3 -3
- package/dist/wire/vsr/operation.d.ts +0 -2
- package/dist/wire/vsr/operation.js +2 -8
- package/dist/wire/vsr/operation.test.js +2 -6
- package/dist/wire/vsr/reply.js +3 -3
- package/dist/wire/vsr/reply.test.js +4 -4
- package/dist/wire/vsr/vsr.test.js +1 -1
- package/package.json +2 -1
|
@@ -21,7 +21,7 @@ import { connect as TLSConnect } from 'node:tls';
|
|
|
21
21
|
import { debug } from './client.debug.js';
|
|
22
22
|
import { DEFAULT_MAX_RESPONSE_FRAME_SIZE } from './client.config.js';
|
|
23
23
|
import { ProtocolFrameError, ResponseFrameDecoder } from './client.frame.js';
|
|
24
|
-
import {
|
|
24
|
+
import { Command, peekCommand } from '../wire/vsr/header.js';
|
|
25
25
|
import { evictionError } from '../wire/vsr/reply.js';
|
|
26
26
|
/**
|
|
27
27
|
* Creates a TCP socket connection.
|
|
@@ -360,7 +360,7 @@ export class IggyConnection extends EventEmitter {
|
|
|
360
360
|
debug('ONDATA', typeof data, Buffer.isBuffer(data), data?.length, this.responseDecoder.hasBufferedData);
|
|
361
361
|
try {
|
|
362
362
|
for (const response of this.responseDecoder.push(data)) {
|
|
363
|
-
if (peekCommand(response) ===
|
|
363
|
+
if (peekCommand(response) === Command.Eviction)
|
|
364
364
|
this.emit('eviction', evictionError(response));
|
|
365
365
|
else
|
|
366
366
|
this.emit('response', response);
|
|
@@ -21,7 +21,7 @@ import { createServer, } from 'node:net';
|
|
|
21
21
|
import { describe, it } from 'node:test';
|
|
22
22
|
import { ProtocolFrameError } from './client.frame.js';
|
|
23
23
|
import { IggyConnection } from './client.connection.js';
|
|
24
|
-
import {
|
|
24
|
+
import { Command, HEADER_SIZE, REPLY_OFFSET } from '../wire/vsr/header.js';
|
|
25
25
|
const FRAME_LIMIT = 2 * HEADER_SIZE;
|
|
26
26
|
const startServer = async () => {
|
|
27
27
|
const server = createServer();
|
|
@@ -42,7 +42,7 @@ const connectionConfig = (server) => ({
|
|
|
42
42
|
const replyFrame = (body) => {
|
|
43
43
|
const frame = Buffer.alloc(HEADER_SIZE + body.length);
|
|
44
44
|
frame.writeUInt32LE(frame.length, REPLY_OFFSET.size);
|
|
45
|
-
frame.writeUInt8(
|
|
45
|
+
frame.writeUInt8(Command.Reply, REPLY_OFFSET.command);
|
|
46
46
|
body.copy(frame, HEADER_SIZE);
|
|
47
47
|
return frame;
|
|
48
48
|
};
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
import assert from 'node:assert/strict';
|
|
18
18
|
import { describe, it } from 'node:test';
|
|
19
19
|
import { extractResponseFrames, ProtocolFrameError, ResponseFrameDecoder } from './client.frame.js';
|
|
20
|
-
import {
|
|
20
|
+
import { Command, HEADER_SIZE, REPLY_OFFSET } from '../wire/vsr/header.js';
|
|
21
21
|
const LIMIT = 1024;
|
|
22
22
|
const vsrFrame = (body) => {
|
|
23
23
|
const frame = Buffer.alloc(HEADER_SIZE + body.length);
|
|
24
24
|
frame.writeUInt32LE(frame.length, REPLY_OFFSET.size);
|
|
25
|
-
frame.writeUInt8(
|
|
25
|
+
frame.writeUInt8(Command.Reply, REPLY_OFFSET.command);
|
|
26
26
|
body.copy(frame, HEADER_SIZE);
|
|
27
27
|
return frame;
|
|
28
28
|
};
|
|
@@ -23,7 +23,7 @@ import { describe, it } from 'node:test';
|
|
|
23
23
|
import { createServer as createTlsServer } from 'node:tls';
|
|
24
24
|
import { COMMAND_CODE } from '../wire/command.code.js';
|
|
25
25
|
import { ResponseError } from '../wire/error.utils.js';
|
|
26
|
-
import {
|
|
26
|
+
import { Command, EVICTION_OFFSET, EvictionReason, HEADER_SIZE, REPLY_OFFSET, REQUEST_OFFSET } from '../wire/vsr/header.js';
|
|
27
27
|
import { Operation } from '../wire/vsr/operation.js';
|
|
28
28
|
import { VsrEvictionError } from '../wire/vsr/reply.js';
|
|
29
29
|
import { CommandResponseStream } from './client.socket.js';
|
|
@@ -64,7 +64,7 @@ const startVsrServer = async (handler, transport = 'TCP') => {
|
|
|
64
64
|
const replyFrame = (operation, body = Buffer.alloc(0), status = 0) => {
|
|
65
65
|
const frame = Buffer.alloc(HEADER_SIZE + body.length);
|
|
66
66
|
frame.writeUInt32LE(frame.length, REPLY_OFFSET.size);
|
|
67
|
-
frame.writeUInt8(
|
|
67
|
+
frame.writeUInt8(Command.Reply, REPLY_OFFSET.command);
|
|
68
68
|
frame.writeUInt8(operation, REPLY_OFFSET.operation);
|
|
69
69
|
frame.writeUInt32LE(status, REPLY_OFFSET.status);
|
|
70
70
|
body.copy(frame, HEADER_SIZE);
|
|
@@ -73,7 +73,7 @@ const replyFrame = (operation, body = Buffer.alloc(0), status = 0) => {
|
|
|
73
73
|
const evictionFrame = (reason) => {
|
|
74
74
|
const frame = Buffer.alloc(HEADER_SIZE);
|
|
75
75
|
frame.writeUInt32LE(HEADER_SIZE, REPLY_OFFSET.size);
|
|
76
|
-
frame.writeUInt8(
|
|
76
|
+
frame.writeUInt8(Command.Eviction, REPLY_OFFSET.command);
|
|
77
77
|
frame.writeUInt8(reason, EVICTION_OFFSET.reason);
|
|
78
78
|
return frame;
|
|
79
79
|
};
|
package/dist/wire/error.code.js
CHANGED
|
@@ -35,11 +35,15 @@ export const translateErrorCode = (code) => {
|
|
|
35
35
|
case '16': return "Invalid state entry checksum: {0}, expected: {1}, for index: {2}";
|
|
36
36
|
case '19': return "Cannot open database, Path: {0}";
|
|
37
37
|
case '20': return "Resource with key: {0} was not found.";
|
|
38
|
+
case '21': return "Cannot close WebSocket connection";
|
|
38
39
|
case '30': return "Stale client";
|
|
39
40
|
case '31': return "TCP error";
|
|
40
41
|
case '32': return "QUIC error";
|
|
41
42
|
case '33': return "Invalid server address";
|
|
42
43
|
case '34': return "Invalid client address";
|
|
44
|
+
case '35': return "Invalid IP address: {0}:{1}";
|
|
45
|
+
case '36': return "Http error {0}";
|
|
46
|
+
case '37': return "Invalid API URL: {0}";
|
|
43
47
|
case '40': return "Unauthenticated";
|
|
44
48
|
case '41': return "Unauthorized";
|
|
45
49
|
case '42': return "Invalid credentials";
|
|
@@ -56,6 +60,10 @@ export const translateErrorCode = (code) => {
|
|
|
56
60
|
case '53': return "Invalid personal access token";
|
|
57
61
|
case '54': return "Personal access token: {0} for user with ID: {1} has expired.";
|
|
58
62
|
case '55': return "Users limit reached.";
|
|
63
|
+
case '56': return "Invalid personal access token expiry";
|
|
64
|
+
case '57': return "Request transiently not committed; retry";
|
|
65
|
+
case '58': return "Request transiently not accepted; retry, on any replica";
|
|
66
|
+
case '59': return "Request already applied; its reply is no longer available";
|
|
59
67
|
case '61': return "Not connected";
|
|
60
68
|
case '63': return "Client shutdown";
|
|
61
69
|
case '64': return "Invalid TLS domain";
|
|
@@ -71,6 +79,7 @@ export const translateErrorCode = (code) => {
|
|
|
71
79
|
case '76': return "Cannot generate JWT";
|
|
72
80
|
case '77': return "Access token is missing";
|
|
73
81
|
case '78': return "Invalid access token";
|
|
82
|
+
case '79': return "Cannot fetch JWKS from URL: {0}";
|
|
74
83
|
case '80': return "Invalid size bytes";
|
|
75
84
|
case '81': return "Invalid UTF-8";
|
|
76
85
|
case '82': return "Invalid number encoding";
|
|
@@ -87,6 +96,11 @@ export const translateErrorCode = (code) => {
|
|
|
87
96
|
case '304': return "Empty response";
|
|
88
97
|
case '305': return "Cannot create endpoint";
|
|
89
98
|
case '306': return "Cannot parse URL";
|
|
99
|
+
case '400': return "WebSocket error";
|
|
100
|
+
case '401': return "WebSocket connection error";
|
|
101
|
+
case '402': return "WebSocket close error";
|
|
102
|
+
case '403': return "WebSocket receive error";
|
|
103
|
+
case '404': return "WebSocket send error";
|
|
90
104
|
// STREAM
|
|
91
105
|
case '1000': return "Cannot create streams directory, Path: {0}";
|
|
92
106
|
case '1001': return "Cannot create stream with ID: {0} directory, Path: {1}";
|
|
@@ -99,15 +113,13 @@ export const translateErrorCode = (code) => {
|
|
|
99
113
|
case '1008': return "Failed to delete stream directory with ID: {0}";
|
|
100
114
|
case '1009': return "Stream with ID: {0} was not found.";
|
|
101
115
|
case '1010': return "Stream with name: {0} was not found.";
|
|
102
|
-
case '1011': return "Stream with
|
|
116
|
+
case '1011': return "Stream with directory {0} was not found.";
|
|
103
117
|
case '1012': return "Stream with name: {0} already exists.";
|
|
104
118
|
case '1013': return "Invalid stream name";
|
|
105
119
|
case '1014': return "Invalid stream ID";
|
|
106
120
|
case '1015': return "Cannot read streams";
|
|
107
|
-
case '1016': return "Missing streams";
|
|
108
|
-
case '1017': return "Missing topics for stream with ID: {0}";
|
|
109
|
-
case '1018': return "Missing partitions for topic with ID: {0} for stream with ID: {1}.";
|
|
110
121
|
case '1019': return "Max topic size cannot be lower than segment size. Max topic size: {0} < segment size: {1}.";
|
|
122
|
+
case '1020': return "Too many streams";
|
|
111
123
|
case '2000': return "Cannot create topics directory for stream with ID: {0}, Path: {1}";
|
|
112
124
|
case '2001': return "Failed to create directory for topic with ID: {0} for stream with ID: {1}, Path: {2}";
|
|
113
125
|
case '2002': return "Failed to create topic info file for topic with ID: {0} for stream with ID: {1}.";
|
|
@@ -120,13 +132,15 @@ export const translateErrorCode = (code) => {
|
|
|
120
132
|
case '2009': return "Cannot poll topic";
|
|
121
133
|
case '2010': return "Topic with ID: {0} for stream with ID: {1} was not found.";
|
|
122
134
|
case '2011': return "Topic with name: {0} for stream with ID: {1} was not found.";
|
|
123
|
-
case '2012': return "Topic with ID: {0} for stream with ID: {1} already exists.";
|
|
124
135
|
case '2013': return "Topic with name: {0} for stream with ID: {1} already exists.";
|
|
125
136
|
case '2014': return "Invalid topic name";
|
|
126
137
|
case '2015': return "Too many partitions";
|
|
127
138
|
case '2016': return "Invalid topic ID";
|
|
128
139
|
case '2017': return "Cannot read topics for stream with ID: {0}";
|
|
129
140
|
case '2018': return "Invalid replication factor";
|
|
141
|
+
case '2019': return "Invalid partitions count";
|
|
142
|
+
case '2020': return "Topic directory: {0} not found";
|
|
143
|
+
case '2021': return "Too many topics";
|
|
130
144
|
// TOPIC
|
|
131
145
|
case '3000': return "Cannot create partition with ID: {0} for stream with ID: {1} and topic with ID: {2}";
|
|
132
146
|
case '3001': return "Failed to create directory for partitions for stream with ID: {0} and topic with ID: {1}";
|
|
@@ -143,6 +157,9 @@ export const translateErrorCode = (code) => {
|
|
|
143
157
|
case '3012': return "Failed to create consumer offsets directory for path: {0}";
|
|
144
158
|
case '3020': return "Failed to read consumers offsets from path: {0}";
|
|
145
159
|
case '3021': return "Consumer offset for consumer with ID: {0} was not found.";
|
|
160
|
+
case '3022': return "Failed to resolve consumer with ID: {0}";
|
|
161
|
+
case '3023': return "Cannot open consumer offsets file for path: {0}";
|
|
162
|
+
case '3013': return "Partition id space exhausted for this topic";
|
|
146
163
|
// MESSAGE
|
|
147
164
|
case '4000': return "Segment not found";
|
|
148
165
|
case '4001': return "Segment with start offset: {0} and partition with ID: {1} is closed";
|
|
@@ -172,17 +189,75 @@ export const translateErrorCode = (code) => {
|
|
|
172
189
|
case '4025': return "Invalid message payload length";
|
|
173
190
|
case '4026': return "Cannot read message checksum";
|
|
174
191
|
case '4027': return "Invalid message checksum: {0}, expected: {1}, for offset: {2}";
|
|
175
|
-
case '4028': return "
|
|
192
|
+
case '4028': return "Invalid key value length";
|
|
193
|
+
case '4029': return "Command length error: {0}";
|
|
194
|
+
case '4030': return "Incorrect Segments Count size: {0}";
|
|
195
|
+
case '4031': return "Non-zero offset: {0} at index: {1}";
|
|
196
|
+
case '4032': return "Non-zero timestamp: {0} at index: {1}";
|
|
197
|
+
case '4033': return "Missing index: {0}";
|
|
198
|
+
case '4034': return "Invalid indexes byte size: {0}B, should be divisible by 16";
|
|
199
|
+
case '4035': return "Invalid indexes count: {0}, expected: {1}";
|
|
200
|
+
case '4036': return "Invalid messages byte size: {0}B, expected: {1}B";
|
|
201
|
+
case '4037': return "Too small message: {0}B, expected: {1}B";
|
|
202
|
+
case '4038': return "Invalid message timestamp delta: {0} microseconds exceeds the per-batch limit";
|
|
203
|
+
case '4039': return "Invalid batch checksum: {0}, expected: {1}, for base offset: {2}";
|
|
204
|
+
case '4040': return "Invalid header kind code: {0}";
|
|
205
|
+
case '4041': return "Unsupported option key: {0}";
|
|
206
|
+
case '4042': return "Invalid option value for key: {0}";
|
|
207
|
+
case '4043': return "Options block exceeds its limits: {0}";
|
|
208
|
+
case '4050': return "Cannot sed messages due to client disconnection";
|
|
209
|
+
case '4051': return "Background send error";
|
|
210
|
+
case '4052': return "Background send timeout";
|
|
211
|
+
case '4053': return "Background send buffer is full";
|
|
212
|
+
case '4054': return "Background worker disconnected";
|
|
213
|
+
case '4055': return "Background send buffer overflow";
|
|
214
|
+
case '4057': return "Producer closed";
|
|
215
|
+
case '4100': return "Invalid offset: {0}";
|
|
216
|
+
case '4101': return "Invalid reserved field value: {0}, expected: 0";
|
|
176
217
|
// CONSUMER GROUP
|
|
177
|
-
case '5000': return "Consumer group ID not found";
|
|
218
|
+
case '5000': return "Consumer group with ID: {0} for topic with ID: {1} was not found.";
|
|
178
219
|
case '5002': return "Invalid consumer group ID";
|
|
179
|
-
case '5003': return "Consumer group name not found";
|
|
180
|
-
case '5004': return "Consumer group name already exists";
|
|
220
|
+
case '5003': return "Consumer group with name: {0} for topic with ID: {1} was not found.";
|
|
221
|
+
case '5004': return "Consumer group with name: {0} for topic with ID: {1} already exists.";
|
|
181
222
|
case '5005': return "Invalid consumer group name";
|
|
182
|
-
case '5006': return "Consumer group member not found";
|
|
183
|
-
case '5007': return "
|
|
184
|
-
case '5008': return "
|
|
185
|
-
case '5009': return "Consumer group
|
|
223
|
+
case '5006': return "Consumer group member with client ID: {0} for group with ID: {1} for topic with ID: {2} was not found.";
|
|
224
|
+
case '5007': return "Failed to create consumer group info file for ID: {0} for topic with ID: {1} for stream with ID: {2}.";
|
|
225
|
+
case '5008': return "Failed to delete consumer group info file for ID: {0} for topic with ID: {1} for stream with ID: {2}.";
|
|
226
|
+
case '5009': return "Consumer group member with client ID: {0} does not own partition: {1} at the current generation (rebalance in progress).";
|
|
227
|
+
case '6000': return "Base offset is missing";
|
|
228
|
+
case '6001': return "Last offset delta is missing";
|
|
229
|
+
case '6002': return "Max timestamp is missing";
|
|
230
|
+
case '6003': return "Length is missing";
|
|
231
|
+
case '6004': return "Payload is missing";
|
|
232
|
+
case '7000': return "Cannot read batch base offset";
|
|
233
|
+
case '7001': return "Cannot read batch length";
|
|
234
|
+
case '7002': return "Cannot read batch last offset delta";
|
|
235
|
+
case '7003': return "Cannot read batch maximum timestamp";
|
|
236
|
+
case '7004': return "Cannot read batch payload";
|
|
237
|
+
case '8000': return "Invalid connection string";
|
|
238
|
+
case '9000': return "Snapshot file completion failed";
|
|
239
|
+
case '10000': return "Cannot serialize resource";
|
|
240
|
+
case '10001': return "Cannot deserialize resource";
|
|
241
|
+
case '10002': return "Cannot read file";
|
|
242
|
+
case '10003': return "Cannot read file metadata";
|
|
243
|
+
case '10004': return "Cannot seek file";
|
|
244
|
+
case '10005': return "Cannot append to file";
|
|
245
|
+
case '10006': return "Cannot write to file";
|
|
246
|
+
case '10007': return "Cannot overwrite file";
|
|
247
|
+
case '10008': return "Cannot delete file";
|
|
248
|
+
case '10009': return "Cannot sync file";
|
|
249
|
+
case '10010': return "Cannot read index offset";
|
|
250
|
+
case '10011': return "Cannot read index position";
|
|
251
|
+
case '10012': return "Cannot read index timestamp";
|
|
252
|
+
case '10013': return "Timestamp out of range: {0}";
|
|
253
|
+
case '11000': return "Shard not found for stream ID: {0}, topic ID: {1}, partition ID: {2}";
|
|
254
|
+
case '11001': return "Shard communication error";
|
|
255
|
+
case '12000': return "Cannot bind to socket with addr: {0}";
|
|
256
|
+
case '12001': return "Task execution timeout";
|
|
257
|
+
case '13000': return "IO error: {0}";
|
|
258
|
+
case '14000': return "VSR session already bound; reset before re-binding";
|
|
259
|
+
case '14001': return "VSR session value {0} is invalid (must be non-zero)";
|
|
260
|
+
case '14003': return "Incompatible binary protocol version: client {}, server accepts [{}, {}]";
|
|
186
261
|
default: return 'error';
|
|
187
262
|
}
|
|
188
263
|
};
|
|
@@ -18,16 +18,16 @@ import assert from 'node:assert/strict';
|
|
|
18
18
|
import { it } from 'node:test';
|
|
19
19
|
import { translateErrorCode } from './error.code.js';
|
|
20
20
|
it('translates the consumer-group error range', () => {
|
|
21
|
-
assert.equal(translateErrorCode(5000), 'Consumer group ID not found');
|
|
21
|
+
assert.equal(translateErrorCode(5000), 'Consumer group with ID: {0} for topic with ID: {1} was not found.');
|
|
22
22
|
assert.equal(translateErrorCode(5001), 'error');
|
|
23
23
|
assert.equal(translateErrorCode(5002), 'Invalid consumer group ID');
|
|
24
|
-
assert.equal(translateErrorCode(5003), 'Consumer group name not found');
|
|
25
|
-
assert.equal(translateErrorCode(5004), 'Consumer group name already exists');
|
|
24
|
+
assert.equal(translateErrorCode(5003), 'Consumer group with name: {0} for topic with ID: {1} was not found.');
|
|
25
|
+
assert.equal(translateErrorCode(5004), 'Consumer group with name: {0} for topic with ID: {1} already exists.');
|
|
26
26
|
assert.equal(translateErrorCode(5005), 'Invalid consumer group name');
|
|
27
|
-
assert.equal(translateErrorCode(5006), 'Consumer group member not found');
|
|
28
|
-
assert.equal(translateErrorCode(5007), '
|
|
29
|
-
assert.equal(translateErrorCode(5008), '
|
|
30
|
-
assert.equal(translateErrorCode(5009), 'Consumer group
|
|
27
|
+
assert.equal(translateErrorCode(5006), 'Consumer group member with client ID: {0} for group with ID: {1} for topic with ID: {2} was not found.');
|
|
28
|
+
assert.equal(translateErrorCode(5007), 'Failed to create consumer group info file for ID: {0} for topic with ID: {1} for stream with ID: {2}.');
|
|
29
|
+
assert.equal(translateErrorCode(5008), 'Failed to delete consumer group info file for ID: {0} for topic with ID: {1} for stream with ID: {2}.');
|
|
30
|
+
assert.equal(translateErrorCode(5009), 'Consumer group member with client ID: {0} does not own partition: {1} at the current generation (rebalance in progress).');
|
|
31
31
|
assert.equal(translateErrorCode(5010), 'error');
|
|
32
32
|
});
|
|
33
33
|
//# sourceMappingURL=error.code.test.js.map
|
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Size of the batch header in bytes.
|
|
3
|
+
* Layout: u64 (partitionId) + u64 (baseOffset) + u64 (baseTimestamp) +
|
|
4
|
+
* u64 (originTimestamp) + u64 (batchLength) + u64 (batchChecksum) +
|
|
5
|
+
* u32 (messageCount) + zero padding up to 256 bytes.
|
|
6
|
+
*/
|
|
7
|
+
export declare const BATCH_HEADER_SIZE = 256;
|
|
8
|
+
/**
|
|
9
|
+
* Size of the per-message frame header in bytes.
|
|
10
|
+
* Layout: u64 (checksum) + u128 (id) + u32 (offsetDelta) +
|
|
11
|
+
* u32 (timestampDelta) + u32 (userHeadersLength) + u32 (payloadLength) +
|
|
12
|
+
* u64 (reserved).
|
|
13
|
+
*/
|
|
14
|
+
export declare const FRAME_HEADER_SIZE = 48;
|
|
15
|
+
/**
|
|
16
|
+
* Batch header describing a run of message frames.
|
|
17
|
+
*/
|
|
18
|
+
export type BatchHeader = {
|
|
19
|
+
/** Partition the batch belongs to (zero when sent by a client) */
|
|
20
|
+
partitionId: bigint;
|
|
21
|
+
/** Offset of the first message in the batch (zero when sent by a client) */
|
|
22
|
+
baseOffset: bigint;
|
|
23
|
+
/** Server timestamp of the batch in microseconds (zero when sent by a client) */
|
|
24
|
+
baseTimestamp: bigint;
|
|
25
|
+
/** Smallest origin timestamp of the batched messages in microseconds */
|
|
26
|
+
originTimestamp: bigint;
|
|
27
|
+
/** Total batch size in bytes, header included */
|
|
28
|
+
batchLength: bigint;
|
|
29
|
+
/** XXH3-64 checksum of the batch header fields and frame checksums */
|
|
30
|
+
batchChecksum: bigint;
|
|
31
|
+
/** Number of message frames in the batch */
|
|
32
|
+
messageCount: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Per-message frame header.
|
|
36
|
+
*/
|
|
37
|
+
export type FrameHeader = {
|
|
38
|
+
/** XXH3-64 checksum of the frame past this field, payload and user headers included */
|
|
39
|
+
checksum: bigint;
|
|
40
|
+
/** Unique message identifier */
|
|
41
|
+
id: bigint;
|
|
42
|
+
/** Index of the message within the batch */
|
|
43
|
+
offsetDelta: number;
|
|
44
|
+
/** Message origin timestamp minus batch origin timestamp in microseconds */
|
|
45
|
+
timestampDelta: number;
|
|
46
|
+
/** Length of user-defined headers in bytes */
|
|
47
|
+
userHeadersLength: number;
|
|
48
|
+
/** Length of message payload in bytes */
|
|
49
|
+
payloadLength: number;
|
|
50
|
+
/** Reserved for future use, must be zero */
|
|
51
|
+
reserved: bigint;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Serializes a batch header to its 256-byte wire format.
|
|
55
|
+
*
|
|
56
|
+
* @param header - Batch header to serialize
|
|
57
|
+
* @returns Serialized batch header buffer
|
|
58
|
+
*/
|
|
59
|
+
export declare const serializeBatchHeader: (header: BatchHeader) => Buffer;
|
|
60
|
+
/**
|
|
61
|
+
* Deserializes a batch header from a buffer.
|
|
62
|
+
*
|
|
63
|
+
* @param b - Buffer containing the serialized batch header
|
|
64
|
+
* @param pos - Starting position in the buffer
|
|
65
|
+
* @returns Parsed BatchHeader object
|
|
66
|
+
*/
|
|
67
|
+
export declare const deserializeBatchHeader: (b: Buffer, pos?: number) => BatchHeader;
|
|
68
|
+
/**
|
|
69
|
+
* Deserializes a frame header from a buffer.
|
|
70
|
+
*
|
|
71
|
+
* @param b - Buffer containing the serialized frame header
|
|
72
|
+
* @param pos - Starting position in the buffer
|
|
73
|
+
* @returns Parsed FrameHeader object
|
|
74
|
+
*/
|
|
75
|
+
export declare const deserializeFrameHeader: (b: Buffer, pos?: number) => FrameHeader;
|
|
76
|
+
/**
|
|
77
|
+
* Computes the XXH3-64 checksum of a complete frame.
|
|
78
|
+
* Covers everything past the checksum field: the remaining frame header,
|
|
79
|
+
* the payload, and the user headers.
|
|
80
|
+
*
|
|
81
|
+
* @param frame - Complete frame buffer [header][payload][user headers]
|
|
82
|
+
* @returns Frame checksum
|
|
83
|
+
*/
|
|
84
|
+
export declare const frameChecksum: (frame: Buffer) => bigint;
|
|
85
|
+
/**
|
|
86
|
+
* Computes the XXH3-64 checksum of a batch.
|
|
87
|
+
* Covers the batch header fields up to the checksum, the message count,
|
|
88
|
+
* and each frame checksum in message order.
|
|
89
|
+
*
|
|
90
|
+
* @param header - Batch header fields, batchChecksum ignored
|
|
91
|
+
* @param frameChecksums - Frame checksums in message order
|
|
92
|
+
* @returns Batch checksum
|
|
93
|
+
*/
|
|
94
|
+
export declare const batchChecksum: (header: Omit<BatchHeader, "batchChecksum" | "messageCount">, frameChecksums: bigint[]) => bigint;
|
|
95
|
+
/**
|
|
96
|
+
* Iggy message header containing metadata for each polled message.
|
|
3
97
|
*/
|
|
4
98
|
export type IggyMessageHeader = {
|
|
5
99
|
/** Message checksum for integrity verification */
|
|
@@ -19,21 +113,6 @@ export type IggyMessageHeader = {
|
|
|
19
113
|
/** Reserved for future use */
|
|
20
114
|
reserved: bigint;
|
|
21
115
|
};
|
|
22
|
-
/**
|
|
23
|
-
* Size of the Iggy message header in bytes.
|
|
24
|
-
* Layout: u64 (checksum) + u128 (id) + u64 (offset) + u64 (timestamp) + u64 (originTimestamp) + u32 (userHeadersLength) + u32 (payloadLength) + u64 (reserved)
|
|
25
|
-
*/
|
|
26
|
-
export declare const IGGY_MESSAGE_HEADER_SIZE: number;
|
|
27
|
-
/**
|
|
28
|
-
* Serializes an Iggy message header to wire format.
|
|
29
|
-
* Sets checksum, offset, and timestamp to zero (filled by server).
|
|
30
|
-
*
|
|
31
|
-
* @param id - Message ID as 16-byte buffer
|
|
32
|
-
* @param payload - Message payload
|
|
33
|
-
* @param userHeaders - Serialized user headers
|
|
34
|
-
* @returns Serialized header buffer
|
|
35
|
-
*/
|
|
36
|
-
export declare const serializeIggyMessageHeader: (id: Buffer, payload: Buffer, userHeaders: Buffer) => Buffer<ArrayBuffer>;
|
|
37
116
|
/**
|
|
38
117
|
* Deserializes a message ID from a 16-byte buffer to BigInt.
|
|
39
118
|
*
|
|
@@ -41,12 +120,4 @@ export declare const serializeIggyMessageHeader: (id: Buffer, payload: Buffer, u
|
|
|
41
120
|
* @returns Message ID as BigInt
|
|
42
121
|
*/
|
|
43
122
|
export declare const deserialiseMessageId: (b: Buffer) => bigint;
|
|
44
|
-
/**
|
|
45
|
-
* Deserializes Iggy message headers from a buffer.
|
|
46
|
-
*
|
|
47
|
-
* @param b - Buffer containing the serialized header
|
|
48
|
-
* @returns Parsed IggyMessageHeader object
|
|
49
|
-
* @throws Error if buffer length doesn't match expected header size
|
|
50
|
-
*/
|
|
51
|
-
export declare const deserializeIggyMessageHeaders: (b: Buffer) => IggyMessageHeader;
|
|
52
123
|
//# sourceMappingURL=iggy-header.utils.d.ts.map
|
|
@@ -15,62 +15,109 @@
|
|
|
15
15
|
// specific language governing permissions and limitations
|
|
16
16
|
// under the License.
|
|
17
17
|
//
|
|
18
|
-
import {
|
|
18
|
+
import { xxh3 } from "@node-rs/xxhash";
|
|
19
19
|
import { u128LEBufToBigint } from "../number.utils.js";
|
|
20
20
|
/**
|
|
21
|
-
* Size of the
|
|
22
|
-
* Layout: u64 (
|
|
21
|
+
* Size of the batch header in bytes.
|
|
22
|
+
* Layout: u64 (partitionId) + u64 (baseOffset) + u64 (baseTimestamp) +
|
|
23
|
+
* u64 (originTimestamp) + u64 (batchLength) + u64 (batchChecksum) +
|
|
24
|
+
* u32 (messageCount) + zero padding up to 256 bytes.
|
|
23
25
|
*/
|
|
24
|
-
export const
|
|
26
|
+
export const BATCH_HEADER_SIZE = 256;
|
|
25
27
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
+
* Size of the per-message frame header in bytes.
|
|
29
|
+
* Layout: u64 (checksum) + u128 (id) + u32 (offsetDelta) +
|
|
30
|
+
* u32 (timestampDelta) + u32 (userHeadersLength) + u32 (payloadLength) +
|
|
31
|
+
* u64 (reserved).
|
|
32
|
+
*/
|
|
33
|
+
export const FRAME_HEADER_SIZE = 48;
|
|
34
|
+
/** Size of the frame checksum field prefixing the frame header. */
|
|
35
|
+
const FRAME_CHECKSUM_SIZE = 8;
|
|
36
|
+
/**
|
|
37
|
+
* Serializes a batch header to its 256-byte wire format.
|
|
28
38
|
*
|
|
29
|
-
* @param
|
|
30
|
-
* @
|
|
31
|
-
* @param userHeaders - Serialized user headers
|
|
32
|
-
* @returns Serialized header buffer
|
|
39
|
+
* @param header - Batch header to serialize
|
|
40
|
+
* @returns Serialized batch header buffer
|
|
33
41
|
*/
|
|
34
|
-
export const
|
|
35
|
-
const b = Buffer.
|
|
36
|
-
b.writeBigUInt64LE(
|
|
37
|
-
b.
|
|
38
|
-
b.writeBigUInt64LE(
|
|
39
|
-
b.writeBigUInt64LE(
|
|
40
|
-
b.
|
|
41
|
-
b.
|
|
42
|
-
b.writeUInt32LE(
|
|
43
|
-
b.writeBigUInt64LE(0n, 56); // reserved u64
|
|
42
|
+
export const serializeBatchHeader = (header) => {
|
|
43
|
+
const b = Buffer.alloc(BATCH_HEADER_SIZE);
|
|
44
|
+
b.writeBigUInt64LE(header.partitionId, 0);
|
|
45
|
+
b.writeBigUInt64LE(header.baseOffset, 8);
|
|
46
|
+
b.writeBigUInt64LE(header.baseTimestamp, 16);
|
|
47
|
+
b.writeBigUInt64LE(header.originTimestamp, 24);
|
|
48
|
+
b.writeBigUInt64LE(header.batchLength, 32);
|
|
49
|
+
b.writeBigUInt64LE(header.batchChecksum, 40);
|
|
50
|
+
b.writeUInt32LE(header.messageCount, 48);
|
|
44
51
|
return b;
|
|
45
52
|
};
|
|
46
53
|
/**
|
|
47
|
-
* Deserializes a
|
|
54
|
+
* Deserializes a batch header from a buffer.
|
|
48
55
|
*
|
|
49
|
-
* @param b -
|
|
50
|
-
* @
|
|
56
|
+
* @param b - Buffer containing the serialized batch header
|
|
57
|
+
* @param pos - Starting position in the buffer
|
|
58
|
+
* @returns Parsed BatchHeader object
|
|
51
59
|
*/
|
|
52
|
-
export const
|
|
60
|
+
export const deserializeBatchHeader = (b, pos = 0) => ({
|
|
61
|
+
partitionId: b.readBigUInt64LE(pos),
|
|
62
|
+
baseOffset: b.readBigUInt64LE(pos + 8),
|
|
63
|
+
baseTimestamp: b.readBigUInt64LE(pos + 16),
|
|
64
|
+
originTimestamp: b.readBigUInt64LE(pos + 24),
|
|
65
|
+
batchLength: b.readBigUInt64LE(pos + 32),
|
|
66
|
+
batchChecksum: b.readBigUInt64LE(pos + 40),
|
|
67
|
+
messageCount: b.readUInt32LE(pos + 48),
|
|
68
|
+
});
|
|
53
69
|
/**
|
|
54
|
-
* Deserializes
|
|
70
|
+
* Deserializes a frame header from a buffer.
|
|
55
71
|
*
|
|
56
|
-
* @param b - Buffer containing the serialized header
|
|
57
|
-
* @
|
|
58
|
-
* @
|
|
72
|
+
* @param b - Buffer containing the serialized frame header
|
|
73
|
+
* @param pos - Starting position in the buffer
|
|
74
|
+
* @returns Parsed FrameHeader object
|
|
59
75
|
*/
|
|
60
|
-
export const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
export const deserializeFrameHeader = (b, pos = 0) => ({
|
|
77
|
+
checksum: b.readBigUInt64LE(pos),
|
|
78
|
+
id: b.readBigUInt64LE(pos + 8) | (b.readBigUInt64LE(pos + 16) << 64n),
|
|
79
|
+
offsetDelta: b.readUInt32LE(pos + 24),
|
|
80
|
+
timestampDelta: b.readUInt32LE(pos + 28),
|
|
81
|
+
userHeadersLength: b.readUInt32LE(pos + 32),
|
|
82
|
+
payloadLength: b.readUInt32LE(pos + 36),
|
|
83
|
+
reserved: b.readBigUInt64LE(pos + 40),
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* Computes the XXH3-64 checksum of a complete frame.
|
|
87
|
+
* Covers everything past the checksum field: the remaining frame header,
|
|
88
|
+
* the payload, and the user headers.
|
|
89
|
+
*
|
|
90
|
+
* @param frame - Complete frame buffer [header][payload][user headers]
|
|
91
|
+
* @returns Frame checksum
|
|
92
|
+
*/
|
|
93
|
+
export const frameChecksum = (frame) => xxh3.xxh64(frame.subarray(FRAME_CHECKSUM_SIZE));
|
|
94
|
+
/**
|
|
95
|
+
* Computes the XXH3-64 checksum of a batch.
|
|
96
|
+
* Covers the batch header fields up to the checksum, the message count,
|
|
97
|
+
* and each frame checksum in message order.
|
|
98
|
+
*
|
|
99
|
+
* @param header - Batch header fields, batchChecksum ignored
|
|
100
|
+
* @param frameChecksums - Frame checksums in message order
|
|
101
|
+
* @returns Batch checksum
|
|
102
|
+
*/
|
|
103
|
+
export const batchChecksum = (header, frameChecksums) => {
|
|
104
|
+
const b = Buffer.allocUnsafe(44 + frameChecksums.length * 8);
|
|
105
|
+
b.writeBigUInt64LE(header.partitionId, 0);
|
|
106
|
+
b.writeBigUInt64LE(header.baseOffset, 8);
|
|
107
|
+
b.writeBigUInt64LE(header.baseTimestamp, 16);
|
|
108
|
+
b.writeBigUInt64LE(header.originTimestamp, 24);
|
|
109
|
+
b.writeBigUInt64LE(header.batchLength, 32);
|
|
110
|
+
b.writeUInt32LE(frameChecksums.length, 40);
|
|
111
|
+
frameChecksums.forEach((checksum, index) => {
|
|
112
|
+
b.writeBigUInt64LE(checksum, 44 + index * 8);
|
|
113
|
+
});
|
|
114
|
+
return xxh3.xxh64(b);
|
|
75
115
|
};
|
|
116
|
+
/**
|
|
117
|
+
* Deserializes a message ID from a 16-byte buffer to BigInt.
|
|
118
|
+
*
|
|
119
|
+
* @param b - 16-byte buffer containing the message ID
|
|
120
|
+
* @returns Message ID as BigInt
|
|
121
|
+
*/
|
|
122
|
+
export const deserialiseMessageId = (b) => u128LEBufToBigint(b);
|
|
76
123
|
//# sourceMappingURL=iggy-header.utils.js.map
|