Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions foreign/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ new session.

The client pings every `heartbeatInterval` milliseconds, 5000 by default, which
keeps an idle session alive when the server's `[heartbeat]` eviction is enabled.
`heartbeatInterval` also accepts a duration expression such as `"10s"` or
`"1h 30m"`, like the Rust SDK.
The server evicts a connection silent for 36 s, which is 1.2 x its 30 s
heartbeat interval. Raising the client interval past that window, or setting it
to 0 to disable client heartbeats, exposes an idle consumer-group member to
Expand Down Expand Up @@ -105,6 +107,43 @@ const client = new Client({
const stats = await client.system.getStats();
```

### Connection strings

Every client constructor (except `SimpleClient` see note) also accepts a
connection string instead of a config object:

```ts
import { Client } from "apache-iggy";

const client = new Client("iggy://iggy:iggy@127.0.0.1:8090");
const stats = await client.system.getStats();
```

Supported schemes are `iggy://` (TCP, default) and `iggy+tcp://`. Credentials
are `username:password` or a single personal access token. Options mirror the
other SDKs: `tls`, `tls_domain`, `tls_ca_file`, `reconnection_retries`,
`reconnection_interval`, `heartbeat_interval` and `nodelay`. `reestablish_after`
is accepted for format compatibility but has no Node equivalent.

note: `SimpleClient` does not accept a connection string: it wraps an existing
`RawClient` instance rather than building one from configuration. Pass the
connection string to `Client`, `SingleClient` or `getRawClient` and hand the
resulting raw client to `SimpleClient` if needed.

### option limits

| option | limit |
| --- | --- |
| `reconnection_retries` | integer up to `4294967295` (u32 max); larger values are rejected like Rust's u32 overflow, and `unlimited` maps to this ceiling. Defaults to unlimited |
| `heartbeat_interval` | duration up to `2147483647ms` (Node's largest timer delay); `0` disables heartbeats |
| `reconnection_interval` | positive duration (`ms`, `s`, `m`, `h`) up to `2147483647ms` (Node's largest timer delay); zero spellings are rejected. Defaults to `1s` |
| port in the authority | decimal up to `65535` |

Durations accept the same expressions as the Rust SDK, for example `500ms`,
`10s`, `1h 30m`, `5d`, `2w`, `1y`; matching is case-insensitive and
`0`, `unlimited`, `disabled` and `none` map to zero. Unit-less numbers such as
`5` are rejected.

## use sources

### Install
Expand Down
44 changes: 44 additions & 0 deletions foreign/node/src/client/client.config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { MAX_U32 } from '../constant.js';
import type { ClientConfig } from './client.type.js';
import {
DEFAULT_HEARTBEAT_INTERVAL,
Expand Down Expand Up @@ -74,6 +75,49 @@ describe('normalizeClientConfig', () => {
);
});

it('accepts a usable reconnect interval', () => {
const reconnect = { enabled: true, interval: 1000, maxRetries: 3 };
assert.deepEqual(
normalizeClientConfig({ ...config(), reconnect }).reconnect,
reconnect
);
});

it('rejects unusable reconnect intervals', () => {
for (const interval of [
0, -1000, Number.NaN, 1.5, 2_147_483_648, Number.MAX_VALUE
])
assert.throws(
() => normalizeClientConfig({
...config(),
reconnect: { enabled: true, interval, maxRetries: 1 }
}),
/reconnect\.interval/
);
});

it('skips the interval check when reconnect is disabled', () => {
assert.doesNotThrow(() =>
normalizeClientConfig({
...config(),
reconnect: { enabled: false, interval: 0, maxRetries: 0 }
})
);
});

it('rejects unusable reconnect maxRetries', () => {
for (const maxRetries of [
-1, Number.NaN, 1.5, MAX_U32 + 1, Number.MAX_VALUE
])
assert.throws(
() => normalizeClientConfig({
...config(),
reconnect: { enabled: true, interval: 1000, maxRetries }
}),
/reconnect\.maxRetries/
);
});

it('restricts the client to one pooled connection', () => {
const normalized = normalizeClientConfig(config());
assert.deepEqual(normalized.poolSize, { min: 1, max: 1 });
Expand Down
33 changes: 31 additions & 2 deletions foreign/node/src/client/client.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
// specific language governing permissions and limitations
// under the License.

import type { ClientConfig } from './client.type.js';
import { MAX_U32 } from '../constant.js';
import type { ClientConfig, ClientConfigOrString } from './client.type.js';
import { parseConnectionString } from './client.connection-string.js';

export const DEFAULT_MAX_RESPONSE_FRAME_SIZE = 64 * 1024 * 1024;

Expand All @@ -29,8 +31,11 @@ export const DEFAULT_HEARTBEAT_INTERVAL = 5 * 1000;
export const MAX_HEARTBEAT_INTERVAL = 2_147_483_647;

export const normalizeClientConfig = (
config: ClientConfig
config: ClientConfigOrString
): ClientConfig => {
if (typeof config === 'string')
config = parseConnectionString(config);

const maxResponseFrameSize =
config.maxResponseFrameSize ?? DEFAULT_MAX_RESPONSE_FRAME_SIZE;
if (!Number.isSafeInteger(maxResponseFrameSize) ||
Expand All @@ -57,6 +62,30 @@ export const normalizeClientConfig = (
`heartbeatInterval must be a safe integer of milliseconds between 0 and ${MAX_HEARTBEAT_INTERVAL} (0 disables heartbeats)`
);

// Unlike the heartbeat, 0 is not a disable here: an immediate retry delay
// turns reconnection into a hot loop. The ceiling guards the same
// setInterval clamp, which would turn a long backoff into a 1 ms spin.
// A disabled reconnect never schedules a retry, so the interval check
// applies only when enabled; callers commonly pass a zero interval with
// enabled: false. maxRetries stays bounded either way, matching the
// connection-string path.
if (config.reconnect !== undefined) {
Comment thread
hubcio marked this conversation as resolved.
const { enabled, interval, maxRetries } = config.reconnect;
if (!Number.isSafeInteger(maxRetries) ||
maxRetries < 0 ||
maxRetries > MAX_U32)
throw new TypeError(
`reconnect.maxRetries must be a non-negative integer of at most ${MAX_U32}`
);
if (enabled &&
(!Number.isSafeInteger(interval) ||
interval < 1 ||
interval > MAX_HEARTBEAT_INTERVAL))
throw new TypeError(
`reconnect.interval must be a safe integer of milliseconds between 1 and ${MAX_HEARTBEAT_INTERVAL}`
);
}

return {
...config,
options: { ...config.options },
Expand Down
Loading
Loading