@muspellheim/shared 0.5.0 → 0.6.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/.prettierignore +3 -0
- package/.prettierrc +5 -0
- package/README.md +10 -5
- package/deno.json +4 -1
- package/deno.mk +68 -0
- package/eslint.config.js +23 -0
- package/lib/assert.js +2 -2
- package/lib/browser/index.js +0 -2
- package/lib/color.js +20 -22
- package/lib/health.js +3 -7
- package/lib/index.js +2 -1
- package/lib/lang.js +3 -5
- package/lib/logging.js +11 -10
- package/lib/long-polling-client.js +21 -10
- package/lib/message-client.js +0 -6
- package/lib/messages.js +68 -0
- package/lib/metrics.js +1 -1
- package/lib/node/actuator-controller.js +7 -8
- package/lib/node/configuration-properties.js +123 -86
- package/lib/node/handler.js +4 -2
- package/lib/node/index.js +1 -2
- package/lib/node/logging.js +5 -2
- package/lib/node/long-polling.js +2 -0
- package/lib/node/static-files-controller.js +15 -0
- package/lib/sse-client.js +21 -10
- package/lib/time.js +13 -11
- package/lib/util.js +45 -26
- package/lib/validation.js +13 -22
- package/lib/vector.js +1 -1
- package/lib/vitest/equality-testers.js +19 -0
- package/lib/vitest/index.js +1 -0
- package/lib/web-socket-client.js +26 -9
- package/package.json +12 -6
- package/tsconfig.json +13 -0
package/lib/util.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Contains several miscellaneous utility classes.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Ported from
|
|
7
7
|
* [Java Util](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/package-summary.html).
|
|
8
8
|
*
|
|
9
9
|
* @module
|
|
@@ -11,36 +11,51 @@
|
|
|
11
11
|
|
|
12
12
|
import { Clock } from './time.js';
|
|
13
13
|
|
|
14
|
-
// TODO check if import from time.js is needed
|
|
15
|
-
// TODO deep copy
|
|
16
14
|
// TODO deep equals
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Creates a deep copy of a value.
|
|
18
|
+
*
|
|
19
|
+
* @param {*} value The value to copy.
|
|
20
|
+
* @returns {*} The deep copy of the value.
|
|
21
|
+
*/
|
|
22
|
+
export function deepCopy(value) {
|
|
23
|
+
return JSON.parse(JSON.stringify(value));
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Merges two objects deeply.
|
|
28
|
+
*
|
|
29
|
+
* @param {*} target The object to merge into.
|
|
30
|
+
* @param {*} source The object to merge.
|
|
31
|
+
* @returns {*} The merged object.
|
|
32
|
+
*/
|
|
33
|
+
export function deepMerge(target, source) {
|
|
34
|
+
if (source === undefined) {
|
|
24
35
|
return target;
|
|
25
36
|
}
|
|
26
37
|
|
|
27
|
-
if (
|
|
28
|
-
|
|
38
|
+
if (typeof source !== 'object' || source === null) {
|
|
39
|
+
return source;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
43
|
+
for (const item of source) {
|
|
29
44
|
const element = deepMerge(undefined, item);
|
|
30
|
-
|
|
45
|
+
target.push(element);
|
|
31
46
|
}
|
|
32
|
-
return
|
|
47
|
+
return target;
|
|
33
48
|
}
|
|
34
49
|
|
|
35
|
-
for (const key in
|
|
36
|
-
if (typeof
|
|
37
|
-
|
|
50
|
+
for (const key in source) {
|
|
51
|
+
if (typeof target !== 'object' || target === null) {
|
|
52
|
+
target = {};
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
target[key] = deepMerge(target[key], source[key]);
|
|
41
56
|
}
|
|
42
57
|
|
|
43
|
-
return
|
|
58
|
+
return target;
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
/**
|
|
@@ -227,20 +242,25 @@ export class Timer extends EventTarget {
|
|
|
227
242
|
* Returns a new `Timer` for testing without side effects.
|
|
228
243
|
*/
|
|
229
244
|
static createNull({ clock = Clock.fixed() } = {}) {
|
|
230
|
-
return new Timer(clock, new TimeoutStub(
|
|
245
|
+
return new Timer(clock, new TimeoutStub());
|
|
231
246
|
}
|
|
232
247
|
|
|
248
|
+
/** @type {Clock} */
|
|
233
249
|
#clock;
|
|
250
|
+
|
|
251
|
+
/** @type {globalThis} */
|
|
234
252
|
#global;
|
|
253
|
+
|
|
254
|
+
/** @type {TimerTask[]} */
|
|
235
255
|
_queue;
|
|
236
256
|
|
|
237
257
|
/**
|
|
238
258
|
* Returns a new `Timer`.
|
|
259
|
+
*
|
|
260
|
+
* @param {Clock=} clock The clock to use.
|
|
261
|
+
* @param {globalThis} global The global object.
|
|
239
262
|
*/
|
|
240
|
-
constructor(
|
|
241
|
-
/** @type {Clock} */ clock = Clock.system(),
|
|
242
|
-
/** @type {globalThis} */ global = globalThis,
|
|
243
|
-
) {
|
|
263
|
+
constructor(clock = Clock.system(), global = globalThis) {
|
|
244
264
|
super();
|
|
245
265
|
this.#clock = clock;
|
|
246
266
|
this.#global = global;
|
|
@@ -331,7 +351,7 @@ export class Timer extends EventTarget {
|
|
|
331
351
|
return;
|
|
332
352
|
}
|
|
333
353
|
|
|
334
|
-
|
|
354
|
+
const task = this._queue[0];
|
|
335
355
|
if (task._state === TASK_CANCELLED) {
|
|
336
356
|
this._queue.shift();
|
|
337
357
|
return this.#runMainLoop();
|
|
@@ -345,9 +365,8 @@ export class Timer extends EventTarget {
|
|
|
345
365
|
this._queue.shift();
|
|
346
366
|
task._state = TASK_EXECUTED;
|
|
347
367
|
} else {
|
|
348
|
-
task._nextExecutionTime =
|
|
349
|
-
? now - task._period
|
|
350
|
-
: executionTime + task._period;
|
|
368
|
+
task._nextExecutionTime =
|
|
369
|
+
task._period < 0 ? now - task._period : executionTime + task._period;
|
|
351
370
|
}
|
|
352
371
|
task.run();
|
|
353
372
|
} else {
|
package/lib/validation.js
CHANGED
|
@@ -99,7 +99,6 @@ export function ensureArguments(args, expectedTypes = [], names = []) {
|
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
/** @return {{value: ?*, error: ?string}}} */
|
|
103
102
|
function checkType(value, expectedType, { name = 'value' } = {}) {
|
|
104
103
|
const valueType = getType(value);
|
|
105
104
|
|
|
@@ -121,11 +120,9 @@ function checkType(value, expectedType, { name = 'value' } = {}) {
|
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
return {
|
|
124
|
-
error: `The ${name} must be ${
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
})
|
|
128
|
-
}, but it was ${describe(valueType, { articles: true })}.`,
|
|
123
|
+
error: `The ${name} must be ${describe(expectedType, {
|
|
124
|
+
articles: true,
|
|
125
|
+
})}, but it was ${describe(valueType, { articles: true })}.`,
|
|
129
126
|
};
|
|
130
127
|
}
|
|
131
128
|
|
|
@@ -135,11 +132,9 @@ function checkType(value, expectedType, { name = 'value' } = {}) {
|
|
|
135
132
|
return { value: expectedType.valueOf(String(value).toUpperCase()) };
|
|
136
133
|
} catch {
|
|
137
134
|
return {
|
|
138
|
-
error: `The ${name} must be ${
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
})
|
|
142
|
-
}, but it was ${describe(valueType, { articles: true })}.`,
|
|
135
|
+
error: `The ${name} must be ${describe(expectedType, {
|
|
136
|
+
articles: true,
|
|
137
|
+
})}, but it was ${describe(valueType, { articles: true })}.`,
|
|
143
138
|
};
|
|
144
139
|
}
|
|
145
140
|
}
|
|
@@ -151,13 +146,11 @@ function checkType(value, expectedType, { name = 'value' } = {}) {
|
|
|
151
146
|
} else {
|
|
152
147
|
const convertedValue = new expectedType(value);
|
|
153
148
|
if (String(convertedValue).toLowerCase().startsWith('invalid')) {
|
|
154
|
-
let error = `The ${name} must be a valid ${
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
)
|
|
158
|
-
}, but it was ${describe(valueType, { articles: true })}`;
|
|
149
|
+
let error = `The ${name} must be a valid ${describe(
|
|
150
|
+
expectedType,
|
|
151
|
+
)}, but it was ${describe(valueType, { articles: true })}`;
|
|
159
152
|
if (valueType != null) {
|
|
160
|
-
error += `: ${JSON.stringify(value
|
|
153
|
+
error += `: ${JSON.stringify(value)}`;
|
|
161
154
|
}
|
|
162
155
|
error += '.';
|
|
163
156
|
return { error };
|
|
@@ -177,11 +170,9 @@ function checkType(value, expectedType, { name = 'value' } = {}) {
|
|
|
177
170
|
}
|
|
178
171
|
|
|
179
172
|
return {
|
|
180
|
-
error: `The ${name} must be ${
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
})
|
|
184
|
-
}, but it was ${describe(valueType, { articles: true })}.`,
|
|
173
|
+
error: `The ${name} must be ${describe(expectedType, {
|
|
174
|
+
articles: true,
|
|
175
|
+
})}, but it was ${describe(valueType, { articles: true })}.`,
|
|
185
176
|
};
|
|
186
177
|
}
|
|
187
178
|
|
package/lib/vector.js
CHANGED
|
@@ -168,7 +168,7 @@ export class Line2D {
|
|
|
168
168
|
* Returns the perpendicular of a point on this line.
|
|
169
169
|
*
|
|
170
170
|
* @param {Vector2D} point A point.
|
|
171
|
-
* @return {{foot:
|
|
171
|
+
* @return {{foot: Vector2D, scalar: number}} The `foot` and the `scalar`.
|
|
172
172
|
*/
|
|
173
173
|
perpendicular(point) {
|
|
174
174
|
// dissolve after r: (line.position + r * line.direction - point) * line.direction = 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
function isEquatable(a) {
|
|
4
|
+
return a != null && typeof a.equals === 'function';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function testEquatable(a, b) {
|
|
8
|
+
const isAEquatable = isEquatable(a);
|
|
9
|
+
const isBEquatable = isEquatable(b);
|
|
10
|
+
if (isAEquatable && isBEquatable) {
|
|
11
|
+
return a.equals(b);
|
|
12
|
+
} else if (isAEquatable === isBEquatable) {
|
|
13
|
+
return undefined;
|
|
14
|
+
} else {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
expect.addEqualityTesters([testEquatable]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './equality-testers.js';
|
package/lib/web-socket-client.js
CHANGED
|
@@ -4,6 +4,10 @@ import { MessageClient } from './message-client.js';
|
|
|
4
4
|
import { OutputTracker } from './output-tracker.js';
|
|
5
5
|
import { Timer, TimerTask } from './util.js';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @ignore @typedef {typeof WebSocket} WebSocketConstructor
|
|
9
|
+
*/
|
|
10
|
+
|
|
7
11
|
export const HEARTBEAT_TYPE = 'heartbeat';
|
|
8
12
|
|
|
9
13
|
const MESSAGE_SENT_EVENT = 'message-sent';
|
|
@@ -61,7 +65,7 @@ export class WebSocketClient extends MessageClient {
|
|
|
61
65
|
constructor(
|
|
62
66
|
/** @type {number} */ heartbeat,
|
|
63
67
|
/** @type {Timer} */ timer,
|
|
64
|
-
/** @type {
|
|
68
|
+
/** @type {WebSocketConstructor} */ webSocketConstructor,
|
|
65
69
|
) {
|
|
66
70
|
super();
|
|
67
71
|
this.#heartbeat = heartbeat;
|
|
@@ -69,14 +73,23 @@ export class WebSocketClient extends MessageClient {
|
|
|
69
73
|
this.#webSocketConstructor = webSocketConstructor;
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @override
|
|
78
|
+
*/
|
|
72
79
|
get isConnected() {
|
|
73
80
|
return this.#webSocket?.readyState === WebSocket.OPEN;
|
|
74
81
|
}
|
|
75
82
|
|
|
83
|
+
/**
|
|
84
|
+
* @override
|
|
85
|
+
*/
|
|
76
86
|
get url() {
|
|
77
87
|
return this.#webSocket?.url;
|
|
78
88
|
}
|
|
79
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @override
|
|
92
|
+
*/
|
|
80
93
|
async connect(/** @type {string | URL} */ url) {
|
|
81
94
|
await new Promise((resolve, reject) => {
|
|
82
95
|
if (this.isConnected) {
|
|
@@ -90,9 +103,8 @@ export class WebSocketClient extends MessageClient {
|
|
|
90
103
|
this.#handleOpen(e);
|
|
91
104
|
resolve();
|
|
92
105
|
});
|
|
93
|
-
this.#webSocket.addEventListener(
|
|
94
|
-
|
|
95
|
-
(e) => this.#handleMessage(e),
|
|
106
|
+
this.#webSocket.addEventListener('message', (e) =>
|
|
107
|
+
this.#handleMessage(e),
|
|
96
108
|
);
|
|
97
109
|
this.#webSocket.addEventListener('close', (e) => this.#handleClose(e));
|
|
98
110
|
this.#webSocket.addEventListener('error', (e) => this.#handleError(e));
|
|
@@ -104,14 +116,15 @@ export class WebSocketClient extends MessageClient {
|
|
|
104
116
|
|
|
105
117
|
/**
|
|
106
118
|
* Sends a message to the server.
|
|
107
|
-
*
|
|
108
119
|
* @param {string} message The message to send.
|
|
120
|
+
* @override
|
|
109
121
|
*/
|
|
110
|
-
send(message) {
|
|
122
|
+
async send(message) {
|
|
111
123
|
this.#webSocket.send(message);
|
|
112
124
|
this.dispatchEvent(
|
|
113
125
|
new CustomEvent(MESSAGE_SENT_EVENT, { detail: message }),
|
|
114
126
|
);
|
|
127
|
+
await Promise.resolve();
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
/**
|
|
@@ -127,9 +140,9 @@ export class WebSocketClient extends MessageClient {
|
|
|
127
140
|
* Closes the connection.
|
|
128
141
|
*
|
|
129
142
|
* If a code is provided, also a reason should be provided.
|
|
130
|
-
*
|
|
131
|
-
* @param {
|
|
132
|
-
* @
|
|
143
|
+
* @param {number} [code] An optional code.
|
|
144
|
+
* @param {string} [reason] An optional reason.
|
|
145
|
+
* @override
|
|
133
146
|
*/
|
|
134
147
|
async close(code, reason) {
|
|
135
148
|
await new Promise((resolve) => {
|
|
@@ -219,12 +232,16 @@ class HeartbeatTask extends TimerTask {
|
|
|
219
232
|
this.#client = client;
|
|
220
233
|
}
|
|
221
234
|
|
|
235
|
+
/**
|
|
236
|
+
* @override
|
|
237
|
+
*/
|
|
222
238
|
run() {
|
|
223
239
|
this.#client.send(HEARTBEAT_TYPE);
|
|
224
240
|
}
|
|
225
241
|
}
|
|
226
242
|
|
|
227
243
|
class WebSocketStub extends EventTarget {
|
|
244
|
+
/** @type {number} */
|
|
228
245
|
readyState = WebSocket.CONNECTING;
|
|
229
246
|
|
|
230
247
|
constructor(url) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muspellheim/shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"author": "Falko Schumann",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -14,19 +14,25 @@
|
|
|
14
14
|
"./browser": "./lib/browser/index.js",
|
|
15
15
|
"./browser/*.js": "./lib/browser/*.js",
|
|
16
16
|
"./node": "./lib/node/index.js",
|
|
17
|
-
"./node/*.js": "./lib/node/*.js"
|
|
17
|
+
"./node/*.js": "./lib/node/*.js",
|
|
18
|
+
"./vitest": "./lib/vitest/index.js",
|
|
19
|
+
"./vitest/*.js": "./lib/vitest/*.js"
|
|
18
20
|
},
|
|
19
21
|
"scripts": {
|
|
20
22
|
"test": "vitest"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
23
|
-
"express": "^4.
|
|
24
|
-
"lit-html": "^3.
|
|
25
|
+
"express": "^4.21.2",
|
|
26
|
+
"lit-html": "^3.1.0"
|
|
25
27
|
},
|
|
26
28
|
"devDependencies": {
|
|
27
|
-
"@
|
|
29
|
+
"@eslint/js": "9.17.0",
|
|
30
|
+
"@vitest/coverage-v8": "2.1.8",
|
|
31
|
+
"eslint": "9.17.0",
|
|
32
|
+
"globals": "15.13.0",
|
|
28
33
|
"jsdoc": "4.0.4",
|
|
29
34
|
"jsdom": "25.0.1",
|
|
30
|
-
"
|
|
35
|
+
"prettier": "3.4.2",
|
|
36
|
+
"vitest": "2.1.8"
|
|
31
37
|
}
|
|
32
38
|
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
|
+
"lib": ["es2022", "dom"],
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"checkJs": false,
|
|
9
|
+
"noImplicitAny": false,
|
|
10
|
+
"strictNullChecks": false
|
|
11
|
+
},
|
|
12
|
+
"exclude": ["coverage", "dist", "node_modules"]
|
|
13
|
+
}
|