@real-router/persistent-params-plugin 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/constants.ts","../../../type-guards/src/guards/primitives.ts","../../src/utils.ts","../../src/plugin.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// packages/persistent-params-plugin/modules/constants.ts\n\n/**\n * Symbol to mark router as initialized with this plugin.\n * Prevents double initialization and memory leaks from method wrapping.\n */\nexport const PLUGIN_MARKER = Symbol(\"persistent-params-plugin\");\n","// packages/type-guards/modules/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 Promise type.\n *\n * @param value - Value to check\n * @returns true if value is a Promise\n */\nexport function isPromise<T = unknown>(value: unknown): value is Promise<T> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"then\" in value &&\n typeof value.then === \"function\"\n );\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\n/**\n * Type guard for primitive values suitable for URL parameters.\n * Only string, number, and boolean are allowed.\n * Rejects NaN and Infinity for numbers.\n *\n * @param value - Value to check\n * @returns true if value is string, number, or boolean\n */\nexport function isPrimitiveValue(\n value: unknown,\n): value is string | number | boolean {\n if (typeof value === \"number\") {\n return Number.isFinite(value);\n }\n\n return typeof value === \"string\" || typeof value === \"boolean\";\n}\n","// packages/persistent-params-plugin/modules/utils.ts\n\nimport { isPrimitiveValue } from \"type-guards\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params } from \"@real-router/core\";\n\nconst INVALID_PARAM_KEY_REGEX = /[\\s#%&/=?\\\\]/;\nconst INVALID_CHARS_MESSAGE = String.raw`Cannot contain: = & ? # % / \\ or whitespace`;\n\nexport function validateParamKey(key: string): void {\n if (INVALID_PARAM_KEY_REGEX.test(key)) {\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Invalid parameter name \"${key}\". ${INVALID_CHARS_MESSAGE}`,\n );\n }\n}\n\n/**\n * Validates params configuration structure and values.\n * Ensures all parameter names are non-empty strings and all default values are primitives.\n *\n * @param config - Configuration to validate\n * @returns true if configuration is valid\n */\n/**\n * Validates params configuration structure and values.\n * Ensures all parameter names are non-empty strings and all default values are primitives.\n *\n * @param config - Configuration to validate\n * @returns true if configuration is valid\n */\nexport function isValidParamsConfig(\n config: unknown,\n): config is PersistentParamsConfig {\n if (config === null || config === undefined) {\n return false;\n }\n\n // Array configuration: all items must be non-empty strings\n if (Array.isArray(config)) {\n return config.every((item) => {\n if (typeof item !== \"string\" || item.length === 0) {\n return false;\n }\n\n try {\n validateParamKey(item);\n\n return true;\n } catch {\n return false;\n }\n });\n }\n\n // Object configuration: must be plain object with primitive values\n if (typeof config === \"object\") {\n // Reject non-plain objects (Date, Map, etc.)\n if (Object.getPrototypeOf(config) !== Object.prototype) {\n return false;\n }\n\n // All keys must be non-empty strings, all values must be primitives\n return Object.entries(config).every(([key, value]) => {\n // Check key is non-empty string\n if (typeof key !== \"string\" || key.length === 0) {\n return false;\n }\n\n // Validate key doesn't contain special characters\n try {\n validateParamKey(key);\n } catch {\n return false;\n }\n\n // Validate value is primitive (NaN/Infinity already rejected by isPrimitiveValue)\n return isPrimitiveValue(value);\n });\n }\n\n return false;\n}\n\n/**\n * Validates parameter value before persisting.\n * Throws descriptive TypeError if value is not valid for URL parameters.\n *\n * @param key - Parameter name for error messages\n * @param value - Value to validate\n * @throws {TypeError} If value is null, array, object, or other non-primitive type\n */\nexport function validateParamValue(key: string, value: unknown): void {\n if (value === null) {\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Parameter \"${key}\" cannot be null. ` +\n `Use undefined to remove the parameter from persistence.`,\n );\n }\n\n if (value !== undefined && !isPrimitiveValue(value)) {\n const actualType = Array.isArray(value) ? \"array\" : typeof value;\n\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Parameter \"${key}\" must be a primitive value ` +\n `(string, number, or boolean), got ${actualType}. ` +\n `Objects and arrays are not supported in URL parameters.`,\n );\n }\n}\n\n/**\n * Safely extracts own properties from params object.\n * Uses Object.hasOwn to prevent prototype pollution attacks.\n *\n * @param params - Parameters object (may contain inherited properties)\n * @returns New object with only own properties\n *\n * @example\n * const malicious = Object.create({ __proto__: { admin: true } });\n * malicious.mode = 'dev';\n * const safe = extractOwnParams(malicious); // { mode: 'dev' } (no __proto__)\n */\nexport function extractOwnParams(params: Params): Params {\n const result: Params = {};\n\n for (const key in params) {\n // Only process own properties, skip inherited ones\n if (Object.hasOwn(params, key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n\n/**\n * Parses path into base path and query string components.\n * Handles edge cases like leading ?, multiple ?, empty path.\n *\n * @param path - Path to parse (e.g., \"/route?param=value\")\n * @returns Object with basePath and queryString\n *\n * @example\n * parseQueryString('/users?page=1') // { basePath: '/users', queryString: 'page=1' }\n * parseQueryString('?existing') // { basePath: '', queryString: 'existing' }\n * parseQueryString('/path') // { basePath: '/path', queryString: '' }\n */\nexport function parseQueryString(path: string): {\n basePath: string;\n queryString: string;\n} {\n const questionMarkIndex = path.indexOf(\"?\");\n\n // No query string\n if (questionMarkIndex === -1) {\n return { basePath: path, queryString: \"\" };\n }\n\n // Path starts with ? (edge case)\n if (questionMarkIndex === 0) {\n return { basePath: \"\", queryString: path.slice(1) };\n }\n\n // Normal case: path?query\n return {\n basePath: path.slice(0, questionMarkIndex),\n queryString: path.slice(questionMarkIndex + 1),\n };\n}\n\n/**\n * Builds query string from parameter names.\n * Preserves existing query parameters and appends new ones.\n *\n * @param existingQuery - Existing query string (without leading ?)\n * @param paramNames - Parameter names to append\n * @returns Combined query string\n *\n * @example\n * buildQueryString('existing=1', ['mode', 'lang']) // 'existing=1&mode&lang'\n * buildQueryString('', ['mode']) // 'mode'\n */\nexport function buildQueryString(\n existingQuery: string,\n paramNames: readonly string[],\n): string {\n if (paramNames.length === 0) {\n return existingQuery;\n }\n\n const separator = existingQuery ? \"&\" : \"\";\n\n return existingQuery + separator + paramNames.join(\"&\");\n}\n\n/**\n * Merges persistent and current parameters into a single Params object.\n * Keys explicitly set to `undefined` in current params are removed from result.\n *\n * Creates a new immutable object - does not mutate input parameters.\n *\n * @param persistent - Frozen persistent parameters\n * @param current - Current parameters from navigation\n * @returns New Params object with merged values\n *\n * @example\n * const persistent = { lang: 'en', theme: 'dark' };\n * const current = { theme: 'light', mode: 'dev' };\n * mergeParams(persistent, current); // { lang: 'en', theme: 'light', mode: 'dev' }\n *\n * @example\n * // Removing parameters with undefined\n * const persistent = { lang: 'en', theme: 'dark' };\n * const current = { theme: undefined };\n * mergeParams(persistent, current); // { lang: 'en' } (theme removed)\n */\nexport function mergeParams(\n persistent: Readonly<Params>,\n current: Params,\n): Params {\n // Safely extract own properties from current params\n const safeCurrentParams = extractOwnParams(current);\n\n // Start with persistent params, but EXCLUDE undefined values\n // (undefined values don't appear in URLs, so we shouldn't include them)\n const result: Params = {};\n\n for (const key in persistent) {\n if (Object.hasOwn(persistent, key) && persistent[key] !== undefined) {\n result[key] = persistent[key];\n }\n }\n\n // Apply current params\n for (const key of Object.keys(safeCurrentParams)) {\n const value = safeCurrentParams[key];\n\n if (value === undefined) {\n // Remove param if explicitly set to undefined\n delete result[key];\n } else {\n // Add or update param\n result[key] = value;\n }\n }\n\n return result;\n}\n","// packages/persistent-params-plugin/modules/plugin.ts\n\nimport { PLUGIN_MARKER } from \"./constants\";\nimport {\n buildQueryString,\n extractOwnParams,\n isValidParamsConfig,\n mergeParams,\n parseQueryString,\n validateParamValue,\n} from \"./utils\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params, PluginFactory, Plugin } from \"@real-router/core\";\n\n/**\n * Factory for the persistent parameters' plugin.\n *\n * This plugin allows you to specify certain route parameters to be persisted across\n * all navigation transitions. Persisted parameters are automatically merged into\n * route parameters when building paths or states.\n *\n * Key features:\n * - Automatic persistence of query parameters across navigations\n * - Support for default values\n * - Type-safe (only primitives: string, number, boolean)\n * - Immutable internal state\n * - Protection against prototype pollution\n * - Full teardown support (can be safely unsubscribed)\n *\n * If a persisted parameter is explicitly set to `undefined` during navigation,\n * it will be removed from the persisted state and omitted from subsequent URLs.\n *\n * The plugin also adjusts the router's root path to include query parameters for\n * all persistent params, ensuring correct URL construction.\n *\n * @param params - Either an array of parameter names (strings) to persist,\n * or an object mapping parameter names to initial values.\n * If an array, initial values will be `undefined`.\n *\n * @returns A PluginFactory that creates the persistent params plugin instance.\n *\n * @example\n * // Persist parameters without default values\n * router.usePlugin(persistentParamsPlugin(['mode', 'lang']));\n *\n * @example\n * // Persist parameters with default values\n * router.usePlugin(persistentParamsPlugin({ mode: 'dev', lang: 'en' }));\n *\n * @example\n * // Removing a persisted parameter\n * router.navigate('route', { mode: undefined }); // mode will be removed\n *\n * @example\n * // Unsubscribing (full cleanup)\n * const unsubscribe = router.usePlugin(persistentParamsPlugin(['mode']));\n * unsubscribe(); // Restores original router state\n *\n * @throws {TypeError} If params is not a valid array of strings or object with primitives\n * @throws {Error} If plugin is already initialized on this router instance\n */\nexport function persistentParamsPluginFactory(\n params: PersistentParamsConfig = {},\n): PluginFactory {\n // Validate input configuration\n if (!isValidParamsConfig(params)) {\n let actualType: string;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (params === null) {\n actualType = \"null\";\n } else if (Array.isArray(params)) {\n actualType = \"array with invalid items\";\n } else {\n actualType = typeof params;\n }\n\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Invalid params configuration. ` +\n `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,\n );\n }\n\n // Empty configuration - valid but does nothing\n if (Array.isArray(params) && params.length === 0) {\n return () => ({});\n }\n\n if (!Array.isArray(params) && Object.keys(params).length === 0) {\n return () => ({});\n }\n\n return (router): Plugin => {\n // Check if plugin is already initialized on this router\n if (PLUGIN_MARKER in router) {\n throw new Error(\n `[@real-router/persistent-params-plugin] Plugin already initialized on this router. ` +\n `To reconfigure, first unsubscribe the existing plugin using the returned unsubscribe function.`,\n );\n }\n\n // Mark router as initialized\n (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER] = true;\n\n // Initialize frozen persistent parameters\n let persistentParams: Readonly<Params>;\n\n if (Array.isArray(params)) {\n const initial: Params = {};\n\n for (const param of params) {\n initial[param] = undefined;\n }\n\n persistentParams = Object.freeze(initial);\n } else {\n persistentParams = Object.freeze({ ...params });\n }\n\n // Track parameter names\n const paramNamesSet = new Set<string>(\n Array.isArray(params) ? [...params] : Object.keys(params),\n );\n\n // Store original router methods for restoration\n const originalBuildPath = router.buildPath.bind(router);\n const originalForwardState = router.forwardState.bind(router);\n const originalRootPath = router.getRootPath();\n\n // Update router root path to include query parameters for persistent params\n try {\n const { basePath, queryString } = parseQueryString(originalRootPath);\n // Note: newQueryString is always non-empty here because:\n // - Empty params are handled by early returns at lines 94-100\n // - So paramNamesSet always has at least one element\n // - So buildQueryString always returns a non-empty string\n const newQueryString = buildQueryString(queryString, [...paramNamesSet]);\n\n router.setRootPath(`${basePath}?${newQueryString}`);\n } catch (error) {\n // Rollback initialization marker on error\n delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];\n\n throw new Error(\n `[@real-router/persistent-params-plugin] Failed to update root path: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n /**\n * Merges persistent parameters with current navigation parameters.\n * Validates all parameter values before merging.\n *\n * @param additionalParams - Parameters passed during navigation\n * @returns Merged parameters object\n * @throws {TypeError} If any parameter value is invalid (not a primitive)\n */\n\n function withPersistentParams(additionalParams: Params): Params {\n // Extract safe params (prevent prototype pollution)\n const safeParams = extractOwnParams(additionalParams);\n\n // Validate and collect parameters to remove in a single pass\n const paramsToRemove: string[] = [];\n\n for (const key of Object.keys(safeParams)) {\n const value = safeParams[key];\n\n // If undefined and tracked, mark for removal (skip validation)\n if (value === undefined && paramNamesSet.has(key)) {\n paramsToRemove.push(key);\n } else {\n // Validate all other parameters\n validateParamValue(key, value);\n }\n }\n\n // Process all removals in one batch\n if (paramsToRemove.length > 0) {\n // Remove from both Set\n for (const key of paramsToRemove) {\n paramNamesSet.delete(key);\n }\n\n // Update persistentParams once (batch freeze)\n const newParams: Params = { ...persistentParams };\n\n for (const key of paramsToRemove) {\n delete newParams[key];\n }\n\n persistentParams = Object.freeze(newParams);\n }\n\n // Merge persistent and current params\n return mergeParams(persistentParams, safeParams);\n }\n\n // Override router methods to inject persistent params\n // buildPath: needed for direct buildPath() calls (doesn't go through forwardState)\n router.buildPath = (routeName, buildPathParams = {}) =>\n originalBuildPath(routeName, withPersistentParams(buildPathParams));\n\n // forwardState: intercepts params normalization for buildState, buildStateWithSegments, and navigate\n // This is the central point where params are normalized before state creation\n router.forwardState = <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => {\n const result = originalForwardState(routeName, routeParams);\n\n return {\n ...result,\n params: withPersistentParams(result.params) as P,\n };\n };\n\n return {\n /**\n * Updates persistent parameters after successful transition.\n * Only processes parameters that are tracked and have changed.\n *\n * @param toState - Target state after successful transition\n */\n onTransitionSuccess(toState) {\n try {\n // Collect changed parameters and removals\n const updates: Params = {};\n const removals: string[] = [];\n let hasChanges = false;\n\n for (const key of paramNamesSet) {\n const value = toState.params[key];\n\n // If parameter is not in state params or is undefined, mark for removal\n if (!Object.hasOwn(toState.params, key) || value === undefined) {\n // Only mark as removal if it currently exists in persistentParams\n if (\n Object.hasOwn(persistentParams, key) &&\n persistentParams[key] !== undefined\n ) {\n removals.push(key);\n hasChanges = true;\n }\n\n continue;\n }\n\n // Validate type before storing\n validateParamValue(key, value);\n\n // Only update if value actually changed\n if (persistentParams[key] !== value) {\n updates[key] = value;\n hasChanges = true;\n }\n }\n\n // Create new frozen object only if there were changes\n if (hasChanges) {\n const newParams: Params = { ...persistentParams, ...updates };\n\n // Remove parameters that were set to undefined\n for (const key of removals) {\n delete newParams[key];\n }\n\n persistentParams = Object.freeze(newParams);\n }\n } catch (error) {\n // Log error but don't break navigation\n /* v8 ignore next 5 -- @preserve defensive: validation happens before navigate() */\n console.error(\n \"persistent-params-plugin\",\n \"Error updating persistent params:\",\n error,\n );\n }\n },\n\n /**\n * Cleanup function to restore original router state.\n * Restores all overridden methods and paths.\n * Called when plugin is unsubscribed.\n */\n teardown() {\n try {\n // Restore original methods\n router.buildPath = originalBuildPath;\n router.forwardState = originalForwardState;\n\n // Restore original root path\n router.setRootPath(originalRootPath);\n\n // Remove initialization marker\n delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];\n } catch (error) {\n console.error(\n \"persistent-params-plugin\",\n \"Error during teardown:\",\n error,\n );\n }\n },\n };\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/utils.ts","../../src/plugin.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// packages/persistent-params-plugin/modules/constants.ts\n\n/**\n * Symbol to mark router as initialized with this plugin.\n * Prevents double initialization and memory leaks from method wrapping.\n */\nexport const PLUGIN_MARKER = Symbol(\"persistent-params-plugin\");\n","// packages/persistent-params-plugin/modules/utils.ts\n\nimport { isPrimitiveValue } from \"type-guards\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params } from \"@real-router/core\";\n\nconst INVALID_PARAM_KEY_REGEX = /[\\s#%&/=?\\\\]/;\nconst INVALID_CHARS_MESSAGE = String.raw`Cannot contain: = & ? # % / \\ or whitespace`;\n\nexport function validateParamKey(key: string): void {\n if (INVALID_PARAM_KEY_REGEX.test(key)) {\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Invalid parameter name \"${key}\". ${INVALID_CHARS_MESSAGE}`,\n );\n }\n}\n\n/**\n * Validates params configuration structure and values.\n * Ensures all parameter names are non-empty strings and all default values are primitives.\n *\n * @param config - Configuration to validate\n * @returns true if configuration is valid\n */\n/**\n * Validates params configuration structure and values.\n * Ensures all parameter names are non-empty strings and all default values are primitives.\n *\n * @param config - Configuration to validate\n * @returns true if configuration is valid\n */\nexport function isValidParamsConfig(\n config: unknown,\n): config is PersistentParamsConfig {\n if (config === null || config === undefined) {\n return false;\n }\n\n // Array configuration: all items must be non-empty strings\n if (Array.isArray(config)) {\n return config.every((item) => {\n if (typeof item !== \"string\" || item.length === 0) {\n return false;\n }\n\n try {\n validateParamKey(item);\n\n return true;\n } catch {\n return false;\n }\n });\n }\n\n // Object configuration: must be plain object with primitive values\n if (typeof config === \"object\") {\n // Reject non-plain objects (Date, Map, etc.)\n if (Object.getPrototypeOf(config) !== Object.prototype) {\n return false;\n }\n\n // All keys must be non-empty strings, all values must be primitives\n return Object.entries(config).every(([key, value]) => {\n // Check key is non-empty string\n if (typeof key !== \"string\" || key.length === 0) {\n return false;\n }\n\n // Validate key doesn't contain special characters\n try {\n validateParamKey(key);\n } catch {\n return false;\n }\n\n // Validate value is primitive (NaN/Infinity already rejected by isPrimitiveValue)\n return isPrimitiveValue(value);\n });\n }\n\n return false;\n}\n\n/**\n * Validates parameter value before persisting.\n * Throws descriptive TypeError if value is not valid for URL parameters.\n *\n * @param key - Parameter name for error messages\n * @param value - Value to validate\n * @throws {TypeError} If value is null, array, object, or other non-primitive type\n */\nexport function validateParamValue(key: string, value: unknown): void {\n if (value === null) {\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Parameter \"${key}\" cannot be null. ` +\n `Use undefined to remove the parameter from persistence.`,\n );\n }\n\n if (value !== undefined && !isPrimitiveValue(value)) {\n const actualType = Array.isArray(value) ? \"array\" : typeof value;\n\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Parameter \"${key}\" must be a primitive value ` +\n `(string, number, or boolean), got ${actualType}. ` +\n `Objects and arrays are not supported in URL parameters.`,\n );\n }\n}\n\n/**\n * Safely extracts own properties from params object.\n * Uses Object.hasOwn to prevent prototype pollution attacks.\n *\n * @param params - Parameters object (may contain inherited properties)\n * @returns New object with only own properties\n *\n * @example\n * const malicious = Object.create({ __proto__: { admin: true } });\n * malicious.mode = 'dev';\n * const safe = extractOwnParams(malicious); // { mode: 'dev' } (no __proto__)\n */\nexport function extractOwnParams(params: Params): Params {\n const result: Params = {};\n\n for (const key in params) {\n // Only process own properties, skip inherited ones\n if (Object.hasOwn(params, key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n\n/**\n * Parses path into base path and query string components.\n * Handles edge cases like leading ?, multiple ?, empty path.\n *\n * @param path - Path to parse (e.g., \"/route?param=value\")\n * @returns Object with basePath and queryString\n *\n * @example\n * parseQueryString('/users?page=1') // { basePath: '/users', queryString: 'page=1' }\n * parseQueryString('?existing') // { basePath: '', queryString: 'existing' }\n * parseQueryString('/path') // { basePath: '/path', queryString: '' }\n */\nexport function parseQueryString(path: string): {\n basePath: string;\n queryString: string;\n} {\n const questionMarkIndex = path.indexOf(\"?\");\n\n // No query string\n if (questionMarkIndex === -1) {\n return { basePath: path, queryString: \"\" };\n }\n\n // Path starts with ? (edge case)\n if (questionMarkIndex === 0) {\n return { basePath: \"\", queryString: path.slice(1) };\n }\n\n // Normal case: path?query\n return {\n basePath: path.slice(0, questionMarkIndex),\n queryString: path.slice(questionMarkIndex + 1),\n };\n}\n\n/**\n * Builds query string from parameter names.\n * Preserves existing query parameters and appends new ones.\n *\n * @param existingQuery - Existing query string (without leading ?)\n * @param paramNames - Parameter names to append\n * @returns Combined query string\n *\n * @example\n * buildQueryString('existing=1', ['mode', 'lang']) // 'existing=1&mode&lang'\n * buildQueryString('', ['mode']) // 'mode'\n */\nexport function buildQueryString(\n existingQuery: string,\n paramNames: readonly string[],\n): string {\n if (paramNames.length === 0) {\n return existingQuery;\n }\n\n const separator = existingQuery ? \"&\" : \"\";\n\n return existingQuery + separator + paramNames.join(\"&\");\n}\n\n/**\n * Merges persistent and current parameters into a single Params object.\n * Keys explicitly set to `undefined` in current params are removed from result.\n *\n * Creates a new immutable object - does not mutate input parameters.\n *\n * @param persistent - Frozen persistent parameters\n * @param current - Current parameters from navigation\n * @returns New Params object with merged values\n *\n * @example\n * const persistent = { lang: 'en', theme: 'dark' };\n * const current = { theme: 'light', mode: 'dev' };\n * mergeParams(persistent, current); // { lang: 'en', theme: 'light', mode: 'dev' }\n *\n * @example\n * // Removing parameters with undefined\n * const persistent = { lang: 'en', theme: 'dark' };\n * const current = { theme: undefined };\n * mergeParams(persistent, current); // { lang: 'en' } (theme removed)\n */\nexport function mergeParams(\n persistent: Readonly<Params>,\n current: Params,\n): Params {\n // Safely extract own properties from current params\n const safeCurrentParams = extractOwnParams(current);\n\n // Start with persistent params, but EXCLUDE undefined values\n // (undefined values don't appear in URLs, so we shouldn't include them)\n const result: Params = {};\n\n for (const key in persistent) {\n if (Object.hasOwn(persistent, key) && persistent[key] !== undefined) {\n result[key] = persistent[key];\n }\n }\n\n // Apply current params\n for (const key of Object.keys(safeCurrentParams)) {\n const value = safeCurrentParams[key];\n\n if (value === undefined) {\n // Remove param if explicitly set to undefined\n delete result[key];\n } else {\n // Add or update param\n result[key] = value;\n }\n }\n\n return result;\n}\n","// packages/persistent-params-plugin/modules/plugin.ts\n\nimport { PLUGIN_MARKER } from \"./constants\";\nimport {\n buildQueryString,\n extractOwnParams,\n isValidParamsConfig,\n mergeParams,\n parseQueryString,\n validateParamValue,\n} from \"./utils\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params, PluginFactory, Plugin } from \"@real-router/core\";\n\n/**\n * Factory for the persistent parameters' plugin.\n *\n * This plugin allows you to specify certain route parameters to be persisted across\n * all navigation transitions. Persisted parameters are automatically merged into\n * route parameters when building paths or states.\n *\n * Key features:\n * - Automatic persistence of query parameters across navigations\n * - Support for default values\n * - Type-safe (only primitives: string, number, boolean)\n * - Immutable internal state\n * - Protection against prototype pollution\n * - Full teardown support (can be safely unsubscribed)\n *\n * If a persisted parameter is explicitly set to `undefined` during navigation,\n * it will be removed from the persisted state and omitted from subsequent URLs.\n *\n * The plugin also adjusts the router's root path to include query parameters for\n * all persistent params, ensuring correct URL construction.\n *\n * @param params - Either an array of parameter names (strings) to persist,\n * or an object mapping parameter names to initial values.\n * If an array, initial values will be `undefined`.\n *\n * @returns A PluginFactory that creates the persistent params plugin instance.\n *\n * @example\n * // Persist parameters without default values\n * router.usePlugin(persistentParamsPlugin(['mode', 'lang']));\n *\n * @example\n * // Persist parameters with default values\n * router.usePlugin(persistentParamsPlugin({ mode: 'dev', lang: 'en' }));\n *\n * @example\n * // Removing a persisted parameter\n * router.navigate('route', { mode: undefined }); // mode will be removed\n *\n * @example\n * // Unsubscribing (full cleanup)\n * const unsubscribe = router.usePlugin(persistentParamsPlugin(['mode']));\n * unsubscribe(); // Restores original router state\n *\n * @throws {TypeError} If params is not a valid array of strings or object with primitives\n * @throws {Error} If plugin is already initialized on this router instance\n */\nexport function persistentParamsPluginFactory(\n params: PersistentParamsConfig = {},\n): PluginFactory {\n // Validate input configuration\n if (!isValidParamsConfig(params)) {\n let actualType: string;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (params === null) {\n actualType = \"null\";\n } else if (Array.isArray(params)) {\n actualType = \"array with invalid items\";\n } else {\n actualType = typeof params;\n }\n\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Invalid params configuration. ` +\n `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,\n );\n }\n\n // Empty configuration - valid but does nothing\n if (Array.isArray(params) && params.length === 0) {\n return () => ({});\n }\n\n if (!Array.isArray(params) && Object.keys(params).length === 0) {\n return () => ({});\n }\n\n return (router): Plugin => {\n // Check if plugin is already initialized on this router\n if (PLUGIN_MARKER in router) {\n throw new Error(\n `[@real-router/persistent-params-plugin] Plugin already initialized on this router. ` +\n `To reconfigure, first unsubscribe the existing plugin using the returned unsubscribe function.`,\n );\n }\n\n // Mark router as initialized\n (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER] = true;\n\n // Initialize frozen persistent parameters\n let persistentParams: Readonly<Params>;\n\n if (Array.isArray(params)) {\n const initial: Params = {};\n\n for (const param of params) {\n initial[param] = undefined;\n }\n\n persistentParams = Object.freeze(initial);\n } else {\n persistentParams = Object.freeze({ ...params });\n }\n\n // Track parameter names\n const paramNamesSet = new Set<string>(\n Array.isArray(params) ? [...params] : Object.keys(params),\n );\n\n // Store original router methods for restoration\n const originalBuildPath = router.buildPath.bind(router);\n const originalForwardState = router.forwardState.bind(router);\n const originalRootPath = router.getRootPath();\n\n // Update router root path to include query parameters for persistent params\n try {\n const { basePath, queryString } = parseQueryString(originalRootPath);\n // Note: newQueryString is always non-empty here because:\n // - Empty params are handled by early returns at lines 94-100\n // - So paramNamesSet always has at least one element\n // - So buildQueryString always returns a non-empty string\n const newQueryString = buildQueryString(queryString, [...paramNamesSet]);\n\n router.setRootPath(`${basePath}?${newQueryString}`);\n } catch (error) {\n // Rollback initialization marker on error\n delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];\n\n throw new Error(\n `[@real-router/persistent-params-plugin] Failed to update root path: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n /**\n * Merges persistent parameters with current navigation parameters.\n * Validates all parameter values before merging.\n *\n * @param additionalParams - Parameters passed during navigation\n * @returns Merged parameters object\n * @throws {TypeError} If any parameter value is invalid (not a primitive)\n */\n\n function withPersistentParams(additionalParams: Params): Params {\n // Extract safe params (prevent prototype pollution)\n const safeParams = extractOwnParams(additionalParams);\n\n // Validate and collect parameters to remove in a single pass\n const paramsToRemove: string[] = [];\n\n for (const key of Object.keys(safeParams)) {\n const value = safeParams[key];\n\n // If undefined and tracked, mark for removal (skip validation)\n if (value === undefined && paramNamesSet.has(key)) {\n paramsToRemove.push(key);\n } else {\n // Validate all other parameters\n validateParamValue(key, value);\n }\n }\n\n // Process all removals in one batch\n if (paramsToRemove.length > 0) {\n // Remove from both Set\n for (const key of paramsToRemove) {\n paramNamesSet.delete(key);\n }\n\n // Update persistentParams once (batch freeze)\n const newParams: Params = { ...persistentParams };\n\n for (const key of paramsToRemove) {\n delete newParams[key];\n }\n\n persistentParams = Object.freeze(newParams);\n }\n\n // Merge persistent and current params\n return mergeParams(persistentParams, safeParams);\n }\n\n // Override router methods to inject persistent params\n // buildPath: needed for direct buildPath() calls (doesn't go through forwardState)\n router.buildPath = (routeName, buildPathParams = {}) =>\n originalBuildPath(routeName, withPersistentParams(buildPathParams));\n\n // forwardState: intercepts params normalization for buildState, buildStateWithSegments, and navigate\n // This is the central point where params are normalized before state creation\n router.forwardState = <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => {\n const result = originalForwardState(routeName, routeParams);\n\n return {\n ...result,\n params: withPersistentParams(result.params) as P,\n };\n };\n\n return {\n /**\n * Updates persistent parameters after successful transition.\n * Only processes parameters that are tracked and have changed.\n *\n * @param toState - Target state after successful transition\n */\n onTransitionSuccess(toState) {\n try {\n // Collect changed parameters and removals\n const updates: Params = {};\n const removals: string[] = [];\n let hasChanges = false;\n\n for (const key of paramNamesSet) {\n const value = toState.params[key];\n\n // If parameter is not in state params or is undefined, mark for removal\n if (!Object.hasOwn(toState.params, key) || value === undefined) {\n // Only mark as removal if it currently exists in persistentParams\n if (\n Object.hasOwn(persistentParams, key) &&\n persistentParams[key] !== undefined\n ) {\n removals.push(key);\n hasChanges = true;\n }\n\n continue;\n }\n\n // Validate type before storing\n validateParamValue(key, value);\n\n // Only update if value actually changed\n if (persistentParams[key] !== value) {\n updates[key] = value;\n hasChanges = true;\n }\n }\n\n // Create new frozen object only if there were changes\n if (hasChanges) {\n const newParams: Params = { ...persistentParams, ...updates };\n\n // Remove parameters that were set to undefined\n for (const key of removals) {\n delete newParams[key];\n }\n\n persistentParams = Object.freeze(newParams);\n }\n } catch (error) {\n // Log error but don't break navigation\n /* v8 ignore next 5 -- @preserve defensive: validation happens before navigate() */\n console.error(\n \"persistent-params-plugin\",\n \"Error updating persistent params:\",\n error,\n );\n }\n },\n\n /**\n * Cleanup function to restore original router state.\n * Restores all overridden methods and paths.\n * Called when plugin is unsubscribed.\n */\n teardown() {\n try {\n // Restore original methods\n router.buildPath = originalBuildPath;\n router.forwardState = originalForwardState;\n\n // Restore original root path\n router.setRootPath(originalRootPath);\n\n // Remove initialization marker\n delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];\n } catch (error) {\n console.error(\n \"persistent-params-plugin\",\n \"Error during teardown:\",\n error,\n );\n }\n },\n };\n };\n}\n"]}
@@ -1 +1 @@
1
- {"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js":{"bytes":569,"imports":[],"format":"esm"},"src/types.ts":{"bytes":515,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/constants.ts":{"bytes":263,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/guards/navigation.ts":{"bytes":1304,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/internal/router-error.ts":{"bytes":1468,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/guards/routes.ts":{"bytes":2060,"imports":[{"path":"../type-guards/src/internal/router-error.ts","kind":"import-statement","original":"../internal/router-error"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/guards/params.ts":{"bytes":7499,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/internal/meta-fields.ts":{"bytes":1765,"imports":[{"path":"../type-guards/src/guards/params.ts","kind":"import-statement","original":"../guards/params"},{"path":"../type-guards/src/guards/routes.ts","kind":"import-statement","original":"../guards/routes"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/guards/state.ts":{"bytes":2697,"imports":[{"path":"../type-guards/src/internal/meta-fields.ts","kind":"import-statement","original":"../internal/meta-fields"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/guards/primitives.ts":{"bytes":1632,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/guards/index.ts":{"bytes":486,"imports":[{"path":"../type-guards/src/guards/navigation.ts","kind":"import-statement","original":"./navigation"},{"path":"../type-guards/src/guards/routes.ts","kind":"import-statement","original":"./routes"},{"path":"../type-guards/src/guards/state.ts","kind":"import-statement","original":"./state"},{"path":"../type-guards/src/guards/primitives.ts","kind":"import-statement","original":"./primitives"},{"path":"../type-guards/src/guards/params.ts","kind":"import-statement","original":"./params"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/validators/routes.ts":{"bytes":3141,"imports":[{"path":"../type-guards/src/internal/router-error.ts","kind":"import-statement","original":"../internal/router-error"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/utilities/type-description.ts":{"bytes":1232,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/validators/state.ts":{"bytes":1063,"imports":[{"path":"../type-guards/src/guards/index.ts","kind":"import-statement","original":"../guards"},{"path":"../type-guards/src/utilities/type-description.ts","kind":"import-statement","original":"../utilities/type-description"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/validators/index.ts":{"bytes":197,"imports":[{"path":"../type-guards/src/validators/routes.ts","kind":"import-statement","original":"./routes"},{"path":"../type-guards/src/validators/state.ts","kind":"import-statement","original":"./state"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/src/index.ts":{"bytes":1416,"imports":[{"path":"../type-guards/src/guards/index.ts","kind":"import-statement","original":"./guards"},{"path":"../type-guards/src/validators/index.ts","kind":"import-statement","original":"./validators"},{"path":"../type-guards/src/utilities/type-description.ts","kind":"import-statement","original":"./utilities/type-description"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/utils.ts":{"bytes":7512,"imports":[{"path":"../type-guards/src/index.ts","kind":"import-statement","original":"type-guards"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/plugin.ts":{"bytes":10580,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/utils.ts","kind":"import-statement","original":"./utils"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":164,"imports":[{"path":"src/types.ts","kind":"import-statement","original":"./types"},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/cjs/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":26129},"dist/cjs/index.js":{"imports":[],"exports":["PersistentParamsConfig","persistentParamsPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/constants.ts":{"bytesInOutput":72},"../type-guards/src/guards/primitives.ts":{"bytesInOutput":177},"../type-guards/src/guards/index.ts":{"bytesInOutput":0},"../type-guards/src/index.ts":{"bytesInOutput":0},"src/utils.ts":{"bytesInOutput":3048},"src/plugin.ts":{"bytesInOutput":5217},"src/index.ts":{"bytesInOutput":0}},"bytes":8741}}}
1
+ {"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js":{"bytes":569,"imports":[],"format":"esm"},"src/types.ts":{"bytes":515,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/constants.ts":{"bytes":263,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/dist/esm/index.mjs":{"bytes":3561,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/utils.ts":{"bytes":7512,"imports":[{"path":"../type-guards/dist/esm/index.mjs","kind":"import-statement","original":"type-guards"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/plugin.ts":{"bytes":10580,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/utils.ts","kind":"import-statement","original":"./utils"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":164,"imports":[{"path":"src/types.ts","kind":"import-statement","original":"./types"},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/cjs/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":24233},"dist/cjs/index.js":{"imports":[],"exports":["PersistentParamsConfig","persistentParamsPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/constants.ts":{"bytesInOutput":72},"../type-guards/dist/esm/index.mjs":{"bytesInOutput":118},"src/utils.ts":{"bytesInOutput":3018},"src/plugin.ts":{"bytesInOutput":5217},"src/index.ts":{"bytesInOutput":0}},"bytes":8646}}}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/constants.ts","../../../type-guards/src/guards/primitives.ts","../../src/utils.ts","../../src/plugin.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// packages/persistent-params-plugin/modules/constants.ts\n\n/**\n * Symbol to mark router as initialized with this plugin.\n * Prevents double initialization and memory leaks from method wrapping.\n */\nexport const PLUGIN_MARKER = Symbol(\"persistent-params-plugin\");\n","// packages/type-guards/modules/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 Promise type.\n *\n * @param value - Value to check\n * @returns true if value is a Promise\n */\nexport function isPromise<T = unknown>(value: unknown): value is Promise<T> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"then\" in value &&\n typeof value.then === \"function\"\n );\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\n/**\n * Type guard for primitive values suitable for URL parameters.\n * Only string, number, and boolean are allowed.\n * Rejects NaN and Infinity for numbers.\n *\n * @param value - Value to check\n * @returns true if value is string, number, or boolean\n */\nexport function isPrimitiveValue(\n value: unknown,\n): value is string | number | boolean {\n if (typeof value === \"number\") {\n return Number.isFinite(value);\n }\n\n return typeof value === \"string\" || typeof value === \"boolean\";\n}\n","// packages/persistent-params-plugin/modules/utils.ts\n\nimport { isPrimitiveValue } from \"type-guards\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params } from \"@real-router/core\";\n\nconst INVALID_PARAM_KEY_REGEX = /[\\s#%&/=?\\\\]/;\nconst INVALID_CHARS_MESSAGE = String.raw`Cannot contain: = & ? # % / \\ or whitespace`;\n\nexport function validateParamKey(key: string): void {\n if (INVALID_PARAM_KEY_REGEX.test(key)) {\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Invalid parameter name \"${key}\". ${INVALID_CHARS_MESSAGE}`,\n );\n }\n}\n\n/**\n * Validates params configuration structure and values.\n * Ensures all parameter names are non-empty strings and all default values are primitives.\n *\n * @param config - Configuration to validate\n * @returns true if configuration is valid\n */\n/**\n * Validates params configuration structure and values.\n * Ensures all parameter names are non-empty strings and all default values are primitives.\n *\n * @param config - Configuration to validate\n * @returns true if configuration is valid\n */\nexport function isValidParamsConfig(\n config: unknown,\n): config is PersistentParamsConfig {\n if (config === null || config === undefined) {\n return false;\n }\n\n // Array configuration: all items must be non-empty strings\n if (Array.isArray(config)) {\n return config.every((item) => {\n if (typeof item !== \"string\" || item.length === 0) {\n return false;\n }\n\n try {\n validateParamKey(item);\n\n return true;\n } catch {\n return false;\n }\n });\n }\n\n // Object configuration: must be plain object with primitive values\n if (typeof config === \"object\") {\n // Reject non-plain objects (Date, Map, etc.)\n if (Object.getPrototypeOf(config) !== Object.prototype) {\n return false;\n }\n\n // All keys must be non-empty strings, all values must be primitives\n return Object.entries(config).every(([key, value]) => {\n // Check key is non-empty string\n if (typeof key !== \"string\" || key.length === 0) {\n return false;\n }\n\n // Validate key doesn't contain special characters\n try {\n validateParamKey(key);\n } catch {\n return false;\n }\n\n // Validate value is primitive (NaN/Infinity already rejected by isPrimitiveValue)\n return isPrimitiveValue(value);\n });\n }\n\n return false;\n}\n\n/**\n * Validates parameter value before persisting.\n * Throws descriptive TypeError if value is not valid for URL parameters.\n *\n * @param key - Parameter name for error messages\n * @param value - Value to validate\n * @throws {TypeError} If value is null, array, object, or other non-primitive type\n */\nexport function validateParamValue(key: string, value: unknown): void {\n if (value === null) {\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Parameter \"${key}\" cannot be null. ` +\n `Use undefined to remove the parameter from persistence.`,\n );\n }\n\n if (value !== undefined && !isPrimitiveValue(value)) {\n const actualType = Array.isArray(value) ? \"array\" : typeof value;\n\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Parameter \"${key}\" must be a primitive value ` +\n `(string, number, or boolean), got ${actualType}. ` +\n `Objects and arrays are not supported in URL parameters.`,\n );\n }\n}\n\n/**\n * Safely extracts own properties from params object.\n * Uses Object.hasOwn to prevent prototype pollution attacks.\n *\n * @param params - Parameters object (may contain inherited properties)\n * @returns New object with only own properties\n *\n * @example\n * const malicious = Object.create({ __proto__: { admin: true } });\n * malicious.mode = 'dev';\n * const safe = extractOwnParams(malicious); // { mode: 'dev' } (no __proto__)\n */\nexport function extractOwnParams(params: Params): Params {\n const result: Params = {};\n\n for (const key in params) {\n // Only process own properties, skip inherited ones\n if (Object.hasOwn(params, key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n\n/**\n * Parses path into base path and query string components.\n * Handles edge cases like leading ?, multiple ?, empty path.\n *\n * @param path - Path to parse (e.g., \"/route?param=value\")\n * @returns Object with basePath and queryString\n *\n * @example\n * parseQueryString('/users?page=1') // { basePath: '/users', queryString: 'page=1' }\n * parseQueryString('?existing') // { basePath: '', queryString: 'existing' }\n * parseQueryString('/path') // { basePath: '/path', queryString: '' }\n */\nexport function parseQueryString(path: string): {\n basePath: string;\n queryString: string;\n} {\n const questionMarkIndex = path.indexOf(\"?\");\n\n // No query string\n if (questionMarkIndex === -1) {\n return { basePath: path, queryString: \"\" };\n }\n\n // Path starts with ? (edge case)\n if (questionMarkIndex === 0) {\n return { basePath: \"\", queryString: path.slice(1) };\n }\n\n // Normal case: path?query\n return {\n basePath: path.slice(0, questionMarkIndex),\n queryString: path.slice(questionMarkIndex + 1),\n };\n}\n\n/**\n * Builds query string from parameter names.\n * Preserves existing query parameters and appends new ones.\n *\n * @param existingQuery - Existing query string (without leading ?)\n * @param paramNames - Parameter names to append\n * @returns Combined query string\n *\n * @example\n * buildQueryString('existing=1', ['mode', 'lang']) // 'existing=1&mode&lang'\n * buildQueryString('', ['mode']) // 'mode'\n */\nexport function buildQueryString(\n existingQuery: string,\n paramNames: readonly string[],\n): string {\n if (paramNames.length === 0) {\n return existingQuery;\n }\n\n const separator = existingQuery ? \"&\" : \"\";\n\n return existingQuery + separator + paramNames.join(\"&\");\n}\n\n/**\n * Merges persistent and current parameters into a single Params object.\n * Keys explicitly set to `undefined` in current params are removed from result.\n *\n * Creates a new immutable object - does not mutate input parameters.\n *\n * @param persistent - Frozen persistent parameters\n * @param current - Current parameters from navigation\n * @returns New Params object with merged values\n *\n * @example\n * const persistent = { lang: 'en', theme: 'dark' };\n * const current = { theme: 'light', mode: 'dev' };\n * mergeParams(persistent, current); // { lang: 'en', theme: 'light', mode: 'dev' }\n *\n * @example\n * // Removing parameters with undefined\n * const persistent = { lang: 'en', theme: 'dark' };\n * const current = { theme: undefined };\n * mergeParams(persistent, current); // { lang: 'en' } (theme removed)\n */\nexport function mergeParams(\n persistent: Readonly<Params>,\n current: Params,\n): Params {\n // Safely extract own properties from current params\n const safeCurrentParams = extractOwnParams(current);\n\n // Start with persistent params, but EXCLUDE undefined values\n // (undefined values don't appear in URLs, so we shouldn't include them)\n const result: Params = {};\n\n for (const key in persistent) {\n if (Object.hasOwn(persistent, key) && persistent[key] !== undefined) {\n result[key] = persistent[key];\n }\n }\n\n // Apply current params\n for (const key of Object.keys(safeCurrentParams)) {\n const value = safeCurrentParams[key];\n\n if (value === undefined) {\n // Remove param if explicitly set to undefined\n delete result[key];\n } else {\n // Add or update param\n result[key] = value;\n }\n }\n\n return result;\n}\n","// packages/persistent-params-plugin/modules/plugin.ts\n\nimport { PLUGIN_MARKER } from \"./constants\";\nimport {\n buildQueryString,\n extractOwnParams,\n isValidParamsConfig,\n mergeParams,\n parseQueryString,\n validateParamValue,\n} from \"./utils\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params, PluginFactory, Plugin } from \"@real-router/core\";\n\n/**\n * Factory for the persistent parameters' plugin.\n *\n * This plugin allows you to specify certain route parameters to be persisted across\n * all navigation transitions. Persisted parameters are automatically merged into\n * route parameters when building paths or states.\n *\n * Key features:\n * - Automatic persistence of query parameters across navigations\n * - Support for default values\n * - Type-safe (only primitives: string, number, boolean)\n * - Immutable internal state\n * - Protection against prototype pollution\n * - Full teardown support (can be safely unsubscribed)\n *\n * If a persisted parameter is explicitly set to `undefined` during navigation,\n * it will be removed from the persisted state and omitted from subsequent URLs.\n *\n * The plugin also adjusts the router's root path to include query parameters for\n * all persistent params, ensuring correct URL construction.\n *\n * @param params - Either an array of parameter names (strings) to persist,\n * or an object mapping parameter names to initial values.\n * If an array, initial values will be `undefined`.\n *\n * @returns A PluginFactory that creates the persistent params plugin instance.\n *\n * @example\n * // Persist parameters without default values\n * router.usePlugin(persistentParamsPlugin(['mode', 'lang']));\n *\n * @example\n * // Persist parameters with default values\n * router.usePlugin(persistentParamsPlugin({ mode: 'dev', lang: 'en' }));\n *\n * @example\n * // Removing a persisted parameter\n * router.navigate('route', { mode: undefined }); // mode will be removed\n *\n * @example\n * // Unsubscribing (full cleanup)\n * const unsubscribe = router.usePlugin(persistentParamsPlugin(['mode']));\n * unsubscribe(); // Restores original router state\n *\n * @throws {TypeError} If params is not a valid array of strings or object with primitives\n * @throws {Error} If plugin is already initialized on this router instance\n */\nexport function persistentParamsPluginFactory(\n params: PersistentParamsConfig = {},\n): PluginFactory {\n // Validate input configuration\n if (!isValidParamsConfig(params)) {\n let actualType: string;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (params === null) {\n actualType = \"null\";\n } else if (Array.isArray(params)) {\n actualType = \"array with invalid items\";\n } else {\n actualType = typeof params;\n }\n\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Invalid params configuration. ` +\n `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,\n );\n }\n\n // Empty configuration - valid but does nothing\n if (Array.isArray(params) && params.length === 0) {\n return () => ({});\n }\n\n if (!Array.isArray(params) && Object.keys(params).length === 0) {\n return () => ({});\n }\n\n return (router): Plugin => {\n // Check if plugin is already initialized on this router\n if (PLUGIN_MARKER in router) {\n throw new Error(\n `[@real-router/persistent-params-plugin] Plugin already initialized on this router. ` +\n `To reconfigure, first unsubscribe the existing plugin using the returned unsubscribe function.`,\n );\n }\n\n // Mark router as initialized\n (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER] = true;\n\n // Initialize frozen persistent parameters\n let persistentParams: Readonly<Params>;\n\n if (Array.isArray(params)) {\n const initial: Params = {};\n\n for (const param of params) {\n initial[param] = undefined;\n }\n\n persistentParams = Object.freeze(initial);\n } else {\n persistentParams = Object.freeze({ ...params });\n }\n\n // Track parameter names\n const paramNamesSet = new Set<string>(\n Array.isArray(params) ? [...params] : Object.keys(params),\n );\n\n // Store original router methods for restoration\n const originalBuildPath = router.buildPath.bind(router);\n const originalForwardState = router.forwardState.bind(router);\n const originalRootPath = router.getRootPath();\n\n // Update router root path to include query parameters for persistent params\n try {\n const { basePath, queryString } = parseQueryString(originalRootPath);\n // Note: newQueryString is always non-empty here because:\n // - Empty params are handled by early returns at lines 94-100\n // - So paramNamesSet always has at least one element\n // - So buildQueryString always returns a non-empty string\n const newQueryString = buildQueryString(queryString, [...paramNamesSet]);\n\n router.setRootPath(`${basePath}?${newQueryString}`);\n } catch (error) {\n // Rollback initialization marker on error\n delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];\n\n throw new Error(\n `[@real-router/persistent-params-plugin] Failed to update root path: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n /**\n * Merges persistent parameters with current navigation parameters.\n * Validates all parameter values before merging.\n *\n * @param additionalParams - Parameters passed during navigation\n * @returns Merged parameters object\n * @throws {TypeError} If any parameter value is invalid (not a primitive)\n */\n\n function withPersistentParams(additionalParams: Params): Params {\n // Extract safe params (prevent prototype pollution)\n const safeParams = extractOwnParams(additionalParams);\n\n // Validate and collect parameters to remove in a single pass\n const paramsToRemove: string[] = [];\n\n for (const key of Object.keys(safeParams)) {\n const value = safeParams[key];\n\n // If undefined and tracked, mark for removal (skip validation)\n if (value === undefined && paramNamesSet.has(key)) {\n paramsToRemove.push(key);\n } else {\n // Validate all other parameters\n validateParamValue(key, value);\n }\n }\n\n // Process all removals in one batch\n if (paramsToRemove.length > 0) {\n // Remove from both Set\n for (const key of paramsToRemove) {\n paramNamesSet.delete(key);\n }\n\n // Update persistentParams once (batch freeze)\n const newParams: Params = { ...persistentParams };\n\n for (const key of paramsToRemove) {\n delete newParams[key];\n }\n\n persistentParams = Object.freeze(newParams);\n }\n\n // Merge persistent and current params\n return mergeParams(persistentParams, safeParams);\n }\n\n // Override router methods to inject persistent params\n // buildPath: needed for direct buildPath() calls (doesn't go through forwardState)\n router.buildPath = (routeName, buildPathParams = {}) =>\n originalBuildPath(routeName, withPersistentParams(buildPathParams));\n\n // forwardState: intercepts params normalization for buildState, buildStateWithSegments, and navigate\n // This is the central point where params are normalized before state creation\n router.forwardState = <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => {\n const result = originalForwardState(routeName, routeParams);\n\n return {\n ...result,\n params: withPersistentParams(result.params) as P,\n };\n };\n\n return {\n /**\n * Updates persistent parameters after successful transition.\n * Only processes parameters that are tracked and have changed.\n *\n * @param toState - Target state after successful transition\n */\n onTransitionSuccess(toState) {\n try {\n // Collect changed parameters and removals\n const updates: Params = {};\n const removals: string[] = [];\n let hasChanges = false;\n\n for (const key of paramNamesSet) {\n const value = toState.params[key];\n\n // If parameter is not in state params or is undefined, mark for removal\n if (!Object.hasOwn(toState.params, key) || value === undefined) {\n // Only mark as removal if it currently exists in persistentParams\n if (\n Object.hasOwn(persistentParams, key) &&\n persistentParams[key] !== undefined\n ) {\n removals.push(key);\n hasChanges = true;\n }\n\n continue;\n }\n\n // Validate type before storing\n validateParamValue(key, value);\n\n // Only update if value actually changed\n if (persistentParams[key] !== value) {\n updates[key] = value;\n hasChanges = true;\n }\n }\n\n // Create new frozen object only if there were changes\n if (hasChanges) {\n const newParams: Params = { ...persistentParams, ...updates };\n\n // Remove parameters that were set to undefined\n for (const key of removals) {\n delete newParams[key];\n }\n\n persistentParams = Object.freeze(newParams);\n }\n } catch (error) {\n // Log error but don't break navigation\n /* v8 ignore next 5 -- @preserve defensive: validation happens before navigate() */\n console.error(\n \"persistent-params-plugin\",\n \"Error updating persistent params:\",\n error,\n );\n }\n },\n\n /**\n * Cleanup function to restore original router state.\n * Restores all overridden methods and paths.\n * Called when plugin is unsubscribed.\n */\n teardown() {\n try {\n // Restore original methods\n router.buildPath = originalBuildPath;\n router.forwardState = originalForwardState;\n\n // Restore original root path\n router.setRootPath(originalRootPath);\n\n // Remove initialization marker\n delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];\n } catch (error) {\n console.error(\n \"persistent-params-plugin\",\n \"Error during teardown:\",\n error,\n );\n }\n },\n };\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/utils.ts","../../src/plugin.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// packages/persistent-params-plugin/modules/constants.ts\n\n/**\n * Symbol to mark router as initialized with this plugin.\n * Prevents double initialization and memory leaks from method wrapping.\n */\nexport const PLUGIN_MARKER = Symbol(\"persistent-params-plugin\");\n","// packages/persistent-params-plugin/modules/utils.ts\n\nimport { isPrimitiveValue } from \"type-guards\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params } from \"@real-router/core\";\n\nconst INVALID_PARAM_KEY_REGEX = /[\\s#%&/=?\\\\]/;\nconst INVALID_CHARS_MESSAGE = String.raw`Cannot contain: = & ? # % / \\ or whitespace`;\n\nexport function validateParamKey(key: string): void {\n if (INVALID_PARAM_KEY_REGEX.test(key)) {\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Invalid parameter name \"${key}\". ${INVALID_CHARS_MESSAGE}`,\n );\n }\n}\n\n/**\n * Validates params configuration structure and values.\n * Ensures all parameter names are non-empty strings and all default values are primitives.\n *\n * @param config - Configuration to validate\n * @returns true if configuration is valid\n */\n/**\n * Validates params configuration structure and values.\n * Ensures all parameter names are non-empty strings and all default values are primitives.\n *\n * @param config - Configuration to validate\n * @returns true if configuration is valid\n */\nexport function isValidParamsConfig(\n config: unknown,\n): config is PersistentParamsConfig {\n if (config === null || config === undefined) {\n return false;\n }\n\n // Array configuration: all items must be non-empty strings\n if (Array.isArray(config)) {\n return config.every((item) => {\n if (typeof item !== \"string\" || item.length === 0) {\n return false;\n }\n\n try {\n validateParamKey(item);\n\n return true;\n } catch {\n return false;\n }\n });\n }\n\n // Object configuration: must be plain object with primitive values\n if (typeof config === \"object\") {\n // Reject non-plain objects (Date, Map, etc.)\n if (Object.getPrototypeOf(config) !== Object.prototype) {\n return false;\n }\n\n // All keys must be non-empty strings, all values must be primitives\n return Object.entries(config).every(([key, value]) => {\n // Check key is non-empty string\n if (typeof key !== \"string\" || key.length === 0) {\n return false;\n }\n\n // Validate key doesn't contain special characters\n try {\n validateParamKey(key);\n } catch {\n return false;\n }\n\n // Validate value is primitive (NaN/Infinity already rejected by isPrimitiveValue)\n return isPrimitiveValue(value);\n });\n }\n\n return false;\n}\n\n/**\n * Validates parameter value before persisting.\n * Throws descriptive TypeError if value is not valid for URL parameters.\n *\n * @param key - Parameter name for error messages\n * @param value - Value to validate\n * @throws {TypeError} If value is null, array, object, or other non-primitive type\n */\nexport function validateParamValue(key: string, value: unknown): void {\n if (value === null) {\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Parameter \"${key}\" cannot be null. ` +\n `Use undefined to remove the parameter from persistence.`,\n );\n }\n\n if (value !== undefined && !isPrimitiveValue(value)) {\n const actualType = Array.isArray(value) ? \"array\" : typeof value;\n\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Parameter \"${key}\" must be a primitive value ` +\n `(string, number, or boolean), got ${actualType}. ` +\n `Objects and arrays are not supported in URL parameters.`,\n );\n }\n}\n\n/**\n * Safely extracts own properties from params object.\n * Uses Object.hasOwn to prevent prototype pollution attacks.\n *\n * @param params - Parameters object (may contain inherited properties)\n * @returns New object with only own properties\n *\n * @example\n * const malicious = Object.create({ __proto__: { admin: true } });\n * malicious.mode = 'dev';\n * const safe = extractOwnParams(malicious); // { mode: 'dev' } (no __proto__)\n */\nexport function extractOwnParams(params: Params): Params {\n const result: Params = {};\n\n for (const key in params) {\n // Only process own properties, skip inherited ones\n if (Object.hasOwn(params, key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n\n/**\n * Parses path into base path and query string components.\n * Handles edge cases like leading ?, multiple ?, empty path.\n *\n * @param path - Path to parse (e.g., \"/route?param=value\")\n * @returns Object with basePath and queryString\n *\n * @example\n * parseQueryString('/users?page=1') // { basePath: '/users', queryString: 'page=1' }\n * parseQueryString('?existing') // { basePath: '', queryString: 'existing' }\n * parseQueryString('/path') // { basePath: '/path', queryString: '' }\n */\nexport function parseQueryString(path: string): {\n basePath: string;\n queryString: string;\n} {\n const questionMarkIndex = path.indexOf(\"?\");\n\n // No query string\n if (questionMarkIndex === -1) {\n return { basePath: path, queryString: \"\" };\n }\n\n // Path starts with ? (edge case)\n if (questionMarkIndex === 0) {\n return { basePath: \"\", queryString: path.slice(1) };\n }\n\n // Normal case: path?query\n return {\n basePath: path.slice(0, questionMarkIndex),\n queryString: path.slice(questionMarkIndex + 1),\n };\n}\n\n/**\n * Builds query string from parameter names.\n * Preserves existing query parameters and appends new ones.\n *\n * @param existingQuery - Existing query string (without leading ?)\n * @param paramNames - Parameter names to append\n * @returns Combined query string\n *\n * @example\n * buildQueryString('existing=1', ['mode', 'lang']) // 'existing=1&mode&lang'\n * buildQueryString('', ['mode']) // 'mode'\n */\nexport function buildQueryString(\n existingQuery: string,\n paramNames: readonly string[],\n): string {\n if (paramNames.length === 0) {\n return existingQuery;\n }\n\n const separator = existingQuery ? \"&\" : \"\";\n\n return existingQuery + separator + paramNames.join(\"&\");\n}\n\n/**\n * Merges persistent and current parameters into a single Params object.\n * Keys explicitly set to `undefined` in current params are removed from result.\n *\n * Creates a new immutable object - does not mutate input parameters.\n *\n * @param persistent - Frozen persistent parameters\n * @param current - Current parameters from navigation\n * @returns New Params object with merged values\n *\n * @example\n * const persistent = { lang: 'en', theme: 'dark' };\n * const current = { theme: 'light', mode: 'dev' };\n * mergeParams(persistent, current); // { lang: 'en', theme: 'light', mode: 'dev' }\n *\n * @example\n * // Removing parameters with undefined\n * const persistent = { lang: 'en', theme: 'dark' };\n * const current = { theme: undefined };\n * mergeParams(persistent, current); // { lang: 'en' } (theme removed)\n */\nexport function mergeParams(\n persistent: Readonly<Params>,\n current: Params,\n): Params {\n // Safely extract own properties from current params\n const safeCurrentParams = extractOwnParams(current);\n\n // Start with persistent params, but EXCLUDE undefined values\n // (undefined values don't appear in URLs, so we shouldn't include them)\n const result: Params = {};\n\n for (const key in persistent) {\n if (Object.hasOwn(persistent, key) && persistent[key] !== undefined) {\n result[key] = persistent[key];\n }\n }\n\n // Apply current params\n for (const key of Object.keys(safeCurrentParams)) {\n const value = safeCurrentParams[key];\n\n if (value === undefined) {\n // Remove param if explicitly set to undefined\n delete result[key];\n } else {\n // Add or update param\n result[key] = value;\n }\n }\n\n return result;\n}\n","// packages/persistent-params-plugin/modules/plugin.ts\n\nimport { PLUGIN_MARKER } from \"./constants\";\nimport {\n buildQueryString,\n extractOwnParams,\n isValidParamsConfig,\n mergeParams,\n parseQueryString,\n validateParamValue,\n} from \"./utils\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params, PluginFactory, Plugin } from \"@real-router/core\";\n\n/**\n * Factory for the persistent parameters' plugin.\n *\n * This plugin allows you to specify certain route parameters to be persisted across\n * all navigation transitions. Persisted parameters are automatically merged into\n * route parameters when building paths or states.\n *\n * Key features:\n * - Automatic persistence of query parameters across navigations\n * - Support for default values\n * - Type-safe (only primitives: string, number, boolean)\n * - Immutable internal state\n * - Protection against prototype pollution\n * - Full teardown support (can be safely unsubscribed)\n *\n * If a persisted parameter is explicitly set to `undefined` during navigation,\n * it will be removed from the persisted state and omitted from subsequent URLs.\n *\n * The plugin also adjusts the router's root path to include query parameters for\n * all persistent params, ensuring correct URL construction.\n *\n * @param params - Either an array of parameter names (strings) to persist,\n * or an object mapping parameter names to initial values.\n * If an array, initial values will be `undefined`.\n *\n * @returns A PluginFactory that creates the persistent params plugin instance.\n *\n * @example\n * // Persist parameters without default values\n * router.usePlugin(persistentParamsPlugin(['mode', 'lang']));\n *\n * @example\n * // Persist parameters with default values\n * router.usePlugin(persistentParamsPlugin({ mode: 'dev', lang: 'en' }));\n *\n * @example\n * // Removing a persisted parameter\n * router.navigate('route', { mode: undefined }); // mode will be removed\n *\n * @example\n * // Unsubscribing (full cleanup)\n * const unsubscribe = router.usePlugin(persistentParamsPlugin(['mode']));\n * unsubscribe(); // Restores original router state\n *\n * @throws {TypeError} If params is not a valid array of strings or object with primitives\n * @throws {Error} If plugin is already initialized on this router instance\n */\nexport function persistentParamsPluginFactory(\n params: PersistentParamsConfig = {},\n): PluginFactory {\n // Validate input configuration\n if (!isValidParamsConfig(params)) {\n let actualType: string;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (params === null) {\n actualType = \"null\";\n } else if (Array.isArray(params)) {\n actualType = \"array with invalid items\";\n } else {\n actualType = typeof params;\n }\n\n throw new TypeError(\n `[@real-router/persistent-params-plugin] Invalid params configuration. ` +\n `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,\n );\n }\n\n // Empty configuration - valid but does nothing\n if (Array.isArray(params) && params.length === 0) {\n return () => ({});\n }\n\n if (!Array.isArray(params) && Object.keys(params).length === 0) {\n return () => ({});\n }\n\n return (router): Plugin => {\n // Check if plugin is already initialized on this router\n if (PLUGIN_MARKER in router) {\n throw new Error(\n `[@real-router/persistent-params-plugin] Plugin already initialized on this router. ` +\n `To reconfigure, first unsubscribe the existing plugin using the returned unsubscribe function.`,\n );\n }\n\n // Mark router as initialized\n (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER] = true;\n\n // Initialize frozen persistent parameters\n let persistentParams: Readonly<Params>;\n\n if (Array.isArray(params)) {\n const initial: Params = {};\n\n for (const param of params) {\n initial[param] = undefined;\n }\n\n persistentParams = Object.freeze(initial);\n } else {\n persistentParams = Object.freeze({ ...params });\n }\n\n // Track parameter names\n const paramNamesSet = new Set<string>(\n Array.isArray(params) ? [...params] : Object.keys(params),\n );\n\n // Store original router methods for restoration\n const originalBuildPath = router.buildPath.bind(router);\n const originalForwardState = router.forwardState.bind(router);\n const originalRootPath = router.getRootPath();\n\n // Update router root path to include query parameters for persistent params\n try {\n const { basePath, queryString } = parseQueryString(originalRootPath);\n // Note: newQueryString is always non-empty here because:\n // - Empty params are handled by early returns at lines 94-100\n // - So paramNamesSet always has at least one element\n // - So buildQueryString always returns a non-empty string\n const newQueryString = buildQueryString(queryString, [...paramNamesSet]);\n\n router.setRootPath(`${basePath}?${newQueryString}`);\n } catch (error) {\n // Rollback initialization marker on error\n delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];\n\n throw new Error(\n `[@real-router/persistent-params-plugin] Failed to update root path: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n /**\n * Merges persistent parameters with current navigation parameters.\n * Validates all parameter values before merging.\n *\n * @param additionalParams - Parameters passed during navigation\n * @returns Merged parameters object\n * @throws {TypeError} If any parameter value is invalid (not a primitive)\n */\n\n function withPersistentParams(additionalParams: Params): Params {\n // Extract safe params (prevent prototype pollution)\n const safeParams = extractOwnParams(additionalParams);\n\n // Validate and collect parameters to remove in a single pass\n const paramsToRemove: string[] = [];\n\n for (const key of Object.keys(safeParams)) {\n const value = safeParams[key];\n\n // If undefined and tracked, mark for removal (skip validation)\n if (value === undefined && paramNamesSet.has(key)) {\n paramsToRemove.push(key);\n } else {\n // Validate all other parameters\n validateParamValue(key, value);\n }\n }\n\n // Process all removals in one batch\n if (paramsToRemove.length > 0) {\n // Remove from both Set\n for (const key of paramsToRemove) {\n paramNamesSet.delete(key);\n }\n\n // Update persistentParams once (batch freeze)\n const newParams: Params = { ...persistentParams };\n\n for (const key of paramsToRemove) {\n delete newParams[key];\n }\n\n persistentParams = Object.freeze(newParams);\n }\n\n // Merge persistent and current params\n return mergeParams(persistentParams, safeParams);\n }\n\n // Override router methods to inject persistent params\n // buildPath: needed for direct buildPath() calls (doesn't go through forwardState)\n router.buildPath = (routeName, buildPathParams = {}) =>\n originalBuildPath(routeName, withPersistentParams(buildPathParams));\n\n // forwardState: intercepts params normalization for buildState, buildStateWithSegments, and navigate\n // This is the central point where params are normalized before state creation\n router.forwardState = <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => {\n const result = originalForwardState(routeName, routeParams);\n\n return {\n ...result,\n params: withPersistentParams(result.params) as P,\n };\n };\n\n return {\n /**\n * Updates persistent parameters after successful transition.\n * Only processes parameters that are tracked and have changed.\n *\n * @param toState - Target state after successful transition\n */\n onTransitionSuccess(toState) {\n try {\n // Collect changed parameters and removals\n const updates: Params = {};\n const removals: string[] = [];\n let hasChanges = false;\n\n for (const key of paramNamesSet) {\n const value = toState.params[key];\n\n // If parameter is not in state params or is undefined, mark for removal\n if (!Object.hasOwn(toState.params, key) || value === undefined) {\n // Only mark as removal if it currently exists in persistentParams\n if (\n Object.hasOwn(persistentParams, key) &&\n persistentParams[key] !== undefined\n ) {\n removals.push(key);\n hasChanges = true;\n }\n\n continue;\n }\n\n // Validate type before storing\n validateParamValue(key, value);\n\n // Only update if value actually changed\n if (persistentParams[key] !== value) {\n updates[key] = value;\n hasChanges = true;\n }\n }\n\n // Create new frozen object only if there were changes\n if (hasChanges) {\n const newParams: Params = { ...persistentParams, ...updates };\n\n // Remove parameters that were set to undefined\n for (const key of removals) {\n delete newParams[key];\n }\n\n persistentParams = Object.freeze(newParams);\n }\n } catch (error) {\n // Log error but don't break navigation\n /* v8 ignore next 5 -- @preserve defensive: validation happens before navigate() */\n console.error(\n \"persistent-params-plugin\",\n \"Error updating persistent params:\",\n error,\n );\n }\n },\n\n /**\n * Cleanup function to restore original router state.\n * Restores all overridden methods and paths.\n * Called when plugin is unsubscribed.\n */\n teardown() {\n try {\n // Restore original methods\n router.buildPath = originalBuildPath;\n router.forwardState = originalForwardState;\n\n // Restore original root path\n router.setRootPath(originalRootPath);\n\n // Remove initialization marker\n delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];\n } catch (error) {\n console.error(\n \"persistent-params-plugin\",\n \"Error during teardown:\",\n error,\n );\n }\n },\n };\n };\n}\n"]}
@@ -1 +1 @@
1
- {"inputs":{"src/types.ts":{"bytes":515,"imports":[],"format":"esm"},"src/constants.ts":{"bytes":263,"imports":[],"format":"esm"},"../type-guards/src/guards/navigation.ts":{"bytes":1304,"imports":[],"format":"esm"},"../type-guards/src/internal/router-error.ts":{"bytes":1468,"imports":[],"format":"esm"},"../type-guards/src/guards/routes.ts":{"bytes":2060,"imports":[{"path":"../type-guards/src/internal/router-error.ts","kind":"import-statement","original":"../internal/router-error"}],"format":"esm"},"../type-guards/src/guards/params.ts":{"bytes":7499,"imports":[],"format":"esm"},"../type-guards/src/internal/meta-fields.ts":{"bytes":1765,"imports":[{"path":"../type-guards/src/guards/params.ts","kind":"import-statement","original":"../guards/params"},{"path":"../type-guards/src/guards/routes.ts","kind":"import-statement","original":"../guards/routes"}],"format":"esm"},"../type-guards/src/guards/state.ts":{"bytes":2697,"imports":[{"path":"../type-guards/src/internal/meta-fields.ts","kind":"import-statement","original":"../internal/meta-fields"}],"format":"esm"},"../type-guards/src/guards/primitives.ts":{"bytes":1632,"imports":[],"format":"esm"},"../type-guards/src/guards/index.ts":{"bytes":486,"imports":[{"path":"../type-guards/src/guards/navigation.ts","kind":"import-statement","original":"./navigation"},{"path":"../type-guards/src/guards/routes.ts","kind":"import-statement","original":"./routes"},{"path":"../type-guards/src/guards/state.ts","kind":"import-statement","original":"./state"},{"path":"../type-guards/src/guards/primitives.ts","kind":"import-statement","original":"./primitives"},{"path":"../type-guards/src/guards/params.ts","kind":"import-statement","original":"./params"}],"format":"esm"},"../type-guards/src/validators/routes.ts":{"bytes":3141,"imports":[{"path":"../type-guards/src/internal/router-error.ts","kind":"import-statement","original":"../internal/router-error"}],"format":"esm"},"../type-guards/src/utilities/type-description.ts":{"bytes":1232,"imports":[],"format":"esm"},"../type-guards/src/validators/state.ts":{"bytes":1063,"imports":[{"path":"../type-guards/src/guards/index.ts","kind":"import-statement","original":"../guards"},{"path":"../type-guards/src/utilities/type-description.ts","kind":"import-statement","original":"../utilities/type-description"}],"format":"esm"},"../type-guards/src/validators/index.ts":{"bytes":197,"imports":[{"path":"../type-guards/src/validators/routes.ts","kind":"import-statement","original":"./routes"},{"path":"../type-guards/src/validators/state.ts","kind":"import-statement","original":"./state"}],"format":"esm"},"../type-guards/src/index.ts":{"bytes":1416,"imports":[{"path":"../type-guards/src/guards/index.ts","kind":"import-statement","original":"./guards"},{"path":"../type-guards/src/validators/index.ts","kind":"import-statement","original":"./validators"},{"path":"../type-guards/src/utilities/type-description.ts","kind":"import-statement","original":"./utilities/type-description"}],"format":"esm"},"src/utils.ts":{"bytes":7512,"imports":[{"path":"../type-guards/src/index.ts","kind":"import-statement","original":"type-guards"}],"format":"esm"},"src/plugin.ts":{"bytes":10580,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/utils.ts","kind":"import-statement","original":"./utils"}],"format":"esm"},"src/index.ts":{"bytes":164,"imports":[{"path":"src/types.ts","kind":"import-statement","original":"./types"},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":26129},"dist/esm/index.mjs":{"imports":[],"exports":["PersistentParamsConfig","persistentParamsPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/constants.ts":{"bytesInOutput":72},"../type-guards/src/guards/primitives.ts":{"bytesInOutput":177},"../type-guards/src/guards/index.ts":{"bytesInOutput":0},"../type-guards/src/index.ts":{"bytesInOutput":0},"src/utils.ts":{"bytesInOutput":3048},"src/plugin.ts":{"bytesInOutput":5217},"src/index.ts":{"bytesInOutput":0}},"bytes":8741}}}
1
+ {"inputs":{"src/types.ts":{"bytes":515,"imports":[],"format":"esm"},"src/constants.ts":{"bytes":263,"imports":[],"format":"esm"},"../type-guards/dist/esm/index.mjs":{"bytes":3561,"imports":[],"format":"esm"},"src/utils.ts":{"bytes":7512,"imports":[{"path":"../type-guards/dist/esm/index.mjs","kind":"import-statement","original":"type-guards"}],"format":"esm"},"src/plugin.ts":{"bytes":10580,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/utils.ts","kind":"import-statement","original":"./utils"}],"format":"esm"},"src/index.ts":{"bytes":164,"imports":[{"path":"src/types.ts","kind":"import-statement","original":"./types"},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":24233},"dist/esm/index.mjs":{"imports":[],"exports":["PersistentParamsConfig","persistentParamsPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/constants.ts":{"bytesInOutput":72},"../type-guards/dist/esm/index.mjs":{"bytesInOutput":118},"src/utils.ts":{"bytesInOutput":3018},"src/plugin.ts":{"bytesInOutput":5217},"src/index.ts":{"bytesInOutput":0}},"bytes":8646}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/persistent-params-plugin",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "type": "commonjs",
5
5
  "description": "Persist query parameters across route transitions",
6
6
  "main": "./dist/cjs/index.js",
@@ -8,6 +8,7 @@
8
8
  "types": "./dist/esm/index.d.mts",
9
9
  "exports": {
10
10
  ".": {
11
+ "development": "./src/index.ts",
11
12
  "types": {
12
13
  "import": "./dist/esm/index.d.mts",
13
14
  "require": "./dist/cjs/index.d.ts"
@@ -17,7 +18,8 @@
17
18
  }
18
19
  },
19
20
  "files": [
20
- "dist"
21
+ "dist",
22
+ "src"
21
23
  ],
22
24
  "repository": {
23
25
  "type": "git",
@@ -43,10 +45,11 @@
43
45
  "homepage": "https://github.com/greydragon888/real-router",
44
46
  "sideEffects": false,
45
47
  "dependencies": {
46
- "@real-router/core": "^0.22.0"
48
+ "@real-router/core": "^0.24.0"
47
49
  },
48
50
  "devDependencies": {
49
- "type-guards": "^0.1.11"
51
+ "@real-router/types": "^0.14.0",
52
+ "type-guards": "^0.2.1"
50
53
  },
51
54
  "scripts": {
52
55
  "test": "vitest",
@@ -0,0 +1,7 @@
1
+ // packages/persistent-params-plugin/modules/constants.ts
2
+
3
+ /**
4
+ * Symbol to mark router as initialized with this plugin.
5
+ * Prevents double initialization and memory leaks from method wrapping.
6
+ */
7
+ export const PLUGIN_MARKER = Symbol("persistent-params-plugin");
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ // packages/persistent-params-plugin/modules/index.ts
2
+
3
+ export { PersistentParamsConfig } from "./types";
4
+
5
+ export { persistentParamsPluginFactory } from "./plugin";
package/src/plugin.ts ADDED
@@ -0,0 +1,308 @@
1
+ // packages/persistent-params-plugin/modules/plugin.ts
2
+
3
+ import { PLUGIN_MARKER } from "./constants";
4
+ import {
5
+ buildQueryString,
6
+ extractOwnParams,
7
+ isValidParamsConfig,
8
+ mergeParams,
9
+ parseQueryString,
10
+ validateParamValue,
11
+ } from "./utils";
12
+
13
+ import type { PersistentParamsConfig } from "./types";
14
+ import type { Params, PluginFactory, Plugin } from "@real-router/core";
15
+
16
+ /**
17
+ * Factory for the persistent parameters' plugin.
18
+ *
19
+ * This plugin allows you to specify certain route parameters to be persisted across
20
+ * all navigation transitions. Persisted parameters are automatically merged into
21
+ * route parameters when building paths or states.
22
+ *
23
+ * Key features:
24
+ * - Automatic persistence of query parameters across navigations
25
+ * - Support for default values
26
+ * - Type-safe (only primitives: string, number, boolean)
27
+ * - Immutable internal state
28
+ * - Protection against prototype pollution
29
+ * - Full teardown support (can be safely unsubscribed)
30
+ *
31
+ * If a persisted parameter is explicitly set to `undefined` during navigation,
32
+ * it will be removed from the persisted state and omitted from subsequent URLs.
33
+ *
34
+ * The plugin also adjusts the router's root path to include query parameters for
35
+ * all persistent params, ensuring correct URL construction.
36
+ *
37
+ * @param params - Either an array of parameter names (strings) to persist,
38
+ * or an object mapping parameter names to initial values.
39
+ * If an array, initial values will be `undefined`.
40
+ *
41
+ * @returns A PluginFactory that creates the persistent params plugin instance.
42
+ *
43
+ * @example
44
+ * // Persist parameters without default values
45
+ * router.usePlugin(persistentParamsPlugin(['mode', 'lang']));
46
+ *
47
+ * @example
48
+ * // Persist parameters with default values
49
+ * router.usePlugin(persistentParamsPlugin({ mode: 'dev', lang: 'en' }));
50
+ *
51
+ * @example
52
+ * // Removing a persisted parameter
53
+ * router.navigate('route', { mode: undefined }); // mode will be removed
54
+ *
55
+ * @example
56
+ * // Unsubscribing (full cleanup)
57
+ * const unsubscribe = router.usePlugin(persistentParamsPlugin(['mode']));
58
+ * unsubscribe(); // Restores original router state
59
+ *
60
+ * @throws {TypeError} If params is not a valid array of strings or object with primitives
61
+ * @throws {Error} If plugin is already initialized on this router instance
62
+ */
63
+ export function persistentParamsPluginFactory(
64
+ params: PersistentParamsConfig = {},
65
+ ): PluginFactory {
66
+ // Validate input configuration
67
+ if (!isValidParamsConfig(params)) {
68
+ let actualType: string;
69
+
70
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
71
+ if (params === null) {
72
+ actualType = "null";
73
+ } else if (Array.isArray(params)) {
74
+ actualType = "array with invalid items";
75
+ } else {
76
+ actualType = typeof params;
77
+ }
78
+
79
+ throw new TypeError(
80
+ `[@real-router/persistent-params-plugin] Invalid params configuration. ` +
81
+ `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,
82
+ );
83
+ }
84
+
85
+ // Empty configuration - valid but does nothing
86
+ if (Array.isArray(params) && params.length === 0) {
87
+ return () => ({});
88
+ }
89
+
90
+ if (!Array.isArray(params) && Object.keys(params).length === 0) {
91
+ return () => ({});
92
+ }
93
+
94
+ return (router): Plugin => {
95
+ // Check if plugin is already initialized on this router
96
+ if (PLUGIN_MARKER in router) {
97
+ throw new Error(
98
+ `[@real-router/persistent-params-plugin] Plugin already initialized on this router. ` +
99
+ `To reconfigure, first unsubscribe the existing plugin using the returned unsubscribe function.`,
100
+ );
101
+ }
102
+
103
+ // Mark router as initialized
104
+ (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER] = true;
105
+
106
+ // Initialize frozen persistent parameters
107
+ let persistentParams: Readonly<Params>;
108
+
109
+ if (Array.isArray(params)) {
110
+ const initial: Params = {};
111
+
112
+ for (const param of params) {
113
+ initial[param] = undefined;
114
+ }
115
+
116
+ persistentParams = Object.freeze(initial);
117
+ } else {
118
+ persistentParams = Object.freeze({ ...params });
119
+ }
120
+
121
+ // Track parameter names
122
+ const paramNamesSet = new Set<string>(
123
+ Array.isArray(params) ? [...params] : Object.keys(params),
124
+ );
125
+
126
+ // Store original router methods for restoration
127
+ const originalBuildPath = router.buildPath.bind(router);
128
+ const originalForwardState = router.forwardState.bind(router);
129
+ const originalRootPath = router.getRootPath();
130
+
131
+ // Update router root path to include query parameters for persistent params
132
+ try {
133
+ const { basePath, queryString } = parseQueryString(originalRootPath);
134
+ // Note: newQueryString is always non-empty here because:
135
+ // - Empty params are handled by early returns at lines 94-100
136
+ // - So paramNamesSet always has at least one element
137
+ // - So buildQueryString always returns a non-empty string
138
+ const newQueryString = buildQueryString(queryString, [...paramNamesSet]);
139
+
140
+ router.setRootPath(`${basePath}?${newQueryString}`);
141
+ } catch (error) {
142
+ // Rollback initialization marker on error
143
+ delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];
144
+
145
+ throw new Error(
146
+ `[@real-router/persistent-params-plugin] Failed to update root path: ${error instanceof Error ? error.message : String(error)}`,
147
+ { cause: error },
148
+ );
149
+ }
150
+
151
+ /**
152
+ * Merges persistent parameters with current navigation parameters.
153
+ * Validates all parameter values before merging.
154
+ *
155
+ * @param additionalParams - Parameters passed during navigation
156
+ * @returns Merged parameters object
157
+ * @throws {TypeError} If any parameter value is invalid (not a primitive)
158
+ */
159
+
160
+ function withPersistentParams(additionalParams: Params): Params {
161
+ // Extract safe params (prevent prototype pollution)
162
+ const safeParams = extractOwnParams(additionalParams);
163
+
164
+ // Validate and collect parameters to remove in a single pass
165
+ const paramsToRemove: string[] = [];
166
+
167
+ for (const key of Object.keys(safeParams)) {
168
+ const value = safeParams[key];
169
+
170
+ // If undefined and tracked, mark for removal (skip validation)
171
+ if (value === undefined && paramNamesSet.has(key)) {
172
+ paramsToRemove.push(key);
173
+ } else {
174
+ // Validate all other parameters
175
+ validateParamValue(key, value);
176
+ }
177
+ }
178
+
179
+ // Process all removals in one batch
180
+ if (paramsToRemove.length > 0) {
181
+ // Remove from both Set
182
+ for (const key of paramsToRemove) {
183
+ paramNamesSet.delete(key);
184
+ }
185
+
186
+ // Update persistentParams once (batch freeze)
187
+ const newParams: Params = { ...persistentParams };
188
+
189
+ for (const key of paramsToRemove) {
190
+ delete newParams[key];
191
+ }
192
+
193
+ persistentParams = Object.freeze(newParams);
194
+ }
195
+
196
+ // Merge persistent and current params
197
+ return mergeParams(persistentParams, safeParams);
198
+ }
199
+
200
+ // Override router methods to inject persistent params
201
+ // buildPath: needed for direct buildPath() calls (doesn't go through forwardState)
202
+ router.buildPath = (routeName, buildPathParams = {}) =>
203
+ originalBuildPath(routeName, withPersistentParams(buildPathParams));
204
+
205
+ // forwardState: intercepts params normalization for buildState, buildStateWithSegments, and navigate
206
+ // This is the central point where params are normalized before state creation
207
+ router.forwardState = <P extends Params = Params>(
208
+ routeName: string,
209
+ routeParams: P,
210
+ ) => {
211
+ const result = originalForwardState(routeName, routeParams);
212
+
213
+ return {
214
+ ...result,
215
+ params: withPersistentParams(result.params) as P,
216
+ };
217
+ };
218
+
219
+ return {
220
+ /**
221
+ * Updates persistent parameters after successful transition.
222
+ * Only processes parameters that are tracked and have changed.
223
+ *
224
+ * @param toState - Target state after successful transition
225
+ */
226
+ onTransitionSuccess(toState) {
227
+ try {
228
+ // Collect changed parameters and removals
229
+ const updates: Params = {};
230
+ const removals: string[] = [];
231
+ let hasChanges = false;
232
+
233
+ for (const key of paramNamesSet) {
234
+ const value = toState.params[key];
235
+
236
+ // If parameter is not in state params or is undefined, mark for removal
237
+ if (!Object.hasOwn(toState.params, key) || value === undefined) {
238
+ // Only mark as removal if it currently exists in persistentParams
239
+ if (
240
+ Object.hasOwn(persistentParams, key) &&
241
+ persistentParams[key] !== undefined
242
+ ) {
243
+ removals.push(key);
244
+ hasChanges = true;
245
+ }
246
+
247
+ continue;
248
+ }
249
+
250
+ // Validate type before storing
251
+ validateParamValue(key, value);
252
+
253
+ // Only update if value actually changed
254
+ if (persistentParams[key] !== value) {
255
+ updates[key] = value;
256
+ hasChanges = true;
257
+ }
258
+ }
259
+
260
+ // Create new frozen object only if there were changes
261
+ if (hasChanges) {
262
+ const newParams: Params = { ...persistentParams, ...updates };
263
+
264
+ // Remove parameters that were set to undefined
265
+ for (const key of removals) {
266
+ delete newParams[key];
267
+ }
268
+
269
+ persistentParams = Object.freeze(newParams);
270
+ }
271
+ } catch (error) {
272
+ // Log error but don't break navigation
273
+ /* v8 ignore next 5 -- @preserve defensive: validation happens before navigate() */
274
+ console.error(
275
+ "persistent-params-plugin",
276
+ "Error updating persistent params:",
277
+ error,
278
+ );
279
+ }
280
+ },
281
+
282
+ /**
283
+ * Cleanup function to restore original router state.
284
+ * Restores all overridden methods and paths.
285
+ * Called when plugin is unsubscribed.
286
+ */
287
+ teardown() {
288
+ try {
289
+ // Restore original methods
290
+ router.buildPath = originalBuildPath;
291
+ router.forwardState = originalForwardState;
292
+
293
+ // Restore original root path
294
+ router.setRootPath(originalRootPath);
295
+
296
+ // Remove initialization marker
297
+ delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];
298
+ } catch (error) {
299
+ console.error(
300
+ "persistent-params-plugin",
301
+ "Error during teardown:",
302
+ error,
303
+ );
304
+ }
305
+ },
306
+ };
307
+ };
308
+ }
package/src/types.ts ADDED
@@ -0,0 +1,17 @@
1
+ // packages/persistent-params-plugin/modules/types.ts
2
+
3
+ /**
4
+ * Configuration for persistent parameters' plugin.
5
+ * Can be either an array of parameter names or an object with default values.
6
+ *
7
+ * @example
8
+ * // Array of parameter names (initial values undefined)
9
+ * persistentParamsPlugin(['lang', 'theme'])
10
+ *
11
+ * @example
12
+ * // Object with default values
13
+ * persistentParamsPlugin({ lang: 'en', theme: 'light' })
14
+ */
15
+ export type PersistentParamsConfig =
16
+ | string[]
17
+ | Record<string, string | number | boolean>;
package/src/utils.ts ADDED
@@ -0,0 +1,250 @@
1
+ // packages/persistent-params-plugin/modules/utils.ts
2
+
3
+ import { isPrimitiveValue } from "type-guards";
4
+
5
+ import type { PersistentParamsConfig } from "./types";
6
+ import type { Params } from "@real-router/core";
7
+
8
+ const INVALID_PARAM_KEY_REGEX = /[\s#%&/=?\\]/;
9
+ const INVALID_CHARS_MESSAGE = String.raw`Cannot contain: = & ? # % / \ or whitespace`;
10
+
11
+ export function validateParamKey(key: string): void {
12
+ if (INVALID_PARAM_KEY_REGEX.test(key)) {
13
+ throw new TypeError(
14
+ `[@real-router/persistent-params-plugin] Invalid parameter name "${key}". ${INVALID_CHARS_MESSAGE}`,
15
+ );
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Validates params configuration structure and values.
21
+ * Ensures all parameter names are non-empty strings and all default values are primitives.
22
+ *
23
+ * @param config - Configuration to validate
24
+ * @returns true if configuration is valid
25
+ */
26
+ /**
27
+ * Validates params configuration structure and values.
28
+ * Ensures all parameter names are non-empty strings and all default values are primitives.
29
+ *
30
+ * @param config - Configuration to validate
31
+ * @returns true if configuration is valid
32
+ */
33
+ export function isValidParamsConfig(
34
+ config: unknown,
35
+ ): config is PersistentParamsConfig {
36
+ if (config === null || config === undefined) {
37
+ return false;
38
+ }
39
+
40
+ // Array configuration: all items must be non-empty strings
41
+ if (Array.isArray(config)) {
42
+ return config.every((item) => {
43
+ if (typeof item !== "string" || item.length === 0) {
44
+ return false;
45
+ }
46
+
47
+ try {
48
+ validateParamKey(item);
49
+
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ });
55
+ }
56
+
57
+ // Object configuration: must be plain object with primitive values
58
+ if (typeof config === "object") {
59
+ // Reject non-plain objects (Date, Map, etc.)
60
+ if (Object.getPrototypeOf(config) !== Object.prototype) {
61
+ return false;
62
+ }
63
+
64
+ // All keys must be non-empty strings, all values must be primitives
65
+ return Object.entries(config).every(([key, value]) => {
66
+ // Check key is non-empty string
67
+ if (typeof key !== "string" || key.length === 0) {
68
+ return false;
69
+ }
70
+
71
+ // Validate key doesn't contain special characters
72
+ try {
73
+ validateParamKey(key);
74
+ } catch {
75
+ return false;
76
+ }
77
+
78
+ // Validate value is primitive (NaN/Infinity already rejected by isPrimitiveValue)
79
+ return isPrimitiveValue(value);
80
+ });
81
+ }
82
+
83
+ return false;
84
+ }
85
+
86
+ /**
87
+ * Validates parameter value before persisting.
88
+ * Throws descriptive TypeError if value is not valid for URL parameters.
89
+ *
90
+ * @param key - Parameter name for error messages
91
+ * @param value - Value to validate
92
+ * @throws {TypeError} If value is null, array, object, or other non-primitive type
93
+ */
94
+ export function validateParamValue(key: string, value: unknown): void {
95
+ if (value === null) {
96
+ throw new TypeError(
97
+ `[@real-router/persistent-params-plugin] Parameter "${key}" cannot be null. ` +
98
+ `Use undefined to remove the parameter from persistence.`,
99
+ );
100
+ }
101
+
102
+ if (value !== undefined && !isPrimitiveValue(value)) {
103
+ const actualType = Array.isArray(value) ? "array" : typeof value;
104
+
105
+ throw new TypeError(
106
+ `[@real-router/persistent-params-plugin] Parameter "${key}" must be a primitive value ` +
107
+ `(string, number, or boolean), got ${actualType}. ` +
108
+ `Objects and arrays are not supported in URL parameters.`,
109
+ );
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Safely extracts own properties from params object.
115
+ * Uses Object.hasOwn to prevent prototype pollution attacks.
116
+ *
117
+ * @param params - Parameters object (may contain inherited properties)
118
+ * @returns New object with only own properties
119
+ *
120
+ * @example
121
+ * const malicious = Object.create({ __proto__: { admin: true } });
122
+ * malicious.mode = 'dev';
123
+ * const safe = extractOwnParams(malicious); // { mode: 'dev' } (no __proto__)
124
+ */
125
+ export function extractOwnParams(params: Params): Params {
126
+ const result: Params = {};
127
+
128
+ for (const key in params) {
129
+ // Only process own properties, skip inherited ones
130
+ if (Object.hasOwn(params, key)) {
131
+ result[key] = params[key];
132
+ }
133
+ }
134
+
135
+ return result;
136
+ }
137
+
138
+ /**
139
+ * Parses path into base path and query string components.
140
+ * Handles edge cases like leading ?, multiple ?, empty path.
141
+ *
142
+ * @param path - Path to parse (e.g., "/route?param=value")
143
+ * @returns Object with basePath and queryString
144
+ *
145
+ * @example
146
+ * parseQueryString('/users?page=1') // { basePath: '/users', queryString: 'page=1' }
147
+ * parseQueryString('?existing') // { basePath: '', queryString: 'existing' }
148
+ * parseQueryString('/path') // { basePath: '/path', queryString: '' }
149
+ */
150
+ export function parseQueryString(path: string): {
151
+ basePath: string;
152
+ queryString: string;
153
+ } {
154
+ const questionMarkIndex = path.indexOf("?");
155
+
156
+ // No query string
157
+ if (questionMarkIndex === -1) {
158
+ return { basePath: path, queryString: "" };
159
+ }
160
+
161
+ // Path starts with ? (edge case)
162
+ if (questionMarkIndex === 0) {
163
+ return { basePath: "", queryString: path.slice(1) };
164
+ }
165
+
166
+ // Normal case: path?query
167
+ return {
168
+ basePath: path.slice(0, questionMarkIndex),
169
+ queryString: path.slice(questionMarkIndex + 1),
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Builds query string from parameter names.
175
+ * Preserves existing query parameters and appends new ones.
176
+ *
177
+ * @param existingQuery - Existing query string (without leading ?)
178
+ * @param paramNames - Parameter names to append
179
+ * @returns Combined query string
180
+ *
181
+ * @example
182
+ * buildQueryString('existing=1', ['mode', 'lang']) // 'existing=1&mode&lang'
183
+ * buildQueryString('', ['mode']) // 'mode'
184
+ */
185
+ export function buildQueryString(
186
+ existingQuery: string,
187
+ paramNames: readonly string[],
188
+ ): string {
189
+ if (paramNames.length === 0) {
190
+ return existingQuery;
191
+ }
192
+
193
+ const separator = existingQuery ? "&" : "";
194
+
195
+ return existingQuery + separator + paramNames.join("&");
196
+ }
197
+
198
+ /**
199
+ * Merges persistent and current parameters into a single Params object.
200
+ * Keys explicitly set to `undefined` in current params are removed from result.
201
+ *
202
+ * Creates a new immutable object - does not mutate input parameters.
203
+ *
204
+ * @param persistent - Frozen persistent parameters
205
+ * @param current - Current parameters from navigation
206
+ * @returns New Params object with merged values
207
+ *
208
+ * @example
209
+ * const persistent = { lang: 'en', theme: 'dark' };
210
+ * const current = { theme: 'light', mode: 'dev' };
211
+ * mergeParams(persistent, current); // { lang: 'en', theme: 'light', mode: 'dev' }
212
+ *
213
+ * @example
214
+ * // Removing parameters with undefined
215
+ * const persistent = { lang: 'en', theme: 'dark' };
216
+ * const current = { theme: undefined };
217
+ * mergeParams(persistent, current); // { lang: 'en' } (theme removed)
218
+ */
219
+ export function mergeParams(
220
+ persistent: Readonly<Params>,
221
+ current: Params,
222
+ ): Params {
223
+ // Safely extract own properties from current params
224
+ const safeCurrentParams = extractOwnParams(current);
225
+
226
+ // Start with persistent params, but EXCLUDE undefined values
227
+ // (undefined values don't appear in URLs, so we shouldn't include them)
228
+ const result: Params = {};
229
+
230
+ for (const key in persistent) {
231
+ if (Object.hasOwn(persistent, key) && persistent[key] !== undefined) {
232
+ result[key] = persistent[key];
233
+ }
234
+ }
235
+
236
+ // Apply current params
237
+ for (const key of Object.keys(safeCurrentParams)) {
238
+ const value = safeCurrentParams[key];
239
+
240
+ if (value === undefined) {
241
+ // Remove param if explicitly set to undefined
242
+ delete result[key];
243
+ } else {
244
+ // Add or update param
245
+ result[key] = value;
246
+ }
247
+ }
248
+
249
+ return result;
250
+ }