@matter/general 0.16.0-alpha.0-20251023-bb23617e5 → 0.16.0-alpha.0-20251029-bd92894d4
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/crypto/Crypto.d.ts +2 -10
- package/dist/cjs/crypto/Crypto.d.ts.map +1 -1
- package/dist/cjs/crypto/Crypto.js +2 -23
- package/dist/cjs/crypto/Crypto.js.map +1 -1
- package/dist/cjs/crypto/StandardCrypto.d.ts.map +1 -1
- package/dist/cjs/crypto/StandardCrypto.js +4 -1
- package/dist/cjs/crypto/StandardCrypto.js.map +1 -1
- package/dist/cjs/net/RetrySchedule.d.ts +2 -2
- package/dist/cjs/net/RetrySchedule.d.ts.map +1 -1
- package/dist/cjs/net/RetrySchedule.js +4 -4
- package/dist/cjs/net/RetrySchedule.js.map +1 -1
- package/dist/cjs/util/Abort.d.ts.map +1 -1
- package/dist/cjs/util/Abort.js +3 -2
- package/dist/cjs/util/Abort.js.map +1 -1
- package/dist/cjs/util/Entropy.d.ts +21 -0
- package/dist/cjs/util/Entropy.d.ts.map +1 -0
- package/dist/cjs/util/Entropy.js +53 -0
- package/dist/cjs/util/Entropy.js.map +6 -0
- package/dist/cjs/util/Observable.d.ts +55 -1
- package/dist/cjs/util/Observable.d.ts.map +1 -1
- package/dist/cjs/util/Observable.js +77 -1
- package/dist/cjs/util/Observable.js.map +1 -1
- package/dist/cjs/util/Promises.d.ts +15 -0
- package/dist/cjs/util/Promises.d.ts.map +1 -1
- package/dist/cjs/util/Promises.js +63 -0
- package/dist/cjs/util/Promises.js.map +2 -2
- package/dist/cjs/util/Set.d.ts +3 -1
- package/dist/cjs/util/Set.d.ts.map +1 -1
- package/dist/cjs/util/Set.js +10 -0
- package/dist/cjs/util/Set.js.map +1 -1
- package/dist/cjs/util/index.d.ts +1 -0
- package/dist/cjs/util/index.d.ts.map +1 -1
- package/dist/cjs/util/index.js +1 -0
- package/dist/cjs/util/index.js.map +1 -1
- package/dist/esm/crypto/Crypto.d.ts +2 -10
- package/dist/esm/crypto/Crypto.d.ts.map +1 -1
- package/dist/esm/crypto/Crypto.js +2 -23
- package/dist/esm/crypto/Crypto.js.map +1 -1
- package/dist/esm/crypto/StandardCrypto.d.ts.map +1 -1
- package/dist/esm/crypto/StandardCrypto.js +4 -1
- package/dist/esm/crypto/StandardCrypto.js.map +1 -1
- package/dist/esm/net/RetrySchedule.d.ts +2 -2
- package/dist/esm/net/RetrySchedule.d.ts.map +1 -1
- package/dist/esm/net/RetrySchedule.js +4 -4
- package/dist/esm/net/RetrySchedule.js.map +1 -1
- package/dist/esm/util/Abort.d.ts.map +1 -1
- package/dist/esm/util/Abort.js +3 -2
- package/dist/esm/util/Abort.js.map +1 -1
- package/dist/esm/util/Entropy.d.ts +21 -0
- package/dist/esm/util/Entropy.d.ts.map +1 -0
- package/dist/esm/util/Entropy.js +33 -0
- package/dist/esm/util/Entropy.js.map +6 -0
- package/dist/esm/util/Observable.d.ts +55 -1
- package/dist/esm/util/Observable.d.ts.map +1 -1
- package/dist/esm/util/Observable.js +77 -1
- package/dist/esm/util/Observable.js.map +1 -1
- package/dist/esm/util/Promises.d.ts +15 -0
- package/dist/esm/util/Promises.d.ts.map +1 -1
- package/dist/esm/util/Promises.js +63 -0
- package/dist/esm/util/Promises.js.map +2 -2
- package/dist/esm/util/Set.d.ts +3 -1
- package/dist/esm/util/Set.d.ts.map +1 -1
- package/dist/esm/util/Set.js +11 -1
- package/dist/esm/util/Set.js.map +1 -1
- package/dist/esm/util/index.d.ts +1 -0
- package/dist/esm/util/index.d.ts.map +1 -1
- package/dist/esm/util/index.js +1 -0
- package/dist/esm/util/index.js.map +1 -1
- package/package.json +2 -2
- package/src/crypto/Crypto.ts +2 -33
- package/src/crypto/StandardCrypto.ts +4 -1
- package/src/net/RetrySchedule.ts +5 -5
- package/src/util/Abort.ts +3 -2
- package/src/util/Entropy.ts +44 -0
- package/src/util/Observable.ts +152 -3
- package/src/util/Promises.ts +109 -0
- package/src/util/Set.ts +14 -1
- package/src/util/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matter/general",
|
|
3
|
-
"version": "0.16.0-alpha.0-
|
|
3
|
+
"version": "0.16.0-alpha.0-20251029-bd92894d4",
|
|
4
4
|
"description": "Non-Matter support for Matter.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"iot",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@noble/curves": "^2.0.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@matter/testing": "0.16.0-alpha.0-
|
|
39
|
+
"@matter/testing": "0.16.0-alpha.0-20251029-bd92894d4"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"dist/**/*",
|
package/src/crypto/Crypto.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { MaybePromise } from "#util/Promises.js";
|
|
|
12
12
|
import * as mod from "@noble/curves/abstract/modular.js";
|
|
13
13
|
import { p256 } from "@noble/curves/nist.js";
|
|
14
14
|
import * as utils from "@noble/curves/utils.js";
|
|
15
|
+
import { Entropy } from "../util/Entropy.js";
|
|
15
16
|
import type { PrivateKey, PublicKey } from "./Key.js";
|
|
16
17
|
|
|
17
18
|
export const ec = {
|
|
@@ -41,7 +42,7 @@ const logger = Logger.get("Crypto");
|
|
|
41
42
|
*
|
|
42
43
|
* WARNING: The standard implementation is unaudited. See relevant warnings in StandardCrypto.ts.
|
|
43
44
|
*/
|
|
44
|
-
export abstract class Crypto {
|
|
45
|
+
export abstract class Crypto extends Entropy {
|
|
45
46
|
/**
|
|
46
47
|
* The name used in log messages.
|
|
47
48
|
*/
|
|
@@ -57,11 +58,6 @@ export abstract class Crypto {
|
|
|
57
58
|
*/
|
|
58
59
|
abstract decrypt(key: Bytes, data: Bytes, nonce: Bytes, aad?: Bytes): Bytes;
|
|
59
60
|
|
|
60
|
-
/**
|
|
61
|
-
* Create a random buffer from the most cryptographically-appropriate source available.
|
|
62
|
-
*/
|
|
63
|
-
abstract randomBytes(length: number): Bytes;
|
|
64
|
-
|
|
65
61
|
/**
|
|
66
62
|
* Compute the SHA-256 hash of a buffer.
|
|
67
63
|
*/
|
|
@@ -113,33 +109,6 @@ export abstract class Crypto {
|
|
|
113
109
|
*/
|
|
114
110
|
abstract generateDhSecret(key: PrivateKey, peerKey: PublicKey): MaybePromise<Bytes>;
|
|
115
111
|
|
|
116
|
-
get randomUint8() {
|
|
117
|
-
return Bytes.of(this.randomBytes(1))[0];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
get randomUint16() {
|
|
121
|
-
return Bytes.dataViewOf(this.randomBytes(2)).getUint16(0);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
get randomUint32() {
|
|
125
|
-
return Bytes.dataViewOf(this.randomBytes(4)).getUint32(0);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
get randomBigUint64() {
|
|
129
|
-
return Bytes.dataViewOf(this.randomBytes(8)).getBigUint64(0);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
randomBigInt(size: number, maxValue?: bigint) {
|
|
133
|
-
if (maxValue === undefined) {
|
|
134
|
-
return Bytes.asBigInt(this.randomBytes(size));
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
while (true) {
|
|
138
|
-
const random = Bytes.asBigInt(this.randomBytes(size));
|
|
139
|
-
if (random < maxValue) return random;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
112
|
reportUsage(component?: string) {
|
|
144
113
|
const message = ["Using", Diagnostic.strong(this.implementationName), "crypto implementation"];
|
|
145
114
|
if (component) {
|
|
@@ -9,6 +9,7 @@ import { DerBigUint, DerCodec, DerError } from "#codec/DerCodec.js";
|
|
|
9
9
|
import { Environment } from "#environment/Environment.js";
|
|
10
10
|
import { ImplementationError, NotImplementedError } from "#MatterError.js";
|
|
11
11
|
import { Bytes } from "#util/Bytes.js";
|
|
12
|
+
import { Entropy } from "#util/Entropy.js";
|
|
12
13
|
import { MaybePromise } from "#util/Promises.js";
|
|
13
14
|
import { describeList } from "#util/String.js";
|
|
14
15
|
import { Ccm } from "./aes/Ccm.js";
|
|
@@ -305,5 +306,7 @@ function assertInterface<T extends {}>(name: string, object: T, requiredMethods:
|
|
|
305
306
|
// If available, unconditionally add to Environment as it has not been exported yet so there can be no other
|
|
306
307
|
// implementation present
|
|
307
308
|
if ("crypto" in globalThis && globalThis.crypto?.subtle) {
|
|
308
|
-
|
|
309
|
+
const crypto = new StandardCrypto();
|
|
310
|
+
Environment.default.set(Entropy, crypto);
|
|
311
|
+
Environment.default.set(Crypto, crypto);
|
|
309
312
|
}
|
package/src/net/RetrySchedule.ts
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Crypto } from "#crypto/Crypto.js";
|
|
8
7
|
import { Duration } from "#time/Duration.js";
|
|
9
8
|
import { Instant, Millis, Seconds } from "#time/TimeUnit.js";
|
|
9
|
+
import { Entropy } from "#util/Entropy.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* An iterable of retry values based on a scheduling configuration.
|
|
13
13
|
*/
|
|
14
14
|
export class RetrySchedule {
|
|
15
|
-
#
|
|
15
|
+
#entropy: Entropy;
|
|
16
16
|
readonly config: RetrySchedule.Configuration;
|
|
17
17
|
|
|
18
|
-
constructor(
|
|
19
|
-
this.#
|
|
18
|
+
constructor(entropy: Entropy, config: RetrySchedule.Configuration) {
|
|
19
|
+
this.#entropy = entropy;
|
|
20
20
|
this.config = config;
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -42,7 +42,7 @@ export class RetrySchedule {
|
|
|
42
42
|
while ((timeout === undefined || timeSoFar < timeout) && (maximumCount === undefined || maximumCount > count)) {
|
|
43
43
|
count++;
|
|
44
44
|
const maxJitter = jitterFactor * baseInterval;
|
|
45
|
-
const jitter = Millis.floor(Millis((maxJitter * this.#
|
|
45
|
+
const jitter = Millis.floor(Millis((maxJitter * this.#entropy.randomUint32) / Math.pow(2, 32)));
|
|
46
46
|
let interval = Millis(baseInterval + jitter);
|
|
47
47
|
|
|
48
48
|
if (timeout !== undefined && timeSoFar + interval > timeout) {
|
package/src/util/Abort.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { TimeoutError } from "#MatterError.js";
|
|
8
8
|
import { Duration } from "#time/Duration.js";
|
|
9
9
|
import { Time, Timer } from "#time/Time.js";
|
|
10
|
+
import { SafePromise } from "./Promises.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Utilities for implementing abort logic.
|
|
@@ -61,14 +62,14 @@ export namespace Abort {
|
|
|
61
62
|
off = () => (signal as AbortSignal).removeEventListener("abort", onabort);
|
|
62
63
|
});
|
|
63
64
|
|
|
64
|
-
return
|
|
65
|
+
return SafePromise.race([aborted, ...promises]).finally(off!);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
if (promises.length === 1) {
|
|
68
69
|
return Promise.resolve(promises[0]);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
return
|
|
72
|
+
return SafePromise.race(promises);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
/**
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2025 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Bytes } from "#util/Bytes.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A source of entropy.
|
|
11
|
+
*/
|
|
12
|
+
export abstract class Entropy {
|
|
13
|
+
/**
|
|
14
|
+
* Create a random buffer from the most cryptographically-appropriate source available.
|
|
15
|
+
*/
|
|
16
|
+
abstract randomBytes(length: number): Bytes;
|
|
17
|
+
|
|
18
|
+
get randomUint8() {
|
|
19
|
+
return Bytes.of(this.randomBytes(1))[0];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get randomUint16() {
|
|
23
|
+
return Bytes.dataViewOf(this.randomBytes(2)).getUint16(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get randomUint32() {
|
|
27
|
+
return Bytes.dataViewOf(this.randomBytes(4)).getUint32(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get randomBigUint64() {
|
|
31
|
+
return Bytes.dataViewOf(this.randomBytes(8)).getBigUint64(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
randomBigInt(size: number, maxValue?: bigint) {
|
|
35
|
+
if (maxValue === undefined) {
|
|
36
|
+
return Bytes.asBigInt(this.randomBytes(size));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while (true) {
|
|
40
|
+
const random = Bytes.asBigInt(this.randomBytes(size));
|
|
41
|
+
if (random < maxValue) return random;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/util/Observable.ts
CHANGED
|
@@ -105,6 +105,14 @@ export interface Observable<T extends any[] = any[], R = void> extends AsyncIter
|
|
|
105
105
|
*/
|
|
106
106
|
handlePromise: ObserverPromiseHandler | boolean;
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Creates a promise that resolves when next emitted.
|
|
110
|
+
*/
|
|
111
|
+
then<TResult1 = T, TResult2 = never>(
|
|
112
|
+
onfulfilled?: ((value: T[0]) => TResult1 | PromiseLike<TResult1>) | null,
|
|
113
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
|
114
|
+
): Promise<TResult1 | TResult2>;
|
|
115
|
+
|
|
108
116
|
/**
|
|
109
117
|
* Observable supports standard "for await (const value of observable").
|
|
110
118
|
*
|
|
@@ -119,6 +127,38 @@ export interface Observable<T extends any[] = any[], R = void> extends AsyncIter
|
|
|
119
127
|
[Symbol.dispose](): void;
|
|
120
128
|
}
|
|
121
129
|
|
|
130
|
+
/**
|
|
131
|
+
* An observable value.
|
|
132
|
+
*
|
|
133
|
+
* This is a stateful observable that remembers its last emitted value and maps to standard Promise semantics.
|
|
134
|
+
*
|
|
135
|
+
* Unlike a normal {@link Observable}, awaiting an {@link ObservableValue} will result in immediate resolution if the
|
|
136
|
+
* value is truthy, and immediately upon updating to a truthy value otherwise.
|
|
137
|
+
*
|
|
138
|
+
* Also unlike a normal {@link Observable}, an {@link ObservableValue} may be placed into an error state which will
|
|
139
|
+
* result in rejection if awaited.
|
|
140
|
+
*/
|
|
141
|
+
export interface ObservableValue<T extends [any, ...any[]] = [boolean]> extends Observable<T, void>, Promise<T[0]> {
|
|
142
|
+
value: T[0] | undefined;
|
|
143
|
+
error?: Error;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Place the observable into an error state.
|
|
147
|
+
*
|
|
148
|
+
* The error is cleared on next emit.
|
|
149
|
+
*/
|
|
150
|
+
reject(cause: unknown): void;
|
|
151
|
+
|
|
152
|
+
then<TResult1 = T, TResult2 = never>(
|
|
153
|
+
onfulfilled?: ((value: T[0]) => TResult1 | PromiseLike<TResult1>) | null,
|
|
154
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
|
155
|
+
): Promise<TResult1 | TResult2>;
|
|
156
|
+
|
|
157
|
+
catch<TResult = never>(
|
|
158
|
+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
|
|
159
|
+
): Promise<T[0] | TResult>;
|
|
160
|
+
}
|
|
161
|
+
|
|
122
162
|
/**
|
|
123
163
|
* An observer may designate itself as "not observant" for the purposes of {@link Observable.isObserved} by returning
|
|
124
164
|
* false from this field.
|
|
@@ -315,12 +355,12 @@ export class BasicObservable<T extends any[] = any[], R = void> implements Obser
|
|
|
315
355
|
}
|
|
316
356
|
|
|
317
357
|
then<TResult1 = T, TResult2 = never>(
|
|
318
|
-
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
358
|
+
onfulfilled?: ((value: T[0]) => TResult1 | PromiseLike<TResult1>) | null,
|
|
319
359
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
|
320
|
-
):
|
|
360
|
+
): Promise<TResult1 | TResult2> {
|
|
321
361
|
return new Promise<T>(resolve => {
|
|
322
362
|
this.once((...payload): undefined => {
|
|
323
|
-
resolve(payload);
|
|
363
|
+
resolve(payload[0]);
|
|
324
364
|
});
|
|
325
365
|
}).then(onfulfilled, onrejected);
|
|
326
366
|
}
|
|
@@ -417,6 +457,115 @@ function event<E, N extends string>(emitter: E, name: N) {
|
|
|
417
457
|
return observer as Observable;
|
|
418
458
|
}
|
|
419
459
|
|
|
460
|
+
/**
|
|
461
|
+
* A concrete {@link ObservableValue} implementation.
|
|
462
|
+
*/
|
|
463
|
+
export class BasicObservableValue<T extends [any, ...any[]] = [boolean]>
|
|
464
|
+
extends BasicObservable<T, void>
|
|
465
|
+
implements ObservableValue<T>
|
|
466
|
+
{
|
|
467
|
+
#value: T | undefined;
|
|
468
|
+
#error?: Error;
|
|
469
|
+
#awaiters?: {
|
|
470
|
+
resolve?: ((value: T[0]) => void) | null;
|
|
471
|
+
reject?: ((reason: any) => void) | null;
|
|
472
|
+
}[];
|
|
473
|
+
|
|
474
|
+
constructor(value?: T, handleError?: ObserverErrorHandler, asyncConfig?: ObserverPromiseHandler | boolean) {
|
|
475
|
+
super(handleError, asyncConfig);
|
|
476
|
+
this.#value = value;
|
|
477
|
+
this.on(this.#maybeResolve.bind(this) as unknown as Observer<T, void>);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* The current value.
|
|
482
|
+
*
|
|
483
|
+
* This will resolve the promise interface but you must use {@link emit} to also emit an event..
|
|
484
|
+
*/
|
|
485
|
+
get value(): T[0] | undefined {
|
|
486
|
+
return this.#value;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
set value(value: T[0] | undefined) {
|
|
490
|
+
this.#maybeResolve([value]);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
get error() {
|
|
494
|
+
return this.#error;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
reject(cause: unknown) {
|
|
498
|
+
cause = asError(cause);
|
|
499
|
+
this.#value = undefined;
|
|
500
|
+
this.#error = cause as Error;
|
|
501
|
+
const awaiters = this.#awaiters;
|
|
502
|
+
if (awaiters) {
|
|
503
|
+
this.#awaiters = undefined;
|
|
504
|
+
for (const awaiter of awaiters) {
|
|
505
|
+
awaiter.reject?.(cause as Error);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
#maybeResolve(value: T[0] | undefined) {
|
|
511
|
+
this.#value = value;
|
|
512
|
+
if (!this.#value) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const awaiters = this.#awaiters;
|
|
517
|
+
if (awaiters) {
|
|
518
|
+
this.#awaiters = undefined;
|
|
519
|
+
for (const awaiter of awaiters) {
|
|
520
|
+
awaiter.resolve?.(this.#value);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
override then<TResult1 = T, TResult2 = never>(
|
|
526
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
527
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
|
528
|
+
): Promise<TResult1 | TResult2> {
|
|
529
|
+
if (this.#error) {
|
|
530
|
+
return Promise.reject(this.#error).then(onfulfilled, onrejected);
|
|
531
|
+
}
|
|
532
|
+
if (this.#value) {
|
|
533
|
+
return Promise.resolve(this.#value).then(onfulfilled, onrejected);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return new Promise<T>((resolve, reject) => {
|
|
537
|
+
if (!this.#awaiters) {
|
|
538
|
+
this.#awaiters = [];
|
|
539
|
+
}
|
|
540
|
+
this.#awaiters.push({ resolve, reject });
|
|
541
|
+
}).then(onfulfilled, onrejected);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
catch<TResult = never>(
|
|
545
|
+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
|
|
546
|
+
): Promise<T | TResult> {
|
|
547
|
+
return this.then(undefined, onrejected);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
finally(onfinally?: (() => void) | null): Promise<T> {
|
|
551
|
+
return Promise.resolve(this).finally(onfinally);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
[Symbol.toStringTag] = "Promise";
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Create an {@link ObservableValue}.
|
|
559
|
+
*/
|
|
560
|
+
export const ObservableValue = constructObservableValue as unknown as {
|
|
561
|
+
new <T extends [any, ...any[]]>(value?: T, errorHandler?: ObserverErrorHandler): ObservableValue<T>;
|
|
562
|
+
<T extends [any, ...any[]]>(value?: T, errorHandler?: ObserverErrorHandler): ObservableValue<T>;
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
function constructObservableValue(value?: [unknown, ...unknown[]], handleError?: ObserverErrorHandler) {
|
|
566
|
+
return new ObservableValue(value, handleError);
|
|
567
|
+
}
|
|
568
|
+
|
|
420
569
|
/**
|
|
421
570
|
* A set of observables. You can bind events using individual observables or the methods emulating a subset Node's
|
|
422
571
|
* EventEmitter.
|
package/src/util/Promises.ts
CHANGED
|
@@ -400,3 +400,112 @@ export class Gate<T = void> implements Promise<T> {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
MaybePromise.toString = () => "MaybePromise";
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Replacements for standard promise functionality that avoid spec pitfalls.
|
|
406
|
+
*/
|
|
407
|
+
export namespace SafePromise {
|
|
408
|
+
/**
|
|
409
|
+
* A version of {@link Promise.race} that won't leak memory with long-lived promises.
|
|
410
|
+
*
|
|
411
|
+
* See:
|
|
412
|
+
*
|
|
413
|
+
* https://github.com/nodejs/node/issues/17469#issuecomment-685216777
|
|
414
|
+
*
|
|
415
|
+
* ...although this isn't an issue specific to Node.
|
|
416
|
+
*/
|
|
417
|
+
export function race<T>(values: Iterable<T>): Promise<Awaited<T>> {
|
|
418
|
+
let listener!: SettlementListener;
|
|
419
|
+
let registered: undefined | Set<SettlementListener>[];
|
|
420
|
+
|
|
421
|
+
let race = new Promise<Awaited<T>>((resolve, reject) => {
|
|
422
|
+
listener = { resolve, reject };
|
|
423
|
+
|
|
424
|
+
for (const value of values) {
|
|
425
|
+
// If this is not a promise we can safely use Promise#resolve
|
|
426
|
+
if (!MaybePromise.is<any>(value)) {
|
|
427
|
+
Promise.resolve(value).then(resolve, reject);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// We only use Promise#then once per promise and dispatch to a set of listeners from there
|
|
432
|
+
const settlement = settlementOf(value);
|
|
433
|
+
if (settlement.isSettled) {
|
|
434
|
+
value.then(resolve, reject);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Register our listener
|
|
439
|
+
settlement.listeners.add(listener);
|
|
440
|
+
|
|
441
|
+
// Save the listener set so we can unregister our listener
|
|
442
|
+
if (registered) {
|
|
443
|
+
registered.push(settlement.listeners);
|
|
444
|
+
} else {
|
|
445
|
+
registered = [settlement.listeners];
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// If there were any unsettled promises, unregister our listener when settled
|
|
451
|
+
if (registered) {
|
|
452
|
+
race = race.finally(() => {
|
|
453
|
+
for (const listeners of registered!) {
|
|
454
|
+
listeners.delete(listener);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return race;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
interface SettlementListener {
|
|
463
|
+
resolve?: (result: any) => any;
|
|
464
|
+
reject?: (cause: any) => any;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
interface Settlement {
|
|
468
|
+
isSettled: boolean;
|
|
469
|
+
listeners: Set<SettlementListener>;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const settlements = new WeakMap<PromiseLike<any>, Settlement>();
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Component of {@link race}.
|
|
476
|
+
*
|
|
477
|
+
* Creates a {@link Settlement} that dispatches settlement to {@link SettlementListener}s for each raced promise.
|
|
478
|
+
*/
|
|
479
|
+
function settlementOf(promise: PromiseLike<any>) {
|
|
480
|
+
const existing = settlements.get(promise);
|
|
481
|
+
if (existing) {
|
|
482
|
+
return existing;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const settlement: Settlement = { isSettled: false, listeners: new Set() };
|
|
486
|
+
settlements.set(promise, settlement);
|
|
487
|
+
|
|
488
|
+
promise.then(
|
|
489
|
+
value => {
|
|
490
|
+
settlement.isSettled = true;
|
|
491
|
+
|
|
492
|
+
for (const listener of settlement.listeners) {
|
|
493
|
+
listener.resolve?.(value);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
settlement.listeners.clear();
|
|
497
|
+
},
|
|
498
|
+
cause => {
|
|
499
|
+
settlement.isSettled = true;
|
|
500
|
+
|
|
501
|
+
for (const listener of settlement.listeners) {
|
|
502
|
+
listener.reject?.(cause);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
settlement.listeners.clear();
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
return settlement;
|
|
510
|
+
}
|
|
511
|
+
}
|
package/src/util/Set.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { ImplementationError } from "#MatterError.js";
|
|
8
|
-
import { Observable } from "./Observable.js";
|
|
8
|
+
import { Observable, ObservableValue } from "./Observable.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A read-only set.
|
|
@@ -34,6 +34,7 @@ export interface MutableSet<T, AddT = T> {
|
|
|
34
34
|
export interface ObservableSet<T> {
|
|
35
35
|
get added(): Observable<[T]>;
|
|
36
36
|
get deleted(): Observable<[T]>;
|
|
37
|
+
get empty(): ObservableValue;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -63,6 +64,7 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
|
|
|
63
64
|
#entries = new Set<T>();
|
|
64
65
|
#added?: Observable<[T]>;
|
|
65
66
|
#deleted?: Observable<[T]>;
|
|
67
|
+
#empty?: ObservableValue;
|
|
66
68
|
#indices?: {
|
|
67
69
|
[field in keyof T]?: Map<T[field], T>;
|
|
68
70
|
};
|
|
@@ -211,6 +213,10 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
|
|
|
211
213
|
|
|
212
214
|
this.#deleted?.emit(item);
|
|
213
215
|
|
|
216
|
+
if (this.#empty && !this.size) {
|
|
217
|
+
this.#empty.emit(true);
|
|
218
|
+
}
|
|
219
|
+
|
|
214
220
|
return true;
|
|
215
221
|
}
|
|
216
222
|
|
|
@@ -234,6 +240,13 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
|
|
|
234
240
|
return this.#deleted;
|
|
235
241
|
}
|
|
236
242
|
|
|
243
|
+
get empty() {
|
|
244
|
+
if (this.#empty === undefined) {
|
|
245
|
+
this.#empty = ObservableValue();
|
|
246
|
+
}
|
|
247
|
+
return this.#empty;
|
|
248
|
+
}
|
|
249
|
+
|
|
237
250
|
protected create(definition: AddT) {
|
|
238
251
|
return definition as unknown as T;
|
|
239
252
|
}
|
package/src/util/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from "./DataWriter.js";
|
|
|
17
17
|
export * from "./Decorator.js";
|
|
18
18
|
export * from "./DeepCopy.js";
|
|
19
19
|
export * from "./DeepEqual.js";
|
|
20
|
+
export * from "./Entropy.js";
|
|
20
21
|
export * from "./Error.js";
|
|
21
22
|
export * from "./FormattedText.js";
|
|
22
23
|
export * from "./GeneratedClass.js";
|