@vizejs/composable 0.343.0 → 0.347.7
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/abort-signal.d.mts +96 -0
- package/dist/abort-signal.mjs +203 -0
- package/dist/async-resource.d.mts +97 -0
- package/dist/async-resource.mjs +99 -0
- package/dist/capability-BNpkvSy5.d.mts +93 -0
- package/dist/capability.d.mts +2 -0
- package/dist/capability.mjs +37 -0
- package/dist/catalog-CuWhR-0q.d.mts +506 -0
- package/dist/catalog-DOXTbk4r.mjs +529 -0
- package/dist/catalog.d.mts +2 -0
- package/dist/catalog.mjs +2 -0
- package/dist/disposal-scope.d.mts +93 -0
- package/dist/disposal-scope.mjs +119 -0
- package/dist/event-listener.d.mts +85 -0
- package/dist/event-listener.mjs +55 -0
- package/dist/index.d.mts +19 -786
- package/dist/index.mjs +19 -751
- package/dist/locale.d.mts +65 -0
- package/dist/locale.mjs +72 -0
- package/dist/media-query.d.mts +56 -0
- package/dist/media-query.mjs +56 -0
- package/dist/retry-async.d.mts +91 -0
- package/dist/retry-async.mjs +136 -0
- package/dist/retry-delay.d.mts +73 -0
- package/dist/retry-delay.mjs +50 -0
- package/dist/scope.d.mts +18 -0
- package/dist/scope.mjs +23 -0
- package/dist/timeout-scheduler.d.mts +18 -0
- package/dist/timeout-scheduler.mjs +1 -0
- package/dist/use-counter.d.mts +91 -0
- package/dist/use-counter.mjs +67 -0
- package/dist/use-debounced.d.mts +87 -0
- package/dist/use-debounced.mjs +98 -0
- package/dist/use-history.d.mts +105 -0
- package/dist/use-history.mjs +137 -0
- package/dist/use-previous.d.mts +43 -0
- package/dist/use-previous.mjs +11 -0
- package/dist/use-throttled.d.mts +107 -0
- package/dist/use-throttled.mjs +124 -0
- package/dist/use-toggle.d.mts +45 -0
- package/dist/use-toggle.mjs +34 -0
- package/package.json +91 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { TimeoutScheduler } from "./timeout-scheduler.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/abort-signal.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a signal that aborts when the first input signal aborts.
|
|
6
|
+
*
|
|
7
|
+
* The standard `AbortSignal.any()` implementation is used when available.
|
|
8
|
+
* Older runtimes receive an equivalent listener-based implementation that
|
|
9
|
+
* removes every retained listener as soon as the result aborts. The first
|
|
10
|
+
* already-aborted input wins in iteration order, and its exact reason is
|
|
11
|
+
* forwarded. An empty iterable returns a fresh signal that never aborts.
|
|
12
|
+
*
|
|
13
|
+
* This function reads runtime constructors only when called and is safe to
|
|
14
|
+
* import during server rendering. Materializing the iterable happens before
|
|
15
|
+
* listeners are attached, so an iterable that throws cannot leak a partial
|
|
16
|
+
* subscription. If a non-standard signal throws while registering, listeners
|
|
17
|
+
* already attached to earlier inputs are released before the error propagates.
|
|
18
|
+
*
|
|
19
|
+
* @param signals Abort signals to compose; consumed exactly once.
|
|
20
|
+
* @returns A new first-abort-wins signal.
|
|
21
|
+
*/
|
|
22
|
+
declare function anyAbortSignal(signals: Iterable<AbortSignal>): AbortSignal;
|
|
23
|
+
/** Options for {@link timeoutAbortSignal}. */
|
|
24
|
+
interface TimeoutAbortSignalOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Abort the returned signal early when this parent aborts.
|
|
27
|
+
*
|
|
28
|
+
* @default undefined
|
|
29
|
+
*/
|
|
30
|
+
readonly signal?: AbortSignal;
|
|
31
|
+
/**
|
|
32
|
+
* Reason used when the timeout elapses. Parent cancellation always forwards
|
|
33
|
+
* the parent's reason instead.
|
|
34
|
+
*
|
|
35
|
+
* @default DOMException("The operation timed out.", "TimeoutError")
|
|
36
|
+
*/
|
|
37
|
+
readonly reason?: unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Deterministic or host-specific single-shot timer implementation. Supplying
|
|
40
|
+
* a scheduler selects the compatibility implementation.
|
|
41
|
+
*
|
|
42
|
+
* @default globalThis timer functions
|
|
43
|
+
*/
|
|
44
|
+
readonly scheduler?: TimeoutScheduler;
|
|
45
|
+
}
|
|
46
|
+
/** Options for {@link deadlineAbortSignal}. */
|
|
47
|
+
interface DeadlineAbortSignalOptions extends TimeoutAbortSignalOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Clock returning Unix epoch milliseconds.
|
|
50
|
+
*
|
|
51
|
+
* @default Date.now
|
|
52
|
+
*/
|
|
53
|
+
readonly now?: () => number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a signal that aborts after a portable, non-negative delay.
|
|
57
|
+
*
|
|
58
|
+
* The native `AbortSignal.timeout()` implementation is used when available
|
|
59
|
+
* and neither a custom scheduler nor a custom reason is supplied. Older
|
|
60
|
+
* runtimes use the same owned-timer path as injected schedulers. Parent
|
|
61
|
+
* cancellation is composed with first-reason-wins semantics. Compatibility
|
|
62
|
+
* scheduling owns exactly one timer and removes its parent listener whenever
|
|
63
|
+
* either source aborts. A zero delay remains asynchronous.
|
|
64
|
+
*
|
|
65
|
+
* The delay must be an integer from `0` through `2_147_483_647`; this common
|
|
66
|
+
* signed 32-bit timer ceiling avoids host-specific clamping. The function
|
|
67
|
+
* accesses timers and abort constructors only when called and is safe to
|
|
68
|
+
* import during server rendering.
|
|
69
|
+
*
|
|
70
|
+
* @param delayMs Delay in milliseconds.
|
|
71
|
+
* @param options Parent signal, timeout reason, and scheduler.
|
|
72
|
+
* @default options {}
|
|
73
|
+
* @throws {RangeError} Tagged `VIZE_COMPOSE_ABORT_TIMEOUT_INVALID_DELAY` when
|
|
74
|
+
* the delay is fractional, negative, non-finite, or exceeds the portable
|
|
75
|
+
* timer ceiling.
|
|
76
|
+
* @returns A new timeout or parent-cancelled signal.
|
|
77
|
+
*/
|
|
78
|
+
declare function timeoutAbortSignal(delayMs: number, options?: TimeoutAbortSignalOptions): AbortSignal;
|
|
79
|
+
/**
|
|
80
|
+
* Create a timeout signal from an absolute Unix-epoch deadline.
|
|
81
|
+
*
|
|
82
|
+
* Fractional positive differences are rounded up so cancellation never occurs
|
|
83
|
+
* before the requested deadline. Past deadlines become an asynchronous
|
|
84
|
+
* zero-delay timeout. `Date` and numeric deadlines are both accepted.
|
|
85
|
+
*
|
|
86
|
+
* @param deadline Absolute deadline as a `Date` or Unix epoch milliseconds.
|
|
87
|
+
* @param options Clock, parent signal, timeout reason, and scheduler.
|
|
88
|
+
* @default options {}
|
|
89
|
+
* @throws {RangeError} Tagged `VIZE_COMPOSE_ABORT_DEADLINE_INVALID` when the
|
|
90
|
+
* deadline, current clock value, or their positive difference is non-finite
|
|
91
|
+
* or cannot fit the portable timeout range.
|
|
92
|
+
* @returns A new deadline or parent-cancelled signal.
|
|
93
|
+
*/
|
|
94
|
+
declare function deadlineAbortSignal(deadline: Date | number, options?: DeadlineAbortSignalOptions): AbortSignal;
|
|
95
|
+
//#endregion
|
|
96
|
+
export { DeadlineAbortSignalOptions, TimeoutAbortSignalOptions, anyAbortSignal, deadlineAbortSignal, timeoutAbortSignal };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
//#region src/abort-signal.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create a signal that aborts when the first input signal aborts.
|
|
4
|
+
*
|
|
5
|
+
* The standard `AbortSignal.any()` implementation is used when available.
|
|
6
|
+
* Older runtimes receive an equivalent listener-based implementation that
|
|
7
|
+
* removes every retained listener as soon as the result aborts. The first
|
|
8
|
+
* already-aborted input wins in iteration order, and its exact reason is
|
|
9
|
+
* forwarded. An empty iterable returns a fresh signal that never aborts.
|
|
10
|
+
*
|
|
11
|
+
* This function reads runtime constructors only when called and is safe to
|
|
12
|
+
* import during server rendering. Materializing the iterable happens before
|
|
13
|
+
* listeners are attached, so an iterable that throws cannot leak a partial
|
|
14
|
+
* subscription. If a non-standard signal throws while registering, listeners
|
|
15
|
+
* already attached to earlier inputs are released before the error propagates.
|
|
16
|
+
*
|
|
17
|
+
* @param signals Abort signals to compose; consumed exactly once.
|
|
18
|
+
* @returns A new first-abort-wins signal.
|
|
19
|
+
*/
|
|
20
|
+
function anyAbortSignal(signals) {
|
|
21
|
+
const inputs = [...signals];
|
|
22
|
+
const nativeAny = conformantNativeAbortSignalAny();
|
|
23
|
+
if (nativeAny !== void 0) return nativeAny.call(AbortSignal, inputs);
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const listeners = [];
|
|
26
|
+
const cleanup = () => {
|
|
27
|
+
for (const [signal, listener] of listeners) signal.removeEventListener("abort", listener);
|
|
28
|
+
listeners.length = 0;
|
|
29
|
+
};
|
|
30
|
+
const abortFrom = (signal) => {
|
|
31
|
+
if (controller.signal.aborted) return;
|
|
32
|
+
cleanup();
|
|
33
|
+
controller.abort(signal.reason);
|
|
34
|
+
};
|
|
35
|
+
for (const signal of inputs) {
|
|
36
|
+
if (signal.aborted) {
|
|
37
|
+
abortFrom(signal);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
const listener = () => abortFrom(signal);
|
|
41
|
+
listeners.push([signal, listener]);
|
|
42
|
+
try {
|
|
43
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
44
|
+
} catch (error) {
|
|
45
|
+
cleanup();
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
if (signal.aborted) {
|
|
49
|
+
abortFrom(signal);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return controller.signal;
|
|
54
|
+
}
|
|
55
|
+
let probedNativeAny;
|
|
56
|
+
let cachedNativeAny;
|
|
57
|
+
/** Resolve the current native implementation only after its first-reason contract passes. */
|
|
58
|
+
function conformantNativeAbortSignalAny() {
|
|
59
|
+
let candidate;
|
|
60
|
+
try {
|
|
61
|
+
candidate = Reflect.get(AbortSignal, "any");
|
|
62
|
+
} catch {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (candidate === probedNativeAny) return cachedNativeAny;
|
|
66
|
+
probedNativeAny = candidate;
|
|
67
|
+
cachedNativeAny = typeof candidate === "function" && nativeAnyReasonIsStable(candidate) ? candidate : void 0;
|
|
68
|
+
return cachedNativeAny;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Detect runtimes whose combined reason changes when an earlier array member
|
|
72
|
+
* aborts after the winner. The probe deliberately does not read the reason
|
|
73
|
+
* until both inputs abort, catching lazy native implementations as well.
|
|
74
|
+
*/
|
|
75
|
+
function nativeAnyReasonIsStable(candidate) {
|
|
76
|
+
try {
|
|
77
|
+
const late = new AbortController();
|
|
78
|
+
const winner = new AbortController();
|
|
79
|
+
const expected = {};
|
|
80
|
+
const combined = candidate.call(AbortSignal, [late.signal, winner.signal]);
|
|
81
|
+
winner.abort(expected);
|
|
82
|
+
late.abort({});
|
|
83
|
+
return combined.aborted && combined.reason === expected;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Create a signal that aborts after a portable, non-negative delay.
|
|
90
|
+
*
|
|
91
|
+
* The native `AbortSignal.timeout()` implementation is used when available
|
|
92
|
+
* and neither a custom scheduler nor a custom reason is supplied. Older
|
|
93
|
+
* runtimes use the same owned-timer path as injected schedulers. Parent
|
|
94
|
+
* cancellation is composed with first-reason-wins semantics. Compatibility
|
|
95
|
+
* scheduling owns exactly one timer and removes its parent listener whenever
|
|
96
|
+
* either source aborts. A zero delay remains asynchronous.
|
|
97
|
+
*
|
|
98
|
+
* The delay must be an integer from `0` through `2_147_483_647`; this common
|
|
99
|
+
* signed 32-bit timer ceiling avoids host-specific clamping. The function
|
|
100
|
+
* accesses timers and abort constructors only when called and is safe to
|
|
101
|
+
* import during server rendering.
|
|
102
|
+
*
|
|
103
|
+
* @param delayMs Delay in milliseconds.
|
|
104
|
+
* @param options Parent signal, timeout reason, and scheduler.
|
|
105
|
+
* @default options {}
|
|
106
|
+
* @throws {RangeError} Tagged `VIZE_COMPOSE_ABORT_TIMEOUT_INVALID_DELAY` when
|
|
107
|
+
* the delay is fractional, negative, non-finite, or exceeds the portable
|
|
108
|
+
* timer ceiling.
|
|
109
|
+
* @returns A new timeout or parent-cancelled signal.
|
|
110
|
+
*/
|
|
111
|
+
function timeoutAbortSignal(delayMs, options = {}) {
|
|
112
|
+
assertPortableTimeout(delayMs);
|
|
113
|
+
const parent = options.signal;
|
|
114
|
+
if (parent?.aborted) {
|
|
115
|
+
const controller = new AbortController();
|
|
116
|
+
controller.abort(parent.reason);
|
|
117
|
+
return controller.signal;
|
|
118
|
+
}
|
|
119
|
+
if (options.scheduler === void 0 && options.reason === void 0 && typeof AbortSignal.timeout === "function") {
|
|
120
|
+
const timeout = AbortSignal.timeout(delayMs);
|
|
121
|
+
return options.signal === void 0 ? timeout : anyAbortSignal([options.signal, timeout]);
|
|
122
|
+
}
|
|
123
|
+
const controller = new AbortController();
|
|
124
|
+
const scheduler = options.scheduler ?? defaultTimeoutScheduler;
|
|
125
|
+
let handle;
|
|
126
|
+
let timerPending = true;
|
|
127
|
+
let parentListening = false;
|
|
128
|
+
const stopTimer = () => {
|
|
129
|
+
if (!timerPending) return;
|
|
130
|
+
timerPending = false;
|
|
131
|
+
scheduler.clearTimeout(handle);
|
|
132
|
+
handle = void 0;
|
|
133
|
+
};
|
|
134
|
+
const stopParent = () => {
|
|
135
|
+
if (!parentListening || parent === void 0) return;
|
|
136
|
+
parentListening = false;
|
|
137
|
+
parent.removeEventListener("abort", abortFromParent);
|
|
138
|
+
};
|
|
139
|
+
const abort = (reason) => {
|
|
140
|
+
if (controller.signal.aborted) return;
|
|
141
|
+
stopTimer();
|
|
142
|
+
stopParent();
|
|
143
|
+
controller.abort(reason);
|
|
144
|
+
};
|
|
145
|
+
const abortFromParent = () => abort(parent?.reason);
|
|
146
|
+
const timeoutReason = options.reason === void 0 ? new DOMException("The operation timed out.", "TimeoutError") : options.reason;
|
|
147
|
+
handle = scheduler.setTimeout(() => {
|
|
148
|
+
timerPending = false;
|
|
149
|
+
handle = void 0;
|
|
150
|
+
abort(timeoutReason);
|
|
151
|
+
}, delayMs);
|
|
152
|
+
if (controller.signal.aborted || parent === void 0) return controller.signal;
|
|
153
|
+
try {
|
|
154
|
+
parentListening = true;
|
|
155
|
+
parent.addEventListener("abort", abortFromParent, { once: true });
|
|
156
|
+
if (controller.signal.aborted) {
|
|
157
|
+
parent.removeEventListener("abort", abortFromParent);
|
|
158
|
+
parentListening = false;
|
|
159
|
+
return controller.signal;
|
|
160
|
+
}
|
|
161
|
+
if (parent.aborted) abortFromParent();
|
|
162
|
+
} catch (error) {
|
|
163
|
+
stopParent();
|
|
164
|
+
stopTimer();
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
return controller.signal;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create a timeout signal from an absolute Unix-epoch deadline.
|
|
171
|
+
*
|
|
172
|
+
* Fractional positive differences are rounded up so cancellation never occurs
|
|
173
|
+
* before the requested deadline. Past deadlines become an asynchronous
|
|
174
|
+
* zero-delay timeout. `Date` and numeric deadlines are both accepted.
|
|
175
|
+
*
|
|
176
|
+
* @param deadline Absolute deadline as a `Date` or Unix epoch milliseconds.
|
|
177
|
+
* @param options Clock, parent signal, timeout reason, and scheduler.
|
|
178
|
+
* @default options {}
|
|
179
|
+
* @throws {RangeError} Tagged `VIZE_COMPOSE_ABORT_DEADLINE_INVALID` when the
|
|
180
|
+
* deadline, current clock value, or their positive difference is non-finite
|
|
181
|
+
* or cannot fit the portable timeout range.
|
|
182
|
+
* @returns A new deadline or parent-cancelled signal.
|
|
183
|
+
*/
|
|
184
|
+
function deadlineAbortSignal(deadline, options = {}) {
|
|
185
|
+
const deadlineMs = deadline instanceof Date ? deadline.getTime() : deadline;
|
|
186
|
+
const nowMs = (options.now ?? Date.now)();
|
|
187
|
+
const difference = deadlineMs - nowMs;
|
|
188
|
+
if (!Number.isFinite(deadlineMs) || !Number.isFinite(nowMs) || !Number.isFinite(difference) || difference > maximumPortableTimeoutMs) throw new RangeError(`[VIZE_COMPOSE_ABORT_DEADLINE_INVALID] deadline and now must produce a finite delay from 0 through ${String(maximumPortableTimeoutMs)} milliseconds; received deadline=${String(deadlineMs)}, now=${String(nowMs)}`);
|
|
189
|
+
const { now: _now, ...timeoutOptions } = options;
|
|
190
|
+
return timeoutAbortSignal(Math.ceil(Math.max(0, difference)), timeoutOptions);
|
|
191
|
+
}
|
|
192
|
+
function assertPortableTimeout(delayMs) {
|
|
193
|
+
if (!Number.isSafeInteger(delayMs) || delayMs < 0 || delayMs > maximumPortableTimeoutMs) throw new RangeError(`[VIZE_COMPOSE_ABORT_TIMEOUT_INVALID_DELAY] delayMs must be an integer from 0 through ${String(maximumPortableTimeoutMs)}; received ${String(delayMs)}`);
|
|
194
|
+
}
|
|
195
|
+
const maximumPortableTimeoutMs = 2147483647;
|
|
196
|
+
const defaultTimeoutScheduler = {
|
|
197
|
+
setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
|
|
198
|
+
clearTimeout: (handle) => {
|
|
199
|
+
globalThis.clearTimeout(handle);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
//#endregion
|
|
203
|
+
export { anyAbortSignal, deadlineAbortSignal, timeoutAbortSignal };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ComputedRef, Ref, ShallowRef } from "vue";
|
|
2
|
+
|
|
3
|
+
//#region src/async-resource.d.ts
|
|
4
|
+
/** Lifecycle state of an asynchronous resource. */
|
|
5
|
+
type AsyncResourceStatus = "idle" | "pending" | "success" | "error" | "cancelled";
|
|
6
|
+
/** Context supplied to an asynchronous resource loader. */
|
|
7
|
+
interface AsyncResourceContext {
|
|
8
|
+
/** Signal aborted by cancellation, reset, scope disposal, or a newer execution. */
|
|
9
|
+
readonly signal: AbortSignal;
|
|
10
|
+
}
|
|
11
|
+
/** Explicit result of one asynchronous resource execution. */
|
|
12
|
+
type AsyncResourceExecution<Data, Failure> = {
|
|
13
|
+
readonly status: "success";
|
|
14
|
+
readonly data: Data;
|
|
15
|
+
} | {
|
|
16
|
+
readonly status: "error";
|
|
17
|
+
readonly error: Failure;
|
|
18
|
+
} | {
|
|
19
|
+
readonly status: "cancelled";
|
|
20
|
+
readonly reason: unknown;
|
|
21
|
+
} | {
|
|
22
|
+
readonly status: "superseded";
|
|
23
|
+
};
|
|
24
|
+
/** Options for {@link useAsyncResource}. */
|
|
25
|
+
interface UseAsyncResourceOptions<Data> {
|
|
26
|
+
/**
|
|
27
|
+
* Initial data restored by {@link AsyncResource.reset}.
|
|
28
|
+
*
|
|
29
|
+
* @default undefined
|
|
30
|
+
*/
|
|
31
|
+
readonly initialData?: Data;
|
|
32
|
+
/**
|
|
33
|
+
* Abort the active execution when a newer execution starts.
|
|
34
|
+
*
|
|
35
|
+
* @default true
|
|
36
|
+
*/
|
|
37
|
+
readonly cancelPrevious?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Retain the current data while a new execution is pending.
|
|
40
|
+
*
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
readonly keepData?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Cancel an active execution when the current reactive scope is disposed.
|
|
46
|
+
*
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
readonly scope?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Reactive state and controls for an asynchronous loader. */
|
|
52
|
+
interface AsyncResource<Data, Arguments extends readonly unknown[], Failure> {
|
|
53
|
+
/** Data of the newest successful execution, retained according to `keepData`. */
|
|
54
|
+
readonly data: Readonly<ShallowRef<Data | undefined>>;
|
|
55
|
+
/** Failure of the newest settled execution, cleared when a new one starts. */
|
|
56
|
+
readonly error: Readonly<ShallowRef<Failure | undefined>>;
|
|
57
|
+
/** Current lifecycle status, driven only by the newest execution. */
|
|
58
|
+
readonly status: Readonly<Ref<AsyncResourceStatus>>;
|
|
59
|
+
/** Whether an execution is currently pending. */
|
|
60
|
+
readonly pending: ComputedRef<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Run the loader. The returned promise never rejects: loader failures,
|
|
63
|
+
* cancellation, and supersession are reported as the discriminated result,
|
|
64
|
+
* and stale executions leave the reactive state untouched.
|
|
65
|
+
*/
|
|
66
|
+
readonly execute: (...arguments_: Arguments) => Promise<AsyncResourceExecution<Data, Failure>>;
|
|
67
|
+
/**
|
|
68
|
+
* Abort the active execution and mark the resource cancelled.
|
|
69
|
+
*
|
|
70
|
+
* @param reason Abort reason forwarded to the loader's signal.
|
|
71
|
+
* @default reason DOMException("AbortError")
|
|
72
|
+
* @returns Whether an active execution was cancelled.
|
|
73
|
+
*/
|
|
74
|
+
readonly cancel: (reason?: unknown) => boolean;
|
|
75
|
+
/** Cancel any active execution and restore the initial idle state. */
|
|
76
|
+
readonly reset: () => void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a scoped, abortable asynchronous resource with latest-result-wins
|
|
80
|
+
* state. Every execution returns a discriminated result, so cancellation,
|
|
81
|
+
* supersession, loader failure, and successful `undefined` data stay distinct.
|
|
82
|
+
*
|
|
83
|
+
* When created inside an active reactive scope (and `scope` is enabled), the
|
|
84
|
+
* active execution is aborted when that scope stops; outside a scope,
|
|
85
|
+
* cancellation ownership stays with the caller. The execute promise never
|
|
86
|
+
* rejects — synchronous and asynchronous loader failures both settle into
|
|
87
|
+
* the `"error"` result. Safe during server rendering: no browser globals are
|
|
88
|
+
* read and abort reasons use the runtime-native `DOMException`.
|
|
89
|
+
*
|
|
90
|
+
* @param loader Asynchronous loader receiving the abort context first.
|
|
91
|
+
* @param options Data retention, supersession, and scope behavior.
|
|
92
|
+
* @default options {}
|
|
93
|
+
* @returns Reactive state and controls for the loader.
|
|
94
|
+
*/
|
|
95
|
+
declare function useAsyncResource<Data, Arguments extends readonly unknown[], Failure = unknown>(loader: (context: AsyncResourceContext, ...arguments_: Arguments) => Promise<Data>, options?: UseAsyncResourceOptions<Data>): AsyncResource<Data, Arguments, Failure>;
|
|
96
|
+
//#endregion
|
|
97
|
+
export { AsyncResource, AsyncResourceContext, AsyncResourceExecution, AsyncResourceStatus, UseAsyncResourceOptions, useAsyncResource };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { tryOnScopeDispose } from "./scope.mjs";
|
|
2
|
+
import { computed, shallowRef } from "vue";
|
|
3
|
+
//#region src/async-resource.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create a scoped, abortable asynchronous resource with latest-result-wins
|
|
6
|
+
* state. Every execution returns a discriminated result, so cancellation,
|
|
7
|
+
* supersession, loader failure, and successful `undefined` data stay distinct.
|
|
8
|
+
*
|
|
9
|
+
* When created inside an active reactive scope (and `scope` is enabled), the
|
|
10
|
+
* active execution is aborted when that scope stops; outside a scope,
|
|
11
|
+
* cancellation ownership stays with the caller. The execute promise never
|
|
12
|
+
* rejects — synchronous and asynchronous loader failures both settle into
|
|
13
|
+
* the `"error"` result. Safe during server rendering: no browser globals are
|
|
14
|
+
* read and abort reasons use the runtime-native `DOMException`.
|
|
15
|
+
*
|
|
16
|
+
* @param loader Asynchronous loader receiving the abort context first.
|
|
17
|
+
* @param options Data retention, supersession, and scope behavior.
|
|
18
|
+
* @default options {}
|
|
19
|
+
* @returns Reactive state and controls for the loader.
|
|
20
|
+
*/
|
|
21
|
+
function useAsyncResource(loader, options = {}) {
|
|
22
|
+
const data = shallowRef(options.initialData);
|
|
23
|
+
const error = shallowRef(void 0);
|
|
24
|
+
const status = shallowRef("idle");
|
|
25
|
+
const pending = computed(() => status.value === "pending");
|
|
26
|
+
let generation = 0;
|
|
27
|
+
let active;
|
|
28
|
+
const cancel = (reason = createAbortReason("The execution was cancelled.")) => {
|
|
29
|
+
if (active === void 0) return false;
|
|
30
|
+
generation += 1;
|
|
31
|
+
active.controller.abort(reason);
|
|
32
|
+
active = void 0;
|
|
33
|
+
status.value = "cancelled";
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
const execute = async (...arguments_) => {
|
|
37
|
+
if ((options.cancelPrevious ?? true) && active !== void 0) {
|
|
38
|
+
active.superseded = true;
|
|
39
|
+
active.controller.abort(createAbortReason("A newer execution started."));
|
|
40
|
+
}
|
|
41
|
+
const record = {
|
|
42
|
+
generation: ++generation,
|
|
43
|
+
controller: new AbortController(),
|
|
44
|
+
superseded: false
|
|
45
|
+
};
|
|
46
|
+
active = record;
|
|
47
|
+
error.value = void 0;
|
|
48
|
+
status.value = "pending";
|
|
49
|
+
if (!(options.keepData ?? true)) data.value = void 0;
|
|
50
|
+
try {
|
|
51
|
+
const result = await loader({ signal: record.controller.signal }, ...arguments_);
|
|
52
|
+
if (record.generation !== generation) return executionAfterInvalidation(record);
|
|
53
|
+
data.value = result;
|
|
54
|
+
status.value = "success";
|
|
55
|
+
return {
|
|
56
|
+
status: "success",
|
|
57
|
+
data: result
|
|
58
|
+
};
|
|
59
|
+
} catch (cause) {
|
|
60
|
+
if (record.generation !== generation || record.controller.signal.aborted) return executionAfterInvalidation(record);
|
|
61
|
+
error.value = cause;
|
|
62
|
+
status.value = "error";
|
|
63
|
+
return {
|
|
64
|
+
status: "error",
|
|
65
|
+
error: cause
|
|
66
|
+
};
|
|
67
|
+
} finally {
|
|
68
|
+
if (active === record) active = void 0;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const reset = () => {
|
|
72
|
+
cancel(createAbortReason("The resource was reset."));
|
|
73
|
+
data.value = options.initialData;
|
|
74
|
+
error.value = void 0;
|
|
75
|
+
status.value = "idle";
|
|
76
|
+
};
|
|
77
|
+
if (options.scope ?? true) tryOnScopeDispose(() => cancel(createAbortReason("The reactive scope was disposed.")));
|
|
78
|
+
return {
|
|
79
|
+
data,
|
|
80
|
+
error,
|
|
81
|
+
status,
|
|
82
|
+
pending,
|
|
83
|
+
execute,
|
|
84
|
+
cancel,
|
|
85
|
+
reset
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function executionAfterInvalidation(execution) {
|
|
89
|
+
if (execution.superseded || !execution.controller.signal.aborted) return { status: "superseded" };
|
|
90
|
+
return {
|
|
91
|
+
status: "cancelled",
|
|
92
|
+
reason: execution.controller.signal.reason
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function createAbortReason(message) {
|
|
96
|
+
return new DOMException(message, "AbortError");
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { useAsyncResource };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//#region src/capability.d.ts
|
|
2
|
+
/** Runtime families understood by composable capability metadata and adapters. */
|
|
3
|
+
type CapabilityTarget = "web" | "server" | "worker" | "native" | "desktop" | "terminal";
|
|
4
|
+
/** Built-in origins from which a capability value can be resolved. */
|
|
5
|
+
type CapabilitySource = "runtime" | "adapter" | "fallback";
|
|
6
|
+
/**
|
|
7
|
+
* Standard reasons why a capability cannot currently provide a value.
|
|
8
|
+
*
|
|
9
|
+
* Adapters may extend this vocabulary with a narrower string-literal union;
|
|
10
|
+
* consumers should preserve unknown extension reasons when crossing process
|
|
11
|
+
* or version boundaries.
|
|
12
|
+
*/
|
|
13
|
+
type CapabilityUnavailableReason = "unsupported" | "unavailable" | "permission-denied" | "insecure-context" | "not-active" | "not-ready" | "cancelled" | "adapter-missing" | "adapter-error";
|
|
14
|
+
/** A capability value that is ready for use. */
|
|
15
|
+
interface AvailableCapability<Value, Source extends string = CapabilitySource> {
|
|
16
|
+
/** Stable discriminant for serialization and exhaustive matching. */
|
|
17
|
+
readonly status: "available";
|
|
18
|
+
/** Boolean discriminant for ergonomic guards in templates and JavaScript. */
|
|
19
|
+
readonly available: true;
|
|
20
|
+
/** Resolved capability implementation or value. */
|
|
21
|
+
readonly value: Value;
|
|
22
|
+
/** Origin that supplied the value, preserved as a string literal. */
|
|
23
|
+
readonly source: Source;
|
|
24
|
+
}
|
|
25
|
+
/** A capability that cannot currently provide a value. */
|
|
26
|
+
interface UnavailableCapability<Reason extends string = CapabilityUnavailableReason, Details = undefined> {
|
|
27
|
+
/** Stable discriminant for serialization and exhaustive matching. */
|
|
28
|
+
readonly status: "unavailable";
|
|
29
|
+
/** Boolean discriminant for ergonomic guards in templates and JavaScript. */
|
|
30
|
+
readonly available: false;
|
|
31
|
+
/** Typed, machine-readable explanation of the unavailable state. */
|
|
32
|
+
readonly reason: Reason;
|
|
33
|
+
/** Structured diagnostic or recovery information supplied by the adapter. */
|
|
34
|
+
readonly details: Details;
|
|
35
|
+
}
|
|
36
|
+
/** Explicit result of capability discovery or adapter negotiation. */
|
|
37
|
+
type CapabilityResult<Value, Reason extends string = CapabilityUnavailableReason, Details = undefined, Source extends string = CapabilitySource> = AvailableCapability<Value, Source> | UnavailableCapability<Reason, Details>;
|
|
38
|
+
/**
|
|
39
|
+
* Create an available capability supplied directly by the current runtime.
|
|
40
|
+
*
|
|
41
|
+
* The value and source retain literal types. The helper performs no global
|
|
42
|
+
* access and is safe during server rendering and module evaluation.
|
|
43
|
+
*
|
|
44
|
+
* @param value Resolved capability implementation or value.
|
|
45
|
+
* @returns An available result whose source is `"runtime"`.
|
|
46
|
+
*/
|
|
47
|
+
declare function availableCapability<const Value>(value: Value): AvailableCapability<Value, "runtime">;
|
|
48
|
+
/**
|
|
49
|
+
* Create an available capability with an explicit, literal-preserving source.
|
|
50
|
+
*
|
|
51
|
+
* @param value Resolved capability implementation or value.
|
|
52
|
+
* @param source Runtime, adapter, fallback, or adapter-specific source name.
|
|
53
|
+
* @returns An available capability result.
|
|
54
|
+
*/
|
|
55
|
+
declare function availableCapability<const Value, const Source extends string>(value: Value, source: Source): AvailableCapability<Value, Source>;
|
|
56
|
+
/**
|
|
57
|
+
* Create an unavailable capability without additional details.
|
|
58
|
+
*
|
|
59
|
+
* The reason retains its string-literal type. Capability absence is returned
|
|
60
|
+
* as data rather than thrown, making unsupported server, worker, native,
|
|
61
|
+
* desktop, and terminal environments deterministic.
|
|
62
|
+
*
|
|
63
|
+
* @param reason Machine-readable unavailability reason.
|
|
64
|
+
* @returns An unavailable result with `undefined` details.
|
|
65
|
+
*/
|
|
66
|
+
declare function unavailableCapability<const Reason extends string>(reason: Reason): UnavailableCapability<Reason, undefined>;
|
|
67
|
+
/**
|
|
68
|
+
* Create an unavailable capability with typed recovery or diagnostic details.
|
|
69
|
+
*
|
|
70
|
+
* Passing `undefined` explicitly is distinct at the call boundary and still
|
|
71
|
+
* preserves the exact `undefined` details type.
|
|
72
|
+
*
|
|
73
|
+
* @param reason Machine-readable unavailability reason.
|
|
74
|
+
* @param details Structured diagnostic or recovery information.
|
|
75
|
+
* @returns An unavailable capability result.
|
|
76
|
+
*/
|
|
77
|
+
declare function unavailableCapability<const Reason extends string, const Details>(reason: Reason, details: Details): UnavailableCapability<Reason, Details>;
|
|
78
|
+
/**
|
|
79
|
+
* Narrow a capability result to its available branch.
|
|
80
|
+
*
|
|
81
|
+
* @param result Capability discovery or negotiation result.
|
|
82
|
+
* @returns Whether `result.value` is ready for use.
|
|
83
|
+
*/
|
|
84
|
+
declare function isCapabilityAvailable<Value, Reason extends string, Details, Source extends string>(result: CapabilityResult<Value, Reason, Details, Source>): result is AvailableCapability<Value, Source>;
|
|
85
|
+
/**
|
|
86
|
+
* Narrow a capability result to its unavailable branch.
|
|
87
|
+
*
|
|
88
|
+
* @param result Capability discovery or negotiation result.
|
|
89
|
+
* @returns Whether the capability has a typed unavailability reason.
|
|
90
|
+
*/
|
|
91
|
+
declare function isCapabilityUnavailable<Value, Reason extends string, Details, Source extends string>(result: CapabilityResult<Value, Reason, Details, Source>): result is UnavailableCapability<Reason, Details>;
|
|
92
|
+
//#endregion
|
|
93
|
+
export { CapabilityUnavailableReason as a, isCapabilityAvailable as c, CapabilityTarget as i, isCapabilityUnavailable as l, CapabilityResult as n, UnavailableCapability as o, CapabilitySource as r, availableCapability as s, AvailableCapability as t, unavailableCapability as u };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as CapabilityUnavailableReason, c as isCapabilityAvailable, i as CapabilityTarget, l as isCapabilityUnavailable, n as CapabilityResult, o as UnavailableCapability, r as CapabilitySource, s as availableCapability, t as AvailableCapability, u as unavailableCapability } from "./capability-BNpkvSy5.mjs";
|
|
2
|
+
export { AvailableCapability, CapabilityResult, CapabilitySource, CapabilityTarget, CapabilityUnavailableReason, UnavailableCapability, availableCapability, isCapabilityAvailable, isCapabilityUnavailable, unavailableCapability };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/capability.ts
|
|
2
|
+
function availableCapability(value, source) {
|
|
3
|
+
return {
|
|
4
|
+
status: "available",
|
|
5
|
+
available: true,
|
|
6
|
+
value,
|
|
7
|
+
source: source ?? "runtime"
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function unavailableCapability(reason, details) {
|
|
11
|
+
return {
|
|
12
|
+
status: "unavailable",
|
|
13
|
+
available: false,
|
|
14
|
+
reason,
|
|
15
|
+
details
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Narrow a capability result to its available branch.
|
|
20
|
+
*
|
|
21
|
+
* @param result Capability discovery or negotiation result.
|
|
22
|
+
* @returns Whether `result.value` is ready for use.
|
|
23
|
+
*/
|
|
24
|
+
function isCapabilityAvailable(result) {
|
|
25
|
+
return result.available;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Narrow a capability result to its unavailable branch.
|
|
29
|
+
*
|
|
30
|
+
* @param result Capability discovery or negotiation result.
|
|
31
|
+
* @returns Whether the capability has a typed unavailability reason.
|
|
32
|
+
*/
|
|
33
|
+
function isCapabilityUnavailable(result) {
|
|
34
|
+
return !result.available;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { availableCapability, isCapabilityAvailable, isCapabilityUnavailable, unavailableCapability };
|