@real-router/persistent-params-plugin 0.1.39 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,37 +1,33 @@
1
1
  # @real-router/persistent-params-plugin
2
2
 
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
3
+ [![npm](https://img.shields.io/npm/v/@real-router/persistent-params-plugin.svg?style=flat-square)](https://www.npmjs.com/package/@real-router/persistent-params-plugin)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@real-router/persistent-params-plugin.svg?style=flat-square)](https://www.npmjs.com/package/@real-router/persistent-params-plugin)
5
+ [![bundle size](https://deno.bundlejs.com/?q=@real-router/persistent-params-plugin&treeshake=[*]&badge=detailed)](https://bundlejs.com/?q=@real-router/persistent-params-plugin&treeshake=[*])
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](../../LICENSE)
5
7
 
6
- Automatically persists query parameters across all navigation transitions.
7
-
8
- ## Problem & Solution
8
+ > Automatically persist query parameters across all navigation transitions in [Real-Router](https://github.com/greydragon888/real-router).
9
9
 
10
10
  ```typescript
11
11
  // Without plugin:
12
12
  router.navigate("products", { lang: "en", theme: "dark" });
13
13
  router.navigate("cart");
14
- // URL: /cart lang and theme are lost
14
+ // URL: /cart lang and theme are lost
15
15
 
16
16
  // With plugin:
17
17
  router.usePlugin(persistentParamsPluginFactory(["lang", "theme"]));
18
18
  router.navigate("products", { lang: "en", theme: "dark" });
19
19
  router.navigate("cart");
20
- // URL: /cart?lang=en&theme=dark automatically preserved
20
+ // URL: /cart?lang=en&theme=dark automatically preserved
21
21
  ```
22
22
 
23
23
  ## Installation
24
24
 
25
25
  ```bash
26
26
  npm install @real-router/persistent-params-plugin
27
- # or
28
- pnpm add @real-router/persistent-params-plugin
29
- # or
30
- yarn add @real-router/persistent-params-plugin
31
- # or
32
- bun add @real-router/persistent-params-plugin
33
27
  ```
34
28
 
29
+ **Peer dependency:** `@real-router/core`
30
+
35
31
  ## Quick Start
36
32
 
37
33
  ```typescript
@@ -40,70 +36,41 @@ import { persistentParamsPluginFactory } from "@real-router/persistent-params-pl
40
36
 
41
37
  const router = createRouter(routes);
42
38
 
43
- // Option 1: Parameter names (values set on first use)
39
+ // Array values set on first navigation
44
40
  router.usePlugin(persistentParamsPluginFactory(["lang", "theme"]));
45
41
 
46
- // Option 2: With default values
47
- router.usePlugin(
48
- persistentParamsPluginFactory({
49
- lang: "en",
50
- theme: "light",
51
- }),
52
- );
53
-
54
- router.start();
42
+ // Object with default values
43
+ router.usePlugin(persistentParamsPluginFactory({ lang: "en", theme: "light" }));
55
44
  ```
56
45
 
57
- ---
58
-
59
46
  ## Configuration
60
47
 
61
- | Config Type | Description | Example |
62
- | --------------------------- | ------------------------------------------- | ------------------- |
63
- | `string[]` | Parameter names, initial values `undefined` | `["lang", "theme"]` |
64
- | `Record<string, primitive>` | Parameter names with defaults | `{ lang: "en" }` |
65
-
66
- **Allowed value types:** `string`, `number`, `boolean`, `undefined` (to remove)
48
+ | Config Type | Description | Example |
49
+ |-------------|-------------|---------|
50
+ | `string[]` | Parameter names, initial values `undefined` | `["lang", "theme"]` |
51
+ | `Record<string, primitive>` | Parameter names with defaults | `{ lang: "en" }` |
67
52
 
68
- See [Wiki](https://github.com/greydragon888/real-router/wiki/persistent-params-plugin#3-configuration-options) for details.
69
-
70
- ---
53
+ **Allowed value types:** `string`, `number`, `boolean`, `undefined` (to remove a param).
71
54
 
72
55
  ## Behavior
73
56
 
74
- ### Persistence
75
-
76
- ```typescript
77
- router.navigate("page1", { lang: "en" }); // Saved: lang=en
78
- router.navigate("page2"); // URL: /page2?lang=en
79
- ```
80
-
81
- ### Update
82
-
83
57
  ```typescript
84
- router.navigate("page", { lang: "fr" }); // Updates saved value
85
- ```
86
-
87
- ### Remove
58
+ // Persist saved on first navigation
59
+ router.navigate("page1", { lang: "en" }); // saved: lang=en
88
60
 
89
- ```typescript
90
- router.navigate("page", { lang: undefined }); // Removes from persistent params
91
- ```
61
+ // Carry — auto-injected into subsequent navigations
62
+ router.navigate("page2"); // URL: /page2?lang=en
92
63
 
93
- ### Priority
64
+ // Update — explicit values override saved ones
65
+ router.navigate("page3", { lang: "fr" }); // URL: /page3?lang=fr, saved: lang=fr
94
66
 
95
- Explicit values override saved ones:
96
-
97
- ```typescript
98
- // Saved: lang=en
99
- router.navigate("page", { lang: "de" }); // URL: /page?lang=de
67
+ // Remove pass undefined to stop persisting
68
+ router.navigate("page4", { lang: undefined }); // lang removed permanently
100
69
  ```
101
70
 
102
- See [Wiki](https://github.com/greydragon888/real-router/wiki/persistent-params-plugin#8-behavior) for edge cases and guarantees.
103
-
104
- ---
71
+ > **Note:** Removal is permanent for the plugin lifetime. Once `undefined` is passed, the param is no longer tracked — even if passed again in a later navigation.
105
72
 
106
- ## Usage Examples
73
+ ## Use Cases
107
74
 
108
75
  ### Multilingual App
109
76
 
@@ -111,8 +78,8 @@ See [Wiki](https://github.com/greydragon888/real-router/wiki/persistent-params-p
111
78
  router.usePlugin(persistentParamsPluginFactory({ lang: "en" }));
112
79
 
113
80
  router.navigate("settings", { lang: "fr" });
114
- router.navigate("products"); // ?lang=fr
115
- router.navigate("cart"); // ?lang=fr
81
+ router.navigate("products"); // ?lang=fr
82
+ router.navigate("cart"); // ?lang=fr
116
83
  ```
117
84
 
118
85
  ### UTM Tracking
@@ -123,41 +90,38 @@ router.usePlugin(
123
90
  );
124
91
 
125
92
  // User arrives: /?utm_source=google&utm_medium=cpc
126
- router.navigate("products"); // UTM params preserved
127
- router.navigate("checkout"); // UTM params preserved
93
+ router.navigate("products"); // UTM params preserved
94
+ router.navigate("checkout"); // UTM params preserved
128
95
  ```
129
96
 
130
- ---
131
-
132
- ## Lifecycle
97
+ ### Cleanup
133
98
 
134
99
  ```typescript
135
100
  const unsubscribe = router.usePlugin(persistentParamsPluginFactory(["mode"]));
136
101
 
137
- // Later: restore original router behavior
102
+ // Later restore original router behavior
138
103
  unsubscribe();
139
104
  ```
140
105
 
141
- **Note:** Double initialization throws an error. Call `unsubscribe()` first.
142
-
143
- ---
144
-
145
106
  ## Documentation
146
107
 
147
- Full documentation on [Wiki](https://github.com/greydragon888/real-router/wiki/persistent-params-plugin):
108
+ Full documentation: [Wiki — persistent-params-plugin](https://github.com/greydragon888/real-router/wiki/persistent-params-plugin)
148
109
 
149
110
  - [Configuration Options](https://github.com/greydragon888/real-router/wiki/persistent-params-plugin#3-configuration-options)
150
- - [Lifecycle Hooks](https://github.com/greydragon888/real-router/wiki/persistent-params-plugin#4-lifecycle-hooks)
151
111
  - [Behavior & Edge Cases](https://github.com/greydragon888/real-router/wiki/persistent-params-plugin#8-behavior)
152
112
  - [Migration from router5](https://github.com/greydragon888/real-router/wiki/persistent-params-plugin#11-migration-from-router5)
153
113
 
154
- ---
155
-
156
114
  ## Related Packages
157
115
 
158
- - [@real-router/core](https://www.npmjs.com/package/@real-router/core) Core router
159
- - [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) — Browser history
116
+ | Package | Description |
117
+ |---------|-------------|
118
+ | [@real-router/core](https://www.npmjs.com/package/@real-router/core) | Core router (required peer dependency) |
119
+ | [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) | Browser History API integration |
120
+
121
+ ## Contributing
122
+
123
+ See [contributing guidelines](../../CONTRIBUTING.md) for development setup and PR process.
160
124
 
161
125
  ## License
162
126
 
163
- MIT © [Oleg Ivanov](https://github.com/greydragon888)
127
+ [MIT](../../LICENSE) © [Oleg Ivanov](https://github.com/greydragon888)
package/dist/cjs/index.js CHANGED
@@ -1 +1 @@
1
- var t=require("@real-router/core"),e="[@real-router/persistent-params-plugin]";function r(t){return"number"==typeof t?Number.isFinite(t):"string"==typeof t||"boolean"==typeof t}var s=/[\s#%&/=?\\]/,a=String.raw`Cannot contain: = & ? # % / \ or whitespace`;function n(t){if(s.test(t))throw new TypeError(`${e} Invalid parameter name "${t}". ${a}`)}function o(t,s){if(null===s)throw new TypeError(`${e} Parameter "${t}" cannot be null. Use undefined to remove the parameter from persistence.`);if(void 0!==s&&!r(s)){const r=Array.isArray(s)?"array":typeof s;throw new TypeError(`${e} Parameter "${t}" must be a primitive value (string, number, or boolean), got ${r}. Objects and arrays are not supported in URL parameters.`)}}var i=class{#t;#e;#r;#s;#a;#n;constructor(t,r,s,a){let n,o;this.#t=t,this.#n=r,this.#e=s,this.#r=a;try{t.setRootPath(`${a}?${[...s].join("&")}`),n=t.addInterceptor("buildPath",(t,e,r)=>t(e,this.#o(r??{}))),o=t.addInterceptor("forwardState",(t,e,r)=>{const s=t(e,r);return{...s,params:this.#o(s.params)}})}catch(r){throw n?.(),o?.(),t.setRootPath(a),new Error(`${e} Failed to initialize: ${r instanceof Error?r.message:String(r)}`,{cause:r})}this.#s=n,this.#a=o}getPlugin(){return{onTransitionSuccess:t=>{this.#i(t)},teardown:()=>{this.#c()}}}#o(t){const e=function(t){const e={};for(const r in t)Object.hasOwn(t,r)&&(e[r]=t[r]);return e}(t);let r;for(const t of Object.keys(e)){const s=e[t];void 0===s&&this.#e.has(t)?(this.#e.delete(t),r??={...this.#n},delete r[t]):o(t,s)}return r&&(this.#n=Object.freeze(r)),function(t,e){const r={};for(const e in t)Object.hasOwn(t,e)&&void 0!==t[e]&&(r[e]=t[e]);for(const t of Object.keys(e)){const s=e[t];void 0===s?delete r[t]:r[t]=s}return r}(this.#n,e)}#i(t){let e;for(const r of this.#e){const s=t.params[r];Object.hasOwn(t.params,r)&&void 0!==s?(o(r,s),this.#n[r]!==s&&(e??={...this.#n},e[r]=s)):Object.hasOwn(this.#n,r)&&void 0!==this.#n[r]&&(e??={...this.#n},delete e[r])}e&&(this.#n=Object.freeze(e))}#c(){this.#s(),this.#a();try{this.#t.setRootPath(this.#r)}catch{}}},c={},p=()=>c;exports.persistentParamsPluginFactory=function(s={}){!function(t){if(null==(s=t)||!(Array.isArray(s)?s.every(t=>{if("string"!=typeof t||0===t.length)return!1;try{return n(t),!0}catch{return!1}}):"object"==typeof s&&Object.getPrototypeOf(s)===Object.prototype&&Object.entries(s).every(([t,e])=>{if("string"!=typeof t||0===t.length)return!1;try{n(t)}catch{return!1}return r(e)}))){let r;throw r=null===t?"null":Array.isArray(t)?"array with invalid items":typeof t,new TypeError(`${e} Invalid params configuration. Expected array of non-empty strings or object with primitive values, got ${r}.`)}var s}(s);const a=Array.isArray(s)?s:Object.keys(s);if(0===a.length)return p;const o={};if(Array.isArray(s))for(const t of s)o[t]=void 0;else Object.assign(o,s);Object.freeze(o);const c=new Set(a);return e=>{const r=t.getPluginApi(e);return new i(r,o,new Set(c),r.getRootPath()).getPlugin()}};//# sourceMappingURL=index.js.map
1
+ var t=require("@real-router/core/api"),e="[@real-router/persistent-params-plugin]";function r(t){return"number"==typeof t?Number.isFinite(t):"string"==typeof t||"boolean"==typeof t}var a=/[\s#%&/=?\\]/,s=String.raw`Cannot contain: = & ? # % / \ or whitespace`;function n(t){if(a.test(t))throw new TypeError(`${e} Invalid parameter name "${t}". ${s}`)}function o(t,a){if(null===a)throw new TypeError(`${e} Parameter "${t}" cannot be null. Use undefined to remove the parameter from persistence.`);if(void 0!==a&&!r(a)){const r=Array.isArray(a)?"array":typeof a;throw new TypeError(`${e} Parameter "${t}" must be a primitive value (string, number, or boolean), got ${r}. Objects and arrays are not supported in URL parameters.`)}}var i=class{#t;#e;#r;#a;#s;#n;constructor(t,r,a,s){let n,o;this.#t=t,this.#n=r,this.#e=a,this.#r=s;try{t.setRootPath(`${s}?${[...a].join("&")}`),n=t.addInterceptor("buildPath",(t,e,r)=>t(e,this.#o(r??{}))),o=t.addInterceptor("forwardState",(t,e,r)=>{const a=t(e,r);return{...a,params:this.#o(a.params)}})}catch(r){throw n?.(),o?.(),t.setRootPath(s),new Error(`${e} Failed to initialize: ${r instanceof Error?r.message:String(r)}`,{cause:r})}this.#a=n,this.#s=o}getPlugin(){return{onTransitionSuccess:t=>{this.#i(t)},teardown:()=>{this.#c()}}}#o(t){const e=function(t){const e={};for(const r in t)Object.hasOwn(t,r)&&(e[r]=t[r]);return e}(t);let r;for(const t of Object.keys(e)){const a=e[t];void 0===a&&this.#e.has(t)?(this.#e.delete(t),r??={...this.#n},delete r[t]):o(t,a)}return r&&(this.#n=Object.freeze(r)),function(t,e){const r={};for(const e in t)Object.hasOwn(t,e)&&void 0!==t[e]&&(r[e]=t[e]);for(const t of Object.keys(e)){const a=e[t];void 0===a?delete r[t]:r[t]=a}return r}(this.#n,e)}#i(t){let e;for(const r of this.#e){const a=t.params[r];Object.hasOwn(t.params,r)&&void 0!==a?(o(r,a),this.#n[r]!==a&&(e??={...this.#n},e[r]=a)):Object.hasOwn(this.#n,r)&&void 0!==this.#n[r]&&(e??={...this.#n},delete e[r])}e&&(this.#n=Object.freeze(e))}#c(){this.#a(),this.#s();try{this.#t.setRootPath(this.#r)}catch{}}},c={},p=()=>c;exports.persistentParamsPluginFactory=function(a={}){!function(t){if(null==(a=t)||!(Array.isArray(a)?a.every(t=>{if("string"!=typeof t||0===t.length)return!1;try{return n(t),!0}catch{return!1}}):"object"==typeof a&&Object.getPrototypeOf(a)===Object.prototype&&Object.entries(a).every(([t,e])=>{if("string"!=typeof t||0===t.length)return!1;try{n(t)}catch{return!1}return r(e)}))){let r;throw r=null===t?"null":Array.isArray(t)?"array with invalid items":typeof t,new TypeError(`${e} Invalid params configuration. Expected array of non-empty strings or object with primitive values, got ${r}.`)}var a}(a);const s=Array.isArray(a)?a:Object.keys(a);if(0===s.length)return p;const o={};if(Array.isArray(a))for(const t of a)o[t]=void 0;else Object.assign(o,a);Object.freeze(o);const c=new Set(s);return e=>{const r=t.getPluginApi(e);return new i(r,o,new Set(c),r.getRootPath()).getPlugin()}};//# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/constants.ts","../../src/param-utils.ts","../../src/validation.ts","../../src/plugin.ts","../../src/factory.ts"],"names":["getPluginApi"],"mappings":"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","file":"index.js","sourcesContent":["// packages/persistent-params-plugin/src/constants.ts\n\nexport const LOGGER_CONTEXT = \"persistent-params-plugin\";\n\nexport const ERROR_PREFIX = `[@real-router/${LOGGER_CONTEXT}]`;\n","// packages/persistent-params-plugin/src/param-utils.ts\n\nimport type { Params } from \"@real-router/core\";\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 /* v8 ignore next -- @preserve: core validates params prototype — inherited keys never reach here */\n if (Object.hasOwn(params, key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n\n/**\n * Merges persistent and current parameters into a single Params object.\n *\n * IMPORTANT: `current` must be pre-sanitized via `extractOwnParams()` by the caller.\n * This function does NOT perform prototype pollution protection on its own.\n *\n * @param persistent - Frozen persistent parameters\n * @param current - Pre-sanitized current parameters (own properties only)\n */\nexport function mergeParams(\n persistent: Readonly<Params>,\n current: Params,\n): Params {\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 for (const key of Object.keys(current)) {\n const value = current[key];\n\n if (value === undefined) {\n delete result[key];\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n","// packages/persistent-params-plugin/src/validation.ts\n\nimport { isPrimitiveValue } from \"type-guards\";\n\nimport { ERROR_PREFIX } from \"./constants\";\n\nimport type { PersistentParamsConfig } from \"./types\";\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 `${ERROR_PREFIX} 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 */\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 `${ERROR_PREFIX} 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 `${ERROR_PREFIX} 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 * Validates the params configuration and throws a descriptive error if invalid.\n *\n * @param params - Configuration to validate\n * @throws {TypeError} If params is not a valid configuration\n */\nexport function validateConfig(params: unknown): void {\n if (!isValidParamsConfig(params)) {\n let actualType: string;\n\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 `${ERROR_PREFIX} Invalid params configuration. ` +\n `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,\n );\n }\n}\n","// packages/persistent-params-plugin/src/plugin.ts\n\nimport { ERROR_PREFIX } from \"./constants\";\nimport { extractOwnParams, mergeParams } from \"./param-utils\";\nimport { validateParamValue } from \"./validation\";\n\nimport type { Params, PluginApi, State, Plugin } from \"@real-router/core\";\n\nexport class PersistentParamsPlugin {\n readonly #api: PluginApi;\n readonly #paramNamesSet: Set<string>;\n readonly #originalRootPath: string;\n readonly #removeBuildPathInterceptor: () => void;\n readonly #removeForwardStateInterceptor: () => void;\n\n #persistentParams: Readonly<Params>;\n\n constructor(\n api: PluginApi,\n persistentParams: Readonly<Params>,\n paramNamesSet: Set<string>,\n originalRootPath: string,\n ) {\n this.#api = api;\n this.#persistentParams = persistentParams;\n this.#paramNamesSet = paramNamesSet;\n this.#originalRootPath = originalRootPath;\n\n let removeBuildPath: (() => void) | undefined;\n let removeForwardState: (() => void) | undefined;\n\n try {\n api.setRootPath(`${originalRootPath}?${[...paramNamesSet].join(\"&\")}`);\n\n removeBuildPath = api.addInterceptor(\n \"buildPath\",\n (next, route, navParams) =>\n next(route, this.#withPersistentParams(navParams ?? {})),\n );\n\n removeForwardState = api.addInterceptor(\n \"forwardState\",\n (next, routeName, routeParams) => {\n const result = next(routeName, routeParams);\n\n return {\n ...result,\n params: this.#withPersistentParams(result.params),\n };\n },\n );\n } /* v8 ignore start -- @preserve: rollback on partial initialization failure */ catch (error) {\n removeBuildPath?.();\n removeForwardState?.();\n api.setRootPath(originalRootPath);\n\n throw new Error(\n `${ERROR_PREFIX} Failed to initialize: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n } /* v8 ignore stop */\n\n this.#removeBuildPathInterceptor = removeBuildPath;\n this.#removeForwardStateInterceptor = removeForwardState;\n }\n\n getPlugin(): Plugin {\n return {\n onTransitionSuccess: (toState) => {\n this.#onTransitionSuccess(toState);\n },\n teardown: () => {\n this.#teardown();\n },\n };\n }\n\n #withPersistentParams(additionalParams: Params): Params {\n const safeParams = extractOwnParams(additionalParams);\n let newParams: Params | undefined;\n\n for (const key of Object.keys(safeParams)) {\n const value = safeParams[key];\n\n if (value === undefined && this.#paramNamesSet.has(key)) {\n this.#paramNamesSet.delete(key);\n newParams ??= { ...this.#persistentParams };\n delete newParams[key];\n } else {\n validateParamValue(key, value);\n }\n }\n\n if (newParams) {\n this.#persistentParams = Object.freeze(newParams);\n }\n\n return mergeParams(this.#persistentParams, safeParams);\n }\n\n #onTransitionSuccess(toState: State): void {\n let newParams: Params | undefined;\n\n for (const key of this.#paramNamesSet) {\n const value = toState.params[key];\n\n if (!Object.hasOwn(toState.params, key) || value === undefined) {\n /* v8 ignore next 4 -- @preserve: defensive removal for states committed via navigateToState bypassing forwardState */\n if (\n Object.hasOwn(this.#persistentParams, key) &&\n this.#persistentParams[key] !== undefined\n ) {\n newParams ??= { ...this.#persistentParams };\n delete newParams[key];\n }\n\n continue;\n }\n\n validateParamValue(key, value);\n\n if (this.#persistentParams[key] !== value) {\n newParams ??= { ...this.#persistentParams };\n newParams[key] = value;\n }\n }\n\n if (newParams) {\n this.#persistentParams = Object.freeze(newParams);\n }\n }\n\n #teardown(): void {\n this.#removeBuildPathInterceptor();\n this.#removeForwardStateInterceptor();\n\n /* v8 ignore start -- @preserve: setRootPath throws RouterError(ROUTER_DISPOSED) during router.dispose() */\n try {\n this.#api.setRootPath(this.#originalRootPath);\n } catch {\n // Expected during router.dispose(): FSM enters DISPOSED before plugin teardown,\n // so setRootPath's throwIfDisposed() check throws. Restoring rootPath on a\n // destroyed router is unnecessary — swallow silently.\n }\n /* v8 ignore stop */\n }\n}\n","// packages/persistent-params-plugin/src/factory.ts\n\nimport { getPluginApi } from \"@real-router/core\";\n\nimport { PersistentParamsPlugin } from \"./plugin\";\nimport { validateConfig } from \"./validation\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params, PluginFactory, Plugin } from \"@real-router/core\";\n\n// Shared singleton — frozen by core on first use. Do not add properties.\nconst EMPTY_PLUGIN: Plugin = {};\nconst noop: PluginFactory = () => EMPTY_PLUGIN;\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 validateConfig(params);\n\n const paramNames = Array.isArray(params) ? params : Object.keys(params);\n\n if (paramNames.length === 0) {\n return noop;\n }\n\n const initialParams: Params = {};\n\n if (Array.isArray(params)) {\n for (const param of params) {\n initialParams[param] = undefined;\n }\n } else {\n Object.assign(initialParams, params);\n }\n\n Object.freeze(initialParams);\n\n const paramNamesSet = new Set<string>(paramNames);\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const plugin = new PersistentParamsPlugin(\n api,\n initialParams,\n new Set(paramNamesSet),\n api.getRootPath(),\n );\n\n return plugin.getPlugin();\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/param-utils.ts","../../src/validation.ts","../../src/plugin.ts","../../src/factory.ts"],"names":["api","getPluginApi"],"mappings":"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","file":"index.js","sourcesContent":["// packages/persistent-params-plugin/src/constants.ts\n\nexport const LOGGER_CONTEXT = \"persistent-params-plugin\";\n\nexport const ERROR_PREFIX = `[@real-router/${LOGGER_CONTEXT}]`;\n","// packages/persistent-params-plugin/src/param-utils.ts\n\nimport type { Params } from \"@real-router/core\";\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 /* v8 ignore next -- @preserve: core validates params prototype — inherited keys never reach here */\n if (Object.hasOwn(params, key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n\n/**\n * Merges persistent and current parameters into a single Params object.\n *\n * IMPORTANT: `current` must be pre-sanitized via `extractOwnParams()` by the caller.\n * This function does NOT perform prototype pollution protection on its own.\n *\n * @param persistent - Frozen persistent parameters\n * @param current - Pre-sanitized current parameters (own properties only)\n */\nexport function mergeParams(\n persistent: Readonly<Params>,\n current: Params,\n): Params {\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 for (const key of Object.keys(current)) {\n const value = current[key];\n\n if (value === undefined) {\n delete result[key];\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n","// packages/persistent-params-plugin/src/validation.ts\n\nimport { isPrimitiveValue } from \"type-guards\";\n\nimport { ERROR_PREFIX } from \"./constants\";\n\nimport type { PersistentParamsConfig } from \"./types\";\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 `${ERROR_PREFIX} 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 */\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 `${ERROR_PREFIX} 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 `${ERROR_PREFIX} 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 * Validates the params configuration and throws a descriptive error if invalid.\n *\n * @param params - Configuration to validate\n * @throws {TypeError} If params is not a valid configuration\n */\nexport function validateConfig(params: unknown): void {\n if (!isValidParamsConfig(params)) {\n let actualType: string;\n\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 `${ERROR_PREFIX} Invalid params configuration. ` +\n `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,\n );\n }\n}\n","// packages/persistent-params-plugin/src/plugin.ts\n\nimport { ERROR_PREFIX } from \"./constants\";\nimport { extractOwnParams, mergeParams } from \"./param-utils\";\nimport { validateParamValue } from \"./validation\";\n\nimport type { Params, State, Plugin } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nexport class PersistentParamsPlugin {\n readonly #api: PluginApi;\n readonly #paramNamesSet: Set<string>;\n readonly #originalRootPath: string;\n readonly #removeBuildPathInterceptor: () => void;\n readonly #removeForwardStateInterceptor: () => void;\n\n #persistentParams: Readonly<Params>;\n\n constructor(\n api: PluginApi,\n persistentParams: Readonly<Params>,\n paramNamesSet: Set<string>,\n originalRootPath: string,\n ) {\n this.#api = api;\n this.#persistentParams = persistentParams;\n this.#paramNamesSet = paramNamesSet;\n this.#originalRootPath = originalRootPath;\n\n let removeBuildPath: (() => void) | undefined;\n let removeForwardState: (() => void) | undefined;\n\n try {\n api.setRootPath(`${originalRootPath}?${[...paramNamesSet].join(\"&\")}`);\n\n removeBuildPath = api.addInterceptor(\n \"buildPath\",\n (next, route, navParams) =>\n next(route, this.#withPersistentParams(navParams ?? {})),\n );\n\n removeForwardState = api.addInterceptor(\n \"forwardState\",\n (next, routeName, routeParams) => {\n const result = next(routeName, routeParams);\n\n return {\n ...result,\n params: this.#withPersistentParams(result.params),\n };\n },\n );\n } /* v8 ignore start -- @preserve: rollback on partial initialization failure */ catch (error) {\n removeBuildPath?.();\n removeForwardState?.();\n api.setRootPath(originalRootPath);\n\n throw new Error(\n `${ERROR_PREFIX} Failed to initialize: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n } /* v8 ignore stop */\n\n this.#removeBuildPathInterceptor = removeBuildPath;\n this.#removeForwardStateInterceptor = removeForwardState;\n }\n\n getPlugin(): Plugin {\n return {\n onTransitionSuccess: (toState) => {\n this.#onTransitionSuccess(toState);\n },\n teardown: () => {\n this.#teardown();\n },\n };\n }\n\n #withPersistentParams(additionalParams: Params): Params {\n const safeParams = extractOwnParams(additionalParams);\n let newParams: Params | undefined;\n\n for (const key of Object.keys(safeParams)) {\n const value = safeParams[key];\n\n if (value === undefined && this.#paramNamesSet.has(key)) {\n this.#paramNamesSet.delete(key);\n newParams ??= { ...this.#persistentParams };\n delete newParams[key];\n } else {\n validateParamValue(key, value);\n }\n }\n\n if (newParams) {\n this.#persistentParams = Object.freeze(newParams);\n }\n\n return mergeParams(this.#persistentParams, safeParams);\n }\n\n #onTransitionSuccess(toState: State): void {\n let newParams: Params | undefined;\n\n for (const key of this.#paramNamesSet) {\n const value = toState.params[key];\n\n if (!Object.hasOwn(toState.params, key) || value === undefined) {\n /* v8 ignore next 4 -- @preserve: defensive removal for states committed via navigateToState bypassing forwardState */\n if (\n Object.hasOwn(this.#persistentParams, key) &&\n this.#persistentParams[key] !== undefined\n ) {\n newParams ??= { ...this.#persistentParams };\n delete newParams[key];\n }\n\n continue;\n }\n\n validateParamValue(key, value);\n\n if (this.#persistentParams[key] !== value) {\n newParams ??= { ...this.#persistentParams };\n newParams[key] = value;\n }\n }\n\n if (newParams) {\n this.#persistentParams = Object.freeze(newParams);\n }\n }\n\n #teardown(): void {\n this.#removeBuildPathInterceptor();\n this.#removeForwardStateInterceptor();\n\n /* v8 ignore start -- @preserve: setRootPath throws RouterError(ROUTER_DISPOSED) during router.dispose() */\n try {\n this.#api.setRootPath(this.#originalRootPath);\n } catch {\n // Expected during router.dispose(): FSM enters DISPOSED before plugin teardown,\n // so setRootPath's throwIfDisposed() check throws. Restoring rootPath on a\n // destroyed router is unnecessary — swallow silently.\n }\n /* v8 ignore stop */\n }\n}\n","// packages/persistent-params-plugin/src/factory.ts\n\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { PersistentParamsPlugin } from \"./plugin\";\nimport { validateConfig } from \"./validation\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params, PluginFactory, Plugin } from \"@real-router/core\";\n\n// Shared singleton — frozen by core on first use. Do not add properties.\nconst EMPTY_PLUGIN: Plugin = {};\nconst noop: PluginFactory = () => EMPTY_PLUGIN;\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 validateConfig(params);\n\n const paramNames = Array.isArray(params) ? params : Object.keys(params);\n\n if (paramNames.length === 0) {\n return noop;\n }\n\n const initialParams: Params = {};\n\n if (Array.isArray(params)) {\n for (const param of params) {\n initialParams[param] = undefined;\n }\n } else {\n Object.assign(initialParams, params);\n }\n\n Object.freeze(initialParams);\n\n const paramNamesSet = new Set<string>(paramNames);\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const plugin = new PersistentParamsPlugin(\n api,\n initialParams,\n new Set(paramNamesSet),\n api.getRootPath(),\n );\n\n return plugin.getPlugin();\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/constants.ts":{"bytes":178,"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/param-utils.ts":{"bytes":1731,"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":3451,"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/validation.ts":{"bytes":3773,"imports":[{"path":"../type-guards/dist/esm/index.mjs","kind":"import-statement","original":"type-guards"},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"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":4412,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/param-utils.ts","kind":"import-statement","original":"./param-utils"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"},{"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/factory.ts":{"bytes":3227,"imports":[{"path":"@real-router/core","kind":"import-statement","external":true},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"},{"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":166,"imports":[{"path":"src/factory.ts","kind":"import-statement","original":"./factory"},{"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":18658},"dist/cjs/index.js":{"imports":[{"path":"@real-router/core","kind":"import-statement","external":true}],"exports":["persistentParamsPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/factory.ts":{"bytesInOutput":822},"src/constants.ts":{"bytesInOutput":104},"src/param-utils.ts":{"bytesInOutput":604},"../type-guards/dist/esm/index.mjs":{"bytesInOutput":118},"src/validation.ts":{"bytesInOutput":2130},"src/plugin.ts":{"bytesInOutput":3126},"src/index.ts":{"bytesInOutput":0}},"bytes":7437}}}
1
+ {"inputs":{"../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js":{"bytes":569,"imports":[],"format":"esm"},"src/constants.ts":{"bytes":178,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/param-utils.ts":{"bytes":1731,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"../type-guards/dist/esm/index.mjs":{"bytes":3451,"imports":[{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/validation.ts":{"bytes":3773,"imports":[{"path":"../type-guards/dist/esm/index.mjs","kind":"import-statement","original":"type-guards"},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/plugin.ts":{"bytes":4457,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/param-utils.ts","kind":"import-statement","original":"./param-utils"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/factory.ts":{"bytes":3231,"imports":[{"path":"@real-router/core/api","kind":"import-statement","external":true},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":166,"imports":[{"path":"src/factory.ts","kind":"import-statement","original":"./factory"},{"path":"/home/runner/work/real-router/real-router/node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/cjs_shims.js","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"dist/cjs/index.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":18710},"dist/cjs/index.js":{"imports":[{"path":"@real-router/core/api","kind":"import-statement","external":true}],"exports":["persistentParamsPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/factory.ts":{"bytesInOutput":826},"src/constants.ts":{"bytesInOutput":104},"src/param-utils.ts":{"bytesInOutput":604},"../type-guards/dist/esm/index.mjs":{"bytesInOutput":118},"src/validation.ts":{"bytesInOutput":2130},"src/plugin.ts":{"bytesInOutput":3126},"src/index.ts":{"bytesInOutput":0}},"bytes":7441}}}
@@ -1 +1 @@
1
- import{getPluginApi as t}from"@real-router/core";var e="[@real-router/persistent-params-plugin]";function r(t){return"number"==typeof t?Number.isFinite(t):"string"==typeof t||"boolean"==typeof t}var a=/[\s#%&/=?\\]/,s=String.raw`Cannot contain: = & ? # % / \ or whitespace`;function n(t){if(a.test(t))throw new TypeError(`${e} Invalid parameter name "${t}". ${s}`)}function o(t,a){if(null===a)throw new TypeError(`${e} Parameter "${t}" cannot be null. Use undefined to remove the parameter from persistence.`);if(void 0!==a&&!r(a)){const r=Array.isArray(a)?"array":typeof a;throw new TypeError(`${e} Parameter "${t}" must be a primitive value (string, number, or boolean), got ${r}. Objects and arrays are not supported in URL parameters.`)}}var i=class{#t;#e;#r;#a;#s;#n;constructor(t,r,a,s){let n,o;this.#t=t,this.#n=r,this.#e=a,this.#r=s;try{t.setRootPath(`${s}?${[...a].join("&")}`),n=t.addInterceptor("buildPath",(t,e,r)=>t(e,this.#o(r??{}))),o=t.addInterceptor("forwardState",(t,e,r)=>{const a=t(e,r);return{...a,params:this.#o(a.params)}})}catch(r){throw n?.(),o?.(),t.setRootPath(s),new Error(`${e} Failed to initialize: ${r instanceof Error?r.message:String(r)}`,{cause:r})}this.#a=n,this.#s=o}getPlugin(){return{onTransitionSuccess:t=>{this.#i(t)},teardown:()=>{this.#c()}}}#o(t){const e=function(t){const e={};for(const r in t)Object.hasOwn(t,r)&&(e[r]=t[r]);return e}(t);let r;for(const t of Object.keys(e)){const a=e[t];void 0===a&&this.#e.has(t)?(this.#e.delete(t),r??={...this.#n},delete r[t]):o(t,a)}return r&&(this.#n=Object.freeze(r)),function(t,e){const r={};for(const e in t)Object.hasOwn(t,e)&&void 0!==t[e]&&(r[e]=t[e]);for(const t of Object.keys(e)){const a=e[t];void 0===a?delete r[t]:r[t]=a}return r}(this.#n,e)}#i(t){let e;for(const r of this.#e){const a=t.params[r];Object.hasOwn(t.params,r)&&void 0!==a?(o(r,a),this.#n[r]!==a&&(e??={...this.#n},e[r]=a)):Object.hasOwn(this.#n,r)&&void 0!==this.#n[r]&&(e??={...this.#n},delete e[r])}e&&(this.#n=Object.freeze(e))}#c(){this.#a(),this.#s();try{this.#t.setRootPath(this.#r)}catch{}}},c={},p=()=>c;function h(a={}){!function(t){if(null==(a=t)||!(Array.isArray(a)?a.every(t=>{if("string"!=typeof t||0===t.length)return!1;try{return n(t),!0}catch{return!1}}):"object"==typeof a&&Object.getPrototypeOf(a)===Object.prototype&&Object.entries(a).every(([t,e])=>{if("string"!=typeof t||0===t.length)return!1;try{n(t)}catch{return!1}return r(e)}))){let r;throw r=null===t?"null":Array.isArray(t)?"array with invalid items":typeof t,new TypeError(`${e} Invalid params configuration. Expected array of non-empty strings or object with primitive values, got ${r}.`)}var a}(a);const s=Array.isArray(a)?a:Object.keys(a);if(0===s.length)return p;const o={};if(Array.isArray(a))for(const t of a)o[t]=void 0;else Object.assign(o,a);Object.freeze(o);const c=new Set(s);return e=>{const r=t(e);return new i(r,o,new Set(c),r.getRootPath()).getPlugin()}}export{h as persistentParamsPluginFactory};//# sourceMappingURL=index.mjs.map
1
+ import{getPluginApi as t}from"@real-router/core/api";var e="[@real-router/persistent-params-plugin]";function r(t){return"number"==typeof t?Number.isFinite(t):"string"==typeof t||"boolean"==typeof t}var a=/[\s#%&/=?\\]/,s=String.raw`Cannot contain: = & ? # % / \ or whitespace`;function n(t){if(a.test(t))throw new TypeError(`${e} Invalid parameter name "${t}". ${s}`)}function o(t,a){if(null===a)throw new TypeError(`${e} Parameter "${t}" cannot be null. Use undefined to remove the parameter from persistence.`);if(void 0!==a&&!r(a)){const r=Array.isArray(a)?"array":typeof a;throw new TypeError(`${e} Parameter "${t}" must be a primitive value (string, number, or boolean), got ${r}. Objects and arrays are not supported in URL parameters.`)}}var i=class{#t;#e;#r;#a;#s;#n;constructor(t,r,a,s){let n,o;this.#t=t,this.#n=r,this.#e=a,this.#r=s;try{t.setRootPath(`${s}?${[...a].join("&")}`),n=t.addInterceptor("buildPath",(t,e,r)=>t(e,this.#o(r??{}))),o=t.addInterceptor("forwardState",(t,e,r)=>{const a=t(e,r);return{...a,params:this.#o(a.params)}})}catch(r){throw n?.(),o?.(),t.setRootPath(s),new Error(`${e} Failed to initialize: ${r instanceof Error?r.message:String(r)}`,{cause:r})}this.#a=n,this.#s=o}getPlugin(){return{onTransitionSuccess:t=>{this.#i(t)},teardown:()=>{this.#c()}}}#o(t){const e=function(t){const e={};for(const r in t)Object.hasOwn(t,r)&&(e[r]=t[r]);return e}(t);let r;for(const t of Object.keys(e)){const a=e[t];void 0===a&&this.#e.has(t)?(this.#e.delete(t),r??={...this.#n},delete r[t]):o(t,a)}return r&&(this.#n=Object.freeze(r)),function(t,e){const r={};for(const e in t)Object.hasOwn(t,e)&&void 0!==t[e]&&(r[e]=t[e]);for(const t of Object.keys(e)){const a=e[t];void 0===a?delete r[t]:r[t]=a}return r}(this.#n,e)}#i(t){let e;for(const r of this.#e){const a=t.params[r];Object.hasOwn(t.params,r)&&void 0!==a?(o(r,a),this.#n[r]!==a&&(e??={...this.#n},e[r]=a)):Object.hasOwn(this.#n,r)&&void 0!==this.#n[r]&&(e??={...this.#n},delete e[r])}e&&(this.#n=Object.freeze(e))}#c(){this.#a(),this.#s();try{this.#t.setRootPath(this.#r)}catch{}}},c={},p=()=>c;function h(a={}){!function(t){if(null==(a=t)||!(Array.isArray(a)?a.every(t=>{if("string"!=typeof t||0===t.length)return!1;try{return n(t),!0}catch{return!1}}):"object"==typeof a&&Object.getPrototypeOf(a)===Object.prototype&&Object.entries(a).every(([t,e])=>{if("string"!=typeof t||0===t.length)return!1;try{n(t)}catch{return!1}return r(e)}))){let r;throw r=null===t?"null":Array.isArray(t)?"array with invalid items":typeof t,new TypeError(`${e} Invalid params configuration. Expected array of non-empty strings or object with primitive values, got ${r}.`)}var a}(a);const s=Array.isArray(a)?a:Object.keys(a);if(0===s.length)return p;const o={};if(Array.isArray(a))for(const t of a)o[t]=void 0;else Object.assign(o,a);Object.freeze(o);const c=new Set(s);return e=>{const r=t(e);return new i(r,o,new Set(c),r.getRootPath()).getPlugin()}}export{h as persistentParamsPluginFactory};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/constants.ts","../../src/param-utils.ts","../../src/validation.ts","../../src/plugin.ts","../../src/factory.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// packages/persistent-params-plugin/src/constants.ts\n\nexport const LOGGER_CONTEXT = \"persistent-params-plugin\";\n\nexport const ERROR_PREFIX = `[@real-router/${LOGGER_CONTEXT}]`;\n","// packages/persistent-params-plugin/src/param-utils.ts\n\nimport type { Params } from \"@real-router/core\";\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 /* v8 ignore next -- @preserve: core validates params prototype — inherited keys never reach here */\n if (Object.hasOwn(params, key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n\n/**\n * Merges persistent and current parameters into a single Params object.\n *\n * IMPORTANT: `current` must be pre-sanitized via `extractOwnParams()` by the caller.\n * This function does NOT perform prototype pollution protection on its own.\n *\n * @param persistent - Frozen persistent parameters\n * @param current - Pre-sanitized current parameters (own properties only)\n */\nexport function mergeParams(\n persistent: Readonly<Params>,\n current: Params,\n): Params {\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 for (const key of Object.keys(current)) {\n const value = current[key];\n\n if (value === undefined) {\n delete result[key];\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n","// packages/persistent-params-plugin/src/validation.ts\n\nimport { isPrimitiveValue } from \"type-guards\";\n\nimport { ERROR_PREFIX } from \"./constants\";\n\nimport type { PersistentParamsConfig } from \"./types\";\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 `${ERROR_PREFIX} 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 */\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 `${ERROR_PREFIX} 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 `${ERROR_PREFIX} 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 * Validates the params configuration and throws a descriptive error if invalid.\n *\n * @param params - Configuration to validate\n * @throws {TypeError} If params is not a valid configuration\n */\nexport function validateConfig(params: unknown): void {\n if (!isValidParamsConfig(params)) {\n let actualType: string;\n\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 `${ERROR_PREFIX} Invalid params configuration. ` +\n `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,\n );\n }\n}\n","// packages/persistent-params-plugin/src/plugin.ts\n\nimport { ERROR_PREFIX } from \"./constants\";\nimport { extractOwnParams, mergeParams } from \"./param-utils\";\nimport { validateParamValue } from \"./validation\";\n\nimport type { Params, PluginApi, State, Plugin } from \"@real-router/core\";\n\nexport class PersistentParamsPlugin {\n readonly #api: PluginApi;\n readonly #paramNamesSet: Set<string>;\n readonly #originalRootPath: string;\n readonly #removeBuildPathInterceptor: () => void;\n readonly #removeForwardStateInterceptor: () => void;\n\n #persistentParams: Readonly<Params>;\n\n constructor(\n api: PluginApi,\n persistentParams: Readonly<Params>,\n paramNamesSet: Set<string>,\n originalRootPath: string,\n ) {\n this.#api = api;\n this.#persistentParams = persistentParams;\n this.#paramNamesSet = paramNamesSet;\n this.#originalRootPath = originalRootPath;\n\n let removeBuildPath: (() => void) | undefined;\n let removeForwardState: (() => void) | undefined;\n\n try {\n api.setRootPath(`${originalRootPath}?${[...paramNamesSet].join(\"&\")}`);\n\n removeBuildPath = api.addInterceptor(\n \"buildPath\",\n (next, route, navParams) =>\n next(route, this.#withPersistentParams(navParams ?? {})),\n );\n\n removeForwardState = api.addInterceptor(\n \"forwardState\",\n (next, routeName, routeParams) => {\n const result = next(routeName, routeParams);\n\n return {\n ...result,\n params: this.#withPersistentParams(result.params),\n };\n },\n );\n } /* v8 ignore start -- @preserve: rollback on partial initialization failure */ catch (error) {\n removeBuildPath?.();\n removeForwardState?.();\n api.setRootPath(originalRootPath);\n\n throw new Error(\n `${ERROR_PREFIX} Failed to initialize: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n } /* v8 ignore stop */\n\n this.#removeBuildPathInterceptor = removeBuildPath;\n this.#removeForwardStateInterceptor = removeForwardState;\n }\n\n getPlugin(): Plugin {\n return {\n onTransitionSuccess: (toState) => {\n this.#onTransitionSuccess(toState);\n },\n teardown: () => {\n this.#teardown();\n },\n };\n }\n\n #withPersistentParams(additionalParams: Params): Params {\n const safeParams = extractOwnParams(additionalParams);\n let newParams: Params | undefined;\n\n for (const key of Object.keys(safeParams)) {\n const value = safeParams[key];\n\n if (value === undefined && this.#paramNamesSet.has(key)) {\n this.#paramNamesSet.delete(key);\n newParams ??= { ...this.#persistentParams };\n delete newParams[key];\n } else {\n validateParamValue(key, value);\n }\n }\n\n if (newParams) {\n this.#persistentParams = Object.freeze(newParams);\n }\n\n return mergeParams(this.#persistentParams, safeParams);\n }\n\n #onTransitionSuccess(toState: State): void {\n let newParams: Params | undefined;\n\n for (const key of this.#paramNamesSet) {\n const value = toState.params[key];\n\n if (!Object.hasOwn(toState.params, key) || value === undefined) {\n /* v8 ignore next 4 -- @preserve: defensive removal for states committed via navigateToState bypassing forwardState */\n if (\n Object.hasOwn(this.#persistentParams, key) &&\n this.#persistentParams[key] !== undefined\n ) {\n newParams ??= { ...this.#persistentParams };\n delete newParams[key];\n }\n\n continue;\n }\n\n validateParamValue(key, value);\n\n if (this.#persistentParams[key] !== value) {\n newParams ??= { ...this.#persistentParams };\n newParams[key] = value;\n }\n }\n\n if (newParams) {\n this.#persistentParams = Object.freeze(newParams);\n }\n }\n\n #teardown(): void {\n this.#removeBuildPathInterceptor();\n this.#removeForwardStateInterceptor();\n\n /* v8 ignore start -- @preserve: setRootPath throws RouterError(ROUTER_DISPOSED) during router.dispose() */\n try {\n this.#api.setRootPath(this.#originalRootPath);\n } catch {\n // Expected during router.dispose(): FSM enters DISPOSED before plugin teardown,\n // so setRootPath's throwIfDisposed() check throws. Restoring rootPath on a\n // destroyed router is unnecessary — swallow silently.\n }\n /* v8 ignore stop */\n }\n}\n","// packages/persistent-params-plugin/src/factory.ts\n\nimport { getPluginApi } from \"@real-router/core\";\n\nimport { PersistentParamsPlugin } from \"./plugin\";\nimport { validateConfig } from \"./validation\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params, PluginFactory, Plugin } from \"@real-router/core\";\n\n// Shared singleton — frozen by core on first use. Do not add properties.\nconst EMPTY_PLUGIN: Plugin = {};\nconst noop: PluginFactory = () => EMPTY_PLUGIN;\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 validateConfig(params);\n\n const paramNames = Array.isArray(params) ? params : Object.keys(params);\n\n if (paramNames.length === 0) {\n return noop;\n }\n\n const initialParams: Params = {};\n\n if (Array.isArray(params)) {\n for (const param of params) {\n initialParams[param] = undefined;\n }\n } else {\n Object.assign(initialParams, params);\n }\n\n Object.freeze(initialParams);\n\n const paramNamesSet = new Set<string>(paramNames);\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const plugin = new PersistentParamsPlugin(\n api,\n initialParams,\n new Set(paramNamesSet),\n api.getRootPath(),\n );\n\n return plugin.getPlugin();\n };\n}\n"]}
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/param-utils.ts","../../src/validation.ts","../../src/plugin.ts","../../src/factory.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// packages/persistent-params-plugin/src/constants.ts\n\nexport const LOGGER_CONTEXT = \"persistent-params-plugin\";\n\nexport const ERROR_PREFIX = `[@real-router/${LOGGER_CONTEXT}]`;\n","// packages/persistent-params-plugin/src/param-utils.ts\n\nimport type { Params } from \"@real-router/core\";\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 /* v8 ignore next -- @preserve: core validates params prototype — inherited keys never reach here */\n if (Object.hasOwn(params, key)) {\n result[key] = params[key];\n }\n }\n\n return result;\n}\n\n/**\n * Merges persistent and current parameters into a single Params object.\n *\n * IMPORTANT: `current` must be pre-sanitized via `extractOwnParams()` by the caller.\n * This function does NOT perform prototype pollution protection on its own.\n *\n * @param persistent - Frozen persistent parameters\n * @param current - Pre-sanitized current parameters (own properties only)\n */\nexport function mergeParams(\n persistent: Readonly<Params>,\n current: Params,\n): Params {\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 for (const key of Object.keys(current)) {\n const value = current[key];\n\n if (value === undefined) {\n delete result[key];\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n","// packages/persistent-params-plugin/src/validation.ts\n\nimport { isPrimitiveValue } from \"type-guards\";\n\nimport { ERROR_PREFIX } from \"./constants\";\n\nimport type { PersistentParamsConfig } from \"./types\";\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 `${ERROR_PREFIX} 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 */\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 `${ERROR_PREFIX} 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 `${ERROR_PREFIX} 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 * Validates the params configuration and throws a descriptive error if invalid.\n *\n * @param params - Configuration to validate\n * @throws {TypeError} If params is not a valid configuration\n */\nexport function validateConfig(params: unknown): void {\n if (!isValidParamsConfig(params)) {\n let actualType: string;\n\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 `${ERROR_PREFIX} Invalid params configuration. ` +\n `Expected array of non-empty strings or object with primitive values, got ${actualType}.`,\n );\n }\n}\n","// packages/persistent-params-plugin/src/plugin.ts\n\nimport { ERROR_PREFIX } from \"./constants\";\nimport { extractOwnParams, mergeParams } from \"./param-utils\";\nimport { validateParamValue } from \"./validation\";\n\nimport type { Params, State, Plugin } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nexport class PersistentParamsPlugin {\n readonly #api: PluginApi;\n readonly #paramNamesSet: Set<string>;\n readonly #originalRootPath: string;\n readonly #removeBuildPathInterceptor: () => void;\n readonly #removeForwardStateInterceptor: () => void;\n\n #persistentParams: Readonly<Params>;\n\n constructor(\n api: PluginApi,\n persistentParams: Readonly<Params>,\n paramNamesSet: Set<string>,\n originalRootPath: string,\n ) {\n this.#api = api;\n this.#persistentParams = persistentParams;\n this.#paramNamesSet = paramNamesSet;\n this.#originalRootPath = originalRootPath;\n\n let removeBuildPath: (() => void) | undefined;\n let removeForwardState: (() => void) | undefined;\n\n try {\n api.setRootPath(`${originalRootPath}?${[...paramNamesSet].join(\"&\")}`);\n\n removeBuildPath = api.addInterceptor(\n \"buildPath\",\n (next, route, navParams) =>\n next(route, this.#withPersistentParams(navParams ?? {})),\n );\n\n removeForwardState = api.addInterceptor(\n \"forwardState\",\n (next, routeName, routeParams) => {\n const result = next(routeName, routeParams);\n\n return {\n ...result,\n params: this.#withPersistentParams(result.params),\n };\n },\n );\n } /* v8 ignore start -- @preserve: rollback on partial initialization failure */ catch (error) {\n removeBuildPath?.();\n removeForwardState?.();\n api.setRootPath(originalRootPath);\n\n throw new Error(\n `${ERROR_PREFIX} Failed to initialize: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n } /* v8 ignore stop */\n\n this.#removeBuildPathInterceptor = removeBuildPath;\n this.#removeForwardStateInterceptor = removeForwardState;\n }\n\n getPlugin(): Plugin {\n return {\n onTransitionSuccess: (toState) => {\n this.#onTransitionSuccess(toState);\n },\n teardown: () => {\n this.#teardown();\n },\n };\n }\n\n #withPersistentParams(additionalParams: Params): Params {\n const safeParams = extractOwnParams(additionalParams);\n let newParams: Params | undefined;\n\n for (const key of Object.keys(safeParams)) {\n const value = safeParams[key];\n\n if (value === undefined && this.#paramNamesSet.has(key)) {\n this.#paramNamesSet.delete(key);\n newParams ??= { ...this.#persistentParams };\n delete newParams[key];\n } else {\n validateParamValue(key, value);\n }\n }\n\n if (newParams) {\n this.#persistentParams = Object.freeze(newParams);\n }\n\n return mergeParams(this.#persistentParams, safeParams);\n }\n\n #onTransitionSuccess(toState: State): void {\n let newParams: Params | undefined;\n\n for (const key of this.#paramNamesSet) {\n const value = toState.params[key];\n\n if (!Object.hasOwn(toState.params, key) || value === undefined) {\n /* v8 ignore next 4 -- @preserve: defensive removal for states committed via navigateToState bypassing forwardState */\n if (\n Object.hasOwn(this.#persistentParams, key) &&\n this.#persistentParams[key] !== undefined\n ) {\n newParams ??= { ...this.#persistentParams };\n delete newParams[key];\n }\n\n continue;\n }\n\n validateParamValue(key, value);\n\n if (this.#persistentParams[key] !== value) {\n newParams ??= { ...this.#persistentParams };\n newParams[key] = value;\n }\n }\n\n if (newParams) {\n this.#persistentParams = Object.freeze(newParams);\n }\n }\n\n #teardown(): void {\n this.#removeBuildPathInterceptor();\n this.#removeForwardStateInterceptor();\n\n /* v8 ignore start -- @preserve: setRootPath throws RouterError(ROUTER_DISPOSED) during router.dispose() */\n try {\n this.#api.setRootPath(this.#originalRootPath);\n } catch {\n // Expected during router.dispose(): FSM enters DISPOSED before plugin teardown,\n // so setRootPath's throwIfDisposed() check throws. Restoring rootPath on a\n // destroyed router is unnecessary — swallow silently.\n }\n /* v8 ignore stop */\n }\n}\n","// packages/persistent-params-plugin/src/factory.ts\n\nimport { getPluginApi } from \"@real-router/core/api\";\n\nimport { PersistentParamsPlugin } from \"./plugin\";\nimport { validateConfig } from \"./validation\";\n\nimport type { PersistentParamsConfig } from \"./types\";\nimport type { Params, PluginFactory, Plugin } from \"@real-router/core\";\n\n// Shared singleton — frozen by core on first use. Do not add properties.\nconst EMPTY_PLUGIN: Plugin = {};\nconst noop: PluginFactory = () => EMPTY_PLUGIN;\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 validateConfig(params);\n\n const paramNames = Array.isArray(params) ? params : Object.keys(params);\n\n if (paramNames.length === 0) {\n return noop;\n }\n\n const initialParams: Params = {};\n\n if (Array.isArray(params)) {\n for (const param of params) {\n initialParams[param] = undefined;\n }\n } else {\n Object.assign(initialParams, params);\n }\n\n Object.freeze(initialParams);\n\n const paramNamesSet = new Set<string>(paramNames);\n\n return (router): Plugin => {\n const api = getPluginApi(router);\n const plugin = new PersistentParamsPlugin(\n api,\n initialParams,\n new Set(paramNamesSet),\n api.getRootPath(),\n );\n\n return plugin.getPlugin();\n };\n}\n"]}
@@ -1 +1 @@
1
- {"inputs":{"src/constants.ts":{"bytes":178,"imports":[],"format":"esm"},"src/param-utils.ts":{"bytes":1731,"imports":[],"format":"esm"},"../type-guards/dist/esm/index.mjs":{"bytes":3451,"imports":[],"format":"esm"},"src/validation.ts":{"bytes":3773,"imports":[{"path":"../type-guards/dist/esm/index.mjs","kind":"import-statement","original":"type-guards"},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"}],"format":"esm"},"src/plugin.ts":{"bytes":4412,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/param-utils.ts","kind":"import-statement","original":"./param-utils"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"}],"format":"esm"},"src/factory.ts":{"bytes":3227,"imports":[{"path":"@real-router/core","kind":"import-statement","external":true},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"}],"format":"esm"},"src/index.ts":{"bytes":166,"imports":[{"path":"src/factory.ts","kind":"import-statement","original":"./factory"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":18658},"dist/esm/index.mjs":{"imports":[{"path":"@real-router/core","kind":"import-statement","external":true}],"exports":["persistentParamsPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/factory.ts":{"bytesInOutput":822},"src/constants.ts":{"bytesInOutput":104},"src/param-utils.ts":{"bytesInOutput":604},"../type-guards/dist/esm/index.mjs":{"bytesInOutput":118},"src/validation.ts":{"bytesInOutput":2130},"src/plugin.ts":{"bytesInOutput":3126},"src/index.ts":{"bytesInOutput":0}},"bytes":7437}}}
1
+ {"inputs":{"src/constants.ts":{"bytes":178,"imports":[],"format":"esm"},"src/param-utils.ts":{"bytes":1731,"imports":[],"format":"esm"},"../type-guards/dist/esm/index.mjs":{"bytes":3451,"imports":[],"format":"esm"},"src/validation.ts":{"bytes":3773,"imports":[{"path":"../type-guards/dist/esm/index.mjs","kind":"import-statement","original":"type-guards"},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"}],"format":"esm"},"src/plugin.ts":{"bytes":4457,"imports":[{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/param-utils.ts","kind":"import-statement","original":"./param-utils"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"}],"format":"esm"},"src/factory.ts":{"bytes":3231,"imports":[{"path":"@real-router/core/api","kind":"import-statement","external":true},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"}],"format":"esm"},"src/index.ts":{"bytes":166,"imports":[{"path":"src/factory.ts","kind":"import-statement","original":"./factory"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":18710},"dist/esm/index.mjs":{"imports":[{"path":"@real-router/core/api","kind":"import-statement","external":true}],"exports":["persistentParamsPluginFactory"],"entryPoint":"src/index.ts","inputs":{"src/factory.ts":{"bytesInOutput":826},"src/constants.ts":{"bytesInOutput":104},"src/param-utils.ts":{"bytesInOutput":604},"../type-guards/dist/esm/index.mjs":{"bytesInOutput":118},"src/validation.ts":{"bytesInOutput":2130},"src/plugin.ts":{"bytesInOutput":3126},"src/index.ts":{"bytesInOutput":0}},"bytes":7441}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/persistent-params-plugin",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "type": "commonjs",
5
5
  "description": "Persist query parameters across route transitions",
6
6
  "main": "./dist/cjs/index.js",
@@ -45,13 +45,14 @@
45
45
  "homepage": "https://github.com/greydragon888/real-router",
46
46
  "sideEffects": false,
47
47
  "dependencies": {
48
- "@real-router/core": "^0.35.1"
48
+ "@real-router/core": "^0.37.0"
49
49
  },
50
50
  "devDependencies": {
51
- "type-guards": "^0.3.5"
51
+ "type-guards": "^0.3.7"
52
52
  },
53
53
  "scripts": {
54
54
  "test": "vitest",
55
+ "test:properties": "vitest run --config vitest.config.properties.mts",
55
56
  "build": "tsup",
56
57
  "type-check": "tsc --noEmit",
57
58
  "lint": "eslint --cache --ext .ts src/ tests/ --fix --max-warnings 0",
package/src/factory.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // packages/persistent-params-plugin/src/factory.ts
2
2
 
3
- import { getPluginApi } from "@real-router/core";
3
+ import { getPluginApi } from "@real-router/core/api";
4
4
 
5
5
  import { PersistentParamsPlugin } from "./plugin";
6
6
  import { validateConfig } from "./validation";
package/src/plugin.ts CHANGED
@@ -4,7 +4,8 @@ import { ERROR_PREFIX } from "./constants";
4
4
  import { extractOwnParams, mergeParams } from "./param-utils";
5
5
  import { validateParamValue } from "./validation";
6
6
 
7
- import type { Params, PluginApi, State, Plugin } from "@real-router/core";
7
+ import type { Params, State, Plugin } from "@real-router/core";
8
+ import type { PluginApi } from "@real-router/core/api";
8
9
 
9
10
  export class PersistentParamsPlugin {
10
11
  readonly #api: PluginApi;