@matter/general 0.13.0-alpha.0-20250311-3eb0af5f2 → 0.13.0-alpha.0-20250322-f085fa576
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/cjs/codec/DnsCodec.js +1 -1
- package/dist/cjs/codec/DnsCodec.js.map +1 -1
- package/dist/cjs/log/Logger.d.ts +11 -6
- package/dist/cjs/log/Logger.d.ts.map +1 -1
- package/dist/cjs/log/Logger.js +19 -8
- package/dist/cjs/log/Logger.js.map +1 -1
- package/dist/cjs/time/Time.d.ts +1 -0
- package/dist/cjs/time/Time.d.ts.map +1 -1
- package/dist/cjs/time/Time.js +3 -0
- package/dist/cjs/time/Time.js.map +1 -1
- package/dist/cjs/transaction/Participant.d.ts +3 -3
- package/dist/cjs/transaction/Participant.d.ts.map +1 -1
- package/dist/cjs/transaction/Transaction.d.ts +20 -20
- package/dist/cjs/transaction/Tx.js +3 -3
- package/dist/cjs/transaction/Tx.js.map +1 -1
- package/dist/cjs/util/Construction.js +2 -2
- package/dist/cjs/util/Construction.js.map +1 -1
- package/dist/cjs/util/DataReadQueue.d.ts +4 -0
- package/dist/cjs/util/DataReadQueue.d.ts.map +1 -1
- package/dist/cjs/util/DataReadQueue.js +6 -0
- package/dist/cjs/util/DataReadQueue.js.map +1 -1
- package/dist/cjs/util/FormattedText.js +4 -2
- package/dist/cjs/util/FormattedText.js.map +1 -1
- package/dist/cjs/util/Observable.d.ts +137 -12
- package/dist/cjs/util/Observable.d.ts.map +1 -1
- package/dist/cjs/util/Observable.js +297 -35
- package/dist/cjs/util/Observable.js.map +2 -2
- package/dist/esm/codec/DnsCodec.js +1 -1
- package/dist/esm/codec/DnsCodec.js.map +1 -1
- package/dist/esm/log/Logger.d.ts +11 -6
- package/dist/esm/log/Logger.d.ts.map +1 -1
- package/dist/esm/log/Logger.js +19 -8
- package/dist/esm/log/Logger.js.map +1 -1
- package/dist/esm/time/Time.d.ts +1 -0
- package/dist/esm/time/Time.d.ts.map +1 -1
- package/dist/esm/time/Time.js +3 -0
- package/dist/esm/time/Time.js.map +1 -1
- package/dist/esm/transaction/Participant.d.ts +3 -3
- package/dist/esm/transaction/Participant.d.ts.map +1 -1
- package/dist/esm/transaction/Transaction.d.ts +20 -20
- package/dist/esm/transaction/Tx.js +3 -3
- package/dist/esm/transaction/Tx.js.map +1 -1
- package/dist/esm/util/Construction.js +2 -2
- package/dist/esm/util/Construction.js.map +1 -1
- package/dist/esm/util/DataReadQueue.d.ts +4 -0
- package/dist/esm/util/DataReadQueue.d.ts.map +1 -1
- package/dist/esm/util/DataReadQueue.js +6 -0
- package/dist/esm/util/DataReadQueue.js.map +1 -1
- package/dist/esm/util/FormattedText.js +4 -2
- package/dist/esm/util/FormattedText.js.map +1 -1
- package/dist/esm/util/Observable.d.ts +137 -12
- package/dist/esm/util/Observable.d.ts.map +1 -1
- package/dist/esm/util/Observable.js +297 -35
- package/dist/esm/util/Observable.js.map +2 -2
- package/package.json +2 -2
- package/src/codec/DnsCodec.ts +1 -1
- package/src/log/Logger.ts +26 -11
- package/src/time/Time.ts +4 -0
- package/src/transaction/Participant.ts +3 -3
- package/src/transaction/Transaction.ts +1 -1
- package/src/transaction/Tx.ts +3 -3
- package/src/util/Construction.ts +2 -2
- package/src/util/DataReadQueue.ts +7 -0
- package/src/util/FormattedText.ts +4 -2
- package/src/util/Observable.ts +453 -47
package/src/util/Construction.ts
CHANGED
|
@@ -239,7 +239,7 @@ export function Construction<const T extends Constructable>(
|
|
|
239
239
|
let change: Observable<[status: Lifecycle.Status, subject: T]> | undefined;
|
|
240
240
|
|
|
241
241
|
const self: Construction<any> = {
|
|
242
|
-
[Symbol.toStringTag]: "
|
|
242
|
+
[Symbol.toStringTag]: "Construction",
|
|
243
243
|
|
|
244
244
|
get error() {
|
|
245
245
|
return error;
|
|
@@ -663,7 +663,7 @@ export function Construction<const T extends Constructable>(
|
|
|
663
663
|
|
|
664
664
|
function createErrorHandler(name: string) {
|
|
665
665
|
return (e: any) => {
|
|
666
|
-
unhandledError(`Unhandled error in ${
|
|
666
|
+
unhandledError(`Unhandled error in ${subject} ${name}:`, e);
|
|
667
667
|
};
|
|
668
668
|
}
|
|
669
669
|
}
|
|
@@ -35,6 +35,13 @@ export class DataReadQueue<T> implements Stream<T> {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async write(data: T) {
|
|
38
|
+
this.push(data);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Same as write but doesn't require the await required to satisfy {@link Stream#write}.
|
|
43
|
+
*/
|
|
44
|
+
push(data: T) {
|
|
38
45
|
if (this.#closed) throw new EndOfStreamError();
|
|
39
46
|
if (this.#pendingRead !== undefined) {
|
|
40
47
|
this.#pendingRead.timeoutTimer?.stop();
|
|
@@ -144,7 +144,7 @@ function detectStructure(text: string): TextStructure {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
function wrapParagraph(input: string, into: string[], wrapWidth: number, padding: number, prefixWidth: number) {
|
|
147
|
-
const segments = input.
|
|
147
|
+
const segments = input.split(/\s+/);
|
|
148
148
|
if (!segments) {
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
@@ -205,10 +205,12 @@ function wrapParagraph(input: string, into: string[], wrapWidth: number, padding
|
|
|
205
205
|
|
|
206
206
|
// Add to the line
|
|
207
207
|
line.push(s);
|
|
208
|
-
|
|
208
|
+
line.push(" ");
|
|
209
|
+
length += segmentLength + 1;
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
// If there is a remaining line, add it
|
|
213
|
+
line.length = line.length - 1; // Remove ending space
|
|
212
214
|
if (line.length) {
|
|
213
215
|
addLine();
|
|
214
216
|
}
|
package/src/util/Observable.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { Time, Timer } from "#time/Time.js";
|
|
7
8
|
import { ImplementationError } from "../MatterError.js";
|
|
8
9
|
import { Logger } from "../log/Logger.js";
|
|
9
10
|
import "../polyfills/disposable.js";
|
|
@@ -69,11 +70,38 @@ export interface Observable<T extends any[] = any[], R = void> extends AsyncIter
|
|
|
69
70
|
isObservedBy(observer: Observer<T, R>): boolean;
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* Errors throw by observers will interrupt emitters unless an error handler is installed here and the handler does
|
|
74
|
+
* not rethrow.
|
|
75
|
+
*
|
|
76
|
+
* The only exception to this is if {@link handlePromise} is false and an observer is asynchronous. In this case
|
|
77
|
+
* the emitter cannot be made aware of the exception.
|
|
75
78
|
*/
|
|
76
|
-
|
|
79
|
+
handleError: ObserverErrorHandler;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* We allow emitters to be async, but we do not want to overburden either the emitter or the observer with promise
|
|
83
|
+
* tracking if the lifetime of the observer is not relevant to the emitter.
|
|
84
|
+
*
|
|
85
|
+
* To facilitate this we allow observables to be configured in one of three promise handling modes:
|
|
86
|
+
*
|
|
87
|
+
* * If you set handlePromise, isAsync is true; the handler is invoked for any observer promise
|
|
88
|
+
*
|
|
89
|
+
* * If isAsync is true but you do not set handlePromise, any observer promise is returned to the emitter which must
|
|
90
|
+
* handle the promise
|
|
91
|
+
*
|
|
92
|
+
* * If isAsync is false, we log observer promise errors but the promise is otherwise untracked
|
|
93
|
+
*
|
|
94
|
+
* If the promiseHandler returns a promise or is true and the emitter returns a promise, the observable will emit to
|
|
95
|
+
* successive observers only after the promise resolves.
|
|
96
|
+
*/
|
|
97
|
+
isAsync: boolean;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A promise handler.
|
|
101
|
+
*
|
|
102
|
+
* If you set {@link isAsync} (either true or false) the promise handler is set by the Observable.
|
|
103
|
+
*/
|
|
104
|
+
handlePromise: ObserverPromiseHandler | boolean;
|
|
77
105
|
|
|
78
106
|
/**
|
|
79
107
|
* Observable supports standard "for await (const value of observable").
|
|
@@ -98,9 +126,7 @@ export const observant = Symbol("consider-observed");
|
|
|
98
126
|
/**
|
|
99
127
|
* An {@link Observable} that explicitly supports asynchronous observers.
|
|
100
128
|
*/
|
|
101
|
-
export interface AsyncObservable<T extends any[] = any[], R = void> extends Observable<T, MaybePromise<R>> {
|
|
102
|
-
isAsync: true;
|
|
103
|
-
}
|
|
129
|
+
export interface AsyncObservable<T extends any[] = any[], R = void> extends Observable<T, MaybePromise<R>> {}
|
|
104
130
|
|
|
105
131
|
function defaultErrorHandler(error: Error) {
|
|
106
132
|
throw error;
|
|
@@ -108,22 +134,29 @@ function defaultErrorHandler(error: Error) {
|
|
|
108
134
|
|
|
109
135
|
export type ObserverErrorHandler = (error: Error, observer: Observer<any[], any>) => void;
|
|
110
136
|
|
|
137
|
+
export type ObserverPromiseHandler = (promise: Promise<unknown>, observer: Observer<any[], any>) => unknown;
|
|
138
|
+
|
|
111
139
|
/**
|
|
112
140
|
* A concrete {@link Observable} implementation.
|
|
113
141
|
*/
|
|
114
142
|
export class BasicObservable<T extends any[] = any[], R = void> implements Observable<T, R> {
|
|
115
|
-
#
|
|
143
|
+
#handleError!: ObserverErrorHandler;
|
|
144
|
+
#isAsync!: boolean;
|
|
145
|
+
#handlePromise!: ObserverPromiseHandler;
|
|
116
146
|
#observers?: Set<Observer<T, R>>;
|
|
117
147
|
#once?: Set<Observer<T, R>>;
|
|
118
|
-
#isAsync?: boolean;
|
|
119
148
|
|
|
120
149
|
#joinIteration?: () => Promise<Next<T>>;
|
|
121
150
|
#removeIterator?: () => void;
|
|
122
151
|
#stopIteration?: () => void;
|
|
123
152
|
|
|
124
|
-
constructor(
|
|
125
|
-
this
|
|
126
|
-
|
|
153
|
+
constructor(handleError?: ObserverErrorHandler, asyncConfig?: ObserverPromiseHandler | boolean) {
|
|
154
|
+
this.handleError = handleError ?? defaultErrorHandler;
|
|
155
|
+
if (typeof asyncConfig === "function") {
|
|
156
|
+
this.handlePromise = asyncConfig;
|
|
157
|
+
} else {
|
|
158
|
+
this.isAsync = asyncConfig ?? false;
|
|
159
|
+
}
|
|
127
160
|
}
|
|
128
161
|
|
|
129
162
|
[Symbol.dispose]() {
|
|
@@ -132,12 +165,51 @@ export class BasicObservable<T extends any[] = any[], R = void> implements Obser
|
|
|
132
165
|
this.#stopIteration?.();
|
|
133
166
|
}
|
|
134
167
|
|
|
168
|
+
set handleError(handleError: ObserverErrorHandler) {
|
|
169
|
+
this.#handleError = handleError;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
get handleError() {
|
|
173
|
+
return this.#handleError;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
set isAsync(isAsync: boolean) {
|
|
177
|
+
this.#isAsync = isAsync;
|
|
178
|
+
if (isAsync) {
|
|
179
|
+
// Promises handled by emitter
|
|
180
|
+
this.#handlePromise = promise => promise;
|
|
181
|
+
} else {
|
|
182
|
+
// We log promise errors but do not otherwise track
|
|
183
|
+
this.#handlePromise = (promise, observer) => {
|
|
184
|
+
promise.catch(error => {
|
|
185
|
+
let identity: string;
|
|
186
|
+
if (observer.name) {
|
|
187
|
+
identity = ` "${observer.name}"`;
|
|
188
|
+
} else {
|
|
189
|
+
identity = "";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (this.#handleError === defaultErrorHandler) {
|
|
193
|
+
logger.error(`Unhandled error in async observer${identity}:`, error);
|
|
194
|
+
} else {
|
|
195
|
+
this.#handleError(error, observer);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
135
202
|
get isAsync() {
|
|
136
203
|
return this.#isAsync;
|
|
137
204
|
}
|
|
138
205
|
|
|
139
|
-
set
|
|
140
|
-
this
|
|
206
|
+
set handlePromise(handlePromise: ObserverPromiseHandler) {
|
|
207
|
+
this.isAsync = true;
|
|
208
|
+
this.#handlePromise = handlePromise;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
get handlePromise() {
|
|
212
|
+
return this.#handlePromise;
|
|
141
213
|
}
|
|
142
214
|
|
|
143
215
|
get isObserved() {
|
|
@@ -185,7 +257,7 @@ export class BasicObservable<T extends any[] = any[], R = void> implements Obser
|
|
|
185
257
|
try {
|
|
186
258
|
result = observer(...payload);
|
|
187
259
|
} catch (e) {
|
|
188
|
-
this.#
|
|
260
|
+
this.#handleError(asError(e), observer);
|
|
189
261
|
}
|
|
190
262
|
|
|
191
263
|
if (this.#once?.has(observer)) {
|
|
@@ -198,27 +270,20 @@ export class BasicObservable<T extends any[] = any[], R = void> implements Obser
|
|
|
198
270
|
}
|
|
199
271
|
|
|
200
272
|
if (MaybePromise.is(result)) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
);
|
|
273
|
+
result = this.#handlePromise(Promise.resolve(result), observer as Observer) as R | undefined;
|
|
274
|
+
|
|
275
|
+
if (MaybePromise.is(result)) {
|
|
276
|
+
return result.then(result => {
|
|
277
|
+
if (result === undefined) {
|
|
278
|
+
return emitNext();
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}) as R;
|
|
282
|
+
}
|
|
212
283
|
|
|
284
|
+
if (result === undefined) {
|
|
213
285
|
continue;
|
|
214
286
|
}
|
|
215
|
-
|
|
216
|
-
return result.then(result => {
|
|
217
|
-
if (result === undefined) {
|
|
218
|
-
return emitNext();
|
|
219
|
-
}
|
|
220
|
-
return result;
|
|
221
|
-
}) as R;
|
|
222
287
|
}
|
|
223
288
|
|
|
224
289
|
return result;
|
|
@@ -318,8 +383,8 @@ export class BasicObservable<T extends any[] = any[], R = void> implements Obser
|
|
|
318
383
|
|
|
319
384
|
type Next<T> = undefined | { value: T; promise: Promise<Next<T>> };
|
|
320
385
|
|
|
321
|
-
function constructObservable(
|
|
322
|
-
return new BasicObservable(
|
|
386
|
+
function constructObservable(handleError?: ObserverErrorHandler) {
|
|
387
|
+
return new BasicObservable(handleError);
|
|
323
388
|
}
|
|
324
389
|
|
|
325
390
|
/**
|
|
@@ -330,16 +395,16 @@ export const Observable = constructObservable as unknown as {
|
|
|
330
395
|
<T extends any[], R = void>(errorHandler?: ObserverErrorHandler): Observable<T, R>;
|
|
331
396
|
};
|
|
332
397
|
|
|
333
|
-
function constructAsyncObservable(
|
|
334
|
-
return new BasicObservable(
|
|
398
|
+
function constructAsyncObservable(handleError?: ObserverErrorHandler) {
|
|
399
|
+
return new BasicObservable(handleError, true);
|
|
335
400
|
}
|
|
336
401
|
|
|
337
402
|
/**
|
|
338
403
|
* Create an {@link AsyncObservable} that explicitly supports asynchronous results
|
|
339
404
|
*/
|
|
340
405
|
export const AsyncObservable = constructAsyncObservable as unknown as {
|
|
341
|
-
new <T extends any[], R = void>(
|
|
342
|
-
<T extends any[], R = void>(
|
|
406
|
+
new <T extends any[], R = void>(handleError?: ObserverErrorHandler): AsyncObservable<T, R>;
|
|
407
|
+
<T extends any[], R = void>(handleError?: ObserverErrorHandler): AsyncObservable<T, R>;
|
|
343
408
|
};
|
|
344
409
|
|
|
345
410
|
function event<E, N extends string>(emitter: E, name: N) {
|
|
@@ -357,6 +422,9 @@ function event<E, N extends string>(emitter: E, name: N) {
|
|
|
357
422
|
* To maintain type safety, implementers define events as observable child properties.
|
|
358
423
|
*/
|
|
359
424
|
export class EventEmitter {
|
|
425
|
+
// True private screws up TS types
|
|
426
|
+
private events?: Record<string, Observable | undefined>;
|
|
427
|
+
|
|
360
428
|
emit<This, N extends EventEmitter.NamesOf<This>>(this: This, name: N, ...payload: EventEmitter.PayloadOf<This, N>) {
|
|
361
429
|
event(this, name).emit(...payload);
|
|
362
430
|
}
|
|
@@ -377,14 +445,40 @@ export class EventEmitter {
|
|
|
377
445
|
event(this, name).off(handler as any);
|
|
378
446
|
}
|
|
379
447
|
|
|
448
|
+
addEvent(name: string, event?: Observable) {
|
|
449
|
+
if (!this.events) {
|
|
450
|
+
this.events = {};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
this.events[name] = event;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
getEvent(name: string) {
|
|
457
|
+
if (!this.events || !(name in this.events)) {
|
|
458
|
+
throw new ImplementationError(`No such event ${name}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return this.events[name] ?? (this.events[name] = Observable());
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
hasEvent(name: string, onlyIfInitialized = false) {
|
|
465
|
+
return this.events && (onlyIfInitialized ? this.events[name] : name in this.events);
|
|
466
|
+
}
|
|
467
|
+
|
|
380
468
|
get eventNames() {
|
|
381
|
-
return Object.keys(this)
|
|
469
|
+
return this.events ? Object.keys(this.events) : [];
|
|
382
470
|
}
|
|
383
471
|
|
|
384
472
|
[Symbol.dispose]() {
|
|
385
|
-
|
|
386
|
-
|
|
473
|
+
if (!this.events) {
|
|
474
|
+
return;
|
|
387
475
|
}
|
|
476
|
+
|
|
477
|
+
for (const event of Object.values(this.events)) {
|
|
478
|
+
event?.[Symbol.dispose]?.();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
this.events = undefined;
|
|
388
482
|
}
|
|
389
483
|
}
|
|
390
484
|
|
|
@@ -418,7 +512,7 @@ export namespace EventEmitter {
|
|
|
418
512
|
/**
|
|
419
513
|
* An {@link Observable} that proxies to another {@link Observable}.
|
|
420
514
|
*
|
|
421
|
-
*
|
|
515
|
+
* Events emitted here instead emit on the target {@link Observable}. Events emitted on the target emit locally via
|
|
422
516
|
* a listener installed by the proxy.
|
|
423
517
|
*
|
|
424
518
|
* This is useful for managing a subset of {@link Observer}s for an {@link Observable}.
|
|
@@ -448,15 +542,15 @@ export class ObservableProxy extends BasicObservable {
|
|
|
448
542
|
super[Symbol.dispose]();
|
|
449
543
|
}
|
|
450
544
|
|
|
451
|
-
override get isAsync() {
|
|
452
|
-
return this.#target.isAsync;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
545
|
override get isObserved(): boolean {
|
|
456
546
|
return this.#target.isObserved;
|
|
457
547
|
}
|
|
458
548
|
|
|
459
549
|
override emit: (...payload: any) => any | undefined;
|
|
550
|
+
|
|
551
|
+
protected get target() {
|
|
552
|
+
return this.#target;
|
|
553
|
+
}
|
|
460
554
|
}
|
|
461
555
|
|
|
462
556
|
/**
|
|
@@ -553,3 +647,315 @@ export namespace ObserverGroup {
|
|
|
553
647
|
*/
|
|
554
648
|
export type VarArgs<T extends any[]> = T extends [...infer R, infer A] ? [...R, A] : T extends [infer A] ? A : [];
|
|
555
649
|
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* An {@link Observable} that emits an algorithmically-reduced number of events.
|
|
653
|
+
*/
|
|
654
|
+
export class QuietObservable<T extends any[] = any[]> extends BasicObservable<T> implements QuietObservable.State<T> {
|
|
655
|
+
#emitAutomatically = QuietObservable.defaults.emitAutomatically;
|
|
656
|
+
#suppressionEnabled = QuietObservable.defaults.suppressionEnabled;
|
|
657
|
+
#minimumEmitIntervalMs = QuietObservable.defaults.minimumEmitIntervalMs;
|
|
658
|
+
#shouldEmit?: QuietObservable.EmitPredicate<T>;
|
|
659
|
+
#source?: Observable<T>;
|
|
660
|
+
#sink?: Observable<T>;
|
|
661
|
+
#sourceObserver?: Observer<T>;
|
|
662
|
+
#sinkObserver?: Observer<T>;
|
|
663
|
+
#deferredPayload?: T;
|
|
664
|
+
#lastEmitAt?: number;
|
|
665
|
+
#emitTimer?: Timer;
|
|
666
|
+
|
|
667
|
+
constructor(config?: QuietObservable.Configuration<T>) {
|
|
668
|
+
super();
|
|
669
|
+
if (config) {
|
|
670
|
+
this.config = config;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
get config() {
|
|
675
|
+
return this;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
set config(config: QuietObservable.Configuration<T>) {
|
|
679
|
+
const { suppressionEnabled, minimumEmitIntervalMs, emitAutomatically } = config;
|
|
680
|
+
if (emitAutomatically !== undefined) {
|
|
681
|
+
this.emitAutomatically = emitAutomatically;
|
|
682
|
+
}
|
|
683
|
+
if (suppressionEnabled !== undefined) {
|
|
684
|
+
this.suppressionEnabled = suppressionEnabled;
|
|
685
|
+
}
|
|
686
|
+
if (minimumEmitIntervalMs !== undefined) {
|
|
687
|
+
this.minimumEmitIntervalMs = minimumEmitIntervalMs;
|
|
688
|
+
}
|
|
689
|
+
if ("shouldEmit" in config) {
|
|
690
|
+
this.shouldEmit = config.shouldEmit;
|
|
691
|
+
}
|
|
692
|
+
if ("source" in config) {
|
|
693
|
+
this.source = config.source;
|
|
694
|
+
}
|
|
695
|
+
if ("sink" in config) {
|
|
696
|
+
this.sink = config.sink;
|
|
697
|
+
}
|
|
698
|
+
if ("handleError" in config) {
|
|
699
|
+
this.handleError = config.handleError ?? defaultErrorHandler;
|
|
700
|
+
}
|
|
701
|
+
if ("handlePromise" in config && config.handlePromise) {
|
|
702
|
+
this.handlePromise = config.handlePromise;
|
|
703
|
+
} else {
|
|
704
|
+
this.isAsync = config.isAsync ?? false;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
get emitAutomatically() {
|
|
709
|
+
return this.#emitAutomatically;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
set emitAutomatically(value: boolean) {
|
|
713
|
+
this.#emitAutomatically = value;
|
|
714
|
+
if (value) {
|
|
715
|
+
this.emitSoon();
|
|
716
|
+
} else if (this.#emitTimer) {
|
|
717
|
+
this.#stop();
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
get suppressionEnabled() {
|
|
722
|
+
return this.#suppressionEnabled;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
set suppressionEnabled(value: boolean) {
|
|
726
|
+
this.#suppressionEnabled = value;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
get minimumEmitIntervalMs() {
|
|
730
|
+
return this.#minimumEmitIntervalMs;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
set minimumEmitIntervalMs(value: number) {
|
|
734
|
+
if (this.#minimumEmitIntervalMs === value) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const needStart = this.#emitTimer !== undefined;
|
|
738
|
+
if (needStart) {
|
|
739
|
+
this.#stop();
|
|
740
|
+
}
|
|
741
|
+
this.#minimumEmitIntervalMs = value;
|
|
742
|
+
if (needStart) {
|
|
743
|
+
this.#start();
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
get source() {
|
|
748
|
+
return this.#source;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
set source(source: Observable<T> | undefined) {
|
|
752
|
+
if (this.#source === source) {
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
if (this.#source && this.#sourceObserver) {
|
|
756
|
+
this.#source.off(this.#sourceObserver);
|
|
757
|
+
} else if (this.#sourceObserver === undefined) {
|
|
758
|
+
this.#sourceObserver = (...payload) => this.emit(...payload);
|
|
759
|
+
}
|
|
760
|
+
this.#source = source;
|
|
761
|
+
if (source) {
|
|
762
|
+
source.on(this.#sourceObserver);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
get sink() {
|
|
767
|
+
return this.#sink;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
set sink(sink: Observable<T> | undefined) {
|
|
771
|
+
if (this.#sink === sink) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (this.#sink && this.#sinkObserver) {
|
|
775
|
+
this.off(this.#sinkObserver);
|
|
776
|
+
}
|
|
777
|
+
this.#sink = sink;
|
|
778
|
+
if (sink) {
|
|
779
|
+
this.#sinkObserver = (...payload) => sink.emit(...payload);
|
|
780
|
+
this.#sinkObserver[observant] = false;
|
|
781
|
+
this.on(this.#sinkObserver);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
get shouldEmit() {
|
|
786
|
+
return this.#shouldEmit;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
set shouldEmit(shouldEmit: QuietObservable.EmitPredicate<T> | undefined) {
|
|
790
|
+
this.#shouldEmit = shouldEmit;
|
|
791
|
+
if (this.#deferredPayload && shouldEmit?.(...this.#deferredPayload) === false) {
|
|
792
|
+
this.#deferredPayload = undefined;
|
|
793
|
+
this.#stop();
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
override get isObserved() {
|
|
798
|
+
return super.isObserved || this.#sink?.isObserved || false;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
override isObservedBy(observer: Observer<T>): boolean {
|
|
802
|
+
return this.#sink?.isObservedBy(observer) || this.isObservedBy(observer) || false;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
override emit(...payload: T) {
|
|
806
|
+
const shouldEmit = this.#shouldEmit?.(...payload);
|
|
807
|
+
if (shouldEmit === false) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
const immediate = shouldEmit === "now";
|
|
811
|
+
if (!immediate && !this.#emitAutomatically) {
|
|
812
|
+
this.#deferredPayload = payload;
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const now = Time.nowMs();
|
|
816
|
+
if (
|
|
817
|
+
immediate ||
|
|
818
|
+
!this.#suppressionEnabled ||
|
|
819
|
+
this.#lastEmitAt === undefined ||
|
|
820
|
+
this.#lastEmitAt + this.#minimumEmitIntervalMs < now
|
|
821
|
+
) {
|
|
822
|
+
return this.#emit(payload, now);
|
|
823
|
+
}
|
|
824
|
+
this.#deferredPayload = payload;
|
|
825
|
+
this.#start(now);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Emit immediately, regardless of suppression configuration.
|
|
830
|
+
*/
|
|
831
|
+
emitNow() {
|
|
832
|
+
this.#stop();
|
|
833
|
+
if (this.#deferredPayload) {
|
|
834
|
+
this.#emit(this.#deferredPayload);
|
|
835
|
+
this.#deferredPayload = undefined;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Emit as soon as allowed by suppression.
|
|
841
|
+
*/
|
|
842
|
+
emitSoon() {
|
|
843
|
+
if (this.#deferredPayload && this.#emitTimer === undefined) {
|
|
844
|
+
this.#start();
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
override [Symbol.dispose]() {
|
|
849
|
+
this.#stop();
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
#emit(payload: T, now?: number) {
|
|
853
|
+
this.#deferredPayload = undefined;
|
|
854
|
+
this.#lastEmitAt = now ?? Time.nowMs();
|
|
855
|
+
this.#stop();
|
|
856
|
+
super.emit(...payload);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
#start(now?: number) {
|
|
860
|
+
if (this.#emitTimer || this.#deferredPayload === undefined) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
let timeout;
|
|
865
|
+
if (this.#lastEmitAt === undefined) {
|
|
866
|
+
timeout = 0;
|
|
867
|
+
} else {
|
|
868
|
+
timeout = this.#minimumEmitIntervalMs - ((now ?? Time.nowMs()) - this.#lastEmitAt);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
if (timeout <= 0) {
|
|
872
|
+
this.emitNow();
|
|
873
|
+
} else {
|
|
874
|
+
this.#emitTimer = Time.getTimer("delayed emit", timeout, this.emitNow.bind(this));
|
|
875
|
+
this.#emitTimer.start();
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
#stop() {
|
|
880
|
+
if (this.#emitTimer) {
|
|
881
|
+
this.#emitTimer.stop();
|
|
882
|
+
this.#emitTimer = undefined;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
export namespace QuietObservable {
|
|
888
|
+
export interface State<T extends any[] = any[]> {
|
|
889
|
+
/**
|
|
890
|
+
* If true this observable will emit within the suppression constraints. If false it will only emit after calls
|
|
891
|
+
* to {@link emitSoon} or {@link emitNow}.
|
|
892
|
+
*/
|
|
893
|
+
emitAutomatically: boolean;
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* If true then emit rate is constrained. If false emits will occur immediately.
|
|
897
|
+
*/
|
|
898
|
+
suppressionEnabled: boolean;
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* The minimum time between emits in milliseconds.
|
|
902
|
+
*/
|
|
903
|
+
minimumEmitIntervalMs: number;
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* An input observable this observable will automatically observe to produce events.
|
|
907
|
+
*/
|
|
908
|
+
source?: Observable<T>;
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* An output observable this observable will automatically emit to whenever it emits.
|
|
912
|
+
*/
|
|
913
|
+
sink?: Observable<T>;
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* A predicate that determine whether a payload should emit.
|
|
917
|
+
*/
|
|
918
|
+
shouldEmit?: EmitPredicate<T>;
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Handler for errors returned by observers.
|
|
922
|
+
*/
|
|
923
|
+
handleError?: ObserverErrorHandler;
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Designates async support (overridden if you supply {@link handlePromise}).
|
|
927
|
+
*/
|
|
928
|
+
isAsync?: boolean;
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Handler for promises returned by observers.
|
|
932
|
+
*/
|
|
933
|
+
handlePromise?: ObserverPromiseHandler;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* An emit predicate may emit this value to force immediate emit.
|
|
938
|
+
*/
|
|
939
|
+
export const now = "now";
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* The return value of an emit predicate. "true" allows the event to emit as normal, "false" prevents the event
|
|
943
|
+
* from emitting, and {@link now} forces immediate emit regardless of interval configuration.
|
|
944
|
+
*/
|
|
945
|
+
export type EmitDirective = true | false | typeof now;
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* A predicate that may filter emits manually.
|
|
949
|
+
*/
|
|
950
|
+
export interface EmitPredicate<T extends any[] = any[]> {
|
|
951
|
+
(...payload: T): EmitDirective;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
export interface Configuration<T extends any[] = any[]> extends Partial<State<T>> {}
|
|
955
|
+
|
|
956
|
+
export const defaults: State = {
|
|
957
|
+
emitAutomatically: true,
|
|
958
|
+
suppressionEnabled: true,
|
|
959
|
+
minimumEmitIntervalMs: 1000,
|
|
960
|
+
};
|
|
961
|
+
}
|