@muspellheim/shared 0.15.0 → 0.16.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 +17 -8
- package/dist/shared.js +125 -89
- 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 +5 -5
- package/src/common/log.ts +11 -1
- package/src/infrastructure/console_log.ts +144 -0
- package/src/infrastructure/mod.ts +1 -1
- package/src/infrastructure/console_stub.ts +0 -72
package/dist/shared.d.ts
CHANGED
|
@@ -125,17 +125,17 @@ export declare class ConfigurableResponses<T = unknown> {
|
|
|
125
125
|
next(): T;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
export declare interface ConsoleMessage {
|
|
129
|
-
level: "log" | "error" | "warn" | "info" | "debug" | "trace";
|
|
130
|
-
message: unknown[];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
128
|
/**
|
|
134
|
-
*
|
|
129
|
+
* Wraps the console interface and allow setting the log level.
|
|
135
130
|
*
|
|
136
131
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API
|
|
137
132
|
*/
|
|
138
|
-
export declare class
|
|
133
|
+
export declare class ConsoleLog extends EventTarget implements Log {
|
|
134
|
+
#private;
|
|
135
|
+
static create(): ConsoleLog;
|
|
136
|
+
static createNull(): ConsoleLog;
|
|
137
|
+
level: LogLevel;
|
|
138
|
+
private constructor();
|
|
139
139
|
log(...data: unknown[]): void;
|
|
140
140
|
error(...data: unknown[]): void;
|
|
141
141
|
warn(...data: unknown[]): void;
|
|
@@ -146,6 +146,12 @@ export declare class ConsoleStub extends EventTarget {
|
|
|
146
146
|
* Track the console messages.
|
|
147
147
|
*/
|
|
148
148
|
trackMessages(): OutputTracker<ConsoleMessage>;
|
|
149
|
+
isLoggable(level: LogLevel): boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export declare interface ConsoleMessage {
|
|
153
|
+
level: LogLevel;
|
|
154
|
+
message: unknown[];
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
/**
|
|
@@ -222,7 +228,7 @@ export declare const HEARTBEAT_TYPE = "heartbeat";
|
|
|
222
228
|
/**
|
|
223
229
|
* A simple logging facade.
|
|
224
230
|
*
|
|
225
|
-
* This is a subset of the `
|
|
231
|
+
* This is a subset of the `Console` interface.
|
|
226
232
|
*
|
|
227
233
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API
|
|
228
234
|
*/
|
|
@@ -235,6 +241,9 @@ export declare interface Log {
|
|
|
235
241
|
trace(...data: unknown[]): void;
|
|
236
242
|
}
|
|
237
243
|
|
|
244
|
+
/** Defines level for setting a log level in a `Log` implementation. */
|
|
245
|
+
export declare type LogLevel = "log" | "error" | "warn" | "info" | "debug" | "trace" | "off";
|
|
246
|
+
|
|
238
247
|
/**
|
|
239
248
|
* An interface for a streaming message client.
|
|
240
249
|
*
|
package/dist/shared.js
CHANGED
|
@@ -26,9 +26,9 @@ class c {
|
|
|
26
26
|
static offset(t, e) {
|
|
27
27
|
return new c(new Date(t.millis() + e));
|
|
28
28
|
}
|
|
29
|
-
#
|
|
29
|
+
#t;
|
|
30
30
|
constructor(t) {
|
|
31
|
-
this.#
|
|
31
|
+
this.#t = t;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Return the current timestamp of the clock.
|
|
@@ -36,7 +36,7 @@ class c {
|
|
|
36
36
|
* @return The current timestamp.
|
|
37
37
|
*/
|
|
38
38
|
date() {
|
|
39
|
-
return this.#
|
|
39
|
+
return this.#t ? new Date(this.#t) : /* @__PURE__ */ new Date();
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Return the current timestamp of the clock in milliseconds.
|
|
@@ -47,7 +47,7 @@ class c {
|
|
|
47
47
|
return this.date().getTime();
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
class
|
|
50
|
+
class f {
|
|
51
51
|
/**
|
|
52
52
|
* Create a tracker for a specific event of an event target.
|
|
53
53
|
*
|
|
@@ -55,10 +55,10 @@ class p {
|
|
|
55
55
|
* @param event The event name to track.
|
|
56
56
|
*/
|
|
57
57
|
static create(t, ...e) {
|
|
58
|
-
return new
|
|
58
|
+
return new f(t, e);
|
|
59
59
|
}
|
|
60
|
-
#e;
|
|
61
60
|
#t;
|
|
61
|
+
#e;
|
|
62
62
|
#s;
|
|
63
63
|
#r;
|
|
64
64
|
/**
|
|
@@ -68,8 +68,8 @@ class p {
|
|
|
68
68
|
* @param event The event name to track.
|
|
69
69
|
*/
|
|
70
70
|
constructor(t, e) {
|
|
71
|
-
this.#
|
|
72
|
-
(s) => this.#
|
|
71
|
+
this.#t = t, this.#e = e, this.#s = [], this.#r = (s) => this.#s.push(s), this.#e.forEach(
|
|
72
|
+
(s) => this.#t.addEventListener(s, this.#r)
|
|
73
73
|
);
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
@@ -93,8 +93,8 @@ class p {
|
|
|
93
93
|
* Stop tracking.
|
|
94
94
|
*/
|
|
95
95
|
stop() {
|
|
96
|
-
this.#
|
|
97
|
-
(t) => this.#
|
|
96
|
+
this.#e.forEach(
|
|
97
|
+
(t) => this.#t.removeEventListener(t, this.#r)
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
@@ -105,24 +105,24 @@ class p {
|
|
|
105
105
|
async waitFor(t = 1) {
|
|
106
106
|
return new Promise((e) => {
|
|
107
107
|
const s = () => {
|
|
108
|
-
this.#s.length >= t && (this.#
|
|
109
|
-
(r) => this.#
|
|
108
|
+
this.#s.length >= t && (this.#e.forEach(
|
|
109
|
+
(r) => this.#t.removeEventListener(r, s)
|
|
110
110
|
), e(this.events));
|
|
111
111
|
};
|
|
112
|
-
this.#
|
|
113
|
-
(r) => this.#
|
|
112
|
+
this.#e.forEach(
|
|
113
|
+
(r) => this.#t.addEventListener(r, s)
|
|
114
114
|
), s();
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
class
|
|
118
|
+
class S {
|
|
119
119
|
isSuccess = !0;
|
|
120
120
|
result;
|
|
121
121
|
constructor(t) {
|
|
122
122
|
this.result = t;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
class
|
|
125
|
+
class L {
|
|
126
126
|
isSuccess = !1;
|
|
127
127
|
errorMessage;
|
|
128
128
|
/**
|
|
@@ -134,7 +134,7 @@ class S {
|
|
|
134
134
|
this.errorMessage = t;
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
class
|
|
137
|
+
class l {
|
|
138
138
|
/**
|
|
139
139
|
* Create a list of responses (by providing an array), or a single repeating
|
|
140
140
|
* response (by providing any other type). 'Name' is optional and used in
|
|
@@ -144,7 +144,7 @@ class u {
|
|
|
144
144
|
* @param name An optional name for the responses.
|
|
145
145
|
*/
|
|
146
146
|
static create(t, e) {
|
|
147
|
-
return new
|
|
147
|
+
return new l(t, e);
|
|
148
148
|
}
|
|
149
149
|
/**
|
|
150
150
|
* Convert all properties in an object into ConfigurableResponse instances.
|
|
@@ -157,12 +157,12 @@ class u {
|
|
|
157
157
|
static mapObject(t, e) {
|
|
158
158
|
const r = Object.entries(t).map(([h, n]) => {
|
|
159
159
|
const d = e === void 0 ? void 0 : `${e}: ${h}`;
|
|
160
|
-
return [h,
|
|
160
|
+
return [h, l.create(n, d)];
|
|
161
161
|
});
|
|
162
162
|
return Object.fromEntries(r);
|
|
163
163
|
}
|
|
164
|
-
#e;
|
|
165
164
|
#t;
|
|
165
|
+
#e;
|
|
166
166
|
/**
|
|
167
167
|
* Create a list of responses (by providing an array), or a single repeating
|
|
168
168
|
* response (by providing any other type). 'Name' is optional and used in
|
|
@@ -172,7 +172,7 @@ class u {
|
|
|
172
172
|
* @param name An optional name for the responses.
|
|
173
173
|
*/
|
|
174
174
|
constructor(t, e) {
|
|
175
|
-
this.#
|
|
175
|
+
this.#t = e == null ? "" : ` in ${e}`, this.#e = Array.isArray(t) ? [...t] : t;
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
178
178
|
* Get the next configured response. Throws an error when configured with a list
|
|
@@ -181,13 +181,13 @@ class u {
|
|
|
181
181
|
* @return The next response.
|
|
182
182
|
*/
|
|
183
183
|
next() {
|
|
184
|
-
const t = Array.isArray(this.#
|
|
184
|
+
const t = Array.isArray(this.#e) ? this.#e.shift() : this.#e;
|
|
185
185
|
if (t === void 0)
|
|
186
|
-
throw new Error(`No more responses configured${this.#
|
|
186
|
+
throw new Error(`No more responses configured${this.#t}.`);
|
|
187
187
|
return t;
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
class
|
|
190
|
+
class u {
|
|
191
191
|
/**
|
|
192
192
|
* Create a tracker for a specific event of an event target.
|
|
193
193
|
*
|
|
@@ -195,10 +195,10 @@ class l {
|
|
|
195
195
|
* @param event The event name to track.
|
|
196
196
|
*/
|
|
197
197
|
static create(t, e) {
|
|
198
|
-
return new
|
|
198
|
+
return new u(t, e);
|
|
199
199
|
}
|
|
200
|
-
#e;
|
|
201
200
|
#t;
|
|
201
|
+
#e;
|
|
202
202
|
#s;
|
|
203
203
|
#r;
|
|
204
204
|
/**
|
|
@@ -208,7 +208,7 @@ class l {
|
|
|
208
208
|
* @param event The event name to track.
|
|
209
209
|
*/
|
|
210
210
|
constructor(t, e) {
|
|
211
|
-
this.#
|
|
211
|
+
this.#t = t, this.#e = e, this.#s = [], this.#r = (s) => this.#s.push(s.detail), this.#t.addEventListener(this.#e, this.#r);
|
|
212
212
|
}
|
|
213
213
|
/**
|
|
214
214
|
* Return the tracked data.
|
|
@@ -231,84 +231,120 @@ class l {
|
|
|
231
231
|
* Stop tracking.
|
|
232
232
|
*/
|
|
233
233
|
stop() {
|
|
234
|
-
this.#
|
|
234
|
+
this.#t.removeEventListener(this.#e, this.#r);
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
const i = "message";
|
|
238
|
-
class
|
|
238
|
+
class E extends EventTarget {
|
|
239
|
+
static create() {
|
|
240
|
+
return new E(globalThis.console);
|
|
241
|
+
}
|
|
242
|
+
static createNull() {
|
|
243
|
+
return new E(new p());
|
|
244
|
+
}
|
|
245
|
+
level = "info";
|
|
246
|
+
#t;
|
|
247
|
+
constructor(t) {
|
|
248
|
+
super(), this.#t = t;
|
|
249
|
+
}
|
|
239
250
|
log(...t) {
|
|
240
|
-
this.dispatchEvent(
|
|
251
|
+
this.isLoggable("log") && (this.#t.log(...t), this.dispatchEvent(
|
|
241
252
|
new CustomEvent(i, {
|
|
242
253
|
detail: { level: "log", message: t }
|
|
243
254
|
})
|
|
244
|
-
);
|
|
255
|
+
));
|
|
245
256
|
}
|
|
246
257
|
error(...t) {
|
|
247
|
-
this.dispatchEvent(
|
|
258
|
+
this.isLoggable("error") && (this.#t.error(...t), this.dispatchEvent(
|
|
248
259
|
new CustomEvent(i, {
|
|
249
260
|
detail: { level: "error", message: t }
|
|
250
261
|
})
|
|
251
|
-
);
|
|
262
|
+
));
|
|
252
263
|
}
|
|
253
264
|
warn(...t) {
|
|
254
|
-
this.dispatchEvent(
|
|
265
|
+
this.isLoggable("warn") && (this.#t.warn(...t), this.dispatchEvent(
|
|
255
266
|
new CustomEvent(i, {
|
|
256
267
|
detail: { level: "warn", message: t }
|
|
257
268
|
})
|
|
258
|
-
);
|
|
269
|
+
));
|
|
259
270
|
}
|
|
260
271
|
info(...t) {
|
|
261
|
-
this.dispatchEvent(
|
|
272
|
+
this.isLoggable("info") && (this.#t.info(...t), this.dispatchEvent(
|
|
262
273
|
new CustomEvent(i, {
|
|
263
274
|
detail: { level: "info", message: t }
|
|
264
275
|
})
|
|
265
|
-
);
|
|
276
|
+
));
|
|
266
277
|
}
|
|
267
278
|
debug(...t) {
|
|
268
|
-
this.dispatchEvent(
|
|
279
|
+
this.isLoggable("debug") && (this.#t.debug(...t), this.dispatchEvent(
|
|
269
280
|
new CustomEvent(i, {
|
|
270
281
|
detail: { level: "debug", message: t }
|
|
271
282
|
})
|
|
272
|
-
);
|
|
283
|
+
));
|
|
273
284
|
}
|
|
274
285
|
trace(...t) {
|
|
275
|
-
this.dispatchEvent(
|
|
286
|
+
this.isLoggable("trace") && (this.#t.trace(...t), this.dispatchEvent(
|
|
276
287
|
new CustomEvent(i, {
|
|
277
288
|
detail: { level: "trace", message: t }
|
|
278
289
|
})
|
|
279
|
-
);
|
|
290
|
+
));
|
|
280
291
|
}
|
|
281
292
|
/**
|
|
282
293
|
* Track the console messages.
|
|
283
294
|
*/
|
|
284
295
|
trackMessages() {
|
|
285
|
-
return new
|
|
296
|
+
return new u(this, i);
|
|
297
|
+
}
|
|
298
|
+
isLoggable(t) {
|
|
299
|
+
const e = (n) => n === "log" ? "info" : n, s = [
|
|
300
|
+
"off",
|
|
301
|
+
"error",
|
|
302
|
+
"warn",
|
|
303
|
+
"info",
|
|
304
|
+
"debug",
|
|
305
|
+
"trace"
|
|
306
|
+
], r = s.indexOf(e(this.level));
|
|
307
|
+
return s.indexOf(e(t)) <= r;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
class p {
|
|
311
|
+
log(...t) {
|
|
312
|
+
}
|
|
313
|
+
error(...t) {
|
|
314
|
+
}
|
|
315
|
+
warn(...t) {
|
|
316
|
+
}
|
|
317
|
+
info(...t) {
|
|
318
|
+
}
|
|
319
|
+
debug(...t) {
|
|
320
|
+
}
|
|
321
|
+
trace(...t) {
|
|
286
322
|
}
|
|
287
323
|
}
|
|
288
324
|
function N(a) {
|
|
289
|
-
const t =
|
|
325
|
+
const t = l.create(a);
|
|
290
326
|
return async function() {
|
|
291
327
|
const e = t.next();
|
|
292
328
|
if (e instanceof Error)
|
|
293
329
|
throw e;
|
|
294
|
-
return new
|
|
330
|
+
return new y(e);
|
|
295
331
|
};
|
|
296
332
|
}
|
|
297
|
-
class
|
|
298
|
-
#e;
|
|
333
|
+
class y {
|
|
299
334
|
#t;
|
|
335
|
+
#e;
|
|
300
336
|
#s;
|
|
301
337
|
constructor({ status: t, statusText: e, body: s = null }) {
|
|
302
|
-
this.#
|
|
338
|
+
this.#t = t, this.#e = e, this.#s = s;
|
|
303
339
|
}
|
|
304
340
|
get ok() {
|
|
305
341
|
return this.status >= 200 && this.status < 300;
|
|
306
342
|
}
|
|
307
343
|
get status() {
|
|
308
|
-
return this.#
|
|
344
|
+
return this.#t;
|
|
309
345
|
}
|
|
310
346
|
get statusText() {
|
|
311
|
-
return this.#
|
|
347
|
+
return this.#e;
|
|
312
348
|
}
|
|
313
349
|
async blob() {
|
|
314
350
|
if (this.#s == null)
|
|
@@ -325,14 +361,14 @@ class g {
|
|
|
325
361
|
return this.#s == null ? "" : String(this.#s);
|
|
326
362
|
}
|
|
327
363
|
}
|
|
328
|
-
class
|
|
364
|
+
class v extends EventTarget {
|
|
329
365
|
/**
|
|
330
366
|
* Create an SSE client.
|
|
331
367
|
*
|
|
332
368
|
* @return A new SSE client.
|
|
333
369
|
*/
|
|
334
370
|
static create() {
|
|
335
|
-
return new
|
|
371
|
+
return new v(EventSource);
|
|
336
372
|
}
|
|
337
373
|
/**
|
|
338
374
|
* Create a nulled SSE client.
|
|
@@ -340,18 +376,18 @@ class E extends EventTarget {
|
|
|
340
376
|
* @return A new SSE client.
|
|
341
377
|
*/
|
|
342
378
|
static createNull() {
|
|
343
|
-
return new
|
|
379
|
+
return new v(o);
|
|
344
380
|
}
|
|
345
|
-
#e;
|
|
346
381
|
#t;
|
|
382
|
+
#e;
|
|
347
383
|
constructor(t) {
|
|
348
|
-
super(), this.#
|
|
384
|
+
super(), this.#t = t;
|
|
349
385
|
}
|
|
350
386
|
get isConnected() {
|
|
351
|
-
return this.#
|
|
387
|
+
return this.#e?.readyState === this.#t.OPEN;
|
|
352
388
|
}
|
|
353
389
|
get url() {
|
|
354
|
-
return this.#
|
|
390
|
+
return this.#e?.url;
|
|
355
391
|
}
|
|
356
392
|
async connect(t, e = "message", ...s) {
|
|
357
393
|
await new Promise((r, h) => {
|
|
@@ -360,18 +396,18 @@ class E extends EventTarget {
|
|
|
360
396
|
return;
|
|
361
397
|
}
|
|
362
398
|
try {
|
|
363
|
-
this.#
|
|
399
|
+
this.#e = new this.#t(t), this.#e.addEventListener("open", (n) => {
|
|
364
400
|
this.#s(n), r();
|
|
365
|
-
}), this.#
|
|
401
|
+
}), this.#e.addEventListener(
|
|
366
402
|
e,
|
|
367
403
|
(n) => this.#r(n)
|
|
368
404
|
);
|
|
369
405
|
for (const n of s)
|
|
370
|
-
this.#
|
|
406
|
+
this.#e.addEventListener(
|
|
371
407
|
n,
|
|
372
408
|
(d) => this.#r(d)
|
|
373
409
|
);
|
|
374
|
-
this.#
|
|
410
|
+
this.#e.addEventListener(
|
|
375
411
|
"error",
|
|
376
412
|
(n) => this.#n(n)
|
|
377
413
|
);
|
|
@@ -390,7 +426,7 @@ class E extends EventTarget {
|
|
|
390
426
|
return;
|
|
391
427
|
}
|
|
392
428
|
try {
|
|
393
|
-
this.#
|
|
429
|
+
this.#e.close(), t();
|
|
394
430
|
} catch (s) {
|
|
395
431
|
e(s);
|
|
396
432
|
}
|
|
@@ -443,8 +479,8 @@ class o extends EventTarget {
|
|
|
443
479
|
this.readyState = o.CLOSED;
|
|
444
480
|
}
|
|
445
481
|
}
|
|
446
|
-
const
|
|
447
|
-
class
|
|
482
|
+
const m = "heartbeat", w = "message-sent";
|
|
483
|
+
class g extends EventTarget {
|
|
448
484
|
/**
|
|
449
485
|
* Create a WebSocket client.
|
|
450
486
|
*
|
|
@@ -455,7 +491,7 @@ class v extends EventTarget {
|
|
|
455
491
|
heartbeat: t = 3e4,
|
|
456
492
|
retry: e = 1e3
|
|
457
493
|
} = {}) {
|
|
458
|
-
return new
|
|
494
|
+
return new g(t, e, WebSocket);
|
|
459
495
|
}
|
|
460
496
|
/**
|
|
461
497
|
* Create a nulled WebSocket client.
|
|
@@ -464,20 +500,20 @@ class v extends EventTarget {
|
|
|
464
500
|
* @return A new nulled WebSocket client.
|
|
465
501
|
*/
|
|
466
502
|
static createNull({ heartbeat: t = 0, retry: e = 0 } = {}) {
|
|
467
|
-
return new
|
|
503
|
+
return new g(
|
|
468
504
|
t,
|
|
469
505
|
e,
|
|
470
|
-
|
|
506
|
+
b
|
|
471
507
|
);
|
|
472
508
|
}
|
|
473
|
-
#e;
|
|
474
509
|
#t;
|
|
510
|
+
#e;
|
|
475
511
|
#s;
|
|
476
512
|
#r;
|
|
477
513
|
#n;
|
|
478
514
|
#i;
|
|
479
515
|
constructor(t, e, s) {
|
|
480
|
-
super(), this.#
|
|
516
|
+
super(), this.#t = t, this.#e = e, this.#s = s;
|
|
481
517
|
}
|
|
482
518
|
get isConnected() {
|
|
483
519
|
return this.#r?.readyState === WebSocket.OPEN;
|
|
@@ -493,7 +529,7 @@ class v extends EventTarget {
|
|
|
493
529
|
}
|
|
494
530
|
try {
|
|
495
531
|
this.#r = new this.#s(t), this.#r.addEventListener("open", (r) => {
|
|
496
|
-
this.#
|
|
532
|
+
this.#u(r), e();
|
|
497
533
|
}), this.#r.addEventListener(
|
|
498
534
|
"message",
|
|
499
535
|
(r) => this.#a(r)
|
|
@@ -516,7 +552,7 @@ class v extends EventTarget {
|
|
|
516
552
|
* @return A new output tracker.
|
|
517
553
|
*/
|
|
518
554
|
trackMessageSent() {
|
|
519
|
-
return
|
|
555
|
+
return u.create(this, w);
|
|
520
556
|
}
|
|
521
557
|
/**
|
|
522
558
|
* Close the connection.
|
|
@@ -547,7 +583,7 @@ class v extends EventTarget {
|
|
|
547
583
|
* Simulate a heartbeat.
|
|
548
584
|
*/
|
|
549
585
|
simulateHeartbeat() {
|
|
550
|
-
this.#
|
|
586
|
+
this.#l();
|
|
551
587
|
}
|
|
552
588
|
/**
|
|
553
589
|
* Simulate a close event.
|
|
@@ -564,7 +600,7 @@ class v extends EventTarget {
|
|
|
564
600
|
simulateError() {
|
|
565
601
|
this.#r?.close(), this.#o(new Event("error"));
|
|
566
602
|
}
|
|
567
|
-
#
|
|
603
|
+
#u(t) {
|
|
568
604
|
this.dispatchEvent(new Event(t.type, t)), this.#E();
|
|
569
605
|
}
|
|
570
606
|
#a(t) {
|
|
@@ -579,28 +615,28 @@ class v extends EventTarget {
|
|
|
579
615
|
this.dispatchEvent(new Event(t.type, t)), this.#d();
|
|
580
616
|
}
|
|
581
617
|
#d() {
|
|
582
|
-
this.#
|
|
618
|
+
this.#e <= 0 || (this.#i = setInterval(
|
|
583
619
|
() => this.connect(this.#r.url),
|
|
584
|
-
this.#
|
|
620
|
+
this.#e
|
|
585
621
|
));
|
|
586
622
|
}
|
|
587
623
|
#c() {
|
|
588
624
|
clearInterval(this.#i), this.#i = void 0;
|
|
589
625
|
}
|
|
590
626
|
#E() {
|
|
591
|
-
this.#
|
|
592
|
-
() => this.#
|
|
593
|
-
this.#
|
|
627
|
+
this.#t <= 0 || (this.#n = setInterval(
|
|
628
|
+
() => this.#l(),
|
|
629
|
+
this.#t
|
|
594
630
|
));
|
|
595
631
|
}
|
|
596
632
|
#v() {
|
|
597
633
|
clearInterval(this.#n), this.#n = void 0;
|
|
598
634
|
}
|
|
599
|
-
#
|
|
600
|
-
this.#n != null && this.send(
|
|
635
|
+
#l() {
|
|
636
|
+
this.#n != null && this.send(m);
|
|
601
637
|
}
|
|
602
638
|
}
|
|
603
|
-
class
|
|
639
|
+
class b extends EventTarget {
|
|
604
640
|
url;
|
|
605
641
|
readyState = WebSocket.CONNECTING;
|
|
606
642
|
constructor(t) {
|
|
@@ -616,15 +652,15 @@ class f extends EventTarget {
|
|
|
616
652
|
}
|
|
617
653
|
export {
|
|
618
654
|
c as Clock,
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
655
|
+
l as ConfigurableResponses,
|
|
656
|
+
E as ConsoleLog,
|
|
657
|
+
f as EventTracker,
|
|
658
|
+
L as Failure,
|
|
659
|
+
m as HEARTBEAT_TYPE,
|
|
660
|
+
u as OutputTracker,
|
|
661
|
+
v as SseClient,
|
|
662
|
+
S as Success,
|
|
663
|
+
g as WebSocketClient,
|
|
628
664
|
N as createFetchStub
|
|
629
665
|
};
|
|
630
666
|
//# sourceMappingURL=shared.js.map
|
package/dist/shared.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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<T = unknown> {\n readonly isSuccess = true;\n readonly result?: T;\n\n constructor(result?: T) {\n this.result = result;\n }\n}\n\n/**\n * A failed status.\n */\nexport class Failure<T = string> {\n readonly isSuccess = false;\n readonly errorMessage: T;\n\n /**\n * Creates a failed status.\n *\n * @param errorMessage\n */\n constructor(errorMessage: T) {\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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"}
|
|
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_log.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<T = unknown> {\n readonly isSuccess = true;\n readonly result?: T;\n\n constructor(result?: T) {\n this.result = result;\n }\n}\n\n/**\n * A failed status.\n */\nexport class Failure<T = string> {\n readonly isSuccess = false;\n readonly errorMessage: T;\n\n /**\n * Creates a failed status.\n *\n * @param errorMessage\n */\n constructor(errorMessage: T) {\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) 2026 Falko Schumann. All rights reserved. MIT license.\n\nimport type { Log, LogLevel } from \"../common/log\";\nimport { OutputTracker } from \"./output_tracker\";\n\nconst MESSAGE_EVENT = \"message\";\n\nexport interface ConsoleMessage {\n level: LogLevel;\n message: unknown[];\n}\n\n/**\n * Wraps the console interface and allow setting the log level.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API\n */\nexport class ConsoleLog extends EventTarget implements Log {\n static create() {\n return new ConsoleLog(globalThis.console);\n }\n\n static createNull() {\n return new ConsoleLog(new ConsoleStub());\n }\n\n level: LogLevel = \"info\";\n\n #console;\n\n private constructor(console: Log) {\n super();\n this.#console = console;\n }\n\n log(...data: unknown[]) {\n if (!this.isLoggable(\"log\")) {\n return;\n }\n\n this.#console.log(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"log\", message: data },\n }),\n );\n }\n\n error(...data: unknown[]) {\n if (!this.isLoggable(\"error\")) {\n return;\n }\n\n this.#console.error(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"error\", message: data },\n }),\n );\n }\n\n warn(...data: unknown[]) {\n if (!this.isLoggable(\"warn\")) {\n return;\n }\n\n this.#console.warn(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"warn\", message: data },\n }),\n );\n }\n\n info(...data: unknown[]) {\n if (!this.isLoggable(\"info\")) {\n return;\n }\n\n this.#console.info(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"info\", message: data },\n }),\n );\n }\n\n debug(...data: unknown[]) {\n if (!this.isLoggable(\"debug\")) {\n return;\n }\n\n this.#console.debug(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"debug\", message: data },\n }),\n );\n }\n\n trace(...data: unknown[]) {\n if (!this.isLoggable(\"trace\")) {\n return;\n }\n\n this.#console.trace(...data);\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 isLoggable(level: LogLevel) {\n const normalize = (level: LogLevel) => (level === \"log\" ? \"info\" : level);\n const levels: LogLevel[] = [\n \"off\",\n \"error\",\n \"warn\",\n \"info\",\n \"debug\",\n \"trace\",\n ];\n const currentLevelIndex = levels.indexOf(normalize(this.level));\n const messageLevelIndex = levels.indexOf(normalize(level));\n return messageLevelIndex <= currentLevelIndex;\n }\n}\n\nclass ConsoleStub implements Log {\n log(..._data: unknown[]) {}\n error(..._data: unknown[]) {}\n warn(..._data: unknown[]) {}\n info(..._data: unknown[]) {}\n debug(..._data: unknown[]) {}\n trace(..._data: unknown[]) {}\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","ConsoleLog","ConsoleStub","#console","console","data","level","normalize","levels","currentLevelIndex","_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":"AAKO,MAAMA,EAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjB,OAAO,SAAgB;AACrB,WAAO,IAAIA,EAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,MAAMC,GAAqC;AAChD,WAAO,IAAID,EAAM,IAAI,KAAKC,CAAI,CAAC;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,OAAOC,GAAcC,GAA6B;AACvD,WAAO,IAAIH,EAAM,IAAI,KAAKE,EAAM,OAAA,IAAWC,CAAY,CAAC;AAAA,EAC1D;AAAA,EAESC;AAAA,EAED,YAAYH,GAAa;AAC/B,SAAKG,KAAQH;AAAA,EACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAa;AACX,WAAO,KAAKG,KAAQ,IAAI,KAAK,KAAKA,EAAK,wBAAQ,KAAA;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAiB;AACf,WAAO,KAAK,KAAA,EAAO,QAAA;AAAA,EACrB;AACF;AClDO,MAAMC,EAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzC,OAAO,OAAwBC,MAA6BC,GAAiB;AAC3E,WAAO,IAAIF,EAAgBC,GAAaC,CAAK;AAAA,EAC/C;AAAA,EAESC;AAAA,EACAC;AAAA,EACAC;AAAA,EACAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQT,YAAYL,GAA0BC,GAAiB;AACrD,SAAKC,KAAeF,GACpB,KAAKG,KAASF,GACd,KAAKG,KAAU,CAAA,GACf,KAAKC,KAAW,CAACJ,MAAiB,KAAKG,GAAQ,KAAKH,CAAU,GAE9D,KAAKE,GAAO;AAAA,MAAQ,CAACF,MACnB,KAAKC,GAAa,iBAAiBD,GAAO,KAAKI,EAAQ;AAAA,IAAA;AAAA,EAE3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,SAAc;AAChB,WAAO,KAAKD;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAa;AACX,UAAME,IAAS,CAAC,GAAG,KAAKF,EAAO;AAC/B,gBAAKA,GAAQ,SAAS,GACfE;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO;AACL,SAAKH,GAAO;AAAA,MAAQ,CAACF,MACnB,KAAKC,GAAa,oBAAoBD,GAAO,KAAKI,EAAQ;AAAA,IAAA;AAAA,EAE9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQE,IAAQ,GAAG;AACvB,WAAO,IAAI,QAAa,CAACC,MAAY;AACnC,YAAMC,IAAc,MAAM;AACxB,QAAI,KAAKL,GAAQ,UAAUG,MACzB,KAAKJ,GAAO;AAAA,UAAQ,CAACF,MACnB,KAAKC,GAAa,oBAAoBD,GAAOQ,CAAW;AAAA,QAAA,GAE1DD,EAAQ,KAAK,MAAM;AAAA,MAEvB;AAEA,WAAKL,GAAO;AAAA,QAAQ,CAACF,MACnB,KAAKC,GAAa,iBAAiBD,GAAOQ,CAAW;AAAA,MAAA,GAEvDA,EAAA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AC5DO,MAAMC,EAAqB;AAAA,EACvB,YAAY;AAAA,EACZ;AAAA,EAET,YAAYJ,GAAY;AACtB,SAAK,SAASA;AAAA,EAChB;AACF;AAKO,MAAMK,EAAoB;AAAA,EACtB,YAAY;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,YAAYC,GAAiB;AAC3B,SAAK,eAAeA;AAAA,EACtB;AACF;ACjCO,MAAMC,EAAmC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS9C,OAAO,OAAUC,GAAqBC,GAAe;AACnD,WAAO,IAAIF,EAAyBC,GAAWC,CAAI;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAO,UACLC,GACAD,GACA;AAEA,UAAME,IADU,OAAO,QAAQD,CAAc,EACX,IAAI,CAAC,CAACE,GAAKC,CAAK,MAAM;AACtD,YAAMC,IAAiBL,MAAS,SAAY,SAAY,GAAGA,CAAI,KAAKG,CAAG;AACvE,aAAO,CAACA,GAAKL,EAAsB,OAAOM,GAAOC,CAAc,CAAC;AAAA,IAClE,CAAC;AACD,WAAO,OAAO,YAAYH,CAAiB;AAAA,EAC7C;AAAA,EAESI;AAAA,EACAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUT,YAAYR,GAAqBC,GAAe;AAC9C,SAAKM,KAAeN,KAAQ,OAAO,KAAK,OAAOA,CAAI,IACnD,KAAKO,KAAa,MAAM,QAAQR,CAAS,IAAI,CAAC,GAAGA,CAAS,IAAIA;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAU;AACR,UAAMS,IAAW,MAAM,QAAQ,KAAKD,EAAU,IAC1C,KAAKA,GAAW,MAAA,IAChB,KAAKA;AACT,QAAIC,MAAa;AACf,YAAM,IAAI,MAAM,+BAA+B,KAAKF,EAAY,GAAG;AAGrE,WAAOE;AAAA,EACT;AACF;AC3DO,MAAMC,EAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOtC,OAAO,OAAUxB,GAA0BC,GAAe;AACxD,WAAO,IAAIuB,EAAiBxB,GAAaC,CAAK;AAAA,EAChD;AAAA,EAESC;AAAA,EACAC;AAAA,EACAsB;AAAA,EACApB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQT,YAAYL,GAA0BC,GAAe;AACnD,SAAKC,KAAeF,GACpB,KAAKG,KAASF,GACd,KAAKwB,KAAQ,CAAA,GACb,KAAKpB,KAAW,CAACJ,MACf,KAAKwB,GAAM,KAAMxB,EAAyB,MAAM,GAElD,KAAKC,GAAa,iBAAiB,KAAKC,IAAQ,KAAKE,EAAQ;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,OAAY;AACd,WAAO,KAAKoB;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAa;AACX,UAAMnB,IAAS,CAAC,GAAG,KAAKmB,EAAK;AAC7B,gBAAKA,GAAM,SAAS,GACbnB;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO;AACL,SAAKJ,GAAa,oBAAoB,KAAKC,IAAQ,KAAKE,EAAQ;AAAA,EAClE;AACF;ACnFA,MAAMqB,IAAgB;AAYf,MAAMC,UAAmB,YAA2B;AAAA,EACzD,OAAO,SAAS;AACd,WAAO,IAAIA,EAAW,WAAW,OAAO;AAAA,EAC1C;AAAA,EAEA,OAAO,aAAa;AAClB,WAAO,IAAIA,EAAW,IAAIC,GAAa;AAAA,EACzC;AAAA,EAEA,QAAkB;AAAA,EAElBC;AAAA,EAEQ,YAAYC,GAAc;AAChC,UAAA,GACA,KAAKD,KAAWC;AAAA,EAClB;AAAA,EAEA,OAAOC,GAAiB;AACtB,IAAK,KAAK,WAAW,KAAK,MAI1B,KAAKF,GAAS,IAAI,GAAGE,CAAI,GACzB,KAAK;AAAA,MACH,IAAI,YAAYL,GAAe;AAAA,QAC7B,QAAQ,EAAE,OAAO,OAAO,SAASK,EAAA;AAAA,MAAK,CACvC;AAAA,IAAA;AAAA,EAEL;AAAA,EAEA,SAASA,GAAiB;AACxB,IAAK,KAAK,WAAW,OAAO,MAI5B,KAAKF,GAAS,MAAM,GAAGE,CAAI,GAC3B,KAAK;AAAA,MACH,IAAI,YAAYL,GAAe;AAAA,QAC7B,QAAQ,EAAE,OAAO,SAAS,SAASK,EAAA;AAAA,MAAK,CACzC;AAAA,IAAA;AAAA,EAEL;AAAA,EAEA,QAAQA,GAAiB;AACvB,IAAK,KAAK,WAAW,MAAM,MAI3B,KAAKF,GAAS,KAAK,GAAGE,CAAI,GAC1B,KAAK;AAAA,MACH,IAAI,YAAYL,GAAe;AAAA,QAC7B,QAAQ,EAAE,OAAO,QAAQ,SAASK,EAAA;AAAA,MAAK,CACxC;AAAA,IAAA;AAAA,EAEL;AAAA,EAEA,QAAQA,GAAiB;AACvB,IAAK,KAAK,WAAW,MAAM,MAI3B,KAAKF,GAAS,KAAK,GAAGE,CAAI,GAC1B,KAAK;AAAA,MACH,IAAI,YAAYL,GAAe;AAAA,QAC7B,QAAQ,EAAE,OAAO,QAAQ,SAASK,EAAA;AAAA,MAAK,CACxC;AAAA,IAAA;AAAA,EAEL;AAAA,EAEA,SAASA,GAAiB;AACxB,IAAK,KAAK,WAAW,OAAO,MAI5B,KAAKF,GAAS,MAAM,GAAGE,CAAI,GAC3B,KAAK;AAAA,MACH,IAAI,YAAYL,GAAe;AAAA,QAC7B,QAAQ,EAAE,OAAO,SAAS,SAASK,EAAA;AAAA,MAAK,CACzC;AAAA,IAAA;AAAA,EAEL;AAAA,EAEA,SAASA,GAAiB;AACxB,IAAK,KAAK,WAAW,OAAO,MAI5B,KAAKF,GAAS,MAAM,GAAGE,CAAI,GAC3B,KAAK;AAAA,MACH,IAAI,YAAYL,GAAe;AAAA,QAC7B,QAAQ,EAAE,OAAO,SAAS,SAASK,EAAA;AAAA,MAAK,CACzC;AAAA,IAAA;AAAA,EAEL;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB;AACd,WAAO,IAAIP,EAA8B,MAAME,CAAa;AAAA,EAC9D;AAAA,EAEA,WAAWM,GAAiB;AAC1B,UAAMC,IAAY,CAACD,MAAqBA,MAAU,QAAQ,SAASA,GAC7DE,IAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA,GAEIC,IAAoBD,EAAO,QAAQD,EAAU,KAAK,KAAK,CAAC;AAE9D,WAD0BC,EAAO,QAAQD,EAAUD,CAAK,CAAC,KAC7BG;AAAA,EAC9B;AACF;AAEA,MAAMP,EAA2B;AAAA,EAC/B,OAAOQ,GAAkB;AAAA,EAAC;AAAA,EAC1B,SAASA,GAAkB;AAAA,EAAC;AAAA,EAC5B,QAAQA,GAAkB;AAAA,EAAC;AAAA,EAC3B,QAAQA,GAAkB;AAAA,EAAC;AAAA,EAC3B,SAASA,GAAkB;AAAA,EAAC;AAAA,EAC5B,SAASA,GAAkB;AAAA,EAAC;AAC9B;ACrHO,SAASC,EACdvB,GACyB;AACzB,QAAMwB,IAAwBzB,EAAsB,OAAOC,CAAS;AACpE,SAAO,iBAAkB;AACvB,UAAMS,IAAWe,EAAsB,KAAA;AACvC,QAAIf,aAAoB;AACtB,YAAMA;AAGR,WAAO,IAAIgB,EAAahB,CAAQ;AAAA,EAClC;AACF;AAEA,MAAMgB,EAAa;AAAA,EACjBC;AAAA,EACAC;AAAA,EACAC;AAAA,EAEA,YAAY,EAAE,QAAAC,GAAQ,YAAAC,GAAY,MAAAC,IAAO,QAAsB;AAC7D,SAAKL,KAAUG,GACf,KAAKF,KAAcG,GACnB,KAAKF,KAAQG;AAAA,EACf;AAAA,EAEA,IAAI,KAAK;AACP,WAAO,KAAK,UAAU,OAAO,KAAK,SAAS;AAAA,EAC7C;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAKL;AAAA,EACd;AAAA,EAEA,IAAI,aAAa;AACf,WAAO,KAAKC;AAAA,EACd;AAAA,EAEA,MAAM,OAAO;AACX,QAAI,KAAKC,MAAS;AAChB,aAAO;AAGT,QAAI,KAAKA,cAAiB;AACxB,aAAO,KAAKA;AAGd,UAAM,IAAI,UAAU,qBAAqB;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAO;AACX,UAAMI,IACJ,OAAO,KAAKJ,MAAU,WAAW,KAAKA,KAAQ,KAAK,UAAU,KAAKA,EAAK;AACzE,WAAO,QAAQ,QAAQ,KAAK,MAAMI,CAAI,CAAC;AAAA,EACzC;AAAA,EAEA,MAAM,OAAO;AACX,WAAI,KAAKJ,MAAS,OACT,KAGF,OAAO,KAAKA,EAAK;AAAA,EAC1B;AACF;ACjFO,MAAMK,UAAkB,YAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlE,OAAO,SAAoB;AACzB,WAAO,IAAIA,EAAU,WAAW;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,aAAwB;AAC7B,WAAO,IAAIA,EAAUC,CAAqC;AAAA,EAC5D;AAAA,EAESC;AAAA,EAETC;AAAA,EAEQ,YAAYC,GAA4C;AAC9D,UAAA,GACA,KAAKF,KAA0BE;AAAA,EACjC;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,KAAKD,IAAc,eAAe,KAAKD,GAAwB;AAAA,EACxE;AAAA,EAEA,IAAI,MAA0B;AAC5B,WAAO,KAAKC,IAAc;AAAA,EAC5B;AAAA,EAEA,MAAM,QACJE,GACAC,IAAY,cACTC,GACY;AACf,UAAM,IAAI,QAAc,CAAC9C,GAAS+C,MAAW;AAC3C,UAAI,KAAK,aAAa;AACpB,QAAAA,EAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC;AAAA,MACF;AAEA,UAAI;AACF,aAAKL,KAAe,IAAI,KAAKD,GAAwBG,CAAG,GACxD,KAAKF,GAAa,iBAAiB,QAAQ,CAACM,MAAM;AAChD,eAAKC,GAAYD,CAAC,GAClBhD,EAAA;AAAA,QACF,CAAC,GACD,KAAK0C,GAAa;AAAA,UAAiBG;AAAA,UAAW,CAACG,MAC7C,KAAKE,GAAeF,CAAC;AAAA,QAAA;AAEvB,mBAAWG,KAAcL;AACvB,eAAKJ,GAAa;AAAA,YAAiBS;AAAA,YAAY,CAACH,MAC9C,KAAKE,GAAeF,CAAC;AAAA,UAAA;AAGzB,aAAKN,GAAa;AAAA,UAAiB;AAAA,UAAS,CAACM,MAC3C,KAAKI,GAAaJ,CAAC;AAAA,QAAA;AAAA,MAEvB,SAASK,GAAO;AACd,QAAAN,EAAOM,CAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,KAAKC,GAAkBC,GAA+B;AACpD,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,IAAI,QAAc,CAACvD,GAAS+C,MAAW;AAC3C,UAAI,CAAC,KAAK,aAAa;AACrB,QAAA/C,EAAA;AACA;AAAA,MACF;AAEA,UAAI;AACF,aAAK0C,GAAc,MAAA,GACnB1C,EAAA;AAAA,MACF,SAASqD,GAAO;AACd,QAAAN,EAAOM,CAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,gBACEG,GACAX,IAAY,WACZY,GACA;AACA,IAAI,OAAOD,KAAY,aACrBA,IAAU,KAAK,UAAUA,CAAO,IAElC,KAAKN;AAAA,MACH,IAAI,aAAaL,GAAW,EAAE,MAAMW,GAAS,aAAAC,GAAa;AAAA,IAAA;AAAA,EAE9D;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB;AACd,SAAKL,GAAa,IAAI,MAAM,OAAO,CAAC;AAAA,EACtC;AAAA,EAEAH,GAAYxD,GAAc;AACxB,SAAK,cAAc,IAAI,MAAMA,EAAM,MAAMA,CAAK,CAAC;AAAA,EACjD;AAAA,EAEAyD,GAAezD,GAAqB;AAClC,SAAK;AAAA,MACH,IAAI,aAAaA,EAAM,MAAMA,CAAoC;AAAA,IAAA;AAAA,EAErE;AAAA,EAEA2D,GAAa3D,GAAc;AACzB,SAAK,cAAc,IAAI,MAAMA,EAAM,MAAMA,CAAK,CAAC;AAAA,EACjD;AACF;AAEA,MAAM+C,UAAwB,YAAY;AAAA;AAAA;AAAA,EAGxC,OAAO,aAAa;AAAA,EACpB,OAAO,OAAO;AAAA,EACd,OAAO,SAAS;AAAA,EAEhB;AAAA,EACA,aAAaA,EAAgB;AAAA,EAE7B,YAAYI,GAAmB;AAC7B,UAAA,GACA,KAAK,MAAMA,EAAI,SAAA,GACf,WAAW,MAAM;AACf,WAAK,aAAaJ,EAAgB,MAClC,KAAK,cAAc,IAAI,MAAM,MAAM,CAAC;AAAA,IACtC,GAAG,CAAC;AAAA,EACN;AAAA,EAEA,QAAQ;AACN,SAAK,aAAaA,EAAgB;AAAA,EACpC;AACF;AC5JO,MAAMkB,IAAiB,aAExBC,IAAqB;AAsBpB,MAAMC,UAAwB,YAAqC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOxE,OAAO,OAAO;AAAA,IACZ,WAAAC,IAAY;AAAA,IACZ,OAAAC,IAAQ;AAAA,EAAA,IACY,IAAqB;AACzC,WAAO,IAAIF,EAAgBC,GAAWC,GAAO,SAAS;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,WAAW,EAAE,WAAAD,IAAY,GAAG,OAAAC,IAAQ,EAAA,IAAwB,IAAI;AACrE,WAAO,IAAIF;AAAA,MACTC;AAAA,MACAC;AAAA,MACAC;AAAA,IAAA;AAAA,EAEJ;AAAA,EAESC;AAAA,EACAC;AAAA,EACAC;AAAA,EAETC;AAAA,EACAC;AAAA,EACAC;AAAA,EAEQ,YACNR,GACAC,GACAQ,GACA;AACA,UAAA,GACA,KAAKN,KAAaH,GAClB,KAAKI,KAASH,GACd,KAAKI,KAAwBI;AAAA,EAC/B;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,KAAKH,IAAY,eAAe,UAAU;AAAA,EACnD;AAAA,EAEA,IAAI,MAA0B;AAC5B,WAAO,KAAKA,IAAY;AAAA,EAC1B;AAAA,EAEA,MAAM,QAAQvB,GAAkC;AAC9C,UAAM,IAAI,QAAc,CAAC5C,GAAS+C,MAAW;AAG3C,UAFA,KAAKwB,GAAA,GAED,KAAK,aAAa;AACpB,QAAAxB,EAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC;AAAA,MACF;AAEA,UAAI;AACF,aAAKoB,KAAa,IAAI,KAAKD,GAAsBtB,CAAG,GACpD,KAAKuB,GAAW,iBAAiB,QAAQ,CAACnB,MAAM;AAC9C,eAAKC,GAAYD,CAAC,GAClBhD,EAAA;AAAA,QACF,CAAC,GACD,KAAKmE,GAAW;AAAA,UAAiB;AAAA,UAAW,CAACnB,MAC3C,KAAKE,GAAeF,CAAC;AAAA,QAAA,GAEvB,KAAKmB,GAAW,iBAAiB,SAAS,CAACnB,MAAM,KAAKwB,GAAaxB,CAAC,CAAC,GACrE,KAAKmB,GAAW,iBAAiB,SAAS,CAACnB,MAAM,KAAKI,GAAaJ,CAAC,CAAC;AAAA,MACvE,SAASK,GAAO;AACd,QAAAN,EAAOM,CAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,KACJG,GACe;AACf,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,gBAAgB;AAGlC,SAAKW,GAAY,KAAKX,CAAO,GAC7B,KAAK;AAAA,MACH,IAAI,YAAYG,GAAoB,EAAE,QAAQH,GAAS;AAAA,IAAA,GAEzD,MAAM,QAAQ,QAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAA0C;AACxC,WAAOxC,EAAc,OAAO,MAAM2C,CAAkB;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,MAAMc,GAAeC,GAAgC;AACzD,UAAM,IAAI,QAAc,CAAC1E,MAAY;AAGnC,UAFA,KAAKuE,GAAA,GAED,CAAC,KAAK,aAAa;AACrB,QAAAvE,EAAA;AACA;AAAA,MACF;AAEA,WAAKmE,GAAY,iBAAiB,SAAS,MAAMnE,GAAS,GAC1D,KAAKmE,GAAY,MAAMM,GAAMC,CAAM;AAAA,IACrC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBACElB,GACA;AACA,IACE,OAAOA,KAAY,YACnB,EAAEA,aAAmB,SACrB,EAAEA,aAAmB,iBAErBA,IAAU,KAAK,UAAUA,CAAO,IAElC,KAAKN,GAAe,IAAI,aAAa,WAAW,EAAE,MAAMM,EAAA,CAAS,CAAC;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB;AAClB,SAAKmB,GAAA;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAcF,GAAeC,GAAiB;AAC5C,SAAKF,GAAa,IAAI,WAAW,SAAS,EAAE,MAAAC,GAAM,QAAAC,EAAA,CAAQ,CAAC;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB;AACd,SAAKP,IAAY,MAAA,GACjB,KAAKf,GAAa,IAAI,MAAM,OAAO,CAAC;AAAA,EACtC;AAAA,EAEAH,GAAYxD,GAAc;AACxB,SAAK,cAAc,IAAI,MAAMA,EAAM,MAAMA,CAAK,CAAC,GAC/C,KAAKmF,GAAA;AAAA,EACP;AAAA,EAEA1B,GAAezD,GAAqB;AAClC,SAAK;AAAA,MACH,IAAI,aAAaA,EAAM,MAAMA,CAAoC;AAAA,IAAA;AAAA,EAErE;AAAA,EAEA+E,GAAa/E,GAAmB;AAC9B,SAAKoF,GAAA,GACL,KAAK,cAAc,IAAI,WAAWpF,EAAM,MAAMA,CAAK,CAAC;AAAA,EACtD;AAAA,EAEA2D,GAAa3D,GAAc;AACzB,SAAK,cAAc,IAAI,MAAMA,EAAM,MAAMA,CAAK,CAAC,GAC/C,KAAKqF,GAAA;AAAA,EACP;AAAA,EAEAA,KAAc;AACZ,IAAI,KAAKb,MAAU,MAGnB,KAAKI,KAAW;AAAA,MACd,MAAM,KAAK,QAAQ,KAAKF,GAAY,GAAG;AAAA,MACvC,KAAKF;AAAA,IAAA;AAAA,EAET;AAAA,EAEAM,KAAa;AACX,kBAAc,KAAKF,EAAQ,GAC3B,KAAKA,KAAW;AAAA,EAClB;AAAA,EAEAO,KAAkB;AAChB,IAAI,KAAKZ,MAAc,MAIvB,KAAKI,KAAe;AAAA,MAClB,MAAM,KAAKO,GAAA;AAAA,MACX,KAAKX;AAAA,IAAA;AAAA,EAET;AAAA,EAEAa,KAAiB;AACf,kBAAc,KAAKT,EAAY,GAC/B,KAAKA,KAAe;AAAA,EACtB;AAAA,EAEAO,KAAiB;AACf,IAAI,KAAKP,MAAgB,QAIpB,KAAK,KAAKV,CAAc;AAAA,EAC/B;AACF;AAEA,MAAMK,UAAsB,YAAY;AAAA,EACtC;AAAA,EACA,aAAqB,UAAU;AAAA,EAE/B,YAAYnB,GAAmB;AAC7B,UAAA,GACA,KAAK,MAAMA,EAAI,SAAA,GACf,WAAW,MAAM;AACf,WAAK,aAAa,UAAU,MAC5B,KAAK,cAAc,IAAI,MAAM,MAAM,CAAC;AAAA,IACtC,GAAG,CAAC;AAAA,EACN;AAAA,EAEA,OAAO;AAAA,EAAC;AAAA,EAER,QAAQ;AACN,SAAK,aAAa,UAAU,QAC5B,KAAK,cAAc,IAAI,MAAM,OAAO,CAAC;AAAA,EACvC;AACF;"}
|
package/dist/shared.umd.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(n,a){typeof exports=="object"&&typeof module<"u"?a(exports):typeof define=="function"&&define.amd?define(["exports"],a):(n=typeof globalThis<"u"?globalThis:n||self,a(n.Shared={}))})(this,(function(n){"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))}#t;constructor(t){this.#t=t}date(){return this.#t?new Date(this.#t):new Date}millis(){return this.date().getTime()}}class f{static create(t,...e){return new f(t,e)}#t;#e;#s;#r;constructor(t,e){this.#t=t,this.#e=e,this.#s=[],this.#r=s=>this.#s.push(s),this.#e.forEach(s=>this.#t.addEventListener(s,this.#r))}get events(){return this.#s}clear(){const t=[...this.#s];return this.#s.length=0,t}stop(){this.#e.forEach(t=>this.#t.removeEventListener(t,this.#r))}async waitFor(t=1){return new Promise(e=>{const s=()=>{this.#s.length>=t&&(this.#e.forEach(r=>this.#t.removeEventListener(r,s)),e(this.events))};this.#e.forEach(r=>this.#t.addEventListener(r,s)),s()})}}class b{isSuccess=!0;result;constructor(t){this.result=t}}class m{isSuccess=!1;errorMessage;constructor(t){this.errorMessage=t}}class l{static create(t,e){return new l(t,e)}static mapObject(t,e){const r=Object.entries(t).map(([c,i])=>{const w=e===void 0?void 0:`${e}: ${c}`;return[c,l.create(i,w)]});return Object.fromEntries(r)}#t;#e;constructor(t,e){this.#t=e==null?"":` in ${e}`,this.#e=Array.isArray(t)?[...t]:t}next(){const t=Array.isArray(this.#e)?this.#e.shift():this.#e;if(t===void 0)throw new Error(`No more responses configured${this.#t}.`);return t}}class u{static create(t,e){return new u(t,e)}#t;#e;#s;#r;constructor(t,e){this.#t=t,this.#e=e,this.#s=[],this.#r=s=>this.#s.push(s.detail),this.#t.addEventListener(this.#e,this.#r)}get data(){return this.#s}clear(){const t=[...this.#s];return this.#s.length=0,t}stop(){this.#t.removeEventListener(this.#e,this.#r)}}const o="message";class E extends EventTarget{static create(){return new E(globalThis.console)}static createNull(){return new E(new S)}level="info";#t;constructor(t){super(),this.#t=t}log(...t){this.isLoggable("log")&&(this.#t.log(...t),this.dispatchEvent(new CustomEvent(o,{detail:{level:"log",message:t}})))}error(...t){this.isLoggable("error")&&(this.#t.error(...t),this.dispatchEvent(new CustomEvent(o,{detail:{level:"error",message:t}})))}warn(...t){this.isLoggable("warn")&&(this.#t.warn(...t),this.dispatchEvent(new CustomEvent(o,{detail:{level:"warn",message:t}})))}info(...t){this.isLoggable("info")&&(this.#t.info(...t),this.dispatchEvent(new CustomEvent(o,{detail:{level:"info",message:t}})))}debug(...t){this.isLoggable("debug")&&(this.#t.debug(...t),this.dispatchEvent(new CustomEvent(o,{detail:{level:"debug",message:t}})))}trace(...t){this.isLoggable("trace")&&(this.#t.trace(...t),this.dispatchEvent(new CustomEvent(o,{detail:{level:"trace",message:t}})))}trackMessages(){return new u(this,o)}isLoggable(t){const e=i=>i==="log"?"info":i,s=["off","error","warn","info","debug","trace"],r=s.indexOf(e(this.level));return s.indexOf(e(t))<=r}}class S{log(...t){}error(...t){}warn(...t){}info(...t){}debug(...t){}trace(...t){}}function T(h){const t=l.create(h);return async function(){const e=t.next();if(e instanceof Error)throw e;return new C(e)}}class C{#t;#e;#s;constructor({status:t,statusText:e,body:s=null}){this.#t=t,this.#e=e,this.#s=s}get ok(){return this.status>=200&&this.status<300}get status(){return this.#t}get statusText(){return this.#e}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 v extends EventTarget{static create(){return new v(EventSource)}static createNull(){return new v(d)}#t;#e;constructor(t){super(),this.#t=t}get isConnected(){return this.#e?.readyState===this.#t.OPEN}get url(){return this.#e?.url}async connect(t,e="message",...s){await new Promise((r,c)=>{if(this.isConnected){c(new Error("Already connected."));return}try{this.#e=new this.#t(t),this.#e.addEventListener("open",i=>{this.#s(i),r()}),this.#e.addEventListener(e,i=>this.#r(i));for(const i of s)this.#e.addEventListener(i,w=>this.#r(w));this.#e.addEventListener("error",i=>this.#n(i))}catch(i){c(i)}})}send(t,e){throw new Error("Method not implemented.")}async close(){await new Promise((t,e)=>{if(!this.isConnected){t();return}try{this.#e.close(),t()}catch(s){e(s)}})}simulateMessage(t,e="message",s){typeof t!="string"&&(t=JSON.stringify(t)),this.#r(new MessageEvent(e,{data:t,lastEventId:s}))}simulateError(){this.#n(new Event("error"))}#s(t){this.dispatchEvent(new Event(t.type,t))}#r(t){this.dispatchEvent(new MessageEvent(t.type,t))}#n(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 p="heartbeat",y="message-sent";class g extends EventTarget{static create({heartbeat:t=3e4,retry:e=1e3}={}){return new g(t,e,WebSocket)}static createNull({heartbeat:t=0,retry:e=0}={}){return new g(t,e,L)}#t;#e;#s;#r;#n;#i;constructor(t,e,s){super(),this.#t=t,this.#e=e,this.#s=s}get isConnected(){return this.#r?.readyState===WebSocket.OPEN}get url(){return this.#r?.url}async connect(t){await new Promise((e,s)=>{if(this.#c(),this.isConnected){s(new Error("Already connected."));return}try{this.#r=new this.#s(t),this.#r.addEventListener("open",r=>{this.#u(r),e()}),this.#r.addEventListener("message",r=>this.#a(r)),this.#r.addEventListener("close",r=>this.#o(r)),this.#r.addEventListener("error",r=>this.#h(r))}catch(r){s(r)}})}async send(t){if(!this.isConnected)throw new Error("Not connected.");this.#r.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.#r.addEventListener("close",()=>s()),this.#r.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.#l()}simulateClose(t,e){this.#o(new CloseEvent("close",{code:t,reason:e}))}simulateError(){this.#r?.close(),this.#h(new Event("error"))}#u(t){this.dispatchEvent(new Event(t.type,t)),this.#E()}#a(t){this.dispatchEvent(new MessageEvent(t.type,t))}#o(t){this.#v(),this.dispatchEvent(new CloseEvent(t.type,t))}#h(t){this.dispatchEvent(new Event(t.type,t)),this.#d()}#d(){this.#e<=0||(this.#i=setInterval(()=>this.connect(this.#r.url),this.#e))}#c(){clearInterval(this.#i),this.#i=void 0}#E(){this.#t<=0||(this.#n=setInterval(()=>this.#l(),this.#t))}#v(){clearInterval(this.#n),this.#n=void 0}#l(){this.#n!=null&&this.send(p)}}class L 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"))}}n.Clock=a,n.ConfigurableResponses=l,n.ConsoleLog=E,n.EventTracker=f,n.Failure=m,n.HEARTBEAT_TYPE=p,n.OutputTracker=u,n.SseClient=v,n.Success=b,n.WebSocketClient=g,n.createFetchStub=T,Object.defineProperty(n,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/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<T = unknown> {\n readonly isSuccess = true;\n readonly result?: T;\n\n constructor(result?: T) {\n this.result = result;\n }\n}\n\n/**\n * A failed status.\n */\nexport class Failure<T = string> {\n readonly isSuccess = false;\n readonly errorMessage: T;\n\n /**\n * Creates a failed status.\n *\n * @param errorMessage\n */\n constructor(errorMessage: T) {\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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"}
|
|
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_log.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<T = unknown> {\n readonly isSuccess = true;\n readonly result?: T;\n\n constructor(result?: T) {\n this.result = result;\n }\n}\n\n/**\n * A failed status.\n */\nexport class Failure<T = string> {\n readonly isSuccess = false;\n readonly errorMessage: T;\n\n /**\n * Creates a failed status.\n *\n * @param errorMessage\n */\n constructor(errorMessage: T) {\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) 2026 Falko Schumann. All rights reserved. MIT license.\n\nimport type { Log, LogLevel } from \"../common/log\";\nimport { OutputTracker } from \"./output_tracker\";\n\nconst MESSAGE_EVENT = \"message\";\n\nexport interface ConsoleMessage {\n level: LogLevel;\n message: unknown[];\n}\n\n/**\n * Wraps the console interface and allow setting the log level.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API\n */\nexport class ConsoleLog extends EventTarget implements Log {\n static create() {\n return new ConsoleLog(globalThis.console);\n }\n\n static createNull() {\n return new ConsoleLog(new ConsoleStub());\n }\n\n level: LogLevel = \"info\";\n\n #console;\n\n private constructor(console: Log) {\n super();\n this.#console = console;\n }\n\n log(...data: unknown[]) {\n if (!this.isLoggable(\"log\")) {\n return;\n }\n\n this.#console.log(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"log\", message: data },\n }),\n );\n }\n\n error(...data: unknown[]) {\n if (!this.isLoggable(\"error\")) {\n return;\n }\n\n this.#console.error(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"error\", message: data },\n }),\n );\n }\n\n warn(...data: unknown[]) {\n if (!this.isLoggable(\"warn\")) {\n return;\n }\n\n this.#console.warn(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"warn\", message: data },\n }),\n );\n }\n\n info(...data: unknown[]) {\n if (!this.isLoggable(\"info\")) {\n return;\n }\n\n this.#console.info(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"info\", message: data },\n }),\n );\n }\n\n debug(...data: unknown[]) {\n if (!this.isLoggable(\"debug\")) {\n return;\n }\n\n this.#console.debug(...data);\n this.dispatchEvent(\n new CustomEvent(MESSAGE_EVENT, {\n detail: { level: \"debug\", message: data },\n }),\n );\n }\n\n trace(...data: unknown[]) {\n if (!this.isLoggable(\"trace\")) {\n return;\n }\n\n this.#console.trace(...data);\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 isLoggable(level: LogLevel) {\n const normalize = (level: LogLevel) => (level === \"log\" ? \"info\" : level);\n const levels: LogLevel[] = [\n \"off\",\n \"error\",\n \"warn\",\n \"info\",\n \"debug\",\n \"trace\",\n ];\n const currentLevelIndex = levels.indexOf(normalize(this.level));\n const messageLevelIndex = levels.indexOf(normalize(level));\n return messageLevelIndex <= currentLevelIndex;\n }\n}\n\nclass ConsoleStub implements Log {\n log(..._data: unknown[]) {}\n error(..._data: unknown[]) {}\n warn(..._data: unknown[]) {}\n info(..._data: unknown[]) {}\n debug(..._data: unknown[]) {}\n trace(..._data: unknown[]) {}\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","ConsoleLog","ConsoleStub","#console","console","data","level","normalize","levels","currentLevelIndex","_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.16.0",
|
|
4
4
|
"author": "Falko Schumann",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -26,17 +26,17 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@eslint/js": "9.39.2",
|
|
29
|
-
"@types/node": "20.19.
|
|
29
|
+
"@types/node": "20.19.28",
|
|
30
30
|
"@vitest/coverage-v8": "4.0.16",
|
|
31
31
|
"eslint": "9.39.2",
|
|
32
32
|
"eslint-plugin-headers": "1.3.3",
|
|
33
33
|
"jsdom": "27.4.0",
|
|
34
34
|
"globals": "17.0.0",
|
|
35
35
|
"prettier": "3.7.4",
|
|
36
|
-
"typedoc": "0.28.
|
|
36
|
+
"typedoc": "0.28.16",
|
|
37
37
|
"typescript": "5.9.3",
|
|
38
|
-
"typescript-eslint": "8.
|
|
39
|
-
"vite": "7.3.
|
|
38
|
+
"typescript-eslint": "8.52.0",
|
|
39
|
+
"vite": "7.3.1",
|
|
40
40
|
"vitest": "4.0.16",
|
|
41
41
|
"vite-plugin-dts": "4.5.4"
|
|
42
42
|
}
|
package/src/common/log.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
|
+
/** Defines level for setting a log level in a `Log` implementation. */
|
|
4
|
+
export type LogLevel =
|
|
5
|
+
| "log"
|
|
6
|
+
| "error"
|
|
7
|
+
| "warn"
|
|
8
|
+
| "info"
|
|
9
|
+
| "debug"
|
|
10
|
+
| "trace"
|
|
11
|
+
| "off";
|
|
12
|
+
|
|
3
13
|
/**
|
|
4
14
|
* A simple logging facade.
|
|
5
15
|
*
|
|
6
|
-
* This is a subset of the `
|
|
16
|
+
* This is a subset of the `Console` interface.
|
|
7
17
|
*
|
|
8
18
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API
|
|
9
19
|
*/
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Copyright (c) 2026 Falko Schumann. All rights reserved. MIT license.
|
|
2
|
+
|
|
3
|
+
import type { Log, LogLevel } from "../common/log";
|
|
4
|
+
import { OutputTracker } from "./output_tracker";
|
|
5
|
+
|
|
6
|
+
const MESSAGE_EVENT = "message";
|
|
7
|
+
|
|
8
|
+
export interface ConsoleMessage {
|
|
9
|
+
level: LogLevel;
|
|
10
|
+
message: unknown[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wraps the console interface and allow setting the log level.
|
|
15
|
+
*
|
|
16
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API
|
|
17
|
+
*/
|
|
18
|
+
export class ConsoleLog extends EventTarget implements Log {
|
|
19
|
+
static create() {
|
|
20
|
+
return new ConsoleLog(globalThis.console);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static createNull() {
|
|
24
|
+
return new ConsoleLog(new ConsoleStub());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
level: LogLevel = "info";
|
|
28
|
+
|
|
29
|
+
#console;
|
|
30
|
+
|
|
31
|
+
private constructor(console: Log) {
|
|
32
|
+
super();
|
|
33
|
+
this.#console = console;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log(...data: unknown[]) {
|
|
37
|
+
if (!this.isLoggable("log")) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.#console.log(...data);
|
|
42
|
+
this.dispatchEvent(
|
|
43
|
+
new CustomEvent(MESSAGE_EVENT, {
|
|
44
|
+
detail: { level: "log", message: data },
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
error(...data: unknown[]) {
|
|
50
|
+
if (!this.isLoggable("error")) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.#console.error(...data);
|
|
55
|
+
this.dispatchEvent(
|
|
56
|
+
new CustomEvent(MESSAGE_EVENT, {
|
|
57
|
+
detail: { level: "error", message: data },
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
warn(...data: unknown[]) {
|
|
63
|
+
if (!this.isLoggable("warn")) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.#console.warn(...data);
|
|
68
|
+
this.dispatchEvent(
|
|
69
|
+
new CustomEvent(MESSAGE_EVENT, {
|
|
70
|
+
detail: { level: "warn", message: data },
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
info(...data: unknown[]) {
|
|
76
|
+
if (!this.isLoggable("info")) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.#console.info(...data);
|
|
81
|
+
this.dispatchEvent(
|
|
82
|
+
new CustomEvent(MESSAGE_EVENT, {
|
|
83
|
+
detail: { level: "info", message: data },
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
debug(...data: unknown[]) {
|
|
89
|
+
if (!this.isLoggable("debug")) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.#console.debug(...data);
|
|
94
|
+
this.dispatchEvent(
|
|
95
|
+
new CustomEvent(MESSAGE_EVENT, {
|
|
96
|
+
detail: { level: "debug", message: data },
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
trace(...data: unknown[]) {
|
|
102
|
+
if (!this.isLoggable("trace")) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.#console.trace(...data);
|
|
107
|
+
this.dispatchEvent(
|
|
108
|
+
new CustomEvent(MESSAGE_EVENT, {
|
|
109
|
+
detail: { level: "trace", message: data },
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Track the console messages.
|
|
116
|
+
*/
|
|
117
|
+
trackMessages() {
|
|
118
|
+
return new OutputTracker<ConsoleMessage>(this, MESSAGE_EVENT);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
isLoggable(level: LogLevel) {
|
|
122
|
+
const normalize = (level: LogLevel) => (level === "log" ? "info" : level);
|
|
123
|
+
const levels: LogLevel[] = [
|
|
124
|
+
"off",
|
|
125
|
+
"error",
|
|
126
|
+
"warn",
|
|
127
|
+
"info",
|
|
128
|
+
"debug",
|
|
129
|
+
"trace",
|
|
130
|
+
];
|
|
131
|
+
const currentLevelIndex = levels.indexOf(normalize(this.level));
|
|
132
|
+
const messageLevelIndex = levels.indexOf(normalize(level));
|
|
133
|
+
return messageLevelIndex <= currentLevelIndex;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
class ConsoleStub implements Log {
|
|
138
|
+
log(..._data: unknown[]) {}
|
|
139
|
+
error(..._data: unknown[]) {}
|
|
140
|
+
warn(..._data: unknown[]) {}
|
|
141
|
+
info(..._data: unknown[]) {}
|
|
142
|
+
debug(..._data: unknown[]) {}
|
|
143
|
+
trace(..._data: unknown[]) {}
|
|
144
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.
|
|
2
2
|
|
|
3
3
|
export * from "./configurable_responses";
|
|
4
|
-
export * from "./
|
|
4
|
+
export * from "./console_log";
|
|
5
5
|
export * from "./fetch_stub";
|
|
6
6
|
export * from "./message_client";
|
|
7
7
|
export * from "./output_tracker";
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 Falko Schumann. All rights reserved. MIT license.
|
|
2
|
-
|
|
3
|
-
import { OutputTracker } from "./output_tracker";
|
|
4
|
-
|
|
5
|
-
const MESSAGE_EVENT = "message";
|
|
6
|
-
|
|
7
|
-
export interface ConsoleMessage {
|
|
8
|
-
level: "log" | "error" | "warn" | "info" | "debug" | "trace";
|
|
9
|
-
message: unknown[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A stub for the console interface.
|
|
14
|
-
*
|
|
15
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API
|
|
16
|
-
*/
|
|
17
|
-
export class ConsoleStub extends EventTarget {
|
|
18
|
-
log(...data: unknown[]) {
|
|
19
|
-
this.dispatchEvent(
|
|
20
|
-
new CustomEvent(MESSAGE_EVENT, {
|
|
21
|
-
detail: { level: "log", message: data },
|
|
22
|
-
}),
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
error(...data: unknown[]) {
|
|
27
|
-
this.dispatchEvent(
|
|
28
|
-
new CustomEvent(MESSAGE_EVENT, {
|
|
29
|
-
detail: { level: "error", message: data },
|
|
30
|
-
}),
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
warn(...data: unknown[]) {
|
|
35
|
-
this.dispatchEvent(
|
|
36
|
-
new CustomEvent(MESSAGE_EVENT, {
|
|
37
|
-
detail: { level: "warn", message: data },
|
|
38
|
-
}),
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
info(...data: unknown[]) {
|
|
43
|
-
this.dispatchEvent(
|
|
44
|
-
new CustomEvent(MESSAGE_EVENT, {
|
|
45
|
-
detail: { level: "info", message: data },
|
|
46
|
-
}),
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
debug(...data: unknown[]) {
|
|
51
|
-
this.dispatchEvent(
|
|
52
|
-
new CustomEvent(MESSAGE_EVENT, {
|
|
53
|
-
detail: { level: "debug", message: data },
|
|
54
|
-
}),
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
trace(...data: unknown[]) {
|
|
59
|
-
this.dispatchEvent(
|
|
60
|
-
new CustomEvent(MESSAGE_EVENT, {
|
|
61
|
-
detail: { level: "trace", message: data },
|
|
62
|
-
}),
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Track the console messages.
|
|
68
|
-
*/
|
|
69
|
-
trackMessages() {
|
|
70
|
-
return new OutputTracker<ConsoleMessage>(this, MESSAGE_EVENT);
|
|
71
|
-
}
|
|
72
|
-
}
|