@native-router/core 1.3.0 → 1.4.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 +26 -1
- package/dist/index.cjs +108 -0
- package/dist/index.mjs +105 -1
- package/dist/types/errors.d.ts +14 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/search.d.ts +45 -0
- package/dist/types/types.d.ts +65 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,9 +59,10 @@ commit(router, entry.task, entry.location); // commit like a click
|
|
|
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
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
|
|
61
61
|
- Navigation API: `navigate`, `refresh`, `go`/`forward`/`back`, `commit`/`commitReplace`, `createHref`, `getParams`, `match`, `toLocation`, `resolve`, `resolveTo`
|
|
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`
|
|
62
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
|
|
63
64
|
- `errorHandler` hook turns resolve failures into fallback views
|
|
64
|
-
- Errors: `NativeRouterError`, `NotFoundError`, `RedirectLoopError`
|
|
65
|
+
- Errors: `NativeRouterError`, `NotFoundError`, `RedirectLoopError`, `SearchError`
|
|
65
66
|
- Tree-shakable: `sideEffects: false`
|
|
66
67
|
|
|
67
68
|
## Matching semantics
|
|
@@ -73,6 +74,30 @@ commit(router, entry.task, entry.location); // commit like a click
|
|
|
73
74
|
- Matching is **case-sensitive**.
|
|
74
75
|
- Params of nested levels are merged **deep over shallow** (`mergeMatchedParams`): for `/:id` + `/posts/:id`, the deeper `id` wins.
|
|
75
76
|
|
|
77
|
+
## Search validation
|
|
78
|
+
|
|
79
|
+
Declare a `search` validator on a route level and parse `location.search` with it in your `resolveView`. Any [Standard Schema](https://standardschema.dev) validator works — zod, valibot and arktype all implement the interface — so the core keeps zero extra runtime dependencies.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import {create, parseSearch} from '@native-router/core';
|
|
83
|
+
import {z} from 'zod';
|
|
84
|
+
|
|
85
|
+
const listSearch = z.object({page: z.coerce.number().default(1)});
|
|
86
|
+
|
|
87
|
+
const router = create(
|
|
88
|
+
{path: '', children: [{path: '/list', search: listSearch}]},
|
|
89
|
+
createBrowserHistory(),
|
|
90
|
+
// Your resolveView consumes route.search itself: parse the location
|
|
91
|
+
// search, then resolve the view from the parsed output
|
|
92
|
+
async (matched, {location}) =>
|
|
93
|
+
renderList(await parseSearch(matched.at(-1)!.route.search!, location.search))
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- `parseSearchInput(search)` degrades a query string into a plain object — single-valued keys are strings, keys repeated in the query are arrays — which is also the input every schema validates
|
|
98
|
+
- `parseSearch(schema, search)` resolves the schema output (async validators are awaited); `parseSearchSync` is the render/guard-time flavor and rejects async validators with a clear error
|
|
99
|
+
- A rejected validation throws `SearchError` (a `NativeRouterError`) carrying the raw `search` and the reported `issues` — route it through your `errorHandler` like any other resolve failure
|
|
100
|
+
|
|
76
101
|
## Install
|
|
77
102
|
|
|
78
103
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -18,6 +18,32 @@ class RedirectLoopError extends NativeRouterError {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when a route {@link BaseRoute.search search schema} rejects the
|
|
23
|
+
* location search. Issues are formatted as `path: message` pairs joined
|
|
24
|
+
* with `; `, e.g.
|
|
25
|
+
* `Invalid search params "?page=abc": page: Expected a positive integer`.
|
|
26
|
+
*/
|
|
27
|
+
class SearchError extends NativeRouterError {
|
|
28
|
+
/** The raw search string that failed validation. */
|
|
29
|
+
|
|
30
|
+
/** The issues reported by the schema. */
|
|
31
|
+
|
|
32
|
+
constructor(search, issues) {
|
|
33
|
+
super(`Invalid search params "${search}": ${issues.map(({
|
|
34
|
+
message,
|
|
35
|
+
path
|
|
36
|
+
}) => `${formatIssuePath(path)}${message}`).join('; ')}`);
|
|
37
|
+
this.search = search;
|
|
38
|
+
this.issues = issues;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function formatIssuePath(path) {
|
|
42
|
+
if (!path?.length) return '';
|
|
43
|
+
const keys = path.map(segment => typeof segment === 'object' ? String(segment.key) : String(segment));
|
|
44
|
+
return `${keys.join('.')}: `;
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
const DEFAULT_MAX_STACK_DEPTH = 100;
|
|
22
48
|
|
|
23
49
|
/** Max redirects followed by {@link resolveEntry} before giving up. */
|
|
@@ -753,9 +779,88 @@ function getParams(router) {
|
|
|
753
779
|
return mergeMatchedParams(match(router, location.pathname) ?? []);
|
|
754
780
|
}
|
|
755
781
|
|
|
782
|
+
/**
|
|
783
|
+
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
784
|
+
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
785
|
+
* single-valued keys are strings, keys repeated in the query string are
|
|
786
|
+
* arrays of their values. An empty search is `{}`.
|
|
787
|
+
*
|
|
788
|
+
* This is also the degraded shape every search API falls back to when no
|
|
789
|
+
* schema is given.
|
|
790
|
+
* @group Methods
|
|
791
|
+
* @category Route
|
|
792
|
+
* @param search the raw `location.search` string, with or without `?`
|
|
793
|
+
* @returns the input object for schema validation
|
|
794
|
+
*/
|
|
795
|
+
function parseSearchInput(search) {
|
|
796
|
+
const input = {};
|
|
797
|
+
// eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled
|
|
798
|
+
new URLSearchParams(search).forEach((value, key) => {
|
|
799
|
+
const prev = input[key];
|
|
800
|
+
if (prev === undefined) {
|
|
801
|
+
input[key] = value;
|
|
802
|
+
} else if (Array.isArray(prev)) {
|
|
803
|
+
prev.push(value);
|
|
804
|
+
} else {
|
|
805
|
+
input[key] = [prev, value];
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
return input;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
813
|
+
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
814
|
+
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
815
|
+
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
816
|
+
*
|
|
817
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
818
|
+
*
|
|
819
|
+
* @group Methods
|
|
820
|
+
* @category Route
|
|
821
|
+
* @param schema the search schema
|
|
822
|
+
* @param search the raw `location.search` string
|
|
823
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
824
|
+
* @throws {SearchError} when the schema reports issues
|
|
825
|
+
*/
|
|
826
|
+
async function parseSearch(schema, search) {
|
|
827
|
+
const result = await schema['~standard'].validate(parseSearchInput(search));
|
|
828
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
829
|
+
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
830
|
+
// degrades to `unknown`.
|
|
831
|
+
return result.value;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
836
|
+
* `useSearch` of `@native-router/react`) and route guards.
|
|
837
|
+
*
|
|
838
|
+
* @group Methods
|
|
839
|
+
* @category Route
|
|
840
|
+
* @param schema the search schema — must validate synchronously
|
|
841
|
+
* @param search the raw `location.search` string
|
|
842
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
843
|
+
* @throws {SearchError} when the schema reports issues
|
|
844
|
+
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
845
|
+
* for async schemas instead
|
|
846
|
+
*/
|
|
847
|
+
function parseSearchSync(schema, search) {
|
|
848
|
+
const result = schema['~standard'].validate(parseSearchInput(search));
|
|
849
|
+
if (isThenable(result)) {
|
|
850
|
+
throw new Error('The search schema validates asynchronously; parse it during resolve ' + '(parseSearch) instead of synchronously');
|
|
851
|
+
}
|
|
852
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
853
|
+
// See parseSearch for the cast rationale.
|
|
854
|
+
return result.value;
|
|
855
|
+
}
|
|
856
|
+
function isThenable(value) {
|
|
857
|
+
return typeof value?.then === 'function';
|
|
858
|
+
}
|
|
859
|
+
|
|
756
860
|
exports.NativeRouterError = NativeRouterError;
|
|
757
861
|
exports.NotFoundError = NotFoundError;
|
|
758
862
|
exports.RedirectLoopError = RedirectLoopError;
|
|
863
|
+
exports.SearchError = SearchError;
|
|
759
864
|
exports.back = back;
|
|
760
865
|
exports.cancel = cancel;
|
|
761
866
|
exports.commit = commit;
|
|
@@ -772,6 +877,9 @@ exports.listen = listen;
|
|
|
772
877
|
exports.match = match;
|
|
773
878
|
exports.mergeMatchedParams = mergeMatchedParams;
|
|
774
879
|
exports.navigate = navigate;
|
|
880
|
+
exports.parseSearch = parseSearch;
|
|
881
|
+
exports.parseSearchInput = parseSearchInput;
|
|
882
|
+
exports.parseSearchSync = parseSearchSync;
|
|
775
883
|
exports.preload = preload;
|
|
776
884
|
exports.refresh = refresh;
|
|
777
885
|
exports.resolve = resolve;
|
package/dist/index.mjs
CHANGED
|
@@ -16,6 +16,32 @@ class RedirectLoopError extends NativeRouterError {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Thrown when a route {@link BaseRoute.search search schema} rejects the
|
|
21
|
+
* location search. Issues are formatted as `path: message` pairs joined
|
|
22
|
+
* with `; `, e.g.
|
|
23
|
+
* `Invalid search params "?page=abc": page: Expected a positive integer`.
|
|
24
|
+
*/
|
|
25
|
+
class SearchError extends NativeRouterError {
|
|
26
|
+
/** The raw search string that failed validation. */
|
|
27
|
+
|
|
28
|
+
/** The issues reported by the schema. */
|
|
29
|
+
|
|
30
|
+
constructor(search, issues) {
|
|
31
|
+
super(`Invalid search params "${search}": ${issues.map(({
|
|
32
|
+
message,
|
|
33
|
+
path
|
|
34
|
+
}) => `${formatIssuePath(path)}${message}`).join('; ')}`);
|
|
35
|
+
this.search = search;
|
|
36
|
+
this.issues = issues;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function formatIssuePath(path) {
|
|
40
|
+
if (!path?.length) return '';
|
|
41
|
+
const keys = path.map(segment => typeof segment === 'object' ? String(segment.key) : String(segment));
|
|
42
|
+
return `${keys.join('.')}: `;
|
|
43
|
+
}
|
|
44
|
+
|
|
19
45
|
const DEFAULT_MAX_STACK_DEPTH = 100;
|
|
20
46
|
|
|
21
47
|
/** Max redirects followed by {@link resolveEntry} before giving up. */
|
|
@@ -751,4 +777,82 @@ function getParams(router) {
|
|
|
751
777
|
return mergeMatchedParams(match(router, location.pathname) ?? []);
|
|
752
778
|
}
|
|
753
779
|
|
|
754
|
-
|
|
780
|
+
/**
|
|
781
|
+
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
782
|
+
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
783
|
+
* single-valued keys are strings, keys repeated in the query string are
|
|
784
|
+
* arrays of their values. An empty search is `{}`.
|
|
785
|
+
*
|
|
786
|
+
* This is also the degraded shape every search API falls back to when no
|
|
787
|
+
* schema is given.
|
|
788
|
+
* @group Methods
|
|
789
|
+
* @category Route
|
|
790
|
+
* @param search the raw `location.search` string, with or without `?`
|
|
791
|
+
* @returns the input object for schema validation
|
|
792
|
+
*/
|
|
793
|
+
function parseSearchInput(search) {
|
|
794
|
+
const input = {};
|
|
795
|
+
// eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled
|
|
796
|
+
new URLSearchParams(search).forEach((value, key) => {
|
|
797
|
+
const prev = input[key];
|
|
798
|
+
if (prev === undefined) {
|
|
799
|
+
input[key] = value;
|
|
800
|
+
} else if (Array.isArray(prev)) {
|
|
801
|
+
prev.push(value);
|
|
802
|
+
} else {
|
|
803
|
+
input[key] = [prev, value];
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
return input;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
811
|
+
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
812
|
+
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
813
|
+
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
814
|
+
*
|
|
815
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
816
|
+
*
|
|
817
|
+
* @group Methods
|
|
818
|
+
* @category Route
|
|
819
|
+
* @param schema the search schema
|
|
820
|
+
* @param search the raw `location.search` string
|
|
821
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
822
|
+
* @throws {SearchError} when the schema reports issues
|
|
823
|
+
*/
|
|
824
|
+
async function parseSearch(schema, search) {
|
|
825
|
+
const result = await schema['~standard'].validate(parseSearchInput(search));
|
|
826
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
827
|
+
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
828
|
+
// degrades to `unknown`.
|
|
829
|
+
return result.value;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
834
|
+
* `useSearch` of `@native-router/react`) and route guards.
|
|
835
|
+
*
|
|
836
|
+
* @group Methods
|
|
837
|
+
* @category Route
|
|
838
|
+
* @param schema the search schema — must validate synchronously
|
|
839
|
+
* @param search the raw `location.search` string
|
|
840
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
841
|
+
* @throws {SearchError} when the schema reports issues
|
|
842
|
+
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
843
|
+
* for async schemas instead
|
|
844
|
+
*/
|
|
845
|
+
function parseSearchSync(schema, search) {
|
|
846
|
+
const result = schema['~standard'].validate(parseSearchInput(search));
|
|
847
|
+
if (isThenable(result)) {
|
|
848
|
+
throw new Error('The search schema validates asynchronously; parse it during resolve ' + '(parseSearch) instead of synchronously');
|
|
849
|
+
}
|
|
850
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
851
|
+
// See parseSearch for the cast rationale.
|
|
852
|
+
return result.value;
|
|
853
|
+
}
|
|
854
|
+
function isThenable(value) {
|
|
855
|
+
return typeof value?.then === 'function';
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export { NativeRouterError, NotFoundError, RedirectLoopError, SearchError, back, cancel, commit, commitReplace, create, createHref, forward, getCurrentView, getLocation, getParams, go, initHistoryStack, listen, match, mergeMatchedParams, navigate, parseSearch, parseSearchInput, parseSearchSync, preload, refresh, resolve, resolveEntry, resolveTo, setOptions, toLocation };
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from './types';
|
|
1
2
|
export declare class NativeRouterError extends Error {
|
|
2
3
|
}
|
|
3
4
|
export declare class NotFoundError extends NativeRouterError {
|
|
@@ -6,3 +7,16 @@ export declare class NotFoundError extends NativeRouterError {
|
|
|
6
7
|
export declare class RedirectLoopError extends NativeRouterError {
|
|
7
8
|
constructor(target?: string);
|
|
8
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when a route {@link BaseRoute.search search schema} rejects the
|
|
12
|
+
* location search. Issues are formatted as `path: message` pairs joined
|
|
13
|
+
* with `; `, e.g.
|
|
14
|
+
* `Invalid search params "?page=abc": page: Expected a positive integer`.
|
|
15
|
+
*/
|
|
16
|
+
export declare class SearchError extends NativeRouterError {
|
|
17
|
+
/** The raw search string that failed validation. */
|
|
18
|
+
readonly search: string;
|
|
19
|
+
/** The issues reported by the schema. */
|
|
20
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
21
|
+
constructor(search: string, issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
22
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SearchInput, SearchOutputOf, StandardSchemaV1 } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
4
|
+
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
5
|
+
* single-valued keys are strings, keys repeated in the query string are
|
|
6
|
+
* arrays of their values. An empty search is `{}`.
|
|
7
|
+
*
|
|
8
|
+
* This is also the degraded shape every search API falls back to when no
|
|
9
|
+
* schema is given.
|
|
10
|
+
* @group Methods
|
|
11
|
+
* @category Route
|
|
12
|
+
* @param search the raw `location.search` string, with or without `?`
|
|
13
|
+
* @returns the input object for schema validation
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseSearchInput(search: string): SearchInput;
|
|
16
|
+
/**
|
|
17
|
+
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
18
|
+
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
19
|
+
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
20
|
+
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
21
|
+
*
|
|
22
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
23
|
+
*
|
|
24
|
+
* @group Methods
|
|
25
|
+
* @category Route
|
|
26
|
+
* @param schema the search schema
|
|
27
|
+
* @param search the raw `location.search` string
|
|
28
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
29
|
+
* @throws {SearchError} when the schema reports issues
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseSearch<S extends StandardSchemaV1>(schema: S, search: string): Promise<SearchOutputOf<S>>;
|
|
32
|
+
/**
|
|
33
|
+
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
34
|
+
* `useSearch` of `@native-router/react`) and route guards.
|
|
35
|
+
*
|
|
36
|
+
* @group Methods
|
|
37
|
+
* @category Route
|
|
38
|
+
* @param schema the search schema — must validate synchronously
|
|
39
|
+
* @param search the raw `location.search` string
|
|
40
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
41
|
+
* @throws {SearchError} when the schema reports issues
|
|
42
|
+
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
43
|
+
* for async schemas instead
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseSearchSync<S extends StandardSchemaV1>(schema: S, search: string): SearchOutputOf<S>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -22,6 +22,63 @@ export type HistoryState = {
|
|
|
22
22
|
};
|
|
23
23
|
export type WrappedLocation = Location<HistoryState>;
|
|
24
24
|
export type Awaitable<T> = T | Promise<T>;
|
|
25
|
+
/**
|
|
26
|
+
* The [Standard Schema](https://standardschema.dev) interface, version 1 —
|
|
27
|
+
* the common validation interface implemented by zod, valibot and arktype.
|
|
28
|
+
*
|
|
29
|
+
* Inlined type-only from `@standard-schema/spec` so the core keeps zero
|
|
30
|
+
* extra runtime dependencies: any schema exposing `~standard` works.
|
|
31
|
+
* @group Types
|
|
32
|
+
* @category Route
|
|
33
|
+
*/
|
|
34
|
+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
35
|
+
readonly '~standard': StandardSchemaV1.Props<Input, Output>;
|
|
36
|
+
}
|
|
37
|
+
export declare namespace StandardSchemaV1 {
|
|
38
|
+
interface Props<Input = unknown, Output = Input> {
|
|
39
|
+
/** The version number of the standard. */
|
|
40
|
+
readonly version: 1;
|
|
41
|
+
/** The vendor name of the schema library. */
|
|
42
|
+
readonly vendor: string;
|
|
43
|
+
/** Validates unknown input values. */
|
|
44
|
+
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
|
|
45
|
+
}
|
|
46
|
+
type Result<Output> = SuccessResult<Output> | FailureResult;
|
|
47
|
+
interface SuccessResult<Output> {
|
|
48
|
+
/** The typed output value. */
|
|
49
|
+
readonly value: Output;
|
|
50
|
+
/** The issues of the input value. */
|
|
51
|
+
readonly issues?: undefined;
|
|
52
|
+
}
|
|
53
|
+
interface FailureResult {
|
|
54
|
+
/** The issues of the input value. */
|
|
55
|
+
readonly issues: ReadonlyArray<Issue>;
|
|
56
|
+
}
|
|
57
|
+
interface Issue {
|
|
58
|
+
/** The issue message. */
|
|
59
|
+
readonly message: string;
|
|
60
|
+
/** The issue path. */
|
|
61
|
+
readonly path?: ReadonlyArray<PropertyKey | PathSegment>;
|
|
62
|
+
}
|
|
63
|
+
interface PathSegment {
|
|
64
|
+
/** The key of the path segment. */
|
|
65
|
+
readonly key: PropertyKey;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The plain input object a search string degrades into before schema
|
|
70
|
+
* validation: single-valued keys are strings, keys repeated in the query
|
|
71
|
+
* string are arrays of their values.
|
|
72
|
+
* @group Types
|
|
73
|
+
* @category Route
|
|
74
|
+
*/
|
|
75
|
+
export type SearchInput = Record<string, string | string[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Parsed output type of a {@link StandardSchemaV1 search schema}.
|
|
78
|
+
* @group Types
|
|
79
|
+
* @category Route
|
|
80
|
+
*/
|
|
81
|
+
export type SearchOutputOf<S> = S extends StandardSchemaV1<any, infer Output> ? Output : never;
|
|
25
82
|
/**
|
|
26
83
|
* Params contributed by a single path segment: `:name` is required,
|
|
27
84
|
* `:name?` is optional, anything else(static or wildcard) contributes
|
|
@@ -70,6 +127,14 @@ export type BaseRoute<T = any> = {
|
|
|
70
127
|
* redirected to the target path before the view resolves.
|
|
71
128
|
*/
|
|
72
129
|
redirect?: string;
|
|
130
|
+
/**
|
|
131
|
+
* Optional Standard Schema(zod/valibot/arktype, ...) validator of the
|
|
132
|
+
* route search. Frameworks parse `location.search` with it at resolve
|
|
133
|
+
* time(see `parseSearch`) and inject the parsed output into their data
|
|
134
|
+
* contexts; a validation failure fails the resolve like any other
|
|
135
|
+
* navigation error.
|
|
136
|
+
*/
|
|
137
|
+
search?: StandardSchemaV1;
|
|
73
138
|
/**
|
|
74
139
|
* Route guard invoked before the view resolves. Return a path string
|
|
75
140
|
* to redirect, or nothing(`undefined`) to continue.
|