@native-router/core 1.4.0 → 1.5.0
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/README.md +1 -1
- package/dist/index.cjs +82 -21
- package/dist/index.mjs +84 -23
- package/dist/types/router.d.ts +22 -5
- package/dist/types/types.d.ts +60 -17
- package/package.json +28 -28
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ commit(router, entry.task, entry.location); // commit like a click
|
|
|
57
57
|
- Framework-agnostic: bring your own `resolveView`, the view type (`V`) is yours — a string, a vdom, anything
|
|
58
58
|
- Route matching via path-to-regexp: declaration order, layout routes without `path`, index/fallback children with `path: ''`, strict trailing slashes, case-sensitive, nested params merged deep over shallow
|
|
59
59
|
- Route guards: static `redirect` and async `beforeLoad` on every route level, run shallow → deep; more than 10 chained redirects reject with `RedirectLoopError`
|
|
60
|
-
- Cancelable async navigation: a new resolve supersedes the in-flight one (`currentGuard`); `cancel()` aborts it; a history POP cancels it too. A superseded or cancelled `navigate()` promise **never settles** — don't `await` a navigation that might be superseded
|
|
60
|
+
- Cancelable async navigation: a new resolve supersedes the in-flight one (`currentGuard`); `cancel()` aborts it; a history POP cancels it too. A superseded or cancelled `navigate()` promise **never settles** — don't `await` a navigation that might be superseded. Superseding or cancelling also aborts the chain's `AbortSignal`: guards (`beforeLoad` ctx) and view loaders (`ResolveViewContext`) receive it as `ctx.signal`, so their in-flight requests stop instead of only having results dropped; `preload` resolutions are shared and therefore never aborted
|
|
61
61
|
- Navigation API: `navigate`, `refresh`, `go`/`forward`/`back`, `commit`/`commitReplace`, `createHref`, `getParams`, `match`, `toLocation`, `resolve`, `resolveTo`
|
|
62
62
|
- Search validation via [Standard Schema](https://standardschema.dev): a `search` schema on any route level (zod/valibot/arktype, no hard dependency), parsed with `parseSearch`/`parseSearchSync`; failures throw `SearchError`
|
|
63
63
|
- `preload(router, to, {ttl})`: resolve a target through the guards ahead of time, sharing one task across concurrent callers (in-flight dedup) with a TTL, default 30s; consumed entries are dropped on commit
|
package/dist/index.cjs
CHANGED
|
@@ -26,9 +26,10 @@ class RedirectLoopError extends NativeRouterError {
|
|
|
26
26
|
*/
|
|
27
27
|
class SearchError extends NativeRouterError {
|
|
28
28
|
/** The raw search string that failed validation. */
|
|
29
|
+
search;
|
|
29
30
|
|
|
30
31
|
/** The issues reported by the schema. */
|
|
31
|
-
|
|
32
|
+
issues;
|
|
32
33
|
constructor(search, issues) {
|
|
33
34
|
super(`Invalid search params "${search}": ${issues.map(({
|
|
34
35
|
message,
|
|
@@ -74,6 +75,10 @@ const DEFAULT_PRELOAD_TTL = 30_000;
|
|
|
74
75
|
* `history index - baseIndex`; entries whose slot falls outside the
|
|
75
76
|
* memory window re-resolve lazily when landed on.
|
|
76
77
|
* - `preloadCache`: router-level cache of {@link preload} results.
|
|
78
|
+
* - `resolvingController`: the in-flight chain's AbortController. It is
|
|
79
|
+
* aborted(supersede/cancel) only while the chain is in flight; a
|
|
80
|
+
* settled chain's controller is left alone so its contexts never
|
|
81
|
+
* report `aborted` for a navigation that actually committed.
|
|
77
82
|
*/
|
|
78
83
|
|
|
79
84
|
/**
|
|
@@ -210,7 +215,7 @@ function match(router, pathname) {
|
|
|
210
215
|
const route = routes[i];
|
|
211
216
|
const end = !route.children;
|
|
212
217
|
const matched = route.path ? pathToRegexp.match(route.path, {
|
|
213
|
-
|
|
218
|
+
trailing: false,
|
|
214
219
|
sensitive: true,
|
|
215
220
|
decode: typeof decodeURIComponent === 'function' ? decodeURIComponent : undefined,
|
|
216
221
|
end
|
|
@@ -273,7 +278,10 @@ function resolve(router, location) {
|
|
|
273
278
|
} = router;
|
|
274
279
|
return (matched ? resolveView(matched, {
|
|
275
280
|
router,
|
|
276
|
-
location
|
|
281
|
+
location,
|
|
282
|
+
// One-shot resolves(warm-up, direct calls) are never superseded
|
|
283
|
+
// or cancelled: their loaders get a signal that never aborts.
|
|
284
|
+
signal: new AbortController().signal
|
|
277
285
|
}) : Promise.reject(new NotFoundError(location.pathname))).catch(errorHandler);
|
|
278
286
|
}
|
|
279
287
|
|
|
@@ -300,22 +308,37 @@ function resolveTo(router, to, state) {
|
|
|
300
308
|
* levels re-run on every hop(keep side-effectful guards idempotent) —
|
|
301
309
|
* carrying the original user state; at most
|
|
302
310
|
* {@link MAX_REDIRECTS 10} redirects are followed before a
|
|
303
|
-
*
|
|
311
|
+
* An unmatched pathname keeps the
|
|
304
312
|
* {@link resolve resolve} behavior: the task rejects with a
|
|
305
313
|
* {@link NotFoundError} and is routed through `router.errorHandler`.
|
|
306
314
|
*
|
|
315
|
+
* `opts.signal` is the abort signal of the whole chain: guards see it in
|
|
316
|
+
* their {@link GuardContext contexts}, the view task's
|
|
317
|
+
* {@link ResolveViewContext context} carries it on, and it is aborted
|
|
318
|
+
* once the navigation is superseded or cancelled. Standalone callers
|
|
319
|
+
* that pass nothing(e.g. {@link preload}) get a signal that never
|
|
320
|
+
* aborts — their resolution may be shared, so cancelling it on behalf of
|
|
321
|
+
* one consumer is not sound yet.
|
|
322
|
+
*
|
|
307
323
|
* @group Methods
|
|
308
324
|
* @category Router
|
|
309
325
|
* @param router router instance
|
|
310
326
|
* @param location the location to resolve; the object itself is never
|
|
311
327
|
* mutated — a redirect rebinds the resolution to a new location
|
|
328
|
+
* @param opts options; `signal` is the chain's abort signal
|
|
312
329
|
* @returns the terminal location and its resolve task
|
|
313
330
|
*/
|
|
314
|
-
async function resolveEntry(router, location) {
|
|
331
|
+
async function resolveEntry(router, location, opts) {
|
|
315
332
|
const {
|
|
316
333
|
resolveView,
|
|
317
334
|
errorHandler
|
|
318
335
|
} = router;
|
|
336
|
+
// The chain owner(navigate/refresh) passes its controller's signal;
|
|
337
|
+
// standalone resolutions get one more controller whose signal never
|
|
338
|
+
// aborts, so downstream consumers always observe a real signal.
|
|
339
|
+
const {
|
|
340
|
+
signal = new AbortController().signal
|
|
341
|
+
} = opts ?? {};
|
|
319
342
|
for (let redirects = 0;; redirects++) {
|
|
320
343
|
if (redirects > MAX_REDIRECTS) {
|
|
321
344
|
throw new RedirectLoopError(location.pathname);
|
|
@@ -339,7 +362,8 @@ async function resolveEntry(router, location) {
|
|
|
339
362
|
await route.beforeLoad?.({
|
|
340
363
|
router,
|
|
341
364
|
location,
|
|
342
|
-
params: mergeMatchedParams(matched, i)
|
|
365
|
+
params: mergeMatchedParams(matched, i),
|
|
366
|
+
signal
|
|
343
367
|
}));
|
|
344
368
|
if (target) {
|
|
345
369
|
location = toLocation(router, target, location.state);
|
|
@@ -353,7 +377,8 @@ async function resolveEntry(router, location) {
|
|
|
353
377
|
location,
|
|
354
378
|
task: resolveView(matched, {
|
|
355
379
|
router,
|
|
356
|
-
location
|
|
380
|
+
location,
|
|
381
|
+
signal
|
|
357
382
|
}).catch(errorHandler)
|
|
358
383
|
};
|
|
359
384
|
}
|
|
@@ -459,12 +484,12 @@ function commit(router, resolvePromise, location) {
|
|
|
459
484
|
task: resolvePromise
|
|
460
485
|
}), location);
|
|
461
486
|
}
|
|
462
|
-
function pushEntry(router, entryPromise, fromLocation) {
|
|
487
|
+
function pushEntry(router, entryPromise, fromLocation, ac) {
|
|
463
488
|
const {
|
|
464
489
|
history
|
|
465
490
|
} = router;
|
|
466
491
|
const nextIndex = getHistoryState(router).index + 1;
|
|
467
|
-
return commitBase(router, entryPromise, fromLocation, (resolvedView, entry) => {
|
|
492
|
+
return commitBase(router, entryPromise, fromLocation, ac, (resolvedView, entry) => {
|
|
468
493
|
const {
|
|
469
494
|
location
|
|
470
495
|
} = entry;
|
|
@@ -511,14 +536,14 @@ function commitReplace(router, resolvePromise, location) {
|
|
|
511
536
|
task: resolvePromise
|
|
512
537
|
}), location);
|
|
513
538
|
}
|
|
514
|
-
function replaceEntry(router, entryPromise, fromLocation) {
|
|
539
|
+
function replaceEntry(router, entryPromise, fromLocation, ac) {
|
|
515
540
|
const {
|
|
516
541
|
history
|
|
517
542
|
} = router;
|
|
518
543
|
const {
|
|
519
544
|
index
|
|
520
545
|
} = getHistoryState(router);
|
|
521
|
-
return commitBase(router, entryPromise, fromLocation, (resolvedView, entry) => {
|
|
546
|
+
return commitBase(router, entryPromise, fromLocation, ac, (resolvedView, entry) => {
|
|
522
547
|
const {
|
|
523
548
|
location
|
|
524
549
|
} = entry;
|
|
@@ -544,7 +569,8 @@ function replaceEntry(router, entryPromise, fromLocation) {
|
|
|
544
569
|
});
|
|
545
570
|
});
|
|
546
571
|
}
|
|
547
|
-
function commitBase(router, entryPromise, location, onResolved) {
|
|
572
|
+
function commitBase(router, entryPromise, location, ac, onResolved) {
|
|
573
|
+
const core = router;
|
|
548
574
|
const {
|
|
549
575
|
currentGuard,
|
|
550
576
|
onLoadingChange = util.noop
|
|
@@ -552,8 +578,16 @@ function commitBase(router, entryPromise, location, onResolved) {
|
|
|
552
578
|
if (router.resolving) {
|
|
553
579
|
// Cancel current resolve
|
|
554
580
|
onLoadingChange();
|
|
581
|
+
// ...and stop its requests: the guard below discards the superseded
|
|
582
|
+
// chain's result, so its in-flight guards/loaders must not keep
|
|
583
|
+
// consuming the network until they settle on their own.
|
|
584
|
+
core.resolvingController?.abort();
|
|
555
585
|
}
|
|
556
586
|
router.resolving = location;
|
|
587
|
+
// External commits(plain tasks from resolveTo/preload) carry no
|
|
588
|
+
// controller; clearing the slot keeps a stale one from being aborted
|
|
589
|
+
// by a later supersede.
|
|
590
|
+
core.resolvingController = ac;
|
|
557
591
|
onLoadingChange('pending');
|
|
558
592
|
return (
|
|
559
593
|
// The whole chain — route guards AND the view task — is guarded from
|
|
@@ -567,15 +601,19 @@ function commitBase(router, entryPromise, location, onResolved) {
|
|
|
567
601
|
entry,
|
|
568
602
|
resolvedView
|
|
569
603
|
}) => {
|
|
604
|
+
// This chain settled: it is no longer in flight. Clearing the
|
|
605
|
+
// mark BEFORE onResolved matters because onResolved commits
|
|
606
|
+
// history, which synchronously re-enters cancel() through the
|
|
607
|
+
// router's own listen() handler — an already-settled chain must
|
|
608
|
+
// not be aborted(or fire a cancel signal) as if it were still
|
|
609
|
+
// running. Superseded chains park forever, so the mark is always
|
|
610
|
+
// ours to clear.
|
|
611
|
+
router.resolving = undefined;
|
|
570
612
|
onResolved(resolvedView, entry);
|
|
571
613
|
// The navigation consumed this resolution: drop its preload
|
|
572
614
|
// cache slots so a later preload re-resolves fresh state.
|
|
573
615
|
evictPreloadCache(router, entry.location);
|
|
574
616
|
}).then(() => {
|
|
575
|
-
// This chain settled, so it is no longer in flight. Superseded
|
|
576
|
-
// chains park forever, so only the latest chain can reach here:
|
|
577
|
-
// the mark is always ours to clear.
|
|
578
|
-
router.resolving = undefined;
|
|
579
617
|
onLoadingChange('resolved');
|
|
580
618
|
}).catch(e => {
|
|
581
619
|
router.resolving = undefined;
|
|
@@ -590,7 +628,10 @@ function commitBase(router, entryPromise, location, onResolved) {
|
|
|
590
628
|
* the view resolves; the history entry is committed on the terminal
|
|
591
629
|
* location when guards redirected. The guard phase is part of the
|
|
592
630
|
* cancelable navigation: a superseding navigate or a `cancel()` while
|
|
593
|
-
* guards are still running discards this navigation
|
|
631
|
+
* guards are still running discards this navigation — and aborts the
|
|
632
|
+
* chain's `signal`, so guards and loaders observing it({@link
|
|
633
|
+
* GuardContext.signal}, {@link ResolveViewContext.signal}) stop their
|
|
634
|
+
* requests instead of only having their results dropped.
|
|
594
635
|
* @group Methods
|
|
595
636
|
* @category Router
|
|
596
637
|
* @param router router instance
|
|
@@ -599,19 +640,29 @@ function commitBase(router, entryPromise, location, onResolved) {
|
|
|
599
640
|
*/
|
|
600
641
|
function navigate(router, to, state) {
|
|
601
642
|
const location = toLocation(router, to, state);
|
|
602
|
-
|
|
643
|
+
// One controller per navigation round: guards and view loaders of the
|
|
644
|
+
// whole chain(including redirect hops) share its signal.
|
|
645
|
+
const ac = new AbortController();
|
|
646
|
+
return pushEntry(router, resolveEntry(router, location, {
|
|
647
|
+
signal: ac.signal
|
|
648
|
+
}), location, ac);
|
|
603
649
|
}
|
|
604
650
|
|
|
605
651
|
/**
|
|
606
652
|
* Refresh the page. Route guards run before the view resolves; a redirect
|
|
607
|
-
* replaces the current entry with the terminal location.
|
|
653
|
+
* replaces the current entry with the terminal location. The refresh is a
|
|
654
|
+
* cancelable navigation chain like {@link navigate}: superseding it or
|
|
655
|
+
* `cancel()` aborts its signal.
|
|
608
656
|
* @group Methods
|
|
609
657
|
* @category Router
|
|
610
658
|
* @param router router instance
|
|
611
659
|
*/
|
|
612
660
|
function refresh(router) {
|
|
613
661
|
const location = getLocation(router);
|
|
614
|
-
|
|
662
|
+
const ac = new AbortController();
|
|
663
|
+
return replaceEntry(router, resolveEntry(router, location, {
|
|
664
|
+
signal: ac.signal
|
|
665
|
+
}), location, ac);
|
|
615
666
|
}
|
|
616
667
|
|
|
617
668
|
/**
|
|
@@ -661,12 +712,22 @@ function createHref({
|
|
|
661
712
|
}
|
|
662
713
|
|
|
663
714
|
/**
|
|
664
|
-
* Cancel the current navigate.
|
|
715
|
+
* Cancel the current navigate. The in-flight chain's guards/loaders are
|
|
716
|
+
* aborted through their signal, not merely discarded.
|
|
665
717
|
* @group Methods
|
|
666
718
|
* @category Router
|
|
667
719
|
* @param router router instance
|
|
668
720
|
*/
|
|
669
721
|
function cancel(router) {
|
|
722
|
+
const core = router;
|
|
723
|
+
// Aborting is reserved for chains that are still running: a chain that
|
|
724
|
+
// just committed re-enters cancel() synchronously through listen()'s
|
|
725
|
+
// history handler and must not have its(possibly still-rendered) view
|
|
726
|
+
// contexts aborted after the fact.
|
|
727
|
+
if (router.resolving) {
|
|
728
|
+
core.resolvingController?.abort();
|
|
729
|
+
core.resolvingController = undefined;
|
|
730
|
+
}
|
|
670
731
|
// The cancelled chain parks forever, so nothing else will clear the
|
|
671
732
|
// in-flight mark: drop it here, or a later navigation would fire a
|
|
672
733
|
// spurious cancel signal(`onLoadingChange()`) for a dead resolve.
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createPath, parsePath } from 'history';
|
|
2
2
|
import { match as match$1 } from 'path-to-regexp';
|
|
3
|
-
import { reject, noop
|
|
3
|
+
import { createCurrentGuard, reject, noop } from './util.mjs';
|
|
4
4
|
|
|
5
5
|
/* eslint-disable max-classes-per-file */
|
|
6
6
|
|
|
@@ -24,9 +24,10 @@ class RedirectLoopError extends NativeRouterError {
|
|
|
24
24
|
*/
|
|
25
25
|
class SearchError extends NativeRouterError {
|
|
26
26
|
/** The raw search string that failed validation. */
|
|
27
|
+
search;
|
|
27
28
|
|
|
28
29
|
/** The issues reported by the schema. */
|
|
29
|
-
|
|
30
|
+
issues;
|
|
30
31
|
constructor(search, issues) {
|
|
31
32
|
super(`Invalid search params "${search}": ${issues.map(({
|
|
32
33
|
message,
|
|
@@ -72,6 +73,10 @@ const DEFAULT_PRELOAD_TTL = 30_000;
|
|
|
72
73
|
* `history index - baseIndex`; entries whose slot falls outside the
|
|
73
74
|
* memory window re-resolve lazily when landed on.
|
|
74
75
|
* - `preloadCache`: router-level cache of {@link preload} results.
|
|
76
|
+
* - `resolvingController`: the in-flight chain's AbortController. It is
|
|
77
|
+
* aborted(supersede/cancel) only while the chain is in flight; a
|
|
78
|
+
* settled chain's controller is left alone so its contexts never
|
|
79
|
+
* report `aborted` for a navigation that actually committed.
|
|
75
80
|
*/
|
|
76
81
|
|
|
77
82
|
/**
|
|
@@ -208,7 +213,7 @@ function match(router, pathname) {
|
|
|
208
213
|
const route = routes[i];
|
|
209
214
|
const end = !route.children;
|
|
210
215
|
const matched = route.path ? match$1(route.path, {
|
|
211
|
-
|
|
216
|
+
trailing: false,
|
|
212
217
|
sensitive: true,
|
|
213
218
|
decode: typeof decodeURIComponent === 'function' ? decodeURIComponent : undefined,
|
|
214
219
|
end
|
|
@@ -271,7 +276,10 @@ function resolve(router, location) {
|
|
|
271
276
|
} = router;
|
|
272
277
|
return (matched ? resolveView(matched, {
|
|
273
278
|
router,
|
|
274
|
-
location
|
|
279
|
+
location,
|
|
280
|
+
// One-shot resolves(warm-up, direct calls) are never superseded
|
|
281
|
+
// or cancelled: their loaders get a signal that never aborts.
|
|
282
|
+
signal: new AbortController().signal
|
|
275
283
|
}) : Promise.reject(new NotFoundError(location.pathname))).catch(errorHandler);
|
|
276
284
|
}
|
|
277
285
|
|
|
@@ -298,22 +306,37 @@ function resolveTo(router, to, state) {
|
|
|
298
306
|
* levels re-run on every hop(keep side-effectful guards idempotent) —
|
|
299
307
|
* carrying the original user state; at most
|
|
300
308
|
* {@link MAX_REDIRECTS 10} redirects are followed before a
|
|
301
|
-
*
|
|
309
|
+
* An unmatched pathname keeps the
|
|
302
310
|
* {@link resolve resolve} behavior: the task rejects with a
|
|
303
311
|
* {@link NotFoundError} and is routed through `router.errorHandler`.
|
|
304
312
|
*
|
|
313
|
+
* `opts.signal` is the abort signal of the whole chain: guards see it in
|
|
314
|
+
* their {@link GuardContext contexts}, the view task's
|
|
315
|
+
* {@link ResolveViewContext context} carries it on, and it is aborted
|
|
316
|
+
* once the navigation is superseded or cancelled. Standalone callers
|
|
317
|
+
* that pass nothing(e.g. {@link preload}) get a signal that never
|
|
318
|
+
* aborts — their resolution may be shared, so cancelling it on behalf of
|
|
319
|
+
* one consumer is not sound yet.
|
|
320
|
+
*
|
|
305
321
|
* @group Methods
|
|
306
322
|
* @category Router
|
|
307
323
|
* @param router router instance
|
|
308
324
|
* @param location the location to resolve; the object itself is never
|
|
309
325
|
* mutated — a redirect rebinds the resolution to a new location
|
|
326
|
+
* @param opts options; `signal` is the chain's abort signal
|
|
310
327
|
* @returns the terminal location and its resolve task
|
|
311
328
|
*/
|
|
312
|
-
async function resolveEntry(router, location) {
|
|
329
|
+
async function resolveEntry(router, location, opts) {
|
|
313
330
|
const {
|
|
314
331
|
resolveView,
|
|
315
332
|
errorHandler
|
|
316
333
|
} = router;
|
|
334
|
+
// The chain owner(navigate/refresh) passes its controller's signal;
|
|
335
|
+
// standalone resolutions get one more controller whose signal never
|
|
336
|
+
// aborts, so downstream consumers always observe a real signal.
|
|
337
|
+
const {
|
|
338
|
+
signal = new AbortController().signal
|
|
339
|
+
} = opts ?? {};
|
|
317
340
|
for (let redirects = 0;; redirects++) {
|
|
318
341
|
if (redirects > MAX_REDIRECTS) {
|
|
319
342
|
throw new RedirectLoopError(location.pathname);
|
|
@@ -337,7 +360,8 @@ async function resolveEntry(router, location) {
|
|
|
337
360
|
await route.beforeLoad?.({
|
|
338
361
|
router,
|
|
339
362
|
location,
|
|
340
|
-
params: mergeMatchedParams(matched, i)
|
|
363
|
+
params: mergeMatchedParams(matched, i),
|
|
364
|
+
signal
|
|
341
365
|
}));
|
|
342
366
|
if (target) {
|
|
343
367
|
location = toLocation(router, target, location.state);
|
|
@@ -351,7 +375,8 @@ async function resolveEntry(router, location) {
|
|
|
351
375
|
location,
|
|
352
376
|
task: resolveView(matched, {
|
|
353
377
|
router,
|
|
354
|
-
location
|
|
378
|
+
location,
|
|
379
|
+
signal
|
|
355
380
|
}).catch(errorHandler)
|
|
356
381
|
};
|
|
357
382
|
}
|
|
@@ -457,12 +482,12 @@ function commit(router, resolvePromise, location) {
|
|
|
457
482
|
task: resolvePromise
|
|
458
483
|
}), location);
|
|
459
484
|
}
|
|
460
|
-
function pushEntry(router, entryPromise, fromLocation) {
|
|
485
|
+
function pushEntry(router, entryPromise, fromLocation, ac) {
|
|
461
486
|
const {
|
|
462
487
|
history
|
|
463
488
|
} = router;
|
|
464
489
|
const nextIndex = getHistoryState(router).index + 1;
|
|
465
|
-
return commitBase(router, entryPromise, fromLocation, (resolvedView, entry) => {
|
|
490
|
+
return commitBase(router, entryPromise, fromLocation, ac, (resolvedView, entry) => {
|
|
466
491
|
const {
|
|
467
492
|
location
|
|
468
493
|
} = entry;
|
|
@@ -509,14 +534,14 @@ function commitReplace(router, resolvePromise, location) {
|
|
|
509
534
|
task: resolvePromise
|
|
510
535
|
}), location);
|
|
511
536
|
}
|
|
512
|
-
function replaceEntry(router, entryPromise, fromLocation) {
|
|
537
|
+
function replaceEntry(router, entryPromise, fromLocation, ac) {
|
|
513
538
|
const {
|
|
514
539
|
history
|
|
515
540
|
} = router;
|
|
516
541
|
const {
|
|
517
542
|
index
|
|
518
543
|
} = getHistoryState(router);
|
|
519
|
-
return commitBase(router, entryPromise, fromLocation, (resolvedView, entry) => {
|
|
544
|
+
return commitBase(router, entryPromise, fromLocation, ac, (resolvedView, entry) => {
|
|
520
545
|
const {
|
|
521
546
|
location
|
|
522
547
|
} = entry;
|
|
@@ -542,7 +567,8 @@ function replaceEntry(router, entryPromise, fromLocation) {
|
|
|
542
567
|
});
|
|
543
568
|
});
|
|
544
569
|
}
|
|
545
|
-
function commitBase(router, entryPromise, location, onResolved) {
|
|
570
|
+
function commitBase(router, entryPromise, location, ac, onResolved) {
|
|
571
|
+
const core = router;
|
|
546
572
|
const {
|
|
547
573
|
currentGuard,
|
|
548
574
|
onLoadingChange = noop
|
|
@@ -550,8 +576,16 @@ function commitBase(router, entryPromise, location, onResolved) {
|
|
|
550
576
|
if (router.resolving) {
|
|
551
577
|
// Cancel current resolve
|
|
552
578
|
onLoadingChange();
|
|
579
|
+
// ...and stop its requests: the guard below discards the superseded
|
|
580
|
+
// chain's result, so its in-flight guards/loaders must not keep
|
|
581
|
+
// consuming the network until they settle on their own.
|
|
582
|
+
core.resolvingController?.abort();
|
|
553
583
|
}
|
|
554
584
|
router.resolving = location;
|
|
585
|
+
// External commits(plain tasks from resolveTo/preload) carry no
|
|
586
|
+
// controller; clearing the slot keeps a stale one from being aborted
|
|
587
|
+
// by a later supersede.
|
|
588
|
+
core.resolvingController = ac;
|
|
555
589
|
onLoadingChange('pending');
|
|
556
590
|
return (
|
|
557
591
|
// The whole chain — route guards AND the view task — is guarded from
|
|
@@ -565,15 +599,19 @@ function commitBase(router, entryPromise, location, onResolved) {
|
|
|
565
599
|
entry,
|
|
566
600
|
resolvedView
|
|
567
601
|
}) => {
|
|
602
|
+
// This chain settled: it is no longer in flight. Clearing the
|
|
603
|
+
// mark BEFORE onResolved matters because onResolved commits
|
|
604
|
+
// history, which synchronously re-enters cancel() through the
|
|
605
|
+
// router's own listen() handler — an already-settled chain must
|
|
606
|
+
// not be aborted(or fire a cancel signal) as if it were still
|
|
607
|
+
// running. Superseded chains park forever, so the mark is always
|
|
608
|
+
// ours to clear.
|
|
609
|
+
router.resolving = undefined;
|
|
568
610
|
onResolved(resolvedView, entry);
|
|
569
611
|
// The navigation consumed this resolution: drop its preload
|
|
570
612
|
// cache slots so a later preload re-resolves fresh state.
|
|
571
613
|
evictPreloadCache(router, entry.location);
|
|
572
614
|
}).then(() => {
|
|
573
|
-
// This chain settled, so it is no longer in flight. Superseded
|
|
574
|
-
// chains park forever, so only the latest chain can reach here:
|
|
575
|
-
// the mark is always ours to clear.
|
|
576
|
-
router.resolving = undefined;
|
|
577
615
|
onLoadingChange('resolved');
|
|
578
616
|
}).catch(e => {
|
|
579
617
|
router.resolving = undefined;
|
|
@@ -588,7 +626,10 @@ function commitBase(router, entryPromise, location, onResolved) {
|
|
|
588
626
|
* the view resolves; the history entry is committed on the terminal
|
|
589
627
|
* location when guards redirected. The guard phase is part of the
|
|
590
628
|
* cancelable navigation: a superseding navigate or a `cancel()` while
|
|
591
|
-
* guards are still running discards this navigation
|
|
629
|
+
* guards are still running discards this navigation — and aborts the
|
|
630
|
+
* chain's `signal`, so guards and loaders observing it({@link
|
|
631
|
+
* GuardContext.signal}, {@link ResolveViewContext.signal}) stop their
|
|
632
|
+
* requests instead of only having their results dropped.
|
|
592
633
|
* @group Methods
|
|
593
634
|
* @category Router
|
|
594
635
|
* @param router router instance
|
|
@@ -597,19 +638,29 @@ function commitBase(router, entryPromise, location, onResolved) {
|
|
|
597
638
|
*/
|
|
598
639
|
function navigate(router, to, state) {
|
|
599
640
|
const location = toLocation(router, to, state);
|
|
600
|
-
|
|
641
|
+
// One controller per navigation round: guards and view loaders of the
|
|
642
|
+
// whole chain(including redirect hops) share its signal.
|
|
643
|
+
const ac = new AbortController();
|
|
644
|
+
return pushEntry(router, resolveEntry(router, location, {
|
|
645
|
+
signal: ac.signal
|
|
646
|
+
}), location, ac);
|
|
601
647
|
}
|
|
602
648
|
|
|
603
649
|
/**
|
|
604
650
|
* Refresh the page. Route guards run before the view resolves; a redirect
|
|
605
|
-
* replaces the current entry with the terminal location.
|
|
651
|
+
* replaces the current entry with the terminal location. The refresh is a
|
|
652
|
+
* cancelable navigation chain like {@link navigate}: superseding it or
|
|
653
|
+
* `cancel()` aborts its signal.
|
|
606
654
|
* @group Methods
|
|
607
655
|
* @category Router
|
|
608
656
|
* @param router router instance
|
|
609
657
|
*/
|
|
610
658
|
function refresh(router) {
|
|
611
659
|
const location = getLocation(router);
|
|
612
|
-
|
|
660
|
+
const ac = new AbortController();
|
|
661
|
+
return replaceEntry(router, resolveEntry(router, location, {
|
|
662
|
+
signal: ac.signal
|
|
663
|
+
}), location, ac);
|
|
613
664
|
}
|
|
614
665
|
|
|
615
666
|
/**
|
|
@@ -659,12 +710,22 @@ function createHref({
|
|
|
659
710
|
}
|
|
660
711
|
|
|
661
712
|
/**
|
|
662
|
-
* Cancel the current navigate.
|
|
713
|
+
* Cancel the current navigate. The in-flight chain's guards/loaders are
|
|
714
|
+
* aborted through their signal, not merely discarded.
|
|
663
715
|
* @group Methods
|
|
664
716
|
* @category Router
|
|
665
717
|
* @param router router instance
|
|
666
718
|
*/
|
|
667
719
|
function cancel(router) {
|
|
720
|
+
const core = router;
|
|
721
|
+
// Aborting is reserved for chains that are still running: a chain that
|
|
722
|
+
// just committed re-enters cancel() synchronously through listen()'s
|
|
723
|
+
// history handler and must not have its(possibly still-rendered) view
|
|
724
|
+
// contexts aborted after the fact.
|
|
725
|
+
if (router.resolving) {
|
|
726
|
+
core.resolvingController?.abort();
|
|
727
|
+
core.resolvingController = undefined;
|
|
728
|
+
}
|
|
668
729
|
// The cancelled chain parks forever, so nothing else will clear the
|
|
669
730
|
// in-flight mark: drop it here, or a later navigation would fire a
|
|
670
731
|
// spurious cancel signal(`onLoadingChange()`) for a dead resolve.
|
package/dist/types/router.d.ts
CHANGED
|
@@ -88,18 +88,29 @@ export declare function resolveTo<R extends BaseRoute = BaseRoute, V = any>(rout
|
|
|
88
88
|
* levels re-run on every hop(keep side-effectful guards idempotent) —
|
|
89
89
|
* carrying the original user state; at most
|
|
90
90
|
* {@link MAX_REDIRECTS 10} redirects are followed before a
|
|
91
|
-
*
|
|
91
|
+
* An unmatched pathname keeps the
|
|
92
92
|
* {@link resolve resolve} behavior: the task rejects with a
|
|
93
93
|
* {@link NotFoundError} and is routed through `router.errorHandler`.
|
|
94
94
|
*
|
|
95
|
+
* `opts.signal` is the abort signal of the whole chain: guards see it in
|
|
96
|
+
* their {@link GuardContext contexts}, the view task's
|
|
97
|
+
* {@link ResolveViewContext context} carries it on, and it is aborted
|
|
98
|
+
* once the navigation is superseded or cancelled. Standalone callers
|
|
99
|
+
* that pass nothing(e.g. {@link preload}) get a signal that never
|
|
100
|
+
* aborts — their resolution may be shared, so cancelling it on behalf of
|
|
101
|
+
* one consumer is not sound yet.
|
|
102
|
+
*
|
|
95
103
|
* @group Methods
|
|
96
104
|
* @category Router
|
|
97
105
|
* @param router router instance
|
|
98
106
|
* @param location the location to resolve; the object itself is never
|
|
99
107
|
* mutated — a redirect rebinds the resolution to a new location
|
|
108
|
+
* @param opts options; `signal` is the chain's abort signal
|
|
100
109
|
* @returns the terminal location and its resolve task
|
|
101
110
|
*/
|
|
102
|
-
export declare function resolveEntry<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, location: Location
|
|
111
|
+
export declare function resolveEntry<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, location: Location, opts?: {
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
}): Promise<ResolvedEntry<V>>;
|
|
103
114
|
/**
|
|
104
115
|
* Resolve a target through the route guards(`redirect`/`beforeLoad`) and
|
|
105
116
|
* cache the result at the router level, keyed by `pathname + search`.
|
|
@@ -147,7 +158,10 @@ export declare function commitReplace<R extends BaseRoute = BaseRoute, V = any>(
|
|
|
147
158
|
* the view resolves; the history entry is committed on the terminal
|
|
148
159
|
* location when guards redirected. The guard phase is part of the
|
|
149
160
|
* cancelable navigation: a superseding navigate or a `cancel()` while
|
|
150
|
-
* guards are still running discards this navigation
|
|
161
|
+
* guards are still running discards this navigation — and aborts the
|
|
162
|
+
* chain's `signal`, so guards and loaders observing it({@link
|
|
163
|
+
* GuardContext.signal}, {@link ResolveViewContext.signal}) stop their
|
|
164
|
+
* requests instead of only having their results dropped.
|
|
151
165
|
* @group Methods
|
|
152
166
|
* @category Router
|
|
153
167
|
* @param router router instance
|
|
@@ -157,7 +171,9 @@ export declare function commitReplace<R extends BaseRoute = BaseRoute, V = any>(
|
|
|
157
171
|
export declare function navigate<R extends BaseRoute = BaseRoute, V = any>(router: RouterInstance<R, V>, to: string, state?: any): Promise<void>;
|
|
158
172
|
/**
|
|
159
173
|
* Refresh the page. Route guards run before the view resolves; a redirect
|
|
160
|
-
* replaces the current entry with the terminal location.
|
|
174
|
+
* replaces the current entry with the terminal location. The refresh is a
|
|
175
|
+
* cancelable navigation chain like {@link navigate}: superseding it or
|
|
176
|
+
* `cancel()` aborts its signal.
|
|
161
177
|
* @group Methods
|
|
162
178
|
* @category Router
|
|
163
179
|
* @param router router instance
|
|
@@ -195,7 +211,8 @@ export declare function back<R extends BaseRoute = BaseRoute, V = any>(router: R
|
|
|
195
211
|
*/
|
|
196
212
|
export declare function createHref<R extends BaseRoute = BaseRoute, V = any>({ baseUrl, history }: RouterInstance<R, V>, to: string): string;
|
|
197
213
|
/**
|
|
198
|
-
* Cancel the current navigate.
|
|
214
|
+
* Cancel the current navigate. The in-flight chain's guards/loaders are
|
|
215
|
+
* aborted through their signal, not merely discarded.
|
|
199
216
|
* @group Methods
|
|
200
217
|
* @category Router
|
|
201
218
|
* @param router router instance
|
package/dist/types/types.d.ts
CHANGED
|
@@ -79,32 +79,61 @@ export type SearchInput = Record<string, string | string[]>;
|
|
|
79
79
|
* @category Route
|
|
80
80
|
*/
|
|
81
81
|
export type SearchOutputOf<S> = S extends StandardSchemaV1<any, infer Output> ? Output : never;
|
|
82
|
+
/** ASCII approximation of path-to-regexp's `ID_Start`. */
|
|
83
|
+
type ParamStartChar = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '_' | '$';
|
|
84
|
+
/** ASCII approximation of path-to-regexp's `ID_Continue`. */
|
|
85
|
+
type ParamContinueChar = ParamStartChar | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
86
|
+
type ParamValueOf<Name extends string, Mode extends 'param' | 'wildcard'> = Mode extends 'wildcard' ? {
|
|
87
|
+
[K in Name]: string[];
|
|
88
|
+
} : {
|
|
89
|
+
[K in Name]: string;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Scan a segment char by char: `\\x` escapes the next char, `:name`
|
|
93
|
+
* starts a param, `*name` starts a wildcard, anything else is static
|
|
94
|
+
* text.
|
|
95
|
+
*/
|
|
96
|
+
type SegmentParamsOf<Seg extends string> = Seg extends `${infer Char}${infer Rest}` ? Char extends '\\' ? Rest extends `${string}${infer Tail}` ? SegmentParamsOf<Tail> : {} : Char extends ':' | '*' ? Rest extends `${infer First}${infer Rest2}` ? First extends ParamStartChar ? ParamNameOf<Rest2, First, Char extends '*' ? 'wildcard' : 'param'> : {} : {} : SegmentParamsOf<Rest> : {};
|
|
97
|
+
/** Consume the identifier run started by a `ParamStartChar`. */
|
|
98
|
+
type ParamNameOf<Rest extends string, Name extends string, Mode extends 'param' | 'wildcard'> = Rest extends `${infer Char}${infer Tail}` ? Char extends ParamContinueChar ? ParamNameOf<Tail, `${Name}${Char}`, Mode> : Char extends '?' | '(' | ')' | '[' | ']' | '+' | '!' | '*' ? {} : ParamValueOf<Name, Mode> & SegmentParamsOf<Rest> : ParamValueOf<Name, Mode>;
|
|
82
99
|
/**
|
|
83
|
-
* Params contributed by a single path segment
|
|
84
|
-
*
|
|
85
|
-
*
|
|
100
|
+
* Params contributed by a single path segment, modeled after the
|
|
101
|
+
* path-to-regexp **8.4.2** string grammar(the version this package
|
|
102
|
+
* locks):
|
|
103
|
+
*
|
|
104
|
+
* - `:name` contributes a required `string` param wherever it appears
|
|
105
|
+
* in the segment — `:id`, `page-:id`, `:from-:to` all work at
|
|
106
|
+
* runtime and are modeled;
|
|
107
|
+
* - `*name` contributes a `string[]` wildcard param(the runtime
|
|
108
|
+
* matcher splits a wildcard value by `/`).
|
|
109
|
+
*
|
|
110
|
+
* Everything else contributes nothing:
|
|
86
111
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
112
|
+
* - the v6-era suffixes `:id?`, `:id+`, `:id*`, `:id(\\d+)` are **not**
|
|
113
|
+
* runtime syntax in 8.4.2 — the matcher throws a `PathError` when the
|
|
114
|
+
* path is compiled, so they are deliberately left unmodeled instead
|
|
115
|
+
* of endorsing a path that crashes;
|
|
116
|
+
* - quoted names(`:"x y"`) and non-ASCII identifier chars are not
|
|
117
|
+
* modeled(the scanner only knows ASCII identifiers).
|
|
118
|
+
*
|
|
119
|
+
* Note: wildcard params surface as `string[]` at runtime while the
|
|
120
|
+
* router-level types(`Matched.params`,
|
|
121
|
+
* {@link GuardContext.params}) are `Record<string, string>` — the
|
|
122
|
+
* router does not model wildcard params.
|
|
91
123
|
* @group Types
|
|
92
124
|
* @category Route
|
|
93
125
|
*/
|
|
94
|
-
export type PathParamsOf<Seg extends string> = Seg
|
|
95
|
-
[K in Name & string]?: string;
|
|
96
|
-
} : Seg extends `:${infer Name}` ? {
|
|
97
|
-
[K in Name & string]: string;
|
|
98
|
-
} : {};
|
|
126
|
+
export type PathParamsOf<Seg extends string> = SegmentParamsOf<Seg>;
|
|
99
127
|
/**
|
|
100
128
|
* Extract the params shape of a route path pattern. Splits the pattern
|
|
101
129
|
* into `/`-separated segments and intersects the params of each, e.g.
|
|
102
|
-
* `ExtractPathParams<'/users/:id/
|
|
103
|
-
* `{id: string} & {
|
|
130
|
+
* `ExtractPathParams<'/users/:id/files/*rest'>` is
|
|
131
|
+
* `{id: string} & {rest: string[]}`.
|
|
104
132
|
*
|
|
105
|
-
* Within the modeled path-to-regexp
|
|
106
|
-
* {@link PathParamsOf});
|
|
107
|
-
*
|
|
133
|
+
* Within the modeled path-to-regexp 8.4.2 syntax scope(see
|
|
134
|
+
* {@link PathParamsOf}); static segments are ignored and v6-era
|
|
135
|
+
* suffixes(`:id?`, `:id(\\d+)`, …) contribute nothing because the
|
|
136
|
+
* runtime matcher rejects them. Distributes over unions of patterns.
|
|
108
137
|
* @group Types
|
|
109
138
|
* @category Route
|
|
110
139
|
*/
|
|
@@ -118,6 +147,13 @@ export type GuardContext<R extends BaseRoute = BaseRoute> = {
|
|
|
118
147
|
router: RouterInstance<R>;
|
|
119
148
|
location: Location;
|
|
120
149
|
params: Record<string, string>;
|
|
150
|
+
/**
|
|
151
|
+
* Aborted when this navigation is superseded by a newer one or
|
|
152
|
+
* cancelled(see {@link RouterInstance.cancelAll cancel}); pass it to
|
|
153
|
+
* the guard's requests(e.g. `fetch(url, {signal})`) so a discarded
|
|
154
|
+
* navigation stops consuming the network.
|
|
155
|
+
*/
|
|
156
|
+
signal: AbortSignal;
|
|
121
157
|
};
|
|
122
158
|
export type BaseRoute<T = any> = {
|
|
123
159
|
path?: Path;
|
|
@@ -147,6 +183,12 @@ export type Matched<R extends BaseRoute = BaseRoute> = {
|
|
|
147
183
|
export type ResolveViewContext<R extends BaseRoute> = {
|
|
148
184
|
router: RouterInstance<R>;
|
|
149
185
|
location: Location;
|
|
186
|
+
/**
|
|
187
|
+
* The navigation chain's abort signal: aborted when this navigation is
|
|
188
|
+
* superseded by a newer one or cancelled. Frameworks forward it into
|
|
189
|
+
* their data contexts so loaders can abort their requests.
|
|
190
|
+
*/
|
|
191
|
+
signal: AbortSignal;
|
|
150
192
|
};
|
|
151
193
|
export type ResolveView<R extends BaseRoute, V> = (matched: Matched<R>[], ctx: ResolveViewContext<R>) => Promise<V>;
|
|
152
194
|
export type Options<V> = {
|
|
@@ -191,3 +233,4 @@ export type RouterInstance<R extends BaseRoute, V = any> = {
|
|
|
191
233
|
cancelAll(): void;
|
|
192
234
|
resolving?: Location;
|
|
193
235
|
} & RequiredOf<Options<V>, 'baseUrl' | 'maxStackDepth'>;
|
|
236
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-router/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -52,24 +52,24 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"history": "^5.3.0",
|
|
55
|
-
"path-to-regexp": "^
|
|
55
|
+
"path-to-regexp": "^8.4.2"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@babel/core": "^
|
|
59
|
-
"@babel/preset-env": "^
|
|
60
|
-
"@babel/preset-typescript": "^
|
|
61
|
-
"@rollup/plugin-babel": "^
|
|
62
|
-
"@rollup/plugin-commonjs": "^
|
|
63
|
-
"@rollup/plugin-node-resolve": "^
|
|
64
|
-
"@rollup/plugin-replace": "^
|
|
65
|
-
"@types/node": "^
|
|
66
|
-
"@types/sinon": "^
|
|
58
|
+
"@babel/core": "^8.0.1",
|
|
59
|
+
"@babel/preset-env": "^8.0.2",
|
|
60
|
+
"@babel/preset-typescript": "^8.0.1",
|
|
61
|
+
"@rollup/plugin-babel": "^7.1.0",
|
|
62
|
+
"@rollup/plugin-commonjs": "^29.0.3",
|
|
63
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
64
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
65
|
+
"@types/node": "^26.2.0",
|
|
66
|
+
"@types/sinon": "^22.0.0",
|
|
67
67
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
|
68
68
|
"@typescript-eslint/parser": "^6.2.0",
|
|
69
|
-
"@vitest/coverage-v8": "^4.
|
|
70
|
-
"commitizen": "^4.3.
|
|
71
|
-
"core-js": "^3.
|
|
72
|
-
"cross-env": "^
|
|
69
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
70
|
+
"commitizen": "^4.3.2",
|
|
71
|
+
"core-js": "^3.50.0",
|
|
72
|
+
"cross-env": "^10.1.0",
|
|
73
73
|
"eslint": "^8.50.0",
|
|
74
74
|
"eslint-config-airbnb": "^19.0.4",
|
|
75
75
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
|
@@ -81,21 +81,21 @@
|
|
|
81
81
|
"eslint-plugin-prettier": "^5.0.0",
|
|
82
82
|
"eslint-plugin-react": "^7.33.0",
|
|
83
83
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
84
|
-
"gh-pages": "^
|
|
85
|
-
"husky": "^
|
|
86
|
-
"lint-staged": "^
|
|
87
|
-
"prettier": "^3.
|
|
88
|
-
"rollup": "^
|
|
84
|
+
"gh-pages": "^6.3.0",
|
|
85
|
+
"husky": "^9.1.7",
|
|
86
|
+
"lint-staged": "^17.3.0",
|
|
87
|
+
"prettier": "^3.9.6",
|
|
88
|
+
"rollup": "^4.62.5",
|
|
89
|
+
"semantic-release": "^25.0.9",
|
|
89
90
|
"should": "^13.2.3",
|
|
90
91
|
"should-sinon": "0.0.6",
|
|
91
|
-
"sinon": "^
|
|
92
|
-
"terser": "^5.
|
|
93
|
-
"tsc-alias": "^1.
|
|
94
|
-
"typedoc": "^0.
|
|
92
|
+
"sinon": "^22.1.0",
|
|
93
|
+
"terser": "^5.50.0",
|
|
94
|
+
"tsc-alias": "^1.9.2",
|
|
95
|
+
"typedoc": "^0.28.20",
|
|
95
96
|
"typedoc-plugin-mark-react-functional-components": "^0.2.2",
|
|
96
|
-
"typedoc-plugin-missing-exports": "^
|
|
97
|
-
"typescript": "
|
|
98
|
-
"vitest": "^4.
|
|
99
|
-
"semantic-release": "^25.0.3"
|
|
97
|
+
"typedoc-plugin-missing-exports": "^4.1.4",
|
|
98
|
+
"typescript": "~5.9.3",
|
|
99
|
+
"vitest": "^4.1.11"
|
|
100
100
|
}
|
|
101
101
|
}
|