@real-router/core 0.38.0 → 0.40.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.
Files changed (89) hide show
  1. package/README.md +27 -5
  2. package/dist/cjs/Router-B-Pev7K2.d.ts +46 -0
  3. package/dist/cjs/RouterValidator-mx2Zooya.d.ts +136 -0
  4. package/dist/cjs/api.d.ts +2 -1
  5. package/dist/cjs/api.js +1 -1
  6. package/dist/cjs/api.js.map +1 -1
  7. package/dist/cjs/index.d-y2b-8_3Y.d.ts +236 -0
  8. package/dist/cjs/index.d.ts +7 -24
  9. package/dist/cjs/index.js +1 -1
  10. package/dist/cjs/index.js.map +1 -1
  11. package/dist/cjs/metafile-cjs.json +1 -1
  12. package/dist/cjs/utils.d.ts +6 -1
  13. package/dist/cjs/utils.js +1 -1
  14. package/dist/cjs/utils.js.map +1 -1
  15. package/dist/cjs/validation.d.ts +184 -0
  16. package/dist/cjs/validation.js +1 -0
  17. package/dist/cjs/validation.js.map +1 -0
  18. package/dist/esm/Router-B-Pev7K2.d.mts +46 -0
  19. package/dist/esm/RouterValidator-mx2Zooya.d.mts +136 -0
  20. package/dist/esm/api.d.mts +2 -1
  21. package/dist/esm/api.mjs +1 -1
  22. package/dist/esm/api.mjs.map +1 -1
  23. package/dist/esm/chunk-5QXFUUDL.mjs +1 -0
  24. package/dist/esm/chunk-5QXFUUDL.mjs.map +1 -0
  25. package/dist/esm/chunk-HHIXK5UM.mjs +1 -0
  26. package/dist/esm/chunk-HHIXK5UM.mjs.map +1 -0
  27. package/dist/esm/chunk-QUUNDESP.mjs +1 -0
  28. package/dist/esm/chunk-QUUNDESP.mjs.map +1 -0
  29. package/dist/esm/chunk-RA5VYM7M.mjs +1 -0
  30. package/dist/esm/chunk-RA5VYM7M.mjs.map +1 -0
  31. package/dist/esm/index.d-y2b-8_3Y.d.mts +236 -0
  32. package/dist/esm/index.d.mts +7 -24
  33. package/dist/esm/index.mjs +1 -1
  34. package/dist/esm/metafile-esm.json +1 -1
  35. package/dist/esm/utils.d.mts +6 -1
  36. package/dist/esm/utils.mjs +1 -1
  37. package/dist/esm/utils.mjs.map +1 -1
  38. package/dist/esm/validation.d.mts +184 -0
  39. package/dist/esm/validation.mjs +1 -0
  40. package/dist/esm/validation.mjs.map +1 -0
  41. package/package.json +18 -5
  42. package/src/Router.ts +73 -99
  43. package/src/api/cloneRouter.ts +1 -30
  44. package/src/api/getDependenciesApi.ts +45 -86
  45. package/src/api/getLifecycleApi.ts +24 -19
  46. package/src/api/getPluginApi.ts +20 -28
  47. package/src/api/getRoutesApi.ts +49 -106
  48. package/src/constants.ts +0 -30
  49. package/src/guards.ts +46 -0
  50. package/src/helpers.ts +0 -17
  51. package/src/index.ts +4 -0
  52. package/src/internals.ts +6 -5
  53. package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +2 -2
  54. package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +0 -25
  55. package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +4 -26
  56. package/src/namespaces/OptionsNamespace/constants.ts +0 -20
  57. package/src/namespaces/OptionsNamespace/index.ts +1 -5
  58. package/src/namespaces/OptionsNamespace/validators.ts +6 -245
  59. package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +18 -59
  60. package/src/namespaces/PluginsNamespace/constants.ts +3 -6
  61. package/src/namespaces/PluginsNamespace/validators.ts +2 -57
  62. package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +27 -84
  63. package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +0 -16
  64. package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +3 -12
  65. package/src/namespaces/RoutesNamespace/constants.ts +0 -8
  66. package/src/namespaces/RoutesNamespace/forwardChain.ts +34 -0
  67. package/src/namespaces/RoutesNamespace/index.ts +1 -1
  68. package/src/namespaces/RoutesNamespace/routeGuards.ts +62 -0
  69. package/src/namespaces/RoutesNamespace/routesStore.ts +7 -51
  70. package/src/namespaces/StateNamespace/StateNamespace.ts +0 -33
  71. package/src/namespaces/StateNamespace/helpers.ts +1 -1
  72. package/src/namespaces/index.ts +0 -3
  73. package/src/typeGuards.ts +1 -15
  74. package/src/types/RouterValidator.ts +155 -0
  75. package/src/utils/getStaticPaths.ts +50 -0
  76. package/src/utils/index.ts +4 -0
  77. package/src/validation.ts +12 -0
  78. package/src/wiring/RouterWiringBuilder.ts +32 -9
  79. package/dist/cjs/index.d-DDimDpYc.d.ts +0 -165
  80. package/dist/esm/chunk-CG7TKDP3.mjs +0 -1
  81. package/dist/esm/chunk-CG7TKDP3.mjs.map +0 -1
  82. package/dist/esm/index.d-DDimDpYc.d.mts +0 -165
  83. package/src/namespaces/DependenciesNamespace/validators.ts +0 -103
  84. package/src/namespaces/EventBusNamespace/validators.ts +0 -36
  85. package/src/namespaces/NavigationNamespace/validators.ts +0 -47
  86. package/src/namespaces/RouteLifecycleNamespace/validators.ts +0 -65
  87. package/src/namespaces/RoutesNamespace/forwardToValidation.ts +0 -408
  88. package/src/namespaces/RoutesNamespace/validators.ts +0 -566
  89. package/src/namespaces/StateNamespace/validators.ts +0 -46
@@ -0,0 +1,184 @@
1
+ import { L as Limits, R as RouterValidator } from './RouterValidator-mx2Zooya.mjs';
2
+ import { DefaultDependencies, Params, ForwardToCallback, GuardFnFactory, State, SimpleState, GuardFn, RouteTreeState, Options, EventName, Plugin, EventMethodMap, Unsubscribe, PluginFactory, Router } from '@real-router/types';
3
+ import { a as RouteDefinition, R as RouteTree, M as Matcher, C as CreateMatcherOptions } from './index.d-y2b-8_3Y.mjs';
4
+
5
+ interface DependenciesStore<Dependencies extends DefaultDependencies = DefaultDependencies> {
6
+ dependencies: Partial<Dependencies>;
7
+ limits: Limits;
8
+ }
9
+
10
+ /**
11
+ * Dependencies injected into RoutesNamespace.
12
+ *
13
+ * These are function references from the Router facade,
14
+ * avoiding the need to pass the entire Router object.
15
+ */
16
+ interface RoutesDependencies<Dependencies extends DefaultDependencies = DefaultDependencies> {
17
+ /** Register canActivate handler for a route */
18
+ addActivateGuard: (name: string, handler: GuardFnFactory<Dependencies>) => void;
19
+ /** Register canDeactivate handler for a route */
20
+ addDeactivateGuard: (name: string, handler: GuardFnFactory<Dependencies>) => void;
21
+ /** Create state object */
22
+ makeState: <P extends Params = Params, MP extends Params = Params>(name: string, params?: P, path?: string, meta?: Record<string, Record<string, "url" | "query">>) => State<P, MP>;
23
+ /** Get current router state */
24
+ getState: () => State | undefined;
25
+ /** Compare two states for equality */
26
+ areStatesEqual: (state1: State | undefined, state2: State | undefined, ignoreQueryParams?: boolean) => boolean;
27
+ /** Get a dependency by name */
28
+ getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K];
29
+ /** Forward state through facade (allows plugin interception) */
30
+ forwardState: <P extends Params = Params>(name: string, params: P) => SimpleState<P>;
31
+ }
32
+ /**
33
+ * Configuration storage for routes.
34
+ * Stores decoders, encoders, default params, and forward mappings.
35
+ */
36
+ interface RouteConfig {
37
+ /** Custom param decoders per route */
38
+ decoders: Record<string, (params: Params) => Params>;
39
+ /** Custom param encoders per route */
40
+ encoders: Record<string, (params: Params) => Params>;
41
+ /** Default params per route */
42
+ defaultParams: Record<string, Params>;
43
+ /** Forward mappings (source -> target) */
44
+ forwardMap: Record<string, string>;
45
+ /** Dynamic forward callbacks (source -> callback) */
46
+ forwardFnMap: Record<string, ForwardToCallback<any>>;
47
+ }
48
+
49
+ interface RouteLifecycleDependencies<Dependencies extends DefaultDependencies = DefaultDependencies> {
50
+ compileFactory: (factory: GuardFnFactory<Dependencies>) => GuardFn;
51
+ }
52
+
53
+ /**
54
+ * Independent namespace for managing route lifecycle handlers.
55
+ *
56
+ * Static methods handle input validation (called by facade).
57
+ * Instance methods handle state-dependent validation, storage and business logic.
58
+ */
59
+ declare class RouteLifecycleNamespace<Dependencies extends DefaultDependencies = DefaultDependencies> {
60
+ #private;
61
+ setDependencies(deps: RouteLifecycleDependencies<Dependencies>): void;
62
+ /**
63
+ * Updates handler registration limits (max lifecycle handlers threshold).
64
+ *
65
+ * @param limits - Limits configuration with maxLifecycleHandlers
66
+ */
67
+ setLimits(limits: Limits): void;
68
+ setValidatorGetter(getter: () => RouterValidator | null): void;
69
+ getHandlerCount(type: "activate" | "deactivate"): number;
70
+ /**
71
+ * Adds a canActivate guard for a route.
72
+ * Handles overwrite detection and registration.
73
+ *
74
+ * @param name - Route name (input-validated by facade)
75
+ * @param handler - Guard function or boolean (input-validated by facade)
76
+ * @param isFromDefinition - True when guard comes from route definition (tracked for HMR replace)
77
+ */
78
+ addCanActivate(name: string, handler: GuardFnFactory<Dependencies> | boolean, isFromDefinition?: boolean): void;
79
+ /**
80
+ * Adds a canDeactivate guard for a route.
81
+ * Handles overwrite detection and registration.
82
+ *
83
+ * @param name - Route name (input-validated by facade)
84
+ * @param handler - Guard function or boolean (input-validated by facade)
85
+ * @param isFromDefinition - True when guard comes from route definition (tracked for HMR replace)
86
+ */
87
+ addCanDeactivate(name: string, handler: GuardFnFactory<Dependencies> | boolean, isFromDefinition?: boolean): void;
88
+ /**
89
+ * Removes a canActivate guard for a route.
90
+ * Input already validated by facade (not registering).
91
+ *
92
+ * @param name - Route name (already validated by facade)
93
+ */
94
+ clearCanActivate(name: string): void;
95
+ /**
96
+ * Removes a canDeactivate guard for a route.
97
+ * Input already validated by facade (not registering).
98
+ *
99
+ * @param name - Route name (already validated by facade)
100
+ */
101
+ clearCanDeactivate(name: string): void;
102
+ /**
103
+ * Clears all lifecycle handlers (canActivate and canDeactivate).
104
+ * Used by clearRoutes to reset all lifecycle state.
105
+ */
106
+ clearAll(): void;
107
+ /**
108
+ * Clears only lifecycle handlers that were registered from route definitions.
109
+ * Used by HMR to remove definition-sourced guards without touching externally-added guards.
110
+ */
111
+ clearDefinitionGuards(): void;
112
+ /**
113
+ * Returns lifecycle factories as records for cloning.
114
+ *
115
+ * @returns Tuple of [canDeactivateFactories, canActivateFactories]
116
+ */
117
+ getFactories(): [
118
+ Record<string, GuardFnFactory<Dependencies>>,
119
+ Record<string, GuardFnFactory<Dependencies>>
120
+ ];
121
+ /**
122
+ * Returns compiled lifecycle functions for transition execution.
123
+ *
124
+ * @returns Tuple of [canDeactivateFunctions, canActivateFunctions] as Maps
125
+ */
126
+ getFunctions(): [Map<string, GuardFn>, Map<string, GuardFn>];
127
+ canNavigateTo(toDeactivate: string[], toActivate: string[], toState: State, fromState: State | undefined): boolean;
128
+ }
129
+
130
+ interface RoutesStore<Dependencies extends DefaultDependencies = DefaultDependencies> {
131
+ readonly definitions: RouteDefinition[];
132
+ readonly config: RouteConfig;
133
+ tree: RouteTree;
134
+ matcher: Matcher;
135
+ resolvedForwardMap: Record<string, string>;
136
+ routeCustomFields: Record<string, Record<string, unknown>>;
137
+ rootPath: string;
138
+ readonly matcherOptions: CreateMatcherOptions | undefined;
139
+ depsStore: RoutesDependencies<Dependencies> | undefined;
140
+ lifecycleNamespace: RouteLifecycleNamespace<Dependencies> | undefined;
141
+ readonly pendingCanActivate: Map<string, GuardFnFactory<Dependencies>>;
142
+ readonly pendingCanDeactivate: Map<string, GuardFnFactory<Dependencies>>;
143
+ readonly treeOperations: {
144
+ readonly commitTreeChanges: (store: RoutesStore<Dependencies>) => void;
145
+ readonly resetStore: (store: RoutesStore<Dependencies>) => void;
146
+ readonly nodeToDefinition: (node: RouteTree) => RouteDefinition;
147
+ };
148
+ }
149
+
150
+ interface RouterInternals<D extends DefaultDependencies = DefaultDependencies> {
151
+ readonly makeState: <P extends Params = Params, MP extends Params = Params>(name: string, params?: P, path?: string, meta?: Record<string, Record<string, "url" | "query">>, forceId?: number) => State<P, MP>;
152
+ readonly forwardState: <P extends Params = Params>(routeName: string, routeParams: P) => SimpleState<P>;
153
+ readonly buildStateResolved: (resolvedName: string, resolvedParams: Params) => RouteTreeState | undefined;
154
+ readonly matchPath: <P extends Params = Params, MP extends Params = Params>(path: string, options?: Options) => State<P, MP> | undefined;
155
+ readonly getOptions: () => Options;
156
+ readonly addEventListener: <E extends EventName>(eventName: E, cb: Plugin[EventMethodMap[E]]) => Unsubscribe;
157
+ readonly buildPath: (route: string, params?: Params) => string;
158
+ readonly start: (path: string) => Promise<State>;
159
+ readonly interceptors: Map<string, ((next: (...args: any[]) => any, ...args: any[]) => any)[]>;
160
+ readonly setRootPath: (rootPath: string) => void;
161
+ readonly getRootPath: () => string;
162
+ readonly getTree: () => RouteTree;
163
+ readonly isDisposed: () => boolean;
164
+ validator: RouterValidator | null;
165
+ readonly dependenciesGetStore: () => DependenciesStore<D>;
166
+ readonly cloneOptions: () => Options;
167
+ readonly cloneDependencies: () => Record<string, unknown>;
168
+ readonly getLifecycleFactories: () => [
169
+ Record<string, GuardFnFactory<D>>,
170
+ Record<string, GuardFnFactory<D>>
171
+ ];
172
+ readonly getPluginFactories: () => PluginFactory<D>[];
173
+ readonly routeGetStore: () => RoutesStore<D>;
174
+ readonly getStateName: () => string | undefined;
175
+ readonly isTransitioning: () => boolean;
176
+ readonly clearState: () => void;
177
+ readonly setState: (state: State) => void;
178
+ readonly routerExtensions: {
179
+ keys: string[];
180
+ }[];
181
+ }
182
+ declare function getInternals<D extends DefaultDependencies>(router: Router<D>): RouterInternals<D>;
183
+
184
+ export { type RouterInternals, RouterValidator, getInternals };
@@ -0,0 +1 @@
1
+ export{getInternals}from"./chunk-QUUNDESP.mjs";//# sourceMappingURL=validation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"validation.mjs"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/core",
3
- "version": "0.38.0",
3
+ "version": "0.40.0",
4
4
  "type": "commonjs",
5
5
  "description": "A simple, powerful, view-agnostic, modular and extensible router",
6
6
  "main": "./dist/cjs/index.js",
@@ -13,6 +13,9 @@
13
13
  ],
14
14
  "utils": [
15
15
  "./dist/cjs/utils.d.ts"
16
+ ],
17
+ "validation": [
18
+ "./dist/cjs/validation.d.ts"
16
19
  ]
17
20
  }
18
21
  },
@@ -43,6 +46,15 @@
43
46
  },
44
47
  "import": "./dist/esm/utils.mjs",
45
48
  "require": "./dist/cjs/utils.js"
49
+ },
50
+ "./validation": {
51
+ "development": "./src/validation.ts",
52
+ "types": {
53
+ "import": "./dist/esm/validation.d.mts",
54
+ "require": "./dist/cjs/validation.d.ts"
55
+ },
56
+ "import": "./dist/esm/validation.mjs",
57
+ "require": "./dist/cjs/validation.js"
46
58
  }
47
59
  },
48
60
  "files": [
@@ -73,16 +85,17 @@
73
85
  "url": "https://github.com/greydragon888/real-router/issues"
74
86
  },
75
87
  "homepage": "https://github.com/greydragon888/real-router",
76
- "sideEffects": false,
88
+ "sideEffects": [
89
+ "./dist/esm/chunk-*.mjs"
90
+ ],
77
91
  "dependencies": {
78
92
  "@real-router/fsm": "^0.2.2",
79
93
  "@real-router/logger": "^0.2.1",
80
- "@real-router/types": "^0.24.0"
94
+ "@real-router/types": "^0.26.0"
81
95
  },
82
96
  "devDependencies": {
83
97
  "event-emitter": "^0.1.2",
84
- "route-tree": "^0.3.4",
85
- "type-guards": "^0.3.7"
98
+ "route-tree": "^0.3.4"
86
99
  },
87
100
  "scripts": {
88
101
  "build": "tsup",
package/src/Router.ts CHANGED
@@ -8,10 +8,10 @@
8
8
 
9
9
  import { logger } from "@real-router/logger";
10
10
  import { EventEmitter } from "event-emitter";
11
- import { validateRouteName } from "type-guards";
12
11
 
13
12
  import { EMPTY_PARAMS, errorCodes } from "./constants";
14
13
  import { createRouterFSM } from "./fsm";
14
+ import { guardDependencies, guardRouteStructure } from "./guards";
15
15
  import { createLimits } from "./helpers";
16
16
  import {
17
17
  createInterceptable,
@@ -30,15 +30,7 @@ import {
30
30
  StateNamespace,
31
31
  createDependenciesStore,
32
32
  } from "./namespaces";
33
- import { validateDependenciesObject } from "./namespaces/DependenciesNamespace/validators";
34
33
  import { CACHED_ALREADY_STARTED_ERROR } from "./namespaces/RouterLifecycleNamespace/constants";
35
- import {
36
- validateAddRouteArgs,
37
- validateBuildPathArgs,
38
- validateIsActiveRouteArgs,
39
- validateRoutes,
40
- validateShouldUpdateNodeArgs,
41
- } from "./namespaces/RoutesNamespace/validators";
42
34
  import { RouterError } from "./RouterError";
43
35
  import { getTransitionPath } from "./transitionPath";
44
36
  import { isLoggerConfig } from "./typeGuards";
@@ -98,12 +90,6 @@ export class Router<
98
90
 
99
91
  readonly #eventBus: EventBusNamespace;
100
92
 
101
- /**
102
- * When true, skips argument validation in public methods for production performance.
103
- * Constructor options are always validated (needed to validate noValidate itself).
104
- */
105
- readonly #noValidate: boolean;
106
-
107
93
  // ============================================================================
108
94
  // Constructor
109
95
  // ============================================================================
@@ -128,22 +114,14 @@ export class Router<
128
114
  // Validate inputs before creating namespaces
129
115
  // =========================================================================
130
116
 
131
- // Always validate options (needed to validate noValidate itself)
132
- OptionsNamespace.validateOptions(options, "constructor");
117
+ // Always validate options
118
+ OptionsNamespace.validateOptionsIsObject(options);
133
119
 
134
- // Extract noValidate BEFORE creating namespaces
135
- const noValidate = options.noValidate ?? false;
120
+ // Unconditional guard-level validation before creating namespaces
121
+ guardDependencies(dependencies);
136
122
 
137
- // Conditional validation for dependencies
138
- if (!noValidate) {
139
- validateDependenciesObject(dependencies, "constructor");
140
- }
141
-
142
- // Conditional validation for initial routes - structure and batch duplicates
143
- // Validation happens BEFORE tree is built, so tree is not passed
144
- if (!noValidate && routes.length > 0) {
145
- validateAddRouteArgs(routes);
146
- validateRoutes(routes);
123
+ if (routes.length > 0) {
124
+ guardRouteStructure(routes);
147
125
  }
148
126
 
149
127
  // =========================================================================
@@ -157,14 +135,12 @@ export class Router<
157
135
  this.#state = new StateNamespace();
158
136
  this.#routes = new RoutesNamespace<Dependencies>(
159
137
  routes,
160
- noValidate,
161
138
  deriveMatcherOptions(this.#options.get()),
162
139
  );
163
140
  this.#routeLifecycle = new RouteLifecycleNamespace<Dependencies>();
164
141
  this.#plugins = new PluginsNamespace<Dependencies>();
165
142
  this.#navigation = new NavigationNamespace();
166
143
  this.#lifecycle = new RouterLifecycleNamespace();
167
- this.#noValidate = noValidate;
168
144
 
169
145
  // =========================================================================
170
146
  // Initialize EventBus
@@ -241,10 +217,6 @@ export class Router<
241
217
  start: createInterceptable(
242
218
  "start",
243
219
  (path: string) => {
244
- if (!noValidate) {
245
- RouterLifecycleNamespace.validateStartArgs([path]);
246
- }
247
-
248
220
  return this.#lifecycle.start(path);
249
221
  },
250
222
  interceptorsMap,
@@ -256,7 +228,7 @@ export class Router<
256
228
  getRootPath: () => this.#routes.getStore().rootPath,
257
229
  getTree: () => this.#routes.getStore().tree,
258
230
  isDisposed: () => this.#eventBus.isDisposed(),
259
- noValidate,
231
+ validator: null,
260
232
  // Dependencies (issue #172)
261
233
  dependenciesGetStore: () => this.#dependenciesStore,
262
234
  // Clone support (issue #173)
@@ -330,14 +302,17 @@ export class Router<
330
302
  strictEquality?: boolean,
331
303
  ignoreQueryParams?: boolean,
332
304
  ): boolean {
333
- if (!this.#noValidate) {
334
- validateIsActiveRouteArgs(
335
- name,
336
- params,
337
- strictEquality,
338
- ignoreQueryParams,
339
- );
340
- }
305
+ getInternals(this).validator?.routes.validateIsActiveRouteArgs(
306
+ name,
307
+ params,
308
+ strictEquality,
309
+ ignoreQueryParams,
310
+ );
311
+
312
+ getInternals(this).validator?.routes.validateRouteName(
313
+ name,
314
+ "isActiveRoute",
315
+ );
341
316
 
342
317
  // Empty string is special case - warn and return false (root node is not a parent)
343
318
  if (name === "") {
@@ -358,11 +333,12 @@ export class Router<
358
333
  }
359
334
 
360
335
  buildPath(route: string, params?: Params): string {
361
- if (!this.#noValidate) {
362
- validateBuildPathArgs(route);
363
- }
336
+ const ctx = getInternals(this);
364
337
 
365
- return getInternals(this).buildPath(route, params);
338
+ ctx.validator?.routes.validateBuildPathArgs(route);
339
+ ctx.validator?.navigation.validateParams(params, "buildPath");
340
+
341
+ return ctx.buildPath(route, params);
366
342
  }
367
343
 
368
344
  // ============================================================================
@@ -384,13 +360,11 @@ export class Router<
384
360
  state2: State | undefined,
385
361
  ignoreQueryParams = true,
386
362
  ): boolean {
387
- if (!this.#noValidate) {
388
- StateNamespace.validateAreStatesEqualArgs(
389
- state1,
390
- state2,
391
- ignoreQueryParams,
392
- );
393
- }
363
+ getInternals(this).validator?.state.validateAreStatesEqualArgs(
364
+ state1,
365
+ state2,
366
+ ignoreQueryParams,
367
+ );
394
368
 
395
369
  return this.#state.areStatesEqual(state1, state2, ignoreQueryParams);
396
370
  }
@@ -398,9 +372,7 @@ export class Router<
398
372
  shouldUpdateNode(
399
373
  nodeName: string,
400
374
  ): (toState: State, fromState?: State) => boolean {
401
- if (!this.#noValidate) {
402
- validateShouldUpdateNodeArgs(nodeName);
403
- }
375
+ getInternals(this).validator?.routes.validateShouldUpdateNodeArgs(nodeName);
404
376
 
405
377
  return RoutesNamespace.shouldUpdateNode(nodeName);
406
378
  }
@@ -418,6 +390,8 @@ export class Router<
418
390
  return Promise.reject(CACHED_ALREADY_STARTED_ERROR);
419
391
  }
420
392
 
393
+ getInternals(this).validator?.navigation.validateStartArgs(startPath);
394
+
421
395
  this.#eventBus.sendStart();
422
396
 
423
397
  const promiseState = getInternals(this)
@@ -494,15 +468,15 @@ export class Router<
494
468
  // ============================================================================
495
469
 
496
470
  canNavigateTo(name: string, params?: Params): boolean {
497
- if (!this.#noValidate) {
498
- validateRouteName(name, "canNavigateTo");
499
- }
471
+ const ctx = getInternals(this);
472
+
473
+ ctx.validator?.routes.validateRouteName(name, "canNavigateTo");
474
+ ctx.validator?.navigation.validateParams(params, "canNavigateTo");
500
475
 
501
476
  if (!this.#routes.hasRoute(name)) {
502
477
  return false;
503
478
  }
504
479
 
505
- const ctx = getInternals(this);
506
480
  const { name: resolvedName, params: resolvedParams } = ctx.forwardState(
507
481
  name,
508
482
  params ?? {},
@@ -524,27 +498,29 @@ export class Router<
524
498
  // Plugins
525
499
  // ============================================================================
526
500
 
527
- usePlugin(...plugins: PluginFactory<Dependencies>[]): Unsubscribe {
528
- if (!this.#noValidate) {
529
- // 1. Validate input arguments
530
- PluginsNamespace.validateUsePluginArgs<Dependencies>(plugins);
501
+ usePlugin(
502
+ ...plugins: (PluginFactory<Dependencies> | false | null | undefined)[]
503
+ ): Unsubscribe {
504
+ const filtered = plugins.filter(Boolean) as PluginFactory<Dependencies>[];
531
505
 
532
- // 2. Validate limit
533
- PluginsNamespace.validatePluginLimit(
534
- this.#plugins.count(),
535
- plugins.length,
536
- this.#limits.maxPlugins,
537
- );
506
+ if (filtered.length === 0) {
507
+ return () => {};
508
+ }
509
+
510
+ const ctx = getInternals(this);
538
511
 
539
- // 3. Validate no duplicates with existing plugins
540
- PluginsNamespace.validateNoDuplicatePlugins(
541
- plugins,
542
- this.#plugins.has.bind(this.#plugins),
512
+ ctx.validator?.plugins.validatePluginLimit(
513
+ this.#plugins.count(),
514
+ this.#limits,
515
+ );
516
+ for (const plugin of filtered) {
517
+ ctx.validator?.plugins.validateNoDuplicatePlugins(
518
+ plugin,
519
+ this.#plugins.getAll(),
543
520
  );
544
521
  }
545
522
 
546
- // 4. Execute (warnings, deduplication, initialization, commit)
547
- return this.#plugins.use(...plugins);
523
+ return this.#plugins.use(...filtered);
548
524
  }
549
525
 
550
526
  // ============================================================================
@@ -552,9 +528,7 @@ export class Router<
552
528
  // ============================================================================
553
529
 
554
530
  subscribe(listener: SubscribeFn): Unsubscribe {
555
- if (!this.#noValidate) {
556
- EventBusNamespace.validateSubscribeListener(listener);
557
- }
531
+ EventBusNamespace.validateSubscribeListener(listener);
558
532
 
559
533
  return this.#eventBus.subscribe(listener);
560
534
  }
@@ -568,19 +542,15 @@ export class Router<
568
542
  routeParams?: Params,
569
543
  options?: NavigationOptions,
570
544
  ): Promise<State> {
571
- // 1. Validate route name
572
- if (!this.#noValidate) {
573
- NavigationNamespace.validateNavigateArgs(routeName);
574
- }
545
+ const ctx = getInternals(this);
546
+
547
+ ctx.validator?.navigation.validateNavigateArgs(routeName);
548
+ ctx.validator?.navigation.validateParams(routeParams, "navigate");
575
549
 
576
- // 2. Validate parsed options
577
550
  const opts = options ?? EMPTY_OPTS;
578
551
 
579
- if (!this.#noValidate) {
580
- NavigationNamespace.validateNavigationOptions(opts, "navigate");
581
- }
552
+ ctx.validator?.navigation.validateNavigationOptions(opts, "navigate");
582
553
 
583
- // 3. Execute navigation with parsed arguments
584
554
  const promiseState = this.#navigation.navigate(
585
555
  routeName,
586
556
  routeParams ?? EMPTY_PARAMS,
@@ -600,19 +570,17 @@ export class Router<
600
570
  }
601
571
 
602
572
  navigateToDefault(options?: NavigationOptions): Promise<State> {
603
- // 1. Validate arguments
604
- if (!this.#noValidate) {
605
- NavigationNamespace.validateNavigateToDefaultArgs(options);
606
- }
573
+ const ctx = getInternals(this);
574
+
575
+ ctx.validator?.navigation.validateNavigateToDefaultArgs(options);
607
576
 
608
- // 2. Validate parsed options
609
577
  const opts = options ?? EMPTY_OPTS;
610
578
 
611
- if (!this.#noValidate) {
612
- NavigationNamespace.validateNavigationOptions(opts, "navigateToDefault");
613
- }
579
+ ctx.validator?.navigation.validateNavigationOptions(
580
+ opts,
581
+ "navigateToDefault",
582
+ );
614
583
 
615
- // 3. Execute navigation with parsed arguments
616
584
  const promiseState = this.#navigation.navigateToDefault(opts);
617
585
 
618
586
  if (this.#navigation.lastSyncResolved) {
@@ -631,6 +599,12 @@ export class Router<
631
599
  throw new RouterError(errorCodes.ROUTER_NOT_STARTED);
632
600
  }
633
601
 
602
+ if (path !== undefined && typeof path !== "string") {
603
+ throw new TypeError(
604
+ `[router.navigateToNotFound] path must be a string, got ${typeof path}`,
605
+ );
606
+ }
607
+
634
608
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- isActive() guarantees state exists
635
609
  const resolvedPath = path ?? this.#state.get()!.path;
636
610
 
@@ -1,5 +1,4 @@
1
1
  import { routeTreeToDefinitions } from "route-tree";
2
- import { getTypeDescription } from "type-guards";
3
2
 
4
3
  import { errorCodes } from "../constants";
5
4
  import { getInternals } from "../internals";
@@ -10,32 +9,6 @@ import { getLifecycleApi } from "./getLifecycleApi";
10
9
  import type { Route } from "../types";
11
10
  import type { DefaultDependencies, Router } from "@real-router/types";
12
11
 
13
- function validateCloneArgs(dependencies: unknown): void {
14
- if (dependencies === undefined) {
15
- return;
16
- }
17
-
18
- if (
19
- !(
20
- dependencies &&
21
- typeof dependencies === "object" &&
22
- dependencies.constructor === Object
23
- )
24
- ) {
25
- throw new TypeError(
26
- `[cloneRouter] Invalid dependencies: expected plain object or undefined, received ${getTypeDescription(dependencies)}`,
27
- );
28
- }
29
-
30
- for (const key in dependencies) {
31
- if (Object.getOwnPropertyDescriptor(dependencies, key)?.get) {
32
- throw new TypeError(
33
- `[cloneRouter] Getters not allowed in dependencies: "${key}"`,
34
- );
35
- }
36
- }
37
- }
38
-
39
12
  export function cloneRouter<
40
13
  Dependencies extends DefaultDependencies = DefaultDependencies,
41
14
  >(
@@ -48,9 +21,7 @@ export function cloneRouter<
48
21
  throw new RouterError(errorCodes.ROUTER_DISPOSED);
49
22
  }
50
23
 
51
- if (!ctx.noValidate) {
52
- validateCloneArgs(dependencies);
53
- }
24
+ ctx.validator?.dependencies.validateCloneArgs(dependencies);
54
25
 
55
26
  // Get source store directly
56
27
  const sourceStore = ctx.routeGetStore();