@lsdsoftware/utils 1.0.5 → 1.1.1
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 +4 -33
- package/dist/connect-socket.d.ts +15 -0
- package/dist/connect-socket.js +25 -0
- package/dist/connect-socket.test.d.ts +1 -0
- package/dist/connect-socket.test.js +18 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5 -3
- package/dist/index.test.js +1 -2
- package/dist/line-reader.js +1 -1
- package/dist/line-reader.test.js +1 -1
- package/dist/semaphore.d.ts +1 -1
- package/dist/semaphore.js +2 -2
- package/dist/spawn-child.d.ts +15 -0
- package/dist/spawn-child.js +23 -0
- package/dist/spawn-child.test.d.ts +1 -0
- package/dist/spawn-child.test.js +20 -0
- package/package.json +4 -1
- package/dist/abortable.d.ts +0 -1
- package/dist/abortable.js +0 -22
- package/dist/abortable.test.d.ts +0 -6
- package/dist/abortable.test.js +0 -71
package/README.md
CHANGED
|
@@ -25,38 +25,9 @@ const result = await semaphore.runTask(async () => {
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
###
|
|
29
|
-
|
|
28
|
+
### Connect Socket
|
|
29
|
+
Observable wrapper for net.connect (see connect-socket.test.ts for usage)
|
|
30
30
|
|
|
31
|
-
```typescript
|
|
32
|
-
async function myTask() {
|
|
33
|
-
await step1()
|
|
34
|
-
await step2()
|
|
35
|
-
await step3()
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
import { makeAbortable } from "@lsdsoftware/utils"
|
|
42
|
-
|
|
43
|
-
const [abort, abortPromise, checkpoint] = makeAbortable()
|
|
44
|
-
|
|
45
|
-
//modify task to support early termination
|
|
46
|
-
async function myTask() {
|
|
47
|
-
await step1
|
|
48
|
-
checkpoint() //will throw if aborted
|
|
49
|
-
await step2
|
|
50
|
-
checkpoint()
|
|
51
|
-
await step3
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
//call abort() when you need to
|
|
55
|
-
setTimeout(() => abort(new Error("Timeout")), 5000)
|
|
56
|
-
|
|
57
|
-
//use Promise.race to run your task
|
|
58
|
-
const result = await Promise.race([
|
|
59
|
-
abortPromise,
|
|
60
|
-
myTask()
|
|
61
|
-
])
|
|
62
|
-
```
|
|
32
|
+
### Spawn Child
|
|
33
|
+
Observable wrapper for child_process.spawn (see spawn-child.test.ts for usage)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { NetConnectOpts, Socket } from "net";
|
|
4
|
+
import * as rxjs from "rxjs";
|
|
5
|
+
export interface Connection {
|
|
6
|
+
data$: rxjs.Observable<string | Buffer>;
|
|
7
|
+
error$: rxjs.Observable<Error>;
|
|
8
|
+
close$: rxjs.Observable<string>;
|
|
9
|
+
write: Socket['write'];
|
|
10
|
+
end: Socket['end'];
|
|
11
|
+
}
|
|
12
|
+
export declare function connectSocket(options: NetConnectOpts & {
|
|
13
|
+
timeout?: number;
|
|
14
|
+
encoding?: BufferEncoding;
|
|
15
|
+
}): rxjs.Observable<Connection>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.connectSocket = void 0;
|
|
4
|
+
const net_1 = require("net");
|
|
5
|
+
const rxjs = require("rxjs");
|
|
6
|
+
function connectSocket(options) {
|
|
7
|
+
return rxjs.defer(() => {
|
|
8
|
+
const sock = (0, net_1.createConnection)(options);
|
|
9
|
+
if (options.timeout)
|
|
10
|
+
sock.setTimeout(options.timeout);
|
|
11
|
+
if (options.encoding)
|
|
12
|
+
sock.setEncoding(options.encoding);
|
|
13
|
+
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
|
+
});
|
|
15
|
+
}
|
|
16
|
+
exports.connectSocket = connectSocket;
|
|
17
|
+
function makeConnection(sock) {
|
|
18
|
+
return {
|
|
19
|
+
data$: rxjs.fromEvent(sock, 'data', (data) => data).pipe(rxjs.takeUntil(rxjs.fromEvent(sock, 'end'))),
|
|
20
|
+
error$: rxjs.fromEvent(sock, 'error', (err) => err),
|
|
21
|
+
close$: rxjs.merge(rxjs.fromEvent(sock, 'close', (hadError) => hadError ? 'Normal close' : 'Close with error'), rxjs.fromEvent(sock, 'timeout', () => 'Socket timeout')),
|
|
22
|
+
write: sock.write.bind(sock),
|
|
23
|
+
end: sock.end.bind(sock)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const connect_socket_1 = require("./connect-socket");
|
|
4
|
+
const rxjs = require("rxjs");
|
|
5
|
+
(0, connect_socket_1.connectSocket)({ host: '127.0.0.1', port: 8080, encoding: 'utf8' }).pipe(rxjs.tap({
|
|
6
|
+
error(err) {
|
|
7
|
+
console.error("Socket connect fail", err);
|
|
8
|
+
}
|
|
9
|
+
}), rxjs.retry({ delay: 10000 }), rxjs.exhaustMap(conn => {
|
|
10
|
+
console.info("Socket connected");
|
|
11
|
+
const write = (text) => {
|
|
12
|
+
console.log('Socket write:', text);
|
|
13
|
+
conn.write(text);
|
|
14
|
+
};
|
|
15
|
+
return rxjs.merge(conn.data$.pipe(rxjs.tap(chunk => console.log('Socket recv:', chunk))), conn.error$.pipe(rxjs.tap(err => console.info('Socket error:', err)))).pipe(rxjs.takeUntil(conn.close$.pipe(rxjs.tap(reason => console.info('Socket close:', reason)))), rxjs.finalize(() => conn.end()), rxjs.ignoreElements(), rxjs.startWith({ write }), rxjs.endWith(null));
|
|
16
|
+
}), rxjs.repeat({ delay: 1000, count: 2 })).subscribe(conn => {
|
|
17
|
+
conn === null || conn === void 0 ? void 0 : conn.write("GET / HTTP/1.1\n\n");
|
|
18
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { makeLineReader } from "./line-reader";
|
|
2
2
|
import { makeSemaphore } from "./semaphore";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { connectSocket } from "./connect-socket";
|
|
4
|
+
import { spawnChild } from "./spawn-child";
|
|
5
|
+
export { makeLineReader, makeSemaphore, connectSocket, spawnChild, };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.spawnChild = exports.connectSocket = exports.makeSemaphore = exports.makeLineReader = void 0;
|
|
4
4
|
const line_reader_1 = require("./line-reader");
|
|
5
5
|
Object.defineProperty(exports, "makeLineReader", { enumerable: true, get: function () { return line_reader_1.makeLineReader; } });
|
|
6
6
|
const semaphore_1 = require("./semaphore");
|
|
7
7
|
Object.defineProperty(exports, "makeSemaphore", { enumerable: true, get: function () { return semaphore_1.makeSemaphore; } });
|
|
8
|
-
const
|
|
9
|
-
Object.defineProperty(exports, "
|
|
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; } });
|
package/dist/index.test.js
CHANGED
|
@@ -11,8 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const line_reader_test_1 = require("./line-reader.test");
|
|
13
13
|
const semaphore_test_1 = require("./semaphore.test");
|
|
14
|
-
|
|
15
|
-
run(Object.assign(Object.assign(Object.assign({}, line_reader_test_1.default), semaphore_test_1.default), abortable_test_1.default))
|
|
14
|
+
run(Object.assign(Object.assign({}, line_reader_test_1.default), semaphore_test_1.default))
|
|
16
15
|
.catch(console.error);
|
|
17
16
|
function run(tests) {
|
|
18
17
|
return __awaiter(this, void 0, void 0, function* () {
|
package/dist/line-reader.js
CHANGED
|
@@ -13,7 +13,7 @@ function makeLineReader(lineCallback) {
|
|
|
13
13
|
write(chunk, encoding, callback) {
|
|
14
14
|
// encoding param here is irrelevant because it applies only to string chunks
|
|
15
15
|
const chunkStr = remainder + decoder.write(chunk);
|
|
16
|
-
const lines = chunkStr.split(
|
|
16
|
+
const lines = chunkStr.split(/\r?\n/);
|
|
17
17
|
// Keep the last line in remainder if it doesn't end with a newline character.
|
|
18
18
|
remainder = lines.pop();
|
|
19
19
|
// Push each complete line.
|
package/dist/line-reader.test.js
CHANGED
|
@@ -16,7 +16,7 @@ exports.default = {
|
|
|
16
16
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17
17
|
const lines = [];
|
|
18
18
|
const splitter = (0, line_reader_1.makeLineReader)(line => lines.push(line));
|
|
19
|
-
splitter.write('This is a line\nThis is another line\nAnd this is a line as well');
|
|
19
|
+
splitter.write('This is a line\nThis is another line\r\nAnd this is a line as well');
|
|
20
20
|
splitter.end();
|
|
21
21
|
assert(lines[0] == "This is a line" &&
|
|
22
22
|
lines[1] == "This is another line" &&
|
package/dist/semaphore.d.ts
CHANGED
package/dist/semaphore.js
CHANGED
|
@@ -13,14 +13,14 @@ exports.makeSemaphore = void 0;
|
|
|
13
13
|
function makeSemaphore(count) {
|
|
14
14
|
const waiters = [];
|
|
15
15
|
return {
|
|
16
|
-
runTask(task,
|
|
16
|
+
runTask(task, onStart) {
|
|
17
17
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18
18
|
if (count > 0)
|
|
19
19
|
count--;
|
|
20
20
|
else
|
|
21
21
|
yield new Promise(f => waiters.push(f));
|
|
22
22
|
try {
|
|
23
|
-
|
|
23
|
+
onStart === null || onStart === void 0 ? void 0 : onStart();
|
|
24
24
|
return yield task();
|
|
25
25
|
}
|
|
26
26
|
finally {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/// <reference types="node" />
|
|
4
|
+
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio } from "child_process";
|
|
5
|
+
import * as rxjs from "rxjs";
|
|
6
|
+
export interface Child {
|
|
7
|
+
pid: number;
|
|
8
|
+
stdout$: rxjs.Observable<string | Buffer>;
|
|
9
|
+
stderr$: rxjs.Observable<string | Buffer>;
|
|
10
|
+
error$: rxjs.Observable<Error>;
|
|
11
|
+
close$: rxjs.Observable<number | NodeJS.Signals>;
|
|
12
|
+
stdin: ChildProcessWithoutNullStreams['stdin'];
|
|
13
|
+
kill: ChildProcessWithoutNullStreams['kill'];
|
|
14
|
+
}
|
|
15
|
+
export declare function spawnChild(command: string, args?: string[], options?: SpawnOptionsWithoutStdio): rxjs.Observable<Child>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spawnChild = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const rxjs = require("rxjs");
|
|
6
|
+
function spawnChild(command, args, options) {
|
|
7
|
+
return rxjs.defer(() => {
|
|
8
|
+
const childProc = (0, child_process_1.spawn)(command, args, options);
|
|
9
|
+
return rxjs.race(rxjs.fromEvent(childProc, 'error', (err) => err).pipe(rxjs.map(err => { throw err; })), rxjs.fromEvent(childProc, 'spawn').pipe(rxjs.take(1), rxjs.map(() => makeChild(childProc))));
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
exports.spawnChild = spawnChild;
|
|
13
|
+
function makeChild(childProc) {
|
|
14
|
+
return {
|
|
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
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const rxjs = require("rxjs");
|
|
4
|
+
const spawn_child_1 = require("./spawn-child");
|
|
5
|
+
(0, spawn_child_1.spawnChild)('cat').pipe(rxjs.tap({
|
|
6
|
+
error(err) {
|
|
7
|
+
console.error("Child spawn fail", err);
|
|
8
|
+
}
|
|
9
|
+
}), rxjs.retry({ delay: 10000 }), rxjs.exhaustMap(child => {
|
|
10
|
+
console.info("Child spawned", child.pid);
|
|
11
|
+
const write = (text) => {
|
|
12
|
+
console.log("Child write:", text);
|
|
13
|
+
child.stdin.write(text);
|
|
14
|
+
};
|
|
15
|
+
return rxjs.merge(child.stdout$.pipe(rxjs.tap(data => console.log("Child stdout:", data.toString()))), child.stderr$.pipe(rxjs.tap(data => console.log("Child stderr:", data.toString()))), child.error$.pipe(rxjs.tap(err => console.info("Child error:", err)))).pipe(rxjs.takeUntil(child.close$.pipe(rxjs.tap(code => console.log("Child close:", code)))), rxjs.finalize(() => child.kill()), rxjs.ignoreElements(), rxjs.startWith({ write }), rxjs.endWith(null));
|
|
16
|
+
}), rxjs.repeat({ delay: 1000, count: 2 }), rxjs.switchMap(child => rxjs.iif(() => child != null, rxjs.fromEvent(process.stdin, 'data', (data) => data).pipe(rxjs.tap(data => child.write(data.toString().trim()))), rxjs.EMPTY))).subscribe({
|
|
17
|
+
complete() {
|
|
18
|
+
process.stdin.pause();
|
|
19
|
+
}
|
|
20
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lsdsoftware/utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Useful JavaScript utilities",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -23,5 +23,8 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^20.11.20",
|
|
25
25
|
"typescript": "^5.3.3"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"rxjs": "^7.8.2"
|
|
26
29
|
}
|
|
27
30
|
}
|
package/dist/abortable.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function makeAbortable(): readonly [(reason: unknown) => void, Promise<never>, () => void];
|
package/dist/abortable.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.makeAbortable = void 0;
|
|
4
|
-
function makeAbortable() {
|
|
5
|
-
let isAborted;
|
|
6
|
-
let reject;
|
|
7
|
-
const promise = new Promise((f, r) => reject = r);
|
|
8
|
-
function abort(reason) {
|
|
9
|
-
isAborted = { reason };
|
|
10
|
-
reject(reason);
|
|
11
|
-
}
|
|
12
|
-
function checkpoint() {
|
|
13
|
-
if (isAborted)
|
|
14
|
-
throw isAborted.reason;
|
|
15
|
-
}
|
|
16
|
-
return [
|
|
17
|
-
abort,
|
|
18
|
-
promise,
|
|
19
|
-
checkpoint
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
|
-
exports.makeAbortable = makeAbortable;
|
package/dist/abortable.test.d.ts
DELETED
package/dist/abortable.test.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
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
|
-
const abortable_1 = require("./abortable");
|
|
13
|
-
const assert = require("assert");
|
|
14
|
-
function task(checkpoint, output) {
|
|
15
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
-
output.push(1);
|
|
17
|
-
yield new Promise(f => setTimeout(f, 100));
|
|
18
|
-
checkpoint();
|
|
19
|
-
output.push(2);
|
|
20
|
-
yield new Promise(f => setTimeout(f, 100));
|
|
21
|
-
checkpoint();
|
|
22
|
-
output.push(3);
|
|
23
|
-
yield new Promise(f => setTimeout(f, 100));
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
exports.default = {
|
|
27
|
-
abortable1() {
|
|
28
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
-
const [abort, abortPromise, checkpoint] = (0, abortable_1.makeAbortable)();
|
|
30
|
-
const timer = setTimeout(() => abort("Canceled"), 10000);
|
|
31
|
-
const output = [];
|
|
32
|
-
yield Promise.race([
|
|
33
|
-
abortPromise,
|
|
34
|
-
task(checkpoint, output)
|
|
35
|
-
.finally(() => clearTimeout(timer))
|
|
36
|
-
]);
|
|
37
|
-
assert(output.length == 3 &&
|
|
38
|
-
output[0] == 1 &&
|
|
39
|
-
output[1] == 2 &&
|
|
40
|
-
output[2] == 3);
|
|
41
|
-
});
|
|
42
|
-
},
|
|
43
|
-
abortable2() {
|
|
44
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
45
|
-
const [abort, abortPromise, checkpoint] = (0, abortable_1.makeAbortable)();
|
|
46
|
-
setTimeout(() => abort("Canceled"), 150);
|
|
47
|
-
const output = [];
|
|
48
|
-
yield Promise.race([
|
|
49
|
-
abortPromise.catch(err => Promise.reject(err + " by promise")),
|
|
50
|
-
task(checkpoint, output)
|
|
51
|
-
]).then(() => assert(false), err => assert(err == "Canceled by promise"));
|
|
52
|
-
assert(output.length == 2 &&
|
|
53
|
-
output[0] == 1 &&
|
|
54
|
-
output[1] == 2);
|
|
55
|
-
});
|
|
56
|
-
},
|
|
57
|
-
abortable3() {
|
|
58
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
-
const [abort, abortPromise, checkpoint] = (0, abortable_1.makeAbortable)();
|
|
60
|
-
setTimeout(() => abort("Canceled"), 150);
|
|
61
|
-
const output = [];
|
|
62
|
-
yield Promise.race([
|
|
63
|
-
abortPromise.catch(err => new Promise(f => "Never")),
|
|
64
|
-
task(checkpoint, output)
|
|
65
|
-
]).then(() => assert(false), err => assert(err == "Canceled"));
|
|
66
|
-
assert(output.length == 2 &&
|
|
67
|
-
output[0] == 1 &&
|
|
68
|
-
output[1] == 2);
|
|
69
|
-
});
|
|
70
|
-
},
|
|
71
|
-
};
|