@youngspe/async-scope 0.1.0-dev.0 → 0.1.0-dev.1
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/cancel.d.ts +1 -1
- package/dist/cancel.js +1 -2
- package/dist/commonResources.d.ts +3 -0
- package/dist/commonResources.js +3 -0
- package/dist/error.d.ts +2 -1
- package/dist/error.js +26 -4
- package/dist/events/cancel.d.ts +2 -1
- package/dist/events/cancel.js +59 -29
- package/dist/events/generic.d.ts +18 -14
- package/dist/events/generic.js +36 -26
- package/dist/events/sub.d.ts +3 -2
- package/dist/events/sub.js +5 -1
- package/dist/events/utils.d.ts +19 -0
- package/dist/events/utils.js +56 -0
- package/dist/events.d.ts +2 -1
- package/dist/events.js +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/lock.d.ts +18 -0
- package/dist/lock.js +200 -0
- package/dist/promise.d.ts +5 -0
- package/dist/promise.js +38 -0
- package/dist/scope/base.d.ts +99 -14
- package/dist/scope/base.js +163 -41
- package/dist/scope/context.d.ts +19 -0
- package/dist/scope/context.js +71 -0
- package/dist/scope/from.d.ts +1 -1
- package/dist/scope/from.js +32 -11
- package/dist/scope/standard.d.ts +4 -1
- package/dist/scope/standard.js +6 -0
- package/dist/scope.d.ts +1 -1
- package/dist/scopedResource.js +1 -2
- package/dist/timers.d.ts +23 -0
- package/dist/{timer.js → timers.js} +2 -4
- package/dist/token/base.d.ts +29 -16
- package/dist/token/base.js +39 -23
- package/dist/token.d.ts +1 -1
- package/dist/types.d.ts +19 -84
- package/package.json +6 -1
- package/dist/join.d.ts +0 -16
- package/dist/join.js +0 -85
- package/dist/timer.d.ts +0 -13
- package/dist/utils.d.ts +0 -51
- package/dist/utils.js +0 -20
package/dist/lock.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Token } from './token.js';
|
|
2
|
+
class RawLock {
|
|
3
|
+
#value;
|
|
4
|
+
/** If positive, locked for reading. If negative, locked for writing. */
|
|
5
|
+
#count = 0;
|
|
6
|
+
#queue = [];
|
|
7
|
+
constructor(value) {
|
|
8
|
+
this.#value = value;
|
|
9
|
+
}
|
|
10
|
+
createReadGuard(token) {
|
|
11
|
+
let raw = this;
|
|
12
|
+
if (token?.isCancelled) {
|
|
13
|
+
raw = undefined;
|
|
14
|
+
}
|
|
15
|
+
const release = () => {
|
|
16
|
+
const _raw = raw;
|
|
17
|
+
sub?.dispose();
|
|
18
|
+
sub = raw = undefined;
|
|
19
|
+
_raw?.releaseReader();
|
|
20
|
+
};
|
|
21
|
+
let sub = token?.add(release);
|
|
22
|
+
return {
|
|
23
|
+
get value() {
|
|
24
|
+
if (!raw)
|
|
25
|
+
throw new TypeError('Cannot read from released guard');
|
|
26
|
+
return raw.#value;
|
|
27
|
+
},
|
|
28
|
+
release,
|
|
29
|
+
[Symbol.dispose]: release,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
createWriteGuard(token) {
|
|
33
|
+
let raw = this;
|
|
34
|
+
if (token?.isCancelled) {
|
|
35
|
+
raw = undefined;
|
|
36
|
+
}
|
|
37
|
+
const release = () => {
|
|
38
|
+
const _raw = raw;
|
|
39
|
+
sub?.dispose();
|
|
40
|
+
sub = raw = undefined;
|
|
41
|
+
_raw?.releaseWriter();
|
|
42
|
+
};
|
|
43
|
+
let sub = token?.add(release);
|
|
44
|
+
return {
|
|
45
|
+
get value() {
|
|
46
|
+
if (!raw)
|
|
47
|
+
throw new TypeError('Cannot read from released guard');
|
|
48
|
+
return raw.#value;
|
|
49
|
+
},
|
|
50
|
+
set value(value) {
|
|
51
|
+
if (!raw)
|
|
52
|
+
throw new TypeError('Cannot write to released guard');
|
|
53
|
+
raw.#value = value;
|
|
54
|
+
},
|
|
55
|
+
release,
|
|
56
|
+
[Symbol.dispose]: release,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
releaseReader() {
|
|
60
|
+
if (this.#count === 0)
|
|
61
|
+
throw new TypeError('Cannot release reader lock when no readers are held');
|
|
62
|
+
if (this.#count < 0)
|
|
63
|
+
throw new TypeError('Cannot release reader lock when it is locked for writing');
|
|
64
|
+
if (this.#count > 1) {
|
|
65
|
+
--this.#count;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const waiter = this.#queue.shift();
|
|
69
|
+
if (!waiter) {
|
|
70
|
+
this.#count = 0;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.#count = 1;
|
|
74
|
+
waiter.resolve(this.createReadGuard(waiter.token));
|
|
75
|
+
}
|
|
76
|
+
#getQueuedReaders() {
|
|
77
|
+
let readerCount = 0;
|
|
78
|
+
for (const waiter of this.#queue) {
|
|
79
|
+
if (waiter.kind !== 'read')
|
|
80
|
+
break;
|
|
81
|
+
++readerCount;
|
|
82
|
+
}
|
|
83
|
+
if (readerCount === 0)
|
|
84
|
+
return;
|
|
85
|
+
return this.#queue.splice(0, readerCount);
|
|
86
|
+
}
|
|
87
|
+
releaseWriter() {
|
|
88
|
+
if (this.#count === 0)
|
|
89
|
+
throw new TypeError('Cannot release writer lock when no writers are held');
|
|
90
|
+
if (this.#count > 0)
|
|
91
|
+
throw new TypeError('Cannot release writer lock when it is locked for reading');
|
|
92
|
+
const readers = this.#getQueuedReaders();
|
|
93
|
+
if (readers) {
|
|
94
|
+
this.#count = readers.length;
|
|
95
|
+
for (const r of readers) {
|
|
96
|
+
r.resolve(this.createReadGuard(r.token));
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const waiter = this.#queue.shift();
|
|
101
|
+
if (!waiter) {
|
|
102
|
+
this.#count = 0;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (waiter.kind === 'write') {
|
|
106
|
+
waiter.resolve(this.createWriteGuard(waiter.token));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
tryAcquireReader(token) {
|
|
111
|
+
// don't acquire if there is anything in the queue because as long as the queue is not empty, it
|
|
112
|
+
// must have at least one writer
|
|
113
|
+
if (token?.isCancelled || this.#count < 0 || this.#queue.length)
|
|
114
|
+
return undefined;
|
|
115
|
+
++this.#count;
|
|
116
|
+
return this.createReadGuard(token);
|
|
117
|
+
}
|
|
118
|
+
tryAcquireWriter(token) {
|
|
119
|
+
if (token?.isCancelled || this.#count !== 0)
|
|
120
|
+
return undefined;
|
|
121
|
+
this.#count = -1;
|
|
122
|
+
return this.createWriteGuard(token);
|
|
123
|
+
}
|
|
124
|
+
#removeWaiter(waiter) {
|
|
125
|
+
const index = this.#queue.indexOf(waiter);
|
|
126
|
+
if (index < 0)
|
|
127
|
+
return;
|
|
128
|
+
this.#queue.splice(index, 1);
|
|
129
|
+
if (index === 0 && this.#count > 0 && waiter.kind === 'write') {
|
|
130
|
+
const readers = this.#getQueuedReaders();
|
|
131
|
+
if (readers) {
|
|
132
|
+
this.#count += readers.length;
|
|
133
|
+
for (const r of readers) {
|
|
134
|
+
r.resolve(this.createReadGuard(r.token));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async acquireReaderAsync(token) {
|
|
140
|
+
token?.throwIfCancelled();
|
|
141
|
+
const guard = this.tryAcquireReader(token);
|
|
142
|
+
if (guard)
|
|
143
|
+
return guard;
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const waiter = {
|
|
146
|
+
kind: 'read',
|
|
147
|
+
resolve: x => {
|
|
148
|
+
sub?.dispose();
|
|
149
|
+
resolve(x);
|
|
150
|
+
},
|
|
151
|
+
token,
|
|
152
|
+
};
|
|
153
|
+
const sub = token?.add(e => {
|
|
154
|
+
this.#removeWaiter(waiter);
|
|
155
|
+
reject(e);
|
|
156
|
+
});
|
|
157
|
+
this.#queue.push(waiter);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async acquireWriterAsync(token) {
|
|
161
|
+
token?.throwIfCancelled();
|
|
162
|
+
const guard = this.tryAcquireWriter(token);
|
|
163
|
+
if (guard)
|
|
164
|
+
return guard;
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
const waiter = {
|
|
167
|
+
kind: 'write',
|
|
168
|
+
resolve: x => {
|
|
169
|
+
sub?.dispose();
|
|
170
|
+
resolve(x);
|
|
171
|
+
},
|
|
172
|
+
token,
|
|
173
|
+
};
|
|
174
|
+
const sub = token?.add(e => {
|
|
175
|
+
this.#removeWaiter(waiter);
|
|
176
|
+
reject(e);
|
|
177
|
+
});
|
|
178
|
+
this.#queue.push(waiter);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export class Lock {
|
|
183
|
+
#raw;
|
|
184
|
+
constructor(...[value]) {
|
|
185
|
+
this.#raw = new RawLock(value);
|
|
186
|
+
}
|
|
187
|
+
acquireShared(options) {
|
|
188
|
+
return this.#raw.acquireReaderAsync(options && Token.from(options));
|
|
189
|
+
}
|
|
190
|
+
acquire(options) {
|
|
191
|
+
return this.#raw.acquireWriterAsync(options && Token.from(options));
|
|
192
|
+
}
|
|
193
|
+
tryAcquireShared(options) {
|
|
194
|
+
return this.#raw.tryAcquireReader(options && Token.from(options));
|
|
195
|
+
}
|
|
196
|
+
tryAcquire(options) {
|
|
197
|
+
return this.#raw.tryAcquireWriter(options && Token.from(options));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ToScope } from './scope.ts';
|
|
2
|
+
import type { Awaitable } from './types.ts';
|
|
3
|
+
export declare function cancellablePromise<T>(promise: Awaitable<T>, scope: ToScope): Promise<T>;
|
|
4
|
+
export declare function runOrCancel<T>(fn: () => Awaitable<T>, scope: ToScope): Promise<T>;
|
|
5
|
+
//# sourceMappingURL=promise.d.ts.map
|
package/dist/promise.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isPromiseLike } from '@youngspe/common-async-utils';
|
|
2
|
+
import { Token } from './token.js';
|
|
3
|
+
function cancellablePromiseInternal(promise, token) {
|
|
4
|
+
if (!isPromiseLike(promise))
|
|
5
|
+
return Promise.resolve(promise);
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const sub = token.add(reject);
|
|
8
|
+
promise.then(x => {
|
|
9
|
+
sub?.dispose();
|
|
10
|
+
resolve(x);
|
|
11
|
+
}, e => {
|
|
12
|
+
sub?.dispose();
|
|
13
|
+
reject(e);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function cancellablePromise(promise, scope) {
|
|
18
|
+
const token = Token.from(scope);
|
|
19
|
+
const { error } = token;
|
|
20
|
+
if (error)
|
|
21
|
+
return Promise.reject(error);
|
|
22
|
+
return cancellablePromiseInternal(promise, token);
|
|
23
|
+
}
|
|
24
|
+
export function runOrCancel(fn, scope) {
|
|
25
|
+
const token = Token.from(scope);
|
|
26
|
+
const { error } = token;
|
|
27
|
+
if (error)
|
|
28
|
+
return Promise.reject(error);
|
|
29
|
+
let promise;
|
|
30
|
+
try {
|
|
31
|
+
promise = fn();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return Promise.reject(error);
|
|
35
|
+
}
|
|
36
|
+
return cancellablePromiseInternal(promise, token);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=promise.js.map
|
package/dist/scope/base.d.ts
CHANGED
|
@@ -1,44 +1,129 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ScopeBase } from '@youngspe/async-scope-common';
|
|
2
|
+
import type { Awaitable, BetterOmit, Falsy, OptionalUndefinedParams, PartialOrUndefined, SetProps, UndefinedIfDefault, UpdateObject } from '../types.ts';
|
|
2
3
|
import { Token } from '../token.ts';
|
|
3
4
|
import { type CancellableOptions } from '../cancel.ts';
|
|
4
5
|
import type { CancellableLike } from '../cancel.ts';
|
|
5
6
|
import { ScopedResources } from '../scopedResource.ts';
|
|
7
|
+
import { StandardScope } from '../scope.ts';
|
|
8
|
+
import { type TimerOptions } from '../timers.ts';
|
|
9
|
+
import { ContextData, type ContextDataBuilder } from './context.ts';
|
|
6
10
|
export interface CommonScopeOptions extends CancellableOptions {
|
|
7
11
|
}
|
|
8
|
-
|
|
12
|
+
interface ScopeLaunchSpecificOptions<V extends object, V2 extends object> {
|
|
13
|
+
transformScope: (scope: Scope<V>) => Scope<V2>;
|
|
9
14
|
}
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
interface ScopeLaunchOptionsBase<V extends object = object, V2 extends object = V> extends CommonScopeOptions, PartialOrUndefined<ScopeLaunchSpecificOptions<V, V2>> {
|
|
16
|
+
}
|
|
17
|
+
interface ScopeLaunchOptionsRequired<V extends object = object, V2 extends object = V> extends CommonScopeOptions, ScopeLaunchSpecificOptions<V, V2> {
|
|
18
|
+
}
|
|
19
|
+
export type ScopeLaunchOptions<V extends object = object, V2 extends object = V> = [
|
|
20
|
+
V
|
|
21
|
+
] extends [V2] ? ScopeLaunchOptionsBase<V, V2> : ScopeLaunchOptionsRequired<V, V2>;
|
|
22
|
+
export type ScopeLaunchOptionsParams<V extends object = object, V2 extends object = V> = OptionalUndefinedParams<[options: UndefinedIfDefault<ScopeLaunchOptions<V, V2>>]>;
|
|
23
|
+
export interface ScopeContextBase<out V extends object = object> {
|
|
24
|
+
readonly scope: Scope<V>;
|
|
12
25
|
readonly token: Token;
|
|
13
26
|
readonly resources: ScopedResources;
|
|
14
27
|
readonly signal: AbortSignal;
|
|
15
28
|
}
|
|
16
|
-
export type ScopeContext<T extends object = object> =
|
|
17
|
-
export declare abstract class Scope {
|
|
29
|
+
export type ScopeContext<T extends object = object> = BetterOmit<T, keyof ScopeContextBase> & ScopeContextBase<T>;
|
|
30
|
+
export declare abstract class Scope<out V extends object = object> extends ScopeBase {
|
|
18
31
|
#private;
|
|
32
|
+
/** The cancellation token that determines when this scope is closed. */
|
|
19
33
|
abstract get token(): Token;
|
|
20
34
|
abstract get resources(): ScopedResources;
|
|
21
35
|
get isClosed(): boolean;
|
|
22
36
|
get signal(): AbortSignal;
|
|
37
|
+
/** @throws if this scope is closed. */
|
|
23
38
|
throwIfClosed(): void;
|
|
24
39
|
protected _onError(value: unknown): void;
|
|
40
|
+
/**
|
|
41
|
+
* @see {@linkcode Token#use}
|
|
42
|
+
*/
|
|
25
43
|
use<T extends CancellableLike>(value: T): T;
|
|
44
|
+
/**
|
|
45
|
+
* @see {@linkcode Token#tryUse}
|
|
46
|
+
*/
|
|
26
47
|
tryUse<T extends CancellableLike>(value: T): T | undefined;
|
|
27
|
-
|
|
28
|
-
|
|
48
|
+
/** This scope */
|
|
49
|
+
readonly scope: Scope<V>;
|
|
50
|
+
resolveOrCancel<T, U = never>(promise: Awaitable<T>, onCancel?: (error: Error) => Awaitable<U>): Promise<T | U>;
|
|
51
|
+
resolveOrUndefined<T>(promise: Awaitable<T>): Promise<T | undefined>;
|
|
52
|
+
get contextData(): ContextData<V>;
|
|
53
|
+
/**
|
|
54
|
+
* @throws if this scope is closed.
|
|
55
|
+
*/
|
|
56
|
+
getContext(options?: CancellableOptions & {
|
|
57
|
+
values?: undefined;
|
|
58
|
+
}): ScopeContext<V>;
|
|
59
|
+
getContext<V2 extends object>(options: CancellableOptions & {
|
|
60
|
+
values: V2;
|
|
61
|
+
}): ScopeContext<UpdateObject<V, V2>>;
|
|
62
|
+
getContext<V2 extends object = V>(options?: CancellableOptions & {
|
|
63
|
+
values?: V2 | undefined;
|
|
64
|
+
}): ScopeContext<UpdateObject<V, Partial<V2>>>;
|
|
29
65
|
/**
|
|
66
|
+
* Launches a task in a subscope context. The subscope will be closed when the function completes.
|
|
67
|
+
*
|
|
68
|
+
* Rejects if the scope is cancelled before the function is complete.
|
|
30
69
|
*
|
|
31
70
|
* @param block the function to run
|
|
32
71
|
* @param options
|
|
33
72
|
* @returns a promise that resolves to the result of `block`, or rejects if the scope is closed first.
|
|
34
73
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
74
|
+
launch<R, V2 extends object = V>(block: (cx: ScopeContext<V2>) => Awaitable<R>, ...params: ScopeLaunchOptionsParams<V, V2>): Promise<R>;
|
|
75
|
+
/**
|
|
76
|
+
* Launches a task in a subscope context. The subscope will be closed when the function completes.
|
|
77
|
+
*
|
|
78
|
+
* If the scope is cancelled before the function is complete, this will resolve to `undefined`.
|
|
79
|
+
*
|
|
80
|
+
* @param block the function to run
|
|
81
|
+
* @param options
|
|
82
|
+
* @returns a promise that resolves to the result of `block`, or `undefined` if the scope is
|
|
83
|
+
* closed first.
|
|
84
|
+
*/
|
|
85
|
+
launchCancellable<R, V2 extends object = V>(block: (cx: ScopeContext<V2>) => Awaitable<R>, ...params: ScopeLaunchOptionsParams<V, V2>): Promise<R | undefined>;
|
|
86
|
+
/**
|
|
87
|
+
* Runs the given function in a subscope context. If the function throws a non-cancellation error,
|
|
88
|
+
* the subscope will be closed.
|
|
89
|
+
*
|
|
90
|
+
* Rejects if the scope is cancelled before the function is complete.
|
|
91
|
+
*
|
|
92
|
+
* @param block the function to run
|
|
93
|
+
* @param options
|
|
94
|
+
* @returns a promise that resolves to the result of `block`, or rejects if the scope is closed first.
|
|
95
|
+
*/
|
|
96
|
+
run<R, V2 extends object = V>(block: (cx: ScopeContext<V2>) => Awaitable<R>, ...params: ScopeLaunchOptionsParams<V, V2>): Promise<R>;
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* Runs the given function in a subscope context. If the function throws a non-cancellation error,
|
|
100
|
+
* the subscope will be closed.
|
|
101
|
+
*
|
|
102
|
+
* If the scope is cancelled before the function is complete, this will resolve to `undefined`.
|
|
103
|
+
*
|
|
104
|
+
* @param block the function to run
|
|
105
|
+
* @param options
|
|
106
|
+
* @returns a promise that resolves to the result of `block`, or `undefined` if the scope is
|
|
107
|
+
* closed first.
|
|
108
|
+
*/
|
|
109
|
+
runCancellable<R, V2 extends object = V>(block: (cx: ScopeContext<V2>) => Awaitable<R>, ...params: ScopeLaunchOptionsParams<V, V2>): Promise<R | undefined>;
|
|
110
|
+
withResources(block: (builder: ScopedResources.Builder) => ScopedResources.Builder | void): Scope<V>;
|
|
111
|
+
withContextData<V2 extends object>(block: (builder: ContextDataBuilder<V>) => ContextDataBuilder<V2>): Scope<V2>;
|
|
112
|
+
withContextData<V2 extends object = V>(block: (builder: ContextDataBuilder<V>) => ContextDataBuilder<V2> | void): Scope<V | V2>;
|
|
113
|
+
withContextData<V2 extends object>(data: ContextData<V2>): Scope<SetProps<V, V2>>;
|
|
114
|
+
withContextData<V2 extends object = V>(data: ContextData<V2> | Falsy): Scope<V | SetProps<V, V2>>;
|
|
115
|
+
withContextValues<V2 extends object = V>(values: V2): Scope<UpdateObject<V, V2>>;
|
|
116
|
+
delay(ms: number, options?: TimerOptions): Promise<void>;
|
|
117
|
+
replaceToken(token: Token): StandardScope<V>;
|
|
118
|
+
static from<V extends object = object>(this: void, src: ToScope<V>): Scope<V>;
|
|
39
119
|
/** An empty scope that will never be closed. */
|
|
40
|
-
static get static(): Scope
|
|
120
|
+
static get static(): Scope<object>;
|
|
41
121
|
}
|
|
42
122
|
export declare const STATIC_SCOPE: Scope;
|
|
43
|
-
|
|
123
|
+
type ToScopeBase<V extends object = object> = Scope<V> | Token | readonly ToScopeBase<V>[] | Set<ToScopeBase<V>> | AbortSignal | CancellableOptions | Falsy;
|
|
124
|
+
type ToScopeWithContext<V extends object> = Scope<V> | readonly [ToScopeWithContext<V>, ...ToScopeBase<Partial<V>>[]] | (CancellableOptions & {
|
|
125
|
+
scope: ToScopeWithContext<V>;
|
|
126
|
+
});
|
|
127
|
+
export type ToScope<V extends object = object> = object extends V ? ToScopeBase<V> : ToScopeWithContext<V>;
|
|
128
|
+
export {};
|
|
44
129
|
//# sourceMappingURL=base.d.ts.map
|
package/dist/scope/base.js
CHANGED
|
@@ -1,38 +1,67 @@
|
|
|
1
|
+
import { ScopeBase } from '@youngspe/async-scope-common';
|
|
2
|
+
import { isPromiseLike } from '@youngspe/common-async-utils';
|
|
1
3
|
import { Token, STATIC_TOKEN } from '../token.js';
|
|
2
4
|
import {} from '../cancel.js';
|
|
3
5
|
import { scopeFrom } from './from.js';
|
|
4
|
-
import { isPromiseLike } from '../utils.js';
|
|
5
6
|
import { ScopedResources } from '../scopedResource.js';
|
|
6
7
|
import { StandardScope } from '../scope.js';
|
|
7
|
-
|
|
8
|
+
import { delay } from '../timers.js';
|
|
9
|
+
import { ContextData } from './context.js';
|
|
10
|
+
import { CancellationError, unwrapCancellationError } from '../error.js';
|
|
11
|
+
export class Scope extends ScopeBase {
|
|
8
12
|
get isClosed() {
|
|
9
13
|
return this.token.isCancelled;
|
|
10
14
|
}
|
|
11
15
|
get signal() {
|
|
12
16
|
return this.token.signal;
|
|
13
17
|
}
|
|
18
|
+
/** @throws if this scope is closed. */
|
|
14
19
|
throwIfClosed() {
|
|
15
20
|
this.token.throwIfCancelled();
|
|
16
21
|
}
|
|
17
22
|
_onError(value) {
|
|
18
23
|
throw value;
|
|
19
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* @see {@linkcode Token#use}
|
|
27
|
+
*/
|
|
20
28
|
use(value) {
|
|
21
29
|
return this.token.use(value);
|
|
22
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* @see {@linkcode Token#tryUse}
|
|
33
|
+
*/
|
|
23
34
|
tryUse(value) {
|
|
24
35
|
return this.token.tryUse(value);
|
|
25
36
|
}
|
|
37
|
+
/** This scope */
|
|
26
38
|
scope = this;
|
|
27
|
-
resolveOrCancel(promise) {
|
|
39
|
+
resolveOrCancel(promise, onCancel) {
|
|
28
40
|
const { token } = this;
|
|
29
41
|
const { error } = token;
|
|
30
|
-
if (error)
|
|
31
|
-
|
|
42
|
+
if (error) {
|
|
43
|
+
if (!onCancel)
|
|
44
|
+
return Promise.reject(error);
|
|
45
|
+
try {
|
|
46
|
+
return Promise.resolve(onCancel(error));
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return Promise.reject(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
32
52
|
if (!isPromiseLike(promise))
|
|
33
53
|
return Promise.resolve(promise);
|
|
34
54
|
return new Promise((resolve, reject) => {
|
|
35
|
-
const sub = token.add(
|
|
55
|
+
const sub = token.add(onCancel ?
|
|
56
|
+
error => {
|
|
57
|
+
try {
|
|
58
|
+
return resolve(onCancel(error));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return reject(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
: reject);
|
|
36
65
|
promise.then(x => {
|
|
37
66
|
sub?.dispose();
|
|
38
67
|
resolve(x);
|
|
@@ -42,55 +71,148 @@ export class Scope {
|
|
|
42
71
|
});
|
|
43
72
|
});
|
|
44
73
|
}
|
|
45
|
-
|
|
46
|
-
return
|
|
74
|
+
resolveOrUndefined(promise) {
|
|
75
|
+
return this.resolveOrCancel(promise, () => undefined);
|
|
47
76
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
77
|
+
#subScope(token, ...[options]) {
|
|
78
|
+
const newScope = scopeFrom([this, token, options]);
|
|
79
|
+
if (options?.transformScope)
|
|
80
|
+
return options.transformScope(newScope);
|
|
81
|
+
return newScope;
|
|
82
|
+
}
|
|
83
|
+
get contextData() {
|
|
84
|
+
return ContextData.empty;
|
|
85
|
+
}
|
|
86
|
+
getContext(options) {
|
|
87
|
+
this.throwIfClosed();
|
|
88
|
+
let scope = this;
|
|
89
|
+
if (options?.scope || options?.token || options?.signal) {
|
|
90
|
+
scope = Scope.from([scope, options]);
|
|
91
|
+
scope.throwIfClosed();
|
|
92
|
+
}
|
|
93
|
+
if (options?.values) {
|
|
94
|
+
scope = scope.withContextValues(options.values);
|
|
95
|
+
}
|
|
96
|
+
const cx = new Scope.#Context(scope);
|
|
97
|
+
return scope.contextData.updateContext(cx);
|
|
98
|
+
}
|
|
99
|
+
async #run(block, cancelOnComplete, onCancel, ...params) {
|
|
100
|
+
const { error } = this.token;
|
|
101
|
+
if (error) {
|
|
102
|
+
if (onCancel)
|
|
103
|
+
return await onCancel(error);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
55
106
|
await using controller = this.use(Token.createController());
|
|
56
|
-
const subScope = this.#subScope(controller.token,
|
|
107
|
+
const subScope = this.#subScope(controller.token, ...params);
|
|
108
|
+
let out;
|
|
57
109
|
try {
|
|
58
|
-
|
|
110
|
+
out = await subScope.resolveOrCancel(block(subScope.getContext()), onCancel ?? Promise.reject.bind(Promise));
|
|
59
111
|
}
|
|
60
112
|
catch (error) {
|
|
61
|
-
await controller.cancel(error);
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function(block, options) {
|
|
66
|
-
let _block = block;
|
|
67
|
-
const controller = this.tryUse(Token.createController());
|
|
68
|
-
if (!controller)
|
|
69
|
-
return () => Promise.reject(this.token.error);
|
|
70
|
-
const subScope = this.#subScope(controller.token, options);
|
|
71
|
-
const sub = subScope.token.add(reason => {
|
|
72
|
-
_block = reason;
|
|
73
|
-
});
|
|
74
|
-
return async (...args) => {
|
|
75
|
-
sub?.dispose();
|
|
76
|
-
if (_block instanceof Error)
|
|
77
|
-
throw _block;
|
|
78
113
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
114
|
+
if (onCancel && error instanceof Error && (subScope.isClosed || unwrapCancellationError(error))) {
|
|
115
|
+
return await onCancel(error);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
82
120
|
}
|
|
83
|
-
|
|
121
|
+
finally {
|
|
84
122
|
await controller.cancel(error);
|
|
85
|
-
throw error;
|
|
86
123
|
}
|
|
87
|
-
}
|
|
124
|
+
}
|
|
125
|
+
if (cancelOnComplete) {
|
|
126
|
+
await controller.cancel(new CancellationError());
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
controller.defuse();
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Launches a task in a subscope context. The subscope will be closed when the function completes.
|
|
135
|
+
*
|
|
136
|
+
* Rejects if the scope is cancelled before the function is complete.
|
|
137
|
+
*
|
|
138
|
+
* @param block the function to run
|
|
139
|
+
* @param options
|
|
140
|
+
* @returns a promise that resolves to the result of `block`, or rejects if the scope is closed first.
|
|
141
|
+
*/
|
|
142
|
+
launch(block, ...params) {
|
|
143
|
+
return this.#run(block, true, undefined, ...params);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Launches a task in a subscope context. The subscope will be closed when the function completes.
|
|
147
|
+
*
|
|
148
|
+
* If the scope is cancelled before the function is complete, this will resolve to `undefined`.
|
|
149
|
+
*
|
|
150
|
+
* @param block the function to run
|
|
151
|
+
* @param options
|
|
152
|
+
* @returns a promise that resolves to the result of `block`, or `undefined` if the scope is
|
|
153
|
+
* closed first.
|
|
154
|
+
*/
|
|
155
|
+
async launchCancellable(block, ...params) {
|
|
156
|
+
return this.#run(block, true, () => undefined, ...params);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Runs the given function in a subscope context. If the function throws a non-cancellation error,
|
|
160
|
+
* the subscope will be closed.
|
|
161
|
+
*
|
|
162
|
+
* Rejects if the scope is cancelled before the function is complete.
|
|
163
|
+
*
|
|
164
|
+
* @param block the function to run
|
|
165
|
+
* @param options
|
|
166
|
+
* @returns a promise that resolves to the result of `block`, or rejects if the scope is closed first.
|
|
167
|
+
*/
|
|
168
|
+
async run(block, ...params) {
|
|
169
|
+
return this.#run(block, false, undefined, ...params);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
*
|
|
173
|
+
* Runs the given function in a subscope context. If the function throws a non-cancellation error,
|
|
174
|
+
* the subscope will be closed.
|
|
175
|
+
*
|
|
176
|
+
* If the scope is cancelled before the function is complete, this will resolve to `undefined`.
|
|
177
|
+
*
|
|
178
|
+
* @param block the function to run
|
|
179
|
+
* @param options
|
|
180
|
+
* @returns a promise that resolves to the result of `block`, or `undefined` if the scope is
|
|
181
|
+
* closed first.
|
|
182
|
+
*/
|
|
183
|
+
async runCancellable(block, ...params) {
|
|
184
|
+
return this.#run(block, false, () => undefined, ...params);
|
|
88
185
|
}
|
|
89
186
|
withResources(block) {
|
|
90
187
|
const builder = ScopedResources.builder(this.token).inherit(this.resources);
|
|
91
188
|
block(builder);
|
|
92
189
|
const resources = builder.finish();
|
|
93
|
-
return new StandardScope({ ...this, resources });
|
|
190
|
+
return new StandardScope({ ...this.#parts(), resources });
|
|
191
|
+
}
|
|
192
|
+
withContextData(data) {
|
|
193
|
+
if (!data || data === ContextData.empty)
|
|
194
|
+
return this;
|
|
195
|
+
const builder = this.contextData.builder();
|
|
196
|
+
if (typeof data === 'function') {
|
|
197
|
+
data(builder);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
builder.merge(data);
|
|
201
|
+
}
|
|
202
|
+
return new StandardScope({ ...this.#parts(), contextData: builder.finish() });
|
|
203
|
+
}
|
|
204
|
+
withContextValues(values) {
|
|
205
|
+
const contextData = this.contextData.builder().values(values).finish();
|
|
206
|
+
return new StandardScope({ ...this.#parts(), contextData });
|
|
207
|
+
}
|
|
208
|
+
delay(ms, options) {
|
|
209
|
+
return delay(ms, { ...options, scope: [this, options?.scope] });
|
|
210
|
+
}
|
|
211
|
+
replaceToken(token) {
|
|
212
|
+
return new StandardScope({ ...this.#parts(), token });
|
|
213
|
+
}
|
|
214
|
+
#parts() {
|
|
215
|
+
return { token: this.token, resources: this.resources, contextData: this.contextData };
|
|
94
216
|
}
|
|
95
217
|
static from(src) {
|
|
96
218
|
return scopeFrom(src);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SetProps, UpdateObject } from '../types.ts';
|
|
2
|
+
import type { ScopeContext, ScopeContextBase } from './base.ts';
|
|
3
|
+
export declare class ContextData<V extends object = object> {
|
|
4
|
+
#private;
|
|
5
|
+
private constructor();
|
|
6
|
+
updateContext(target: ScopeContextBase<V>): ScopeContext<V>;
|
|
7
|
+
builder(): ContextDataBuilder<V>;
|
|
8
|
+
static builder(this: void): ContextDataBuilder;
|
|
9
|
+
static empty: ContextData<object>;
|
|
10
|
+
}
|
|
11
|
+
export interface ContextDataBuilder<V extends object = object> {
|
|
12
|
+
values<V2 extends object>(props: V2): ContextDataBuilder<UpdateObject<V, V2>>;
|
|
13
|
+
getters<V2 extends object>(getters: {
|
|
14
|
+
[K in keyof V2]: (cx: ScopeContext<SetProps<V, V2>>) => V2[K];
|
|
15
|
+
}): ContextDataBuilder<SetProps<V, V2>>;
|
|
16
|
+
merge<V2 extends object>(contextData: ContextData<V2>): ContextDataBuilder<SetProps<V, V2>>;
|
|
17
|
+
finish(): ContextData<V>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=context.d.ts.map
|