@matter/general 0.16.0-alpha.0-20251003-dc6d5523d → 0.16.0-alpha.0-20251004-92135c7df

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 (115) hide show
  1. package/dist/cjs/codec/DnsCodec.d.ts +28 -4
  2. package/dist/cjs/codec/DnsCodec.d.ts.map +1 -1
  3. package/dist/cjs/codec/DnsCodec.js +24 -3
  4. package/dist/cjs/codec/DnsCodec.js.map +2 -2
  5. package/dist/cjs/net/mock/MockNetwork.d.ts +1 -5
  6. package/dist/cjs/net/mock/MockNetwork.d.ts.map +1 -1
  7. package/dist/cjs/net/mock/MockRouter.d.ts +2 -5
  8. package/dist/cjs/net/mock/MockRouter.d.ts.map +1 -1
  9. package/dist/cjs/net/mock/MockRouter.js +8 -1
  10. package/dist/cjs/net/mock/MockRouter.js.map +1 -1
  11. package/dist/cjs/net/mock/MockUdpChannel.d.ts +2 -1
  12. package/dist/cjs/net/mock/MockUdpChannel.d.ts.map +1 -1
  13. package/dist/cjs/net/mock/MockUdpChannel.js +3 -2
  14. package/dist/cjs/net/mock/MockUdpChannel.js.map +1 -1
  15. package/dist/cjs/net/mock/NetworkSimulator.d.ts +1 -5
  16. package/dist/cjs/net/mock/NetworkSimulator.d.ts.map +1 -1
  17. package/dist/cjs/util/Abort.d.ts +39 -0
  18. package/dist/cjs/util/Abort.d.ts.map +1 -0
  19. package/dist/cjs/util/Abort.js +77 -0
  20. package/dist/cjs/util/Abort.js.map +6 -0
  21. package/dist/cjs/util/Bytes.d.ts +3 -1
  22. package/dist/cjs/util/Bytes.d.ts.map +1 -1
  23. package/dist/cjs/util/Bytes.js +11 -0
  24. package/dist/cjs/util/Bytes.js.map +1 -1
  25. package/dist/cjs/util/Cancelable.d.ts +0 -44
  26. package/dist/cjs/util/Cancelable.d.ts.map +1 -1
  27. package/dist/cjs/util/Cancelable.js +0 -141
  28. package/dist/cjs/util/Cancelable.js.map +2 -2
  29. package/dist/cjs/util/DataReadQueue.d.ts +1 -2
  30. package/dist/cjs/util/DataReadQueue.d.ts.map +1 -1
  31. package/dist/cjs/util/DataReadQueue.js +5 -5
  32. package/dist/cjs/util/DataReadQueue.js.map +1 -1
  33. package/dist/cjs/util/Promises.d.ts +35 -0
  34. package/dist/cjs/util/Promises.d.ts.map +1 -1
  35. package/dist/cjs/util/Promises.js +84 -0
  36. package/dist/cjs/util/Promises.js.map +1 -1
  37. package/dist/cjs/util/Set.d.ts.map +1 -1
  38. package/dist/cjs/util/Set.js +3 -1
  39. package/dist/cjs/util/Set.js.map +1 -1
  40. package/dist/cjs/util/Streams.d.ts +37 -0
  41. package/dist/cjs/util/Streams.d.ts.map +1 -0
  42. package/dist/cjs/util/Streams.js +114 -0
  43. package/dist/cjs/util/Streams.js.map +6 -0
  44. package/dist/cjs/util/index.d.ts +2 -1
  45. package/dist/cjs/util/index.d.ts.map +1 -1
  46. package/dist/cjs/util/index.js +2 -1
  47. package/dist/cjs/util/index.js.map +1 -1
  48. package/dist/esm/codec/DnsCodec.d.ts +28 -4
  49. package/dist/esm/codec/DnsCodec.d.ts.map +1 -1
  50. package/dist/esm/codec/DnsCodec.js +24 -3
  51. package/dist/esm/codec/DnsCodec.js.map +2 -2
  52. package/dist/esm/net/mock/MockNetwork.d.ts +1 -5
  53. package/dist/esm/net/mock/MockNetwork.d.ts.map +1 -1
  54. package/dist/esm/net/mock/MockRouter.d.ts +2 -5
  55. package/dist/esm/net/mock/MockRouter.d.ts.map +1 -1
  56. package/dist/esm/net/mock/MockRouter.js +8 -1
  57. package/dist/esm/net/mock/MockRouter.js.map +1 -1
  58. package/dist/esm/net/mock/MockUdpChannel.d.ts +2 -1
  59. package/dist/esm/net/mock/MockUdpChannel.d.ts.map +1 -1
  60. package/dist/esm/net/mock/MockUdpChannel.js +3 -2
  61. package/dist/esm/net/mock/MockUdpChannel.js.map +1 -1
  62. package/dist/esm/net/mock/NetworkSimulator.d.ts +1 -5
  63. package/dist/esm/net/mock/NetworkSimulator.d.ts.map +1 -1
  64. package/dist/esm/util/Abort.d.ts +39 -0
  65. package/dist/esm/util/Abort.d.ts.map +1 -0
  66. package/dist/esm/util/Abort.js +57 -0
  67. package/dist/esm/util/Abort.js.map +6 -0
  68. package/dist/esm/util/Bytes.d.ts +3 -1
  69. package/dist/esm/util/Bytes.d.ts.map +1 -1
  70. package/dist/esm/util/Bytes.js +11 -0
  71. package/dist/esm/util/Bytes.js.map +1 -1
  72. package/dist/esm/util/Cancelable.d.ts +0 -44
  73. package/dist/esm/util/Cancelable.d.ts.map +1 -1
  74. package/dist/esm/util/Cancelable.js +1 -142
  75. package/dist/esm/util/Cancelable.js.map +2 -2
  76. package/dist/esm/util/DataReadQueue.d.ts +1 -2
  77. package/dist/esm/util/DataReadQueue.d.ts.map +1 -1
  78. package/dist/esm/util/DataReadQueue.js +1 -1
  79. package/dist/esm/util/DataReadQueue.js.map +1 -1
  80. package/dist/esm/util/Promises.d.ts +35 -0
  81. package/dist/esm/util/Promises.d.ts.map +1 -1
  82. package/dist/esm/util/Promises.js +84 -0
  83. package/dist/esm/util/Promises.js.map +1 -1
  84. package/dist/esm/util/Set.d.ts.map +1 -1
  85. package/dist/esm/util/Set.js +3 -1
  86. package/dist/esm/util/Set.js.map +1 -1
  87. package/dist/esm/util/Streams.d.ts +37 -0
  88. package/dist/esm/util/Streams.d.ts.map +1 -0
  89. package/dist/esm/util/Streams.js +94 -0
  90. package/dist/esm/util/Streams.js.map +6 -0
  91. package/dist/esm/util/index.d.ts +2 -1
  92. package/dist/esm/util/index.d.ts.map +1 -1
  93. package/dist/esm/util/index.js +2 -1
  94. package/dist/esm/util/index.js.map +1 -1
  95. package/package.json +2 -2
  96. package/src/codec/DnsCodec.ts +44 -5
  97. package/src/net/mock/MockRouter.ts +10 -1
  98. package/src/net/mock/MockUdpChannel.ts +7 -2
  99. package/src/util/Abort.ts +93 -0
  100. package/src/util/Bytes.ts +15 -1
  101. package/src/util/Cancelable.ts +1 -224
  102. package/src/util/DataReadQueue.ts +2 -2
  103. package/src/util/Promises.ts +113 -0
  104. package/src/util/Set.ts +3 -1
  105. package/src/util/Streams.ts +130 -0
  106. package/src/util/index.ts +2 -1
  107. package/dist/cjs/util/Stream.d.ts +0 -16
  108. package/dist/cjs/util/Stream.d.ts.map +0 -1
  109. package/dist/cjs/util/Stream.js +0 -38
  110. package/dist/cjs/util/Stream.js.map +0 -6
  111. package/dist/esm/util/Stream.d.ts +0 -16
  112. package/dist/esm/util/Stream.d.ts.map +0 -1
  113. package/dist/esm/util/Stream.js +0 -18
  114. package/dist/esm/util/Stream.js.map +0 -6
  115. package/src/util/Stream.ts +0 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matter/general",
3
- "version": "0.16.0-alpha.0-20251003-dc6d5523d",
3
+ "version": "0.16.0-alpha.0-20251004-92135c7df",
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-20251003-dc6d5523d"
39
+ "@matter/testing": "0.16.0-alpha.0-20251004-92135c7df"
40
40
  },
41
41
  "files": [
42
42
  "dist/**/*",
@@ -122,11 +122,50 @@ export type DnsMessagePartiallyPreEncoded = Omit<DnsMessage, "answers" | "additi
122
122
  additionalRecords: (DnsRecord<any> | Bytes)[];
123
123
  };
124
124
 
125
+ /** Bit flags to use to determine separate flags in the DnsMessageType field */
126
+ export enum DnsMessageTypeFlag {
127
+ /** Indicates if the message is a query (0) or a reply (1). */
128
+ QR = 0x8000,
129
+
130
+ /** The type can be QUERY (standard query, 0), IQUERY (inverse query, 1), or STATUS (server status request, 2). */
131
+ OPCODE = 0x7800,
132
+
133
+ /** Authoritative Answer, in a response, indicates if the DNS server is authoritative for the queried hostname. */
134
+ AA = 0x0400,
135
+
136
+ /** TrunCation, indicates that this message was truncated due to excessive length. */
137
+ TC = 0x0200,
138
+
139
+ /** Recursion Desired, indicates if the client means a recursive query. */
140
+ RD = 0x0100,
141
+
142
+ /** Recursion Available, in a response, indicates if the replying DNS server supports recursion. */
143
+ RA = 0x0080,
144
+
145
+ /** Authentic Data, in a response, indicates if the replying DNS server verified the data. */
146
+ AD = 0x0020,
147
+
148
+ /** Checking Disabled, in a query, indicates that non-verified data is acceptable in a response. */
149
+ CD = 0x0010,
150
+
151
+ /** Response code, can be NOERROR (0), FORMERR (1, Format error), SERVFAIL (2), NXDOMAIN (3, Nonexistent domain), etc. */
152
+ RCODE = 0x000f,
153
+ }
154
+
155
+ /** Convenient Message types we use when sending mDNS messages */
125
156
  export enum DnsMessageType {
126
- Query = 0x0000,
127
- TruncatedQuery = 0x0200,
128
- Response = 0x8400, // Authoritative Answer
129
- TruncatedResponse = 0x8600,
157
+ Query = 0, // No bit set
158
+ Response = DnsMessageTypeFlag.QR | DnsMessageTypeFlag.AA, // Authoritative Answer 0x8400
159
+ }
160
+
161
+ export namespace DnsMessageType {
162
+ export function isQuery(type: number) {
163
+ return (type & DnsMessageTypeFlag.QR) === 0;
164
+ }
165
+
166
+ export function isResponse(type: number) {
167
+ return (type & DnsMessageTypeFlag.QR) !== 0;
168
+ }
130
169
  }
131
170
 
132
171
  export enum DnsRecordType {
@@ -288,7 +327,7 @@ export class DnsCodec {
288
327
  additionalRecords = [],
289
328
  }: Partial<DnsMessagePartiallyPreEncoded>): Bytes {
290
329
  if (messageType === undefined) throw new InternalError("Message type must be specified!");
291
- if (queries.length > 0 && messageType !== DnsMessageType.Query && messageType !== DnsMessageType.TruncatedQuery)
330
+ if (queries.length > 0 && !DnsMessageType.isQuery(messageType))
292
331
  throw new InternalError("Queries can only be included in query messages!");
293
332
  if (authorities.length > 0) throw new NotImplementedError("Authority answers are not supported yet!");
294
333
 
@@ -14,10 +14,17 @@ export interface MockRouter extends MockRouter.Route {
14
14
  delete(route: MockRouter.Route): void;
15
15
  }
16
16
 
17
- export function MockRouter() {
17
+ export function MockRouter(manipulator?: MockRouter.PacketManipulator): MockRouter {
18
18
  const routes = new Set<MockRouter.Route>();
19
19
 
20
20
  const router = function router(packet: MockRouter.Packet) {
21
+ if (manipulator) {
22
+ const manipulatedPacket = manipulator(packet);
23
+ if (manipulatedPacket === null) {
24
+ return;
25
+ }
26
+ packet = manipulatedPacket;
27
+ }
21
28
  for (const route of routes) {
22
29
  try {
23
30
  route(packet);
@@ -46,4 +53,6 @@ export namespace MockRouter {
46
53
  export interface Route {
47
54
  (packet: Packet): void;
48
55
  }
56
+
57
+ export type PacketManipulator = (packet: Packet) => Packet | null;
49
58
  }
@@ -13,13 +13,18 @@ import { MockRouter } from "./MockRouter.js";
13
13
 
14
14
  export class MockUdpChannel implements UdpChannel {
15
15
  readonly #host: MockNetwork;
16
- readonly #router = MockRouter();
16
+ readonly #router: MockRouter;
17
17
  readonly #sendFrom: string;
18
18
  readonly #receiveFrom: Set<string>;
19
19
  readonly #listeningPort: number;
20
20
  readonly maxPayloadSize = MAX_UDP_MESSAGE_SIZE;
21
21
 
22
- constructor(network: MockNetwork, { listeningAddress, listeningPort, netInterface, type }: UdpChannelOptions) {
22
+ constructor(
23
+ network: MockNetwork,
24
+ { listeningAddress, listeningPort, netInterface, type }: UdpChannelOptions,
25
+ packetManipulator?: MockRouter.PacketManipulator,
26
+ ) {
27
+ this.#router = MockRouter(packetManipulator);
23
28
  const { ipV4, ipV6 } = network.getIpMac(netInterface ?? "fake0");
24
29
  let addresses = type === "udp4" ? ipV4 : ipV6;
25
30
 
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ /**
8
+ * Utilities for implementing abort logic.
9
+ */
10
+ export namespace Abort {
11
+ /**
12
+ * An entity that may be used to signal abort of an operation.
13
+ */
14
+ export type Signal = AbortController | AbortSignal;
15
+
16
+ /**
17
+ * An abort controller that can be closed.
18
+ */
19
+ export interface DisposableController extends AbortController {
20
+ [Symbol.dispose](): void;
21
+ }
22
+
23
+ /**
24
+ * Determine whether a {@link Signal} is aborted.
25
+ */
26
+ export function is(signal: Signal | undefined) {
27
+ if (!signal) {
28
+ return;
29
+ }
30
+
31
+ if ("signal" in signal) {
32
+ signal = signal.signal;
33
+ }
34
+
35
+ return signal.aborted;
36
+ }
37
+
38
+ /**
39
+ * Race one or more promises with an optional abort signal.
40
+ *
41
+ * If the abort signal is present and signals abort, the race will end and return undefined. It will not throw the
42
+ * abort reason.
43
+ */
44
+ export function race<T>(
45
+ signal: Signal | undefined,
46
+ ...promises: Array<T | PromiseLike<T>>
47
+ ): Promise<Awaited<T> | void> {
48
+ if (signal) {
49
+ if ("signal" in signal) {
50
+ signal = signal.signal;
51
+ }
52
+
53
+ let off: () => void;
54
+ const aborted = new Promise<void>(resolve => {
55
+ const onabort = () => resolve();
56
+ (signal as AbortSignal).addEventListener("abort", onabort);
57
+ off = () => (signal as AbortSignal).removeEventListener("abort", onabort);
58
+ });
59
+
60
+ return Promise.race([aborted, ...promises]).finally(off!);
61
+ }
62
+
63
+ if (promises.length === 1) {
64
+ return Promise.resolve(promises[0]);
65
+ }
66
+
67
+ return Promise.race(promises);
68
+ }
69
+
70
+ /**
71
+ * Create independently abortable subtask with a new {@link AbortController} that is aborted if another controller
72
+ * aborts.
73
+ *
74
+ * Closing the returned controller unregisters with the input controller. It does not perform an abort.
75
+ */
76
+ export function subtask(signal: Signal | undefined): DisposableController {
77
+ if (signal && "signal" in signal) {
78
+ signal = signal.signal;
79
+ }
80
+
81
+ const controller = new AbortController() as DisposableController;
82
+
83
+ if (signal) {
84
+ const outerHandler = () => controller.abort();
85
+ signal.addEventListener("abort", outerHandler);
86
+ controller[Symbol.dispose] = () => signal.removeEventListener("abort", outerHandler);
87
+ } else {
88
+ controller[Symbol.dispose] = () => {};
89
+ }
90
+
91
+ return controller;
92
+ }
93
+ }
package/src/util/Bytes.ts CHANGED
@@ -122,10 +122,22 @@ export namespace Bytes {
122
122
  return fromHex(result);
123
123
  }
124
124
 
125
- export function fromString(string: string): Bytes {
125
+ export function fromString(string: string | Bytes): Bytes {
126
+ if (Bytes.isBytes(string)) {
127
+ return string;
128
+ }
129
+
126
130
  return new TextEncoder().encode(string);
127
131
  }
128
132
 
133
+ export function toString(bytes: string | Bytes): string {
134
+ if (!Bytes.isBytes(bytes)) {
135
+ return bytes;
136
+ }
137
+
138
+ return new TextDecoder().decode(bytes);
139
+ }
140
+
129
141
  export function concat(...arrays: Bytes[]): Bytes {
130
142
  let length = 0;
131
143
  const data = arrays.map(array => Bytes.of(array));
@@ -161,4 +173,6 @@ export namespace Bytes {
161
173
  }
162
174
  return result;
163
175
  }
176
+
177
+ export const empty = new Uint8Array();
164
178
  }
@@ -7,7 +7,7 @@
7
7
  import type { Logger } from "#log/Logger.js";
8
8
  import { CanceledError } from "#MatterError.js";
9
9
  import { asError, errorOf } from "./Error.js";
10
- import { createPromise, MaybePromise } from "./Promises.js";
10
+ import { MaybePromise } from "./Promises.js";
11
11
 
12
12
  /**
13
13
  * An operation that may be canceled.
@@ -155,226 +155,3 @@ export class CancelablePromise<T = void> implements Promise<T>, Cancelable {
155
155
  return this.#logger;
156
156
  }
157
157
  }
158
-
159
- /**
160
- * An {@link AsyncIterator} that may be canceled.
161
- */
162
- export class CancelableAsyncIterator<T, TReturn = T, TNext = void>
163
- implements AsyncIterator<T, TReturn, TNext>, Cancelable
164
- {
165
- // The result of the final iteration
166
- #settled?: boolean;
167
-
168
- // The input next implementation
169
- #next: (...[value]: [] | [TNext]) => Promise<IteratorResult<T, TReturn>>;
170
-
171
- // We race against this promise to detect cancelation during next()
172
- #canceled: Promise<IteratorResult<T, TReturn>>;
173
-
174
- // Rejects #canceled
175
- #reject!: (reason: any) => void;
176
-
177
- /**
178
- * Create a new instance.
179
- *
180
- * @param next the function that produces results
181
- * @param onCancel if provided this will overwrite {@link onCancel}
182
- */
183
- constructor(
184
- next: (...[value]: [] | [TNext]) => Promise<IteratorResult<T, TReturn>>,
185
- onCancel?: (reason: Error) => void,
186
- ) {
187
- this.#next = next;
188
- this.#canceled = new Promise((_resolve, reject) => (this.#reject = reject));
189
- if (onCancel !== undefined) {
190
- this.onCancel = onCancel;
191
- }
192
- }
193
-
194
- next(...[value]: [] | [TNext]): Promise<IteratorResult<T, TReturn>> {
195
- if (this.#settled) {
196
- // This type is a lie if TReturn does not allow undefined but TS doesn't give us any options here, and
197
- // calling next after the final iteration doesn't make semantic sense anyway. The spec does say subsequent
198
- // calls should return done = true
199
- return Promise.resolve({ done: true } as IteratorResult<T, TReturn>);
200
- }
201
-
202
- const next = value === undefined ? this.#next() : this.#next(value);
203
-
204
- // If next resolves after we've canceled we ignore it, but if we were to do that with an error it would be
205
- // unhandled. Errors from next will still cause an unhandled error if not caught before we set this.#final
206
- // because we rethrow from the race below where this.#final cannot be set
207
- next.catch(reason => {
208
- if (this.#settled) {
209
- CancelablePromise.logger.warn(`Cancelable async iterator rejected after return:`, reason);
210
- }
211
- });
212
-
213
- return Promise.race([next, this.#canceled]).then(
214
- result => {
215
- // next resolved (this.#canceled can only be rejected)
216
- if (result.done) {
217
- this.#settled = true;
218
- }
219
- return result;
220
- },
221
- reason => {
222
- // next or this.#canceled rejected; regardless we use this for all subsequent calls to next() since we
223
- // will never receive TReturn
224
- this.#settled = true;
225
- throw reason;
226
- },
227
- );
228
- }
229
-
230
- cancel(reason: any): void {
231
- if (this.#settled) {
232
- return;
233
- }
234
-
235
- try {
236
- this.onCancel(reason);
237
- } catch (e) {
238
- this.#reject(e);
239
- }
240
- }
241
-
242
- /**
243
- * Handle cancelation.
244
- *
245
- * If the underlying operation supports cancelation then it is better to use that. Otherwise throwing
246
- * {@link reason} (the default behavior) will reject the current (or next) iteration regardless of the state of the
247
- * underyling operation.
248
- *
249
- * @param reason the reason provided to {@link cancel}
250
- */
251
- protected onCancel(reason: any) {
252
- throw reason;
253
- }
254
-
255
- static is(value: unknown): value is CancelableAsyncIterator<unknown> {
256
- return (
257
- typeof value === "object" &&
258
- value !== null &&
259
- Symbol.asyncIterator in value &&
260
- typeof value[Symbol.asyncIterator] === "function" &&
261
- "cancel" in value &&
262
- typeof value["cancel"] === "function"
263
- );
264
- }
265
- }
266
-
267
- /**
268
- * Create a function that returns a {@link CancelablePromise} and delegates cancelation internally to other async logic.
269
- *
270
- * The output function invokes the supplied {@link executor} with an additional "cancelable" argument. This function
271
- * wraps supported types (currently {@link CancelablePromise}, {@link CancelableAsyncIterator} and {@link Promise}) with
272
- * cancelation logic.
273
- *
274
- * Any such wrapped object behaves normally but will throw with the cancelation reason on cancel.
275
- */
276
- export function Cancelable<ThisT, ArgsT extends unknown[], ReturnT>(
277
- executor: Cancelable.Executor<ThisT, ArgsT, ReturnT>,
278
- ) {
279
- // The proxy that invokes the executor with a "canceable" argument used for delegation
280
- return function cancelable(this: ThisT, ...args: ArgsT): CancelablePromise<ReturnT> {
281
- // Active delegates register here
282
- let delegates: undefined | Set<(reason: any) => void>;
283
-
284
- // The return value from the proxy function
285
- return new CancelablePromise<ReturnT>(
286
- (resolve, reject) => {
287
- // Invoke the executor
288
- try {
289
- const result = executor.call(this, cancelable, ...args);
290
-
291
- if (MaybePromise.is(result)) {
292
- result.then(resolve, reject);
293
- return;
294
- }
295
-
296
- resolve(result);
297
- } catch (e) {
298
- reject(e);
299
- }
300
-
301
- // We pass this function to the executor; the executor invokes on supported objects to perform
302
- // delegation
303
- function cancelable<T>(value: T): T {
304
- // Delegation to cancelable promise - just cancel when we are canceled; unregister when the target
305
- // resolves
306
- if (CancelablePromise.is(value)) {
307
- const undelegate = addDelegate(reason => value.cancel(reason));
308
- return value.finally(undelegate) as T;
309
- }
310
-
311
- // Delegation to cancelable async iterators - cancel when we cancel; unregister when iteration
312
- // completes
313
- if (CancelableAsyncIterator.is(value)) {
314
- const undelegate = addDelegate(reason => value.cancel(reason));
315
- const next = value.next.bind(value);
316
- value.next = () => {
317
- return next().then(
318
- result => {
319
- if (result.done) {
320
- undelegate();
321
- }
322
- return result;
323
- },
324
- reason => {
325
- undelegate();
326
- throw reason;
327
- },
328
- );
329
- };
330
-
331
- return value;
332
- }
333
-
334
- // Delegation to non-cancelable promises - use a race to abort the wait but operation will proceed
335
- if (MaybePromise.is(value)) {
336
- const { promise, rejecter } = createPromise<T>();
337
- const undelegate = addDelegate(reason => rejecter(reason));
338
- return Promise.race([promise, value]).finally(() => undelegate) as T;
339
- }
340
-
341
- // No delegation possible; just return the original value
342
- return value;
343
- }
344
- },
345
-
346
- // Our "onCancel" that delegates to any registered delegators or simply rethrows if no delegation is active
347
- reason => {
348
- if (!delegates?.size) {
349
- throw reason;
350
- }
351
-
352
- for (const delegate of delegates) {
353
- delegate(reason);
354
- }
355
- },
356
- );
357
-
358
- // Register a delegate
359
- function addDelegate(delegate: (reason: any) => void) {
360
- if (!delegates) {
361
- delegates = new Set();
362
- }
363
- delegates.add(delegate);
364
-
365
- return () => {
366
- delegates?.delete(delegate);
367
- };
368
- }
369
- };
370
- }
371
-
372
- export namespace Cancelable {
373
- export interface Delegator {
374
- <T>(value: T): T;
375
- }
376
-
377
- export interface Executor<ThisT, ArgsT extends unknown[], ReturnT> {
378
- (this: ThisT, cancelable: Delegator, ...args: ArgsT): MaybePromise<ReturnT>;
379
- }
380
- }
@@ -11,9 +11,9 @@ import { Minutes } from "#time/TimeUnit.js";
11
11
  import { MatterFlowError } from "../MatterError.js";
12
12
  import { Time, Timer } from "../time/Time.js";
13
13
  import { createPromise } from "./Promises.js";
14
- import { EndOfStreamError, NoResponseTimeoutError, Stream } from "./Stream.js";
14
+ import { EndOfStreamError, NoResponseTimeoutError } from "./Streams.js";
15
15
 
16
- export class DataReadQueue<T> implements Stream<T> {
16
+ export class DataReadQueue<T> {
17
17
  readonly #queue = new Array<T>();
18
18
  #pendingRead?: { resolver: (data: T) => void; rejecter: (reason: any) => void; timeoutTimer?: Timer };
19
19
  #closed = false;
@@ -286,4 +286,117 @@ export const MaybePromise = {
286
286
  [Symbol.toStringTag]: "MaybePromise",
287
287
  };
288
288
 
289
+ /**
290
+ * A recurring promise.
291
+ *
292
+ * A gate is duck-compatible with {@link Promise} and includes resolution methods on its public API.
293
+ *
294
+ * As an extension to normal promise semantics, a gate may be "opened" or "closed". {@link open} provides multi-
295
+ * resolution so that future awaits return a new value. {@link close} returns the promise to an unsettled state so that
296
+ * future awaits block.
297
+ *
298
+ * This is useful for basic synchronization of two tightly coupled ongoing tasks.
299
+ */
300
+ export class Gate<T = void> implements Promise<T> {
301
+ #promise: Promise<T>;
302
+ #resolve: (value: T) => void;
303
+ #reject: (reason: any) => void;
304
+ #resolvedAs: unknown;
305
+ #isResolved = false;
306
+ #isRejected = false;
307
+
308
+ constructor() {
309
+ let resolve: (value: T) => void;
310
+ let reject: (reason: any) => void;
311
+
312
+ this.#promise = new Promise<T>((res, rej) => {
313
+ resolve = res;
314
+ reject = rej;
315
+ });
316
+
317
+ this.#resolve = resolve!;
318
+ this.#reject = reject!;
319
+ }
320
+
321
+ /**
322
+ * Move to resolved state with the specified value.
323
+ */
324
+ open(value: T) {
325
+ if (this.#isResolved) {
326
+ if (this.#resolvedAs === value) {
327
+ return;
328
+ }
329
+
330
+ this.#resolvedAs = value;
331
+ this.#promise = Promise.resolve(value);
332
+ return;
333
+ }
334
+
335
+ if (this.#isRejected) {
336
+ this.#resolvedAs = value;
337
+ this.#isResolved = true;
338
+ this.#isRejected = false;
339
+ this.#promise = Promise.resolve(value);
340
+ return;
341
+ }
342
+
343
+ this.resolve(value);
344
+ }
345
+
346
+ /**
347
+ * Move to unresolved state.
348
+ */
349
+ close() {
350
+ if (!this.#isResolved && !this.#isRejected) {
351
+ return;
352
+ }
353
+
354
+ this.#isResolved = this.#isRejected = false;
355
+ this.#promise = new Promise((resolve, reject) => {
356
+ this.#resolve = resolve;
357
+ this.#reject = reject;
358
+ });
359
+ }
360
+
361
+ /**
362
+ * Perform normal promise resolution. Ignored if promise is already settled.
363
+ */
364
+ resolve(value: T) {
365
+ if (this.#isResolved || this.#isRejected) {
366
+ return;
367
+ }
368
+ this.#resolvedAs = value;
369
+ this.#isResolved = true;
370
+ this.#resolve(value);
371
+ }
372
+
373
+ /**
374
+ * Perform normal promise rejection. Ignored if promise is already settled.
375
+ */
376
+ reject(cause: any) {
377
+ if (this.#isResolved || this.#isRejected) {
378
+ return;
379
+ }
380
+ this.#isRejected = true;
381
+ this.#reject(cause);
382
+ }
383
+
384
+ then<TResult1 = T, TResult2 = never>(
385
+ resolve?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
386
+ reject?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
387
+ ) {
388
+ return this.#promise.then(resolve, reject);
389
+ }
390
+
391
+ catch<TResult = never>(reject?: ((reason: any) => TResult | PromiseLike<TResult>) | null) {
392
+ return this.#promise.catch(reject);
393
+ }
394
+
395
+ finally(then?: (() => void) | null) {
396
+ return this.#promise.finally(then);
397
+ }
398
+
399
+ [Symbol.toStringTag] = Promise.prototype[Symbol.toStringTag];
400
+ }
401
+
289
402
  MaybePromise.toString = () => "MaybePromise";
package/src/util/Set.ts CHANGED
@@ -215,7 +215,9 @@ export class BasicSet<T, AddT = T> implements ImmutableSet<T>, MutableSet<T, Add
215
215
  }
216
216
 
217
217
  clear() {
218
- this.#entries.clear();
218
+ for (const entry of [...this]) {
219
+ this.delete(entry);
220
+ }
219
221
  }
220
222
 
221
223
  get added() {