@native-router/core 1.7.0 → 1.9.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 +34 -2
- package/dist/index.cjs +215 -86
- package/dist/index.mjs +213 -87
- package/dist/types/errors.d.ts +13 -0
- package/dist/types/router.d.ts +10 -0
- package/dist/types/search.d.ts +31 -0
- package/dist/types/types.d.ts +34 -2
- package/package.json +20 -18
package/README.md
CHANGED
|
@@ -108,10 +108,42 @@ const router = create(
|
|
|
108
108
|
);
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
- `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
|
|
112
|
-
- `parseSearch(schema, search)` resolves the schema output (async validators are awaited); `parseSearchSync` is the render
|
|
111
|
+
- `parseSearchInput(search)` degrades a query string into a plain object — single-valued keys are strings, keys repeated in the query string are arrays — which is also the input every schema validates
|
|
112
|
+
- `parseSearch(schema, search)` resolves the schema output (async validators are awaited); `parseSearchSync` is the render-time flavor and rejects async validators with a clear error
|
|
113
|
+
- Guards: `beforeLoad` receives the level's parsed search as `ctx.search` — the schema output (parsed with `parseSearch`, so async validators work), or the degraded input on schema-less levels; an invalid search fails the resolution through the `errorHandler` channel like a data-phase search error
|
|
113
114
|
- 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
|
|
114
115
|
|
|
116
|
+
## Params validation
|
|
117
|
+
|
|
118
|
+
Params are always strings (wildcards: string arrays) — the URL has no types. Declare a `params` schema on a route level and the core validates/coerces the merged params of that level before its `beforeLoad` runs, so guards see numbers instead of `Number(id)` everywhere.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import {create} from '@native-router/core';
|
|
122
|
+
import {z} from 'zod';
|
|
123
|
+
|
|
124
|
+
const router = create(
|
|
125
|
+
{
|
|
126
|
+
path: '',
|
|
127
|
+
children: [
|
|
128
|
+
{
|
|
129
|
+
path: '/users/:id',
|
|
130
|
+
params: z.object({id: z.coerce.number().int().positive()}),
|
|
131
|
+
beforeLoad: ({params}) => {
|
|
132
|
+
params.id; // number — coerced, or the navigation failed
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
createBrowserHistory(),
|
|
138
|
+
(matched) => renderUser(matched)
|
|
139
|
+
);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- No `params` schema → behavior unchanged: the raw string map flows through
|
|
143
|
+
- The parse runs per level (shallow → deep): a level's schema validates the params merged up to it; a deeper schema sees the (possibly coerced) output of the shallower ones
|
|
144
|
+
- A rejected validation fails the resolution through the `errorHandler` channel with a `ParamsError` (a `NativeRouterError`) carrying the raw `params` and the reported `issues` — the same route a search-schema failure takes
|
|
145
|
+
- `parseParams`/`parseParamsSync` are exported for custom `resolveView` implementations (the async/sync flavors mirror `parseSearch`/`parseSearchSync`)
|
|
146
|
+
|
|
115
147
|
## Design principles
|
|
116
148
|
|
|
117
149
|
**Navigation semantics follow the browser** — native-router aligns with browser-native navigation semantics, not with what other SPA routers happen to do. Every navigation API decision is measured against that yardstick; "a popular router has it" is not, by itself, a reason to follow. These are deliberate choices, not bugs to fix.
|
package/dist/index.cjs
CHANGED
|
@@ -39,12 +39,159 @@ class SearchError extends NativeRouterError {
|
|
|
39
39
|
this.issues = issues;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Thrown when a route {@link BaseRoute.params params schema} rejects the
|
|
45
|
+
* merged path params. Issues are formatted like {@link SearchError}'s —
|
|
46
|
+
* `path: message` pairs joined with `; `, e.g.
|
|
47
|
+
* `Invalid path params "/users/abc": id: expected a number`.
|
|
48
|
+
*/
|
|
49
|
+
class ParamsError extends NativeRouterError {
|
|
50
|
+
/** The raw params object that failed validation. */
|
|
51
|
+
params;
|
|
52
|
+
|
|
53
|
+
/** The issues reported by the schema. */
|
|
54
|
+
issues;
|
|
55
|
+
constructor(params, issues) {
|
|
56
|
+
super(`Invalid path params "${JSON.stringify(params)}": ${issues.map(({
|
|
57
|
+
message,
|
|
58
|
+
path
|
|
59
|
+
}) => `${formatIssuePath(path)}${message}`).join('; ')}`);
|
|
60
|
+
this.params = params;
|
|
61
|
+
this.issues = issues;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
42
64
|
function formatIssuePath(path) {
|
|
43
65
|
if (!path?.length) return '';
|
|
44
66
|
const keys = path.map(segment => typeof segment === 'object' ? String(segment.key) : String(segment));
|
|
45
67
|
return `${keys.join('.')}: `;
|
|
46
68
|
}
|
|
47
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
72
|
+
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
73
|
+
* single-valued keys are strings, keys repeated in the query string are
|
|
74
|
+
* arrays of their values. An empty search is `{}`.
|
|
75
|
+
*
|
|
76
|
+
* This is also the degraded shape every search API falls back to when no
|
|
77
|
+
* schema is given.
|
|
78
|
+
* @group Methods
|
|
79
|
+
* @category Route
|
|
80
|
+
* @param search the raw `location.search` string, with or without `?`
|
|
81
|
+
* @returns the input object for schema validation
|
|
82
|
+
*/
|
|
83
|
+
function parseSearchInput(search) {
|
|
84
|
+
const input = {};
|
|
85
|
+
// eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled
|
|
86
|
+
new URLSearchParams(search).forEach((value, key) => {
|
|
87
|
+
const prev = input[key];
|
|
88
|
+
if (prev === undefined) {
|
|
89
|
+
input[key] = value;
|
|
90
|
+
} else if (Array.isArray(prev)) {
|
|
91
|
+
prev.push(value);
|
|
92
|
+
} else {
|
|
93
|
+
input[key] = [prev, value];
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return input;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
101
|
+
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
102
|
+
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
103
|
+
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
104
|
+
*
|
|
105
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
106
|
+
*
|
|
107
|
+
* @group Methods
|
|
108
|
+
* @category Route
|
|
109
|
+
* @param schema the search schema
|
|
110
|
+
* @param search the raw `location.search` string
|
|
111
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
112
|
+
* @throws {SearchError} when the schema reports issues
|
|
113
|
+
*/
|
|
114
|
+
async function parseSearch(schema, search) {
|
|
115
|
+
const result = await schema['~standard'].validate(parseSearchInput(search));
|
|
116
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
117
|
+
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
118
|
+
// degrades to `unknown`.
|
|
119
|
+
return result.value;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
124
|
+
* `useSearch` of `@native-router/react`) and route guards.
|
|
125
|
+
*
|
|
126
|
+
* @group Methods
|
|
127
|
+
* @category Route
|
|
128
|
+
* @param schema the search schema — must validate synchronously
|
|
129
|
+
* @param search the raw `location.search` string
|
|
130
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
131
|
+
* @throws {SearchError} when the schema reports issues
|
|
132
|
+
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
133
|
+
* for async schemas instead
|
|
134
|
+
*/
|
|
135
|
+
function parseSearchSync(schema, search) {
|
|
136
|
+
const result = schema['~standard'].validate(parseSearchInput(search));
|
|
137
|
+
if (isThenable(result)) {
|
|
138
|
+
throw new Error('The search schema validates asynchronously; parse it during resolve ' + '(parseSearch) instead of synchronously');
|
|
139
|
+
}
|
|
140
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
141
|
+
// See parseSearch for the cast rationale.
|
|
142
|
+
return result.value;
|
|
143
|
+
}
|
|
144
|
+
function isThenable(value) {
|
|
145
|
+
return typeof value?.then === 'function';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate the merged path params of a route level with a
|
|
150
|
+
* {@link StandardSchemaV1} schema — any zod/valibot/arktype schema
|
|
151
|
+
* works, no hard dependency. Params arrive as the plain string map the
|
|
152
|
+
* matcher extracted(see `mergeMatchedParams`), so schemas can coerce
|
|
153
|
+
* (`'7'` → `7`) and normalize along the way.
|
|
154
|
+
*
|
|
155
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
156
|
+
*
|
|
157
|
+
* @group Methods
|
|
158
|
+
* @category Route
|
|
159
|
+
* @param schema the params schema
|
|
160
|
+
* @param params the merged raw params of the level
|
|
161
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
162
|
+
* @throws {ParamsError} when the schema reports issues
|
|
163
|
+
*/
|
|
164
|
+
async function parseParams(schema, params) {
|
|
165
|
+
const result = await schema['~standard'].validate(params);
|
|
166
|
+
if (result.issues) throw new ParamsError(params, result.issues);
|
|
167
|
+
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
168
|
+
// degrades to `unknown`.
|
|
169
|
+
return result.value;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Synchronous flavor of {@link parseParams}, for render-time reads and
|
|
174
|
+
* custom `resolveView` implementations.
|
|
175
|
+
*
|
|
176
|
+
* @group Methods
|
|
177
|
+
* @category Route
|
|
178
|
+
* @param schema the params schema — must validate synchronously
|
|
179
|
+
* @param params the merged raw params of the level
|
|
180
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
181
|
+
* @throws {ParamsError} when the schema reports issues
|
|
182
|
+
* @throws when the schema validates asynchronously; use {@link parseParams}
|
|
183
|
+
* for async schemas instead
|
|
184
|
+
*/
|
|
185
|
+
function parseParamsSync(schema, params) {
|
|
186
|
+
const result = schema['~standard'].validate(params);
|
|
187
|
+
if (isThenable(result)) {
|
|
188
|
+
throw new Error('The params schema validates asynchronously; parse it during resolve ' + '(parseParams) instead of synchronously');
|
|
189
|
+
}
|
|
190
|
+
if (result.issues) throw new ParamsError(params, result.issues);
|
|
191
|
+
// See parseParams for the cast rationale.
|
|
192
|
+
return result.value;
|
|
193
|
+
}
|
|
194
|
+
|
|
48
195
|
const DEFAULT_MAX_STACK_DEPTH = 100;
|
|
49
196
|
|
|
50
197
|
/** Max redirects followed by {@link resolveEntry} before giving up. */
|
|
@@ -327,6 +474,16 @@ function resolveTo(router, to, state) {
|
|
|
327
474
|
* aborts — their resolution may be shared, so cancelling it on behalf of
|
|
328
475
|
* one consumer is not sound yet.
|
|
329
476
|
*
|
|
477
|
+
* A guard's context also carries the level's parsed
|
|
478
|
+
* {@link GuardContext.search search}: the {@link BaseRoute.search schema}
|
|
479
|
+
* output(its validation failure rejects this resolution with a
|
|
480
|
+
* `SearchError`), or the degraded input without a schema. Its
|
|
481
|
+
* {@link GuardContext.params params} are likewise the merged raw string
|
|
482
|
+
* map unless some level declares a {@link BaseRoute.params params
|
|
483
|
+
* schema} — the deepest schema seen so far has already upgraded them to
|
|
484
|
+
* its output(its validation failure rides the same channel with a
|
|
485
|
+
* `ParamsError`).
|
|
486
|
+
*
|
|
330
487
|
* @group Methods
|
|
331
488
|
* @category Router
|
|
332
489
|
* @param router router instance
|
|
@@ -358,20 +515,67 @@ async function resolveEntry(router, location, opts) {
|
|
|
358
515
|
};
|
|
359
516
|
}
|
|
360
517
|
let redirected = false;
|
|
518
|
+
// Raw params merged level-by-level; a level with a `params` schema
|
|
519
|
+
// upgrades them to the schema output before its guard runs, so
|
|
520
|
+
// guards of deeper levels see coerced params of the whole prefix.
|
|
521
|
+
let params = {};
|
|
361
522
|
for (let i = 0; i < matched.length; i++) {
|
|
362
523
|
const {
|
|
363
524
|
route
|
|
364
525
|
} = matched[i];
|
|
526
|
+
params = {
|
|
527
|
+
...params,
|
|
528
|
+
...matched[i].params
|
|
529
|
+
};
|
|
530
|
+
// The level's params schema runs before its guard, so the guard
|
|
531
|
+
// sees the coerced output. A validation failure fails the
|
|
532
|
+
// resolution through the task's errorHandler channel — the same
|
|
533
|
+
// route a search-schema failure takes — instead of rejecting this
|
|
534
|
+
// entry, which preload consumers share.
|
|
535
|
+
if (route.params) {
|
|
536
|
+
try {
|
|
537
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
538
|
+
params = await parseParams(route.params, params);
|
|
539
|
+
} catch (e) {
|
|
540
|
+
return {
|
|
541
|
+
location,
|
|
542
|
+
task: Promise.reject(e).catch(errorHandler)
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
365
546
|
// `redirect` wins over `beforeLoad`; a non-empty string target
|
|
366
547
|
// restarts the resolution at the redirected location.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
548
|
+
let target = route.redirect;
|
|
549
|
+
if (!target && route.beforeLoad) {
|
|
550
|
+
// The level's search schema runs before its guard, so the guard
|
|
551
|
+
// sees the parsed output(degraded input without a schema). A
|
|
552
|
+
// validation failure fails the resolution through the task's
|
|
553
|
+
// errorHandler channel — the same route a data-phase search
|
|
554
|
+
// error takes — instead of rejecting this entry, which preload
|
|
555
|
+
// consumers share.
|
|
556
|
+
let search;
|
|
557
|
+
if (route.search) {
|
|
558
|
+
try {
|
|
559
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
560
|
+
search = await parseSearch(route.search, location.search);
|
|
561
|
+
} catch (e) {
|
|
562
|
+
return {
|
|
563
|
+
location,
|
|
564
|
+
task: Promise.reject(e).catch(errorHandler)
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
} else {
|
|
568
|
+
search = parseSearchInput(location.search);
|
|
569
|
+
}
|
|
570
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
571
|
+
target = await route.beforeLoad({
|
|
572
|
+
router,
|
|
573
|
+
location,
|
|
574
|
+
params,
|
|
575
|
+
signal,
|
|
576
|
+
search
|
|
577
|
+
});
|
|
578
|
+
}
|
|
375
579
|
if (target) {
|
|
376
580
|
location = toLocation(router, target, location.state);
|
|
377
581
|
redirected = true;
|
|
@@ -1076,86 +1280,9 @@ function getParams(router) {
|
|
|
1076
1280
|
return mergeMatchedParams(match(router, location.pathname) ?? []);
|
|
1077
1281
|
}
|
|
1078
1282
|
|
|
1079
|
-
/**
|
|
1080
|
-
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
1081
|
-
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
1082
|
-
* single-valued keys are strings, keys repeated in the query string are
|
|
1083
|
-
* arrays of their values. An empty search is `{}`.
|
|
1084
|
-
*
|
|
1085
|
-
* This is also the degraded shape every search API falls back to when no
|
|
1086
|
-
* schema is given.
|
|
1087
|
-
* @group Methods
|
|
1088
|
-
* @category Route
|
|
1089
|
-
* @param search the raw `location.search` string, with or without `?`
|
|
1090
|
-
* @returns the input object for schema validation
|
|
1091
|
-
*/
|
|
1092
|
-
function parseSearchInput(search) {
|
|
1093
|
-
const input = {};
|
|
1094
|
-
// eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled
|
|
1095
|
-
new URLSearchParams(search).forEach((value, key) => {
|
|
1096
|
-
const prev = input[key];
|
|
1097
|
-
if (prev === undefined) {
|
|
1098
|
-
input[key] = value;
|
|
1099
|
-
} else if (Array.isArray(prev)) {
|
|
1100
|
-
prev.push(value);
|
|
1101
|
-
} else {
|
|
1102
|
-
input[key] = [prev, value];
|
|
1103
|
-
}
|
|
1104
|
-
});
|
|
1105
|
-
return input;
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
/**
|
|
1109
|
-
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
1110
|
-
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
1111
|
-
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
1112
|
-
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
1113
|
-
*
|
|
1114
|
-
* Async schemas(`validate` returning a promise) are awaited.
|
|
1115
|
-
*
|
|
1116
|
-
* @group Methods
|
|
1117
|
-
* @category Route
|
|
1118
|
-
* @param schema the search schema
|
|
1119
|
-
* @param search the raw `location.search` string
|
|
1120
|
-
* @returns the parsed(and possibly coerced) output of the schema
|
|
1121
|
-
* @throws {SearchError} when the schema reports issues
|
|
1122
|
-
*/
|
|
1123
|
-
async function parseSearch(schema, search) {
|
|
1124
|
-
const result = await schema['~standard'].validate(parseSearchInput(search));
|
|
1125
|
-
if (result.issues) throw new SearchError(search, result.issues);
|
|
1126
|
-
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
1127
|
-
// degrades to `unknown`.
|
|
1128
|
-
return result.value;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
/**
|
|
1132
|
-
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
1133
|
-
* `useSearch` of `@native-router/react`) and route guards.
|
|
1134
|
-
*
|
|
1135
|
-
* @group Methods
|
|
1136
|
-
* @category Route
|
|
1137
|
-
* @param schema the search schema — must validate synchronously
|
|
1138
|
-
* @param search the raw `location.search` string
|
|
1139
|
-
* @returns the parsed(and possibly coerced) output of the schema
|
|
1140
|
-
* @throws {SearchError} when the schema reports issues
|
|
1141
|
-
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
1142
|
-
* for async schemas instead
|
|
1143
|
-
*/
|
|
1144
|
-
function parseSearchSync(schema, search) {
|
|
1145
|
-
const result = schema['~standard'].validate(parseSearchInput(search));
|
|
1146
|
-
if (isThenable(result)) {
|
|
1147
|
-
throw new Error('The search schema validates asynchronously; parse it during resolve ' + '(parseSearch) instead of synchronously');
|
|
1148
|
-
}
|
|
1149
|
-
if (result.issues) throw new SearchError(search, result.issues);
|
|
1150
|
-
// See parseSearch for the cast rationale.
|
|
1151
|
-
return result.value;
|
|
1152
|
-
}
|
|
1153
|
-
function isThenable(value) {
|
|
1154
|
-
return typeof value?.then === 'function';
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
1283
|
exports.NativeRouterError = NativeRouterError;
|
|
1158
1284
|
exports.NotFoundError = NotFoundError;
|
|
1285
|
+
exports.ParamsError = ParamsError;
|
|
1159
1286
|
exports.RedirectLoopError = RedirectLoopError;
|
|
1160
1287
|
exports.SearchError = SearchError;
|
|
1161
1288
|
exports.back = back;
|
|
@@ -1175,6 +1302,8 @@ exports.listen = listen;
|
|
|
1175
1302
|
exports.match = match;
|
|
1176
1303
|
exports.mergeMatchedParams = mergeMatchedParams;
|
|
1177
1304
|
exports.navigate = navigate;
|
|
1305
|
+
exports.parseParams = parseParams;
|
|
1306
|
+
exports.parseParamsSync = parseParamsSync;
|
|
1178
1307
|
exports.parseSearch = parseSearch;
|
|
1179
1308
|
exports.parseSearchInput = parseSearchInput;
|
|
1180
1309
|
exports.parseSearchSync = parseSearchSync;
|
package/dist/index.mjs
CHANGED
|
@@ -37,12 +37,159 @@ class SearchError extends NativeRouterError {
|
|
|
37
37
|
this.issues = issues;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when a route {@link BaseRoute.params params schema} rejects the
|
|
43
|
+
* merged path params. Issues are formatted like {@link SearchError}'s —
|
|
44
|
+
* `path: message` pairs joined with `; `, e.g.
|
|
45
|
+
* `Invalid path params "/users/abc": id: expected a number`.
|
|
46
|
+
*/
|
|
47
|
+
class ParamsError extends NativeRouterError {
|
|
48
|
+
/** The raw params object that failed validation. */
|
|
49
|
+
params;
|
|
50
|
+
|
|
51
|
+
/** The issues reported by the schema. */
|
|
52
|
+
issues;
|
|
53
|
+
constructor(params, issues) {
|
|
54
|
+
super(`Invalid path params "${JSON.stringify(params)}": ${issues.map(({
|
|
55
|
+
message,
|
|
56
|
+
path
|
|
57
|
+
}) => `${formatIssuePath(path)}${message}`).join('; ')}`);
|
|
58
|
+
this.params = params;
|
|
59
|
+
this.issues = issues;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
40
62
|
function formatIssuePath(path) {
|
|
41
63
|
if (!path?.length) return '';
|
|
42
64
|
const keys = path.map(segment => typeof segment === 'object' ? String(segment.key) : String(segment));
|
|
43
65
|
return `${keys.join('.')}: `;
|
|
44
66
|
}
|
|
45
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
70
|
+
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
71
|
+
* single-valued keys are strings, keys repeated in the query string are
|
|
72
|
+
* arrays of their values. An empty search is `{}`.
|
|
73
|
+
*
|
|
74
|
+
* This is also the degraded shape every search API falls back to when no
|
|
75
|
+
* schema is given.
|
|
76
|
+
* @group Methods
|
|
77
|
+
* @category Route
|
|
78
|
+
* @param search the raw `location.search` string, with or without `?`
|
|
79
|
+
* @returns the input object for schema validation
|
|
80
|
+
*/
|
|
81
|
+
function parseSearchInput(search) {
|
|
82
|
+
const input = {};
|
|
83
|
+
// eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled
|
|
84
|
+
new URLSearchParams(search).forEach((value, key) => {
|
|
85
|
+
const prev = input[key];
|
|
86
|
+
if (prev === undefined) {
|
|
87
|
+
input[key] = value;
|
|
88
|
+
} else if (Array.isArray(prev)) {
|
|
89
|
+
prev.push(value);
|
|
90
|
+
} else {
|
|
91
|
+
input[key] = [prev, value];
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return input;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
99
|
+
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
100
|
+
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
101
|
+
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
102
|
+
*
|
|
103
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
104
|
+
*
|
|
105
|
+
* @group Methods
|
|
106
|
+
* @category Route
|
|
107
|
+
* @param schema the search schema
|
|
108
|
+
* @param search the raw `location.search` string
|
|
109
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
110
|
+
* @throws {SearchError} when the schema reports issues
|
|
111
|
+
*/
|
|
112
|
+
async function parseSearch(schema, search) {
|
|
113
|
+
const result = await schema['~standard'].validate(parseSearchInput(search));
|
|
114
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
115
|
+
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
116
|
+
// degrades to `unknown`.
|
|
117
|
+
return result.value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
122
|
+
* `useSearch` of `@native-router/react`) and route guards.
|
|
123
|
+
*
|
|
124
|
+
* @group Methods
|
|
125
|
+
* @category Route
|
|
126
|
+
* @param schema the search schema — must validate synchronously
|
|
127
|
+
* @param search the raw `location.search` string
|
|
128
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
129
|
+
* @throws {SearchError} when the schema reports issues
|
|
130
|
+
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
131
|
+
* for async schemas instead
|
|
132
|
+
*/
|
|
133
|
+
function parseSearchSync(schema, search) {
|
|
134
|
+
const result = schema['~standard'].validate(parseSearchInput(search));
|
|
135
|
+
if (isThenable(result)) {
|
|
136
|
+
throw new Error('The search schema validates asynchronously; parse it during resolve ' + '(parseSearch) instead of synchronously');
|
|
137
|
+
}
|
|
138
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
139
|
+
// See parseSearch for the cast rationale.
|
|
140
|
+
return result.value;
|
|
141
|
+
}
|
|
142
|
+
function isThenable(value) {
|
|
143
|
+
return typeof value?.then === 'function';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Validate the merged path params of a route level with a
|
|
148
|
+
* {@link StandardSchemaV1} schema — any zod/valibot/arktype schema
|
|
149
|
+
* works, no hard dependency. Params arrive as the plain string map the
|
|
150
|
+
* matcher extracted(see `mergeMatchedParams`), so schemas can coerce
|
|
151
|
+
* (`'7'` → `7`) and normalize along the way.
|
|
152
|
+
*
|
|
153
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
154
|
+
*
|
|
155
|
+
* @group Methods
|
|
156
|
+
* @category Route
|
|
157
|
+
* @param schema the params schema
|
|
158
|
+
* @param params the merged raw params of the level
|
|
159
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
160
|
+
* @throws {ParamsError} when the schema reports issues
|
|
161
|
+
*/
|
|
162
|
+
async function parseParams(schema, params) {
|
|
163
|
+
const result = await schema['~standard'].validate(params);
|
|
164
|
+
if (result.issues) throw new ParamsError(params, result.issues);
|
|
165
|
+
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
166
|
+
// degrades to `unknown`.
|
|
167
|
+
return result.value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Synchronous flavor of {@link parseParams}, for render-time reads and
|
|
172
|
+
* custom `resolveView` implementations.
|
|
173
|
+
*
|
|
174
|
+
* @group Methods
|
|
175
|
+
* @category Route
|
|
176
|
+
* @param schema the params schema — must validate synchronously
|
|
177
|
+
* @param params the merged raw params of the level
|
|
178
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
179
|
+
* @throws {ParamsError} when the schema reports issues
|
|
180
|
+
* @throws when the schema validates asynchronously; use {@link parseParams}
|
|
181
|
+
* for async schemas instead
|
|
182
|
+
*/
|
|
183
|
+
function parseParamsSync(schema, params) {
|
|
184
|
+
const result = schema['~standard'].validate(params);
|
|
185
|
+
if (isThenable(result)) {
|
|
186
|
+
throw new Error('The params schema validates asynchronously; parse it during resolve ' + '(parseParams) instead of synchronously');
|
|
187
|
+
}
|
|
188
|
+
if (result.issues) throw new ParamsError(params, result.issues);
|
|
189
|
+
// See parseParams for the cast rationale.
|
|
190
|
+
return result.value;
|
|
191
|
+
}
|
|
192
|
+
|
|
46
193
|
const DEFAULT_MAX_STACK_DEPTH = 100;
|
|
47
194
|
|
|
48
195
|
/** Max redirects followed by {@link resolveEntry} before giving up. */
|
|
@@ -325,6 +472,16 @@ function resolveTo(router, to, state) {
|
|
|
325
472
|
* aborts — their resolution may be shared, so cancelling it on behalf of
|
|
326
473
|
* one consumer is not sound yet.
|
|
327
474
|
*
|
|
475
|
+
* A guard's context also carries the level's parsed
|
|
476
|
+
* {@link GuardContext.search search}: the {@link BaseRoute.search schema}
|
|
477
|
+
* output(its validation failure rejects this resolution with a
|
|
478
|
+
* `SearchError`), or the degraded input without a schema. Its
|
|
479
|
+
* {@link GuardContext.params params} are likewise the merged raw string
|
|
480
|
+
* map unless some level declares a {@link BaseRoute.params params
|
|
481
|
+
* schema} — the deepest schema seen so far has already upgraded them to
|
|
482
|
+
* its output(its validation failure rides the same channel with a
|
|
483
|
+
* `ParamsError`).
|
|
484
|
+
*
|
|
328
485
|
* @group Methods
|
|
329
486
|
* @category Router
|
|
330
487
|
* @param router router instance
|
|
@@ -356,20 +513,67 @@ async function resolveEntry(router, location, opts) {
|
|
|
356
513
|
};
|
|
357
514
|
}
|
|
358
515
|
let redirected = false;
|
|
516
|
+
// Raw params merged level-by-level; a level with a `params` schema
|
|
517
|
+
// upgrades them to the schema output before its guard runs, so
|
|
518
|
+
// guards of deeper levels see coerced params of the whole prefix.
|
|
519
|
+
let params = {};
|
|
359
520
|
for (let i = 0; i < matched.length; i++) {
|
|
360
521
|
const {
|
|
361
522
|
route
|
|
362
523
|
} = matched[i];
|
|
524
|
+
params = {
|
|
525
|
+
...params,
|
|
526
|
+
...matched[i].params
|
|
527
|
+
};
|
|
528
|
+
// The level's params schema runs before its guard, so the guard
|
|
529
|
+
// sees the coerced output. A validation failure fails the
|
|
530
|
+
// resolution through the task's errorHandler channel — the same
|
|
531
|
+
// route a search-schema failure takes — instead of rejecting this
|
|
532
|
+
// entry, which preload consumers share.
|
|
533
|
+
if (route.params) {
|
|
534
|
+
try {
|
|
535
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
536
|
+
params = await parseParams(route.params, params);
|
|
537
|
+
} catch (e) {
|
|
538
|
+
return {
|
|
539
|
+
location,
|
|
540
|
+
task: Promise.reject(e).catch(errorHandler)
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
363
544
|
// `redirect` wins over `beforeLoad`; a non-empty string target
|
|
364
545
|
// restarts the resolution at the redirected location.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
546
|
+
let target = route.redirect;
|
|
547
|
+
if (!target && route.beforeLoad) {
|
|
548
|
+
// The level's search schema runs before its guard, so the guard
|
|
549
|
+
// sees the parsed output(degraded input without a schema). A
|
|
550
|
+
// validation failure fails the resolution through the task's
|
|
551
|
+
// errorHandler channel — the same route a data-phase search
|
|
552
|
+
// error takes — instead of rejecting this entry, which preload
|
|
553
|
+
// consumers share.
|
|
554
|
+
let search;
|
|
555
|
+
if (route.search) {
|
|
556
|
+
try {
|
|
557
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
558
|
+
search = await parseSearch(route.search, location.search);
|
|
559
|
+
} catch (e) {
|
|
560
|
+
return {
|
|
561
|
+
location,
|
|
562
|
+
task: Promise.reject(e).catch(errorHandler)
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
search = parseSearchInput(location.search);
|
|
567
|
+
}
|
|
568
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
569
|
+
target = await route.beforeLoad({
|
|
570
|
+
router,
|
|
571
|
+
location,
|
|
572
|
+
params,
|
|
573
|
+
signal,
|
|
574
|
+
search
|
|
575
|
+
});
|
|
576
|
+
}
|
|
373
577
|
if (target) {
|
|
374
578
|
location = toLocation(router, target, location.state);
|
|
375
579
|
redirected = true;
|
|
@@ -1074,82 +1278,4 @@ function getParams(router) {
|
|
|
1074
1278
|
return mergeMatchedParams(match(router, location.pathname) ?? []);
|
|
1075
1279
|
}
|
|
1076
1280
|
|
|
1077
|
-
|
|
1078
|
-
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
1079
|
-
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
1080
|
-
* single-valued keys are strings, keys repeated in the query string are
|
|
1081
|
-
* arrays of their values. An empty search is `{}`.
|
|
1082
|
-
*
|
|
1083
|
-
* This is also the degraded shape every search API falls back to when no
|
|
1084
|
-
* schema is given.
|
|
1085
|
-
* @group Methods
|
|
1086
|
-
* @category Route
|
|
1087
|
-
* @param search the raw `location.search` string, with or without `?`
|
|
1088
|
-
* @returns the input object for schema validation
|
|
1089
|
-
*/
|
|
1090
|
-
function parseSearchInput(search) {
|
|
1091
|
-
const input = {};
|
|
1092
|
-
// eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled
|
|
1093
|
-
new URLSearchParams(search).forEach((value, key) => {
|
|
1094
|
-
const prev = input[key];
|
|
1095
|
-
if (prev === undefined) {
|
|
1096
|
-
input[key] = value;
|
|
1097
|
-
} else if (Array.isArray(prev)) {
|
|
1098
|
-
prev.push(value);
|
|
1099
|
-
} else {
|
|
1100
|
-
input[key] = [prev, value];
|
|
1101
|
-
}
|
|
1102
|
-
});
|
|
1103
|
-
return input;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
1108
|
-
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
1109
|
-
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
1110
|
-
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
1111
|
-
*
|
|
1112
|
-
* Async schemas(`validate` returning a promise) are awaited.
|
|
1113
|
-
*
|
|
1114
|
-
* @group Methods
|
|
1115
|
-
* @category Route
|
|
1116
|
-
* @param schema the search schema
|
|
1117
|
-
* @param search the raw `location.search` string
|
|
1118
|
-
* @returns the parsed(and possibly coerced) output of the schema
|
|
1119
|
-
* @throws {SearchError} when the schema reports issues
|
|
1120
|
-
*/
|
|
1121
|
-
async function parseSearch(schema, search) {
|
|
1122
|
-
const result = await schema['~standard'].validate(parseSearchInput(search));
|
|
1123
|
-
if (result.issues) throw new SearchError(search, result.issues);
|
|
1124
|
-
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
1125
|
-
// degrades to `unknown`.
|
|
1126
|
-
return result.value;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
/**
|
|
1130
|
-
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
1131
|
-
* `useSearch` of `@native-router/react`) and route guards.
|
|
1132
|
-
*
|
|
1133
|
-
* @group Methods
|
|
1134
|
-
* @category Route
|
|
1135
|
-
* @param schema the search schema — must validate synchronously
|
|
1136
|
-
* @param search the raw `location.search` string
|
|
1137
|
-
* @returns the parsed(and possibly coerced) output of the schema
|
|
1138
|
-
* @throws {SearchError} when the schema reports issues
|
|
1139
|
-
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
1140
|
-
* for async schemas instead
|
|
1141
|
-
*/
|
|
1142
|
-
function parseSearchSync(schema, search) {
|
|
1143
|
-
const result = schema['~standard'].validate(parseSearchInput(search));
|
|
1144
|
-
if (isThenable(result)) {
|
|
1145
|
-
throw new Error('The search schema validates asynchronously; parse it during resolve ' + '(parseSearch) instead of synchronously');
|
|
1146
|
-
}
|
|
1147
|
-
if (result.issues) throw new SearchError(search, result.issues);
|
|
1148
|
-
// See parseSearch for the cast rationale.
|
|
1149
|
-
return result.value;
|
|
1150
|
-
}
|
|
1151
|
-
function isThenable(value) {
|
|
1152
|
-
return typeof value?.then === 'function';
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
export { NativeRouterError, NotFoundError, RedirectLoopError, SearchError, back, cancel, commit, commitReplace, create, createHref, forward, getCurrentView, getLocation, getParams, go, initHistoryStack, invalidate, listen, match, mergeMatchedParams, navigate, parseSearch, parseSearchInput, parseSearchSync, preload, refresh, resolve, resolveEntry, resolveTo, setBlocker, setOptions, toLocation };
|
|
1281
|
+
export { NativeRouterError, NotFoundError, ParamsError, RedirectLoopError, SearchError, back, cancel, commit, commitReplace, create, createHref, forward, getCurrentView, getLocation, getParams, go, initHistoryStack, invalidate, listen, match, mergeMatchedParams, navigate, parseParams, parseParamsSync, parseSearch, parseSearchInput, parseSearchSync, preload, refresh, resolve, resolveEntry, resolveTo, setBlocker, setOptions, toLocation };
|
package/dist/types/errors.d.ts
CHANGED
|
@@ -20,3 +20,16 @@ export declare class SearchError extends NativeRouterError {
|
|
|
20
20
|
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
21
21
|
constructor(search: string, issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when a route {@link BaseRoute.params params schema} rejects the
|
|
25
|
+
* merged path params. Issues are formatted like {@link SearchError}'s —
|
|
26
|
+
* `path: message` pairs joined with `; `, e.g.
|
|
27
|
+
* `Invalid path params "/users/abc": id: expected a number`.
|
|
28
|
+
*/
|
|
29
|
+
export declare class ParamsError extends NativeRouterError {
|
|
30
|
+
/** The raw params object that failed validation. */
|
|
31
|
+
readonly params: Record<string, string>;
|
|
32
|
+
/** The issues reported by the schema. */
|
|
33
|
+
readonly issues: ReadonlyArray<StandardSchemaV1.Issue>;
|
|
34
|
+
constructor(params: Record<string, string>, issues: ReadonlyArray<StandardSchemaV1.Issue>);
|
|
35
|
+
}
|
package/dist/types/router.d.ts
CHANGED
|
@@ -100,6 +100,16 @@ export declare function resolveTo<R extends BaseRoute = BaseRoute, V = any>(rout
|
|
|
100
100
|
* aborts — their resolution may be shared, so cancelling it on behalf of
|
|
101
101
|
* one consumer is not sound yet.
|
|
102
102
|
*
|
|
103
|
+
* A guard's context also carries the level's parsed
|
|
104
|
+
* {@link GuardContext.search search}: the {@link BaseRoute.search schema}
|
|
105
|
+
* output(its validation failure rejects this resolution with a
|
|
106
|
+
* `SearchError`), or the degraded input without a schema. Its
|
|
107
|
+
* {@link GuardContext.params params} are likewise the merged raw string
|
|
108
|
+
* map unless some level declares a {@link BaseRoute.params params
|
|
109
|
+
* schema} — the deepest schema seen so far has already upgraded them to
|
|
110
|
+
* its output(its validation failure rides the same channel with a
|
|
111
|
+
* `ParamsError`).
|
|
112
|
+
*
|
|
103
113
|
* @group Methods
|
|
104
114
|
* @category Router
|
|
105
115
|
* @param router router instance
|
package/dist/types/search.d.ts
CHANGED
|
@@ -43,3 +43,34 @@ export declare function parseSearch<S extends StandardSchemaV1>(schema: S, searc
|
|
|
43
43
|
* for async schemas instead
|
|
44
44
|
*/
|
|
45
45
|
export declare function parseSearchSync<S extends StandardSchemaV1>(schema: S, search: string): SearchOutputOf<S>;
|
|
46
|
+
/**
|
|
47
|
+
* Validate the merged path params of a route level with a
|
|
48
|
+
* {@link StandardSchemaV1} schema — any zod/valibot/arktype schema
|
|
49
|
+
* works, no hard dependency. Params arrive as the plain string map the
|
|
50
|
+
* matcher extracted(see `mergeMatchedParams`), so schemas can coerce
|
|
51
|
+
* (`'7'` → `7`) and normalize along the way.
|
|
52
|
+
*
|
|
53
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
54
|
+
*
|
|
55
|
+
* @group Methods
|
|
56
|
+
* @category Route
|
|
57
|
+
* @param schema the params schema
|
|
58
|
+
* @param params the merged raw params of the level
|
|
59
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
60
|
+
* @throws {ParamsError} when the schema reports issues
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseParams<S extends StandardSchemaV1>(schema: S, params: Record<string, string>): Promise<SearchOutputOf<S>>;
|
|
63
|
+
/**
|
|
64
|
+
* Synchronous flavor of {@link parseParams}, for render-time reads and
|
|
65
|
+
* custom `resolveView` implementations.
|
|
66
|
+
*
|
|
67
|
+
* @group Methods
|
|
68
|
+
* @category Route
|
|
69
|
+
* @param schema the params schema — must validate synchronously
|
|
70
|
+
* @param params the merged raw params of the level
|
|
71
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
72
|
+
* @throws {ParamsError} when the schema reports issues
|
|
73
|
+
* @throws when the schema validates asynchronously; use {@link parseParams}
|
|
74
|
+
* for async schemas instead
|
|
75
|
+
*/
|
|
76
|
+
export declare function parseParamsSync<S extends StandardSchemaV1>(schema: S, params: Record<string, string>): SearchOutputOf<S>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -143,10 +143,26 @@ export type ExtractPathParams<P extends string> = P extends `${infer Head}/${inf
|
|
|
143
143
|
* `params` are accumulated from the root level down to the level that
|
|
144
144
|
* owns the guard, so a guard only sees params of itself and its parents.
|
|
145
145
|
*/
|
|
146
|
-
export type GuardContext<R extends BaseRoute = BaseRoute> = {
|
|
146
|
+
export type GuardContext<R extends BaseRoute = BaseRoute, S = unknown> = {
|
|
147
147
|
router: RouterInstance<R>;
|
|
148
148
|
location: Location;
|
|
149
|
+
/**
|
|
150
|
+
* The merged params of this level and its parents: when any level
|
|
151
|
+
* declares a {@link BaseRoute.params params schema}, the merged raw
|
|
152
|
+
* params are parsed through the deepest matching schema before the
|
|
153
|
+
* guard runs; without schemas the raw string map the matcher
|
|
154
|
+
* extracted.
|
|
155
|
+
*/
|
|
149
156
|
params: Record<string, string>;
|
|
157
|
+
/**
|
|
158
|
+
* The search the guard sees: the route's {@link BaseRoute.search search
|
|
159
|
+
* schema} output(parsed and validated before the guard runs), or the
|
|
160
|
+
* degraded {@link SearchInput} when the route declares no schema. The
|
|
161
|
+
* loose default types it `unknown` — narrow it in the guard, or let a
|
|
162
|
+
* typed route table(see `createRoutes` of `@native-router/react`)
|
|
163
|
+
* derive it from the schema.
|
|
164
|
+
*/
|
|
165
|
+
search: S;
|
|
150
166
|
/**
|
|
151
167
|
* Aborted when this navigation is superseded by a newer one or
|
|
152
168
|
* cancelled(see {@link RouterInstance.cancelAll cancel}); pass it to
|
|
@@ -171,9 +187,25 @@ export type BaseRoute<T = any> = {
|
|
|
171
187
|
* navigation error.
|
|
172
188
|
*/
|
|
173
189
|
search?: StandardSchemaV1;
|
|
190
|
+
/**
|
|
191
|
+
* Optional Standard Schema validator of the merged path params this
|
|
192
|
+
* level and its parents contribute(see `mergeMatchedParams`). The core
|
|
193
|
+
* runs it in {@link resolveEntry} after matching and before the level's
|
|
194
|
+
* `beforeLoad`, so guards and loaders see coerced params(e.g. `:id`
|
|
195
|
+
* as a number) instead of raw strings; a validation failure fails the
|
|
196
|
+
* resolve like any other navigation error(via `ParamsError`).
|
|
197
|
+
*
|
|
198
|
+
* Omit it and the params stay the raw `Record<string, string>` the
|
|
199
|
+
* matcher extracted — behavior is unchanged.
|
|
200
|
+
*/
|
|
201
|
+
params?: StandardSchemaV1;
|
|
174
202
|
/**
|
|
175
203
|
* Route guard invoked before the view resolves. Return a path string
|
|
176
|
-
* to redirect, or nothing(`undefined`) to continue.
|
|
204
|
+
* to redirect, or nothing(`undefined`) to continue. The guard's
|
|
205
|
+
* {@link GuardContext context} carries the level's parsed
|
|
206
|
+
* {@link GuardContext.search search}(schema output, or the degraded
|
|
207
|
+
* input without a schema); an invalid search fails the resolution at
|
|
208
|
+
* this phase like any other navigation error.
|
|
177
209
|
*/
|
|
178
210
|
beforeLoad?(ctx: GuardContext<BaseRoute<T>>): Awaitable<string | void>;
|
|
179
211
|
} & Omit<T, 'path' | 'children'>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-router/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"build": "rm -rf dist && rollup -c && tsc -p tsconfig.production.json && tsc-alias -p tsconfig.production.json",
|
|
32
32
|
"commit": "lint-staged && git-cz -n",
|
|
33
33
|
"coverage": "vitest run --coverage",
|
|
34
|
-
"lint": "eslint --fix src test *.js
|
|
34
|
+
"lint": "eslint --fix src test *.js",
|
|
35
35
|
"doc:gen": "typedoc",
|
|
36
36
|
"deploy": "npm run doc:gen && gh-pages -d dist",
|
|
37
37
|
"test": "vitest run"
|
|
@@ -58,44 +58,46 @@
|
|
|
58
58
|
"@babel/core": "^8.0.1",
|
|
59
59
|
"@babel/preset-env": "^8.0.2",
|
|
60
60
|
"@babel/preset-typescript": "^8.0.1",
|
|
61
|
+
"@eslint/eslintrc": "^3.3.6",
|
|
61
62
|
"@rollup/plugin-babel": "^7.1.0",
|
|
62
63
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
63
64
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
64
65
|
"@rollup/plugin-replace": "^6.0.3",
|
|
65
|
-
"@types/node": "^26.
|
|
66
|
+
"@types/node": "^26.4.0",
|
|
66
67
|
"@types/sinon": "^22.0.0",
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
68
|
-
"@typescript-eslint/parser": "^
|
|
68
|
+
"@typescript-eslint/eslint-plugin": "^8.68.0",
|
|
69
|
+
"@typescript-eslint/parser": "^8.68.0",
|
|
69
70
|
"@vitest/coverage-v8": "^4.1.11",
|
|
70
71
|
"commitizen": "^4.3.2",
|
|
71
72
|
"core-js": "^3.50.0",
|
|
72
73
|
"cross-env": "^10.1.0",
|
|
73
|
-
"eslint": "^
|
|
74
|
+
"eslint": "^10.9.1",
|
|
74
75
|
"eslint-config-airbnb": "^19.0.4",
|
|
75
|
-
"eslint-config-airbnb-typescript": "^
|
|
76
|
-
"eslint-config-prettier": "^
|
|
77
|
-
"eslint-import-resolver-typescript": "^
|
|
78
|
-
"eslint-plugin-compat": "^
|
|
79
|
-
"eslint-plugin-import": "^2.
|
|
80
|
-
"eslint-plugin-jsx-a11y": "^6.
|
|
81
|
-
"eslint-plugin-prettier": "^5.
|
|
82
|
-
"eslint-plugin-react": "^7.
|
|
83
|
-
"eslint-plugin-react-hooks": "^
|
|
76
|
+
"eslint-config-airbnb-typescript": "^18.0.0",
|
|
77
|
+
"eslint-config-prettier": "^10.1.8",
|
|
78
|
+
"eslint-import-resolver-typescript": "^4.4.5",
|
|
79
|
+
"eslint-plugin-compat": "^7.0.2",
|
|
80
|
+
"eslint-plugin-import": "^2.32.0",
|
|
81
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
82
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
83
|
+
"eslint-plugin-react": "^7.37.5",
|
|
84
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
84
85
|
"gh-pages": "^6.3.0",
|
|
86
|
+
"globals": "^17.11.0",
|
|
85
87
|
"husky": "^9.1.7",
|
|
86
88
|
"lint-staged": "^17.3.0",
|
|
87
89
|
"prettier": "^3.9.6",
|
|
88
|
-
"rollup": "^4.
|
|
90
|
+
"rollup": "^4.63.0",
|
|
89
91
|
"semantic-release": "^25.0.9",
|
|
90
92
|
"should": "^13.2.3",
|
|
91
93
|
"should-sinon": "0.0.6",
|
|
92
94
|
"sinon": "^22.1.0",
|
|
93
|
-
"terser": "^5.
|
|
95
|
+
"terser": "^5.51.0",
|
|
94
96
|
"tsc-alias": "^1.9.2",
|
|
95
97
|
"typedoc": "^0.28.20",
|
|
96
98
|
"typedoc-plugin-mark-react-functional-components": "^0.2.2",
|
|
97
99
|
"typedoc-plugin-missing-exports": "^4.1.4",
|
|
98
|
-
"typescript": "~
|
|
100
|
+
"typescript": "~6.0.3",
|
|
99
101
|
"vitest": "^4.1.11"
|
|
100
102
|
}
|
|
101
103
|
}
|