@real-router/validation-plugin 0.7.4 → 0.7.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/validation-plugin",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "type": "commonjs",
5
5
  "description": "Route validation plugin with type guards and parameter validation",
6
6
  "main": "./dist/cjs/index.js",
@@ -18,8 +18,7 @@
18
18
  }
19
19
  },
20
20
  "files": [
21
- "dist",
22
- "src"
21
+ "dist"
23
22
  ],
24
23
  "repository": {
25
24
  "type": "git",
@@ -44,15 +43,15 @@
44
43
  },
45
44
  "sideEffects": false,
46
45
  "dependencies": {
47
- "@real-router/core": "^0.55.0",
46
+ "@real-router/core": "^0.57.0",
48
47
  "@real-router/logger": "^0.3.0"
49
48
  },
50
49
  "peerDependencies": {
51
- "@real-router/core": "^0.55.0"
50
+ "@real-router/core": "^0.57.0"
52
51
  },
53
52
  "devDependencies": {
54
53
  "route-tree": "^0.3.4",
55
- "type-guards": "^0.4.9"
54
+ "type-guards": "^0.4.10"
56
55
  },
57
56
  "scripts": {
58
57
  "test": "vitest",
package/src/helpers.ts DELETED
@@ -1,11 +0,0 @@
1
- // packages/validation-plugin/src/helpers.ts
2
-
3
- export function computeThresholds(limit: number): {
4
- warn: number;
5
- error: number;
6
- } {
7
- return {
8
- warn: Math.floor(limit * 0.2),
9
- error: Math.floor(limit * 0.5),
10
- };
11
- }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- // packages/validation-plugin/src/index.ts
2
-
3
- export { validationPlugin } from "./validationPlugin";
4
-
5
- export type { RouterValidator } from "@real-router/core/validation";
@@ -1,338 +0,0 @@
1
- // packages/validation-plugin/src/validationPlugin.ts
2
-
3
- import { RouterError } from "@real-router/core";
4
- import { getInternals } from "@real-router/core/validation";
5
- import {
6
- validateRouteName,
7
- isState,
8
- isBoolean,
9
- getTypeDescription,
10
- } from "type-guards";
11
-
12
- import {
13
- validateDependencyName,
14
- validateSetDependencyArgs as validateSetDependencyArgsRaw,
15
- validateDependenciesObject,
16
- validateDependencyExists as validateDependencyExistsRaw,
17
- validateDependencyCount,
18
- validateCloneArgs,
19
- warnOverwrite as warnDepsOverwrite,
20
- warnBatchOverwrite,
21
- warnRemoveNonExistent,
22
- } from "./validators/dependencies";
23
- import { validateEventName, validateListenerArgs } from "./validators/eventBus";
24
- import {
25
- validateHandler,
26
- validateNotRegistering,
27
- validateHandlerLimit,
28
- validateLifecycleCountThresholds,
29
- warnOverwrite as warnLifecycleOverwrite,
30
- warnAsyncGuardSync,
31
- } from "./validators/lifecycle";
32
- import {
33
- validateNavigateArgs,
34
- validateNavigateToDefaultArgs,
35
- validateNavigateToStateArgs,
36
- validateNavigationOptions,
37
- validateNavigateParams,
38
- validateStartArgs,
39
- } from "./validators/navigation";
40
- import {
41
- validateLimitValue,
42
- validateLimits,
43
- validateOptions,
44
- } from "./validators/options";
45
- import {
46
- validatePluginLimit,
47
- validatePluginKeys,
48
- validateCountThresholds as validatePluginCountThresholds,
49
- warnBatchDuplicates,
50
- warnPluginMethodType,
51
- warnPluginAfterStart,
52
- validateAddInterceptorArgs,
53
- } from "./validators/plugins";
54
- import {
55
- validateExistingRoutes,
56
- validateForwardToConsistency,
57
- validateRoutePropertiesStore,
58
- validateForwardToTargetsStore,
59
- validateDependenciesStructure,
60
- validateLimitsConsistency,
61
- validateResolvedDefaultRoute,
62
- } from "./validators/retrospective";
63
- import {
64
- validateBuildPathArgs,
65
- validateMatchPathArgs,
66
- validateIsActiveRouteArgs,
67
- validateShouldUpdateNodeArgs,
68
- validateStateBuilderArgs,
69
- validateAddRouteArgs,
70
- validateRoutes,
71
- validateRemoveRouteArgs,
72
- validateUpdateRouteBasicArgs,
73
- validateUpdateRoutePropertyTypes,
74
- validateUpdateRoute,
75
- validateParentOption as validateParentOptionRaw,
76
- throwIfInternalRoute,
77
- throwIfInternalRouteInArray,
78
- validateSetRootPathArgs,
79
- guardRouteCallbacks,
80
- guardNoAsyncCallbacks,
81
- } from "./validators/routes";
82
- import { validateMakeStateArgs } from "./validators/state";
83
-
84
- import type { PluginFactory, RouterValidator } from "@real-router/core";
85
- import type { RouterInternals } from "@real-router/core/validation";
86
-
87
- function buildValidatorObject(ctx: RouterInternals): RouterValidator {
88
- return {
89
- routes: {
90
- validateBuildPathArgs,
91
- validateMatchPathArgs,
92
- validateIsActiveRouteArgs,
93
- validateShouldUpdateNodeArgs,
94
- validateStateBuilderArgs,
95
-
96
- validateAddRouteArgs(routes) {
97
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
98
- validateAddRouteArgs(routes as any);
99
- },
100
-
101
- validateRoutes(routes, store) {
102
- const typedStore = store as {
103
- tree?: unknown;
104
- config?: { forwardMap?: Record<string, string> };
105
- };
106
-
107
- validateRoutes(
108
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
109
- routes as any,
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
111
- typedStore.tree as any,
112
- typedStore.config?.forwardMap,
113
- );
114
- },
115
- validateRemoveRouteArgs,
116
- validateUpdateRouteBasicArgs,
117
- validateUpdateRoutePropertyTypes(_name, updates) {
118
- const upd = updates as Record<string, unknown>;
119
-
120
- validateUpdateRoutePropertyTypes(
121
- upd.forwardTo,
122
- upd.defaultParams,
123
- upd.decodeParams,
124
- upd.encodeParams,
125
- );
126
- },
127
- validateUpdateRoute(name, updates, store) {
128
- const typedStore = store as {
129
- matcher: {
130
- hasRoute: (routeName: string) => boolean;
131
- getSegmentsByName: (routeName: string) => unknown;
132
- };
133
- config: { forwardMap: Record<string, string> };
134
- };
135
- const forwardTo = (updates as Record<string, unknown>).forwardTo;
136
-
137
- validateUpdateRoute(
138
- name,
139
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
140
- forwardTo as any,
141
- (routeName: string) => typedStore.matcher.hasRoute(routeName),
142
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
143
- typedStore.matcher as any,
144
- typedStore.config,
145
- );
146
- },
147
- validateParentOption(parent, tree) {
148
- validateParentOptionRaw(parent);
149
- let node = tree as { children: Map<string, unknown> };
150
-
151
- for (const segment of parent.split(".")) {
152
- const child = node.children.get(segment) as
153
- | { children: Map<string, unknown> }
154
- | undefined;
155
-
156
- if (!child) {
157
- throw new ReferenceError(
158
- `[router.addRoute] Parent route "${parent}" does not exist`,
159
- );
160
- }
161
-
162
- node = child;
163
- }
164
- },
165
- validateRouteName(name, caller) {
166
- validateRouteName(name, caller);
167
- },
168
- throwIfInternalRoute(name, caller) {
169
- throwIfInternalRoute(name as string, caller);
170
- },
171
-
172
- throwIfInternalRouteInArray(routes, caller) {
173
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
174
- throwIfInternalRouteInArray(routes as any, caller);
175
- },
176
- validateExistingRoutes,
177
- validateForwardToConsistency,
178
- validateSetRootPathArgs,
179
- guardRouteCallbacks,
180
- guardNoAsyncCallbacks,
181
- },
182
- options: {
183
- validateLimitValue(name, value) {
184
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
185
- validateLimitValue(name as any, value, "validate");
186
- },
187
- validateLimits(limits) {
188
- validateLimits(limits, "validate");
189
- },
190
- validateOptions,
191
- validateResolvedDefaultRoute,
192
- },
193
- dependencies: {
194
- validateDependencyName,
195
- validateSetDependencyArgs(_name, _value, _caller) {
196
- validateSetDependencyArgsRaw(_name);
197
- },
198
- validateDependenciesObject,
199
- validateDependencyExists(name, store) {
200
- const typedStore = store as { dependencies?: Record<string, unknown> };
201
- const value = typedStore.dependencies?.[name];
202
-
203
- validateDependencyExistsRaw(value, name);
204
- },
205
- // eslint-disable-next-line @typescript-eslint/no-empty-function
206
- validateDependencyLimit(_store, _limits) {},
207
- validateDependenciesStructure,
208
- validateDependencyCount,
209
- validateCloneArgs,
210
- warnOverwrite: warnDepsOverwrite,
211
- warnBatchOverwrite,
212
- warnRemoveNonExistent,
213
- },
214
- plugins: {
215
- validatePluginLimit(count, limits) {
216
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
217
- validatePluginLimit(count, 1, (limits as any)?.maxPlugins);
218
- },
219
- // eslint-disable-next-line @typescript-eslint/no-empty-function
220
- validateNoDuplicatePlugins(_factory, _factories) {},
221
- validatePluginKeys,
222
- validateCountThresholds(count) {
223
- const maxPlugins = ctx.getOptions().limits?.maxPlugins ?? 50;
224
-
225
- validatePluginCountThresholds(count, maxPlugins);
226
- },
227
- warnBatchDuplicates,
228
- warnPluginMethodType,
229
- warnPluginAfterStart,
230
- validateAddInterceptorArgs,
231
- },
232
- lifecycle: {
233
- validateHandler,
234
- validateNotRegistering(name, _guards, caller) {
235
- validateNotRegistering(false, name, caller);
236
- },
237
- validateHandlerLimit(count, limits, caller) {
238
- validateHandlerLimit(
239
- count,
240
- caller,
241
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
242
- (limits as any)?.maxLifecycleHandlers,
243
- );
244
- },
245
- validateCountThresholds(count, methodName) {
246
- const maxHandlers =
247
- ctx.getOptions().limits?.maxLifecycleHandlers ?? 200;
248
-
249
- validateLifecycleCountThresholds(count, methodName, maxHandlers);
250
- },
251
- warnOverwrite: warnLifecycleOverwrite,
252
- warnAsyncGuardSync,
253
- },
254
- navigation: {
255
- validateNavigateArgs,
256
- validateNavigateToDefaultArgs,
257
- validateNavigateToStateArgs,
258
- validateNavigationOptions,
259
- validateParams: validateNavigateParams,
260
- validateStartArgs,
261
- },
262
- state: {
263
- validateMakeStateArgs,
264
- validateAreStatesEqualArgs(s1, s2, ignoreQP) {
265
- if (!isState(s1)) {
266
- throw new TypeError(
267
- `[router.areStatesEqual] Invalid state1: ${getTypeDescription(s1)}. Expected State object.`,
268
- );
269
- }
270
- if (!isState(s2)) {
271
- throw new TypeError(
272
- `[router.areStatesEqual] Invalid state2: ${getTypeDescription(s2)}. Expected State object.`,
273
- );
274
- }
275
- if (ignoreQP !== undefined && !isBoolean(ignoreQP)) {
276
- throw new TypeError(
277
- `[router.areStatesEqual] Invalid ignoreQueryParams: ${getTypeDescription(ignoreQP)}. Expected boolean.`,
278
- );
279
- }
280
- },
281
- },
282
- eventBus: {
283
- validateEventName,
284
-
285
- validateListenerArgs(name, cb) {
286
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
- validateListenerArgs(name as any, cb as any);
288
- },
289
- },
290
- };
291
- }
292
-
293
- export function validationPlugin(): PluginFactory {
294
- // eslint-disable-next-line unicorn/consistent-function-scoping
295
- return (router) => {
296
- const ctx = getInternals(router);
297
-
298
- if (router.isActive()) {
299
- throw new RouterError("VALIDATION_PLUGIN_AFTER_START", {
300
- message: "validation-plugin must be registered before router.start()",
301
- });
302
- }
303
-
304
- // RouterInternals.validator is now mutable — direct assignment works
305
- ctx.validator = buildValidatorObject(ctx);
306
-
307
- try {
308
- const store = ctx.routeGetStore();
309
- const deps = ctx.dependenciesGetStore();
310
- const options = ctx.getOptions();
311
-
312
- validateExistingRoutes(store);
313
- validateForwardToConsistency(store);
314
- validateRoutePropertiesStore(store);
315
- validateForwardToTargetsStore(store);
316
- validateDependenciesStructure(deps);
317
- validateLimitsConsistency(options, store, deps);
318
- ctx.validator.options.validateOptions(
319
- options,
320
- "constructor (retrospective)",
321
- );
322
-
323
- if (typeof options.defaultRoute === "string") {
324
- validateResolvedDefaultRoute(options.defaultRoute, store);
325
- }
326
- } catch (error) {
327
- ctx.validator = null;
328
-
329
- throw error;
330
- }
331
-
332
- return {
333
- teardown() {
334
- ctx.validator = null;
335
- },
336
- };
337
- };
338
- }
@@ -1,159 +0,0 @@
1
- // packages/validation-plugin/src/validators/dependencies.ts
2
-
3
- import { logger } from "@real-router/logger";
4
- import { getTypeDescription } from "type-guards";
5
-
6
- import { computeThresholds } from "../helpers";
7
-
8
- const DEFAULT_MAX_DEPENDENCIES = 100;
9
-
10
- export function validateDependencyName(
11
- name: unknown,
12
- methodName: string,
13
- ): asserts name is string {
14
- if (typeof name !== "string") {
15
- throw new TypeError(
16
- `[router.${methodName}] dependency name must be a string, got ${typeof name}`,
17
- );
18
- }
19
- }
20
-
21
- export function validateSetDependencyArgs(
22
- name: unknown,
23
- ): asserts name is string {
24
- if (typeof name !== "string") {
25
- throw new TypeError(
26
- `[router.setDependency] dependency name must be a string, got ${typeof name}`,
27
- );
28
- }
29
- }
30
-
31
- export function validateDependenciesObject(
32
- deps: unknown,
33
- methodName: string,
34
- ): asserts deps is Record<string, unknown> {
35
- if (!(deps && typeof deps === "object" && deps.constructor === Object)) {
36
- throw new TypeError(
37
- `[router.${methodName}] Invalid argument: expected plain object, received ${getTypeDescription(deps)}`,
38
- );
39
- }
40
-
41
- for (const key in deps) {
42
- if (Object.getOwnPropertyDescriptor(deps, key)?.get) {
43
- throw new TypeError(
44
- `[router.${methodName}] Getters not allowed: "${key}"`,
45
- );
46
- }
47
- }
48
- }
49
-
50
- export function validateDependencyExists(
51
- value: unknown,
52
- dependencyName: string,
53
- ): asserts value is NonNullable<unknown> {
54
- if (value === undefined) {
55
- throw new ReferenceError(
56
- `[router.getDependency] dependency "${dependencyName}" not found`,
57
- );
58
- }
59
- }
60
-
61
- export function validateDependencyLimit(
62
- currentCount: number,
63
- newCount: number,
64
- methodName: string,
65
- maxDependencies: number = DEFAULT_MAX_DEPENDENCIES,
66
- ): void {
67
- if (maxDependencies === 0) {
68
- return;
69
- }
70
-
71
- const totalCount = currentCount + newCount;
72
-
73
- if (totalCount >= maxDependencies) {
74
- throw new RangeError(
75
- `[router.${methodName}] Dependency limit exceeded (${maxDependencies}). ` +
76
- `Current: ${totalCount}. This is likely a bug in your code.`,
77
- );
78
- }
79
- }
80
-
81
- export function validateDependencyCount(
82
- store: unknown,
83
- methodName: string,
84
- ): void {
85
- const typedStore = store as {
86
- dependencies: Record<string, unknown>;
87
- limits?: { maxDependencies?: number };
88
- };
89
- const maxDependencies =
90
- typedStore.limits?.maxDependencies ?? DEFAULT_MAX_DEPENDENCIES;
91
-
92
- if (maxDependencies === 0) {
93
- return;
94
- }
95
-
96
- const currentCount = Object.keys(typedStore.dependencies).length;
97
- const { warn, error } = computeThresholds(maxDependencies);
98
-
99
- if (currentCount >= maxDependencies) {
100
- throw new RangeError(
101
- `[router.${methodName}] Dependency limit exceeded (${maxDependencies}). Current: ${currentCount}.`,
102
- );
103
- } else if (currentCount === error) {
104
- logger.error(
105
- `router.${methodName}`,
106
- `${currentCount} dependencies registered! This indicates architectural problems. Hard limit at ${maxDependencies}.`,
107
- );
108
- } else if (currentCount === warn) {
109
- logger.warn(
110
- `router.${methodName}`,
111
- `${currentCount} dependencies registered. Consider if all are necessary.`,
112
- );
113
- }
114
- }
115
-
116
- export function validateCloneArgs(dependencies: unknown): void {
117
- if (dependencies === undefined) {
118
- return;
119
- }
120
-
121
- if (
122
- !(
123
- dependencies &&
124
- typeof dependencies === "object" &&
125
- dependencies.constructor === Object
126
- )
127
- ) {
128
- throw new TypeError(
129
- `[cloneRouter] Invalid dependencies: expected plain object or undefined, received ${typeof dependencies}`,
130
- );
131
- }
132
-
133
- for (const key in dependencies as Record<string, unknown>) {
134
- if (Object.getOwnPropertyDescriptor(dependencies, key)?.get) {
135
- throw new TypeError(
136
- `[cloneRouter] Getters not allowed in dependencies: "${key}"`,
137
- );
138
- }
139
- }
140
- }
141
-
142
- export function warnOverwrite(name: string, methodName: string): void {
143
- logger.warn(
144
- `router.${methodName}`,
145
- "Router dependency already exists and is being overwritten:",
146
- name,
147
- );
148
- }
149
-
150
- export function warnBatchOverwrite(keys: string[], methodName: string): void {
151
- logger.warn(`router.${methodName}`, "Overwritten:", keys.join(", "));
152
- }
153
-
154
- export function warnRemoveNonExistent(name: unknown): void {
155
- logger.warn(
156
- "router.removeDependency",
157
- `Attempted to remove non-existent dependency: "${typeof name === "string" ? name : String(name)}"`,
158
- );
159
- }
@@ -1,50 +0,0 @@
1
- // packages/validation-plugin/src/validators/eventBus.ts
2
-
3
- import type { Plugin } from "@real-router/core";
4
-
5
- // Local types — mirrors EventName and EventMethodMap from @real-router/types
6
- // (@real-router/types is not a direct dependency of this package)
7
- interface EventMethodMap {
8
- $start: "onStart";
9
- $stop: "onStop";
10
- $$start: "onTransitionStart";
11
- $$leaveApprove: "onTransitionLeaveApprove";
12
- $$cancel: "onTransitionCancel";
13
- $$success: "onTransitionSuccess";
14
- $$error: "onTransitionError";
15
- }
16
-
17
- type EventName = keyof EventMethodMap;
18
-
19
- // Local set — mirrors validEventNames from @real-router/core/constants
20
- // (not exported from @real-router/core public API)
21
- const validEventNames = new Set<EventName>([
22
- "$start",
23
- "$stop",
24
- "$$start",
25
- "$$leaveApprove",
26
- "$$cancel",
27
- "$$success",
28
- "$$error",
29
- ]);
30
-
31
- export function validateEventName(eventName: unknown): void {
32
- if (!validEventNames.has(eventName as EventName)) {
33
- throw new TypeError(
34
- `[router.addEventListener] Invalid event name: ${String(eventName)}. Must be one of: $start, $stop, $$start, $$leaveApprove, $$cancel, $$success, $$error`,
35
- );
36
- }
37
- }
38
-
39
- export function validateListenerArgs<E extends EventName>(
40
- eventName: E,
41
- cb: Plugin[EventMethodMap[E]],
42
- ): void {
43
- validateEventName(eventName);
44
-
45
- if (typeof cb !== "function") {
46
- throw new TypeError(
47
- `[router.addEventListener] callback must be a function, got ${typeof cb}`,
48
- );
49
- }
50
- }