@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.
Files changed (78) hide show
  1. package/dist/cjs/crypto/Crypto.d.ts +2 -10
  2. package/dist/cjs/crypto/Crypto.d.ts.map +1 -1
  3. package/dist/cjs/crypto/Crypto.js +2 -23
  4. package/dist/cjs/crypto/Crypto.js.map +1 -1
  5. package/dist/cjs/crypto/StandardCrypto.d.ts.map +1 -1
  6. package/dist/cjs/crypto/StandardCrypto.js +4 -1
  7. package/dist/cjs/crypto/StandardCrypto.js.map +1 -1
  8. package/dist/cjs/net/RetrySchedule.d.ts +2 -2
  9. package/dist/cjs/net/RetrySchedule.d.ts.map +1 -1
  10. package/dist/cjs/net/RetrySchedule.js +4 -4
  11. package/dist/cjs/net/RetrySchedule.js.map +1 -1
  12. package/dist/cjs/util/Abort.d.ts.map +1 -1
  13. package/dist/cjs/util/Abort.js +3 -2
  14. package/dist/cjs/util/Abort.js.map +1 -1
  15. package/dist/cjs/util/Entropy.d.ts +21 -0
  16. package/dist/cjs/util/Entropy.d.ts.map +1 -0
  17. package/dist/cjs/util/Entropy.js +53 -0
  18. package/dist/cjs/util/Entropy.js.map +6 -0
  19. package/dist/cjs/util/Observable.d.ts +55 -1
  20. package/dist/cjs/util/Observable.d.ts.map +1 -1
  21. package/dist/cjs/util/Observable.js +77 -1
  22. package/dist/cjs/util/Observable.js.map +1 -1
  23. package/dist/cjs/util/Promises.d.ts +15 -0
  24. package/dist/cjs/util/Promises.d.ts.map +1 -1
  25. package/dist/cjs/util/Promises.js +63 -0
  26. package/dist/cjs/util/Promises.js.map +2 -2
  27. package/dist/cjs/util/Set.d.ts +3 -1
  28. package/dist/cjs/util/Set.d.ts.map +1 -1
  29. package/dist/cjs/util/Set.js +10 -0
  30. package/dist/cjs/util/Set.js.map +1 -1
  31. package/dist/cjs/util/index.d.ts +1 -0
  32. package/dist/cjs/util/index.d.ts.map +1 -1
  33. package/dist/cjs/util/index.js +1 -0
  34. package/dist/cjs/util/index.js.map +1 -1
  35. package/dist/esm/crypto/Crypto.d.ts +2 -10
  36. package/dist/esm/crypto/Crypto.d.ts.map +1 -1
  37. package/dist/esm/crypto/Crypto.js +2 -23
  38. package/dist/esm/crypto/Crypto.js.map +1 -1
  39. package/dist/esm/crypto/StandardCrypto.d.ts.map +1 -1
  40. package/dist/esm/crypto/StandardCrypto.js +4 -1
  41. package/dist/esm/crypto/StandardCrypto.js.map +1 -1
  42. package/dist/esm/net/RetrySchedule.d.ts +2 -2
  43. package/dist/esm/net/RetrySchedule.d.ts.map +1 -1
  44. package/dist/esm/net/RetrySchedule.js +4 -4
  45. package/dist/esm/net/RetrySchedule.js.map +1 -1
  46. package/dist/esm/util/Abort.d.ts.map +1 -1
  47. package/dist/esm/util/Abort.js +3 -2
  48. package/dist/esm/util/Abort.js.map +1 -1
  49. package/dist/esm/util/Entropy.d.ts +21 -0
  50. package/dist/esm/util/Entropy.d.ts.map +1 -0
  51. package/dist/esm/util/Entropy.js +33 -0
  52. package/dist/esm/util/Entropy.js.map +6 -0
  53. package/dist/esm/util/Observable.d.ts +55 -1
  54. package/dist/esm/util/Observable.d.ts.map +1 -1
  55. package/dist/esm/util/Observable.js +77 -1
  56. package/dist/esm/util/Observable.js.map +1 -1
  57. package/dist/esm/util/Promises.d.ts +15 -0
  58. package/dist/esm/util/Promises.d.ts.map +1 -1
  59. package/dist/esm/util/Promises.js +63 -0
  60. package/dist/esm/util/Promises.js.map +2 -2
  61. package/dist/esm/util/Set.d.ts +3 -1
  62. package/dist/esm/util/Set.d.ts.map +1 -1
  63. package/dist/esm/util/Set.js +11 -1
  64. package/dist/esm/util/Set.js.map +1 -1
  65. package/dist/esm/util/index.d.ts +1 -0
  66. package/dist/esm/util/index.d.ts.map +1 -1
  67. package/dist/esm/util/index.js +1 -0
  68. package/dist/esm/util/index.js.map +1 -1
  69. package/package.json +2 -2
  70. package/src/crypto/Crypto.ts +2 -33
  71. package/src/crypto/StandardCrypto.ts +4 -1
  72. package/src/net/RetrySchedule.ts +5 -5
  73. package/src/util/Abort.ts +3 -2
  74. package/src/util/Entropy.ts +44 -0
  75. package/src/util/Observable.ts +152 -3
  76. package/src/util/Promises.ts +109 -0
  77. package/src/util/Set.ts +14 -1
  78. 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-20251023-bb23617e5",
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-20251023-bb23617e5"
39
+ "@matter/testing": "0.16.0-alpha.0-20251029-bd92894d4"
40
40
  },
41
41
  "files": [
42
42
  "dist/**/*",
@@ -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
- Environment.default.set(Crypto, new StandardCrypto());
309
+ const crypto = new StandardCrypto();
310
+ Environment.default.set(Entropy, crypto);
311
+ Environment.default.set(Crypto, crypto);
309
312
  }
@@ -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
- #crypto: Crypto;
15
+ #entropy: Entropy;
16
16
  readonly config: RetrySchedule.Configuration;
17
17
 
18
- constructor(crypto: Crypto, config: RetrySchedule.Configuration) {
19
- this.#crypto = crypto;
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.#crypto.randomUint32) / Math.pow(2, 32)));
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 Promise.race([aborted, ...promises]).finally(off!);
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 Promise.race(promises);
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
+ }
@@ -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
- ): PromiseLike<TResult1 | TResult2> {
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.
@@ -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";