@matter/general 0.12.4-alpha.0-20250211-56b2c53a0 → 0.12.4-alpha.0-20250213-1187f81eb
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/MatterError.d.ts +12 -0
- package/dist/cjs/MatterError.d.ts.map +1 -1
- package/dist/cjs/MatterError.js +12 -0
- package/dist/cjs/MatterError.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/log/Logger.d.ts.map +1 -1
- package/dist/cjs/log/Logger.js +2 -0
- package/dist/cjs/log/Logger.js.map +1 -1
- package/dist/cjs/time/Time.d.ts +1 -1
- package/dist/cjs/time/Time.d.ts.map +1 -1
- package/dist/cjs/time/Time.js +2 -2
- package/dist/cjs/time/Time.js.map +1 -1
- package/dist/cjs/transaction/Participant.d.ts +47 -0
- package/dist/cjs/transaction/Participant.d.ts.map +1 -0
- package/dist/cjs/transaction/Participant.js +22 -0
- package/dist/cjs/transaction/Participant.js.map +6 -0
- package/dist/cjs/transaction/Resource.d.ts +29 -0
- package/dist/cjs/transaction/Resource.d.ts.map +1 -0
- package/dist/cjs/transaction/Resource.js +40 -0
- package/dist/cjs/transaction/Resource.js.map +6 -0
- package/dist/cjs/transaction/ResourceSet.d.ts +36 -0
- package/dist/cjs/transaction/ResourceSet.d.ts.map +1 -0
- package/dist/cjs/transaction/ResourceSet.js +155 -0
- package/dist/cjs/transaction/ResourceSet.js.map +6 -0
- package/dist/cjs/transaction/Status.d.ts +49 -0
- package/dist/cjs/transaction/Status.d.ts.map +1 -0
- package/dist/cjs/transaction/Status.js +55 -0
- package/dist/cjs/transaction/Status.js.map +6 -0
- package/dist/cjs/transaction/Transaction.d.ts +197 -0
- package/dist/cjs/transaction/Transaction.d.ts.map +1 -0
- package/dist/cjs/transaction/Transaction.js +50 -0
- package/dist/cjs/transaction/Transaction.js.map +6 -0
- package/dist/cjs/transaction/Tx.d.ts +47 -0
- package/dist/cjs/transaction/Tx.d.ts.map +1 -0
- package/dist/cjs/transaction/Tx.js +586 -0
- package/dist/cjs/transaction/Tx.js.map +6 -0
- package/dist/cjs/transaction/errors.d.ts +52 -0
- package/dist/cjs/transaction/errors.d.ts.map +1 -0
- package/dist/cjs/transaction/errors.js +47 -0
- package/dist/cjs/transaction/errors.js.map +6 -0
- package/dist/cjs/transaction/index.d.ts +8 -0
- package/dist/cjs/transaction/index.d.ts.map +1 -0
- package/dist/cjs/transaction/index.js +25 -0
- package/dist/cjs/transaction/index.js.map +6 -0
- package/dist/cjs/util/Cancelable.d.ts +101 -0
- package/dist/cjs/util/Cancelable.d.ts.map +1 -0
- package/dist/cjs/util/Cancelable.js +279 -0
- package/dist/cjs/util/Cancelable.js.map +6 -0
- package/dist/cjs/util/Observable.js +1 -1
- package/dist/cjs/util/Observable.js.map +1 -1
- package/dist/cjs/util/Promises.d.ts +0 -15
- package/dist/cjs/util/Promises.d.ts.map +1 -1
- package/dist/cjs/util/Promises.js +0 -33
- package/dist/cjs/util/Promises.js.map +1 -1
- package/dist/cjs/util/index.d.ts +1 -0
- package/dist/cjs/util/index.d.ts.map +1 -1
- package/dist/cjs/util/index.js +1 -0
- package/dist/cjs/util/index.js.map +1 -1
- package/dist/esm/MatterError.d.ts +12 -0
- package/dist/esm/MatterError.d.ts.map +1 -1
- package/dist/esm/MatterError.js +12 -0
- package/dist/esm/MatterError.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/log/Logger.d.ts.map +1 -1
- package/dist/esm/log/Logger.js +2 -0
- package/dist/esm/log/Logger.js.map +1 -1
- package/dist/esm/time/Time.d.ts +1 -1
- package/dist/esm/time/Time.d.ts.map +1 -1
- package/dist/esm/time/Time.js +1 -1
- package/dist/esm/transaction/Participant.d.ts +47 -0
- package/dist/esm/transaction/Participant.d.ts.map +1 -0
- package/dist/esm/transaction/Participant.js +6 -0
- package/dist/esm/transaction/Participant.js.map +6 -0
- package/dist/esm/transaction/Resource.d.ts +29 -0
- package/dist/esm/transaction/Resource.d.ts.map +1 -0
- package/dist/esm/transaction/Resource.js +20 -0
- package/dist/esm/transaction/Resource.js.map +6 -0
- package/dist/esm/transaction/ResourceSet.d.ts +36 -0
- package/dist/esm/transaction/ResourceSet.d.ts.map +1 -0
- package/dist/esm/transaction/ResourceSet.js +135 -0
- package/dist/esm/transaction/ResourceSet.js.map +6 -0
- package/dist/esm/transaction/Status.d.ts +49 -0
- package/dist/esm/transaction/Status.d.ts.map +1 -0
- package/dist/esm/transaction/Status.js +35 -0
- package/dist/esm/transaction/Status.js.map +6 -0
- package/dist/esm/transaction/Transaction.d.ts +197 -0
- package/dist/esm/transaction/Transaction.d.ts.map +1 -0
- package/dist/esm/transaction/Transaction.js +30 -0
- package/dist/esm/transaction/Transaction.js.map +6 -0
- package/dist/esm/transaction/Tx.d.ts +47 -0
- package/dist/esm/transaction/Tx.d.ts.map +1 -0
- package/dist/esm/transaction/Tx.js +566 -0
- package/dist/esm/transaction/Tx.js.map +6 -0
- package/dist/esm/transaction/errors.d.ts +52 -0
- package/dist/esm/transaction/errors.d.ts.map +1 -0
- package/dist/esm/transaction/errors.js +27 -0
- package/dist/esm/transaction/errors.js.map +6 -0
- package/dist/esm/transaction/index.d.ts +8 -0
- package/dist/esm/transaction/index.d.ts.map +1 -0
- package/dist/esm/transaction/index.js +8 -0
- package/dist/esm/transaction/index.js.map +6 -0
- package/dist/esm/util/Cancelable.d.ts +101 -0
- package/dist/esm/util/Cancelable.d.ts.map +1 -0
- package/dist/esm/util/Cancelable.js +259 -0
- package/dist/esm/util/Cancelable.js.map +6 -0
- package/dist/esm/util/Observable.js +1 -1
- package/dist/esm/util/Observable.js.map +1 -1
- package/dist/esm/util/Promises.d.ts +0 -15
- package/dist/esm/util/Promises.d.ts.map +1 -1
- package/dist/esm/util/Promises.js +0 -33
- package/dist/esm/util/Promises.js.map +1 -1
- package/dist/esm/util/index.d.ts +1 -0
- package/dist/esm/util/index.d.ts.map +1 -1
- package/dist/esm/util/index.js +1 -0
- package/dist/esm/util/index.js.map +1 -1
- package/package.json +2 -2
- package/src/MatterError.ts +18 -0
- package/src/index.ts +1 -0
- package/src/log/Logger.ts +3 -0
- package/src/time/Time.ts +1 -1
- package/src/transaction/Participant.ts +54 -0
- package/src/transaction/Resource.ts +39 -0
- package/src/transaction/ResourceSet.ts +160 -0
- package/src/transaction/Status.ts +68 -0
- package/src/transaction/Transaction.ts +190 -0
- package/src/transaction/Tx.ts +734 -0
- package/src/transaction/errors.ts +53 -0
- package/src/transaction/index.ts +8 -0
- package/src/util/Cancelable.ts +380 -0
- package/src/util/Observable.ts +1 -1
- package/src/util/Promises.ts +0 -52
- package/src/util/index.ts +1 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2025 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { MatterError } from "#MatterError.js";
|
|
8
|
+
import type { Transaction } from "./Transaction.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when a {@link Transaction} operation cannot complete because the transaction has the wrong status.
|
|
12
|
+
*/
|
|
13
|
+
export class TransactionFlowError extends MatterError {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Thrown if a {@link Transaction} attempts to lock a {@link Resource} synchronously but the resource is already locked.
|
|
17
|
+
*
|
|
18
|
+
* If you see this error you are probably modifying {@link Behavior} state. You can use
|
|
19
|
+
* {@link Transaction.addResources} and {@link Transaction.begin} to lock the behavior before performing your write.
|
|
20
|
+
*/
|
|
21
|
+
export class SynchronousTransactionConflictError extends MatterError {}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Thrown if a transaction attempts to obtain exclusivity in a manner that would lead to deadlock.
|
|
25
|
+
*/
|
|
26
|
+
export class TransactionDeadlockError extends MatterError {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Thrown if an error occurs during commit or rollback.
|
|
30
|
+
*/
|
|
31
|
+
export class FinalizationError extends MatterError {}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Thrown if a {@link Transaction} is accessed after it has been destroyed.
|
|
35
|
+
*
|
|
36
|
+
* If you see this error, you have probably kept a reference to a contextual object its exited. You may need to create
|
|
37
|
+
* a new, independent transaction context.
|
|
38
|
+
*
|
|
39
|
+
* A possible cause of this error is forgetting to use await on an async function. The context will remain open so long
|
|
40
|
+
* as there is an unresolved {@link Promise} it can await.
|
|
41
|
+
*/
|
|
42
|
+
export class TransactionDestroyedError extends MatterError {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Thrown if a {@link Transaction} cannot commit because state has mutated continuously for too many pre-commit cycles.
|
|
46
|
+
*
|
|
47
|
+
* "Pre-commit" is a commit event triggered by {@link Transaction} before stage 1 commit. During pre-commit listeners
|
|
48
|
+
* may mutate state. If state does change, the transaction re-runs pre-commit so all listeners see the same state.
|
|
49
|
+
*
|
|
50
|
+
* If state continues to mutate for too many of these cycles then the transaction will abort. This likely indicates a
|
|
51
|
+
* logic error that will result in an infinite loop.
|
|
52
|
+
*/
|
|
53
|
+
export class UnsettledStateError extends FinalizationError {}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Logger } from "#log/Logger.js";
|
|
8
|
+
import { CanceledError } from "#MatterError.js";
|
|
9
|
+
import { errorOf } from "./Error.js";
|
|
10
|
+
import { createPromise, MaybePromise } from "./Promises.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* An operation that may be canceled.
|
|
14
|
+
*/
|
|
15
|
+
export interface Cancelable {
|
|
16
|
+
/**
|
|
17
|
+
* Cancel the operation.
|
|
18
|
+
*/
|
|
19
|
+
cancel(reason: any): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A {@link PromiseLike} that may be canceled.
|
|
24
|
+
*
|
|
25
|
+
* Behaves like a normal promise but does not actually extend {@link Promise} because that's a huge PITA.
|
|
26
|
+
*/
|
|
27
|
+
export class CancelablePromise<T = void> implements Promise<T>, Cancelable {
|
|
28
|
+
#reject!: (cause: any) => void;
|
|
29
|
+
#promise: Promise<T>;
|
|
30
|
+
#isSettled = false;
|
|
31
|
+
|
|
32
|
+
// Cancelable cannot create its own logger because that would create a circular dependency
|
|
33
|
+
static #logger: Logger | Console = console;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a new cancelable promise.
|
|
37
|
+
*
|
|
38
|
+
* If the promise is rejected due to cancelation, the {@link executor} callbacks have no effect.
|
|
39
|
+
*
|
|
40
|
+
* If you supply {@link onCancel} it overwrites the {@link CancelablePromise#onCancel} method.
|
|
41
|
+
*
|
|
42
|
+
* @param executor the normal executor supplied to a {@link Promise} constructor
|
|
43
|
+
* @param onCancel rejection handler supplied with a reason and a callback for optionally rejecting the promise
|
|
44
|
+
*/
|
|
45
|
+
constructor(
|
|
46
|
+
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void,
|
|
47
|
+
onCancel?: (reason: Error) => void,
|
|
48
|
+
) {
|
|
49
|
+
if (onCancel !== undefined) {
|
|
50
|
+
this.onCancel = onCancel;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.#promise = new Promise((resolve, reject) => {
|
|
54
|
+
this.#reject = (reason?: any) => {
|
|
55
|
+
this.#isSettled = true;
|
|
56
|
+
reject(errorOf(reason));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
executor(
|
|
60
|
+
(value: T | PromiseLike<T>) => {
|
|
61
|
+
if (this.#isSettled) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.#isSettled = true;
|
|
66
|
+
resolve(value);
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
(reason?: any) => {
|
|
70
|
+
if (this.#isSettled) {
|
|
71
|
+
CancelablePromise.logger.warn(`Cancelable promise rejected after settle:`, reason);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.#reject(reason);
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Cancel the operation.
|
|
83
|
+
*/
|
|
84
|
+
cancel(reason: any = new CanceledError()) {
|
|
85
|
+
if (this.#isSettled) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
this.onCancel(reason);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
this.#reject(e);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Implement cancelation. This is only invoked if the promise has not resolved.
|
|
98
|
+
*
|
|
99
|
+
* Throwing causes the promise to reject with the error thrown. The default implementation rethrows {@link reason}.
|
|
100
|
+
*
|
|
101
|
+
* This is overwritten if there is an "onCancel" argument to the constructor.
|
|
102
|
+
*/
|
|
103
|
+
protected onCancel(reason: Error) {
|
|
104
|
+
throw reason;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
then<TResult1 = T, TResult2 = never>(
|
|
108
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
109
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
|
110
|
+
): CancelablePromise<TResult1 | TResult2> {
|
|
111
|
+
const result = this.#promise.then(onfulfilled, onrejected) as CancelablePromise<TResult1 | TResult2>;
|
|
112
|
+
result.cancel = this.cancel.bind(this);
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
catch<TResult = never>(
|
|
117
|
+
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
|
|
118
|
+
): CancelablePromise<T | TResult> {
|
|
119
|
+
return this.then(onrejected);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
finally(onfinally?: (() => void) | null): CancelablePromise<T> {
|
|
123
|
+
const handler = (result: any) => {
|
|
124
|
+
onfinally?.();
|
|
125
|
+
return result;
|
|
126
|
+
};
|
|
127
|
+
return this.then(handler, handler);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get [Symbol.toStringTag]() {
|
|
131
|
+
return this.#promise[Symbol.toStringTag];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static is<T>(value: MaybePromise<T>): value is CancelablePromise<T> {
|
|
135
|
+
return MaybePromise.is(value) && typeof (value as CancelablePromise<T>).cancel === "function";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static resolve<T>(value: T): CancelablePromise<T> {
|
|
139
|
+
const result = Promise.resolve(value) as CancelablePromise<T>;
|
|
140
|
+
result.cancel = () => {};
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static reject(cause: any): CancelablePromise<any> {
|
|
145
|
+
const result = Promise.reject(errorOf(cause)) as CancelablePromise<any>;
|
|
146
|
+
result.cancel = () => {};
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static set logger(logger: Logger | Console) {
|
|
151
|
+
this.#logger = logger;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static get logger() {
|
|
155
|
+
return this.#logger;
|
|
156
|
+
}
|
|
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
|
+
}
|
package/src/util/Observable.ts
CHANGED
package/src/util/Promises.ts
CHANGED
|
@@ -291,55 +291,3 @@ export const MaybePromise = {
|
|
|
291
291
|
};
|
|
292
292
|
|
|
293
293
|
MaybePromise.toString = () => "MaybePromise";
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* A "promise" that may be canceled.
|
|
297
|
-
*
|
|
298
|
-
* Behaviors like a normal promise but does not actually extend {@link Promise} because that makes extension a PITA.
|
|
299
|
-
*/
|
|
300
|
-
export class CancelablePromise<T = void> implements Promise<T> {
|
|
301
|
-
#promise: Promise<T>;
|
|
302
|
-
|
|
303
|
-
constructor(
|
|
304
|
-
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void,
|
|
305
|
-
onCancel?: () => void,
|
|
306
|
-
) {
|
|
307
|
-
this.#promise = new Promise(executor);
|
|
308
|
-
if (onCancel !== undefined) {
|
|
309
|
-
this.cancel = onCancel;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
cancel() {}
|
|
314
|
-
|
|
315
|
-
then<TResult1 = T, TResult2 = never>(
|
|
316
|
-
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
317
|
-
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null,
|
|
318
|
-
): CancelablePromise<TResult1 | TResult2> {
|
|
319
|
-
const result = this.#promise.then(onfulfilled, onrejected) as CancelablePromise<TResult1 | TResult2>;
|
|
320
|
-
result.cancel = this.cancel.bind(this);
|
|
321
|
-
return result;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
catch<TResult = never>(
|
|
325
|
-
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null,
|
|
326
|
-
): CancelablePromise<T | TResult> {
|
|
327
|
-
return this.then(onrejected);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
finally(onfinally?: (() => void) | null): CancelablePromise<T> {
|
|
331
|
-
const handler = (result: any) => {
|
|
332
|
-
onfinally?.();
|
|
333
|
-
return result;
|
|
334
|
-
};
|
|
335
|
-
return this.then(handler, handler);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
get [Symbol.toStringTag]() {
|
|
339
|
-
return this.#promise[Symbol.toStringTag];
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
static is<T>(value: MaybePromise<T>): value is CancelablePromise<T> {
|
|
343
|
-
return MaybePromise.is(value) && typeof (value as CancelablePromise<T>).cancel === "function";
|
|
344
|
-
}
|
|
345
|
-
}
|
package/src/util/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./Array.js";
|
|
|
8
8
|
export * from "./Boot.js";
|
|
9
9
|
export * from "./Bytes.js";
|
|
10
10
|
export * from "./Cache.js";
|
|
11
|
+
export * from "./Cancelable.js";
|
|
11
12
|
export * from "./Construction.js";
|
|
12
13
|
export * from "./DataReader.js";
|
|
13
14
|
export * from "./DataReadQueue.js";
|