@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
@@ -0,0 +1,71 @@
1
+ const contextProps = {
2
+ resources: undefined,
3
+ scope: undefined,
4
+ signal: undefined,
5
+ token: undefined,
6
+ };
7
+ export class ContextData {
8
+ #descriptors;
9
+ constructor(descriptors) {
10
+ this.#descriptors = descriptors;
11
+ }
12
+ updateContext(target) {
13
+ if (this.#descriptors) {
14
+ const descriptors = this.#descriptors;
15
+ for (const key of [...Object.keys(descriptors), ...Object.getOwnPropertySymbols(descriptors)]) {
16
+ const descriptor = descriptors[key];
17
+ if (!descriptor || key in contextProps)
18
+ continue;
19
+ Object.defineProperty(target, key, descriptor);
20
+ }
21
+ }
22
+ return target;
23
+ }
24
+ builder() {
25
+ return new ContextData.#Builder(Object.assign({}, this.#descriptors));
26
+ }
27
+ static builder() {
28
+ return new ContextData.#Builder(undefined);
29
+ }
30
+ static empty = Object.freeze(new ContextData(undefined));
31
+ static #Builder = class Builder {
32
+ #builderDescriptors;
33
+ constructor(descriptors) {
34
+ this.#builderDescriptors = descriptors;
35
+ }
36
+ values(props) {
37
+ const descriptors = (this.#builderDescriptors ??= {});
38
+ for (const key of [...Object.keys(props), ...Object.getOwnPropertySymbols(props)]) {
39
+ const value = props[key];
40
+ if (value === undefined)
41
+ continue;
42
+ descriptors[key] = { value };
43
+ }
44
+ return this;
45
+ }
46
+ getters(getters) {
47
+ const descriptors = (this.#builderDescriptors ??= {});
48
+ for (const key of [...Object.keys(getters), ...Object.getOwnPropertySymbols(getters)]) {
49
+ const value = getters[key];
50
+ if (value === undefined)
51
+ continue;
52
+ descriptors[key] = {
53
+ get() {
54
+ return value(this);
55
+ },
56
+ };
57
+ }
58
+ return this;
59
+ }
60
+ merge(contextData) {
61
+ Object.assign((this.#builderDescriptors ??= {}), contextData.#descriptors);
62
+ return this;
63
+ }
64
+ finish() {
65
+ if (!this.#builderDescriptors)
66
+ return ContextData.empty;
67
+ return new ContextData(this.#builderDescriptors);
68
+ }
69
+ };
70
+ }
71
+ //# sourceMappingURL=context.js.map
@@ -1,3 +1,3 @@
1
1
  import { Scope, type ToScope } from '../scope.ts';
2
- export declare function scopeFrom(src: ToScope, onError?: (error: unknown) => void): Scope;
2
+ export declare function scopeFrom<V extends object = object>(src: ToScope<V>, onError?: (error: unknown) => void): Scope<V>;
3
3
  //# sourceMappingURL=from.d.ts.map
@@ -1,13 +1,27 @@
1
1
  import { Token } from '../token.js';
2
2
  import { Scope, STATIC_SCOPE, StandardScope } from '../scope.js';
3
- import { isArray } from '../utils.js';
4
3
  import { ScopedResources } from '../scopedResource.js';
4
+ import { isIterable } from '@youngspe/common-async-utils';
5
+ import { ContextData } from './context.js';
6
+ import { CancellationTokenBase, ScopeBase } from '@youngspe/async-scope-common';
7
+ function combineContextData(values) {
8
+ const firstResult = values.next();
9
+ if (firstResult.done)
10
+ return undefined;
11
+ const first = firstResult.value;
12
+ let builder;
13
+ for (const item of values) {
14
+ builder ??= first.builder();
15
+ builder.merge(item);
16
+ }
17
+ return builder?.finish() ?? first;
18
+ }
5
19
  export function scopeFrom(src, onError) {
6
20
  if (!src)
7
21
  return STATIC_SCOPE;
8
- if (src instanceof Scope)
22
+ if (src instanceof ScopeBase)
9
23
  return src;
10
- if (src instanceof Token)
24
+ if (src instanceof CancellationTokenBase)
11
25
  return new StandardScope({ token: src, onError });
12
26
  if (src instanceof AbortSignal) {
13
27
  return new StandardScope({ token: Token.fromAbortSignal(src, { onError }), onError });
@@ -17,12 +31,15 @@ export function scopeFrom(src, onError) {
17
31
  const tokens = new Set();
18
32
  const abortSignals = new Set();
19
33
  const resources = new Set();
20
- function flatten(src) {
21
- if (!src)
34
+ const contextData = new Set();
35
+ function flatten(src, includeScope) {
36
+ if (!src || typeof src !== 'object')
22
37
  return;
23
- if (src instanceof Scope) {
38
+ if (src instanceof Scope && includeScope) {
24
39
  if (src.token.isCancelled)
25
40
  return src.token;
41
+ if (src === STATIC_SCOPE)
42
+ return;
26
43
  scopes.add(src);
27
44
  if (!src.token.isDefused) {
28
45
  tokens.add(src.token);
@@ -30,6 +47,9 @@ export function scopeFrom(src, onError) {
30
47
  if (!src.resources.isEmpty) {
31
48
  resources.add(src.resources);
32
49
  }
50
+ if (src.contextData !== ContextData.empty) {
51
+ contextData.add(src.contextData);
52
+ }
33
53
  return;
34
54
  }
35
55
  if (src instanceof Token) {
@@ -48,17 +68,17 @@ export function scopeFrom(src, onError) {
48
68
  abortSignals.add(src);
49
69
  return;
50
70
  }
51
- if (isArray(src) || src instanceof Set) {
71
+ if (isIterable(src)) {
52
72
  for (const child of src) {
53
- const cancelled = flatten(child);
73
+ const cancelled = flatten(child, includeScope);
54
74
  if (cancelled)
55
75
  return cancelled;
56
76
  }
57
77
  return;
58
78
  }
59
- return flatten(src.scope) || flatten(src.token) || flatten(src.signal);
79
+ return flatten(src.scope, true) || flatten(src.token, false) || flatten(src.signal, false);
60
80
  }
61
- const cancelled = flatten(src);
81
+ const cancelled = flatten(src, true);
62
82
  if (!hasNonScopeToken && scopes.size <= 1) {
63
83
  // If only one scope was passed in with no other tokens, we can return the
64
84
  // scope directly.
@@ -67,7 +87,7 @@ export function scopeFrom(src, onError) {
67
87
  //
68
88
  const [scope] = scopes;
69
89
  if (!cancelled || scope?.token === cancelled)
70
- return scope ?? Scope.static;
90
+ return (scope ?? STATIC_SCOPE);
71
91
  }
72
92
  if (cancelled)
73
93
  return new StandardScope({ token: cancelled });
@@ -81,6 +101,7 @@ export function scopeFrom(src, onError) {
81
101
  return new StandardScope({
82
102
  token: Token.combine(tokens),
83
103
  resources: ScopedResources.combine(resources),
104
+ contextData: combineContextData(contextData.values()),
84
105
  onError,
85
106
  });
86
107
  }
@@ -1,14 +1,17 @@
1
1
  import { Token } from '../token.ts';
2
2
  import { Scope } from '../scope.ts';
3
3
  import { ScopedResources } from '../scopedResource.js';
4
- export declare class StandardScope extends Scope {
4
+ import { ContextData } from './context.ts';
5
+ export declare class StandardScope<V extends object> extends Scope<V> {
5
6
  #private;
6
7
  get token(): Token;
7
8
  get resources(): ScopedResources;
9
+ get contextData(): ContextData<V>;
8
10
  protected _onError(value: unknown): void;
9
11
  constructor(params: {
10
12
  token?: Token | undefined;
11
13
  resources?: ScopedResources | undefined;
14
+ contextData?: ContextData<V> | undefined;
12
15
  onError?: ((e: unknown) => void) | undefined;
13
16
  });
14
17
  }
@@ -1,16 +1,21 @@
1
1
  import { Token } from '../token.js';
2
2
  import { Scope } from '../scope.js';
3
3
  import { ScopedResources } from '../scopedResource.js';
4
+ import { ContextData } from './context.js';
4
5
  export class StandardScope extends Scope {
5
6
  #resources;
6
7
  #token;
7
8
  #onError;
9
+ #contextData;
8
10
  get token() {
9
11
  return this.#token;
10
12
  }
11
13
  get resources() {
12
14
  return this.#resources;
13
15
  }
16
+ get contextData() {
17
+ return this.#contextData ?? super.contextData;
18
+ }
14
19
  _onError(value) {
15
20
  return this.#onError ? this.#onError(value) : super._onError(value);
16
21
  }
@@ -18,6 +23,7 @@ export class StandardScope extends Scope {
18
23
  super();
19
24
  this.#token = params.token ?? Token.static;
20
25
  this.#resources = (params.token?.isCancelled ? undefined : params.resources) ?? ScopedResources.empty;
26
+ this.#contextData = params.token?.isCancelled ? undefined : params.contextData;
21
27
  this.#onError = params.onError;
22
28
  }
23
29
  }
package/dist/scope.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Scope, STATIC_SCOPE } from './scope/base.ts';
2
2
  export { StandardScope } from './scope/standard.ts';
3
- export type { CommonScopeOptions, ScopeContext, ScopeRunOptions, ToScope } from './scope/base.ts';
3
+ export type { CommonScopeOptions, ScopeContext, ScopeLaunchOptions as ScopeRunOptions, ToScope, } from './scope/base.ts';
4
4
  //# sourceMappingURL=scope.d.ts.map
@@ -1,6 +1,5 @@
1
- import { joinPromises } from './join.js';
1
+ import { isAsyncDisposable, isDisposable, joinPromises } from '@youngspe/common-async-utils';
2
2
  import { Scope } from './scope.js';
3
- import { isAsyncDisposable, isDisposable } from './utils.js';
4
3
  export class ResourceError extends Error {
5
4
  static {
6
5
  this.prototype.name = this.name;
@@ -0,0 +1,23 @@
1
+ import type { CancellableOptions } from './cancel.ts';
2
+ declare const _timerId: unique symbol;
3
+ interface _TimerId {
4
+ [_timerId]: typeof _timerId;
5
+ }
6
+ type TimerId = typeof globalThis extends {
7
+ setTimeout(...args: any): infer X;
8
+ } ? X : _TimerId;
9
+ type SetTimerFunction = <A extends any[] = []>(action: (...args: A) => void, ms: number, ...args: A) => TimerId;
10
+ type ClearTimerFunction = (id: TimerId | undefined) => void;
11
+ export interface Clock {
12
+ setTimeout: SetTimerFunction;
13
+ setInterval: SetTimerFunction;
14
+ clearTimeout: ClearTimerFunction;
15
+ clearInterval: ClearTimerFunction;
16
+ }
17
+ export declare const GlobalClock: Clock;
18
+ export interface TimerOptions extends CancellableOptions {
19
+ clock?: Clock | undefined;
20
+ }
21
+ export declare function delay(ms: number, options?: TimerOptions): Promise<void>;
22
+ export {};
23
+ //# sourceMappingURL=timers.d.ts.map
@@ -1,8 +1,6 @@
1
1
  import { Token } from './token.js';
2
- const GlobalClock = {
3
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
2
+ export const GlobalClock = {
4
3
  setTimeout: (f, ...args) => globalThis.setTimeout(f, ...args),
5
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
6
4
  setInterval: (f, ...args) => globalThis.setInterval(f, ...args),
7
5
  clearTimeout: id => globalThis.clearTimeout(id),
8
6
  clearInterval: id => globalThis.clearInterval(id),
@@ -25,4 +23,4 @@ export function delay(ms, options) {
25
23
  }, ms);
26
24
  });
27
25
  }
28
- //# sourceMappingURL=timer.js.map
26
+ //# sourceMappingURL=timers.js.map
@@ -1,8 +1,14 @@
1
+ import { CancellationTokenBase } from '@youngspe/async-scope-common';
1
2
  import type { CancellableOrDisposable, CancellableLike, CancellationListener, Cancellable } from '../cancel.ts';
2
3
  import type { Awaitable, Falsy } from '../types.ts';
3
4
  import { Subscription, type SubscriptionLifecycle } from '../events/sub.js';
4
5
  import type { ToScope } from '../scope.ts';
5
- export declare abstract class Token {
6
+ export interface AddCancellableOptions {
7
+ paused?: boolean | undefined;
8
+ passive?: boolean | undefined;
9
+ }
10
+ export type ErrorFilter = (this: void, error: Error) => true | Error | 'defuse' | Falsy;
11
+ export declare abstract class Token extends CancellationTokenBase {
6
12
  #private;
7
13
  /**
8
14
  * The reason for the cancellation if this token has already been cancelled, otherwise `undefined`.
@@ -13,7 +19,7 @@ export declare abstract class Token {
13
19
  /** If `true`, this token has been _defused_, meaning it is guaranteed never to be cancelled. */
14
20
  get isDefused(): boolean;
15
21
  /**
16
- * If `true`, this token has been _cancelled_, and trigger any additional listeners.
22
+ * If `true`, this token has been _cancelled_, and will not trigger any additional listeners.
17
23
  *
18
24
  * This is equivalent to `token.error !== undefined`
19
25
  *
@@ -32,26 +38,25 @@ export declare abstract class Token {
32
38
  * - `undefined` if the listener could not be safely added. This is typically due to the token being
33
39
  * cancelled and indicates any future attempts will also fail.
34
40
  */
35
- protected abstract addOne(listener: CancellableOrDisposable): Subscription | undefined;
41
+ protected abstract addOne(listener: CancellableOrDisposable, options?: AddCancellableOptions): Subscription | undefined;
36
42
  /**
37
43
  * Adds listeners
38
44
  *
39
- * @param listeners - listeners that should be added to this token.
40
- * Each parameter may be:
41
- * - A {@link CancellableLike}:
42
- * - A {@link Cancellable}
43
- * - A {@link Disposable}
44
- * - An {@link AsyncDisposable}
45
- * - A falsy value, which will be ignored
46
- * - A (possibly nested) array of any of the above.
45
+ * @param listener - a listener that should be added to this token.
46
+ * This may be:
47
+ * - A {@link Cancellable}
48
+ * - A {@link Disposable}
49
+ * - An {@link AsyncDisposable}
50
+ * - A falsy value, which will be ignored
47
51
  * - A function that receives an {@link Error} and optionally returns a promise.
52
+ * - A (possibly nested) array of any of the above.
48
53
  *
49
54
  * @returns
50
55
  * - A {@link Subscription} that may be used to pause, resume, or remove the provide listeners if either the listeners were safely added or the token is defused.
51
56
  * - `undefined` if no listener could be safely added. This is typically due to the token being cancelled
52
57
  * and indicates any future attempts will also fail.
53
58
  */
54
- add(...listeners: (CancellableLike | CancellationListener)[]): Subscription | undefined;
59
+ add(listener: CancellableLike<CancellationListener>, options?: AddCancellableOptions): Subscription | undefined;
55
60
  use<X extends CancellableLike>(target: X): X;
56
61
  /**
57
62
  * Attempts to add a listener to this token.
@@ -65,6 +70,7 @@ export declare abstract class Token {
65
70
  */
66
71
  get signal(): AbortSignal;
67
72
  throwIfCancelled(): void;
73
+ filter(filter: ErrorFilter): Token;
68
74
  static createController(this: void, options?: Token.CreateParams): TokenController;
69
75
  static create(this: void, options: Token.CreateParams): Token;
70
76
  /** A token that will never be cancelled. */
@@ -91,10 +97,10 @@ export declare namespace Token {
91
97
  */
92
98
  onDefuse?: ((this: void) => void) | undefined;
93
99
  /**
94
- * Called on {@link CancellationController.cancel}. If a promise-like is returned, no listeners will be
100
+ * Called on {@link TokenController#cancel}. If a promise-like is returned, no listeners will be
95
101
  * notified of the cancellation until after the promise-like resolves.
96
102
  *
97
- * If this callback throws or rejects, the {@link CancellationController.cancel} promise will also
103
+ * If this callback throws or rejects, the {@link TokenController#cancel} promise will also
98
104
  * reject after all cancellation listeners and the {@link onBeforeCancel} promise is settled
99
105
  * if present.
100
106
  */
@@ -102,15 +108,22 @@ export declare namespace Token {
102
108
  /**
103
109
  * Called after all promises returned by cancellation listeners have either resolved or rejected.
104
110
  *
105
- * If this callback exists and returns a promise-like, the {@link CancellationController.cancel} promise
111
+ * If this callback exists and returns a promise-like, the {@link TokenController#cancel} promise
106
112
  * will not resolve until after this promise-like is settled.
107
113
  *
108
- * If this callback throws or rejects, the {@link CancellationController.cancel} promise will
114
+ * If this callback throws or rejects, the {@link TokenController#cancel} promise will
109
115
  * also reject.
110
116
  */
111
117
  onAfterCancel?: ((this: void, error: Error) => Awaitable<void>) | undefined;
112
118
  }
113
119
  interface CreateParams extends SubscriptionLifecycle<[ctrl: TokenController], [ctrl: TokenController]>, Callbacks {
120
+ filter?: ErrorFilter;
121
+ /**
122
+ * If `true`, then when all parent tokens are defused, the token will be defused.
123
+ *
124
+ * @default false
125
+ */
126
+ sealed?: boolean | undefined;
114
127
  }
115
128
  interface FromAbortSignalParams extends Callbacks {
116
129
  /**
@@ -1,15 +1,16 @@
1
- import { CancellationError, toError } from '../error.js';
1
+ import { CancellationTokenBase } from '@youngspe/async-scope-common';
2
+ import { CancellationError, toErrorForCancellation } from '../error.js';
2
3
  import * as Symbols from '../symbols.js';
3
- import { isArray, isIterable } from '../utils.js';
4
4
  import { Subscription } from '../events/sub.js';
5
5
  import { CancelEvent } from '../token.js';
6
- export class Token {
6
+ import { isArray, isIterable } from '@youngspe/common-async-utils';
7
+ export class Token extends CancellationTokenBase {
7
8
  /** If `true`, this token has been _defused_, meaning it is guaranteed never to be cancelled. */
8
9
  get isDefused() {
9
10
  return false;
10
11
  }
11
12
  /**
12
- * If `true`, this token has been _cancelled_, and trigger any additional listeners.
13
+ * If `true`, this token has been _cancelled_, and will not trigger any additional listeners.
13
14
  *
14
15
  * This is equivalent to `token.error !== undefined`
15
16
  *
@@ -21,42 +22,47 @@ export class Token {
21
22
  /**
22
23
  * Adds listeners
23
24
  *
24
- * @param listeners - listeners that should be added to this token.
25
- * Each parameter may be:
26
- * - A {@link CancellableLike}:
27
- * - A {@link Cancellable}
28
- * - A {@link Disposable}
29
- * - An {@link AsyncDisposable}
30
- * - A falsy value, which will be ignored
31
- * - A (possibly nested) array of any of the above.
25
+ * @param listener - a listener that should be added to this token.
26
+ * This may be:
27
+ * - A {@link Cancellable}
28
+ * - A {@link Disposable}
29
+ * - An {@link AsyncDisposable}
30
+ * - A falsy value, which will be ignored
32
31
  * - A function that receives an {@link Error} and optionally returns a promise.
32
+ * - A (possibly nested) array of any of the above.
33
33
  *
34
34
  * @returns
35
35
  * - A {@link Subscription} that may be used to pause, resume, or remove the provide listeners if either the listeners were safely added or the token is defused.
36
36
  * - `undefined` if no listener could be safely added. This is typically due to the token being cancelled
37
37
  * and indicates any future attempts will also fail.
38
38
  */
39
- add(...listeners) {
39
+ add(listener, options) {
40
40
  if (this.isCancelled)
41
41
  return undefined;
42
42
  if (this.isDefused)
43
43
  return Subscription.noop;
44
44
  const subs = [];
45
- for (let listener of listeners) {
45
+ const inner = (listener) => {
46
46
  if (!listener)
47
- continue;
47
+ return true;
48
48
  if (isArray(listener)) {
49
- this.add(...listener);
50
- continue;
49
+ for (const child of listener) {
50
+ if (!inner(child))
51
+ return false;
52
+ }
53
+ return true;
51
54
  }
52
55
  if (typeof listener === 'function') {
53
56
  listener = { cancel: listener };
54
57
  }
55
- const sub = this.addOne(listener);
58
+ const sub = this.addOne(listener, options);
56
59
  if (!sub)
57
- return undefined;
60
+ return false;
58
61
  subs.push(sub);
59
- }
62
+ return true;
63
+ };
64
+ if (!inner(listener))
65
+ return undefined;
60
66
  return Subscription.collect(subs);
61
67
  }
62
68
  use(target) {
@@ -106,6 +112,11 @@ export class Token {
106
112
  if (error)
107
113
  throw error;
108
114
  }
115
+ filter(filter) {
116
+ const ctrl = Token.createController({ filter, sealed: true });
117
+ this.add(ctrl, { passive: true });
118
+ return ctrl.token;
119
+ }
109
120
  static createController(options) {
110
121
  return CancelEvent.createController(options);
111
122
  }
@@ -118,7 +129,7 @@ export class Token {
118
129
  }
119
130
  /** @returns a token that has already been cancelled. */
120
131
  static cancelled(reason = new CancellationError()) {
121
- return new CancelledToken(toError(reason));
132
+ return new CancelledToken(toErrorForCancellation(reason));
122
133
  }
123
134
  /**
124
135
  *
@@ -142,8 +153,9 @@ export class Token {
142
153
  }
143
154
  if (tokens.size > 1) {
144
155
  return Token.create({
156
+ sealed: true,
145
157
  init: ctrl => {
146
- const sub = Subscription.collect(Array.from(tokens, t => t.add(ctrl)));
158
+ const sub = Subscription.collect(Array.from(tokens, t => t.add(ctrl, { passive: true })));
147
159
  return {
148
160
  resume: () => {
149
161
  sub.resume();
@@ -162,7 +174,7 @@ export class Token {
162
174
  /** @returns a {@link Token} that is cancelled when `signal` is aborted. */
163
175
  static fromAbortSignal(signal, options) {
164
176
  if (signal.aborted)
165
- return new CancelledToken(toError(signal.reason));
177
+ return new CancelledToken(toErrorForCancellation(signal.reason));
166
178
  const { onError, ...callbacks } = options ?? {};
167
179
  return Token.create({
168
180
  ...callbacks,
@@ -180,6 +192,7 @@ export class Token {
180
192
  static from(src) {
181
193
  const tokens = new Set();
182
194
  const signals = new Set();
195
+ const visited = new Set();
183
196
  const flatten = (src) => {
184
197
  if (!src)
185
198
  return undefined;
@@ -205,6 +218,9 @@ export class Token {
205
218
  }
206
219
  return undefined;
207
220
  }
221
+ if (visited.has(src))
222
+ return undefined;
223
+ visited.add(src);
208
224
  return flatten(src.scope) || flatten(src.token) || flatten(src.signal);
209
225
  };
210
226
  const cancelled = flatten(src);
package/dist/token.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Token, STATIC_TOKEN } from './token/base.ts';
2
2
  export { CancelEvent } from './events/cancel.ts';
3
- export type { TokenController } from './token/base.ts';
3
+ export type { TokenController, AddCancellableOptions, ErrorFilter } from './token/base.ts';
4
4
  //# sourceMappingURL=token.d.ts.map
package/dist/types.d.ts CHANGED
@@ -1,17 +1,6 @@
1
- /**
2
- * A value that yields `T` when awaited. Useful for functions that take either the value itself or a
3
- * promise that yields it.
4
- *
5
- * @template T The type of the value returned by `value` when awaited.
6
- *
7
- * @example
8
- * function asyncMultiply(lhs: Awaitable<number>, rhs: Awaitable<number>) {
9
- * const [l, r] = await Promise.all([lhs, rhs]);
10
- * return l * r;
11
- * }
12
- *
13
- */
14
- export type Awaitable<T> = T | Promise<T> | PromiseLike<T>;
1
+ import type { OptionalUndefinedProps } from '@youngspe/common-async-utils';
2
+ export type { Awaitable } from '@youngspe/async-scope-common';
3
+ export type { OptionalUndefinedParams, OptionalUndefinedProps } from '@youngspe/common-async-utils';
15
4
  /**
16
5
  * A value that's treated like `false` in conditional operations like `if` or `&&`
17
6
  *
@@ -43,75 +32,21 @@ export type IfExactOptionalPropertiesEnabled<Then, Else> = {
43
32
  } extends {
44
33
  x?: never;
45
34
  } ? Else : Then;
46
- /** Evalutes to `Then` if `T` is `never`, otherwise `Else`. */
47
- type IfNever<T, Then, Else> = (T extends any ? Else : never) | (Then & ([T] extends [never] ? unknown : never));
48
- /**
49
- * Evaluates to `never` if `T` is `never`, otherwise `U`.
50
- *
51
- * @example
52
- *
53
- * // This type may or may not contain an error value of type `E`:
54
- * type State<E> = Ready | Failed<E>;
55
- * type Ready = { status: 'ready', value: number }
56
- * // When `E` is `never`, `Failed<E>` is also never thanks to `OrNever<E>`:
57
- * type Failed<E> = { status: 'failed'; error: E } & OrNever<E>;
58
- *
59
- * // This means when `E` is `never`, the type `State<never>` evaluates to
60
- * // `Ready` to correctly reflect that it can never be in the `Failed` state.
61
- *
62
- * // Given a function with this signature:
63
- * function createState<E>(): State<E> {
64
- * return { value: 0 }
65
- * }
66
- *
67
- * // The following operation is valid because we have specified there will
68
- * // never be an error:
69
- * const { value } = createState<never>();
70
- */
71
- export type OrNever<T, U = unknown> = IfNever<T, never, U>;
72
- /** Evaluates to `T`, unless `T` is never, in which case it evaluates to `Else`. */
73
- export type UnlessNeverElse<T, Else> = [T] extends [never] ? Else : T;
74
- type _OptionalUndefinedParams<A extends any[], R extends any[]> = {
75
- [K in keyof R]: undefined;
76
- } extends R ? A extends [...infer L, ...R] ? [
77
- ...L,
78
- ...Partial<R>
79
- ] : A : R extends [any, ...infer Rest] ? _OptionalUndefinedParams<A, Rest> : A;
80
- export type OptionalUndefinedParams<A extends any[]> = _OptionalUndefinedParams<A, A>;
81
- type SimplifyObject<T> = {
82
- [K in keyof T]: T[K];
35
+ export type PartialOrUndefined<T> = IfExactOptionalPropertiesEnabled<{
36
+ [P in keyof T]?: T[P] | undefined;
37
+ }, Partial<T>>;
38
+ type EraseProps<T> = {
39
+ [K in keyof T]: any;
83
40
  };
84
- /**
85
- * Makes properties of `T` that may be `undefined` optional.
86
- * Useful for a params/options object for a function when a value might not be required depending
87
- * on the type parameters.
88
- *
89
- * @example
90
- *
91
- * interface MyParams<T, U> {
92
- * items: T[]
93
- * // This property may be undefined if a `T` is already a valid `U`, but
94
- * // as-is you'll still need to include `transform: undefined` in your options.
95
- * transform: ((value: T) => U) | (T extends U ? undefined : never);
96
- * }
97
- *
98
- * function myMap<T, U = T>({
99
- * items,
100
- * transform,
101
- * }: OptionalUndefinedProps<MyParams<T, U>>): U[] {
102
- * return transform ? items.map(transform) : items as (T & U)[]
103
- * }
104
- *
105
- * const strings = myMap({
106
- * items: [1, 2, 3],
107
- * transform: String,
108
- * }); // ['1', '2', '3']
109
- *
110
- * // `transform` is not required because `T` and `U` are both `number`.
111
- * const unchanged = myMap({ items: [1, 2, 3] }); // [1, 2, 3]
112
- */
113
- export type OptionalUndefinedProps<T> = SimplifyObject<Partial<T> & {
114
- [K in keyof T as undefined extends T[K] ? never : K]: T[K];
115
- }>;
116
- export {};
41
+ export type Defined<T = unknown> = NonNullable<T> | (T & null);
42
+ export type UpdateObject<T, U> = T extends Partial<U> ? T & U : U extends never ? never : {
43
+ [K in keyof (EraseProps<T> & EraseProps<OptionalUndefinedProps<U>>)]: K extends keyof U ? Extract<PropertyKey, K> extends never ? Defined<U[K]> | (undefined extends U[K] ? T[K & keyof T] : never) : T[K & keyof T] | U[K & keyof T] : T[K & keyof T];
44
+ };
45
+ export type SetProps<T, U> = T extends Partial<U> ? T & U : U extends never ? never : U & {
46
+ [K in keyof T as Exclude<K, keyof (EraseProps<T> | EraseProps<U>)>]: U extends Record<K, any> ? U[K] : U extends Partial<Record<K, infer X>> ? Extract<X, U[K]> | T[K & keyof T] : T[K & keyof T];
47
+ };
48
+ export type BetterOmit<T, K extends PropertyKey> = T extends never ? never : {
49
+ [_K in keyof T as Exclude<_K, K>]: T[_K];
50
+ };
51
+ export type UndefinedIfDefault<T, Def = object> = T | (Def extends T ? undefined : never);
117
52
  //# sourceMappingURL=types.d.ts.map