@real-router/persistent-params-plugin 0.1.35 → 0.1.37
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/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/metafile-cjs.json +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/metafile-esm.json +1 -1
- package/package.json +3 -3
- package/src/factory.ts +106 -0
- package/src/index.ts +1 -1
- package/src/param-utils.ts +82 -0
- package/src/plugin.ts +104 -266
- package/src/validation.ts +128 -0
- package/src/constants.ts +0 -7
- package/src/utils.ts +0 -250
package/src/plugin.ts
CHANGED
|
@@ -1,299 +1,137 @@
|
|
|
1
|
-
// packages/persistent-params-plugin/
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* - Support for default values
|
|
28
|
-
* - Type-safe (only primitives: string, number, boolean)
|
|
29
|
-
* - Immutable internal state
|
|
30
|
-
* - Protection against prototype pollution
|
|
31
|
-
* - Full teardown support (can be safely unsubscribed)
|
|
32
|
-
*
|
|
33
|
-
* If a persisted parameter is explicitly set to `undefined` during navigation,
|
|
34
|
-
* it will be removed from the persisted state and omitted from subsequent URLs.
|
|
35
|
-
*
|
|
36
|
-
* The plugin also adjusts the router's root path to include query parameters for
|
|
37
|
-
* all persistent params, ensuring correct URL construction.
|
|
38
|
-
*
|
|
39
|
-
* @param params - Either an array of parameter names (strings) to persist,
|
|
40
|
-
* or an object mapping parameter names to initial values.
|
|
41
|
-
* If an array, initial values will be `undefined`.
|
|
42
|
-
*
|
|
43
|
-
* @returns A PluginFactory that creates the persistent params plugin instance.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* // Persist parameters without default values
|
|
47
|
-
* router.usePlugin(persistentParamsPlugin(['mode', 'lang']));
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* // Persist parameters with default values
|
|
51
|
-
* router.usePlugin(persistentParamsPlugin({ mode: 'dev', lang: 'en' }));
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* // Removing a persisted parameter
|
|
55
|
-
* router.navigate('route', { mode: undefined }); // mode will be removed
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* // Unsubscribing (full cleanup)
|
|
59
|
-
* const unsubscribe = router.usePlugin(persistentParamsPlugin(['mode']));
|
|
60
|
-
* unsubscribe(); // Restores original router state
|
|
61
|
-
*
|
|
62
|
-
* @throws {TypeError} If params is not a valid array of strings or object with primitives
|
|
63
|
-
* @throws {Error} If plugin is already initialized on this router instance
|
|
64
|
-
*/
|
|
65
|
-
export function persistentParamsPluginFactory(
|
|
66
|
-
params: PersistentParamsConfig = {},
|
|
67
|
-
): PluginFactory {
|
|
68
|
-
// Validate input configuration
|
|
69
|
-
if (!isValidParamsConfig(params)) {
|
|
70
|
-
let actualType: string;
|
|
71
|
-
|
|
72
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
73
|
-
if (params === null) {
|
|
74
|
-
actualType = "null";
|
|
75
|
-
} else if (Array.isArray(params)) {
|
|
76
|
-
actualType = "array with invalid items";
|
|
77
|
-
} else {
|
|
78
|
-
actualType = typeof params;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
throw new TypeError(
|
|
82
|
-
`[@real-router/persistent-params-plugin] Invalid params configuration. ` +
|
|
83
|
-
`Expected array of non-empty strings or object with primitive values, got ${actualType}.`,
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Empty configuration - valid but does nothing
|
|
88
|
-
if (Array.isArray(params) && params.length === 0) {
|
|
89
|
-
return () => ({});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (!Array.isArray(params) && Object.keys(params).length === 0) {
|
|
93
|
-
return () => ({});
|
|
1
|
+
// packages/persistent-params-plugin/src/plugin.ts
|
|
2
|
+
|
|
3
|
+
import { extractOwnParams, mergeParams } from "./param-utils";
|
|
4
|
+
import { validateParamValue } from "./validation";
|
|
5
|
+
|
|
6
|
+
import type { Params, PluginApi, State, Plugin } from "@real-router/core";
|
|
7
|
+
|
|
8
|
+
export class PersistentParamsPlugin {
|
|
9
|
+
readonly #api: PluginApi;
|
|
10
|
+
readonly #paramNamesSet: Set<string>;
|
|
11
|
+
readonly #originalRootPath: string;
|
|
12
|
+
|
|
13
|
+
#persistentParams: Readonly<Params>;
|
|
14
|
+
#removeBuildPathInterceptor: (() => void) | undefined;
|
|
15
|
+
#removeForwardStateInterceptor: (() => void) | undefined;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
api: PluginApi,
|
|
19
|
+
persistentParams: Readonly<Params>,
|
|
20
|
+
paramNamesSet: Set<string>,
|
|
21
|
+
originalRootPath: string,
|
|
22
|
+
) {
|
|
23
|
+
this.#api = api;
|
|
24
|
+
this.#persistentParams = persistentParams;
|
|
25
|
+
this.#paramNamesSet = paramNamesSet;
|
|
26
|
+
this.#originalRootPath = originalRootPath;
|
|
94
27
|
}
|
|
95
28
|
|
|
96
|
-
|
|
97
|
-
// Check if plugin is already initialized on this router
|
|
98
|
-
if (PLUGIN_MARKER in router) {
|
|
99
|
-
throw new Error(
|
|
100
|
-
`[@real-router/persistent-params-plugin] Plugin already initialized on this router. ` +
|
|
101
|
-
`To reconfigure, first unsubscribe the existing plugin using the returned unsubscribe function.`,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Mark router as initialized
|
|
106
|
-
(router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER] = true;
|
|
107
|
-
|
|
108
|
-
// Initialize frozen persistent parameters
|
|
109
|
-
let persistentParams: Readonly<Params>;
|
|
110
|
-
|
|
111
|
-
if (Array.isArray(params)) {
|
|
112
|
-
const initial: Params = {};
|
|
113
|
-
|
|
114
|
-
for (const param of params) {
|
|
115
|
-
initial[param] = undefined;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
persistentParams = Object.freeze(initial);
|
|
119
|
-
} else {
|
|
120
|
-
persistentParams = Object.freeze({ ...params });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Track parameter names
|
|
124
|
-
const paramNamesSet = new Set<string>(
|
|
125
|
-
Array.isArray(params) ? [...params] : Object.keys(params),
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const api = getPluginApi(router);
|
|
129
|
-
|
|
130
|
-
// Store original router methods for restoration
|
|
131
|
-
const originalForwardState = api.getForwardState();
|
|
132
|
-
const originalRootPath = api.getRootPath();
|
|
133
|
-
|
|
134
|
-
// Update router root path to include query parameters for persistent params
|
|
29
|
+
getPlugin(): Plugin {
|
|
135
30
|
try {
|
|
136
|
-
const
|
|
137
|
-
const newQueryString = buildQueryString(queryString, [...paramNamesSet]);
|
|
31
|
+
const queryString = [...this.#paramNamesSet].join("&");
|
|
138
32
|
|
|
139
|
-
api.setRootPath(`${
|
|
33
|
+
this.#api.setRootPath(`${this.#originalRootPath}?${queryString}`);
|
|
140
34
|
} /* v8 ignore start -- @preserve: defensive error wrapping for setRootPath failure */ catch (error) {
|
|
141
|
-
delete (router as unknown as Record<symbol, boolean>)[PLUGIN_MARKER];
|
|
142
|
-
|
|
143
35
|
throw new Error(
|
|
144
36
|
`[@real-router/persistent-params-plugin] Failed to update root path: ${error instanceof Error ? error.message : String(error)}`,
|
|
145
37
|
{ cause: error },
|
|
146
38
|
);
|
|
147
39
|
} /* v8 ignore stop */
|
|
148
40
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
* @param additionalParams - Parameters passed during navigation
|
|
154
|
-
* @returns Merged parameters object
|
|
155
|
-
* @throws {TypeError} If any parameter value is invalid (not a primitive)
|
|
156
|
-
*/
|
|
157
|
-
|
|
158
|
-
function withPersistentParams(additionalParams: Params): Params {
|
|
159
|
-
// Extract safe params (prevent prototype pollution)
|
|
160
|
-
const safeParams = extractOwnParams(additionalParams);
|
|
161
|
-
|
|
162
|
-
// Validate and collect parameters to remove in a single pass
|
|
163
|
-
const paramsToRemove: string[] = [];
|
|
164
|
-
|
|
165
|
-
for (const key of Object.keys(safeParams)) {
|
|
166
|
-
const value = safeParams[key];
|
|
167
|
-
|
|
168
|
-
// If undefined and tracked, mark for removal (skip validation)
|
|
169
|
-
if (value === undefined && paramNamesSet.has(key)) {
|
|
170
|
-
paramsToRemove.push(key);
|
|
171
|
-
} else {
|
|
172
|
-
// Validate all other parameters
|
|
173
|
-
validateParamValue(key, value);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Process all removals in one batch
|
|
178
|
-
if (paramsToRemove.length > 0) {
|
|
179
|
-
// Remove from both Set
|
|
180
|
-
for (const key of paramsToRemove) {
|
|
181
|
-
paramNamesSet.delete(key);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Update persistentParams once (batch freeze)
|
|
185
|
-
const newParams: Params = { ...persistentParams };
|
|
186
|
-
|
|
187
|
-
for (const key of paramsToRemove) {
|
|
188
|
-
delete newParams[key];
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
persistentParams = Object.freeze(newParams);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Merge persistent and current params
|
|
195
|
-
return mergeParams(persistentParams, safeParams);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Intercept buildPath to inject persistent params into path construction
|
|
199
|
-
const removeBuildPathInterceptor = api.addBuildPathInterceptor(
|
|
200
|
-
(_routeName, buildPathParams) => withPersistentParams(buildPathParams),
|
|
41
|
+
this.#removeBuildPathInterceptor = this.#api.addInterceptor(
|
|
42
|
+
"buildPath",
|
|
43
|
+
(next, route, navParams) =>
|
|
44
|
+
next(route, this.#withPersistentParams(navParams ?? {})),
|
|
201
45
|
);
|
|
202
46
|
|
|
203
|
-
api.
|
|
204
|
-
|
|
205
|
-
|
|
47
|
+
this.#removeForwardStateInterceptor = this.#api.addInterceptor(
|
|
48
|
+
"forwardState",
|
|
49
|
+
(next, routeName, routeParams) => {
|
|
50
|
+
const result = next(routeName, routeParams);
|
|
206
51
|
|
|
207
52
|
return {
|
|
208
53
|
...result,
|
|
209
|
-
params: withPersistentParams(result.params)
|
|
54
|
+
params: this.#withPersistentParams(result.params),
|
|
210
55
|
};
|
|
211
56
|
},
|
|
212
57
|
);
|
|
213
58
|
|
|
214
59
|
return {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
// Collect changed parameters and removals
|
|
224
|
-
const updates: Params = {};
|
|
225
|
-
const removals: string[] = [];
|
|
226
|
-
let hasChanges = false;
|
|
60
|
+
onTransitionSuccess: (toState) => {
|
|
61
|
+
this.#onTransitionSuccess(toState);
|
|
62
|
+
},
|
|
63
|
+
teardown: () => {
|
|
64
|
+
this.#teardown();
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
227
68
|
|
|
228
|
-
|
|
229
|
-
|
|
69
|
+
#withPersistentParams(additionalParams: Params): Params {
|
|
70
|
+
const safeParams = extractOwnParams(additionalParams);
|
|
71
|
+
let newParams: Params | undefined;
|
|
230
72
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
/* v8 ignore next 6 -- @preserve: defensive removal for states committed via navigateToState bypassing forwardState */
|
|
234
|
-
if (
|
|
235
|
-
Object.hasOwn(persistentParams, key) &&
|
|
236
|
-
persistentParams[key] !== undefined
|
|
237
|
-
) {
|
|
238
|
-
removals.push(key);
|
|
239
|
-
hasChanges = true;
|
|
240
|
-
}
|
|
73
|
+
for (const key of Object.keys(safeParams)) {
|
|
74
|
+
const value = safeParams[key];
|
|
241
75
|
|
|
242
|
-
|
|
243
|
-
|
|
76
|
+
if (value === undefined && this.#paramNamesSet.has(key)) {
|
|
77
|
+
this.#paramNamesSet.delete(key);
|
|
78
|
+
newParams ??= { ...this.#persistentParams };
|
|
79
|
+
delete newParams[key];
|
|
80
|
+
} else {
|
|
81
|
+
validateParamValue(key, value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
244
84
|
|
|
245
|
-
|
|
246
|
-
|
|
85
|
+
if (newParams) {
|
|
86
|
+
this.#persistentParams = Object.freeze(newParams);
|
|
87
|
+
}
|
|
247
88
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
updates[key] = value;
|
|
251
|
-
hasChanges = true;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
89
|
+
return mergeParams(this.#persistentParams, safeParams);
|
|
90
|
+
}
|
|
254
91
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const newParams: Params = { ...persistentParams, ...updates };
|
|
92
|
+
#onTransitionSuccess(toState: State): void {
|
|
93
|
+
let newParams: Params | undefined;
|
|
258
94
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
delete newParams[key];
|
|
262
|
-
}
|
|
95
|
+
for (const key of this.#paramNamesSet) {
|
|
96
|
+
const value = toState.params[key];
|
|
263
97
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
error,
|
|
273
|
-
);
|
|
98
|
+
if (!Object.hasOwn(toState.params, key) || value === undefined) {
|
|
99
|
+
/* v8 ignore next 4 -- @preserve: defensive removal for states committed via navigateToState bypassing forwardState */
|
|
100
|
+
if (
|
|
101
|
+
Object.hasOwn(this.#persistentParams, key) &&
|
|
102
|
+
this.#persistentParams[key] !== undefined
|
|
103
|
+
) {
|
|
104
|
+
newParams ??= { ...this.#persistentParams };
|
|
105
|
+
delete newParams[key];
|
|
274
106
|
}
|
|
275
|
-
},
|
|
276
107
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
* Restores all overridden methods and paths.
|
|
280
|
-
* Called when plugin is unsubscribed.
|
|
281
|
-
*/
|
|
282
|
-
teardown() {
|
|
283
|
-
try {
|
|
284
|
-
removeBuildPathInterceptor();
|
|
285
|
-
api.setForwardState(originalForwardState);
|
|
286
|
-
api.setRootPath(originalRootPath);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
287
110
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
111
|
+
validateParamValue(key, value);
|
|
112
|
+
|
|
113
|
+
if (this.#persistentParams[key] !== value) {
|
|
114
|
+
newParams ??= { ...this.#persistentParams };
|
|
115
|
+
newParams[key] = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (newParams) {
|
|
120
|
+
this.#persistentParams = Object.freeze(newParams);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#teardown(): void {
|
|
125
|
+
try {
|
|
126
|
+
this.#removeBuildPathInterceptor?.();
|
|
127
|
+
this.#removeForwardStateInterceptor?.();
|
|
128
|
+
this.#api.setRootPath(this.#originalRootPath);
|
|
129
|
+
} /* v8 ignore start -- @preserve: defensive error logging for teardown failure */ catch (error) {
|
|
130
|
+
console.error(
|
|
131
|
+
"persistent-params-plugin",
|
|
132
|
+
"Error during teardown:",
|
|
133
|
+
error,
|
|
134
|
+
);
|
|
135
|
+
} /* v8 ignore stop */
|
|
136
|
+
}
|
|
299
137
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// packages/persistent-params-plugin/src/validation.ts
|
|
2
|
+
|
|
3
|
+
import { isPrimitiveValue } from "type-guards";
|
|
4
|
+
|
|
5
|
+
import type { PersistentParamsConfig } from "./types";
|
|
6
|
+
|
|
7
|
+
const INVALID_PARAM_KEY_REGEX = /[\s#%&/=?\\]/;
|
|
8
|
+
const INVALID_CHARS_MESSAGE = String.raw`Cannot contain: = & ? # % / \ or whitespace`;
|
|
9
|
+
|
|
10
|
+
export function validateParamKey(key: string): void {
|
|
11
|
+
if (INVALID_PARAM_KEY_REGEX.test(key)) {
|
|
12
|
+
throw new TypeError(
|
|
13
|
+
`[@real-router/persistent-params-plugin] Invalid parameter name "${key}". ${INVALID_CHARS_MESSAGE}`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validates params configuration structure and values.
|
|
20
|
+
* Ensures all parameter names are non-empty strings and all default values are primitives.
|
|
21
|
+
*
|
|
22
|
+
* @param config - Configuration to validate
|
|
23
|
+
* @returns true if configuration is valid
|
|
24
|
+
*/
|
|
25
|
+
export function isValidParamsConfig(
|
|
26
|
+
config: unknown,
|
|
27
|
+
): config is PersistentParamsConfig {
|
|
28
|
+
if (config === null || config === undefined) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Array configuration: all items must be non-empty strings
|
|
33
|
+
if (Array.isArray(config)) {
|
|
34
|
+
return config.every((item) => {
|
|
35
|
+
if (typeof item !== "string" || item.length === 0) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
validateParamKey(item);
|
|
41
|
+
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Object configuration: must be plain object with primitive values
|
|
50
|
+
if (typeof config === "object") {
|
|
51
|
+
// Reject non-plain objects (Date, Map, etc.)
|
|
52
|
+
if (Object.getPrototypeOf(config) !== Object.prototype) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// All keys must be non-empty strings, all values must be primitives
|
|
57
|
+
return Object.entries(config).every(([key, value]) => {
|
|
58
|
+
// Check key is non-empty string
|
|
59
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate key doesn't contain special characters
|
|
64
|
+
try {
|
|
65
|
+
validateParamKey(key);
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate value is primitive (NaN/Infinity already rejected by isPrimitiveValue)
|
|
71
|
+
return isPrimitiveValue(value);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validates parameter value before persisting.
|
|
80
|
+
* Throws descriptive TypeError if value is not valid for URL parameters.
|
|
81
|
+
*
|
|
82
|
+
* @param key - Parameter name for error messages
|
|
83
|
+
* @param value - Value to validate
|
|
84
|
+
* @throws {TypeError} If value is null, array, object, or other non-primitive type
|
|
85
|
+
*/
|
|
86
|
+
export function validateParamValue(key: string, value: unknown): void {
|
|
87
|
+
if (value === null) {
|
|
88
|
+
throw new TypeError(
|
|
89
|
+
`[@real-router/persistent-params-plugin] Parameter "${key}" cannot be null. ` +
|
|
90
|
+
`Use undefined to remove the parameter from persistence.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (value !== undefined && !isPrimitiveValue(value)) {
|
|
95
|
+
const actualType = Array.isArray(value) ? "array" : typeof value;
|
|
96
|
+
|
|
97
|
+
throw new TypeError(
|
|
98
|
+
`[@real-router/persistent-params-plugin] Parameter "${key}" must be a primitive value ` +
|
|
99
|
+
`(string, number, or boolean), got ${actualType}. ` +
|
|
100
|
+
`Objects and arrays are not supported in URL parameters.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates the params configuration and throws a descriptive error if invalid.
|
|
107
|
+
*
|
|
108
|
+
* @param params - Configuration to validate
|
|
109
|
+
* @throws {TypeError} If params is not a valid configuration
|
|
110
|
+
*/
|
|
111
|
+
export function validateConfig(params: unknown): void {
|
|
112
|
+
if (!isValidParamsConfig(params)) {
|
|
113
|
+
let actualType: string;
|
|
114
|
+
|
|
115
|
+
if (params === null) {
|
|
116
|
+
actualType = "null";
|
|
117
|
+
} else if (Array.isArray(params)) {
|
|
118
|
+
actualType = "array with invalid items";
|
|
119
|
+
} else {
|
|
120
|
+
actualType = typeof params;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw new TypeError(
|
|
124
|
+
`[@real-router/persistent-params-plugin] Invalid params configuration. ` +
|
|
125
|
+
`Expected array of non-empty strings or object with primitive values, got ${actualType}.`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/constants.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
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");
|