@muspellheim/shared 0.5.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.
@@ -0,0 +1,3 @@
1
+ coverage/
2
+ docs/
3
+ *-corrupt.*
package/.prettierrc ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "proseWrap": "always",
3
+ "singleQuote": true,
4
+ "trailingComma": "all"
5
+ }
package/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # Shared [![Build](https://github.com/falkoschumann/muspellheim-utils-javascript/actions/workflows/pipeline.yml/badge.svg)](https://github.com/falkoschumann/muspellheim-utils-javascript/actions/workflows/pipeline.yml)
1
+ # Shared
2
+
3
+ [![Build](https://github.com/falkoschumann/muspellheim-utils-javascript/actions/workflows/build.yml/badge.svg)](https://github.com/falkoschumann/muspellheim-utils-javascript/actions/workflows/build.yml)
4
+ [![NPM Version](https://img.shields.io/npm/v/%40muspellheim%2Fshared)](https://www.npmjs.com/package/@muspellheim/shared)
2
5
 
3
6
  Some shared modules for the browser and Node from my projects.
4
7
 
@@ -9,8 +12,8 @@ Some shared modules for the browser and Node from my projects.
9
12
  ## Usage
10
13
 
11
14
  import { XXX } from '@muspellheim/shared';
12
- import { XXX } from '@muspellheim/shared/browser/index.js';
13
- import { XXX } from '@muspellheim/shared/browser/node.js';
15
+ import { XXX } from '@muspellheim/shared/browser';
16
+ import { XXX } from '@muspellheim/shared/node';
14
17
 
15
18
  See https://falkoschumann.github.io/muspellheim-shared-javascript/
16
19
 
@@ -25,5 +28,7 @@ The `Makefile` runs the build as default task. Other tasks are
25
28
 
26
29
  ## Open issues
27
30
 
28
- - [ ] Add missing docs.
29
- - [ ] Use asserts for parameters.
31
+ - [ ] Add missing docs
32
+ - [ ] Use asserts for parameters
33
+ - [ ] Rename project to `muspellheim-shared-js`
34
+ - [ ] Use equatable protocol for value objects
package/deno.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "checkJs": false
3
+ "lib": ["es2022", "dom"],
4
+ "checkJs": true,
5
+ "noImplicitAny": false,
6
+ "strictNullChecks": false
4
7
  },
5
8
  "fmt": {
6
9
  "singleQuote": true,
package/deno.mk ADDED
@@ -0,0 +1,68 @@
1
+ # Possible values: major, minor, patch or concrete version
2
+ VERSION = minor
3
+
4
+ all: dist docs check
5
+
6
+ clean:
7
+ rm -rf coverage docs
8
+
9
+ distclean: clean
10
+ rm -rf dist
11
+ rm -rf node_modules
12
+
13
+ dist: build
14
+
15
+ release: all
16
+ npm version $(VERSION) -m "chore: create release v%s"
17
+ git push
18
+ git push --tags
19
+
20
+ publish: all
21
+ deno publish --dry-run
22
+
23
+ docs:
24
+ # FIXME deno doc --html --name="Muspellheim Shared" --lint lib
25
+ # FIXME deno doc --html --name="Muspellheim Shared" lib
26
+
27
+ check: test
28
+ deno run --allow-all npm:eslint lib test
29
+ deno run --allow-all npm:prettier . --check
30
+ # TODO deno fmt --check
31
+ deno lint
32
+
33
+ format:
34
+ deno run --allow-all npm:eslint --fix lib test
35
+ deno run --allow-all npm:prettier . --write
36
+ # TODO deno fmt
37
+ deno lint --fix
38
+
39
+ dev: build
40
+ deno run --allow-all npm:vitest
41
+
42
+ test: build
43
+ deno run --allow-all npm:vitest run
44
+
45
+ unit-tests: build
46
+ deno run --allow-all npm:vitest run --testPathPattern=".*\/unit\/.*"
47
+
48
+ integration-tests: build
49
+ deno run --allow-all npm:vitest run --testPathPattern=".*\/integration\/.*"
50
+
51
+ e2e-tests: build
52
+ deno run --allow-all npm:vitest run --testPathPattern=".*\/e2e\/.*"
53
+
54
+ coverage: build
55
+ deno run --allow-all npm:vitest run --coverage
56
+
57
+ build: prepare
58
+
59
+ prepare: version
60
+ deno install
61
+
62
+ version:
63
+ @echo "Use Deno $(shell deno --version)"
64
+
65
+ .PHONY: all clean distclean dist release publish docs \
66
+ check format \
67
+ dev test unit-tests integration-tests e2e-tests coverage \
68
+ build prepare version
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js';
2
+ import globals from 'globals';
3
+
4
+ /** @type { import("eslint").Linter.FlatConfig[] } */
5
+ export default [
6
+ js.configs.recommended,
7
+ {
8
+ languageOptions: {
9
+ ecmaVersion: 2022,
10
+ globals: {
11
+ ...globals.es2021,
12
+ ...globals.node,
13
+ ...globals.browser,
14
+ //...globals.serviceworker,
15
+ //...globals['shared-node-browser'],
16
+ },
17
+ },
18
+ ignores: ['coverage/**', 'docs/**'],
19
+ rules: {
20
+ 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
21
+ },
22
+ },
23
+ ];
package/lib/assert.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  export function assertNotNull(object, message) {
11
11
  if (object == null) {
12
- message = typeof message === 'function' ? message() : message;
13
- throw new ReferenceError(message);
12
+ const m = typeof message === 'function' ? message() : message;
13
+ throw new ReferenceError(m);
14
14
  }
15
15
  }
@@ -1,5 +1,3 @@
1
1
  // Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
2
2
 
3
- export * from '../index.js';
4
-
5
3
  export * from './components.js';
package/lib/color.js CHANGED
@@ -6,37 +6,35 @@ const FACTOR = 0.7;
6
6
  * The Color class represents a color in the RGB color space.
7
7
  */
8
8
  export class Color {
9
- #value;
9
+ /**
10
+ * The RGB value of the color.
11
+ *
12
+ * @type {number}
13
+ */
14
+ rgb;
10
15
 
11
16
  /**
12
17
  * Creates a color instance from RGB values.
13
18
  *
14
- * @param {number} red The red component or the RGB value.
19
+ * @param {number|string} [red] The red component or the RGB value.
15
20
  * @param {number} [green] The green component.
16
21
  * @param {number} [blue] The blue component.
17
22
  */
18
23
  constructor(red, green, blue) {
19
- if (green === undefined && blue === undefined) {
20
- if (typeof red === 'string') {
21
- this.#value = parseInt(red, 16);
22
- return;
23
- }
24
-
25
- this.#value = Number(red);
26
- return;
24
+ if (typeof red === 'string') {
25
+ this.rgb = parseInt(red, 16);
26
+ } else if (
27
+ typeof red === 'number' &&
28
+ typeof green === 'number' &&
29
+ typeof blue === 'number'
30
+ ) {
31
+ this.rgb =
32
+ ((red & 0xff) << 16) | ((green & 0xff) << 8) | ((blue & 0xff) << 0);
33
+ } else if (typeof red === 'number') {
34
+ this.rgb = red;
35
+ } else {
36
+ this.rgb = NaN;
27
37
  }
28
-
29
- this.#value = ((red & 0xff) << 16) | ((green & 0xff) << 8) |
30
- ((blue & 0xff) << 0);
31
- }
32
-
33
- /**
34
- * The RGB value of the color.
35
- *
36
- * @type {number}
37
- */
38
- get rgb() {
39
- return this.#value;
40
38
  }
41
39
 
42
40
  /**
package/lib/health.js CHANGED
@@ -161,10 +161,7 @@ export class Health {
161
161
  * @param {Status} status The status of the health.
162
162
  * @param {Record<string, *>} details The details of the health.
163
163
  */
164
- constructor(
165
- /** @type {Status} */ status,
166
- /** @type {?Record<string, *>} */ details,
167
- ) {
164
+ constructor(status, details) {
168
165
  assertNotNull(status, 'Status must not be null.');
169
166
  // TODO assertNotNull(details, 'Details must not be null.');
170
167
 
@@ -502,9 +499,8 @@ export class HealthEndpoint {
502
499
 
503
500
  let health;
504
501
  if (statuses.length > 0) {
505
- const status = this.#groups.primary.statusAggregator.getAggregateStatus(
506
- statuses,
507
- );
502
+ const status =
503
+ this.#groups.primary.statusAggregator.getAggregateStatus(statuses);
508
504
  health = new CompositeHealth(status, components);
509
505
  } else {
510
506
  health = Health.up();
package/lib/index.js CHANGED
@@ -3,12 +3,13 @@
3
3
  export * from './assert.js';
4
4
  export * from './color.js';
5
5
  export * from './configurable-responses.js';
6
- export * from './lang.js';
7
6
  export * from './feature-toggle.js';
8
7
  export * from './health.js';
8
+ export * from './lang.js';
9
9
  export * from './logging.js';
10
10
  export * from './long-polling-client.js';
11
11
  export * from './message-client.js';
12
+ export * from './messages.js';
12
13
  export * from './metrics.js';
13
14
  export * from './output-tracker.js';
14
15
  export * from './service-locator.js';
package/lib/lang.js CHANGED
@@ -22,14 +22,12 @@ import { ensureArguments } from './validation.js';
22
22
  * static NO = new YesNo('NO', 1);
23
23
  * }
24
24
  * ```
25
- *
26
- * @template [T=Enum] - the type of the enum object
27
25
  */
28
26
  export class Enum {
29
27
  /**
30
28
  * Returns all enum constants.
31
29
  *
32
- * @return {T[]} All enum constants.
30
+ * @return {Enum[]} All enum constants.
33
31
  */
34
32
  static values() {
35
33
  return Object.values(this);
@@ -39,7 +37,7 @@ export class Enum {
39
37
  * Returns an enum constant by its name.
40
38
  *
41
39
  * @param {string} name The name of the enum constant.
42
- * @return {T} The enum constant.
40
+ * @return {Enum} The enum constant.
43
41
  */
44
42
  static valueOf(name) {
45
43
  const value = this.values().find((v) => v.name === name);
@@ -53,8 +51,8 @@ export class Enum {
53
51
  /**
54
52
  * Creates an enum object.
55
53
  *
56
- * @param {number} ordinal The ordinal of the enum constant.
57
54
  * @param {string} name The name of the enum constant.
55
+ * @param {number} ordinal The ordinal of the enum constant.
58
56
  */
59
57
  constructor(name, ordinal) {
60
58
  ensureArguments(arguments, [String, Number]);
package/lib/logging.js CHANGED
@@ -179,7 +179,7 @@ export class Logger extends EventTarget {
179
179
  *
180
180
  * The root logger has not a parent.
181
181
  *
182
- * @type {?Logger}
182
+ * @type {Logger=}
183
183
  */
184
184
  parent;
185
185
 
@@ -202,8 +202,7 @@ export class Logger extends EventTarget {
202
202
  /**
203
203
  * Initializes a new logger with the given name.
204
204
  *
205
- * @param {string} name The name of the logger.
206
- * @private
205
+ * @param {?string} name The name of the logger.
207
206
  */
208
207
  constructor(name) {
209
208
  super();
@@ -213,7 +212,7 @@ export class Logger extends EventTarget {
213
212
  /**
214
213
  * The name of the logger.
215
214
  *
216
- * @type {string}
215
+ * @type {?string}
217
216
  */
218
217
  get name() {
219
218
  return this.#name;
@@ -307,7 +306,7 @@ export class Logger extends EventTarget {
307
306
  isLoggable(level) {
308
307
  return this.level != null
309
308
  ? level >= this.level
310
- : this.parent.isLoggable(level);
309
+ : (this.parent?.isLoggable(level) ?? false);
311
310
  }
312
311
 
313
312
  /**
@@ -376,7 +375,7 @@ export class LogRecord {
376
375
  /**
377
376
  * The name of the logger.
378
377
  *
379
- * @type {string|undefined}
378
+ * @type {string|null}
380
379
  */
381
380
  loggerName;
382
381
 
@@ -428,7 +427,7 @@ export class Handler {
428
427
  * @param {LogRecord} record The log record to publish.
429
428
  * @abstract
430
429
  */
431
- async publish() {
430
+ async publish(_record) {
432
431
  await Promise.reject('Not implemented');
433
432
  }
434
433
 
@@ -489,7 +488,7 @@ export class Formatter {
489
488
  * @return {string} The formatted log record.
490
489
  * @abstract
491
490
  */
492
- format() {
491
+ format(_record) {
493
492
  throw new Error('Not implemented');
494
493
  }
495
494
  }
@@ -507,7 +506,8 @@ export class SimpleFormatter extends Formatter {
507
506
  s += ' [' + record.loggerName + ']';
508
507
  }
509
508
  s += ' ' + record.level.toString();
510
- s += ' - ' +
509
+ s +=
510
+ ' - ' +
511
511
  record.message
512
512
  .map((m) => (typeof m === 'object' ? JSON.stringify(m) : m))
513
513
  .join(' ');
@@ -572,7 +572,8 @@ class LogManager {
572
572
  }
573
573
 
574
574
  addLogger(/** @type {Logger} */ logger) {
575
- this.#namedLoggers.set(logger.name, logger);
575
+ const loggerName = logger.name;
576
+ this.#namedLoggers.set(loggerName, logger);
576
577
  }
577
578
 
578
579
  getLogger(/** @type {string} */ name) {
@@ -35,16 +35,14 @@ export class LongPollingClient extends MessageClient {
35
35
  * @param {object} options
36
36
  * @return {LongPollingClient} A new nulled long polling client.
37
37
  */
38
- static createNull(
39
- {
40
- fetchResponse = {
41
- status: 304,
42
- statusText: 'Not Modified',
43
- headers: undefined,
44
- body: null,
45
- },
46
- } = {},
47
- ) {
38
+ static createNull({
39
+ fetchResponse = {
40
+ status: 304,
41
+ statusText: 'Not Modified',
42
+ headers: undefined,
43
+ body: null,
44
+ },
45
+ } = {}) {
48
46
  return new LongPollingClient(90000, 0, createFetchStub(fetchResponse));
49
47
  }
50
48
 
@@ -75,14 +73,23 @@ export class LongPollingClient extends MessageClient {
75
73
  this.#aboutController = new AbortController();
76
74
  }
77
75
 
76
+ /**
77
+ * @override
78
+ */
78
79
  get isConnected() {
79
80
  return this.#connected;
80
81
  }
81
82
 
83
+ /**
84
+ * @override
85
+ */
82
86
  get url() {
83
87
  return this.#url;
84
88
  }
85
89
 
90
+ /**
91
+ * @override
92
+ */
86
93
  async connect(url) {
87
94
  if (this.isConnected) {
88
95
  throw new Error('Already connected.');
@@ -103,6 +110,9 @@ export class LongPollingClient extends MessageClient {
103
110
  return OutputTracker.create(this, REQUEST_SENT_EVENT);
104
111
  }
105
112
 
113
+ /**
114
+ * @override
115
+ */
106
116
  async close() {
107
117
  this.#aboutController.abort();
108
118
  this.#connected = false;
@@ -126,6 +136,7 @@ export class LongPollingClient extends MessageClient {
126
136
  });
127
137
  await this.#handleResponse(response);
128
138
  } catch (error) {
139
+ // @ts-ignore NodeJS error code
129
140
  if (error.name === 'AbortError') {
130
141
  break;
131
142
  } else {
@@ -1,11 +1,5 @@
1
1
  // Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
2
2
 
3
- /**
4
- * @import { LongPollingClient } from './long-polling-client.js'
5
- * @import { SseClient } from './sse-client.js'
6
- * @import { WebSocketClient } from './web-socket-client.js'
7
- */
8
-
9
3
  /**
10
4
  * An interface for a streaming message client.
11
5
  *
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Provides CQNS features.
3
+ *
4
+ * The Command Query Notification Separation principle is a software design
5
+ * principle that separates the concerns of commands, queries, and
6
+ * notifications.
7
+ *
8
+ * Message hierarchy:
9
+ *
10
+ * - Message
11
+ * - Incoming / outgoing
12
+ * - Request (outgoing) -> response (incoming)
13
+ * - Command -> command status
14
+ * - Query -> query result
15
+ * - Notification
16
+ * - Incoming: notification -> commands
17
+ * - Outgoing
18
+ * - Event (internal)
19
+ *
20
+ * @see https://ralfw.de/command-query-notification-separation-cqns/
21
+ * @module
22
+ */
23
+
24
+ /**
25
+ * The status returned by a command handler.
26
+ */
27
+ export class CommandStatus {
28
+ /**
29
+ * Creates a successful status.
30
+ */
31
+ static success() {
32
+ return new CommandStatus(true);
33
+ }
34
+
35
+ /**
36
+ * Creates a failed status.
37
+ *
38
+ * @param {string} errorMessage
39
+ */
40
+ static failure(errorMessage) {
41
+ return new CommandStatus(false, errorMessage);
42
+ }
43
+
44
+ /**
45
+ * Indicates whether the command was successful.
46
+ *
47
+ * @type {boolean}
48
+ */
49
+ isSuccess;
50
+
51
+ /**
52
+ * The error message if the command failed.
53
+ *
54
+ * @type {string}
55
+ */
56
+ errorMessage;
57
+
58
+ /**
59
+ * Creates a new instance of CommandStatus.
60
+ *
61
+ * @param {boolean} isSuccess Indicates whether the command was successful.
62
+ * @param {string} [errorMessage] The error message if the command failed.
63
+ */
64
+ constructor(isSuccess, errorMessage) {
65
+ this.isSuccess = isSuccess;
66
+ this.errorMessage = errorMessage;
67
+ }
68
+ }
package/lib/metrics.js CHANGED
@@ -30,7 +30,7 @@ export class MeterRegistry {
30
30
  counter(name, tags) {
31
31
  const id = MeterId.create({ name, tags, type: MeterType.COUNTER });
32
32
  /** @type {Counter} */ let meter = this.#meters.find((meter) =>
33
- meter.id.equals(id)
33
+ meter.id.equals(id),
34
34
  );
35
35
  if (!meter) {
36
36
  meter = new Counter(id);
@@ -10,6 +10,8 @@ import process from 'node:process';
10
10
 
11
11
  import * as handler from './handler.js';
12
12
 
13
+ // TODO Remove dependency to express
14
+
13
15
  export class ActuatorController {
14
16
  #services;
15
17
  #healthContributorRegistry;
@@ -36,8 +38,8 @@ export class ActuatorController {
36
38
  /** @type {express.Request} */ request,
37
39
  /** @type {express.Response} */ response,
38
40
  ) {
39
- let requestedUrl = request.protocol + '://' + request.get('host') +
40
- request.originalUrl;
41
+ let requestedUrl =
42
+ request.protocol + '://' + request.get('host') + request.originalUrl;
41
43
  if (!requestedUrl.endsWith('/')) {
42
44
  requestedUrl += '/';
43
45
  }
@@ -92,12 +94,9 @@ export class ActuatorController {
92
94
 
93
95
  const metrics = await this.#services.getMetrics();
94
96
  const timestamp = new Date().getTime();
95
- let body =
96
- `# TYPE talks_count gauge\ntalks_count ${metrics.talksCount} ${timestamp}\n\n`;
97
- body +=
98
- `# TYPE presenters_count gauge\npresenters_count ${metrics.presentersCount} ${timestamp}\n\n`;
99
- body +=
100
- `# TYPE comments_count gauge\ncomments_count ${metrics.commentsCount} ${timestamp}\n\n`;
97
+ let body = `# TYPE talks_count gauge\ntalks_count ${metrics.talksCount} ${timestamp}\n\n`;
98
+ body += `# TYPE presenters_count gauge\npresenters_count ${metrics.presentersCount} ${timestamp}\n\n`;
99
+ body += `# TYPE comments_count gauge\ncomments_count ${metrics.commentsCount} ${timestamp}\n\n`;
101
100
  handler.reply(response, { body });
102
101
  }
103
102
  }