@eplightning/nats-server-client 0.1.0

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 ADDED
@@ -0,0 +1 @@
1
+ # @eplightning/nats-server-client
package/dist/index.cjs ADDED
@@ -0,0 +1,230 @@
1
+ var import_node_module = require("node:module");
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
9
+ var __toCommonJS = (from) => {
10
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
11
+ if (entry)
12
+ return entry;
13
+ entry = __defProp({}, "__esModule", { value: true });
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (var key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(entry, key))
17
+ __defProp(entry, key, {
18
+ get: __accessProp.bind(from, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ __moduleCache.set(from, entry);
23
+ return entry;
24
+ };
25
+ var __moduleCache;
26
+ var __returnValue = (v) => v;
27
+ function __exportSetter(name, newValue) {
28
+ this[name] = __returnValue.bind(null, newValue);
29
+ }
30
+ var __export = (target, all) => {
31
+ for (var name in all)
32
+ __defProp(target, name, {
33
+ get: all[name],
34
+ enumerable: true,
35
+ configurable: true,
36
+ set: __exportSetter.bind(all, name)
37
+ });
38
+ };
39
+
40
+ // src/index.ts
41
+ var exports_src = {};
42
+ __export(exports_src, {
43
+ RANDOM_PORT: () => RANDOM_PORT,
44
+ NatsServer: () => NatsServer
45
+ });
46
+ module.exports = __toCommonJS(exports_src);
47
+
48
+ // src/options.ts
49
+ var RANDOM_PORT = -1;
50
+ function buildArgs(opts) {
51
+ const args = [];
52
+ if (opts.port !== undefined) {
53
+ args.push("--port", String(opts.port));
54
+ }
55
+ if (opts.host !== undefined) {
56
+ args.push("--addr", opts.host);
57
+ }
58
+ if (opts.jetstream) {
59
+ args.push("--jetstream");
60
+ if (opts.storeDirectory !== undefined) {
61
+ args.push("--store_dir", opts.storeDirectory);
62
+ }
63
+ }
64
+ if (opts.username !== undefined) {
65
+ args.push("--user", opts.username);
66
+ }
67
+ if (opts.password !== undefined) {
68
+ args.push("--pass", opts.password);
69
+ }
70
+ if (opts.auth !== undefined) {
71
+ args.push("--auth", opts.auth);
72
+ }
73
+ if (opts.extraArguments !== undefined) {
74
+ args.push(...opts.extraArguments);
75
+ }
76
+ return args;
77
+ }
78
+ // src/server.ts
79
+ var import_promises = require("node:fs/promises");
80
+ var import_node_os = require("node:os");
81
+ var import_node_path = require("node:path");
82
+ var import_nats_server = require("@eplightning/nats-server");
83
+
84
+ // src/process.ts
85
+ var import_node_child_process = require("node:child_process");
86
+ function spawnProcess(binaryPath, options) {
87
+ const child = import_node_child_process.spawn(binaryPath, options.args, {
88
+ stdio: ["ignore", "ignore", "pipe"]
89
+ });
90
+ let exitResolve;
91
+ const exitPromise = new Promise((resolve) => {
92
+ exitResolve = resolve;
93
+ });
94
+ child.stderr.setEncoding("utf8");
95
+ const stderrBuffer = { value: "" };
96
+ child.stderr.on("data", (chunk) => {
97
+ stderrBuffer.value = stderrBuffer.value.concat(chunk);
98
+ const lines = stderrBuffer.value.split(/\r?\n/);
99
+ stderrBuffer.value = lines.pop() ?? "";
100
+ for (const line of lines) {
101
+ options.onStderr(line);
102
+ }
103
+ });
104
+ child.on("exit", (code) => {
105
+ if (stderrBuffer.value !== "") {
106
+ options.onStderr(stderrBuffer.value);
107
+ }
108
+ options.onExit(code);
109
+ exitResolve(code);
110
+ });
111
+ return {
112
+ kill(signal) {
113
+ child.kill(signal);
114
+ },
115
+ waitForExit() {
116
+ return exitPromise;
117
+ }
118
+ };
119
+ }
120
+
121
+ // src/server.ts
122
+ var CLIENT_LISTEN_RE = /Listening for client connections on .+:(\d+)$/;
123
+ var SERVER_READY_RE = /Server is ready$/;
124
+
125
+ class NatsServer {
126
+ host;
127
+ port;
128
+ url;
129
+ _process;
130
+ _stopped = false;
131
+ constructor(process, port, host) {
132
+ this._process = process;
133
+ this.port = port;
134
+ this.host = host;
135
+ this.url = `nats://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
136
+ }
137
+ static async start(options = {}) {
138
+ const opts = {
139
+ host: "127.0.0.1",
140
+ startupTimeoutMillis: 20000,
141
+ storeDirectory: import_node_path.join(import_node_os.tmpdir(), `nats-${crypto.randomUUID()}`),
142
+ removeStoreDirectoryOnExit: options.storeDirectory == null,
143
+ ...options
144
+ };
145
+ const host = opts.host ?? "0.0.0.0";
146
+ const isRandomPort = opts.port === RANDOM_PORT;
147
+ const binaryPath = opts.binaryPath ?? await import_nats_server.getBinaryPath();
148
+ const args = buildArgs(opts);
149
+ if (opts.jetstream && opts.storeDirectory) {
150
+ await import_promises.mkdir(opts.storeDirectory, { recursive: true });
151
+ }
152
+ return new Promise((resolve, reject) => {
153
+ let resolved = false;
154
+ let assignedPort = isRandomPort ? undefined : opts.port ?? 4222;
155
+ let serverReady = false;
156
+ let proc;
157
+ const timeoutTimer = setTimeout(() => {
158
+ if (!resolved) {
159
+ resolved = true;
160
+ try {
161
+ proc.kill("SIGKILL");
162
+ } catch {}
163
+ reject(new Error(`NATS server did not become ready within ${opts.startupTimeoutMillis}ms.`));
164
+ }
165
+ }, opts.startupTimeoutMillis);
166
+ function tryResolve() {
167
+ if (resolved || assignedPort == null || !serverReady)
168
+ return;
169
+ resolved = true;
170
+ clearTimeout(timeoutTimer);
171
+ resolve(new NatsServer(proc, assignedPort, host));
172
+ }
173
+ function handleLine(line) {
174
+ if (isRandomPort && assignedPort == null) {
175
+ const match = CLIENT_LISTEN_RE.exec(line);
176
+ if (match) {
177
+ assignedPort = parseInt(match[1], 10);
178
+ }
179
+ }
180
+ if (!serverReady && SERVER_READY_RE.test(line)) {
181
+ serverReady = true;
182
+ }
183
+ tryResolve();
184
+ }
185
+ try {
186
+ proc = spawnProcess(binaryPath, {
187
+ args,
188
+ onStderr: handleLine,
189
+ onExit: async (code) => {
190
+ if (!resolved) {
191
+ resolved = true;
192
+ clearTimeout(timeoutTimer);
193
+ reject(new Error(`NATS server exited unexpectedly with code ${code} before becoming ready.`));
194
+ } else {
195
+ if (opts.removeStoreDirectoryOnExit && opts.storeDirectory && opts.storeDirectory !== "/") {
196
+ try {
197
+ await import_promises.rm(opts.storeDirectory, {
198
+ recursive: true,
199
+ force: true
200
+ });
201
+ } catch (_error) {}
202
+ }
203
+ }
204
+ }
205
+ });
206
+ } catch (err) {
207
+ resolved = true;
208
+ clearTimeout(timeoutTimer);
209
+ const msg = err instanceof Error ? err.message : String(err);
210
+ reject(new Error(`Failed to start NATS server: ${msg}`));
211
+ }
212
+ });
213
+ }
214
+ static async startOnRandomPort(options = {}) {
215
+ return NatsServer.start({ ...options, port: RANDOM_PORT });
216
+ }
217
+ async stop(kill = false) {
218
+ if (this._stopped)
219
+ return;
220
+ this._stopped = true;
221
+ this._process.kill(kill ? "SIGKILL" : "SIGTERM");
222
+ await this._process.waitForExit();
223
+ }
224
+ waitForExit() {
225
+ return this._process.waitForExit();
226
+ }
227
+ get stopped() {
228
+ return this._stopped;
229
+ }
230
+ }
@@ -0,0 +1,107 @@
1
+ declare const RANDOM_PORT = -1;
2
+ /**
3
+ * NATS server options.
4
+ */
5
+ interface NatsServerOptions {
6
+ /**
7
+ * Path to the nats-server binary.
8
+ *
9
+ * If omitted, resolved automatically from the `@eplightning/nats-server` package.
10
+ */
11
+ binaryPath?: string;
12
+ /**
13
+ * Port to listen on for client connections.
14
+ *
15
+ * Use -1 to let the OS choose a random available port.
16
+ * Defaults to 4222 if not specified.
17
+ */
18
+ port?: number;
19
+ /**
20
+ * Host to listen on. Defaults to "127.0.0.1".
21
+ */
22
+ host?: string;
23
+ /**
24
+ * Enable JetStream.
25
+ */
26
+ jetstream?: boolean;
27
+ /**
28
+ * Directory for JetStream storage.
29
+ *
30
+ * Defaults to random temporary directory.
31
+ */
32
+ storeDirectory?: string;
33
+ /**
34
+ * Whether to remove store directory on stop.
35
+ *
36
+ * Defaults to true if using random temporary directory, false otherwise.
37
+ */
38
+ removeStoreDirectoryOnExit?: boolean;
39
+ /**
40
+ * Maximum time in milliseconds to wait for the server to become ready.
41
+ *
42
+ * Defaults to 20000 (20 seconds).
43
+ */
44
+ startupTimeoutMillis?: number;
45
+ /**
46
+ * User for authentication.
47
+ */
48
+ username?: string;
49
+ /**
50
+ * Password for authentication.
51
+ */
52
+ password?: string;
53
+ /**
54
+ * Authentication token.
55
+ */
56
+ auth?: string;
57
+ /**
58
+ * Extra arguments to be passed.
59
+ */
60
+ extraArguments?: string[];
61
+ }
62
+ /**
63
+ * Represents a running NATS server process.
64
+ */
65
+ declare class NatsServer {
66
+ /**
67
+ * The host the server is bound to.
68
+ */
69
+ readonly host: string;
70
+ /**
71
+ * The port the server is listening on.
72
+ */
73
+ readonly port: number;
74
+ /**
75
+ * The client URL for connecting (e.g. "nats://127.0.0.1:4222")
76
+ */
77
+ readonly url: string;
78
+ private readonly _process;
79
+ private _stopped;
80
+ private constructor();
81
+ /**
82
+ * Starts a NATS server process with the given options.
83
+ *
84
+ * @param options
85
+ * @returns A `NatsServer` instance, ready to accept connections.
86
+ */
87
+ static start(options?: NatsServerOptions): Promise<NatsServer>;
88
+ /**
89
+ * Starts a server on a random port.
90
+ *
91
+ * Equivalent to `NatsServer.start({ ...options, port: RANDOM_PORT })`.
92
+ */
93
+ static startOnRandomPort(options?: Omit<NatsServerOptions, "port">): Promise<NatsServer>;
94
+ /**
95
+ * Sends SIGTERM to the server and waits for the process to exit.
96
+ */
97
+ stop(kill?: boolean): Promise<void>;
98
+ /**
99
+ * Waits for the server process to exit.
100
+ */
101
+ waitForExit(): Promise<number | null>;
102
+ /**
103
+ * Whether the server has been stopped.
104
+ */
105
+ get stopped(): boolean;
106
+ }
107
+ export { RANDOM_PORT, NatsServerOptions, NatsServer };
@@ -0,0 +1,107 @@
1
+ declare const RANDOM_PORT = -1;
2
+ /**
3
+ * NATS server options.
4
+ */
5
+ interface NatsServerOptions {
6
+ /**
7
+ * Path to the nats-server binary.
8
+ *
9
+ * If omitted, resolved automatically from the `@eplightning/nats-server` package.
10
+ */
11
+ binaryPath?: string;
12
+ /**
13
+ * Port to listen on for client connections.
14
+ *
15
+ * Use -1 to let the OS choose a random available port.
16
+ * Defaults to 4222 if not specified.
17
+ */
18
+ port?: number;
19
+ /**
20
+ * Host to listen on. Defaults to "127.0.0.1".
21
+ */
22
+ host?: string;
23
+ /**
24
+ * Enable JetStream.
25
+ */
26
+ jetstream?: boolean;
27
+ /**
28
+ * Directory for JetStream storage.
29
+ *
30
+ * Defaults to random temporary directory.
31
+ */
32
+ storeDirectory?: string;
33
+ /**
34
+ * Whether to remove store directory on stop.
35
+ *
36
+ * Defaults to true if using random temporary directory, false otherwise.
37
+ */
38
+ removeStoreDirectoryOnExit?: boolean;
39
+ /**
40
+ * Maximum time in milliseconds to wait for the server to become ready.
41
+ *
42
+ * Defaults to 20000 (20 seconds).
43
+ */
44
+ startupTimeoutMillis?: number;
45
+ /**
46
+ * User for authentication.
47
+ */
48
+ username?: string;
49
+ /**
50
+ * Password for authentication.
51
+ */
52
+ password?: string;
53
+ /**
54
+ * Authentication token.
55
+ */
56
+ auth?: string;
57
+ /**
58
+ * Extra arguments to be passed.
59
+ */
60
+ extraArguments?: string[];
61
+ }
62
+ /**
63
+ * Represents a running NATS server process.
64
+ */
65
+ declare class NatsServer {
66
+ /**
67
+ * The host the server is bound to.
68
+ */
69
+ readonly host: string;
70
+ /**
71
+ * The port the server is listening on.
72
+ */
73
+ readonly port: number;
74
+ /**
75
+ * The client URL for connecting (e.g. "nats://127.0.0.1:4222")
76
+ */
77
+ readonly url: string;
78
+ private readonly _process;
79
+ private _stopped;
80
+ private constructor();
81
+ /**
82
+ * Starts a NATS server process with the given options.
83
+ *
84
+ * @param options
85
+ * @returns A `NatsServer` instance, ready to accept connections.
86
+ */
87
+ static start(options?: NatsServerOptions): Promise<NatsServer>;
88
+ /**
89
+ * Starts a server on a random port.
90
+ *
91
+ * Equivalent to `NatsServer.start({ ...options, port: RANDOM_PORT })`.
92
+ */
93
+ static startOnRandomPort(options?: Omit<NatsServerOptions, "port">): Promise<NatsServer>;
94
+ /**
95
+ * Sends SIGTERM to the server and waits for the process to exit.
96
+ */
97
+ stop(kill?: boolean): Promise<void>;
98
+ /**
99
+ * Waits for the server process to exit.
100
+ */
101
+ waitForExit(): Promise<number | null>;
102
+ /**
103
+ * Whether the server has been stopped.
104
+ */
105
+ get stopped(): boolean;
106
+ }
107
+ export { RANDOM_PORT, NatsServerOptions, NatsServer };
package/dist/index.js ADDED
@@ -0,0 +1,187 @@
1
+ // src/options.ts
2
+ var RANDOM_PORT = -1;
3
+ function buildArgs(opts) {
4
+ const args = [];
5
+ if (opts.port !== undefined) {
6
+ args.push("--port", String(opts.port));
7
+ }
8
+ if (opts.host !== undefined) {
9
+ args.push("--addr", opts.host);
10
+ }
11
+ if (opts.jetstream) {
12
+ args.push("--jetstream");
13
+ if (opts.storeDirectory !== undefined) {
14
+ args.push("--store_dir", opts.storeDirectory);
15
+ }
16
+ }
17
+ if (opts.username !== undefined) {
18
+ args.push("--user", opts.username);
19
+ }
20
+ if (opts.password !== undefined) {
21
+ args.push("--pass", opts.password);
22
+ }
23
+ if (opts.auth !== undefined) {
24
+ args.push("--auth", opts.auth);
25
+ }
26
+ if (opts.extraArguments !== undefined) {
27
+ args.push(...opts.extraArguments);
28
+ }
29
+ return args;
30
+ }
31
+ // src/server.ts
32
+ import { mkdir, rm } from "node:fs/promises";
33
+ import { tmpdir } from "node:os";
34
+ import { join } from "node:path";
35
+ import { getBinaryPath } from "@eplightning/nats-server";
36
+
37
+ // src/process.ts
38
+ import { spawn } from "node:child_process";
39
+ function spawnProcess(binaryPath, options) {
40
+ const child = spawn(binaryPath, options.args, {
41
+ stdio: ["ignore", "ignore", "pipe"]
42
+ });
43
+ let exitResolve;
44
+ const exitPromise = new Promise((resolve) => {
45
+ exitResolve = resolve;
46
+ });
47
+ child.stderr.setEncoding("utf8");
48
+ const stderrBuffer = { value: "" };
49
+ child.stderr.on("data", (chunk) => {
50
+ stderrBuffer.value = stderrBuffer.value.concat(chunk);
51
+ const lines = stderrBuffer.value.split(/\r?\n/);
52
+ stderrBuffer.value = lines.pop() ?? "";
53
+ for (const line of lines) {
54
+ options.onStderr(line);
55
+ }
56
+ });
57
+ child.on("exit", (code) => {
58
+ if (stderrBuffer.value !== "") {
59
+ options.onStderr(stderrBuffer.value);
60
+ }
61
+ options.onExit(code);
62
+ exitResolve(code);
63
+ });
64
+ return {
65
+ kill(signal) {
66
+ child.kill(signal);
67
+ },
68
+ waitForExit() {
69
+ return exitPromise;
70
+ }
71
+ };
72
+ }
73
+
74
+ // src/server.ts
75
+ var CLIENT_LISTEN_RE = /Listening for client connections on .+:(\d+)$/;
76
+ var SERVER_READY_RE = /Server is ready$/;
77
+
78
+ class NatsServer {
79
+ host;
80
+ port;
81
+ url;
82
+ _process;
83
+ _stopped = false;
84
+ constructor(process, port, host) {
85
+ this._process = process;
86
+ this.port = port;
87
+ this.host = host;
88
+ this.url = `nats://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
89
+ }
90
+ static async start(options = {}) {
91
+ const opts = {
92
+ host: "127.0.0.1",
93
+ startupTimeoutMillis: 20000,
94
+ storeDirectory: join(tmpdir(), `nats-${crypto.randomUUID()}`),
95
+ removeStoreDirectoryOnExit: options.storeDirectory == null,
96
+ ...options
97
+ };
98
+ const host = opts.host ?? "0.0.0.0";
99
+ const isRandomPort = opts.port === RANDOM_PORT;
100
+ const binaryPath = opts.binaryPath ?? await getBinaryPath();
101
+ const args = buildArgs(opts);
102
+ if (opts.jetstream && opts.storeDirectory) {
103
+ await mkdir(opts.storeDirectory, { recursive: true });
104
+ }
105
+ return new Promise((resolve, reject) => {
106
+ let resolved = false;
107
+ let assignedPort = isRandomPort ? undefined : opts.port ?? 4222;
108
+ let serverReady = false;
109
+ let proc;
110
+ const timeoutTimer = setTimeout(() => {
111
+ if (!resolved) {
112
+ resolved = true;
113
+ try {
114
+ proc.kill("SIGKILL");
115
+ } catch {}
116
+ reject(new Error(`NATS server did not become ready within ${opts.startupTimeoutMillis}ms.`));
117
+ }
118
+ }, opts.startupTimeoutMillis);
119
+ function tryResolve() {
120
+ if (resolved || assignedPort == null || !serverReady)
121
+ return;
122
+ resolved = true;
123
+ clearTimeout(timeoutTimer);
124
+ resolve(new NatsServer(proc, assignedPort, host));
125
+ }
126
+ function handleLine(line) {
127
+ if (isRandomPort && assignedPort == null) {
128
+ const match = CLIENT_LISTEN_RE.exec(line);
129
+ if (match) {
130
+ assignedPort = parseInt(match[1], 10);
131
+ }
132
+ }
133
+ if (!serverReady && SERVER_READY_RE.test(line)) {
134
+ serverReady = true;
135
+ }
136
+ tryResolve();
137
+ }
138
+ try {
139
+ proc = spawnProcess(binaryPath, {
140
+ args,
141
+ onStderr: handleLine,
142
+ onExit: async (code) => {
143
+ if (!resolved) {
144
+ resolved = true;
145
+ clearTimeout(timeoutTimer);
146
+ reject(new Error(`NATS server exited unexpectedly with code ${code} before becoming ready.`));
147
+ } else {
148
+ if (opts.removeStoreDirectoryOnExit && opts.storeDirectory && opts.storeDirectory !== "/") {
149
+ try {
150
+ await rm(opts.storeDirectory, {
151
+ recursive: true,
152
+ force: true
153
+ });
154
+ } catch (_error) {}
155
+ }
156
+ }
157
+ }
158
+ });
159
+ } catch (err) {
160
+ resolved = true;
161
+ clearTimeout(timeoutTimer);
162
+ const msg = err instanceof Error ? err.message : String(err);
163
+ reject(new Error(`Failed to start NATS server: ${msg}`));
164
+ }
165
+ });
166
+ }
167
+ static async startOnRandomPort(options = {}) {
168
+ return NatsServer.start({ ...options, port: RANDOM_PORT });
169
+ }
170
+ async stop(kill = false) {
171
+ if (this._stopped)
172
+ return;
173
+ this._stopped = true;
174
+ this._process.kill(kill ? "SIGKILL" : "SIGTERM");
175
+ await this._process.waitForExit();
176
+ }
177
+ waitForExit() {
178
+ return this._process.waitForExit();
179
+ }
180
+ get stopped() {
181
+ return this._stopped;
182
+ }
183
+ }
184
+ export {
185
+ RANDOM_PORT,
186
+ NatsServer
187
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@eplightning/nats-server-client",
3
+ "version": "0.1.0",
4
+ "description": "Easy way of running NATS server for Bun, Node and Deno",
5
+ "type": "module",
6
+ "module": "./dist/index.js",
7
+ "main": "./dist/index.cjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist/",
23
+ "README.md"
24
+ ],
25
+ "scripts": {
26
+ "build": "bunup --format esm,cjs",
27
+ "test": "bun test"
28
+ },
29
+ "dependencies": {
30
+ "@eplightning/nats-server": "*"
31
+ },
32
+ "devDependencies": {
33
+ "@nats-io/jetstream": "^3.3.1",
34
+ "@nats-io/transport-node": "^3.3.1"
35
+ },
36
+ "license": "Apache-2.0",
37
+ "keywords": [
38
+ "nats",
39
+ "nats-server",
40
+ "testing",
41
+ "binary"
42
+ ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/eplightning/nats-server-npm",
46
+ "directory": "packages/nats-server"
47
+ },
48
+ "engines": {
49
+ "bun": ">=1",
50
+ "node": ">=22",
51
+ "deno": ">=2"
52
+ }
53
+ }