apache-iggy 0.10.0-edge.3 → 0.10.0-edge.5
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/NOTICE +1 -1
- package/README.md +39 -6
- package/dist/client/client.config.d.ts +2 -2
- package/dist/client/client.config.js +23 -0
- package/dist/client/client.config.test.js +29 -0
- package/dist/client/client.connection-string.d.ts +14 -0
- package/dist/client/client.connection-string.js +188 -0
- package/dist/client/client.connection-string.test.d.ts +2 -0
- package/dist/client/client.connection-string.test.js +209 -0
- package/dist/client/client.connection.d.ts +74 -5
- package/dist/client/client.connection.js +255 -60
- package/dist/client/client.connection.test.js +415 -5
- package/dist/client/client.d.ts +7 -7
- package/dist/client/client.debug.js +0 -1
- package/dist/client/client.frame.test.js +2 -2
- package/dist/client/client.js +24 -25
- package/dist/client/client.socket.d.ts +59 -4
- package/dist/client/client.socket.js +350 -31
- package/dist/client/client.socket.test.js +472 -4
- package/dist/client/client.type.d.ts +37 -13
- package/dist/client/client.type.js +0 -1
- package/dist/client/client.utils.js +0 -1
- package/dist/client/client.utils.test.js +0 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -1
- package/dist/client.e2e.js +0 -1
- package/dist/constant.d.ts +3 -0
- package/dist/constant.js +19 -0
- package/dist/debug-send.js +0 -1
- package/dist/debug.js +0 -1
- package/dist/duration.utils.d.ts +23 -0
- package/dist/duration.utils.js +302 -0
- package/dist/duration.utils.test.d.ts +2 -0
- package/dist/duration.utils.test.js +140 -0
- package/dist/e2e/tcp.client.e2e.js +0 -1
- package/dist/e2e/tcp.cluster.e2e.js +0 -1
- package/dist/e2e/tcp.connection-string.e2e.d.ts +2 -0
- package/dist/e2e/tcp.connection-string.e2e.js +117 -0
- package/dist/e2e/tcp.consumer-group.e2e.js +0 -1
- package/dist/e2e/tcp.consumer-stream.e2e.js +0 -1
- package/dist/e2e/tcp.parallel.e2e.js +0 -1
- package/dist/e2e/tcp.raw.e2e.js +0 -1
- package/dist/e2e/tcp.send-message.e2e.js +0 -1
- package/dist/e2e/tcp.stream.e2e.js +0 -1
- package/dist/e2e/tcp.system.e2e.js +0 -1
- package/dist/e2e/tcp.token.e2e.js +0 -1
- package/dist/e2e/tcp.topic.e2e.js +0 -1
- package/dist/e2e/tcp.user.e2e.js +0 -1
- package/dist/e2e/test-client.utils.js +0 -1
- package/dist/e2e/tls.system.e2e.js +41 -1
- package/dist/index.js +0 -1
- package/dist/stream/consumer-stream.d.ts +3 -3
- package/dist/stream/consumer-stream.js +0 -1
- package/dist/stream/index.js +0 -1
- package/dist/tcp.e2e.js +0 -1
- package/dist/tcp.sm.utils.js +0 -1
- package/dist/tls.system.e2e.js +0 -1
- package/dist/type.utils.js +0 -1
- package/dist/wire/client/client.utils.js +0 -1
- package/dist/wire/client/get-client.command.js +0 -1
- package/dist/wire/client/get-clients.command.js +0 -1
- package/dist/wire/client/get-me.command.js +0 -1
- package/dist/wire/client/index.js +0 -1
- package/dist/wire/cluster/cluster.type.js +0 -1
- package/dist/wire/cluster/cluster.utils.js +0 -1
- package/dist/wire/cluster/get-cluster-metadata.command.js +0 -1
- package/dist/wire/cluster/index.js +0 -1
- package/dist/wire/command-set.js +0 -1
- package/dist/wire/command-set.test.js +0 -1
- package/dist/wire/command.code.d.ts +0 -2
- package/dist/wire/command.code.js +0 -3
- package/dist/wire/command.utils.js +0 -1
- package/dist/wire/consumer-group/create-group.command.js +0 -1
- package/dist/wire/consumer-group/create-group.command.test.js +0 -1
- package/dist/wire/consumer-group/delete-group.command.js +0 -1
- package/dist/wire/consumer-group/ensure-group.virtual.command.js +0 -1
- package/dist/wire/consumer-group/get-group.command.js +0 -1
- package/dist/wire/consumer-group/get-groups.command.js +0 -1
- package/dist/wire/consumer-group/group.command.test.js +0 -1
- package/dist/wire/consumer-group/group.utils.js +0 -1
- package/dist/wire/consumer-group/index.js +0 -1
- package/dist/wire/consumer-group/join-group.command.js +0 -1
- package/dist/wire/consumer-group/leave-group.command.js +0 -1
- package/dist/wire/consumer-group/sync-group.command.js +0 -1
- package/dist/wire/consumer-group/sync-group.command.test.js +0 -1
- package/dist/wire/error.code.js +89 -14
- package/dist/wire/error.code.test.js +7 -7
- package/dist/wire/error.utils.js +0 -1
- package/dist/wire/identifier.utils.js +0 -1
- package/dist/wire/identifier.utils.test.js +0 -1
- package/dist/wire/index.js +0 -1
- package/dist/wire/message/flush-unsaved-buffers.command.js +0 -1
- package/dist/wire/message/header.type.d.ts +1 -1
- package/dist/wire/message/header.type.js +0 -1
- package/dist/wire/message/header.utils.js +0 -1
- package/dist/wire/message/header.utils.test.js +0 -1
- package/dist/wire/message/iggy-header.utils.d.ts +95 -24
- package/dist/wire/message/iggy-header.utils.js +90 -44
- package/dist/wire/message/index.js +0 -1
- package/dist/wire/message/message-batch.test.d.ts +2 -0
- package/dist/wire/message/message-batch.test.js +172 -0
- package/dist/wire/message/message.utils.d.ts +25 -22
- package/dist/wire/message/message.utils.js +81 -51
- package/dist/wire/message/partitioning.utils.js +0 -1
- package/dist/wire/message/poll-messages.command.js +0 -1
- package/dist/wire/message/poll-messages.command.test.js +1 -2
- package/dist/wire/message/poll.utils.d.ts +36 -3
- package/dist/wire/message/poll.utils.js +62 -28
- package/dist/wire/message/send-messages.command.js +0 -1
- package/dist/wire/message/send-messages.command.test.js +3 -2
- package/dist/wire/number.utils.js +0 -1
- package/dist/wire/offset/delete-offset.command.js +2 -3
- package/dist/wire/offset/get-offset.command.js +0 -1
- package/dist/wire/offset/index.js +0 -1
- package/dist/wire/offset/offset.utils.d.ts +10 -0
- package/dist/wire/offset/offset.utils.js +17 -2
- package/dist/wire/offset/store-offset.command.js +0 -1
- package/dist/wire/options.utils.js +0 -1
- package/dist/wire/options.utils.test.js +0 -1
- package/dist/wire/partition/create-partition.command.js +0 -1
- package/dist/wire/partition/delete-partition.command.js +0 -1
- package/dist/wire/partition/index.js +0 -1
- package/dist/wire/partition/partition.utils.js +0 -1
- package/dist/wire/partition/partition.utils.test.js +0 -1
- package/dist/wire/segment/delete-segments.command.js +0 -1
- package/dist/wire/segment/index.js +0 -1
- package/dist/wire/segment/segment.utils.js +0 -1
- package/dist/wire/serialize.utils.js +0 -1
- package/dist/wire/session/index.js +0 -1
- package/dist/wire/session/login-with-token.command.js +0 -1
- package/dist/wire/session/login-with-token.command.test.js +0 -1
- package/dist/wire/session/login.command.js +0 -1
- package/dist/wire/session/login.command.test.js +0 -1
- package/dist/wire/session/login.utils.js +0 -1
- package/dist/wire/session/logout.command.js +0 -1
- package/dist/wire/stream/create-stream.command.js +0 -1
- package/dist/wire/stream/create-stream.command.test.js +0 -1
- package/dist/wire/stream/delete-stream.command.js +0 -1
- package/dist/wire/stream/ensure-stream.virtual.command.js +0 -1
- package/dist/wire/stream/get-stream.command.js +0 -1
- package/dist/wire/stream/get-streams.command.js +0 -1
- package/dist/wire/stream/index.js +0 -1
- package/dist/wire/stream/purge-stream.command.js +0 -1
- package/dist/wire/stream/stream.utils.js +0 -1
- package/dist/wire/stream/update-stream.command.js +0 -1
- package/dist/wire/system/describe-options.command.js +0 -1
- package/dist/wire/system/get-stats.command.js +0 -1
- package/dist/wire/system/index.js +0 -1
- package/dist/wire/system/ping.command.js +0 -1
- package/dist/wire/token/create-token.command.js +0 -1
- package/dist/wire/token/create-token.command.test.js +0 -1
- package/dist/wire/token/delete-token.command.js +0 -1
- package/dist/wire/token/delete-token.command.test.js +0 -1
- package/dist/wire/token/get-tokens.command.js +0 -1
- package/dist/wire/token/index.js +0 -1
- package/dist/wire/token/token.utils.js +0 -1
- package/dist/wire/topic/create-topic.command.js +0 -1
- package/dist/wire/topic/create-topic.command.test.js +0 -1
- package/dist/wire/topic/delete-topic.command.js +0 -1
- package/dist/wire/topic/ensure-topic.virtual.command.js +0 -1
- package/dist/wire/topic/get-topic.command.js +0 -1
- package/dist/wire/topic/get-topics.command.js +0 -1
- package/dist/wire/topic/index.js +0 -1
- package/dist/wire/topic/purge-topic.command.js +0 -1
- package/dist/wire/topic/topic.utils.js +0 -1
- package/dist/wire/topic/topic.utils.test.js +0 -1
- package/dist/wire/topic/update-topic.command.js +0 -1
- package/dist/wire/user/change-password.command.js +0 -1
- package/dist/wire/user/create-user.command.js +0 -1
- package/dist/wire/user/create-user.command.test.js +0 -1
- package/dist/wire/user/delete-user.command.js +0 -1
- package/dist/wire/user/get-user.command.js +0 -1
- package/dist/wire/user/get-users.command.js +0 -1
- package/dist/wire/user/index.js +0 -1
- package/dist/wire/user/permissions.utils.js +0 -1
- package/dist/wire/user/permissions.utils.test.js +0 -1
- package/dist/wire/user/update-permissions.command.js +0 -1
- package/dist/wire/user/update-user.command.js +0 -1
- package/dist/wire/user/user.utils.js +0 -1
- package/dist/wire/uuid.utils.js +0 -1
- package/dist/wire/vsr/header.d.ts +2 -2
- package/dist/wire/vsr/header.js +3 -4
- package/dist/wire/vsr/header.test.js +3 -3
- package/dist/wire/vsr/index.js +0 -1
- package/dist/wire/vsr/operation.d.ts +0 -2
- package/dist/wire/vsr/operation.js +2 -9
- package/dist/wire/vsr/operation.test.js +2 -6
- package/dist/wire/vsr/register.js +0 -1
- package/dist/wire/vsr/reply.js +3 -4
- package/dist/wire/vsr/reply.test.js +4 -4
- package/dist/wire/vsr/session.js +0 -1
- package/dist/wire/vsr/vsr.test.js +1 -2
- package/package.json +5 -4
package/NOTICE
CHANGED
package/README.md
CHANGED
|
@@ -10,12 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
Apache Iggy Node.js client written in typescript, it currently only supports tcp & tls transports.
|
|
12
12
|
|
|
13
|
-
> Apache Iggy (Incubating) is an effort undergoing incubation at the Apache Software Foundation (ASF), sponsored by the Apache Incubator PMC.
|
|
14
|
-
>
|
|
15
|
-
> Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making process have stabilized in a manner consistent with other successful ASF projects.
|
|
16
|
-
>
|
|
17
|
-
> While incubation status is not necessarily a reflection of the completeness or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.
|
|
18
|
-
|
|
19
13
|
diclaimer: although all iggy commands & basic client/stream are implemented this is still a WIP, provided as is, and has still a long way to go to be considered "battle tested".
|
|
20
14
|
|
|
21
15
|
note: This lib started as _iggy-bin_ ( [github](https://github.com/T1B0/iggy-bin) / [npm](https://www.npmjs.com/package/iggy-bin)) before migrating under iggy-rs org. package iggy-bin@v1.3.4 is equivalent to @iggy.rs/sdk@v1.0.3 and migrating again under apache iggy monorepo ( [github](https://github.com/apache/iggy/tree/master/foreign/node) and is now published on npmjs as apache-iggy
|
|
@@ -73,6 +67,8 @@ new session.
|
|
|
73
67
|
|
|
74
68
|
The client pings every `heartbeatInterval` milliseconds, 5000 by default, which
|
|
75
69
|
keeps an idle session alive when the server's `[heartbeat]` eviction is enabled.
|
|
70
|
+
`heartbeatInterval` also accepts a duration expression such as `"10s"` or
|
|
71
|
+
`"1h 30m"`, like the Rust SDK.
|
|
76
72
|
The server evicts a connection silent for 36 s, which is 1.2 x its 30 s
|
|
77
73
|
heartbeat interval. Raising the client interval past that window, or setting it
|
|
78
74
|
to 0 to disable client heartbeats, exposes an idle consumer-group member to
|
|
@@ -111,6 +107,43 @@ const client = new Client({
|
|
|
111
107
|
const stats = await client.system.getStats();
|
|
112
108
|
```
|
|
113
109
|
|
|
110
|
+
### Connection strings
|
|
111
|
+
|
|
112
|
+
Every client constructor (except `SimpleClient` see note) also accepts a
|
|
113
|
+
connection string instead of a config object:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { Client } from "apache-iggy";
|
|
117
|
+
|
|
118
|
+
const client = new Client("iggy://iggy:iggy@127.0.0.1:8090");
|
|
119
|
+
const stats = await client.system.getStats();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Supported schemes are `iggy://` (TCP, default) and `iggy+tcp://`. Credentials
|
|
123
|
+
are `username:password` or a single personal access token. Options mirror the
|
|
124
|
+
other SDKs: `tls`, `tls_domain`, `tls_ca_file`, `reconnection_retries`,
|
|
125
|
+
`reconnection_interval`, `heartbeat_interval` and `nodelay`. `reestablish_after`
|
|
126
|
+
is accepted for format compatibility but has no Node equivalent.
|
|
127
|
+
|
|
128
|
+
note: `SimpleClient` does not accept a connection string: it wraps an existing
|
|
129
|
+
`RawClient` instance rather than building one from configuration. Pass the
|
|
130
|
+
connection string to `Client`, `SingleClient` or `getRawClient` and hand the
|
|
131
|
+
resulting raw client to `SimpleClient` if needed.
|
|
132
|
+
|
|
133
|
+
### option limits
|
|
134
|
+
|
|
135
|
+
| option | limit |
|
|
136
|
+
| --- | --- |
|
|
137
|
+
| `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 |
|
|
138
|
+
| `heartbeat_interval` | duration up to `2147483647ms` (Node's largest timer delay); `0` disables heartbeats |
|
|
139
|
+
| `reconnection_interval` | positive duration (`ms`, `s`, `m`, `h`) up to `2147483647ms` (Node's largest timer delay); zero spellings are rejected. Defaults to `1s` |
|
|
140
|
+
| port in the authority | decimal up to `65535` |
|
|
141
|
+
|
|
142
|
+
Durations accept the same expressions as the Rust SDK, for example `500ms`,
|
|
143
|
+
`10s`, `1h 30m`, `5d`, `2w`, `1y`; matching is case-insensitive and
|
|
144
|
+
`0`, `unlimited`, `disabled` and `none` map to zero. Unit-less numbers such as
|
|
145
|
+
`5` are rejected.
|
|
146
|
+
|
|
114
147
|
## use sources
|
|
115
148
|
|
|
116
149
|
### Install
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientConfig } from './client.type.js';
|
|
1
|
+
import type { ClientConfig, ClientConfigOrString } from './client.type.js';
|
|
2
2
|
export declare const DEFAULT_MAX_RESPONSE_FRAME_SIZE: number;
|
|
3
3
|
/**
|
|
4
4
|
* The server evicts a connection silent for 36 s (1.2 x its 30 s heartbeat
|
|
@@ -7,5 +7,5 @@ export declare const DEFAULT_MAX_RESPONSE_FRAME_SIZE: number;
|
|
|
7
7
|
export declare const DEFAULT_HEARTBEAT_INTERVAL: number;
|
|
8
8
|
/** Node's largest timer delay: setInterval clamps anything above it to 1 ms. */
|
|
9
9
|
export declare const MAX_HEARTBEAT_INTERVAL = 2147483647;
|
|
10
|
-
export declare const normalizeClientConfig: (config:
|
|
10
|
+
export declare const normalizeClientConfig: (config: ClientConfigOrString) => ClientConfig;
|
|
11
11
|
//# sourceMappingURL=client.config.d.ts.map
|
|
@@ -14,6 +14,8 @@
|
|
|
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 { MAX_U32 } from '../constant.js';
|
|
18
|
+
import { parseConnectionString } from './client.connection-string.js';
|
|
17
19
|
export const DEFAULT_MAX_RESPONSE_FRAME_SIZE = 64 * 1024 * 1024;
|
|
18
20
|
/**
|
|
19
21
|
* The server evicts a connection silent for 36 s (1.2 x its 30 s heartbeat
|
|
@@ -23,6 +25,8 @@ export const DEFAULT_HEARTBEAT_INTERVAL = 5 * 1000;
|
|
|
23
25
|
/** Node's largest timer delay: setInterval clamps anything above it to 1 ms. */
|
|
24
26
|
export const MAX_HEARTBEAT_INTERVAL = 2_147_483_647;
|
|
25
27
|
export const normalizeClientConfig = (config) => {
|
|
28
|
+
if (typeof config === 'string')
|
|
29
|
+
config = parseConnectionString(config);
|
|
26
30
|
const maxResponseFrameSize = config.maxResponseFrameSize ?? DEFAULT_MAX_RESPONSE_FRAME_SIZE;
|
|
27
31
|
if (!Number.isSafeInteger(maxResponseFrameSize) ||
|
|
28
32
|
maxResponseFrameSize < 256)
|
|
@@ -38,6 +42,25 @@ export const normalizeClientConfig = (config) => {
|
|
|
38
42
|
heartbeatInterval < 0 ||
|
|
39
43
|
heartbeatInterval > MAX_HEARTBEAT_INTERVAL)
|
|
40
44
|
throw new TypeError(`heartbeatInterval must be a safe integer of milliseconds between 0 and ${MAX_HEARTBEAT_INTERVAL} (0 disables heartbeats)`);
|
|
45
|
+
// Unlike the heartbeat, 0 is not a disable here: an immediate retry delay
|
|
46
|
+
// turns reconnection into a hot loop. The ceiling guards the same
|
|
47
|
+
// setInterval clamp, which would turn a long backoff into a 1 ms spin.
|
|
48
|
+
// A disabled reconnect never schedules a retry, so the interval check
|
|
49
|
+
// applies only when enabled; callers commonly pass a zero interval with
|
|
50
|
+
// enabled: false. maxRetries stays bounded either way, matching the
|
|
51
|
+
// connection-string path.
|
|
52
|
+
if (config.reconnect !== undefined) {
|
|
53
|
+
const { enabled, interval, maxRetries } = config.reconnect;
|
|
54
|
+
if (!Number.isSafeInteger(maxRetries) ||
|
|
55
|
+
maxRetries < 0 ||
|
|
56
|
+
maxRetries > MAX_U32)
|
|
57
|
+
throw new TypeError(`reconnect.maxRetries must be a non-negative integer of at most ${MAX_U32}`);
|
|
58
|
+
if (enabled &&
|
|
59
|
+
(!Number.isSafeInteger(interval) ||
|
|
60
|
+
interval < 1 ||
|
|
61
|
+
interval > MAX_HEARTBEAT_INTERVAL))
|
|
62
|
+
throw new TypeError(`reconnect.interval must be a safe integer of milliseconds between 1 and ${MAX_HEARTBEAT_INTERVAL}`);
|
|
63
|
+
}
|
|
41
64
|
return {
|
|
42
65
|
...config,
|
|
43
66
|
options: { ...config.options },
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// under the License.
|
|
17
17
|
import assert from 'node:assert/strict';
|
|
18
18
|
import { describe, it } from 'node:test';
|
|
19
|
+
import { MAX_U32 } from '../constant.js';
|
|
19
20
|
import { DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_MAX_RESPONSE_FRAME_SIZE, normalizeClientConfig } from './client.config.js';
|
|
20
21
|
const config = () => ({
|
|
21
22
|
transport: 'TCP',
|
|
@@ -44,6 +45,34 @@ describe('normalizeClientConfig', () => {
|
|
|
44
45
|
heartbeatInterval
|
|
45
46
|
}), /heartbeatInterval/);
|
|
46
47
|
});
|
|
48
|
+
it('accepts a usable reconnect interval', () => {
|
|
49
|
+
const reconnect = { enabled: true, interval: 1000, maxRetries: 3 };
|
|
50
|
+
assert.deepEqual(normalizeClientConfig({ ...config(), reconnect }).reconnect, reconnect);
|
|
51
|
+
});
|
|
52
|
+
it('rejects unusable reconnect intervals', () => {
|
|
53
|
+
for (const interval of [
|
|
54
|
+
0, -1000, Number.NaN, 1.5, 2_147_483_648, Number.MAX_VALUE
|
|
55
|
+
])
|
|
56
|
+
assert.throws(() => normalizeClientConfig({
|
|
57
|
+
...config(),
|
|
58
|
+
reconnect: { enabled: true, interval, maxRetries: 1 }
|
|
59
|
+
}), /reconnect\.interval/);
|
|
60
|
+
});
|
|
61
|
+
it('skips the interval check when reconnect is disabled', () => {
|
|
62
|
+
assert.doesNotThrow(() => normalizeClientConfig({
|
|
63
|
+
...config(),
|
|
64
|
+
reconnect: { enabled: false, interval: 0, maxRetries: 0 }
|
|
65
|
+
}));
|
|
66
|
+
});
|
|
67
|
+
it('rejects unusable reconnect maxRetries', () => {
|
|
68
|
+
for (const maxRetries of [
|
|
69
|
+
-1, Number.NaN, 1.5, MAX_U32 + 1, Number.MAX_VALUE
|
|
70
|
+
])
|
|
71
|
+
assert.throws(() => normalizeClientConfig({
|
|
72
|
+
...config(),
|
|
73
|
+
reconnect: { enabled: true, interval: 1000, maxRetries }
|
|
74
|
+
}), /reconnect\.maxRetries/);
|
|
75
|
+
});
|
|
47
76
|
it('restricts the client to one pooled connection', () => {
|
|
48
77
|
const normalized = normalizeClientConfig(config());
|
|
49
78
|
assert.deepEqual(normalized.poolSize, { min: 1, max: 1 });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ClientConfig } from './client.type.js';
|
|
2
|
+
/** Parses a duration with the exact grammar of the Rust SDK's
|
|
3
|
+
* `IggyDuration::from_str`, returning whole milliseconds.
|
|
4
|
+
*/
|
|
5
|
+
export declare const parseDuration: (value: string) => number;
|
|
6
|
+
/**
|
|
7
|
+
* Parses an Iggy connection string into a client configuration.
|
|
8
|
+
*
|
|
9
|
+
* Supports `iggy://` and `iggy+tcp://`; the Node SDK implements TCP/TLS only.
|
|
10
|
+
* Credentials are either `username:password` or a single personal access
|
|
11
|
+
* token before the `@`. TLS is enabled with `tls=true`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const parseConnectionString: (connectionString: string) => ClientConfig;
|
|
14
|
+
//# sourceMappingURL=client.connection-string.d.ts.map
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
// or more contributor license agreements. See the NOTICE file
|
|
3
|
+
// distributed with this work for additional information
|
|
4
|
+
// regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
// to you under the Apache License, Version 2.0 (the
|
|
6
|
+
// "License"); you may not use this file except in compliance
|
|
7
|
+
// with the License. You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing,
|
|
12
|
+
// software distributed under the License is distributed on an
|
|
13
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
// KIND, either express or implied. See the License for the
|
|
15
|
+
// specific language governing permissions and limitations
|
|
16
|
+
// under the License.
|
|
17
|
+
import { MAX_U32 } from '../constant.js';
|
|
18
|
+
import { nanosecondsToMilliseconds, parseIggyDurationNanoseconds } from '../duration.utils.js';
|
|
19
|
+
const DEFAULT_PROTOCOL = 'iggy';
|
|
20
|
+
const SCHEME_PREFIX = 'iggy+';
|
|
21
|
+
const SUPPORTED_PROTOCOLS = ['tcp'];
|
|
22
|
+
/** Reconnection defaults carried by every connection string.
|
|
23
|
+
*
|
|
24
|
+
* Mirrors TcpConnectionStringOptions: retries default to unlimited and the
|
|
25
|
+
* interval to 1s. Deliberately not DefaultReconnectOption, whose 12/5s pair
|
|
26
|
+
* only applies to object configs.
|
|
27
|
+
*/
|
|
28
|
+
const CONNECTION_STRING_RECONNECT = {
|
|
29
|
+
enabled: true,
|
|
30
|
+
interval: 1000,
|
|
31
|
+
maxRetries: MAX_U32
|
|
32
|
+
};
|
|
33
|
+
/** Parses a duration with the exact grammar of the Rust SDK's
|
|
34
|
+
* `IggyDuration::from_str`, returning whole milliseconds.
|
|
35
|
+
*/
|
|
36
|
+
export const parseDuration = (value) => {
|
|
37
|
+
try {
|
|
38
|
+
return nanosecondsToMilliseconds(parseIggyDurationNanoseconds(value));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new TypeError(`invalid duration "${value}"`, { cause: error });
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Parses an Iggy connection string into a client configuration.
|
|
46
|
+
*
|
|
47
|
+
* Supports `iggy://` and `iggy+tcp://`; the Node SDK implements TCP/TLS only.
|
|
48
|
+
* Credentials are either `username:password` or a single personal access
|
|
49
|
+
* token before the `@`. TLS is enabled with `tls=true`.
|
|
50
|
+
*/
|
|
51
|
+
export const parseConnectionString = (connectionString) => {
|
|
52
|
+
if (typeof connectionString !== 'string' || connectionString.length === 0)
|
|
53
|
+
throw new TypeError('connection string must be a non-empty string');
|
|
54
|
+
const protocolParts = connectionString.split('://');
|
|
55
|
+
if (protocolParts.length !== 2)
|
|
56
|
+
throw new TypeError('invalid connection string');
|
|
57
|
+
const scheme = protocolParts[0];
|
|
58
|
+
const protocol = scheme === DEFAULT_PROTOCOL
|
|
59
|
+
? 'tcp'
|
|
60
|
+
: scheme.startsWith(SCHEME_PREFIX)
|
|
61
|
+
? scheme.slice(SCHEME_PREFIX.length)
|
|
62
|
+
: undefined;
|
|
63
|
+
if (protocol === undefined)
|
|
64
|
+
throw new TypeError('invalid connection string');
|
|
65
|
+
if (!SUPPORTED_PROTOCOLS.includes(protocol))
|
|
66
|
+
throw new TypeError(`unsupported transport "${protocol}", Node SDK supports tcp only`);
|
|
67
|
+
const parts = protocolParts[1].split('@');
|
|
68
|
+
if (parts.length !== 2)
|
|
69
|
+
throw new TypeError('invalid connection string');
|
|
70
|
+
const credentials = parts[0].split(':');
|
|
71
|
+
const tokenCredentials = credentials.length === 1;
|
|
72
|
+
if (!tokenCredentials && credentials.length !== 2)
|
|
73
|
+
throw new TypeError('invalid connection string');
|
|
74
|
+
const username = credentials[0];
|
|
75
|
+
const password = credentials[1] ?? '';
|
|
76
|
+
if (!tokenCredentials && (username.length === 0 || password.length === 0))
|
|
77
|
+
throw new TypeError('invalid connection string');
|
|
78
|
+
const serverAndOptions = parts[1].split('?');
|
|
79
|
+
if (serverAndOptions.length > 2)
|
|
80
|
+
throw new TypeError('invalid connection string');
|
|
81
|
+
const serverAddress = serverAndOptions[0];
|
|
82
|
+
// One match covers both `[ipv6]:port` and `host:port`, where the
|
|
83
|
+
// unbracketed host may not contain a colon. Multi-colon authorities such
|
|
84
|
+
// as `2001:db8::1:8090` or `host:8090:9090` are rejected
|
|
85
|
+
const addressMatch = /^(?:\[([^\]]+)\]|([^:\[\]]+)):(\d+)$/.exec(serverAddress);
|
|
86
|
+
if (!addressMatch)
|
|
87
|
+
throw new TypeError('invalid connection string');
|
|
88
|
+
const host = addressMatch[1] ?? addressMatch[2];
|
|
89
|
+
const port = Number(addressMatch[3]);
|
|
90
|
+
if (port > 65535)
|
|
91
|
+
throw new TypeError('invalid connection string');
|
|
92
|
+
const options = serverAndOptions.length === 2
|
|
93
|
+
? parseConnectionOptions(serverAndOptions[1])
|
|
94
|
+
: {
|
|
95
|
+
tls: false,
|
|
96
|
+
reconnect: { ...CONNECTION_STRING_RECONNECT }
|
|
97
|
+
};
|
|
98
|
+
const { tls, reconnect, heartbeatInterval, ...transportOptions } = options;
|
|
99
|
+
const config = {
|
|
100
|
+
transport: tls ? 'TLS' : 'TCP',
|
|
101
|
+
options: {
|
|
102
|
+
host,
|
|
103
|
+
port: Number(port),
|
|
104
|
+
...transportOptions
|
|
105
|
+
},
|
|
106
|
+
credentials: tokenCredentials
|
|
107
|
+
? { token: username }
|
|
108
|
+
: { username, password },
|
|
109
|
+
// Always present on connection strings: Rust applies its unlimited/1s
|
|
110
|
+
// reconnection defaults even without query options.
|
|
111
|
+
reconnect
|
|
112
|
+
};
|
|
113
|
+
if (heartbeatInterval !== undefined)
|
|
114
|
+
config.heartbeatInterval = heartbeatInterval;
|
|
115
|
+
return config;
|
|
116
|
+
};
|
|
117
|
+
const parseConnectionOptions = (optionsString) => {
|
|
118
|
+
const parsed = {
|
|
119
|
+
tls: false,
|
|
120
|
+
reconnect: { ...CONNECTION_STRING_RECONNECT }
|
|
121
|
+
};
|
|
122
|
+
for (const option of optionsString.split('&')) {
|
|
123
|
+
const optionParts = option.split('=');
|
|
124
|
+
if (optionParts.length !== 2)
|
|
125
|
+
throw new TypeError('invalid connection string');
|
|
126
|
+
const [name, value] = optionParts;
|
|
127
|
+
switch (name) {
|
|
128
|
+
case 'tls':
|
|
129
|
+
parsed.tls = parseBoolean(name, value);
|
|
130
|
+
break;
|
|
131
|
+
case 'nodelay':
|
|
132
|
+
parsed.noDelay = parseBoolean(name, value);
|
|
133
|
+
break;
|
|
134
|
+
case 'tls_domain':
|
|
135
|
+
parsed.servername = value;
|
|
136
|
+
break;
|
|
137
|
+
case 'tls_ca_file':
|
|
138
|
+
// The path is stored unread; the certificate is loaded when the
|
|
139
|
+
// TLS socket is created, so plain-TCP configs never touch the
|
|
140
|
+
// filesystem (matches the Rust SDK).
|
|
141
|
+
parsed.caFile = value;
|
|
142
|
+
break;
|
|
143
|
+
case 'reconnection_retries': {
|
|
144
|
+
// Values above u32::MAX are rejected like the Rust SDK's u32
|
|
145
|
+
// overflow; otherwise they would act as a second, undocumented
|
|
146
|
+
// spelling of "unlimited".
|
|
147
|
+
const maxRetries = value === 'unlimited'
|
|
148
|
+
? MAX_U32
|
|
149
|
+
: parseNumber(name, value);
|
|
150
|
+
if (maxRetries > MAX_U32)
|
|
151
|
+
throw new TypeError(`option "${name}" must be at most ${MAX_U32}`);
|
|
152
|
+
parsed.reconnect.maxRetries = maxRetries;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case 'reconnection_interval': {
|
|
156
|
+
const interval = parseDuration(value);
|
|
157
|
+
// With retries defaulting to unlimited, a zero interval would turn
|
|
158
|
+
// the reconnect loop into an unbounded hot loop.
|
|
159
|
+
if (interval <= 0)
|
|
160
|
+
throw new TypeError(`option "${name}" must be positive`);
|
|
161
|
+
parsed.reconnect.interval = interval;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case 'reestablish_after':
|
|
165
|
+
// No Node equivalent: validated as a duration like the Rust SDK
|
|
166
|
+
// does, then discarded.
|
|
167
|
+
parseDuration(value);
|
|
168
|
+
break;
|
|
169
|
+
case 'heartbeat_interval':
|
|
170
|
+
parsed.heartbeatInterval = parseDuration(value);
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
throw new TypeError(`unknown option "${name}"`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return parsed;
|
|
177
|
+
};
|
|
178
|
+
const parseBoolean = (name, value) => {
|
|
179
|
+
if (value !== 'true' && value !== 'false')
|
|
180
|
+
throw new TypeError(`option "${name}" must be true or false`);
|
|
181
|
+
return value === 'true';
|
|
182
|
+
};
|
|
183
|
+
const parseNumber = (name, value) => {
|
|
184
|
+
if (!/^\d+$/.test(value))
|
|
185
|
+
throw new TypeError(`option "${name}" must be a non-negative integer`);
|
|
186
|
+
return Number(value);
|
|
187
|
+
};
|
|
188
|
+
//# sourceMappingURL=client.connection-string.js.map
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Licensed to the Apache Software Foundation (ASF) under one
|
|
2
|
+
// or more contributor license agreements. See the NOTICE file
|
|
3
|
+
// distributed with this work for additional information
|
|
4
|
+
// regarding copyright ownership. The ASF licenses this file
|
|
5
|
+
// to you under the Apache License, Version 2.0 (the
|
|
6
|
+
// "License"); you may not use this file except in compliance
|
|
7
|
+
// with the License. You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing,
|
|
12
|
+
// software distributed under the License is distributed on an
|
|
13
|
+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
14
|
+
// KIND, either express or implied. See the License for the
|
|
15
|
+
// specific language governing permissions and limitations
|
|
16
|
+
// under the License.
|
|
17
|
+
import assert from 'node:assert/strict';
|
|
18
|
+
import { describe, it } from 'node:test';
|
|
19
|
+
import { MAX_U32 } from '../constant.js';
|
|
20
|
+
import { parseConnectionString, parseDuration } from './client.connection-string.js';
|
|
21
|
+
import { DEFAULT_HEARTBEAT_INTERVAL, normalizeClientConfig } from './client.config.js';
|
|
22
|
+
describe('parseConnectionString', () => {
|
|
23
|
+
it('parses the default scheme with password credentials', () => {
|
|
24
|
+
assert.deepEqual(parseConnectionString('iggy://iggy:secret@127.0.0.1:8090'), {
|
|
25
|
+
transport: 'TCP',
|
|
26
|
+
options: { host: '127.0.0.1', port: 8090 },
|
|
27
|
+
credentials: { username: 'iggy', password: 'secret' },
|
|
28
|
+
reconnect: { enabled: true, interval: 1000, maxRetries: MAX_U32 }
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
it('parses the explicit tcp scheme with a personal access token', () => {
|
|
32
|
+
assert.deepEqual(parseConnectionString('iggy+tcp://iggypat-1234567890abcdef@localhost:8090'), {
|
|
33
|
+
transport: 'TCP',
|
|
34
|
+
options: { host: 'localhost', port: 8090 },
|
|
35
|
+
credentials: { token: 'iggypat-1234567890abcdef' },
|
|
36
|
+
reconnect: { enabled: true, interval: 1000, maxRetries: MAX_U32 }
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it('maps tls options to the TLS transport', () => {
|
|
40
|
+
assert.deepEqual(parseConnectionString('iggy://iggy:secret@localhost:8090?tls=true&tls_domain=iggy.apache.org'), {
|
|
41
|
+
transport: 'TLS',
|
|
42
|
+
options: {
|
|
43
|
+
host: 'localhost',
|
|
44
|
+
port: 8090,
|
|
45
|
+
servername: 'iggy.apache.org'
|
|
46
|
+
},
|
|
47
|
+
credentials: { username: 'iggy', password: 'secret' },
|
|
48
|
+
reconnect: { enabled: true, interval: 1000, maxRetries: MAX_U32 }
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it('maps reconnection and heartbeat options', () => {
|
|
52
|
+
assert.deepEqual(parseConnectionString('iggy+tcp://iggy:secret@localhost:8090' +
|
|
53
|
+
'?reconnection_retries=3&reconnection_interval=5s&heartbeat_interval=10s'), {
|
|
54
|
+
transport: 'TCP',
|
|
55
|
+
options: { host: 'localhost', port: 8090 },
|
|
56
|
+
credentials: { username: 'iggy', password: 'secret' },
|
|
57
|
+
reconnect: {
|
|
58
|
+
enabled: true,
|
|
59
|
+
maxRetries: 3,
|
|
60
|
+
interval: 5000
|
|
61
|
+
},
|
|
62
|
+
heartbeatInterval: 10000
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
it('applies unlimited/1s reconnection defaults to partial options', () => {
|
|
66
|
+
// retries alone keep the 1s interval; interval alone keeps unlimited.
|
|
67
|
+
assert.deepEqual(parseConnectionString('iggy://iggy:secret@localhost:8090?reconnection_retries=3').reconnect, { enabled: true, interval: 1000, maxRetries: 3 });
|
|
68
|
+
assert.deepEqual(parseConnectionString('iggy://iggy:secret@localhost:8090?reconnection_interval=5s').reconnect, { enabled: true, interval: 5000, maxRetries: MAX_U32 });
|
|
69
|
+
});
|
|
70
|
+
it('maps nodelay to the socket option', () => {
|
|
71
|
+
assert.equal(parseConnectionString('iggy://iggy:secret@localhost:8090?nodelay=true')
|
|
72
|
+
.options.noDelay, true);
|
|
73
|
+
});
|
|
74
|
+
it('maps unlimited retries to the u32 ceiling', () => {
|
|
75
|
+
assert.equal(parseConnectionString('iggy://iggy:secret@localhost:8090?reconnection_retries=unlimited').reconnect?.maxRetries, MAX_U32);
|
|
76
|
+
});
|
|
77
|
+
it('accepts retry counts up to u32::MAX and rejects overflow', () => {
|
|
78
|
+
assert.equal(parseConnectionString(`iggy://iggy:secret@localhost:8090?reconnection_retries=${MAX_U32}`).reconnect?.maxRetries, MAX_U32);
|
|
79
|
+
for (const value of [
|
|
80
|
+
'iggy://iggy:secret@localhost:8090?reconnection_retries=4294967296',
|
|
81
|
+
'iggy://iggy:secret@localhost:8090?reconnection_retries=99999999999999'
|
|
82
|
+
])
|
|
83
|
+
assert.throws(() => parseConnectionString(value), TypeError);
|
|
84
|
+
});
|
|
85
|
+
it('rejects a non-positive reconnection interval', () => {
|
|
86
|
+
// Zero spellings parse but are rejected by the positivity bound.
|
|
87
|
+
for (const value of ['0', '0ms', 'none'])
|
|
88
|
+
assert.throws(() => parseConnectionString(`iggy://iggy:secret@localhost:8090?reconnection_interval=${value}`), /must be positive/);
|
|
89
|
+
// Negative durations shall not parse
|
|
90
|
+
assert.throws(() => parseConnectionString('iggy://iggy:secret@localhost:8090?reconnection_interval=-1s'), TypeError);
|
|
91
|
+
});
|
|
92
|
+
it('ignores reestablish_after for format compatibility', () => {
|
|
93
|
+
assert.deepEqual(parseConnectionString('iggy://iggy:secret@localhost:8090?reestablish_after=10s'), {
|
|
94
|
+
transport: 'TCP',
|
|
95
|
+
options: { host: 'localhost', port: 8090 },
|
|
96
|
+
credentials: { username: 'iggy', password: 'secret' },
|
|
97
|
+
reconnect: { enabled: true, interval: 1000, maxRetries: MAX_U32 }
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
it('rejects unsupported transports', () => {
|
|
101
|
+
for (const value of [
|
|
102
|
+
'iggy+quic://iggy:secret@localhost:8090',
|
|
103
|
+
'iggy+ws://iggy:secret@localhost:8090'
|
|
104
|
+
])
|
|
105
|
+
assert.throws(() => parseConnectionString(value), /unsupported transport/);
|
|
106
|
+
});
|
|
107
|
+
it('rejects malformed connection strings', () => {
|
|
108
|
+
for (const value of [
|
|
109
|
+
'',
|
|
110
|
+
'iggy',
|
|
111
|
+
'iggy://',
|
|
112
|
+
'iggy://:secret@localhost:8090',
|
|
113
|
+
'iggy://iggy:@localhost:8090',
|
|
114
|
+
'iggy://iggy:secret@localhost',
|
|
115
|
+
'iggy://iggy:secret@:8090',
|
|
116
|
+
'iggy://iggy:secret@localhost:port',
|
|
117
|
+
'iggy://iggy:secret@localhost:70000',
|
|
118
|
+
'iggy://iggy:secret@localhost:8090?unknown=value',
|
|
119
|
+
'iggy://iggy:secret@localhost:8090?tls=maybe',
|
|
120
|
+
'iggy://iggy:secret@localhost:8090?reconnection_retries=three',
|
|
121
|
+
'iggy://iggy:secret@[::1:8090',
|
|
122
|
+
'iggy://iggy:secret@[]:8090',
|
|
123
|
+
'iggy://iggy:secret@[::1]x:8090',
|
|
124
|
+
'iggy://iggy:secret@2001:db8::1:8090',
|
|
125
|
+
'iggy://iggy:secret@host:8090:9090',
|
|
126
|
+
'iggy://iggy:secret@localhost:8090?',
|
|
127
|
+
'iggy://iggy:secret@localhost:8090?&',
|
|
128
|
+
'iggy://iggy:secret@localhost:8090?reestablish_after=garbage'
|
|
129
|
+
])
|
|
130
|
+
assert.throws(() => parseConnectionString(value), TypeError);
|
|
131
|
+
});
|
|
132
|
+
it('never includes the connection string in error messages', () => {
|
|
133
|
+
const secrets = ['hunter2', 'iggypat-1234567890abcdef'];
|
|
134
|
+
for (const value of [
|
|
135
|
+
'iggy://iggy:hunter2@localhost',
|
|
136
|
+
`iggy+tcp://iggypat-1234567890abcdef@localhost`,
|
|
137
|
+
'iggy://iggy:hunter2@localhost:8090?unknown=value',
|
|
138
|
+
'iggy://iggy:hunter2@localhost:8090?tls=maybe',
|
|
139
|
+
'iggy://iggy:hunter2@localhost:8090?reconnection_retries=three',
|
|
140
|
+
'iggy://iggy:hunter2@localhost:70000'
|
|
141
|
+
]) {
|
|
142
|
+
try {
|
|
143
|
+
parseConnectionString(value);
|
|
144
|
+
assert.fail(`expected "${value}" to be rejected`);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
assert.ok(error instanceof TypeError);
|
|
148
|
+
for (const secret of secrets)
|
|
149
|
+
assert.ok(!error.message.includes(secret), `error message leaked a secret: ${error.message}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
it('parses IPv6 host addresses without their brackets', () => {
|
|
154
|
+
assert.deepEqual(parseConnectionString('iggy://iggy:secret@[::1]:8090').options, { host: '::1', port: 8090 });
|
|
155
|
+
});
|
|
156
|
+
it('stores tls_ca_file as a path without reading it at parse time', () => {
|
|
157
|
+
assert.deepEqual(parseConnectionString('iggy://iggy:secret@localhost:8090' +
|
|
158
|
+
'?tls=true&tls_ca_file=/does/not/exist.pem').options, {
|
|
159
|
+
host: 'localhost',
|
|
160
|
+
port: 8090,
|
|
161
|
+
caFile: '/does/not/exist.pem'
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
describe('parseDuration', () => {
|
|
166
|
+
it('converts supported units to milliseconds', () => {
|
|
167
|
+
assert.equal(parseDuration('500ms'), 500);
|
|
168
|
+
assert.equal(parseDuration('5s'), 5000);
|
|
169
|
+
assert.equal(parseDuration('2m'), 120000);
|
|
170
|
+
assert.equal(parseDuration('1h'), 3600000);
|
|
171
|
+
assert.equal(parseDuration('0.5s'), 500);
|
|
172
|
+
assert.equal(parseDuration('1h 1m 1s'), 3661000);
|
|
173
|
+
assert.equal(parseDuration('1h30m'), 5400000);
|
|
174
|
+
assert.equal(parseDuration('5d'), 432000000);
|
|
175
|
+
assert.equal(parseDuration('2w'), 1209600000);
|
|
176
|
+
assert.equal(parseDuration('1y'), 31557600000);
|
|
177
|
+
assert.equal(parseDuration('5sec'), 5000);
|
|
178
|
+
assert.equal(parseDuration('5msec'), 5);
|
|
179
|
+
// Fractional results are rounded to whole milliseconds.
|
|
180
|
+
assert.equal(parseDuration('1.005s'), 1005);
|
|
181
|
+
assert.equal(parseDuration('5usec'), 0);
|
|
182
|
+
assert.equal(parseDuration('500nsec'), 0);
|
|
183
|
+
for (const zero of ['0', 'unlimited', 'disabled', 'none', 'UNLIMITED'])
|
|
184
|
+
assert.equal(parseDuration(zero), 0);
|
|
185
|
+
});
|
|
186
|
+
it('rejects unsupported durations', () => {
|
|
187
|
+
for (const value of ['5', '-1s', 'ms', '', 'abc', 's'])
|
|
188
|
+
assert.throws(() => parseDuration(value), /invalid duration/);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
describe('normalizeClientConfig with connection strings', () => {
|
|
192
|
+
it('applies client defaults to the parsed config', () => {
|
|
193
|
+
const normalized = normalizeClientConfig('iggy://iggy:secret@localhost:8090');
|
|
194
|
+
assert.equal(normalized.transport, 'TCP');
|
|
195
|
+
assert.equal(normalized.options.host, 'localhost');
|
|
196
|
+
assert.equal(normalized.options.port, 8090);
|
|
197
|
+
assert.deepEqual(normalized.credentials, {
|
|
198
|
+
username: 'iggy',
|
|
199
|
+
password: 'secret'
|
|
200
|
+
});
|
|
201
|
+
assert.equal(normalized.heartbeatInterval, DEFAULT_HEARTBEAT_INTERVAL);
|
|
202
|
+
assert.deepEqual(normalized.poolSize, { min: 1, max: 1 });
|
|
203
|
+
});
|
|
204
|
+
it('rejects reconnect intervals beyond the node timer ceiling', () => {
|
|
205
|
+
// Parses to 3_600_000_000 ms; setInterval would clamp it back to 1 ms.
|
|
206
|
+
assert.throws(() => normalizeClientConfig('iggy://iggy:secret@localhost:8090?reconnection_interval=1000h'), /reconnect\.interval/);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
//# sourceMappingURL=client.connection-string.test.js.map
|