apache-iggy 0.8.1-edge.4 → 0.10.0-edge.1

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.
Files changed (37) hide show
  1. package/README.md +15 -10
  2. package/dist/client/client.config.d.ts +2 -4
  3. package/dist/client/client.config.js +2 -8
  4. package/dist/client/client.config.test.js +5 -22
  5. package/dist/client/client.connection.d.ts +0 -8
  6. package/dist/client/client.connection.js +2 -15
  7. package/dist/client/client.connection.test.js +18 -17
  8. package/dist/client/client.frame.d.ts +2 -5
  9. package/dist/client/client.frame.js +12 -22
  10. package/dist/client/client.frame.test.js +38 -53
  11. package/dist/client/client.socket.d.ts +1 -4
  12. package/dist/client/client.socket.js +2 -18
  13. package/dist/client/client.socket.test.js +0 -1
  14. package/dist/client/client.type.d.ts +0 -6
  15. package/dist/client/client.utils.d.ts +0 -30
  16. package/dist/client/client.utils.js +0 -57
  17. package/dist/client/client.utils.test.js +9 -36
  18. package/dist/e2e/tcp.cluster.e2e.js +5 -16
  19. package/dist/e2e/tcp.consumer-group.e2e.js +1 -5
  20. package/dist/e2e/tcp.consumer-stream.e2e.js +1 -5
  21. package/dist/e2e/tcp.send-message.e2e.js +13 -1
  22. package/dist/e2e/test-client.utils.js +0 -2
  23. package/dist/e2e/tls.system.e2e.js +3 -5
  24. package/dist/index.d.ts +1 -1
  25. package/dist/tcp.sm.utils.d.ts +1 -1
  26. package/dist/wire/cluster/cluster.type.d.ts +2 -2
  27. package/dist/wire/command-set.d.ts +1 -1
  28. package/dist/wire/command-set.test.js +0 -1
  29. package/dist/wire/message/header.type.d.ts +1 -1
  30. package/dist/wire/message/poll-messages.command.js +1 -2
  31. package/dist/wire/message/send-messages.command.d.ts +32 -3
  32. package/dist/wire/message/send-messages.command.js +41 -3
  33. package/dist/wire/message/send-messages.command.test.js +74 -15
  34. package/dist/wire/vsr/namespace.js +2 -0
  35. package/dist/wire/vsr/register.d.ts +1 -1
  36. package/dist/wire/vsr/register.js +2 -2
  37. package/package.json +2 -3
package/README.md CHANGED
@@ -32,18 +32,17 @@ npm i --save apache-iggy
32
32
 
33
33
  ### Response frame limit
34
34
 
35
- **Compatibility note:** response frames larger than `maxResponseFrameSize` (default 64 MiB) are now rejected and close the connection under both framing modes. This is a behavior change for existing classic-framing clients. Raise the limit in the client configuration when polling very large batches.
35
+ **Compatibility note:** response frames larger than `maxResponseFrameSize` (default 64 MiB) are rejected and close the connection. Raise the limit in the client configuration when polling very large batches.
36
36
 
37
37
  ### VSR framing
38
38
 
39
- Classic framing remains the default. Select VSR explicitly when connecting to
40
- an Iggy VSR server:
39
+ The SDK speaks the VSR wire protocol exclusively and requires an Iggy VSR
40
+ server:
41
41
 
42
42
  ```typescript
43
43
  import { SimpleClient, getRawClient } from "apache-iggy";
44
44
 
45
45
  const config = {
46
- protocol: "vsr" as const,
47
46
  transport: "TCP" as const,
48
47
  options: { host: "127.0.0.1", port: 8090 },
49
48
  credentials: { username: "iggy", password: "iggy" },
@@ -52,12 +51,18 @@ const client = new SimpleClient(getRawClient(config));
52
51
  const stats = await client.system.getStats();
53
52
  ```
54
53
 
55
- VSR is a runtime protocol choice in Node.js, not a build feature. Codes absent
56
- from the SDK command table use `Operation::NonReplicated` and carry the command
57
- code in the request header's reserved field. The server remains authoritative
58
- for classifying or rejecting extension commands.
54
+ Codes absent from the SDK command table use `Operation::NonReplicated` and
55
+ carry the command code in the request header's reserved field. The server
56
+ remains authoritative for classifying or rejecting extension commands.
59
57
 
60
- The same npm package supports both framing modes over TCP and TLS. VSR restricts `Client` to one pooled connection because authentication, request sequencing, and consumer-group assignments belong to one consensus session. Configurations requesting more than one pooled connection fail before a socket is opened.
58
+ Sends must use explicit `Partitioning.PartitionId` partitioning: the client
59
+ routes each request to a partition-scoped namespace, so broker-side balancing
60
+ (`Partitioning.Balanced`) and key hashing (`Partitioning.MessageKey`) are
61
+ rejected before the request is sent.
62
+ <!-- TODO(hubcio): Balanced and MessageKey partitioning to be implemented;
63
+ not decided yet whether it'll be on server side or client side. -->
64
+
65
+ VSR works over TCP and TLS. It restricts `Client` to one pooled connection because authentication, request sequencing, and consumer-group assignments belong to one consensus session. Configurations requesting more than one pooled connection fail before a socket is opened.
61
66
 
62
67
  VSR authentication translates the existing password and personal-access-token
63
68
  login APIs into the register handshake required by the consensus protocol. A
@@ -68,7 +73,7 @@ new session.
68
73
 
69
74
  When the server's `[heartbeat]` eviction is enabled, configure the client's `heartbeatInterval` below the server heartbeat interval. Client heartbeats are disabled when `heartbeatInterval` is unset.
70
75
 
71
- `sendBinaryRequest(code, payload)` has the same signature under classic and VSR framing. Known replicated commands use their registered operation, while unknown codes reach the server as non-replicated requests and are rejected by servers that do not register them. Classic request bytes remain unchanged.
76
+ `sendBinaryRequest(code, payload)` sends an arbitrary command code. Known replicated commands use their registered operation, while unknown codes reach the server as non-replicated requests and are rejected by servers that do not register them.
72
77
 
73
78
  ```typescript
74
79
  import { ResponseError } from "apache-iggy";
@@ -1,6 +1,4 @@
1
- import type { ClientConfig, Protocol } from './client.type.js';
1
+ import type { ClientConfig } from './client.type.js';
2
2
  export declare const DEFAULT_MAX_RESPONSE_FRAME_SIZE: number;
3
- export declare const normalizeClientConfig: (config: ClientConfig) => ClientConfig & {
4
- protocol: Protocol;
5
- };
3
+ export declare const normalizeClientConfig: (config: ClientConfig) => ClientConfig;
6
4
  //# sourceMappingURL=client.config.d.ts.map
@@ -15,24 +15,18 @@
15
15
  // specific language governing permissions and limitations
16
16
  // under the License.
17
17
  export const DEFAULT_MAX_RESPONSE_FRAME_SIZE = 64 * 1024 * 1024;
18
- const isProtocol = (value) => value === 'classic' || value === 'vsr';
19
18
  export const normalizeClientConfig = (config) => {
20
- const protocol = config.protocol ?? 'classic';
21
- if (!isProtocol(protocol))
22
- throw new TypeError(`unsupported wire protocol: ${String(protocol)}`);
23
19
  const maxResponseFrameSize = config.maxResponseFrameSize ?? DEFAULT_MAX_RESPONSE_FRAME_SIZE;
24
20
  if (!Number.isSafeInteger(maxResponseFrameSize) ||
25
21
  maxResponseFrameSize < 256)
26
22
  throw new TypeError('maxResponseFrameSize must be a safe integer of at least 256 bytes');
27
- if (protocol === 'vsr' &&
28
- ((config.poolSize?.min ?? 1) > 1 || (config.poolSize?.max ?? 1) > 1))
23
+ if ((config.poolSize?.min ?? 1) > 1 || (config.poolSize?.max ?? 1) > 1)
29
24
  throw new TypeError('VSR clients currently support exactly one pooled connection');
30
25
  return {
31
26
  ...config,
32
- protocol,
33
27
  options: { ...config.options },
34
28
  maxResponseFrameSize,
35
- ...(protocol === 'vsr' ? { poolSize: { min: 1, max: 1 } } : {})
29
+ poolSize: { min: 1, max: 1 }
36
30
  };
37
31
  };
38
32
  //# sourceMappingURL=client.config.js.map
@@ -23,40 +23,23 @@ const config = () => ({
23
23
  credentials: { username: 'iggy', password: 'iggy' }
24
24
  });
25
25
  describe('normalizeClientConfig', () => {
26
- it('defaults to classic without changing classic pool sizing', () => {
27
- const normalized = normalizeClientConfig({
28
- ...config(),
29
- poolSize: { min: 2, max: 4 }
30
- });
31
- assert.equal(normalized.protocol, 'classic');
32
- assert.deepEqual(normalized.poolSize, { min: 2, max: 4 });
26
+ it('applies the default response frame limit', () => {
27
+ const normalized = normalizeClientConfig(config());
33
28
  assert.equal(normalized.maxResponseFrameSize, DEFAULT_MAX_RESPONSE_FRAME_SIZE);
34
29
  });
35
- it('restricts VSR to one pooled connection', () => {
36
- const normalized = normalizeClientConfig({
37
- ...config(),
38
- protocol: 'vsr'
39
- });
30
+ it('restricts the client to one pooled connection', () => {
31
+ const normalized = normalizeClientConfig(config());
40
32
  assert.deepEqual(normalized.poolSize, { min: 1, max: 1 });
41
33
  assert.throws(() => normalizeClientConfig({
42
34
  ...config(),
43
- protocol: 'vsr',
44
35
  poolSize: { max: 2 }
45
36
  }), /exactly one pooled connection/);
46
37
  });
47
- it('rejects invalid protocols before opening a socket', () => {
48
- assert.throws(() => normalizeClientConfig({
49
- ...config(),
50
- protocol: 'auto'
51
- }), /unsupported wire protocol/);
52
- });
53
- it('supports VSR over TLS', () => {
38
+ it('supports TLS transport', () => {
54
39
  const normalized = normalizeClientConfig({
55
40
  ...config(),
56
- protocol: 'vsr',
57
41
  transport: 'TLS'
58
42
  });
59
- assert.equal(normalized.protocol, 'vsr');
60
43
  assert.equal(normalized.transport, 'TLS');
61
44
  assert.deepEqual(normalized.poolSize, { min: 1, max: 1 });
62
45
  });
@@ -79,14 +79,6 @@ export declare class IggyConnection extends EventEmitter {
79
79
  * @param data - Incoming data buffer
80
80
  */
81
81
  _onData(data: Buffer): void;
82
- /**
83
- * Writes a command to the socket.
84
- *
85
- * @param command - Command code
86
- * @param payload - Command payload
87
- * @returns True if the write was successful
88
- */
89
- writeCommand(command: number, payload: Buffer): void;
90
82
  writeFrame(frame: Buffer): void;
91
83
  }
92
84
  //# sourceMappingURL=client.connection.d.ts.map
@@ -18,7 +18,6 @@
18
18
  import { EventEmitter } from 'node:events';
19
19
  import { createConnection } from 'node:net';
20
20
  import { connect as TLSConnect } from 'node:tls';
21
- import { serializeCommand } from './client.utils.js';
22
21
  import { debug } from './client.debug.js';
23
22
  import { DEFAULT_MAX_RESPONSE_FRAME_SIZE } from './client.config.js';
24
23
  import { ProtocolFrameError, ResponseFrameDecoder } from './client.frame.js';
@@ -121,7 +120,7 @@ export class IggyConnection extends EventEmitter {
121
120
  this.reconnectCount = 0;
122
121
  this.connectPromise = undefined;
123
122
  this.reconnectPromise = undefined;
124
- this.responseDecoder = new ResponseFrameDecoder(config.protocol ?? 'classic', config.maxResponseFrameSize ?? DEFAULT_MAX_RESPONSE_FRAME_SIZE);
123
+ this.responseDecoder = new ResponseFrameDecoder(config.maxResponseFrameSize ?? DEFAULT_MAX_RESPONSE_FRAME_SIZE);
125
124
  this.socket = this._installSocket(getTransport(config));
126
125
  }
127
126
  /**
@@ -361,8 +360,7 @@ export class IggyConnection extends EventEmitter {
361
360
  debug('ONDATA', typeof data, Buffer.isBuffer(data), data?.length, this.responseDecoder.hasBufferedData);
362
361
  try {
363
362
  for (const response of this.responseDecoder.push(data)) {
364
- if (this.config.protocol === 'vsr' &&
365
- peekCommand(response) === Command2.Eviction)
363
+ if (peekCommand(response) === Command2.Eviction)
366
364
  this.emit('eviction', evictionError(response));
367
365
  else
368
366
  this.emit('response', response);
@@ -374,17 +372,6 @@ export class IggyConnection extends EventEmitter {
374
372
  this.socket.destroy();
375
373
  }
376
374
  }
377
- /**
378
- * Writes a command to the socket.
379
- *
380
- * @param command - Command code
381
- * @param payload - Command payload
382
- * @returns True if the write was successful
383
- */
384
- writeCommand(command, payload) {
385
- const cmd = serializeCommand(command, payload);
386
- this.socket.write(cmd);
387
- }
388
375
  writeFrame(frame) {
389
376
  this.socket.write(frame);
390
377
  }
@@ -21,6 +21,8 @@ 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';
25
+ const FRAME_LIMIT = 2 * HEADER_SIZE;
24
26
  const startServer = async () => {
25
27
  const server = createServer();
26
28
  server.listen(0, '127.0.0.1');
@@ -28,7 +30,6 @@ const startServer = async () => {
28
30
  return server;
29
31
  };
30
32
  const connectionConfig = (server) => ({
31
- protocol: 'classic',
32
33
  transport: 'TCP',
33
34
  options: {
34
35
  host: '127.0.0.1',
@@ -36,8 +37,15 @@ const connectionConfig = (server) => ({
36
37
  },
37
38
  credentials: { username: 'iggy', password: 'iggy' },
38
39
  reconnect: { enabled: false, interval: 0, maxRetries: 0 },
39
- maxResponseFrameSize: 256
40
+ maxResponseFrameSize: FRAME_LIMIT
40
41
  });
42
+ const replyFrame = (body) => {
43
+ const frame = Buffer.alloc(HEADER_SIZE + body.length);
44
+ frame.writeUInt32LE(frame.length, REPLY_OFFSET.size);
45
+ frame.writeUInt8(Command2.Reply, REPLY_OFFSET.command);
46
+ body.copy(frame, HEADER_SIZE);
47
+ return frame;
48
+ };
41
49
  const closeConnection = async (connection, server) => {
42
50
  connection._destroy();
43
51
  if (!connection.socket.destroyed)
@@ -59,7 +67,7 @@ describe('IggyConnection', () => {
59
67
  await closeConnection(connection, server);
60
68
  }
61
69
  });
62
- it('shares connection attempts, recognizes endpoints, and writes commands', async () => {
70
+ it('shares connection attempts, recognizes endpoints, and writes frames', async () => {
63
71
  const server = await startServer();
64
72
  const received = new Promise((resolve) => {
65
73
  server.once('connection', (socket) => {
@@ -78,10 +86,9 @@ describe('IggyConnection', () => {
78
86
  host: 'broker.example'
79
87
  };
80
88
  assert.equal(connection.isConnectedTo('broker.example', server.address().port), true);
81
- connection.writeCommand(1, Buffer.from('payload'));
82
- const command = await received;
83
- assert.equal(command.readUInt32LE(4), 1);
84
- assert.deepEqual(command.subarray(8), Buffer.from('payload'));
89
+ const frame = replyFrame(Buffer.from('payload'));
90
+ connection.writeFrame(frame);
91
+ assert.deepEqual(await received, frame);
85
92
  }
86
93
  finally {
87
94
  await closeConnection(connection, server);
@@ -92,16 +99,13 @@ describe('IggyConnection', () => {
92
99
  const connection = new IggyConnection(connectionConfig(server));
93
100
  try {
94
101
  await connection.connect();
95
- const body = Buffer.from('response');
96
- const frame = Buffer.alloc(8 + body.length);
97
- frame.writeUInt32LE(body.length, 4);
98
- body.copy(frame, 8);
102
+ const frame = replyFrame(Buffer.from('response'));
99
103
  const response = once(connection, 'response');
100
104
  connection._onData(frame.subarray(0, 6));
101
105
  connection._onData(frame.subarray(6));
102
106
  assert.deepEqual((await response)[0], frame);
103
- const malformed = Buffer.alloc(8);
104
- malformed.writeUInt32LE(256, 4);
107
+ const malformed = replyFrame(Buffer.alloc(0));
108
+ malformed.writeUInt32LE(FRAME_LIMIT + 1, REPLY_OFFSET.size);
105
109
  const error = once(connection, 'error');
106
110
  connection._onData(malformed);
107
111
  assert.ok((await error)[0] instanceof ProtocolFrameError);
@@ -178,10 +182,7 @@ describe('IggyConnection', () => {
178
182
  assert.equal(await first, connection);
179
183
  assert.equal(connection.connected, true);
180
184
  assert.equal(connections, 2);
181
- const body = Buffer.from('fresh');
182
- const frame = Buffer.alloc(8 + body.length);
183
- frame.writeUInt32LE(body.length, 4);
184
- body.copy(frame, 8);
185
+ const frame = replyFrame(Buffer.from('fresh'));
185
186
  const response = once(connection, 'response');
186
187
  oldSocket.emit('data', Buffer.alloc(8));
187
188
  connection._onData(frame);
@@ -1,4 +1,3 @@
1
- import type { Protocol } from './client.type.js';
2
1
  export declare class ProtocolFrameError extends Error {
3
2
  constructor(message: string);
4
3
  }
@@ -6,21 +5,19 @@ export type ExtractedFrames = {
6
5
  frames: Buffer[];
7
6
  remainder: Buffer;
8
7
  };
9
- export declare const extractResponseFrames: (protocol: Protocol, buffer: Buffer, maximumFrameSize: number) => ExtractedFrames;
8
+ export declare const extractResponseFrames: (buffer: Buffer, maximumFrameSize: number) => ExtractedFrames;
10
9
  /**
11
10
  * Incrementally extracts response frames without repeatedly copying an
12
11
  * incomplete frame as new socket chunks arrive.
13
12
  */
14
13
  export declare class ResponseFrameDecoder {
15
- private readonly protocol;
16
14
  private readonly maximumFrameSize;
17
- private readonly headerSize;
18
15
  private chunks;
19
16
  private chunkIndex;
20
17
  private chunkOffset;
21
18
  private bufferedLength;
22
19
  private expectedFrameSize?;
23
- constructor(protocol: Protocol, maximumFrameSize: number);
20
+ constructor(maximumFrameSize: number);
24
21
  get hasBufferedData(): boolean;
25
22
  clear(): void;
26
23
  push(data: Buffer): Buffer[];
@@ -14,34 +14,28 @@
14
14
  // KIND, either express or implied. See the License for the
15
15
  // specific language governing permissions and limitations
16
16
  // under the License.
17
- import { HEADER_SIZE as VSR_HEADER_SIZE, readSize as readVsrSize } from '../wire/vsr/header.js';
18
- const CLASSIC_HEADER_SIZE = 8;
17
+ import { HEADER_SIZE, readSize } from '../wire/vsr/header.js';
19
18
  export class ProtocolFrameError extends Error {
20
19
  constructor(message) {
21
20
  super(message);
22
21
  this.name = 'ProtocolFrameError';
23
22
  }
24
23
  }
25
- const headerSizeFor = (protocol) => protocol === 'vsr' ? VSR_HEADER_SIZE : CLASSIC_HEADER_SIZE;
26
- const declaredFrameSize = (protocol, header, maximumFrameSize) => {
27
- const headerSize = headerSizeFor(protocol);
28
- const declaredSize = protocol === 'vsr'
29
- ? readVsrSize(header)
30
- : CLASSIC_HEADER_SIZE + header.readUInt32LE(4);
31
- if (declaredSize < headerSize)
32
- throw new ProtocolFrameError(`declared ${protocol} frame size ${declaredSize} is below header size`);
24
+ const declaredFrameSize = (header, maximumFrameSize) => {
25
+ const declaredSize = readSize(header);
26
+ if (declaredSize < HEADER_SIZE)
27
+ throw new ProtocolFrameError(`declared frame size ${declaredSize} is below header size`);
33
28
  if (declaredSize > maximumFrameSize)
34
- throw new ProtocolFrameError(`declared ${protocol} frame size ${declaredSize} exceeds ` +
29
+ throw new ProtocolFrameError(`declared frame size ${declaredSize} exceeds ` +
35
30
  `the ${maximumFrameSize} byte limit`);
36
31
  return declaredSize;
37
32
  };
38
- export const extractResponseFrames = (protocol, buffer, maximumFrameSize) => {
39
- const headerSize = headerSizeFor(protocol);
33
+ export const extractResponseFrames = (buffer, maximumFrameSize) => {
40
34
  const frames = [];
41
35
  let offset = 0;
42
- while (buffer.length - offset >= headerSize) {
36
+ while (buffer.length - offset >= HEADER_SIZE) {
43
37
  const available = buffer.length - offset;
44
- const declaredSize = declaredFrameSize(protocol, buffer.subarray(offset, offset + headerSize), maximumFrameSize);
38
+ const declaredSize = declaredFrameSize(buffer.subarray(offset, offset + HEADER_SIZE), maximumFrameSize);
45
39
  if (available < declaredSize)
46
40
  break;
47
41
  frames.push(buffer.subarray(offset, offset + declaredSize));
@@ -59,18 +53,14 @@ export const extractResponseFrames = (protocol, buffer, maximumFrameSize) => {
59
53
  * incomplete frame as new socket chunks arrive.
60
54
  */
61
55
  export class ResponseFrameDecoder {
62
- protocol;
63
56
  maximumFrameSize;
64
- headerSize;
65
57
  chunks;
66
58
  chunkIndex;
67
59
  chunkOffset;
68
60
  bufferedLength;
69
61
  expectedFrameSize;
70
- constructor(protocol, maximumFrameSize) {
71
- this.protocol = protocol;
62
+ constructor(maximumFrameSize) {
72
63
  this.maximumFrameSize = maximumFrameSize;
73
- this.headerSize = headerSizeFor(protocol);
74
64
  this.chunks = [];
75
65
  this.chunkIndex = 0;
76
66
  this.chunkOffset = 0;
@@ -95,9 +85,9 @@ export class ResponseFrameDecoder {
95
85
  const frames = [];
96
86
  while (true) {
97
87
  if (this.expectedFrameSize === undefined) {
98
- if (this.bufferedLength < this.headerSize)
88
+ if (this.bufferedLength < HEADER_SIZE)
99
89
  break;
100
- this.expectedFrameSize = declaredFrameSize(this.protocol, this.peek(this.headerSize), this.maximumFrameSize);
90
+ this.expectedFrameSize = declaredFrameSize(this.peek(HEADER_SIZE), this.maximumFrameSize);
101
91
  }
102
92
  if (this.bufferedLength < this.expectedFrameSize)
103
93
  break;
@@ -19,12 +19,6 @@ import { describe, it } from 'node:test';
19
19
  import { extractResponseFrames, ProtocolFrameError, ResponseFrameDecoder } from './client.frame.js';
20
20
  import { Command2, HEADER_SIZE, REPLY_OFFSET } from '../wire/vsr/header.js';
21
21
  const LIMIT = 1024;
22
- const classicFrame = (body) => {
23
- const frame = Buffer.alloc(8 + body.length);
24
- frame.writeUInt32LE(body.length, 4);
25
- body.copy(frame, 8);
26
- return frame;
27
- };
28
22
  const vsrFrame = (body) => {
29
23
  const frame = Buffer.alloc(HEADER_SIZE + body.length);
30
24
  frame.writeUInt32LE(frame.length, REPLY_OFFSET.size);
@@ -33,59 +27,50 @@ const vsrFrame = (body) => {
33
27
  return frame;
34
28
  };
35
29
  describe('extractResponseFrames', () => {
36
- for (const protocol of ['classic', 'vsr']) {
37
- const makeFrame = protocol === 'classic' ? classicFrame : vsrFrame;
38
- it(`buffers ${protocol} headers split at every boundary`, () => {
39
- const frame = makeFrame(Buffer.from('payload'));
40
- const headerSize = protocol === 'classic' ? 8 : HEADER_SIZE;
41
- for (let split = 0; split < headerSize; split += 1) {
42
- const first = extractResponseFrames(protocol, frame.subarray(0, split), LIMIT);
43
- assert.equal(first.frames.length, 0);
44
- const second = extractResponseFrames(protocol, Buffer.concat([first.remainder, frame.subarray(split)]), LIMIT);
45
- assert.deepEqual(second.frames, [frame]);
46
- assert.equal(second.remainder.length, 0);
47
- }
48
- });
49
- it(`buffers a fragmented ${protocol} body`, () => {
50
- const frame = makeFrame(Buffer.from('payload'));
51
- const split = frame.length - 2;
52
- const first = extractResponseFrames(protocol, frame.subarray(0, split), LIMIT);
30
+ it('buffers headers split at every boundary', () => {
31
+ const frame = vsrFrame(Buffer.from('payload'));
32
+ for (let split = 0; split < HEADER_SIZE; split += 1) {
33
+ const first = extractResponseFrames(frame.subarray(0, split), LIMIT);
53
34
  assert.equal(first.frames.length, 0);
54
- const second = extractResponseFrames(protocol, Buffer.concat([first.remainder, frame.subarray(split)]), LIMIT);
35
+ const second = extractResponseFrames(Buffer.concat([first.remainder, frame.subarray(split)]), LIMIT);
55
36
  assert.deepEqual(second.frames, [frame]);
56
- });
57
- it(`extracts coalesced ${protocol} frames and a partial tail`, () => {
58
- const first = makeFrame(Buffer.from('one'));
59
- const second = makeFrame(Buffer.from('two'));
60
- const third = makeFrame(Buffer.from('three'));
61
- const input = Buffer.concat([first, second, third.subarray(0, 3)]);
62
- const extracted = extractResponseFrames(protocol, input, LIMIT);
63
- assert.deepEqual(extracted.frames, [first, second]);
64
- assert.deepEqual(extracted.remainder, third.subarray(0, 3));
65
- assert.equal(extracted.remainder.buffer, input.buffer);
66
- });
67
- }
68
- it('rejects a VSR size below the header', () => {
37
+ assert.equal(second.remainder.length, 0);
38
+ }
39
+ });
40
+ it('buffers a fragmented body', () => {
41
+ const frame = vsrFrame(Buffer.from('payload'));
42
+ const split = frame.length - 2;
43
+ const first = extractResponseFrames(frame.subarray(0, split), LIMIT);
44
+ assert.equal(first.frames.length, 0);
45
+ const second = extractResponseFrames(Buffer.concat([first.remainder, frame.subarray(split)]), LIMIT);
46
+ assert.deepEqual(second.frames, [frame]);
47
+ });
48
+ it('extracts coalesced frames and a partial tail', () => {
49
+ const first = vsrFrame(Buffer.from('one'));
50
+ const second = vsrFrame(Buffer.from('two'));
51
+ const third = vsrFrame(Buffer.from('three'));
52
+ const input = Buffer.concat([first, second, third.subarray(0, 3)]);
53
+ const extracted = extractResponseFrames(input, LIMIT);
54
+ assert.deepEqual(extracted.frames, [first, second]);
55
+ assert.deepEqual(extracted.remainder, third.subarray(0, 3));
56
+ assert.equal(extracted.remainder.buffer, input.buffer);
57
+ });
58
+ it('rejects a size below the header', () => {
69
59
  const frame = vsrFrame(Buffer.alloc(0));
70
60
  frame.writeUInt32LE(0, REPLY_OFFSET.size);
71
- assert.throws(() => extractResponseFrames('vsr', frame, LIMIT), ProtocolFrameError);
61
+ assert.throws(() => extractResponseFrames(frame, LIMIT), ProtocolFrameError);
72
62
  });
73
- it('rejects an oversized VSR frame before buffering its body', () => {
63
+ it('rejects an oversized frame before buffering its body', () => {
74
64
  const header = vsrFrame(Buffer.alloc(0));
75
65
  header.writeUInt32LE(LIMIT + 1, REPLY_OFFSET.size);
76
- assert.throws(() => extractResponseFrames('vsr', header, LIMIT), ProtocolFrameError);
77
- });
78
- it('rejects an oversized classic frame before buffering its body', () => {
79
- const header = classicFrame(Buffer.alloc(0));
80
- header.writeUInt32LE(LIMIT, 4);
81
- assert.throws(() => extractResponseFrames('classic', header, LIMIT), ProtocolFrameError);
66
+ assert.throws(() => extractResponseFrames(header, LIMIT), ProtocolFrameError);
82
67
  });
83
68
  });
84
69
  describe('ResponseFrameDecoder', () => {
85
70
  it('decodes bytewise input without losing coalesced frames', () => {
86
- const decoder = new ResponseFrameDecoder('classic', LIMIT);
87
- const first = classicFrame(Buffer.from('first'));
88
- const second = classicFrame(Buffer.from('second'));
71
+ const decoder = new ResponseFrameDecoder(LIMIT);
72
+ const first = vsrFrame(Buffer.from('first'));
73
+ const second = vsrFrame(Buffer.from('second'));
89
74
  const input = Buffer.concat([first, second]);
90
75
  const frames = [];
91
76
  for (const byte of input)
@@ -94,16 +79,16 @@ describe('ResponseFrameDecoder', () => {
94
79
  assert.equal(decoder.hasBufferedData, false);
95
80
  });
96
81
  it('clears a partial frame', () => {
97
- const decoder = new ResponseFrameDecoder('vsr', LIMIT);
98
- decoder.push(vsrFrame(Buffer.from('body')).subarray(0, 100));
82
+ const decoder = new ResponseFrameDecoder(LIMIT);
83
+ decoder.push(vsrFrame(Buffer.from('body')).subarray(0, HEADER_SIZE - 2));
99
84
  assert.equal(decoder.hasBufferedData, true);
100
85
  decoder.clear();
101
86
  assert.equal(decoder.hasBufferedData, false);
102
87
  });
103
88
  it('rejects an oversized frame as soon as its header is complete', () => {
104
- const decoder = new ResponseFrameDecoder('classic', LIMIT);
105
- const header = Buffer.alloc(8);
106
- header.writeUInt32LE(LIMIT, 4);
89
+ const decoder = new ResponseFrameDecoder(LIMIT);
90
+ const header = vsrFrame(Buffer.alloc(0));
91
+ header.writeUInt32LE(LIMIT + 1, REPLY_OFFSET.size);
107
92
  assert.throws(() => decoder.push(header), ProtocolFrameError);
108
93
  });
109
94
  });
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import type { ClientConfig, ClientCredentials, CommandResponse, PasswordCredentials, Protocol, RawClient, SendCommandOptions, TokenCredentials } from '../client/client.type.js';
2
+ import type { ClientConfig, ClientCredentials, CommandResponse, PasswordCredentials, RawClient, SendCommandOptions, TokenCredentials } from '../client/client.type.js';
3
3
  export declare class VsrResponseTimeoutError extends Error {
4
4
  constructor(timeout: number);
5
5
  }
@@ -8,8 +8,6 @@ export declare class VsrResponseTimeoutError extends Error {
8
8
  * Implements command queuing, authentication, and heartbeat functionality.
9
9
  */
10
10
  export declare class CommandResponseStream extends EventEmitter {
11
- /** Server wire protocol used by this connection */
12
- readonly protocol: Protocol;
13
11
  /** Client configuration */
14
12
  private options;
15
13
  /** Underlying connection to the server */
@@ -69,7 +67,6 @@ export declare class CommandResponseStream extends EventEmitter {
69
67
  */
70
68
  _processNext(command: number, payload: Buffer, handleResp?: boolean): Promise<CommandResponse>;
71
69
  private _processVsrLogin;
72
- private _processClassic;
73
70
  private _processVsr;
74
71
  private _exchange;
75
72
  private isUnloggedCommand;
@@ -16,7 +16,6 @@
16
16
  // under the License.
17
17
  //
18
18
  import { EventEmitter } from 'node:events';
19
- import { handleResponse } from './client.utils.js';
20
19
  import { ResponseError, responseError } from '../wire/error.utils.js';
21
20
  import { debug } from './client.debug.js';
22
21
  import { IggyConnection } from './client.connection.js';
@@ -49,8 +48,6 @@ export class VsrResponseTimeoutError extends Error {
49
48
  * Implements command queuing, authentication, and heartbeat functionality.
50
49
  */
51
50
  export class CommandResponseStream extends EventEmitter {
52
- /** Server wire protocol used by this connection */
53
- protocol;
54
51
  /** Client configuration */
55
52
  options;
56
53
  /** Underlying connection to the server */
@@ -82,7 +79,6 @@ export class CommandResponseStream extends EventEmitter {
82
79
  super();
83
80
  const normalizedConfig = normalizeClientConfig(options);
84
81
  this.options = normalizedConfig;
85
- this.protocol = normalizedConfig.protocol;
86
82
  this.connection = new IggyConnection(normalizedConfig);
87
83
  this.busy = false;
88
84
  this.isAuthenticated = false;
@@ -126,7 +122,7 @@ export class CommandResponseStream extends EventEmitter {
126
122
  const { handleResponse = true, last = true } = options;
127
123
  if (!this.connection.connected)
128
124
  await this.connection.connect();
129
- if (this.options.protocol === 'vsr' && isLoginCommand(command))
125
+ if (isLoginCommand(command))
130
126
  await this._ensureVsrLeader();
131
127
  if (!this.isAuthenticated && !this.isUnloggedCommand(command))
132
128
  await this.authenticate(this.options.credentials);
@@ -191,8 +187,6 @@ export class CommandResponseStream extends EventEmitter {
191
187
  * @returns Promise resolving to the command response
192
188
  */
193
189
  _processNext(command, payload, handleResp = true) {
194
- if (this.options.protocol !== 'vsr')
195
- return this._processClassic(command, payload, handleResp);
196
190
  if (isLoginCommand(command) && this.isAuthenticated)
197
191
  return this._processVsrLogin(command, payload, handleResp);
198
192
  return this._processVsr(command, payload, handleResp);
@@ -201,15 +195,6 @@ export class CommandResponseStream extends EventEmitter {
201
195
  await this._processVsr(LOGOUT.code, LOGOUT.serialize(), true);
202
196
  return this._processVsr(command, payload, handleResp);
203
197
  }
204
- async _processClassic(command, payload, handleResp) {
205
- const response = await this._exchange(() => this.connection.writeCommand(command, payload));
206
- if (!handleResp)
207
- return response;
208
- const parsed = handleResponse(response);
209
- if (parsed.status !== 0)
210
- throw responseError(command, parsed.status);
211
- return parsed;
212
- }
213
198
  async _processVsr(command, payload, handleResp) {
214
199
  let requestWritten = false;
215
200
  try {
@@ -326,8 +311,7 @@ export class CommandResponseStream extends EventEmitter {
326
311
  }
327
312
  isUnloggedCommand(command) {
328
313
  return UNLOGGED_COMMAND_CODE.includes(command) ||
329
- (this.options.protocol === 'vsr' &&
330
- command === COMMAND_CODE.GetClusterMetadata);
314
+ command === COMMAND_CODE.GetClusterMetadata;
331
315
  }
332
316
  async _ensureVsrLeader() {
333
317
  for (let attempt = 0; attempt < 3; attempt += 1) {
@@ -161,7 +161,6 @@ const singleNodeHandler = (port) => (frame, socket) => {
161
161
  socket.write(replyFrame(operation));
162
162
  };
163
163
  const vsrConfig = (port) => ({
164
- protocol: 'vsr',
165
164
  transport: 'TCP',
166
165
  options: { host: '127.0.0.1', port },
167
166
  credentials: { username: 'iggy', password: 'iggy' },
@@ -35,8 +35,6 @@ export type SendCommandOptions = {
35
35
  * Provides direct access to command sending and event handling.
36
36
  */
37
37
  export type RawClient = {
38
- /** Server wire protocol used by this connection */
39
- readonly protocol: Protocol;
40
38
  /** Sends a command to the server and returns the response */
41
39
  sendCommand: (code: number, payload: Buffer, options?: SendCommandOptions) => Promise<CommandResponse>;
42
40
  /** Whether the client has been authenticated */
@@ -83,8 +81,6 @@ export type ReconnectOption = {
83
81
  * Union type for transport-specific connection options.
84
82
  */
85
83
  export type TransportOption = TcpOption | TlsOption;
86
- /** Server wire protocol. */
87
- export type Protocol = 'classic' | 'vsr';
88
84
  /**
89
85
  * Token-based authentication credentials.
90
86
  */
@@ -119,8 +115,6 @@ export type PoolSizeOption = {
119
115
  * Complete client configuration for connecting to the Iggy server.
120
116
  */
121
117
  export type ClientConfig = {
122
- /** Server wire protocol (default: classic) */
123
- protocol?: Protocol;
124
118
  /** Transport protocol to use (TCP or TLS) */
125
119
  transport: TransportType;
126
120
  /** Transport-specific connection options */