@matter/general 0.16.1 → 0.16.2-alpha.0-20260114-d3127faee

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 (98) hide show
  1. package/dist/cjs/net/ServerAddress.d.ts +52 -14
  2. package/dist/cjs/net/ServerAddress.d.ts.map +1 -1
  3. package/dist/cjs/net/ServerAddress.js +41 -6
  4. package/dist/cjs/net/ServerAddress.js.map +2 -2
  5. package/dist/cjs/net/ServerAddressSet.d.ts +65 -0
  6. package/dist/cjs/net/ServerAddressSet.d.ts.map +1 -0
  7. package/dist/cjs/net/ServerAddressSet.js +144 -0
  8. package/dist/cjs/net/ServerAddressSet.js.map +6 -0
  9. package/dist/cjs/net/dns-sd/MdnsSocket.d.ts +40 -0
  10. package/dist/cjs/net/dns-sd/MdnsSocket.d.ts.map +1 -0
  11. package/dist/cjs/net/dns-sd/MdnsSocket.js +164 -0
  12. package/dist/cjs/net/dns-sd/MdnsSocket.js.map +6 -0
  13. package/dist/cjs/net/dns-sd/index.d.ts +7 -0
  14. package/dist/cjs/net/dns-sd/index.d.ts.map +1 -0
  15. package/dist/cjs/net/dns-sd/index.js +24 -0
  16. package/dist/cjs/net/dns-sd/index.js.map +6 -0
  17. package/dist/cjs/net/index.d.ts +2 -0
  18. package/dist/cjs/net/index.d.ts.map +1 -1
  19. package/dist/cjs/net/index.js +2 -0
  20. package/dist/cjs/net/index.js.map +1 -1
  21. package/dist/cjs/util/Abort.d.ts +9 -0
  22. package/dist/cjs/util/Abort.d.ts.map +1 -1
  23. package/dist/cjs/util/Abort.js +20 -0
  24. package/dist/cjs/util/Abort.js.map +1 -1
  25. package/dist/cjs/util/Heap.d.ts +84 -0
  26. package/dist/cjs/util/Heap.d.ts.map +1 -0
  27. package/dist/cjs/util/Heap.js +286 -0
  28. package/dist/cjs/util/Heap.js.map +6 -0
  29. package/dist/cjs/util/Observable.d.ts +29 -6
  30. package/dist/cjs/util/Observable.d.ts.map +1 -1
  31. package/dist/cjs/util/Observable.js +40 -6
  32. package/dist/cjs/util/Observable.js.map +1 -1
  33. package/dist/cjs/util/Promises.d.ts +3 -0
  34. package/dist/cjs/util/Promises.d.ts.map +1 -1
  35. package/dist/cjs/util/Promises.js +33 -3
  36. package/dist/cjs/util/Promises.js.map +2 -2
  37. package/dist/cjs/util/Set.d.ts.map +1 -1
  38. package/dist/cjs/util/Set.js +14 -8
  39. package/dist/cjs/util/Set.js.map +1 -1
  40. package/dist/cjs/util/index.d.ts +1 -0
  41. package/dist/cjs/util/index.d.ts.map +1 -1
  42. package/dist/cjs/util/index.js +1 -0
  43. package/dist/cjs/util/index.js.map +1 -1
  44. package/dist/esm/net/ServerAddress.d.ts +52 -14
  45. package/dist/esm/net/ServerAddress.d.ts.map +1 -1
  46. package/dist/esm/net/ServerAddress.js +41 -6
  47. package/dist/esm/net/ServerAddress.js.map +2 -2
  48. package/dist/esm/net/ServerAddressSet.d.ts +65 -0
  49. package/dist/esm/net/ServerAddressSet.d.ts.map +1 -0
  50. package/dist/esm/net/ServerAddressSet.js +124 -0
  51. package/dist/esm/net/ServerAddressSet.js.map +6 -0
  52. package/dist/esm/net/dns-sd/MdnsSocket.d.ts +40 -0
  53. package/dist/esm/net/dns-sd/MdnsSocket.d.ts.map +1 -0
  54. package/dist/esm/net/dns-sd/MdnsSocket.js +149 -0
  55. package/dist/esm/net/dns-sd/MdnsSocket.js.map +6 -0
  56. package/dist/esm/net/dns-sd/index.d.ts +7 -0
  57. package/dist/esm/net/dns-sd/index.d.ts.map +1 -0
  58. package/dist/esm/net/dns-sd/index.js +7 -0
  59. package/dist/esm/net/dns-sd/index.js.map +6 -0
  60. package/dist/esm/net/index.d.ts +2 -0
  61. package/dist/esm/net/index.d.ts.map +1 -1
  62. package/dist/esm/net/index.js +2 -0
  63. package/dist/esm/net/index.js.map +1 -1
  64. package/dist/esm/util/Abort.d.ts +9 -0
  65. package/dist/esm/util/Abort.d.ts.map +1 -1
  66. package/dist/esm/util/Abort.js +20 -0
  67. package/dist/esm/util/Abort.js.map +1 -1
  68. package/dist/esm/util/Heap.d.ts +84 -0
  69. package/dist/esm/util/Heap.d.ts.map +1 -0
  70. package/dist/esm/util/Heap.js +266 -0
  71. package/dist/esm/util/Heap.js.map +6 -0
  72. package/dist/esm/util/Observable.d.ts +29 -6
  73. package/dist/esm/util/Observable.d.ts.map +1 -1
  74. package/dist/esm/util/Observable.js +40 -6
  75. package/dist/esm/util/Observable.js.map +1 -1
  76. package/dist/esm/util/Promises.d.ts +3 -0
  77. package/dist/esm/util/Promises.d.ts.map +1 -1
  78. package/dist/esm/util/Promises.js +33 -3
  79. package/dist/esm/util/Promises.js.map +2 -2
  80. package/dist/esm/util/Set.d.ts.map +1 -1
  81. package/dist/esm/util/Set.js +14 -8
  82. package/dist/esm/util/Set.js.map +1 -1
  83. package/dist/esm/util/index.d.ts +1 -0
  84. package/dist/esm/util/index.d.ts.map +1 -1
  85. package/dist/esm/util/index.js +1 -0
  86. package/dist/esm/util/index.js.map +1 -1
  87. package/package.json +2 -2
  88. package/src/net/ServerAddress.ts +93 -19
  89. package/src/net/ServerAddressSet.ts +225 -0
  90. package/src/net/dns-sd/MdnsSocket.ts +203 -0
  91. package/src/net/dns-sd/index.ts +7 -0
  92. package/src/net/index.ts +2 -0
  93. package/src/util/Abort.ts +25 -0
  94. package/src/util/Heap.ts +332 -0
  95. package/src/util/Observable.ts +74 -10
  96. package/src/util/Promises.ts +61 -4
  97. package/src/util/Set.ts +15 -8
  98. package/src/util/index.ts +1 -0
@@ -0,0 +1,332 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2026 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { InternalError } from "#MatterError.js";
8
+ import { Abort } from "./Abort.js";
9
+ import { Observable } from "./Observable.js";
10
+
11
+ /**
12
+ * A heap, useful as a priority queue.
13
+ *
14
+ * Features:
15
+ *
16
+ * * Configurable as min-heap or max-heap using comparator
17
+ *
18
+ * * O(1) lookup of first item
19
+ *
20
+ * * O(log(n)) enqueue and dequeue
21
+ *
22
+ * * O(1) deletion of arbitrary position, but will add expense of maintaining index of positions
23
+ *
24
+ * * {@link added}, {@link deleted} and {@link firstChanged} events
25
+ */
26
+ export class Heap<T> {
27
+ // We encode the heap as an array for efficiency. See #leftChildOf and #rightChildOf for how to navigate the tree
28
+ // when encoded this way
29
+ readonly #buffer = Array<T>();
30
+
31
+ readonly #compare: (a: T, b: T) => number;
32
+ readonly #normalize?: (a: T) => T;
33
+ #firstChanged?: Observable<[T | undefined]>;
34
+ #deleted?: Observable<[T]>;
35
+ #added?: Observable<[T]>;
36
+ #positions?: Map<T, number>;
37
+
38
+ /**
39
+ * Create new heap.
40
+ *
41
+ * @param comparator performs ordering of items in the heap
42
+ * @param normalizer optionally converts items to normal form on insert
43
+ */
44
+ constructor(comparator: (a: T, b: T) => number, normalizer?: (entry: T) => T) {
45
+ this.#compare = comparator;
46
+ this.#normalize = normalizer;
47
+ }
48
+
49
+ /**
50
+ * Return lowest-ranked item.
51
+ */
52
+ shift() {
53
+ if (!this.#buffer?.length) {
54
+ return undefined;
55
+ }
56
+ const result = this.first;
57
+
58
+ this.#deleteAt(0);
59
+
60
+ return result;
61
+ }
62
+
63
+ /**
64
+ * The lowest-ranked item.
65
+ */
66
+ get first(): T | undefined {
67
+ return this.#buffer?.[0];
68
+ }
69
+
70
+ /**
71
+ * The queue length.
72
+ */
73
+ get size() {
74
+ return this.#buffer.length;
75
+ }
76
+
77
+ /**
78
+ * Is the heap empty?
79
+ */
80
+ get isEmpty() {
81
+ return !!this.#buffer.length;
82
+ }
83
+
84
+ /**
85
+ * Emits when the head of the queue changes.
86
+ */
87
+ get firstChanged() {
88
+ if (!this.#firstChanged) {
89
+ this.#firstChanged = new Observable();
90
+ }
91
+ return this.#firstChanged;
92
+ }
93
+
94
+ /**
95
+ * Emits when an item is added to the heap.
96
+ */
97
+ get added() {
98
+ if (!this.#added) {
99
+ this.#added = new Observable();
100
+ }
101
+ return this.#added;
102
+ }
103
+
104
+ /**
105
+ * Emits when an item is deleted.
106
+ */
107
+ get deleted() {
108
+ if (!this.#deleted) {
109
+ this.#deleted = new Observable();
110
+ }
111
+ return this.#deleted;
112
+ }
113
+
114
+ /**
115
+ * Add an item.
116
+ */
117
+ add(...items: T[]) {
118
+ for (let item of items) {
119
+ if (this.#normalize) {
120
+ item = this.#normalize(item);
121
+ }
122
+
123
+ this.#buffer.push(item);
124
+ this.#bubbleUp(this.#buffer.length - 1);
125
+
126
+ this.#added?.emit(item);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Delete an item.
132
+ *
133
+ * The first delete is O(n); subsequent deletes are O(1) but insertions and deletions will have additional cost
134
+ * associated.
135
+ */
136
+ delete(item: T) {
137
+ if (this.#buffer === undefined) {
138
+ return false;
139
+ }
140
+
141
+ if (!this.#positions) {
142
+ this.#positions = new Map();
143
+ for (let i = 0; i < this.#buffer.length; i++) {
144
+ this.#positions.set(this.#buffer[i], i);
145
+ }
146
+ }
147
+
148
+ if (this.#normalize) {
149
+ item = this.#normalize(item);
150
+ }
151
+
152
+ const pos = this.#positions.get(item);
153
+ if (pos === undefined) {
154
+ return false;
155
+ }
156
+
157
+ this.#deleteAt(pos);
158
+ return true;
159
+ }
160
+
161
+ /**
162
+ * Remove all entries.
163
+ */
164
+ clear() {
165
+ if (this.#buffer.length) this.#buffer.length = 0;
166
+ }
167
+
168
+ /**
169
+ * Stream the first value from the heap until aborted.
170
+ */
171
+ async *stream(abort?: Abort.Signal) {
172
+ if (Abort.is(abort)) {
173
+ return;
174
+ }
175
+
176
+ while (true) {
177
+ // Yield values currently available
178
+ while (this.size) {
179
+ yield this.shift()!;
180
+
181
+ if (Abort.is(abort)) {
182
+ return;
183
+ }
184
+ }
185
+
186
+ // Create promise to resolve when a new value is ready
187
+ let resolve!: () => void;
188
+ const ready = new Promise<void>(r => (resolve = r));
189
+
190
+ // Wait for new value
191
+ try {
192
+ this.added.once(resolve);
193
+
194
+ await Abort.race(abort, ready);
195
+
196
+ if (Abort.is(abort)) {
197
+ return;
198
+ }
199
+ } finally {
200
+ this.added.off(resolve);
201
+ }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Perform internal validation of queue order.
207
+ */
208
+ validate() {
209
+ for (let i = 0; i < this.#buffer.length; i++) {
210
+ const leftChild = this.#leftChildOf(i);
211
+ if (leftChild < this.#buffer.length) {
212
+ if (this.#compare(this.#buffer[i], this.#buffer[leftChild]) > 0) {
213
+ throw new InternalError(
214
+ `Heap error: buffer #${i} (${this.#buffer[i]}) is greater than left child #${leftChild} (${this.#buffer[leftChild]})`,
215
+ );
216
+ }
217
+
218
+ const rightChild = this.#rightChildOf(i);
219
+ if (rightChild < this.#buffer.length) {
220
+ if (this.#compare(this.#buffer[i], this.#buffer[rightChild]) > 0) {
221
+ throw new InternalError(
222
+ `Heap error: buffer #${i} (${this.#buffer[i]}) is greater than right child #${rightChild} (${this.#buffer[rightChild]})`,
223
+ );
224
+ }
225
+
226
+ if (this.#compare(this.#buffer[leftChild], this.#buffer[rightChild]) > 0) {
227
+ throw new InternalError(
228
+ `Heap error: buffer #${leftChild} (${this.#buffer[leftChild]}) is greater than right sibling #${rightChild} (${this.#buffer[rightChild]})`,
229
+ );
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ #deleteAt(index: number) {
237
+ if (index >= this.#buffer.length) {
238
+ return;
239
+ }
240
+
241
+ const item = this.#buffer[index];
242
+ if (this.#buffer.length === 1) {
243
+ this.#buffer.length = 0;
244
+ } else {
245
+ const lastIndex = this.#buffer.length - 1;
246
+ this.#buffer[index] = this.#buffer[lastIndex];
247
+ this.#positions?.set(this.#buffer[index], index);
248
+ this.#buffer.length = lastIndex;
249
+ if (index < this.#buffer.length) {
250
+ if (index) {
251
+ index = this.#bubbleUp(index);
252
+ }
253
+ this.#sinkDown(index);
254
+ }
255
+ }
256
+
257
+ this.#positions?.delete(item);
258
+
259
+ this.#deleted?.emit(item);
260
+ if (!index) {
261
+ this.#firstChanged?.emit(this.first);
262
+ }
263
+ }
264
+
265
+ #sinkDown(index: number) {
266
+ while (true) {
267
+ let moveTo: number | undefined;
268
+
269
+ const leftChild = this.#leftChildOf(index);
270
+
271
+ if (leftChild < this.#buffer.length) {
272
+ if (this.#compare(this.#buffer[index], this.#buffer[leftChild]) > 0) {
273
+ moveTo = leftChild;
274
+ }
275
+
276
+ const rightChild = this.#rightChildOf(index);
277
+
278
+ if (rightChild < this.#buffer.length) {
279
+ if (this.#compare(this.#buffer[moveTo ?? index], this.#buffer[rightChild]) > 0) {
280
+ moveTo = rightChild;
281
+ }
282
+ }
283
+ }
284
+
285
+ if (moveTo === undefined) {
286
+ break;
287
+ }
288
+
289
+ this.#swap(index, moveTo);
290
+ index = moveTo;
291
+ }
292
+ }
293
+
294
+ #bubbleUp(index: number) {
295
+ while (index) {
296
+ const parent = this.#parentOf(index);
297
+
298
+ if (this.#compare(this.#buffer[parent], this.#buffer[index]) <= 0) {
299
+ break;
300
+ }
301
+
302
+ this.#swap(index, parent);
303
+ index = parent;
304
+ }
305
+
306
+ if (!index) {
307
+ this.#firstChanged?.emit(this.first);
308
+ }
309
+
310
+ return index;
311
+ }
312
+
313
+ #swap(index1: number, index2: number) {
314
+ [this.#buffer[index1], this.#buffer[index2]] = [this.#buffer[index2], this.#buffer[index1]];
315
+ if (this.#positions) {
316
+ this.#positions.set(this.#buffer[index1], index1);
317
+ this.#positions.set(this.#buffer[index2], index2);
318
+ }
319
+ }
320
+
321
+ #leftChildOf(index: number) {
322
+ return 2 * index + 1;
323
+ }
324
+
325
+ #rightChildOf(index: number) {
326
+ return 2 * index + 2;
327
+ }
328
+
329
+ #parentOf(index: number) {
330
+ return Math.floor((index - 1) / 2);
331
+ }
332
+ }
@@ -54,6 +54,16 @@ export interface Observable<T extends any[] = any[], R = void> extends AsyncIter
54
54
  */
55
55
  on(observer: Observer<T, R>): void;
56
56
 
57
+ /**
58
+ * Add an observer that may be released via disposal.
59
+ */
60
+ use(observer: Observer<T, R>): Disposable;
61
+
62
+ /**
63
+ * Add a "once" observer that may be released via disposal.
64
+ */
65
+ useOnce(observer: Observer<T, R>): Disposable;
66
+
57
67
  /**
58
68
  * Remove an observer.
59
69
  */
@@ -148,7 +158,13 @@ export interface Observable<T extends any[] = any[], R = void> extends AsyncIter
148
158
  */
149
159
  export interface ObservableValue<T extends [any, ...any[]] = [boolean], R extends MaybePromise<void> = void>
150
160
  extends Observable<T, R>, Promise<T[0]> {
161
+ /**
162
+ * The current value.
163
+ *
164
+ * Setting the value will resolve the promise interface but you must use {@link emit} to also emit an event.
165
+ */
151
166
  value: T[0] | undefined;
167
+
152
168
  error?: Error;
153
169
 
154
170
  /**
@@ -166,6 +182,12 @@ export interface ObservableValue<T extends [any, ...any[]] = [boolean], R extend
166
182
  catch<TResult = never>(
167
183
  onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
168
184
  ): Promise<T[0] | TResult>;
185
+
186
+ onError(handler: (cause: Error) => void): void;
187
+
188
+ offError(handler: (cause: Error) => void): void;
189
+
190
+ useError(handler: (cause: Error) => void): Disposable;
169
191
  }
170
192
 
171
193
  /**
@@ -344,7 +366,7 @@ export class BasicObservable<T extends any[] = any[], R = void> implements Obser
344
366
  };
345
367
  }
346
368
 
347
- // Initially emit using a synchronous loop. When we hit the first promies we convert to an async function
369
+ // Initially emit using a synchronous loop. When we hit the first promise we convert to an async function
348
370
  for (; nextObserver < observers.length; nextObserver++) {
349
371
  let result: ReturnType<Observer<T, R>>;
350
372
 
@@ -410,8 +432,27 @@ export class BasicObservable<T extends any[] = any[], R = void> implements Obser
410
432
  this.#observers.add(observer);
411
433
  }
412
434
 
435
+ use(observer: Observer<T, R>) {
436
+ this.on(observer);
437
+ return {
438
+ [Symbol.dispose]: () => {
439
+ this.off(observer);
440
+ },
441
+ };
442
+ }
443
+
444
+ useOnce(observer: Observer<T, R>) {
445
+ this.once(observer);
446
+ return {
447
+ [Symbol.dispose]: () => {
448
+ this.off(observer);
449
+ },
450
+ };
451
+ }
452
+
413
453
  off(observer: Observer<T, R>) {
414
454
  this.#observers?.delete(observer);
455
+ this.#once?.delete(observer);
415
456
  }
416
457
 
417
458
  once(observer: Observer<T, R>) {
@@ -552,9 +593,9 @@ function event<E, N extends string>(emitter: E, name: N) {
552
593
  /**
553
594
  * A concrete {@link ObservableValue} implementation.
554
595
  */
555
- export class BasicObservableValue<T extends [any, ...any[]] = [boolean]>
556
- extends BasicObservable<T, void>
557
- implements ObservableValue<T>
596
+ export class BasicObservableValue<T extends [any, ...any[]] = [boolean], R extends MaybePromise<void> = void>
597
+ extends BasicObservable<T, R>
598
+ implements ObservableValue<T, R>
558
599
  {
559
600
  #value: T | undefined;
560
601
  #error?: Error;
@@ -566,14 +607,12 @@ export class BasicObservableValue<T extends [any, ...any[]] = [boolean]>
566
607
  constructor(value?: T[0], handleError?: ObserverErrorHandler, asyncConfig?: ObserverPromiseHandler | boolean) {
567
608
  super(handleError, asyncConfig);
568
609
  this.#value = value;
569
- this.on(this.#maybeResolve.bind(this) as unknown as Observer<T, void>);
610
+
611
+ const maybeResolve = this.#maybeResolve.bind(this) as unknown as Observer<T, R>;
612
+ Object.defineProperty(maybeResolve, observant, { value: false });
613
+ this.on(maybeResolve);
570
614
  }
571
615
 
572
- /**
573
- * The current value.
574
- *
575
- * This will resolve the promise interface but you must use {@link emit} to also emit an event..
576
- */
577
616
  get value(): T[0] | undefined {
578
617
  return this.#value;
579
618
  }
@@ -639,6 +678,27 @@ export class BasicObservableValue<T extends [any, ...any[]] = [boolean]>
639
678
  return this.then(undefined, onrejected);
640
679
  }
641
680
 
681
+ onError(handler: (cause: Error) => void) {
682
+ if (!this.#awaiters) {
683
+ this.#awaiters = [];
684
+ }
685
+ this.#awaiters?.push({ resolve: undefined, reject: handler });
686
+ }
687
+
688
+ offError(handler: (cause: Error) => void) {
689
+ this.#awaiters = this.#awaiters?.filter(awaiter => awaiter.resolve === undefined && awaiter.reject === handler);
690
+ }
691
+
692
+ useError(handler: (cause: Error) => void) {
693
+ this.onError(handler);
694
+
695
+ return {
696
+ [Symbol.dispose]: () => {
697
+ this.offError(handler);
698
+ },
699
+ };
700
+ }
701
+
642
702
  finally(onfinally?: (() => void) | null): Promise<T> {
643
703
  return Promise.resolve(this).finally(onfinally);
644
704
  }
@@ -924,6 +984,10 @@ export class ObserverGroup {
924
984
  this.#observers.clear();
925
985
  this.#boundObservers.clear();
926
986
  }
987
+
988
+ [Symbol.dispose]() {
989
+ this.close();
990
+ }
927
991
  }
928
992
 
929
993
  /**
@@ -10,6 +10,13 @@ import { Duration } from "#time/Duration.js";
10
10
  import { asError } from "#util/Error.js";
11
11
  import { InternalError, TimeoutError } from "../MatterError.js";
12
12
  import { Time } from "../time/Time.js";
13
+ import type {
14
+ AsyncObservable,
15
+ AsyncObservableValue,
16
+ AsyncObserver,
17
+ Observable,
18
+ ObservableValue,
19
+ } from "./Observable.js";
13
20
 
14
21
  /**
15
22
  * Obtain a promise with functions to resolve and reject.
@@ -413,10 +420,14 @@ export namespace SafePromise {
413
420
  * https://github.com/nodejs/node/issues/17469#issuecomment-685216777
414
421
  *
415
422
  * ...although this isn't an issue specific to Node.
423
+ *
424
+ * We specialize support for {@link Observable} and {@link ObservableValue}. Those contracts are awaitable like a
425
+ * {@link Promise} but we instead register listeners directly so we can unregister using {@link Observable#off}.
416
426
  */
417
427
  export function race<T>(values: Iterable<T>): Promise<Awaited<T>> {
418
428
  let listener!: SettlementListener;
419
429
  let registered: undefined | Set<SettlementListener>[];
430
+ let disposables: undefined | Disposable[];
420
431
 
421
432
  let race = new Promise<Awaited<T>>((resolve, reject) => {
422
433
  listener = { resolve, reject };
@@ -428,6 +439,44 @@ export namespace SafePromise {
428
439
  continue;
429
440
  }
430
441
 
442
+ // If this is an Observable, use on/off so we can reliably unregister listeners
443
+ if (
444
+ "use" in value &&
445
+ "off" in value &&
446
+ typeof value.use === "function" &&
447
+ typeof value.off === "function"
448
+ ) {
449
+ // Further specialize for ObservableValue contract, which behaves as resolved when a truthy value is
450
+ // present
451
+ if ("value" in value && value.value) {
452
+ Promise.resolve(value.value as Awaited<T>).then(resolve, reject);
453
+ continue;
454
+ }
455
+
456
+ if (!disposables) {
457
+ disposables = [];
458
+ }
459
+
460
+ let observer: AsyncObserver<[Awaited<T>]>;
461
+ if ("value" in value) {
462
+ // For observable value, only resolve if value is truthy
463
+ observer = value => {
464
+ if (value) {
465
+ resolve(value);
466
+ }
467
+ };
468
+
469
+ // And handle errors
470
+ disposables.push((value as unknown as AsyncObservableValue).useError(reject));
471
+ } else {
472
+ // Normal observables "resolve" on any emit and do not have an error channel
473
+ observer = resolve;
474
+ }
475
+ disposables.push((value as unknown as AsyncObservable<[Awaited<T>]>).use(observer));
476
+
477
+ continue;
478
+ }
479
+
431
480
  // We only use Promise#then once per promise and dispatch to a set of listeners from there
432
481
  const settlement = settlementOf(value);
433
482
  if (settlement.isSettled) {
@@ -447,11 +496,19 @@ export namespace SafePromise {
447
496
  }
448
497
  });
449
498
 
450
- // If there were any unsettled promises, unregister our listener when settled
451
- if (registered) {
499
+ // Ensure we unregister listeners when settled
500
+ if (registered || disposables) {
452
501
  race = race.finally(() => {
453
- for (const listeners of registered!) {
454
- listeners.delete(listener);
502
+ if (registered) {
503
+ for (const listeners of registered) {
504
+ listeners.delete(listener);
505
+ }
506
+ }
507
+
508
+ if (disposables) {
509
+ for (const disposable of disposables) {
510
+ disposable[Symbol.dispose]();
511
+ }
455
512
  }
456
513
  });
457
514
  }
package/src/util/Set.ts CHANGED
@@ -61,7 +61,7 @@ export interface IndexedSet<T> {
61
61
  * Unused features have minimal performance impact.
62
62
  */
63
63
  export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, AddT>, ObservableSet<T>, IndexedSet<T> {
64
- #entries = new Set<T>();
64
+ #entries?: Set<T>;
65
65
  #added?: Observable<[T]>;
66
66
  #deleted?: Observable<[T]>;
67
67
  #empty?: ObservableValue;
@@ -79,11 +79,11 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
79
79
  }
80
80
 
81
81
  [Symbol.iterator]() {
82
- return this.#entries[Symbol.iterator]();
82
+ return this.#definedEntries[Symbol.iterator]();
83
83
  }
84
84
 
85
85
  get size() {
86
- return this.#entries.size;
86
+ return this.#entries?.size ?? 0;
87
87
  }
88
88
 
89
89
  map<R>(mapper: (item: T) => R) {
@@ -109,17 +109,17 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
109
109
  }
110
110
 
111
111
  has(item: T) {
112
- return this.#entries.has(item);
112
+ return this.#entries?.has(item) ?? false;
113
113
  }
114
114
 
115
115
  add(item: AddT) {
116
116
  const created = this.create(item);
117
117
 
118
- if (this.#entries.has(item as any)) {
118
+ if (this.#definedEntries.has(item as any)) {
119
119
  return;
120
120
  }
121
121
 
122
- this.#entries.add(item as any);
122
+ this.#definedEntries.add(item as any);
123
123
 
124
124
  if (this.#indices) {
125
125
  for (const field in this.#indices) {
@@ -148,6 +148,13 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
148
148
  return this.#indexOf(field).get(value);
149
149
  }
150
150
 
151
+ get #definedEntries() {
152
+ if (this.#entries === undefined) {
153
+ this.#entries = new Set();
154
+ }
155
+ return this.#entries;
156
+ }
157
+
151
158
  #indexOf<F extends keyof T>(field: F) {
152
159
  if (!this.#indices) {
153
160
  this.#indices = {};
@@ -197,7 +204,7 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
197
204
  }
198
205
  }
199
206
 
200
- if (!this.#entries.delete(item)) {
207
+ if (!this.#entries?.delete(item)) {
201
208
  return false;
202
209
  }
203
210
 
@@ -246,7 +253,7 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
246
253
 
247
254
  get empty() {
248
255
  if (this.#empty === undefined) {
249
- this.#empty = ObservableValue(!this.#entries.size);
256
+ this.#empty = ObservableValue(!this.#entries?.size);
250
257
  }
251
258
  return this.#empty;
252
259
  }
package/src/util/index.ts CHANGED
@@ -23,6 +23,7 @@ export * from "./FormattedText.js";
23
23
  export * from "./Function.js";
24
24
  export * from "./GeneratedClass.js";
25
25
  export * from "./Github.js";
26
+ export * from "./Heap.js";
26
27
  export * from "./identifier-case.js";
27
28
  export * from "./Ip.js";
28
29
  export * from "./Lifecycle.js";