@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.
- package/dist/cjs/codec/DnsCodec.d.ts +28 -4
- package/dist/cjs/codec/DnsCodec.d.ts.map +1 -1
- package/dist/cjs/codec/DnsCodec.js +24 -3
- package/dist/cjs/codec/DnsCodec.js.map +2 -2
- package/dist/cjs/net/mock/MockNetwork.d.ts +1 -5
- package/dist/cjs/net/mock/MockNetwork.d.ts.map +1 -1
- package/dist/cjs/net/mock/MockRouter.d.ts +2 -5
- package/dist/cjs/net/mock/MockRouter.d.ts.map +1 -1
- package/dist/cjs/net/mock/MockRouter.js +8 -1
- package/dist/cjs/net/mock/MockRouter.js.map +1 -1
- package/dist/cjs/net/mock/MockUdpChannel.d.ts +2 -1
- package/dist/cjs/net/mock/MockUdpChannel.d.ts.map +1 -1
- package/dist/cjs/net/mock/MockUdpChannel.js +3 -2
- package/dist/cjs/net/mock/MockUdpChannel.js.map +1 -1
- package/dist/cjs/net/mock/NetworkSimulator.d.ts +1 -5
- package/dist/cjs/net/mock/NetworkSimulator.d.ts.map +1 -1
- package/dist/cjs/util/Abort.d.ts +39 -0
- package/dist/cjs/util/Abort.d.ts.map +1 -0
- package/dist/cjs/util/Abort.js +77 -0
- package/dist/cjs/util/Abort.js.map +6 -0
- package/dist/cjs/util/Bytes.d.ts +3 -1
- package/dist/cjs/util/Bytes.d.ts.map +1 -1
- package/dist/cjs/util/Bytes.js +11 -0
- package/dist/cjs/util/Bytes.js.map +1 -1
- package/dist/cjs/util/Cancelable.d.ts +0 -44
- package/dist/cjs/util/Cancelable.d.ts.map +1 -1
- package/dist/cjs/util/Cancelable.js +0 -141
- package/dist/cjs/util/Cancelable.js.map +2 -2
- package/dist/cjs/util/DataReadQueue.d.ts +1 -2
- package/dist/cjs/util/DataReadQueue.d.ts.map +1 -1
- package/dist/cjs/util/DataReadQueue.js +5 -5
- package/dist/cjs/util/DataReadQueue.js.map +1 -1
- package/dist/cjs/util/Promises.d.ts +35 -0
- package/dist/cjs/util/Promises.d.ts.map +1 -1
- package/dist/cjs/util/Promises.js +84 -0
- package/dist/cjs/util/Promises.js.map +1 -1
- package/dist/cjs/util/Set.d.ts.map +1 -1
- package/dist/cjs/util/Set.js +3 -1
- package/dist/cjs/util/Set.js.map +1 -1
- package/dist/cjs/util/Streams.d.ts +37 -0
- package/dist/cjs/util/Streams.d.ts.map +1 -0
- package/dist/cjs/util/Streams.js +114 -0
- package/dist/cjs/util/Streams.js.map +6 -0
- package/dist/cjs/util/index.d.ts +2 -1
- package/dist/cjs/util/index.d.ts.map +1 -1
- package/dist/cjs/util/index.js +2 -1
- package/dist/cjs/util/index.js.map +1 -1
- package/dist/esm/codec/DnsCodec.d.ts +28 -4
- package/dist/esm/codec/DnsCodec.d.ts.map +1 -1
- package/dist/esm/codec/DnsCodec.js +24 -3
- package/dist/esm/codec/DnsCodec.js.map +2 -2
- package/dist/esm/net/mock/MockNetwork.d.ts +1 -5
- package/dist/esm/net/mock/MockNetwork.d.ts.map +1 -1
- package/dist/esm/net/mock/MockRouter.d.ts +2 -5
- package/dist/esm/net/mock/MockRouter.d.ts.map +1 -1
- package/dist/esm/net/mock/MockRouter.js +8 -1
- package/dist/esm/net/mock/MockRouter.js.map +1 -1
- package/dist/esm/net/mock/MockUdpChannel.d.ts +2 -1
- package/dist/esm/net/mock/MockUdpChannel.d.ts.map +1 -1
- package/dist/esm/net/mock/MockUdpChannel.js +3 -2
- package/dist/esm/net/mock/MockUdpChannel.js.map +1 -1
- package/dist/esm/net/mock/NetworkSimulator.d.ts +1 -5
- package/dist/esm/net/mock/NetworkSimulator.d.ts.map +1 -1
- package/dist/esm/util/Abort.d.ts +39 -0
- package/dist/esm/util/Abort.d.ts.map +1 -0
- package/dist/esm/util/Abort.js +57 -0
- package/dist/esm/util/Abort.js.map +6 -0
- package/dist/esm/util/Bytes.d.ts +3 -1
- package/dist/esm/util/Bytes.d.ts.map +1 -1
- package/dist/esm/util/Bytes.js +11 -0
- package/dist/esm/util/Bytes.js.map +1 -1
- package/dist/esm/util/Cancelable.d.ts +0 -44
- package/dist/esm/util/Cancelable.d.ts.map +1 -1
- package/dist/esm/util/Cancelable.js +1 -142
- package/dist/esm/util/Cancelable.js.map +2 -2
- package/dist/esm/util/DataReadQueue.d.ts +1 -2
- package/dist/esm/util/DataReadQueue.d.ts.map +1 -1
- package/dist/esm/util/DataReadQueue.js +1 -1
- package/dist/esm/util/DataReadQueue.js.map +1 -1
- package/dist/esm/util/Promises.d.ts +35 -0
- package/dist/esm/util/Promises.d.ts.map +1 -1
- package/dist/esm/util/Promises.js +84 -0
- package/dist/esm/util/Promises.js.map +1 -1
- package/dist/esm/util/Set.d.ts.map +1 -1
- package/dist/esm/util/Set.js +3 -1
- package/dist/esm/util/Set.js.map +1 -1
- package/dist/esm/util/Streams.d.ts +37 -0
- package/dist/esm/util/Streams.d.ts.map +1 -0
- package/dist/esm/util/Streams.js +94 -0
- package/dist/esm/util/Streams.js.map +6 -0
- package/dist/esm/util/index.d.ts +2 -1
- package/dist/esm/util/index.d.ts.map +1 -1
- package/dist/esm/util/index.js +2 -1
- package/dist/esm/util/index.js.map +1 -1
- package/package.json +2 -2
- package/src/codec/DnsCodec.ts +44 -5
- package/src/net/mock/MockRouter.ts +10 -1
- package/src/net/mock/MockUdpChannel.ts +7 -2
- package/src/util/Abort.ts +93 -0
- package/src/util/Bytes.ts +15 -1
- package/src/util/Cancelable.ts +1 -224
- package/src/util/DataReadQueue.ts +2 -2
- package/src/util/Promises.ts +113 -0
- package/src/util/Set.ts +3 -1
- package/src/util/Streams.ts +130 -0
- package/src/util/index.ts +2 -1
- package/dist/cjs/util/Stream.d.ts +0 -16
- package/dist/cjs/util/Stream.d.ts.map +0 -1
- package/dist/cjs/util/Stream.js +0 -38
- package/dist/cjs/util/Stream.js.map +0 -6
- package/dist/esm/util/Stream.d.ts +0 -16
- package/dist/esm/util/Stream.d.ts.map +0 -1
- package/dist/esm/util/Stream.js +0 -18
- package/dist/esm/util/Stream.js.map +0 -6
- 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-
|
|
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-
|
|
39
|
+
"@matter/testing": "0.16.0-alpha.0-20251004-92135c7df"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"dist/**/*",
|
package/src/codec/DnsCodec.ts
CHANGED
|
@@ -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 =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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 &&
|
|
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
|
|
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(
|
|
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
|
}
|
package/src/util/Cancelable.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
14
|
+
import { EndOfStreamError, NoResponseTimeoutError } from "./Streams.js";
|
|
15
15
|
|
|
16
|
-
export class DataReadQueue<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;
|
package/src/util/Promises.ts
CHANGED
|
@@ -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