apache-iggy 0.8.1-edge.3 → 0.8.1-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/README.md CHANGED
@@ -57,11 +57,7 @@ from the SDK command table use `Operation::NonReplicated` and carry the command
57
57
  code in the request header's reserved field. The server remains authoritative
58
58
  for classifying or rejecting extension commands.
59
59
 
60
- The same npm package supports both framing modes. VSR currently supports TCP
61
- only and restricts `Client` to one pooled connection because authentication,
62
- request sequencing, and consumer-group assignments belong to one consensus
63
- session. Configurations requesting VSR over TLS or more than one pooled
64
- connection fail before a socket is opened.
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.
65
61
 
66
62
  VSR authentication translates the existing password and personal-access-token
67
63
  login APIs into the register handshake required by the consensus protocol. A
@@ -24,8 +24,6 @@ export const normalizeClientConfig = (config) => {
24
24
  if (!Number.isSafeInteger(maxResponseFrameSize) ||
25
25
  maxResponseFrameSize < 256)
26
26
  throw new TypeError('maxResponseFrameSize must be a safe integer of at least 256 bytes');
27
- if (protocol === 'vsr' && config.transport === 'TLS')
28
- throw new TypeError('VSR framing currently supports the TCP transport only');
29
27
  if (protocol === 'vsr' &&
30
28
  ((config.poolSize?.min ?? 1) > 1 || (config.poolSize?.max ?? 1) > 1))
31
29
  throw new TypeError('VSR clients currently support exactly one pooled connection');
@@ -50,12 +50,15 @@ describe('normalizeClientConfig', () => {
50
50
  protocol: 'auto'
51
51
  }), /unsupported wire protocol/);
52
52
  });
53
- it('rejects VSR over an untested TLS transport', () => {
54
- assert.throws(() => normalizeClientConfig({
53
+ it('supports VSR over TLS', () => {
54
+ const normalized = normalizeClientConfig({
55
55
  ...config(),
56
56
  protocol: 'vsr',
57
57
  transport: 'TLS'
58
- }), /TCP transport only/);
58
+ });
59
+ assert.equal(normalized.protocol, 'vsr');
60
+ assert.equal(normalized.transport, 'TLS');
61
+ assert.deepEqual(normalized.poolSize, { min: 1, max: 1 });
59
62
  });
60
63
  it('rejects unsafe response frame limits', () => {
61
64
  for (const maxResponseFrameSize of [0, 255, 1.5, Number.MAX_VALUE])
@@ -17,8 +17,10 @@
17
17
  //
18
18
  import assert from 'node:assert/strict';
19
19
  import { once } from 'node:events';
20
+ import { readFileSync } from 'node:fs';
20
21
  import { createServer } from 'node:net';
21
22
  import { describe, it } from 'node:test';
23
+ import { createServer as createTlsServer } from 'node:tls';
22
24
  import { COMMAND_CODE } from '../wire/command.code.js';
23
25
  import { ResponseError } from '../wire/error.utils.js';
24
26
  import { Command2, EVICTION_OFFSET, EvictionReason, HEADER_SIZE, REPLY_OFFSET, REQUEST_OFFSET } from '../wire/vsr/header.js';
@@ -26,10 +28,13 @@ import { Operation } from '../wire/vsr/operation.js';
26
28
  import { VsrEvictionError } from '../wire/vsr/reply.js';
27
29
  import { CommandResponseStream } from './client.socket.js';
28
30
  const TEST_SESSION = 42n;
31
+ const TLS_CERTIFICATE = readFileSync(new URL('../../../../core/certs/iggy_cert.pem', import.meta.url));
32
+ const TLS_KEY = readFileSync(new URL('../../../../core/certs/iggy_key.pem', import.meta.url));
33
+ const TLS_CA_CERTIFICATE = readFileSync(new URL('../../../../core/certs/iggy_ca_cert.pem', import.meta.url));
29
34
  /** Loopback server speaking just enough VSR framing for the client tests. */
30
- const startVsrServer = async (handler) => {
35
+ const startVsrServer = async (handler, transport = 'TCP') => {
31
36
  const frames = [];
32
- const server = createServer((socket) => {
37
+ const handleConnection = (socket) => {
33
38
  let pending = Buffer.alloc(0);
34
39
  socket.on('data', (data) => {
35
40
  pending = Buffer.concat([pending, data]);
@@ -44,7 +49,10 @@ const startVsrServer = async (handler) => {
44
49
  }
45
50
  });
46
51
  socket.on('error', () => { });
47
- });
52
+ };
53
+ const server = transport === 'TLS'
54
+ ? createTlsServer({ cert: TLS_CERTIFICATE, key: TLS_KEY }, handleConnection)
55
+ : createServer(handleConnection);
48
56
  server.listen(0, '127.0.0.1');
49
57
  await once(server, 'listening');
50
58
  return {
@@ -160,6 +168,28 @@ const vsrConfig = (port) => ({
160
168
  reconnect: { enabled: false, interval: 100, maxRetries: 1 }
161
169
  });
162
170
  describe('VSR client socket', () => {
171
+ it('exchanges VSR frames over TLS', async () => {
172
+ const server = await startVsrServer((frame, socket) => singleNodeHandler(server.port)(frame, socket), 'TLS');
173
+ const client = new CommandResponseStream({
174
+ ...vsrConfig(server.port),
175
+ transport: 'TLS',
176
+ options: {
177
+ host: '127.0.0.1',
178
+ port: server.port,
179
+ servername: 'localhost',
180
+ ca: TLS_CA_CERTIFICATE
181
+ }
182
+ });
183
+ try {
184
+ const response = await client.sendCommand(60_015, Buffer.from('tls'));
185
+ assert.equal(response.status, 0);
186
+ assert.equal(server.frames.length, 3);
187
+ }
188
+ finally {
189
+ client.destroy();
190
+ await server.close();
191
+ }
192
+ });
163
193
  it('registers one session before the first authenticated command', async () => {
164
194
  const server = await startVsrServer((frame, socket) => singleNodeHandler(server.port)(frame, socket));
165
195
  const client = new CommandResponseStream(vsrConfig(server.port));
@@ -46,9 +46,13 @@ const caCertPath = process.env.E2E_ROOT_CA_CERT
46
46
  const getTlsClient = () => {
47
47
  const [, port] = getIggyAddress();
48
48
  const caCert = readFileSync(caCertPath);
49
+ const protocol = process.env.IGGY_TEST_PROTOCOL === 'vsr'
50
+ ? 'vsr'
51
+ : 'classic';
49
52
  // The server certificate SAN is DNS:localhost, so we connect via 'localhost'
50
53
  // for proper hostname verification (consistent with Python and C# TLS tests).
51
54
  return new Client({
55
+ protocol,
52
56
  transport: 'TLS',
53
57
  options: {
54
58
  port,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "apache-iggy",
3
3
  "type": "module",
4
- "version": "0.8.1-edge.3",
4
+ "version": "0.8.1-edge.4",
5
5
  "description": "Official Apache Iggy NodeJS SDK",
6
6
  "keywords": [
7
7
  "iggy",
@@ -58,8 +58,8 @@
58
58
  "devDependencies": {
59
59
  "@commitlint/cli": "21.2.1",
60
60
  "@commitlint/config-conventional": "21.2.0",
61
- "@cucumber/cucumber": "13.1.1",
62
- "@swc-node/register": "1.12.0",
61
+ "@cucumber/cucumber": "13.2.0",
62
+ "@swc-node/register": "1.12.1",
63
63
  "@types/debug": "4.1.13",
64
64
  "@types/node": "26.1.1",
65
65
  "c8": "^12.0.0",