@real-router/validation-plugin 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.mts.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +5 -4
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["getPrototypeOf","objectValues","hasOwn","raiser","obj","objectEntries","LOGGER_CTX","raiser","objectKeys","getOwnPropertyDescriptor","getPrototypeOf","isPlainBag","warnOverwrite","raiser","events","atAddRoute","raiser","objectKeys","buildParamMeta","raiser","raiser","atNavigateToDefault","hasOwn","getPrototypeOf","objectEntries","hasOwn","objectKeys","raiser","raiser","raiser","atValidationPlugin","coreResolveForwardChain","assertNotAsync","raiser","atAddRoute","findMisChanneledKey","raiser","raiser","getInternals","getPluginApi","getRoutesApi"],"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/forwardTo.ts","../../src/validators/lifecycle.ts","../../src/validators/navigation.ts","../../src/validators/options.ts","../../src/validators/plugins.ts","../../src/validators/retrospective.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\nimport { raiser } from \"@real-router/core/utils\";\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 const at = raiser(\"router\", methodName);\n\n return at.type`${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 { raiser } from \"@real-router/core/utils\";\n\nimport { computeThresholds } from \"../helpers\";\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { RouterLogger } from \"@real-router/core\";\n\nconst atCloneRouter = raiser(\"cloneRouter\");\nconst atGetDependency = raiser(\"router\", \"getDependency\");\nconst atSetDependency = raiser(\"router\", \"setDependency\");\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 const at = raiser(\"router\", methodName);\n\n throw at.type`dependency name must be a string, got ${typeof name}`;\n }\n}\n\nexport function validateSetDependencyArgs(\n name: unknown,\n): asserts name is string {\n if (typeof name !== \"string\") {\n throw atSetDependency.type`dependency name must be a string, got ${typeof name}`;\n }\n}\n\nexport function validateDependenciesObject(\n deps: unknown,\n methodName: string,\n): asserts deps is Record<string, unknown> {\n if (!isPlainBag(deps)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid argument: expected plain object, received ${getTypeDescription(deps)}`;\n }\n\n for (const key of objectKeys(deps)) {\n if (getOwnPropertyDescriptor(deps, key)?.get) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Getters not allowed: \"${key}\"`;\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 heldKeys: readonly string[],\n maxDependencies: number,\n methodName: string,\n): void {\n if (maxDependencies === 0) {\n return;\n }\n\n // ⚑ The size and the membership test come from the SAME list (#1815 /\n // #2064) — `PluginApi.getDependencyKeys()`, one `Object.keys` of core's\n // record — so they cannot disagree on a key the count refuses to see.\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 const at = raiser(\"router\", methodName);\n\n throw at.range`Dependency limit exceeded (${maxDependencies}). Current: ${currentCount}, this batch adds ${added}.`;\n }\n}\n\nexport function validateDependencyExists(\n value: unknown,\n dependencyName: string,\n): asserts value is NonNullable<unknown> {\n if (value === undefined) {\n throw atGetDependency.ref`dependency \"${dependencyName}\" not found`;\n }\n}\n\nexport function validateDependencyCount(\n currentCount: number,\n maxDependencies: number,\n methodName: string,\n logger: RouterLogger,\n): void {\n if (maxDependencies === 0) {\n return;\n }\n\n const { warn, error } = computeThresholds(maxDependencies);\n\n if (currentCount >= maxDependencies) {\n const at = raiser(\"router\", methodName);\n\n throw at.range`Dependency limit exceeded (${maxDependencies}). Current: ${currentCount}.`;\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 atCloneRouter.type`Invalid dependencies: expected plain object or undefined, received ${typeof dependencies}`;\n }\n\n for (const key of objectKeys(dependencies)) {\n if (getOwnPropertyDescriptor(dependencies, key)?.get) {\n throw atCloneRouter.type`Getters not allowed in dependencies: \"${key}\"`;\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\";\nimport { raiser } from \"@real-router/core/utils\";\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\nconst atAddEventListener = raiser(\"router\", \"addEventListener\");\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 atAddEventListener.type`Invalid event name: ${String(eventName)}. Must be one of: $start, $stop, $$start, $$leaveApprove, $$cancel, $$success, $$error`;\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 atAddEventListener.type`callback must be a function, got ${typeof cb}`;\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/forwardTo.ts\n\nimport { resolveForwardChain } from \"@real-router/core\";\nimport { internalDefect, raiser, putField } from \"@real-router/core/utils\";\nimport { buildParamMeta } from \"@real-router/core/validation\";\n\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { Route, DefaultDependencies } from \"@real-router/core\";\n\nconst atAddRoute = raiser(\"router\", \"addRoute\");\n\n/**\n * What a route validator asks about routes that ALREADY exist (#2382).\n *\n * ⚑ Two questions, both data — so the route matcher is not handed to them. For\n * the registered table both are answered from the curated surface: existence\n * walks `PluginApi.getTree()`, and the path slots come from\n * `PluginApi.getUrlParams`. The table a `replace()` batch joins holds no route,\n * and its lookup answers accordingly (#2562).\n */\nexport interface RouteLookup {\n hasRoute: (name: string) => boolean;\n getUrlParams: (name: string) => readonly string[];\n}\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\n/**\n * A batch route's name as the table holds it: `${parent}.${name}` under\n * `{ parent }` (#2566), which is also how a nested route is named.\n *\n * ⚠ Built from strings only, joined as core joins them (an empty parent adds\n * nothing). Both batch walks run before the names are judged, so either part\n * may still be anything; a name that is not a string is passed on as it is.\n */\nexport function nameInTable<Name>(\n parentName: unknown,\n name: Name,\n): Name | string {\n return typeof parentName === \"string\" &&\n parentName !== \"\" &&\n typeof name === \"string\"\n ? `${parentName}.${name}`\n : name;\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 atAddRoute.type`forwardTo must be a string or function for route \"${fullName}\", got ${getTypeDescription(forwardTo)}`;\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 atAddRoute.type`forwardTo callback cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`;\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 atAddRoute.type`${slot} must be an object for route \"${fullName}\", got ${getTypeDescription(bag)}`;\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 atAddRoute.type`canActivate must be a function for route \"${fullName}\", got ${getTypeDescription(route.canActivate)}`;\n }\n\n if (\n route.canDeactivate !== undefined &&\n typeof route.canDeactivate !== \"function\"\n ) {\n throw atAddRoute.type`canDeactivate must be a function for route \"${fullName}\", got ${getTypeDescription(route.canDeactivate)}`;\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 // A codec that is not a function is `validateRoute`'s to refuse; reading its\n // `constructor` here would throw first on one that has none.\n const { decodeParams, encodeParams } = route;\n\n if (\n typeof decodeParams === \"function\" &&\n decodeParams.constructor.name === \"AsyncFunction\"\n ) {\n throw atAddRoute.type`decodeParams cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`;\n }\n\n if (\n typeof encodeParams === \"function\" &&\n encodeParams.constructor.name === \"AsyncFunction\"\n ) {\n throw atAddRoute.type`encodeParams cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`;\n }\n\n validateForwardToProperty(route.forwardTo, fullName);\n\n if (route.children) {\n for (const child of route.children) {\n const childFullName = nameInTable(fullName, child.name);\n\n validateRouteProperties(child, childFullName);\n }\n }\n}\n\n// ============================================================================\n// ForwardTo Validation\n// ============================================================================\n\n/**\n * The URL params a batch route's paths declare, read with core's\n * `buildParamMeta` — what `getUrlParams` is built from for a route the table\n * holds (#2569).\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 buildParamMeta(path).urlParams) {\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 internalDefect.plain`collectPathsToRoute: route \"${routeName}\" not found`;\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\n/** The paths from the parent down to a batch route, found by its full name. */\ntype BatchPaths = (fullName: string) => string[];\n\nfunction getTargetParams(\n targetRoute: string,\n exists: boolean,\n lookup: RouteLookup,\n pathsTo: BatchPaths,\n): Set<string> {\n if (exists) {\n return new Set(lookup.getUrlParams(targetRoute));\n }\n\n return extractParamsFromPaths(pathsTo(targetRoute));\n}\n\nconst EMPTY_PARENT_PARAMS: ReadonlySet<string> = new Set();\n\nfunction validateSingleForward(\n fromRoute: string,\n targetRoute: string,\n pathsTo: BatchPaths,\n batchNames: Set<string>,\n lookup: RouteLookup,\n parentParams: ReadonlySet<string>,\n): void {\n const exists = lookup.hasRoute(targetRoute);\n const existsInBatch = batchNames.has(targetRoute);\n\n if (!exists && !existsInBatch) {\n throw atAddRoute.ref`forwardTo target \"${targetRoute}\" does not exist for route \"${fromRoute}\"`;\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(pathsTo(fromRoute)),\n ]);\n\n const toParams = getTargetParams(targetRoute, exists, lookup, pathsTo);\n\n const missingParams = [...toParams].filter((param) => !fromParams.has(param));\n\n if (missingParams.length > 0) {\n throw atAddRoute.plain`forwardTo target \"${targetRoute}\" requires params [${missingParams.join(\", \")}] that are not available in source route \"${fromRoute}\"`;\n }\n}\n\nexport function validateForwardToTargets<\n Dependencies extends DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n existingForwardMap: Readonly<Record<string, string>>,\n lookup: RouteLookup,\n parentName?: string,\n): void {\n // ⚑ Added under { parent }, a batch route is the table's\n // `${parent}.${short}`, and a `forwardTo` names routes by those full names.\n // So every question about the batch — which names it holds, the paths down\n // to one of them, the key its forward sits under — starts from the parent\n // (#1224, #2566).\n const batchNames = collectRouteNames(routes, parentName);\n // The paths walk starts inside the batch, so the parent is cut off the name\n // once here rather than joined to every name the walk compares.\n const pathsTo: BatchPaths = (fullName) =>\n collectPathsToRoute(\n routes,\n parentName ? fullName.slice(parentName.length + 1) : fullName,\n );\n // `validateRoutes` throws on a missing parent before forwardTo validation runs,\n // so these are the parent's own slots, never the empty answer `getUrlParams`\n // gives for a route the tree does not hold.\n const parentParams: ReadonlySet<string> = parentName\n ? new Set(lookup.getUrlParams(parentName))\n : EMPTY_PARENT_PARAMS;\n\n const batchForwards = collectForwardMappings(routes, parentName);\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 pathsTo,\n batchNames,\n lookup,\n parentParams,\n );\n }\n\n for (const fromRoute of objectKeys(combinedForwardMap)) {\n resolveForwardChain(fromRoute, combinedForwardMap);\n }\n}\n","// packages/validation-plugin/src/validators/lifecycle.ts\n\nimport { raiser } from \"@real-router/core/utils\";\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Handler must be a boolean or factory function, got ${getTypeDescription(handler)}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.range`Lifecycle handler limit exceeded (${maxLifecycleHandlers}). This indicates too many routes with individual handlers. Consider using plugins for cross-cutting concerns.`;\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 { raiser } from \"@real-router/core/utils\";\n\nimport {\n getTypeDescription,\n isNavigationOptions,\n isParams,\n isString,\n} from \"../type-guards\";\n\nimport type { NavigationOptions } from \"@real-router/core\";\n\nconst atStart = raiser(\"router\", \"start\");\nconst atNavigateToState = raiser(\"router\", \"navigateToState\");\nconst atNavigateToDefault = raiser(\"router\", \"navigateToDefault\");\nconst atNavigate = raiser(\"router\", \"navigate\");\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 atNavigate.type`Invalid route name: expected string, got ${getTypeDescription(name)}`;\n }\n}\n\nexport function validateNavigateToDefaultArgs(opts: unknown): void {\n if (opts !== undefined && (typeof opts !== \"object\" || opts === null)) {\n throw atNavigateToDefault.type`Invalid options: ${getTypeDescription(opts)}. Expected NavigationOptions object.`;\n }\n}\n\nexport function validateNavigateToStateArgs(state: unknown): void {\n if (typeof state !== \"object\" || state === null) {\n throw atNavigateToState.type`Invalid state: ${getTypeDescription(state)}. Expected State object.`;\n }\n\n const candidate = state as { name: unknown; params: unknown; path: unknown };\n\n if (!isString(candidate.name)) {\n throw atNavigateToState.type`Invalid state.name: ${getTypeDescription(candidate.name)}. Expected string.`;\n }\n if (!isParams(candidate.params)) {\n throw atNavigateToState.type`Invalid state.params: ${getTypeDescription(candidate.params)}. Expected plain object.`;\n }\n if (!isString(candidate.path)) {\n throw atNavigateToState.type`Invalid state.path: ${getTypeDescription(candidate.path)}. Expected string.`;\n }\n}\n\nexport function validateNavigationOptions(\n opts: unknown,\n methodName: string,\n): asserts opts is NavigationOptions {\n if (!isNavigationOptions(opts)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid options: ${getTypeDescription(opts)}. Expected NavigationOptions object.`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`param \"${key}\" cannot be a ${valueType} — it does not round-trip through the URL path. Use a string, number, or boolean.`;\n }\n\n if (valueType === \"string\" && CONTROL_CHARS_RE.test(value as string)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`param \"${key}\" must not contain control characters (NUL / C0 / DEL) — they corrupt the URL path.`;\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 = params !== null && (getPrototypeOf(params) as object | null);\n\n if (Array.isArray(params) || (proto !== null && proto !== Object.prototype)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`params must be a plain object, got ${getTypeDescription(params)}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`params must be a plain object, got ${getTypeDescription(params)}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`search must be a plain object, got ${getTypeDescription(search)}`;\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 atStart.type`path must be a string, got ${getTypeDescription(path)}.`;\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 atStart.type`path must not contain control characters (NUL / C0 / DEL).`;\n }\n if (path !== \"\" && !path.startsWith(\"/\")) {\n throw atStart.type`path must start with \"/\", got \"${path}\".`;\n }\n }\n}\n","// packages/validation-plugin/src/validators/options.ts\n\nimport { raiser } from \"@real-router/core/utils\";\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 shape #2091 names. 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] 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 const at = raiser(\"router\", methodName);\n\n throw at.type`limit \"${limitName}\" must be an integer, got ${String(value)}`;\n }\n\n const bounds = LIMIT_BOUNDS[limitName];\n\n if (value < bounds.min || value > bounds.max) {\n const at = raiser(\"router\", methodName);\n\n throw at.range`limit \"${limitName}\" must be between ${bounds.min} and ${bounds.max}, got ${value}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`invalid limits: expected plain object, got ${typeof limits}`;\n }\n\n for (const [key, value] of objectEntries(limits)) {\n if (!hasOwn(LIMIT_BOUNDS, key)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`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 const at = raiser(\"router\", methodName);\n\n throw at.range`\"limits.warnListeners\" (${warnListeners}) must not exceed \"limits.maxListeners\" (${maxListeners}) — the warning channel would be unreachable`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"${optionName}\": \"${display}\". Must be one of: ${validList}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"defaultRoute\": expected string or function, got ${typeof defaultRoute}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"${optionName}\": expected plain object or function, got ${typeof value}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"queryParams\": expected plain object`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"queryParams.${key}\": unknown option`;\n }\n }\n}\n\nexport function validateOptions(options: unknown, methodName: string): void {\n if (!options || typeof options !== \"object\" || Array.isArray(options)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid options: expected plain object`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"allowNotFound\": expected boolean, got ${typeof opts.allowNotFound}`;\n }\n\n if (\n opts.rewritePathOnMatch !== undefined &&\n typeof opts.rewritePathOnMatch !== \"boolean\"\n ) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"rewritePathOnMatch\": expected boolean, got ${typeof opts.rewritePathOnMatch}`;\n }\n\n if (\n opts.caseSensitive !== undefined &&\n typeof opts.caseSensitive !== \"boolean\"\n ) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"caseSensitive\": expected boolean, got ${typeof opts.caseSensitive}`;\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 { raiser } from \"@real-router/core/utils\";\n\nimport { computeThresholds, CORE_LIMIT_DEFAULTS } from \"../helpers\";\n\nimport type { RouterLogger } from \"@real-router/core\";\n\nconst atUsePlugin = raiser(\"router\", \"usePlugin\");\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 atUsePlugin.range`Plugin limit exceeded (${maxPlugins}). Current: ${currentCount}, Attempting to add: ${newCount}. This indicates an architectural problem. Consider consolidating plugins.`;\n }\n}\n\nexport function validateNoDuplicatePlugins(\n factory: unknown,\n factories: unknown[],\n): void {\n if (factories.includes(factory)) {\n throw atUsePlugin.plain`Plugin factory already registered. To re-register, first unsubscribe the existing plugin.`;\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 atUsePlugin.type`Unknown property '${key}'. Plugin must only contain event handlers and optional teardown.`;\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\";\nimport { raiser } from \"@real-router/core/utils\";\n\nimport type { RouteLookup } from \"./forwardTo\";\nimport type { LimitsConfig } from \"@real-router/core\";\nimport type { RouterLogger } from \"@real-router/core/types\";\n\nconst atNavigateToDefault = raiser(\"router\", \"navigateToDefault\");\nconst atValidationPlugin = raiser(\"validation-plugin\");\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 once, at `usePlugin()` time, over what the\n * router already holds. Called by the validation plugin in a try/catch with\n * rollback.\n *\n * Every function takes FACTS read from the published surface (#2382): the\n * routes as `RoutesApi.get` reports them, the one-hop forward map, the resolved\n * limits, the dependency names. None of them receives a core store.\n */\n\n/**\n * A route as `RoutesApi.get` reports it, with the fields this pass judges typed\n * `unknown` — the pass exists to catch values the declared type rules out.\n */\ninterface RouteFacts {\n name: string;\n path: string;\n forwardTo?: unknown;\n defaultParams?: unknown;\n defaultSearch?: unknown;\n decodeParams?: unknown;\n encodeParams?: unknown;\n children?: readonly RouteFacts[];\n}\n\n// =============================================================================\n// Private helpers\n// =============================================================================\n\nfunction walkRoutes(\n routes: readonly RouteFacts[],\n callback: (route: RouteFacts, fullName: string) => void,\n parentName = \"\",\n): void {\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n callback(route, fullName);\n\n if (route.children) {\n walkRoutes(route.children, callback, fullName);\n }\n }\n}\n\n/** One slot of every route, in walk order, skipping routes that leave it unset. */\nfunction collectSlot(\n routes: readonly RouteFacts[],\n pick: (route: RouteFacts) => unknown,\n): [string, unknown][] {\n const entries: [string, unknown][] = [];\n\n walkRoutes(routes, (route, fullName) => {\n const value = pick(route);\n\n if (value !== undefined) {\n entries.push([fullName, value]);\n }\n });\n\n return entries;\n}\n\n/**\n * Wraps core's `resolveForwardChain` so a retrospective refusal names the pass\n * that raised it. The retrospective walk is not a `router.*` call — it runs over\n * a table that is already registered — and `README.md` documents\n * `[validation-plugin]` for this pass.\n *\n * ⚠ Core's own message opens with `[router] ` (#2456), and that head is\n * REPLACED rather than stacked: two prefixes on one message name two subsystems\n * for one fault.\n */\nfunction resolveForwardChainWithPrefix(\n startRoute: string,\n forwardMap: Readonly<Record<string, string>>,\n): string {\n try {\n return coreResolveForwardChain(startRoute, forwardMap);\n } catch (error) {\n const bare = (error as Error).message.replace(/^\\[router\\] /u, \"\");\n\n throw atValidationPlugin.plain({ cause: error })`${bare}`;\n }\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 atValidationPlugin.type`Route \"${routeName}\" ${label} cannot be async`;\n }\n}\n\n// =============================================================================\n// 1. validateExistingRoutes\n// =============================================================================\n\n/**\n * Validates the existing routes for structural integrity.\n * Walks every route, 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 routes - Every top-level route, nested, as `RoutesApi.get` reports it\n * @throws {TypeError} If a route has structural issues\n */\nexport function validateExistingRoutes(routes: readonly RouteFacts[]): void {\n walkRoutes(routes, (route, fullName) => {\n if (typeof route.name !== \"string\" || !route.name) {\n throw atValidationPlugin.type`validateExistingRoutes: route has invalid name: ${route.name}`;\n }\n\n // ⚑ No dotted-name check here, and its absence is load-bearing rather\n // than a cleanup. Bare core refuses a dotted route name at registration,\n // with this package's message (#1763), so `createRouter` throws before a\n // plugin exists and nothing dotted can reach this pass. The names walked\n // here come off the TREE, whose nested children carry bare names by\n // construction.\n\n if (typeof route.path !== \"string\") {\n throw atValidationPlugin.type`validateExistingRoutes: route \"${fullName}\" has non-string path (${typeof route.path})`;\n }\n });\n}\n\n// =============================================================================\n// 2. validateForwardToConsistency\n// =============================================================================\n\n/**\n * Validates forwardTo consistency across all chains.\n * Checks target existence, param compatibility, and circular chain detection.\n * Adapted from: validateForwardToTargets() in forwardToValidation.ts\n *\n * @param forwardMap - The ONE-HOP forward map, from `PluginApi.getForwardMap()`\n * @param lookup - Existence and path slots of routes that already exist\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(\n forwardMap: Readonly<Record<string, string>>,\n lookup: RouteLookup,\n): void {\n // Check target existence and param compatibility for each static mapping\n for (const [fromRoute, targetRoute] of objectEntries(forwardMap)) {\n if (!lookup.hasRoute(targetRoute)) {\n throw atValidationPlugin.plain`validateForwardToConsistency: forwardTo target \"${targetRoute}\" does not exist in tree (source route: \"${fromRoute}\")`;\n }\n\n // Validate param compatibility: target must not require params absent in source\n const sourceParams = new Set(lookup.getUrlParams(fromRoute));\n const missingParams = [...new Set(lookup.getUrlParams(targetRoute))].filter(\n (param) => !sourceParams.has(param),\n );\n\n if (missingParams.length > 0) {\n throw atValidationPlugin.plain`validateForwardToConsistency: forwardTo target \"${targetRoute}\" requires params [${missingParams.join(\", \")}] not available in source route \"${fromRoute}\"`;\n }\n }\n\n // Detect cycles in the full forwardMap (catches multi-hop cycles)\n for (const fromRoute of objectKeys(forwardMap)) {\n resolveForwardChainWithPrefix(fromRoute, forwardMap);\n }\n}\n\n// =============================================================================\n// 3. validateRouteProperties\n// =============================================================================\n\n/** Every entry of a default-bag slot is a plain object, or the route is bad. */\nfunction assertPlainBagSlot(\n slot: [string, unknown][],\n slotName: \"defaultParams\" | \"defaultSearch\",\n): void {\n for (const [routeName, bag] of slot) {\n if (bag === null || typeof bag !== \"object\" || Array.isArray(bag)) {\n throw atValidationPlugin.type`validateRoutePropertiesStore: route \"${routeName}\" ${slotName} must be a plain object, got ${Array.isArray(bag) ? \"array\" : typeof bag}`;\n }\n }\n}\n\n/**\n * Validates route properties for every registered route.\n * Checks decoder/encoder types, defaultParams structure, and async forwardTo callbacks.\n * Adapted from: validateRouteProperties() in forwardToValidation.ts\n *\n * @param routes - Every top-level route, nested, as `RoutesApi.get` reports it\n * @throws {TypeError} If any registered decoder/encoder is not a valid sync function\n * @throws {TypeError} If any defaultParams is not a plain object\n * @throws {TypeError} If any forwardTo callback is async\n */\nexport function validateRoutePropertiesStore(\n routes: readonly RouteFacts[],\n): void {\n const decoders = collectSlot(routes, (route) => route.decodeParams);\n const encoders = collectSlot(routes, (route) => route.encodeParams);\n // A string `forwardTo` is a target name, judged by the forward-map checks.\n const forwardCallbacks = collectSlot(routes, (route) =>\n typeof route.forwardTo === \"string\" ? undefined : route.forwardTo,\n );\n\n // Validate decoders — must be non-async functions (sync required for matchPath/buildPath)\n for (const [routeName, decoder] of decoders) {\n if (typeof decoder !== \"function\") {\n throw atValidationPlugin.type`validateRoutePropertiesStore: route \"${routeName}\" decoder must be a function, got ${typeof decoder}`;\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 encoders) {\n if (typeof encoder !== \"function\") {\n throw atValidationPlugin.type`validateRoutePropertiesStore: route \"${routeName}\" encoder must be a function, got ${typeof encoder}`;\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(\n collectSlot(routes, (route) => route.defaultParams),\n \"defaultParams\",\n );\n assertPlainBagSlot(\n collectSlot(routes, (route) => route.defaultSearch),\n \"defaultSearch\",\n );\n\n // Validate forwardTo function callbacks — must be non-async functions\n for (const [routeName, callback] of forwardCallbacks) {\n if (typeof callback !== \"function\") {\n throw atValidationPlugin.type`validateRoutePropertiesStore: route \"${routeName}\" forwardTo callback must be a function, got ${typeof callback}`;\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 forwardMap - The ONE-HOP forward map, from `PluginApi.getForwardMap()`\n * @param lookup - Existence of routes that already exist\n * @throws {Error} If any forwardTo target route does not exist in the tree\n */\nexport function validateForwardToTargetsStore(\n forwardMap: Readonly<Record<string, string>>,\n lookup: RouteLookup,\n): void {\n for (const [fromRoute, targetRoute] of objectEntries(forwardMap)) {\n if (!lookup.hasRoute(targetRoute)) {\n throw atValidationPlugin.plain`validateForwardToTargetsStore: forwardTo target \"${targetRoute}\" does not exist for route \"${fromRoute}\"`;\n }\n }\n}\n\n// =============================================================================\n// 5. validateDependenciesStructure\n// =============================================================================\n\n/**\n * Validates the dependency record and the resolved limits.\n *\n * ⚠ The getter walk reads core's LIVE dependency record, the one read of a\n * core store this pass makes. Core refuses a getter at every dependency\n * door, so the only way one lands there is `Object.defineProperty` through the\n * record `getInternals(router).dependenciesGetStore()` hands out, and the walk\n * exists for as long as that door does (#2386).\n *\n * @param dependencies - Core's dependency record\n * @param limits - The resolved limits, from `PluginApi.getResolvedLimits()`\n * @throws {TypeError} If a dependency is a getter\n * @throws {TypeError} If a limit is not an integer\n */\nexport function validateDependenciesStructure(\n dependencies: object,\n limits: Readonly<LimitsConfig>,\n): void {\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 atValidationPlugin.type`validateDependenciesStructure: dependency \"${key}\" must not use a getter`;\n }\n }\n\n for (const [key, value] of objectEntries(limits)) {\n // ⚑ `Number.isInteger`, not `typeof === \"number\"`. Core coerces the caller's\n // limits ONCE at construction (#1875), so every resolved limit is already\n // `typeof \"number\"` — and `Number(undefined)`, `Number(\"abc\")` and\n // `Number({})` are all `NaN`, which passes a `typeof` test. The values\n // worth rejecting are spread across that type; `retrospective.test.ts`\n // owns which ones and reds on both `typeof` and `isFinite`. This also lands\n // the check on the same predicate `validateLimitValue` already uses, so the\n // two mirrors agree.\n if (!Number.isInteger(value)) {\n throw atValidationPlugin.type`validateDependenciesStructure: deps.limits.${key} must be an integer, got ${String(value)}`;\n }\n }\n}\n\n// =============================================================================\n// 6. validateLimitsConsistency\n// =============================================================================\n\n/**\n * Validates that the dependency count does not exceed the configured limit.\n * Adapted from: validateLimits() in OptionsNamespace/validators.ts\n *\n * @param options - Router options (typed as unknown to avoid core coupling)\n * @param dependencyCount - How many dependencies the router holds\n * @param resolvedMaxDependencies - `maxDependencies` from the resolved limits\n * @throws {RangeError} If dependency count exceeds maxDependencies limit (#1225:\n * `>` not `>=` — an at-limit store is legal, mirroring the live limiter)\n */\nexport function validateLimitsConsistency(\n options: unknown,\n dependencyCount: number,\n resolvedMaxDependencies: number,\n): void {\n const opts =\n options && typeof options === \"object\"\n ? (options as Record<string, unknown>)\n : {};\n const configuredLimits =\n opts.limits && typeof opts.limits === \"object\"\n ? (opts.limits as Record<string, unknown>)\n : {};\n const maxDepsFromOptions = configuredLimits.maxDependencies;\n const maxDeps =\n typeof maxDepsFromOptions === \"number\"\n ? maxDepsFromOptions\n : resolvedMaxDependencies;\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 (maxDeps > 0 && dependencyCount > maxDeps) {\n throw atValidationPlugin.range`validateLimitsConsistency: dependency count (${dependencyCount}) exceeds maxDependencies limit (${maxDeps})`;\n }\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 */\n/** Shared predicate; each door owns its own head, so no prefix travels as data. */\nfunction namesNoRoute(\n routeName: unknown,\n lookup: RouteLookup,\n): routeName is string {\n return (\n typeof routeName === \"string\" &&\n routeName !== \"\" &&\n !lookup.hasRoute(routeName)\n );\n}\n\n/**\n * The retrospective pass, for a `defaultRoute` configured as a STRING. No call\n * reaches it — the sweep runs over a table that is already registered — so the\n * refusal names the package rather than a door.\n */\nexport function validateConfiguredDefaultRoute(\n routeName: unknown,\n lookup: RouteLookup,\n): void {\n if (namesNoRoute(routeName, lookup)) {\n throw atValidationPlugin.plain`defaultRoute resolved to non-existent route: \"${routeName}\"`;\n }\n}\n\n/**\n * The runtime pass, for a `defaultRoute` configured as a CALLBACK. Reached from\n * `navigateToDefault()` alone, so the caller made a call they can look up and the\n * refusal names it. ⚠ It arrives as a rejected promise: the `catch` around\n * `resolveDefault()` preserves the user callback's throw shape.\n */\nexport function validateResolvedDefaultRoute(\n routeName: unknown,\n lookup: RouteLookup,\n): void {\n if (namesNoRoute(routeName, lookup)) {\n throw atNavigateToDefault.plain`defaultRoute callback resolved to non-existent route: \"${routeName}\"`;\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. `PluginApi.getExternalGuardNames()` hands\n * out exactly that origin, each name once.\n */\nexport function warnOrphanedGuards(\n guardNames: readonly string[],\n lookup: RouteLookup,\n logger: RouterLogger,\n): void {\n for (const name of guardNames) {\n if (!lookup.hasRoute(name)) {\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}\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 { raiser } from \"@real-router/core/utils\";\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 {\n nameInTable,\n validateForwardToTargets,\n validateRouteProperties,\n} from \"./forwardTo\";\nimport { validateNavigateParamsShape } from \"./navigation\";\n\nimport type { RouteLookup } from \"./forwardTo\";\nimport type {\n ForwardToCallback,\n Params,\n Route,\n RouteConfigUpdate,\n DefaultDependencies,\n} from \"@real-router/core\";\nimport type { RouteTree } from \"@real-router/core/validation\";\n\nconst atUpdateRoute = raiser(\"router\", \"updateRoute\");\nconst atAddRoute = raiser(\"router\", \"addRoute\");\nconst atShouldUpdateNode = raiser(\"router\", \"shouldUpdateNode\");\nconst atMatchPath = raiser(\"router\", \"matchPath\");\nconst atBuildPath = raiser(\"router\", \"buildPath\");\nconst atIsActiveRoute = raiser(\"router\", \"isActiveRoute\");\nconst atSetRootPath = raiser(\"router\", \"setRootPath\");\n\n// Internal constant (matches core's INTERNAL_ROUTE_PREFIX)\nconst INTERNAL_ROUTE_PREFIX = \"@@\";\n\nexport function throwIfInternalRoute(name: string, methodName: string): void {\n if (name.startsWith(INTERNAL_ROUTE_PREFIX)) {\n const at = raiser(\"router\", methodName);\n\n throw at.plain`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\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 @typescript-eslint/no-unnecessary-condition -- runtime safety guard\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 atSetRootPath.type`rootPath must be a string, got ${getTypeDescription(rootPath)}`;\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 atAddRoute.type`canActivate must be a function, got ${getTypeDescription(routeObj.canActivate)}`;\n }\n\n if (\n routeObj.canDeactivate !== undefined &&\n typeof routeObj.canDeactivate !== \"function\"\n ) {\n throw atAddRoute.type`canDeactivate must be a function, got ${getTypeDescription(routeObj.canDeactivate)}`;\n }\n}\n\nexport function guardNoAsyncCallbacks(route: unknown, fullName: unknown): void {\n const routeObj = route as {\n decodeParams?: unknown;\n encodeParams?: unknown;\n forwardTo?: unknown;\n };\n\n // Only a function is asked whether it is async: any other codec is\n // `validateRoute`'s to refuse, and `isAsyncFunction` reads its `constructor`.\n if (\n typeof routeObj.decodeParams === \"function\" &&\n isAsyncFunction(routeObj.decodeParams)\n ) {\n throw atAddRoute.type`decodeParams cannot be async for route \"${String(fullName)}\"`;\n }\n\n if (\n typeof routeObj.encodeParams === \"function\" &&\n isAsyncFunction(routeObj.encodeParams)\n ) {\n throw atAddRoute.type`encodeParams cannot be async for route \"${String(fullName)}\"`;\n }\n\n if (\n typeof routeObj.forwardTo === \"function\" &&\n isAsyncFunction(routeObj.forwardTo)\n ) {\n throw atAddRoute.type`forwardTo callback cannot be async for route \"${String(fullName)}\"`;\n }\n}\n\n/**\n * Validates addRoute arguments (route structure and properties).\n * State-dependent validation (duplicates, tree) happens in instance method.\n */\nexport function validateAddRouteArgs(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Route type\n routes: readonly Route<any>[],\n parentName?: string,\n): 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 atAddRoute.type`Route must be an object, got ${getTypeDescription(route)}`;\n }\n\n // Validate route properties (canActivate, canDeactivate, defaultParams, async checks)\n // Note: validateRouteProperties handles children recursively\n validateRouteProperties(route, nameInTable(parentName, 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 atAddRoute.type`parent option must be a non-empty string, got ${getTypeDescription(parent)}`;\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 atIsActiveRoute.type`name must be a string, got ${typeof name}`;\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 atIsActiveRoute.type`strictEquality must be a boolean, got ${typeof strictEquality}`;\n }\n\n // Validate ignoreQueryParams if provided\n if (\n ignoreQueryParams !== undefined &&\n typeof ignoreQueryParams !== \"boolean\"\n ) {\n throw atIsActiveRoute.type`ignoreQueryParams must be a boolean, got ${typeof ignoreQueryParams}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid routeName: ${getTypeDescription(routeName)}. Expected string.`;\n }\n\n if (!isParams(routeParams)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid routeParams: ${getTypeDescription(routeParams)}. Expected plain object.`;\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 atUpdateRoute.ref`Invalid name: empty string. Cannot update root node.`;\n }\n\n // Validate updates is not null\n\n if (updates === null) {\n throw atUpdateRoute.type`updates must be an object, got null`;\n }\n\n // Validate updates is an object (not array)\n if (typeof updates !== \"object\" || Array.isArray(updates)) {\n throw atUpdateRoute.type`updates must be an object, got ${getTypeDescription(updates)}`;\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 atUpdateRoute.type`${paramName} cannot be an async function`;\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 atUpdateRoute.type`${paramName} must be a function or null, got ${typeof value}`;\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 atUpdateRoute.type`forwardTo must be a string, function, or null, got ${getTypeDescription(forwardTo)}`;\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 atUpdateRoute.type`${paramName} must be an object or null, got ${getTypeDescription(value)}`;\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 atBuildPath.type`route must be a non-empty string, got ${typeof route === \"string\" ? '\"\"' : typeof route}`;\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 atMatchPath.type`path must be a string, got ${typeof path}`;\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 atShouldUpdateNode.type`nodeName must be a string, got ${typeof nodeName}`;\n }\n}\n\n/**\n * Validates a batch against the table it joins.\n * Checks parent existence, duplicates, and forwardTo targets/cycles.\n *\n * The table is the registered one for `add`, and an empty one under the kept\n * root for `replace` (#2562) — `validationPlugin.ts` picks it per door.\n *\n * @param routes - Routes to validate\n * @param tree - The tree of the table the batch joins\n * @param lookup - Existence and path slots of that table's routes\n * @param forwardMap - That table's ONE-HOP forward map\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 lookup: RouteLookup,\n forwardMap: Readonly<Record<string, string>>,\n parentName?: string,\n): void {\n // Validate parent route exists in tree\n if (parentName) {\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 atAddRoute.ref`Parent route \"${parentName}\" does not exist`;\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 validateForwardToTargets(routes, forwardMap, lookup, parentName);\n}\n\n// ============================================================================\n// Instance-level validators (moved from routesCrud.ts)\n// ============================================================================\n\n/**\n * Validates that forwardTo target doesn't require params that source doesn't have.\n *\n * ⚠ The `update` door is the only one that reaches this, so the message names\n * it. The batch doors carry their own copy in `forwardTo.ts`, which also\n * resolves a target the tree does not hold YET from the batch being registered\n * — and names `addRoute`, as the batch copy of this check does.\n *\n * @param sourceName - Source route name\n * @param targetName - Target route name\n * @param lookup - Path slots of routes that already exist\n */\nexport function validateForwardToParamCompatibility(\n sourceName: string,\n targetName: string,\n lookup: RouteLookup,\n): void {\n const sourceParams = new Set(lookup.getUrlParams(sourceName));\n\n // Check if target requires params that source doesn't have\n const missingParams = lookup\n .getUrlParams(targetName)\n .filter((param) => !sourceParams.has(param));\n\n if (missingParams.length > 0) {\n throw atUpdateRoute.plain`forwardTo target \"${targetName}\" requires params [${missingParams.join(\", \")}] that are not available in source route \"${sourceName}\"`;\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 forwardMap - The ONE-HOP forward map (never the resolved one)\n */\nexport function validateForwardToCycle(\n sourceName: string,\n targetName: string,\n forwardMap: Readonly<Record<string, string>>,\n): void {\n // Create a test map with the new entry to validate BEFORE mutation\n const testMap = {\n ...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 lookup - Existence and path slots of routes that already exist\n * @param forwardMap - The ONE-HOP forward map\n */\nexport function validateUpdateRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n name: string,\n forwardTo: string | ForwardToCallback<Dependencies> | null | undefined,\n lookup: RouteLookup,\n forwardMap: Readonly<Record<string, string>>,\n): void {\n // Validate route exists\n if (!lookup.hasRoute(name)) {\n throw atUpdateRoute.ref`route \"${name}\" does not exist`;\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 (!lookup.hasRoute(forwardTo)) {\n throw atUpdateRoute.plain`forwardTo target \"${forwardTo}\" does not exist`;\n }\n\n // Check forwardTo param compatibility\n validateForwardToParamCompatibility(name, forwardTo, lookup);\n\n // Check for cycle detection\n validateForwardToCycle(name, forwardTo, forwardMap);\n }\n}\n","// packages/validation-plugin/src/validators/state.ts\n\nimport { raiser } from \"@real-router/core/utils\";\n\nimport { isString, isParams, getTypeDescription } from \"../type-guards\";\n\nconst atMakeState = raiser(\"router\", \"makeState\");\n\nexport function validateMakeStateArgs(\n name: unknown,\n params: unknown,\n path: unknown,\n): void {\n if (!isString(name)) {\n throw atMakeState.type`Invalid name: ${getTypeDescription(name)}. Expected string.`;\n }\n\n if (params !== undefined && !isParams(params)) {\n throw atMakeState.type`Invalid params: ${getTypeDescription(params)}. Expected plain object.`;\n }\n\n if (path !== undefined && !isString(path)) {\n throw atMakeState.type`Invalid path: ${getTypeDescription(path)}. Expected string.`;\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 { getPluginApi, getRoutesApi } from \"@real-router/core/api\";\nimport { raiser } 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 { nameInTable } from \"./validators/forwardTo\";\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 validateConfiguredDefaultRoute,\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 { RouteLookup } from \"./validators/forwardTo\";\nimport type {\n DefaultDependencies,\n PluginApi,\n PluginFactory,\n RouterValidator,\n Route,\n RoutesApi,\n Plugin,\n} from \"@real-router/core\";\nimport type { RouteTree } from \"@real-router/core/validation\";\n\nconst atValidationPlugin = raiser(\"validation-plugin\");\nconst atAddRoute = raiser(\"router\", \"addRoute\");\nconst atAreStatesEqual = raiser(\"router\", \"areStatesEqual\");\n\n/** The one question existence asks of a tree node: its children by segment. */\ninterface TreeNode {\n children: ReadonlyMap<string, TreeNode>;\n}\n\n/**\n * The two questions the validators ask about routes that already exist,\n * answered from the curated surface (#2382).\n */\nfunction createRouteLookup(api: PluginApi): RouteLookup {\n // ⚑ Existence WALKS the published tree rather than asking the matcher:\n // `getRoutesApi.has` would do the lookup too, but it runs `validateRouteName`\n // first and would rename a malformed `forwardTo` target's refusal to\n // `[router.hasRoute]`.\n //\n // ⚠ The walk answers what `matcher.hasRoute` answers except after\n // `children.set` on a handed-out tree — the Map shell-freeze exception\n // `engine/INVARIANTS.md` records.\n return {\n hasRoute: (name) => {\n let node = api.getTree() as TreeNode | undefined;\n\n for (const segment of name.split(\".\")) {\n node = node?.children.get(segment);\n\n if (!node) {\n return false;\n }\n }\n\n return true;\n },\n getUrlParams: (name) => api.getUrlParams(name),\n };\n}\n\n/** What the route validators read about the table a batch joins. */\ninterface JoinedTable {\n tree: RouteTree;\n lookup: RouteLookup;\n forwardMap: Readonly<Record<string, string>>;\n}\n\n/**\n * The lookup of a table with no route in it. A `RouteLookup` is asked for the\n * path slots of a route that exists, so this one is never asked for them.\n */\nconst NO_ROUTES: RouteLookup = {\n hasRoute: () => false,\n /* v8 ignore next -- @preserve unreachable: the table holds no route to ask about */\n getUrlParams: () => [],\n};\n\n/**\n * The table a `replace()` batch joins (#2562): the root the router keeps, with\n * no route and no forward under it. `add` extends the registered table and\n * `replace` discards it, so the route checks judge a replace batch as they judge\n * the same batch added to an empty router under that root —\n * `replace-batch.properties.ts` holds the two doors to that.\n *\n * ⚠ `children` and `nonAbsoluteChildren` both list the root's routes. Only\n * `children` is read by a validator today; the second is emptied too, so a\n * check that starts reading it does not get the discarded routes back.\n */\nfunction emptyTableUnder(root: RouteTree): JoinedTable {\n return {\n tree: { ...root, children: new Map(), nonAbsoluteChildren: [] },\n lookup: NO_ROUTES,\n forwardMap: {},\n };\n}\n\n/**\n * Every top-level route, nested, as `RoutesApi.get` reports it — the config\n * slots the retrospective pass judges included.\n */\nfunction readRoutes<Dependencies extends DefaultDependencies>(\n api: PluginApi,\n routesApi: RoutesApi<Dependencies>,\n): Route<Dependencies>[] {\n const routes: Route<Dependencies>[] = [];\n\n for (const name of (api.getTree() as TreeNode).children.keys()) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- a top-level name of the tree always names a route\n routes.push(routesApi.get(name)!);\n }\n\n return routes;\n}\n\nfunction buildValidatorObject(\n api: PluginApi,\n lookup: RouteLookup,\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 api.getDeclaredQueryNames(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 validateRouteName(name, caller) {\n validateRouteName(name, caller);\n },\n validateSetRootPathArgs,\n },\n options: {\n validateOptions,\n validateResolvedDefaultRoute(routeName) {\n validateResolvedDefaultRoute(routeName, lookup);\n },\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 api.getDependencyKeys(),\n api.getResolvedLimits().maxDependencies,\n methodName,\n );\n },\n validateDependencyExists(name, value) {\n validateDependencyExistsRaw(value, name);\n },\n validateDependencyCount(currentCount, maxDependencies, methodName) {\n validateDependencyCount(\n currentCount,\n maxDependencies,\n methodName,\n api.logger,\n );\n },\n validateCloneArgs,\n warnOverwrite(name, methodName) {\n warnDepsOverwrite(name, methodName, api.logger);\n },\n warnBatchOverwrite(keys, methodName) {\n warnBatchOverwrite(keys, methodName, api.logger);\n },\n warnRemoveNonExistent(name) {\n warnRemoveNonExistent(name, api.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 api.getOptions().limits?.maxPlugins ?? CORE_LIMIT_DEFAULTS.maxPlugins;\n\n validatePluginCountThresholds(count, maxPlugins, api.logger);\n },\n warnBatchDuplicates() {\n warnBatchDuplicates(api.logger);\n },\n warnPluginMethodType(methodName) {\n warnPluginMethodType(methodName, api.logger);\n },\n },\n lifecycle: {\n validateHandlerLimit(count, methodName) {\n const maxHandlers =\n api.getOptions().limits?.maxLifecycleHandlers ??\n CORE_LIMIT_DEFAULTS.maxLifecycleHandlers;\n\n validateHandlerLimit(count, methodName, maxHandlers);\n },\n validateCountThresholds(count, methodName) {\n const maxHandlers =\n api.getOptions().limits?.maxLifecycleHandlers ??\n CORE_LIMIT_DEFAULTS.maxLifecycleHandlers;\n\n validateLifecycleCountThresholds(\n count,\n methodName,\n maxHandlers,\n api.logger,\n );\n },\n warnOverwrite(name, type, methodName) {\n warnLifecycleOverwrite(name, type, methodName, api.logger);\n },\n warnAsyncGuardSync(name, methodName) {\n warnAsyncGuardSync(name, methodName, api.logger);\n },\n },\n navigation: {\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(api.logger);\n },\n validateNavigateToStateArgs,\n validateNavigationOptions,\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 atAreStatesEqual.type`Invalid state1: ${getTypeDescription(s1)}. Expected State object.`;\n }\n if (!isState(s2)) {\n throw atAreStatesEqual.type`Invalid state2: ${getTypeDescription(s2)}. Expected State object.`;\n }\n if (ignoreQP !== undefined && !isBoolean(ignoreQP)) {\n throw atAreStatesEqual.type`Invalid ignoreQueryParams: ${getTypeDescription(ignoreQP)}. Expected boolean.`;\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 api.getOptions().limits?.maxListeners ??\n CORE_LIMIT_DEFAULTS.maxListeners;\n\n validateListenerCountThresholds(\n count,\n eventName,\n methodName,\n api.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 const api = getPluginApi(router);\n\n if (router.isActive()) {\n throw atValidationPlugin.code(\n \"VALIDATION_PLUGIN_AFTER_START\",\n )`must be registered before router.start()`;\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 atValidationPlugin.code(\n \"VALIDATION_PLUGIN_ALREADY_INSTALLED\",\n )`is already installed on this router — a clone re-runs plugin factories, so it needs no usePlugin of its own`;\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(api.getAdoptedOrigins());\n\n // ⚑ The object is held so teardown can prove it is still the holder.\n // The slot has no owner — `RouterInternals.validator` is plain data on a\n // surface pinned `accessorNames === []`, so a second writer cannot be\n // REFUSED here. What it can be is not silently destroyed: an\n // unconditional `= null` on teardown nulls whoever holds the slot now.\n // Measured: with a second writer in place, teardown destroyed the\n // foreign validator; the control (no second writer) nulled its own.\n // Same shape as `claimContextNamespace`, which checks the holder on\n // write and on release (#2059 / #1929) — here only the release half is\n // reachable, and the write half waits for the slot to go away.\n const lookup = createRouteLookup(api);\n const ownValidator = buildValidatorObject(api, lookup, defaultsWatch);\n // One branch, shared by the error path below and by `teardown`: both ask\n // the same question, so they share the site rather than each growing an\n // arm the other's test has to reach.\n const releaseIfStillOurs = () => {\n if (ctx.validator === ownValidator) {\n ctx.validator = null;\n }\n };\n\n ctx.validator = ownValidator;\n\n try {\n const routes = readRoutes(api, getRoutesApi(router));\n const forwardMap = api.getForwardMap();\n const limits = api.getResolvedLimits();\n const options = api.getOptions();\n\n validateExistingRoutes(routes);\n validateForwardToConsistency(forwardMap, lookup);\n validateRoutePropertiesStore(routes);\n validateForwardToTargetsStore(forwardMap, lookup);\n validateDependenciesStructure(\n ctx.dependenciesGetStore().dependencies,\n limits,\n );\n validateLimitsConsistency(\n options,\n api.getDependencyKeys().length,\n limits.maxDependencies,\n );\n ctx.validator.options.validateOptions(\n options,\n \"constructor (retrospective)\",\n );\n\n if (typeof options.defaultRoute === \"string\") {\n validateConfiguredDefaultRoute(options.defaultRoute, lookup);\n }\n } catch (error) {\n releaseIfStillOurs();\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 = api.addInterceptor(\"start\", (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(api.getExternalGuardNames(), lookup, api.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 // ⚠ A CHECK, not a `RouterValidator` member, and the distinction is the\n // argument rather than the style: core hands this position `ownParams` —\n // the copy it will print the path from — which no seam can reach, because\n // at the call boundary that object does not exist yet (#2134 / #2388).\n //\n // ⚠ Registered AFTER the retrospective pass above, so the error path's\n // `releaseIfStillOurs` leaves nothing registered behind it.\n const removeParamsCheck = api.addCheck(\"buildPath:params\", (ownParams) => {\n validateNavigateParams(ownParams, \"buildPath\");\n });\n\n // ⚠ The resolved printer is reached INDEPENDENTLY of the facade's — the\n // href door runs the forward chain itself and lands here — so it carries its\n // own registration rather than sharing one, and the message names it.\n const removeResolvedParamsCheck = api.addCheck(\n \"buildPathResolved:params\",\n (ownParams) => {\n validateNavigateParams(ownParams, \"buildPathResolved\");\n },\n );\n\n // ⚠ The calls inside each ENTRY check keep the order core consulted them\n // in: the first refusal is the message the caller gets, so reordering them\n // changes which defect a caller hears about when an argument has two.\n const removeCanNavigateToEntryCheck = api.addCheck(\n \"canNavigateTo:entry\",\n (name, params, search) => {\n validateRouteName(name, \"canNavigateTo\");\n validateNavigateParamsShape(params, \"canNavigateTo\");\n validateSearch(search, \"canNavigateTo\");\n },\n );\n\n const removeCanNavigateToParamsCheck = api.addCheck(\n \"canNavigateTo:params\",\n (ownParams) => {\n validateNavigateParams(ownParams, \"canNavigateTo\");\n },\n );\n\n const removeNavigateEntryCheck = api.addCheck(\n \"navigate:entry\",\n (routeName, routeParams, search, options) => {\n validateNavigateArgs(routeName);\n validateNavigateParamsShape(routeParams, \"navigate\");\n validateSearch(search, \"navigate\");\n validateNavigationOptions(options, \"navigate\");\n },\n );\n\n const removeNavigateParamsCheck = api.addCheck(\n \"navigate:params\",\n (ownParams) => {\n validateNavigateParams(ownParams, \"navigate\");\n },\n );\n\n // ⚠ The callback walk runs FIRST at both batch positions, because that is\n // where core ran it: a route whose `canActivate` is not a function is\n // refused before anything asks about names or paths.\n const walkRouteCallbacks = (\n batch: readonly Route[],\n parentName: unknown,\n ): void => {\n for (const route of batch) {\n const fullName = nameInTable(parentName, route.name);\n\n guardRouteCallbacks(route);\n guardNoAsyncCallbacks(route, fullName);\n\n if (route.children !== undefined) {\n walkRouteCallbacks(route.children, fullName);\n }\n }\n };\n\n const checkRouteBatch = (\n batch: readonly Route[],\n caller: \"addRoute\" | \"replaceRoutes\",\n parentName: string | undefined,\n readTable: () => JoinedTable,\n ): void => {\n // ⚠ The parent option is judged below the walk, so the walk joins it to\n // a name only once the table holds it: joined to an option about to be\n // refused, the name is one no route can have.\n walkRouteCallbacks(\n batch,\n typeof parentName === \"string\" && lookup.hasRoute(parentName)\n ? parentName\n : undefined,\n );\n\n if (parentName !== undefined) {\n validateParentOptionRaw(parentName);\n\n if (!lookup.hasRoute(parentName)) {\n throw atAddRoute.ref`Parent route \"${parentName}\" does not exist`;\n }\n }\n\n throwIfInternalRouteInArray(batch, caller);\n validateAddRouteArgs(batch, parentName);\n\n // ⚠ Read HERE, not at the door: the walk and the argument checks above\n // run application code (a route function's own `toString`), and a table\n // read before it can be one the router no longer holds.\n const table = readTable();\n\n validateRoutes(\n batch as Route[],\n table.tree,\n table.lookup,\n table.forwardMap,\n parentName,\n );\n };\n\n const removeAddRouteCheck = api.addCheck(\n \"addRoute:batch\",\n (batch, parentName) => {\n checkRouteBatch(\n batch as readonly Route[],\n \"addRoute\",\n parentName,\n () => ({\n tree: api.getTree(),\n lookup,\n forwardMap: api.getForwardMap(),\n }),\n );\n },\n );\n\n const removeReplaceRoutesCheck = api.addCheck(\n \"replaceRoutes:batch\",\n (batch) => {\n checkRouteBatch(\n batch as readonly Route[],\n \"replaceRoutes\",\n undefined,\n () => emptyTableUnder(api.getTree()),\n );\n },\n );\n\n const removeRemoveRouteCheck = api.addCheck(\"removeRoute:entry\", (name) => {\n validateRemoveRouteArgs(name);\n throwIfInternalRoute(name, \"removeRoute\");\n });\n\n const removeUpdateRouteCheck = api.addCheck(\n \"updateRoute:entry\",\n (name, updates) => {\n const upd = updates as Record<string, unknown>;\n\n validateUpdateRouteBasicArgs(name, updates);\n throwIfInternalRoute(name, \"updateRoute\");\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 validateUpdateRoute(\n name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\n upd.forwardTo as any,\n lookup,\n api.getForwardMap(),\n );\n },\n );\n\n const removeHasRouteCheck = api.addCheck(\"hasRoute:entry\", (name) => {\n validateRouteName(name, \"hasRoute\");\n });\n\n const removeGetRouteCheck = api.addCheck(\"getRoute:entry\", (name) => {\n validateRouteName(name, \"getRoute\");\n });\n\n // ⚠ The two state builders consult the SAME pair in opposite orders, and\n // each check keeps its own door's order: the first refusal is the message\n // the caller gets.\n const removeForwardStateCheck = api.addCheck(\n \"forwardState:entry\",\n (routeName, routeParams, routeSearch) => {\n validateStateBuilderArgs(routeName, routeParams, \"forwardState\");\n validateSearch(routeSearch, \"forwardState\");\n },\n );\n\n const removeBuildNavigationStateCheck = api.addCheck(\n \"buildNavigationState:state\",\n (name, ownParams, search) => {\n validateSearch(search, \"buildNavigationState\");\n validateStateBuilderArgs(name, ownParams, \"buildNavigationState\");\n },\n );\n\n const removeAddActivateGuardCheck = api.addCheck(\n \"addActivateGuard:entry\",\n (name, handler) => {\n validateRouteName(name, \"addActivateGuard\");\n validateHandler(handler, \"addActivateGuard\");\n },\n );\n\n const removeAddDeactivateGuardCheck = api.addCheck(\n \"addDeactivateGuard:entry\",\n (name, handler) => {\n validateRouteName(name, \"addDeactivateGuard\");\n validateHandler(handler, \"addDeactivateGuard\");\n },\n );\n\n const removeRemoveActivateGuardCheck = api.addCheck(\n \"removeActivateGuard:entry\",\n (name) => {\n validateRouteName(name, \"removeActivateGuard\");\n },\n );\n\n const removeRemoveDeactivateGuardCheck = api.addCheck(\n \"removeDeactivateGuard:entry\",\n (name) => {\n validateRouteName(name, \"removeDeactivateGuard\");\n },\n );\n\n // ⚠ A DIAGNOSTIC, not a check, and the channel is the difference rather\n // than the style: this one cannot refuse the registration it describes —\n // the plugin is already installed by the time core reports.\n const removePluginAfterStartDiagnostic = api.subscribeDiagnostic(\n \"PLUGIN_AFTER_START\",\n (methodName) => {\n warnPluginAfterStart(methodName, api.logger);\n },\n );\n\n return {\n teardown() {\n removeParamsCheck();\n\n removeResolvedParamsCheck();\n\n removeCanNavigateToEntryCheck();\n\n removeCanNavigateToParamsCheck();\n\n removeNavigateEntryCheck();\n\n removeNavigateParamsCheck();\n\n removeAddRouteCheck();\n\n removeReplaceRoutesCheck();\n\n removeRemoveRouteCheck();\n\n removeUpdateRouteCheck();\n\n removeHasRouteCheck();\n\n removeGetRouteCheck();\n\n removeForwardStateCheck();\n\n removeBuildNavigationStateCheck();\n\n removeAddActivateGuardCheck();\n\n removeAddDeactivateGuardCheck();\n\n removeRemoveActivateGuardCheck();\n\n removeRemoveDeactivateGuardCheck();\n\n removePluginAfterStartDiagnostic();\n\n removeInterceptor();\n\n releaseIfStillOurs();\n },\n };\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["getPrototypeOf","objectValues","hasOwn","raiser","obj","objectEntries","LOGGER_CTX","raiser","objectKeys","getOwnPropertyDescriptor","getPrototypeOf","isPlainBag","warnOverwrite","raiser","events","atAddRoute","raiser","objectKeys","buildParamMeta","raiser","raiser","atNavigateToDefault","hasOwn","getPrototypeOf","objectEntries","hasOwn","objectKeys","raiser","raiser","raiser","atValidationPlugin","coreResolveForwardChain","assertNotAsync","raiser","atAddRoute","findMisChanneledKey","raiser","raiser","getInternals","getPluginApi","getRoutesApi","_real_router_core_api","_real_router_core_utils","_real_router_core_validation","_real_router_core","CORE_LIMIT_DEFAULTS","computeThresholds","limit","isString","value","isBoolean","isObjKey","key","obj","getPrototypeOf$3","objectValues$1","hasOwn$3","isPlainContainer","proto","pushChildren","children","child","stack","isSerializableLeaf","type","SubtreeExit","container","visitContainer","onPath","done","isSerializable","root","isPrimitiveValue","isParams","isParamsUnsafe","needsDeepCheck","val","NAVIGATION_OPTIONS_FIELDS","isNavigationOptions","field","fieldValue","signalValue","HAS_NON_WHITESPACE","FULL_ROUTE_PATTERN","createRouterError","methodName","message","isRouteName","name","isRequiredFields","isState","validateRouteName","getTypeDescription","ctor","objectEntries$2","WATCHED","snapshot","bag","moved","now","before","DefaultsMutationWatch","slot","ref","origins","#watched","#reported","logger","atCloneRouter","atGetDependency","atSetDependency","objectKeys$3","getOwnPropertyDescriptor$1","getPrototypeOf$2","ObjectCtor","isPlainBag$1","validateDependencyName","validateSetDependencyArgs","validateDependenciesObject","deps","validateDependencyBatchLimit","maxDependencies","held","heldKeys","added","currentCount","validateDependencyExists","dependencyName","validateDependencyCount","error","warn","validateCloneArgs","dependencies","warnOverwrite$1","warnBatchOverwrite","keys","warnRemoveNonExistent","atAddEventListener","objectValues","validEventNames","validateEventName","eventName","validateListenerArgs","cb","validateListenerCountThresholds","maxListeners","count","atAddRoute$2","objectKeys$2","nameInTable","parentName","validateForwardToProperty","forwardTo","fullName","isNativeAsync","isTranspiledAsync","assertPlainBag","validateRouteProperties","route","decodeParams","encodeParams","extractParamsFromPaths","params","path","paths","param","collectPathsToRoute","routes","currentPaths","routeName","collectRouteNames","names","childName","collectForwardMappings","mappings","getTargetParams","exists","lookup","targetRoute","pathsTo","EMPTY_PARENT_PARAMS","validateSingleForward","existsInBatch","batchNames","fromRoute","fromParams","parentParams","missingParams","validateForwardToTargets","batchForwards","combinedForwardMap","existingForwardMap","from","to","validateHandler","handler","validateHandlerLimit","maxLifecycleHandlers","validateLifecycleCountThresholds","maxHandlers","warnOverwrite","warnAsyncGuardSync","nonBooleanGuardMessage","answer","warnNonBooleanGuardSync","warnNonBooleanGuard","atStart","atNavigateToState","atNavigateToDefault$1","atNavigate","hasOwn$2","getPrototypeOf$1","validateNavigateArgs","validateNavigateToDefaultArgs","opts","validateNavigateToStateArgs","state","candidate","validateNavigationOptions","CONTROL_CHARS_RE","assertValidParamValues","valueType","validateNavigateParamsShape","validateNavigateParams","validateSearch","search","validateStartArgs","objectEntries$1","hasOwn$1","objectKeys$1","getPrototypeOf","VALID_OPTION_VALUES","KNOWN_QUERY_PARAMS","KNOWN_OPTIONS","LIMIT_BOUNDS","validateLimitValue","limitName","bounds","isPlainBag","validateLimits","limits","warnListeners","validateStringEnum","validValues","validList","display","optionName","validateDefaultRoute","defaultRoute","validateDefaultBag","validateQueryParamsOptions","queryParams","qp","validateOptions","options","atUsePlugin","hasOwn","LOGGER_CTX","PLUGIN_EVENTS_MAP","validatePluginLimit","maxPlugins","newCount","validateNoDuplicatePlugins","factories","factory","validateCountThresholds","validatePluginKeys","plugin","warnBatchDuplicates","warnPluginMethodType","warnPluginAfterStart","atNavigateToDefault","atValidationPlugin$1","objectEntries","getOwnPropertyDescriptor","objectKeys","walkRoutes","callback","collectSlot","entries","pick","resolveForwardChainWithPrefix","startRoute","forwardMap","bare","assertNotAsync$1","function_","fn","label","validateExistingRoutes","validateForwardToConsistency","sourceParams","assertPlainBagSlot","slotName","validateRoutePropertiesStore","decoders","encoders","forwardCallbacks","decoder","encoder","validateForwardToTargetsStore","validateDependenciesStructure","validateLimitsConsistency","maxDepsFromOptions","maxDeps","resolvedMaxDependencies","dependencyCount","namesNoRoute","validateConfiguredDefaultRoute","validateResolvedDefaultRoute","warnOrphanedGuards","guardNames","atUpdateRoute","atAddRoute$1","atShouldUpdateNode","atMatchPath","atBuildPath","atIsActiveRoute","atSetRootPath","throwIfInternalRoute","throwIfInternalRouteInArray","validateRemoveRouteArgs","validateSetRootPathArgs","rootPath","isAsyncFunction","guardRouteCallbacks","routeObj","guardNoAsyncCallbacks","validateAddRouteArgs","validateParentOption","parent","validateIsActiveRouteArgs","strictEquality","ignoreQueryParams","validateStateBuilderArgs","routeParams","validateUpdateRouteBasicArgs","updates","assertNotAsync","paramName","validateFunctionParam","validateUpdateRoutePropertyTypes","cached","validateBagParam","defaultParams","defaultSearch","canActivate","canDeactivate","validateBuildPathArgs","createMisChanneledKeyReporter","reported","queryNamesOf","seen","validateMatchPathArgs","validateShouldUpdateNodeArgs","nodeName","validateRoutes","node","tree","segment","seenNames","seenPathsByParent","validateForwardToParamCompatibility","sourceName","targetName","validateForwardToCycle","testMap","validateUpdateRoute","atMakeState","validateMakeStateArgs","createDroppedQueryKeyReporter","createUndeclaredParamKeyReporter","undeclaredReported","atValidationPlugin","atAddRoute","atAreStatesEqual","createRouteLookup","api","NO_ROUTES","emptyTableUnder","readRoutes","routesApi","buildValidatorObject","reportMisChanneledKey","strict","ignoreQP","caller","_name","defaultsWatch","s1","s2","validationPlugin","ctx","router","ownValidator","releaseIfStillOurs","removeInterceptor","next","removeParamsCheck","ownParams","removeResolvedParamsCheck","removeCanNavigateToEntryCheck","removeCanNavigateToParamsCheck","removeNavigateEntryCheck","removeNavigateParamsCheck","walkRouteCallbacks","batch","checkRouteBatch","table","readTable","removeAddRouteCheck","removeReplaceRoutesCheck","removeRemoveRouteCheck","removeUpdateRouteCheck","upd","removeHasRouteCheck","removeGetRouteCheck","removeForwardStateCheck","routeSearch","removeBuildNavigationStateCheck","removeAddActivateGuardCheck","removeAddDeactivateGuardCheck","removeRemoveActivateGuardCheck","removeRemoveDeactivateGuardCheck","removePluginAfterStartDiagnostic"],"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/forwardTo.ts","../../src/validators/lifecycle.ts","../../src/validators/navigation.ts","../../src/validators/options.ts","../../src/validators/plugins.ts","../../src/validators/retrospective.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\nimport { raiser } from \"@real-router/core/utils\";\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 const at = raiser(\"router\", methodName);\n\n return at.type`${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 * It answers a string for every value and never throws: the description lands\n * in a message, and the message must not become the thing that fails.\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 // Primitive types and functions: `typeof` reads nothing off the value.\n if (typeof value !== \"object\") {\n return typeof value;\n }\n\n // Describing an object reads it, and every read can run application code:\n // `Array.isArray` throws on a revoked proxy, `length` and `constructor` are\n // accessors or traps, and an adversarial own `constructor` (null, a string, a\n // number, …) is not a real constructor (#787). A read that throws (#1052), or\n // a `name` that is not a string, falls back to \"object\".\n try {\n // Array with length info\n if (Array.isArray(value)) {\n return `array[${value.length}]`;\n }\n\n const ctor: unknown = (value as { constructor?: unknown }).constructor;\n\n if (typeof ctor === \"function\") {\n const name: unknown = ctor.name;\n\n // Return constructor name for class instances; an anonymous class has\n // an empty one.\n if (typeof name === \"string\" && name !== \"\" && name !== \"Object\") {\n return name;\n }\n }\n } catch {\n // A throwing read describes nothing; \"object\" stands.\n }\n\n // Plain object, null-prototype object, or adversarial / throwing constructor\n return \"object\";\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 { raiser } from \"@real-router/core/utils\";\n\nimport { computeThresholds } from \"../helpers\";\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { RouterLogger } from \"@real-router/core\";\n\nconst atCloneRouter = raiser(\"cloneRouter\");\nconst atGetDependency = raiser(\"router\", \"getDependency\");\nconst atSetDependency = raiser(\"router\", \"setDependency\");\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 const at = raiser(\"router\", methodName);\n\n throw at.type`dependency name must be a string, got ${typeof name}`;\n }\n}\n\nexport function validateSetDependencyArgs(\n name: unknown,\n): asserts name is string {\n if (typeof name !== \"string\") {\n throw atSetDependency.type`dependency name must be a string, got ${typeof name}`;\n }\n}\n\nexport function validateDependenciesObject(\n deps: unknown,\n methodName: string,\n): asserts deps is Record<string, unknown> {\n if (!isPlainBag(deps)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid argument: expected plain object, received ${getTypeDescription(deps)}`;\n }\n\n for (const key of objectKeys(deps)) {\n if (getOwnPropertyDescriptor(deps, key)?.get) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Getters not allowed: \"${key}\"`;\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 heldKeys: readonly string[],\n maxDependencies: number,\n methodName: string,\n): void {\n if (maxDependencies === 0) {\n return;\n }\n\n // ⚑ The size and the membership test come from the SAME list (#1815 /\n // #2064) — `PluginApi.getDependencyKeys()`, one `Object.keys` of core's\n // record — so they cannot disagree on a key the count refuses to see.\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 const at = raiser(\"router\", methodName);\n\n throw at.range`Dependency limit exceeded (${maxDependencies}). Current: ${currentCount}, this batch adds ${added}.`;\n }\n}\n\nexport function validateDependencyExists(\n value: unknown,\n dependencyName: string,\n): asserts value is NonNullable<unknown> {\n if (value === undefined) {\n throw atGetDependency.ref`dependency \"${dependencyName}\" not found`;\n }\n}\n\nexport function validateDependencyCount(\n currentCount: number,\n maxDependencies: number,\n methodName: string,\n logger: RouterLogger,\n): void {\n if (maxDependencies === 0) {\n return;\n }\n\n const { warn, error } = computeThresholds(maxDependencies);\n\n if (currentCount >= maxDependencies) {\n const at = raiser(\"router\", methodName);\n\n throw at.range`Dependency limit exceeded (${maxDependencies}). Current: ${currentCount}.`;\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 atCloneRouter.type`Invalid dependencies: expected plain object or undefined, received ${typeof dependencies}`;\n }\n\n for (const key of objectKeys(dependencies)) {\n if (getOwnPropertyDescriptor(dependencies, key)?.get) {\n throw atCloneRouter.type`Getters not allowed in dependencies: \"${key}\"`;\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\";\nimport { raiser } from \"@real-router/core/utils\";\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\nconst atAddEventListener = raiser(\"router\", \"addEventListener\");\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 atAddEventListener.type`Invalid event name: ${String(eventName)}. Must be one of: $start, $stop, $$start, $$leaveApprove, $$cancel, $$success, $$error`;\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 atAddEventListener.type`callback must be a function, got ${typeof cb}`;\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`, and from\n * `usePlugin` for a plugin's hooks, with the POST-add count. Core keeps the\n * emitter's bare-`Error` hard cap; this only surfaces an actionable,\n * `[router.<method>]`-prefixed signal well before it, so a listener leak (e.g. a\n * missing unsubscribe — #766) is caught early instead of OOM-ing at the\n * 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/forwardTo.ts\n\nimport { resolveForwardChain } from \"@real-router/core\";\nimport { internalDefect, raiser, putField } from \"@real-router/core/utils\";\nimport { buildParamMeta } from \"@real-router/core/validation\";\n\nimport { getTypeDescription } from \"../type-guards\";\n\nimport type { Route, DefaultDependencies } from \"@real-router/core\";\n\nconst atAddRoute = raiser(\"router\", \"addRoute\");\n\n/**\n * What a route validator asks about routes that ALREADY exist (#2382).\n *\n * ⚑ Two questions, both data — so the route matcher is not handed to them. For\n * the registered table both are answered from the curated surface: existence\n * walks `PluginApi.getTree()`, and the path slots come from\n * `PluginApi.getUrlParams`. The table a `replace()` batch joins holds no route,\n * and its lookup answers accordingly (#2562).\n */\nexport interface RouteLookup {\n hasRoute: (name: string) => boolean;\n getUrlParams: (name: string) => readonly string[];\n}\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\n/**\n * A batch route's name as the table holds it: `${parent}.${name}` under\n * `{ parent }` (#2566), which is also how a nested route is named.\n *\n * ⚠ Built from strings only, joined as core joins them (an empty parent adds\n * nothing). Both batch walks run before the names are judged, so either part\n * may still be anything; a name that is not a string is passed on as it is.\n */\nexport function nameInTable<Name>(\n parentName: unknown,\n name: Name,\n): Name | string {\n return typeof parentName === \"string\" &&\n parentName !== \"\" &&\n typeof name === \"string\"\n ? `${parentName}.${name}`\n : name;\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 atAddRoute.type`forwardTo must be a string or function for route \"${fullName}\", got ${getTypeDescription(forwardTo)}`;\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 atAddRoute.type`forwardTo callback cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`;\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 atAddRoute.type`${slot} must be an object for route \"${fullName}\", got ${getTypeDescription(bag)}`;\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 atAddRoute.type`canActivate must be a function for route \"${fullName}\", got ${getTypeDescription(route.canActivate)}`;\n }\n\n if (\n route.canDeactivate !== undefined &&\n typeof route.canDeactivate !== \"function\"\n ) {\n throw atAddRoute.type`canDeactivate must be a function for route \"${fullName}\", got ${getTypeDescription(route.canDeactivate)}`;\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 // A codec that is not a function is `validateRoute`'s to refuse; reading its\n // `constructor` here would throw first on one that has none.\n const { decodeParams, encodeParams } = route;\n\n if (\n typeof decodeParams === \"function\" &&\n decodeParams.constructor.name === \"AsyncFunction\"\n ) {\n throw atAddRoute.type`decodeParams cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`;\n }\n\n if (\n typeof encodeParams === \"function\" &&\n encodeParams.constructor.name === \"AsyncFunction\"\n ) {\n throw atAddRoute.type`encodeParams cannot be async for route \"${fullName}\". Async functions break matchPath/buildPath.`;\n }\n\n validateForwardToProperty(route.forwardTo, fullName);\n\n if (route.children) {\n for (const child of route.children) {\n const childFullName = nameInTable(fullName, child.name);\n\n validateRouteProperties(child, childFullName);\n }\n }\n}\n\n// ============================================================================\n// ForwardTo Validation\n// ============================================================================\n\n/**\n * The URL params a batch route's paths declare, read with core's\n * `buildParamMeta` — what `getUrlParams` is built from for a route the table\n * holds (#2569).\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 buildParamMeta(path).urlParams) {\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 internalDefect.plain`collectPathsToRoute: route \"${routeName}\" not found`;\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\n/** The paths from the parent down to a batch route, found by its full name. */\ntype BatchPaths = (fullName: string) => string[];\n\nfunction getTargetParams(\n targetRoute: string,\n exists: boolean,\n lookup: RouteLookup,\n pathsTo: BatchPaths,\n): Set<string> {\n if (exists) {\n return new Set(lookup.getUrlParams(targetRoute));\n }\n\n return extractParamsFromPaths(pathsTo(targetRoute));\n}\n\nconst EMPTY_PARENT_PARAMS: ReadonlySet<string> = new Set();\n\nfunction validateSingleForward(\n fromRoute: string,\n targetRoute: string,\n pathsTo: BatchPaths,\n batchNames: Set<string>,\n lookup: RouteLookup,\n parentParams: ReadonlySet<string>,\n): void {\n const exists = lookup.hasRoute(targetRoute);\n const existsInBatch = batchNames.has(targetRoute);\n\n if (!exists && !existsInBatch) {\n throw atAddRoute.ref`forwardTo target \"${targetRoute}\" does not exist for route \"${fromRoute}\"`;\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(pathsTo(fromRoute)),\n ]);\n\n const toParams = getTargetParams(targetRoute, exists, lookup, pathsTo);\n\n const missingParams = [...toParams].filter((param) => !fromParams.has(param));\n\n if (missingParams.length > 0) {\n throw atAddRoute.plain`forwardTo target \"${targetRoute}\" requires params [${missingParams.join(\", \")}] that are not available in source route \"${fromRoute}\"`;\n }\n}\n\nexport function validateForwardToTargets<\n Dependencies extends DefaultDependencies,\n>(\n routes: readonly Route<Dependencies>[],\n existingForwardMap: Readonly<Record<string, string>>,\n lookup: RouteLookup,\n parentName?: string,\n): void {\n // ⚑ Added under { parent }, a batch route is the table's\n // `${parent}.${short}`, and a `forwardTo` names routes by those full names.\n // So every question about the batch — which names it holds, the paths down\n // to one of them, the key its forward sits under — starts from the parent\n // (#1224, #2566).\n const batchNames = collectRouteNames(routes, parentName);\n // The paths walk starts inside the batch, so the parent is cut off the name\n // once here rather than joined to every name the walk compares.\n const pathsTo: BatchPaths = (fullName) =>\n collectPathsToRoute(\n routes,\n parentName ? fullName.slice(parentName.length + 1) : fullName,\n );\n // `validateRoutes` throws on a missing parent before forwardTo validation runs,\n // so these are the parent's own slots, never the empty answer `getUrlParams`\n // gives for a route the tree does not hold.\n const parentParams: ReadonlySet<string> = parentName\n ? new Set(lookup.getUrlParams(parentName))\n : EMPTY_PARENT_PARAMS;\n\n const batchForwards = collectForwardMappings(routes, parentName);\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 pathsTo,\n batchNames,\n lookup,\n parentParams,\n );\n }\n\n for (const fromRoute of objectKeys(combinedForwardMap)) {\n resolveForwardChain(fromRoute, combinedForwardMap);\n }\n}\n","// packages/validation-plugin/src/validators/lifecycle.ts\n\nimport { raiser } from \"@real-router/core/utils\";\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Handler must be a boolean or factory function, got ${getTypeDescription(handler)}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.range`Lifecycle handler limit exceeded (${maxLifecycleHandlers}). This indicates too many routes with individual handlers. Consider using plugins for cross-cutting concerns.`;\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\n/**\n * A guard answered neither `true` nor `false` (#2823). Core refuses such an\n * answer on every path; this names the guard and what it answered.\n */\nfunction nonBooleanGuardMessage(name: string, answer: unknown): string {\n return `Guard for \"${name}\" must answer true or false, got ${getTypeDescription(answer)} — read as a refusal.`;\n}\n\n/**\n * The synchronous ask met a non-boolean answer; it reports under the channel\n * `warnAsyncGuardSync` takes for the same ask.\n */\nexport function warnNonBooleanGuardSync(\n name: string,\n answer: unknown,\n methodName: string,\n logger: RouterLogger,\n): void {\n logger.warn(`router.${methodName}`, nonBooleanGuardMessage(name, answer));\n}\n\n/**\n * A transition met a non-boolean answer. Several calls start a transition, so\n * the message logs under bare `router`.\n */\nexport function warnNonBooleanGuard(\n name: string,\n answer: unknown,\n logger: RouterLogger,\n): void {\n logger.warn(\"router\", nonBooleanGuardMessage(name, answer));\n}\n","// packages/validation-plugin/src/validators/navigation.ts\n\nimport { raiser } from \"@real-router/core/utils\";\n\nimport {\n getTypeDescription,\n isNavigationOptions,\n isParams,\n isString,\n} from \"../type-guards\";\n\nimport type { NavigationOptions } from \"@real-router/core\";\n\nconst atStart = raiser(\"router\", \"start\");\nconst atNavigateToState = raiser(\"router\", \"navigateToState\");\nconst atNavigateToDefault = raiser(\"router\", \"navigateToDefault\");\nconst atNavigate = raiser(\"router\", \"navigate\");\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 atNavigate.type`Invalid route name: expected string, got ${getTypeDescription(name)}`;\n }\n}\n\nexport function validateNavigateToDefaultArgs(opts: unknown): void {\n if (opts !== undefined && (typeof opts !== \"object\" || opts === null)) {\n throw atNavigateToDefault.type`Invalid options: ${getTypeDescription(opts)}. Expected NavigationOptions object.`;\n }\n}\n\nexport function validateNavigateToStateArgs(state: unknown): void {\n if (typeof state !== \"object\" || state === null) {\n throw atNavigateToState.type`Invalid state: ${getTypeDescription(state)}. Expected State object.`;\n }\n\n const candidate = state as { name: unknown; params: unknown; path: unknown };\n\n if (!isString(candidate.name)) {\n throw atNavigateToState.type`Invalid state.name: ${getTypeDescription(candidate.name)}. Expected string.`;\n }\n if (!isParams(candidate.params)) {\n throw atNavigateToState.type`Invalid state.params: ${getTypeDescription(candidate.params)}. Expected plain object.`;\n }\n if (!isString(candidate.path)) {\n throw atNavigateToState.type`Invalid state.path: ${getTypeDescription(candidate.path)}. Expected string.`;\n }\n}\n\nexport function validateNavigationOptions(\n opts: unknown,\n methodName: string,\n): asserts opts is NavigationOptions {\n if (!isNavigationOptions(opts)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid options: ${getTypeDescription(opts)}. Expected NavigationOptions object.`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`param \"${key}\" cannot be a ${valueType} — it does not round-trip through the URL path. Use a string, number, or boolean.`;\n }\n\n if (valueType === \"string\" && CONTROL_CHARS_RE.test(value as string)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`param \"${key}\" must not contain control characters (NUL / C0 / DEL) — they corrupt the URL path.`;\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 = params !== null && (getPrototypeOf(params) as object | null);\n\n if (Array.isArray(params) || (proto !== null && proto !== Object.prototype)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`params must be a plain object, got ${getTypeDescription(params)}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`params must be a plain object, got ${getTypeDescription(params)}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`search must be a plain object, got ${getTypeDescription(search)}`;\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 atStart.type`path must be a string, got ${getTypeDescription(path)}.`;\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 atStart.type`path must not contain control characters (NUL / C0 / DEL).`;\n }\n if (path !== \"\" && !path.startsWith(\"/\")) {\n throw atStart.type`path must start with \"/\", got \"${path}\".`;\n }\n }\n}\n","// packages/validation-plugin/src/validators/options.ts\n\nimport { raiser } from \"@real-router/core/utils\";\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 shape #2091 names. 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] 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 const at = raiser(\"router\", methodName);\n\n throw at.type`limit \"${limitName}\" must be an integer, got ${String(value)}`;\n }\n\n const bounds = LIMIT_BOUNDS[limitName];\n\n if (value < bounds.min || value > bounds.max) {\n const at = raiser(\"router\", methodName);\n\n throw at.range`limit \"${limitName}\" must be between ${bounds.min} and ${bounds.max}, got ${value}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`invalid limits: expected plain object, got ${typeof limits}`;\n }\n\n for (const [key, value] of objectEntries(limits)) {\n if (!hasOwn(LIMIT_BOUNDS, key)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`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 const at = raiser(\"router\", methodName);\n\n throw at.range`\"limits.warnListeners\" (${warnListeners}) must not exceed \"limits.maxListeners\" (${maxListeners}) — the warning channel would be unreachable`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"${optionName}\": \"${display}\". Must be one of: ${validList}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"defaultRoute\": expected string or function, got ${typeof defaultRoute}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"${optionName}\": expected plain object or function, got ${typeof value}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"queryParams\": expected plain object`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"queryParams.${key}\": unknown option`;\n }\n }\n}\n\nexport function validateOptions(options: unknown, methodName: string): void {\n if (!options || typeof options !== \"object\" || Array.isArray(options)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid options: expected plain object`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"allowNotFound\": expected boolean, got ${typeof opts.allowNotFound}`;\n }\n\n if (\n opts.rewritePathOnMatch !== undefined &&\n typeof opts.rewritePathOnMatch !== \"boolean\"\n ) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"rewritePathOnMatch\": expected boolean, got ${typeof opts.rewritePathOnMatch}`;\n }\n\n if (\n opts.caseSensitive !== undefined &&\n typeof opts.caseSensitive !== \"boolean\"\n ) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid \"caseSensitive\": expected boolean, got ${typeof opts.caseSensitive}`;\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 { raiser } from \"@real-router/core/utils\";\n\nimport { computeThresholds, CORE_LIMIT_DEFAULTS } from \"../helpers\";\n\nimport type { RouterLogger } from \"@real-router/core\";\n\nconst atUsePlugin = raiser(\"router\", \"usePlugin\");\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 atUsePlugin.range`Plugin limit exceeded (${maxPlugins}). Current: ${currentCount}, Attempting to add: ${newCount}. This indicates an architectural problem. Consider consolidating plugins.`;\n }\n}\n\nexport function validateNoDuplicatePlugins(\n factory: unknown,\n factories: unknown[],\n): void {\n if (factories.includes(factory)) {\n throw atUsePlugin.plain`Plugin factory already registered. To re-register, first unsubscribe the existing plugin.`;\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 atUsePlugin.type`Unknown property '${key}'. Plugin must only contain event handlers and optional teardown.`;\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\";\nimport { raiser } from \"@real-router/core/utils\";\n\nimport type { RouteLookup } from \"./forwardTo\";\nimport type { LimitsConfig } from \"@real-router/core\";\nimport type { RouterLogger } from \"@real-router/core/types\";\n\nconst atNavigateToDefault = raiser(\"router\", \"navigateToDefault\");\nconst atValidationPlugin = raiser(\"validation-plugin\");\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 once, at `usePlugin()` time, over what the\n * router already holds. Called by the validation plugin in a try/catch with\n * rollback.\n *\n * Every function takes FACTS read from the published surface (#2382): the\n * routes as `RoutesApi.get` reports them, the one-hop forward map, the resolved\n * limits, the dependency names. None of them receives a core store.\n */\n\n/**\n * A route as `RoutesApi.get` reports it, with the fields this pass judges typed\n * `unknown` — the pass exists to catch values the declared type rules out.\n */\ninterface RouteFacts {\n name: string;\n path: string;\n forwardTo?: unknown;\n defaultParams?: unknown;\n defaultSearch?: unknown;\n decodeParams?: unknown;\n encodeParams?: unknown;\n children?: readonly RouteFacts[];\n}\n\n// =============================================================================\n// Private helpers\n// =============================================================================\n\nfunction walkRoutes(\n routes: readonly RouteFacts[],\n callback: (route: RouteFacts, fullName: string) => void,\n parentName = \"\",\n): void {\n for (const route of routes) {\n const fullName = parentName ? `${parentName}.${route.name}` : route.name;\n\n callback(route, fullName);\n\n if (route.children) {\n walkRoutes(route.children, callback, fullName);\n }\n }\n}\n\n/** One slot of every route, in walk order, skipping routes that leave it unset. */\nfunction collectSlot(\n routes: readonly RouteFacts[],\n pick: (route: RouteFacts) => unknown,\n): [string, unknown][] {\n const entries: [string, unknown][] = [];\n\n walkRoutes(routes, (route, fullName) => {\n const value = pick(route);\n\n if (value !== undefined) {\n entries.push([fullName, value]);\n }\n });\n\n return entries;\n}\n\n/**\n * Wraps core's `resolveForwardChain` so a retrospective refusal names the pass\n * that raised it. The retrospective walk is not a `router.*` call — it runs over\n * a table that is already registered — and `README.md` documents\n * `[validation-plugin]` for this pass.\n *\n * ⚠ Core's own message opens with `[router] ` (#2456), and that head is\n * REPLACED rather than stacked: two prefixes on one message name two subsystems\n * for one fault.\n */\nfunction resolveForwardChainWithPrefix(\n startRoute: string,\n forwardMap: Readonly<Record<string, string>>,\n): string {\n try {\n return coreResolveForwardChain(startRoute, forwardMap);\n } catch (error) {\n const bare = (error as Error).message.replace(/^\\[router\\] /u, \"\");\n\n throw atValidationPlugin.plain({ cause: error })`${bare}`;\n }\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 atValidationPlugin.type`Route \"${routeName}\" ${label} cannot be async`;\n }\n}\n\n// =============================================================================\n// 1. validateExistingRoutes\n// =============================================================================\n\n/**\n * Validates the existing routes for structural integrity.\n * Walks every route, 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 routes - Every top-level route, nested, as `RoutesApi.get` reports it\n * @throws {TypeError} If a route has structural issues\n */\nexport function validateExistingRoutes(routes: readonly RouteFacts[]): void {\n walkRoutes(routes, (route, fullName) => {\n if (typeof route.name !== \"string\" || !route.name) {\n throw atValidationPlugin.type`validateExistingRoutes: route has invalid name: ${route.name}`;\n }\n\n // ⚑ No dotted-name check here, and its absence is load-bearing rather\n // than a cleanup. Bare core refuses a dotted route name at registration,\n // with this package's message (#1763), so `createRouter` throws before a\n // plugin exists and nothing dotted can reach this pass. The names walked\n // here come off the TREE, whose nested children carry bare names by\n // construction.\n\n if (typeof route.path !== \"string\") {\n throw atValidationPlugin.type`validateExistingRoutes: route \"${fullName}\" has non-string path (${typeof route.path})`;\n }\n });\n}\n\n// =============================================================================\n// 2. validateForwardToConsistency\n// =============================================================================\n\n/**\n * Validates forwardTo consistency across all chains.\n * Checks target existence, param compatibility, and circular chain detection.\n * Adapted from: validateForwardToTargets() in forwardToValidation.ts\n *\n * @param forwardMap - The ONE-HOP forward map, from `PluginApi.getForwardMap()`\n * @param lookup - Existence and path slots of routes that already exist\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(\n forwardMap: Readonly<Record<string, string>>,\n lookup: RouteLookup,\n): void {\n // Check target existence and param compatibility for each static mapping\n for (const [fromRoute, targetRoute] of objectEntries(forwardMap)) {\n if (!lookup.hasRoute(targetRoute)) {\n throw atValidationPlugin.plain`validateForwardToConsistency: forwardTo target \"${targetRoute}\" does not exist in tree (source route: \"${fromRoute}\")`;\n }\n\n // Validate param compatibility: target must not require params absent in source\n const sourceParams = new Set(lookup.getUrlParams(fromRoute));\n const missingParams = [...new Set(lookup.getUrlParams(targetRoute))].filter(\n (param) => !sourceParams.has(param),\n );\n\n if (missingParams.length > 0) {\n throw atValidationPlugin.plain`validateForwardToConsistency: forwardTo target \"${targetRoute}\" requires params [${missingParams.join(\", \")}] not available in source route \"${fromRoute}\"`;\n }\n }\n\n // Detect cycles in the full forwardMap (catches multi-hop cycles)\n for (const fromRoute of objectKeys(forwardMap)) {\n resolveForwardChainWithPrefix(fromRoute, forwardMap);\n }\n}\n\n// =============================================================================\n// 3. validateRouteProperties\n// =============================================================================\n\n/** Every entry of a default-bag slot is a plain object, or the route is bad. */\nfunction assertPlainBagSlot(\n slot: [string, unknown][],\n slotName: \"defaultParams\" | \"defaultSearch\",\n): void {\n for (const [routeName, bag] of slot) {\n if (bag === null || typeof bag !== \"object\" || Array.isArray(bag)) {\n throw atValidationPlugin.type`validateRoutePropertiesStore: route \"${routeName}\" ${slotName} must be a plain object, got ${Array.isArray(bag) ? \"array\" : typeof bag}`;\n }\n }\n}\n\n/**\n * Validates route properties for every registered route.\n * Checks decoder/encoder types, defaultParams structure, and async forwardTo callbacks.\n * Adapted from: validateRouteProperties() in forwardToValidation.ts\n *\n * @param routes - Every top-level route, nested, as `RoutesApi.get` reports it\n * @throws {TypeError} If any registered decoder/encoder is not a valid sync function\n * @throws {TypeError} If any defaultParams is not a plain object\n * @throws {TypeError} If any forwardTo callback is async\n */\nexport function validateRoutePropertiesStore(\n routes: readonly RouteFacts[],\n): void {\n const decoders = collectSlot(routes, (route) => route.decodeParams);\n const encoders = collectSlot(routes, (route) => route.encodeParams);\n // A string `forwardTo` is a target name, judged by the forward-map checks.\n const forwardCallbacks = collectSlot(routes, (route) =>\n typeof route.forwardTo === \"string\" ? undefined : route.forwardTo,\n );\n\n // Validate decoders — must be non-async functions (sync required for matchPath/buildPath)\n for (const [routeName, decoder] of decoders) {\n if (typeof decoder !== \"function\") {\n throw atValidationPlugin.type`validateRoutePropertiesStore: route \"${routeName}\" decoder must be a function, got ${typeof decoder}`;\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 encoders) {\n if (typeof encoder !== \"function\") {\n throw atValidationPlugin.type`validateRoutePropertiesStore: route \"${routeName}\" encoder must be a function, got ${typeof encoder}`;\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(\n collectSlot(routes, (route) => route.defaultParams),\n \"defaultParams\",\n );\n assertPlainBagSlot(\n collectSlot(routes, (route) => route.defaultSearch),\n \"defaultSearch\",\n );\n\n // Validate forwardTo function callbacks — must be non-async functions\n for (const [routeName, callback] of forwardCallbacks) {\n if (typeof callback !== \"function\") {\n throw atValidationPlugin.type`validateRoutePropertiesStore: route \"${routeName}\" forwardTo callback must be a function, got ${typeof callback}`;\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 forwardMap - The ONE-HOP forward map, from `PluginApi.getForwardMap()`\n * @param lookup - Existence of routes that already exist\n * @throws {Error} If any forwardTo target route does not exist in the tree\n */\nexport function validateForwardToTargetsStore(\n forwardMap: Readonly<Record<string, string>>,\n lookup: RouteLookup,\n): void {\n for (const [fromRoute, targetRoute] of objectEntries(forwardMap)) {\n if (!lookup.hasRoute(targetRoute)) {\n throw atValidationPlugin.plain`validateForwardToTargetsStore: forwardTo target \"${targetRoute}\" does not exist for route \"${fromRoute}\"`;\n }\n }\n}\n\n// =============================================================================\n// 5. validateDependenciesStructure\n// =============================================================================\n\n/**\n * Validates the dependency record and the resolved limits.\n *\n * ⚠ The getter walk reads core's LIVE dependency record, the one read of a\n * core store this pass makes. Core refuses a getter at every dependency\n * door, so the only way one lands there is `Object.defineProperty` through the\n * record `getInternals(router).dependenciesGetStore()` hands out, and the walk\n * exists for as long as that door does (#2386).\n *\n * @param dependencies - Core's dependency record\n * @param limits - The resolved limits, from `PluginApi.getResolvedLimits()`\n * @throws {TypeError} If a dependency is a getter\n * @throws {TypeError} If a limit is not an integer\n */\nexport function validateDependenciesStructure(\n dependencies: object,\n limits: Readonly<LimitsConfig>,\n): void {\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 atValidationPlugin.type`validateDependenciesStructure: dependency \"${key}\" must not use a getter`;\n }\n }\n\n for (const [key, value] of objectEntries(limits)) {\n // ⚑ `Number.isInteger`, not `typeof === \"number\"`. Core coerces the caller's\n // limits ONCE at construction (#1875), so every resolved limit is already\n // `typeof \"number\"` — and `Number(undefined)`, `Number(\"abc\")` and\n // `Number({})` are all `NaN`, which passes a `typeof` test. The values\n // worth rejecting are spread across that type; `retrospective.test.ts`\n // owns which ones and reds on both `typeof` and `isFinite`. This also lands\n // the check on the same predicate `validateLimitValue` already uses, so the\n // two mirrors agree.\n if (!Number.isInteger(value)) {\n throw atValidationPlugin.type`validateDependenciesStructure: deps.limits.${key} must be an integer, got ${String(value)}`;\n }\n }\n}\n\n// =============================================================================\n// 6. validateLimitsConsistency\n// =============================================================================\n\n/**\n * Validates that the dependency count does not exceed the configured limit.\n * Adapted from: validateLimits() in OptionsNamespace/validators.ts\n *\n * @param options - Router options (typed as unknown to avoid core coupling)\n * @param dependencyCount - How many dependencies the router holds\n * @param resolvedMaxDependencies - `maxDependencies` from the resolved limits\n * @throws {RangeError} If dependency count exceeds maxDependencies limit (#1225:\n * `>` not `>=` — an at-limit store is legal, mirroring the live limiter)\n */\nexport function validateLimitsConsistency(\n options: unknown,\n dependencyCount: number,\n resolvedMaxDependencies: number,\n): void {\n const opts =\n options && typeof options === \"object\"\n ? (options as Record<string, unknown>)\n : {};\n const configuredLimits =\n opts.limits && typeof opts.limits === \"object\"\n ? (opts.limits as Record<string, unknown>)\n : {};\n const maxDepsFromOptions = configuredLimits.maxDependencies;\n const maxDeps =\n typeof maxDepsFromOptions === \"number\"\n ? maxDepsFromOptions\n : resolvedMaxDependencies;\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 (maxDeps > 0 && dependencyCount > maxDeps) {\n throw atValidationPlugin.range`validateLimitsConsistency: dependency count (${dependencyCount}) exceeds maxDependencies limit (${maxDeps})`;\n }\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 */\n/** Shared predicate; each door owns its own head, so no prefix travels as data. */\nfunction namesNoRoute(\n routeName: unknown,\n lookup: RouteLookup,\n): routeName is string {\n return (\n typeof routeName === \"string\" &&\n routeName !== \"\" &&\n !lookup.hasRoute(routeName)\n );\n}\n\n/**\n * The retrospective pass, for a `defaultRoute` configured as a STRING. No call\n * reaches it — the sweep runs over a table that is already registered — so the\n * refusal names the package rather than a door.\n */\nexport function validateConfiguredDefaultRoute(\n routeName: unknown,\n lookup: RouteLookup,\n): void {\n if (namesNoRoute(routeName, lookup)) {\n throw atValidationPlugin.plain`defaultRoute resolved to non-existent route: \"${routeName}\"`;\n }\n}\n\n/**\n * The runtime pass, for a `defaultRoute` configured as a CALLBACK. Reached from\n * `navigateToDefault()` alone, so the caller made a call they can look up and the\n * refusal names it. ⚠ It arrives as a rejected promise: the `catch` around\n * `resolveDefault()` preserves the user callback's throw shape.\n */\nexport function validateResolvedDefaultRoute(\n routeName: unknown,\n lookup: RouteLookup,\n): void {\n if (namesNoRoute(routeName, lookup)) {\n throw atNavigateToDefault.plain`defaultRoute callback resolved to non-existent route: \"${routeName}\"`;\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. `PluginApi.getExternalGuardNames()` hands\n * out exactly that origin, each name once.\n */\nexport function warnOrphanedGuards(\n guardNames: readonly string[],\n lookup: RouteLookup,\n logger: RouterLogger,\n): void {\n for (const name of guardNames) {\n if (!lookup.hasRoute(name)) {\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}\n","// packages/validation-plugin/src/validators/routes.ts\n\n/**\n * DX-only validator functions for RoutesNamespace.\n */\n\nimport { resolveForwardChain } from \"@real-router/core\";\nimport { raiser } from \"@real-router/core/utils\";\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 {\n nameInTable,\n validateForwardToTargets,\n validateRouteProperties,\n} from \"./forwardTo\";\nimport { validateNavigateParamsShape } from \"./navigation\";\n\nimport type { RouteLookup } from \"./forwardTo\";\nimport type {\n ForwardToCallback,\n Params,\n Route,\n RouteConfigUpdate,\n DefaultDependencies,\n} from \"@real-router/core\";\nimport type { RouteTree } from \"@real-router/core/validation\";\n\nconst atUpdateRoute = raiser(\"router\", \"updateRoute\");\nconst atAddRoute = raiser(\"router\", \"addRoute\");\nconst atShouldUpdateNode = raiser(\"router\", \"shouldUpdateNode\");\nconst atMatchPath = raiser(\"router\", \"matchPath\");\nconst atBuildPath = raiser(\"router\", \"buildPath\");\nconst atIsActiveRoute = raiser(\"router\", \"isActiveRoute\");\nconst atSetRootPath = raiser(\"router\", \"setRootPath\");\n\n// Internal constant (matches core's INTERNAL_ROUTE_PREFIX)\nconst INTERNAL_ROUTE_PREFIX = \"@@\";\n\nexport function throwIfInternalRoute(name: string, methodName: string): void {\n if (name.startsWith(INTERNAL_ROUTE_PREFIX)) {\n const at = raiser(\"router\", methodName);\n\n throw at.plain`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\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 @typescript-eslint/no-unnecessary-condition -- runtime safety guard\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 atSetRootPath.type`rootPath must be a string, got ${getTypeDescription(rootPath)}`;\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 atAddRoute.type`canActivate must be a function, got ${getTypeDescription(routeObj.canActivate)}`;\n }\n\n if (\n routeObj.canDeactivate !== undefined &&\n typeof routeObj.canDeactivate !== \"function\"\n ) {\n throw atAddRoute.type`canDeactivate must be a function, got ${getTypeDescription(routeObj.canDeactivate)}`;\n }\n}\n\nexport function guardNoAsyncCallbacks(route: unknown, fullName: unknown): void {\n const routeObj = route as {\n decodeParams?: unknown;\n encodeParams?: unknown;\n forwardTo?: unknown;\n };\n\n // Only a function is asked whether it is async: any other codec is\n // `validateRoute`'s to refuse, and `isAsyncFunction` reads its `constructor`.\n if (\n typeof routeObj.decodeParams === \"function\" &&\n isAsyncFunction(routeObj.decodeParams)\n ) {\n throw atAddRoute.type`decodeParams cannot be async for route \"${String(fullName)}\"`;\n }\n\n if (\n typeof routeObj.encodeParams === \"function\" &&\n isAsyncFunction(routeObj.encodeParams)\n ) {\n throw atAddRoute.type`encodeParams cannot be async for route \"${String(fullName)}\"`;\n }\n\n if (\n typeof routeObj.forwardTo === \"function\" &&\n isAsyncFunction(routeObj.forwardTo)\n ) {\n throw atAddRoute.type`forwardTo callback cannot be async for route \"${String(fullName)}\"`;\n }\n}\n\n/**\n * Validates addRoute arguments (route structure and properties).\n * State-dependent validation (duplicates, tree) happens in instance method.\n */\nexport function validateAddRouteArgs(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Route type\n routes: readonly Route<any>[],\n parentName?: string,\n): 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 atAddRoute.type`Route must be an object, got ${getTypeDescription(route)}`;\n }\n\n // Validate route properties (canActivate, canDeactivate, defaultParams, async checks)\n // Note: validateRouteProperties handles children recursively\n validateRouteProperties(route, nameInTable(parentName, 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 atAddRoute.type`parent option must be a non-empty string, got ${getTypeDescription(parent)}`;\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 atIsActiveRoute.type`name must be a string, got ${typeof name}`;\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 atIsActiveRoute.type`strictEquality must be a boolean, got ${typeof strictEquality}`;\n }\n\n // Validate ignoreQueryParams if provided\n if (\n ignoreQueryParams !== undefined &&\n typeof ignoreQueryParams !== \"boolean\"\n ) {\n throw atIsActiveRoute.type`ignoreQueryParams must be a boolean, got ${typeof ignoreQueryParams}`;\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 const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid routeName: ${getTypeDescription(routeName)}. Expected string.`;\n }\n\n if (!isParams(routeParams)) {\n const at = raiser(\"router\", methodName);\n\n throw at.type`Invalid routeParams: ${getTypeDescription(routeParams)}. Expected plain object.`;\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 atUpdateRoute.ref`Invalid name: empty string. Cannot update root node.`;\n }\n\n // Validate updates is not null\n\n if (updates === null) {\n throw atUpdateRoute.type`updates must be an object, got null`;\n }\n\n // Validate updates is an object (not array)\n if (typeof updates !== \"object\" || Array.isArray(updates)) {\n throw atUpdateRoute.type`updates must be an object, got ${getTypeDescription(updates)}`;\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 atUpdateRoute.type`${paramName} cannot be an async function`;\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 atUpdateRoute.type`${paramName} must be a function or null, got ${typeof value}`;\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 atUpdateRoute.type`forwardTo must be a string, function, or null, got ${getTypeDescription(forwardTo)}`;\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 atUpdateRoute.type`${paramName} must be an object or null, got ${getTypeDescription(value)}`;\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 atBuildPath.type`route must be a non-empty string, got ${typeof route === \"string\" ? '\"\"' : typeof route}`;\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 atMatchPath.type`path must be a string, got ${typeof path}`;\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 atShouldUpdateNode.type`nodeName must be a string, got ${typeof nodeName}`;\n }\n}\n\n/**\n * Validates a batch against the table it joins.\n * Checks parent existence, duplicates, and forwardTo targets/cycles.\n *\n * The table is the registered one for `add`, and an empty one under the kept\n * root for `replace` (#2562) — `validationPlugin.ts` picks it per door.\n *\n * @param routes - Routes to validate\n * @param tree - The tree of the table the batch joins\n * @param lookup - Existence and path slots of that table's routes\n * @param forwardMap - That table's ONE-HOP forward map\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 lookup: RouteLookup,\n forwardMap: Readonly<Record<string, string>>,\n parentName?: string,\n): void {\n // Validate parent route exists in tree\n if (parentName) {\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 atAddRoute.ref`Parent route \"${parentName}\" does not exist`;\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 validateForwardToTargets(routes, forwardMap, lookup, parentName);\n}\n\n// ============================================================================\n// Instance-level validators (moved from routesCrud.ts)\n// ============================================================================\n\n/**\n * Validates that forwardTo target doesn't require params that source doesn't have.\n *\n * ⚠ The `update` door is the only one that reaches this, so the message names\n * it. The batch doors carry their own copy in `forwardTo.ts`, which also\n * resolves a target the tree does not hold YET from the batch being registered\n * — and names `addRoute`, as the batch copy of this check does.\n *\n * @param sourceName - Source route name\n * @param targetName - Target route name\n * @param lookup - Path slots of routes that already exist\n */\nexport function validateForwardToParamCompatibility(\n sourceName: string,\n targetName: string,\n lookup: RouteLookup,\n): void {\n const sourceParams = new Set(lookup.getUrlParams(sourceName));\n\n // Check if target requires params that source doesn't have\n const missingParams = lookup\n .getUrlParams(targetName)\n .filter((param) => !sourceParams.has(param));\n\n if (missingParams.length > 0) {\n throw atUpdateRoute.plain`forwardTo target \"${targetName}\" requires params [${missingParams.join(\", \")}] that are not available in source route \"${sourceName}\"`;\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 forwardMap - The ONE-HOP forward map (never the resolved one)\n */\nexport function validateForwardToCycle(\n sourceName: string,\n targetName: string,\n forwardMap: Readonly<Record<string, string>>,\n): void {\n // Create a test map with the new entry to validate BEFORE mutation\n const testMap = {\n ...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 lookup - Existence and path slots of routes that already exist\n * @param forwardMap - The ONE-HOP forward map\n */\nexport function validateUpdateRoute<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n name: string,\n forwardTo: string | ForwardToCallback<Dependencies> | null | undefined,\n lookup: RouteLookup,\n forwardMap: Readonly<Record<string, string>>,\n): void {\n // Validate route exists\n if (!lookup.hasRoute(name)) {\n throw atUpdateRoute.ref`route \"${name}\" does not exist`;\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 (!lookup.hasRoute(forwardTo)) {\n throw atUpdateRoute.plain`forwardTo target \"${forwardTo}\" does not exist`;\n }\n\n // Check forwardTo param compatibility\n validateForwardToParamCompatibility(name, forwardTo, lookup);\n\n // Check for cycle detection\n validateForwardToCycle(name, forwardTo, forwardMap);\n }\n}\n","// packages/validation-plugin/src/validators/state.ts\n\nimport { raiser } from \"@real-router/core/utils\";\n\nimport { isString, isParams, getTypeDescription } from \"../type-guards\";\n\nconst atMakeState = raiser(\"router\", \"makeState\");\n\nexport function validateMakeStateArgs(\n name: unknown,\n params: unknown,\n path: unknown,\n): void {\n if (!isString(name)) {\n throw atMakeState.type`Invalid name: ${getTypeDescription(name)}. Expected string.`;\n }\n\n if (params !== undefined && !isParams(params)) {\n throw atMakeState.type`Invalid params: ${getTypeDescription(params)}. Expected plain object.`;\n }\n\n if (path !== undefined && !isString(path)) {\n throw atMakeState.type`Invalid path: ${getTypeDescription(path)}. Expected string.`;\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 { getPluginApi, getRoutesApi } from \"@real-router/core/api\";\nimport { raiser } 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 { nameInTable } from \"./validators/forwardTo\";\nimport {\n validateHandler,\n validateHandlerLimit,\n validateLifecycleCountThresholds,\n warnOverwrite as warnLifecycleOverwrite,\n warnAsyncGuardSync,\n warnNonBooleanGuard,\n warnNonBooleanGuardSync,\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 validateConfiguredDefaultRoute,\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 { RouteLookup } from \"./validators/forwardTo\";\nimport type {\n DefaultDependencies,\n PluginApi,\n PluginFactory,\n RouterValidator,\n Route,\n RoutesApi,\n Plugin,\n} from \"@real-router/core\";\nimport type { RouteTree } from \"@real-router/core/validation\";\n\nconst atValidationPlugin = raiser(\"validation-plugin\");\nconst atAddRoute = raiser(\"router\", \"addRoute\");\nconst atAreStatesEqual = raiser(\"router\", \"areStatesEqual\");\n\n/** The one question existence asks of a tree node: its children by segment. */\ninterface TreeNode {\n children: ReadonlyMap<string, TreeNode>;\n}\n\n/**\n * The two questions the validators ask about routes that already exist,\n * answered from the curated surface (#2382).\n */\nfunction createRouteLookup(api: PluginApi): RouteLookup {\n // ⚑ Existence WALKS the published tree rather than asking the matcher:\n // `getRoutesApi.has` would do the lookup too, but it runs `validateRouteName`\n // first and would rename a malformed `forwardTo` target's refusal to\n // `[router.hasRoute]`.\n //\n // ⚠ The walk answers what `matcher.hasRoute` answers except after\n // `children.set` on a handed-out tree — the Map shell-freeze exception\n // `engine/INVARIANTS.md` records.\n return {\n hasRoute: (name) => {\n let node = api.getTree() as TreeNode | undefined;\n\n for (const segment of name.split(\".\")) {\n node = node?.children.get(segment);\n\n if (!node) {\n return false;\n }\n }\n\n return true;\n },\n getUrlParams: (name) => api.getUrlParams(name),\n };\n}\n\n/** What the route validators read about the table a batch joins. */\ninterface JoinedTable {\n tree: RouteTree;\n lookup: RouteLookup;\n forwardMap: Readonly<Record<string, string>>;\n}\n\n/**\n * The lookup of a table with no route in it. A `RouteLookup` is asked for the\n * path slots of a route that exists, so this one is never asked for them.\n */\nconst NO_ROUTES: RouteLookup = {\n hasRoute: () => false,\n /* v8 ignore next -- @preserve unreachable: the table holds no route to ask about */\n getUrlParams: () => [],\n};\n\n/**\n * The table a `replace()` batch joins (#2562): the root the router keeps, with\n * no route and no forward under it. `add` extends the registered table and\n * `replace` discards it, so the route checks judge a replace batch as they judge\n * the same batch added to an empty router under that root —\n * `replace-batch.properties.ts` holds the two doors to that.\n *\n * ⚠ `children` and `nonAbsoluteChildren` both list the root's routes. Only\n * `children` is read by a validator today; the second is emptied too, so a\n * check that starts reading it does not get the discarded routes back.\n */\nfunction emptyTableUnder(root: RouteTree): JoinedTable {\n return {\n tree: { ...root, children: new Map(), nonAbsoluteChildren: [] },\n lookup: NO_ROUTES,\n forwardMap: {},\n };\n}\n\n/**\n * Every top-level route, nested, as `RoutesApi.get` reports it — the config\n * slots the retrospective pass judges included.\n */\nfunction readRoutes<Dependencies extends DefaultDependencies>(\n api: PluginApi,\n routesApi: RoutesApi<Dependencies>,\n): Route<Dependencies>[] {\n const routes: Route<Dependencies>[] = [];\n\n for (const name of (api.getTree() as TreeNode).children.keys()) {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- a top-level name of the tree always names a route\n routes.push(routesApi.get(name)!);\n }\n\n return routes;\n}\n\nfunction buildValidatorObject(\n api: PluginApi,\n lookup: RouteLookup,\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 api.getDeclaredQueryNames(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 validateRouteName(name, caller) {\n validateRouteName(name, caller);\n },\n validateSetRootPathArgs,\n },\n options: {\n validateOptions,\n validateResolvedDefaultRoute(routeName) {\n validateResolvedDefaultRoute(routeName, lookup);\n },\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 api.getDependencyKeys(),\n api.getResolvedLimits().maxDependencies,\n methodName,\n );\n },\n validateDependencyExists(name, value) {\n validateDependencyExistsRaw(value, name);\n },\n validateDependencyCount(currentCount, maxDependencies, methodName) {\n validateDependencyCount(\n currentCount,\n maxDependencies,\n methodName,\n api.logger,\n );\n },\n validateCloneArgs,\n warnOverwrite(name, methodName) {\n warnDepsOverwrite(name, methodName, api.logger);\n },\n warnBatchOverwrite(keys, methodName) {\n warnBatchOverwrite(keys, methodName, api.logger);\n },\n warnRemoveNonExistent(name) {\n warnRemoveNonExistent(name, api.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 api.getOptions().limits?.maxPlugins ?? CORE_LIMIT_DEFAULTS.maxPlugins;\n\n validatePluginCountThresholds(count, maxPlugins, api.logger);\n },\n warnBatchDuplicates() {\n warnBatchDuplicates(api.logger);\n },\n warnPluginMethodType(methodName) {\n warnPluginMethodType(methodName, api.logger);\n },\n },\n lifecycle: {\n validateHandlerLimit(count, methodName) {\n const maxHandlers =\n api.getOptions().limits?.maxLifecycleHandlers ??\n CORE_LIMIT_DEFAULTS.maxLifecycleHandlers;\n\n validateHandlerLimit(count, methodName, maxHandlers);\n },\n validateCountThresholds(count, methodName) {\n const maxHandlers =\n api.getOptions().limits?.maxLifecycleHandlers ??\n CORE_LIMIT_DEFAULTS.maxLifecycleHandlers;\n\n validateLifecycleCountThresholds(\n count,\n methodName,\n maxHandlers,\n api.logger,\n );\n },\n warnOverwrite(name, type, methodName) {\n warnLifecycleOverwrite(name, type, methodName, api.logger);\n },\n warnAsyncGuardSync(name, methodName) {\n warnAsyncGuardSync(name, methodName, api.logger);\n },\n warnNonBooleanGuardSync(name, answer, methodName) {\n warnNonBooleanGuardSync(name, answer, methodName, api.logger);\n },\n warnNonBooleanGuard(name, answer) {\n warnNonBooleanGuard(name, answer, api.logger);\n },\n },\n navigation: {\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(api.logger);\n },\n validateNavigateToStateArgs,\n validateNavigationOptions,\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 atAreStatesEqual.type`Invalid state1: ${getTypeDescription(s1)}. Expected State object.`;\n }\n if (!isState(s2)) {\n throw atAreStatesEqual.type`Invalid state2: ${getTypeDescription(s2)}. Expected State object.`;\n }\n if (ignoreQP !== undefined && !isBoolean(ignoreQP)) {\n throw atAreStatesEqual.type`Invalid ignoreQueryParams: ${getTypeDescription(ignoreQP)}. Expected boolean.`;\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 api.getOptions().limits?.maxListeners ??\n CORE_LIMIT_DEFAULTS.maxListeners;\n\n validateListenerCountThresholds(\n count,\n eventName,\n methodName,\n api.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 const api = getPluginApi(router);\n\n if (router.isActive()) {\n throw atValidationPlugin.code(\n \"VALIDATION_PLUGIN_AFTER_START\",\n )`must be registered before router.start()`;\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 atValidationPlugin.code(\n \"VALIDATION_PLUGIN_ALREADY_INSTALLED\",\n )`is already installed on this router — a clone re-runs plugin factories, so it needs no usePlugin of its own`;\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(api.getAdoptedOrigins());\n\n // ⚑ The object is held so teardown can prove it is still the holder.\n // The slot has no owner — `RouterInternals.validator` is plain data on a\n // surface pinned `accessorNames === []`, so a second writer cannot be\n // REFUSED here. What it can be is not silently destroyed: an\n // unconditional `= null` on teardown nulls whoever holds the slot now.\n // Measured: with a second writer in place, teardown destroyed the\n // foreign validator; the control (no second writer) nulled its own.\n // Same shape as `claimContextNamespace`, which checks the holder on\n // write and on release (#2059 / #1929) — here only the release half is\n // reachable, and the write half waits for the slot to go away.\n const lookup = createRouteLookup(api);\n const ownValidator = buildValidatorObject(api, lookup, defaultsWatch);\n // One branch, shared by the error path below and by `teardown`: both ask\n // the same question, so they share the site rather than each growing an\n // arm the other's test has to reach.\n const releaseIfStillOurs = () => {\n if (ctx.validator === ownValidator) {\n ctx.validator = null;\n }\n };\n\n ctx.validator = ownValidator;\n\n try {\n const routes = readRoutes(api, getRoutesApi(router));\n const forwardMap = api.getForwardMap();\n const limits = api.getResolvedLimits();\n const options = api.getOptions();\n\n validateExistingRoutes(routes);\n validateForwardToConsistency(forwardMap, lookup);\n validateRoutePropertiesStore(routes);\n validateForwardToTargetsStore(forwardMap, lookup);\n validateDependenciesStructure(\n ctx.dependenciesGetStore().dependencies,\n limits,\n );\n validateLimitsConsistency(\n options,\n api.getDependencyKeys().length,\n limits.maxDependencies,\n );\n ctx.validator.options.validateOptions(\n options,\n \"constructor (retrospective)\",\n );\n\n if (typeof options.defaultRoute === \"string\") {\n validateConfiguredDefaultRoute(options.defaultRoute, lookup);\n }\n } catch (error) {\n releaseIfStillOurs();\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 = api.addInterceptor(\"start\", (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(api.getExternalGuardNames(), lookup, api.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 // ⚠ A CHECK, not a `RouterValidator` member, and the distinction is the\n // argument rather than the style: core hands this position `ownParams` —\n // the copy it will print the path from — which no seam can reach, because\n // at the call boundary that object does not exist yet (#2134 / #2388).\n //\n // ⚠ Registered AFTER the retrospective pass above, so the error path's\n // `releaseIfStillOurs` leaves nothing registered behind it.\n const removeParamsCheck = api.addCheck(\"buildPath:params\", (ownParams) => {\n validateNavigateParams(ownParams, \"buildPath\");\n });\n\n // ⚠ The resolved printer is reached INDEPENDENTLY of the facade's — the\n // href door runs the forward chain itself and lands here — so it carries its\n // own registration rather than sharing one, and the message names it.\n const removeResolvedParamsCheck = api.addCheck(\n \"buildPathResolved:params\",\n (ownParams) => {\n validateNavigateParams(ownParams, \"buildPathResolved\");\n },\n );\n\n // ⚠ The calls inside each ENTRY check keep the order core consulted them\n // in: the first refusal is the message the caller gets, so reordering them\n // changes which defect a caller hears about when an argument has two.\n const removeCanNavigateToEntryCheck = api.addCheck(\n \"canNavigateTo:entry\",\n (name, params, search) => {\n validateRouteName(name, \"canNavigateTo\");\n validateNavigateParamsShape(params, \"canNavigateTo\");\n validateSearch(search, \"canNavigateTo\");\n },\n );\n\n const removeCanNavigateToParamsCheck = api.addCheck(\n \"canNavigateTo:params\",\n (ownParams) => {\n validateNavigateParams(ownParams, \"canNavigateTo\");\n },\n );\n\n const removeNavigateEntryCheck = api.addCheck(\n \"navigate:entry\",\n (routeName, routeParams, search, options) => {\n validateNavigateArgs(routeName);\n validateNavigateParamsShape(routeParams, \"navigate\");\n validateSearch(search, \"navigate\");\n validateNavigationOptions(options, \"navigate\");\n },\n );\n\n const removeNavigateParamsCheck = api.addCheck(\n \"navigate:params\",\n (ownParams) => {\n validateNavigateParams(ownParams, \"navigate\");\n },\n );\n\n // ⚠ The callback walk runs FIRST at both batch positions, because that is\n // where core ran it: a route whose `canActivate` is not a function is\n // refused before anything asks about names or paths.\n const walkRouteCallbacks = (\n batch: readonly Route[],\n parentName: unknown,\n ): void => {\n for (const route of batch) {\n const fullName = nameInTable(parentName, route.name);\n\n guardRouteCallbacks(route);\n guardNoAsyncCallbacks(route, fullName);\n\n if (route.children !== undefined) {\n walkRouteCallbacks(route.children, fullName);\n }\n }\n };\n\n const checkRouteBatch = (\n batch: readonly Route[],\n caller: \"addRoute\" | \"replaceRoutes\",\n parentName: string | undefined,\n readTable: () => JoinedTable,\n ): void => {\n // ⚠ The parent option is judged below the walk, so the walk joins it to\n // a name only once the table holds it: joined to an option about to be\n // refused, the name is one no route can have.\n walkRouteCallbacks(\n batch,\n typeof parentName === \"string\" && lookup.hasRoute(parentName)\n ? parentName\n : undefined,\n );\n\n if (parentName !== undefined) {\n validateParentOptionRaw(parentName);\n\n if (!lookup.hasRoute(parentName)) {\n throw atAddRoute.ref`Parent route \"${parentName}\" does not exist`;\n }\n }\n\n throwIfInternalRouteInArray(batch, caller);\n validateAddRouteArgs(batch, parentName);\n\n // ⚠ Read HERE, not at the door: the walk and the argument checks above\n // run application code (a route function's own `toString`), and a table\n // read before it can be one the router no longer holds.\n const table = readTable();\n\n validateRoutes(\n batch as Route[],\n table.tree,\n table.lookup,\n table.forwardMap,\n parentName,\n );\n };\n\n const removeAddRouteCheck = api.addCheck(\n \"addRoute:batch\",\n (batch, parentName) => {\n checkRouteBatch(\n batch as readonly Route[],\n \"addRoute\",\n parentName,\n () => ({\n tree: api.getTree(),\n lookup,\n forwardMap: api.getForwardMap(),\n }),\n );\n },\n );\n\n const removeReplaceRoutesCheck = api.addCheck(\n \"replaceRoutes:batch\",\n (batch) => {\n checkRouteBatch(\n batch as readonly Route[],\n \"replaceRoutes\",\n undefined,\n () => emptyTableUnder(api.getTree()),\n );\n },\n );\n\n const removeRemoveRouteCheck = api.addCheck(\"removeRoute:entry\", (name) => {\n validateRemoveRouteArgs(name);\n throwIfInternalRoute(name, \"removeRoute\");\n });\n\n const removeUpdateRouteCheck = api.addCheck(\n \"updateRoute:entry\",\n (name, updates) => {\n const upd = updates as Record<string, unknown>;\n\n validateUpdateRouteBasicArgs(name, updates);\n throwIfInternalRoute(name, \"updateRoute\");\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 validateUpdateRoute(\n name,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument\n upd.forwardTo as any,\n lookup,\n api.getForwardMap(),\n );\n },\n );\n\n const removeHasRouteCheck = api.addCheck(\"hasRoute:entry\", (name) => {\n validateRouteName(name, \"hasRoute\");\n });\n\n const removeGetRouteCheck = api.addCheck(\"getRoute:entry\", (name) => {\n validateRouteName(name, \"getRoute\");\n });\n\n // ⚠ The two state builders consult the SAME pair in opposite orders, and\n // each check keeps its own door's order: the first refusal is the message\n // the caller gets.\n const removeForwardStateCheck = api.addCheck(\n \"forwardState:entry\",\n (routeName, routeParams, routeSearch) => {\n validateStateBuilderArgs(routeName, routeParams, \"forwardState\");\n validateSearch(routeSearch, \"forwardState\");\n },\n );\n\n const removeBuildNavigationStateCheck = api.addCheck(\n \"buildNavigationState:state\",\n (name, ownParams, search) => {\n validateSearch(search, \"buildNavigationState\");\n validateStateBuilderArgs(name, ownParams, \"buildNavigationState\");\n },\n );\n\n const removeAddActivateGuardCheck = api.addCheck(\n \"addActivateGuard:entry\",\n (name, handler) => {\n validateRouteName(name, \"addActivateGuard\");\n validateHandler(handler, \"addActivateGuard\");\n },\n );\n\n const removeAddDeactivateGuardCheck = api.addCheck(\n \"addDeactivateGuard:entry\",\n (name, handler) => {\n validateRouteName(name, \"addDeactivateGuard\");\n validateHandler(handler, \"addDeactivateGuard\");\n },\n );\n\n const removeRemoveActivateGuardCheck = api.addCheck(\n \"removeActivateGuard:entry\",\n (name) => {\n validateRouteName(name, \"removeActivateGuard\");\n },\n );\n\n const removeRemoveDeactivateGuardCheck = api.addCheck(\n \"removeDeactivateGuard:entry\",\n (name) => {\n validateRouteName(name, \"removeDeactivateGuard\");\n },\n );\n\n // ⚠ A DIAGNOSTIC, not a check, and the channel is the difference rather\n // than the style: this one cannot refuse the registration it describes —\n // the plugin is already installed by the time core reports.\n const removePluginAfterStartDiagnostic = api.subscribeDiagnostic(\n \"PLUGIN_AFTER_START\",\n (methodName) => {\n warnPluginAfterStart(methodName, api.logger);\n },\n );\n\n return {\n teardown() {\n removeParamsCheck();\n\n removeResolvedParamsCheck();\n\n removeCanNavigateToEntryCheck();\n\n removeCanNavigateToParamsCheck();\n\n removeNavigateEntryCheck();\n\n removeNavigateParamsCheck();\n\n removeAddRouteCheck();\n\n removeReplaceRoutesCheck();\n\n removeRemoveRouteCheck();\n\n removeUpdateRouteCheck();\n\n removeHasRouteCheck();\n\n removeGetRouteCheck();\n\n removeForwardStateCheck();\n\n removeBuildNavigationStateCheck();\n\n removeAddActivateGuardCheck();\n\n removeAddDeactivateGuardCheck();\n\n removeRemoveActivateGuardCheck();\n\n removeRemoveDeactivateGuardCheck();\n\n removePluginAfterStartDiagnostic();\n\n removeInterceptor();\n\n releaseIfStillOurs();\n },\n };\n };\n}\n"],"mappings":"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"}
|