@muspellheim/shared 0.6.1 → 0.8.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.
Files changed (46) hide show
  1. package/LICENSE.txt +1 -1
  2. package/README.md +11 -13
  3. package/dist/shared.d.ts +423 -0
  4. package/dist/shared.js +535 -0
  5. package/dist/shared.umd.cjs +1 -0
  6. package/package.json +27 -23
  7. package/.prettierignore +0 -3
  8. package/.prettierrc +0 -5
  9. package/deno.json +0 -15
  10. package/deno.mk +0 -68
  11. package/eslint.config.js +0 -23
  12. package/lib/assert.js +0 -15
  13. package/lib/browser/components.js +0 -165
  14. package/lib/browser/index.js +0 -3
  15. package/lib/color.js +0 -137
  16. package/lib/configurable-responses.js +0 -69
  17. package/lib/feature-toggle.js +0 -9
  18. package/lib/health.js +0 -510
  19. package/lib/index.js +0 -23
  20. package/lib/lang.js +0 -100
  21. package/lib/logging.js +0 -599
  22. package/lib/long-polling-client.js +0 -186
  23. package/lib/message-client.js +0 -68
  24. package/lib/messages.js +0 -68
  25. package/lib/metrics.js +0 -120
  26. package/lib/node/actuator-controller.js +0 -102
  27. package/lib/node/configuration-properties.js +0 -291
  28. package/lib/node/handler.js +0 -25
  29. package/lib/node/index.js +0 -9
  30. package/lib/node/logging.js +0 -60
  31. package/lib/node/long-polling.js +0 -83
  32. package/lib/node/sse-emitter.js +0 -104
  33. package/lib/node/static-files-controller.js +0 -15
  34. package/lib/output-tracker.js +0 -89
  35. package/lib/service-locator.js +0 -44
  36. package/lib/sse-client.js +0 -163
  37. package/lib/stop-watch.js +0 -54
  38. package/lib/store.js +0 -129
  39. package/lib/time.js +0 -445
  40. package/lib/util.js +0 -380
  41. package/lib/validation.js +0 -290
  42. package/lib/vector.js +0 -194
  43. package/lib/vitest/equality-testers.js +0 -19
  44. package/lib/vitest/index.js +0 -1
  45. package/lib/web-socket-client.js +0 -262
  46. package/tsconfig.json +0 -13
@@ -1,186 +0,0 @@
1
- // Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
2
-
3
- import { ConfigurableResponses } from './configurable-responses.js';
4
- import { MessageClient } from './message-client.js';
5
- import { OutputTracker } from './output-tracker.js';
6
- import { sleep } from './lang.js';
7
-
8
- const REQUEST_SENT_EVENT = 'request-sent';
9
-
10
- /**
11
- * A client handling long polling a HTTP request.
12
- *
13
- * @implements {MessageClient}
14
- */
15
- export class LongPollingClient extends MessageClient {
16
- /**
17
- * Creates a long polling client.
18
- *
19
- * @param {object} options
20
- * @param {number} [options.wait=90000] The wait interval for a response.
21
- * @param {number} [options.retry=1000] The retry interval after an error.
22
- * @return {LongPollingClient} A new long polling client.
23
- */
24
- static create({ wait = 90000, retry = 1000 } = {}) {
25
- return new LongPollingClient(
26
- wait,
27
- retry,
28
- globalThis.fetch.bind(globalThis),
29
- );
30
- }
31
-
32
- /**
33
- * Creates a nulled long polling client.
34
- *
35
- * @param {object} options
36
- * @return {LongPollingClient} A new nulled long polling client.
37
- */
38
- static createNull({
39
- fetchResponse = {
40
- status: 304,
41
- statusText: 'Not Modified',
42
- headers: undefined,
43
- body: null,
44
- },
45
- } = {}) {
46
- return new LongPollingClient(90000, 0, createFetchStub(fetchResponse));
47
- }
48
-
49
- #wait;
50
- #retry;
51
- #fetch;
52
- #connected;
53
- #aboutController;
54
- #url;
55
- #tag;
56
-
57
- /**
58
- * The constructor is for internal use. Use the factory methods instead.
59
- *
60
- * @see LongPollingClient.create
61
- * @see LongPollingClient.createNull
62
- */
63
- constructor(
64
- /** @type {number} */ wait,
65
- /** @type {number} */ retry,
66
- /** @type {fetch} */ fetchFunc,
67
- ) {
68
- super();
69
- this.#wait = wait;
70
- this.#retry = retry;
71
- this.#fetch = fetchFunc;
72
- this.#connected = false;
73
- this.#aboutController = new AbortController();
74
- }
75
-
76
- /**
77
- * @override
78
- */
79
- get isConnected() {
80
- return this.#connected;
81
- }
82
-
83
- /**
84
- * @override
85
- */
86
- get url() {
87
- return this.#url;
88
- }
89
-
90
- /**
91
- * @override
92
- */
93
- async connect(url) {
94
- if (this.isConnected) {
95
- throw new Error('Already connected.');
96
- }
97
-
98
- this.#url = url;
99
- this.#startPolling();
100
- this.dispatchEvent(new Event('open'));
101
- await Promise.resolve();
102
- }
103
-
104
- /**
105
- * Returns a tracker for requests sent.
106
- *
107
- * @return {OutputTracker} A new output tracker.
108
- */
109
- trackRequestSent() {
110
- return OutputTracker.create(this, REQUEST_SENT_EVENT);
111
- }
112
-
113
- /**
114
- * @override
115
- */
116
- async close() {
117
- this.#aboutController.abort();
118
- this.#connected = false;
119
- await Promise.resolve();
120
- }
121
-
122
- async #startPolling() {
123
- this.#connected = true;
124
- while (this.isConnected) {
125
- try {
126
- const headers = { Prefer: `wait=${this.#wait / 1000}` };
127
- if (this.#tag) {
128
- headers['If-None-Match'] = this.#tag;
129
- }
130
- this.dispatchEvent(
131
- new CustomEvent(REQUEST_SENT_EVENT, { detail: { headers } }),
132
- );
133
- const response = await this.#fetch(this.#url, {
134
- headers,
135
- signal: this.#aboutController.signal,
136
- });
137
- await this.#handleResponse(response);
138
- } catch (error) {
139
- // @ts-ignore NodeJS error code
140
- if (error.name === 'AbortError') {
141
- break;
142
- } else {
143
- await this.#handleError(error);
144
- }
145
- }
146
- }
147
- }
148
-
149
- async #handleResponse(/** @type {Response} */ response) {
150
- if (response.status === 304) {
151
- return;
152
- }
153
-
154
- if (!response.ok) {
155
- throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
156
- }
157
-
158
- this.#tag = response.headers.get('ETag');
159
- const message = await response.text();
160
- this.dispatchEvent(new MessageEvent('message', { data: message }));
161
- }
162
-
163
- async #handleError(error) {
164
- console.error(error);
165
- this.dispatchEvent(new Event('error'));
166
- await sleep(this.#retry);
167
- }
168
- }
169
-
170
- function createFetchStub(response) {
171
- const responses = ConfigurableResponses.create(response);
172
- return async (_url, options) => {
173
- await sleep(0);
174
- return new Promise((resolve, reject) => {
175
- options?.signal?.addEventListener('abort', () => reject());
176
- const res = responses.next();
177
- resolve(
178
- new Response(res.body, {
179
- status: res.status,
180
- statusText: res.statusText,
181
- headers: res.headers,
182
- }),
183
- );
184
- });
185
- };
186
- }
@@ -1,68 +0,0 @@
1
- // Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
2
-
3
- /**
4
- * An interface for a streaming message client.
5
- *
6
- * Emits the following events:
7
- *
8
- * - open, {@link Event}
9
- * - message, {@link MessageEvent}
10
- * - error, {@link Event}
11
- *
12
- * It is used for wrappers around {@link EventSource} and {@link WebSocket},
13
- * also for long polling.
14
- *
15
- * @interface
16
- * @see SseClient
17
- * @see WebSocketClient
18
- * @see LongPollingClient
19
- */
20
- export class MessageClient extends EventTarget {
21
- /**
22
- * Returns whether the client is connected.
23
- *
24
- * @type {boolean}
25
- * @readonly
26
- */
27
- get isConnected() {
28
- throw new Error('Not implemented.');
29
- }
30
-
31
- /**
32
- * Returns the server URL.
33
- *
34
- * @type {string}
35
- * @readonly
36
- */
37
- get url() {
38
- throw new Error('Not implemented.');
39
- }
40
-
41
- /**
42
- * Connects to the server.
43
- *
44
- * @param {URL | string} url The server URL to connect to.
45
- */
46
- async connect(_url) {
47
- await Promise.reject('Not implemented.');
48
- }
49
-
50
- /**
51
- * Sends a message to the server.
52
- *
53
- * This is an optional method for streams with bidirectional communication.
54
- *
55
- * @param {string} message The message to send.
56
- * @param {string} type The optional message type.
57
- */
58
- async send(_message, _type) {
59
- await Promise.reject('Not implemented.');
60
- }
61
-
62
- /**
63
- * Closes the connection.
64
- */
65
- async close() {
66
- await Promise.reject('Not implemented.');
67
- }
68
- }
package/lib/messages.js DELETED
@@ -1,68 +0,0 @@
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 DELETED
@@ -1,120 +0,0 @@
1
- // Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
2
-
3
- /**
4
- * Defines a simple metrics library.
5
- *
6
- * Portated from
7
- * [Micrometer](https://docs.micrometer.io/micrometer/reference/overview.html).
8
- *
9
- * @module
10
- */
11
-
12
- import { Enum } from './lang.js';
13
-
14
- // TODO Create gauges (can increment and decrement)
15
- // TODO Create timers (total time, average time and count)
16
- // TODO Publish metrics to a server via HTTP
17
- // TODO Provide metrics for Prometheus
18
-
19
- export class MeterRegistry {
20
- static create() {
21
- return new MeterRegistry();
22
- }
23
-
24
- #meters = [];
25
-
26
- get meters() {
27
- return this.#meters;
28
- }
29
-
30
- counter(name, tags) {
31
- const id = MeterId.create({ name, tags, type: MeterType.COUNTER });
32
- /** @type {Counter} */ let meter = this.#meters.find((meter) =>
33
- meter.id.equals(id),
34
- );
35
- if (!meter) {
36
- meter = new Counter(id);
37
- this.#meters.push(meter);
38
- }
39
-
40
- // TODO validate found meter is a counter
41
- return meter;
42
- }
43
- }
44
-
45
- export class Meter {
46
- #id;
47
-
48
- constructor(/** @type {MeterId} */ id) {
49
- // TODO validate parameters are not null
50
- this.#id = id;
51
- }
52
-
53
- get id() {
54
- return this.#id;
55
- }
56
- }
57
-
58
- export class Counter extends Meter {
59
- #count = 0;
60
-
61
- constructor(/** @type {MeterId} */ id) {
62
- super(id);
63
- // TODO validate type is counter
64
- }
65
-
66
- count() {
67
- return this.#count;
68
- }
69
-
70
- increment(amount = 1) {
71
- this.#count += amount;
72
- }
73
- }
74
-
75
- export class MeterId {
76
- static create({ name, tags = [], type }) {
77
- return new MeterId(name, tags, type);
78
- }
79
-
80
- #name;
81
- #tags;
82
- #type;
83
-
84
- constructor(
85
- /** @type {string} */ name,
86
- /** @type {string[]} */ tags,
87
- /** @type {MeterType} */ type,
88
- ) {
89
- // TODO validate parameters are not null
90
- this.#name = name;
91
- this.#tags = Array.from(tags).sort();
92
- this.#type = type;
93
- }
94
-
95
- get name() {
96
- return this.#name;
97
- }
98
-
99
- get tags() {
100
- return this.#tags;
101
- }
102
-
103
- get type() {
104
- return this.#type;
105
- }
106
-
107
- equals(other) {
108
- return (
109
- this.name === other.name &&
110
- this.tags.length === other.tags.length &&
111
- this.tags.every((tag, index) => tag === other.tags[index])
112
- );
113
- }
114
- }
115
-
116
- export class MeterType extends Enum {
117
- static COUNTER = new MeterType('COUNTER', 0);
118
- static GAUGE = new MeterType('GAUGE', 1);
119
- static TIMER = new MeterType('TIMER', 2);
120
- }
@@ -1,102 +0,0 @@
1
- // Copyright (c) 2023-2024 Falko Schumann. All rights reserved. MIT license.
2
-
3
- /**
4
- * @import * as express from 'express'
5
- *
6
- * @import { HealthContributorRegistry } from '../health.js'
7
- */
8
-
9
- import process from 'node:process';
10
-
11
- import * as handler from './handler.js';
12
-
13
- // TODO Remove dependency to express
14
-
15
- export class ActuatorController {
16
- #services;
17
- #healthContributorRegistry;
18
-
19
- constructor(
20
- services, // FIXME Services is not defined in library
21
- /** @type {HealthContributorRegistry} */ healthContributorRegistry,
22
- /** @type {express.Express} */ app,
23
- ) {
24
- this.#services = services;
25
- this.#healthContributorRegistry = healthContributorRegistry;
26
-
27
- app.get('/actuator', this.#getActuator.bind(this));
28
- app.get('/actuator/info', this.#getActuatorInfo.bind(this));
29
- app.get('/actuator/metrics', this.#getActuatorMetrics.bind(this));
30
- app.get('/actuator/health', this.#getActuatorHealth.bind(this));
31
- app.get(
32
- '/actuator/prometheus',
33
- handler.runSafe(this.#getMetrics.bind(this)),
34
- );
35
- }
36
-
37
- #getActuator(
38
- /** @type {express.Request} */ request,
39
- /** @type {express.Response} */ response,
40
- ) {
41
- let requestedUrl =
42
- request.protocol + '://' + request.get('host') + request.originalUrl;
43
- if (!requestedUrl.endsWith('/')) {
44
- requestedUrl += '/';
45
- }
46
- response.status(200).json({
47
- _links: {
48
- self: { href: requestedUrl },
49
- info: { href: requestedUrl + 'info' },
50
- metrics: { href: requestedUrl + 'metrics' },
51
- health: { href: requestedUrl + 'health' },
52
- prometheus: { href: requestedUrl + 'prometheus' },
53
- },
54
- });
55
- }
56
-
57
- #getActuatorInfo(
58
- /** @type {express.Request} */ _request,
59
- /** @type {express.Response} */ response,
60
- ) {
61
- const info = {};
62
- info[process.env.npm_package_name] = {
63
- version: process.env.npm_package_version,
64
- };
65
- response.status(200).json(info);
66
- }
67
-
68
- #getActuatorMetrics(
69
- /** @type {express.Request} */ _request,
70
- /** @type {express.Response} */ response,
71
- ) {
72
- response.status(200).json({
73
- cpu: process.cpuUsage(),
74
- mem: process.memoryUsage(),
75
- uptime: process.uptime(),
76
- });
77
- }
78
-
79
- #getActuatorHealth(
80
- /** @type {express.Request} */ _request,
81
- /** @type {express.Response} */ response,
82
- ) {
83
- const health = this.#healthContributorRegistry.health();
84
- const status = health.status === 'UP' ? 200 : 503;
85
- response.status(status).json(health);
86
- }
87
-
88
- async #getMetrics(
89
- /** @type {express.Request} */ _request,
90
- /** @type {express.Response} */ response,
91
- ) {
92
- // TODO count warnings and errors
93
- // TODO create class MeterRegistry
94
-
95
- const metrics = await this.#services.getMetrics();
96
- const timestamp = new Date().getTime();
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`;
100
- handler.reply(response, { body });
101
- }
102
- }