@real-router/validation-plugin 0.13.28 → 0.13.30

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["obj","warnOverwrite","collectUrlParams","assertNotAsync","typedStore","RouterError"],"sources":["../../src/type-guards/guards/primitives.ts","../../src/type-guards/guards/params.ts","../../src/type-guards/guards/navigation.ts","../../src/type-guards/internal/router-error.ts","../../src/type-guards/guards/routes.ts","../../src/type-guards/internal/meta-fields.ts","../../src/type-guards/guards/state.ts","../../src/type-guards/validators/routes.ts","../../src/type-guards/utilities/type-description.ts","../../src/helpers.ts","../../src/validators/dependencies.ts","../../src/validators/eventBus.ts","../../src/validators/lifecycle.ts","../../src/validators/navigation.ts","../../src/validators/options.ts","../../src/validators/plugins.ts","../../src/validators/retrospective.ts","../../src/validators/forwardTo.ts","../../src/validators/routes.ts","../../src/validators/state.ts","../../src/validationPlugin.ts"],"sourcesContent":["// packages/validation-plugin/src/type-guards/guards/primitives.ts\n\n/**\n * Type guard for string type.\n *\n * @param value - Value to check\n * @returns true if value is a string\n */\nexport function isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\n/**\n * Type guard for boolean type.\n *\n * @param value - Value to check\n * @returns true if value is a boolean\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === \"boolean\";\n}\n\n/**\n * Type guard for object key existence.\n * Narrows the key type to a valid key of the object.\n *\n * @param key - Key to check\n * @param obj - Object to check in\n * @returns true if key exists in object\n */\nexport function isObjKey<T extends object>(\n key: string,\n obj: T,\n): key is Extract<keyof T, string> {\n return key in obj;\n}\n","// packages/validation-plugin/src/type-guards/guards/params.ts\n\nimport type { Params } from \"@real-router/core\";\n\n/**\n * Is `value` an array, or a plain object (`Object.prototype` / `null` prototype)?\n * Class instances (Date, RegExp, Map, Set, ...) are not plain containers.\n *\n * @internal\n */\nfunction isPlainContainer(value: object): boolean {\n if (Array.isArray(value)) {\n return true;\n }\n\n const proto = Object.getPrototypeOf(value) as object | null;\n\n return proto === null || proto === Object.prototype;\n}\n\n/**\n * Pushes every child of an array or plain object onto the work-stack.\n *\n * @internal\n */\nfunction pushChildren(value: object, stack: unknown[]): void {\n const children = Array.isArray(value) ? value : Object.values(value);\n\n for (const child of children) {\n stack.push(child);\n }\n}\n\n/**\n * Is `value` a serializable primitive leaf? `string` and `boolean` always are; a\n * `number` only if finite (NaN/Infinity are not). Everything else (function,\n * symbol, bigint) is not serializable.\n *\n * @internal\n */\nfunction isSerializableLeaf(value: unknown): boolean {\n const type = typeof value;\n\n if (type === \"string\" || type === \"boolean\") {\n return true;\n }\n\n if (type === \"number\") {\n return Number.isFinite(value);\n }\n\n return false;\n}\n\n/**\n * Marker pushed onto the work-stack after a container's children. Popping it\n * means that container's whole subtree has been validated, so the container\n * leaves the active DFS path (`onPath`) and joins the fully-checked set\n * (`done`). A module-private class, so user data can never be mistaken for it.\n *\n * @internal\n */\nclass SubtreeExit {\n constructor(readonly container: object) {}\n}\n\n/**\n * Inspects one container popped from the work-stack. Rejects cycles (a back-edge\n * to a container still on the current DFS path) and class instances; skips\n * already-validated shared references; otherwise marks the container on-path and\n * queues its children plus a {@link SubtreeExit} marker.\n *\n * @returns false only when the container is invalid (cycle or class instance)\n * @internal\n */\nfunction visitContainer(\n value: object,\n stack: unknown[],\n onPath: WeakSet<object>,\n done: WeakSet<object>,\n): boolean {\n if (onPath.has(value)) {\n return false; // back-edge → genuine circular reference\n }\n\n if (done.has(value)) {\n return true; // shared reference / diamond — subtree already validated\n }\n\n if (!isPlainContainer(value)) {\n return false; // instance of a class\n }\n\n onPath.add(value);\n stack.push(new SubtreeExit(value));\n pushChildren(value, stack);\n\n return true;\n}\n\n/**\n * Internal helper to check if value is serializable (no circular refs, functions, instances).\n * Validates the entire object tree.\n *\n * Iterative (explicit work-stack) rather than recursive: a recursive walk overflows\n * the call stack with `RangeError` at ~2.4k levels of nesting on V8, which would break\n * the boolean contract on adversarial input reachable via `history.state` / user-supplied\n * params (#901). A heap-allocated stack scales to any depth.\n *\n * Cycle detection uses on-path (DFS gray/black) semantics, not \"ever-visited\":\n * `onPath` holds the containers on the current branch — a back-edge to one of them is\n * a genuine cycle and is rejected; `done` holds containers whose subtree is already\n * fully validated. A shared reference / diamond (the same object reached again *off*\n * the current path) is serializable — `JSON.stringify` duplicates it — so it is\n * accepted, and `done` makes the re-encounter O(1), keeping the walk linear on diamond\n * chains instead of exponential (#786). Genuine cycles stay rejected: the cycle vertex\n * is still on the path when re-encountered.\n *\n * @param root - Value to check\n * @returns true if value can be serialized\n * @internal\n */\nfunction isSerializable(root: unknown): boolean {\n const stack: unknown[] = [root];\n const onPath = new WeakSet<object>();\n const done = new WeakSet<object>();\n\n while (stack.length > 0) {\n const value = stack.pop();\n\n // Subtree fully processed: leave the current path, mark as validated.\n if (value instanceof SubtreeExit) {\n onPath.delete(value.container);\n done.add(value.container);\n\n continue;\n }\n\n // null/undefined are serializable (JSON.stringify handles them)\n if (value === null || value === undefined) {\n continue;\n }\n\n // Arrays and plain objects (typeof null is \"object\", handled above).\n if (typeof value === \"object\") {\n if (!visitContainer(value, stack, onPath, done)) {\n return false;\n }\n\n continue;\n }\n\n // Primitive leaf: string / boolean / finite number pass; function, symbol,\n // bigint, and NaN/Infinity do not.\n if (!isSerializableLeaf(value)) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Fast path check for primitive values (no recursion needed).\n * Returns true if primitive, false if needs deeper inspection.\n *\n * @internal\n */\nfunction isPrimitiveValue(value: unknown): boolean {\n if (value === null || value === undefined) {\n return true;\n }\n\n const type = typeof value;\n\n if (type === \"string\" || type === \"boolean\") {\n return true;\n }\n\n if (type === \"number\") {\n return Number.isFinite(value);\n }\n\n // object, array, function, symbol — need deeper check\n return false;\n}\n\n/**\n * Type guard for Params object.\n * Validates that all values are serializable (primitives, arrays, nested arrays, or nested objects).\n * Rejects circular references, functions, symbols, and class instances.\n * Allows null/undefined anywhere (objects and arrays).\n *\n * Performance optimization: Uses two-phase validation.\n * Phase 1 (fast path): Check if all values are primitives — O(p) without recursion.\n * Phase 2 (slow path): Full recursive validation only if nested structures detected.\n *\n * @param value - Value to check\n * @returns true if value is a valid Params object\n *\n * @example\n * // Valid\n * isParams({ id: '123', page: 1 }); // true (fast path)\n * isParams({ sort: ['name', 'age'] }); // true (slow path)\n * isParams({ filter: { status: 'active' } }); // true (slow path)\n * isParams({ teamId: null, orgId: undefined }); // true (fast path)\n * isParams({ matrix: [[1, 2], [3, 4]] }); // true (slow path)\n * isParams({ users: [{ id: 1 }, { id: 2 }] }); // true (slow path)\n * isParams({ scores: [100, null, 85] }); // true (slow path)\n * isParams({ a: shared, b: shared }); // true (shared reference / diamond, not a cycle)\n *\n * // Invalid\n * isParams({ fn: () => {} }); // false (function)\n * isParams({ date: new Date() }); // false (class instance)\n * isParams({ self: circularRef }); // false (circular reference)\n * isParams(null); // false (not an object)\n * isParams([]); // false (array, not object)\n */\nexport function isParams(value: unknown): value is Params {\n // Getter-safe boundary (#1052): a throwing `[[Get]]` (a value getter that\n // throws, or a Proxy trap) during the structural walk below must not crash —\n // an object whose structure cannot even be read is not valid params. Mirrors\n // the never-throw contract of #786/#901 (deep nesting / cycles), for a throwing\n // accessor. Wraps the whole walk (proto read, fast-path value reads, and the\n // recursive isSerializable slow path) in one boundary.\n try {\n return isParamsUnsafe(value);\n } catch {\n return false;\n }\n}\n\nfunction isParamsUnsafe(value: unknown): value is Params {\n // Reject null, undefined, and arrays (must be a plain object)\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n\n // Reject objects with custom prototype (e.g., Object.create(proto), class instances)\n // This check is required for both fast and slow paths\n const proto = Object.getPrototypeOf(value) as object | null;\n\n if (proto !== null && proto !== Object.prototype) {\n return false;\n }\n\n // Phase 1: Fast path for flat objects (95%+ of real-world params)\n // Check if all values are primitives — no recursion, no WeakSet allocation\n let needsDeepCheck = false;\n\n for (const key in value) {\n // Skip inherited properties (only check own properties for fast path)\n // Note: With proto === Object.prototype check above, inherited properties\n // would only come from Object.prototype, which has no enumerable properties.\n // This check is defensive against Object.prototype pollution — covered by the\n // prototype-pollution test, which surfaces an inherited enumerable key here.\n if (!Object.hasOwn(value, key)) {\n continue;\n }\n\n const val = (value as Record<string, unknown>)[key];\n\n if (!isPrimitiveValue(val)) {\n // Found non-primitive — need full validation\n // But first check for obvious invalids (functions, symbols)\n const type = typeof val;\n\n if (type === \"function\" || type === \"symbol\") {\n return false; // Early reject\n }\n\n needsDeepCheck = true;\n\n break; // Exit fast path, proceed to slow path\n }\n }\n\n // Fast path: all primitives, valid params\n if (!needsDeepCheck) {\n return true;\n }\n\n // Phase 2: Slow path — full recursive validation\n return isSerializable(value);\n}\n","// packages/validation-plugin/src/type-guards/guards/navigation.ts\n\nimport type { NavigationOptions } from \"@real-router/core\";\n\n// Constant in module scope - created once, not on every call\nconst NAVIGATION_OPTIONS_FIELDS = [\n \"replace\",\n \"reload\",\n \"force\",\n \"forceDeactivate\",\n \"redirected\",\n] as const;\n\n/**\n * Type guard for NavigationOptions.\n * Validates all optional boolean fields.\n *\n * @param value - Value to check\n * @returns true if value is a valid NavigationOptions object\n *\n * @example\n * isNavigationOptions({ replace: true }); // true\n * isNavigationOptions({ reload: false, force: true }); // true\n * isNavigationOptions({ replace: \"true\" }); // false (not boolean)\n * isNavigationOptions(null); // false\n */\nexport function isNavigationOptions(\n value: unknown,\n): value is NavigationOptions {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n\n const obj = value as Record<string, unknown>;\n\n // for-of instead of .every() - no iterator/callback allocation\n for (const field of NAVIGATION_OPTIONS_FIELDS) {\n const fieldValue = obj[field];\n\n if (fieldValue !== undefined && typeof fieldValue !== \"boolean\") {\n return false;\n }\n }\n\n // Validate signal field\n const signalValue = obj.signal;\n\n return signalValue === undefined || signalValue instanceof AbortSignal;\n}\n","// packages/validation-plugin/src/type-guards/internal/router-error.ts\n\n/**\n * Checks if string contains at least one non-whitespace character.\n * Used to validate that route name is not empty or only whitespace.\n * Matches: any non-whitespace character (\\S = [^ \\t\\n\\r\\f])\n *\n * @internal\n */\nexport const HAS_NON_WHITESPACE = /\\S/;\n\n/**\n * Pattern for complete route validation (all segments at once).\n * Matches: single segment or multiple segments separated by dots.\n * Each segment must start with letter/underscore, followed by alphanumeric/hyphen/underscore.\n * Rejects: leading/trailing/consecutive dots, segments starting with numbers/hyphens.\n * Note: Empty string is handled by early return in validateRouteName.\n *\n * @internal\n */\n// eslint-disable-next-line security/detect-unsafe-regex -- safe: each `(?:\\.…)*` repetition is anchored by a literal `.`, which `[\\w-]*` cannot match, so the inner/outer quantifiers consume disjoint classes — no catastrophic backtracking (safe-regex over-flags the nested `*`).\nexport const FULL_ROUTE_PATTERN = /^[A-Z_a-z][\\w-]*(?:\\.[A-Z_a-z][\\w-]*)*$/;\n\n/**\n * Maximum route name length to prevent DoS and performance issues.\n * This is a technical limit, not a business constraint.\n * Real route names rarely exceed 200 characters even with deep nesting.\n *\n * @internal\n */\nexport const MAX_ROUTE_NAME_LENGTH = 10_000;\n\n/**\n * Creates a TypeError with consistent router error message format.\n *\n * @param methodName - Name of the method that triggered the error\n * @param message - Error message\n * @returns TypeError with formatted message\n * @internal\n */\nexport function createRouterError(\n methodName: string,\n message: string,\n): TypeError {\n return new TypeError(`[router.${methodName}] ${message}`);\n}\n","// packages/validation-plugin/src/type-guards/guards/routes.ts\n\nimport {\n FULL_ROUTE_PATTERN,\n MAX_ROUTE_NAME_LENGTH,\n} from \"../internal/router-error\";\n\n/**\n * Type guard that checks if a value is a valid route name.\n *\n * Route name rules:\n * - Must be a string\n * - Empty string (\"\") is valid and represents the root node\n * - Can use dots (.) for hierarchy (e.g., \"users.profile\")\n * - Each segment must match [a-zA-Z_][a-zA-Z0-9_-]*\n * - No consecutive dots (..)\n * - No leading/trailing dots\n * - Cannot contain only whitespace\n * - System routes (starting with @@) bypass pattern validation\n *\n * @param name - Value to check\n * @returns true if name is a valid route name string\n *\n * @example\n * // Valid names\n * isRouteName(\"\"); // true (root node)\n * isRouteName(\"home\"); // true\n * isRouteName(\"users.profile\"); // true\n * isRouteName(\"admin_panel\"); // true\n * isRouteName(\"api-v2\"); // true\n * isRouteName(\"@@router/UNKNOWN_ROUTE\"); // true (system route)\n *\n * @example\n * // Invalid names\n * isRouteName(\" \"); // false (only whitespace)\n * isRouteName(\".users\"); // false (leading dot)\n * isRouteName(\"users.\"); // false (trailing dot)\n * isRouteName(\"users..profile\"); // false (consecutive dots)\n * isRouteName(\"users.123\"); // false (segment starts with number)\n * isRouteName(\"users profile\"); // false (contains space)\n */\nexport function isRouteName(name: unknown): name is string {\n if (typeof name !== \"string\") {\n return false;\n }\n\n // Empty string is valid (represents root node)\n if (name === \"\") {\n return true;\n }\n\n // Too long is invalid\n if (name.length > MAX_ROUTE_NAME_LENGTH) {\n return false;\n }\n\n // System routes are valid (bypass pattern validation)\n if (name.startsWith(\"@@\")) {\n return true;\n }\n\n // Regular routes must match pattern\n // Note: FULL_ROUTE_PATTERN rejects whitespace-only strings\n return FULL_ROUTE_PATTERN.test(name);\n}\n","// packages/validation-plugin/src/type-guards/internal/meta-fields.ts\n\nimport { isParams } from \"../guards/params\";\nimport { isRouteName } from \"../guards/routes\";\n\n/**\n * Type guard helper that checks if required State fields have valid types.\n * Validates that name, path, and params exist and have correct types.\n * Used by isState and isStateStrict.\n *\n * @param obj - Object to check\n * @returns true if object has valid required fields\n * @internal\n */\nexport function isRequiredFields(obj: Record<string, unknown>): boolean {\n return (\n isRouteName(obj.name) &&\n typeof obj.path === \"string\" &&\n isParams(obj.params)\n );\n}\n","// packages/validation-plugin/src/type-guards/guards/state.ts\n\nimport { isRequiredFields } from \"../internal/meta-fields\";\n\nimport type { Params, State } from \"@real-router/core\";\n\n/**\n * Type guard for State object.\n * Checks for required fields: name, params, path.\n * Does NOT validate meta field deeply.\n *\n * @param value - Value to check\n * @returns true if value is a valid State object\n *\n * @example\n * isState({ name: 'home', params: {}, path: '/' }); // true\n * isState({ name: 'home', path: '/' }); // false (missing params)\n */\nexport function isState<P extends Params = Params>(\n value: unknown,\n): value is State<P> {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n const obj = value as Record<string, unknown>;\n\n return isRequiredFields(obj);\n}\n","// packages/validation-plugin/src/type-guards/validators/routes.ts\n\nimport {\n createRouterError,\n FULL_ROUTE_PATTERN,\n HAS_NON_WHITESPACE,\n MAX_ROUTE_NAME_LENGTH,\n} from \"../internal/router-error\";\n\n/**\n * Validates that a route name is a valid string and matches the expected format.\n * Throws a descriptive error if validation fails.\n *\n * Route name rules:\n * - Must be a string\n * - Empty string (\"\") is valid and represents the root node\n * - Can use dots (.) for hierarchy (e.g., \"users.profile\")\n * - Each segment must match [a-zA-Z_][a-zA-Z0-9_-]*\n * - No consecutive dots (..)\n * - No leading/trailing dots\n * - Cannot contain only whitespace\n * - System routes (starting with @@) bypass pattern validation\n *\n * @param name - Route name to validate\n * @param methodName - Name of calling method for error messages\n * @throws {TypeError} If name is invalid\n *\n * @example\n * // Valid names\n * validateRouteName(\"\", \"add\"); // ok (root node)\n * validateRouteName(\"home\", \"add\"); // ok\n * validateRouteName(\"users.profile\", \"add\"); // ok\n * validateRouteName(\"@@router/UNKNOWN_ROUTE\", \"add\"); // ok (system route)\n *\n * @example\n * // Invalid names (throws)\n * validateRouteName(\" \", \"add\"); // throws (only whitespace)\n * validateRouteName(\".users\", \"add\"); // throws (leading dot)\n * validateRouteName(\"users.\", \"add\"); // throws (trailing dot)\n * validateRouteName(\"users..profile\", \"add\"); // throws (consecutive dots)\n * validateRouteName(\"users.123\", \"add\"); // throws (segment starts with number)\n * validateRouteName(\"users profile\", \"add\"); // throws (contains space)\n */\nexport function validateRouteName(\n name: unknown,\n methodName: string,\n): asserts name is string {\n // Type check\n if (typeof name !== \"string\") {\n throw createRouterError(\n methodName,\n `Route name must be a string, got ${typeof name}`,\n );\n }\n\n // Empty string is valid (represents root node) - fast path\n if (name === \"\") {\n return;\n }\n\n // Whitespace-only strings are invalid\n if (!HAS_NON_WHITESPACE.test(name)) {\n throw createRouterError(\n methodName,\n \"Route name cannot contain only whitespace\",\n );\n }\n\n // Length check for technical safety\n if (name.length > MAX_ROUTE_NAME_LENGTH) {\n throw createRouterError(\n methodName,\n `Route name exceeds maximum length of ${MAX_ROUTE_NAME_LENGTH} characters. This is a technical safety limit.`,\n );\n }\n\n // System routes bypass validation (e.g., @@router/UNKNOWN_ROUTE)\n // SECURITY NOTE: System routes are currently created ONLY in router code,\n // not from user input. If this changes, add sanitization for <>\"'&\\x00-\\x1F\n if (name.startsWith(\"@@\")) {\n return;\n }\n\n // Validate route pattern\n if (!FULL_ROUTE_PATTERN.test(name)) {\n throw createRouterError(\n methodName,\n `Invalid route name \"${name}\". Each segment must start with a letter or underscore, followed by letters, numbers, underscores, or hyphens. Segments are separated by dots (e.g., \"users.profile\").`,\n );\n }\n}\n","// packages/validation-plugin/src/type-guards/utilities/type-description.ts\n\n/**\n * Gets a human-readable description of a value's type.\n * Used for error messages to provide helpful debugging information.\n *\n * @param value - Value to describe\n * @returns String description of the value's type\n *\n * @example\n * getTypeDescription(null); // \"null\"\n * getTypeDescription([1, 2, 3]); // \"array[3]\"\n * getTypeDescription(new Date()); // \"Date\"\n * getTypeDescription({}); // \"object\"\n * getTypeDescription(\"hello\"); // \"string\"\n */\nexport function getTypeDescription(value: unknown): string {\n // Handle null explicitly (typeof null === \"object\")\n if (value === null) {\n return \"null\";\n }\n\n // Array with length info\n if (Array.isArray(value)) {\n return `array[${value.length}]`;\n }\n\n if (typeof value === \"object\") {\n // Read `constructor`/`.name` defensively: an adversarial own `constructor`\n // (null, a string, a number, …) is not a real constructor and must not crash\n // here nor yield a non-string (#787); a THROWING accessor — a `constructor`\n // getter, a function constructor with a throwing `.name` getter, or a Proxy\n // that throws on [[Get]] — must not crash either (#1052). Both fall back to\n // \"object\", the same as the non-function-value branch.\n try {\n const ctor: unknown = (value as { constructor?: unknown }).constructor;\n\n // Return constructor name for class instances\n if (typeof ctor === \"function\" && ctor.name !== \"Object\") {\n return ctor.name || \"object\"; // empty name (anonymous class) → \"object\"\n }\n } catch {\n // Throwing constructor/.name getter or Proxy [[Get]] → fall through (#1052).\n }\n\n // Plain object, null-prototype object, or adversarial / throwing constructor\n return \"object\";\n }\n\n // Primitive types (string, number, boolean, undefined, etc.)\n // Note: typeof undefined === \"undefined\", so we don't need explicit check\n return typeof value;\n}\n","// packages/validation-plugin/src/helpers.ts\n\nexport function computeThresholds(limit: number): {\n warn: number;\n error: number;\n} {\n return {\n warn: Math.floor(limit * 0.2),\n error: Math.floor(limit * 0.5),\n };\n}\n","// packages/validation-plugin/src/validators/dependencies.ts\n\nimport { computeThresholds } from \"../helpers\";\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { RouterLogger } from \"@real-router/core\";\n\nconst DEFAULT_MAX_DEPENDENCIES = 100;\n\n/**\n * Captured at module load, mirroring `core/src/guards.ts`. A validator is only\n * as strong as the intrinsic it reads WHEN IT RUNS.\n */\nconst objectKeys = Object.keys;\nconst getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nconst getPrototypeOf = Object.getPrototypeOf;\nconst ObjectCtor = Object;\n\n/**\n * ⚑ The MIRROR of `ingestDependencies` (core `guards.ts` — renamed from\n * `guardDependencies` when it absorbed the copy walk, #1860/#1861), and it has to\n * stay one.\n *\n * This plugin's whole contract is `plugin ⊇ core`: it may diagnose more, never\n * refuse what core accepts. Core moved this predicate from `deps.constructor` to\n * the PROTOTYPE's `constructor` (#1858) and began admitting a null prototype;\n * while these two copies still read the instance, an application running the\n * plugin got a false reject on exactly the shapes core had just widened —\n * `setDependencies` and `cloneRouter`'s override bag both threw on a bag\n * carrying a `constructor` key, which is the #1858 defect on a different door.\n *\n * ⚠ The walk is `objectKeys`, not `for…in`, for the same reason core's is\n * (#1799): `for…in` visits inherited names that `getOwnPropertyDescriptor`\n * answers `undefined` for, so the loop iterated exactly the names it could not\n * judge — and on a Proxy the two disagree about ownership outright.\n */\nfunction isPlainBag(value: unknown): value is Record<string, unknown> {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const proto = getPrototypeOf(value) as { constructor?: unknown } | null;\n\n return proto === null || proto.constructor === ObjectCtor;\n}\n\nexport function validateDependencyName(\n name: unknown,\n methodName: string,\n): asserts name is string {\n if (typeof name !== \"string\") {\n throw new TypeError(\n `[router.${methodName}] dependency name must be a string, got ${typeof name}`,\n );\n }\n}\n\nexport function validateSetDependencyArgs(\n name: unknown,\n): asserts name is string {\n if (typeof name !== \"string\") {\n throw new TypeError(\n `[router.setDependency] dependency name must be a string, got ${typeof name}`,\n );\n }\n}\n\nexport function validateDependenciesObject(\n deps: unknown,\n methodName: string,\n): asserts deps is Record<string, unknown> {\n if (!isPlainBag(deps)) {\n throw new TypeError(\n `[router.${methodName}] Invalid argument: expected plain object, received ${getTypeDescription(deps)}`,\n );\n }\n\n for (const key of objectKeys(deps)) {\n if (getOwnPropertyDescriptor(deps, key)?.get) {\n throw new TypeError(\n `[router.${methodName}] Getters not allowed: \"${key}\"`,\n );\n }\n }\n}\n\nexport function validateDependencyExists(\n value: unknown,\n dependencyName: string,\n): asserts value is NonNullable<unknown> {\n if (value === undefined) {\n throw new ReferenceError(\n `[router.getDependency] dependency \"${dependencyName}\" not found`,\n );\n }\n}\n\nexport function validateDependencyCount(\n store: unknown,\n methodName: string,\n logger: RouterLogger,\n): void {\n const typedStore = store as {\n dependencies: Record<string, unknown>;\n limits?: { maxDependencies?: number };\n };\n const maxDependencies =\n typedStore.limits?.maxDependencies ?? DEFAULT_MAX_DEPENDENCIES;\n\n if (maxDependencies === 0) {\n return;\n }\n\n const currentCount = Object.keys(typedStore.dependencies).length;\n const { warn, error } = computeThresholds(maxDependencies);\n\n if (currentCount >= maxDependencies) {\n throw new RangeError(\n `[router.${methodName}] Dependency limit exceeded (${maxDependencies}). Current: ${currentCount}.`,\n );\n }\n if (currentCount === error) {\n logger.error(\n `router.${methodName}`,\n `${currentCount} dependencies registered! This indicates architectural problems. Hard limit at ${maxDependencies}.`,\n );\n } else if (currentCount === warn) {\n logger.warn(\n `router.${methodName}`,\n `${currentCount} dependencies registered. Consider if all are necessary.`,\n );\n }\n}\n\nexport function validateCloneArgs(dependencies: unknown): void {\n if (dependencies === undefined) {\n return;\n }\n\n if (!isPlainBag(dependencies)) {\n throw new TypeError(\n `[cloneRouter] Invalid dependencies: expected plain object or undefined, received ${typeof dependencies}`,\n );\n }\n\n for (const key of objectKeys(dependencies)) {\n if (getOwnPropertyDescriptor(dependencies, key)?.get) {\n throw new TypeError(\n `[cloneRouter] Getters not allowed in dependencies: \"${key}\"`,\n );\n }\n }\n}\n\nexport function warnOverwrite(\n name: string,\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(\n `router.${methodName}`,\n \"Router dependency already exists and is being overwritten:\",\n name,\n );\n}\n\nexport function warnBatchOverwrite(\n keys: string[],\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(`router.${methodName}`, \"Overwritten:\", keys.join(\", \"));\n}\n\nexport function warnRemoveNonExistent(\n name: unknown,\n logger: RouterLogger,\n): void {\n logger.warn(\n \"router.removeDependency\",\n `Attempted to remove non-existent dependency: \"${typeof name === \"string\" ? name : String(name)}\"`,\n );\n}\n","// packages/validation-plugin/src/validators/eventBus.ts\n\nimport { computeThresholds } from \"../helpers\";\n\nimport type { Plugin, RouterLogger } from \"@real-router/core\";\n\nconst DEFAULT_MAX_LISTENERS = 10_000;\n\n// Local types — mirrors EventName and EventMethodMap from @real-router/types\n// (@real-router/types is not a direct dependency of this package)\nexport interface EventMethodMap {\n $start: \"onStart\";\n $stop: \"onStop\";\n $$start: \"onTransitionStart\";\n $$leaveApprove: \"onTransitionLeaveApprove\";\n $$cancel: \"onTransitionCancel\";\n $$success: \"onTransitionSuccess\";\n $$error: \"onTransitionError\";\n}\n\nexport type EventName = keyof EventMethodMap;\n\n// Single source of truth (plugin-owned): core has no `validEventNames` constant\n// and does not enforce event-name validity — this set is the sole owner.\nconst validEventNames = new Set<EventName>([\n \"$start\",\n \"$stop\",\n \"$$start\",\n \"$$leaveApprove\",\n \"$$cancel\",\n \"$$success\",\n \"$$error\",\n]);\n\nexport function validateEventName(eventName: unknown): void {\n if (!validEventNames.has(eventName as EventName)) {\n throw new TypeError(\n `[router.addEventListener] Invalid event name: ${String(eventName)}. Must be one of: $start, $stop, $$start, $$leaveApprove, $$cancel, $$success, $$error`,\n );\n }\n}\n\nexport function validateListenerArgs<E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n): void {\n validateEventName(eventName);\n\n if (typeof cb !== \"function\") {\n throw new TypeError(\n `[router.addEventListener] callback must be a function, got ${typeof cb}`,\n );\n }\n}\n\n/**\n * Proactive warn@20% / error@50% threshold for the per-event listener counter\n * (#1188) — the fourth resource counter, mirroring plugins / lifecycle /\n * dependencies. Fired by core from `subscribe` / `addEventListener` with the\n * POST-add count. Core keeps the emitter's bare-`Error` hard cap; this only\n * surfaces an actionable, `[router.<method>]`-prefixed signal well before it, so\n * a listener leak (e.g. a missing unsubscribe — #766) is caught early instead of\n * OOM-ing at the 10 000-listener wall. `maxListeners === 0` means unlimited.\n */\nexport function validateListenerCountThresholds(\n count: number,\n eventName: string,\n methodName: string,\n // logger precedes the defaulted maxListeners — defaults must come last\n // (S1788; logger was appended when the per-router RouterLogger replaced the\n // former global singleton, #724).\n logger: RouterLogger,\n maxListeners: number = DEFAULT_MAX_LISTENERS,\n): void {\n if (maxListeners === 0) {\n return;\n }\n\n const { warn, error } = computeThresholds(maxListeners);\n\n if (count >= error) {\n logger.error(\n `router.${methodName}`,\n `${count} listeners registered for \"${eventName}\"! This is excessive. Hard limit at ${maxListeners}.`,\n );\n } else if (count >= warn) {\n logger.warn(\n `router.${methodName}`,\n `${count} listeners registered for \"${eventName}\". Consider whether all are necessary — a growing count usually means a missing unsubscribe.`,\n );\n }\n}\n","// packages/validation-plugin/src/validators/lifecycle.ts\n\nimport { computeThresholds } from \"../helpers\";\nimport { isBoolean, getTypeDescription } from \"../type-guards\";\n\nimport type {\n GuardFnFactory,\n DefaultDependencies,\n RouterLogger,\n} from \"@real-router/core\";\n\nconst DEFAULT_MAX_LIFECYCLE_HANDLERS = 200;\n\nexport function validateHandler<D extends DefaultDependencies>(\n handler: unknown,\n methodName: string,\n): asserts handler is GuardFnFactory<D> | boolean {\n if (!isBoolean(handler) && typeof handler !== \"function\") {\n throw new TypeError(\n `[router.${methodName}] Handler must be a boolean or factory function, ` +\n `got ${getTypeDescription(handler)}`,\n );\n }\n}\n\nexport function validateHandlerLimit(\n currentCount: number,\n methodName: string,\n maxLifecycleHandlers: number = DEFAULT_MAX_LIFECYCLE_HANDLERS,\n): void {\n if (maxLifecycleHandlers === 0) {\n return;\n }\n\n if (currentCount >= maxLifecycleHandlers) {\n throw new RangeError(\n `[router.${methodName}] Lifecycle handler limit exceeded (${maxLifecycleHandlers}). ` +\n `This indicates too many routes with individual handlers. ` +\n `Consider using plugins for cross-cutting concerns.`,\n );\n }\n}\n\nexport function validateLifecycleCountThresholds(\n count: number,\n methodName: string,\n maxHandlers: number,\n logger: RouterLogger,\n): void {\n if (maxHandlers === 0) {\n return;\n }\n\n const { warn, error } = computeThresholds(maxHandlers);\n\n if (count >= error) {\n logger.error(\n `router.${methodName}`,\n `${count} lifecycle handlers registered! This is excessive. Hard limit at ${maxHandlers}.`,\n );\n } else if (count >= warn) {\n logger.warn(\n `router.${methodName}`,\n `${count} lifecycle handlers registered. Consider consolidating logic.`,\n );\n }\n}\n\nexport function warnOverwrite(\n name: string,\n type: string,\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(\n `router.${methodName}`,\n `Overwriting existing ${type} handler for route \"${name}\"`,\n );\n}\n\nexport function warnAsyncGuardSync(\n name: string,\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(\n `router.${methodName}`,\n `Guard for \"${name}\" returned a Promise. Sync check cannot resolve async guards — returning false.`,\n );\n}\n","// packages/validation-plugin/src/validators/navigation.ts\n\nimport {\n getTypeDescription,\n isNavigationOptions,\n isParams,\n isString,\n} from \"../type-guards\";\n\nimport type { NavigationOptions } from \"@real-router/core\";\n\nexport function validateNavigateArgs(name: unknown): asserts name is string {\n if (typeof name !== \"string\") {\n throw new TypeError(\n `[router.navigate] Invalid route name: expected string, got ${getTypeDescription(name)}`,\n );\n }\n}\n\nexport function validateNavigateToDefaultArgs(opts: unknown): void {\n if (opts !== undefined && (typeof opts !== \"object\" || opts === null)) {\n throw new TypeError(\n `[router.navigateToDefault] Invalid options: ${getTypeDescription(opts)}. Expected NavigationOptions object.`,\n );\n }\n}\n\nexport function validateNavigateToStateArgs(state: unknown): void {\n if (typeof state !== \"object\" || state === null) {\n throw new TypeError(\n `[router.navigateToState] Invalid state: ${getTypeDescription(state)}. Expected State object.`,\n );\n }\n\n const candidate = state as { name: unknown; params: unknown; path: unknown };\n\n if (!isString(candidate.name)) {\n throw new TypeError(\n `[router.navigateToState] Invalid state.name: ${getTypeDescription(candidate.name)}. Expected string.`,\n );\n }\n if (!isParams(candidate.params)) {\n throw new TypeError(\n `[router.navigateToState] Invalid state.params: ${getTypeDescription(candidate.params)}. Expected plain object.`,\n );\n }\n if (!isString(candidate.path)) {\n throw new TypeError(\n `[router.navigateToState] Invalid state.path: ${getTypeDescription(candidate.path)}. Expected string.`,\n );\n }\n}\n\nexport function validateNavigationOptions(\n opts: unknown,\n methodName: string,\n): asserts opts is NavigationOptions {\n if (!isNavigationOptions(opts)) {\n throw new TypeError(\n `[router.${methodName}] Invalid options: ${getTypeDescription(opts)}. Expected NavigationOptions object.`,\n );\n }\n}\n\n// C0 control chars (U+0000–U+001F) and DEL (U+007F). Core percent-encodes them\n// into the URL path (%00, %01, …) instead of failing, admitting unreadable,\n// non-copyable paths into committed state (#942).\n// eslint-disable-next-line no-control-regex -- matching control characters IS the validation here (#942)\nconst CONTROL_CHARS_RE = /[\\u0000-\\u001F\\u007F]/;\n\n/**\n * Rejects param VALUES that cannot safely round-trip through a URL path:\n * - `symbol` / `bigint` stringify lossily — a Symbol keeps its raw identity in\n * `state.params` and never matches back from the path, so navigation\n * \"succeeds\" with a corrupt, non-round-tripping path (#934);\n * - a `string` carrying control characters corrupts the path segment (#942).\n *\n * Pinpoints the offending key so the message is actionable, instead of the\n * generic \"params must be a plain object\" shape error.\n */\nfunction assertValidParamValues(\n params: Record<string, unknown>,\n methodName: string,\n): void {\n for (const key in params) {\n if (!Object.hasOwn(params, key)) {\n continue;\n }\n\n const value = params[key];\n const valueType = typeof value;\n\n if (valueType === \"symbol\" || valueType === \"bigint\") {\n throw new TypeError(\n `[router.${methodName}] param \"${key}\" cannot be a ${valueType} — it does not round-trip through the URL path. Use a string, number, or boolean.`,\n );\n }\n\n if (valueType === \"string\" && CONTROL_CHARS_RE.test(value as string)) {\n throw new TypeError(\n `[router.${methodName}] param \"${key}\" must not contain control characters (NUL / C0 / DEL) — they corrupt the URL path.`,\n );\n }\n }\n}\n\nexport function validateNavigateParams(\n params: unknown,\n methodName: string,\n): void {\n if (params === undefined) {\n return;\n }\n\n // Inspect individual values first so a Symbol/BigInt/control-char value gets a\n // precise, value-specific message instead of the generic shape error below.\n if (typeof params === \"object\" && params !== null && !Array.isArray(params)) {\n assertValidParamValues(params as Record<string, unknown>, methodName);\n }\n\n if (!isParams(params)) {\n throw new TypeError(\n `[router.${methodName}] params must be a plain object, got ${getTypeDescription(params)}`,\n );\n }\n}\n\nexport function validateStartArgs(path: unknown): void {\n // undefined is allowed — browser-plugin injects path via interceptor AFTER facade validation\n if (path !== undefined && typeof path !== \"string\") {\n throw new TypeError(\n `[router.start] path must be a string, got ${getTypeDescription(path)}.`,\n );\n }\n if (typeof path === \"string\") {\n // #942: a NUL byte / control char would be silently percent-encoded into\n // state.path (%00, %01) by core — reject it with an actionable error.\n if (CONTROL_CHARS_RE.test(path)) {\n throw new TypeError(\n `[router.start] path must not contain control characters (NUL / C0 / DEL).`,\n );\n }\n if (path !== \"\" && !path.startsWith(\"/\")) {\n throw new TypeError(\n `[router.start] path must start with \"/\", got \"${path}\".`,\n );\n }\n }\n}\n","// packages/validation-plugin/src/validators/options.ts\n\nimport { isObjKey } from \"../type-guards\";\n\nconst VALID_OPTION_VALUES = {\n trailingSlash: [\"strict\", \"never\", \"always\", \"preserve\"] as const,\n queryParamsMode: [\"default\", \"strict\", \"loose\"] as const,\n urlParamsEncoding: [\"default\", \"uri\", \"uriComponent\", \"none\"] as const,\n} as const;\n\nconst VALID_QUERY_PARAMS = {\n arrayFormat: [\"none\", \"brackets\", \"index\", \"comma\"] as const,\n booleanFormat: [\"none\", \"auto\", \"empty-true\"] as const,\n nullFormat: [\"default\", \"hidden\"] as const,\n numberFormat: [\"none\", \"auto\"] as const,\n} as const;\n\n// `logger` is a valid option name, but its contents are NOT validated here.\n// The Router constructor consumes `options.logger` (applies it to the\n// process-global logger singleton via `logger.configure()`) and strips the key\n// before options are stored (#724). The retrospective pass reads the stored,\n// logger-stripped options, so any logger validation in this plugin is dead on\n// the live path. Logger config is therefore validated solely by core's\n// `isLoggerConfig` guard at construction — the only place the input exists (#789).\nconst KNOWN_OPTIONS = new Set<string>([\n \"defaultRoute\",\n \"defaultParams\",\n \"defaultSearch\",\n \"trailingSlash\",\n \"caseSensitive\",\n \"queryParamsMode\",\n \"queryParams\",\n \"urlParamsEncoding\",\n \"allowNotFound\",\n \"rewritePathOnMatch\",\n \"logger\",\n \"limits\",\n]);\n\n// Local type - mirrors LimitsConfig from @real-router/types\n// (@real-router/types is not a direct dependency of this package)\ninterface LimitsConfig {\n maxDependencies: number;\n maxPlugins: number;\n maxListeners: number;\n warnListeners: number;\n maxLifecycleHandlers: number;\n}\n\n// Single source of truth (plugin-owned): core has no `LIMIT_BOUNDS` constant and\n// does not enforce these bounds — this constant is the sole owner.\nconst LIMIT_BOUNDS = {\n maxDependencies: { min: 0, max: 10_000 },\n maxPlugins: { min: 0, max: 1000 },\n maxListeners: { min: 0, max: 100_000 },\n warnListeners: { min: 0, max: 100_000 },\n maxLifecycleHandlers: { min: 0, max: 10_000 },\n} as const;\n\nexport function validateLimitValue(\n limitName: keyof LimitsConfig,\n value: unknown,\n methodName: string,\n): void {\n if (typeof value !== \"number\" || !Number.isInteger(value)) {\n throw new TypeError(\n `[router.${methodName}] limit \"${limitName}\" must be an integer, got ${String(value)}`,\n );\n }\n\n const bounds = LIMIT_BOUNDS[limitName];\n\n if (value < bounds.min || value > bounds.max) {\n throw new RangeError(\n `[router.${methodName}] limit \"${limitName}\" must be between ${bounds.min} and ${bounds.max}, got ${value}`,\n );\n }\n}\n\nexport function validateLimits(\n limits: unknown,\n methodName: string,\n): asserts limits is Partial<LimitsConfig> {\n if (!limits || typeof limits !== \"object\" || limits.constructor !== Object) {\n throw new TypeError(\n `[router.${methodName}] invalid limits: expected plain object, got ${typeof limits}`,\n );\n }\n\n for (const [key, value] of Object.entries(limits)) {\n if (!Object.hasOwn(LIMIT_BOUNDS, key)) {\n throw new TypeError(`[router.${methodName}] unknown limit: \"${key}\"`);\n }\n\n if (value === undefined) {\n continue;\n }\n\n validateLimitValue(key as keyof LimitsConfig, value, methodName);\n }\n\n const { warnListeners, maxListeners } = limits as Partial<LimitsConfig>;\n\n if (\n typeof warnListeners === \"number\" &&\n typeof maxListeners === \"number\" &&\n maxListeners > 0 &&\n warnListeners > maxListeners\n ) {\n throw new RangeError(\n `[router.${methodName}] \"limits.warnListeners\" (${warnListeners}) must not exceed \"limits.maxListeners\" (${maxListeners}) — the warning channel would be unreachable`,\n );\n }\n}\n\nfunction validateStringEnum(\n value: unknown,\n optionName: string,\n validValues: readonly string[],\n methodName: string,\n): void {\n if (value === undefined) {\n return;\n }\n\n if (typeof value !== \"string\" || !validValues.includes(value)) {\n const validList = validValues.map((val) => `\"${val}\"`).join(\", \");\n const display = typeof value === \"string\" ? value : `(${typeof value})`;\n\n throw new TypeError(\n `[router.${methodName}] Invalid \"${optionName}\": \"${display}\". Must be one of: ${validList}`,\n );\n }\n}\n\nfunction validateDefaultRoute(defaultRoute: unknown, methodName: string): void {\n if (defaultRoute === undefined) {\n return;\n }\n\n if (typeof defaultRoute !== \"string\" && typeof defaultRoute !== \"function\") {\n throw new TypeError(\n `[router.${methodName}] Invalid \"defaultRoute\": expected string or function, got ${typeof defaultRoute}`,\n );\n }\n}\n\n/**\n * Shared by `defaultParams` and its query-channel twin `defaultSearch` — one\n * rule, two channels (RFC-4 M2 / #1548). Parameterised by the option name\n * rather than copied, so the two can never drift into accepting different\n * shapes for the same kind of value.\n */\nfunction validateDefaultBag(\n value: unknown,\n optionName: string,\n methodName: string,\n): void {\n if (value === undefined) {\n return;\n }\n\n if (typeof value === \"function\") {\n return;\n }\n\n if (\n !value ||\n typeof value !== \"object\" ||\n Array.isArray(value) ||\n value.constructor !== Object\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"${optionName}\": expected plain object or function, got ${typeof value}`,\n );\n }\n}\n\nfunction validateQueryParamsOptions(\n queryParams: unknown,\n methodName: string,\n): void {\n if (queryParams === undefined) {\n return;\n }\n\n if (\n !queryParams ||\n typeof queryParams !== \"object\" ||\n Array.isArray(queryParams)\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"queryParams\": expected plain object`,\n );\n }\n\n const qp = queryParams as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(qp)) {\n if (!isObjKey(key, VALID_QUERY_PARAMS)) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"queryParams.${key}\": unknown option`,\n );\n }\n\n validateStringEnum(\n value,\n `queryParams.${key}`,\n VALID_QUERY_PARAMS[key],\n methodName,\n );\n }\n}\n\nexport function validateOptions(options: unknown, methodName: string): void {\n if (!options || typeof options !== \"object\" || Array.isArray(options)) {\n throw new TypeError(\n `[router.${methodName}] Invalid options: expected plain object`,\n );\n }\n\n const opts = options as Record<string, unknown>;\n\n for (const key of Object.keys(opts)) {\n if (!KNOWN_OPTIONS.has(key)) {\n throw new TypeError(`[router.${methodName}] Unknown option: \"${key}\"`);\n }\n }\n\n validateDefaultRoute(opts.defaultRoute, methodName);\n validateDefaultBag(opts.defaultParams, \"defaultParams\", methodName);\n validateDefaultBag(opts.defaultSearch, \"defaultSearch\", methodName);\n validateStringEnum(\n opts.trailingSlash,\n \"trailingSlash\",\n VALID_OPTION_VALUES.trailingSlash,\n methodName,\n );\n validateStringEnum(\n opts.queryParamsMode,\n \"queryParamsMode\",\n VALID_OPTION_VALUES.queryParamsMode,\n methodName,\n );\n validateStringEnum(\n opts.urlParamsEncoding,\n \"urlParamsEncoding\",\n VALID_OPTION_VALUES.urlParamsEncoding,\n methodName,\n );\n\n if (\n opts.allowNotFound !== undefined &&\n typeof opts.allowNotFound !== \"boolean\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"allowNotFound\": expected boolean, got ${typeof opts.allowNotFound}`,\n );\n }\n\n if (\n opts.rewritePathOnMatch !== undefined &&\n typeof opts.rewritePathOnMatch !== \"boolean\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"rewritePathOnMatch\": expected boolean, got ${typeof opts.rewritePathOnMatch}`,\n );\n }\n\n if (\n opts.caseSensitive !== undefined &&\n typeof opts.caseSensitive !== \"boolean\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"caseSensitive\": expected boolean, got ${typeof opts.caseSensitive}`,\n );\n }\n\n validateQueryParamsOptions(opts.queryParams, methodName);\n\n if (opts.limits !== undefined) {\n validateLimits(opts.limits, methodName);\n }\n}\n","// packages/validation-plugin/src/validators/plugins.ts\n\nimport { computeThresholds } from \"../helpers\";\n\nimport type { RouterLogger } from \"@real-router/core\";\n\nconst DEFAULT_MAX_PLUGINS = 50;\n\nconst LOGGER_CTX = \"router.usePlugin\";\n\nconst PLUGIN_EVENTS_MAP: Record<string, true> = {\n onStart: true,\n onStop: true,\n onTransitionStart: true,\n onTransitionLeaveApprove: true,\n onTransitionSuccess: true,\n onTransitionError: true,\n onTransitionCancel: true,\n};\n\nexport function validatePluginLimit(\n currentCount: number,\n newCount: number,\n maxPlugins: number = DEFAULT_MAX_PLUGINS,\n): void {\n if (maxPlugins === 0) {\n return;\n }\n\n const totalCount = currentCount + newCount;\n\n if (totalCount > maxPlugins) {\n throw new RangeError(\n `[router.usePlugin] Plugin limit exceeded (${maxPlugins}). ` +\n `Current: ${currentCount}, Attempting to add: ${newCount}. ` +\n `This indicates an architectural problem. Consider consolidating plugins.`,\n );\n }\n}\n\nexport function validateNoDuplicatePlugins(\n factory: unknown,\n factories: unknown[],\n): void {\n if (factories.includes(factory)) {\n throw new Error(\n `[router.usePlugin] Plugin factory already registered. ` +\n `To re-register, first unsubscribe the existing plugin.`,\n );\n }\n}\n\nexport function validateCountThresholds(\n count: number,\n maxPlugins: number,\n logger: RouterLogger,\n): void {\n if (maxPlugins === 0) {\n return;\n }\n\n const { warn, error } = computeThresholds(maxPlugins);\n\n if (count >= error) {\n logger.error(\n LOGGER_CTX,\n `${count} plugins registered! This is excessive. Hard limit at ${maxPlugins}.`,\n );\n } else if (count >= warn) {\n logger.warn(\n LOGGER_CTX,\n `${count} plugins registered. Consider if all are necessary.`,\n );\n }\n}\n\nexport function validatePluginKeys(plugin: unknown): void {\n for (const key in plugin as Record<string, unknown>) {\n if (!(key === \"teardown\" || Object.hasOwn(PLUGIN_EVENTS_MAP, key))) {\n throw new TypeError(\n `[router.usePlugin] Unknown property '${key}'. Plugin must only contain event handlers and optional teardown.`,\n );\n }\n }\n}\n\nexport function warnBatchDuplicates(logger: RouterLogger): void {\n logger.warn(\n LOGGER_CTX,\n \"Duplicate factory in batch, will be registered once\",\n );\n}\n\nexport function warnPluginMethodType(\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(\n LOGGER_CTX,\n `Property '${methodName}' is not a function, skipping`,\n );\n}\n\nexport function warnPluginAfterStart(\n methodName: string,\n logger: RouterLogger,\n): void {\n if (methodName === \"onStart\") {\n logger.warn(\n LOGGER_CTX,\n \"Router already started, onStart will not be called\",\n );\n }\n}\n\nexport function validateAddInterceptorArgs(method: unknown, fn: unknown): void {\n const validMethods = [\"start\", \"buildPath\", \"forwardState\"];\n\n if (typeof method !== \"string\" || !validMethods.includes(method)) {\n throw new TypeError(\n `[router.addInterceptor] Invalid method: \"${String(method)}\". Must be one of: ${validMethods.join(\", \")}`,\n );\n }\n if (typeof fn !== \"function\") {\n throw new TypeError(\n `[router.addInterceptor] interceptor must be a function, got ${typeof fn}`,\n );\n }\n}\n","// packages/validation-plugin/src/validators/retrospective.ts\n\nimport { resolveForwardChain as coreResolveForwardChain } from \"@real-router/core\";\n\n/**\n * Retrospective validators — run AFTER the route tree is already built.\n * Called by the validation plugin at usePlugin() time, in a try/catch with rollback.\n *\n * The plugin is registered AFTER the constructor, so all routes are already in the store.\n * These functions receive store objects as parameters and cast internally using\n * local structural interfaces to avoid tight coupling to core internal types.\n *\n * All parameters are typed as `unknown` — cast internally as needed.\n */\n\n// =============================================================================\n// Local structural interfaces (cast-only, not imported from core internals)\n// =============================================================================\n\ninterface LocalSegmentParamMeta {\n urlParams: readonly string[];\n spatParams: readonly string[];\n}\n\ninterface LocalRouteSegment {\n paramMeta: LocalSegmentParamMeta;\n}\n\ninterface LocalRouteTree {\n children: Map<string, LocalRouteTree>;\n paramMeta: LocalSegmentParamMeta;\n}\n\ninterface LocalRouteMatcher {\n getSegmentsByName: (\n name: string,\n ) => readonly LocalRouteSegment[] | null | undefined;\n}\n\ninterface LocalRouteConfig {\n forwardMap: Record<string, string>;\n forwardFnMap: Record<string, unknown>;\n defaultParams: Record<string, unknown>;\n decoders: Record<string, unknown>;\n encoders: Record<string, unknown>;\n}\n\ninterface LocalRouteDefinition {\n name: string;\n path: string;\n children?: LocalRouteDefinition[];\n}\n\ninterface LocalRoutesStore {\n definitions: LocalRouteDefinition[];\n config: LocalRouteConfig;\n tree: LocalRouteTree;\n matcher: LocalRouteMatcher;\n}\n\ninterface LocalDependencyLimits {\n maxDependencies: number;\n maxPlugins: number;\n maxListeners: number;\n warnListeners: number;\n maxLifecycleHandlers: number;\n}\n\n// =============================================================================\n// Private helpers\n// =============================================================================\n\nfunction assertRoutesStore(store: unknown, fnName: string): LocalRoutesStore {\n if (!store || typeof store !== \"object\") {\n throw new TypeError(\n `[validation-plugin] ${fnName}: store must be an object`,\n );\n }\n\n const storeRecord = store as Record<string, unknown>;\n\n if (!Array.isArray(storeRecord.definitions)) {\n throw new TypeError(\n `[validation-plugin] ${fnName}: store.definitions must be an array`,\n );\n }\n\n if (!storeRecord.config || typeof storeRecord.config !== \"object\") {\n throw new TypeError(\n `[validation-plugin] ${fnName}: store.config must be an object`,\n );\n }\n\n if (!storeRecord.tree || typeof storeRecord.tree !== \"object\") {\n throw new TypeError(\n `[validation-plugin] ${fnName}: store.tree must be an object`,\n );\n }\n\n return storeRecord as unknown as LocalRoutesStore;\n}\n\nfunction walkDefinitions(\n definitions: LocalRouteDefinition[],\n callback: (def: LocalRouteDefinition, fullName: string) => void,\n parentName = \"\",\n): void {\n for (const def of definitions) {\n const fullName = parentName ? `${parentName}.${def.name}` : def.name;\n\n callback(def, fullName);\n\n if (def.children) {\n walkDefinitions(def.children, callback, fullName);\n }\n }\n}\n\nfunction routeExistsInTree(tree: LocalRouteTree, routeName: string): boolean {\n const segments = routeName.split(\".\");\n let current: LocalRouteTree | undefined = tree;\n\n for (const segment of segments) {\n current = current.children.get(segment);\n\n if (!current) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Wraps core's resolveForwardChain with [validation-plugin] prefix on errors.\n * Core's version throws plain Error messages; retrospective validation\n * needs the [validation-plugin] prefix for consistency.\n */\nfunction resolveForwardChainWithPrefix(\n startRoute: string,\n forwardMap: Record<string, string>,\n): string {\n try {\n return coreResolveForwardChain(startRoute, forwardMap);\n } catch (error) {\n throw new Error(`[validation-plugin] ${(error as Error).message}`, {\n cause: error,\n });\n }\n}\n\nfunction collectUrlParams(segments: readonly LocalRouteSegment[]): Set<string> {\n const params = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n params.add(param);\n }\n\n for (const param of segment.paramMeta.spatParams) {\n params.add(param);\n }\n }\n\n return params;\n}\n\n/**\n * Asserts that a function value is not async (native or transpiled).\n * Adapted from: assertNotAsync() in RoutesNamespace/validators.ts\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- needs constructor.name access\nfunction assertNotAsync(fn: Function, label: string, routeName: string): void {\n const function_ = fn as {\n constructor: { name: string };\n toString: () => string;\n };\n\n if (\n function_.constructor.name === \"AsyncFunction\" ||\n function_.toString().includes(\"__awaiter\")\n ) {\n throw new TypeError(\n `[validation-plugin] Route \"${routeName}\" ${label} cannot be async`,\n );\n }\n}\n\n// =============================================================================\n// 1. validateExistingRoutes\n// =============================================================================\n\n/**\n * Validates the existing route tree/definitions for structural integrity.\n * Walks all route definitions, checking each name and path shape.\n * Adapted from: validateRoutes() in RoutesNamespace/validators.ts\n *\n * Duplicate route names are intentionally NOT checked here. Bare core rejects a\n * duplicate name on every route-population entry point — `createRouter([...])`\n * initial routes (#1351), `add()` (within-batch #953 + the \"already exists\"\n * guard for cross-batch collisions), and `replace()` (#968) — so a built store\n * can never carry a duplicate for the retrospective pass to catch. Core is the\n * sole authority for the name-uniqueness invariant; mirroring it here was dead\n * code kept alive only by white-box unit tests (#1226).\n *\n * @param store - RoutesStore instance (typed as unknown to avoid core coupling)\n * @throws {TypeError} If store shape is invalid or definitions have structural issues\n */\nexport function validateExistingRoutes(store: unknown): void {\n const routesStore = assertRoutesStore(store, \"validateExistingRoutes\");\n\n walkDefinitions(routesStore.definitions, (def, fullName) => {\n if (typeof def.name !== \"string\" || !def.name) {\n throw new TypeError(\n `[validation-plugin] validateExistingRoutes: route has invalid name: ${def.name}`,\n );\n }\n\n // ⚑ The dotted-name check that #1194 added here is GONE, and its absence is\n // load-bearing rather than a cleanup. It closed a real hole: `add()` and\n // `replace()` rejected a dotted route name while the constructor did not, so\n // `createRouter([{ name: \"a.c\" }])` + this plugin slipped one past validation\n // into a name-vs-URL split-brain. #1763 moved the rule to where it belongs —\n // bare core now refuses the spelling at registration, with this exact\n // message — so `createRouter` throws before a plugin exists and nothing\n // dotted can reach this pass. It was unreachable code, not defence in depth:\n // `store.definitions` is derived from the TREE, whose nested children carry\n // bare names by construction.\n\n if (typeof def.path !== \"string\") {\n throw new TypeError(\n `[validation-plugin] validateExistingRoutes: route \"${fullName}\" has non-string path (${typeof def.path})`,\n );\n }\n });\n}\n\n// =============================================================================\n// 2. validateForwardToConsistency\n// =============================================================================\n\n/**\n * Validates forwardTo consistency across all chains in the store.\n * Checks target existence, param compatibility, and circular chain detection.\n * Adapted from: validateForwardToTargets() in forwardToValidation.ts\n *\n * @param store - RoutesStore instance (typed as unknown to avoid core coupling)\n * @throws {Error} If any forwardTo target does not exist in the tree\n * @throws {Error} If param incompatibility is detected across a forwardTo pair\n * @throws {Error} If a circular forwardTo chain is detected\n */\nexport function validateForwardToConsistency(store: unknown): void {\n const routesStore = assertRoutesStore(store, \"validateForwardToConsistency\");\n const { config, tree, matcher } = routesStore;\n\n // Check target existence and param compatibility for each static mapping\n for (const [fromRoute, targetRoute] of Object.entries(config.forwardMap)) {\n if (!routeExistsInTree(tree, targetRoute)) {\n throw new Error(\n `[validation-plugin] validateForwardToConsistency: forwardTo target \"${targetRoute}\" ` +\n `does not exist in tree (source route: \"${fromRoute}\")`,\n );\n }\n\n // Validate param compatibility: target must not require params absent in source\n const sourceSegments = matcher.getSegmentsByName(fromRoute);\n const targetSegments = matcher.getSegmentsByName(targetRoute);\n\n if (sourceSegments && targetSegments) {\n const sourceParams = collectUrlParams(sourceSegments);\n const targetParams = collectUrlParams(targetSegments);\n const missingParams = [...targetParams].filter(\n (param) => !sourceParams.has(param),\n );\n\n if (missingParams.length > 0) {\n throw new Error(\n `[validation-plugin] validateForwardToConsistency: forwardTo target \"${targetRoute}\" ` +\n `requires params [${missingParams.join(\", \")}] not available in source route \"${fromRoute}\"`,\n );\n }\n }\n }\n\n // Detect cycles in the full forwardMap (catches multi-hop cycles)\n for (const fromRoute of Object.keys(config.forwardMap)) {\n resolveForwardChainWithPrefix(fromRoute, config.forwardMap);\n }\n}\n\n// =============================================================================\n// 3. validateRouteProperties\n// =============================================================================\n\n/**\n * Validates route properties for all registered routes in the store.\n * Checks decoder/encoder types, defaultParams structure, and async forwardTo callbacks.\n * Adapted from: validateRouteProperties() in forwardToValidation.ts\n *\n * @param store - RoutesStore instance (typed as unknown to avoid core coupling)\n * @throws {TypeError} If any registered decoder/encoder is not a valid sync function\n * @throws {TypeError} If any defaultParams is not a plain object\n * @throws {TypeError} If any forwardTo callback is async\n */\nexport function validateRoutePropertiesStore(store: unknown): void {\n const routesStore = assertRoutesStore(store, \"validateRoutePropertiesStore\");\n const { config } = routesStore;\n\n // Validate decoders — must be non-async functions (sync required for matchPath/buildPath)\n for (const [routeName, decoder] of Object.entries(config.decoders)) {\n if (typeof decoder !== \"function\") {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" decoder must be a function, got ${typeof decoder}`,\n );\n }\n\n assertNotAsync(decoder, \"decoder\", routeName);\n }\n\n // Validate encoders — must be non-async functions (sync required for matchPath/buildPath)\n for (const [routeName, encoder] of Object.entries(config.encoders)) {\n if (typeof encoder !== \"function\") {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" encoder must be a function, got ${typeof encoder}`,\n );\n }\n\n assertNotAsync(encoder, \"encoder\", routeName);\n }\n\n // Validate defaultParams — must be plain objects (not null, array, or other types)\n for (const [routeName, params] of Object.entries(config.defaultParams)) {\n if (\n params === null ||\n typeof params !== \"object\" ||\n Array.isArray(params)\n ) {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" defaultParams must be a plain object, got ${Array.isArray(params) ? \"array\" : typeof params}`,\n );\n }\n }\n\n // Validate forwardTo function callbacks — must be non-async functions\n for (const [routeName, callback] of Object.entries(config.forwardFnMap)) {\n if (typeof callback !== \"function\") {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" forwardTo callback must be a function, got ${typeof callback}`,\n );\n }\n\n assertNotAsync(callback, \"forwardTo callback\", routeName);\n }\n}\n\n// =============================================================================\n// 4. validateForwardToTargets\n// =============================================================================\n\n/**\n * Validates that all static forwardTo targets exist in the route tree.\n * This is a focused existence-only check (param compat is in validateForwardToConsistency).\n * Adapted from: validateForwardToTargets() in forwardToValidation.ts\n *\n * @param store - RoutesStore instance (typed as unknown to avoid core coupling)\n * @throws {Error} If any forwardTo target route does not exist in the tree\n */\nexport function validateForwardToTargetsStore(store: unknown): void {\n const routesStore = assertRoutesStore(store, \"validateForwardToTargetsStore\");\n const { config, tree } = routesStore;\n\n for (const [fromRoute, targetRoute] of Object.entries(config.forwardMap)) {\n if (!routeExistsInTree(tree, targetRoute)) {\n throw new Error(\n `[validation-plugin] validateForwardToTargetsStore: forwardTo target \"${targetRoute}\" ` +\n `does not exist for route \"${fromRoute}\"`,\n );\n }\n }\n}\n\n// =============================================================================\n// 5. validateDependenciesStructure\n// =============================================================================\n\n/**\n * Validates the full structure of the dependencies store.\n * Checks that the dependencies object is valid, has no getters, and limits are well-formed.\n * Adapted from: validateDependenciesObject() in DependenciesNamespace/validators.ts\n *\n * @param deps - DependenciesStore instance (typed as unknown to avoid core coupling)\n * @throws {TypeError} If deps is not an object\n * @throws {TypeError} If deps.dependencies is not a valid plain object (or has getters)\n * @throws {TypeError} If deps.limits is missing or has non-numeric limit values\n */\nexport function validateDependenciesStructure(deps: unknown): void {\n if (!deps || typeof deps !== \"object\") {\n throw new TypeError(\n \"[validation-plugin] validateDependenciesStructure: deps must be an object\",\n );\n }\n\n const depsRecord = deps as Record<string, unknown>;\n\n // Validate dependencies field exists and is an object\n if (!depsRecord.dependencies || typeof depsRecord.dependencies !== \"object\") {\n throw new TypeError(\n \"[validation-plugin] validateDependenciesStructure: deps.dependencies must be an object\",\n );\n }\n\n const dependencies = depsRecord.dependencies as Record<string, unknown>;\n\n // Getters can throw, return different values, or have side effects — reject them\n for (const key of Object.keys(dependencies)) {\n if (Object.getOwnPropertyDescriptor(dependencies, key)?.get) {\n throw new TypeError(\n `[validation-plugin] validateDependenciesStructure: dependency \"${key}\" must not use a getter`,\n );\n }\n }\n\n // Validate limits field exists and is an object\n if (!depsRecord.limits || typeof depsRecord.limits !== \"object\") {\n throw new TypeError(\n \"[validation-plugin] validateDependenciesStructure: deps.limits must be an object\",\n );\n }\n\n const limits = depsRecord.limits as Record<string, unknown>;\n const expectedLimitKeys: (keyof LocalDependencyLimits)[] = [\n \"maxDependencies\",\n \"maxPlugins\",\n \"maxListeners\",\n \"warnListeners\",\n \"maxLifecycleHandlers\",\n ];\n\n for (const key of expectedLimitKeys) {\n // ⚑ `Number.isInteger`, not `typeof === \"number\"`. Core coerces the caller's\n // limits ONCE at construction (#1875), so by the time they reach this store\n // they are always `typeof \"number\"` — and `Number(undefined)`,\n // `Number(\"abc\")` and `Number({})` are all `NaN`, which passes a `typeof`\n // test. A `typeof` check therefore stopped diagnosing the exact population\n // it was written for the moment the coercion moved upstream: measured,\n // `{ maxListeners: undefined }` was refused at install before that change\n // and installed clean after it. This also lands the check on the same\n // predicate `validateLimitValue` already uses, so the two mirrors agree.\n if (!Number.isInteger(limits[key])) {\n throw new TypeError(\n `[validation-plugin] validateDependenciesStructure: deps.limits.${key} must be an integer, got ${String(limits[key])}`,\n );\n }\n }\n}\n\n// =============================================================================\n// 6. validateLimitsConsistency\n// =============================================================================\n\n/**\n * Validates that actual resource counts don't exceed configured limits.\n * Compares dependency count vs maxDependencies limit from the deps store.\n * Adapted from: validateLimits() in OptionsNamespace/validators.ts\n *\n * @param options - Router options (typed as unknown to avoid core coupling)\n * @throws {RangeError} If dependency count exceeds maxDependencies limit (#1225:\n * `>` not `>=` — an at-limit store is legal, mirroring the live limiter)\n */\nfunction extractConfiguredLimits(options: unknown): Record<string, unknown> {\n const opts =\n options && typeof options === \"object\"\n ? (options as Record<string, unknown>)\n : {};\n\n return opts.limits && typeof opts.limits === \"object\"\n ? (opts.limits as Record<string, unknown>)\n : {};\n}\n\nfunction checkDepCountLimit(\n deps: unknown,\n configuredLimits: Record<string, unknown>,\n): void {\n if (!deps || typeof deps !== \"object\") {\n return;\n }\n\n const depsRecord = deps as Record<string, unknown>;\n const dependencies = depsRecord.dependencies;\n const depsLimits = depsRecord.limits;\n\n if (\n !dependencies ||\n typeof dependencies !== \"object\" ||\n !depsLimits ||\n typeof depsLimits !== \"object\"\n ) {\n return;\n }\n\n const depCount = Object.keys(dependencies).length;\n const limitsRecord = depsLimits as Record<string, unknown>;\n const maxDepsFromOptions = configuredLimits.maxDependencies;\n const maxDepsFromStore = limitsRecord.maxDependencies;\n const maxDeps =\n typeof maxDepsFromOptions === \"number\"\n ? maxDepsFromOptions\n : maxDepsFromStore;\n\n // `>`, not `>=` (#1225): the live limiter (`validateDependencyCount`) counts\n // BEFORE the insert, so a store may legally REACH exactly maxDependencies. This\n // retrospective pass checks committed STATE (re-run on usePlugin AND every\n // cloneRouter), so it must accept an at-limit store and reject only one that\n // STRICTLY exceeds the limit — else every SSR per-request clone of an at-limit\n // base throws.\n if (typeof maxDeps === \"number\" && maxDeps > 0 && depCount > maxDeps) {\n throw new RangeError(\n `[validation-plugin] validateLimitsConsistency: dependency count (${depCount}) exceeds maxDependencies limit (${maxDeps})`,\n );\n }\n}\n\nexport function validateLimitsConsistency(\n options: unknown,\n deps: unknown,\n): void {\n const configuredLimits = extractConfiguredLimits(options);\n\n checkDepCountLimit(deps, configuredLimits);\n}\n\n// =============================================================================\n// 7. validateResolvedDefaultRoute\n// =============================================================================\n\n/**\n * Validates that a resolved defaultRoute name points to a route that exists\n * in the tree. Called at two places:\n *\n * 1. At plugin registration (retrospective) — with the static string value\n * of options.defaultRoute, if any.\n * 2. At runtime inside resolveDefault() — with the return value of a\n * DefaultRouteCallback, on every navigateToDefault(). (`start()` has no\n * `defaultRoute` fallback — measured, it consults the option zero times.)\n *\n * No-op for empty string (means \"no default configured\" — handled upstream by\n * NavigationNamespace.navigateToDefault).\n */\nexport function validateResolvedDefaultRoute(\n routeName: unknown,\n store: unknown,\n): void {\n if (typeof routeName !== \"string\" || !routeName) {\n return;\n }\n\n const routesStore = assertRoutesStore(store, \"validateResolvedDefaultRoute\");\n\n if (!routeExistsInTree(routesStore.tree, routeName)) {\n throw new Error(\n `[validation-plugin] defaultRoute resolved to non-existent route: \"${routeName}\"`,\n );\n }\n}\n","// packages/validation-plugin/src/validators/forwardTo.ts\n\nimport { resolveForwardChain } from \"@real-router/core\";\nimport { putField } from \"@real-router/core/utils\";\n\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { Route, DefaultDependencies } from \"@real-router/core\";\nimport type { Matcher, RouteTree } from \"@real-router/core/validation\";\n\n// ============================================================================\n// Route Property Validation\n// ============================================================================\n\nfunction validateForwardToProperty(forwardTo: unknown, fullName: string): void {\n if (forwardTo === undefined) {\n return;\n }\n\n if (typeof forwardTo === \"function\") {\n const isNativeAsync =\n (forwardTo as { constructor: { name: string } }).constructor.name ===\n \"AsyncFunction\";\n const isTranspiledAsync = forwardTo.toString().includes(\"__awaiter\");\n\n if (isNativeAsync || isTranspiledAsync) {\n throw new TypeError(\n `[router.addRoute] forwardTo callback cannot be async for route \"${fullName}\". ` +\n `Async functions break matchPath/buildPath.`,\n );\n }\n }\n}\n\nexport function validateRouteProperties<\n Dependencies extends DefaultDependencies,\n>(route: Route<Dependencies>, fullName: string): void {\n if (\n route.canActivate !== undefined &&\n typeof route.canActivate !== \"function\"\n ) {\n throw new TypeError(\n `[router.addRoute] canActivate must be a function for route \"${fullName}\", ` +\n `got ${getTypeDescription(route.canActivate)}`,\n );\n }\n\n if (\n route.canDeactivate !== undefined &&\n typeof route.canDeactivate !== \"function\"\n ) {\n throw new TypeError(\n `[router.addRoute] canDeactivate must be a function for route \"${fullName}\", ` +\n `got ${getTypeDescription(route.canDeactivate)}`,\n );\n }\n\n if (route.defaultParams !== undefined) {\n const params: unknown = route.defaultParams;\n\n if (\n params === null ||\n typeof params !== \"object\" ||\n Array.isArray(params)\n ) {\n throw new TypeError(\n `[router.addRoute] defaultParams must be an object for route \"${fullName}\", ` +\n `got ${getTypeDescription(route.defaultParams)}`,\n );\n }\n }\n\n if (route.decodeParams?.constructor.name === \"AsyncFunction\") {\n throw new TypeError(\n `[router.addRoute] decodeParams cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`,\n );\n }\n\n if (route.encodeParams?.constructor.name === \"AsyncFunction\") {\n throw new TypeError(\n `[router.addRoute] encodeParams cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`,\n );\n }\n\n validateForwardToProperty(route.forwardTo, fullName);\n\n if (route.children) {\n for (const child of route.children) {\n const childFullName = `${fullName}.${child.name}`;\n\n validateRouteProperties(child, childFullName);\n }\n }\n}\n\n// ============================================================================\n// ForwardTo Validation\n// ============================================================================\n\nfunction extractParamsFromPath(path: string): Set<string> {\n const params = new Set<string>();\n const paramRegex = /[*:]([A-Z_a-z]\\w*)/g;\n let match;\n\n while ((match = paramRegex.exec(path)) !== null) {\n params.add(match[1]);\n }\n\n return params;\n}\n\nfunction extractParamsFromPaths(paths: readonly string[]): Set<string> {\n const params = new Set<string>();\n\n for (const path of paths) {\n for (const param of extractParamsFromPath(path)) {\n params.add(param);\n }\n }\n\n return params;\n}\n\nfunction collectPathsToRoute<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n routeName: string,\n parentName = \"\",\n paths: string[] = [],\n): string[] {\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n const currentPaths = [...paths, route.path];\n\n if (fullName === routeName) {\n return currentPaths;\n }\n\n if (route.children && routeName.startsWith(`${fullName}.`)) {\n // eslint-disable-next-line unicorn/no-useless-recursion -- intentional route-tree descent; a loop rewrite needs a labeled break and obscures path resolution\n return collectPathsToRoute(\n route.children,\n routeName,\n fullName,\n currentPaths,\n );\n }\n }\n\n /* v8 ignore next -- @preserve unreachable: callers validate existence */\n throw new Error(\n `[internal] collectPathsToRoute: route \"${routeName}\" not found`,\n );\n}\n\nfunction collectRouteNames<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n parentName = \"\",\n): Set<string> {\n const names = new Set<string>();\n\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n names.add(fullName);\n\n if (route.children) {\n for (const childName of collectRouteNames(route.children, fullName)) {\n names.add(childName);\n }\n }\n }\n\n return names;\n}\n\nfunction collectForwardMappings<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n parentName = \"\",\n): Map<string, string> {\n const mappings = new Map<string, string>();\n\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n if (route.forwardTo && typeof route.forwardTo === \"string\") {\n mappings.set(fullName, route.forwardTo);\n }\n\n if (route.children) {\n for (const [key, value] of collectForwardMappings(\n route.children,\n fullName,\n )) {\n mappings.set(key, value);\n }\n }\n }\n\n return mappings;\n}\n\nfunction getRequiredParams(segments: readonly RouteTree[]): Set<string> {\n const params = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n params.add(param);\n }\n\n for (const param of segment.paramMeta.spatParams) {\n params.add(param);\n }\n }\n\n return params;\n}\n\nfunction getTargetParams<Dependencies extends DefaultDependencies>(\n targetRoute: string,\n existsInMatcher: boolean,\n matcher: Matcher,\n routes: readonly Route<Dependencies>[],\n): Set<string> {\n if (existsInMatcher) {\n /* v8 ignore next -- @preserve: ?? fallback unreachable — existsInMatcher guarantees non-null */\n return getRequiredParams(\n (matcher.getSegmentsByName(targetRoute) as\n readonly RouteTree[] | undefined) ?? [],\n );\n }\n\n return extractParamsFromPaths(collectPathsToRoute(routes, targetRoute));\n}\n\nconst EMPTY_PARENT_PARAMS: ReadonlySet<string> = new Set();\n\nfunction validateSingleForward<Dependencies extends DefaultDependencies>(\n fromRoute: string,\n targetRoute: string,\n routes: readonly Route<Dependencies>[],\n batchNames: Set<string>,\n matcher: Matcher,\n parentParams: ReadonlySet<string>,\n): void {\n const existsInMatcher = matcher.hasRoute(targetRoute);\n const existsInBatch = batchNames.has(targetRoute);\n\n if (!existsInMatcher && !existsInBatch) {\n throw new ReferenceError(\n `[router.addRoute] forwardTo target \"${targetRoute}\" does not exist ` +\n `for route \"${fromRoute}\"`,\n );\n }\n\n // A batch route added under { parent } inherits the parent's path params, so\n // the forward source's available params are the parent's plus its own (#1224).\n const fromParams = new Set<string>([\n ...parentParams,\n ...extractParamsFromPaths(collectPathsToRoute(routes, fromRoute)),\n ]);\n\n const toParams = getTargetParams(\n targetRoute,\n existsInMatcher,\n matcher,\n routes,\n );\n\n const missingParams = [...toParams].filter((param) => !fromParams.has(param));\n\n if (missingParams.length > 0) {\n throw new Error(\n `[router.addRoute] forwardTo target \"${targetRoute}\" requires params ` +\n `[${missingParams.join(\", \")}] that are not available in source route \"${fromRoute}\"`,\n );\n }\n}\n\nexport function validateForwardToTargets<\n Dependencies extends DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n existingForwardMap: Record<string, string>,\n matcher: Matcher,\n parentName?: string,\n): void {\n const batchNames = collectRouteNames(routes);\n // Added under { parent }, a batch route's full name is `${parent}.${short}` and\n // it inherits the parent's path params — resolve both from the parent (#1224).\n const batchFullNames = parentName\n ? new Set([...batchNames].map((name) => `${parentName}.${name}`))\n : batchNames;\n // `validateRoutes` throws on a missing parent before forwardTo validation runs\n // (both go through the `add({ parent })` path with tree + matcher present), so\n // the parent's segments are always resolvable here — assert non-null.\n const parentParams: ReadonlySet<string> = parentName\n ? getRequiredParams(\n matcher.getSegmentsByName(parentName) as readonly RouteTree[],\n )\n : EMPTY_PARENT_PARAMS;\n\n const batchForwards = collectForwardMappings(routes);\n\n const combinedForwardMap: Record<string, string> = { ...existingForwardMap };\n\n for (const [from, to] of batchForwards) {\n // A ROUTE NAME as the key (#1852): an ambient accessor under it made\n // `routesApi.add()` throw, so the routes were never registered — the\n // validator becoming the failure.\n putField(combinedForwardMap, from, to);\n }\n\n for (const [fromRoute, targetRoute] of batchForwards) {\n validateSingleForward(\n fromRoute,\n targetRoute,\n routes,\n batchFullNames,\n matcher,\n parentParams,\n );\n }\n\n for (const fromRoute of Object.keys(combinedForwardMap)) {\n resolveForwardChain(fromRoute, combinedForwardMap);\n }\n}\n","// packages/validation-plugin/src/validators/routes.ts\n\n/**\n * DX-only validator functions for RoutesNamespace.\n * Copied from packages/core/src/namespaces/RoutesNamespace/validators.ts\n * (excludes validateRemoveRoute/validateClearRoutes — those are in routeGuards.ts)\n */\n\nimport { resolveForwardChain } from \"@real-router/core\";\nimport { validateRoute } from \"@real-router/core/validation\";\n\nimport {\n isString,\n validateRouteName,\n isParams,\n getTypeDescription,\n} from \"../type-guards\";\nimport { validateForwardToTargets, validateRouteProperties } from \"./forwardTo\";\n\nimport type {\n Route,\n RouteConfigUpdate,\n DefaultDependencies,\n Params,\n} from \"@real-router/core\";\nimport type { Matcher, RouteTree } from \"@real-router/core/validation\";\n\n// Internal constant (matches core's INTERNAL_ROUTE_PREFIX)\nconst INTERNAL_ROUTE_PREFIX = \"@@\";\n\n// Minimal local type — only the forwardMap field used by this file\n// (RouteConfig from core is not exported from @real-router/core public API)\ninterface RouteConfigLike {\n forwardMap: Record<string, string>;\n}\n\n// Local type - matches ForwardToCallback from @real-router/types\n// (@real-router/types is not a direct dependency of this package)\ntype ForwardToCallback<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> = (\n getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K],\n params: Params,\n) => string;\n\nexport function throwIfInternalRoute(name: string, methodName: string): void {\n if (name.startsWith(INTERNAL_ROUTE_PREFIX)) {\n throw new Error(\n `[router.${methodName}] Route name \"${name}\" uses the reserved \"${INTERNAL_ROUTE_PREFIX}\" prefix. Routes with this prefix are internal and cannot be modified through the public API.`,\n );\n }\n}\n\nexport function throwIfInternalRouteInArray(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Route type\n routes: readonly Route<any>[],\n methodName: string,\n): void {\n for (const route of routes) {\n // eslint-disable-next-line unicorn/prefer-continue, @typescript-eslint/no-unnecessary-condition -- runtime safety guard; prefer-continue inversion would split the condition off this single-line disable\n if (route && typeof route === \"object\" && typeof route.name === \"string\") {\n throwIfInternalRoute(route.name, methodName);\n\n if (route.children) {\n throwIfInternalRouteInArray(route.children, methodName);\n }\n }\n }\n}\n\n/**\n * Validates removeRoute arguments.\n */\nexport function validateRemoveRouteArgs(name: unknown): asserts name is string {\n validateRouteName(name, \"removeRoute\");\n}\n\nexport function validateSetRootPathArgs(\n rootPath: unknown,\n): asserts rootPath is string {\n if (typeof rootPath !== \"string\") {\n throw new TypeError(\n `[router.setRootPath] rootPath must be a string, got ${getTypeDescription(rootPath)}`,\n );\n }\n}\n\nfunction isAsyncFunction(fn: unknown): boolean {\n return (\n (fn as { constructor: { name: string } }).constructor.name ===\n \"AsyncFunction\" || String(fn).includes(\"__awaiter\")\n );\n}\n\nexport function guardRouteCallbacks(route: unknown): void {\n const routeObj = route as { canActivate?: unknown; canDeactivate?: unknown };\n\n if (\n routeObj.canActivate !== undefined &&\n typeof routeObj.canActivate !== \"function\"\n ) {\n throw new TypeError(\n `[router.addRoute] canActivate must be a function, got ${getTypeDescription(routeObj.canActivate)}`,\n );\n }\n\n if (\n routeObj.canDeactivate !== undefined &&\n typeof routeObj.canDeactivate !== \"function\"\n ) {\n throw new TypeError(\n `[router.addRoute] canDeactivate must be a function, got ${getTypeDescription(routeObj.canDeactivate)}`,\n );\n }\n}\n\nexport function guardNoAsyncCallbacks(route: unknown): void {\n const routeObj = route as {\n decodeParams?: unknown;\n encodeParams?: unknown;\n forwardTo?: unknown;\n name?: unknown;\n };\n const routeName = routeObj.name;\n\n if (\n routeObj.decodeParams !== undefined &&\n isAsyncFunction(routeObj.decodeParams)\n ) {\n throw new TypeError(\n `[router.addRoute] decodeParams cannot be async for route \"${String(routeName)}\"`,\n );\n }\n\n if (\n routeObj.encodeParams !== undefined &&\n isAsyncFunction(routeObj.encodeParams)\n ) {\n throw new TypeError(\n `[router.addRoute] encodeParams cannot be async for route \"${String(routeName)}\"`,\n );\n }\n\n if (\n typeof routeObj.forwardTo === \"function\" &&\n isAsyncFunction(routeObj.forwardTo)\n ) {\n throw new TypeError(\n `[router.addRoute] forwardTo callback cannot be async for route \"${String(routeName)}\"`,\n );\n }\n}\n\n/**\n * Validates addRoute arguments (route structure and properties).\n * State-dependent validation (duplicates, tree) happens in instance method.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Route type\nexport function validateAddRouteArgs(routes: readonly Route<any>[]): void {\n for (const route of routes) {\n // First check if route is an object (before accessing route.name)\n // Runtime check for invalid types passed via `as any`\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime check\n if (route === null || typeof route !== \"object\" || Array.isArray(route)) {\n throw new TypeError(\n `[router.addRoute] Route must be an object, got ${getTypeDescription(route)}`,\n );\n }\n\n // Validate route properties (canActivate, canDeactivate, defaultParams, async checks)\n // Note: validateRouteProperties handles children recursively\n validateRouteProperties(route, route.name);\n }\n}\n\n/**\n * Validates parent option for addRoute.\n */\nexport function validateParentOption(\n parent: unknown,\n): asserts parent is string {\n if (typeof parent !== \"string\" || parent === \"\") {\n throw new TypeError(\n `[router.addRoute] parent option must be a non-empty string, got ${getTypeDescription(parent)}`,\n );\n }\n\n // Validate parent is a valid route name format (can contain dots — it's a fullName reference)\n validateRouteName(parent, \"addRoute\");\n}\n\n/**\n * Validates isActiveRoute arguments.\n */\nexport function validateIsActiveRouteArgs(\n name: unknown,\n params: unknown,\n strictEquality: unknown,\n ignoreQueryParams: unknown,\n): void {\n // Validate name - non-string throws\n if (!isString(name)) {\n throw new TypeError(\n `[router.isActiveRoute] name must be a string, got ${typeof name}`,\n );\n }\n\n // Validate params if provided\n if (params !== undefined && !isParams(params)) {\n throw new TypeError(\n `[router.isActiveRoute] params must be a plain object, got ${getTypeDescription(params)}`,\n );\n }\n\n // Validate strictEquality if provided\n if (strictEquality !== undefined && typeof strictEquality !== \"boolean\") {\n throw new TypeError(\n `[router.isActiveRoute] strictEquality must be a boolean, got ${typeof strictEquality}`,\n );\n }\n\n // Validate ignoreQueryParams if provided\n if (\n ignoreQueryParams !== undefined &&\n typeof ignoreQueryParams !== \"boolean\"\n ) {\n throw new TypeError(\n `[router.isActiveRoute] ignoreQueryParams must be a boolean, got ${typeof ignoreQueryParams}`,\n );\n }\n}\n\n/**\n * Validates state-builder arguments (navigate / forwardState /\n * buildNavigationState).\n */\nexport function validateStateBuilderArgs(\n routeName: unknown,\n routeParams: unknown,\n methodName: string,\n): void {\n if (!isString(routeName)) {\n throw new TypeError(\n `[router.${methodName}] Invalid routeName: ${getTypeDescription(routeName)}. Expected string.`,\n );\n }\n\n if (!isParams(routeParams)) {\n throw new TypeError(\n `[router.${methodName}] Invalid routeParams: ${getTypeDescription(routeParams)}. Expected plain object.`,\n );\n }\n}\n\n/**\n * Validates updateRoute basic arguments (name and updates object structure).\n * Does NOT read property values to allow caller to cache them first.\n */\nexport function validateUpdateRouteBasicArgs<\n Dependencies extends DefaultDependencies,\n>(\n name: unknown,\n updates: unknown,\n): asserts updates is RouteConfigUpdate<Dependencies> {\n // Validate name\n validateRouteName(name, \"updateRoute\");\n\n if (name === \"\") {\n throw new ReferenceError(\n `[router.updateRoute] Invalid name: empty string. Cannot update root node.`,\n );\n }\n\n // Validate updates is not null\n\n if (updates === null) {\n throw new TypeError(\n `[router.updateRoute] updates must be an object, got null`,\n );\n }\n\n // Validate updates is an object (not array)\n if (typeof updates !== \"object\" || Array.isArray(updates)) {\n throw new TypeError(\n `[router.updateRoute] updates must be an object, got ${getTypeDescription(updates)}`,\n );\n }\n}\n\n/**\n * Asserts that a function is not async (native or transpiled).\n * Checks both constructor name and toString() for __awaiter pattern.\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- needs constructor.name access\nfunction assertNotAsync(value: Function, paramName: string): void {\n if (\n (value as { constructor: { name: string } }).constructor.name ===\n \"AsyncFunction\" ||\n (value as { toString: () => string }).toString().includes(\"__awaiter\")\n ) {\n throw new TypeError(\n `[router.updateRoute] ${paramName} cannot be an async function`,\n );\n }\n}\n\n/**\n * Validates that a value is a non-async function, if provided.\n */\nfunction validateFunctionParam(value: unknown, paramName: string): void {\n if (value === undefined || value === null) {\n return;\n }\n\n if (typeof value !== \"function\") {\n throw new TypeError(\n `[router.updateRoute] ${paramName} must be a function or null, got ${typeof value}`,\n );\n }\n\n assertNotAsync(value, paramName);\n}\n\n/**\n * Validates updateRoute property types using pre-cached values.\n * Called AFTER properties are cached to ensure getters are called only once.\n */\nexport function validateUpdateRoutePropertyTypes(\n forwardTo: unknown,\n defaultParams: unknown,\n decodeParams: unknown,\n encodeParams: unknown,\n): void {\n // Validate forwardTo type (existence check is done by instance method)\n if (forwardTo !== undefined && forwardTo !== null) {\n if (typeof forwardTo !== \"string\" && typeof forwardTo !== \"function\") {\n throw new TypeError(\n `[router.updateRoute] forwardTo must be a string, function, or null, got ${getTypeDescription(forwardTo)}`,\n );\n }\n\n if (typeof forwardTo === \"function\") {\n assertNotAsync(forwardTo, \"forwardTo callback\");\n }\n }\n\n // Validate defaultParams\n if (\n defaultParams !== undefined &&\n defaultParams !== null &&\n (typeof defaultParams !== \"object\" || Array.isArray(defaultParams))\n ) {\n throw new TypeError(\n `[router.updateRoute] defaultParams must be an object or null, got ${getTypeDescription(defaultParams)}`,\n );\n }\n\n validateFunctionParam(decodeParams, \"decodeParams\");\n validateFunctionParam(encodeParams, \"encodeParams\");\n}\n\n/**\n * Validates buildPath arguments.\n */\nexport function validateBuildPathArgs(route: unknown): asserts route is string {\n if (!isString(route) || route === \"\") {\n throw new TypeError(\n `[router.buildPath] route must be a non-empty string, got ${typeof route === \"string\" ? '\"\"' : typeof route}`,\n );\n }\n}\n\n/**\n * Validates matchPath arguments.\n */\nexport function validateMatchPathArgs(path: unknown): asserts path is string {\n if (!isString(path)) {\n throw new TypeError(\n `[router.matchPath] path must be a string, got ${typeof path}`,\n );\n }\n}\n\n/**\n * Validates shouldUpdateNode arguments.\n */\nexport function validateShouldUpdateNodeArgs(\n nodeName: unknown,\n): asserts nodeName is string {\n if (!isString(nodeName)) {\n throw new TypeError(\n `[router.shouldUpdateNode] nodeName must be a string, got ${typeof nodeName}`,\n );\n }\n}\n\n/**\n * Validates routes for addition to the router.\n * Checks parent existence, duplicates, and forwardTo targets/cycles.\n *\n * @param routes - Routes to validate\n * @param tree - Current route tree (optional for initial validation)\n * @param matcher - Current route matcher (segment lookup + existence for forwardTo)\n * @param forwardMap - Current forward map for cycle detection\n * @param parentName - Optional parent route fullName for nesting via addRoute({ parent })\n */\nexport function validateRoutes<Dependencies extends DefaultDependencies>(\n routes: Route<Dependencies>[],\n tree?: RouteTree,\n matcher?: Matcher,\n forwardMap?: Record<string, string>,\n parentName?: string,\n): void {\n // Validate parent route exists in tree\n if (parentName && tree) {\n let node: RouteTree | undefined = tree;\n\n for (const segment of parentName.split(\".\")) {\n node = node.children.get(segment);\n\n if (!node) {\n throw new ReferenceError(\n `[router.addRoute] Parent route \"${parentName}\" does not exist`,\n );\n }\n }\n }\n\n // Tracking sets for duplicate detection\n const seenNames = new Set<string>();\n const seenPathsByParent = new Map<string, Set<string>>();\n\n for (const route of routes) {\n validateRoute(\n route,\n \"addRoute\",\n tree,\n parentName ?? \"\",\n seenNames,\n seenPathsByParent,\n );\n }\n\n if (matcher && forwardMap) {\n validateForwardToTargets(routes, forwardMap, matcher, parentName);\n }\n}\n\n// ============================================================================\n// Instance-level validators (moved from routesCrud.ts)\n// ============================================================================\n\n/**\n * Collects URL params from segments into a Set.\n */\nfunction collectUrlParams(segments: readonly RouteTree[]): Set<string> {\n const params = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n params.add(param);\n }\n }\n\n return params;\n}\n\n/**\n * Validates that forwardTo target doesn't require params that source doesn't have.\n *\n * @param sourceName - Source route name\n * @param targetName - Target route name\n * @param matcher - Current route matcher\n */\nexport function validateForwardToParamCompatibility(\n sourceName: string,\n targetName: string,\n matcher: Matcher,\n): void {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const sourceSegments = matcher.getSegmentsByName(\n sourceName,\n )! as readonly RouteTree[];\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const targetSegments = matcher.getSegmentsByName(\n targetName,\n )! as readonly RouteTree[];\n\n // Get source URL params as a Set for O(1) lookup\n const sourceParams = collectUrlParams(sourceSegments);\n\n // Build target URL params array (inline — no separate helper needed)\n const targetParams: string[] = [];\n\n for (const segment of targetSegments) {\n for (const param of segment.paramMeta.urlParams) {\n targetParams.push(param);\n }\n }\n\n // Check if target requires params that source doesn't have\n const missingParams = targetParams.filter(\n (param) => !sourceParams.has(param),\n );\n\n if (missingParams.length > 0) {\n throw new Error(\n `[router.addRoute] forwardTo target \"${targetName}\" requires params ` +\n `[${missingParams.join(\", \")}] that are not available in source route \"${sourceName}\"`,\n );\n }\n}\n\n/**\n * Validates that adding forwardTo doesn't create a cycle.\n * Creates a test map with the new entry and uses resolveForwardChain\n * to detect cycles before any mutation happens.\n *\n * @param sourceName - Source route name\n * @param targetName - Target route name\n * @param config - Current route config (forwardMap read-only in this call)\n */\nexport function validateForwardToCycle(\n sourceName: string,\n targetName: string,\n config: RouteConfigLike,\n): void {\n // Create a test map with the new entry to validate BEFORE mutation\n const testMap = {\n ...config.forwardMap,\n [sourceName]: targetName,\n };\n\n // resolveForwardChain will throw if cycle is detected or max depth exceeded\n resolveForwardChain(sourceName, testMap);\n}\n\n/**\n * Validates updateRoute instance-level constraints (route existence, forwardTo).\n *\n * @param name - Route name (already validated by static method)\n * @param forwardTo - Cached forwardTo value\n * @param hasRoute - Function to check route existence\n * @param matcher - Current route matcher\n * @param config - Current route config\n */\nexport function validateUpdateRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n name: string,\n forwardTo: string | ForwardToCallback<Dependencies> | null | undefined,\n hasRoute: (n: string) => boolean,\n matcher: Matcher,\n config: RouteConfigLike,\n): void {\n // Validate route exists\n if (!hasRoute(name)) {\n throw new ReferenceError(\n `[router.updateRoute] route \"${name}\" does not exist`,\n );\n }\n\n // Validate forwardTo target exists and is valid (only for string forwardTo)\n if (\n forwardTo !== undefined &&\n forwardTo !== null &&\n typeof forwardTo === \"string\"\n ) {\n if (!hasRoute(forwardTo)) {\n throw new Error(\n `[router.updateRoute] forwardTo target \"${forwardTo}\" does not exist`,\n );\n }\n\n // Check forwardTo param compatibility\n validateForwardToParamCompatibility(name, forwardTo, matcher);\n\n // Check for cycle detection\n validateForwardToCycle(name, forwardTo, config);\n }\n}\n","// packages/validation-plugin/src/validators/state.ts\n\nimport { isString, isParams, getTypeDescription } from \"../type-guards\";\n\nexport function validateMakeStateArgs(\n name: unknown,\n params: unknown,\n path: unknown,\n): void {\n if (!isString(name)) {\n throw new TypeError(\n `[router.makeState] Invalid name: ${getTypeDescription(name)}. Expected string.`,\n );\n }\n\n if (params !== undefined && !isParams(params)) {\n throw new TypeError(\n `[router.makeState] Invalid params: ${getTypeDescription(params)}. Expected plain object.`,\n );\n }\n\n if (path !== undefined && !isString(path)) {\n throw new TypeError(\n `[router.makeState] Invalid path: ${getTypeDescription(path)}. Expected string.`,\n );\n }\n}\n\n/**\n * The mode gate's opt-in diagnostic (#1575).\n *\n * Core DROPS a query key the active `queryParamsMode` will not print — always-on\n * and silent, so `state.search` can never carry what `state.path` does not show.\n * The drop is correct but invisible, and the two ways to hit it look nothing\n * alike from the outside:\n *\n * - a caller passing a key the route never declared with `?name`;\n * - a `defaultSearch` entry for such a key, which is simply DEAD CONFIG under\n * `default` / `strict` — nothing the caller wrote is wrong, the route config\n * just cannot take effect.\n *\n * One message covers both: core cannot tell them apart at the drop, and a guess\n * would be worse than the plain fact. Warn rather than throw — the mode is a\n * serialization option, not an error policy, and turning it into one would give\n * three behaviours for a single rule (RFC §4.7).\n *\n * De-duplicated per `route + key`: the gate runs on every navigation and every\n * `matchPath`, so an un-deduped warning would flood a dev console the moment a\n * route is revisited.\n *\n * ⚠ **The cache belongs to the ROUTER, not to the module (#1583).** A `Set` at\n * module scope is one per PROCESS: the first router to report a `route + key`\n * silenced every router after it, so under SSR / SSG the diagnostic fired for\n * request #1 and stayed quiet for the life of the process — precisely backwards\n * for a dev-time signal. It also survived `teardown()`, so re-registering the\n * plugin bought silence, and it grew without bound because nothing evicted.\n *\n * A factory rather than a `WeakMap<Router, Set>`: `buildValidatorObject` already\n * runs once per registration, so closing over a fresh `Set` there gives\n * per-router lifetime, per-router isolation and collection with the router at\n * zero new module state — and it retires the two `reset*` test seams, which\n * existed only to work around the module scope.\n */\nexport function createDroppedQueryKeyReporter(): (\n routeName: string,\n key: string,\n) => void {\n const reported = new Set<string>();\n\n return function reportDroppedQueryKey(routeName: string, key: string): void {\n const seen = `${routeName} ${key}`;\n\n if (reported.has(seen)) {\n return;\n }\n\n reported.add(seen);\n\n console.warn(\n `[router] Query key \"${key}\" is not declared on route \"${routeName}\" (\\`?${key}\\`), so the ` +\n `current \\`queryParamsMode\\` will not print it — it was dropped from \\`state.search\\` rather ` +\n `than published beside a \\`state.path\\` that cannot show it. Declare it on the route path, ` +\n `or use \\`queryParamsMode: \"loose\"\\` to keep undeclared keys. A \\`defaultSearch\\` entry for ` +\n `\"${key}\" is dead config in this mode for the same reason.`,\n );\n };\n}\n\n/**\n * The undeclared-params-bag diagnostic (#1579 — the params half of #1553).\n *\n * A key the route declares NOWHERE — neither as a path slot nor with `?` — has\n * no channel to own it, so core keeps it in `state.params` as app-level data\n * (documented: wiki `Route.md`). Correct, but it means the state does not\n * round-trip through its own `state.path`: reopen the same link and the key is\n * gone.\n *\n * Core's behaviour is deliberately UNCHANGED — dropping the key was measured\n * and rejected. It retires a shipped capability (52 tests across 6 packages plus\n * the wiki), and the \"declared nowhere\" predicate cannot separate that case from\n * a legitimate one: `navigate(\"users\", { id })` on a PARENT route whose child\n * declares `:id` is indistinguishable from a typo. A diagnostic can afford that\n * ambiguity — it says what happened and lets the developer judge; a gate cannot.\n *\n * De-duplicated per `route + key`, like the mode gate's: this runs on every\n * navigation, so an un-deduped warning floods the console on a revisit — and\n * per ROUTER, for the same reasons and by the same factory shape (#1583).\n */\nexport function createUndeclaredParamKeyReporter(): (\n routeName: string,\n key: string,\n) => void {\n const undeclaredReported = new Set<string>();\n\n return function reportUndeclaredParamKey(\n routeName: string,\n key: string,\n ): void {\n const seen = `${routeName} ${key}`;\n\n if (undeclaredReported.has(seen)) {\n return;\n }\n\n undeclaredReported.add(seen);\n\n console.warn(\n `[router] Param \"${key}\" is declared nowhere on route \"${routeName}\" — neither as a path ` +\n `slot (:${key}) nor as a query param (?${key}), so it stays in state.params but never ` +\n `reaches the URL. The state will not round-trip: matchPath(state.path) cannot recover ` +\n `it. Declare it on the route path if it belongs in the URL, or pass it through the ` +\n `search channel; keep it here only if it is deliberately app-level data.`,\n );\n };\n}\n","// packages/validation-plugin/src/validationPlugin.ts\n\nimport { RouterError } from \"@real-router/core\";\nimport { getInternals } from \"@real-router/core/validation\";\n\nimport {\n validateRouteName,\n isState,\n isBoolean,\n getTypeDescription,\n} from \"./type-guards\";\nimport {\n validateDependencyName,\n validateSetDependencyArgs as validateSetDependencyArgsRaw,\n validateDependenciesObject,\n validateDependencyExists as validateDependencyExistsRaw,\n validateDependencyCount,\n validateCloneArgs,\n warnOverwrite as warnDepsOverwrite,\n warnBatchOverwrite,\n warnRemoveNonExistent,\n} from \"./validators/dependencies\";\nimport {\n validateListenerArgs,\n validateListenerCountThresholds,\n} from \"./validators/eventBus\";\nimport {\n validateHandler,\n validateHandlerLimit,\n validateLifecycleCountThresholds,\n warnOverwrite as warnLifecycleOverwrite,\n warnAsyncGuardSync,\n} from \"./validators/lifecycle\";\nimport {\n validateNavigateArgs,\n validateNavigateToDefaultArgs,\n validateNavigateToStateArgs,\n validateNavigationOptions,\n validateNavigateParams,\n validateStartArgs,\n} from \"./validators/navigation\";\nimport { validateOptions } from \"./validators/options\";\nimport {\n validatePluginLimit,\n validateNoDuplicatePlugins,\n validatePluginKeys,\n validateCountThresholds as validatePluginCountThresholds,\n warnBatchDuplicates,\n warnPluginMethodType,\n warnPluginAfterStart,\n validateAddInterceptorArgs,\n} from \"./validators/plugins\";\nimport {\n validateExistingRoutes,\n validateForwardToConsistency,\n validateRoutePropertiesStore,\n validateForwardToTargetsStore,\n validateDependenciesStructure,\n validateLimitsConsistency,\n validateResolvedDefaultRoute,\n} from \"./validators/retrospective\";\nimport {\n validateBuildPathArgs,\n validateMatchPathArgs,\n validateIsActiveRouteArgs,\n validateShouldUpdateNodeArgs,\n validateStateBuilderArgs,\n validateAddRouteArgs,\n validateRoutes,\n validateRemoveRouteArgs,\n validateUpdateRouteBasicArgs,\n validateUpdateRoutePropertyTypes,\n validateUpdateRoute,\n validateParentOption as validateParentOptionRaw,\n throwIfInternalRoute,\n throwIfInternalRouteInArray,\n validateSetRootPathArgs,\n guardRouteCallbacks,\n guardNoAsyncCallbacks,\n} from \"./validators/routes\";\nimport {\n createDroppedQueryKeyReporter,\n createUndeclaredParamKeyReporter,\n validateMakeStateArgs,\n} from \"./validators/state\";\n\nimport type { EventName, EventMethodMap } from \"./validators/eventBus\";\nimport type {\n DefaultDependencies,\n PluginFactory,\n RouterValidator,\n Route,\n RouteTree,\n Plugin,\n} from \"@real-router/core\";\nimport type { RouterInternals, Matcher } from \"@real-router/core/validation\";\n\nfunction buildValidatorObject<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(ctx: RouterInternals<Dependencies>): RouterValidator {\n return {\n routes: {\n validateBuildPathArgs,\n validateMatchPathArgs,\n validateIsActiveRouteArgs,\n validateShouldUpdateNodeArgs,\n validateStateBuilderArgs,\n\n validateAddRouteArgs(routes) {\n validateAddRouteArgs(routes as readonly Route[]);\n },\n\n validateRoutes(routes, store, parentName) {\n const typedStore = store as {\n tree?: unknown;\n matcher?: unknown;\n config?: { forwardMap?: Record<string, string> };\n };\n\n validateRoutes(\n routes as Route[],\n typedStore.tree as RouteTree | undefined,\n typedStore.matcher as Matcher | undefined,\n typedStore.config?.forwardMap,\n parentName,\n );\n },\n validateRemoveRouteArgs,\n validateUpdateRouteBasicArgs,\n validateUpdateRoutePropertyTypes(_name, updates) {\n const upd = updates as Record<string, unknown>;\n\n validateUpdateRoutePropertyTypes(\n upd.forwardTo,\n upd.defaultParams,\n upd.decodeParams,\n upd.encodeParams,\n );\n },\n validateUpdateRoute(name, updates, store) {\n const typedStore = store as {\n matcher: {\n hasRoute: (routeName: string) => boolean;\n getSegmentsByName: (routeName: string) => unknown;\n };\n config: { forwardMap: Record<string, string> };\n };\n const forwardTo = (updates as Record<string, unknown>).forwardTo;\n\n validateUpdateRoute(\n name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\n forwardTo as any,\n (routeName: string) => typedStore.matcher.hasRoute(routeName),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\n typedStore.matcher as any,\n typedStore.config,\n );\n },\n validateParentOption(parent, tree) {\n validateParentOptionRaw(parent);\n let node = tree as { children: Map<string, unknown> };\n\n for (const segment of parent.split(\".\")) {\n const child = node.children.get(segment) as\n { children: Map<string, unknown> } | undefined;\n\n if (!child) {\n throw new ReferenceError(\n `[router.addRoute] Parent route \"${parent}\" does not exist`,\n );\n }\n\n node = child;\n }\n },\n validateRouteName(name, caller) {\n validateRouteName(name, caller);\n },\n throwIfInternalRoute(name, caller) {\n throwIfInternalRoute(name as string, caller);\n },\n\n throwIfInternalRouteInArray(routes, caller) {\n throwIfInternalRouteInArray(routes as readonly Route[], caller);\n },\n validateSetRootPathArgs,\n guardRouteCallbacks,\n guardNoAsyncCallbacks,\n },\n options: {\n validateOptions,\n validateResolvedDefaultRoute,\n },\n dependencies: {\n validateDependencyName,\n validateSetDependencyArgs(_name, _value, _caller) {\n validateSetDependencyArgsRaw(_name);\n },\n validateDependenciesObject,\n validateDependencyExists(name, store) {\n const typedStore = store as { dependencies?: Record<string, unknown> };\n const value = typedStore.dependencies?.[name];\n\n validateDependencyExistsRaw(value, name);\n },\n validateDependencyCount(store, methodName) {\n validateDependencyCount(store, methodName, ctx.logger);\n },\n validateCloneArgs,\n warnOverwrite(name, methodName) {\n warnDepsOverwrite(name, methodName, ctx.logger);\n },\n warnBatchOverwrite(keys, methodName) {\n warnBatchOverwrite(keys, methodName, ctx.logger);\n },\n warnRemoveNonExistent(name) {\n warnRemoveNonExistent(name, ctx.logger);\n },\n },\n plugins: {\n validatePluginLimit(count, limits) {\n validatePluginLimit(\n count,\n 1,\n (limits as { maxPlugins?: number } | undefined)?.maxPlugins,\n );\n },\n validateNoDuplicatePlugins,\n validatePluginKeys,\n validateCountThresholds(count) {\n const maxPlugins = ctx.getOptions().limits?.maxPlugins ?? 50;\n\n validatePluginCountThresholds(count, maxPlugins, ctx.logger);\n },\n warnBatchDuplicates() {\n warnBatchDuplicates(ctx.logger);\n },\n warnPluginMethodType(methodName) {\n warnPluginMethodType(methodName, ctx.logger);\n },\n warnPluginAfterStart(methodName) {\n warnPluginAfterStart(methodName, ctx.logger);\n },\n validateAddInterceptorArgs,\n },\n lifecycle: {\n validateHandler,\n validateHandlerLimit(count, methodName) {\n const maxHandlers =\n ctx.getOptions().limits?.maxLifecycleHandlers ?? 200;\n\n validateHandlerLimit(count, methodName, maxHandlers);\n },\n validateCountThresholds(count, methodName) {\n const maxHandlers =\n ctx.getOptions().limits?.maxLifecycleHandlers ?? 200;\n\n validateLifecycleCountThresholds(\n count,\n methodName,\n maxHandlers,\n ctx.logger,\n );\n },\n warnOverwrite(name, type, methodName) {\n warnLifecycleOverwrite(name, type, methodName, ctx.logger);\n },\n warnAsyncGuardSync(name, methodName) {\n warnAsyncGuardSync(name, methodName, ctx.logger);\n },\n },\n navigation: {\n validateNavigateArgs,\n validateNavigateToDefaultArgs,\n validateNavigateToStateArgs,\n validateNavigationOptions,\n validateParams: validateNavigateParams,\n validateStartArgs,\n },\n state: {\n validateMakeStateArgs,\n // One de-dup cache per validator object, i.e. per registration, i.e. per\n // router (#1583). Module-level `Set`s made the first router in a process\n // silence every one after it — the SSR / SSG case where the diagnostic\n // fired for request #1 and never again.\n reportDroppedQueryKey: createDroppedQueryKeyReporter(),\n reportUndeclaredParamKey: createUndeclaredParamKeyReporter(),\n validateAreStatesEqualArgs(s1, s2, ignoreQP) {\n if (!isState(s1)) {\n throw new TypeError(\n `[router.areStatesEqual] Invalid state1: ${getTypeDescription(s1)}. Expected State object.`,\n );\n }\n if (!isState(s2)) {\n throw new TypeError(\n `[router.areStatesEqual] Invalid state2: ${getTypeDescription(s2)}. Expected State object.`,\n );\n }\n if (ignoreQP !== undefined && !isBoolean(ignoreQP)) {\n throw new TypeError(\n `[router.areStatesEqual] Invalid ignoreQueryParams: ${getTypeDescription(ignoreQP)}. Expected boolean.`,\n );\n }\n },\n },\n eventBus: {\n validateListenerArgs(name, cb) {\n validateListenerArgs<EventName>(\n name as EventName,\n cb as Plugin[EventMethodMap[EventName]],\n );\n },\n validateCountThresholds(count, eventName, methodName) {\n const maxListeners = ctx.getOptions().limits?.maxListeners ?? 10_000;\n\n validateListenerCountThresholds(\n count,\n eventName,\n methodName,\n ctx.logger,\n maxListeners,\n );\n },\n },\n };\n}\n\n/**\n * The dependency map is a type parameter so the factory carries the CALLER's\n * `D` rather than the `object` default (#1621). `keyof object` is `never`, so a\n * bare `PluginFactory` types `getDependency` as `(key: never) => never`, which\n * TypeScript 7 refuses to assign where `PluginFactory<D>` is expected for any\n * `D` with an index signature — `usePlugin(validationPlugin())` stops compiling\n * for a consumer whose dependencies are `Record<string, T>`. TS 6 skipped that\n * variance check, so the imprecision was always there and simply invisible.\n *\n * `PluginFactory<never>` does NOT work as a shorthand here (unlike\n * `AnyOptions = Options<never>` in core): it fails on both compilers, because\n * this plugin actually reads dependencies.\n */\nexport function validationPlugin<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(): PluginFactory<Dependencies> {\n // eslint-disable-next-line unicorn/consistent-function-scoping\n return (router) => {\n const ctx = getInternals(router);\n\n if (router.isActive()) {\n throw new RouterError(\"VALIDATION_PLUGIN_AFTER_START\", {\n message: \"validation-plugin must be registered before router.start()\",\n });\n }\n\n // RouterInternals.validator is now mutable — direct assignment works\n ctx.validator = buildValidatorObject(ctx);\n\n try {\n const store = ctx.routeGetStore();\n const deps = ctx.dependenciesGetStore();\n const options = ctx.getOptions();\n\n validateExistingRoutes(store);\n validateForwardToConsistency(store);\n validateRoutePropertiesStore(store);\n validateForwardToTargetsStore(store);\n validateDependenciesStructure(deps);\n validateLimitsConsistency(options, deps);\n ctx.validator.options.validateOptions(\n options,\n \"constructor (retrospective)\",\n );\n\n if (typeof options.defaultRoute === \"string\") {\n validateResolvedDefaultRoute(options.defaultRoute, store);\n }\n } catch (error) {\n ctx.validator = null;\n\n throw error;\n }\n\n return {\n teardown() {\n ctx.validator = null;\n },\n };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["getPrototypeOf","hasOwn","obj","objectKeys","getOwnPropertyDescriptor","warnOverwrite","hasOwn","objectEntries","hasOwn","objectKeys","objectKeys","collectUrlParams","assertNotAsync","typedStore","RouterError"],"sources":["../../src/type-guards/guards/primitives.ts","../../src/type-guards/guards/params.ts","../../src/type-guards/guards/navigation.ts","../../src/type-guards/internal/router-error.ts","../../src/type-guards/guards/routes.ts","../../src/type-guards/internal/meta-fields.ts","../../src/type-guards/guards/state.ts","../../src/type-guards/validators/routes.ts","../../src/type-guards/utilities/type-description.ts","../../src/helpers.ts","../../src/validators/dependencies.ts","../../src/validators/eventBus.ts","../../src/validators/lifecycle.ts","../../src/validators/navigation.ts","../../src/validators/options.ts","../../src/validators/plugins.ts","../../src/validators/retrospective.ts","../../src/validators/forwardTo.ts","../../src/validators/routes.ts","../../src/validators/state.ts","../../src/validationPlugin.ts"],"sourcesContent":["// packages/validation-plugin/src/type-guards/guards/primitives.ts\n\n/**\n * Type guard for string type.\n *\n * @param value - Value to check\n * @returns true if value is a string\n */\nexport function isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\n/**\n * Type guard for boolean type.\n *\n * @param value - Value to check\n * @returns true if value is a boolean\n */\nexport function isBoolean(value: unknown): value is boolean {\n return typeof value === \"boolean\";\n}\n\n/**\n * Type guard for object key existence.\n * Narrows the key type to a valid key of the object.\n *\n * @param key - Key to check\n * @param obj - Object to check in\n * @returns true if key exists in object\n */\nexport function isObjKey<T extends object>(\n key: string,\n obj: T,\n): key is Extract<keyof T, string> {\n return key in obj;\n}\n","// packages/validation-plugin/src/type-guards/guards/params.ts\n\nimport type { Params } from \"@real-router/core\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ All three DECIDE, and this file's verdict is what they decide — so read off\n * the live global they are the guard's weakest point, not its input. Measured by\n * re-pointing each AFTER boot, all three FAIL OPEN: the guard starts accepting\n * what it exists to refuse.\n *\n * Object.getPrototypeOf -> null a Date instance is ACCEPTED into params\n * Object.values -> [] a nested function is ACCEPTED\n * Object.hasOwn -> false the own-key filter is skipped\n *\n * That is a sharper profile than core's raw reads, which mostly degrade toward\n * refusal or a wrong-but-loud outcome.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does NOT close it — a shim evaluated ahead of the module still wins\n * (#1798). That caveat is core's own, in `guards.ts`, and it travels with the\n * doctrine rather than being an argument against it.\n *\n * ⚠ LOCKSTEP: these names are referenced from functions this file shares\n * byte-for-byte with its twin, so the block must exist identically in both.\n */\nconst getPrototypeOf = Object.getPrototypeOf;\nconst objectValues = Object.values;\nconst hasOwn = Object.hasOwn;\n\n/**\n * Is `value` an array, or a plain object (`Object.prototype` / `null` prototype)?\n * Class instances (Date, RegExp, Map, Set, ...) are not plain containers.\n *\n * @internal\n */\nfunction isPlainContainer(value: object): boolean {\n if (Array.isArray(value)) {\n return true;\n }\n\n const proto = getPrototypeOf(value) as object | null;\n\n return proto === null || proto === Object.prototype;\n}\n\n/**\n * Pushes every child of an array or plain object onto the work-stack.\n *\n * @internal\n */\nfunction pushChildren(value: object, stack: unknown[]): void {\n const children = Array.isArray(value) ? value : objectValues(value);\n\n for (const child of children) {\n stack.push(child);\n }\n}\n\n/**\n * Is `value` a serializable primitive leaf? `string` and `boolean` always are; a\n * `number` only if finite (NaN/Infinity are not). Everything else (function,\n * symbol, bigint) is not serializable.\n *\n * @internal\n */\nfunction isSerializableLeaf(value: unknown): boolean {\n const type = typeof value;\n\n if (type === \"string\" || type === \"boolean\") {\n return true;\n }\n\n if (type === \"number\") {\n return Number.isFinite(value);\n }\n\n return false;\n}\n\n/**\n * Marker pushed onto the work-stack after a container's children. Popping it\n * means that container's whole subtree has been validated, so the container\n * leaves the active DFS path (`onPath`) and joins the fully-checked set\n * (`done`). A module-private class, so user data can never be mistaken for it.\n *\n * @internal\n */\nclass SubtreeExit {\n constructor(readonly container: object) {}\n}\n\n/**\n * Inspects one container popped from the work-stack. Rejects cycles (a back-edge\n * to a container still on the current DFS path) and class instances; skips\n * already-validated shared references; otherwise marks the container on-path and\n * queues its children plus a {@link SubtreeExit} marker.\n *\n * @returns false only when the container is invalid (cycle or class instance)\n * @internal\n */\nfunction visitContainer(\n value: object,\n stack: unknown[],\n onPath: WeakSet<object>,\n done: WeakSet<object>,\n): boolean {\n if (onPath.has(value)) {\n return false; // back-edge → genuine circular reference\n }\n\n if (done.has(value)) {\n return true; // shared reference / diamond — subtree already validated\n }\n\n if (!isPlainContainer(value)) {\n return false; // instance of a class\n }\n\n onPath.add(value);\n stack.push(new SubtreeExit(value));\n pushChildren(value, stack);\n\n return true;\n}\n\n/**\n * Internal helper to check if value is serializable (no circular refs, functions, instances).\n * Validates the entire object tree.\n *\n * Iterative (explicit work-stack) rather than recursive: a recursive walk overflows\n * the call stack with `RangeError` at ~2.4k levels of nesting on V8, which would break\n * the boolean contract on adversarial input reachable via `history.state` / user-supplied\n * params (#901). A heap-allocated stack scales to any depth.\n *\n * Cycle detection uses on-path (DFS gray/black) semantics, not \"ever-visited\":\n * `onPath` holds the containers on the current branch — a back-edge to one of them is\n * a genuine cycle and is rejected; `done` holds containers whose subtree is already\n * fully validated. A shared reference / diamond (the same object reached again *off*\n * the current path) is serializable — `JSON.stringify` duplicates it — so it is\n * accepted, and `done` makes the re-encounter O(1), keeping the walk linear on diamond\n * chains instead of exponential (#786). Genuine cycles stay rejected: the cycle vertex\n * is still on the path when re-encountered.\n *\n * @param root - Value to check\n * @returns true if value can be serialized\n * @internal\n */\nfunction isSerializable(root: unknown): boolean {\n const stack: unknown[] = [root];\n const onPath = new WeakSet<object>();\n const done = new WeakSet<object>();\n\n while (stack.length > 0) {\n const value = stack.pop();\n\n // Subtree fully processed: leave the current path, mark as validated.\n if (value instanceof SubtreeExit) {\n onPath.delete(value.container);\n done.add(value.container);\n\n continue;\n }\n\n // null/undefined are serializable (JSON.stringify handles them)\n if (value === null || value === undefined) {\n continue;\n }\n\n // Arrays and plain objects (typeof null is \"object\", handled above).\n if (typeof value === \"object\") {\n if (!visitContainer(value, stack, onPath, done)) {\n return false;\n }\n\n continue;\n }\n\n // Primitive leaf: string / boolean / finite number pass; function, symbol,\n // bigint, and NaN/Infinity do not.\n if (!isSerializableLeaf(value)) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Fast path check for primitive values (no recursion needed).\n * Returns true if primitive, false if needs deeper inspection.\n *\n * @internal\n */\nfunction isPrimitiveValue(value: unknown): boolean {\n if (value === null || value === undefined) {\n return true;\n }\n\n const type = typeof value;\n\n if (type === \"string\" || type === \"boolean\") {\n return true;\n }\n\n if (type === \"number\") {\n return Number.isFinite(value);\n }\n\n // object, array, function, symbol — need deeper check\n return false;\n}\n\n/**\n * Type guard for Params object.\n * Validates that all values are serializable (primitives, arrays, nested arrays, or nested objects).\n * Rejects circular references, functions, symbols, and class instances.\n * Allows null/undefined anywhere (objects and arrays).\n *\n * Performance optimization: Uses two-phase validation.\n * Phase 1 (fast path): Check if all values are primitives — O(p) without recursion.\n * Phase 2 (slow path): Full recursive validation only if nested structures detected.\n *\n * @param value - Value to check\n * @returns true if value is a valid Params object\n *\n * @example\n * // Valid\n * isParams({ id: '123', page: 1 }); // true (fast path)\n * isParams({ sort: ['name', 'age'] }); // true (slow path)\n * isParams({ filter: { status: 'active' } }); // true (slow path)\n * isParams({ teamId: null, orgId: undefined }); // true (fast path)\n * isParams({ matrix: [[1, 2], [3, 4]] }); // true (slow path)\n * isParams({ users: [{ id: 1 }, { id: 2 }] }); // true (slow path)\n * isParams({ scores: [100, null, 85] }); // true (slow path)\n * isParams({ a: shared, b: shared }); // true (shared reference / diamond, not a cycle)\n *\n * // Invalid\n * isParams({ fn: () => {} }); // false (function)\n * isParams({ date: new Date() }); // false (class instance)\n * isParams({ self: circularRef }); // false (circular reference)\n * isParams(null); // false (not an object)\n * isParams([]); // false (array, not object)\n */\nexport function isParams(value: unknown): value is Params {\n // Getter-safe boundary (#1052): a throwing `[[Get]]` (a value getter that\n // throws, or a Proxy trap) during the structural walk below must not crash —\n // an object whose structure cannot even be read is not valid params. Mirrors\n // the never-throw contract of #786/#901 (deep nesting / cycles), for a throwing\n // accessor. Wraps the whole walk (proto read, fast-path value reads, and the\n // recursive isSerializable slow path) in one boundary.\n try {\n return isParamsUnsafe(value);\n } catch {\n return false;\n }\n}\n\nfunction isParamsUnsafe(value: unknown): value is Params {\n // Reject null, undefined, and arrays (must be a plain object)\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n\n // Reject objects with custom prototype (e.g., Object.create(proto), class instances)\n // This check is required for both fast and slow paths\n const proto = getPrototypeOf(value) as object | null;\n\n if (proto !== null && proto !== Object.prototype) {\n return false;\n }\n\n // Phase 1: Fast path for flat objects (95%+ of real-world params)\n // Check if all values are primitives — no recursion, no WeakSet allocation\n let needsDeepCheck = false;\n\n for (const key in value) {\n // Skip inherited properties (only check own properties for fast path)\n // Note: With proto === Object.prototype check above, inherited properties\n // would only come from Object.prototype, which has no enumerable properties.\n // This check is defensive against Object.prototype pollution — covered by the\n // prototype-pollution test, which surfaces an inherited enumerable key here.\n if (!hasOwn(value, key)) {\n continue;\n }\n\n const val = (value as Record<string, unknown>)[key];\n\n if (!isPrimitiveValue(val)) {\n // Found non-primitive — need full validation\n // But first check for obvious invalids (functions, symbols)\n const type = typeof val;\n\n if (type === \"function\" || type === \"symbol\") {\n return false; // Early reject\n }\n\n needsDeepCheck = true;\n\n break; // Exit fast path, proceed to slow path\n }\n }\n\n // Fast path: all primitives, valid params\n if (!needsDeepCheck) {\n return true;\n }\n\n // Phase 2: Slow path — full recursive validation\n return isSerializable(value);\n}\n","// packages/validation-plugin/src/type-guards/guards/navigation.ts\n\nimport type { NavigationOptions } from \"@real-router/core\";\n\n// Constant in module scope - created once, not on every call\nconst NAVIGATION_OPTIONS_FIELDS = [\n \"replace\",\n \"reload\",\n \"force\",\n \"forceDeactivate\",\n \"redirected\",\n] as const;\n\n/**\n * Type guard for NavigationOptions.\n * Validates all optional boolean fields.\n *\n * @param value - Value to check\n * @returns true if value is a valid NavigationOptions object\n *\n * @example\n * isNavigationOptions({ replace: true }); // true\n * isNavigationOptions({ reload: false, force: true }); // true\n * isNavigationOptions({ replace: \"true\" }); // false (not boolean)\n * isNavigationOptions(null); // false\n */\nexport function isNavigationOptions(\n value: unknown,\n): value is NavigationOptions {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return false;\n }\n\n const obj = value as Record<string, unknown>;\n\n // for-of instead of .every() - no iterator/callback allocation\n for (const field of NAVIGATION_OPTIONS_FIELDS) {\n const fieldValue = obj[field];\n\n if (fieldValue !== undefined && typeof fieldValue !== \"boolean\") {\n return false;\n }\n }\n\n // Validate signal field\n const signalValue = obj.signal;\n\n return signalValue === undefined || signalValue instanceof AbortSignal;\n}\n","// packages/validation-plugin/src/type-guards/internal/router-error.ts\n\n/**\n * Checks if string contains at least one non-whitespace character.\n * Used to validate that route name is not empty or only whitespace.\n * Matches: any non-whitespace character (\\S = [^ \\t\\n\\r\\f])\n *\n * @internal\n */\nexport const HAS_NON_WHITESPACE = /\\S/;\n\n/**\n * Pattern for complete route validation (all segments at once).\n * Matches: single segment or multiple segments separated by dots.\n * Each segment must start with letter/underscore, followed by alphanumeric/hyphen/underscore.\n * Rejects: leading/trailing/consecutive dots, segments starting with numbers/hyphens.\n * Note: Empty string is handled by early return in validateRouteName.\n *\n * @internal\n */\n// eslint-disable-next-line security/detect-unsafe-regex -- safe: each `(?:\\.…)*` repetition is anchored by a literal `.`, which `[\\w-]*` cannot match, so the inner/outer quantifiers consume disjoint classes — no catastrophic backtracking (safe-regex over-flags the nested `*`).\nexport const FULL_ROUTE_PATTERN = /^[A-Z_a-z][\\w-]*(?:\\.[A-Z_a-z][\\w-]*)*$/;\n\n/**\n * Maximum route name length to prevent DoS and performance issues.\n * This is a technical limit, not a business constraint.\n * Real route names rarely exceed 200 characters even with deep nesting.\n *\n * @internal\n */\nexport const MAX_ROUTE_NAME_LENGTH = 10_000;\n\n/**\n * Creates a TypeError with consistent router error message format.\n *\n * @param methodName - Name of the method that triggered the error\n * @param message - Error message\n * @returns TypeError with formatted message\n * @internal\n */\nexport function createRouterError(\n methodName: string,\n message: string,\n): TypeError {\n return new TypeError(`[router.${methodName}] ${message}`);\n}\n","// packages/validation-plugin/src/type-guards/guards/routes.ts\n\nimport {\n FULL_ROUTE_PATTERN,\n MAX_ROUTE_NAME_LENGTH,\n} from \"../internal/router-error\";\n\n/**\n * Type guard that checks if a value is a valid route name.\n *\n * Route name rules:\n * - Must be a string\n * - Empty string (\"\") is valid and represents the root node\n * - Can use dots (.) for hierarchy (e.g., \"users.profile\")\n * - Each segment must match [a-zA-Z_][a-zA-Z0-9_-]*\n * - No consecutive dots (..)\n * - No leading/trailing dots\n * - Cannot contain only whitespace\n * - System routes (starting with @@) bypass pattern validation\n *\n * @param name - Value to check\n * @returns true if name is a valid route name string\n *\n * @example\n * // Valid names\n * isRouteName(\"\"); // true (root node)\n * isRouteName(\"home\"); // true\n * isRouteName(\"users.profile\"); // true\n * isRouteName(\"admin_panel\"); // true\n * isRouteName(\"api-v2\"); // true\n * isRouteName(\"@@router/UNKNOWN_ROUTE\"); // true (system route)\n *\n * @example\n * // Invalid names\n * isRouteName(\" \"); // false (only whitespace)\n * isRouteName(\".users\"); // false (leading dot)\n * isRouteName(\"users.\"); // false (trailing dot)\n * isRouteName(\"users..profile\"); // false (consecutive dots)\n * isRouteName(\"users.123\"); // false (segment starts with number)\n * isRouteName(\"users profile\"); // false (contains space)\n */\nexport function isRouteName(name: unknown): name is string {\n if (typeof name !== \"string\") {\n return false;\n }\n\n // Empty string is valid (represents root node)\n if (name === \"\") {\n return true;\n }\n\n // Too long is invalid\n if (name.length > MAX_ROUTE_NAME_LENGTH) {\n return false;\n }\n\n // System routes are valid (bypass pattern validation)\n if (name.startsWith(\"@@\")) {\n return true;\n }\n\n // Regular routes must match pattern\n // Note: FULL_ROUTE_PATTERN rejects whitespace-only strings\n return FULL_ROUTE_PATTERN.test(name);\n}\n","// packages/validation-plugin/src/type-guards/internal/meta-fields.ts\n\nimport { isParams } from \"../guards/params\";\nimport { isRouteName } from \"../guards/routes\";\n\n/**\n * Type guard helper that checks if required State fields have valid types.\n * Validates that name, path, and params exist and have correct types.\n * Used by isState and isStateStrict.\n *\n * @param obj - Object to check\n * @returns true if object has valid required fields\n * @internal\n */\nexport function isRequiredFields(obj: Record<string, unknown>): boolean {\n return (\n isRouteName(obj.name) &&\n typeof obj.path === \"string\" &&\n isParams(obj.params)\n );\n}\n","// packages/validation-plugin/src/type-guards/guards/state.ts\n\nimport { isRequiredFields } from \"../internal/meta-fields\";\n\nimport type { Params, State } from \"@real-router/core\";\n\n/**\n * Type guard for State object.\n * Checks for required fields: name, params, path.\n * Does NOT validate meta field deeply.\n *\n * @param value - Value to check\n * @returns true if value is a valid State object\n *\n * @example\n * isState({ name: 'home', params: {}, path: '/' }); // true\n * isState({ name: 'home', path: '/' }); // false (missing params)\n */\nexport function isState<P extends Params = Params>(\n value: unknown,\n): value is State<P> {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n\n const obj = value as Record<string, unknown>;\n\n return isRequiredFields(obj);\n}\n","// packages/validation-plugin/src/type-guards/validators/routes.ts\n\nimport {\n createRouterError,\n FULL_ROUTE_PATTERN,\n HAS_NON_WHITESPACE,\n MAX_ROUTE_NAME_LENGTH,\n} from \"../internal/router-error\";\n\n/**\n * Validates that a route name is a valid string and matches the expected format.\n * Throws a descriptive error if validation fails.\n *\n * Route name rules:\n * - Must be a string\n * - Empty string (\"\") is valid and represents the root node\n * - Can use dots (.) for hierarchy (e.g., \"users.profile\")\n * - Each segment must match [a-zA-Z_][a-zA-Z0-9_-]*\n * - No consecutive dots (..)\n * - No leading/trailing dots\n * - Cannot contain only whitespace\n * - System routes (starting with @@) bypass pattern validation\n *\n * @param name - Route name to validate\n * @param methodName - Name of calling method for error messages\n * @throws {TypeError} If name is invalid\n *\n * @example\n * // Valid names\n * validateRouteName(\"\", \"add\"); // ok (root node)\n * validateRouteName(\"home\", \"add\"); // ok\n * validateRouteName(\"users.profile\", \"add\"); // ok\n * validateRouteName(\"@@router/UNKNOWN_ROUTE\", \"add\"); // ok (system route)\n *\n * @example\n * // Invalid names (throws)\n * validateRouteName(\" \", \"add\"); // throws (only whitespace)\n * validateRouteName(\".users\", \"add\"); // throws (leading dot)\n * validateRouteName(\"users.\", \"add\"); // throws (trailing dot)\n * validateRouteName(\"users..profile\", \"add\"); // throws (consecutive dots)\n * validateRouteName(\"users.123\", \"add\"); // throws (segment starts with number)\n * validateRouteName(\"users profile\", \"add\"); // throws (contains space)\n */\nexport function validateRouteName(\n name: unknown,\n methodName: string,\n): asserts name is string {\n // Type check\n if (typeof name !== \"string\") {\n throw createRouterError(\n methodName,\n `Route name must be a string, got ${typeof name}`,\n );\n }\n\n // Empty string is valid (represents root node) - fast path\n if (name === \"\") {\n return;\n }\n\n // Whitespace-only strings are invalid\n if (!HAS_NON_WHITESPACE.test(name)) {\n throw createRouterError(\n methodName,\n \"Route name cannot contain only whitespace\",\n );\n }\n\n // Length check for technical safety\n if (name.length > MAX_ROUTE_NAME_LENGTH) {\n throw createRouterError(\n methodName,\n `Route name exceeds maximum length of ${MAX_ROUTE_NAME_LENGTH} characters. This is a technical safety limit.`,\n );\n }\n\n // System routes bypass validation (e.g., @@router/UNKNOWN_ROUTE)\n // SECURITY NOTE: System routes are currently created ONLY in router code,\n // not from user input. If this changes, add sanitization for <>\"'&\\x00-\\x1F\n if (name.startsWith(\"@@\")) {\n return;\n }\n\n // Validate route pattern\n if (!FULL_ROUTE_PATTERN.test(name)) {\n throw createRouterError(\n methodName,\n `Invalid route name \"${name}\". Each segment must start with a letter or underscore, followed by letters, numbers, underscores, or hyphens. Segments are separated by dots (e.g., \"users.profile\").`,\n );\n }\n}\n","// packages/validation-plugin/src/type-guards/utilities/type-description.ts\n\n/**\n * Gets a human-readable description of a value's type.\n * Used for error messages to provide helpful debugging information.\n *\n * @param value - Value to describe\n * @returns String description of the value's type\n *\n * @example\n * getTypeDescription(null); // \"null\"\n * getTypeDescription([1, 2, 3]); // \"array[3]\"\n * getTypeDescription(new Date()); // \"Date\"\n * getTypeDescription({}); // \"object\"\n * getTypeDescription(\"hello\"); // \"string\"\n */\nexport function getTypeDescription(value: unknown): string {\n // Handle null explicitly (typeof null === \"object\")\n if (value === null) {\n return \"null\";\n }\n\n // Array with length info\n if (Array.isArray(value)) {\n return `array[${value.length}]`;\n }\n\n if (typeof value === \"object\") {\n // Read `constructor`/`.name` defensively: an adversarial own `constructor`\n // (null, a string, a number, …) is not a real constructor and must not crash\n // here nor yield a non-string (#787); a THROWING accessor — a `constructor`\n // getter, a function constructor with a throwing `.name` getter, or a Proxy\n // that throws on [[Get]] — must not crash either (#1052). Both fall back to\n // \"object\", the same as the non-function-value branch.\n try {\n const ctor: unknown = (value as { constructor?: unknown }).constructor;\n\n // Return constructor name for class instances\n if (typeof ctor === \"function\" && ctor.name !== \"Object\") {\n return ctor.name || \"object\"; // empty name (anonymous class) → \"object\"\n }\n } catch {\n // Throwing constructor/.name getter or Proxy [[Get]] → fall through (#1052).\n }\n\n // Plain object, null-prototype object, or adversarial / throwing constructor\n return \"object\";\n }\n\n // Primitive types (string, number, boolean, undefined, etc.)\n // Note: typeof undefined === \"undefined\", so we don't need explicit check\n return typeof value;\n}\n","// packages/validation-plugin/src/helpers.ts\n\nexport function computeThresholds(limit: number): {\n warn: number;\n error: number;\n} {\n return {\n warn: Math.floor(limit * 0.2),\n error: Math.floor(limit * 0.5),\n };\n}\n","// packages/validation-plugin/src/validators/dependencies.ts\n\nimport { computeThresholds } from \"../helpers\";\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { RouterLogger } from \"@real-router/core\";\n\nconst DEFAULT_MAX_DEPENDENCIES = 100;\n\n/**\n * Captured at module load, mirroring `core/src/guards.ts`. A validator is only\n * as strong as the intrinsic it reads WHEN IT RUNS.\n */\nconst objectKeys = Object.keys;\nconst getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nconst getPrototypeOf = Object.getPrototypeOf;\nconst ObjectCtor = Object;\n\n/**\n * ⚑ The MIRROR of `ingestDependencies` (core `guards.ts` — renamed from\n * `guardDependencies` when it absorbed the copy walk, #1860/#1861), and it has to\n * stay one.\n *\n * This plugin's whole contract is `plugin ⊇ core`: it may diagnose more, never\n * refuse what core accepts. Core moved this predicate from `deps.constructor` to\n * the PROTOTYPE's `constructor` (#1858) and began admitting a null prototype;\n * while these two copies still read the instance, an application running the\n * plugin got a false reject on exactly the shapes core had just widened —\n * `setDependencies` and `cloneRouter`'s override bag both threw on a bag\n * carrying a `constructor` key, which is the #1858 defect on a different door.\n *\n * ⚠ The walk is `objectKeys`, not `for…in`, for the same reason core's is\n * (#1799): `for…in` visits inherited names that `getOwnPropertyDescriptor`\n * answers `undefined` for, so the loop iterated exactly the names it could not\n * judge — and on a Proxy the two disagree about ownership outright.\n */\nfunction isPlainBag(value: unknown): value is Record<string, unknown> {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const proto = getPrototypeOf(value) as { constructor?: unknown } | null;\n\n return proto === null || proto.constructor === ObjectCtor;\n}\n\nexport function validateDependencyName(\n name: unknown,\n methodName: string,\n): asserts name is string {\n if (typeof name !== \"string\") {\n throw new TypeError(\n `[router.${methodName}] dependency name must be a string, got ${typeof name}`,\n );\n }\n}\n\nexport function validateSetDependencyArgs(\n name: unknown,\n): asserts name is string {\n if (typeof name !== \"string\") {\n throw new TypeError(\n `[router.setDependency] dependency name must be a string, got ${typeof name}`,\n );\n }\n}\n\nexport function validateDependenciesObject(\n deps: unknown,\n methodName: string,\n): asserts deps is Record<string, unknown> {\n if (!isPlainBag(deps)) {\n throw new TypeError(\n `[router.${methodName}] Invalid argument: expected plain object, received ${getTypeDescription(deps)}`,\n );\n }\n\n for (const key of objectKeys(deps)) {\n if (getOwnPropertyDescriptor(deps, key)?.get) {\n throw new TypeError(\n `[router.${methodName}] Getters not allowed: \"${key}\"`,\n );\n }\n }\n}\n\nexport function validateDependencyExists(\n value: unknown,\n dependencyName: string,\n): asserts value is NonNullable<unknown> {\n if (value === undefined) {\n throw new ReferenceError(\n `[router.getDependency] dependency \"${dependencyName}\" not found`,\n );\n }\n}\n\nexport function validateDependencyCount(\n store: unknown,\n methodName: string,\n logger: RouterLogger,\n): void {\n const typedStore = store as {\n dependencies: Record<string, unknown>;\n limits?: { maxDependencies?: number };\n };\n const maxDependencies =\n typedStore.limits?.maxDependencies ?? DEFAULT_MAX_DEPENDENCIES;\n\n if (maxDependencies === 0) {\n return;\n }\n\n const currentCount = objectKeys(typedStore.dependencies).length;\n const { warn, error } = computeThresholds(maxDependencies);\n\n if (currentCount >= maxDependencies) {\n throw new RangeError(\n `[router.${methodName}] Dependency limit exceeded (${maxDependencies}). Current: ${currentCount}.`,\n );\n }\n if (currentCount === error) {\n logger.error(\n `router.${methodName}`,\n `${currentCount} dependencies registered! This indicates architectural problems. Hard limit at ${maxDependencies}.`,\n );\n } else if (currentCount === warn) {\n logger.warn(\n `router.${methodName}`,\n `${currentCount} dependencies registered. Consider if all are necessary.`,\n );\n }\n}\n\nexport function validateCloneArgs(dependencies: unknown): void {\n if (dependencies === undefined) {\n return;\n }\n\n if (!isPlainBag(dependencies)) {\n throw new TypeError(\n `[cloneRouter] Invalid dependencies: expected plain object or undefined, received ${typeof dependencies}`,\n );\n }\n\n for (const key of objectKeys(dependencies)) {\n if (getOwnPropertyDescriptor(dependencies, key)?.get) {\n throw new TypeError(\n `[cloneRouter] Getters not allowed in dependencies: \"${key}\"`,\n );\n }\n }\n}\n\nexport function warnOverwrite(\n name: string,\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(\n `router.${methodName}`,\n \"Router dependency already exists and is being overwritten:\",\n name,\n );\n}\n\nexport function warnBatchOverwrite(\n keys: string[],\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(`router.${methodName}`, \"Overwritten:\", keys.join(\", \"));\n}\n\nexport function warnRemoveNonExistent(\n name: unknown,\n logger: RouterLogger,\n): void {\n logger.warn(\n \"router.removeDependency\",\n `Attempted to remove non-existent dependency: \"${typeof name === \"string\" ? name : String(name)}\"`,\n );\n}\n","// packages/validation-plugin/src/validators/eventBus.ts\n\nimport { computeThresholds } from \"../helpers\";\n\nimport type { Plugin, RouterLogger } from \"@real-router/core\";\n\nconst DEFAULT_MAX_LISTENERS = 10_000;\n\n// Local types — mirrors EventName and EventMethodMap from @real-router/types\n// (@real-router/types is not a direct dependency of this package)\nexport interface EventMethodMap {\n $start: \"onStart\";\n $stop: \"onStop\";\n $$start: \"onTransitionStart\";\n $$leaveApprove: \"onTransitionLeaveApprove\";\n $$cancel: \"onTransitionCancel\";\n $$success: \"onTransitionSuccess\";\n $$error: \"onTransitionError\";\n}\n\nexport type EventName = keyof EventMethodMap;\n\n// Single source of truth (plugin-owned): core has no `validEventNames` constant\n// and does not enforce event-name validity — this set is the sole owner.\nconst validEventNames = new Set<EventName>([\n \"$start\",\n \"$stop\",\n \"$$start\",\n \"$$leaveApprove\",\n \"$$cancel\",\n \"$$success\",\n \"$$error\",\n]);\n\nexport function validateEventName(eventName: unknown): void {\n if (!validEventNames.has(eventName as EventName)) {\n throw new TypeError(\n `[router.addEventListener] Invalid event name: ${String(eventName)}. Must be one of: $start, $stop, $$start, $$leaveApprove, $$cancel, $$success, $$error`,\n );\n }\n}\n\nexport function validateListenerArgs<E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n): void {\n validateEventName(eventName);\n\n if (typeof cb !== \"function\") {\n throw new TypeError(\n `[router.addEventListener] callback must be a function, got ${typeof cb}`,\n );\n }\n}\n\n/**\n * Proactive warn@20% / error@50% threshold for the per-event listener counter\n * (#1188) — the fourth resource counter, mirroring plugins / lifecycle /\n * dependencies. Fired by core from `subscribe` / `addEventListener` with the\n * POST-add count. Core keeps the emitter's bare-`Error` hard cap; this only\n * surfaces an actionable, `[router.<method>]`-prefixed signal well before it, so\n * a listener leak (e.g. a missing unsubscribe — #766) is caught early instead of\n * OOM-ing at the 10 000-listener wall. `maxListeners === 0` means unlimited.\n */\nexport function validateListenerCountThresholds(\n count: number,\n eventName: string,\n methodName: string,\n // logger precedes the defaulted maxListeners — defaults must come last\n // (S1788; logger was appended when the per-router RouterLogger replaced the\n // former global singleton, #724).\n logger: RouterLogger,\n maxListeners: number = DEFAULT_MAX_LISTENERS,\n): void {\n if (maxListeners === 0) {\n return;\n }\n\n const { warn, error } = computeThresholds(maxListeners);\n\n if (count >= error) {\n logger.error(\n `router.${methodName}`,\n `${count} listeners registered for \"${eventName}\"! This is excessive. Hard limit at ${maxListeners}.`,\n );\n } else if (count >= warn) {\n logger.warn(\n `router.${methodName}`,\n `${count} listeners registered for \"${eventName}\". Consider whether all are necessary — a growing count usually means a missing unsubscribe.`,\n );\n }\n}\n","// packages/validation-plugin/src/validators/lifecycle.ts\n\nimport { computeThresholds } from \"../helpers\";\nimport { isBoolean, getTypeDescription } from \"../type-guards\";\n\nimport type {\n GuardFnFactory,\n DefaultDependencies,\n RouterLogger,\n} from \"@real-router/core\";\n\nconst DEFAULT_MAX_LIFECYCLE_HANDLERS = 200;\n\nexport function validateHandler<D extends DefaultDependencies>(\n handler: unknown,\n methodName: string,\n): asserts handler is GuardFnFactory<D> | boolean {\n if (!isBoolean(handler) && typeof handler !== \"function\") {\n throw new TypeError(\n `[router.${methodName}] Handler must be a boolean or factory function, ` +\n `got ${getTypeDescription(handler)}`,\n );\n }\n}\n\nexport function validateHandlerLimit(\n currentCount: number,\n methodName: string,\n maxLifecycleHandlers: number = DEFAULT_MAX_LIFECYCLE_HANDLERS,\n): void {\n if (maxLifecycleHandlers === 0) {\n return;\n }\n\n if (currentCount >= maxLifecycleHandlers) {\n throw new RangeError(\n `[router.${methodName}] Lifecycle handler limit exceeded (${maxLifecycleHandlers}). ` +\n `This indicates too many routes with individual handlers. ` +\n `Consider using plugins for cross-cutting concerns.`,\n );\n }\n}\n\nexport function validateLifecycleCountThresholds(\n count: number,\n methodName: string,\n maxHandlers: number,\n logger: RouterLogger,\n): void {\n if (maxHandlers === 0) {\n return;\n }\n\n const { warn, error } = computeThresholds(maxHandlers);\n\n if (count >= error) {\n logger.error(\n `router.${methodName}`,\n `${count} lifecycle handlers registered! This is excessive. Hard limit at ${maxHandlers}.`,\n );\n } else if (count >= warn) {\n logger.warn(\n `router.${methodName}`,\n `${count} lifecycle handlers registered. Consider consolidating logic.`,\n );\n }\n}\n\nexport function warnOverwrite(\n name: string,\n type: string,\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(\n `router.${methodName}`,\n `Overwriting existing ${type} handler for route \"${name}\"`,\n );\n}\n\nexport function warnAsyncGuardSync(\n name: string,\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(\n `router.${methodName}`,\n `Guard for \"${name}\" returned a Promise. Sync check cannot resolve async guards — returning false.`,\n );\n}\n","// packages/validation-plugin/src/validators/navigation.ts\n\nimport {\n getTypeDescription,\n isNavigationOptions,\n isParams,\n isString,\n} from \"../type-guards\";\n\nimport type { NavigationOptions } from \"@real-router/core\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst hasOwn = Object.hasOwn;\n\nexport function validateNavigateArgs(name: unknown): asserts name is string {\n if (typeof name !== \"string\") {\n throw new TypeError(\n `[router.navigate] Invalid route name: expected string, got ${getTypeDescription(name)}`,\n );\n }\n}\n\nexport function validateNavigateToDefaultArgs(opts: unknown): void {\n if (opts !== undefined && (typeof opts !== \"object\" || opts === null)) {\n throw new TypeError(\n `[router.navigateToDefault] Invalid options: ${getTypeDescription(opts)}. Expected NavigationOptions object.`,\n );\n }\n}\n\nexport function validateNavigateToStateArgs(state: unknown): void {\n if (typeof state !== \"object\" || state === null) {\n throw new TypeError(\n `[router.navigateToState] Invalid state: ${getTypeDescription(state)}. Expected State object.`,\n );\n }\n\n const candidate = state as { name: unknown; params: unknown; path: unknown };\n\n if (!isString(candidate.name)) {\n throw new TypeError(\n `[router.navigateToState] Invalid state.name: ${getTypeDescription(candidate.name)}. Expected string.`,\n );\n }\n if (!isParams(candidate.params)) {\n throw new TypeError(\n `[router.navigateToState] Invalid state.params: ${getTypeDescription(candidate.params)}. Expected plain object.`,\n );\n }\n if (!isString(candidate.path)) {\n throw new TypeError(\n `[router.navigateToState] Invalid state.path: ${getTypeDescription(candidate.path)}. Expected string.`,\n );\n }\n}\n\nexport function validateNavigationOptions(\n opts: unknown,\n methodName: string,\n): asserts opts is NavigationOptions {\n if (!isNavigationOptions(opts)) {\n throw new TypeError(\n `[router.${methodName}] Invalid options: ${getTypeDescription(opts)}. Expected NavigationOptions object.`,\n );\n }\n}\n\n// C0 control chars (U+0000–U+001F) and DEL (U+007F). Core percent-encodes them\n// into the URL path (%00, %01, …) instead of failing, admitting unreadable,\n// non-copyable paths into committed state (#942).\n// eslint-disable-next-line no-control-regex -- matching control characters IS the validation here (#942)\nconst CONTROL_CHARS_RE = /[\\u0000-\\u001F\\u007F]/;\n\n/**\n * Rejects param VALUES that cannot safely round-trip through a URL path:\n * - `symbol` / `bigint` stringify lossily — a Symbol keeps its raw identity in\n * `state.params` and never matches back from the path, so navigation\n * \"succeeds\" with a corrupt, non-round-tripping path (#934);\n * - a `string` carrying control characters corrupts the path segment (#942).\n *\n * Pinpoints the offending key so the message is actionable, instead of the\n * generic \"params must be a plain object\" shape error.\n */\nfunction assertValidParamValues(\n params: Record<string, unknown>,\n methodName: string,\n): void {\n for (const key in params) {\n if (!hasOwn(params, key)) {\n continue;\n }\n\n const value = params[key];\n const valueType = typeof value;\n\n if (valueType === \"symbol\" || valueType === \"bigint\") {\n throw new TypeError(\n `[router.${methodName}] param \"${key}\" cannot be a ${valueType} — it does not round-trip through the URL path. Use a string, number, or boolean.`,\n );\n }\n\n if (valueType === \"string\" && CONTROL_CHARS_RE.test(value as string)) {\n throw new TypeError(\n `[router.${methodName}] param \"${key}\" must not contain control characters (NUL / C0 / DEL) — they corrupt the URL path.`,\n );\n }\n }\n}\n\nexport function validateNavigateParams(\n params: unknown,\n methodName: string,\n): void {\n if (params === undefined) {\n return;\n }\n\n // Inspect individual values first so a Symbol/BigInt/control-char value gets a\n // precise, value-specific message instead of the generic shape error below.\n if (typeof params === \"object\" && params !== null && !Array.isArray(params)) {\n assertValidParamValues(params as Record<string, unknown>, methodName);\n }\n\n if (!isParams(params)) {\n throw new TypeError(\n `[router.${methodName}] params must be a plain object, got ${getTypeDescription(params)}`,\n );\n }\n}\n\nexport function validateStartArgs(path: unknown): void {\n // undefined is allowed — browser-plugin injects path via interceptor AFTER facade validation\n if (path !== undefined && typeof path !== \"string\") {\n throw new TypeError(\n `[router.start] path must be a string, got ${getTypeDescription(path)}.`,\n );\n }\n if (typeof path === \"string\") {\n // #942: a NUL byte / control char would be silently percent-encoded into\n // state.path (%00, %01) by core — reject it with an actionable error.\n if (CONTROL_CHARS_RE.test(path)) {\n throw new TypeError(\n `[router.start] path must not contain control characters (NUL / C0 / DEL).`,\n );\n }\n if (path !== \"\" && !path.startsWith(\"/\")) {\n throw new TypeError(\n `[router.start] path must start with \"/\", got \"${path}\".`,\n );\n }\n }\n}\n","// packages/validation-plugin/src/validators/options.ts\n\nimport { isObjKey } from \"../type-guards\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectEntries = Object.entries;\nconst hasOwn = Object.hasOwn;\nconst objectKeys = Object.keys;\n\nconst VALID_OPTION_VALUES = {\n trailingSlash: [\"strict\", \"never\", \"always\", \"preserve\"] as const,\n queryParamsMode: [\"default\", \"strict\", \"loose\"] as const,\n urlParamsEncoding: [\"default\", \"uri\", \"uriComponent\", \"none\"] as const,\n} as const;\n\nconst VALID_QUERY_PARAMS = {\n arrayFormat: [\"none\", \"brackets\", \"index\", \"comma\"] as const,\n booleanFormat: [\"none\", \"auto\", \"empty-true\"] as const,\n nullFormat: [\"default\", \"hidden\"] as const,\n numberFormat: [\"none\", \"auto\"] as const,\n} as const;\n\n// `logger` is a valid option name, but its contents are NOT validated here.\n// The Router constructor consumes `options.logger` (applies it to the\n// process-global logger singleton via `logger.configure()`) and strips the key\n// before options are stored (#724). The retrospective pass reads the stored,\n// logger-stripped options, so any logger validation in this plugin is dead on\n// the live path. Logger config is therefore validated solely by core's\n// `isLoggerConfig` guard at construction — the only place the input exists (#789).\nconst KNOWN_OPTIONS = new Set<string>([\n \"defaultRoute\",\n \"defaultParams\",\n \"defaultSearch\",\n \"trailingSlash\",\n \"caseSensitive\",\n \"queryParamsMode\",\n \"queryParams\",\n \"urlParamsEncoding\",\n \"allowNotFound\",\n \"rewritePathOnMatch\",\n \"logger\",\n \"limits\",\n]);\n\n// Local type - mirrors LimitsConfig from @real-router/types\n// (@real-router/types is not a direct dependency of this package)\ninterface LimitsConfig {\n maxDependencies: number;\n maxPlugins: number;\n maxListeners: number;\n warnListeners: number;\n maxLifecycleHandlers: number;\n}\n\n// Single source of truth (plugin-owned): core has no `LIMIT_BOUNDS` constant and\n// does not enforce these bounds — this constant is the sole owner.\nconst LIMIT_BOUNDS = {\n maxDependencies: { min: 0, max: 10_000 },\n maxPlugins: { min: 0, max: 1000 },\n maxListeners: { min: 0, max: 100_000 },\n warnListeners: { min: 0, max: 100_000 },\n maxLifecycleHandlers: { min: 0, max: 10_000 },\n} as const;\n\nexport function validateLimitValue(\n limitName: keyof LimitsConfig,\n value: unknown,\n methodName: string,\n): void {\n if (typeof value !== \"number\" || !Number.isInteger(value)) {\n throw new TypeError(\n `[router.${methodName}] limit \"${limitName}\" must be an integer, got ${String(value)}`,\n );\n }\n\n const bounds = LIMIT_BOUNDS[limitName];\n\n if (value < bounds.min || value > bounds.max) {\n throw new RangeError(\n `[router.${methodName}] limit \"${limitName}\" must be between ${bounds.min} and ${bounds.max}, got ${value}`,\n );\n }\n}\n\nexport function validateLimits(\n limits: unknown,\n methodName: string,\n): asserts limits is Partial<LimitsConfig> {\n if (!limits || typeof limits !== \"object\" || limits.constructor !== Object) {\n throw new TypeError(\n `[router.${methodName}] invalid limits: expected plain object, got ${typeof limits}`,\n );\n }\n\n for (const [key, value] of objectEntries(limits)) {\n if (!hasOwn(LIMIT_BOUNDS, key)) {\n throw new TypeError(`[router.${methodName}] unknown limit: \"${key}\"`);\n }\n\n if (value === undefined) {\n continue;\n }\n\n validateLimitValue(key as keyof LimitsConfig, value, methodName);\n }\n\n const { warnListeners, maxListeners } = limits as Partial<LimitsConfig>;\n\n if (\n typeof warnListeners === \"number\" &&\n typeof maxListeners === \"number\" &&\n maxListeners > 0 &&\n warnListeners > maxListeners\n ) {\n throw new RangeError(\n `[router.${methodName}] \"limits.warnListeners\" (${warnListeners}) must not exceed \"limits.maxListeners\" (${maxListeners}) — the warning channel would be unreachable`,\n );\n }\n}\n\nfunction validateStringEnum(\n value: unknown,\n optionName: string,\n validValues: readonly string[],\n methodName: string,\n): void {\n if (value === undefined) {\n return;\n }\n\n if (typeof value !== \"string\" || !validValues.includes(value)) {\n const validList = validValues.map((val) => `\"${val}\"`).join(\", \");\n const display = typeof value === \"string\" ? value : `(${typeof value})`;\n\n throw new TypeError(\n `[router.${methodName}] Invalid \"${optionName}\": \"${display}\". Must be one of: ${validList}`,\n );\n }\n}\n\nfunction validateDefaultRoute(defaultRoute: unknown, methodName: string): void {\n if (defaultRoute === undefined) {\n return;\n }\n\n if (typeof defaultRoute !== \"string\" && typeof defaultRoute !== \"function\") {\n throw new TypeError(\n `[router.${methodName}] Invalid \"defaultRoute\": expected string or function, got ${typeof defaultRoute}`,\n );\n }\n}\n\n/**\n * Shared by `defaultParams` and its query-channel twin `defaultSearch` — one\n * rule, two channels (RFC-4 M2 / #1548). Parameterised by the option name\n * rather than copied, so the two can never drift into accepting different\n * shapes for the same kind of value.\n */\nfunction validateDefaultBag(\n value: unknown,\n optionName: string,\n methodName: string,\n): void {\n if (value === undefined) {\n return;\n }\n\n if (typeof value === \"function\") {\n return;\n }\n\n if (\n !value ||\n typeof value !== \"object\" ||\n Array.isArray(value) ||\n value.constructor !== Object\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"${optionName}\": expected plain object or function, got ${typeof value}`,\n );\n }\n}\n\nfunction validateQueryParamsOptions(\n queryParams: unknown,\n methodName: string,\n): void {\n if (queryParams === undefined) {\n return;\n }\n\n if (\n !queryParams ||\n typeof queryParams !== \"object\" ||\n Array.isArray(queryParams)\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"queryParams\": expected plain object`,\n );\n }\n\n const qp = queryParams as Record<string, unknown>;\n\n for (const [key, value] of objectEntries(qp)) {\n if (!isObjKey(key, VALID_QUERY_PARAMS)) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"queryParams.${key}\": unknown option`,\n );\n }\n\n validateStringEnum(\n value,\n `queryParams.${key}`,\n VALID_QUERY_PARAMS[key],\n methodName,\n );\n }\n}\n\nexport function validateOptions(options: unknown, methodName: string): void {\n if (!options || typeof options !== \"object\" || Array.isArray(options)) {\n throw new TypeError(\n `[router.${methodName}] Invalid options: expected plain object`,\n );\n }\n\n const opts = options as Record<string, unknown>;\n\n for (const key of objectKeys(opts)) {\n if (!KNOWN_OPTIONS.has(key)) {\n throw new TypeError(`[router.${methodName}] Unknown option: \"${key}\"`);\n }\n }\n\n validateDefaultRoute(opts.defaultRoute, methodName);\n validateDefaultBag(opts.defaultParams, \"defaultParams\", methodName);\n validateDefaultBag(opts.defaultSearch, \"defaultSearch\", methodName);\n validateStringEnum(\n opts.trailingSlash,\n \"trailingSlash\",\n VALID_OPTION_VALUES.trailingSlash,\n methodName,\n );\n validateStringEnum(\n opts.queryParamsMode,\n \"queryParamsMode\",\n VALID_OPTION_VALUES.queryParamsMode,\n methodName,\n );\n validateStringEnum(\n opts.urlParamsEncoding,\n \"urlParamsEncoding\",\n VALID_OPTION_VALUES.urlParamsEncoding,\n methodName,\n );\n\n if (\n opts.allowNotFound !== undefined &&\n typeof opts.allowNotFound !== \"boolean\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"allowNotFound\": expected boolean, got ${typeof opts.allowNotFound}`,\n );\n }\n\n if (\n opts.rewritePathOnMatch !== undefined &&\n typeof opts.rewritePathOnMatch !== \"boolean\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"rewritePathOnMatch\": expected boolean, got ${typeof opts.rewritePathOnMatch}`,\n );\n }\n\n if (\n opts.caseSensitive !== undefined &&\n typeof opts.caseSensitive !== \"boolean\"\n ) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"caseSensitive\": expected boolean, got ${typeof opts.caseSensitive}`,\n );\n }\n\n validateQueryParamsOptions(opts.queryParams, methodName);\n\n if (opts.limits !== undefined) {\n validateLimits(opts.limits, methodName);\n }\n}\n","// packages/validation-plugin/src/validators/plugins.ts\n\nimport { computeThresholds } from \"../helpers\";\n\nimport type { RouterLogger } from \"@real-router/core\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst hasOwn = Object.hasOwn;\n\nconst DEFAULT_MAX_PLUGINS = 50;\n\nconst LOGGER_CTX = \"router.usePlugin\";\n\nconst PLUGIN_EVENTS_MAP: Record<string, true> = {\n onStart: true,\n onStop: true,\n onTransitionStart: true,\n onTransitionLeaveApprove: true,\n onTransitionSuccess: true,\n onTransitionError: true,\n onTransitionCancel: true,\n};\n\nexport function validatePluginLimit(\n currentCount: number,\n newCount: number,\n maxPlugins: number = DEFAULT_MAX_PLUGINS,\n): void {\n if (maxPlugins === 0) {\n return;\n }\n\n const totalCount = currentCount + newCount;\n\n if (totalCount > maxPlugins) {\n throw new RangeError(\n `[router.usePlugin] Plugin limit exceeded (${maxPlugins}). ` +\n `Current: ${currentCount}, Attempting to add: ${newCount}. ` +\n `This indicates an architectural problem. Consider consolidating plugins.`,\n );\n }\n}\n\nexport function validateNoDuplicatePlugins(\n factory: unknown,\n factories: unknown[],\n): void {\n if (factories.includes(factory)) {\n throw new Error(\n `[router.usePlugin] Plugin factory already registered. ` +\n `To re-register, first unsubscribe the existing plugin.`,\n );\n }\n}\n\nexport function validateCountThresholds(\n count: number,\n maxPlugins: number,\n logger: RouterLogger,\n): void {\n if (maxPlugins === 0) {\n return;\n }\n\n const { warn, error } = computeThresholds(maxPlugins);\n\n if (count >= error) {\n logger.error(\n LOGGER_CTX,\n `${count} plugins registered! This is excessive. Hard limit at ${maxPlugins}.`,\n );\n } else if (count >= warn) {\n logger.warn(\n LOGGER_CTX,\n `${count} plugins registered. Consider if all are necessary.`,\n );\n }\n}\n\nexport function validatePluginKeys(plugin: unknown): void {\n for (const key in plugin as Record<string, unknown>) {\n if (!(key === \"teardown\" || hasOwn(PLUGIN_EVENTS_MAP, key))) {\n throw new TypeError(\n `[router.usePlugin] Unknown property '${key}'. Plugin must only contain event handlers and optional teardown.`,\n );\n }\n }\n}\n\nexport function warnBatchDuplicates(logger: RouterLogger): void {\n logger.warn(\n LOGGER_CTX,\n \"Duplicate factory in batch, will be registered once\",\n );\n}\n\nexport function warnPluginMethodType(\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(\n LOGGER_CTX,\n `Property '${methodName}' is not a function, skipping`,\n );\n}\n\nexport function warnPluginAfterStart(\n methodName: string,\n logger: RouterLogger,\n): void {\n if (methodName === \"onStart\") {\n logger.warn(\n LOGGER_CTX,\n \"Router already started, onStart will not be called\",\n );\n }\n}\n\nexport function validateAddInterceptorArgs(method: unknown, fn: unknown): void {\n const validMethods = [\"start\", \"buildPath\", \"forwardState\"];\n\n if (typeof method !== \"string\" || !validMethods.includes(method)) {\n throw new TypeError(\n `[router.addInterceptor] Invalid method: \"${String(method)}\". Must be one of: ${validMethods.join(\", \")}`,\n );\n }\n if (typeof fn !== \"function\") {\n throw new TypeError(\n `[router.addInterceptor] interceptor must be a function, got ${typeof fn}`,\n );\n }\n}\n","// packages/validation-plugin/src/validators/retrospective.ts\n\nimport { resolveForwardChain as coreResolveForwardChain } from \"@real-router/core\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectEntries = Object.entries;\nconst getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nconst objectKeys = Object.keys;\n\n/**\n * Retrospective validators — run AFTER the route tree is already built.\n * Called by the validation plugin at usePlugin() time, in a try/catch with rollback.\n *\n * The plugin is registered AFTER the constructor, so all routes are already in the store.\n * These functions receive store objects as parameters and cast internally using\n * local structural interfaces to avoid tight coupling to core internal types.\n *\n * All parameters are typed as `unknown` — cast internally as needed.\n */\n\n// =============================================================================\n// Local structural interfaces (cast-only, not imported from core internals)\n// =============================================================================\n\ninterface LocalSegmentParamMeta {\n urlParams: readonly string[];\n spatParams: readonly string[];\n}\n\ninterface LocalRouteSegment {\n paramMeta: LocalSegmentParamMeta;\n}\n\ninterface LocalRouteTree {\n children: Map<string, LocalRouteTree>;\n paramMeta: LocalSegmentParamMeta;\n}\n\ninterface LocalRouteMatcher {\n getSegmentsByName: (\n name: string,\n ) => readonly LocalRouteSegment[] | null | undefined;\n}\n\ninterface LocalRouteConfig {\n forwardMap: Record<string, string>;\n forwardFnMap: Record<string, unknown>;\n defaultParams: Record<string, unknown>;\n decoders: Record<string, unknown>;\n encoders: Record<string, unknown>;\n}\n\ninterface LocalRouteDefinition {\n name: string;\n path: string;\n children?: LocalRouteDefinition[];\n}\n\ninterface LocalRoutesStore {\n definitions: LocalRouteDefinition[];\n config: LocalRouteConfig;\n tree: LocalRouteTree;\n matcher: LocalRouteMatcher;\n}\n\ninterface LocalDependencyLimits {\n maxDependencies: number;\n maxPlugins: number;\n maxListeners: number;\n warnListeners: number;\n maxLifecycleHandlers: number;\n}\n\n// =============================================================================\n// Private helpers\n// =============================================================================\n\nfunction assertRoutesStore(store: unknown, fnName: string): LocalRoutesStore {\n if (!store || typeof store !== \"object\") {\n throw new TypeError(\n `[validation-plugin] ${fnName}: store must be an object`,\n );\n }\n\n const storeRecord = store as Record<string, unknown>;\n\n if (!Array.isArray(storeRecord.definitions)) {\n throw new TypeError(\n `[validation-plugin] ${fnName}: store.definitions must be an array`,\n );\n }\n\n if (!storeRecord.config || typeof storeRecord.config !== \"object\") {\n throw new TypeError(\n `[validation-plugin] ${fnName}: store.config must be an object`,\n );\n }\n\n if (!storeRecord.tree || typeof storeRecord.tree !== \"object\") {\n throw new TypeError(\n `[validation-plugin] ${fnName}: store.tree must be an object`,\n );\n }\n\n return storeRecord as unknown as LocalRoutesStore;\n}\n\nfunction walkDefinitions(\n definitions: LocalRouteDefinition[],\n callback: (def: LocalRouteDefinition, fullName: string) => void,\n parentName = \"\",\n): void {\n for (const def of definitions) {\n const fullName = parentName ? `${parentName}.${def.name}` : def.name;\n\n callback(def, fullName);\n\n if (def.children) {\n walkDefinitions(def.children, callback, fullName);\n }\n }\n}\n\nfunction routeExistsInTree(tree: LocalRouteTree, routeName: string): boolean {\n const segments = routeName.split(\".\");\n let current: LocalRouteTree | undefined = tree;\n\n for (const segment of segments) {\n current = current.children.get(segment);\n\n if (!current) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Wraps core's resolveForwardChain with [validation-plugin] prefix on errors.\n * Core's version throws plain Error messages; retrospective validation\n * needs the [validation-plugin] prefix for consistency.\n */\nfunction resolveForwardChainWithPrefix(\n startRoute: string,\n forwardMap: Record<string, string>,\n): string {\n try {\n return coreResolveForwardChain(startRoute, forwardMap);\n } catch (error) {\n throw new Error(`[validation-plugin] ${(error as Error).message}`, {\n cause: error,\n });\n }\n}\n\nfunction collectUrlParams(segments: readonly LocalRouteSegment[]): Set<string> {\n const params = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n params.add(param);\n }\n\n for (const param of segment.paramMeta.spatParams) {\n params.add(param);\n }\n }\n\n return params;\n}\n\n/**\n * Asserts that a function value is not async (native or transpiled).\n * Adapted from: assertNotAsync() in RoutesNamespace/validators.ts\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- needs constructor.name access\nfunction assertNotAsync(fn: Function, label: string, routeName: string): void {\n const function_ = fn as {\n constructor: { name: string };\n toString: () => string;\n };\n\n if (\n function_.constructor.name === \"AsyncFunction\" ||\n function_.toString().includes(\"__awaiter\")\n ) {\n throw new TypeError(\n `[validation-plugin] Route \"${routeName}\" ${label} cannot be async`,\n );\n }\n}\n\n// =============================================================================\n// 1. validateExistingRoutes\n// =============================================================================\n\n/**\n * Validates the existing route tree/definitions for structural integrity.\n * Walks all route definitions, checking each name and path shape.\n * Adapted from: validateRoutes() in RoutesNamespace/validators.ts\n *\n * Duplicate route names are intentionally NOT checked here. Bare core rejects a\n * duplicate name on every route-population entry point — `createRouter([...])`\n * initial routes (#1351), `add()` (within-batch #953 + the \"already exists\"\n * guard for cross-batch collisions), and `replace()` (#968) — so a built store\n * can never carry a duplicate for the retrospective pass to catch. Core is the\n * sole authority for the name-uniqueness invariant; mirroring it here was dead\n * code kept alive only by white-box unit tests (#1226).\n *\n * @param store - RoutesStore instance (typed as unknown to avoid core coupling)\n * @throws {TypeError} If store shape is invalid or definitions have structural issues\n */\nexport function validateExistingRoutes(store: unknown): void {\n const routesStore = assertRoutesStore(store, \"validateExistingRoutes\");\n\n walkDefinitions(routesStore.definitions, (def, fullName) => {\n if (typeof def.name !== \"string\" || !def.name) {\n throw new TypeError(\n `[validation-plugin] validateExistingRoutes: route has invalid name: ${def.name}`,\n );\n }\n\n // ⚑ The dotted-name check that #1194 added here is GONE, and its absence is\n // load-bearing rather than a cleanup. It closed a real hole: `add()` and\n // `replace()` rejected a dotted route name while the constructor did not, so\n // `createRouter([{ name: \"a.c\" }])` + this plugin slipped one past validation\n // into a name-vs-URL split-brain. #1763 moved the rule to where it belongs —\n // bare core now refuses the spelling at registration, with this exact\n // message — so `createRouter` throws before a plugin exists and nothing\n // dotted can reach this pass. It was unreachable code, not defence in depth:\n // `store.definitions` is derived from the TREE, whose nested children carry\n // bare names by construction.\n\n if (typeof def.path !== \"string\") {\n throw new TypeError(\n `[validation-plugin] validateExistingRoutes: route \"${fullName}\" has non-string path (${typeof def.path})`,\n );\n }\n });\n}\n\n// =============================================================================\n// 2. validateForwardToConsistency\n// =============================================================================\n\n/**\n * Validates forwardTo consistency across all chains in the store.\n * Checks target existence, param compatibility, and circular chain detection.\n * Adapted from: validateForwardToTargets() in forwardToValidation.ts\n *\n * @param store - RoutesStore instance (typed as unknown to avoid core coupling)\n * @throws {Error} If any forwardTo target does not exist in the tree\n * @throws {Error} If param incompatibility is detected across a forwardTo pair\n * @throws {Error} If a circular forwardTo chain is detected\n */\nexport function validateForwardToConsistency(store: unknown): void {\n const routesStore = assertRoutesStore(store, \"validateForwardToConsistency\");\n const { config, tree, matcher } = routesStore;\n\n // Check target existence and param compatibility for each static mapping\n for (const [fromRoute, targetRoute] of objectEntries(config.forwardMap)) {\n if (!routeExistsInTree(tree, targetRoute)) {\n throw new Error(\n `[validation-plugin] validateForwardToConsistency: forwardTo target \"${targetRoute}\" ` +\n `does not exist in tree (source route: \"${fromRoute}\")`,\n );\n }\n\n // Validate param compatibility: target must not require params absent in source\n const sourceSegments = matcher.getSegmentsByName(fromRoute);\n const targetSegments = matcher.getSegmentsByName(targetRoute);\n\n if (sourceSegments && targetSegments) {\n const sourceParams = collectUrlParams(sourceSegments);\n const targetParams = collectUrlParams(targetSegments);\n const missingParams = [...targetParams].filter(\n (param) => !sourceParams.has(param),\n );\n\n if (missingParams.length > 0) {\n throw new Error(\n `[validation-plugin] validateForwardToConsistency: forwardTo target \"${targetRoute}\" ` +\n `requires params [${missingParams.join(\", \")}] not available in source route \"${fromRoute}\"`,\n );\n }\n }\n }\n\n // Detect cycles in the full forwardMap (catches multi-hop cycles)\n for (const fromRoute of objectKeys(config.forwardMap)) {\n resolveForwardChainWithPrefix(fromRoute, config.forwardMap);\n }\n}\n\n// =============================================================================\n// 3. validateRouteProperties\n// =============================================================================\n\n/**\n * Validates route properties for all registered routes in the store.\n * Checks decoder/encoder types, defaultParams structure, and async forwardTo callbacks.\n * Adapted from: validateRouteProperties() in forwardToValidation.ts\n *\n * @param store - RoutesStore instance (typed as unknown to avoid core coupling)\n * @throws {TypeError} If any registered decoder/encoder is not a valid sync function\n * @throws {TypeError} If any defaultParams is not a plain object\n * @throws {TypeError} If any forwardTo callback is async\n */\nexport function validateRoutePropertiesStore(store: unknown): void {\n const routesStore = assertRoutesStore(store, \"validateRoutePropertiesStore\");\n const { config } = routesStore;\n\n // Validate decoders — must be non-async functions (sync required for matchPath/buildPath)\n for (const [routeName, decoder] of objectEntries(config.decoders)) {\n if (typeof decoder !== \"function\") {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" decoder must be a function, got ${typeof decoder}`,\n );\n }\n\n assertNotAsync(decoder, \"decoder\", routeName);\n }\n\n // Validate encoders — must be non-async functions (sync required for matchPath/buildPath)\n for (const [routeName, encoder] of objectEntries(config.encoders)) {\n if (typeof encoder !== \"function\") {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" encoder must be a function, got ${typeof encoder}`,\n );\n }\n\n assertNotAsync(encoder, \"encoder\", routeName);\n }\n\n // Validate defaultParams — must be plain objects (not null, array, or other types)\n for (const [routeName, params] of objectEntries(config.defaultParams)) {\n if (\n params === null ||\n typeof params !== \"object\" ||\n Array.isArray(params)\n ) {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" defaultParams must be a plain object, got ${Array.isArray(params) ? \"array\" : typeof params}`,\n );\n }\n }\n\n // Validate forwardTo function callbacks — must be non-async functions\n for (const [routeName, callback] of objectEntries(config.forwardFnMap)) {\n if (typeof callback !== \"function\") {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" forwardTo callback must be a function, got ${typeof callback}`,\n );\n }\n\n assertNotAsync(callback, \"forwardTo callback\", routeName);\n }\n}\n\n// =============================================================================\n// 4. validateForwardToTargets\n// =============================================================================\n\n/**\n * Validates that all static forwardTo targets exist in the route tree.\n * This is a focused existence-only check (param compat is in validateForwardToConsistency).\n * Adapted from: validateForwardToTargets() in forwardToValidation.ts\n *\n * @param store - RoutesStore instance (typed as unknown to avoid core coupling)\n * @throws {Error} If any forwardTo target route does not exist in the tree\n */\nexport function validateForwardToTargetsStore(store: unknown): void {\n const routesStore = assertRoutesStore(store, \"validateForwardToTargetsStore\");\n const { config, tree } = routesStore;\n\n for (const [fromRoute, targetRoute] of objectEntries(config.forwardMap)) {\n if (!routeExistsInTree(tree, targetRoute)) {\n throw new Error(\n `[validation-plugin] validateForwardToTargetsStore: forwardTo target \"${targetRoute}\" ` +\n `does not exist for route \"${fromRoute}\"`,\n );\n }\n }\n}\n\n// =============================================================================\n// 5. validateDependenciesStructure\n// =============================================================================\n\n/**\n * Validates the full structure of the dependencies store.\n * Checks that the dependencies object is valid, has no getters, and limits are well-formed.\n * Adapted from: validateDependenciesObject() in DependenciesNamespace/validators.ts\n *\n * @param deps - DependenciesStore instance (typed as unknown to avoid core coupling)\n * @throws {TypeError} If deps is not an object\n * @throws {TypeError} If deps.dependencies is not a valid plain object (or has getters)\n * @throws {TypeError} If deps.limits is missing or has non-numeric limit values\n */\nexport function validateDependenciesStructure(deps: unknown): void {\n if (!deps || typeof deps !== \"object\") {\n throw new TypeError(\n \"[validation-plugin] validateDependenciesStructure: deps must be an object\",\n );\n }\n\n const depsRecord = deps as Record<string, unknown>;\n\n // Validate dependencies field exists and is an object\n if (!depsRecord.dependencies || typeof depsRecord.dependencies !== \"object\") {\n throw new TypeError(\n \"[validation-plugin] validateDependenciesStructure: deps.dependencies must be an object\",\n );\n }\n\n const dependencies = depsRecord.dependencies as Record<string, unknown>;\n\n // Getters can throw, return different values, or have side effects — reject them\n for (const key of objectKeys(dependencies)) {\n if (getOwnPropertyDescriptor(dependencies, key)?.get) {\n throw new TypeError(\n `[validation-plugin] validateDependenciesStructure: dependency \"${key}\" must not use a getter`,\n );\n }\n }\n\n // Validate limits field exists and is an object\n if (!depsRecord.limits || typeof depsRecord.limits !== \"object\") {\n throw new TypeError(\n \"[validation-plugin] validateDependenciesStructure: deps.limits must be an object\",\n );\n }\n\n const limits = depsRecord.limits as Record<string, unknown>;\n const expectedLimitKeys: (keyof LocalDependencyLimits)[] = [\n \"maxDependencies\",\n \"maxPlugins\",\n \"maxListeners\",\n \"warnListeners\",\n \"maxLifecycleHandlers\",\n ];\n\n for (const key of expectedLimitKeys) {\n // ⚑ `Number.isInteger`, not `typeof === \"number\"`. Core coerces the caller's\n // limits ONCE at construction (#1875), so by the time they reach this store\n // they are always `typeof \"number\"` — and `Number(undefined)`,\n // `Number(\"abc\")` and `Number({})` are all `NaN`, which passes a `typeof`\n // test. A `typeof` check therefore stopped diagnosing the exact population\n // it was written for the moment the coercion moved upstream: measured,\n // `{ maxListeners: undefined }` was refused at install before that change\n // and installed clean after it. This also lands the check on the same\n // predicate `validateLimitValue` already uses, so the two mirrors agree.\n if (!Number.isInteger(limits[key])) {\n throw new TypeError(\n `[validation-plugin] validateDependenciesStructure: deps.limits.${key} must be an integer, got ${String(limits[key])}`,\n );\n }\n }\n}\n\n// =============================================================================\n// 6. validateLimitsConsistency\n// =============================================================================\n\n/**\n * Validates that actual resource counts don't exceed configured limits.\n * Compares dependency count vs maxDependencies limit from the deps store.\n * Adapted from: validateLimits() in OptionsNamespace/validators.ts\n *\n * @param options - Router options (typed as unknown to avoid core coupling)\n * @throws {RangeError} If dependency count exceeds maxDependencies limit (#1225:\n * `>` not `>=` — an at-limit store is legal, mirroring the live limiter)\n */\nfunction extractConfiguredLimits(options: unknown): Record<string, unknown> {\n const opts =\n options && typeof options === \"object\"\n ? (options as Record<string, unknown>)\n : {};\n\n return opts.limits && typeof opts.limits === \"object\"\n ? (opts.limits as Record<string, unknown>)\n : {};\n}\n\nfunction checkDepCountLimit(\n deps: unknown,\n configuredLimits: Record<string, unknown>,\n): void {\n if (!deps || typeof deps !== \"object\") {\n return;\n }\n\n const depsRecord = deps as Record<string, unknown>;\n const dependencies = depsRecord.dependencies;\n const depsLimits = depsRecord.limits;\n\n if (\n !dependencies ||\n typeof dependencies !== \"object\" ||\n !depsLimits ||\n typeof depsLimits !== \"object\"\n ) {\n return;\n }\n\n const depCount = objectKeys(dependencies).length;\n const limitsRecord = depsLimits as Record<string, unknown>;\n const maxDepsFromOptions = configuredLimits.maxDependencies;\n const maxDepsFromStore = limitsRecord.maxDependencies;\n const maxDeps =\n typeof maxDepsFromOptions === \"number\"\n ? maxDepsFromOptions\n : maxDepsFromStore;\n\n // `>`, not `>=` (#1225): the live limiter (`validateDependencyCount`) counts\n // BEFORE the insert, so a store may legally REACH exactly maxDependencies. This\n // retrospective pass checks committed STATE (re-run on usePlugin AND every\n // cloneRouter), so it must accept an at-limit store and reject only one that\n // STRICTLY exceeds the limit — else every SSR per-request clone of an at-limit\n // base throws.\n if (typeof maxDeps === \"number\" && maxDeps > 0 && depCount > maxDeps) {\n throw new RangeError(\n `[validation-plugin] validateLimitsConsistency: dependency count (${depCount}) exceeds maxDependencies limit (${maxDeps})`,\n );\n }\n}\n\nexport function validateLimitsConsistency(\n options: unknown,\n deps: unknown,\n): void {\n const configuredLimits = extractConfiguredLimits(options);\n\n checkDepCountLimit(deps, configuredLimits);\n}\n\n// =============================================================================\n// 7. validateResolvedDefaultRoute\n// =============================================================================\n\n/**\n * Validates that a resolved defaultRoute name points to a route that exists\n * in the tree. Called at two places:\n *\n * 1. At plugin registration (retrospective) — with the static string value\n * of options.defaultRoute, if any.\n * 2. At runtime inside resolveDefault() — with the return value of a\n * DefaultRouteCallback, on every navigateToDefault(). (`start()` has no\n * `defaultRoute` fallback — measured, it consults the option zero times.)\n *\n * No-op for empty string (means \"no default configured\" — handled upstream by\n * NavigationNamespace.navigateToDefault).\n */\nexport function validateResolvedDefaultRoute(\n routeName: unknown,\n store: unknown,\n): void {\n if (typeof routeName !== \"string\" || !routeName) {\n return;\n }\n\n const routesStore = assertRoutesStore(store, \"validateResolvedDefaultRoute\");\n\n if (!routeExistsInTree(routesStore.tree, routeName)) {\n throw new Error(\n `[validation-plugin] defaultRoute resolved to non-existent route: \"${routeName}\"`,\n );\n }\n}\n","// packages/validation-plugin/src/validators/forwardTo.ts\n\nimport { resolveForwardChain } from \"@real-router/core\";\nimport { putField } from \"@real-router/core/utils\";\n\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { Route, DefaultDependencies } from \"@real-router/core\";\nimport type { Matcher, RouteTree } from \"@real-router/core/validation\";\n\n/**\n * Intrinsics captured at module load (#1971).\n *\n * ⚑ These DECIDE — each answers \"what is on this object\" for a value this module\n * did not build, so read off the live global they are the weakest point of every\n * check built on them. `guards.ts` states the doctrine and its measurement: one\n * naive `Object.hasOwn` polyfill walked straight through five sibling readers\n * while the single captured guard held.\n *\n * ⚠ Capture narrows the window from \"any time after boot\" to \"before this module\n * loads\". It does not close it — a shim evaluated ahead of core still wins\n * (#1798), which is the doctrine's own caveat and travels with it.\n */\nconst objectKeys = Object.keys;\n\n// ============================================================================\n// Route Property Validation\n// ============================================================================\n\nfunction validateForwardToProperty(forwardTo: unknown, fullName: string): void {\n if (forwardTo === undefined) {\n return;\n }\n\n if (typeof forwardTo === \"function\") {\n const isNativeAsync =\n (forwardTo as { constructor: { name: string } }).constructor.name ===\n \"AsyncFunction\";\n const isTranspiledAsync = forwardTo.toString().includes(\"__awaiter\");\n\n if (isNativeAsync || isTranspiledAsync) {\n throw new TypeError(\n `[router.addRoute] forwardTo callback cannot be async for route \"${fullName}\". ` +\n `Async functions break matchPath/buildPath.`,\n );\n }\n }\n}\n\nexport function validateRouteProperties<\n Dependencies extends DefaultDependencies,\n>(route: Route<Dependencies>, fullName: string): void {\n if (\n route.canActivate !== undefined &&\n typeof route.canActivate !== \"function\"\n ) {\n throw new TypeError(\n `[router.addRoute] canActivate must be a function for route \"${fullName}\", ` +\n `got ${getTypeDescription(route.canActivate)}`,\n );\n }\n\n if (\n route.canDeactivate !== undefined &&\n typeof route.canDeactivate !== \"function\"\n ) {\n throw new TypeError(\n `[router.addRoute] canDeactivate must be a function for route \"${fullName}\", ` +\n `got ${getTypeDescription(route.canDeactivate)}`,\n );\n }\n\n if (route.defaultParams !== undefined) {\n const params: unknown = route.defaultParams;\n\n if (\n params === null ||\n typeof params !== \"object\" ||\n Array.isArray(params)\n ) {\n throw new TypeError(\n `[router.addRoute] defaultParams must be an object for route \"${fullName}\", ` +\n `got ${getTypeDescription(route.defaultParams)}`,\n );\n }\n }\n\n if (route.decodeParams?.constructor.name === \"AsyncFunction\") {\n throw new TypeError(\n `[router.addRoute] decodeParams cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`,\n );\n }\n\n if (route.encodeParams?.constructor.name === \"AsyncFunction\") {\n throw new TypeError(\n `[router.addRoute] encodeParams cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`,\n );\n }\n\n validateForwardToProperty(route.forwardTo, fullName);\n\n if (route.children) {\n for (const child of route.children) {\n const childFullName = `${fullName}.${child.name}`;\n\n validateRouteProperties(child, childFullName);\n }\n }\n}\n\n// ============================================================================\n// ForwardTo Validation\n// ============================================================================\n\nfunction extractParamsFromPath(path: string): Set<string> {\n const params = new Set<string>();\n const paramRegex = /[*:]([A-Z_a-z]\\w*)/g;\n let match;\n\n while ((match = paramRegex.exec(path)) !== null) {\n params.add(match[1]);\n }\n\n return params;\n}\n\nfunction extractParamsFromPaths(paths: readonly string[]): Set<string> {\n const params = new Set<string>();\n\n for (const path of paths) {\n for (const param of extractParamsFromPath(path)) {\n params.add(param);\n }\n }\n\n return params;\n}\n\nfunction collectPathsToRoute<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n routeName: string,\n parentName = \"\",\n paths: string[] = [],\n): string[] {\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n const currentPaths = [...paths, route.path];\n\n if (fullName === routeName) {\n return currentPaths;\n }\n\n if (route.children && routeName.startsWith(`${fullName}.`)) {\n // eslint-disable-next-line unicorn/no-useless-recursion -- intentional route-tree descent; a loop rewrite needs a labeled break and obscures path resolution\n return collectPathsToRoute(\n route.children,\n routeName,\n fullName,\n currentPaths,\n );\n }\n }\n\n /* v8 ignore next -- @preserve unreachable: callers validate existence */\n throw new Error(\n `[internal] collectPathsToRoute: route \"${routeName}\" not found`,\n );\n}\n\nfunction collectRouteNames<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n parentName = \"\",\n): Set<string> {\n const names = new Set<string>();\n\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n names.add(fullName);\n\n if (route.children) {\n for (const childName of collectRouteNames(route.children, fullName)) {\n names.add(childName);\n }\n }\n }\n\n return names;\n}\n\nfunction collectForwardMappings<Dependencies extends DefaultDependencies>(\n routes: readonly Route<Dependencies>[],\n parentName = \"\",\n): Map<string, string> {\n const mappings = new Map<string, string>();\n\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n if (route.forwardTo && typeof route.forwardTo === \"string\") {\n mappings.set(fullName, route.forwardTo);\n }\n\n if (route.children) {\n for (const [key, value] of collectForwardMappings(\n route.children,\n fullName,\n )) {\n mappings.set(key, value);\n }\n }\n }\n\n return mappings;\n}\n\nfunction getRequiredParams(segments: readonly RouteTree[]): Set<string> {\n const params = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n params.add(param);\n }\n\n for (const param of segment.paramMeta.spatParams) {\n params.add(param);\n }\n }\n\n return params;\n}\n\nfunction getTargetParams<Dependencies extends DefaultDependencies>(\n targetRoute: string,\n existsInMatcher: boolean,\n matcher: Matcher,\n routes: readonly Route<Dependencies>[],\n): Set<string> {\n if (existsInMatcher) {\n /* v8 ignore next -- @preserve: ?? fallback unreachable — existsInMatcher guarantees non-null */\n return getRequiredParams(\n (matcher.getSegmentsByName(targetRoute) as\n readonly RouteTree[] | undefined) ?? [],\n );\n }\n\n return extractParamsFromPaths(collectPathsToRoute(routes, targetRoute));\n}\n\nconst EMPTY_PARENT_PARAMS: ReadonlySet<string> = new Set();\n\nfunction validateSingleForward<Dependencies extends DefaultDependencies>(\n fromRoute: string,\n targetRoute: string,\n routes: readonly Route<Dependencies>[],\n batchNames: Set<string>,\n matcher: Matcher,\n parentParams: ReadonlySet<string>,\n): void {\n const existsInMatcher = matcher.hasRoute(targetRoute);\n const existsInBatch = batchNames.has(targetRoute);\n\n if (!existsInMatcher && !existsInBatch) {\n throw new ReferenceError(\n `[router.addRoute] forwardTo target \"${targetRoute}\" does not exist ` +\n `for route \"${fromRoute}\"`,\n );\n }\n\n // A batch route added under { parent } inherits the parent's path params, so\n // the forward source's available params are the parent's plus its own (#1224).\n const fromParams = new Set<string>([\n ...parentParams,\n ...extractParamsFromPaths(collectPathsToRoute(routes, fromRoute)),\n ]);\n\n const toParams = getTargetParams(\n targetRoute,\n existsInMatcher,\n matcher,\n routes,\n );\n\n const missingParams = [...toParams].filter((param) => !fromParams.has(param));\n\n if (missingParams.length > 0) {\n throw new Error(\n `[router.addRoute] forwardTo target \"${targetRoute}\" requires params ` +\n `[${missingParams.join(\", \")}] that are not available in source route \"${fromRoute}\"`,\n );\n }\n}\n\nexport function validateForwardToTargets<\n Dependencies extends DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n existingForwardMap: Record<string, string>,\n matcher: Matcher,\n parentName?: string,\n): void {\n const batchNames = collectRouteNames(routes);\n // Added under { parent }, a batch route's full name is `${parent}.${short}` and\n // it inherits the parent's path params — resolve both from the parent (#1224).\n const batchFullNames = parentName\n ? new Set([...batchNames].map((name) => `${parentName}.${name}`))\n : batchNames;\n // `validateRoutes` throws on a missing parent before forwardTo validation runs\n // (both go through the `add({ parent })` path with tree + matcher present), so\n // the parent's segments are always resolvable here — assert non-null.\n const parentParams: ReadonlySet<string> = parentName\n ? getRequiredParams(\n matcher.getSegmentsByName(parentName) as readonly RouteTree[],\n )\n : EMPTY_PARENT_PARAMS;\n\n const batchForwards = collectForwardMappings(routes);\n\n const combinedForwardMap: Record<string, string> = { ...existingForwardMap };\n\n for (const [from, to] of batchForwards) {\n // A ROUTE NAME as the key (#1852): an ambient accessor under it made\n // `routesApi.add()` throw, so the routes were never registered — the\n // validator becoming the failure.\n putField(combinedForwardMap, from, to);\n }\n\n for (const [fromRoute, targetRoute] of batchForwards) {\n validateSingleForward(\n fromRoute,\n targetRoute,\n routes,\n batchFullNames,\n matcher,\n parentParams,\n );\n }\n\n for (const fromRoute of objectKeys(combinedForwardMap)) {\n resolveForwardChain(fromRoute, combinedForwardMap);\n }\n}\n","// packages/validation-plugin/src/validators/routes.ts\n\n/**\n * DX-only validator functions for RoutesNamespace.\n * Copied from packages/core/src/namespaces/RoutesNamespace/validators.ts\n * (excludes validateRemoveRoute/validateClearRoutes — those are in routeGuards.ts)\n */\n\nimport { resolveForwardChain } from \"@real-router/core\";\nimport { validateRoute } from \"@real-router/core/validation\";\n\nimport {\n isString,\n validateRouteName,\n isParams,\n getTypeDescription,\n} from \"../type-guards\";\nimport { validateForwardToTargets, validateRouteProperties } from \"./forwardTo\";\n\nimport type {\n Route,\n RouteConfigUpdate,\n DefaultDependencies,\n Params,\n} from \"@real-router/core\";\nimport type { Matcher, RouteTree } from \"@real-router/core/validation\";\n\n// Internal constant (matches core's INTERNAL_ROUTE_PREFIX)\nconst INTERNAL_ROUTE_PREFIX = \"@@\";\n\n// Minimal local type — only the forwardMap field used by this file\n// (RouteConfig from core is not exported from @real-router/core public API)\ninterface RouteConfigLike {\n forwardMap: Record<string, string>;\n}\n\n// Local type - matches ForwardToCallback from @real-router/types\n// (@real-router/types is not a direct dependency of this package)\ntype ForwardToCallback<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> = (\n getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K],\n params: Params,\n) => string;\n\nexport function throwIfInternalRoute(name: string, methodName: string): void {\n if (name.startsWith(INTERNAL_ROUTE_PREFIX)) {\n throw new Error(\n `[router.${methodName}] Route name \"${name}\" uses the reserved \"${INTERNAL_ROUTE_PREFIX}\" prefix. Routes with this prefix are internal and cannot be modified through the public API.`,\n );\n }\n}\n\nexport function throwIfInternalRouteInArray(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Route type\n routes: readonly Route<any>[],\n methodName: string,\n): void {\n for (const route of routes) {\n // eslint-disable-next-line unicorn/prefer-continue, @typescript-eslint/no-unnecessary-condition -- runtime safety guard; prefer-continue inversion would split the condition off this single-line disable\n if (route && typeof route === \"object\" && typeof route.name === \"string\") {\n throwIfInternalRoute(route.name, methodName);\n\n if (route.children) {\n throwIfInternalRouteInArray(route.children, methodName);\n }\n }\n }\n}\n\n/**\n * Validates removeRoute arguments.\n */\nexport function validateRemoveRouteArgs(name: unknown): asserts name is string {\n validateRouteName(name, \"removeRoute\");\n}\n\nexport function validateSetRootPathArgs(\n rootPath: unknown,\n): asserts rootPath is string {\n if (typeof rootPath !== \"string\") {\n throw new TypeError(\n `[router.setRootPath] rootPath must be a string, got ${getTypeDescription(rootPath)}`,\n );\n }\n}\n\nfunction isAsyncFunction(fn: unknown): boolean {\n return (\n (fn as { constructor: { name: string } }).constructor.name ===\n \"AsyncFunction\" || String(fn).includes(\"__awaiter\")\n );\n}\n\nexport function guardRouteCallbacks(route: unknown): void {\n const routeObj = route as { canActivate?: unknown; canDeactivate?: unknown };\n\n if (\n routeObj.canActivate !== undefined &&\n typeof routeObj.canActivate !== \"function\"\n ) {\n throw new TypeError(\n `[router.addRoute] canActivate must be a function, got ${getTypeDescription(routeObj.canActivate)}`,\n );\n }\n\n if (\n routeObj.canDeactivate !== undefined &&\n typeof routeObj.canDeactivate !== \"function\"\n ) {\n throw new TypeError(\n `[router.addRoute] canDeactivate must be a function, got ${getTypeDescription(routeObj.canDeactivate)}`,\n );\n }\n}\n\nexport function guardNoAsyncCallbacks(route: unknown): void {\n const routeObj = route as {\n decodeParams?: unknown;\n encodeParams?: unknown;\n forwardTo?: unknown;\n name?: unknown;\n };\n const routeName = routeObj.name;\n\n if (\n routeObj.decodeParams !== undefined &&\n isAsyncFunction(routeObj.decodeParams)\n ) {\n throw new TypeError(\n `[router.addRoute] decodeParams cannot be async for route \"${String(routeName)}\"`,\n );\n }\n\n if (\n routeObj.encodeParams !== undefined &&\n isAsyncFunction(routeObj.encodeParams)\n ) {\n throw new TypeError(\n `[router.addRoute] encodeParams cannot be async for route \"${String(routeName)}\"`,\n );\n }\n\n if (\n typeof routeObj.forwardTo === \"function\" &&\n isAsyncFunction(routeObj.forwardTo)\n ) {\n throw new TypeError(\n `[router.addRoute] forwardTo callback cannot be async for route \"${String(routeName)}\"`,\n );\n }\n}\n\n/**\n * Validates addRoute arguments (route structure and properties).\n * State-dependent validation (duplicates, tree) happens in instance method.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Route type\nexport function validateAddRouteArgs(routes: readonly Route<any>[]): void {\n for (const route of routes) {\n // First check if route is an object (before accessing route.name)\n // Runtime check for invalid types passed via `as any`\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime check\n if (route === null || typeof route !== \"object\" || Array.isArray(route)) {\n throw new TypeError(\n `[router.addRoute] Route must be an object, got ${getTypeDescription(route)}`,\n );\n }\n\n // Validate route properties (canActivate, canDeactivate, defaultParams, async checks)\n // Note: validateRouteProperties handles children recursively\n validateRouteProperties(route, route.name);\n }\n}\n\n/**\n * Validates parent option for addRoute.\n */\nexport function validateParentOption(\n parent: unknown,\n): asserts parent is string {\n if (typeof parent !== \"string\" || parent === \"\") {\n throw new TypeError(\n `[router.addRoute] parent option must be a non-empty string, got ${getTypeDescription(parent)}`,\n );\n }\n\n // Validate parent is a valid route name format (can contain dots — it's a fullName reference)\n validateRouteName(parent, \"addRoute\");\n}\n\n/**\n * Validates isActiveRoute arguments.\n */\nexport function validateIsActiveRouteArgs(\n name: unknown,\n params: unknown,\n strictEquality: unknown,\n ignoreQueryParams: unknown,\n): void {\n // Validate name - non-string throws\n if (!isString(name)) {\n throw new TypeError(\n `[router.isActiveRoute] name must be a string, got ${typeof name}`,\n );\n }\n\n // Validate params if provided\n if (params !== undefined && !isParams(params)) {\n throw new TypeError(\n `[router.isActiveRoute] params must be a plain object, got ${getTypeDescription(params)}`,\n );\n }\n\n // Validate strictEquality if provided\n if (strictEquality !== undefined && typeof strictEquality !== \"boolean\") {\n throw new TypeError(\n `[router.isActiveRoute] strictEquality must be a boolean, got ${typeof strictEquality}`,\n );\n }\n\n // Validate ignoreQueryParams if provided\n if (\n ignoreQueryParams !== undefined &&\n typeof ignoreQueryParams !== \"boolean\"\n ) {\n throw new TypeError(\n `[router.isActiveRoute] ignoreQueryParams must be a boolean, got ${typeof ignoreQueryParams}`,\n );\n }\n}\n\n/**\n * Validates state-builder arguments (navigate / forwardState /\n * buildNavigationState).\n */\nexport function validateStateBuilderArgs(\n routeName: unknown,\n routeParams: unknown,\n methodName: string,\n): void {\n if (!isString(routeName)) {\n throw new TypeError(\n `[router.${methodName}] Invalid routeName: ${getTypeDescription(routeName)}. Expected string.`,\n );\n }\n\n if (!isParams(routeParams)) {\n throw new TypeError(\n `[router.${methodName}] Invalid routeParams: ${getTypeDescription(routeParams)}. Expected plain object.`,\n );\n }\n}\n\n/**\n * Validates updateRoute basic arguments (name and updates object structure).\n * Does NOT read property values to allow caller to cache them first.\n */\nexport function validateUpdateRouteBasicArgs<\n Dependencies extends DefaultDependencies,\n>(\n name: unknown,\n updates: unknown,\n): asserts updates is RouteConfigUpdate<Dependencies> {\n // Validate name\n validateRouteName(name, \"updateRoute\");\n\n if (name === \"\") {\n throw new ReferenceError(\n `[router.updateRoute] Invalid name: empty string. Cannot update root node.`,\n );\n }\n\n // Validate updates is not null\n\n if (updates === null) {\n throw new TypeError(\n `[router.updateRoute] updates must be an object, got null`,\n );\n }\n\n // Validate updates is an object (not array)\n if (typeof updates !== \"object\" || Array.isArray(updates)) {\n throw new TypeError(\n `[router.updateRoute] updates must be an object, got ${getTypeDescription(updates)}`,\n );\n }\n}\n\n/**\n * Asserts that a function is not async (native or transpiled).\n * Checks both constructor name and toString() for __awaiter pattern.\n */\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- needs constructor.name access\nfunction assertNotAsync(value: Function, paramName: string): void {\n if (\n (value as { constructor: { name: string } }).constructor.name ===\n \"AsyncFunction\" ||\n (value as { toString: () => string }).toString().includes(\"__awaiter\")\n ) {\n throw new TypeError(\n `[router.updateRoute] ${paramName} cannot be an async function`,\n );\n }\n}\n\n/**\n * Validates that a value is a non-async function, if provided.\n */\nfunction validateFunctionParam(value: unknown, paramName: string): void {\n if (value === undefined || value === null) {\n return;\n }\n\n if (typeof value !== \"function\") {\n throw new TypeError(\n `[router.updateRoute] ${paramName} must be a function or null, got ${typeof value}`,\n );\n }\n\n assertNotAsync(value, paramName);\n}\n\n/**\n * Validates updateRoute property types using pre-cached values.\n * Called AFTER properties are cached to ensure getters are called only once.\n */\nexport function validateUpdateRoutePropertyTypes(\n forwardTo: unknown,\n defaultParams: unknown,\n decodeParams: unknown,\n encodeParams: unknown,\n): void {\n // Validate forwardTo type (existence check is done by instance method)\n if (forwardTo !== undefined && forwardTo !== null) {\n if (typeof forwardTo !== \"string\" && typeof forwardTo !== \"function\") {\n throw new TypeError(\n `[router.updateRoute] forwardTo must be a string, function, or null, got ${getTypeDescription(forwardTo)}`,\n );\n }\n\n if (typeof forwardTo === \"function\") {\n assertNotAsync(forwardTo, \"forwardTo callback\");\n }\n }\n\n // Validate defaultParams\n if (\n defaultParams !== undefined &&\n defaultParams !== null &&\n (typeof defaultParams !== \"object\" || Array.isArray(defaultParams))\n ) {\n throw new TypeError(\n `[router.updateRoute] defaultParams must be an object or null, got ${getTypeDescription(defaultParams)}`,\n );\n }\n\n validateFunctionParam(decodeParams, \"decodeParams\");\n validateFunctionParam(encodeParams, \"encodeParams\");\n}\n\n/**\n * Validates buildPath arguments.\n */\nexport function validateBuildPathArgs(route: unknown): asserts route is string {\n if (!isString(route) || route === \"\") {\n throw new TypeError(\n `[router.buildPath] route must be a non-empty string, got ${typeof route === \"string\" ? '\"\"' : typeof route}`,\n );\n }\n}\n\n/**\n * Validates matchPath arguments.\n */\nexport function validateMatchPathArgs(path: unknown): asserts path is string {\n if (!isString(path)) {\n throw new TypeError(\n `[router.matchPath] path must be a string, got ${typeof path}`,\n );\n }\n}\n\n/**\n * Validates shouldUpdateNode arguments.\n */\nexport function validateShouldUpdateNodeArgs(\n nodeName: unknown,\n): asserts nodeName is string {\n if (!isString(nodeName)) {\n throw new TypeError(\n `[router.shouldUpdateNode] nodeName must be a string, got ${typeof nodeName}`,\n );\n }\n}\n\n/**\n * Validates routes for addition to the router.\n * Checks parent existence, duplicates, and forwardTo targets/cycles.\n *\n * @param routes - Routes to validate\n * @param tree - Current route tree (optional for initial validation)\n * @param matcher - Current route matcher (segment lookup + existence for forwardTo)\n * @param forwardMap - Current forward map for cycle detection\n * @param parentName - Optional parent route fullName for nesting via addRoute({ parent })\n */\nexport function validateRoutes<Dependencies extends DefaultDependencies>(\n routes: Route<Dependencies>[],\n tree?: RouteTree,\n matcher?: Matcher,\n forwardMap?: Record<string, string>,\n parentName?: string,\n): void {\n // Validate parent route exists in tree\n if (parentName && tree) {\n let node: RouteTree | undefined = tree;\n\n for (const segment of parentName.split(\".\")) {\n node = node.children.get(segment);\n\n if (!node) {\n throw new ReferenceError(\n `[router.addRoute] Parent route \"${parentName}\" does not exist`,\n );\n }\n }\n }\n\n // Tracking sets for duplicate detection\n const seenNames = new Set<string>();\n const seenPathsByParent = new Map<string, Set<string>>();\n\n for (const route of routes) {\n validateRoute(\n route,\n \"addRoute\",\n tree,\n parentName ?? \"\",\n seenNames,\n seenPathsByParent,\n );\n }\n\n if (matcher && forwardMap) {\n validateForwardToTargets(routes, forwardMap, matcher, parentName);\n }\n}\n\n// ============================================================================\n// Instance-level validators (moved from routesCrud.ts)\n// ============================================================================\n\n/**\n * Collects URL params from segments into a Set.\n */\nfunction collectUrlParams(segments: readonly RouteTree[]): Set<string> {\n const params = new Set<string>();\n\n for (const segment of segments) {\n for (const param of segment.paramMeta.urlParams) {\n params.add(param);\n }\n }\n\n return params;\n}\n\n/**\n * Validates that forwardTo target doesn't require params that source doesn't have.\n *\n * @param sourceName - Source route name\n * @param targetName - Target route name\n * @param matcher - Current route matcher\n */\nexport function validateForwardToParamCompatibility(\n sourceName: string,\n targetName: string,\n matcher: Matcher,\n): void {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const sourceSegments = matcher.getSegmentsByName(\n sourceName,\n )! as readonly RouteTree[];\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const targetSegments = matcher.getSegmentsByName(\n targetName,\n )! as readonly RouteTree[];\n\n // Get source URL params as a Set for O(1) lookup\n const sourceParams = collectUrlParams(sourceSegments);\n\n // Build target URL params array (inline — no separate helper needed)\n const targetParams: string[] = [];\n\n for (const segment of targetSegments) {\n for (const param of segment.paramMeta.urlParams) {\n targetParams.push(param);\n }\n }\n\n // Check if target requires params that source doesn't have\n const missingParams = targetParams.filter(\n (param) => !sourceParams.has(param),\n );\n\n if (missingParams.length > 0) {\n throw new Error(\n `[router.addRoute] forwardTo target \"${targetName}\" requires params ` +\n `[${missingParams.join(\", \")}] that are not available in source route \"${sourceName}\"`,\n );\n }\n}\n\n/**\n * Validates that adding forwardTo doesn't create a cycle.\n * Creates a test map with the new entry and uses resolveForwardChain\n * to detect cycles before any mutation happens.\n *\n * @param sourceName - Source route name\n * @param targetName - Target route name\n * @param config - Current route config (forwardMap read-only in this call)\n */\nexport function validateForwardToCycle(\n sourceName: string,\n targetName: string,\n config: RouteConfigLike,\n): void {\n // Create a test map with the new entry to validate BEFORE mutation\n const testMap = {\n ...config.forwardMap,\n [sourceName]: targetName,\n };\n\n // resolveForwardChain will throw if cycle is detected or max depth exceeded\n resolveForwardChain(sourceName, testMap);\n}\n\n/**\n * Validates updateRoute instance-level constraints (route existence, forwardTo).\n *\n * @param name - Route name (already validated by static method)\n * @param forwardTo - Cached forwardTo value\n * @param hasRoute - Function to check route existence\n * @param matcher - Current route matcher\n * @param config - Current route config\n */\nexport function validateUpdateRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n name: string,\n forwardTo: string | ForwardToCallback<Dependencies> | null | undefined,\n hasRoute: (n: string) => boolean,\n matcher: Matcher,\n config: RouteConfigLike,\n): void {\n // Validate route exists\n if (!hasRoute(name)) {\n throw new ReferenceError(\n `[router.updateRoute] route \"${name}\" does not exist`,\n );\n }\n\n // Validate forwardTo target exists and is valid (only for string forwardTo)\n if (\n forwardTo !== undefined &&\n forwardTo !== null &&\n typeof forwardTo === \"string\"\n ) {\n if (!hasRoute(forwardTo)) {\n throw new Error(\n `[router.updateRoute] forwardTo target \"${forwardTo}\" does not exist`,\n );\n }\n\n // Check forwardTo param compatibility\n validateForwardToParamCompatibility(name, forwardTo, matcher);\n\n // Check for cycle detection\n validateForwardToCycle(name, forwardTo, config);\n }\n}\n","// packages/validation-plugin/src/validators/state.ts\n\nimport { isString, isParams, getTypeDescription } from \"../type-guards\";\n\nexport function validateMakeStateArgs(\n name: unknown,\n params: unknown,\n path: unknown,\n): void {\n if (!isString(name)) {\n throw new TypeError(\n `[router.makeState] Invalid name: ${getTypeDescription(name)}. Expected string.`,\n );\n }\n\n if (params !== undefined && !isParams(params)) {\n throw new TypeError(\n `[router.makeState] Invalid params: ${getTypeDescription(params)}. Expected plain object.`,\n );\n }\n\n if (path !== undefined && !isString(path)) {\n throw new TypeError(\n `[router.makeState] Invalid path: ${getTypeDescription(path)}. Expected string.`,\n );\n }\n}\n\n/**\n * The mode gate's opt-in diagnostic (#1575).\n *\n * Core DROPS a query key the active `queryParamsMode` will not print — always-on\n * and silent, so `state.search` can never carry what `state.path` does not show.\n * The drop is correct but invisible, and the two ways to hit it look nothing\n * alike from the outside:\n *\n * - a caller passing a key the route never declared with `?name`;\n * - a `defaultSearch` entry for such a key, which is simply DEAD CONFIG under\n * `default` / `strict` — nothing the caller wrote is wrong, the route config\n * just cannot take effect.\n *\n * One message covers both: core cannot tell them apart at the drop, and a guess\n * would be worse than the plain fact. Warn rather than throw — the mode is a\n * serialization option, not an error policy, and turning it into one would give\n * three behaviours for a single rule (RFC §4.7).\n *\n * De-duplicated per `route + key`: the gate runs on every navigation and every\n * `matchPath`, so an un-deduped warning would flood a dev console the moment a\n * route is revisited.\n *\n * ⚠ **The cache belongs to the ROUTER, not to the module (#1583).** A `Set` at\n * module scope is one per PROCESS: the first router to report a `route + key`\n * silenced every router after it, so under SSR / SSG the diagnostic fired for\n * request #1 and stayed quiet for the life of the process — precisely backwards\n * for a dev-time signal. It also survived `teardown()`, so re-registering the\n * plugin bought silence, and it grew without bound because nothing evicted.\n *\n * A factory rather than a `WeakMap<Router, Set>`: `buildValidatorObject` already\n * runs once per registration, so closing over a fresh `Set` there gives\n * per-router lifetime, per-router isolation and collection with the router at\n * zero new module state — and it retires the two `reset*` test seams, which\n * existed only to work around the module scope.\n */\nexport function createDroppedQueryKeyReporter(): (\n routeName: string,\n key: string,\n) => void {\n const reported = new Set<string>();\n\n return function reportDroppedQueryKey(routeName: string, key: string): void {\n const seen = `${routeName} ${key}`;\n\n if (reported.has(seen)) {\n return;\n }\n\n reported.add(seen);\n\n console.warn(\n `[router] Query key \"${key}\" is not declared on route \"${routeName}\" (\\`?${key}\\`), so the ` +\n `current \\`queryParamsMode\\` will not print it — it was dropped from \\`state.search\\` rather ` +\n `than published beside a \\`state.path\\` that cannot show it. Declare it on the route path, ` +\n `or use \\`queryParamsMode: \"loose\"\\` to keep undeclared keys. A \\`defaultSearch\\` entry for ` +\n `\"${key}\" is dead config in this mode for the same reason.`,\n );\n };\n}\n\n/**\n * The undeclared-params-bag diagnostic (#1579 — the params half of #1553).\n *\n * A key the route declares NOWHERE — neither as a path slot nor with `?` — has\n * no channel to own it, so core keeps it in `state.params` as app-level data\n * (documented: wiki `Route.md`). Correct, but it means the state does not\n * round-trip through its own `state.path`: reopen the same link and the key is\n * gone.\n *\n * Core's behaviour is deliberately UNCHANGED — dropping the key was measured\n * and rejected. It retires a shipped capability (52 tests across 6 packages plus\n * the wiki), and the \"declared nowhere\" predicate cannot separate that case from\n * a legitimate one: `navigate(\"users\", { id })` on a PARENT route whose child\n * declares `:id` is indistinguishable from a typo. A diagnostic can afford that\n * ambiguity — it says what happened and lets the developer judge; a gate cannot.\n *\n * De-duplicated per `route + key`, like the mode gate's: this runs on every\n * navigation, so an un-deduped warning floods the console on a revisit — and\n * per ROUTER, for the same reasons and by the same factory shape (#1583).\n */\nexport function createUndeclaredParamKeyReporter(): (\n routeName: string,\n key: string,\n) => void {\n const undeclaredReported = new Set<string>();\n\n return function reportUndeclaredParamKey(\n routeName: string,\n key: string,\n ): void {\n const seen = `${routeName} ${key}`;\n\n if (undeclaredReported.has(seen)) {\n return;\n }\n\n undeclaredReported.add(seen);\n\n console.warn(\n `[router] Param \"${key}\" is declared nowhere on route \"${routeName}\" — neither as a path ` +\n `slot (:${key}) nor as a query param (?${key}), so it stays in state.params but never ` +\n `reaches the URL. The state will not round-trip: matchPath(state.path) cannot recover ` +\n `it. Declare it on the route path if it belongs in the URL, or pass it through the ` +\n `search channel; keep it here only if it is deliberately app-level data.`,\n );\n };\n}\n","// packages/validation-plugin/src/validationPlugin.ts\n\nimport { RouterError } from \"@real-router/core\";\nimport { getInternals } from \"@real-router/core/validation\";\n\nimport {\n validateRouteName,\n isState,\n isBoolean,\n getTypeDescription,\n} from \"./type-guards\";\nimport {\n validateDependencyName,\n validateSetDependencyArgs as validateSetDependencyArgsRaw,\n validateDependenciesObject,\n validateDependencyExists as validateDependencyExistsRaw,\n validateDependencyCount,\n validateCloneArgs,\n warnOverwrite as warnDepsOverwrite,\n warnBatchOverwrite,\n warnRemoveNonExistent,\n} from \"./validators/dependencies\";\nimport {\n validateListenerArgs,\n validateListenerCountThresholds,\n} from \"./validators/eventBus\";\nimport {\n validateHandler,\n validateHandlerLimit,\n validateLifecycleCountThresholds,\n warnOverwrite as warnLifecycleOverwrite,\n warnAsyncGuardSync,\n} from \"./validators/lifecycle\";\nimport {\n validateNavigateArgs,\n validateNavigateToDefaultArgs,\n validateNavigateToStateArgs,\n validateNavigationOptions,\n validateNavigateParams,\n validateStartArgs,\n} from \"./validators/navigation\";\nimport { validateOptions } from \"./validators/options\";\nimport {\n validatePluginLimit,\n validateNoDuplicatePlugins,\n validatePluginKeys,\n validateCountThresholds as validatePluginCountThresholds,\n warnBatchDuplicates,\n warnPluginMethodType,\n warnPluginAfterStart,\n validateAddInterceptorArgs,\n} from \"./validators/plugins\";\nimport {\n validateExistingRoutes,\n validateForwardToConsistency,\n validateRoutePropertiesStore,\n validateForwardToTargetsStore,\n validateDependenciesStructure,\n validateLimitsConsistency,\n validateResolvedDefaultRoute,\n} from \"./validators/retrospective\";\nimport {\n validateBuildPathArgs,\n validateMatchPathArgs,\n validateIsActiveRouteArgs,\n validateShouldUpdateNodeArgs,\n validateStateBuilderArgs,\n validateAddRouteArgs,\n validateRoutes,\n validateRemoveRouteArgs,\n validateUpdateRouteBasicArgs,\n validateUpdateRoutePropertyTypes,\n validateUpdateRoute,\n validateParentOption as validateParentOptionRaw,\n throwIfInternalRoute,\n throwIfInternalRouteInArray,\n validateSetRootPathArgs,\n guardRouteCallbacks,\n guardNoAsyncCallbacks,\n} from \"./validators/routes\";\nimport {\n createDroppedQueryKeyReporter,\n createUndeclaredParamKeyReporter,\n validateMakeStateArgs,\n} from \"./validators/state\";\n\nimport type { EventName, EventMethodMap } from \"./validators/eventBus\";\nimport type {\n DefaultDependencies,\n PluginFactory,\n RouterValidator,\n Route,\n RouteTree,\n Plugin,\n} from \"@real-router/core\";\nimport type { RouterInternals, Matcher } from \"@real-router/core/validation\";\n\nfunction buildValidatorObject<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(ctx: RouterInternals<Dependencies>): RouterValidator {\n return {\n routes: {\n validateBuildPathArgs,\n validateMatchPathArgs,\n validateIsActiveRouteArgs,\n validateShouldUpdateNodeArgs,\n validateStateBuilderArgs,\n\n validateAddRouteArgs(routes) {\n validateAddRouteArgs(routes as readonly Route[]);\n },\n\n validateRoutes(routes, store, parentName) {\n const typedStore = store as {\n tree?: unknown;\n matcher?: unknown;\n config?: { forwardMap?: Record<string, string> };\n };\n\n validateRoutes(\n routes as Route[],\n typedStore.tree as RouteTree | undefined,\n typedStore.matcher as Matcher | undefined,\n typedStore.config?.forwardMap,\n parentName,\n );\n },\n validateRemoveRouteArgs,\n validateUpdateRouteBasicArgs,\n validateUpdateRoutePropertyTypes(_name, updates) {\n const upd = updates as Record<string, unknown>;\n\n validateUpdateRoutePropertyTypes(\n upd.forwardTo,\n upd.defaultParams,\n upd.decodeParams,\n upd.encodeParams,\n );\n },\n validateUpdateRoute(name, updates, store) {\n const typedStore = store as {\n matcher: {\n hasRoute: (routeName: string) => boolean;\n getSegmentsByName: (routeName: string) => unknown;\n };\n config: { forwardMap: Record<string, string> };\n };\n const forwardTo = (updates as Record<string, unknown>).forwardTo;\n\n validateUpdateRoute(\n name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\n forwardTo as any,\n (routeName: string) => typedStore.matcher.hasRoute(routeName),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\n typedStore.matcher as any,\n typedStore.config,\n );\n },\n validateParentOption(parent, tree) {\n validateParentOptionRaw(parent);\n let node = tree as { children: Map<string, unknown> };\n\n for (const segment of parent.split(\".\")) {\n const child = node.children.get(segment) as\n { children: Map<string, unknown> } | undefined;\n\n if (!child) {\n throw new ReferenceError(\n `[router.addRoute] Parent route \"${parent}\" does not exist`,\n );\n }\n\n node = child;\n }\n },\n validateRouteName(name, caller) {\n validateRouteName(name, caller);\n },\n throwIfInternalRoute(name, caller) {\n throwIfInternalRoute(name as string, caller);\n },\n\n throwIfInternalRouteInArray(routes, caller) {\n throwIfInternalRouteInArray(routes as readonly Route[], caller);\n },\n validateSetRootPathArgs,\n guardRouteCallbacks,\n guardNoAsyncCallbacks,\n },\n options: {\n validateOptions,\n validateResolvedDefaultRoute,\n },\n dependencies: {\n validateDependencyName,\n validateSetDependencyArgs(_name, _value, _caller) {\n validateSetDependencyArgsRaw(_name);\n },\n validateDependenciesObject,\n validateDependencyExists(name, store) {\n const typedStore = store as { dependencies?: Record<string, unknown> };\n const value = typedStore.dependencies?.[name];\n\n validateDependencyExistsRaw(value, name);\n },\n validateDependencyCount(store, methodName) {\n validateDependencyCount(store, methodName, ctx.logger);\n },\n validateCloneArgs,\n warnOverwrite(name, methodName) {\n warnDepsOverwrite(name, methodName, ctx.logger);\n },\n warnBatchOverwrite(keys, methodName) {\n warnBatchOverwrite(keys, methodName, ctx.logger);\n },\n warnRemoveNonExistent(name) {\n warnRemoveNonExistent(name, ctx.logger);\n },\n },\n plugins: {\n validatePluginLimit(count, limits) {\n validatePluginLimit(\n count,\n 1,\n (limits as { maxPlugins?: number } | undefined)?.maxPlugins,\n );\n },\n validateNoDuplicatePlugins,\n validatePluginKeys,\n validateCountThresholds(count) {\n const maxPlugins = ctx.getOptions().limits?.maxPlugins ?? 50;\n\n validatePluginCountThresholds(count, maxPlugins, ctx.logger);\n },\n warnBatchDuplicates() {\n warnBatchDuplicates(ctx.logger);\n },\n warnPluginMethodType(methodName) {\n warnPluginMethodType(methodName, ctx.logger);\n },\n warnPluginAfterStart(methodName) {\n warnPluginAfterStart(methodName, ctx.logger);\n },\n validateAddInterceptorArgs,\n },\n lifecycle: {\n validateHandler,\n validateHandlerLimit(count, methodName) {\n const maxHandlers =\n ctx.getOptions().limits?.maxLifecycleHandlers ?? 200;\n\n validateHandlerLimit(count, methodName, maxHandlers);\n },\n validateCountThresholds(count, methodName) {\n const maxHandlers =\n ctx.getOptions().limits?.maxLifecycleHandlers ?? 200;\n\n validateLifecycleCountThresholds(\n count,\n methodName,\n maxHandlers,\n ctx.logger,\n );\n },\n warnOverwrite(name, type, methodName) {\n warnLifecycleOverwrite(name, type, methodName, ctx.logger);\n },\n warnAsyncGuardSync(name, methodName) {\n warnAsyncGuardSync(name, methodName, ctx.logger);\n },\n },\n navigation: {\n validateNavigateArgs,\n validateNavigateToDefaultArgs,\n validateNavigateToStateArgs,\n validateNavigationOptions,\n validateParams: validateNavigateParams,\n validateStartArgs,\n },\n state: {\n validateMakeStateArgs,\n // One de-dup cache per validator object, i.e. per registration, i.e. per\n // router (#1583). Module-level `Set`s made the first router in a process\n // silence every one after it — the SSR / SSG case where the diagnostic\n // fired for request #1 and never again.\n reportDroppedQueryKey: createDroppedQueryKeyReporter(),\n reportUndeclaredParamKey: createUndeclaredParamKeyReporter(),\n validateAreStatesEqualArgs(s1, s2, ignoreQP) {\n if (!isState(s1)) {\n throw new TypeError(\n `[router.areStatesEqual] Invalid state1: ${getTypeDescription(s1)}. Expected State object.`,\n );\n }\n if (!isState(s2)) {\n throw new TypeError(\n `[router.areStatesEqual] Invalid state2: ${getTypeDescription(s2)}. Expected State object.`,\n );\n }\n if (ignoreQP !== undefined && !isBoolean(ignoreQP)) {\n throw new TypeError(\n `[router.areStatesEqual] Invalid ignoreQueryParams: ${getTypeDescription(ignoreQP)}. Expected boolean.`,\n );\n }\n },\n },\n eventBus: {\n validateListenerArgs(name, cb) {\n validateListenerArgs<EventName>(\n name as EventName,\n cb as Plugin[EventMethodMap[EventName]],\n );\n },\n validateCountThresholds(count, eventName, methodName) {\n const maxListeners = ctx.getOptions().limits?.maxListeners ?? 10_000;\n\n validateListenerCountThresholds(\n count,\n eventName,\n methodName,\n ctx.logger,\n maxListeners,\n );\n },\n },\n };\n}\n\n/**\n * The dependency map is a type parameter so the factory carries the CALLER's\n * `D` rather than the `object` default (#1621). `keyof object` is `never`, so a\n * bare `PluginFactory` types `getDependency` as `(key: never) => never`, which\n * TypeScript 7 refuses to assign where `PluginFactory<D>` is expected for any\n * `D` with an index signature — `usePlugin(validationPlugin())` stops compiling\n * for a consumer whose dependencies are `Record<string, T>`. TS 6 skipped that\n * variance check, so the imprecision was always there and simply invisible.\n *\n * `PluginFactory<never>` does NOT work as a shorthand here (unlike\n * `AnyOptions = Options<never>` in core): it fails on both compilers, because\n * this plugin actually reads dependencies.\n */\nexport function validationPlugin<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(): PluginFactory<Dependencies> {\n // eslint-disable-next-line unicorn/consistent-function-scoping\n return (router) => {\n const ctx = getInternals(router);\n\n if (router.isActive()) {\n throw new RouterError(\"VALIDATION_PLUGIN_AFTER_START\", {\n message: \"validation-plugin must be registered before router.start()\",\n });\n }\n\n // RouterInternals.validator is now mutable — direct assignment works\n ctx.validator = buildValidatorObject(ctx);\n\n try {\n const store = ctx.routeGetStore();\n const deps = ctx.dependenciesGetStore();\n const options = ctx.getOptions();\n\n validateExistingRoutes(store);\n validateForwardToConsistency(store);\n validateRoutePropertiesStore(store);\n validateForwardToTargetsStore(store);\n validateDependenciesStructure(deps);\n validateLimitsConsistency(options, deps);\n ctx.validator.options.validateOptions(\n options,\n \"constructor (retrospective)\",\n );\n\n if (typeof options.defaultRoute === \"string\") {\n validateResolvedDefaultRoute(options.defaultRoute, store);\n }\n } catch (error) {\n ctx.validator = null;\n\n throw error;\n }\n\n return {\n teardown() {\n ctx.validator = null;\n },\n };\n };\n}\n"],"mappings":"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"}