@lsdsoftware/utils 1.1.1 → 2.0.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/dist/connect-socket.d.ts +3 -6
- package/dist/connect-socket.js +9 -15
- package/dist/connect-socket.test.js +24 -17
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -11
- package/dist/index.test.d.ts +4 -1
- package/dist/index.test.js +4 -23
- package/dist/line-reader.d.ts +0 -1
- package/dist/line-reader.js +5 -12
- package/dist/line-reader.test.d.ts +1 -6
- package/dist/line-reader.test.js +29 -45
- package/dist/semaphore.js +15 -30
- package/dist/semaphore.test.d.ts +1 -6
- package/dist/semaphore.test.js +67 -83
- package/dist/spawn-child.d.ts +6 -12
- package/dist/spawn-child.js +14 -22
- package/dist/spawn-child.test.js +16 -19
- package/package.json +5 -3
package/dist/connect-socket.d.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import { NetConnectOpts, Socket } from "net";
|
|
4
2
|
import * as rxjs from "rxjs";
|
|
5
3
|
export interface Connection {
|
|
4
|
+
socket: Socket;
|
|
6
5
|
data$: rxjs.Observable<string | Buffer>;
|
|
7
6
|
error$: rxjs.Observable<Error>;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
end: Socket['end'];
|
|
7
|
+
timeout$: rxjs.Observable<void>;
|
|
8
|
+
close$: rxjs.Observable<boolean>;
|
|
11
9
|
}
|
|
12
10
|
export declare function connectSocket(options: NetConnectOpts & {
|
|
13
|
-
timeout?: number;
|
|
14
11
|
encoding?: BufferEncoding;
|
|
15
12
|
}): rxjs.Observable<Connection>;
|
package/dist/connect-socket.js
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const net_1 = require("net");
|
|
5
|
-
const rxjs = require("rxjs");
|
|
6
|
-
function connectSocket(options) {
|
|
1
|
+
import { createConnection } from "net";
|
|
2
|
+
import * as rxjs from "rxjs";
|
|
3
|
+
export function connectSocket(options) {
|
|
7
4
|
return rxjs.defer(() => {
|
|
8
|
-
const sock =
|
|
9
|
-
if (options.timeout)
|
|
10
|
-
sock.setTimeout(options.timeout);
|
|
5
|
+
const sock = createConnection(options);
|
|
11
6
|
if (options.encoding)
|
|
12
7
|
sock.setEncoding(options.encoding);
|
|
13
8
|
return rxjs.race(rxjs.fromEvent(sock, 'error').pipe(rxjs.map(err => { throw err; })), rxjs.fromEvent(sock, 'connect').pipe(rxjs.take(1), rxjs.map(() => makeConnection(sock))));
|
|
14
9
|
});
|
|
15
10
|
}
|
|
16
|
-
exports.connectSocket = connectSocket;
|
|
17
11
|
function makeConnection(sock) {
|
|
18
12
|
return {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
socket: sock,
|
|
14
|
+
data$: rxjs.fromEvent(sock, 'data').pipe(rxjs.takeUntil(rxjs.fromEvent(sock, 'end'))),
|
|
15
|
+
error$: rxjs.fromEvent(sock, 'error'),
|
|
16
|
+
timeout$: rxjs.fromEvent(sock, 'timeout'),
|
|
17
|
+
close$: rxjs.fromEvent(sock, 'close').pipe(rxjs.take(1)),
|
|
24
18
|
};
|
|
25
19
|
}
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { describe, expect } from "@service-broker/test-utils";
|
|
2
|
+
import assert from "assert";
|
|
3
|
+
import * as rxjs from "rxjs";
|
|
4
|
+
import { connectSocket } from './connect-socket.js';
|
|
5
|
+
describe('connect-socket', ({ test }) => {
|
|
6
|
+
test('fail', () => rxjs.lastValueFrom(connectSocket({ port: -1 })).then(() => assert(false, '!throw'), err => expect(err.code, 'ERR_SOCKET_BAD_PORT')));
|
|
7
|
+
test('success', () => rxjs.lastValueFrom(connectSocket({ host: 'lsdsoftware.com', port: 80, encoding: 'utf8', timeout: 100 }).pipe(rxjs.exhaustMap(conn => rxjs.forkJoin({
|
|
8
|
+
request: rxjs.timer(120).pipe(rxjs.tap(() => {
|
|
9
|
+
conn.socket.write('GET / HTTP/1.1\nHost: lsdsoftware.com\n\n');
|
|
10
|
+
conn.socket.end();
|
|
11
|
+
})),
|
|
12
|
+
response: conn.data$.pipe(rxjs.takeUntil(conn.close$), rxjs.reduce((acc, chunk) => {
|
|
13
|
+
assert(typeof chunk == 'string');
|
|
14
|
+
return acc.concat(chunk);
|
|
15
|
+
}, '')),
|
|
16
|
+
errors: conn.error$.pipe(rxjs.takeUntil(conn.close$), rxjs.reduce((acc, err) => acc.concat(err), [])),
|
|
17
|
+
timeouts: conn.timeout$.pipe(rxjs.takeUntil(conn.close$), rxjs.reduce(acc => acc + 1, 0)),
|
|
18
|
+
closedWithError: conn.close$
|
|
19
|
+
})))).then(({ response, errors, timeouts, closedWithError }) => {
|
|
20
|
+
assert(response.startsWith('HTTP/1.1 301'));
|
|
21
|
+
expect(errors, []);
|
|
22
|
+
expect(timeouts, 1);
|
|
23
|
+
expect(closedWithError, false);
|
|
24
|
+
}));
|
|
18
25
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export { makeLineReader, makeSemaphore, connectSocket, spawnChild, };
|
|
1
|
+
export * from './connect-socket.js';
|
|
2
|
+
export * from './line-reader.js';
|
|
3
|
+
export * from './semaphore.js';
|
|
4
|
+
export * from './spawn-child.js';
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "makeLineReader", { enumerable: true, get: function () { return line_reader_1.makeLineReader; } });
|
|
6
|
-
const semaphore_1 = require("./semaphore");
|
|
7
|
-
Object.defineProperty(exports, "makeSemaphore", { enumerable: true, get: function () { return semaphore_1.makeSemaphore; } });
|
|
8
|
-
const connect_socket_1 = require("./connect-socket");
|
|
9
|
-
Object.defineProperty(exports, "connectSocket", { enumerable: true, get: function () { return connect_socket_1.connectSocket; } });
|
|
10
|
-
const spawn_child_1 = require("./spawn-child");
|
|
11
|
-
Object.defineProperty(exports, "spawnChild", { enumerable: true, get: function () { return spawn_child_1.spawnChild; } });
|
|
1
|
+
export * from './connect-socket.js';
|
|
2
|
+
export * from './line-reader.js';
|
|
3
|
+
export * from './semaphore.js';
|
|
4
|
+
export * from './spawn-child.js';
|
package/dist/index.test.d.ts
CHANGED
package/dist/index.test.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
const line_reader_test_1 = require("./line-reader.test");
|
|
13
|
-
const semaphore_test_1 = require("./semaphore.test");
|
|
14
|
-
run(Object.assign(Object.assign({}, line_reader_test_1.default), semaphore_test_1.default))
|
|
15
|
-
.catch(console.error);
|
|
16
|
-
function run(tests) {
|
|
17
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
18
|
-
for (const name in tests) {
|
|
19
|
-
console.log("Running test", name);
|
|
20
|
-
yield tests[name]();
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
}
|
|
1
|
+
import './connect-socket.test.js';
|
|
2
|
+
import './line-reader.test.js';
|
|
3
|
+
import './semaphore.test.js';
|
|
4
|
+
import './spawn-child.test.js';
|
package/dist/line-reader.d.ts
CHANGED
package/dist/line-reader.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const stream_1 = require("stream");
|
|
5
|
-
const string_decoder_1 = require("string_decoder");
|
|
6
|
-
function makeLineReader(lineCallback) {
|
|
1
|
+
import { Writable } from "stream";
|
|
2
|
+
import { StringDecoder } from "string_decoder";
|
|
3
|
+
export function makeLineReader(lineCallback) {
|
|
7
4
|
// Buffer for the part of the chunk that doesn't form a complete line.
|
|
8
5
|
let remainder = '';
|
|
9
|
-
const decoder = new
|
|
10
|
-
return new
|
|
11
|
-
//prevent auto-decoding string to Buffer, so processed chunk could be either string or Buffer
|
|
12
|
-
decodeStrings: false,
|
|
6
|
+
const decoder = new StringDecoder();
|
|
7
|
+
return new Writable({
|
|
13
8
|
write(chunk, encoding, callback) {
|
|
14
|
-
// encoding param here is irrelevant because it applies only to string chunks
|
|
15
9
|
const chunkStr = remainder + decoder.write(chunk);
|
|
16
10
|
const lines = chunkStr.split(/\r?\n/);
|
|
17
11
|
// Keep the last line in remainder if it doesn't end with a newline character.
|
|
@@ -29,4 +23,3 @@ function makeLineReader(lineCallback) {
|
|
|
29
23
|
}
|
|
30
24
|
});
|
|
31
25
|
}
|
|
32
|
-
exports.makeLineReader = makeLineReader;
|
package/dist/line-reader.test.js
CHANGED
|
@@ -1,46 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { describe } from "@service-broker/test-utils";
|
|
2
|
+
import assert from "assert";
|
|
3
|
+
import { makeLineReader } from "./line-reader.js";
|
|
4
|
+
describe('line-reader', ({ test }) => {
|
|
5
|
+
test('one', () => {
|
|
6
|
+
const lines = [];
|
|
7
|
+
const splitter = makeLineReader(line => lines.push(line));
|
|
8
|
+
splitter.write('This is a line\nThis is another line\r\nAnd this is a line as well');
|
|
9
|
+
splitter.end();
|
|
10
|
+
assert(lines[0] == "This is a line" &&
|
|
11
|
+
lines[1] == "This is another line" &&
|
|
12
|
+
lines[2] == "And this is a line as well");
|
|
9
13
|
});
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
-
const lines = [];
|
|
29
|
-
const splitter = (0, line_reader_1.makeLineReader)(line => lines.push(line));
|
|
30
|
-
splitter.write('\nThis is a line\n');
|
|
31
|
-
splitter.end();
|
|
32
|
-
assert(lines[0] == "" &&
|
|
33
|
-
lines[1] == "This is a line");
|
|
34
|
-
});
|
|
35
|
-
},
|
|
36
|
-
lineReader3() {
|
|
37
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
-
const lines = [];
|
|
39
|
-
const splitter = (0, line_reader_1.makeLineReader)(line => lines.push(line));
|
|
40
|
-
splitter.write('\n\n');
|
|
41
|
-
splitter.end();
|
|
42
|
-
assert(lines[0] == "" &&
|
|
43
|
-
lines[1] == "");
|
|
44
|
-
});
|
|
45
|
-
},
|
|
46
|
-
};
|
|
14
|
+
test('two', () => {
|
|
15
|
+
const lines = [];
|
|
16
|
+
const splitter = makeLineReader(line => lines.push(line));
|
|
17
|
+
splitter.write('\nThis is a line\n');
|
|
18
|
+
splitter.end();
|
|
19
|
+
assert(lines[0] == "" &&
|
|
20
|
+
lines[1] == "This is a line");
|
|
21
|
+
});
|
|
22
|
+
test('three', () => {
|
|
23
|
+
const lines = [];
|
|
24
|
+
const splitter = makeLineReader(line => lines.push(line));
|
|
25
|
+
splitter.write('\n\n');
|
|
26
|
+
splitter.end();
|
|
27
|
+
assert(lines[0] == "" &&
|
|
28
|
+
lines[1] == "");
|
|
29
|
+
});
|
|
30
|
+
});
|
package/dist/semaphore.js
CHANGED
|
@@ -1,37 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.makeSemaphore = void 0;
|
|
13
|
-
function makeSemaphore(count) {
|
|
1
|
+
export function makeSemaphore(count) {
|
|
14
2
|
const waiters = [];
|
|
15
3
|
return {
|
|
16
|
-
runTask(task, onStart) {
|
|
17
|
-
|
|
18
|
-
|
|
4
|
+
async runTask(task, onStart) {
|
|
5
|
+
if (count > 0)
|
|
6
|
+
count--;
|
|
7
|
+
else
|
|
8
|
+
await new Promise(f => waiters.push(f));
|
|
9
|
+
try {
|
|
10
|
+
onStart?.();
|
|
11
|
+
return await task();
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
count++;
|
|
15
|
+
while (count > 0 && waiters.length > 0) {
|
|
19
16
|
count--;
|
|
20
|
-
|
|
21
|
-
yield new Promise(f => waiters.push(f));
|
|
22
|
-
try {
|
|
23
|
-
onStart === null || onStart === void 0 ? void 0 : onStart();
|
|
24
|
-
return yield task();
|
|
17
|
+
waiters.shift()();
|
|
25
18
|
}
|
|
26
|
-
|
|
27
|
-
count++;
|
|
28
|
-
while (count > 0 && waiters.length > 0) {
|
|
29
|
-
count--;
|
|
30
|
-
waiters.shift()();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
});
|
|
19
|
+
}
|
|
34
20
|
}
|
|
35
21
|
};
|
|
36
22
|
}
|
|
37
|
-
exports.makeSemaphore = makeSemaphore;
|
package/dist/semaphore.test.d.ts
CHANGED
package/dist/semaphore.test.js
CHANGED
|
@@ -1,84 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { describe } from "@service-broker/test-utils";
|
|
2
|
+
import assert from "assert";
|
|
3
|
+
import { makeSemaphore } from "./semaphore.js";
|
|
4
|
+
describe('semaphore', ({ test }) => {
|
|
5
|
+
test('one', async () => {
|
|
6
|
+
const sema = makeSemaphore(1);
|
|
7
|
+
const output = [];
|
|
8
|
+
await Promise.all([
|
|
9
|
+
sema.runTask(async () => {
|
|
10
|
+
output.push(1);
|
|
11
|
+
await new Promise(f => setTimeout(f, 100));
|
|
12
|
+
output.push(2);
|
|
13
|
+
}),
|
|
14
|
+
sema.runTask(async () => {
|
|
15
|
+
output.push(3);
|
|
16
|
+
await new Promise(f => setTimeout(f, 50));
|
|
17
|
+
output.push(4);
|
|
18
|
+
})
|
|
19
|
+
]);
|
|
20
|
+
assert(output.length == 4 &&
|
|
21
|
+
output[0] == 1 &&
|
|
22
|
+
output[1] == 2 &&
|
|
23
|
+
output[2] == 3 &&
|
|
24
|
+
output[3] == 4);
|
|
9
25
|
});
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
]);
|
|
54
|
-
assert(output.length == 4 &&
|
|
55
|
-
output[0] == 1 &&
|
|
56
|
-
output[1] == 3 &&
|
|
57
|
-
output[2] == 4 &&
|
|
58
|
-
output[3] == 2);
|
|
59
|
-
});
|
|
60
|
-
},
|
|
61
|
-
semaphore3() {
|
|
62
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
-
const sema = (0, semaphore_1.makeSemaphore)(1);
|
|
64
|
-
const output = [];
|
|
65
|
-
yield Promise.all([
|
|
66
|
-
sema.runTask(() => __awaiter(this, void 0, void 0, function* () {
|
|
67
|
-
output.push(1);
|
|
68
|
-
yield new Promise(f => setTimeout(f, 100));
|
|
69
|
-
output.push(2);
|
|
70
|
-
}), () => {
|
|
71
|
-
throw new Error("Canceled");
|
|
72
|
-
}).then(() => assert(false), err => assert(err.message == "Canceled")),
|
|
73
|
-
sema.runTask(() => __awaiter(this, void 0, void 0, function* () {
|
|
74
|
-
output.push(3);
|
|
75
|
-
yield new Promise(f => setTimeout(f, 50));
|
|
76
|
-
output.push(4);
|
|
77
|
-
}))
|
|
78
|
-
]);
|
|
79
|
-
assert(output.length == 2 &&
|
|
80
|
-
output[0] == 3 &&
|
|
81
|
-
output[1] == 4);
|
|
82
|
-
});
|
|
83
|
-
},
|
|
84
|
-
};
|
|
26
|
+
test('two', async () => {
|
|
27
|
+
const sema = makeSemaphore(2);
|
|
28
|
+
const output = [];
|
|
29
|
+
await Promise.all([
|
|
30
|
+
sema.runTask(async () => {
|
|
31
|
+
output.push(1);
|
|
32
|
+
await new Promise(f => setTimeout(f, 100));
|
|
33
|
+
output.push(2);
|
|
34
|
+
}),
|
|
35
|
+
sema.runTask(async () => {
|
|
36
|
+
output.push(3);
|
|
37
|
+
await new Promise(f => setTimeout(f, 50));
|
|
38
|
+
output.push(4);
|
|
39
|
+
})
|
|
40
|
+
]);
|
|
41
|
+
assert(output.length == 4 &&
|
|
42
|
+
output[0] == 1 &&
|
|
43
|
+
output[1] == 3 &&
|
|
44
|
+
output[2] == 4 &&
|
|
45
|
+
output[3] == 2);
|
|
46
|
+
});
|
|
47
|
+
test('three', async () => {
|
|
48
|
+
const sema = makeSemaphore(1);
|
|
49
|
+
const output = [];
|
|
50
|
+
await Promise.all([
|
|
51
|
+
sema.runTask(async () => {
|
|
52
|
+
output.push(1);
|
|
53
|
+
await new Promise(f => setTimeout(f, 100));
|
|
54
|
+
output.push(2);
|
|
55
|
+
}, () => {
|
|
56
|
+
throw new Error("Canceled");
|
|
57
|
+
}).then(() => assert(false), err => assert(err.message == "Canceled")),
|
|
58
|
+
sema.runTask(async () => {
|
|
59
|
+
output.push(3);
|
|
60
|
+
await new Promise(f => setTimeout(f, 50));
|
|
61
|
+
output.push(4);
|
|
62
|
+
})
|
|
63
|
+
]);
|
|
64
|
+
assert(output.length == 2 &&
|
|
65
|
+
output[0] == 3 &&
|
|
66
|
+
output[1] == 4);
|
|
67
|
+
});
|
|
68
|
+
});
|
package/dist/spawn-child.d.ts
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
/// <reference types="node" />
|
|
4
|
-
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio } from "child_process";
|
|
1
|
+
import { ChildProcess } from "child_process";
|
|
5
2
|
import * as rxjs from "rxjs";
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
stdout$: rxjs.Observable<string | Buffer
|
|
9
|
-
stderr$: rxjs.Observable<string | Buffer
|
|
3
|
+
export declare function spawnChild<T extends ChildProcess>(spawn: () => T): rxjs.Observable<{
|
|
4
|
+
process: T;
|
|
5
|
+
stdout$: rxjs.Observable<string | Buffer<ArrayBufferLike>>;
|
|
6
|
+
stderr$: rxjs.Observable<string | Buffer<ArrayBufferLike>>;
|
|
10
7
|
error$: rxjs.Observable<Error>;
|
|
11
8
|
close$: rxjs.Observable<number | NodeJS.Signals>;
|
|
12
|
-
|
|
13
|
-
kill: ChildProcessWithoutNullStreams['kill'];
|
|
14
|
-
}
|
|
15
|
-
export declare function spawnChild(command: string, args?: string[], options?: SpawnOptionsWithoutStdio): rxjs.Observable<Child>;
|
|
9
|
+
}>;
|
package/dist/spawn-child.js
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
pid: childProc.pid,
|
|
16
|
-
stdout$: rxjs.fromEvent(childProc.stdout, 'data', (data) => data),
|
|
17
|
-
stderr$: rxjs.fromEvent(childProc.stderr, 'data', (data) => data),
|
|
18
|
-
error$: rxjs.fromEvent(childProc, 'error', (err) => err),
|
|
19
|
-
close$: rxjs.fromEvent(childProc, 'close', (code, signal) => signal || code),
|
|
20
|
-
stdin: childProc.stdin,
|
|
21
|
-
kill: childProc.kill.bind(childProc)
|
|
22
|
-
};
|
|
1
|
+
import * as rxjs from "rxjs";
|
|
2
|
+
export function spawnChild(spawn) {
|
|
3
|
+
const child = spawn();
|
|
4
|
+
return rxjs.race(rxjs.fromEvent(child, 'error', (err) => err).pipe(rxjs.map(err => { throw err; })), rxjs.fromEvent(child, 'spawn').pipe(rxjs.take(1), rxjs.map(() => ({
|
|
5
|
+
process: child,
|
|
6
|
+
stdout$: child.stdout
|
|
7
|
+
? rxjs.fromEvent(child.stdout, 'data')
|
|
8
|
+
: rxjs.EMPTY,
|
|
9
|
+
stderr$: child.stderr
|
|
10
|
+
? rxjs.fromEvent(child.stderr, 'data')
|
|
11
|
+
: rxjs.EMPTY,
|
|
12
|
+
error$: rxjs.fromEvent(child, 'error'),
|
|
13
|
+
close$: rxjs.fromEvent(child, 'close', (code, signal) => signal || code).pipe(rxjs.take(1)),
|
|
14
|
+
}))));
|
|
23
15
|
}
|
package/dist/spawn-child.test.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})
|
|
17
|
-
complete() {
|
|
18
|
-
process.stdin.pause();
|
|
19
|
-
}
|
|
1
|
+
import { describe, expect } from "@service-broker/test-utils";
|
|
2
|
+
import assert from "assert";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import * as rxjs from "rxjs";
|
|
5
|
+
import { spawnChild } from "./spawn-child.js";
|
|
6
|
+
describe('spawn-child', ({ test }) => {
|
|
7
|
+
test('fail', () => rxjs.lastValueFrom(spawnChild(() => spawn('bad-cmd'))).then(() => assert(false, '!throw'), err => expect(err.code, 'ENOENT')));
|
|
8
|
+
test('success', () => rxjs.lastValueFrom(spawnChild(() => spawn('echo Hello, world && echo Bye, world >&2 && exit 42', { shell: true })).pipe(rxjs.exhaustMap(child => rxjs.forkJoin({
|
|
9
|
+
stdout: child.stdout$.pipe(rxjs.takeUntil(child.close$), rxjs.reduce((acc, chunk) => acc.concat(chunk), '')),
|
|
10
|
+
stderr: child.stderr$.pipe(rxjs.takeUntil(child.close$), rxjs.reduce((acc, chunk) => acc.concat(chunk), '')),
|
|
11
|
+
exitCode: child.close$
|
|
12
|
+
})))).then(({ stdout, stderr, exitCode }) => {
|
|
13
|
+
expect(stdout, 'Hello, world\n');
|
|
14
|
+
expect(stderr, 'Bye, world\n');
|
|
15
|
+
expect(exitCode, 42);
|
|
16
|
+
}));
|
|
20
17
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lsdsoftware/utils",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "2.0.0",
|
|
4
5
|
"description": "Useful JavaScript utilities",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"files": [
|
|
@@ -21,8 +22,9 @@
|
|
|
21
22
|
},
|
|
22
23
|
"homepage": "https://github.com/lsdsoftware/utils#readme",
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"
|
|
25
|
+
"@service-broker/test-utils": "^1.1.2",
|
|
26
|
+
"@types/node": "^25.2.3",
|
|
27
|
+
"typescript": "^5.9.3"
|
|
26
28
|
},
|
|
27
29
|
"dependencies": {
|
|
28
30
|
"rxjs": "^7.8.2"
|