@muspellheim/shared 0.12.5 → 0.13.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/dist/shared.d.ts +3 -3
- package/dist/shared.js +85 -77
- package/dist/shared.js.map +1 -1
- package/dist/shared.umd.cjs +1 -1
- package/dist/shared.umd.cjs.map +1 -1
- package/package.json +11 -11
- package/src/common/event_tracker.ts +16 -6
- package/src/common/mod.ts +0 -2
- package/src/infrastructure/console_stub.ts +1 -1
- package/src/infrastructure/fetch_stub.ts +1 -1
- package/src/infrastructure/mod.ts +2 -0
- package/src/infrastructure/web_socket_client.ts +9 -3
- /package/src/{common → infrastructure}/configurable_responses.ts +0 -0
- /package/src/{common → infrastructure}/output_tracker.ts +0 -0
package/dist/shared.d.ts
CHANGED
|
@@ -171,14 +171,14 @@ export declare class EventTracker<T extends Event> {
|
|
|
171
171
|
* @param eventTarget The target to track.
|
|
172
172
|
* @param event The event name to track.
|
|
173
173
|
*/
|
|
174
|
-
static create<T extends Event>(eventTarget: EventTarget, event: string): EventTracker<T>;
|
|
174
|
+
static create<T extends Event>(eventTarget: EventTarget, ...event: string[]): EventTracker<T>;
|
|
175
175
|
/**
|
|
176
176
|
* Create a tracker for a specific event of an event target.
|
|
177
177
|
*
|
|
178
178
|
* @param eventTarget The target to track.
|
|
179
179
|
* @param event The event name to track.
|
|
180
180
|
*/
|
|
181
|
-
constructor(eventTarget: EventTarget, event: string);
|
|
181
|
+
constructor(eventTarget: EventTarget, event: string[]);
|
|
182
182
|
/**
|
|
183
183
|
* Return the tracked events.
|
|
184
184
|
*
|
|
@@ -441,7 +441,7 @@ export declare class WebSocketClient extends EventTarget implements MessageClien
|
|
|
441
441
|
*
|
|
442
442
|
* @param message The message to receive.
|
|
443
443
|
*/
|
|
444
|
-
simulateMessage(message: string | number | boolean | object | null): void;
|
|
444
|
+
simulateMessage(message: string | number | boolean | object | null | Blob | ArrayBuffer): void;
|
|
445
445
|
/**
|
|
446
446
|
* Simulate a heartbeat.
|
|
447
447
|
*/
|
package/dist/shared.js
CHANGED
|
@@ -47,59 +47,6 @@ class c {
|
|
|
47
47
|
return this.date().getTime();
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
class u {
|
|
51
|
-
/**
|
|
52
|
-
* Create a list of responses (by providing an array), or a single repeating
|
|
53
|
-
* response (by providing any other type). 'Name' is optional and used in
|
|
54
|
-
* error messages.
|
|
55
|
-
*
|
|
56
|
-
* @param responses A single response or an array of responses.
|
|
57
|
-
* @param name An optional name for the responses.
|
|
58
|
-
*/
|
|
59
|
-
static create(t, e) {
|
|
60
|
-
return new u(t, e);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Convert all properties in an object into ConfigurableResponse instances.
|
|
64
|
-
* For example, { a: 1 } becomes { a: ConfigurableResponses.create(1) }.
|
|
65
|
-
* 'Name' is optional and used in error messages.
|
|
66
|
-
*
|
|
67
|
-
* @param responseObject An object with single response or an array of responses.
|
|
68
|
-
* @param name An optional name for the responses.
|
|
69
|
-
*/
|
|
70
|
-
static mapObject(t, e) {
|
|
71
|
-
const r = Object.entries(t).map(([h, n]) => {
|
|
72
|
-
const l = e === void 0 ? void 0 : `${e}: ${h}`;
|
|
73
|
-
return [h, u.create(n, l)];
|
|
74
|
-
});
|
|
75
|
-
return Object.fromEntries(r);
|
|
76
|
-
}
|
|
77
|
-
#e;
|
|
78
|
-
#t;
|
|
79
|
-
/**
|
|
80
|
-
* Create a list of responses (by providing an array), or a single repeating
|
|
81
|
-
* response (by providing any other type). 'Name' is optional and used in
|
|
82
|
-
* error messages.
|
|
83
|
-
*
|
|
84
|
-
* @param responses A single response or an array of responses.
|
|
85
|
-
* @param name An optional name for the responses.
|
|
86
|
-
*/
|
|
87
|
-
constructor(t, e) {
|
|
88
|
-
this.#e = e == null ? "" : ` in ${e}`, this.#t = Array.isArray(t) ? [...t] : t;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Get the next configured response. Throws an error when configured with a list
|
|
92
|
-
* of responses and no more responses remain.
|
|
93
|
-
*
|
|
94
|
-
* @return The next response.
|
|
95
|
-
*/
|
|
96
|
-
next() {
|
|
97
|
-
const t = Array.isArray(this.#t) ? this.#t.shift() : this.#t;
|
|
98
|
-
if (t === void 0)
|
|
99
|
-
throw new Error(`No more responses configured${this.#e}.`);
|
|
100
|
-
return t;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
50
|
class p {
|
|
104
51
|
/**
|
|
105
52
|
* Create a tracker for a specific event of an event target.
|
|
@@ -107,7 +54,7 @@ class p {
|
|
|
107
54
|
* @param eventTarget The target to track.
|
|
108
55
|
* @param event The event name to track.
|
|
109
56
|
*/
|
|
110
|
-
static create(t, e) {
|
|
57
|
+
static create(t, ...e) {
|
|
111
58
|
return new p(t, e);
|
|
112
59
|
}
|
|
113
60
|
#e;
|
|
@@ -121,7 +68,9 @@ class p {
|
|
|
121
68
|
* @param event The event name to track.
|
|
122
69
|
*/
|
|
123
70
|
constructor(t, e) {
|
|
124
|
-
this.#e = t, this.#t = e, this.#s = [], this.#r = (s) => this.#s.push(s), this.#
|
|
71
|
+
this.#e = t, this.#t = e, this.#s = [], this.#r = (s) => this.#s.push(s), this.#t.forEach(
|
|
72
|
+
(s) => this.#e.addEventListener(s, this.#r)
|
|
73
|
+
);
|
|
125
74
|
}
|
|
126
75
|
/**
|
|
127
76
|
* Return the tracked events.
|
|
@@ -144,7 +93,9 @@ class p {
|
|
|
144
93
|
* Stop tracking.
|
|
145
94
|
*/
|
|
146
95
|
stop() {
|
|
147
|
-
this.#
|
|
96
|
+
this.#t.forEach(
|
|
97
|
+
(t) => this.#e.removeEventListener(t, this.#r)
|
|
98
|
+
);
|
|
148
99
|
}
|
|
149
100
|
/**
|
|
150
101
|
* Wait asynchronously for a number of events.
|
|
@@ -154,10 +105,82 @@ class p {
|
|
|
154
105
|
async waitFor(t = 1) {
|
|
155
106
|
return new Promise((e) => {
|
|
156
107
|
const s = () => {
|
|
157
|
-
this.#s.length >= t && (this.#
|
|
108
|
+
this.#s.length >= t && (this.#t.forEach(
|
|
109
|
+
(r) => this.#e.removeEventListener(r, s)
|
|
110
|
+
), e(this.events));
|
|
158
111
|
};
|
|
159
|
-
this.#
|
|
112
|
+
this.#t.forEach(
|
|
113
|
+
(r) => this.#e.addEventListener(r, s)
|
|
114
|
+
), s();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
class m {
|
|
119
|
+
isSuccess = !0;
|
|
120
|
+
}
|
|
121
|
+
class S {
|
|
122
|
+
isSuccess = !1;
|
|
123
|
+
errorMessage;
|
|
124
|
+
/**
|
|
125
|
+
* Creates a failed status.
|
|
126
|
+
*
|
|
127
|
+
* @param errorMessage
|
|
128
|
+
*/
|
|
129
|
+
constructor(t) {
|
|
130
|
+
this.errorMessage = t;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
class u {
|
|
134
|
+
/**
|
|
135
|
+
* Create a list of responses (by providing an array), or a single repeating
|
|
136
|
+
* response (by providing any other type). 'Name' is optional and used in
|
|
137
|
+
* error messages.
|
|
138
|
+
*
|
|
139
|
+
* @param responses A single response or an array of responses.
|
|
140
|
+
* @param name An optional name for the responses.
|
|
141
|
+
*/
|
|
142
|
+
static create(t, e) {
|
|
143
|
+
return new u(t, e);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Convert all properties in an object into ConfigurableResponse instances.
|
|
147
|
+
* For example, { a: 1 } becomes { a: ConfigurableResponses.create(1) }.
|
|
148
|
+
* 'Name' is optional and used in error messages.
|
|
149
|
+
*
|
|
150
|
+
* @param responseObject An object with single response or an array of responses.
|
|
151
|
+
* @param name An optional name for the responses.
|
|
152
|
+
*/
|
|
153
|
+
static mapObject(t, e) {
|
|
154
|
+
const r = Object.entries(t).map(([h, n]) => {
|
|
155
|
+
const l = e === void 0 ? void 0 : `${e}: ${h}`;
|
|
156
|
+
return [h, u.create(n, l)];
|
|
160
157
|
});
|
|
158
|
+
return Object.fromEntries(r);
|
|
159
|
+
}
|
|
160
|
+
#e;
|
|
161
|
+
#t;
|
|
162
|
+
/**
|
|
163
|
+
* Create a list of responses (by providing an array), or a single repeating
|
|
164
|
+
* response (by providing any other type). 'Name' is optional and used in
|
|
165
|
+
* error messages.
|
|
166
|
+
*
|
|
167
|
+
* @param responses A single response or an array of responses.
|
|
168
|
+
* @param name An optional name for the responses.
|
|
169
|
+
*/
|
|
170
|
+
constructor(t, e) {
|
|
171
|
+
this.#e = e == null ? "" : ` in ${e}`, this.#t = Array.isArray(t) ? [...t] : t;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get the next configured response. Throws an error when configured with a list
|
|
175
|
+
* of responses and no more responses remain.
|
|
176
|
+
*
|
|
177
|
+
* @return The next response.
|
|
178
|
+
*/
|
|
179
|
+
next() {
|
|
180
|
+
const t = Array.isArray(this.#t) ? this.#t.shift() : this.#t;
|
|
181
|
+
if (t === void 0)
|
|
182
|
+
throw new Error(`No more responses configured${this.#e}.`);
|
|
183
|
+
return t;
|
|
161
184
|
}
|
|
162
185
|
}
|
|
163
186
|
class d {
|
|
@@ -207,21 +230,6 @@ class d {
|
|
|
207
230
|
this.#e.removeEventListener(this.#t, this.#r);
|
|
208
231
|
}
|
|
209
232
|
}
|
|
210
|
-
class f {
|
|
211
|
-
isSuccess = !0;
|
|
212
|
-
}
|
|
213
|
-
class S {
|
|
214
|
-
isSuccess = !1;
|
|
215
|
-
errorMessage;
|
|
216
|
-
/**
|
|
217
|
-
* Creates a failed status.
|
|
218
|
-
*
|
|
219
|
-
* @param errorMessage
|
|
220
|
-
*/
|
|
221
|
-
constructor(t) {
|
|
222
|
-
this.errorMessage = t;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
233
|
const i = "message";
|
|
226
234
|
class b extends EventTarget {
|
|
227
235
|
log(...t) {
|
|
@@ -455,7 +463,7 @@ class v extends EventTarget {
|
|
|
455
463
|
return new v(
|
|
456
464
|
t,
|
|
457
465
|
e,
|
|
458
|
-
|
|
466
|
+
f
|
|
459
467
|
);
|
|
460
468
|
}
|
|
461
469
|
#e;
|
|
@@ -529,7 +537,7 @@ class v extends EventTarget {
|
|
|
529
537
|
* @param message The message to receive.
|
|
530
538
|
*/
|
|
531
539
|
simulateMessage(t) {
|
|
532
|
-
typeof t != "string" && (t = JSON.stringify(t)), this.#a(new MessageEvent("message", { data: t }));
|
|
540
|
+
typeof t != "string" && !(t instanceof Blob) && !(t instanceof ArrayBuffer) && (t = JSON.stringify(t)), this.#a(new MessageEvent("message", { data: t }));
|
|
533
541
|
}
|
|
534
542
|
/**
|
|
535
543
|
* Simulate a heartbeat.
|
|
@@ -588,7 +596,7 @@ class v extends EventTarget {
|
|
|
588
596
|
this.#n != null && this.send(y);
|
|
589
597
|
}
|
|
590
598
|
}
|
|
591
|
-
class
|
|
599
|
+
class f extends EventTarget {
|
|
592
600
|
url;
|
|
593
601
|
readyState = WebSocket.CONNECTING;
|
|
594
602
|
constructor(t) {
|
|
@@ -611,7 +619,7 @@ export {
|
|
|
611
619
|
y as HEARTBEAT_TYPE,
|
|
612
620
|
d as OutputTracker,
|
|
613
621
|
E as SseClient,
|
|
614
|
-
|
|
622
|
+
m as Success,
|
|
615
623
|
v as WebSocketClient,
|
|
616
624
|
N as createFetchStub
|
|
617
625
|
};
|
package/dist/shared.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sources":["../src/common/clock.ts","../src/common/configurable_responses.ts","../src/common/event_tracker.ts","../src/common/output_tracker.ts","../src/domain/messages.ts","../src/infrastructure/console_stub.ts","../src/infrastructure/fetch_stub.ts","../src/infrastructure/sse_client.ts","../src/infrastructure/web_socket_client.ts"],"sourcesContent":["// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * A clock provides access to the current timestamp.\n */\nexport class Clock {\n /**\n * Create a clock using system the clock.\n *\n * @return A clock that uses the system clock.\n */\n static system(): Clock {\n return new Clock();\n }\n\n /**\n * Create a clock using a fixed date.\n *\n * @param date The fixed date of the clock.\n * @return A clock that always returns a fixed date.\n */\n static fixed(date: Date | string | number): Clock {\n return new Clock(new Date(date));\n }\n\n /**\n * Create a clock that returns a fixed offset from the given clock.\n *\n * @param clock The clock to offset from.\n * @param offsetMillis The offset in milliseconds.\n * @return A clock that returns a fixed offset from the given clock.\n */\n static offset(clock: Clock, offsetMillis: number): Clock {\n return new Clock(new Date(clock.millis() + offsetMillis));\n }\n\n readonly #date?: Date;\n\n private constructor(date?: Date) {\n this.#date = date;\n }\n\n /**\n * Return the current timestamp of the clock.\n *\n * @return The current timestamp.\n */\n date(): Date {\n return this.#date ? new Date(this.#date) : new Date();\n }\n\n /**\n * Return the current timestamp of the clock in milliseconds.\n *\n * @return The current timestamp in milliseconds.\n */\n millis(): number {\n return this.date().getTime();\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n// Copyright 2023 Titanium I.T. LLC. MIT License.\n\n/**\n * Handle returning pre-configured responses.\n *\n * This is one of the nullability patterns from James Shore's article on\n * [testing without mocks](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#configurable-responses).\n *\n * Example usage for stubbing `fetch` function:\n *\n * ```javascript\n * function createFetchStub(responses) {\n * const configurableResponses = ConfigurableResponses.create(responses);\n * return async function () {\n * const response = configurableResponses.next();\n * return {\n * status: response.status,\n * json: async () => response.body,\n * };\n * };\n * }\n * ```\n */\nexport class ConfigurableResponses<T = unknown> {\n /**\n * Create a list of responses (by providing an array), or a single repeating\n * response (by providing any other type). 'Name' is optional and used in\n * error messages.\n *\n * @param responses A single response or an array of responses.\n * @param name An optional name for the responses.\n */\n static create<T>(responses?: T | T[], name?: string) {\n return new ConfigurableResponses<T>(responses, name);\n }\n\n /**\n * Convert all properties in an object into ConfigurableResponse instances.\n * For example, { a: 1 } becomes { a: ConfigurableResponses.create(1) }.\n * 'Name' is optional and used in error messages.\n *\n * @param responseObject An object with single response or an array of responses.\n * @param name An optional name for the responses.\n */\n static mapObject<T extends Record<string, unknown>>(\n responseObject: T,\n name?: string,\n ) {\n const entries = Object.entries(responseObject);\n const translatedEntries = entries.map(([key, value]) => {\n const translatedName = name === undefined ? undefined : `${name}: ${key}`;\n return [key, ConfigurableResponses.create(value, translatedName)];\n });\n return Object.fromEntries(translatedEntries);\n }\n\n readonly #description;\n readonly #responses;\n\n /**\n * Create a list of responses (by providing an array), or a single repeating\n * response (by providing any other type). 'Name' is optional and used in\n * error messages.\n *\n * @param responses A single response or an array of responses.\n * @param name An optional name for the responses.\n */\n constructor(responses?: T | T[], name?: string) {\n this.#description = name == null ? \"\" : ` in ${name}`;\n this.#responses = Array.isArray(responses) ? [...responses] : responses;\n }\n\n /**\n * Get the next configured response. Throws an error when configured with a list\n * of responses and no more responses remain.\n *\n * @return The next response.\n */\n next(): T {\n const response = Array.isArray(this.#responses)\n ? this.#responses.shift()\n : this.#responses;\n if (response === undefined) {\n throw new Error(`No more responses configured${this.#description}.`);\n }\n\n return response;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * Track events from an event target.\n *\n * Wait asynchronously for events. Useful in test code.\n */\nexport class EventTracker<T extends Event> {\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n static create<T extends Event>(eventTarget: EventTarget, event: string) {\n return new EventTracker<T>(eventTarget, event);\n }\n\n readonly #eventTarget;\n readonly #event;\n readonly #events: T[];\n readonly #tracker;\n\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n constructor(eventTarget: EventTarget, event: string) {\n this.#eventTarget = eventTarget;\n this.#event = event;\n this.#events = [];\n this.#tracker = (event: Event) => this.#events.push(event as T);\n\n this.#eventTarget.addEventListener(this.#event, this.#tracker);\n }\n\n /**\n * Return the tracked events.\n *\n * @return The tracked events.\n */\n get events(): T[] {\n return this.#events;\n }\n\n /**\n * Clear the tracked events and return the cleared events.\n *\n * @return The cleared events.\n */\n clear(): T[] {\n const result = [...this.#events];\n this.#events.length = 0;\n return result;\n }\n\n /**\n * Stop tracking.\n */\n stop() {\n this.#eventTarget.removeEventListener(this.#event, this.#tracker);\n }\n\n /**\n * Wait asynchronously for a number of events.\n *\n * @param count number of events, default 1.\n */\n async waitFor(count = 1) {\n return new Promise<T[]>((resolve) => {\n const checkEvents = () => {\n if (this.#events.length >= count) {\n this.#eventTarget.removeEventListener(this.#event, checkEvents);\n resolve(this.events);\n }\n };\n\n this.#eventTarget.addEventListener(this.#event, checkEvents);\n checkEvents();\n });\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n// Copyright 2020-2022 Titanium I.T. LLC. MIT License.\n\n/**\n * Track output events.\n *\n * This is one of the nullability patterns from James Shore's article on\n * [testing without mocks](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking).\n *\n * Example implementation of an event store:\n *\n * ```javascript\n * async record(event) {\n * // ...\n * this.dispatchEvent(new CustomEvent(\"eventRecorded\", { detail: event }));\n * }\n *\n * trackEventsRecorded() {\n * return new OutputTracker(this, \"eventRecorded\");\n * }\n * ```\n *\n * Example usage:\n *\n * ```javascript\n * const eventsRecorded = eventStore.trackEventsRecorded();\n * // ...\n * const data = eventsRecorded.data(); // [event1, event2, ...]\n * ```\n */\nexport class OutputTracker<T = unknown> {\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n static create<T>(eventTarget: EventTarget, event: string) {\n return new OutputTracker<T>(eventTarget, event);\n }\n\n readonly #eventTarget;\n readonly #event;\n readonly #data: T[];\n readonly #tracker;\n\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n constructor(eventTarget: EventTarget, event: string) {\n this.#eventTarget = eventTarget;\n this.#event = event;\n this.#data = [];\n this.#tracker = (event: Event) =>\n this.#data.push((event as CustomEvent<T>).detail);\n\n this.#eventTarget.addEventListener(this.#event, this.#tracker);\n }\n\n /**\n * Return the tracked data.\n *\n * @return The tracked data.\n */\n get data(): T[] {\n return this.#data;\n }\n\n /**\n * Clear the tracked data and return the cleared data.\n *\n * @return The cleared data.\n */\n clear(): T[] {\n const result = [...this.#data];\n this.#data.length = 0;\n return result;\n }\n\n /**\n * Stop tracking.\n */\n stop() {\n this.#eventTarget.removeEventListener(this.#event, this.#tracker);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * Provides CQNS features.\n *\n * The Command Query Notification Separation principle is a software design\n * principle that separates the concerns of commands, queries, and\n * notifications.\n *\n * Message hierarchy:\n *\n * - Message\n * - Incoming / outgoing\n * - Request (outgoing) -> response (incoming)\n * - Command -> command status\n * - Query -> query result\n * - Notification\n * - Incoming: notification -> commands\n * - Outgoing\n * - Event (internal)\n *\n * @see https://ralfw.de/command-query-notification-separation-cqns/\n * @module\n */\n\n/**\n * The status returned by a command handler.\n */\nexport type CommandStatus = Success | Failure;\n\n/**\n * A successful status.\n */\nexport class Success {\n readonly isSuccess = true;\n}\n\n/**\n * A failed status.\n */\nexport class Failure {\n readonly isSuccess = false;\n errorMessage: string;\n\n /**\n * Creates a failed status.\n *\n * @param errorMessage\n */\n constructor(errorMessage: string) {\n this.errorMessage = errorMessage;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { OutputTracker } from \"../common/output_tracker\";\n\nconst MESSAGE_EVENT = \"message\";\n\nexport interface ConsoleMessage {\n level: \"log\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\";\n message: unknown[];\n}\n\n/**\n * A stub for the console interface.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API\n */\nexport class ConsoleStub extends EventTarget {\n log(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"log\", message: data },\n }),\n );\n }\n\n error(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"error\", message: data },\n }),\n );\n }\n\n warn(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"warn\", message: data },\n }),\n );\n }\n\n info(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"info\", message: data },\n }),\n );\n }\n\n debug(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"debug\", message: data },\n }),\n );\n }\n\n trace(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"trace\", message: data },\n }),\n );\n }\n\n /**\n * Track the console messages.\n */\n trackMessages() {\n return new OutputTracker<ConsoleMessage>(this, MESSAGE_EVENT);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { ConfigurableResponses } from \"../common/configurable_responses\";\n\n/**\n * This data object configures the response of a fetch stub call.\n */\nexport interface ResponseData {\n /** The HTTP status code. */\n status: number;\n\n /** The HTTP status text. */\n statusText: string;\n\n /** The optional response body. */\n body?: Blob | object | string | null;\n}\n\n/**\n * Create a fetch stub.\n *\n * The stub returns a response from the provided response data or throws an provided error.\n *\n * @param responses A single response or an array of responses.\n * @returns The fetch stub.\n */\nexport function createFetchStub(\n responses?: ResponseData | Error | (ResponseData | Error)[],\n): () => Promise<Response> {\n const configurableResponses = ConfigurableResponses.create(responses);\n return async function () {\n const response = configurableResponses.next();\n if (response instanceof Error) {\n throw response;\n }\n\n return new ResponseStub(response) as unknown as Response;\n };\n}\n\nclass ResponseStub {\n #status: number;\n #statusText: string;\n #body?: Blob | object | string | null;\n\n constructor({ status, statusText, body = null }: ResponseData) {\n this.#status = status;\n this.#statusText = statusText;\n this.#body = body;\n }\n\n get ok() {\n return this.status >= 200 && this.status < 300;\n }\n\n get status() {\n return this.#status;\n }\n\n get statusText() {\n return this.#statusText;\n }\n\n async blob() {\n if (this.#body == null) {\n return null;\n }\n\n if (this.#body instanceof Blob) {\n return this.#body;\n }\n\n throw new TypeError(\"Body is not a Blob.\");\n }\n\n async json() {\n const json =\n typeof this.#body === \"string\" ? this.#body : JSON.stringify(this.#body);\n return Promise.resolve(JSON.parse(json));\n }\n\n async text() {\n if (this.#body == null) {\n return \"\";\n }\n\n return String(this.#body);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport type { MessageClient } from \"./message_client\";\n\n/**\n * A client for the server-sent events protocol.\n */\nexport class SseClient extends EventTarget implements MessageClient {\n /**\n * Create an SSE client.\n *\n * @return A new SSE client.\n */\n static create(): SseClient {\n return new SseClient(EventSource);\n }\n\n /**\n * Create a nulled SSE client.\n *\n * @return A new SSE client.\n */\n static createNull(): SseClient {\n return new SseClient(EventSourceStub as typeof EventSource);\n }\n\n readonly #eventSourceConstructor: typeof EventSource;\n\n #eventSource?: EventSource;\n\n private constructor(eventSourceConstructor: typeof EventSource) {\n super();\n this.#eventSourceConstructor = eventSourceConstructor;\n }\n\n get isConnected(): boolean {\n return this.#eventSource?.readyState === this.#eventSourceConstructor.OPEN;\n }\n\n get url(): string | undefined {\n return this.#eventSource?.url;\n }\n\n async connect(\n url: string | URL,\n eventName = \"message\",\n ...otherEvents: string[]\n ): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (this.isConnected) {\n reject(new Error(\"Already connected.\"));\n return;\n }\n\n try {\n this.#eventSource = new this.#eventSourceConstructor(url);\n this.#eventSource.addEventListener(\"open\", (e) => {\n this.#handleOpen(e);\n resolve();\n });\n this.#eventSource.addEventListener(eventName, (e) =>\n this.#handleMessage(e),\n );\n for (const otherEvent of otherEvents) {\n this.#eventSource.addEventListener(otherEvent, (e) =>\n this.#handleMessage(e),\n );\n }\n this.#eventSource.addEventListener(\"error\", (e) =>\n this.#handleError(e),\n );\n } catch (error) {\n reject(error);\n }\n });\n }\n\n send(_message: string, _type?: string): Promise<void> {\n throw new Error(\"Method not implemented.\");\n }\n\n async close(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (!this.isConnected) {\n resolve();\n return;\n }\n\n try {\n this.#eventSource!.close();\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Simulate a message event from the server.\n *\n * @param message The message to receive.\n * @param eventName The optional event type.\n * @param lastEventId The optional last event ID.\n */\n simulateMessage(\n message: string | number | boolean | object | null,\n eventName = \"message\",\n lastEventId?: string,\n ) {\n if (typeof message !== \"string\") {\n message = JSON.stringify(message);\n }\n this.#handleMessage(\n new MessageEvent(eventName, { data: message, lastEventId }),\n );\n }\n\n /**\n * Simulate an error event.\n */\n simulateError() {\n this.#handleError(new Event(\"error\"));\n }\n\n #handleOpen(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n }\n\n #handleMessage(event: MessageEvent) {\n this.dispatchEvent(\n new MessageEvent(event.type, event as unknown as MessageEventInit),\n );\n }\n\n #handleError(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n }\n}\n\nclass EventSourceStub extends EventTarget {\n // The constants have to be defined here because Node.js support is currently\n // experimental only.\n static CONNECTING = 0;\n static OPEN = 1;\n static CLOSED = 2;\n\n url: string;\n readyState = EventSourceStub.CONNECTING;\n\n constructor(url: string | URL) {\n super();\n this.url = url.toString();\n setTimeout(() => {\n this.readyState = EventSourceStub.OPEN;\n this.dispatchEvent(new Event(\"open\"));\n }, 0);\n }\n\n close() {\n this.readyState = EventSourceStub.CLOSED;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { OutputTracker } from \"../common/output_tracker\";\nimport type { MessageClient } from \"./message_client\";\n\nexport const HEARTBEAT_TYPE = \"heartbeat\";\n\nconst MESSAGE_SENT_EVENT = \"message-sent\";\n\n/**\n * Options for the WebSocket client.\n */\nexport interface WebSocketOptions {\n /**\n * The heartbeat interval in milliseconds. A value <= 0 disables the\n * heartbeat.\n */\n heartbeat?: number;\n\n /**\n * The time in milliseconds to wait before retrying a connection after an\n * error. A value <= 0 disables automatic retries.\n */\n retry?: number;\n}\n\n/**\n * A client for the WebSocket protocol.\n */\nexport class WebSocketClient extends EventTarget implements MessageClient {\n /**\n * Create a WebSocket client.\n *\n * @param options The options for the WebSocket client.\n * @return A new WebSocket client.\n */\n static create({\n heartbeat = 30000,\n retry = 1000,\n }: WebSocketOptions = {}): WebSocketClient {\n return new WebSocketClient(heartbeat, retry, WebSocket);\n }\n\n /**\n * Create a nulled WebSocket client.\n *\n * @param options The options for the WebSocket client.\n * @return A new nulled WebSocket client.\n */\n static createNull({ heartbeat = 0, retry = 0 }: WebSocketOptions = {}) {\n return new WebSocketClient(\n heartbeat,\n retry,\n WebSocketStub as unknown as typeof WebSocket,\n );\n }\n\n readonly #heartbeat: number;\n readonly #retry: number;\n readonly #webSocketConstructor: typeof WebSocket;\n\n #webSocket?: WebSocket;\n #heartbeatId?: ReturnType<typeof setTimeout>;\n #retryId?: ReturnType<typeof setTimeout>;\n\n private constructor(\n heartbeat: number,\n retry: number,\n webSocketConstructor: typeof WebSocket,\n ) {\n super();\n this.#heartbeat = heartbeat;\n this.#retry = retry;\n this.#webSocketConstructor = webSocketConstructor;\n }\n\n get isConnected(): boolean {\n return this.#webSocket?.readyState === WebSocket.OPEN;\n }\n\n get url(): string | undefined {\n return this.#webSocket?.url;\n }\n\n async connect(url: string | URL): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.#stopRetry();\n\n if (this.isConnected) {\n reject(new Error(\"Already connected.\"));\n return;\n }\n\n try {\n this.#webSocket = new this.#webSocketConstructor(url);\n this.#webSocket.addEventListener(\"open\", (e) => {\n this.#handleOpen(e);\n resolve();\n });\n this.#webSocket.addEventListener(\"message\", (e) =>\n this.#handleMessage(e),\n );\n this.#webSocket.addEventListener(\"close\", (e) => this.#handleClose(e));\n this.#webSocket.addEventListener(\"error\", (e) => this.#handleError(e));\n } catch (error) {\n reject(error);\n }\n });\n }\n\n async send(\n message: string | ArrayBuffer | Blob | ArrayBufferView,\n ): Promise<void> {\n if (!this.isConnected) {\n throw new Error(\"Not connected.\");\n }\n\n this.#webSocket!.send(message);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_SENT_EVENT, { detail: message }),\n );\n await Promise.resolve();\n }\n\n /**\n * Return a tracker for messages sent.\n *\n * @return A new output tracker.\n */\n trackMessageSent(): OutputTracker<string> {\n return OutputTracker.create(this, MESSAGE_SENT_EVENT);\n }\n\n /**\n * Close the connection.\n *\n * If a code is provided, also a reason should be provided.\n *\n * @param code An optional code.\n * @param reason An optional reason.\n */\n async close(code?: number, reason?: string): Promise<void> {\n await new Promise<void>((resolve) => {\n this.#stopRetry();\n\n if (!this.isConnected) {\n resolve();\n return;\n }\n\n this.#webSocket!.addEventListener(\"close\", () => resolve());\n this.#webSocket!.close(code, reason);\n });\n }\n\n /**\n * Simulate a message event from the server.\n *\n * @param message The message to receive.\n */\n simulateMessage(message: string | number | boolean | object | null) {\n if (typeof message !== \"string\") {\n message = JSON.stringify(message);\n }\n this.#handleMessage(new MessageEvent(\"message\", { data: message }));\n }\n\n /**\n * Simulate a heartbeat.\n */\n simulateHeartbeat() {\n this.#sendHeartbeat();\n }\n\n /**\n * Simulate a close event.\n *\n * @param code An optional code.\n * @param reason An optional reason.\n */\n simulateClose(code?: number, reason?: string) {\n this.#handleClose(new CloseEvent(\"close\", { code, reason }));\n }\n\n /**\n * Simulate an error event.\n */\n simulateError() {\n this.#webSocket?.close();\n this.#handleError(new Event(\"error\"));\n }\n\n #handleOpen(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n this.#startHeartbeat();\n }\n\n #handleMessage(event: MessageEvent) {\n this.dispatchEvent(\n new MessageEvent(event.type, event as unknown as MessageEventInit),\n );\n }\n\n #handleClose(event: CloseEvent) {\n this.#stopHeartbeat();\n this.dispatchEvent(new CloseEvent(event.type, event));\n }\n\n #handleError(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n this.#startRetry();\n }\n\n #startRetry() {\n if (this.#retry <= 0) {\n return;\n }\n this.#retryId = setInterval(\n () => this.connect(this.#webSocket!.url),\n this.#retry,\n );\n }\n\n #stopRetry() {\n clearInterval(this.#retryId);\n this.#retryId = undefined;\n }\n\n #startHeartbeat() {\n if (this.#heartbeat <= 0) {\n return;\n }\n\n this.#heartbeatId = setInterval(\n () => this.#sendHeartbeat(),\n this.#heartbeat,\n );\n }\n\n #stopHeartbeat() {\n clearInterval(this.#heartbeatId);\n this.#heartbeatId = undefined;\n }\n\n #sendHeartbeat() {\n if (this.#heartbeatId == null) {\n return;\n }\n\n void this.send(HEARTBEAT_TYPE);\n }\n}\n\nclass WebSocketStub extends EventTarget {\n url: string;\n readyState: number = WebSocket.CONNECTING;\n\n constructor(url: string | URL) {\n super();\n this.url = url.toString();\n setTimeout(() => {\n this.readyState = WebSocket.OPEN;\n this.dispatchEvent(new Event(\"open\"));\n }, 0);\n }\n\n send() {}\n\n close() {\n this.readyState = WebSocket.CLOSED;\n this.dispatchEvent(new Event(\"close\"));\n }\n}\n"],"names":["Clock","date","clock","offsetMillis","#date","ConfigurableResponses","responses","name","responseObject","translatedEntries","key","value","translatedName","#description","#responses","response","EventTracker","eventTarget","event","#eventTarget","#event","#events","#tracker","result","count","resolve","checkEvents","OutputTracker","#data","Success","Failure","errorMessage","MESSAGE_EVENT","ConsoleStub","data","createFetchStub","configurableResponses","ResponseStub","#status","#statusText","#body","status","statusText","body","json","SseClient","EventSourceStub","#eventSourceConstructor","#eventSource","eventSourceConstructor","url","eventName","otherEvents","reject","e","#handleOpen","#handleMessage","otherEvent","#handleError","error","_message","_type","message","lastEventId","HEARTBEAT_TYPE","MESSAGE_SENT_EVENT","WebSocketClient","heartbeat","retry","WebSocketStub","#heartbeat","#retry","#webSocketConstructor","#webSocket","#heartbeatId","#retryId","webSocketConstructor","#stopRetry","#handleClose","code","reason","#sendHeartbeat","#startHeartbeat","#stopHeartbeat","#startRetry"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shared.js","sources":["../src/common/clock.ts","../src/common/event_tracker.ts","../src/domain/messages.ts","../src/infrastructure/configurable_responses.ts","../src/infrastructure/output_tracker.ts","../src/infrastructure/console_stub.ts","../src/infrastructure/fetch_stub.ts","../src/infrastructure/sse_client.ts","../src/infrastructure/web_socket_client.ts"],"sourcesContent":["// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * A clock provides access to the current timestamp.\n */\nexport class Clock {\n /**\n * Create a clock using system the clock.\n *\n * @return A clock that uses the system clock.\n */\n static system(): Clock {\n return new Clock();\n }\n\n /**\n * Create a clock using a fixed date.\n *\n * @param date The fixed date of the clock.\n * @return A clock that always returns a fixed date.\n */\n static fixed(date: Date | string | number): Clock {\n return new Clock(new Date(date));\n }\n\n /**\n * Create a clock that returns a fixed offset from the given clock.\n *\n * @param clock The clock to offset from.\n * @param offsetMillis The offset in milliseconds.\n * @return A clock that returns a fixed offset from the given clock.\n */\n static offset(clock: Clock, offsetMillis: number): Clock {\n return new Clock(new Date(clock.millis() + offsetMillis));\n }\n\n readonly #date?: Date;\n\n private constructor(date?: Date) {\n this.#date = date;\n }\n\n /**\n * Return the current timestamp of the clock.\n *\n * @return The current timestamp.\n */\n date(): Date {\n return this.#date ? new Date(this.#date) : new Date();\n }\n\n /**\n * Return the current timestamp of the clock in milliseconds.\n *\n * @return The current timestamp in milliseconds.\n */\n millis(): number {\n return this.date().getTime();\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n// TODO monitor multiple events\n\n/**\n * Track events from an event target.\n *\n * Wait asynchronously for events. Useful in test code.\n */\nexport class EventTracker<T extends Event> {\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n static create<T extends Event>(eventTarget: EventTarget, ...event: string[]) {\n return new EventTracker<T>(eventTarget, event);\n }\n\n readonly #eventTarget;\n readonly #event;\n readonly #events: T[];\n readonly #tracker;\n\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n constructor(eventTarget: EventTarget, event: string[]) {\n this.#eventTarget = eventTarget;\n this.#event = event;\n this.#events = [];\n this.#tracker = (event: Event) => this.#events.push(event as T);\n\n this.#event.forEach((event) =>\n this.#eventTarget.addEventListener(event, this.#tracker),\n );\n }\n\n /**\n * Return the tracked events.\n *\n * @return The tracked events.\n */\n get events(): T[] {\n return this.#events;\n }\n\n /**\n * Clear the tracked events and return the cleared events.\n *\n * @return The cleared events.\n */\n clear(): T[] {\n const result = [...this.#events];\n this.#events.length = 0;\n return result;\n }\n\n /**\n * Stop tracking.\n */\n stop() {\n this.#event.forEach((event) =>\n this.#eventTarget.removeEventListener(event, this.#tracker),\n );\n }\n\n /**\n * Wait asynchronously for a number of events.\n *\n * @param count number of events, default 1.\n */\n async waitFor(count = 1) {\n return new Promise<T[]>((resolve) => {\n const checkEvents = () => {\n if (this.#events.length >= count) {\n this.#event.forEach((event) =>\n this.#eventTarget.removeEventListener(event, checkEvents),\n );\n resolve(this.events);\n }\n };\n\n this.#event.forEach((event) =>\n this.#eventTarget.addEventListener(event, checkEvents),\n );\n checkEvents();\n });\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * Provides CQNS features.\n *\n * The Command Query Notification Separation principle is a software design\n * principle that separates the concerns of commands, queries, and\n * notifications.\n *\n * Message hierarchy:\n *\n * - Message\n * - Incoming / outgoing\n * - Request (outgoing) -> response (incoming)\n * - Command -> command status\n * - Query -> query result\n * - Notification\n * - Incoming: notification -> commands\n * - Outgoing\n * - Event (internal)\n *\n * @see https://ralfw.de/command-query-notification-separation-cqns/\n * @module\n */\n\n/**\n * The status returned by a command handler.\n */\nexport type CommandStatus = Success | Failure;\n\n/**\n * A successful status.\n */\nexport class Success {\n readonly isSuccess = true;\n}\n\n/**\n * A failed status.\n */\nexport class Failure {\n readonly isSuccess = false;\n errorMessage: string;\n\n /**\n * Creates a failed status.\n *\n * @param errorMessage\n */\n constructor(errorMessage: string) {\n this.errorMessage = errorMessage;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n// Copyright 2023 Titanium I.T. LLC. MIT License.\n\n/**\n * Handle returning pre-configured responses.\n *\n * This is one of the nullability patterns from James Shore's article on\n * [testing without mocks](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#configurable-responses).\n *\n * Example usage for stubbing `fetch` function:\n *\n * ```javascript\n * function createFetchStub(responses) {\n * const configurableResponses = ConfigurableResponses.create(responses);\n * return async function () {\n * const response = configurableResponses.next();\n * return {\n * status: response.status,\n * json: async () => response.body,\n * };\n * };\n * }\n * ```\n */\nexport class ConfigurableResponses<T = unknown> {\n /**\n * Create a list of responses (by providing an array), or a single repeating\n * response (by providing any other type). 'Name' is optional and used in\n * error messages.\n *\n * @param responses A single response or an array of responses.\n * @param name An optional name for the responses.\n */\n static create<T>(responses?: T | T[], name?: string) {\n return new ConfigurableResponses<T>(responses, name);\n }\n\n /**\n * Convert all properties in an object into ConfigurableResponse instances.\n * For example, { a: 1 } becomes { a: ConfigurableResponses.create(1) }.\n * 'Name' is optional and used in error messages.\n *\n * @param responseObject An object with single response or an array of responses.\n * @param name An optional name for the responses.\n */\n static mapObject<T extends Record<string, unknown>>(\n responseObject: T,\n name?: string,\n ) {\n const entries = Object.entries(responseObject);\n const translatedEntries = entries.map(([key, value]) => {\n const translatedName = name === undefined ? undefined : `${name}: ${key}`;\n return [key, ConfigurableResponses.create(value, translatedName)];\n });\n return Object.fromEntries(translatedEntries);\n }\n\n readonly #description;\n readonly #responses;\n\n /**\n * Create a list of responses (by providing an array), or a single repeating\n * response (by providing any other type). 'Name' is optional and used in\n * error messages.\n *\n * @param responses A single response or an array of responses.\n * @param name An optional name for the responses.\n */\n constructor(responses?: T | T[], name?: string) {\n this.#description = name == null ? \"\" : ` in ${name}`;\n this.#responses = Array.isArray(responses) ? [...responses] : responses;\n }\n\n /**\n * Get the next configured response. Throws an error when configured with a list\n * of responses and no more responses remain.\n *\n * @return The next response.\n */\n next(): T {\n const response = Array.isArray(this.#responses)\n ? this.#responses.shift()\n : this.#responses;\n if (response === undefined) {\n throw new Error(`No more responses configured${this.#description}.`);\n }\n\n return response;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n// Copyright 2020-2022 Titanium I.T. LLC. MIT License.\n\n/**\n * Track output events.\n *\n * This is one of the nullability patterns from James Shore's article on\n * [testing without mocks](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking).\n *\n * Example implementation of an event store:\n *\n * ```javascript\n * async record(event) {\n * // ...\n * this.dispatchEvent(new CustomEvent(\"eventRecorded\", { detail: event }));\n * }\n *\n * trackEventsRecorded() {\n * return new OutputTracker(this, \"eventRecorded\");\n * }\n * ```\n *\n * Example usage:\n *\n * ```javascript\n * const eventsRecorded = eventStore.trackEventsRecorded();\n * // ...\n * const data = eventsRecorded.data(); // [event1, event2, ...]\n * ```\n */\nexport class OutputTracker<T = unknown> {\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n static create<T>(eventTarget: EventTarget, event: string) {\n return new OutputTracker<T>(eventTarget, event);\n }\n\n readonly #eventTarget;\n readonly #event;\n readonly #data: T[];\n readonly #tracker;\n\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n constructor(eventTarget: EventTarget, event: string) {\n this.#eventTarget = eventTarget;\n this.#event = event;\n this.#data = [];\n this.#tracker = (event: Event) =>\n this.#data.push((event as CustomEvent<T>).detail);\n\n this.#eventTarget.addEventListener(this.#event, this.#tracker);\n }\n\n /**\n * Return the tracked data.\n *\n * @return The tracked data.\n */\n get data(): T[] {\n return this.#data;\n }\n\n /**\n * Clear the tracked data and return the cleared data.\n *\n * @return The cleared data.\n */\n clear(): T[] {\n const result = [...this.#data];\n this.#data.length = 0;\n return result;\n }\n\n /**\n * Stop tracking.\n */\n stop() {\n this.#eventTarget.removeEventListener(this.#event, this.#tracker);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { OutputTracker } from \"./output_tracker\";\n\nconst MESSAGE_EVENT = \"message\";\n\nexport interface ConsoleMessage {\n level: \"log\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\";\n message: unknown[];\n}\n\n/**\n * A stub for the console interface.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API\n */\nexport class ConsoleStub extends EventTarget {\n log(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"log\", message: data },\n }),\n );\n }\n\n error(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"error\", message: data },\n }),\n );\n }\n\n warn(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"warn\", message: data },\n }),\n );\n }\n\n info(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"info\", message: data },\n }),\n );\n }\n\n debug(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"debug\", message: data },\n }),\n );\n }\n\n trace(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"trace\", message: data },\n }),\n );\n }\n\n /**\n * Track the console messages.\n */\n trackMessages() {\n return new OutputTracker<ConsoleMessage>(this, MESSAGE_EVENT);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { ConfigurableResponses } from \"./configurable_responses\";\n\n/**\n * This data object configures the response of a fetch stub call.\n */\nexport interface ResponseData {\n /** The HTTP status code. */\n status: number;\n\n /** The HTTP status text. */\n statusText: string;\n\n /** The optional response body. */\n body?: Blob | object | string | null;\n}\n\n/**\n * Create a fetch stub.\n *\n * The stub returns a response from the provided response data or throws an provided error.\n *\n * @param responses A single response or an array of responses.\n * @returns The fetch stub.\n */\nexport function createFetchStub(\n responses?: ResponseData | Error | (ResponseData | Error)[],\n): () => Promise<Response> {\n const configurableResponses = ConfigurableResponses.create(responses);\n return async function () {\n const response = configurableResponses.next();\n if (response instanceof Error) {\n throw response;\n }\n\n return new ResponseStub(response) as unknown as Response;\n };\n}\n\nclass ResponseStub {\n #status: number;\n #statusText: string;\n #body?: Blob | object | string | null;\n\n constructor({ status, statusText, body = null }: ResponseData) {\n this.#status = status;\n this.#statusText = statusText;\n this.#body = body;\n }\n\n get ok() {\n return this.status >= 200 && this.status < 300;\n }\n\n get status() {\n return this.#status;\n }\n\n get statusText() {\n return this.#statusText;\n }\n\n async blob() {\n if (this.#body == null) {\n return null;\n }\n\n if (this.#body instanceof Blob) {\n return this.#body;\n }\n\n throw new TypeError(\"Body is not a Blob.\");\n }\n\n async json() {\n const json =\n typeof this.#body === \"string\" ? this.#body : JSON.stringify(this.#body);\n return Promise.resolve(JSON.parse(json));\n }\n\n async text() {\n if (this.#body == null) {\n return \"\";\n }\n\n return String(this.#body);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport type { MessageClient } from \"./message_client\";\n\n/**\n * A client for the server-sent events protocol.\n */\nexport class SseClient extends EventTarget implements MessageClient {\n /**\n * Create an SSE client.\n *\n * @return A new SSE client.\n */\n static create(): SseClient {\n return new SseClient(EventSource);\n }\n\n /**\n * Create a nulled SSE client.\n *\n * @return A new SSE client.\n */\n static createNull(): SseClient {\n return new SseClient(EventSourceStub as typeof EventSource);\n }\n\n readonly #eventSourceConstructor: typeof EventSource;\n\n #eventSource?: EventSource;\n\n private constructor(eventSourceConstructor: typeof EventSource) {\n super();\n this.#eventSourceConstructor = eventSourceConstructor;\n }\n\n get isConnected(): boolean {\n return this.#eventSource?.readyState === this.#eventSourceConstructor.OPEN;\n }\n\n get url(): string | undefined {\n return this.#eventSource?.url;\n }\n\n async connect(\n url: string | URL,\n eventName = \"message\",\n ...otherEvents: string[]\n ): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (this.isConnected) {\n reject(new Error(\"Already connected.\"));\n return;\n }\n\n try {\n this.#eventSource = new this.#eventSourceConstructor(url);\n this.#eventSource.addEventListener(\"open\", (e) => {\n this.#handleOpen(e);\n resolve();\n });\n this.#eventSource.addEventListener(eventName, (e) =>\n this.#handleMessage(e),\n );\n for (const otherEvent of otherEvents) {\n this.#eventSource.addEventListener(otherEvent, (e) =>\n this.#handleMessage(e),\n );\n }\n this.#eventSource.addEventListener(\"error\", (e) =>\n this.#handleError(e),\n );\n } catch (error) {\n reject(error);\n }\n });\n }\n\n send(_message: string, _type?: string): Promise<void> {\n throw new Error(\"Method not implemented.\");\n }\n\n async close(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (!this.isConnected) {\n resolve();\n return;\n }\n\n try {\n this.#eventSource!.close();\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Simulate a message event from the server.\n *\n * @param message The message to receive.\n * @param eventName The optional event type.\n * @param lastEventId The optional last event ID.\n */\n simulateMessage(\n message: string | number | boolean | object | null,\n eventName = \"message\",\n lastEventId?: string,\n ) {\n if (typeof message !== \"string\") {\n message = JSON.stringify(message);\n }\n this.#handleMessage(\n new MessageEvent(eventName, { data: message, lastEventId }),\n );\n }\n\n /**\n * Simulate an error event.\n */\n simulateError() {\n this.#handleError(new Event(\"error\"));\n }\n\n #handleOpen(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n }\n\n #handleMessage(event: MessageEvent) {\n this.dispatchEvent(\n new MessageEvent(event.type, event as unknown as MessageEventInit),\n );\n }\n\n #handleError(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n }\n}\n\nclass EventSourceStub extends EventTarget {\n // The constants have to be defined here because Node.js support is currently\n // experimental only.\n static CONNECTING = 0;\n static OPEN = 1;\n static CLOSED = 2;\n\n url: string;\n readyState = EventSourceStub.CONNECTING;\n\n constructor(url: string | URL) {\n super();\n this.url = url.toString();\n setTimeout(() => {\n this.readyState = EventSourceStub.OPEN;\n this.dispatchEvent(new Event(\"open\"));\n }, 0);\n }\n\n close() {\n this.readyState = EventSourceStub.CLOSED;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { OutputTracker } from \"./output_tracker\";\nimport type { MessageClient } from \"./message_client\";\n\nexport const HEARTBEAT_TYPE = \"heartbeat\";\n\nconst MESSAGE_SENT_EVENT = \"message-sent\";\n\n/**\n * Options for the WebSocket client.\n */\nexport interface WebSocketOptions {\n /**\n * The heartbeat interval in milliseconds. A value <= 0 disables the\n * heartbeat.\n */\n heartbeat?: number;\n\n /**\n * The time in milliseconds to wait before retrying a connection after an\n * error. A value <= 0 disables automatic retries.\n */\n retry?: number;\n}\n\n/**\n * A client for the WebSocket protocol.\n */\nexport class WebSocketClient extends EventTarget implements MessageClient {\n /**\n * Create a WebSocket client.\n *\n * @param options The options for the WebSocket client.\n * @return A new WebSocket client.\n */\n static create({\n heartbeat = 30000,\n retry = 1000,\n }: WebSocketOptions = {}): WebSocketClient {\n return new WebSocketClient(heartbeat, retry, WebSocket);\n }\n\n /**\n * Create a nulled WebSocket client.\n *\n * @param options The options for the WebSocket client.\n * @return A new nulled WebSocket client.\n */\n static createNull({ heartbeat = 0, retry = 0 }: WebSocketOptions = {}) {\n return new WebSocketClient(\n heartbeat,\n retry,\n WebSocketStub as unknown as typeof WebSocket,\n );\n }\n\n readonly #heartbeat: number;\n readonly #retry: number;\n readonly #webSocketConstructor: typeof WebSocket;\n\n #webSocket?: WebSocket;\n #heartbeatId?: ReturnType<typeof setTimeout>;\n #retryId?: ReturnType<typeof setTimeout>;\n\n private constructor(\n heartbeat: number,\n retry: number,\n webSocketConstructor: typeof WebSocket,\n ) {\n super();\n this.#heartbeat = heartbeat;\n this.#retry = retry;\n this.#webSocketConstructor = webSocketConstructor;\n }\n\n get isConnected(): boolean {\n return this.#webSocket?.readyState === WebSocket.OPEN;\n }\n\n get url(): string | undefined {\n return this.#webSocket?.url;\n }\n\n async connect(url: string | URL): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.#stopRetry();\n\n if (this.isConnected) {\n reject(new Error(\"Already connected.\"));\n return;\n }\n\n try {\n this.#webSocket = new this.#webSocketConstructor(url);\n this.#webSocket.addEventListener(\"open\", (e) => {\n this.#handleOpen(e);\n resolve();\n });\n this.#webSocket.addEventListener(\"message\", (e) =>\n this.#handleMessage(e),\n );\n this.#webSocket.addEventListener(\"close\", (e) => this.#handleClose(e));\n this.#webSocket.addEventListener(\"error\", (e) => this.#handleError(e));\n } catch (error) {\n reject(error);\n }\n });\n }\n\n async send(\n message: string | ArrayBuffer | Blob | ArrayBufferView,\n ): Promise<void> {\n if (!this.isConnected) {\n throw new Error(\"Not connected.\");\n }\n\n this.#webSocket!.send(message);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_SENT_EVENT, { detail: message }),\n );\n await Promise.resolve();\n }\n\n /**\n * Return a tracker for messages sent.\n *\n * @return A new output tracker.\n */\n trackMessageSent(): OutputTracker<string> {\n return OutputTracker.create(this, MESSAGE_SENT_EVENT);\n }\n\n /**\n * Close the connection.\n *\n * If a code is provided, also a reason should be provided.\n *\n * @param code An optional code.\n * @param reason An optional reason.\n */\n async close(code?: number, reason?: string): Promise<void> {\n await new Promise<void>((resolve) => {\n this.#stopRetry();\n\n if (!this.isConnected) {\n resolve();\n return;\n }\n\n this.#webSocket!.addEventListener(\"close\", () => resolve());\n this.#webSocket!.close(code, reason);\n });\n }\n\n /**\n * Simulate a message event from the server.\n *\n * @param message The message to receive.\n */\n simulateMessage(\n message: string | number | boolean | object | null | Blob | ArrayBuffer,\n ) {\n if (\n typeof message !== \"string\" &&\n !(message instanceof Blob) &&\n !(message instanceof ArrayBuffer)\n ) {\n message = JSON.stringify(message);\n }\n this.#handleMessage(new MessageEvent(\"message\", { data: message }));\n }\n\n /**\n * Simulate a heartbeat.\n */\n simulateHeartbeat() {\n this.#sendHeartbeat();\n }\n\n /**\n * Simulate a close event.\n *\n * @param code An optional code.\n * @param reason An optional reason.\n */\n simulateClose(code?: number, reason?: string) {\n this.#handleClose(new CloseEvent(\"close\", { code, reason }));\n }\n\n /**\n * Simulate an error event.\n */\n simulateError() {\n this.#webSocket?.close();\n this.#handleError(new Event(\"error\"));\n }\n\n #handleOpen(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n this.#startHeartbeat();\n }\n\n #handleMessage(event: MessageEvent) {\n this.dispatchEvent(\n new MessageEvent(event.type, event as unknown as MessageEventInit),\n );\n }\n\n #handleClose(event: CloseEvent) {\n this.#stopHeartbeat();\n this.dispatchEvent(new CloseEvent(event.type, event));\n }\n\n #handleError(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n this.#startRetry();\n }\n\n #startRetry() {\n if (this.#retry <= 0) {\n return;\n }\n this.#retryId = setInterval(\n () => this.connect(this.#webSocket!.url),\n this.#retry,\n );\n }\n\n #stopRetry() {\n clearInterval(this.#retryId);\n this.#retryId = undefined;\n }\n\n #startHeartbeat() {\n if (this.#heartbeat <= 0) {\n return;\n }\n\n this.#heartbeatId = setInterval(\n () => this.#sendHeartbeat(),\n this.#heartbeat,\n );\n }\n\n #stopHeartbeat() {\n clearInterval(this.#heartbeatId);\n this.#heartbeatId = undefined;\n }\n\n #sendHeartbeat() {\n if (this.#heartbeatId == null) {\n return;\n }\n\n void this.send(HEARTBEAT_TYPE);\n }\n}\n\nclass WebSocketStub extends EventTarget {\n url: string;\n readyState: number = WebSocket.CONNECTING;\n\n constructor(url: string | URL) {\n super();\n this.url = url.toString();\n setTimeout(() => {\n this.readyState = WebSocket.OPEN;\n this.dispatchEvent(new Event(\"open\"));\n }, 0);\n }\n\n send() {}\n\n close() {\n this.readyState = WebSocket.CLOSED;\n this.dispatchEvent(new Event(\"close\"));\n }\n}\n"],"names":["Clock","date","clock","offsetMillis","#date","EventTracker","eventTarget","event","#eventTarget","#event","#events","#tracker","result","count","resolve","checkEvents","Success","Failure","errorMessage","ConfigurableResponses","responses","name","responseObject","translatedEntries","key","value","translatedName","#description","#responses","response","OutputTracker","#data","MESSAGE_EVENT","ConsoleStub","data","createFetchStub","configurableResponses","ResponseStub","#status","#statusText","#body","status","statusText","body","json","SseClient","EventSourceStub","#eventSourceConstructor","#eventSource","eventSourceConstructor","url","eventName","otherEvents","reject","e","#handleOpen","#handleMessage","otherEvent","#handleError","error","_message","_type","message","lastEventId","HEARTBEAT_TYPE","MESSAGE_SENT_EVENT","WebSocketClient","heartbeat","retry","WebSocketStub","#heartbeat","#retry","#webSocketConstructor","#webSocket","#heartbeatId","#retryId","webSocketConstructor","#stopRetry","#handleClose","code","reason","#sendHeartbeat","#startHeartbeat","#stopHeartbeat","#startRetry"],"mappings":"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"}
|
package/dist/shared.umd.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(r,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(r=typeof globalThis<"u"?globalThis:r||self,a(r.Shared={}))})(this,(function(r){"use strict";class a{static system(){return new a}static fixed(t){return new a(new Date(t))}static offset(t,e){return new a(new Date(t.millis()+e))}#e;constructor(t){this.#e=t}date(){return this.#e?new Date(this.#e):new Date}millis(){return this.date().getTime()}}class w{static create(t,...e){return new w(t,e)}#e;#t;#s;#n;constructor(t,e){this.#e=t,this.#t=e,this.#s=[],this.#n=s=>this.#s.push(s),this.#t.forEach(s=>this.#e.addEventListener(s,this.#n))}get events(){return this.#s}clear(){const t=[...this.#s];return this.#s.length=0,t}stop(){this.#t.forEach(t=>this.#e.removeEventListener(t,this.#n))}async waitFor(t=1){return new Promise(e=>{const s=()=>{this.#s.length>=t&&(this.#t.forEach(n=>this.#e.removeEventListener(n,s)),e(this.events))};this.#t.forEach(n=>this.#e.addEventListener(n,s)),s()})}}class p{isSuccess=!0}class S{isSuccess=!1;errorMessage;constructor(t){this.errorMessage=t}}class c{static create(t,e){return new c(t,e)}static mapObject(t,e){const n=Object.entries(t).map(([l,i])=>{const f=e===void 0?void 0:`${e}: ${l}`;return[l,c.create(i,f)]});return Object.fromEntries(n)}#e;#t;constructor(t,e){this.#e=e==null?"":` in ${e}`,this.#t=Array.isArray(t)?[...t]:t}next(){const t=Array.isArray(this.#t)?this.#t.shift():this.#t;if(t===void 0)throw new Error(`No more responses configured${this.#e}.`);return t}}class u{static create(t,e){return new u(t,e)}#e;#t;#s;#n;constructor(t,e){this.#e=t,this.#t=e,this.#s=[],this.#n=s=>this.#s.push(s.detail),this.#e.addEventListener(this.#t,this.#n)}get data(){return this.#s}clear(){const t=[...this.#s];return this.#s.length=0,t}stop(){this.#e.removeEventListener(this.#t,this.#n)}}const h="message";class m extends EventTarget{log(...t){this.dispatchEvent(new CustomEvent(h,{detail:{level:"log",message:t}}))}error(...t){this.dispatchEvent(new CustomEvent(h,{detail:{level:"error",message:t}}))}warn(...t){this.dispatchEvent(new CustomEvent(h,{detail:{level:"warn",message:t}}))}info(...t){this.dispatchEvent(new CustomEvent(h,{detail:{level:"info",message:t}}))}debug(...t){this.dispatchEvent(new CustomEvent(h,{detail:{level:"debug",message:t}}))}trace(...t){this.dispatchEvent(new CustomEvent(h,{detail:{level:"trace",message:t}}))}trackMessages(){return new u(this,h)}}function b(o){const t=c.create(o);return async function(){const e=t.next();if(e instanceof Error)throw e;return new C(e)}}class C{#e;#t;#s;constructor({status:t,statusText:e,body:s=null}){this.#e=t,this.#t=e,this.#s=s}get ok(){return this.status>=200&&this.status<300}get status(){return this.#e}get statusText(){return this.#t}async blob(){if(this.#s==null)return null;if(this.#s instanceof Blob)return this.#s;throw new TypeError("Body is not a Blob.")}async json(){const t=typeof this.#s=="string"?this.#s:JSON.stringify(this.#s);return Promise.resolve(JSON.parse(t))}async text(){return this.#s==null?"":String(this.#s)}}class E extends EventTarget{static create(){return new E(EventSource)}static createNull(){return new E(d)}#e;#t;constructor(t){super(),this.#e=t}get isConnected(){return this.#t?.readyState===this.#e.OPEN}get url(){return this.#t?.url}async connect(t,e="message",...s){await new Promise((n,l)=>{if(this.isConnected){l(new Error("Already connected."));return}try{this.#t=new this.#e(t),this.#t.addEventListener("open",i=>{this.#s(i),n()}),this.#t.addEventListener(e,i=>this.#n(i));for(const i of s)this.#t.addEventListener(i,f=>this.#n(f));this.#t.addEventListener("error",i=>this.#r(i))}catch(i){l(i)}})}send(t,e){throw new Error("Method not implemented.")}async close(){await new Promise((t,e)=>{if(!this.isConnected){t();return}try{this.#t.close(),t()}catch(s){e(s)}})}simulateMessage(t,e="message",s){typeof t!="string"&&(t=JSON.stringify(t)),this.#n(new MessageEvent(e,{data:t,lastEventId:s}))}simulateError(){this.#r(new Event("error"))}#s(t){this.dispatchEvent(new Event(t.type,t))}#n(t){this.dispatchEvent(new MessageEvent(t.type,t))}#r(t){this.dispatchEvent(new Event(t.type,t))}}class d extends EventTarget{static CONNECTING=0;static OPEN=1;static CLOSED=2;url;readyState=d.CONNECTING;constructor(t){super(),this.url=t.toString(),setTimeout(()=>{this.readyState=d.OPEN,this.dispatchEvent(new Event("open"))},0)}close(){this.readyState=d.CLOSED}}const g="heartbeat",y="message-sent";class v extends EventTarget{static create({heartbeat:t=3e4,retry:e=1e3}={}){return new v(t,e,WebSocket)}static createNull({heartbeat:t=0,retry:e=0}={}){return new v(t,e,T)}#e;#t;#s;#n;#r;#i;constructor(t,e,s){super(),this.#e=t,this.#t=e,this.#s=s}get isConnected(){return this.#n?.readyState===WebSocket.OPEN}get url(){return this.#n?.url}async connect(t){await new Promise((e,s)=>{if(this.#c(),this.isConnected){s(new Error("Already connected."));return}try{this.#n=new this.#s(t),this.#n.addEventListener("open",n=>{this.#d(n),e()}),this.#n.addEventListener("message",n=>this.#a(n)),this.#n.addEventListener("close",n=>this.#h(n)),this.#n.addEventListener("error",n=>this.#o(n))}catch(n){s(n)}})}async send(t){if(!this.isConnected)throw new Error("Not connected.");this.#n.send(t),this.dispatchEvent(new CustomEvent(y,{detail:t})),await Promise.resolve()}trackMessageSent(){return u.create(this,y)}async close(t,e){await new Promise(s=>{if(this.#c(),!this.isConnected){s();return}this.#n.addEventListener("close",()=>s()),this.#n.close(t,e)})}simulateMessage(t){typeof t!="string"&&!(t instanceof Blob)&&!(t instanceof ArrayBuffer)&&(t=JSON.stringify(t)),this.#a(new MessageEvent("message",{data:t}))}simulateHeartbeat(){this.#u()}simulateClose(t,e){this.#h(new CloseEvent("close",{code:t,reason:e}))}simulateError(){this.#n?.close(),this.#o(new Event("error"))}#d(t){this.dispatchEvent(new Event(t.type,t)),this.#E()}#a(t){this.dispatchEvent(new MessageEvent(t.type,t))}#h(t){this.#v(),this.dispatchEvent(new CloseEvent(t.type,t))}#o(t){this.dispatchEvent(new Event(t.type,t)),this.#l()}#l(){this.#t<=0||(this.#i=setInterval(()=>this.connect(this.#n.url),this.#t))}#c(){clearInterval(this.#i),this.#i=void 0}#E(){this.#e<=0||(this.#r=setInterval(()=>this.#u(),this.#e))}#v(){clearInterval(this.#r),this.#r=void 0}#u(){this.#r!=null&&this.send(g)}}class T extends EventTarget{url;readyState=WebSocket.CONNECTING;constructor(t){super(),this.url=t.toString(),setTimeout(()=>{this.readyState=WebSocket.OPEN,this.dispatchEvent(new Event("open"))},0)}send(){}close(){this.readyState=WebSocket.CLOSED,this.dispatchEvent(new Event("close"))}}r.Clock=a,r.ConfigurableResponses=c,r.ConsoleStub=m,r.EventTracker=w,r.Failure=S,r.HEARTBEAT_TYPE=g,r.OutputTracker=u,r.SseClient=E,r.Success=p,r.WebSocketClient=v,r.createFetchStub=b,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=shared.umd.cjs.map
|
package/dist/shared.umd.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.umd.cjs","sources":["../src/common/clock.ts","../src/common/configurable_responses.ts","../src/common/event_tracker.ts","../src/common/output_tracker.ts","../src/domain/messages.ts","../src/infrastructure/console_stub.ts","../src/infrastructure/fetch_stub.ts","../src/infrastructure/sse_client.ts","../src/infrastructure/web_socket_client.ts"],"sourcesContent":["// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * A clock provides access to the current timestamp.\n */\nexport class Clock {\n /**\n * Create a clock using system the clock.\n *\n * @return A clock that uses the system clock.\n */\n static system(): Clock {\n return new Clock();\n }\n\n /**\n * Create a clock using a fixed date.\n *\n * @param date The fixed date of the clock.\n * @return A clock that always returns a fixed date.\n */\n static fixed(date: Date | string | number): Clock {\n return new Clock(new Date(date));\n }\n\n /**\n * Create a clock that returns a fixed offset from the given clock.\n *\n * @param clock The clock to offset from.\n * @param offsetMillis The offset in milliseconds.\n * @return A clock that returns a fixed offset from the given clock.\n */\n static offset(clock: Clock, offsetMillis: number): Clock {\n return new Clock(new Date(clock.millis() + offsetMillis));\n }\n\n readonly #date?: Date;\n\n private constructor(date?: Date) {\n this.#date = date;\n }\n\n /**\n * Return the current timestamp of the clock.\n *\n * @return The current timestamp.\n */\n date(): Date {\n return this.#date ? new Date(this.#date) : new Date();\n }\n\n /**\n * Return the current timestamp of the clock in milliseconds.\n *\n * @return The current timestamp in milliseconds.\n */\n millis(): number {\n return this.date().getTime();\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n// Copyright 2023 Titanium I.T. LLC. MIT License.\n\n/**\n * Handle returning pre-configured responses.\n *\n * This is one of the nullability patterns from James Shore's article on\n * [testing without mocks](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#configurable-responses).\n *\n * Example usage for stubbing `fetch` function:\n *\n * ```javascript\n * function createFetchStub(responses) {\n * const configurableResponses = ConfigurableResponses.create(responses);\n * return async function () {\n * const response = configurableResponses.next();\n * return {\n * status: response.status,\n * json: async () => response.body,\n * };\n * };\n * }\n * ```\n */\nexport class ConfigurableResponses<T = unknown> {\n /**\n * Create a list of responses (by providing an array), or a single repeating\n * response (by providing any other type). 'Name' is optional and used in\n * error messages.\n *\n * @param responses A single response or an array of responses.\n * @param name An optional name for the responses.\n */\n static create<T>(responses?: T | T[], name?: string) {\n return new ConfigurableResponses<T>(responses, name);\n }\n\n /**\n * Convert all properties in an object into ConfigurableResponse instances.\n * For example, { a: 1 } becomes { a: ConfigurableResponses.create(1) }.\n * 'Name' is optional and used in error messages.\n *\n * @param responseObject An object with single response or an array of responses.\n * @param name An optional name for the responses.\n */\n static mapObject<T extends Record<string, unknown>>(\n responseObject: T,\n name?: string,\n ) {\n const entries = Object.entries(responseObject);\n const translatedEntries = entries.map(([key, value]) => {\n const translatedName = name === undefined ? undefined : `${name}: ${key}`;\n return [key, ConfigurableResponses.create(value, translatedName)];\n });\n return Object.fromEntries(translatedEntries);\n }\n\n readonly #description;\n readonly #responses;\n\n /**\n * Create a list of responses (by providing an array), or a single repeating\n * response (by providing any other type). 'Name' is optional and used in\n * error messages.\n *\n * @param responses A single response or an array of responses.\n * @param name An optional name for the responses.\n */\n constructor(responses?: T | T[], name?: string) {\n this.#description = name == null ? \"\" : ` in ${name}`;\n this.#responses = Array.isArray(responses) ? [...responses] : responses;\n }\n\n /**\n * Get the next configured response. Throws an error when configured with a list\n * of responses and no more responses remain.\n *\n * @return The next response.\n */\n next(): T {\n const response = Array.isArray(this.#responses)\n ? this.#responses.shift()\n : this.#responses;\n if (response === undefined) {\n throw new Error(`No more responses configured${this.#description}.`);\n }\n\n return response;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * Track events from an event target.\n *\n * Wait asynchronously for events. Useful in test code.\n */\nexport class EventTracker<T extends Event> {\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n static create<T extends Event>(eventTarget: EventTarget, event: string) {\n return new EventTracker<T>(eventTarget, event);\n }\n\n readonly #eventTarget;\n readonly #event;\n readonly #events: T[];\n readonly #tracker;\n\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n constructor(eventTarget: EventTarget, event: string) {\n this.#eventTarget = eventTarget;\n this.#event = event;\n this.#events = [];\n this.#tracker = (event: Event) => this.#events.push(event as T);\n\n this.#eventTarget.addEventListener(this.#event, this.#tracker);\n }\n\n /**\n * Return the tracked events.\n *\n * @return The tracked events.\n */\n get events(): T[] {\n return this.#events;\n }\n\n /**\n * Clear the tracked events and return the cleared events.\n *\n * @return The cleared events.\n */\n clear(): T[] {\n const result = [...this.#events];\n this.#events.length = 0;\n return result;\n }\n\n /**\n * Stop tracking.\n */\n stop() {\n this.#eventTarget.removeEventListener(this.#event, this.#tracker);\n }\n\n /**\n * Wait asynchronously for a number of events.\n *\n * @param count number of events, default 1.\n */\n async waitFor(count = 1) {\n return new Promise<T[]>((resolve) => {\n const checkEvents = () => {\n if (this.#events.length >= count) {\n this.#eventTarget.removeEventListener(this.#event, checkEvents);\n resolve(this.events);\n }\n };\n\n this.#eventTarget.addEventListener(this.#event, checkEvents);\n checkEvents();\n });\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n// Copyright 2020-2022 Titanium I.T. LLC. MIT License.\n\n/**\n * Track output events.\n *\n * This is one of the nullability patterns from James Shore's article on\n * [testing without mocks](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking).\n *\n * Example implementation of an event store:\n *\n * ```javascript\n * async record(event) {\n * // ...\n * this.dispatchEvent(new CustomEvent(\"eventRecorded\", { detail: event }));\n * }\n *\n * trackEventsRecorded() {\n * return new OutputTracker(this, \"eventRecorded\");\n * }\n * ```\n *\n * Example usage:\n *\n * ```javascript\n * const eventsRecorded = eventStore.trackEventsRecorded();\n * // ...\n * const data = eventsRecorded.data(); // [event1, event2, ...]\n * ```\n */\nexport class OutputTracker<T = unknown> {\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n static create<T>(eventTarget: EventTarget, event: string) {\n return new OutputTracker<T>(eventTarget, event);\n }\n\n readonly #eventTarget;\n readonly #event;\n readonly #data: T[];\n readonly #tracker;\n\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n constructor(eventTarget: EventTarget, event: string) {\n this.#eventTarget = eventTarget;\n this.#event = event;\n this.#data = [];\n this.#tracker = (event: Event) =>\n this.#data.push((event as CustomEvent<T>).detail);\n\n this.#eventTarget.addEventListener(this.#event, this.#tracker);\n }\n\n /**\n * Return the tracked data.\n *\n * @return The tracked data.\n */\n get data(): T[] {\n return this.#data;\n }\n\n /**\n * Clear the tracked data and return the cleared data.\n *\n * @return The cleared data.\n */\n clear(): T[] {\n const result = [...this.#data];\n this.#data.length = 0;\n return result;\n }\n\n /**\n * Stop tracking.\n */\n stop() {\n this.#eventTarget.removeEventListener(this.#event, this.#tracker);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * Provides CQNS features.\n *\n * The Command Query Notification Separation principle is a software design\n * principle that separates the concerns of commands, queries, and\n * notifications.\n *\n * Message hierarchy:\n *\n * - Message\n * - Incoming / outgoing\n * - Request (outgoing) -> response (incoming)\n * - Command -> command status\n * - Query -> query result\n * - Notification\n * - Incoming: notification -> commands\n * - Outgoing\n * - Event (internal)\n *\n * @see https://ralfw.de/command-query-notification-separation-cqns/\n * @module\n */\n\n/**\n * The status returned by a command handler.\n */\nexport type CommandStatus = Success | Failure;\n\n/**\n * A successful status.\n */\nexport class Success {\n readonly isSuccess = true;\n}\n\n/**\n * A failed status.\n */\nexport class Failure {\n readonly isSuccess = false;\n errorMessage: string;\n\n /**\n * Creates a failed status.\n *\n * @param errorMessage\n */\n constructor(errorMessage: string) {\n this.errorMessage = errorMessage;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { OutputTracker } from \"../common/output_tracker\";\n\nconst MESSAGE_EVENT = \"message\";\n\nexport interface ConsoleMessage {\n level: \"log\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\";\n message: unknown[];\n}\n\n/**\n * A stub for the console interface.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API\n */\nexport class ConsoleStub extends EventTarget {\n log(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"log\", message: data },\n }),\n );\n }\n\n error(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"error\", message: data },\n }),\n );\n }\n\n warn(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"warn\", message: data },\n }),\n );\n }\n\n info(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"info\", message: data },\n }),\n );\n }\n\n debug(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"debug\", message: data },\n }),\n );\n }\n\n trace(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"trace\", message: data },\n }),\n );\n }\n\n /**\n * Track the console messages.\n */\n trackMessages() {\n return new OutputTracker<ConsoleMessage>(this, MESSAGE_EVENT);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { ConfigurableResponses } from \"../common/configurable_responses\";\n\n/**\n * This data object configures the response of a fetch stub call.\n */\nexport interface ResponseData {\n /** The HTTP status code. */\n status: number;\n\n /** The HTTP status text. */\n statusText: string;\n\n /** The optional response body. */\n body?: Blob | object | string | null;\n}\n\n/**\n * Create a fetch stub.\n *\n * The stub returns a response from the provided response data or throws an provided error.\n *\n * @param responses A single response or an array of responses.\n * @returns The fetch stub.\n */\nexport function createFetchStub(\n responses?: ResponseData | Error | (ResponseData | Error)[],\n): () => Promise<Response> {\n const configurableResponses = ConfigurableResponses.create(responses);\n return async function () {\n const response = configurableResponses.next();\n if (response instanceof Error) {\n throw response;\n }\n\n return new ResponseStub(response) as unknown as Response;\n };\n}\n\nclass ResponseStub {\n #status: number;\n #statusText: string;\n #body?: Blob | object | string | null;\n\n constructor({ status, statusText, body = null }: ResponseData) {\n this.#status = status;\n this.#statusText = statusText;\n this.#body = body;\n }\n\n get ok() {\n return this.status >= 200 && this.status < 300;\n }\n\n get status() {\n return this.#status;\n }\n\n get statusText() {\n return this.#statusText;\n }\n\n async blob() {\n if (this.#body == null) {\n return null;\n }\n\n if (this.#body instanceof Blob) {\n return this.#body;\n }\n\n throw new TypeError(\"Body is not a Blob.\");\n }\n\n async json() {\n const json =\n typeof this.#body === \"string\" ? this.#body : JSON.stringify(this.#body);\n return Promise.resolve(JSON.parse(json));\n }\n\n async text() {\n if (this.#body == null) {\n return \"\";\n }\n\n return String(this.#body);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport type { MessageClient } from \"./message_client\";\n\n/**\n * A client for the server-sent events protocol.\n */\nexport class SseClient extends EventTarget implements MessageClient {\n /**\n * Create an SSE client.\n *\n * @return A new SSE client.\n */\n static create(): SseClient {\n return new SseClient(EventSource);\n }\n\n /**\n * Create a nulled SSE client.\n *\n * @return A new SSE client.\n */\n static createNull(): SseClient {\n return new SseClient(EventSourceStub as typeof EventSource);\n }\n\n readonly #eventSourceConstructor: typeof EventSource;\n\n #eventSource?: EventSource;\n\n private constructor(eventSourceConstructor: typeof EventSource) {\n super();\n this.#eventSourceConstructor = eventSourceConstructor;\n }\n\n get isConnected(): boolean {\n return this.#eventSource?.readyState === this.#eventSourceConstructor.OPEN;\n }\n\n get url(): string | undefined {\n return this.#eventSource?.url;\n }\n\n async connect(\n url: string | URL,\n eventName = \"message\",\n ...otherEvents: string[]\n ): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (this.isConnected) {\n reject(new Error(\"Already connected.\"));\n return;\n }\n\n try {\n this.#eventSource = new this.#eventSourceConstructor(url);\n this.#eventSource.addEventListener(\"open\", (e) => {\n this.#handleOpen(e);\n resolve();\n });\n this.#eventSource.addEventListener(eventName, (e) =>\n this.#handleMessage(e),\n );\n for (const otherEvent of otherEvents) {\n this.#eventSource.addEventListener(otherEvent, (e) =>\n this.#handleMessage(e),\n );\n }\n this.#eventSource.addEventListener(\"error\", (e) =>\n this.#handleError(e),\n );\n } catch (error) {\n reject(error);\n }\n });\n }\n\n send(_message: string, _type?: string): Promise<void> {\n throw new Error(\"Method not implemented.\");\n }\n\n async close(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (!this.isConnected) {\n resolve();\n return;\n }\n\n try {\n this.#eventSource!.close();\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Simulate a message event from the server.\n *\n * @param message The message to receive.\n * @param eventName The optional event type.\n * @param lastEventId The optional last event ID.\n */\n simulateMessage(\n message: string | number | boolean | object | null,\n eventName = \"message\",\n lastEventId?: string,\n ) {\n if (typeof message !== \"string\") {\n message = JSON.stringify(message);\n }\n this.#handleMessage(\n new MessageEvent(eventName, { data: message, lastEventId }),\n );\n }\n\n /**\n * Simulate an error event.\n */\n simulateError() {\n this.#handleError(new Event(\"error\"));\n }\n\n #handleOpen(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n }\n\n #handleMessage(event: MessageEvent) {\n this.dispatchEvent(\n new MessageEvent(event.type, event as unknown as MessageEventInit),\n );\n }\n\n #handleError(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n }\n}\n\nclass EventSourceStub extends EventTarget {\n // The constants have to be defined here because Node.js support is currently\n // experimental only.\n static CONNECTING = 0;\n static OPEN = 1;\n static CLOSED = 2;\n\n url: string;\n readyState = EventSourceStub.CONNECTING;\n\n constructor(url: string | URL) {\n super();\n this.url = url.toString();\n setTimeout(() => {\n this.readyState = EventSourceStub.OPEN;\n this.dispatchEvent(new Event(\"open\"));\n }, 0);\n }\n\n close() {\n this.readyState = EventSourceStub.CLOSED;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { OutputTracker } from \"../common/output_tracker\";\nimport type { MessageClient } from \"./message_client\";\n\nexport const HEARTBEAT_TYPE = \"heartbeat\";\n\nconst MESSAGE_SENT_EVENT = \"message-sent\";\n\n/**\n * Options for the WebSocket client.\n */\nexport interface WebSocketOptions {\n /**\n * The heartbeat interval in milliseconds. A value <= 0 disables the\n * heartbeat.\n */\n heartbeat?: number;\n\n /**\n * The time in milliseconds to wait before retrying a connection after an\n * error. A value <= 0 disables automatic retries.\n */\n retry?: number;\n}\n\n/**\n * A client for the WebSocket protocol.\n */\nexport class WebSocketClient extends EventTarget implements MessageClient {\n /**\n * Create a WebSocket client.\n *\n * @param options The options for the WebSocket client.\n * @return A new WebSocket client.\n */\n static create({\n heartbeat = 30000,\n retry = 1000,\n }: WebSocketOptions = {}): WebSocketClient {\n return new WebSocketClient(heartbeat, retry, WebSocket);\n }\n\n /**\n * Create a nulled WebSocket client.\n *\n * @param options The options for the WebSocket client.\n * @return A new nulled WebSocket client.\n */\n static createNull({ heartbeat = 0, retry = 0 }: WebSocketOptions = {}) {\n return new WebSocketClient(\n heartbeat,\n retry,\n WebSocketStub as unknown as typeof WebSocket,\n );\n }\n\n readonly #heartbeat: number;\n readonly #retry: number;\n readonly #webSocketConstructor: typeof WebSocket;\n\n #webSocket?: WebSocket;\n #heartbeatId?: ReturnType<typeof setTimeout>;\n #retryId?: ReturnType<typeof setTimeout>;\n\n private constructor(\n heartbeat: number,\n retry: number,\n webSocketConstructor: typeof WebSocket,\n ) {\n super();\n this.#heartbeat = heartbeat;\n this.#retry = retry;\n this.#webSocketConstructor = webSocketConstructor;\n }\n\n get isConnected(): boolean {\n return this.#webSocket?.readyState === WebSocket.OPEN;\n }\n\n get url(): string | undefined {\n return this.#webSocket?.url;\n }\n\n async connect(url: string | URL): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.#stopRetry();\n\n if (this.isConnected) {\n reject(new Error(\"Already connected.\"));\n return;\n }\n\n try {\n this.#webSocket = new this.#webSocketConstructor(url);\n this.#webSocket.addEventListener(\"open\", (e) => {\n this.#handleOpen(e);\n resolve();\n });\n this.#webSocket.addEventListener(\"message\", (e) =>\n this.#handleMessage(e),\n );\n this.#webSocket.addEventListener(\"close\", (e) => this.#handleClose(e));\n this.#webSocket.addEventListener(\"error\", (e) => this.#handleError(e));\n } catch (error) {\n reject(error);\n }\n });\n }\n\n async send(\n message: string | ArrayBuffer | Blob | ArrayBufferView,\n ): Promise<void> {\n if (!this.isConnected) {\n throw new Error(\"Not connected.\");\n }\n\n this.#webSocket!.send(message);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_SENT_EVENT, { detail: message }),\n );\n await Promise.resolve();\n }\n\n /**\n * Return a tracker for messages sent.\n *\n * @return A new output tracker.\n */\n trackMessageSent(): OutputTracker<string> {\n return OutputTracker.create(this, MESSAGE_SENT_EVENT);\n }\n\n /**\n * Close the connection.\n *\n * If a code is provided, also a reason should be provided.\n *\n * @param code An optional code.\n * @param reason An optional reason.\n */\n async close(code?: number, reason?: string): Promise<void> {\n await new Promise<void>((resolve) => {\n this.#stopRetry();\n\n if (!this.isConnected) {\n resolve();\n return;\n }\n\n this.#webSocket!.addEventListener(\"close\", () => resolve());\n this.#webSocket!.close(code, reason);\n });\n }\n\n /**\n * Simulate a message event from the server.\n *\n * @param message The message to receive.\n */\n simulateMessage(message: string | number | boolean | object | null) {\n if (typeof message !== \"string\") {\n message = JSON.stringify(message);\n }\n this.#handleMessage(new MessageEvent(\"message\", { data: message }));\n }\n\n /**\n * Simulate a heartbeat.\n */\n simulateHeartbeat() {\n this.#sendHeartbeat();\n }\n\n /**\n * Simulate a close event.\n *\n * @param code An optional code.\n * @param reason An optional reason.\n */\n simulateClose(code?: number, reason?: string) {\n this.#handleClose(new CloseEvent(\"close\", { code, reason }));\n }\n\n /**\n * Simulate an error event.\n */\n simulateError() {\n this.#webSocket?.close();\n this.#handleError(new Event(\"error\"));\n }\n\n #handleOpen(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n this.#startHeartbeat();\n }\n\n #handleMessage(event: MessageEvent) {\n this.dispatchEvent(\n new MessageEvent(event.type, event as unknown as MessageEventInit),\n );\n }\n\n #handleClose(event: CloseEvent) {\n this.#stopHeartbeat();\n this.dispatchEvent(new CloseEvent(event.type, event));\n }\n\n #handleError(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n this.#startRetry();\n }\n\n #startRetry() {\n if (this.#retry <= 0) {\n return;\n }\n this.#retryId = setInterval(\n () => this.connect(this.#webSocket!.url),\n this.#retry,\n );\n }\n\n #stopRetry() {\n clearInterval(this.#retryId);\n this.#retryId = undefined;\n }\n\n #startHeartbeat() {\n if (this.#heartbeat <= 0) {\n return;\n }\n\n this.#heartbeatId = setInterval(\n () => this.#sendHeartbeat(),\n this.#heartbeat,\n );\n }\n\n #stopHeartbeat() {\n clearInterval(this.#heartbeatId);\n this.#heartbeatId = undefined;\n }\n\n #sendHeartbeat() {\n if (this.#heartbeatId == null) {\n return;\n }\n\n void this.send(HEARTBEAT_TYPE);\n }\n}\n\nclass WebSocketStub extends EventTarget {\n url: string;\n readyState: number = WebSocket.CONNECTING;\n\n constructor(url: string | URL) {\n super();\n this.url = url.toString();\n setTimeout(() => {\n this.readyState = WebSocket.OPEN;\n this.dispatchEvent(new Event(\"open\"));\n }, 0);\n }\n\n send() {}\n\n close() {\n this.readyState = WebSocket.CLOSED;\n this.dispatchEvent(new Event(\"close\"));\n }\n}\n"],"names":["Clock","date","clock","offsetMillis","#date","ConfigurableResponses","responses","name","responseObject","translatedEntries","key","value","translatedName","#description","#responses","response","EventTracker","eventTarget","event","#eventTarget","#event","#events","#tracker","result","count","resolve","checkEvents","OutputTracker","#data","Success","Failure","errorMessage","MESSAGE_EVENT","ConsoleStub","data","createFetchStub","configurableResponses","ResponseStub","#status","#statusText","#body","status","statusText","body","json","SseClient","EventSourceStub","#eventSourceConstructor","#eventSource","eventSourceConstructor","url","eventName","otherEvents","reject","e","#handleOpen","#handleMessage","otherEvent","#handleError","error","_message","_type","message","lastEventId","HEARTBEAT_TYPE","MESSAGE_SENT_EVENT","WebSocketClient","heartbeat","retry","WebSocketStub","#heartbeat","#retry","#webSocketConstructor","#webSocket","#heartbeatId","#retryId","webSocketConstructor","#stopRetry","#handleClose","code","reason","#sendHeartbeat","#startHeartbeat","#stopHeartbeat","#startRetry"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"shared.umd.cjs","sources":["../src/common/clock.ts","../src/common/event_tracker.ts","../src/domain/messages.ts","../src/infrastructure/configurable_responses.ts","../src/infrastructure/output_tracker.ts","../src/infrastructure/console_stub.ts","../src/infrastructure/fetch_stub.ts","../src/infrastructure/sse_client.ts","../src/infrastructure/web_socket_client.ts"],"sourcesContent":["// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * A clock provides access to the current timestamp.\n */\nexport class Clock {\n /**\n * Create a clock using system the clock.\n *\n * @return A clock that uses the system clock.\n */\n static system(): Clock {\n return new Clock();\n }\n\n /**\n * Create a clock using a fixed date.\n *\n * @param date The fixed date of the clock.\n * @return A clock that always returns a fixed date.\n */\n static fixed(date: Date | string | number): Clock {\n return new Clock(new Date(date));\n }\n\n /**\n * Create a clock that returns a fixed offset from the given clock.\n *\n * @param clock The clock to offset from.\n * @param offsetMillis The offset in milliseconds.\n * @return A clock that returns a fixed offset from the given clock.\n */\n static offset(clock: Clock, offsetMillis: number): Clock {\n return new Clock(new Date(clock.millis() + offsetMillis));\n }\n\n readonly #date?: Date;\n\n private constructor(date?: Date) {\n this.#date = date;\n }\n\n /**\n * Return the current timestamp of the clock.\n *\n * @return The current timestamp.\n */\n date(): Date {\n return this.#date ? new Date(this.#date) : new Date();\n }\n\n /**\n * Return the current timestamp of the clock in milliseconds.\n *\n * @return The current timestamp in milliseconds.\n */\n millis(): number {\n return this.date().getTime();\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n// TODO monitor multiple events\n\n/**\n * Track events from an event target.\n *\n * Wait asynchronously for events. Useful in test code.\n */\nexport class EventTracker<T extends Event> {\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n static create<T extends Event>(eventTarget: EventTarget, ...event: string[]) {\n return new EventTracker<T>(eventTarget, event);\n }\n\n readonly #eventTarget;\n readonly #event;\n readonly #events: T[];\n readonly #tracker;\n\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n constructor(eventTarget: EventTarget, event: string[]) {\n this.#eventTarget = eventTarget;\n this.#event = event;\n this.#events = [];\n this.#tracker = (event: Event) => this.#events.push(event as T);\n\n this.#event.forEach((event) =>\n this.#eventTarget.addEventListener(event, this.#tracker),\n );\n }\n\n /**\n * Return the tracked events.\n *\n * @return The tracked events.\n */\n get events(): T[] {\n return this.#events;\n }\n\n /**\n * Clear the tracked events and return the cleared events.\n *\n * @return The cleared events.\n */\n clear(): T[] {\n const result = [...this.#events];\n this.#events.length = 0;\n return result;\n }\n\n /**\n * Stop tracking.\n */\n stop() {\n this.#event.forEach((event) =>\n this.#eventTarget.removeEventListener(event, this.#tracker),\n );\n }\n\n /**\n * Wait asynchronously for a number of events.\n *\n * @param count number of events, default 1.\n */\n async waitFor(count = 1) {\n return new Promise<T[]>((resolve) => {\n const checkEvents = () => {\n if (this.#events.length >= count) {\n this.#event.forEach((event) =>\n this.#eventTarget.removeEventListener(event, checkEvents),\n );\n resolve(this.events);\n }\n };\n\n this.#event.forEach((event) =>\n this.#eventTarget.addEventListener(event, checkEvents),\n );\n checkEvents();\n });\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\n/**\n * Provides CQNS features.\n *\n * The Command Query Notification Separation principle is a software design\n * principle that separates the concerns of commands, queries, and\n * notifications.\n *\n * Message hierarchy:\n *\n * - Message\n * - Incoming / outgoing\n * - Request (outgoing) -> response (incoming)\n * - Command -> command status\n * - Query -> query result\n * - Notification\n * - Incoming: notification -> commands\n * - Outgoing\n * - Event (internal)\n *\n * @see https://ralfw.de/command-query-notification-separation-cqns/\n * @module\n */\n\n/**\n * The status returned by a command handler.\n */\nexport type CommandStatus = Success | Failure;\n\n/**\n * A successful status.\n */\nexport class Success {\n readonly isSuccess = true;\n}\n\n/**\n * A failed status.\n */\nexport class Failure {\n readonly isSuccess = false;\n errorMessage: string;\n\n /**\n * Creates a failed status.\n *\n * @param errorMessage\n */\n constructor(errorMessage: string) {\n this.errorMessage = errorMessage;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n// Copyright 2023 Titanium I.T. LLC. MIT License.\n\n/**\n * Handle returning pre-configured responses.\n *\n * This is one of the nullability patterns from James Shore's article on\n * [testing without mocks](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#configurable-responses).\n *\n * Example usage for stubbing `fetch` function:\n *\n * ```javascript\n * function createFetchStub(responses) {\n * const configurableResponses = ConfigurableResponses.create(responses);\n * return async function () {\n * const response = configurableResponses.next();\n * return {\n * status: response.status,\n * json: async () => response.body,\n * };\n * };\n * }\n * ```\n */\nexport class ConfigurableResponses<T = unknown> {\n /**\n * Create a list of responses (by providing an array), or a single repeating\n * response (by providing any other type). 'Name' is optional and used in\n * error messages.\n *\n * @param responses A single response or an array of responses.\n * @param name An optional name for the responses.\n */\n static create<T>(responses?: T | T[], name?: string) {\n return new ConfigurableResponses<T>(responses, name);\n }\n\n /**\n * Convert all properties in an object into ConfigurableResponse instances.\n * For example, { a: 1 } becomes { a: ConfigurableResponses.create(1) }.\n * 'Name' is optional and used in error messages.\n *\n * @param responseObject An object with single response or an array of responses.\n * @param name An optional name for the responses.\n */\n static mapObject<T extends Record<string, unknown>>(\n responseObject: T,\n name?: string,\n ) {\n const entries = Object.entries(responseObject);\n const translatedEntries = entries.map(([key, value]) => {\n const translatedName = name === undefined ? undefined : `${name}: ${key}`;\n return [key, ConfigurableResponses.create(value, translatedName)];\n });\n return Object.fromEntries(translatedEntries);\n }\n\n readonly #description;\n readonly #responses;\n\n /**\n * Create a list of responses (by providing an array), or a single repeating\n * response (by providing any other type). 'Name' is optional and used in\n * error messages.\n *\n * @param responses A single response or an array of responses.\n * @param name An optional name for the responses.\n */\n constructor(responses?: T | T[], name?: string) {\n this.#description = name == null ? \"\" : ` in ${name}`;\n this.#responses = Array.isArray(responses) ? [...responses] : responses;\n }\n\n /**\n * Get the next configured response. Throws an error when configured with a list\n * of responses and no more responses remain.\n *\n * @return The next response.\n */\n next(): T {\n const response = Array.isArray(this.#responses)\n ? this.#responses.shift()\n : this.#responses;\n if (response === undefined) {\n throw new Error(`No more responses configured${this.#description}.`);\n }\n\n return response;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n// Copyright 2020-2022 Titanium I.T. LLC. MIT License.\n\n/**\n * Track output events.\n *\n * This is one of the nullability patterns from James Shore's article on\n * [testing without mocks](https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#output-tracking).\n *\n * Example implementation of an event store:\n *\n * ```javascript\n * async record(event) {\n * // ...\n * this.dispatchEvent(new CustomEvent(\"eventRecorded\", { detail: event }));\n * }\n *\n * trackEventsRecorded() {\n * return new OutputTracker(this, \"eventRecorded\");\n * }\n * ```\n *\n * Example usage:\n *\n * ```javascript\n * const eventsRecorded = eventStore.trackEventsRecorded();\n * // ...\n * const data = eventsRecorded.data(); // [event1, event2, ...]\n * ```\n */\nexport class OutputTracker<T = unknown> {\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n static create<T>(eventTarget: EventTarget, event: string) {\n return new OutputTracker<T>(eventTarget, event);\n }\n\n readonly #eventTarget;\n readonly #event;\n readonly #data: T[];\n readonly #tracker;\n\n /**\n * Create a tracker for a specific event of an event target.\n *\n * @param eventTarget The target to track.\n * @param event The event name to track.\n */\n constructor(eventTarget: EventTarget, event: string) {\n this.#eventTarget = eventTarget;\n this.#event = event;\n this.#data = [];\n this.#tracker = (event: Event) =>\n this.#data.push((event as CustomEvent<T>).detail);\n\n this.#eventTarget.addEventListener(this.#event, this.#tracker);\n }\n\n /**\n * Return the tracked data.\n *\n * @return The tracked data.\n */\n get data(): T[] {\n return this.#data;\n }\n\n /**\n * Clear the tracked data and return the cleared data.\n *\n * @return The cleared data.\n */\n clear(): T[] {\n const result = [...this.#data];\n this.#data.length = 0;\n return result;\n }\n\n /**\n * Stop tracking.\n */\n stop() {\n this.#eventTarget.removeEventListener(this.#event, this.#tracker);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { OutputTracker } from \"./output_tracker\";\n\nconst MESSAGE_EVENT = \"message\";\n\nexport interface ConsoleMessage {\n level: \"log\" | \"error\" | \"warn\" | \"info\" | \"debug\" | \"trace\";\n message: unknown[];\n}\n\n/**\n * A stub for the console interface.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API\n */\nexport class ConsoleStub extends EventTarget {\n log(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"log\", message: data },\n }),\n );\n }\n\n error(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"error\", message: data },\n }),\n );\n }\n\n warn(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"warn\", message: data },\n }),\n );\n }\n\n info(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"info\", message: data },\n }),\n );\n }\n\n debug(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"debug\", message: data },\n }),\n );\n }\n\n trace(...data: unknown[]) {\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"trace\", message: data },\n }),\n );\n }\n\n /**\n * Track the console messages.\n */\n trackMessages() {\n return new OutputTracker<ConsoleMessage>(this, MESSAGE_EVENT);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { ConfigurableResponses } from \"./configurable_responses\";\n\n/**\n * This data object configures the response of a fetch stub call.\n */\nexport interface ResponseData {\n /** The HTTP status code. */\n status: number;\n\n /** The HTTP status text. */\n statusText: string;\n\n /** The optional response body. */\n body?: Blob | object | string | null;\n}\n\n/**\n * Create a fetch stub.\n *\n * The stub returns a response from the provided response data or throws an provided error.\n *\n * @param responses A single response or an array of responses.\n * @returns The fetch stub.\n */\nexport function createFetchStub(\n responses?: ResponseData | Error | (ResponseData | Error)[],\n): () => Promise<Response> {\n const configurableResponses = ConfigurableResponses.create(responses);\n return async function () {\n const response = configurableResponses.next();\n if (response instanceof Error) {\n throw response;\n }\n\n return new ResponseStub(response) as unknown as Response;\n };\n}\n\nclass ResponseStub {\n #status: number;\n #statusText: string;\n #body?: Blob | object | string | null;\n\n constructor({ status, statusText, body = null }: ResponseData) {\n this.#status = status;\n this.#statusText = statusText;\n this.#body = body;\n }\n\n get ok() {\n return this.status >= 200 && this.status < 300;\n }\n\n get status() {\n return this.#status;\n }\n\n get statusText() {\n return this.#statusText;\n }\n\n async blob() {\n if (this.#body == null) {\n return null;\n }\n\n if (this.#body instanceof Blob) {\n return this.#body;\n }\n\n throw new TypeError(\"Body is not a Blob.\");\n }\n\n async json() {\n const json =\n typeof this.#body === \"string\" ? this.#body : JSON.stringify(this.#body);\n return Promise.resolve(JSON.parse(json));\n }\n\n async text() {\n if (this.#body == null) {\n return \"\";\n }\n\n return String(this.#body);\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport type { MessageClient } from \"./message_client\";\n\n/**\n * A client for the server-sent events protocol.\n */\nexport class SseClient extends EventTarget implements MessageClient {\n /**\n * Create an SSE client.\n *\n * @return A new SSE client.\n */\n static create(): SseClient {\n return new SseClient(EventSource);\n }\n\n /**\n * Create a nulled SSE client.\n *\n * @return A new SSE client.\n */\n static createNull(): SseClient {\n return new SseClient(EventSourceStub as typeof EventSource);\n }\n\n readonly #eventSourceConstructor: typeof EventSource;\n\n #eventSource?: EventSource;\n\n private constructor(eventSourceConstructor: typeof EventSource) {\n super();\n this.#eventSourceConstructor = eventSourceConstructor;\n }\n\n get isConnected(): boolean {\n return this.#eventSource?.readyState === this.#eventSourceConstructor.OPEN;\n }\n\n get url(): string | undefined {\n return this.#eventSource?.url;\n }\n\n async connect(\n url: string | URL,\n eventName = \"message\",\n ...otherEvents: string[]\n ): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (this.isConnected) {\n reject(new Error(\"Already connected.\"));\n return;\n }\n\n try {\n this.#eventSource = new this.#eventSourceConstructor(url);\n this.#eventSource.addEventListener(\"open\", (e) => {\n this.#handleOpen(e);\n resolve();\n });\n this.#eventSource.addEventListener(eventName, (e) =>\n this.#handleMessage(e),\n );\n for (const otherEvent of otherEvents) {\n this.#eventSource.addEventListener(otherEvent, (e) =>\n this.#handleMessage(e),\n );\n }\n this.#eventSource.addEventListener(\"error\", (e) =>\n this.#handleError(e),\n );\n } catch (error) {\n reject(error);\n }\n });\n }\n\n send(_message: string, _type?: string): Promise<void> {\n throw new Error(\"Method not implemented.\");\n }\n\n async close(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n if (!this.isConnected) {\n resolve();\n return;\n }\n\n try {\n this.#eventSource!.close();\n resolve();\n } catch (error) {\n reject(error);\n }\n });\n }\n\n /**\n * Simulate a message event from the server.\n *\n * @param message The message to receive.\n * @param eventName The optional event type.\n * @param lastEventId The optional last event ID.\n */\n simulateMessage(\n message: string | number | boolean | object | null,\n eventName = \"message\",\n lastEventId?: string,\n ) {\n if (typeof message !== \"string\") {\n message = JSON.stringify(message);\n }\n this.#handleMessage(\n new MessageEvent(eventName, { data: message, lastEventId }),\n );\n }\n\n /**\n * Simulate an error event.\n */\n simulateError() {\n this.#handleError(new Event(\"error\"));\n }\n\n #handleOpen(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n }\n\n #handleMessage(event: MessageEvent) {\n this.dispatchEvent(\n new MessageEvent(event.type, event as unknown as MessageEventInit),\n );\n }\n\n #handleError(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n }\n}\n\nclass EventSourceStub extends EventTarget {\n // The constants have to be defined here because Node.js support is currently\n // experimental only.\n static CONNECTING = 0;\n static OPEN = 1;\n static CLOSED = 2;\n\n url: string;\n readyState = EventSourceStub.CONNECTING;\n\n constructor(url: string | URL) {\n super();\n this.url = url.toString();\n setTimeout(() => {\n this.readyState = EventSourceStub.OPEN;\n this.dispatchEvent(new Event(\"open\"));\n }, 0);\n }\n\n close() {\n this.readyState = EventSourceStub.CLOSED;\n }\n}\n","// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.\n\nimport { OutputTracker } from \"./output_tracker\";\nimport type { MessageClient } from \"./message_client\";\n\nexport const HEARTBEAT_TYPE = \"heartbeat\";\n\nconst MESSAGE_SENT_EVENT = \"message-sent\";\n\n/**\n * Options for the WebSocket client.\n */\nexport interface WebSocketOptions {\n /**\n * The heartbeat interval in milliseconds. A value <= 0 disables the\n * heartbeat.\n */\n heartbeat?: number;\n\n /**\n * The time in milliseconds to wait before retrying a connection after an\n * error. A value <= 0 disables automatic retries.\n */\n retry?: number;\n}\n\n/**\n * A client for the WebSocket protocol.\n */\nexport class WebSocketClient extends EventTarget implements MessageClient {\n /**\n * Create a WebSocket client.\n *\n * @param options The options for the WebSocket client.\n * @return A new WebSocket client.\n */\n static create({\n heartbeat = 30000,\n retry = 1000,\n }: WebSocketOptions = {}): WebSocketClient {\n return new WebSocketClient(heartbeat, retry, WebSocket);\n }\n\n /**\n * Create a nulled WebSocket client.\n *\n * @param options The options for the WebSocket client.\n * @return A new nulled WebSocket client.\n */\n static createNull({ heartbeat = 0, retry = 0 }: WebSocketOptions = {}) {\n return new WebSocketClient(\n heartbeat,\n retry,\n WebSocketStub as unknown as typeof WebSocket,\n );\n }\n\n readonly #heartbeat: number;\n readonly #retry: number;\n readonly #webSocketConstructor: typeof WebSocket;\n\n #webSocket?: WebSocket;\n #heartbeatId?: ReturnType<typeof setTimeout>;\n #retryId?: ReturnType<typeof setTimeout>;\n\n private constructor(\n heartbeat: number,\n retry: number,\n webSocketConstructor: typeof WebSocket,\n ) {\n super();\n this.#heartbeat = heartbeat;\n this.#retry = retry;\n this.#webSocketConstructor = webSocketConstructor;\n }\n\n get isConnected(): boolean {\n return this.#webSocket?.readyState === WebSocket.OPEN;\n }\n\n get url(): string | undefined {\n return this.#webSocket?.url;\n }\n\n async connect(url: string | URL): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.#stopRetry();\n\n if (this.isConnected) {\n reject(new Error(\"Already connected.\"));\n return;\n }\n\n try {\n this.#webSocket = new this.#webSocketConstructor(url);\n this.#webSocket.addEventListener(\"open\", (e) => {\n this.#handleOpen(e);\n resolve();\n });\n this.#webSocket.addEventListener(\"message\", (e) =>\n this.#handleMessage(e),\n );\n this.#webSocket.addEventListener(\"close\", (e) => this.#handleClose(e));\n this.#webSocket.addEventListener(\"error\", (e) => this.#handleError(e));\n } catch (error) {\n reject(error);\n }\n });\n }\n\n async send(\n message: string | ArrayBuffer | Blob | ArrayBufferView,\n ): Promise<void> {\n if (!this.isConnected) {\n throw new Error(\"Not connected.\");\n }\n\n this.#webSocket!.send(message);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_SENT_EVENT, { detail: message }),\n );\n await Promise.resolve();\n }\n\n /**\n * Return a tracker for messages sent.\n *\n * @return A new output tracker.\n */\n trackMessageSent(): OutputTracker<string> {\n return OutputTracker.create(this, MESSAGE_SENT_EVENT);\n }\n\n /**\n * Close the connection.\n *\n * If a code is provided, also a reason should be provided.\n *\n * @param code An optional code.\n * @param reason An optional reason.\n */\n async close(code?: number, reason?: string): Promise<void> {\n await new Promise<void>((resolve) => {\n this.#stopRetry();\n\n if (!this.isConnected) {\n resolve();\n return;\n }\n\n this.#webSocket!.addEventListener(\"close\", () => resolve());\n this.#webSocket!.close(code, reason);\n });\n }\n\n /**\n * Simulate a message event from the server.\n *\n * @param message The message to receive.\n */\n simulateMessage(\n message: string | number | boolean | object | null | Blob | ArrayBuffer,\n ) {\n if (\n typeof message !== \"string\" &&\n !(message instanceof Blob) &&\n !(message instanceof ArrayBuffer)\n ) {\n message = JSON.stringify(message);\n }\n this.#handleMessage(new MessageEvent(\"message\", { data: message }));\n }\n\n /**\n * Simulate a heartbeat.\n */\n simulateHeartbeat() {\n this.#sendHeartbeat();\n }\n\n /**\n * Simulate a close event.\n *\n * @param code An optional code.\n * @param reason An optional reason.\n */\n simulateClose(code?: number, reason?: string) {\n this.#handleClose(new CloseEvent(\"close\", { code, reason }));\n }\n\n /**\n * Simulate an error event.\n */\n simulateError() {\n this.#webSocket?.close();\n this.#handleError(new Event(\"error\"));\n }\n\n #handleOpen(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n this.#startHeartbeat();\n }\n\n #handleMessage(event: MessageEvent) {\n this.dispatchEvent(\n new MessageEvent(event.type, event as unknown as MessageEventInit),\n );\n }\n\n #handleClose(event: CloseEvent) {\n this.#stopHeartbeat();\n this.dispatchEvent(new CloseEvent(event.type, event));\n }\n\n #handleError(event: Event) {\n this.dispatchEvent(new Event(event.type, event));\n this.#startRetry();\n }\n\n #startRetry() {\n if (this.#retry <= 0) {\n return;\n }\n this.#retryId = setInterval(\n () => this.connect(this.#webSocket!.url),\n this.#retry,\n );\n }\n\n #stopRetry() {\n clearInterval(this.#retryId);\n this.#retryId = undefined;\n }\n\n #startHeartbeat() {\n if (this.#heartbeat <= 0) {\n return;\n }\n\n this.#heartbeatId = setInterval(\n () => this.#sendHeartbeat(),\n this.#heartbeat,\n );\n }\n\n #stopHeartbeat() {\n clearInterval(this.#heartbeatId);\n this.#heartbeatId = undefined;\n }\n\n #sendHeartbeat() {\n if (this.#heartbeatId == null) {\n return;\n }\n\n void this.send(HEARTBEAT_TYPE);\n }\n}\n\nclass WebSocketStub extends EventTarget {\n url: string;\n readyState: number = WebSocket.CONNECTING;\n\n constructor(url: string | URL) {\n super();\n this.url = url.toString();\n setTimeout(() => {\n this.readyState = WebSocket.OPEN;\n this.dispatchEvent(new Event(\"open\"));\n }, 0);\n }\n\n send() {}\n\n close() {\n this.readyState = WebSocket.CLOSED;\n this.dispatchEvent(new Event(\"close\"));\n }\n}\n"],"names":["Clock","date","clock","offsetMillis","#date","EventTracker","eventTarget","event","#eventTarget","#event","#events","#tracker","result","count","resolve","checkEvents","Success","Failure","errorMessage","ConfigurableResponses","responses","name","responseObject","translatedEntries","key","value","translatedName","#description","#responses","response","OutputTracker","#data","MESSAGE_EVENT","ConsoleStub","data","createFetchStub","configurableResponses","ResponseStub","#status","#statusText","#body","status","statusText","body","json","SseClient","EventSourceStub","#eventSourceConstructor","#eventSource","eventSourceConstructor","url","eventName","otherEvents","reject","e","#handleOpen","#handleMessage","otherEvent","#handleError","error","_message","_type","message","lastEventId","HEARTBEAT_TYPE","MESSAGE_SENT_EVENT","WebSocketClient","heartbeat","retry","WebSocketStub","#heartbeat","#retry","#webSocketConstructor","#webSocket","#heartbeatId","#retryId","webSocketConstructor","#stopRetry","#handleClose","code","reason","#sendHeartbeat","#startHeartbeat","#stopHeartbeat","#startRetry"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muspellheim/shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"author": "Falko Schumann",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -25,19 +25,19 @@
|
|
|
25
25
|
"test": "vitest"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@eslint/js": "9.
|
|
29
|
-
"@types/node": "
|
|
30
|
-
"@vitest/coverage-v8": "
|
|
31
|
-
"eslint": "9.
|
|
28
|
+
"@eslint/js": "9.39.1",
|
|
29
|
+
"@types/node": "20.19.25",
|
|
30
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
31
|
+
"eslint": "9.39.1",
|
|
32
32
|
"eslint-plugin-headers": "1.3.3",
|
|
33
|
-
"jsdom": "27.0
|
|
34
|
-
"globals": "16.
|
|
33
|
+
"jsdom": "27.2.0",
|
|
34
|
+
"globals": "16.5.0",
|
|
35
35
|
"prettier": "3.6.2",
|
|
36
36
|
"typedoc": "0.28.14",
|
|
37
|
-
"typescript": "5.
|
|
38
|
-
"typescript-eslint": "8.
|
|
39
|
-
"vite": "7.
|
|
40
|
-
"vitest": "
|
|
37
|
+
"typescript": "5.9.3",
|
|
38
|
+
"typescript-eslint": "8.48.0",
|
|
39
|
+
"vite": "7.2.4",
|
|
40
|
+
"vitest": "3.2.4",
|
|
41
41
|
"vite-plugin-dts": "4.5.4"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
|
+
// TODO monitor multiple events
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Track events from an event target.
|
|
5
7
|
*
|
|
@@ -12,7 +14,7 @@ export class EventTracker<T extends Event> {
|
|
|
12
14
|
* @param eventTarget The target to track.
|
|
13
15
|
* @param event The event name to track.
|
|
14
16
|
*/
|
|
15
|
-
static create<T extends Event>(eventTarget: EventTarget, event: string) {
|
|
17
|
+
static create<T extends Event>(eventTarget: EventTarget, ...event: string[]) {
|
|
16
18
|
return new EventTracker<T>(eventTarget, event);
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -27,13 +29,15 @@ export class EventTracker<T extends Event> {
|
|
|
27
29
|
* @param eventTarget The target to track.
|
|
28
30
|
* @param event The event name to track.
|
|
29
31
|
*/
|
|
30
|
-
constructor(eventTarget: EventTarget, event: string) {
|
|
32
|
+
constructor(eventTarget: EventTarget, event: string[]) {
|
|
31
33
|
this.#eventTarget = eventTarget;
|
|
32
34
|
this.#event = event;
|
|
33
35
|
this.#events = [];
|
|
34
36
|
this.#tracker = (event: Event) => this.#events.push(event as T);
|
|
35
37
|
|
|
36
|
-
this.#
|
|
38
|
+
this.#event.forEach((event) =>
|
|
39
|
+
this.#eventTarget.addEventListener(event, this.#tracker),
|
|
40
|
+
);
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
/**
|
|
@@ -60,7 +64,9 @@ export class EventTracker<T extends Event> {
|
|
|
60
64
|
* Stop tracking.
|
|
61
65
|
*/
|
|
62
66
|
stop() {
|
|
63
|
-
this.#
|
|
67
|
+
this.#event.forEach((event) =>
|
|
68
|
+
this.#eventTarget.removeEventListener(event, this.#tracker),
|
|
69
|
+
);
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
/**
|
|
@@ -72,12 +78,16 @@ export class EventTracker<T extends Event> {
|
|
|
72
78
|
return new Promise<T[]>((resolve) => {
|
|
73
79
|
const checkEvents = () => {
|
|
74
80
|
if (this.#events.length >= count) {
|
|
75
|
-
this.#
|
|
81
|
+
this.#event.forEach((event) =>
|
|
82
|
+
this.#eventTarget.removeEventListener(event, checkEvents),
|
|
83
|
+
);
|
|
76
84
|
resolve(this.events);
|
|
77
85
|
}
|
|
78
86
|
};
|
|
79
87
|
|
|
80
|
-
this.#
|
|
88
|
+
this.#event.forEach((event) =>
|
|
89
|
+
this.#eventTarget.addEventListener(event, checkEvents),
|
|
90
|
+
);
|
|
81
91
|
checkEvents();
|
|
82
92
|
});
|
|
83
93
|
}
|
package/src/common/mod.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
|
-
import { ConfigurableResponses } from "
|
|
3
|
+
import { ConfigurableResponses } from "./configurable_responses";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* This data object configures the response of a fetch stub call.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
|
+
export * from "./configurable_responses";
|
|
3
4
|
export * from "./console_stub";
|
|
4
5
|
export * from "./fetch_stub";
|
|
5
6
|
export * from "./message_client";
|
|
7
|
+
export * from "./output_tracker";
|
|
6
8
|
export * from "./sse_client";
|
|
7
9
|
export * from "./web_socket_client";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
|
-
import { OutputTracker } from "
|
|
3
|
+
import { OutputTracker } from "./output_tracker";
|
|
4
4
|
import type { MessageClient } from "./message_client";
|
|
5
5
|
|
|
6
6
|
export const HEARTBEAT_TYPE = "heartbeat";
|
|
@@ -158,8 +158,14 @@ export class WebSocketClient extends EventTarget implements MessageClient {
|
|
|
158
158
|
*
|
|
159
159
|
* @param message The message to receive.
|
|
160
160
|
*/
|
|
161
|
-
simulateMessage(
|
|
162
|
-
|
|
161
|
+
simulateMessage(
|
|
162
|
+
message: string | number | boolean | object | null | Blob | ArrayBuffer,
|
|
163
|
+
) {
|
|
164
|
+
if (
|
|
165
|
+
typeof message !== "string" &&
|
|
166
|
+
!(message instanceof Blob) &&
|
|
167
|
+
!(message instanceof ArrayBuffer)
|
|
168
|
+
) {
|
|
163
169
|
message = JSON.stringify(message);
|
|
164
170
|
}
|
|
165
171
|
this.#handleMessage(new MessageEvent("message", { data: message }));
|
|
File without changes
|
|
File without changes
|