@real-router/validation-plugin 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["getPrototypeOf","objectValues","hasOwn","obj","objectEntries","LOGGER_CTX","objectKeys","getOwnPropertyDescriptor","getPrototypeOf","isPlainBag","warnOverwrite","hasOwn","getPrototypeOf","objectEntries","hasOwn","objectKeys","objectKeys","coreResolveForwardChain","collectUrlParams","assertNotAsync","typedStore"],"sources":["../../src/helpers.ts","../../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/validators/defaultsMutation.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/helpers.ts\n\nimport type { LimitsConfig } from \"@real-router/core\";\n\n/**\n * Core's `DEFAULT_LIMITS`, mirrored — the plugin's ONE copy (#1879).\n *\n * ⚑ A copy on purpose: core keeps the table internal, and exporting it to remove\n * the duplication would make the numbers part of its published contract (owner\n * decision, #1879). What keeps the copy honest is\n * `limit-defaults-authority-1879.test.ts`, which reads what core actually\n * ENFORCES rather than what any source file says.\n *\n * ⚑ `Readonly<LimitsConfig>` is core's own interface, so a sixth limit added\n * there fails this object with TS2741 before a test runs — the same thing that\n * catches a limit `createLimits` forgets.\n */\nexport const CORE_LIMIT_DEFAULTS: Readonly<LimitsConfig> = Object.freeze({\n maxDependencies: 100,\n maxPlugins: 50,\n maxListeners: 10_000,\n warnListeners: 1000,\n maxLifecycleHandlers: 200,\n});\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/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 core still wins\n * (#1798), which is the doctrine's own caveat and travels with 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/**\n * The boolean fields of core's `NavigationOptions` — every key except `signal`,\n * which is an `AbortSignal` and is checked separately below.\n *\n * ⚠ **NOT keyed `Record<keyof NavigationOptions, true>`, and the reason is\n * measured (#2311).** That interface is OPEN: `browser-plugin`, `hash-plugin` and\n * `navigation-plugin` augment it, so `keyof` answers differently depending on\n * which plugins the compilation unit can see — seven keys from inside this\n * package, ten from inside `@real-router/react`. An exhaustive `Record` is\n * therefore ill-defined: it compiles here and fails in every consumer that\n * installs a URL plugin. Measured — it did, on `@real-router/react#type-check`.\n * The sibling tables in `validators/options.ts` ARE keyed that way because\n * `Options`, `QueryParamsOptions` and `LoggerConfig` are augmented by nobody.\n *\n * ⚑ Exhaustiveness against CORE is held instead by\n * `core-union-mirror-authority-2091`, which walks core's declaration in the\n * SOURCE FILE. Augmentations are invisible to a file walk, so it asks exactly the\n * question the type cannot: does this list carry every field core itself\n * declares? `satisfies` below adds the other direction — a name that is not a\n * navigation option at all fails to compile.\n *\n * ⚠ A plugin's own augmented booleans are deliberately unchecked. This package\n * cannot enumerate them, and claiming otherwise is what the open interface makes\n * impossible rather than merely hard.\n */\nconst NAVIGATION_OPTIONS_FIELDS = [\n \"replace\",\n \"reload\",\n \"force\",\n \"forceDeactivate\",\n \"redirected\",\n \"revalidate\",\n] as const satisfies readonly Exclude<keyof NavigationOptions, \"signal\">[];\n\n/**\n * Type guard for NavigationOptions.\n * Validates all optional boolean fields, derived from core's type (#2311).\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","import type { RouterLogger } from \"@real-router/core/types\";\n\n/**\n * Captured at module load: `entries`.\n *\n * ⚑ A comparison is only as honest as the intrinsic it reads WHEN IT RUNS, and\n * an application can re-point `Object.entries` after boot. This one decides\n * whether a bag MOVED, so a re-pointed reader could answer \"unchanged\" for a bag\n * that changed — silencing the very report this module exists to make.\n *\n * ⚠ It does NOT close a shim evaluated BEFORE this module, the ordinary polyfill\n * order. Same limit `OptionsNamespace` states for its own capture.\n */\nconst objectEntries = Object.entries;\n\nconst LOGGER_CTX = \"router.navigateToDefault\";\n\n/** The two slots whose late mutation changes nothing and says nothing (#2148). */\nconst WATCHED = [\"defaultParams\", \"defaultSearch\"] as const;\n\ntype Slot = (typeof WATCHED)[number];\n\n/**\n * A snapshot of one bag AS THE APPLICATION HANDED IT.\n *\n * ⚠ The baseline is this, never core's adopted copy. That copy is normalised —\n * `dropUnsafeKey` takes an own `__proto__` out of it on the way in (#1957) — so\n * comparing against it reports a bag nobody touched. Measured: a caller bag\n * spelling `id,__proto__,extra` becomes `id,extra` in core, which a naive\n * comparison reads as two mutations before the application has done anything.\n */\ntype Snapshot = ReadonlyMap<string, unknown>;\n\nconst snapshot = (bag: object): Snapshot =>\n new Map(objectEntries(bag as Record<string, unknown>));\n\n/**\n * Did this bag move since the snapshot?\n *\n * ⚠ One level, matching adoption's own depth. A deeper walk would report a\n * change core never had a chance to take either way, and this message is about\n * what the router DID read — the level it copied.\n */\nconst moved = (bag: object, before: Snapshot): boolean => {\n const now = objectEntries(bag as Record<string, unknown>);\n\n if (now.length !== before.size) {\n return true;\n }\n\n return now.some(\n ([key, value]) => !before.has(key) || !Object.is(before.get(key), value),\n );\n};\n\n/**\n * Reports, once, that a defaults bag was mutated after `createRouter()`.\n *\n * ⚑ **The failure this exists for is silence.** Since #2171 core copies these\n * bags at construction, so an application that mutates one afterwards keeps\n * running and simply stops having any effect — no throw, no warning, no type\n * error. Core cannot speak here: it is the layer that degrades, and this is the\n * layer that reports (`packages/core/CLAUDE.md` › Supported Input Shapes).\n *\n * ⚠ The replacement it names is not a workaround. `defaultParams` and\n * `defaultSearch` already accept a CALLBACK, resolved at the point of use, so an\n * application that wants a live value has a supported mechanism rather than a\n * compromise.\n *\n * ⚠ ONCE per slot, and the flag is why. This runs on every\n * `navigateToDefault`, and a report that repeated would turn a real diagnostic\n * into the noise an application learns to filter.\n */\nexport class DefaultsMutationWatch {\n /**\n * ⚠ ONE map, not a pair keyed alike. Two maps make `before` look optional at\n * every read — a branch that cannot be taken, because both are written in the\n * same iteration, and an untakeable branch is coverage nobody can earn.\n */\n readonly #watched = new Map<\n Slot,\n { readonly ref: WeakRef<object>; readonly before: Snapshot }\n >();\n readonly #reported = new Set<Slot>();\n\n /**\n * ⚠ Takes the snapshot AT INSTALL, which is the earliest this layer exists.\n * A mutation between `createRouter()` and `usePlugin()` is therefore invisible\n * — and correctly so: it happened while the plugin was not there to be asked,\n * and reporting it would be a claim about a window this code never saw.\n */\n watch(origins: Readonly<Partial<Record<Slot, WeakRef<object>>>>): void {\n for (const slot of WATCHED) {\n const ref = origins[slot];\n const bag = ref?.deref();\n\n if (ref === undefined || bag === undefined) {\n continue;\n }\n\n this.#watched.set(slot, { ref, before: snapshot(bag) });\n }\n }\n\n check(logger: RouterLogger): void {\n for (const [slot, { ref, before }] of this.#watched) {\n if (this.#reported.has(slot)) {\n continue;\n }\n\n const bag = ref.deref();\n\n // ⚠ A collected bag is the RIGHT silence, not a missed report: the\n // application no longer holds it, so there is no mutation left to make.\n if (bag === undefined) {\n continue;\n }\n\n if (!moved(bag, before)) {\n continue;\n }\n\n this.#reported.add(slot);\n logger.warn(\n LOGGER_CTX,\n `mutating \\`${slot}\\` after createRouter() no longer affects routing — ` +\n `the router copied it at construction. Use the callback form ` +\n `(\\`${slot}: () => ({ … })\\`), which is resolved at the point of use.`,\n );\n }\n }\n}\n","// packages/validation-plugin/src/validators/dependencies.ts\n\nimport { computeThresholds, CORE_LIMIT_DEFAULTS } from \"../helpers\";\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { RouterLogger } from \"@real-router/core\";\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 core's `guardDependencyShape` (`core/src/guards.ts`), and it\n * has to 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 // ⚑ Mirrors core's `Array.isArray` term (#2282), and has to: the read below\n // goes through a `Proxy`'s `getPrototypeOf` trap, so without it this copy\n // ACCEPTS an array that core refuses — which is the mirror parting, in the\n // direction the contract above forbids least loudly and a consumer notices\n // last.\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\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\n/**\n * The batch form of the limit, run BEFORE the ingest pass (#2253).\n *\n * ⚑ {@link validateDependencyCount} asks about the store as it stands, and the\n * ingest loop calls it once per NEW key — so the key that trips the limit\n * arrives after its predecessors are already stored, and a caught `RangeError`\n * describes a call that moved the store. This one answers the same question for\n * the whole batch, from the pre-flight position, so the refusal is atomic.\n *\n * ⚠ Only keys the store does not already hold count: an overwrite leaves the\n * total unchanged, which is the rule the ingest loop follows by checking on its\n * new-key branch alone. A batch of pure overwrites is therefore legal at the\n * limit — `held + 0 > max` is false there, so nothing short-circuits on\n * `added === 0`. Such a short-circuit would be equivalent rather than\n * protective: a store ALREADY over the limit never reaches this function,\n * because `validateLimitsConsistency` refuses the registration that would\n * install it.\n *\n * ⚠ It does NOT carry the advisory thresholds. Those fire as the store grows and\n * belong to the per-key call the loop keeps making; a projection cannot say\n * which of them a batch would cross without replaying it.\n *\n * The shape is `RouteLifecycleNamespace.preflightHandlerLimit`'s, which moved the\n * handler limit out of a post-commit tear for the same reason.\n */\nexport function validateDependencyBatchLimit(\n deps: Record<string, unknown>,\n store: unknown,\n methodName: string,\n): void {\n const typedStore = store as {\n dependencies: Record<string, unknown>;\n limits?: { maxDependencies?: number };\n };\n const maxDependencies =\n typedStore.limits?.maxDependencies ?? CORE_LIMIT_DEFAULTS.maxDependencies;\n\n if (maxDependencies === 0) {\n return;\n }\n\n // ⚑ ONE question about the store's own keys, asked once (#1815 / #2064). The\n // size and the membership test come from the SAME list: `Object.keys` is own\n // AND enumerable while `hasOwn` is own only, so two calls would disagree on\n // exactly the keys the count refuses to see. `store.dependencies` is a handout\n // a plugin can replace (core INVARIANTS 13b), so the receiver is not\n // guaranteed to answer `ownKeys` and `getOwnPropertyDescriptor` alike.\n const heldKeys = objectKeys(typedStore.dependencies);\n const held = new Set(heldKeys);\n let added = 0;\n\n for (const key of objectKeys(deps)) {\n if (!held.has(key)) {\n added += 1;\n }\n }\n\n const currentCount = heldKeys.length;\n\n if (currentCount + added > maxDependencies) {\n throw new RangeError(\n `[router.${methodName}] Dependency limit exceeded (${maxDependencies}). ` +\n `Current: ${currentCount}, this batch adds ${added}.`,\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 ?? CORE_LIMIT_DEFAULTS.maxDependencies;\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 { events } from \"@real-router/core\";\n\nimport { computeThresholds, CORE_LIMIT_DEFAULTS } from \"../helpers\";\n\nimport type {\n EventMethodMap,\n EventName,\n Plugin,\n RouterLogger,\n} from \"@real-router/core\";\n\n// Derived from the constant core declares them in (#1888) — core enforces\n// membership at `addEventListener` itself, so a hand-written second list here\n// would be a copy that can drift from the emitter it describes.\nconst objectValues = Object.values;\n\nconst validEventNames = new Set<EventName>(\n objectValues(events) as readonly EventName[],\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 = CORE_LIMIT_DEFAULTS.maxListeners,\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\n// Re-exported so the rest of the plugin keeps naming them here (#1879). They are\n// core's: a hand-written copy accepts the event set core had when it was typed,\n// which is the type-level half of the drift #1888 closed at runtime.\nexport { type EventMethodMap, type EventName } from \"@real-router/core\";\n","// packages/validation-plugin/src/validators/lifecycle.ts\n\nimport { computeThresholds, CORE_LIMIT_DEFAULTS } from \"../helpers\";\nimport { isBoolean, getTypeDescription } from \"../type-guards\";\n\nimport type {\n GuardFnFactory,\n DefaultDependencies,\n RouterLogger,\n} from \"@real-router/core\";\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 = CORE_LIMIT_DEFAULTS.maxLifecycleHandlers,\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;\nconst getPrototypeOf = Object.getPrototypeOf;\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\n/**\n * The path bag's SHAPE, judged on the object the CALLER still owns (#2134).\n *\n * ⚑ **Split from `validateNavigateParams` because the two halves belong to two\n * different objects.** The shape has to be judged before core copies, because a\n * copy of anything is a plain object: core's copy is a spread, so `\"abc\"` arrives\n * as `{0:\"a\",1:\"b\",2:\"c\"}` and a class instance arrives without its prototype.\n * Judged after the copy, every shape this function exists to refuse would be\n * LAUNDERED into an acceptable one.\n *\n * ⚑ **The values belong to the copy, and that is the defect this split closes.**\n * Judged on the caller's bag, they are a different read from the one core ships —\n * a key answering `v1` then `v2` is admitted on one and printed on the other, and\n * `judged-equals-shipped-2134` holds the three producer doors to the bare-core\n * answer; the plugin-API doors that take a bag are not in that table.\n *\n * ⚠ **Reads no value, and that is a contract rather than an optimisation.**\n * Every read of a caller-owned bag is a call into application code. This half\n * runs before core has read anything, so it must not run the application's\n * getters — `validateNavigateParams` walks the values afterwards, on core's own\n * copy, where a read is a read of data.\n */\nexport function validateNavigateParamsShape(\n params: unknown,\n methodName: string,\n): void {\n if (params === undefined) {\n return;\n }\n\n // ⚠ `null` first, because it is the one shape that cannot be ASKED for a\n // prototype: `Object.getPrototypeOf(null)` raises a bare `TypeError` naming\n // neither the door nor the argument.\n //\n // ⚑ `Array.isArray` alongside the prototype, because the prototype read is\n // the caller's to answer (#2282): a `Proxy` traps `getPrototypeOf`, so one lie\n // presents an array as plain here — and core's `adoptChannel`, fooled by the\n // same lie one layer down, SPREADS it into a genuine `{0:…,1:…}`. By the time\n // `isParamsUnsafe` applies its own array term, the value it is handed is no\n // longer an array. This door is where the shape is still visible, and\n // `IsArray` is the one question a proxy cannot answer for.\n const proto =\n params === null ? false : (getPrototypeOf(params) as object | null);\n\n if (Array.isArray(params) || (proto !== null && proto !== Object.prototype)) {\n throw new TypeError(\n `[router.${methodName}] params must be a plain object, got ${getTypeDescription(params)}`,\n );\n }\n}\n\n/**\n * The path bag's VALUES, judged on the object CORE will ship (#2134).\n *\n * ⚑ **The argument is core's own copy, not the caller's bag**, and that is the\n * whole point of the split: `validateNavigateParamsShape` has already refused\n * every shape a copy would launder, and core has read the caller's object\n * exactly once to build this one. A key that answers differently per read is\n * therefore admitted on the same value the URL prints.\n *\n * ⚠ **No shape branch here, and its absence is load bearing rather than an\n * omission.** Every caller passes `undefined` or core's own copy of the bag,\n * so a `typeof` guard would be an unreachable arm — the shape half runs one\n * call earlier, on the object that can still be the wrong shape. `isParams`\n * below still re-applies the shape rules to the copy, so a caller that\n * bypassed the pair is refused rather than admitted.\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 error below.\n assertValidParamValues(params as Record<string, unknown>, methodName);\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\n/**\n * The QUERY channel's shape, the twin `validateParams` never had (#1972).\n *\n * ⚑ Shape only, and deliberately not the value inspection its path twin runs:\n * a query value is printed with `String()` and round-trips through the URL, so\n * the Symbol/BigInt/control-char rules that make a PATH segment unrepresentable\n * do not transfer. What was missing is that nothing asked whether the bag was a\n * bag at all — a string spread character by character into `state.search`.\n */\nexport function validateSearch(search: unknown, methodName: string): void {\n if (search === undefined) {\n return;\n }\n\n if (typeof search !== \"object\" || search === null || Array.isArray(search)) {\n throw new TypeError(\n `[router.${methodName}] search must be a plain object, got ${getTypeDescription(search)}`,\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\nimport type {\n AnyOptions,\n LimitsConfig,\n QueryParamsOptions,\n} 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 hasOwn = Object.hasOwn;\nconst objectKeys = Object.keys;\nconst getPrototypeOf = Object.getPrototypeOf;\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\n// The NAMES of the `queryParams` sub-options, and only the names: this plugin\n// owns which keys exist there, core owns which values each admits.\n//\n// ⚑ Keyed by core's own `QueryParamsOptions` (#2307), the same shape and the same\n// reason as `LIMIT_BOUNDS` below. A sub-option core adds and this table does not\n// is a TS2741 here — rather than a legitimate option the loop below refuses as\n// `unknown option`, which is the `plugin ⊇ core` false-reject of #1224 / #1225.\n// Nothing else binds this set: the authority walk beside it is the runtime half,\n// and without the annotation a core-side addition is silent until a consumer\n// hits it.\n//\n// ⚠ A `Record`, not an array of `keyof QueryParamsOptions`. An annotated array\n// binds each ELEMENT to the union and says nothing about the SET, so a missing\n// key compiles — the two-level mirror #2091 found in `expectedLimitKeys`. The\n// `Record` is what makes a missing key TS2741 and an extra key TS2353.\n//\n// ⚑ The VALUES are absent on purpose, and this is the whole of #2307. Core\n// refuses an unknown format BY NAME at construction (`requireStrategy`, #1318,\n// hoisted to matcher construction by #1819) and prints\n// `[router.constructor] Invalid \"queryParams.<key>\"` — the prefix and the field\n// path deliberately copied from this module, so a reader lands on the same\n// option. ⚠ The TAIL is core's own (`— expected \"a\" | \"b\"` against this\n// module's `. Must be one of: \"a\", \"b\"`), which is as far as the agreement\n// goes. #1819 states the intent in\n// `engine/search-params/strategies/index.ts`: \"the hoist makes the plugin's\n// message unreachable for these four fields\". This plugin's only door into\n// `validateOptions` is the retrospective pass at `usePlugin`, which runs AFTER\n// `createRouter`, and `setOption` was removed in #63 — so a bogus format never\n// arrives here, and a value list kept for one would be unreachable (#2307).\nconst KNOWN_QUERY_PARAMS: Record<keyof QueryParamsOptions, true> = {\n arrayFormat: true,\n booleanFormat: true,\n nullFormat: true,\n numberFormat: true,\n};\n\n// `logger` is a valid option name, but its contents are NOT validated here.\n// The Router constructor consumes `options.logger` (it builds the router's own\n// `RouterLogger` from it — per-router, no singleton) and strips the key before\n// 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).\n//\n// ⚑ Keyed by core's own `AnyOptions` (#2311) — `Options<never>`, which core\n// designates for readers that hold no dependency map, exactly this plugin's\n// position. An option core adds and this table does not is a TS2741 here, rather\n// than a legitimate option `validateOptions` refuses as `Unknown option`, which\n// is the `plugin ⊇ core` false-reject of #1224 / #1225.\n//\n// ⚠ A `Record`, not a `Set<string>` of literals. The `Set` carried the names and\n// bound none of them; the type system cannot see inside it. The `Set` below is\n// DERIVED from this table and keeps the O(1) membership test the loop wants.\nexport const KNOWN_OPTION_NAMES: Record<keyof AnyOptions, true> = {\n defaultRoute: true,\n defaultParams: true,\n defaultSearch: true,\n trailingSlash: true,\n caseSensitive: true,\n queryParamsMode: true,\n queryParams: true,\n urlParamsEncoding: true,\n allowNotFound: true,\n rewritePathOnMatch: true,\n logger: true,\n limits: true,\n};\n\nconst KNOWN_OPTIONS = new Set<string>(objectKeys(KNOWN_OPTION_NAMES));\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.\n//\n// ⚑ Keyed by core's own `LimitsConfig` (#1879). The BOUNDS are the plugin's; the\n// KEY SET is core's, so a limit core adds and this table does not is a TS2741\n// here — rather than a legitimate option the loop below rejects as `unknown\n// limit`, which is the `plugin ⊇ core` false-reject of #1224 / #1225.\nexport const LIMIT_BOUNDS: Readonly<\n Record<keyof LimitsConfig, { readonly min: number; readonly max: number }>\n> = {\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};\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\n/**\n * Is `value` a bag the router takes — `Object.prototype` or no prototype at all?\n *\n * ⚠ The PROTOTYPE, not `value.constructor` (#2217). A constructor read walks the\n * value's own chain, so `Object.create(null)` answers `undefined` and a bag\n * carrying no chain at all — the most conformant shape `packages/core/CLAUDE.md`\n * › Supported Input Shapes admits — reads as non-plain. It also resolves through\n * the writable `Object.prototype.constructor`, and against the live `Object`\n * where every other intrinsic here is captured at module load.\n *\n * ⚠ NOT the `proto.constructor` term `core/guards.ts` uses at the dependency\n * door. That one is deliberate and its `⚠` states the constraint it exists for;\n * this door has no such constraint. `prototype-term-authority-2197` owns the\n * per-site mapping.\n */\nfunction isPlainBag(value: object): boolean {\n const proto = getPrototypeOf(value) as object | null;\n\n return proto === null || proto === Object.prototype;\n}\n\nexport function validateLimits(\n limits: unknown,\n methodName: string,\n): asserts limits is Partial<LimitsConfig> {\n if (!limits || typeof limits !== \"object\" || !isPlainBag(limits)) {\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 !isPlainBag(value)\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 // Keys only. The value of a known key is core's to judge, and it already did —\n // see `KNOWN_QUERY_PARAMS`.\n for (const key of objectKeys(qp)) {\n if (!isObjKey(key, KNOWN_QUERY_PARAMS)) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"queryParams.${key}\": unknown option`,\n );\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, CORE_LIMIT_DEFAULTS } 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 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 = CORE_LIMIT_DEFAULTS.maxPlugins,\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","// packages/validation-plugin/src/validators/retrospective.ts\n\nimport { resolveForwardChain as coreResolveForwardChain } from \"@real-router/core\";\n\nimport type { RouterLogger } from \"@real-router/core/types\";\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}\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\n/**\n * The slots this pass reads off the store's `RouteConfig`.\n *\n * ⚠ A hand-written mirror of core's `RouteConfig`\n * (`namespaces/RoutesNamespace/types.ts`), and the TYPE system keeps nothing in\n * step: adding a slot in core compiles here unchanged, so a field can be\n * invisible in this pass while the store carries it (#1787 — `defaultSearch`\n * was). `core-union-mirror-authority-2091` binds the two key sets and reds on\n * either side moving — measured by mutation in both directions.\n *\n * ⚑ `structural-field-coverage-authority-1787` does NOT bind this, though the\n * two read as neighbours: that table classifies RUNTIME behaviour per field and\n * door, and dropping a slot from this interface leaves it green — measured.\n */\ninterface LocalRouteConfig {\n forwardMap: Record<string, string>;\n forwardFnMap: Record<string, unknown>;\n defaultParams: Record<string, unknown>;\n defaultSearch: 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\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; a mirror here would be\n * reachable only from a white-box unit test (#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 */\n/** Every entry of a default-bag slot is a plain object, or the store is bad. */\nfunction assertPlainBagSlot(\n slot: Record<string, unknown>,\n slotName: \"defaultParams\" | \"defaultSearch\",\n): void {\n for (const [routeName, bag] of objectEntries(slot)) {\n if (bag === null || typeof bag !== \"object\" || Array.isArray(bag)) {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" ${slotName} must be a plain object, got ${Array.isArray(bag) ? \"array\" : typeof bag}`,\n );\n }\n }\n}\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 // The two default bags carry the same rule, one slot apart (#1787).\n // ⚠ Reachable only for a TRUTHY value: core drops a falsy structural field\n // before anything is stored, so this pass has nothing left to read for one.\n // `structural-field-coverage-authority-1787` derives that boundary.\n assertPlainBagSlot(config.defaultParams, \"defaultParams\");\n assertPlainBagSlot(config.defaultSearch, \"defaultSearch\");\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 *\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 diagnoses nothing here: whatever reaches\n // this store is already `typeof \"number\"`, and the values worth rejecting\n // are spread across that type — `retrospective.test.ts` owns which ones and\n // reds on both `typeof` and `isFinite`. 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\n/**\n * Reports EXTERNAL guards bound to a name the route tree does not carry\n * (#2049).\n *\n * ⚑ **Why this cannot live at the door.** Registering a guard before its route\n * exists is a declared, working capability — register for `\"later\"`, add the\n * route, navigate, and the guard fires. At `addActivateGuard` a typo and a\n * not-yet-added route are therefore indistinguishable, and a reject there would\n * retire the capability. `start()` is the first moment they come apart: the tree\n * is built by then, so a guard naming nothing is a typo or a route the\n * application will add later, and the second is rare after boot.\n *\n * ⚠ **A warning, not a throw, unlike `validateResolvedDefaultRoute` one door\n * over.** A `defaultRoute` naming nothing is unusable, so that one throws. A\n * guard for a route added after `start()` is unusual but legitimate — the\n * capability above is not scoped to before-start — so this reports and steps\n * aside.\n *\n * ⚠ **EXTERNAL origins only.** A definition guard arrives attached to a route in\n * the config, so its name cannot be a typo by construction; reporting it would\n * be noise no caller could act on.\n */\nexport function warnOrphanedGuards(store: unknown, logger: RouterLogger): void {\n const routesStore = assertRoutesStore(store, \"warnOrphanedGuards\");\n const lifecycle = (\n routesStore as { lifecycleNamespace?: { getFactoriesByOrigin?: unknown } }\n ).lifecycleNamespace;\n\n /* v8 ignore start -- @preserve: `lifecycleNamespace` is declared optional on\n the store, so TypeScript requires the arm, but `routeGetStore()` at\n `start()` always carries a wired namespace. Unreachable through the plugin's\n only caller; kept because the type admits it. */\n if (typeof lifecycle?.getFactoriesByOrigin !== \"function\") {\n return;\n }\n /* v8 ignore stop */\n\n const byOrigin = (\n lifecycle as {\n getFactoriesByOrigin: () => {\n external: [Record<string, unknown>, Record<string, unknown>];\n };\n }\n ).getFactoriesByOrigin();\n\n // `[deactivate, activate]` — the namespace's own ordering convention, stated\n // on the class. Both halves take the same door and the same typo.\n //\n // ⚠ These are RECORDS, not Maps, and they are `Object.create(null)`-backed\n // (#1801) — the namespace builds them that way precisely because `cloneRouter`\n // enumerates them. `Object.keys` is the read that matches.\n const [deactivate, activate] = byOrigin.external;\n const orphaned = new Set<string>();\n\n for (const name of [...objectKeys(deactivate), ...objectKeys(activate)]) {\n if (!routeExistsInTree(routesStore.tree, name)) {\n orphaned.add(name);\n }\n }\n\n for (const name of orphaned) {\n logger.warn(\n \"router.start\",\n `Guard registered for route \"${name}\", which the route tree does not contain. ` +\n `It will never run unless that route is added. Check the name for a typo.`,\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 // ⚑ The TYPE, not only the async-ness (#1787). Without it a number reaches the\n // async branch's `typeof` test, fails it, and leaves the function — measured,\n // and the reason this door admitted what `update` refuses.\n if (typeof forwardTo !== \"string\" && typeof forwardTo !== \"function\") {\n throw new TypeError(\n `[router.addRoute] forwardTo must be a string or function for route \"${fullName}\", ` +\n `got ${getTypeDescription(forwardTo)}`,\n );\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\n/**\n * A route's own default bag: a plain object, or absent. Read with `typeof` and\n * `Array.isArray` only — the bag belongs to the application and may be\n * Proxy-backed, so a check that enumerated it would invoke its accessors.\n */\nfunction assertPlainBag(\n bag: unknown,\n slot: \"defaultParams\" | \"defaultSearch\",\n fullName: string,\n): void {\n if (bag === undefined) {\n return;\n }\n\n if (bag === null || typeof bag !== \"object\" || Array.isArray(bag)) {\n throw new TypeError(\n `[router.addRoute] ${slot} must be an object for route \"${fullName}\", ` +\n `got ${getTypeDescription(bag)}`,\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 assertPlainBag(route.defaultParams, \"defaultParams\", fullName);\n // ⚑ `Route.defaultSearch` is a `SearchParams` bag, unlike `Options.defaultSearch`\n // which may legally be a callback — the two slots spell the same name and the\n // predicate must not be shared (#1787).\n assertPlainBag(route.defaultSearch, \"defaultSearch\", fullName);\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\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 {\n findMisChanneledKey,\n validateRoute,\n} from \"@real-router/core/validation\";\n\nimport {\n isString,\n validateRouteName,\n isParams,\n getTypeDescription,\n} from \"../type-guards\";\nimport { validateForwardToTargets, validateRouteProperties } from \"./forwardTo\";\nimport { validateNavigateParamsShape } from \"./navigation\";\n\nimport type {\n ForwardToCallback,\n Params,\n Route,\n RouteConfigUpdate,\n DefaultDependencies,\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\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 *\n * ⚑ **The path bag is judged by SHAPE only, and the missing value walk is the\n * point (#2134).** This door returns a boolean and ships nothing out of the bag,\n * so there is no shipped value for a judged one to disagree with — what a value\n * walk buys here is a call into the application's accessors on a door where bare\n * core makes none. Measured: an inactive link reads the bag zero times without\n * this plugin, and an adapter's `<Link>` asks this predicate on every render.\n *\n * ⚠ **The answer is bare core's, not a weaker one.** Bare core answers `false`\n * for a `Symbol`, a function, a `BigInt` or a cyclic bag — the values are\n * compared, never printed, and a value the active state cannot hold cannot match\n * it. The plugin agrees with that answer rather than raising over it.\n *\n * ⚠ **The sibling predicate `canNavigateTo` still runs the value walk, and the\n * two are not required to agree.** They already do not: a control character in\n * a param makes `canNavigateTo` throw while this door answers `false`, and a\n * throwing accessor does the reverse. The difference is what each door does with\n * the bag — `canNavigateTo` builds a path from it, this one compares it.\n */\nexport function validateIsActiveRouteArgs(\n name: unknown,\n params: unknown,\n strictEquality: unknown,\n ignoreQueryParams: unknown,\n): asserts name is string {\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 // The shape half only — same rule, same message, one spelling.\n validateNavigateParamsShape(params, \"isActiveRoute\");\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(cached: {\n forwardTo: unknown;\n defaultParams: unknown;\n defaultSearch: unknown;\n decodeParams: unknown;\n encodeParams: unknown;\n canActivate: unknown;\n canDeactivate: unknown;\n}): void {\n const {\n forwardTo,\n defaultParams,\n defaultSearch,\n decodeParams,\n encodeParams,\n canActivate,\n canDeactivate,\n } = cached;\n\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 validateBagParam(defaultParams, \"defaultParams\");\n // ⚑ The patch slot mirrors `Route.defaultSearch` — a bag, never a callback.\n // `Options.defaultSearch` is the one that may be a function, and it is a\n // different door with a different validator (#1787).\n validateBagParam(defaultSearch, \"defaultSearch\");\n\n validateFunctionParam(decodeParams, \"decodeParams\");\n validateFunctionParam(encodeParams, \"encodeParams\");\n // ⚑ `RouteConfigUpdate` declares these as `GuardFnFactory | null`, with no\n // boolean — unlike `addActivateGuard`, whose handler may be one. A shared\n // predicate would break that door.\n validateFunctionParam(canActivate, \"canActivate\");\n validateFunctionParam(canDeactivate, \"canDeactivate\");\n}\n\n/** A patch's default bag: a plain object, `null` to remove, or absent. */\nfunction validateBagParam(\n value: unknown,\n paramName: \"defaultParams\" | \"defaultSearch\",\n): void {\n if (\n value !== undefined &&\n value !== null &&\n (typeof value !== \"object\" || Array.isArray(value))\n ) {\n throw new TypeError(\n `[router.updateRoute] ${paramName} must be an object or null, got ${getTypeDescription(value)}`,\n );\n }\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 * The retired single-bag spelling, reported at the doors that stay silent about\n * it (#2238).\n *\n * A declared QUERY name carrying a value in the PATH bag is the v1 spelling the\n * channel split retired. The committing doors already answer — `navigate` throws\n * `WRONG_CHANNEL`, `canNavigateTo` returns `false` — but `buildPath` prints an\n * href without the key and `isActiveRoute` judges the location that href\n * describes. Both are right about their own question, and both leave the caller\n * with a wrong URL in the DOM that only a plain left-click ever complains about:\n * a ⌘-click, a copied link and SSR markup all follow it in silence.\n *\n * ⚠ **A warning, not a throw.** Neither door has an error channel — one returns a\n * string, the other a boolean — and #2124 measured that wiring core's guard here\n * changes an ANSWER rather than revealing a silence.\n *\n * ⚠ **The predicate comes from core, it is not re-derived.** `findMisChanneledKey`\n * carries three carve-outs a copy would lose, and this package has already paid\n * for a mirrored rule drifting from its original (#1224 / #1225).\n *\n * De-duplicated per `route + key`, on a cache owned by the VALIDATOR object and\n * so by the router (#1583) — the same shape, and the same reason, as the mode\n * gate's reporter one file over.\n */\nexport function createMisChanneledKeyReporter(\n queryNamesOf: (routeName: string) => readonly string[],\n): (routeName: string, params: unknown) => void {\n const reported = new Set<string>();\n\n return function reportMisChanneledKey(\n routeName: string,\n params: unknown,\n ): void {\n // No name guard and no `try`: every caller runs the door's own validator\n // first, which throws on anything but a non-empty string, and\n // `getQueryParams` answers `[]` for a name the router does not hold rather\n // than throwing — measured, not assumed. A branch neither reachable nor\n // provable is a coverage hole with a comment attached.\n const key = findMisChanneledKey(\n params as Params | undefined,\n queryNamesOf(routeName),\n );\n\n if (key === undefined) {\n return;\n }\n\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] Route \"${routeName}\" declares \\`${key}\\` as a query param, but it was given in the ` +\n `\\`params\\` bag — the path channel. The URL is built without it, so the href does not match ` +\n `what you asked for; a plain click throws, but a ⌘-click, a copied link and server-rendered ` +\n `markup all follow the wrong URL in silence. Pass it in \\`search\\` instead.`,\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 { getPluginApi } from \"@real-router/core/api\";\nimport { freezeThrownError } from \"@real-router/core/utils\";\nimport { getInternals } from \"@real-router/core/validation\";\n\nimport { CORE_LIMIT_DEFAULTS } from \"./helpers\";\nimport {\n validateRouteName,\n isState,\n isBoolean,\n getTypeDescription,\n} from \"./type-guards\";\nimport { DefaultsMutationWatch } from \"./validators/defaultsMutation\";\nimport {\n validateDependencyName,\n validateSetDependencyArgs as validateSetDependencyArgsRaw,\n validateDependenciesObject,\n validateDependencyBatchLimit,\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 validateNavigateParamsShape,\n validateSearch,\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} from \"./validators/plugins\";\nimport {\n validateExistingRoutes,\n validateForwardToConsistency,\n validateRoutePropertiesStore,\n validateForwardToTargetsStore,\n validateDependenciesStructure,\n validateLimitsConsistency,\n validateResolvedDefaultRoute,\n warnOrphanedGuards,\n} from \"./validators/retrospective\";\nimport {\n createMisChanneledKeyReporter,\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>(\n ctx: RouterInternals<Dependencies>,\n defaultsWatch: DefaultsMutationWatch,\n): RouterValidator {\n // One de-dup cache per validator object, i.e. per registration, i.e. per router\n // (#1583) — the same lifetime the mode gate's reporters get, for the same\n // reason: a module-level Set would let the first router in a process silence\n // every one after it.\n const reportMisChanneledKey = createMisChanneledKeyReporter((routeName) =>\n ctx.getQueryParams(routeName),\n );\n\n return {\n routes: {\n validateBuildPathArgs(route, params) {\n validateBuildPathArgs(route);\n reportMisChanneledKey(route, params);\n },\n validateMatchPathArgs,\n validateIsActiveRouteArgs(name, params, strict, ignoreQP) {\n // An assertion, so `name` is a string below by the type system rather\n // than by a second runtime check.\n validateIsActiveRouteArgs(name, params, strict, ignoreQP);\n reportMisChanneledKey(name, params);\n },\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 forwardTo: upd.forwardTo,\n defaultParams: upd.defaultParams,\n defaultSearch: upd.defaultSearch,\n decodeParams: upd.decodeParams,\n encodeParams: upd.encodeParams,\n canActivate: upd.canActivate,\n canDeactivate: upd.canDeactivate,\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(deps, methodName) {\n validateDependenciesObject(deps, methodName);\n\n // ⚑ The limit, asked here rather than from inside the ingest loop\n // (#2253). This is core's only pre-flight call on the whole bag, so it\n // is the one position where a refusal can precede every write. The\n // shape check above runs first: a bag that is not a plain object, or\n // carries a getter, is refused for what it IS before it is measured.\n validateDependencyBatchLimit(\n deps,\n ctx.dependenciesGetStore(),\n methodName,\n );\n },\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 =\n ctx.getOptions().limits?.maxPlugins ?? CORE_LIMIT_DEFAULTS.maxPlugins;\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 },\n lifecycle: {\n validateHandler,\n validateHandlerLimit(count, methodName) {\n const maxHandlers =\n ctx.getOptions().limits?.maxLifecycleHandlers ??\n CORE_LIMIT_DEFAULTS.maxLifecycleHandlers;\n\n validateHandlerLimit(count, methodName, maxHandlers);\n },\n validateCountThresholds(count, methodName) {\n const maxHandlers =\n ctx.getOptions().limits?.maxLifecycleHandlers ??\n CORE_LIMIT_DEFAULTS.maxLifecycleHandlers;\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(opts) {\n validateNavigateToDefaultArgs(opts);\n // ⚑ HERE, and not on every navigation. No door READS the watched bags\n // after construction — core runs on the copy adoption took (#2171) — so\n // the question is where a stale one would have CHANGED the outcome, and\n // this is the only door where those slots take effect. Measured: with a\n // counting getter on the bag, `start`, `navigate` and `buildPath` add\n // zero reads, and `buildPath` refuses a missing param rather than filling\n // it from the option. An application that never calls this is never\n // charged for the check.\n defaultsWatch.check(ctx.logger);\n },\n validateNavigateToStateArgs,\n validateNavigationOptions,\n validateParams: validateNavigateParams,\n validateParamsShape: validateNavigateParamsShape,\n validateSearch,\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 =\n ctx.getOptions().limits?.maxListeners ??\n CORE_LIMIT_DEFAULTS.maxListeners;\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 freezeThrownError(\n new RouterError(\"VALIDATION_PLUGIN_AFTER_START\", {\n message: \"validation-plugin must be registered before router.start()\",\n }),\n );\n }\n\n // RouterInternals.validator is now mutable — direct assignment works\n const defaultsWatch = new DefaultsMutationWatch();\n\n // ⚠ BEFORE the validator goes live, so the first `navigateToDefault` after\n // this line already has a baseline to compare against.\n defaultsWatch.watch(getPluginApi(router).getAdoptedOrigins());\n\n ctx.validator = buildValidatorObject(ctx, defaultsWatch);\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 // ⚠ At START, not at registration. The tree can still grow between the two —\n // `usePlugin` must run before `start()` here — so a check at registration\n // would report a guard for a route the caller is about to add. #2049 states\n // why the DOOR cannot answer it either.\n //\n // ⚠ An INTERCEPTOR, not an `onStart` hook, and the difference is measured: a\n // plugin hook is wired as a `$start` LISTENER, so it spends one of the\n // router's `maxListeners`. That budget is advertised (10000 by default) and\n // pinned by `limits.test.ts`, which registers exactly that many and expects\n // the next to throw — an `onStart` here turns the advertised number into\n // 9999 for every application that installs this plugin. Interceptors are\n // counted against nothing.\n const removeInterceptor = getPluginApi(router).addInterceptor(\n \"start\",\n (next, path) => {\n // ⚠ The diagnostic must NEVER be worse than its own absence. A start\n // interceptor WRAPS the call, so an unguarded throw here rejects\n // `start()` and leaves the router inactive — measured, core turns it\n // into \"a `start` interceptor returned without calling next()\". A\n // `$start` listener would have swallowed it instead, so this catch is\n // what buys the same safety explicitly rather than by accident.\n /* v8 ignore start -- @preserve: the catch exists so a bug in the\n diagnostic cannot reject `start()`; by construction nothing above it\n throws, so the arm has no reachable input. Removing it is the change\n this comment exists to argue against. */\n try {\n warnOrphanedGuards(ctx.routeGetStore(), ctx.logger);\n } catch {\n // A broken diagnostic is not the application's problem.\n }\n /* v8 ignore stop */\n\n return next(path);\n },\n );\n\n return {\n teardown() {\n removeInterceptor();\n ctx.validator = null;\n },\n };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["getPrototypeOf","objectValues","hasOwn","obj","objectEntries","LOGGER_CTX","objectKeys","getOwnPropertyDescriptor","getPrototypeOf","isPlainBag","warnOverwrite","hasOwn","getPrototypeOf","objectEntries","hasOwn","objectKeys","objectKeys","coreResolveForwardChain","collectUrlParams","assertNotAsync","typedStore"],"sources":["../../src/helpers.ts","../../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/validators/defaultsMutation.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/helpers.ts\n\nimport type { LimitsConfig } from \"@real-router/core\";\n\n/**\n * Core's `DEFAULT_LIMITS`, mirrored — the plugin's ONE copy (#1879).\n *\n * ⚑ A copy on purpose: core keeps the table internal, and exporting it to remove\n * the duplication would make the numbers part of its published contract (owner\n * decision, #1879). What keeps the copy honest is\n * `limit-defaults-authority-1879.test.ts`, which reads what core actually\n * ENFORCES rather than what any source file says.\n *\n * ⚑ `Readonly<LimitsConfig>` is core's own interface, so a sixth limit added\n * there fails this object with TS2741 before a test runs — the same thing that\n * catches a limit `createLimits` forgets.\n */\nexport const CORE_LIMIT_DEFAULTS: Readonly<LimitsConfig> = Object.freeze({\n maxDependencies: 100,\n maxPlugins: 50,\n maxListeners: 10_000,\n warnListeners: 1000,\n maxLifecycleHandlers: 200,\n});\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/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 core still wins\n * (#1798), which is the doctrine's own caveat and travels with 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/**\n * The boolean fields of core's `NavigationOptions` — every key except `signal`,\n * which is an `AbortSignal` and is checked separately below.\n *\n * ⚠ **NOT keyed `Record<keyof NavigationOptions, true>`, and the reason is\n * measured (#2311).** That interface is OPEN: `browser-plugin`, `hash-plugin` and\n * `navigation-plugin` augment it, so `keyof` answers differently depending on\n * which plugins the compilation unit can see — seven keys from inside this\n * package, ten from inside `@real-router/react`. An exhaustive `Record` is\n * therefore ill-defined: it compiles here and fails in every consumer that\n * installs a URL plugin. Measured — it did, on `@real-router/react#type-check`.\n * The sibling tables in `validators/options.ts` ARE keyed that way because\n * `Options`, `QueryParamsOptions` and `LoggerConfig` are augmented by nobody.\n *\n * ⚑ Exhaustiveness against CORE is held instead by\n * `core-union-mirror-authority-2091`, which walks core's declaration in the\n * SOURCE FILE. Augmentations are invisible to a file walk, so it asks exactly the\n * question the type cannot: does this list carry every field core itself\n * declares? `satisfies` below adds the other direction — a name that is not a\n * navigation option at all fails to compile.\n *\n * ⚠ A plugin's own augmented booleans are deliberately unchecked. This package\n * cannot enumerate them, and claiming otherwise is what the open interface makes\n * impossible rather than merely hard.\n */\nconst NAVIGATION_OPTIONS_FIELDS = [\n \"replace\",\n \"reload\",\n \"force\",\n \"forceDeactivate\",\n \"redirected\",\n \"revalidate\",\n] as const satisfies readonly Exclude<keyof NavigationOptions, \"signal\">[];\n\n/**\n * Type guard for NavigationOptions.\n * Validates all optional boolean fields, derived from core's type (#2311).\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","import type { RouterLogger } from \"@real-router/core/types\";\n\n/**\n * Captured at module load: `entries`.\n *\n * ⚑ A comparison is only as honest as the intrinsic it reads WHEN IT RUNS, and\n * an application can re-point `Object.entries` after boot. This one decides\n * whether a bag MOVED, so a re-pointed reader could answer \"unchanged\" for a bag\n * that changed — silencing the very report this module exists to make.\n *\n * ⚠ It does NOT close a shim evaluated BEFORE this module, the ordinary polyfill\n * order. Same limit `OptionsNamespace` states for its own capture.\n */\nconst objectEntries = Object.entries;\n\nconst LOGGER_CTX = \"router.navigateToDefault\";\n\n/** The two slots whose late mutation changes nothing and says nothing (#2148). */\nconst WATCHED = [\"defaultParams\", \"defaultSearch\"] as const;\n\ntype Slot = (typeof WATCHED)[number];\n\n/**\n * A snapshot of one bag AS THE APPLICATION HANDED IT.\n *\n * ⚠ The baseline is this, never core's adopted copy. That copy is normalised —\n * `dropUnsafeKey` takes an own `__proto__` out of it on the way in (#1957) — so\n * comparing against it reports a bag nobody touched. Measured: a caller bag\n * spelling `id,__proto__,extra` becomes `id,extra` in core, which a naive\n * comparison reads as two mutations before the application has done anything.\n */\ntype Snapshot = ReadonlyMap<string, unknown>;\n\nconst snapshot = (bag: object): Snapshot =>\n new Map(objectEntries(bag as Record<string, unknown>));\n\n/**\n * Did this bag move since the snapshot?\n *\n * ⚠ One level, matching adoption's own depth. A deeper walk would report a\n * change core never had a chance to take either way, and this message is about\n * what the router DID read — the level it copied.\n */\nconst moved = (bag: object, before: Snapshot): boolean => {\n const now = objectEntries(bag as Record<string, unknown>);\n\n if (now.length !== before.size) {\n return true;\n }\n\n return now.some(\n ([key, value]) => !before.has(key) || !Object.is(before.get(key), value),\n );\n};\n\n/**\n * Reports, once, that a defaults bag was mutated after `createRouter()`.\n *\n * ⚑ **The failure this exists for is silence.** Since #2171 core copies these\n * bags at construction, so an application that mutates one afterwards keeps\n * running and simply stops having any effect — no throw, no warning, no type\n * error. Core cannot speak here: it is the layer that degrades, and this is the\n * layer that reports (`packages/core/CLAUDE.md` › Supported Input Shapes).\n *\n * ⚠ The replacement it names is not a workaround. `defaultParams` and\n * `defaultSearch` already accept a CALLBACK, resolved at the point of use, so an\n * application that wants a live value has a supported mechanism rather than a\n * compromise.\n *\n * ⚠ ONCE per slot, and the flag is why. This runs on every\n * `navigateToDefault`, and a report that repeated would turn a real diagnostic\n * into the noise an application learns to filter.\n */\nexport class DefaultsMutationWatch {\n /**\n * ⚠ ONE map, not a pair keyed alike. Two maps make `before` look optional at\n * every read — a branch that cannot be taken, because both are written in the\n * same iteration, and an untakeable branch is coverage nobody can earn.\n */\n readonly #watched = new Map<\n Slot,\n { readonly ref: WeakRef<object>; readonly before: Snapshot }\n >();\n readonly #reported = new Set<Slot>();\n\n /**\n * ⚠ Takes the snapshot AT INSTALL, which is the earliest this layer exists.\n * A mutation between `createRouter()` and `usePlugin()` is therefore invisible\n * — and correctly so: it happened while the plugin was not there to be asked,\n * and reporting it would be a claim about a window this code never saw.\n */\n watch(origins: Readonly<Partial<Record<Slot, WeakRef<object>>>>): void {\n for (const slot of WATCHED) {\n const ref = origins[slot];\n const bag = ref?.deref();\n\n if (ref === undefined || bag === undefined) {\n continue;\n }\n\n this.#watched.set(slot, { ref, before: snapshot(bag) });\n }\n }\n\n check(logger: RouterLogger): void {\n for (const [slot, { ref, before }] of this.#watched) {\n if (this.#reported.has(slot)) {\n continue;\n }\n\n const bag = ref.deref();\n\n // ⚠ A collected bag is the RIGHT silence, not a missed report: the\n // application no longer holds it, so there is no mutation left to make.\n if (bag === undefined) {\n continue;\n }\n\n if (!moved(bag, before)) {\n continue;\n }\n\n this.#reported.add(slot);\n logger.warn(\n LOGGER_CTX,\n `mutating \\`${slot}\\` after createRouter() no longer affects routing — ` +\n `the router copied it at construction. Use the callback form ` +\n `(\\`${slot}: () => ({ … })\\`), which is resolved at the point of use.`,\n );\n }\n }\n}\n","// packages/validation-plugin/src/validators/dependencies.ts\n\nimport { computeThresholds, CORE_LIMIT_DEFAULTS } from \"../helpers\";\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { RouterLogger } from \"@real-router/core\";\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 core's `guardDependencyShape` (`core/src/guards.ts`), and it\n * has to 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 // ⚑ Mirrors core's `Array.isArray` term (#2282), and has to: the read below\n // goes through a `Proxy`'s `getPrototypeOf` trap, so without it this copy\n // ACCEPTS an array that core refuses — which is the mirror parting, in the\n // direction the contract above forbids least loudly and a consumer notices\n // last.\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\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\n/**\n * The batch form of the limit, run BEFORE the ingest pass (#2253).\n *\n * ⚑ {@link validateDependencyCount} asks about the store as it stands, and the\n * ingest loop calls it once per NEW key — so the key that trips the limit\n * arrives after its predecessors are already stored, and a caught `RangeError`\n * describes a call that moved the store. This one answers the same question for\n * the whole batch, from the pre-flight position, so the refusal is atomic.\n *\n * ⚠ Only keys the store does not already hold count: an overwrite leaves the\n * total unchanged, which is the rule the ingest loop follows by checking on its\n * new-key branch alone. A batch of pure overwrites is therefore legal at the\n * limit — `held + 0 > max` is false there, so nothing short-circuits on\n * `added === 0`. Such a short-circuit would be equivalent rather than\n * protective: a store ALREADY over the limit never reaches this function,\n * because `validateLimitsConsistency` refuses the registration that would\n * install it.\n *\n * ⚠ It does NOT carry the advisory thresholds. Those fire as the store grows and\n * belong to the per-key call the loop keeps making; a projection cannot say\n * which of them a batch would cross without replaying it.\n *\n * The shape is `RouteLifecycleNamespace.preflightHandlerLimit`'s, which moved the\n * handler limit out of a post-commit tear for the same reason.\n */\nexport function validateDependencyBatchLimit(\n deps: Record<string, unknown>,\n store: unknown,\n methodName: string,\n): void {\n const typedStore = store as {\n dependencies: Record<string, unknown>;\n limits?: { maxDependencies?: number };\n };\n const maxDependencies =\n typedStore.limits?.maxDependencies ?? CORE_LIMIT_DEFAULTS.maxDependencies;\n\n if (maxDependencies === 0) {\n return;\n }\n\n // ⚑ ONE question about the store's own keys, asked once (#1815 / #2064). The\n // size and the membership test come from the SAME list: `Object.keys` is own\n // AND enumerable while `hasOwn` is own only, so two calls would disagree on\n // exactly the keys the count refuses to see. `store.dependencies` is a handout\n // a plugin can replace (core INVARIANTS 13b), so the receiver is not\n // guaranteed to answer `ownKeys` and `getOwnPropertyDescriptor` alike.\n const heldKeys = objectKeys(typedStore.dependencies);\n const held = new Set(heldKeys);\n let added = 0;\n\n for (const key of objectKeys(deps)) {\n if (!held.has(key)) {\n added += 1;\n }\n }\n\n const currentCount = heldKeys.length;\n\n if (currentCount + added > maxDependencies) {\n throw new RangeError(\n `[router.${methodName}] Dependency limit exceeded (${maxDependencies}). ` +\n `Current: ${currentCount}, this batch adds ${added}.`,\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 ?? CORE_LIMIT_DEFAULTS.maxDependencies;\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 { events } from \"@real-router/core\";\n\nimport { computeThresholds, CORE_LIMIT_DEFAULTS } from \"../helpers\";\n\nimport type {\n EventMethodMap,\n EventName,\n Plugin,\n RouterLogger,\n} from \"@real-router/core\";\n\n// Derived from the constant core declares them in (#1888) — core enforces\n// membership at `addEventListener` itself, so a hand-written second list here\n// would be a copy that can drift from the emitter it describes.\nconst objectValues = Object.values;\n\nconst validEventNames = new Set<EventName>(\n objectValues(events) as readonly EventName[],\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 = CORE_LIMIT_DEFAULTS.maxListeners,\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\n// Re-exported so the rest of the plugin keeps naming them here (#1879). They are\n// core's: a hand-written copy accepts the event set core had when it was typed,\n// which is the type-level half of the drift #1888 closed at runtime.\nexport { type EventMethodMap, type EventName } from \"@real-router/core\";\n","// packages/validation-plugin/src/validators/lifecycle.ts\n\nimport { computeThresholds, CORE_LIMIT_DEFAULTS } from \"../helpers\";\nimport { isBoolean, getTypeDescription } from \"../type-guards\";\n\nimport type {\n GuardFnFactory,\n DefaultDependencies,\n RouterLogger,\n} from \"@real-router/core\";\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 = CORE_LIMIT_DEFAULTS.maxLifecycleHandlers,\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;\nconst getPrototypeOf = Object.getPrototypeOf;\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\n/**\n * The path bag's SHAPE, judged on the object the CALLER still owns (#2134).\n *\n * ⚑ **Split from `validateNavigateParams` because the two halves belong to two\n * different objects.** The shape has to be judged before core copies, because a\n * copy of anything is a plain object: core's copy is a spread, so `\"abc\"` arrives\n * as `{0:\"a\",1:\"b\",2:\"c\"}` and a class instance arrives without its prototype.\n * Judged after the copy, every shape this function exists to refuse would be\n * LAUNDERED into an acceptable one.\n *\n * ⚑ **The values belong to the copy, and that is the defect this split closes.**\n * Judged on the caller's bag, they are a different read from the one core ships —\n * a key answering `v1` then `v2` is admitted on one and printed on the other, and\n * `judged-equals-shipped-2134` holds the three producer doors to the bare-core\n * answer; the plugin-API doors that take a bag are not in that table.\n *\n * ⚠ **Reads no value, and that is a contract rather than an optimisation.**\n * Every read of a caller-owned bag is a call into application code. This half\n * runs before core has read anything, so it must not run the application's\n * getters — `validateNavigateParams` walks the values afterwards, on core's own\n * copy, where a read is a read of data.\n */\nexport function validateNavigateParamsShape(\n params: unknown,\n methodName: string,\n): void {\n if (params === undefined) {\n return;\n }\n\n // ⚠ `null` first, because it is the one shape that cannot be ASKED for a\n // prototype: `Object.getPrototypeOf(null)` raises a bare `TypeError` naming\n // neither the door nor the argument.\n //\n // ⚑ `Array.isArray` alongside the prototype, because the prototype read is\n // the caller's to answer (#2282): a `Proxy` traps `getPrototypeOf`, so one lie\n // presents an array as plain here — and core's `adoptChannel`, fooled by the\n // same lie one layer down, SPREADS it into a genuine `{0:…,1:…}`. By the time\n // `isParamsUnsafe` applies its own array term, the value it is handed is no\n // longer an array. This door is where the shape is still visible, and\n // `IsArray` is the one question a proxy cannot answer for.\n const proto =\n params === null ? false : (getPrototypeOf(params) as object | null);\n\n if (Array.isArray(params) || (proto !== null && proto !== Object.prototype)) {\n throw new TypeError(\n `[router.${methodName}] params must be a plain object, got ${getTypeDescription(params)}`,\n );\n }\n}\n\n/**\n * The path bag's VALUES, judged on the object CORE will ship (#2134).\n *\n * ⚑ **The argument is core's own copy, not the caller's bag**, and that is the\n * whole point of the split: `validateNavigateParamsShape` has already refused\n * every shape a copy would launder, and core has read the caller's object\n * exactly once to build this one. A key that answers differently per read is\n * therefore admitted on the same value the URL prints.\n *\n * ⚠ **No shape branch here, and its absence is load bearing rather than an\n * omission.** Every caller passes `undefined` or core's own copy of the bag,\n * so a `typeof` guard would be an unreachable arm — the shape half runs one\n * call earlier, on the object that can still be the wrong shape. `isParams`\n * below still re-applies the shape rules to the copy, so a caller that\n * bypassed the pair is refused rather than admitted.\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 error below.\n assertValidParamValues(params as Record<string, unknown>, methodName);\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\n/**\n * The QUERY channel's shape, the twin `validateParams` never had (#1972).\n *\n * ⚑ Shape only, and deliberately not the value inspection its path twin runs:\n * a query value is printed with `String()` and round-trips through the URL, so\n * the Symbol/BigInt/control-char rules that make a PATH segment unrepresentable\n * do not transfer. What was missing is that nothing asked whether the bag was a\n * bag at all — a string spread character by character into `state.search`.\n */\nexport function validateSearch(search: unknown, methodName: string): void {\n if (search === undefined) {\n return;\n }\n\n if (typeof search !== \"object\" || search === null || Array.isArray(search)) {\n throw new TypeError(\n `[router.${methodName}] search must be a plain object, got ${getTypeDescription(search)}`,\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\nimport type {\n AnyOptions,\n LimitsConfig,\n QueryParamsOptions,\n} 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 hasOwn = Object.hasOwn;\nconst objectKeys = Object.keys;\nconst getPrototypeOf = Object.getPrototypeOf;\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\n// The NAMES of the `queryParams` sub-options, and only the names: this plugin\n// owns which keys exist there, core owns which values each admits.\n//\n// ⚑ Keyed by core's own `QueryParamsOptions` (#2307), the same shape and the same\n// reason as `LIMIT_BOUNDS` below. A sub-option core adds and this table does not\n// is a TS2741 here — rather than a legitimate option the loop below refuses as\n// `unknown option`, which is the `plugin ⊇ core` false-reject of #1224 / #1225.\n// Nothing else binds this set: the authority walk beside it is the runtime half,\n// and without the annotation a core-side addition is silent until a consumer\n// hits it.\n//\n// ⚠ A `Record`, not an array of `keyof QueryParamsOptions`. An annotated array\n// binds each ELEMENT to the union and says nothing about the SET, so a missing\n// key compiles — the two-level mirror #2091 found in `expectedLimitKeys`. The\n// `Record` is what makes a missing key TS2741 and an extra key TS2353.\n//\n// ⚑ The VALUES are absent on purpose, and this is the whole of #2307. Core\n// refuses an unknown format BY NAME at construction (`requireStrategy`, #1318,\n// hoisted to matcher construction by #1819) and prints\n// `[router.constructor] Invalid \"queryParams.<key>\"` — the prefix and the field\n// path deliberately copied from this module, so a reader lands on the same\n// option. ⚠ The TAIL is core's own (`— expected \"a\" | \"b\"` against this\n// module's `. Must be one of: \"a\", \"b\"`), which is as far as the agreement\n// goes. #1819 states the intent in\n// `engine/search-params/strategies/index.ts`: \"the hoist makes the plugin's\n// message unreachable for these four fields\". This plugin's only door into\n// `validateOptions` is the retrospective pass at `usePlugin`, which runs AFTER\n// `createRouter`, and `setOption` was removed in #63 — so a bogus format never\n// arrives here, and a value list kept for one would be unreachable (#2307).\nconst KNOWN_QUERY_PARAMS: Record<keyof QueryParamsOptions, true> = {\n arrayFormat: true,\n booleanFormat: true,\n nullFormat: true,\n numberFormat: true,\n};\n\n// `logger` is a valid option name, but its contents are NOT validated here.\n// The Router constructor consumes `options.logger` (it builds the router's own\n// `RouterLogger` from it — per-router, no singleton) and strips the key before\n// 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).\n//\n// ⚑ Keyed by core's own `AnyOptions` (#2311) — `Options<never>`, which core\n// designates for readers that hold no dependency map, exactly this plugin's\n// position. An option core adds and this table does not is a TS2741 here, rather\n// than a legitimate option `validateOptions` refuses as `Unknown option`, which\n// is the `plugin ⊇ core` false-reject of #1224 / #1225.\n//\n// ⚠ A `Record`, not a `Set<string>` of literals. The `Set` carried the names and\n// bound none of them; the type system cannot see inside it. The `Set` below is\n// DERIVED from this table and keeps the O(1) membership test the loop wants.\nexport const KNOWN_OPTION_NAMES: Record<keyof AnyOptions, true> = {\n defaultRoute: true,\n defaultParams: true,\n defaultSearch: true,\n trailingSlash: true,\n caseSensitive: true,\n queryParamsMode: true,\n queryParams: true,\n urlParamsEncoding: true,\n allowNotFound: true,\n rewritePathOnMatch: true,\n logger: true,\n limits: true,\n};\n\nconst KNOWN_OPTIONS = new Set<string>(objectKeys(KNOWN_OPTION_NAMES));\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.\n//\n// ⚑ Keyed by core's own `LimitsConfig` (#1879). The BOUNDS are the plugin's; the\n// KEY SET is core's, so a limit core adds and this table does not is a TS2741\n// here — rather than a legitimate option the loop below rejects as `unknown\n// limit`, which is the `plugin ⊇ core` false-reject of #1224 / #1225.\nexport const LIMIT_BOUNDS: Readonly<\n Record<keyof LimitsConfig, { readonly min: number; readonly max: number }>\n> = {\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};\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\n/**\n * Is `value` a bag the router takes — `Object.prototype` or no prototype at all?\n *\n * ⚠ The PROTOTYPE, not `value.constructor` (#2217). A constructor read walks the\n * value's own chain, so `Object.create(null)` answers `undefined` and a bag\n * carrying no chain at all — the most conformant shape `packages/core/CLAUDE.md`\n * › Supported Input Shapes admits — reads as non-plain. It also resolves through\n * the writable `Object.prototype.constructor`, and against the live `Object`\n * where every other intrinsic here is captured at module load.\n *\n * ⚠ NOT the `proto.constructor` term `core/guards.ts` uses at the dependency\n * door. That one is deliberate and its `⚠` states the constraint it exists for;\n * this door has no such constraint. `prototype-term-authority-2197` owns the\n * per-site mapping.\n */\nfunction isPlainBag(value: object): boolean {\n const proto = getPrototypeOf(value) as object | null;\n\n return proto === null || proto === Object.prototype;\n}\n\nexport function validateLimits(\n limits: unknown,\n methodName: string,\n): asserts limits is Partial<LimitsConfig> {\n if (!limits || typeof limits !== \"object\" || !isPlainBag(limits)) {\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 !isPlainBag(value)\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 // Keys only. The value of a known key is core's to judge, and it already did —\n // see `KNOWN_QUERY_PARAMS`.\n for (const key of objectKeys(qp)) {\n if (!isObjKey(key, KNOWN_QUERY_PARAMS)) {\n throw new TypeError(\n `[router.${methodName}] Invalid \"queryParams.${key}\": unknown option`,\n );\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, CORE_LIMIT_DEFAULTS } 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 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 = CORE_LIMIT_DEFAULTS.maxPlugins,\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","// packages/validation-plugin/src/validators/retrospective.ts\n\nimport { resolveForwardChain as coreResolveForwardChain } from \"@real-router/core\";\n\nimport type { RouterLogger } from \"@real-router/core/types\";\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}\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\n/**\n * The slots this pass reads off the store's `RouteConfig`.\n *\n * ⚠ A hand-written mirror of core's `RouteConfig`\n * (`namespaces/RoutesNamespace/types.ts`), and the TYPE system keeps nothing in\n * step: adding a slot in core compiles here unchanged, so a field can be\n * invisible in this pass while the store carries it (#1787 — `defaultSearch`\n * was). `core-union-mirror-authority-2091` binds the two key sets and reds on\n * either side moving — measured by mutation in both directions.\n *\n * ⚑ `structural-field-coverage-authority-1787` does NOT bind this, though the\n * two read as neighbours: that table classifies RUNTIME behaviour per field and\n * door, and dropping a slot from this interface leaves it green — measured.\n */\ninterface LocalRouteConfig {\n forwardMap: Record<string, string>;\n forwardFnMap: Record<string, unknown>;\n defaultParams: Record<string, unknown>;\n defaultSearch: 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\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; a mirror here would be\n * reachable only from a white-box unit test (#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 */\n/** Every entry of a default-bag slot is a plain object, or the store is bad. */\nfunction assertPlainBagSlot(\n slot: Record<string, unknown>,\n slotName: \"defaultParams\" | \"defaultSearch\",\n): void {\n for (const [routeName, bag] of objectEntries(slot)) {\n if (bag === null || typeof bag !== \"object\" || Array.isArray(bag)) {\n throw new TypeError(\n `[validation-plugin] validateRoutePropertiesStore: route \"${routeName}\" ${slotName} must be a plain object, got ${Array.isArray(bag) ? \"array\" : typeof bag}`,\n );\n }\n }\n}\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 // The two default bags carry the same rule, one slot apart (#1787).\n // ⚠ Reachable only for a TRUTHY value: core drops a falsy structural field\n // before anything is stored, so this pass has nothing left to read for one.\n // `structural-field-coverage-authority-1787` derives that boundary.\n assertPlainBagSlot(config.defaultParams, \"defaultParams\");\n assertPlainBagSlot(config.defaultSearch, \"defaultSearch\");\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 *\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 diagnoses nothing here: whatever reaches\n // this store is already `typeof \"number\"`, and the values worth rejecting\n // are spread across that type — `retrospective.test.ts` owns which ones and\n // reds on both `typeof` and `isFinite`. 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\n/**\n * Reports EXTERNAL guards bound to a name the route tree does not carry\n * (#2049).\n *\n * ⚑ **Why this cannot live at the door.** Registering a guard before its route\n * exists is a declared, working capability — register for `\"later\"`, add the\n * route, navigate, and the guard fires. At `addActivateGuard` a typo and a\n * not-yet-added route are therefore indistinguishable, and a reject there would\n * retire the capability. `start()` is the first moment they come apart: the tree\n * is built by then, so a guard naming nothing is a typo or a route the\n * application will add later, and the second is rare after boot.\n *\n * ⚠ **A warning, not a throw, unlike `validateResolvedDefaultRoute` one door\n * over.** A `defaultRoute` naming nothing is unusable, so that one throws. A\n * guard for a route added after `start()` is unusual but legitimate — the\n * capability above is not scoped to before-start — so this reports and steps\n * aside.\n *\n * ⚠ **EXTERNAL origins only.** A definition guard arrives attached to a route in\n * the config, so its name cannot be a typo by construction; reporting it would\n * be noise no caller could act on.\n */\nexport function warnOrphanedGuards(store: unknown, logger: RouterLogger): void {\n const routesStore = assertRoutesStore(store, \"warnOrphanedGuards\");\n const lifecycle = (\n routesStore as { lifecycleNamespace?: { getFactoriesByOrigin?: unknown } }\n ).lifecycleNamespace;\n\n /* v8 ignore start -- @preserve: `lifecycleNamespace` is declared optional on\n the store, so TypeScript requires the arm, but `routeGetStore()` at\n `start()` always carries a wired namespace. Unreachable through the plugin's\n only caller; kept because the type admits it. */\n if (typeof lifecycle?.getFactoriesByOrigin !== \"function\") {\n return;\n }\n /* v8 ignore stop */\n\n const byOrigin = (\n lifecycle as {\n getFactoriesByOrigin: () => {\n external: [Record<string, unknown>, Record<string, unknown>];\n };\n }\n ).getFactoriesByOrigin();\n\n // `[deactivate, activate]` — the namespace's own ordering convention, stated\n // on the class. Both halves take the same door and the same typo.\n //\n // ⚠ These are RECORDS, not Maps, and they are `Object.create(null)`-backed\n // (#1801) — the namespace builds them that way precisely because `cloneRouter`\n // enumerates them. `Object.keys` is the read that matches.\n const [deactivate, activate] = byOrigin.external;\n const orphaned = new Set<string>();\n\n for (const name of [...objectKeys(deactivate), ...objectKeys(activate)]) {\n if (!routeExistsInTree(routesStore.tree, name)) {\n orphaned.add(name);\n }\n }\n\n for (const name of orphaned) {\n logger.warn(\n \"router.start\",\n `Guard registered for route \"${name}\", which the route tree does not contain. ` +\n `It will never run unless that route is added. Check the name for a typo.`,\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 // ⚑ The TYPE, not only the async-ness (#1787). Without it a number reaches the\n // async branch's `typeof` test, fails it, and leaves the function — measured,\n // and the reason this door admitted what `update` refuses.\n if (typeof forwardTo !== \"string\" && typeof forwardTo !== \"function\") {\n throw new TypeError(\n `[router.addRoute] forwardTo must be a string or function for route \"${fullName}\", ` +\n `got ${getTypeDescription(forwardTo)}`,\n );\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\n/**\n * A route's own default bag: a plain object, or absent. Read with `typeof` and\n * `Array.isArray` only — the bag belongs to the application and may be\n * Proxy-backed, so a check that enumerated it would invoke its accessors.\n */\nfunction assertPlainBag(\n bag: unknown,\n slot: \"defaultParams\" | \"defaultSearch\",\n fullName: string,\n): void {\n if (bag === undefined) {\n return;\n }\n\n if (bag === null || typeof bag !== \"object\" || Array.isArray(bag)) {\n throw new TypeError(\n `[router.addRoute] ${slot} must be an object for route \"${fullName}\", ` +\n `got ${getTypeDescription(bag)}`,\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 assertPlainBag(route.defaultParams, \"defaultParams\", fullName);\n // ⚑ `Route.defaultSearch` is a `SearchParams` bag, unlike `Options.defaultSearch`\n // which may legally be a callback — the two slots spell the same name and the\n // predicate must not be shared (#1787).\n assertPlainBag(route.defaultSearch, \"defaultSearch\", fullName);\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\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 {\n findMisChanneledKey,\n validateRoute,\n} from \"@real-router/core/validation\";\n\nimport {\n isString,\n validateRouteName,\n isParams,\n getTypeDescription,\n} from \"../type-guards\";\nimport { validateForwardToTargets, validateRouteProperties } from \"./forwardTo\";\nimport { validateNavigateParamsShape } from \"./navigation\";\n\nimport type {\n ForwardToCallback,\n Params,\n Route,\n RouteConfigUpdate,\n DefaultDependencies,\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\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 *\n * ⚑ **The path bag is judged by SHAPE only, and the missing value walk is the\n * point (#2134).** This door returns a boolean and ships nothing out of the bag,\n * so there is no shipped value for a judged one to disagree with — what a value\n * walk buys here is a call into the application's accessors on a door where bare\n * core makes none. Measured: an inactive link reads the bag zero times without\n * this plugin, and an adapter's `<Link>` asks this predicate on every render.\n *\n * ⚠ **The answer is bare core's, not a weaker one.** Bare core answers `false`\n * for a `Symbol`, a function, a `BigInt` or a cyclic bag — the values are\n * compared, never printed, and a value the active state cannot hold cannot match\n * it. The plugin agrees with that answer rather than raising over it.\n *\n * ⚠ **The sibling predicate `canNavigateTo` still runs the value walk, and the\n * two are not required to agree.** They already do not: a control character in\n * a param makes `canNavigateTo` throw while this door answers `false`, and a\n * throwing accessor does the reverse. The difference is what each door does with\n * the bag — `canNavigateTo` builds a path from it, this one compares it.\n */\nexport function validateIsActiveRouteArgs(\n name: unknown,\n params: unknown,\n strictEquality: unknown,\n ignoreQueryParams: unknown,\n): asserts name is string {\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 // The shape half only — same rule, same message, one spelling.\n validateNavigateParamsShape(params, \"isActiveRoute\");\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(cached: {\n forwardTo: unknown;\n defaultParams: unknown;\n defaultSearch: unknown;\n decodeParams: unknown;\n encodeParams: unknown;\n canActivate: unknown;\n canDeactivate: unknown;\n}): void {\n const {\n forwardTo,\n defaultParams,\n defaultSearch,\n decodeParams,\n encodeParams,\n canActivate,\n canDeactivate,\n } = cached;\n\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 validateBagParam(defaultParams, \"defaultParams\");\n // ⚑ The patch slot mirrors `Route.defaultSearch` — a bag, never a callback.\n // `Options.defaultSearch` is the one that may be a function, and it is a\n // different door with a different validator (#1787).\n validateBagParam(defaultSearch, \"defaultSearch\");\n\n validateFunctionParam(decodeParams, \"decodeParams\");\n validateFunctionParam(encodeParams, \"encodeParams\");\n // ⚑ `RouteConfigUpdate` declares these as `GuardFnFactory | null`, with no\n // boolean — unlike `addActivateGuard`, whose handler may be one. A shared\n // predicate would break that door.\n validateFunctionParam(canActivate, \"canActivate\");\n validateFunctionParam(canDeactivate, \"canDeactivate\");\n}\n\n/** A patch's default bag: a plain object, `null` to remove, or absent. */\nfunction validateBagParam(\n value: unknown,\n paramName: \"defaultParams\" | \"defaultSearch\",\n): void {\n if (\n value !== undefined &&\n value !== null &&\n (typeof value !== \"object\" || Array.isArray(value))\n ) {\n throw new TypeError(\n `[router.updateRoute] ${paramName} must be an object or null, got ${getTypeDescription(value)}`,\n );\n }\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 * The retired single-bag spelling, reported at the doors that stay silent about\n * it (#2238).\n *\n * A declared QUERY name carrying a value in the PATH bag is the v1 spelling the\n * channel split retired. The committing doors already answer — `navigate` throws\n * `WRONG_CHANNEL`, `canNavigateTo` returns `false` — but `buildPath` prints an\n * href without the key and `isActiveRoute` judges the location that href\n * describes. Both are right about their own question, and both leave the caller\n * with a wrong URL in the DOM that only a plain left-click ever complains about:\n * a ⌘-click, a copied link and SSR markup all follow it in silence.\n *\n * ⚠ **A warning, not a throw.** Neither door has an error channel — one returns a\n * string, the other a boolean — and #2124 measured that wiring core's guard here\n * changes an ANSWER rather than revealing a silence.\n *\n * ⚠ **The predicate comes from core, it is not re-derived.** `findMisChanneledKey`\n * carries three carve-outs a copy would lose, and this package has already paid\n * for a mirrored rule drifting from its original (#1224 / #1225).\n *\n * De-duplicated per `route + key`, on a cache owned by the VALIDATOR object and\n * so by the router (#1583) — the same shape, and the same reason, as the mode\n * gate's reporter one file over.\n */\nexport function createMisChanneledKeyReporter(\n queryNamesOf: (routeName: string) => readonly string[],\n): (routeName: string, params: unknown) => void {\n const reported = new Set<string>();\n\n return function reportMisChanneledKey(\n routeName: string,\n params: unknown,\n ): void {\n // No name guard and no `try`: every caller runs the door's own validator\n // first, which throws on anything but a non-empty string, and\n // `getQueryParams` answers `[]` for a name the router does not hold rather\n // than throwing — measured, not assumed. A branch neither reachable nor\n // provable is a coverage hole with a comment attached.\n const key = findMisChanneledKey(\n params as Params | undefined,\n queryNamesOf(routeName),\n );\n\n if (key === undefined) {\n return;\n }\n\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] Route \"${routeName}\" declares \\`${key}\\` as a query param, but it was given in the ` +\n `\\`params\\` bag — the path channel. The URL is built without it, so the href does not match ` +\n `what you asked for; a plain click throws, but a ⌘-click, a copied link and server-rendered ` +\n `markup all follow the wrong URL in silence. Pass it in \\`search\\` instead.`,\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 { getPluginApi } from \"@real-router/core/api\";\nimport { freezeThrownError } from \"@real-router/core/utils\";\nimport { getInternals } from \"@real-router/core/validation\";\n\nimport { CORE_LIMIT_DEFAULTS } from \"./helpers\";\nimport {\n validateRouteName,\n isState,\n isBoolean,\n getTypeDescription,\n} from \"./type-guards\";\nimport { DefaultsMutationWatch } from \"./validators/defaultsMutation\";\nimport {\n validateDependencyName,\n validateSetDependencyArgs as validateSetDependencyArgsRaw,\n validateDependenciesObject,\n validateDependencyBatchLimit,\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 validateNavigateParamsShape,\n validateSearch,\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} from \"./validators/plugins\";\nimport {\n validateExistingRoutes,\n validateForwardToConsistency,\n validateRoutePropertiesStore,\n validateForwardToTargetsStore,\n validateDependenciesStructure,\n validateLimitsConsistency,\n validateResolvedDefaultRoute,\n warnOrphanedGuards,\n} from \"./validators/retrospective\";\nimport {\n createMisChanneledKeyReporter,\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>(\n ctx: RouterInternals<Dependencies>,\n defaultsWatch: DefaultsMutationWatch,\n): RouterValidator {\n // One de-dup cache per validator object, i.e. per registration, i.e. per router\n // (#1583) — the same lifetime the mode gate's reporters get, for the same\n // reason: a module-level Set would let the first router in a process silence\n // every one after it.\n const reportMisChanneledKey = createMisChanneledKeyReporter((routeName) =>\n ctx.getQueryParams(routeName),\n );\n\n return {\n routes: {\n validateBuildPathArgs(route, params) {\n validateBuildPathArgs(route);\n reportMisChanneledKey(route, params);\n },\n validateMatchPathArgs,\n validateIsActiveRouteArgs(name, params, strict, ignoreQP) {\n // An assertion, so `name` is a string below by the type system rather\n // than by a second runtime check.\n validateIsActiveRouteArgs(name, params, strict, ignoreQP);\n reportMisChanneledKey(name, params);\n },\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 forwardTo: upd.forwardTo,\n defaultParams: upd.defaultParams,\n defaultSearch: upd.defaultSearch,\n decodeParams: upd.decodeParams,\n encodeParams: upd.encodeParams,\n canActivate: upd.canActivate,\n canDeactivate: upd.canDeactivate,\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(deps, methodName) {\n validateDependenciesObject(deps, methodName);\n\n // ⚑ The limit, asked here rather than from inside the ingest loop\n // (#2253). This is core's only pre-flight call on the whole bag, so it\n // is the one position where a refusal can precede every write. The\n // shape check above runs first: a bag that is not a plain object, or\n // carries a getter, is refused for what it IS before it is measured.\n validateDependencyBatchLimit(\n deps,\n ctx.dependenciesGetStore(),\n methodName,\n );\n },\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 =\n ctx.getOptions().limits?.maxPlugins ?? CORE_LIMIT_DEFAULTS.maxPlugins;\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 },\n lifecycle: {\n validateHandler,\n validateHandlerLimit(count, methodName) {\n const maxHandlers =\n ctx.getOptions().limits?.maxLifecycleHandlers ??\n CORE_LIMIT_DEFAULTS.maxLifecycleHandlers;\n\n validateHandlerLimit(count, methodName, maxHandlers);\n },\n validateCountThresholds(count, methodName) {\n const maxHandlers =\n ctx.getOptions().limits?.maxLifecycleHandlers ??\n CORE_LIMIT_DEFAULTS.maxLifecycleHandlers;\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(opts) {\n validateNavigateToDefaultArgs(opts);\n // ⚑ HERE, and not on every navigation. No door READS the watched bags\n // after construction — core runs on the copy adoption took (#2171) — so\n // the question is where a stale one would have CHANGED the outcome, and\n // this is the only door where those slots take effect. Measured: with a\n // counting getter on the bag, `start`, `navigate` and `buildPath` add\n // zero reads, and `buildPath` refuses a missing param rather than filling\n // it from the option. An application that never calls this is never\n // charged for the check.\n defaultsWatch.check(ctx.logger);\n },\n validateNavigateToStateArgs,\n validateNavigationOptions,\n validateParams: validateNavigateParams,\n validateParamsShape: validateNavigateParamsShape,\n validateSearch,\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 =\n ctx.getOptions().limits?.maxListeners ??\n CORE_LIMIT_DEFAULTS.maxListeners;\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 freezeThrownError(\n new RouterError(\"VALIDATION_PLUGIN_AFTER_START\", {\n message: \"validation-plugin must be registered before router.start()\",\n }),\n );\n }\n\n // ⚑ One router, one validator (#2349). `RouterInternals.validator` is a\n // single slot and `teardown` clears it, so with two installs either\n // teardown leaves a registered plugin whose validator no longer answers.\n // The refusal keeps that state unrepresentable; a tally of installs only\n // models an ownership the slot does not grant.\n //\n // ⚠ A CLONE needs no install of its own: `cloneRouter` re-runs plugin\n // factories by contract, which is what the message points at.\n if (ctx.validator !== null) {\n throw freezeThrownError(\n new RouterError(\"VALIDATION_PLUGIN_ALREADY_INSTALLED\", {\n message:\n \"validation-plugin is already installed on this router — \" +\n \"a clone re-runs plugin factories, so it needs no usePlugin of its own\",\n }),\n );\n }\n\n // RouterInternals.validator is now mutable — direct assignment works\n const defaultsWatch = new DefaultsMutationWatch();\n\n // ⚠ BEFORE the validator goes live, so the first `navigateToDefault` after\n // this line already has a baseline to compare against.\n defaultsWatch.watch(getPluginApi(router).getAdoptedOrigins());\n\n ctx.validator = buildValidatorObject(ctx, defaultsWatch);\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 // ⚠ At START, not at registration. The tree can still grow between the two —\n // `usePlugin` must run before `start()` here — so a check at registration\n // would report a guard for a route the caller is about to add. #2049 states\n // why the DOOR cannot answer it either.\n //\n // ⚠ An INTERCEPTOR, not an `onStart` hook, and the difference is measured: a\n // plugin hook is wired as a `$start` LISTENER, so it spends one of the\n // router's `maxListeners`. That budget is advertised (10000 by default) and\n // pinned by `limits.test.ts`, which registers exactly that many and expects\n // the next to throw — an `onStart` here turns the advertised number into\n // 9999 for every application that installs this plugin. Interceptors are\n // counted against nothing.\n const removeInterceptor = getPluginApi(router).addInterceptor(\n \"start\",\n (next, path) => {\n // ⚠ The diagnostic must NEVER be worse than its own absence. A start\n // interceptor WRAPS the call, so an unguarded throw here rejects\n // `start()` and leaves the router inactive — measured, core turns it\n // into \"a `start` interceptor returned without calling next()\". A\n // `$start` listener would have swallowed it instead, so this catch is\n // what buys the same safety explicitly rather than by accident.\n /* v8 ignore start -- @preserve: the catch exists so a bug in the\n diagnostic cannot reject `start()`; by construction nothing above it\n throws, so the arm has no reachable input. Removing it is the change\n this comment exists to argue against. */\n try {\n warnOrphanedGuards(ctx.routeGetStore(), ctx.logger);\n } catch {\n // A broken diagnostic is not the application's problem.\n }\n /* v8 ignore stop */\n\n return next(path);\n },\n );\n\n return {\n teardown() {\n removeInterceptor();\n ctx.validator = null;\n },\n };\n };\n}\n"],"mappings":"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"}