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.
@@ -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 { Command2, peekCommand } from '../wire/vsr/header.js';
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) === Command2.Eviction)
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 { Command2, HEADER_SIZE, REPLY_OFFSET } from '../wire/vsr/header.js';
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(Command2.Reply, REPLY_OFFSET.command);
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 { Command2, HEADER_SIZE, REPLY_OFFSET } from '../wire/vsr/header.js';
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(Command2.Reply, REPLY_OFFSET.command);
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 { Command2, EVICTION_OFFSET, EvictionReason, HEADER_SIZE, REPLY_OFFSET, REQUEST_OFFSET } from '../wire/vsr/header.js';
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(Command2.Reply, REPLY_OFFSET.command);
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(Command2.Eviction, REPLY_OFFSET.command);
76
+ frame.writeUInt8(Command.Eviction, REPLY_OFFSET.command);
77
77
  frame.writeUInt8(reason, EVICTION_OFFSET.reason);
78
78
  return frame;
79
79
  };
@@ -28,8 +28,6 @@ export declare const COMMAND_CODE: {
28
28
  GetOffset: number;
29
29
  StoreOffset: number;
30
30
  DeleteConsumerOffset: number;
31
- StoreOffset2: number;
32
- DeleteConsumerOffset2: number;
33
31
  GetStream: number;
34
32
  GetStreams: number;
35
33
  CreateStream: number;
@@ -46,8 +46,6 @@ export const COMMAND_CODE = {
46
46
  GetOffset: 120,
47
47
  StoreOffset: 121,
48
48
  DeleteConsumerOffset: 122,
49
- StoreOffset2: 123,
50
- DeleteConsumerOffset2: 124,
51
49
  GetStream: 200,
52
50
  GetStreams: 201,
53
51
  CreateStream: 202,
@@ -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 ID: {0} already exists.";
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 "Cannot append message to segment at offset: {0}";
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 "Cannot create consumer group info";
184
- case '5008': return "Cannot delete consumer group info";
185
- case '5009': return "Consumer group partition not owned";
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), 'Cannot create consumer group info');
29
- assert.equal(translateErrorCode(5008), 'Cannot delete consumer group info');
30
- assert.equal(translateErrorCode(5009), 'Consumer group partition not owned');
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
- * Iggy message header containing metadata for each message.
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 { toDate } from "../serialize.utils.js";
18
+ import { xxh3 } from "@node-rs/xxhash";
19
19
  import { u128LEBufToBigint } from "../number.utils.js";
20
20
  /**
21
- * Size of the Iggy message header in bytes.
22
- * Layout: u64 (checksum) + u128 (id) + u64 (offset) + u64 (timestamp) + u64 (originTimestamp) + u32 (userHeadersLength) + u32 (payloadLength) + u64 (reserved)
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 IGGY_MESSAGE_HEADER_SIZE = 8 + 16 + 8 + 8 + 8 + 4 + 4 + 8;
26
+ export const BATCH_HEADER_SIZE = 256;
25
27
  /**
26
- * Serializes an Iggy message header to wire format.
27
- * Sets checksum, offset, and timestamp to zero (filled by server).
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 id - Message ID as 16-byte buffer
30
- * @param payload - Message payload
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 serializeIggyMessageHeader = (id, payload, userHeaders) => {
35
- const b = Buffer.allocUnsafe(IGGY_MESSAGE_HEADER_SIZE);
36
- b.writeBigUInt64LE(0n, 0); // checksum u64
37
- b.fill(id, 8, 24); // id u128
38
- b.writeBigUInt64LE(0n, 24); // offset u64
39
- b.writeBigUInt64LE(0n, 32); // timestamp u64
40
- b.writeBigUint64LE(BigInt(new Date().getTime()), 40); // originTimestamp u64
41
- b.writeUInt32LE(userHeaders.length, 48); // userHeaders len u32
42
- b.writeUInt32LE(payload.length, 52); // payload len u32
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 message ID from a 16-byte buffer to BigInt.
54
+ * Deserializes a batch header from a buffer.
48
55
  *
49
- * @param b - 16-byte buffer containing the message ID
50
- * @returns Message ID as BigInt
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 deserialiseMessageId = (b) => u128LEBufToBigint(b);
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 Iggy message headers from a buffer.
70
+ * Deserializes a frame header from a buffer.
55
71
  *
56
- * @param b - Buffer containing the serialized header
57
- * @returns Parsed IggyMessageHeader object
58
- * @throws Error if buffer length doesn't match expected header size
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 deserializeIggyMessageHeaders = (b) => {
61
- if (b.length !== IGGY_MESSAGE_HEADER_SIZE)
62
- throw new Error(`deserialize message headers error, length = ${b.length} ` +
63
- `expected ${IGGY_MESSAGE_HEADER_SIZE}`);
64
- const headers = {
65
- checksum: b.readBigUInt64LE(0),
66
- id: deserialiseMessageId(b.subarray(8, 24)),
67
- offset: b.readBigUInt64LE(24),
68
- timestamp: toDate(b.readBigUInt64LE(32)),
69
- originTimestamp: toDate(b.readBigUInt64LE(40)),
70
- userHeadersLength: b.readUInt32LE(48),
71
- payloadLength: b.readUInt32LE(52),
72
- reserved: b.readBigUInt64LE(56),
73
- };
74
- return headers;
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
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=message-batch.test.d.ts.map