@lsdsoftware/utils 1.0.2 → 1.0.4
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 +34 -140
- package/dist/abortable.d.ts +1 -0
- package/dist/abortable.js +22 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +3 -9
- package/dist/index.test.js +1 -3
- package/dist/semaphore.d.ts +1 -1
- package/dist/semaphore.js +2 -1
- package/package.json +1 -1
- package/dist/connection-manager.d.ts +0 -13
- package/dist/connection-manager.js +0 -70
- package/dist/message-dispatcher.d.ts +0 -29
- package/dist/message-dispatcher.js +0 -72
- package/dist/rate-limiter.d.ts +0 -7
- package/dist/rate-limiter.js +0 -43
- package/dist/rate-limiter.test.d.ts +0 -4
- package/dist/rate-limiter.test.js +0 -39
- package/dist/state-machine.d.ts +0 -7
- package/dist/state-machine.js +0 -36
- package/dist/state-machine.test.d.ts +0 -4
- package/dist/state-machine.test.js +0 -69
package/README.md
CHANGED
|
@@ -1,168 +1,62 @@
|
|
|
1
1
|
# Useful JavaScript utilities
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
###
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { makeStateMachine } from "@lsdsoftware/utils"
|
|
9
|
-
|
|
10
|
-
const sm = makeStateMachine({
|
|
11
|
-
IDLE: {
|
|
12
|
-
startIt() {
|
|
13
|
-
//do something
|
|
14
|
-
return "BUSY"
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
BUSY: {
|
|
18
|
-
stopIt() {
|
|
19
|
-
//stop doing it
|
|
20
|
-
return "IDLE"
|
|
21
|
-
},
|
|
22
|
-
stopAfterDelay() {
|
|
23
|
-
return "STOPPING"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
STOPPING: {
|
|
27
|
-
onTransitionIn(this: any) {
|
|
28
|
-
//do some clean up
|
|
29
|
-
this.timer = setTimeout(() => sm.trigger("onDone"), 3000)
|
|
30
|
-
},
|
|
31
|
-
onDone() {
|
|
32
|
-
return "IDLE"
|
|
33
|
-
},
|
|
34
|
-
stopIt() {
|
|
35
|
-
console.log("Already stopping, be patient!")
|
|
36
|
-
//return void to stay in same state
|
|
37
|
-
},
|
|
38
|
-
forceIt(this: any) {
|
|
39
|
-
clearTimeout(this.timer)
|
|
40
|
-
return "IDLE"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
sm.trigger("startIt")
|
|
46
|
-
sm.getState() //BUSY
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
### Message Dispatcher
|
|
52
|
-
Dispatch messages to handlers. This utility assumes messages are one of three types: request, response, or notification; and follow a predefined format (see type definition below).
|
|
4
|
+
### Line Reader
|
|
5
|
+
Split text into lines
|
|
53
6
|
|
|
54
7
|
```typescript
|
|
55
|
-
|
|
56
|
-
to: string
|
|
57
|
-
type: "request"
|
|
58
|
-
id: "string"
|
|
59
|
-
method: string
|
|
60
|
-
args: Record<string, unknown>
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface Notification {
|
|
64
|
-
to: string
|
|
65
|
-
type: "notification"
|
|
66
|
-
method: string
|
|
67
|
-
args: Record<string, unknown>
|
|
68
|
-
}
|
|
8
|
+
import { makeLineReader } from "@lsdsoftware/utils"
|
|
69
9
|
|
|
70
|
-
|
|
71
|
-
type: "response"
|
|
72
|
-
id: string
|
|
73
|
-
error: unknown
|
|
74
|
-
result: unknown
|
|
75
|
-
}
|
|
10
|
+
myStream.pipe(makeLineReader(line => console.log(line)))
|
|
76
11
|
```
|
|
77
12
|
|
|
78
|
-
Call `dispatch` to dispatch a message you received. Requests and notifications are dispatched to request handlers that you provide at construction. Responses are dispatched to response listeners; to listen for a response, call `waitForResponse` with the request ID.
|
|
79
13
|
|
|
80
|
-
|
|
14
|
+
### Semaphore
|
|
15
|
+
Control concurrent access to resources
|
|
81
16
|
|
|
82
17
|
```typescript
|
|
83
|
-
import {
|
|
84
|
-
|
|
85
|
-
const requestHandlers = {
|
|
86
|
-
method1({paramA, paramB}, sender) {
|
|
87
|
-
//do something
|
|
88
|
-
return result
|
|
89
|
-
},
|
|
90
|
-
async method2({x, y, z}, sender) {
|
|
91
|
-
//do something
|
|
92
|
-
return result
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const dispatcher = makeMessageDispatcher("myAddress", requestHandlers)
|
|
18
|
+
import { makeSemaphore } from "@lsdsoftware/utils"
|
|
97
19
|
|
|
98
|
-
|
|
20
|
+
const semaphore = makeSemaphore(3)
|
|
99
21
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const sender = {window: event.source, origin: event.origin}
|
|
103
|
-
const sendResponse = response => sender.window.postMessage(response, sender.origin)
|
|
104
|
-
dispatcher.dispatch(event.data, sender, sendResponse)
|
|
22
|
+
const result = await semaphore.runTask(async () => {
|
|
23
|
+
//use the limited resource
|
|
105
24
|
})
|
|
106
|
-
|
|
107
|
-
//sending requests
|
|
108
|
-
const id = String(Math.random())
|
|
109
|
-
const request = {to: "someAddress", type: "request", id, method: "someMethod", args: {}}
|
|
110
|
-
iframeWindow.postMessage(request, "*")
|
|
111
|
-
dispatcher.waitForResponse(id)
|
|
112
|
-
.then(result => console.log(result))
|
|
113
|
-
.catch(console.error)
|
|
114
25
|
```
|
|
115
26
|
|
|
116
27
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Basic rate limiter using the token bucket algorithm.
|
|
28
|
+
### Abortable
|
|
29
|
+
If you have an async operation, e.g.:
|
|
120
30
|
|
|
121
31
|
```typescript
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
function handleRequest(userId, req) {
|
|
127
|
-
if (limiter.tryRemoveTokens(userId, 1)) return processRequest(req)
|
|
128
|
-
else throw "Rate limit exceeded"
|
|
32
|
+
async function myTask() {
|
|
33
|
+
await step1()
|
|
34
|
+
await step2()
|
|
35
|
+
await step3()
|
|
129
36
|
}
|
|
130
37
|
```
|
|
131
38
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
### Connection Manager
|
|
135
|
-
Takes a connect() method and:
|
|
136
|
-
- Only call it to create a connection when needed
|
|
137
|
-
- Automatically retry on failure
|
|
138
|
-
- Automatically reconnect if the previous connection was closed
|
|
139
|
-
- Properly handle shutdown sequence
|
|
140
|
-
|
|
39
|
+
And you need to make it abortable:
|
|
141
40
|
```typescript
|
|
142
|
-
import {
|
|
143
|
-
|
|
144
|
-
const conMgr = new ConnectionManager({
|
|
145
|
-
async connect() {
|
|
146
|
-
//...
|
|
147
|
-
return connection
|
|
148
|
-
},
|
|
149
|
-
retryDelay: 10*1000
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
//wherever you need the connection
|
|
153
|
-
const connection = await conMgr.get()
|
|
154
|
-
|
|
155
|
-
//shutdown
|
|
156
|
-
conMgr.shutdown()
|
|
157
|
-
```
|
|
158
|
-
|
|
41
|
+
import { makeAbortable } from "@lsdsoftware/utils"
|
|
159
42
|
|
|
43
|
+
const [abort, abortPromise, checkpoint] = makeAbortable()
|
|
160
44
|
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
}
|
|
163
53
|
|
|
164
|
-
|
|
165
|
-
|
|
54
|
+
//call abort() when you need to
|
|
55
|
+
setTimeout(() => abort(new Error("Timeout")), 5000)
|
|
166
56
|
|
|
167
|
-
|
|
57
|
+
//use Promise.race to run your task
|
|
58
|
+
const result = await Promise.race([
|
|
59
|
+
abortPromise,
|
|
60
|
+
myTask()
|
|
61
|
+
])
|
|
168
62
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function makeAbortable(): readonly [(reason: unknown) => void, Promise<never>, () => void];
|
|
@@ -0,0 +1,22 @@
|
|
|
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/index.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { makeLineReader } from "./line-reader";
|
|
2
|
-
import { makeStateMachine } from "./state-machine";
|
|
3
|
-
import { makeConnectionManager } from "./connection-manager";
|
|
4
|
-
import { makeMessageDispatcher } from "./message-dispatcher";
|
|
5
|
-
import { makeRateLimiter } from "./rate-limiter";
|
|
6
2
|
import { makeSemaphore } from "./semaphore";
|
|
7
|
-
|
|
3
|
+
import { makeAbortable } from "./abortable";
|
|
4
|
+
export { makeLineReader, makeSemaphore, makeAbortable, };
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.makeAbortable = 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
|
-
const state_machine_1 = require("./state-machine");
|
|
7
|
-
Object.defineProperty(exports, "makeStateMachine", { enumerable: true, get: function () { return state_machine_1.makeStateMachine; } });
|
|
8
|
-
const connection_manager_1 = require("./connection-manager");
|
|
9
|
-
Object.defineProperty(exports, "makeConnectionManager", { enumerable: true, get: function () { return connection_manager_1.makeConnectionManager; } });
|
|
10
|
-
const message_dispatcher_1 = require("./message-dispatcher");
|
|
11
|
-
Object.defineProperty(exports, "makeMessageDispatcher", { enumerable: true, get: function () { return message_dispatcher_1.makeMessageDispatcher; } });
|
|
12
|
-
const rate_limiter_1 = require("./rate-limiter");
|
|
13
|
-
Object.defineProperty(exports, "makeRateLimiter", { enumerable: true, get: function () { return rate_limiter_1.makeRateLimiter; } });
|
|
14
6
|
const semaphore_1 = require("./semaphore");
|
|
15
7
|
Object.defineProperty(exports, "makeSemaphore", { enumerable: true, get: function () { return semaphore_1.makeSemaphore; } });
|
|
8
|
+
const abortable_1 = require("./abortable");
|
|
9
|
+
Object.defineProperty(exports, "makeAbortable", { enumerable: true, get: function () { return abortable_1.makeAbortable; } });
|
package/dist/index.test.js
CHANGED
|
@@ -10,9 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const line_reader_test_1 = require("./line-reader.test");
|
|
13
|
-
|
|
14
|
-
const state_machine_test_1 = require("./state-machine.test");
|
|
15
|
-
run(Object.assign(Object.assign(Object.assign({}, line_reader_test_1.default), rate_limiter_test_1.default), state_machine_test_1.default))
|
|
13
|
+
run(Object.assign({}, line_reader_test_1.default))
|
|
16
14
|
.catch(console.error);
|
|
17
15
|
function run(tests) {
|
|
18
16
|
return __awaiter(this, void 0, void 0, function* () {
|
package/dist/semaphore.d.ts
CHANGED
package/dist/semaphore.js
CHANGED
|
@@ -13,12 +13,13 @@ exports.makeSemaphore = void 0;
|
|
|
13
13
|
function makeSemaphore(count) {
|
|
14
14
|
const waiters = [];
|
|
15
15
|
return {
|
|
16
|
-
runTask(task) {
|
|
16
|
+
runTask(task, checkpoint) {
|
|
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
|
+
checkpoint === null || checkpoint === void 0 ? void 0 : checkpoint();
|
|
22
23
|
try {
|
|
23
24
|
return yield task();
|
|
24
25
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
interface Closeable {
|
|
2
|
-
close(): void;
|
|
3
|
-
once(event: "close", callback: Function): void;
|
|
4
|
-
}
|
|
5
|
-
export interface ConnectionManager<Connection> {
|
|
6
|
-
get(): Promise<Connection>;
|
|
7
|
-
shutdown(): void;
|
|
8
|
-
}
|
|
9
|
-
export declare function makeConnectionManager<Connection extends Closeable>({ connect, retryDelay }: {
|
|
10
|
-
connect(): Promise<Connection>;
|
|
11
|
-
retryDelay: number;
|
|
12
|
-
}): ConnectionManager<Connection>;
|
|
13
|
-
export {};
|
|
@@ -1,70 +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
|
-
exports.makeConnectionManager = void 0;
|
|
13
|
-
function makeConnectionManager({ connect, retryDelay }) {
|
|
14
|
-
let connectionPromise;
|
|
15
|
-
let shutdownFlag = false;
|
|
16
|
-
return {
|
|
17
|
-
get() {
|
|
18
|
-
if (!connectionPromise)
|
|
19
|
-
connectionPromise = start();
|
|
20
|
-
return connectionPromise;
|
|
21
|
-
},
|
|
22
|
-
shutdown() {
|
|
23
|
-
shutdownFlag = true;
|
|
24
|
-
connectionPromise === null || connectionPromise === void 0 ? void 0 : connectionPromise.then(con => con.close()).catch(err => "OK");
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
function start() {
|
|
28
|
-
return new Promise(fulfill => {
|
|
29
|
-
let firstTime = true;
|
|
30
|
-
keepAlive(promise => {
|
|
31
|
-
if (firstTime) {
|
|
32
|
-
fulfill(promise);
|
|
33
|
-
firstTime = false;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
connectionPromise = promise;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
function keepAlive(onUpdate) {
|
|
42
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
-
try {
|
|
44
|
-
while (true) {
|
|
45
|
-
const promise = connectUntilSucceed();
|
|
46
|
-
onUpdate(promise);
|
|
47
|
-
const connection = yield promise;
|
|
48
|
-
yield new Promise(f => connection.once("close", f));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
function connectUntilSucceed() {
|
|
56
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
-
while (true) {
|
|
58
|
-
if (shutdownFlag)
|
|
59
|
-
throw new Error("Shutting down");
|
|
60
|
-
try {
|
|
61
|
-
return yield connect();
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
yield new Promise(f => setTimeout(f, retryDelay));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
exports.makeConnectionManager = makeConnectionManager;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export type Message = Request | Notification | Response;
|
|
2
|
-
interface Request {
|
|
3
|
-
to: string;
|
|
4
|
-
type: "request";
|
|
5
|
-
id: "string";
|
|
6
|
-
method: string;
|
|
7
|
-
args: Record<string, unknown>;
|
|
8
|
-
}
|
|
9
|
-
interface Notification {
|
|
10
|
-
to: string;
|
|
11
|
-
type: "notification";
|
|
12
|
-
method: string;
|
|
13
|
-
args: Record<string, unknown>;
|
|
14
|
-
}
|
|
15
|
-
interface Response {
|
|
16
|
-
type: "response";
|
|
17
|
-
id: string;
|
|
18
|
-
error: unknown;
|
|
19
|
-
result: unknown;
|
|
20
|
-
}
|
|
21
|
-
interface RequestHandler {
|
|
22
|
-
(args: Record<string, unknown>, sender: unknown): unknown;
|
|
23
|
-
}
|
|
24
|
-
export declare function makeMessageDispatcher(myAddress: string, requestHandlers: Record<string, RequestHandler>): {
|
|
25
|
-
waitForResponse<T>(requestId: string): Promise<T>;
|
|
26
|
-
dispatch(message: Message, sender: unknown, sendResponse: (res: Response) => void): boolean | void;
|
|
27
|
-
updateRequestHandlers(newHandlers: typeof requestHandlers): void;
|
|
28
|
-
};
|
|
29
|
-
export {};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.makeMessageDispatcher = void 0;
|
|
4
|
-
function makeMessageDispatcher(myAddress, requestHandlers) {
|
|
5
|
-
const pendingRequests = new Map();
|
|
6
|
-
return {
|
|
7
|
-
waitForResponse(requestId) {
|
|
8
|
-
let pending = pendingRequests.get(requestId);
|
|
9
|
-
if (!pending)
|
|
10
|
-
pendingRequests.set(requestId, pending = makePending());
|
|
11
|
-
return pending.promise;
|
|
12
|
-
},
|
|
13
|
-
dispatch(message, sender, sendResponse) {
|
|
14
|
-
switch (message.type) {
|
|
15
|
-
case "request": return handleRequest(message, sender, sendResponse);
|
|
16
|
-
case "notification": return handleNotification(message, sender);
|
|
17
|
-
case "response": return handleResponse(message);
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
updateRequestHandlers(newHandlers) {
|
|
21
|
-
requestHandlers = newHandlers;
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
function makePending() {
|
|
25
|
-
const pending = {};
|
|
26
|
-
pending.promise = new Promise((fulfill, reject) => {
|
|
27
|
-
pending.fulfill = fulfill;
|
|
28
|
-
pending.reject = reject;
|
|
29
|
-
});
|
|
30
|
-
return pending;
|
|
31
|
-
}
|
|
32
|
-
function handleRequest(req, sender, sendResponse) {
|
|
33
|
-
if (req.to == myAddress) {
|
|
34
|
-
if (requestHandlers[req.method]) {
|
|
35
|
-
Promise.resolve()
|
|
36
|
-
.then(() => requestHandlers[req.method](req.args, sender))
|
|
37
|
-
.then(result => sendResponse({ type: "response", id: req.id, result, error: undefined }), error => sendResponse({ type: "response", id: req.id, result: undefined, error }));
|
|
38
|
-
//let caller know that sendResponse will be called asynchronously
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
console.error("No handler for method", req);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function handleNotification(ntf, sender) {
|
|
47
|
-
if (ntf.to == myAddress) {
|
|
48
|
-
if (requestHandlers[ntf.method]) {
|
|
49
|
-
Promise.resolve()
|
|
50
|
-
.then(() => requestHandlers[ntf.method](ntf.args, sender))
|
|
51
|
-
.catch(error => console.error("Failed to handle notification", ntf, error));
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
console.error("No handler for method", ntf);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function handleResponse(res) {
|
|
59
|
-
const pending = pendingRequests.get(res.id);
|
|
60
|
-
if (pending) {
|
|
61
|
-
pendingRequests.delete(res.id);
|
|
62
|
-
if (res.error)
|
|
63
|
-
pending.reject(res.error);
|
|
64
|
-
else
|
|
65
|
-
pending.fulfill(res.result);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
console.error("Stray response", res);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
exports.makeMessageDispatcher = makeMessageDispatcher;
|
package/dist/rate-limiter.d.ts
DELETED
package/dist/rate-limiter.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.makeRateLimiter = void 0;
|
|
4
|
-
class Bucket {
|
|
5
|
-
constructor(count, expire) {
|
|
6
|
-
this.count = count;
|
|
7
|
-
this.expire = expire;
|
|
8
|
-
}
|
|
9
|
-
isValid() {
|
|
10
|
-
return this.expire > Date.now();
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
function makeRateLimiter({ interval, tokensPerInterval }) {
|
|
14
|
-
const buckets = new Map();
|
|
15
|
-
let lastCleanup = Date.now();
|
|
16
|
-
return {
|
|
17
|
-
getTokensRemaining(key) {
|
|
18
|
-
const bucket = buckets.get(key);
|
|
19
|
-
return (bucket === null || bucket === void 0 ? void 0 : bucket.isValid()) ? bucket.count : tokensPerInterval;
|
|
20
|
-
},
|
|
21
|
-
tryRemoveTokens(key, numTokens) {
|
|
22
|
-
if (Date.now() - lastCleanup > 2 * interval) {
|
|
23
|
-
lastCleanup = Date.now();
|
|
24
|
-
for (const [key, bucket] of buckets)
|
|
25
|
-
if (!bucket.isValid())
|
|
26
|
-
buckets.delete(key);
|
|
27
|
-
}
|
|
28
|
-
let bucket = buckets.get(key);
|
|
29
|
-
if (!(bucket === null || bucket === void 0 ? void 0 : bucket.isValid())) {
|
|
30
|
-
bucket = new Bucket(tokensPerInterval, Date.now() + interval);
|
|
31
|
-
buckets.set(key, bucket);
|
|
32
|
-
}
|
|
33
|
-
if (numTokens <= bucket.count) {
|
|
34
|
-
bucket.count -= numTokens;
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
exports.makeRateLimiter = makeRateLimiter;
|
|
@@ -1,39 +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 rate_limiter_1 = require("./rate-limiter");
|
|
13
|
-
const assert = require("assert");
|
|
14
|
-
exports.default = {
|
|
15
|
-
rateLimiter1() {
|
|
16
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
-
const limiter = (0, rate_limiter_1.makeRateLimiter)({ tokensPerInterval: 5, interval: 2000 });
|
|
18
|
-
assert(limiter.getTokensRemaining("k1") == 5);
|
|
19
|
-
assert(limiter.tryRemoveTokens("k1", 3) == true);
|
|
20
|
-
assert(limiter.getTokensRemaining("k1") == 2);
|
|
21
|
-
assert(limiter.tryRemoveTokens("k1", 4) == false);
|
|
22
|
-
assert(limiter.tryRemoveTokens("k2", 4) == true);
|
|
23
|
-
yield new Promise(f => setTimeout(f, 1500));
|
|
24
|
-
assert(limiter.tryRemoveTokens("k1", 4) == false);
|
|
25
|
-
assert(limiter.getTokensRemaining("k1") == 2);
|
|
26
|
-
yield new Promise(f => setTimeout(f, 1000));
|
|
27
|
-
assert(limiter.tryRemoveTokens("k1", 4) == true);
|
|
28
|
-
//new bucket, won't expire until 2 seconds from now
|
|
29
|
-
assert(limiter.getTokensRemaining("k1") == 1);
|
|
30
|
-
assert(limiter.tryRemoveTokens("k1", 2) == false);
|
|
31
|
-
yield new Promise(f => setTimeout(f, 1900));
|
|
32
|
-
assert(limiter.tryRemoveTokens("k1", 2) == false);
|
|
33
|
-
assert(limiter.getTokensRemaining("k1") == 1);
|
|
34
|
-
yield new Promise(f => setTimeout(f, 200));
|
|
35
|
-
assert(limiter.tryRemoveTokens("k1", 2) == true);
|
|
36
|
-
assert(limiter.getTokensRemaining("k1") == 3);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
};
|
package/dist/state-machine.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
type EventHandler<StateName> = (...args: any) => StateName | void;
|
|
2
|
-
export type State<StateName extends string, EventName extends string> = Partial<Record<EventName | "onTransitionIn", EventHandler<StateName>>>;
|
|
3
|
-
export declare function makeStateMachine<StateName extends string, EventName extends string>(states: Record<StateName | "IDLE", State<StateName, EventName>>): {
|
|
4
|
-
trigger(eventName: EventName, ...args: any): void;
|
|
5
|
-
getState(): StateName | "IDLE";
|
|
6
|
-
};
|
|
7
|
-
export {};
|
package/dist/state-machine.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.makeStateMachine = void 0;
|
|
4
|
-
function makeStateMachine(states) {
|
|
5
|
-
var _a, _b;
|
|
6
|
-
let currentStateName = "IDLE";
|
|
7
|
-
(_b = (_a = states[currentStateName]).onTransitionIn) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
8
|
-
let lock = 0;
|
|
9
|
-
return {
|
|
10
|
-
trigger(eventName, ...args) {
|
|
11
|
-
var _a, _b;
|
|
12
|
-
if (lock)
|
|
13
|
-
throw new Error("Cannot trigger an event synchronously while inside an event handler");
|
|
14
|
-
lock++;
|
|
15
|
-
try {
|
|
16
|
-
const currentState = states[currentStateName];
|
|
17
|
-
if (!(eventName in currentState))
|
|
18
|
-
throw new Error("Missing handler " + currentStateName + "." + eventName);
|
|
19
|
-
const nextStateName = currentState[eventName](...args);
|
|
20
|
-
if (nextStateName) {
|
|
21
|
-
if (!(nextStateName in states))
|
|
22
|
-
throw new Error("Missing state " + nextStateName);
|
|
23
|
-
currentStateName = nextStateName;
|
|
24
|
-
(_b = (_a = states[currentStateName]).onTransitionIn) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
finally {
|
|
28
|
-
lock--;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
getState() {
|
|
32
|
-
return currentStateName;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
exports.makeStateMachine = makeStateMachine;
|
|
@@ -1,69 +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 state_machine_1 = require("./state-machine");
|
|
13
|
-
const assert = require("assert");
|
|
14
|
-
exports.default = {
|
|
15
|
-
stateMachine1() {
|
|
16
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
-
const sm = (0, state_machine_1.makeStateMachine)({
|
|
18
|
-
IDLE: {
|
|
19
|
-
play() {
|
|
20
|
-
return "PLAYING";
|
|
21
|
-
},
|
|
22
|
-
stop() { },
|
|
23
|
-
goto(state) {
|
|
24
|
-
return state;
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
PLAYING: {
|
|
28
|
-
play() { },
|
|
29
|
-
stop() {
|
|
30
|
-
return "STOPPING";
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
STOPPING: {
|
|
34
|
-
onTransitionIn() {
|
|
35
|
-
this.timer = setTimeout(() => sm.trigger("onStop"), 2000);
|
|
36
|
-
},
|
|
37
|
-
onStop() {
|
|
38
|
-
return "IDLE";
|
|
39
|
-
},
|
|
40
|
-
play() {
|
|
41
|
-
clearTimeout(this.timer);
|
|
42
|
-
return "PLAYING";
|
|
43
|
-
},
|
|
44
|
-
stop() { }
|
|
45
|
-
},
|
|
46
|
-
STUCK: {}
|
|
47
|
-
});
|
|
48
|
-
assert(sm.getState() == "IDLE");
|
|
49
|
-
sm.trigger("play");
|
|
50
|
-
assert(sm.getState() == "PLAYING");
|
|
51
|
-
sm.trigger("stop");
|
|
52
|
-
assert(sm.getState() == "STOPPING");
|
|
53
|
-
yield sleep(1000);
|
|
54
|
-
sm.trigger("play");
|
|
55
|
-
assert(sm.getState() == "PLAYING");
|
|
56
|
-
yield sleep(3000);
|
|
57
|
-
assert(sm.getState() == "PLAYING");
|
|
58
|
-
sm.trigger("stop");
|
|
59
|
-
assert(sm.getState() == "STOPPING");
|
|
60
|
-
yield sleep(3000);
|
|
61
|
-
assert(sm.getState() == "IDLE");
|
|
62
|
-
sm.trigger("goto", "STUCK");
|
|
63
|
-
assert(sm.getState() == "STUCK");
|
|
64
|
-
});
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
function sleep(ms) {
|
|
68
|
-
return new Promise(f => setTimeout(f, ms));
|
|
69
|
-
}
|