@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.
Files changed (44) hide show
  1. package/dist/cancel.d.ts +1 -1
  2. package/dist/cancel.js +1 -2
  3. package/dist/commonResources.d.ts +3 -0
  4. package/dist/commonResources.js +3 -0
  5. package/dist/error.d.ts +2 -1
  6. package/dist/error.js +26 -4
  7. package/dist/events/cancel.d.ts +2 -1
  8. package/dist/events/cancel.js +59 -29
  9. package/dist/events/generic.d.ts +18 -14
  10. package/dist/events/generic.js +36 -26
  11. package/dist/events/sub.d.ts +3 -2
  12. package/dist/events/sub.js +5 -1
  13. package/dist/events/utils.d.ts +19 -0
  14. package/dist/events/utils.js +56 -0
  15. package/dist/events.d.ts +2 -1
  16. package/dist/events.js +1 -0
  17. package/dist/index.d.ts +4 -1
  18. package/dist/index.js +3 -0
  19. package/dist/lock.d.ts +18 -0
  20. package/dist/lock.js +200 -0
  21. package/dist/promise.d.ts +5 -0
  22. package/dist/promise.js +38 -0
  23. package/dist/scope/base.d.ts +99 -14
  24. package/dist/scope/base.js +163 -41
  25. package/dist/scope/context.d.ts +19 -0
  26. package/dist/scope/context.js +71 -0
  27. package/dist/scope/from.d.ts +1 -1
  28. package/dist/scope/from.js +32 -11
  29. package/dist/scope/standard.d.ts +4 -1
  30. package/dist/scope/standard.js +6 -0
  31. package/dist/scope.d.ts +1 -1
  32. package/dist/scopedResource.js +1 -2
  33. package/dist/timers.d.ts +23 -0
  34. package/dist/{timer.js → timers.js} +2 -4
  35. package/dist/token/base.d.ts +29 -16
  36. package/dist/token/base.js +39 -23
  37. package/dist/token.d.ts +1 -1
  38. package/dist/types.d.ts +19 -84
  39. package/package.json +6 -1
  40. package/dist/join.d.ts +0 -16
  41. package/dist/join.js +0 -85
  42. package/dist/timer.d.ts +0 -13
  43. package/dist/utils.d.ts +0 -51
  44. 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
@@ -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
@@ -1,44 +1,129 @@
1
- import type { Awaitable, Falsy } from '../types.ts';
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
- export interface ScopeRunOptions extends CommonScopeOptions {
12
+ interface ScopeLaunchSpecificOptions<V extends object, V2 extends object> {
13
+ transformScope: (scope: Scope<V>) => Scope<V2>;
9
14
  }
10
- export interface ScopeContextBase {
11
- readonly scope: Scope;
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> = Omit<T, keyof ScopeContextBase> & ScopeContextBase;
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
- readonly scope: Scope;
28
- resolveOrCancel<T>(promise: Awaitable<T>): Promise<T>;
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
- run<R>(block: (cx: ScopeContext) => Awaitable<R>, options?: ScopeRunOptions): Promise<R>;
36
- function<R, Args extends readonly unknown[]>(block: (cx: ScopeContext, ...args: Args) => Awaitable<R>, options?: ScopeRunOptions): (...args: Args) => Promise<R>;
37
- withResources(block: (builder: ScopedResources.Builder) => ScopedResources.Builder | void): Scope;
38
- static from(this: void, src: ToScope): Scope;
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
- export type ToScope = Scope | Token | readonly ToScope[] | Set<ToScope> | AbortSignal | CancellableOptions | Falsy;
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
@@ -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
- export class Scope {
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
- return Promise.reject(error);
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(reject);
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
- #subScope(token, options) {
46
- return scopeFrom([this, token, options]);
74
+ resolveOrUndefined(promise) {
75
+ return this.resolveOrCancel(promise, () => undefined);
47
76
  }
48
- /**
49
- *
50
- * @param block the function to run
51
- * @param options
52
- * @returns a promise that resolves to the result of `block`, or rejects if the scope is closed first.
53
- */
54
- async run(block, options) {
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, options);
107
+ const subScope = this.#subScope(controller.token, ...params);
108
+ let out;
57
109
  try {
58
- return await subScope.resolveOrCancel(block(new Scope.#Context(subScope)));
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
- const ret = await subScope.resolveOrCancel(_block(new Scope.#Context(subScope), ...args));
80
- await controller.cancel();
81
- return ret;
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
- catch (error) {
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