@native-router/core 1.7.0 → 1.8.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 +3 -2
- package/dist/index.cjs +114 -86
- package/dist/index.mjs +114 -86
- package/dist/types/router.d.ts +5 -0
- package/dist/types/types.d.ts +15 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,8 +108,9 @@ 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
|
|
|
115
116
|
## Design principles
|
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,84 @@ function formatIssuePath(path) {
|
|
|
45
45
|
return `${keys.join('.')}: `;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
50
|
+
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
51
|
+
* single-valued keys are strings, keys repeated in the query string are
|
|
52
|
+
* arrays of their values. An empty search is `{}`.
|
|
53
|
+
*
|
|
54
|
+
* This is also the degraded shape every search API falls back to when no
|
|
55
|
+
* schema is given.
|
|
56
|
+
* @group Methods
|
|
57
|
+
* @category Route
|
|
58
|
+
* @param search the raw `location.search` string, with or without `?`
|
|
59
|
+
* @returns the input object for schema validation
|
|
60
|
+
*/
|
|
61
|
+
function parseSearchInput(search) {
|
|
62
|
+
const input = {};
|
|
63
|
+
// eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled
|
|
64
|
+
new URLSearchParams(search).forEach((value, key) => {
|
|
65
|
+
const prev = input[key];
|
|
66
|
+
if (prev === undefined) {
|
|
67
|
+
input[key] = value;
|
|
68
|
+
} else if (Array.isArray(prev)) {
|
|
69
|
+
prev.push(value);
|
|
70
|
+
} else {
|
|
71
|
+
input[key] = [prev, value];
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return input;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
79
|
+
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
80
|
+
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
81
|
+
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
82
|
+
*
|
|
83
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
84
|
+
*
|
|
85
|
+
* @group Methods
|
|
86
|
+
* @category Route
|
|
87
|
+
* @param schema the search schema
|
|
88
|
+
* @param search the raw `location.search` string
|
|
89
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
90
|
+
* @throws {SearchError} when the schema reports issues
|
|
91
|
+
*/
|
|
92
|
+
async function parseSearch(schema, search) {
|
|
93
|
+
const result = await schema['~standard'].validate(parseSearchInput(search));
|
|
94
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
95
|
+
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
96
|
+
// degrades to `unknown`.
|
|
97
|
+
return result.value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
102
|
+
* `useSearch` of `@native-router/react`) and route guards.
|
|
103
|
+
*
|
|
104
|
+
* @group Methods
|
|
105
|
+
* @category Route
|
|
106
|
+
* @param schema the search schema — must validate synchronously
|
|
107
|
+
* @param search the raw `location.search` string
|
|
108
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
109
|
+
* @throws {SearchError} when the schema reports issues
|
|
110
|
+
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
111
|
+
* for async schemas instead
|
|
112
|
+
*/
|
|
113
|
+
function parseSearchSync(schema, search) {
|
|
114
|
+
const result = schema['~standard'].validate(parseSearchInput(search));
|
|
115
|
+
if (isThenable(result)) {
|
|
116
|
+
throw new Error('The search schema validates asynchronously; parse it during resolve ' + '(parseSearch) instead of synchronously');
|
|
117
|
+
}
|
|
118
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
119
|
+
// See parseSearch for the cast rationale.
|
|
120
|
+
return result.value;
|
|
121
|
+
}
|
|
122
|
+
function isThenable(value) {
|
|
123
|
+
return typeof value?.then === 'function';
|
|
124
|
+
}
|
|
125
|
+
|
|
48
126
|
const DEFAULT_MAX_STACK_DEPTH = 100;
|
|
49
127
|
|
|
50
128
|
/** Max redirects followed by {@link resolveEntry} before giving up. */
|
|
@@ -327,6 +405,11 @@ function resolveTo(router, to, state) {
|
|
|
327
405
|
* aborts — their resolution may be shared, so cancelling it on behalf of
|
|
328
406
|
* one consumer is not sound yet.
|
|
329
407
|
*
|
|
408
|
+
* A guard's context also carries the level's parsed
|
|
409
|
+
* {@link GuardContext.search search}: the {@link BaseRoute.search schema}
|
|
410
|
+
* output(its validation failure rejects this resolution with a
|
|
411
|
+
* `SearchError`), or the degraded input without a schema.
|
|
412
|
+
*
|
|
330
413
|
* @group Methods
|
|
331
414
|
* @category Router
|
|
332
415
|
* @param router router instance
|
|
@@ -364,14 +447,37 @@ async function resolveEntry(router, location, opts) {
|
|
|
364
447
|
} = matched[i];
|
|
365
448
|
// `redirect` wins over `beforeLoad`; a non-empty string target
|
|
366
449
|
// restarts the resolution at the redirected location.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
450
|
+
let target = route.redirect;
|
|
451
|
+
if (!target && route.beforeLoad) {
|
|
452
|
+
// The level's search schema runs before its guard, so the guard
|
|
453
|
+
// sees the parsed output(degraded input without a schema). A
|
|
454
|
+
// validation failure fails the resolution through the task's
|
|
455
|
+
// errorHandler channel — the same route a data-phase search
|
|
456
|
+
// error takes — instead of rejecting this entry, which preload
|
|
457
|
+
// consumers share.
|
|
458
|
+
let search;
|
|
459
|
+
if (route.search) {
|
|
460
|
+
try {
|
|
461
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
462
|
+
search = await parseSearch(route.search, location.search);
|
|
463
|
+
} catch (e) {
|
|
464
|
+
return {
|
|
465
|
+
location,
|
|
466
|
+
task: Promise.reject(e).catch(errorHandler)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
search = parseSearchInput(location.search);
|
|
471
|
+
}
|
|
472
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
473
|
+
target = await route.beforeLoad({
|
|
474
|
+
router,
|
|
475
|
+
location,
|
|
476
|
+
params: mergeMatchedParams(matched, i),
|
|
477
|
+
signal,
|
|
478
|
+
search
|
|
479
|
+
});
|
|
480
|
+
}
|
|
375
481
|
if (target) {
|
|
376
482
|
location = toLocation(router, target, location.state);
|
|
377
483
|
redirected = true;
|
|
@@ -1076,84 +1182,6 @@ function getParams(router) {
|
|
|
1076
1182
|
return mergeMatchedParams(match(router, location.pathname) ?? []);
|
|
1077
1183
|
}
|
|
1078
1184
|
|
|
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
1185
|
exports.NativeRouterError = NativeRouterError;
|
|
1158
1186
|
exports.NotFoundError = NotFoundError;
|
|
1159
1187
|
exports.RedirectLoopError = RedirectLoopError;
|
package/dist/index.mjs
CHANGED
|
@@ -43,6 +43,84 @@ function formatIssuePath(path) {
|
|
|
43
43
|
return `${keys.join('.')}: `;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Parse a raw search string(e.g. `?page=2&tag=a&tag=b`) into the plain
|
|
48
|
+
* input object consumed by {@link StandardSchemaV1 search schemas}:
|
|
49
|
+
* single-valued keys are strings, keys repeated in the query string are
|
|
50
|
+
* arrays of their values. An empty search is `{}`.
|
|
51
|
+
*
|
|
52
|
+
* This is also the degraded shape every search API falls back to when no
|
|
53
|
+
* schema is given.
|
|
54
|
+
* @group Methods
|
|
55
|
+
* @category Route
|
|
56
|
+
* @param search the raw `location.search` string, with or without `?`
|
|
57
|
+
* @returns the input object for schema validation
|
|
58
|
+
*/
|
|
59
|
+
function parseSearchInput(search) {
|
|
60
|
+
const input = {};
|
|
61
|
+
// eslint-disable-next-line compat/compat -- URLSearchParams support is the app's polyfill concern, not bundled
|
|
62
|
+
new URLSearchParams(search).forEach((value, key) => {
|
|
63
|
+
const prev = input[key];
|
|
64
|
+
if (prev === undefined) {
|
|
65
|
+
input[key] = value;
|
|
66
|
+
} else if (Array.isArray(prev)) {
|
|
67
|
+
prev.push(value);
|
|
68
|
+
} else {
|
|
69
|
+
input[key] = [prev, value];
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return input;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validate a search string with a {@link StandardSchemaV1} schema — any
|
|
77
|
+
* zod/valibot/arktype schema works, no hard dependency. The string is
|
|
78
|
+
* first degraded via {@link parseSearchInput}, then parsed by the schema,
|
|
79
|
+
* so schemas can coerce(`'2'` → `2`) and normalize along the way.
|
|
80
|
+
*
|
|
81
|
+
* Async schemas(`validate` returning a promise) are awaited.
|
|
82
|
+
*
|
|
83
|
+
* @group Methods
|
|
84
|
+
* @category Route
|
|
85
|
+
* @param schema the search schema
|
|
86
|
+
* @param search the raw `location.search` string
|
|
87
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
88
|
+
* @throws {SearchError} when the schema reports issues
|
|
89
|
+
*/
|
|
90
|
+
async function parseSearch(schema, search) {
|
|
91
|
+
const result = await schema['~standard'].validate(parseSearchInput(search));
|
|
92
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
93
|
+
// The schema's declared output; the loose `StandardSchemaV1` default
|
|
94
|
+
// degrades to `unknown`.
|
|
95
|
+
return result.value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Synchronous flavor of {@link parseSearch}, for render-time reads(see
|
|
100
|
+
* `useSearch` of `@native-router/react`) and route guards.
|
|
101
|
+
*
|
|
102
|
+
* @group Methods
|
|
103
|
+
* @category Route
|
|
104
|
+
* @param schema the search schema — must validate synchronously
|
|
105
|
+
* @param search the raw `location.search` string
|
|
106
|
+
* @returns the parsed(and possibly coerced) output of the schema
|
|
107
|
+
* @throws {SearchError} when the schema reports issues
|
|
108
|
+
* @throws when the schema validates asynchronously; use {@link parseSearch}
|
|
109
|
+
* for async schemas instead
|
|
110
|
+
*/
|
|
111
|
+
function parseSearchSync(schema, search) {
|
|
112
|
+
const result = schema['~standard'].validate(parseSearchInput(search));
|
|
113
|
+
if (isThenable(result)) {
|
|
114
|
+
throw new Error('The search schema validates asynchronously; parse it during resolve ' + '(parseSearch) instead of synchronously');
|
|
115
|
+
}
|
|
116
|
+
if (result.issues) throw new SearchError(search, result.issues);
|
|
117
|
+
// See parseSearch for the cast rationale.
|
|
118
|
+
return result.value;
|
|
119
|
+
}
|
|
120
|
+
function isThenable(value) {
|
|
121
|
+
return typeof value?.then === 'function';
|
|
122
|
+
}
|
|
123
|
+
|
|
46
124
|
const DEFAULT_MAX_STACK_DEPTH = 100;
|
|
47
125
|
|
|
48
126
|
/** Max redirects followed by {@link resolveEntry} before giving up. */
|
|
@@ -325,6 +403,11 @@ function resolveTo(router, to, state) {
|
|
|
325
403
|
* aborts — their resolution may be shared, so cancelling it on behalf of
|
|
326
404
|
* one consumer is not sound yet.
|
|
327
405
|
*
|
|
406
|
+
* A guard's context also carries the level's parsed
|
|
407
|
+
* {@link GuardContext.search search}: the {@link BaseRoute.search schema}
|
|
408
|
+
* output(its validation failure rejects this resolution with a
|
|
409
|
+
* `SearchError`), or the degraded input without a schema.
|
|
410
|
+
*
|
|
328
411
|
* @group Methods
|
|
329
412
|
* @category Router
|
|
330
413
|
* @param router router instance
|
|
@@ -362,14 +445,37 @@ async function resolveEntry(router, location, opts) {
|
|
|
362
445
|
} = matched[i];
|
|
363
446
|
// `redirect` wins over `beforeLoad`; a non-empty string target
|
|
364
447
|
// restarts the resolution at the redirected location.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
448
|
+
let target = route.redirect;
|
|
449
|
+
if (!target && route.beforeLoad) {
|
|
450
|
+
// The level's search schema runs before its guard, so the guard
|
|
451
|
+
// sees the parsed output(degraded input without a schema). A
|
|
452
|
+
// validation failure fails the resolution through the task's
|
|
453
|
+
// errorHandler channel — the same route a data-phase search
|
|
454
|
+
// error takes — instead of rejecting this entry, which preload
|
|
455
|
+
// consumers share.
|
|
456
|
+
let search;
|
|
457
|
+
if (route.search) {
|
|
458
|
+
try {
|
|
459
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
460
|
+
search = await parseSearch(route.search, location.search);
|
|
461
|
+
} catch (e) {
|
|
462
|
+
return {
|
|
463
|
+
location,
|
|
464
|
+
task: Promise.reject(e).catch(errorHandler)
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
search = parseSearchInput(location.search);
|
|
469
|
+
}
|
|
470
|
+
// eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
|
|
471
|
+
target = await route.beforeLoad({
|
|
472
|
+
router,
|
|
473
|
+
location,
|
|
474
|
+
params: mergeMatchedParams(matched, i),
|
|
475
|
+
signal,
|
|
476
|
+
search
|
|
477
|
+
});
|
|
478
|
+
}
|
|
373
479
|
if (target) {
|
|
374
480
|
location = toLocation(router, target, location.state);
|
|
375
481
|
redirected = true;
|
|
@@ -1074,82 +1180,4 @@ function getParams(router) {
|
|
|
1074
1180
|
return mergeMatchedParams(match(router, location.pathname) ?? []);
|
|
1075
1181
|
}
|
|
1076
1182
|
|
|
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
1183
|
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 };
|
package/dist/types/router.d.ts
CHANGED
|
@@ -100,6 +100,11 @@ 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.
|
|
107
|
+
*
|
|
103
108
|
* @group Methods
|
|
104
109
|
* @category Router
|
|
105
110
|
* @param router router instance
|
package/dist/types/types.d.ts
CHANGED
|
@@ -143,10 +143,19 @@ 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
149
|
params: Record<string, string>;
|
|
150
|
+
/**
|
|
151
|
+
* The search the guard sees: the route's {@link BaseRoute.search search
|
|
152
|
+
* schema} output(parsed and validated before the guard runs), or the
|
|
153
|
+
* degraded {@link SearchInput} when the route declares no schema. The
|
|
154
|
+
* loose default types it `unknown` — narrow it in the guard, or let a
|
|
155
|
+
* typed route table(see `createRoutes` of `@native-router/react`)
|
|
156
|
+
* derive it from the schema.
|
|
157
|
+
*/
|
|
158
|
+
search: S;
|
|
150
159
|
/**
|
|
151
160
|
* Aborted when this navigation is superseded by a newer one or
|
|
152
161
|
* cancelled(see {@link RouterInstance.cancelAll cancel}); pass it to
|
|
@@ -173,7 +182,11 @@ export type BaseRoute<T = any> = {
|
|
|
173
182
|
search?: StandardSchemaV1;
|
|
174
183
|
/**
|
|
175
184
|
* Route guard invoked before the view resolves. Return a path string
|
|
176
|
-
* to redirect, or nothing(`undefined`) to continue.
|
|
185
|
+
* to redirect, or nothing(`undefined`) to continue. The guard's
|
|
186
|
+
* {@link GuardContext context} carries the level's parsed
|
|
187
|
+
* {@link GuardContext.search search}(schema output, or the degraded
|
|
188
|
+
* input without a schema); an invalid search fails the resolution at
|
|
189
|
+
* this phase like any other navigation error.
|
|
177
190
|
*/
|
|
178
191
|
beforeLoad?(ctx: GuardContext<BaseRoute<T>>): Awaitable<string | void>;
|
|
179
192
|
} & Omit<T, 'path' | 'children'>;
|