@native-router/core 1.9.0 → 1.9.1

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 CHANGED
@@ -141,6 +141,8 @@ const router = create(
141
141
 
142
142
  - No `params` schema → behavior unchanged: the raw string map flows through
143
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 `redirect` level skips its params schema entirely — the level's guard never runs, so there is nothing to hand coerced params to; the same asymmetry the search schema has (`redirect` wins over `beforeLoad`). Hanging a params schema on a redirect level is inert, it cannot fail the navigation
145
+ - A same-name param on both a parent and a child segment (`/users/:id/files/:id`): the deep-over-shallow merge operates on the **raw** strings, so the child segment's value overwrites the parent's coerced one — a child guard sees the raw string again. Declare the coercing schema on (or below) the deepest level that reads the param — a deeper schema validates the whole merged map anyway — or avoid reusing a param name across levels
144
146
  - 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
147
  - `parseParams`/`parseParamsSync` are exported for custom `resolveView` implementations (the async/sync flavors mirror `parseSearch`/`parseSearchSync`)
146
148
 
package/dist/index.cjs CHANGED
@@ -528,11 +528,16 @@ async function resolveEntry(router, location, opts) {
528
528
  ...matched[i].params
529
529
  };
530
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) {
531
+ // sees the coerced output. A redirect level never runs its guard,
532
+ // so its schema is skipped — the same asymmetry the level's
533
+ // search schema already has(`redirect` wins over `beforeLoad`):
534
+ // hanging a params schema on a redirect level must not be able to
535
+ // fail the navigation, its only observable effect would be the
536
+ // failure. A validation failure fails the resolution through the
537
+ // task's errorHandler channel — the same route a search-schema
538
+ // failure takes — instead of rejecting this entry, which preload
539
+ // consumers share.
540
+ if (route.params && !route.redirect) {
536
541
  try {
537
542
  // eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
538
543
  params = await parseParams(route.params, params);
package/dist/index.mjs CHANGED
@@ -526,11 +526,16 @@ async function resolveEntry(router, location, opts) {
526
526
  ...matched[i].params
527
527
  };
528
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) {
529
+ // sees the coerced output. A redirect level never runs its guard,
530
+ // so its schema is skipped — the same asymmetry the level's
531
+ // search schema already has(`redirect` wins over `beforeLoad`):
532
+ // hanging a params schema on a redirect level must not be able to
533
+ // fail the navigation, its only observable effect would be the
534
+ // failure. A validation failure fails the resolution through the
535
+ // task's errorHandler channel — the same route a search-schema
536
+ // failure takes — instead of rejecting this entry, which preload
537
+ // consumers share.
538
+ if (route.params && !route.redirect) {
534
539
  try {
535
540
  // eslint-disable-next-line no-await-in-loop -- guards must run in declaration order, sequentially
536
541
  params = await parseParams(route.params, params);
@@ -142,8 +142,16 @@ export type ExtractPathParams<P extends string> = P extends `${infer Head}/${inf
142
142
  * Context passed to a route guard({@link BaseRoute.beforeLoad beforeLoad}).
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
+ *
146
+ * Type arguments: `S` types {@link GuardContext.search search}(schema
147
+ * output, or the degraded input without a schema), `P` types
148
+ * {@link GuardContext.params params}. Both default to what a
149
+ * schema-less route produces — `search: unknown`, `params: the raw
150
+ * string map` — so plain guards keep compiling unchanged; thread a
151
+ * params schema's coerced output through `P` to type what the guard
152
+ * actually receives at runtime.
145
153
  */
146
- export type GuardContext<R extends BaseRoute = BaseRoute, S = unknown> = {
154
+ export type GuardContext<R extends BaseRoute = BaseRoute, S = unknown, P = Record<string, string>> = {
147
155
  router: RouterInstance<R>;
148
156
  location: Location;
149
157
  /**
@@ -151,9 +159,13 @@ export type GuardContext<R extends BaseRoute = BaseRoute, S = unknown> = {
151
159
  * declares a {@link BaseRoute.params params schema}, the merged raw
152
160
  * params are parsed through the deepest matching schema before the
153
161
  * guard runs; without schemas the raw string map the matcher
154
- * extracted.
162
+ * extracted. The loose default models the raw map; give the third
163
+ * type argument the schema's output(`GuardContext<R, S, {id: number}>`
164
+ * for a `z.coerce.number()` id) — the runtime value is the parse
165
+ * result, which a coercing schema makes anything but
166
+ * `Record<string, string>`.
155
167
  */
156
- params: Record<string, string>;
168
+ params: P;
157
169
  /**
158
170
  * The search the guard sees: the route's {@link BaseRoute.search search
159
171
  * schema} output(parsed and validated before the guard runs), or the
@@ -193,7 +205,9 @@ export type BaseRoute<T = any> = {
193
205
  * runs it in {@link resolveEntry} after matching and before the level's
194
206
  * `beforeLoad`, so guards and loaders see coerced params(e.g. `:id`
195
207
  * as a number) instead of raw strings; a validation failure fails the
196
- * resolve like any other navigation error(via `ParamsError`).
208
+ * resolve like any other navigation error(via `ParamsError`). A level
209
+ * with a {@link BaseRoute.redirect redirect} skips the schema — the
210
+ * guard never runs there, so the schema would have no consumer.
197
211
  *
198
212
  * Omit it and the params stay the raw `Record<string, string>` the
199
213
  * matcher extracted — behavior is unchanged.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@native-router/core",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "exports": {
5
5
  ".": {
6
6
  "types": "./dist/types/index.d.ts",
@@ -34,7 +34,8 @@
34
34
  "lint": "eslint --fix src test *.js",
35
35
  "doc:gen": "typedoc",
36
36
  "deploy": "npm run doc:gen && gh-pages -d dist",
37
- "test": "vitest run"
37
+ "test": "vitest run",
38
+ "typecheck": "tsc -p tsconfig.test.json --noEmit"
38
39
  },
39
40
  "repository": {
40
41
  "type": "git",