@muspellheim/shared 0.4.0 → 0.6.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/lib/util.js CHANGED
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Contains several miscellaneous utility classes.
5
5
  *
6
- * Portated from
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
- export function deepMerge(source, target) {
19
- if (target === undefined) {
20
- return source;
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
- if (typeof target !== 'object' || target === null) {
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 (Array.isArray(source) && Array.isArray(target)) {
28
- for (const item of target) {
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
- source.push(element);
45
+ target.push(element);
31
46
  }
32
- return source;
47
+ return target;
33
48
  }
34
49
 
35
- for (const key in target) {
36
- if (typeof source !== 'object' || source === null) {
37
- source = {};
50
+ for (const key in source) {
51
+ if (typeof target !== 'object' || target === null) {
52
+ target = {};
38
53
  }
39
54
 
40
- source[key] = deepMerge(source[key], target[key]);
55
+ target[key] = deepMerge(target[key], source[key]);
41
56
  }
42
57
 
43
- return source;
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(clock));
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
- /** @type {TimerTask} */ const task = this._queue[0];
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 = task._period < 0
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
- describe(expectedType, {
126
- articles: true,
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
- describe(expectedType, {
140
- articles: true,
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
- describe(
156
- expectedType,
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, { articles: true })}`;
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
- describe(expectedType, {
182
- articles: true,
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: number, scalar: number}} The `foot` and the `scalar`.
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';
@@ -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 {function(new:WebSocket)} */ webSocketConstructor,
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
- 'message',
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 {number} code An optional code.
132
- * @param {string} reason An optional reason.
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.4.0",
3
+ "version": "0.6.0",
4
4
  "author": "Falko Schumann",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -8,21 +8,31 @@
8
8
  "node": ">=18.7.0"
9
9
  },
10
10
  "type": "module",
11
- "main": "dist/index.cjs",
12
- "browser": "lib/browser/index.js",
11
+ "exports": {
12
+ ".": "./lib/index.js",
13
+ "./*.js": "./lib/*.js",
14
+ "./browser": "./lib/browser/index.js",
15
+ "./browser/*.js": "./lib/browser/*.js",
16
+ "./node": "./lib/node/index.js",
17
+ "./node/*.js": "./lib/node/*.js",
18
+ "./vitest": "./lib/vitest/index.js",
19
+ "./vitest/*.js": "./lib/vitest/*.js"
20
+ },
13
21
  "scripts": {
14
- "build": "rollup -c",
15
22
  "test": "vitest"
16
23
  },
17
24
  "dependencies": {
18
- "express": "^4.20.0",
19
- "lit-html": "^3.2.0"
25
+ "express": "^4.21.2",
26
+ "lit-html": "^3.1.0"
20
27
  },
21
28
  "devDependencies": {
22
- "@vitest/coverage-v8": "2.1.4",
29
+ "@eslint/js": "9.17.0",
30
+ "@vitest/coverage-v8": "2.1.8",
31
+ "eslint": "9.17.0",
32
+ "globals": "15.13.0",
23
33
  "jsdoc": "4.0.4",
24
34
  "jsdom": "25.0.1",
25
- "rollup": "4.24.3",
26
- "vitest": "2.1.4"
35
+ "prettier": "3.4.2",
36
+ "vitest": "2.1.8"
27
37
  }
28
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
+ }