@real-router/core 0.80.0 → 0.82.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -55
- package/dist/cjs/Router-CCmcEi-S.js +2 -0
- package/dist/cjs/Router-CCmcEi-S.js.map +1 -0
- package/dist/cjs/Router.d.ts +7 -6
- package/dist/cjs/Router.d.ts.map +1 -1
- package/dist/cjs/Router.js +1 -1
- package/dist/cjs/Router.js.map +1 -1
- package/dist/cjs/api/getPluginApi.d.ts.map +1 -1
- package/dist/cjs/api/getPluginApi.js +1 -1
- package/dist/cjs/api/getPluginApi.js.map +1 -1
- package/dist/cjs/api/getRoutesApi.d.ts.map +1 -1
- package/dist/cjs/api/getRoutesApi.js +1 -1
- package/dist/cjs/api/getRoutesApi.js.map +1 -1
- package/dist/cjs/api.js +2 -1
- package/dist/cjs/api.js.map +1 -1
- package/dist/cjs/buildParamMeta-0m11OIJS.js +2 -0
- package/dist/cjs/buildParamMeta-0m11OIJS.js.map +1 -0
- package/dist/cjs/channels/defaults.js +2 -0
- package/dist/cjs/channels/defaults.js.map +1 -0
- package/dist/cjs/channels/guard.js +2 -0
- package/dist/cjs/channels/guard.js.map +1 -0
- package/dist/cjs/channels/modeGate.js +2 -0
- package/dist/cjs/channels/modeGate.js.map +1 -0
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +1 -1
- package/dist/cjs/constants.js.map +1 -1
- package/dist/cjs/createRouter.d.ts +1 -1
- package/dist/cjs/createRouter.d.ts.map +1 -1
- package/dist/cjs/createRouter.js.map +1 -1
- package/dist/cjs/engine/builder/buildTree.js +1 -1
- package/dist/cjs/engine/builder/buildTree.js.map +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts +14 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.d.ts.map +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js +1 -1
- package/dist/cjs/engine/path-matcher/SegmentMatcher.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/buildParts.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/context.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/context.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/errors.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/index.js +1 -1
- package/dist/cjs/engine/path-matcher/registration/index.js.map +1 -1
- package/dist/cjs/engine/path-matcher/registration/trieNodes.js.map +1 -1
- package/dist/cjs/engine/path-matcher/types.d.ts +3 -0
- package/dist/cjs/engine/path-matcher/types.d.ts.map +1 -1
- package/dist/cjs/engine/search-params/strategies/number.js +1 -1
- package/dist/cjs/engine/search-params/strategies/number.js.map +1 -1
- package/dist/cjs/engine/validation/routes.js +1 -1
- package/dist/cjs/engine/validation/routes.js.map +1 -1
- package/dist/cjs/helpers.js +1 -1
- package/dist/cjs/helpers.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internals.d.ts +30 -8
- package/dist/cjs/internals.d.ts.map +1 -1
- package/dist/cjs/internals.js +1 -1
- package/dist/cjs/internals.js.map +1 -1
- package/dist/cjs/limits.js +2 -0
- package/dist/cjs/limits.js.map +1 -0
- package/dist/cjs/namespaces/EventBusNamespace/EventBusNamespace.js.map +1 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js +1 -1
- package/dist/cjs/namespaces/NavigationNamespace/NavigationNamespace.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/OptionsNamespace.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/constants.js.map +1 -1
- package/dist/cjs/namespaces/OptionsNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/RoutesNamespace.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/helpers.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts +8 -0
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.d.ts.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/routesStore.js.map +1 -1
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts +24 -7
- package/dist/cjs/namespaces/RoutesNamespace/types.d.ts.map +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js +1 -1
- package/dist/cjs/namespaces/StateNamespace/StateNamespace.js.map +1 -1
- package/dist/cjs/pipeline/buildURL.js +2 -0
- package/dist/cjs/pipeline/buildURL.js.map +1 -0
- package/dist/cjs/pipeline/canonicalize.js +2 -0
- package/dist/cjs/pipeline/canonicalize.js.map +1 -0
- package/dist/cjs/pipeline/materialize.js +2 -0
- package/dist/cjs/pipeline/materialize.js.map +1 -0
- package/dist/cjs/pipeline/port.d.ts +147 -0
- package/dist/cjs/pipeline/port.d.ts.map +1 -0
- package/dist/cjs/routerFSM.js +1 -1
- package/dist/cjs/routerFSM.js.map +1 -1
- package/dist/cjs/transitionPath.js +1 -1
- package/dist/cjs/transitionPath.js.map +1 -1
- package/dist/cjs/types/RouterValidator.d.ts +14 -0
- package/dist/cjs/types/RouterValidator.d.ts.map +1 -1
- package/dist/cjs/types/api.d.ts +20 -11
- package/dist/cjs/types/api.d.ts.map +1 -1
- package/dist/cjs/types/base.d.ts +63 -4
- package/dist/cjs/types/base.d.ts.map +1 -1
- package/dist/cjs/types/constants.d.ts +1 -0
- package/dist/cjs/types/constants.d.ts.map +1 -1
- package/dist/cjs/types/router.d.ts +112 -23
- package/dist/cjs/types/router.d.ts.map +1 -1
- package/dist/cjs/types/tree-changed.d.ts +1 -1
- package/dist/cjs/types/tree-changed.d.ts.map +1 -1
- package/dist/cjs/types.d.ts +20 -11
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/utils/event-emitter/EventEmitter.js.map +1 -0
- package/dist/cjs/utils/fsm/fsm.js.map +1 -0
- package/dist/cjs/utils/logger/RouterLogger.js.map +1 -0
- package/dist/cjs/utils/logger/constants.js.map +1 -0
- package/dist/cjs/validation.js +2 -1
- package/dist/cjs/validation.js.map +1 -1
- package/dist/cjs/wiring/wireNamespaces.js +1 -1
- package/dist/cjs/wiring/wireNamespaces.js.map +1 -1
- package/dist/esm/Router-DEpNuwBW.mjs +2 -0
- package/dist/esm/Router-DEpNuwBW.mjs.map +1 -0
- package/dist/esm/Router.d.mts +7 -6
- package/dist/esm/Router.d.mts.map +1 -1
- package/dist/esm/api/getPluginApi.d.mts.map +1 -1
- package/dist/esm/api/getRoutesApi.d.mts.map +1 -1
- package/dist/esm/api.mjs +1 -1
- package/dist/esm/api.mjs.map +1 -1
- package/dist/esm/buildParamMeta-D8td47fL.mjs +2 -0
- package/dist/esm/buildParamMeta-D8td47fL.mjs.map +1 -0
- package/dist/esm/constants.d.mts.map +1 -1
- package/dist/esm/createRouter.d.mts +1 -1
- package/dist/esm/createRouter.d.mts.map +1 -1
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts +14 -1
- package/dist/esm/engine/path-matcher/SegmentMatcher.d.mts.map +1 -1
- package/dist/esm/engine/path-matcher/types.d.mts +3 -0
- package/dist/esm/engine/path-matcher/types.d.mts.map +1 -1
- package/dist/esm/index.d.mts +3 -3
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/internals.d.mts +30 -8
- package/dist/esm/internals.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts +8 -0
- package/dist/esm/namespaces/RoutesNamespace/routesStore.d.mts.map +1 -1
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts +24 -7
- package/dist/esm/namespaces/RoutesNamespace/types.d.mts.map +1 -1
- package/dist/esm/pipeline/port.d.mts +147 -0
- package/dist/esm/pipeline/port.d.mts.map +1 -0
- package/dist/esm/types/RouterValidator.d.mts +14 -0
- package/dist/esm/types/RouterValidator.d.mts.map +1 -1
- package/dist/esm/types/api.d.mts +20 -11
- package/dist/esm/types/api.d.mts.map +1 -1
- package/dist/esm/types/base.d.mts +63 -4
- package/dist/esm/types/base.d.mts.map +1 -1
- package/dist/esm/types/constants.d.mts +1 -0
- package/dist/esm/types/constants.d.mts.map +1 -1
- package/dist/esm/types/router.d.mts +112 -23
- package/dist/esm/types/router.d.mts.map +1 -1
- package/dist/esm/types/tree-changed.d.mts +1 -1
- package/dist/esm/types/tree-changed.d.mts.map +1 -1
- package/dist/esm/types.d.mts +20 -11
- package/dist/esm/types.d.mts.map +1 -1
- package/dist/esm/validation.mjs +1 -1
- package/dist/esm/validation.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/cjs/Router-DGUed2F4.js +0 -2
- package/dist/cjs/Router-DGUed2F4.js.map +0 -1
- package/dist/cjs/buildParamMeta-DniluAIV.js +0 -2
- package/dist/cjs/buildParamMeta-DniluAIV.js.map +0 -1
- package/dist/cjs/foundation/event-emitter/EventEmitter.js.map +0 -1
- package/dist/cjs/foundation/fsm/fsm.js.map +0 -1
- package/dist/cjs/foundation/logger/RouterLogger.js.map +0 -1
- package/dist/cjs/foundation/logger/constants.js.map +0 -1
- package/dist/cjs/namespaces/StateNamespace/helpers.js +0 -2
- package/dist/cjs/namespaces/StateNamespace/helpers.js.map +0 -1
- package/dist/cjs/stateMetaStore.js +0 -2
- package/dist/cjs/stateMetaStore.js.map +0 -1
- package/dist/esm/Router-Bk6PfSE8.mjs +0 -2
- package/dist/esm/Router-Bk6PfSE8.mjs.map +0 -1
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs +0 -2
- package/dist/esm/buildParamMeta-bOLhLF9h.mjs.map +0 -1
- /package/dist/cjs/{foundation → utils}/event-emitter/EventEmitter.js +0 -0
- /package/dist/cjs/{foundation → utils}/fsm/fsm.js +0 -0
- /package/dist/cjs/{foundation → utils}/logger/RouterLogger.js +0 -0
- /package/dist/cjs/{foundation → utils}/logger/constants.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { QueryParamsMode, QueryParamsOptions } from "./route-node-types.js";
|
|
2
|
-
import { Params, RouterError, State, Unsubscribe } from "./base.js";
|
|
2
|
+
import { NavigationTarget, Params, ParamsSearch, RouterError, SearchParams, State, Unsubscribe } from "./base.js";
|
|
3
3
|
import { LimitsConfig } from "./limits.js";
|
|
4
4
|
import { NavigationOptions } from "../types.js";
|
|
5
5
|
//#region src/types/router.d.ts
|
|
@@ -18,7 +18,7 @@ interface LoggerConfig {
|
|
|
18
18
|
* Core stores a concrete instance on `RouterInternals.logger` (built from
|
|
19
19
|
* `options.logger` in the `Router` constructor); plugins and other consumers
|
|
20
20
|
* reach that same per-instance logger via `getPluginApi(router).logger`. The
|
|
21
|
-
* concrete `RouterLogger` class lives in core's `
|
|
21
|
+
* concrete `RouterLogger` class lives in core's `utils/logger` — this
|
|
22
22
|
* interface is the shared contract, so nothing outside core needs to import the
|
|
23
23
|
* class (or depend on the former standalone `@real-router/logger` package).
|
|
24
24
|
*/
|
|
@@ -42,26 +42,60 @@ type ForwardToCallback<Dependencies = object> = (getDependency: <K extends keyof
|
|
|
42
42
|
* Receives a dependency getter function to access router dependencies.
|
|
43
43
|
*/
|
|
44
44
|
type DefaultParamsCallback<Dependencies = object> = (getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K]) => Params;
|
|
45
|
+
/**
|
|
46
|
+
* Callback function for dynamically resolving the default query params.
|
|
47
|
+
* Receives a dependency getter function to access router dependencies.
|
|
48
|
+
*/
|
|
49
|
+
type DefaultSearchCallback<Dependencies = object> = (getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K]) => SearchParams;
|
|
45
50
|
/**
|
|
46
51
|
* Router configuration options.
|
|
47
52
|
*
|
|
48
53
|
* Note: For input, use `Partial<Options>` as all fields have defaults.
|
|
49
54
|
* After initialization, `getOptions()` returns resolved `Options` with all fields populated.
|
|
55
|
+
*
|
|
56
|
+
* Generic over the router's dependency map so the three resolver callbacks
|
|
57
|
+
* (`defaultRoute` / `defaultParams` / `defaultSearch`) receive a TYPED
|
|
58
|
+
* `getDependency`. Code that cannot know that map — anything plugin-facing,
|
|
59
|
+
* and every consumer that reads configuration rather than resolving it —
|
|
60
|
+
* takes {@link AnyOptions} instead of infecting itself with the parameter.
|
|
50
61
|
*/
|
|
51
|
-
interface Options {
|
|
62
|
+
interface Options<Dependencies extends DefaultDependencies = DefaultDependencies> {
|
|
52
63
|
/**
|
|
53
64
|
* Default route to navigate to on start.
|
|
54
65
|
* Empty string means no default route.
|
|
55
66
|
*
|
|
56
67
|
* @default ""
|
|
57
68
|
*/
|
|
58
|
-
defaultRoute: string | DefaultRouteCallback
|
|
69
|
+
defaultRoute: string | DefaultRouteCallback<Dependencies>;
|
|
59
70
|
/**
|
|
60
|
-
* Default parameters for the default route.
|
|
71
|
+
* Default **path** parameters for the default route.
|
|
72
|
+
*
|
|
73
|
+
* Query defaults belong in {@link defaultSearch} — the two are separate
|
|
74
|
+
* channels (RFC-4 M2 / #1548), exactly as on a route's own config. Until that
|
|
75
|
+
* twin existed, a query-declared name written here reached the URL only
|
|
76
|
+
* because the `forwardState` seam still re-channelled the bag on its way
|
|
77
|
+
* through; that repair is scheduled for removal, so this slot is for path
|
|
78
|
+
* params and arbitrary app-level data, nothing else.
|
|
61
79
|
*
|
|
62
80
|
* @default {}
|
|
63
81
|
*/
|
|
64
|
-
defaultParams: Params | DefaultParamsCallback
|
|
82
|
+
defaultParams: Params | DefaultParamsCallback<Dependencies>;
|
|
83
|
+
/**
|
|
84
|
+
* Default **query** parameters for the default route (RFC-4 M2 / #1548) —
|
|
85
|
+
* the query-channel twin of {@link defaultParams}, and the router-level
|
|
86
|
+
* counterpart of a route's own `defaultSearch`.
|
|
87
|
+
*
|
|
88
|
+
* Passed to `navigateToDefault()` in the query slot, so it is merged into
|
|
89
|
+
* `state.search` and, subject to `queryParamsMode`, printed into the URL
|
|
90
|
+
* query string. Like `defaultRoute` / `defaultParams`, it may be a callback,
|
|
91
|
+
* re-evaluated on every `navigateToDefault()` — the default route can itself
|
|
92
|
+
* be chosen dynamically, so its query defaults have to be able to follow.
|
|
93
|
+
* `Options` is generic over the dependency map, so the callback's
|
|
94
|
+
* `getDependency` is typed against the router's OWN dependencies.
|
|
95
|
+
*
|
|
96
|
+
* @default {}
|
|
97
|
+
*/
|
|
98
|
+
defaultSearch: SearchParams | DefaultSearchCallback<Dependencies>;
|
|
65
99
|
/**
|
|
66
100
|
* How to handle trailing slashes in URLs.
|
|
67
101
|
* - "strict": Route must match exactly
|
|
@@ -144,6 +178,21 @@ interface Options {
|
|
|
144
178
|
*/
|
|
145
179
|
limits?: Partial<LimitsConfig>;
|
|
146
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* `Options` as seen by code that cannot know the router's dependency map —
|
|
183
|
+
* `PluginApi.getOptions()`, the matcher, the URL builders.
|
|
184
|
+
*
|
|
185
|
+
* `Options<never>` rather than `Options<object>`, and the difference is the
|
|
186
|
+
* whole point: `keyof never` is `PropertyKey`, so the erased `getDependency`
|
|
187
|
+
* accepts ANY key and returns `never` — a wider parameter and a narrower
|
|
188
|
+
* return, which is exactly what contravariance needs for `Options<D>` to flow
|
|
189
|
+
* in for every `D`. `Options<object>` erases `keyof` to `never` instead and
|
|
190
|
+
* therefore accepts NOTHING but itself (verified: the assignment fails).
|
|
191
|
+
*
|
|
192
|
+
* Every field stays visible; only the callbacks become uncallable, which is
|
|
193
|
+
* honest — a plugin has no dependency map to resolve them against.
|
|
194
|
+
*/
|
|
195
|
+
type AnyOptions = Options<never>;
|
|
147
196
|
type GuardFn = (toState: State, fromState: State | undefined, signal?: AbortSignal) => boolean | Promise<boolean>;
|
|
148
197
|
type DefaultDependencies = object;
|
|
149
198
|
interface Plugin {
|
|
@@ -186,10 +235,13 @@ interface Subscription {
|
|
|
186
235
|
* For full router access, use the Router interface directly or the useRouter() hook.
|
|
187
236
|
*/
|
|
188
237
|
interface Navigator {
|
|
189
|
-
navigate:
|
|
238
|
+
navigate: {
|
|
239
|
+
(target: NavigationTarget, options?: NavigationOptions): Promise<State>;
|
|
240
|
+
(routeName: string, routeParams?: Params, routeSearch?: SearchParams, options?: NavigationOptions): Promise<State>;
|
|
241
|
+
};
|
|
190
242
|
getState: () => State | undefined;
|
|
191
|
-
isActiveRoute: (name: string, params?: Params, strictEquality?: boolean, ignoreQueryParams?: boolean) => boolean;
|
|
192
|
-
canNavigateTo: (name: string, params?: Params) => boolean;
|
|
243
|
+
isActiveRoute: (name: string, params?: Params, search?: SearchParams, strictEquality?: boolean, ignoreQueryParams?: boolean) => boolean;
|
|
244
|
+
canNavigateTo: (name: string, params?: Params, search?: SearchParams) => boolean;
|
|
193
245
|
subscribe: (listener: SubscribeFn) => Unsubscribe;
|
|
194
246
|
subscribeLeave: (listener: LeaveFn) => Unsubscribe;
|
|
195
247
|
isLeaveApproved: () => boolean;
|
|
@@ -203,8 +255,8 @@ interface Navigator {
|
|
|
203
255
|
*/
|
|
204
256
|
interface Router<D extends DefaultDependencies = DefaultDependencies> {
|
|
205
257
|
[key: string]: unknown;
|
|
206
|
-
isActiveRoute: (name: string, params?: Params, strictEquality?: boolean, ignoreQueryParams?: boolean) => boolean;
|
|
207
|
-
buildPath: (route: string, params?: Params) => string;
|
|
258
|
+
isActiveRoute: (name: string, params?: Params, search?: SearchParams, strictEquality?: boolean, ignoreQueryParams?: boolean) => boolean;
|
|
259
|
+
buildPath: (route: string, params?: Params, search?: SearchParams) => string;
|
|
208
260
|
getState: <P extends Params = Params>() => State<P> | undefined;
|
|
209
261
|
getPreviousState: () => State | undefined;
|
|
210
262
|
areStatesEqual: (state1: State | undefined, state2: State | undefined, ignoreQueryParams?: boolean) => boolean;
|
|
@@ -213,12 +265,15 @@ interface Router<D extends DefaultDependencies = DefaultDependencies> {
|
|
|
213
265
|
start: (startPath: string) => Promise<State>;
|
|
214
266
|
stop: () => this;
|
|
215
267
|
dispose: () => void;
|
|
216
|
-
canNavigateTo: (name: string, params?: Params) => boolean;
|
|
268
|
+
canNavigateTo: (name: string, params?: Params, search?: SearchParams) => boolean;
|
|
217
269
|
usePlugin: (...plugins: (PluginFactory<D> | false | null | undefined)[]) => Unsubscribe;
|
|
218
270
|
subscribe: (listener: SubscribeFn) => Unsubscribe;
|
|
219
271
|
subscribeLeave: (listener: LeaveFn) => Unsubscribe;
|
|
220
272
|
isLeaveApproved: () => boolean;
|
|
221
|
-
navigate:
|
|
273
|
+
navigate: {
|
|
274
|
+
(target: NavigationTarget, options?: NavigationOptions): Promise<State>;
|
|
275
|
+
(routeName: string, routeParams?: Params, routeSearch?: SearchParams, options?: NavigationOptions): Promise<State>;
|
|
276
|
+
};
|
|
222
277
|
navigateToDefault: (options?: NavigationOptions) => Promise<State>;
|
|
223
278
|
navigateToNotFound: (path?: string) => State;
|
|
224
279
|
}
|
|
@@ -257,17 +312,49 @@ interface Route<Dependencies extends DefaultDependencies = DefaultDependencies>
|
|
|
257
312
|
forwardTo?: string | ForwardToCallback<Dependencies>;
|
|
258
313
|
/** Nested child routes. */
|
|
259
314
|
children?: Route<Dependencies>[];
|
|
260
|
-
/** Encodes state params to URL params. */
|
|
261
|
-
encodeParams?: (stateParams: Params) => Params;
|
|
262
|
-
/** Decodes URL params to state params. */
|
|
263
|
-
decodeParams?: (pathParams: Params) => Params;
|
|
264
315
|
/**
|
|
265
|
-
*
|
|
316
|
+
* Encodes the state channels to URL channels before path building (RFC-4 M2 /
|
|
317
|
+
* #1548). Receives `{ params, search }` and returns `{ params, search }` —
|
|
318
|
+
* transform whichever channel you own and pass the other through. The path
|
|
319
|
+
* slots are built from the returned `params`, the query string from the
|
|
320
|
+
* returned `search`.
|
|
321
|
+
*/
|
|
322
|
+
encodeParams?: (channels: ParamsSearch) => ParamsSearch;
|
|
323
|
+
/**
|
|
324
|
+
* Decodes the matched URL channels to state channels (RFC-4 M2 / #1548).
|
|
325
|
+
* Receives `{ params, search }` (path params + parsed query) and returns
|
|
326
|
+
* `{ params, search }`. Runs inside `matchPath`, **before** any search-schema
|
|
327
|
+
* plugin validation — the v1 transformation order (engine codec → plugin).
|
|
328
|
+
*/
|
|
329
|
+
decodeParams?: (channels: ParamsSearch) => ParamsSearch;
|
|
330
|
+
/**
|
|
331
|
+
* Default **path** parameters for this route (and arbitrary app-level
|
|
332
|
+
* defaults). Merged into `state.params`; missing path params are filled from
|
|
333
|
+
* here. Query defaults belong in {@link defaultSearch} (RFC-4 M2 / #1548).
|
|
334
|
+
*
|
|
335
|
+
* ⚠ **The slot IS the channel** — `ba0f6b18b` retired the routing #1549
|
|
336
|
+
* introduced, and this doc described it for one release. A name the route
|
|
337
|
+
* declares with `?` written here is NOT re-routed to the query string: it is
|
|
338
|
+
* REFUSED at registration, so `createRouter` / `add` / `replace` / `update` /
|
|
339
|
+
* `setRootPath` throw, naming the key and telling you to move it to
|
|
340
|
+
* {@link defaultSearch}. Without that check the router would build a state out
|
|
341
|
+
* of config it had just accepted and its own always-on channel guard would
|
|
342
|
+
* reject it on `start()`.
|
|
266
343
|
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
344
|
+
* A key the route declares NOWHERE is legitimate here and stays in
|
|
345
|
+
* `state.params` as app-level data — it never reaches the URL, which
|
|
346
|
+
* `@real-router/validation-plugin` reports once per route+key (#1579).
|
|
269
347
|
*/
|
|
270
348
|
defaultParams?: Params;
|
|
349
|
+
/**
|
|
350
|
+
* Default **query** (search) parameters for this route (RFC-4 M2 / #1548) —
|
|
351
|
+
* the query-channel twin of {@link defaultParams}. Merged into `state.search`
|
|
352
|
+
* and, subject to `queryParamsMode`, printed into the URL query string. A key
|
|
353
|
+
* the route does not declare as a query param (`?name`) follows the same
|
|
354
|
+
* `queryParamsMode` rules as a runtime `search` value (loose prints,
|
|
355
|
+
* default/strict drops).
|
|
356
|
+
*/
|
|
357
|
+
defaultSearch?: SearchParams;
|
|
271
358
|
}
|
|
272
359
|
/**
|
|
273
360
|
* Configuration update options for `updateRoute()`.
|
|
@@ -299,15 +386,17 @@ interface RouteConfigUpdate<Dependencies extends DefaultDependencies = DefaultDe
|
|
|
299
386
|
forwardTo?: string | ForwardToCallback<Dependencies> | null;
|
|
300
387
|
/** Set to null to remove defaultParams */
|
|
301
388
|
defaultParams?: Params | null;
|
|
389
|
+
/** Set to null to remove defaultSearch (RFC-4 M2 / #1548) */
|
|
390
|
+
defaultSearch?: SearchParams | null;
|
|
302
391
|
/** Set to null to remove decoder */
|
|
303
|
-
decodeParams?: ((
|
|
392
|
+
decodeParams?: ((channels: ParamsSearch) => ParamsSearch) | null;
|
|
304
393
|
/** Set to null to remove encoder */
|
|
305
|
-
encodeParams?: ((
|
|
394
|
+
encodeParams?: ((channels: ParamsSearch) => ParamsSearch) | null;
|
|
306
395
|
/** Set to null to remove canActivate */
|
|
307
396
|
canActivate?: GuardFnFactory<Dependencies> | null;
|
|
308
397
|
/** Set to null to remove canDeactivate */
|
|
309
398
|
canDeactivate?: GuardFnFactory<Dependencies> | null;
|
|
310
399
|
}
|
|
311
400
|
//#endregion
|
|
312
|
-
export { DefaultDependencies, DefaultParamsCallback, DefaultRouteCallback, ForwardToCallback, GuardFn, GuardFnFactory, LeaveFn, LeaveState, Listener, LogCallback, LogLevel, LogLevelConfig, LoggerConfig, Navigator, Options, Plugin, PluginFactory, Route, RouteConfigUpdate, Router, RouterLogger, SubscribeFn, SubscribeState, Subscription };
|
|
401
|
+
export { AnyOptions, DefaultDependencies, DefaultParamsCallback, DefaultRouteCallback, DefaultSearchCallback, ForwardToCallback, GuardFn, GuardFnFactory, LeaveFn, LeaveState, Listener, LogCallback, LogLevel, LogLevelConfig, LoggerConfig, Navigator, Options, Plugin, PluginFactory, Route, RouteConfigUpdate, Router, RouterLogger, SubscribeFn, SubscribeState, Subscription };
|
|
313
402
|
//# sourceMappingURL=router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","names":[],"sources":["../../../src/types/router.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"router.d.ts","names":[],"sources":["../../../src/types/router.ts"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { DefaultDependencies, Route, RouteConfigUpdate } from "./router.js";
|
|
|
8
8
|
* guards are invoked-on-demand (fresh-read per navigation), not cached derived
|
|
9
9
|
* state, so they need no observation channel.
|
|
10
10
|
*/
|
|
11
|
-
type TreeStructuralPatch<Dependencies extends DefaultDependencies = DefaultDependencies> = Pick<RouteConfigUpdate<Dependencies>, "forwardTo" | "defaultParams" | "encodeParams" | "decodeParams">;
|
|
11
|
+
type TreeStructuralPatch<Dependencies extends DefaultDependencies = DefaultDependencies> = Pick<RouteConfigUpdate<Dependencies>, "forwardTo" | "defaultParams" | "defaultSearch" | "encodeParams" | "decodeParams">;
|
|
12
12
|
interface TreeChangedAdd<Dependencies extends DefaultDependencies = DefaultDependencies> {
|
|
13
13
|
readonly op: "add";
|
|
14
14
|
/** Top-level routes that were added (deep-cloned + frozen; caller untouched). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree-changed.d.ts","names":[],"sources":["../../../src/types/tree-changed.ts"],"mappings":";;;;;;;;;;KAsBY,oBACV,qBAAqB,sBAAsB,uBACzC,KACF,kBAAkB;
|
|
1
|
+
{"version":3,"file":"tree-changed.d.ts","names":[],"sources":["../../../src/types/tree-changed.ts"],"mappings":";;;;;;;;;;KAsBY,oBACV,qBAAqB,sBAAsB,uBACzC,KACF,kBAAkB;UAQH,eACf,qBAAqB,sBAAsB;WAElC;;WAEA,gBAAgB,MAAM;;WAEtB;;UAGM,kBACf,qBAAqB,sBAAsB;WAElC;WACA;;;;;WAKA,yBAAyB,MAAM;;UAGzB,kBACf,qBAAqB,sBAAsB;WAElC;WACA;;WAEA,OAAO,SAAS,oBAAoB;;UAG9B,mBACf,qBAAqB,sBAAsB;WAElC;;WAEA,kBAAkB,MAAM;;WAExB,gBAAgB,MAAM;;UAGhB,iBACf,qBAAqB,sBAAsB;WAElC;;WAEA,kBAAkB,MAAM;;;;;;;;KASvB,iBACV,qBAAqB,sBAAsB,uBAEzC,eAAe,gBACf,kBAAkB,gBAClB,kBAAkB,gBAClB,mBAAmB,gBACnB,iBAAiB"}
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { QueryParamsMode, QueryParamsOptions, RouteParams, RouteTreeState } from "./types/route-node-types.js";
|
|
2
|
-
import { Params, RouterError, SerializedRouterState, SimpleState, State,
|
|
2
|
+
import { NavigationTarget, Params, ParamsSearch, RouterError, SearchParamPrimitive, SearchParamValue, SearchParams, SerializedRouterState, SimpleState, State, TransitionMeta, TransitionPhase, TransitionReason, Unsubscribe } from "./types/base.js";
|
|
3
3
|
import { LimitsConfig } from "./types/limits.js";
|
|
4
|
-
import { DefaultDependencies, DefaultParamsCallback, DefaultRouteCallback, ForwardToCallback, GuardFn, GuardFnFactory, LeaveFn, LeaveState, Listener, LogCallback, LogLevel, LogLevelConfig, LoggerConfig, Navigator, Options, Plugin, PluginFactory, Route, RouteConfigUpdate, Router, RouterLogger, SubscribeFn, SubscribeState, Subscription } from "./types/router.js";
|
|
4
|
+
import { AnyOptions, DefaultDependencies, DefaultParamsCallback, DefaultRouteCallback, DefaultSearchCallback, ForwardToCallback, GuardFn, GuardFnFactory, LeaveFn, LeaveState, Listener, LogCallback, LogLevel, LogLevelConfig, LoggerConfig, Navigator, Options, Plugin, PluginFactory, Route, RouteConfigUpdate, Router, RouterLogger, SubscribeFn, SubscribeState, Subscription } from "./types/router.js";
|
|
5
5
|
import { ErrorCodeKeys, ErrorCodeToValueMap, ErrorCodeValues, EventMethodMap, EventName, EventToNameMap, EventToPluginMap, EventsKeys, PluginMethod } from "./types/constants.js";
|
|
6
6
|
import { TreeChangedAdd, TreeChangedClear, TreeChangedEvent, TreeChangedRemove, TreeChangedReplace, TreeChangedUpdate, TreeStructuralPatch } from "./types/tree-changed.js";
|
|
7
7
|
import { ContextNamespaceClaim, DependenciesApi, InterceptableMethodMap, InterceptorFn, LifecycleApi, PluginApi, RoutesApi } from "./types/api.js";
|
|
@@ -55,7 +55,7 @@ interface NavigationOptions {
|
|
|
55
55
|
*
|
|
56
56
|
* @example
|
|
57
57
|
* // Redirect after login - prevent back button to login page
|
|
58
|
-
* router.navigate('dashboard', {}, { replace: true });
|
|
58
|
+
* router.navigate('dashboard', {}, undefined, { replace: true });
|
|
59
59
|
*
|
|
60
60
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState}
|
|
61
61
|
*/
|
|
@@ -97,13 +97,22 @@ interface NavigationOptions {
|
|
|
97
97
|
* @default false
|
|
98
98
|
*
|
|
99
99
|
* @example
|
|
100
|
-
* // Refresh current page data
|
|
101
|
-
*
|
|
100
|
+
* // Refresh current page data. Options sit at slot 4 since RFC-4 M2
|
|
101
|
+
* // (#1548) — slot 3 is the query channel, so the pre-M2 three-argument
|
|
102
|
+
* // spelling puts `{ reload: true }` in `search`: the reload never happens
|
|
103
|
+
* // and the page's own query is rebuilt from an object that does not have
|
|
104
|
+
* // it (measured: `/search?term=react` → `/search`).
|
|
105
|
+
* router.navigate(
|
|
106
|
+
* currentRoute.name,
|
|
107
|
+
* currentRoute.params,
|
|
108
|
+
* currentRoute.search,
|
|
109
|
+
* { reload: true },
|
|
110
|
+
* );
|
|
102
111
|
*
|
|
103
112
|
* @example
|
|
104
|
-
* // Force re-fetch on same route with different query
|
|
105
|
-
* //
|
|
106
|
-
* router.navigate('search', { term: 'react' }, { reload: true });
|
|
113
|
+
* // Force re-fetch on the same route with a different query. The query is
|
|
114
|
+
* // its own channel now — pass it at slot 3, not inside the path bag.
|
|
115
|
+
* router.navigate('search', {}, { term: 'react' }, { reload: true });
|
|
107
116
|
*
|
|
108
117
|
* @see {@link force} for alternative that forces transition
|
|
109
118
|
* @see {@link Router.areStatesEqual} for state comparison logic
|
|
@@ -130,7 +139,7 @@ interface NavigationOptions {
|
|
|
130
139
|
*
|
|
131
140
|
* @example
|
|
132
141
|
* // Force transition for tracking even if params didn't change
|
|
133
|
-
* router.navigate('analytics', { event: 'pageview' }, { force: true });
|
|
142
|
+
* router.navigate('analytics', {}, { event: 'pageview' }, { force: true });
|
|
134
143
|
*
|
|
135
144
|
* @see {@link reload} for semantic equivalent (preferred for refresh scenarios)
|
|
136
145
|
*/
|
|
@@ -159,7 +168,7 @@ interface NavigationOptions {
|
|
|
159
168
|
* @example
|
|
160
169
|
* // Force logout even with unsaved changes
|
|
161
170
|
* function forceLogout() {
|
|
162
|
-
* router.navigate('login', {}, {
|
|
171
|
+
* router.navigate('login', {}, undefined, {
|
|
163
172
|
* forceDeactivate: true,
|
|
164
173
|
* replace: true
|
|
165
174
|
* });
|
|
@@ -204,5 +213,5 @@ interface NavigationOptions {
|
|
|
204
213
|
signal?: AbortSignal | undefined;
|
|
205
214
|
}
|
|
206
215
|
//#endregion
|
|
207
|
-
export { type ContextNamespaceClaim, type DefaultDependencies, type DefaultParamsCallback, type DefaultRouteCallback, type DependenciesApi, type ErrorCodeKeys, type ErrorCodeToValueMap, type ErrorCodeValues, type EventMethodMap, type EventName, type EventToNameMap, type EventToPluginMap, type EventsKeys, type ForwardToCallback, type GuardFn, type GuardFnFactory, type InterceptableMethodMap, type InterceptorFn, type LeaveFn, type LeaveState, type LifecycleApi, type LimitsConfig, type Listener, type LogCallback, type LogLevel, type LogLevelConfig, type LoggerConfig, NavigationOptions, type Navigator, type Options, type Params, type Plugin, type PluginApi, type PluginFactory, type PluginMethod, type QueryParamsMode, type QueryParamsOptions, type Route, type RouteConfigUpdate, type RouteParams, type RouteTreeState, type Router, type RouterError, type RouterLogger, type RoutesApi, type SerializedRouterState, type SimpleState, type State, StateContext, type
|
|
216
|
+
export { type AnyOptions, type ContextNamespaceClaim, type DefaultDependencies, type DefaultParamsCallback, type DefaultRouteCallback, type DefaultSearchCallback, type DependenciesApi, type ErrorCodeKeys, type ErrorCodeToValueMap, type ErrorCodeValues, type EventMethodMap, type EventName, type EventToNameMap, type EventToPluginMap, type EventsKeys, type ForwardToCallback, type GuardFn, type GuardFnFactory, type InterceptableMethodMap, type InterceptorFn, type LeaveFn, type LeaveState, type LifecycleApi, type LimitsConfig, type Listener, type LogCallback, type LogLevel, type LogLevelConfig, type LoggerConfig, NavigationOptions, type NavigationTarget, type Navigator, type Options, type Params, type ParamsSearch, type Plugin, type PluginApi, type PluginFactory, type PluginMethod, type QueryParamsMode, type QueryParamsOptions, type Route, type RouteConfigUpdate, type RouteParams, type RouteTreeState, type Router, type RouterError, type RouterLogger, type RoutesApi, type SearchParamPrimitive, type SearchParamValue, type SearchParams, type SerializedRouterState, type SimpleState, type State, StateContext, type SubscribeFn, type SubscribeState, type Subscription, type TransitionMeta, type TransitionPhase, type TransitionReason, type TreeChangedAdd, type TreeChangedClear, type TreeChangedEvent, type TreeChangedRemove, type TreeChangedReplace, type TreeChangedUpdate, type TreeStructuralPatch, type Unsubscribe };
|
|
208
217
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/cjs/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/types/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/types/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+DiB;;;;;;;;;;;;;;UAeA;;;;;;;;;;;;;;;;;EAiBf;;;;;;;;;;;;;;;EAgBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CA;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkCA;;;;;;;;;;;;;;;;;;;;;;;EAwBA;;;;;;;;;;;EAYA,SAAS"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventEmitter.js","names":["#callbacks","#dispatching","#onListenerError","#onListenerWarn","#limits","#warnedEvents","#invokeIsolated","#callListener"],"sources":["../../../../src/utils/event-emitter/EventEmitter.ts"],"sourcesContent":["import type {\n EventEmitterLimits,\n EventEmitterOptions,\n Unsubscribe,\n} from \"./types\";\n\nconst DEFAULT_LIMITS: EventEmitterLimits = {\n maxListeners: 0,\n warnListeners: 0,\n};\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\ntype AnyCallback = Function;\n\n/**\n * Generic typed event emitter with listener limits, duplicate detection,\n * re-entrancy coalescing, and per-listener error isolation.\n *\n * All limits are opt-in via constructor options.\n */\nexport class EventEmitter<TEventMap extends Record<string, unknown[]>> {\n readonly #callbacks = new Map<string, Set<AnyCallback>>();\n // Names currently being dispatched. A re-entrant `emit` of an event already\n // on this set is coalesced to a no-op (see `emit`), so an event can never\n // re-enter its own dispatch — recursion is structurally impossible (depth ≤ 1)\n // with no depth bound and no stack-overflow path (#1033).\n readonly #dispatching = new Set<string>();\n #warnedEvents: Set<string> | null = null;\n #limits: EventEmitterLimits = DEFAULT_LIMITS;\n readonly #onListenerError:\n ((eventName: string, error: unknown) => void) | null;\n readonly #onListenerWarn: ((eventName: string, count: number) => void) | null;\n\n constructor(options?: EventEmitterOptions) {\n if (options?.limits) {\n this.#limits = options.limits;\n }\n\n this.#onListenerError = options?.onListenerError ?? null;\n this.#onListenerWarn = options?.onListenerWarn ?? null;\n }\n\n /**\n * Validates that a callback is a function.\n */\n static validateCallback(\n cb: unknown,\n eventName: string,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n ): asserts cb is Function {\n if (typeof cb !== \"function\") {\n throw new TypeError(\n `Expected callback to be a function for event ${eventName}`,\n );\n }\n }\n\n /**\n * Replaces current limits with the provided limits.\n */\n setLimits(limits: EventEmitterLimits): void {\n this.#limits = limits;\n }\n\n /**\n * Adds an event listener and returns an unsubscribe function.\n * Throws on duplicate listeners or when maxListeners is reached.\n *\n * Registration is atomic (validate-before-mutate, #1358): every rejection\n * check runs against the CURRENT record (read once, never created early), the\n * advisory warn hook runs before any mutation, and the record is created +\n * the listener added only after all checks pass. So a throw — a rejected\n * limit, or a throwing `onListenerWarn` — leaves NO side-effect behind: no\n * orphaned empty record (#1167) and no burnt warn latch (#1168).\n */\n on<E extends keyof TEventMap & string>(\n eventName: E,\n cb: (...args: TEventMap[E]) => void,\n ): Unsubscribe {\n const existing = this.#callbacks.get(eventName);\n const size = existing?.size ?? 0;\n\n if (existing?.has(cb)) {\n throw new Error(`Duplicate listener for \"${eventName}\"`);\n }\n\n const { maxListeners, warnListeners } = this.#limits;\n\n // Enforce the hard limit before warning, so onListenerWarn never fires for\n // a registration that then throws (the warnListeners === maxListeners case).\n if (maxListeners !== 0 && size >= maxListeners) {\n throw new Error(\n `Listener limit (${maxListeners}) reached for \"${eventName}\"`,\n );\n }\n\n // Warn at most once per emitter+event, using the PRE-add size. The hook is\n // invoked first and the latch set only after it returns without throwing, so\n // a throwing hook fails the registration atomically and leaves the latch\n // unspent — the next (W+1)th registration warns as documented (#1168). The\n // latch keeps the advisory hint \"exactly once\" across off/on churn around\n // the threshold; reset by clearAll() or by removing the last listener.\n if (\n warnListeners !== 0 &&\n size === warnListeners &&\n this.#onListenerWarn !== null\n ) {\n this.#warnedEvents ??= new Set();\n\n if (!this.#warnedEvents.has(eventName)) {\n this.#onListenerWarn(eventName, warnListeners);\n this.#warnedEvents.add(eventName);\n }\n }\n\n // Mutate last — create the record only now, so a rejected registration\n // above never strands an empty record (#1167).\n let set = existing;\n\n if (set === undefined) {\n set = new Set();\n this.#callbacks.set(eventName, set);\n }\n\n set.add(cb);\n\n return () => {\n this.off(eventName, cb);\n };\n }\n\n /**\n * Removes an event listener.\n */\n off<E extends keyof TEventMap & string>(\n eventName: E,\n cb: (...args: TEventMap[E]) => void,\n ): void {\n const set = this.#callbacks.get(eventName);\n\n if (!set) {\n return;\n }\n\n set.delete(cb);\n\n if (set.size === 0) {\n // Release per-event records once the last listener is gone, so consumers\n // with dynamic event names don't accumulate empty Sets unbounded\n // (listenerCount stays 0 either way, masking the growth). See #750.\n this.#callbacks.delete(eventName);\n this.#warnedEvents?.delete(eventName);\n }\n }\n\n /**\n * Emits an event, calling all registered listeners with the provided args.\n *\n * Uses snapshot iteration — listeners added/removed during emit don't affect\n * the current invocation. Per-listener errors are caught and reported via the\n * `onListenerError` callback; other listeners still run.\n *\n * Re-entrant emit is coalesced: emitting an event that is already being\n * dispatched (a listener that synchronously re-emits the same event) is a\n * no-op, so dispatch never recurses into itself (#1033).\n *\n * Uses explicit params instead of rest params to avoid V8 array materialization.\n * Extra undefined args are harmless — JS functions ignore extra arguments.\n */\n emit(\n eventName: keyof TEventMap & string,\n arg1?: unknown,\n arg2?: unknown,\n arg3?: unknown,\n arg4?: unknown,\n ): void {\n const set = this.#callbacks.get(eventName);\n\n if (!set || set.size === 0) {\n return;\n }\n\n // Coalesce a re-entrant emit of an in-flight event (depth ≤ 1, #1033).\n if (this.#dispatching.has(eventName)) {\n return;\n }\n\n // arguments.length is O(1) in V8 strict mode — no deopt\n const argc = arguments.length - 1;\n\n this.#dispatching.add(eventName);\n\n try {\n // Single-listener fast path — skip the [...set] snapshot allocation.\n if (set.size === 1) {\n const [cb] = set;\n\n this.#invokeIsolated(eventName, cb, argc, arg1, arg2, arg3, arg4);\n } else {\n const listeners = [...set];\n\n for (const cb of listeners) {\n this.#invokeIsolated(eventName, cb, argc, arg1, arg2, arg3, arg4);\n }\n }\n } finally {\n this.#dispatching.delete(eventName);\n }\n }\n\n /**\n * Removes all listeners and resets the warn latch.\n *\n * Does NOT touch `#dispatching`: the in-flight coalesce guard is owned by the\n * active `emit` frame (added when dispatch starts, self-released in that\n * frame's `finally`). Clearing it here would lift the guard for a live frame\n * when `clearAll()` runs from inside a listener, so a re-entrant same-event\n * emit would no longer coalesce and would re-enter — violating the depth-≤-1\n * contract (#1164). The guard self-releases; `clearAll()` has no business\n * sweeping state owned by active emit frames.\n */\n clearAll(): void {\n this.#callbacks.clear();\n this.#warnedEvents = null;\n }\n\n /**\n * Returns the number of listeners for the given event.\n */\n listenerCount(eventName: keyof TEventMap & string): number {\n return this.#callbacks.get(eventName)?.size ?? 0;\n }\n\n /**\n * Returns whether the given event is currently being dispatched (an `emit`\n * for it is on the stack). Single source of truth for \"is this event\n * in-flight\" — consumers read it to reject re-entrant operations that would\n * trigger such an emit (the emit itself would be coalesced regardless).\n */\n isDispatching(eventName: keyof TEventMap & string): boolean {\n return this.#dispatching.has(eventName);\n }\n\n // ===========================================================================\n // Private methods\n // ===========================================================================\n\n /**\n * Calls a listener with the correct number of arguments.\n * Dispatches by argc to preserve exact call semantics.\n */\n #invokeIsolated(\n eventName: keyof TEventMap & string,\n cb: AnyCallback,\n argc: number,\n arg1: unknown,\n arg2: unknown,\n arg3: unknown,\n arg4: unknown,\n ): void {\n try {\n const result = this.#callListener(cb, argc, arg1, arg2, arg3, arg4);\n\n // A listener typed `=> void` may still return a Promise at runtime (an\n // async hook or any-cast misuse). The sync `catch` below cannot see its\n // rejection, so route it to the same `#onListenerError` sink — otherwise\n // it escapes as a Node `unhandledRejection` (fatal under\n // `--unhandled-rejections=strict`, the Node 22+ default). Centralised here\n // so every listener kind (plugin hooks, `subscribe`, …) is isolated\n // symmetrically (#1412; `subscribe`'s per-site #944 wrapper folds in).\n if (\n result !== null &&\n result !== undefined &&\n typeof (result as PromiseLike<unknown>).then === \"function\"\n ) {\n Promise.resolve(result as PromiseLike<unknown>).catch(\n (error: unknown) => {\n this.#onListenerError?.(eventName, error);\n },\n );\n }\n } catch (error) {\n this.#onListenerError?.(eventName, error);\n }\n }\n\n #callListener(\n cb: AnyCallback,\n argc: number,\n arg1: unknown,\n arg2: unknown,\n arg3: unknown,\n arg4: unknown,\n ): unknown {\n switch (argc) {\n case 0: {\n return (cb as () => unknown)();\n }\n case 1: {\n return (cb as (a: unknown) => unknown)(arg1);\n }\n case 2: {\n return (cb as (a: unknown, b: unknown) => unknown)(arg1, arg2);\n }\n case 3: {\n return (cb as (a: unknown, b: unknown, c: unknown) => unknown)(\n arg1,\n arg2,\n arg3,\n );\n }\n default: {\n return (\n cb as (a: unknown, b: unknown, c: unknown, d: unknown) => unknown\n )(arg1, arg2, arg3, arg4);\n }\n }\n }\n\n // (record creation is inlined into `on()` so a rejected registration never\n // creates one — see the atomicity note there, #1167/#1358.)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fsm.js","names":["#context","#transitions","#listeners","#state","#currentTransitions","#actions","#listenerCount"],"sources":["../../../../src/utils/fsm/fsm.ts"],"sourcesContent":["import type { FSMConfig, TransitionInfo, TransitionListener } from \"./types\";\n\n/**\n * Shared guard for the engine-wide invariant \"the state is declared in\n * `config.transitions`\". Applied at every state-entry-point (constructor\n * `initial` and `on`'s `from`) so an undeclared state fails loud with\n * an explicit error instead of bricking the FSM or dead-registering an action\n * (#885). Returns the state's transition map for the caller to reuse.\n */\nfunction requireDeclared<TStates extends string, TEvents extends string>(\n transitions: Record<TStates, Partial<Record<TEvents, TStates>>>,\n state: TStates,\n where: string,\n): Partial<Record<TEvents, TStates>> {\n const stateTransitions = transitions[state];\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for JS / cast / string-typed callers passing a state outside TStates\n if (stateTransitions === undefined) {\n throw new Error(\n `[FSM.${where}] state \"${state}\" is not declared in config.transitions`,\n );\n }\n\n return stateTransitions;\n}\n\n/**\n * Synchronous finite state machine engine.\n *\n * Reentrancy: `send()` inside `onTransition` listener is allowed but unbounded —\n * callers are responsible for preventing infinite loops.\n *\n * Exceptions: if a listener throws, the exception propagates to the caller.\n * State is already updated before listeners fire, so `getState()` reflects the\n * new state even if the exception escapes `send()`.\n */\nexport class FSM<\n TStates extends string,\n TEvents extends string,\n TContext,\n TPayloadMap extends Partial<Record<TEvents, unknown>> = Record<never, never>,\n> {\n #state: TStates;\n #currentTransitions: Partial<Record<TEvents, TStates>>;\n #listenerCount = 0;\n #actions: Map<TStates, Map<TEvents, (payload: unknown) => void>> | null =\n null;\n readonly #context: TContext;\n readonly #transitions: Record<TStates, Partial<Record<TEvents, TStates>>>;\n readonly #listeners: (TransitionListener<\n TStates,\n TEvents,\n TPayloadMap\n > | null)[] = [];\n\n constructor(config: FSMConfig<TStates, TEvents, TContext>) {\n this.#state = config.initial;\n this.#context = config.context;\n this.#transitions = config.transitions;\n this.#currentTransitions = requireDeclared(\n config.transitions,\n config.initial,\n \"constructor\",\n );\n\n // #1159: validate table closure — every declared transition target must\n // itself be a declared state. `send()` applies table values\n // (`this.#transitions[nextState]`) without re-checking, so a dangling\n // target would silently enter an undeclared state (violating Validity #1)\n // and brick `canSend()` (violating No-bricking #10). One cold-path\n // O(states×events) pass at construction fails loud instead — the fourth\n // state-entry-point, mirroring the `initial` / `on` guards. Explicit\n // `undefined` values are the declared \"no transition\" no-op (send() returns\n // the current state) and are skipped. Post-construction mutation of the\n // shared table stays a documented GIGO boundary (Edge #5).\n for (const state of Object.keys(config.transitions)) {\n const stateTransitions = config.transitions[state as TStates];\n\n for (const event of Object.keys(stateTransitions)) {\n const target = stateTransitions[event as TEvents];\n\n if (target !== undefined) {\n requireDeclared(config.transitions, target, \"constructor\");\n }\n }\n }\n }\n\n send<E extends TEvents>(\n event: E,\n ...args: E extends keyof TPayloadMap ? [TPayloadMap[E]] : [undefined?]\n ): TStates {\n const nextState = this.#currentTransitions[event];\n\n if (nextState === undefined) {\n return this.#state;\n }\n\n const from = this.#state;\n\n this.#state = nextState;\n this.#currentTransitions = this.#transitions[nextState];\n\n const payload = args[0] as TPayloadMap[TEvents] | undefined;\n\n if (this.#actions !== null) {\n const action = this.#actions.get(from)?.get(event);\n\n if (action !== undefined) {\n action(payload);\n }\n }\n\n // Stryker disable next-line ConditionalExpression: equivalent — count>0 is a perf gate to skip the dispatch loop; `true` always enters it, but with no live listener `#listeners` holds only null slots and the loop body guards `listener !== null`, so dispatch is a no-op either way. The EqualityOperator `<=0` sibling on this line stays killed (not silenced here).\n if (this.#listenerCount > 0) {\n // `info` is structurally a valid TransitionInfo, but the distributive\n // union can't be matched to one variant while `event`/`payload` are\n // generic here — erase through `unknown` (TS2352), same spirit as the\n // `args[0]` cast above.\n const info = {\n from,\n to: nextState,\n event,\n payload,\n } as unknown as TransitionInfo<TStates, TEvents, TPayloadMap>;\n\n for (const listener of this.#listeners) {\n if (listener !== null) {\n listener(info);\n }\n }\n }\n\n return this.#state;\n }\n\n canSend(event: TEvents): boolean {\n return this.#currentTransitions[event] !== undefined;\n }\n\n getState(): TStates {\n return this.#state;\n }\n\n getContext(): TContext {\n return this.#context;\n }\n\n on<E extends TEvents>(\n from: TStates,\n event: E,\n action: E extends keyof TPayloadMap\n ? (payload: TPayloadMap[E]) => void\n : () => void,\n ): () => void {\n requireDeclared(this.#transitions, from, \"on\");\n\n this.#actions ??= new Map();\n\n let stateActions = this.#actions.get(from);\n\n if (!stateActions) {\n stateActions = new Map();\n this.#actions.set(from, stateActions);\n }\n\n const capturedAction = action as (payload: unknown) => void;\n\n stateActions.set(event, capturedAction);\n\n return () => {\n // Stryker disable next-line OptionalChaining: equivalent — `#actions` is assigned (`??= new Map()` above) before this unsubscribe closure is created and returned, so it is never null when the closure runs; `?.` can't short-circuit and behaves identically to `.get`.\n const stateMap = this.#actions?.get(from);\n\n if (stateMap?.get(event) === capturedAction) {\n stateMap.delete(event);\n }\n };\n }\n\n onTransition(\n listener: (info: TransitionInfo<TStates, TEvents, TPayloadMap>) => void,\n ): () => void {\n const nullIndex = this.#listeners.indexOf(null);\n let index: number;\n\n if (nullIndex === -1) {\n index = this.#listeners.length;\n this.#listeners.push(listener);\n } else {\n this.#listeners[nullIndex] = listener;\n index = nullIndex;\n }\n\n this.#listenerCount++;\n let subscribed = true;\n\n return () => {\n if (!subscribed) {\n return;\n }\n\n subscribed = false;\n this.#listeners[index] = null;\n // Stryker disable next-line UpdateOperator: equivalent — #listenerCount feeds only the `> 0` loop gate; `++` inflates it but the loop then iterates already-nulled slots (no-op), and no public reader exposes the count, so the miscount is unobservable.\n this.#listenerCount--;\n };\n }\n}\n"],"mappings":"AASA,SAAS,EACP,EACA,EACA,EACmC,CACnC,IAAM,EAAmB,EAAY,GAGrC,GAAI,IAAqB,IAAA,GACvB,MAAU,MACR,QAAQ,EAAM,WAAW,EAAM,wCACjC,EAGF,OAAO,CACT,CAYA,IAAa,EAAb,KAKE,CACA,GACA,GACA,GAAiB,EACjB,GACE,KACF,GACA,GACA,GAIc,CAAC,EAEf,YAAY,EAA+C,CACzD,KAAKG,GAAS,EAAO,QACrB,KAAKH,GAAW,EAAO,QACvB,KAAKC,GAAe,EAAO,YAC3B,KAAKG,GAAsB,EACzB,EAAO,YACP,EAAO,QACP,aACF,EAYA,IAAK,IAAM,KAAS,OAAO,KAAK,EAAO,WAAW,EAAG,CACnD,IAAM,EAAmB,EAAO,YAAY,GAE5C,IAAK,IAAM,KAAS,OAAO,KAAK,CAAgB,EAAG,CACjD,IAAM,EAAS,EAAiB,GAE5B,IAAW,IAAA,IACb,EAAgB,EAAO,YAAa,EAAQ,aAAa,CAE7D,CACF,CACF,CAEA,KACE,EACA,GAAG,EACM,CACT,IAAM,EAAY,KAAKA,GAAoB,GAE3C,GAAI,IAAc,IAAA,GAChB,OAAO,KAAKD,GAGd,IAAM,EAAO,KAAKA,GAElB,KAAKA,GAAS,EACd,KAAKC,GAAsB,KAAKH,GAAa,GAE7C,IAAM,EAAU,EAAK,GAErB,GAAI,KAAKI,KAAa,KAAM,CAC1B,IAAM,EAAS,KAAKA,GAAS,IAAI,CAAI,CAAC,EAAE,IAAI,CAAK,EAE7C,IAAW,IAAA,IACb,EAAO,CAAO,CAElB,CAGA,GAAI,KAAKC,GAAiB,EAAG,CAK3B,IAAM,EAAO,CACX,OACA,GAAI,EACJ,QACA,SACF,EAEA,IAAK,IAAM,KAAY,KAAKJ,GACtB,IAAa,MACf,EAAS,CAAI,CAGnB,CAEA,OAAO,KAAKC,EACd,CAEA,QAAQ,EAAyB,CAC/B,OAAO,KAAKC,GAAoB,KAAW,IAAA,EAC7C,CAEA,UAAoB,CAClB,OAAO,KAAKD,EACd,CAEA,YAAuB,CACrB,OAAO,KAAKH,EACd,CAEA,GACE,EACA,EACA,EAGY,CACZ,EAAgB,KAAKC,GAAc,EAAM,IAAI,EAE7C,KAAKI,KAAa,IAAI,IAEtB,IAAI,EAAe,KAAKA,GAAS,IAAI,CAAI,EAEpC,IACH,EAAe,IAAI,IACnB,KAAKA,GAAS,IAAI,EAAM,CAAY,GAGtC,IAAM,EAAiB,EAIvB,OAFA,EAAa,IAAI,EAAO,CAAc,MAEzB,CAEX,IAAM,EAAW,KAAKA,IAAU,IAAI,CAAI,EAEpC,GAAU,IAAI,CAAK,IAAM,GAC3B,EAAS,OAAO,CAAK,CAEzB,CACF,CAEA,aACE,EACY,CACZ,IAAM,EAAY,KAAKH,GAAW,QAAQ,IAAI,EAC1C,EAEA,IAAc,IAChB,EAAQ,KAAKA,GAAW,OACxB,KAAKA,GAAW,KAAK,CAAQ,IAE7B,KAAKA,GAAW,GAAa,EAC7B,EAAQ,GAGV,KAAKI,KACL,IAAI,EAAa,GAEjB,UAAa,CACN,IAIL,EAAa,GACb,KAAKJ,GAAW,GAAS,KAEzB,KAAKI,KACP,CACF,CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RouterLogger.js","names":["#config","LEVEL_CONFIGS","#currentThreshold","#writeLog","LOG_LEVELS","#writeToConsole","#invokeCallback","#inCallback","#reportError"],"sources":["../../../../src/utils/logger/RouterLogger.ts"],"sourcesContent":["// packages/core/src/utils/logger/RouterLogger.ts\n\nimport { LOG_LEVELS, LEVEL_CONFIGS } from \"./constants\";\n\nimport type {\n LogLevel,\n LoggerConfig,\n LogLevelConfig,\n LogCallback,\n} from \"../../types\";\n\n/**\n * Internal config type with required callbackIgnoresLevel\n * (always initialized to false)\n */\ninterface InternalLoggerConfig {\n level: LogLevelConfig;\n callback?: LogCallback | undefined;\n callbackIgnoresLevel: boolean;\n}\n\n/**\n * Logger class for centralized logging with configurable levels and callbacks.\n *\n * Features:\n * - Three log levels: log, warn, error\n * - Configurable threshold filtering (all, warn-error, error-only, none)\n * - Optional callback for custom log processing\n * - Callback can optionally ignore level threshold\n * - Context-based message formatting\n *\n * @example\n * ```ts\n * import { logger } from './Logger';\n *\n * // Configure logger\n * logger.configure({ level: 'warn-error' });\n *\n * // Use logger\n * logger.log('Router', 'Navigation started'); // Won't show (below threshold)\n * logger.warn('Router', 'Deprecated API used'); // Will show\n * ```\n */\nexport class RouterLogger {\n /** Internal configuration storage using private field */\n readonly #config: InternalLoggerConfig = {\n level: \"all\",\n callbackIgnoresLevel: false,\n };\n\n /** Cached numeric threshold value for performance (avoids repeated lookups) */\n #currentThreshold = 0;\n\n /**\n * Re-entrancy guard: true while a user callback is executing. Prevents a\n * callback that itself calls `logger.*` from recursing back through\n * `#invokeCallback` (which would otherwise spin ~5.9k deep until a swallowed\n * RangeError, see #791). Console output is unaffected.\n */\n #inCallback = false;\n\n /**\n * @param config - Optional initial configuration (level / callback /\n * callbackIgnoresLevel), applied once at construction.\n *\n * Each router owns its own `RouterLogger` instance, built from\n * `options.logger` in the `Router` constructor. This replaces the former\n * process-global singleton whose `configure()` leaked across every router in\n * the process — the last `createRouter` won (#724).\n */\n constructor(config?: Partial<LoggerConfig>) {\n if (config) {\n this.configure(config);\n }\n }\n\n /**\n * Configures the logger with new settings.\n *\n * @param config - Partial configuration to merge with existing config\n * @param config.level - Minimum log level to output ('all' | 'warn-error' | 'error-only' | 'none')\n * @param config.callback - Optional callback function to receive log messages\n * @param config.callbackIgnoresLevel - If true, callback receives all messages regardless of level\n *\n * @example\n * ```ts\n * // Set minimum level to warnings\n * logger.configure({ level: 'warn-error' });\n *\n * // Add custom callback that ignores level\n * logger.configure({\n * callback: (level, context, message) => {\n * sendToAnalytics({ level, context, message });\n * },\n * callbackIgnoresLevel: true\n * });\n * ```\n */\n configure(config: Partial<LoggerConfig>): void {\n // Read each field ONCE into a local — an unstable getter must not be re-read\n // between validation and storage: re-reading could pass validation with a\n // valid level and then store a later, unvalidated one, disabling the\n // threshold filter (a TOCTOU, #1162).\n const level = config.level;\n\n if (level !== undefined) {\n // Validate that the provided level is a valid configuration level\n if (!Object.hasOwn(LEVEL_CONFIGS, level)) {\n throw new Error(\n `Invalid log level: \"${level}\". Valid levels are: ${Object.keys(LEVEL_CONFIGS).join(\", \")}`,\n );\n }\n\n this.#config.level = level;\n this.#currentThreshold = LEVEL_CONFIGS[level];\n }\n if (Object.hasOwn(config, \"callback\")) {\n this.#config.callback = config.callback;\n }\n\n const callbackIgnoresLevel = config.callbackIgnoresLevel;\n\n if (callbackIgnoresLevel !== undefined) {\n this.#config.callbackIgnoresLevel = callbackIgnoresLevel;\n }\n }\n\n /**\n * Returns the current logger configuration.\n *\n * @returns Current configuration object with level, callback, and callbackIgnoresLevel\n *\n * @example\n * ```ts\n * const config = logger.getConfig();\n * console.log(config.level); // 'warn'\n * console.log(config.callbackIgnoresLevel); // false\n * ```\n */\n getConfig(): LoggerConfig {\n return {\n level: this.#config.level,\n callback: this.#config.callback,\n callbackIgnoresLevel: this.#config.callbackIgnoresLevel,\n };\n }\n\n /**\n * Logs an informational message at 'log' level.\n *\n * This is the lowest severity level. Messages are shown when level is 'all'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Main log message\n * @param args - Additional arguments to log (objects, arrays, etc.)\n *\n * @example\n * ```ts\n * logger.log('Router', 'Navigation started', { from: '/home', to: '/about' });\n * // Output: [Router] Navigation started { from: '/home', to: '/about' }\n * ```\n */\n log(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"log\", context, message, args);\n }\n\n /**\n * Logs a warning message at 'warn' level.\n *\n * Use for deprecation notices, non-critical issues, or potential problems.\n * Messages are shown when level is 'all' or 'warn-error'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Warning message\n * @param args - Additional arguments to log\n *\n * @example\n * ```ts\n * logger.warn('Router', 'Using deprecated API', { method: 'oldNavigate' });\n * // Output: [Router] Using deprecated API { method: 'oldNavigate' }\n * ```\n */\n warn(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"warn\", context, message, args);\n }\n\n /**\n * Logs an error message at 'error' level.\n *\n * Use for critical errors, exceptions, or failures that require attention.\n * Messages are shown when level is 'all', 'warn-error', or 'error-only'.\n *\n * @param context - Context identifier (e.g., 'Router', 'Plugin')\n * @param message - Error message\n * @param args - Additional arguments to log (often error objects)\n *\n * @example\n * ```ts\n * logger.error('Router', 'Navigation failed', new Error('Route not found'));\n * // Output: [Router] Navigation failed Error: Route not found\n * ```\n */\n error(context: string, message: string, ...args: unknown[]): void {\n this.#writeLog(\"error\", context, message, args);\n }\n\n /**\n * Central logging method that coordinates console output and callback invocation.\n *\n * This method implements the core logging logic:\n * 1. Early exit optimization for 'none' level (unless callback ignores level)\n * 2. Level threshold comparison for console output filtering\n * 3. Delegates to #writeToConsole and #invokeCallback\n *\n * @param level - Log level ('log' | 'warn' | 'error')\n * @param context - Context identifier\n * @param message - Log message\n * @param args - Additional arguments\n *\n * @private\n */\n #writeLog(\n level: LogLevel,\n context: string,\n message: string,\n args: unknown[],\n ): void {\n // Early exit optimization: if level is 'none' and callback doesn't ignore level,\n // skip all processing (both console and callback)\n // Stryker disable next-line BlockStatement: equivalent — emptying this early-exit block falls through, but at level \"none\" the downstream guards already yield no output: #writeToConsole skips (threshold 3 > every message level) and #invokeCallback returns (this branch runs only when callbackIgnoresLevel is false). Pure perf shortcut; the ConditionalExpression →true sibling on this line stays killed (not silenced here).\n if (this.#config.level === \"none\" && !this.#config.callbackIgnoresLevel) {\n return;\n }\n\n // Convert message level to numeric value for threshold comparison\n // LOG_LEVELS: { log: 0, warn: 1, error: 2 }\n const messageLevelValue = LOG_LEVELS[level];\n\n // Determine if this message should skip console output\n // Example: if threshold is 'warn' (1), then 'log' messages (0) are skipped\n const shouldSkipConsole = messageLevelValue < this.#currentThreshold;\n\n // Console output (respects level threshold)\n if (!shouldSkipConsole) {\n this.#writeToConsole(level, context, message, args);\n }\n\n // Callback handling (may ignore level threshold based on config)\n this.#invokeCallback(level, context, message, shouldSkipConsole, args);\n }\n\n /**\n * Writes a formatted log message to the console.\n *\n * Features:\n * - Formats message with context: \"[Context] message\"\n * - Uses appropriate console method (log/warn/error)\n * - Safe: checks for console existence (for non-browser environments)\n *\n * @param level - Console method to use ('log' | 'warn' | 'error')\n * @param context - Context identifier (prepended to message if present)\n * @param message - Log message\n * @param args - Additional arguments to pass to console\n *\n * @private\n */\n #writeToConsole(\n level: LogLevel,\n context: string,\n message: string,\n args: unknown[],\n ): void {\n // Safety check: ensure console exists and has the required method\n // This is important for environments like Node.js tests or edge cases\n if (\n typeof console !== \"undefined\" &&\n typeof console[level] === \"function\"\n ) {\n // Format message with context bracket notation for visual clarity\n // Note: formatting is done inside the check to avoid unnecessary string allocation\n // when console is not available\n const formattedMessage = context ? `[${context}] ${message}` : message;\n\n console[level](formattedMessage, ...args);\n }\n }\n\n /**\n * Invokes the configured callback with log data, respecting level settings.\n *\n * Complex logic handling:\n * 1. Skip if no callback configured\n * 2. Skip if callback respects level AND message is below threshold\n * 3. Call callback with error handling (prevents callback errors from breaking logger)\n *\n * The callbackIgnoresLevel flag enables two modes:\n * - false (default): callback only receives messages that pass threshold (same as console)\n * - true: callback receives ALL messages regardless of threshold (useful for analytics)\n *\n * @param level - Log level\n * @param context - Context identifier\n * @param message - Log message\n * @param shouldSkipConsole - Whether console output was skipped (used for level logic)\n * @param args - Additional arguments\n *\n * @private\n */\n #invokeCallback(\n level: LogLevel,\n context: string,\n message: string,\n shouldSkipConsole: boolean,\n args: unknown[],\n ): void {\n // Early exit: no callback configured, or callback respects level and message is filtered\n if (\n !this.#config.callback ||\n (!this.#config.callbackIgnoresLevel && shouldSkipConsole)\n ) {\n return;\n }\n\n // Re-entrancy guard: a callback calling logger.* re-enters here via\n // #writeLog → #invokeCallback. Skip the nested invocation so the pattern is\n // a safe no-op (console output already happened in #writeLog) instead of\n // recursing to a swallowed RangeError (#791).\n if (this.#inCallback) {\n return;\n }\n\n // Wrap callback invocation in try-catch to prevent user code errors\n // from breaking the logger or causing cascading failures\n this.#inCallback = true;\n try {\n // An async callback (`(...) => Promise<void>` is assignable to the\n // void-typed LogCallback) returns a Promise whose rejection would otherwise\n // leak as a Node `unhandledRejection` — process-fatal under\n // `--unhandled-rejections=strict` (Node 22+ default). Read the runtime\n // return and isolate it like core's subscribe (#944): duck-check the\n // thenable + `.catch` into the same console.error sink a sync throw uses\n // (#1161).\n // eslint-disable-next-line @typescript-eslint/no-confusing-void-expression -- read the runtime Promise of a void-typed async callback (#1161)\n const result: unknown = this.#config.callback(\n level,\n context,\n message,\n ...args,\n );\n\n if (\n result !== null &&\n result !== undefined &&\n typeof (result as PromiseLike<unknown>).then === \"function\"\n ) {\n Promise.resolve(result as PromiseLike<unknown>).catch(\n (error: unknown) => {\n this.#reportError(\"[Logger] Error in async callback:\", error);\n },\n );\n }\n } catch (error) {\n // Fallback error reporting if the callback throws synchronously\n this.#reportError(\"[Logger] Error in callback:\", error);\n } finally {\n this.#inCallback = false;\n }\n }\n\n // Report a callback error via console.error directly — never call the logger\n // (would recurse). Shared by the sync-throw catch and the async-rejection\n // `.catch` (#1161). Console-safety guard mirrors #writeToConsole.\n #reportError(message: string, error: unknown): void {\n if (typeof console !== \"undefined\" && typeof console.error === \"function\") {\n console.error(message, error);\n }\n }\n}\n"],"mappings":"kCA2CA,IAAa,EAAb,KAA0B,CAExB,GAAyC,CACvC,MAAO,MACP,qBAAsB,EACxB,EAGA,GAAoB,EAQpB,GAAc,GAWd,YAAY,EAAgC,CACtC,GACF,KAAK,UAAU,CAAM,CAEzB,CAwBA,UAAU,EAAqC,CAK7C,IAAM,EAAQ,EAAO,MAErB,GAAI,IAAU,IAAA,GAAW,CAEvB,GAAI,CAAC,OAAO,OAAOC,EAAAA,cAAe,CAAK,EACrC,MAAU,MACR,uBAAuB,EAAM,uBAAuB,OAAO,KAAKA,EAAAA,aAAa,CAAC,CAAC,KAAK,IAAI,GAC1F,EAGF,KAAKD,GAAQ,MAAQ,EACrB,KAAKE,GAAoBD,EAAAA,cAAc,EACzC,CACI,OAAO,OAAO,EAAQ,UAAU,IAClC,KAAKD,GAAQ,SAAW,EAAO,UAGjC,IAAM,EAAuB,EAAO,qBAEhC,IAAyB,IAAA,KAC3B,KAAKA,GAAQ,qBAAuB,EAExC,CAcA,WAA0B,CACxB,MAAO,CACL,MAAO,KAAKA,GAAQ,MACpB,SAAU,KAAKA,GAAQ,SACvB,qBAAsB,KAAKA,GAAQ,oBACrC,CACF,CAiBA,IAAI,EAAiB,EAAiB,GAAG,EAAuB,CAC9D,KAAKG,GAAU,MAAO,EAAS,EAAS,CAAI,CAC9C,CAkBA,KAAK,EAAiB,EAAiB,GAAG,EAAuB,CAC/D,KAAKA,GAAU,OAAQ,EAAS,EAAS,CAAI,CAC/C,CAkBA,MAAM,EAAiB,EAAiB,GAAG,EAAuB,CAChE,KAAKA,GAAU,QAAS,EAAS,EAAS,CAAI,CAChD,CAiBA,GACE,EACA,EACA,EACA,EACM,CAIN,GAAI,KAAKH,GAAQ,QAAU,QAAU,CAAC,KAAKA,GAAQ,qBACjD,OASF,IAAM,EAJoBI,EAAAA,WAAW,GAIS,KAAKF,GAG9C,GACH,KAAKG,GAAgB,EAAO,EAAS,EAAS,CAAI,EAIpD,KAAKC,GAAgB,EAAO,EAAS,EAAS,EAAmB,CAAI,CACvE,CAiBA,GACE,EACA,EACA,EACA,EACM,CAGN,GACE,OAAO,QAAY,KACnB,OAAO,QAAQ,IAAW,WAC1B,CAIA,IAAM,EAAmB,EAAU,IAAI,EAAQ,IAAI,IAAY,EAE/D,QAAQ,EAAM,CAAC,EAAkB,GAAG,CAAI,CAC1C,CACF,CAsBA,GACE,EACA,EACA,EACA,EACA,EACM,CAGJ,MAAC,KAAKN,GAAQ,UACb,CAAC,KAAKA,GAAQ,sBAAwB,IASrC,MAAKO,GAMT,MAAKA,GAAc,GACnB,GAAI,CASF,IAAM,EAAkB,KAAKP,GAAQ,SACnC,EACA,EACA,EACA,GAAG,CACL,EAGE,GAAW,MAEX,OAAQ,EAAgC,MAAS,YAEjD,QAAQ,QAAQ,CAA8B,CAAC,CAAC,MAC7C,GAAmB,CAClB,KAAKQ,GAAa,oCAAqC,CAAK,CAC9D,CACF,CAEJ,OAAS,EAAO,CAEd,KAAKA,GAAa,8BAA+B,CAAK,CACxD,QAAU,CACR,KAAKD,GAAc,EACrB,CAjCmB,CAkCrB,CAKA,GAAa,EAAiB,EAAsB,CAC9C,OAAO,QAAY,KAAe,OAAO,QAAQ,OAAU,YAC7D,QAAQ,MAAM,EAAS,CAAK,CAEhC,CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../../../../src/utils/logger/constants.ts"],"sourcesContent":["import type { LogLevel, LogLevelConfig } from \"../../types\";\n\n/**\n * Numeric mapping for log message severity levels.\n *\n * Maps each severity level to a numeric value for threshold comparison.\n * Lower values = lower severity, higher values = higher severity.\n *\n * Used internally to determine if a message should be filtered based on\n * the configured threshold level.\n *\n * Mapping:\n * - `log`: 0 (lowest severity - informational)\n * - `warn`: 1 (medium severity - warnings)\n * - `error`: 2 (highest severity - critical errors)\n *\n * @example\n * ```ts\n * const messageLevel = LOG_LEVELS['warn']; // 1\n * const threshold = 2; // error-only\n * const shouldFilter = messageLevel < threshold; // true (warn is filtered)\n * ```\n *\n * @internal This is used for internal filtering logic\n */\nexport const LOG_LEVELS: Record<LogLevel, number> = Object.freeze({\n log: 0,\n warn: 1,\n error: 2,\n});\n\n/**\n * Numeric thresholds for logger configuration levels.\n *\n * Maps each configuration level to a minimum threshold value.\n * Messages with a severity level below this threshold are filtered out.\n *\n * Threshold logic:\n * - A message is shown if: `LOG_LEVELS[messageLevel] >= LEVEL_CONFIGS[configLevel]`\n * - Higher threshold value = stricter filtering = fewer messages shown\n *\n * Mapping:\n * - `all`: 0 (no filtering - show everything)\n * - Shows: log (0), warn (1), error (2) ✓\n * - `warn-error`: 1 (filter log messages)\n * - Shows: warn (1), error (2) ✓\n * - Filters: log (0) ✗\n * - `error-only`: 2 (filter log and warn messages)\n * - Shows: error (2) ✓\n * - Filters: log (0), warn (1) ✗\n * - `none`: 3 (filter all messages - complete silence)\n * - Filters: log (0), warn (1), error (2) ✗\n *\n * @example\n * ```ts\n * // Configuration: warn-error\n * const threshold = LEVEL_CONFIGS['warn-error']; // 1\n *\n * // Check if 'log' message should be shown\n * LOG_LEVELS['log'] >= threshold // 0 >= 1 = false (filtered)\n *\n * // Check if 'warn' message should be shown\n * LOG_LEVELS['warn'] >= threshold // 1 >= 1 = true (shown)\n *\n * // Check if 'error' message should be shown\n * LOG_LEVELS['error'] >= threshold // 2 >= 1 = true (shown)\n * ```\n *\n * @internal This is used for internal threshold comparison\n */\nexport const LEVEL_CONFIGS: Record<LogLevelConfig, number> = Object.freeze({\n all: 0,\n \"warn-error\": 1,\n \"error-only\": 2,\n none: 3,\n});\n"],"mappings":"AAyBA,MAAa,EAAuC,OAAO,OAAO,CAChE,IAAK,EACL,KAAM,EACN,MAAO,CACT,CAAC,EAyCY,EAAgD,OAAO,OAAO,CACzE,IAAK,EACL,aAAc,EACd,aAAc,EACd,KAAM,CACR,CAAC"}
|
package/dist/cjs/validation.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./buildParamMeta-0m11OIJS.js");function t(e,t){return TypeError(`[router.${e}] ${t}`)}function n(e,n,r,i){let a=new Set;for(let o of e){if(a.has(o))throw t(r,`Invalid path for route "${n}": duplicate parameter name ':${o}' in "${i}" (a param name must be unique within a route — the second binding would overwrite the first)`);a.add(o)}}function r(n,r,i,a){for(let o of n)if(e.n.test(o))throw t(i,`Invalid path for route "${r}": invalid query-param name "${o}" in "${a}" (a query-param name cannot contain '<' or '>' — it would never round-trip; rename the query param)`)}function i(e){let t=!0,n=!1;for(let r of e){if(r===`/`){t=!0;continue}if(t&&=(n=r===`:`||r===`*`,!1),!n&&r.codePointAt(0)>=128)return!0}return!1}function a(e,t,n){switch(e){case`name-less`:case`trailing-marker`:return`Invalid path for route "${t}": parameter marker (':' or '*') without a name in "${n}"`;case`fused-marker`:return`Invalid path for route "${t}": parameter marker (':' or '*') must begin a segment, but "${n}" fuses one to a static prefix (use a boundary marker like "/a/:b")`}}function o(e,t){return e.code===`optional-removed`?`Invalid path for route "${t}": optional params are not supported — "${e.segment}". Declare two sibling routes instead: "${e.withoutSegment}" and "${e.requiredForm}" (the route hierarchy already expresses optionality)`:`Invalid path for route "${t}": regex constraints are not supported — '<' and '>' are reserved in path segments ("${e.segment}"). Match the segment as a plain string and validate the value in a guard (canActivate) or app code`}function s(s,c,l,u){if(typeof s!=`string`){let e;throw e=s===null?`null`:Array.isArray(s)?`array`:typeof s,t(l,`Route path must be a string, got ${e}`)}if(s===``)return;if(/\s/.test(s))throw t(l,`Invalid path for route "${c}": whitespace not allowed in "${s}"`);if(!/^([/?~]|[^/]+$)/.test(s))throw t(l,`Route "${c}" has invalid path format: "${s}". Path should start with '/', '~', '?' or be a relative segment.`);if(s.includes(`//`))throw t(l,`Invalid path for route "${c}": double slashes not allowed in "${s}"`);let{pathPattern:d,urlParams:f,queryParams:p}=e.r(s);n(f,c,l,s),r(p,c,l,s);let m=e.i(d);if(m!==void 0)throw t(l,o(m,c));let h=e.a(d);if(h!==void 0)throw t(l,a(h,c,s));if(i(d))throw t(l,`Invalid path for route "${c}": non-ASCII static segment in "${s}" — match compares static segments raw and rejects non-ASCII input, so this route would never match. Percent-encode it (e.g. '/caf%C3%A9') or use a param`);let g=u&&u.paramMeta.urlParams.length>0;if(s.startsWith(`~`)&&g)throw t(l,`Absolute path "${s}" cannot be used under parent route with URL parameters`)}const c=/^[A-Z_a-z][\w-]*$/,l=/\S/,u=1e4;function d(e){if(e===null)return`null`;if(typeof e==`object`){try{let t=e.constructor;if(typeof t==`function`&&t.name!==`Object`)return t.name||`object`}catch{}return`object`}return typeof e}function f(e){for(let t of Object.keys(e)){let n=Object.getOwnPropertyDescriptor(e,t);if(n&&(n.get||n.set))return!0}return!1}function p(e,t){if(!e||typeof e!=`object`)throw TypeError(`[router.${t}] Route must be an object, got ${d(e)}`);let n=Object.getPrototypeOf(e);if(n!==Object.prototype&&n!==null)throw TypeError(`[router.${t}] Route must be a plain object, got ${d(e)}`);if(f(e))throw TypeError(`[router.${t}] Route must not have getters or setters`)}function m(e,t){if(e.encodeParams!==void 0&&typeof e.encodeParams!=`function`)throw TypeError(`[router.${t}] Route "${String(e.name)}" encodeParams must be a function`)}function h(e,t){if(e.decodeParams!==void 0&&typeof e.decodeParams!=`function`)throw TypeError(`[router.${t}] Route "${String(e.name)}" decodeParams must be a function`)}function g(e,t){if(typeof e.name!=`string`)throw TypeError(`[router.${t}] Route name must be a string, got ${d(e.name)}`);let n=e.name;if(n===``)throw TypeError(`[router.${t}] Route name cannot be empty`);if(!l.test(n))throw TypeError(`[router.${t}] Route name cannot contain only whitespace`);if(n.length>u)throw TypeError(`[router.${t}] Route name exceeds maximum length of ${u} characters`);if(!n.startsWith(`@@`)){if(n.includes(`.`))throw TypeError(`[router.${t}] Route name "${n}" cannot contain dots. Use children array or { parent } option in addRoute() instead.`);if(!c.test(n))throw TypeError(`[router.${t}] Invalid route name "${n}". Name must start with a letter or underscore, followed by letters, numbers, underscores, or hyphens.`)}}function _(e,t){if(!t.includes(`.`))return e.children.get(t);let n=e;for(let e of t.split(`.`))if(n=n.children.get(e),!n)return;return n}function v(e,t,n){if(_(e,t))throw Error(`[router.${n}] Route "${t}" already exists`)}function y(e,t,n){if(e.has(t))throw Error(`[router.${n}] Duplicate route "${t}" in batch`);e.add(t)}function b(e,t,n,r){let i=t===``?e:_(e,t);if(i){for(let e of i.children.values())if(e.path===n)throw Error(`[router.${r}] Path "${n}" is already defined`)}}function x(e,t,n,r){let i=e.get(t);if(i?.has(n))throw Error(`[router.${r}] Path "${n}" is already defined`);i?i.add(n):e.set(t,new Set([n]))}function S(e,t,n,r=``,i,a){p(e,t);let o=e;g(o,t),s(o.path,o.name,t,n),m(o,t),h(o,t);let c=o.name,l=r?`${r}.${c}`:c;n&&l&&v(n,l,t),i&&y(i,l,t);let u=o.path,f=r;if(n&&b(n,f,u,t),a&&x(a,f,u,t),o.children!==void 0){if(!Array.isArray(o.children))throw TypeError(`[router.${t}] Route "${c}" children must be an array, got ${d(o.children)}`);for(let e of o.children)S(e,t,n,l,i,a)}}exports.getInternals=e.u,exports.validateRoute=S;
|
|
2
|
+
//# sourceMappingURL=validation.js.map
|