UNPKG

@riogz/router

Version:

A simple, lightweight, powerful, view-agnostic, modular and extensible router

330 lines (329 loc) 15.1 kB
import { TrailingSlashMode, QueryParamsMode, QueryParamsOptions, RouteNode, RouteNodeState, URLParamsEncodingType } from '../lib/route-node'; import { State, SimpleState, Params, DoneFn, NavigationOptions, Unsubscribe, CancelFn } from './base'; /** * Route definition interface that describes a single route in the application. * * @template Dependencies - Type of dependencies available to route handlers */ export interface Route<Dependencies extends DefaultDependencies = DefaultDependencies> { /** Unique name identifier for the route */ name: string; /** URL path pattern with optional parameters (e.g., '/users/:id') */ path: string; /** Browser title for the route - can be static string or dynamic function */ browserTitle?: string | ((state: State, deps: Dependencies) => Promise<string>); /** Guard function to control route activation */ canActivate?: ActivationFnFactory<Dependencies>; /** Guard function to control route deactivation */ canDeactivate?: ActivationFnFactory<Dependencies>; /** Route name to forward to instead of rendering this route */ forwardTo?: string; /** Automatically redirect to the first accessible child route when this route is accessed */ redirectToFirstAllowNode?: boolean; /** Child routes nested under this route */ children?: Array<Route<Dependencies>> | RouteNode; /** Function to encode route parameters before building URLs */ encodeParams?: (params: Params) => Params; /** Function to decode route parameters after parsing URLs */ decodeParams?: (params: Params) => Params; /** Default parameter values for this route */ defaultParams?: Params; /** Lifecycle hook called when entering this route */ onEnterNode?: (toState: State, fromState: State | null, deps: Dependencies) => Promise<void>; /** Lifecycle hook called when exiting this route */ onExitNode?: (toState: State | null, fromState: State, deps: Dependencies) => Promise<void>; /** Lifecycle hook called when this route is in the active chain */ onNodeInActiveChain?: (toState: State, fromState: State | null, deps: Dependencies) => Promise<void>; } /** * Configuration options for router behavior and features. */ export interface Options { /** Default route to navigate to when no route is specified */ defaultRoute?: string; /** Default parameter values applied to all routes */ defaultParams?: Params; /** Whether to enforce strict trailing slash matching */ strictTrailingSlash: boolean; /** How to handle trailing slashes in URLs */ trailingSlashMode: TrailingSlashMode; /** How to handle query parameters */ queryParamsMode: QueryParamsMode; /** Whether to automatically clean up event listeners on stop */ autoCleanUp: boolean; /** Whether to allow navigation to non-existent routes */ allowNotFound: boolean; /** Whether to use strong matching for route parameters */ strongMatching: boolean; /** Whether to rewrite the path when a route matches */ rewritePathOnMatch: boolean; /** Configuration for query parameter handling */ queryParams?: QueryParamsOptions; /** Whether route matching is case sensitive */ caseSensitive: boolean; /** How to encode URL parameters */ urlParamsEncoding?: URLParamsEncodingType; } /** * Function signature for route activation guards. * * @param toState - The state being navigated to * @param fromState - The current state being navigated from * @param done - Callback to signal completion or error * @returns Boolean indicating if navigation should proceed, Promise for async checks, or void */ export type ActivationFn = (toState: State, fromState: State, done: DoneFn) => boolean | Promise<boolean> | void; /** * Factory function that creates activation functions with access to router and dependencies. * * @template Dependencies - Type of dependencies available to the factory * @param router - Router instance * @param dependencies - Injected dependencies * @returns An activation function */ export type ActivationFnFactory<Dependencies extends DefaultDependencies = DefaultDependencies> = (router: Router<Dependencies>, dependencies?: Dependencies) => ActivationFn; /** * Default type for dependency injection - allows any key-value pairs. */ export type DefaultDependencies = Record<string, any>; /** * Internal configuration object for router state management. */ export interface Config { /** Parameter decoders for route parameters */ decoders: Record<string, any>; /** Parameter encoders for route parameters */ encoders: Record<string, any>; /** Default parameter values */ defaultParams: Record<string, any>; /** Route forwarding mappings */ forwardMap: Record<string, any>; /** Routes that should redirect to first accessible child */ redirectToFirstAllowNodeMap?: Record<string, boolean>; } /** * Main router interface providing all routing functionality. * * @template Dependencies - Type of dependencies injected into the router */ export interface Router<Dependencies extends DefaultDependencies = DefaultDependencies> { /** Internal configuration object */ config: Config; /** Root node of the route tree */ rootNode: RouteNode; /** * Add routes to the router * @param routes - Route definitions to add * @param finalSort - Whether to perform final sorting after adding * @returns Router instance for chaining */ add(routes: Array<Route<Dependencies>> | Route<Dependencies>, finalSort?: boolean): Router<Dependencies>; /** * Add a single route node programmatically * @param name - Route name * @param path - Route path pattern * @param canActivateHandler - Optional activation guard * @returns Router instance for chaining */ addNode(name: string, path: string, canActivateHandler?: ActivationFnFactory<Dependencies>): Router<Dependencies>; /** * Check if a route is currently active * @param name - Route name to check * @param params - Route parameters to match * @param strictEquality - Whether to use strict parameter matching * @param ignoreQueryParams - Whether to ignore query parameters in comparison * @returns True if the route is active */ isActive(name: string, params?: Params, strictEquality?: boolean, ignoreQueryParams?: boolean): boolean; /** * Build a URL path for a route * @param route - Route name * @param params - Route parameters * @returns Built URL path */ buildPath(route: string, params?: Params): string; /** * Removes a route node and all its children from the router. * This includes cleaning up associated route guards, lifecycle hooks, and other configurations. * * @param name - The full name of the route node to remove (e.g., 'users.profile'). * @returns The router instance for chaining. */ removeNode(name: string): Router<Dependencies>; /** * Match a URL path to a route and return the resulting state * @param path - URL path to match * @param source - Source of the path (for debugging) * @returns Matched state or null if no match */ matchPath(path: string, source?: string): State | null; /** * Set the root path for the router * @param rootPath - Root path to set */ setRootPath(rootPath: string): void; getOptions(): Options; setOption(option: string, value: any): Router<Dependencies>; makeState(name: string, params?: Params, path?: string, meta?: any, forceId?: number): State; makeNotFoundState(path: string, options?: NavigationOptions): State; getState(): State; setState(state: State): void; areStatesEqual(state1: State, state2: State, ignoreQueryParams?: boolean): boolean; areStatesDescendants(parentState: State, childState: State): boolean; forwardState(routeName: string, routeParams: Params): SimpleState; buildState(routeName: string, routeParams: Params): RouteNodeState | null; isStarted(): boolean; start(startPathOrState: string | State, done?: DoneFn): Router<Dependencies>; start(done?: DoneFn): Router<Dependencies>; stop(): void; canDeactivate(name: string, canDeactivateHandler: ActivationFnFactory<Dependencies> | boolean): Router<Dependencies>; clearCanDeactivate(name: string): Router; canActivate(name: string, canActivateHandler: ActivationFnFactory<Dependencies> | boolean): Router<Dependencies>; getLifecycleFactories(): [ { [key: string]: ActivationFnFactory<Dependencies>; }, { [key: string]: ActivationFnFactory<Dependencies>; } ]; getLifecycleFunctions(): [ { [key: string]: ActivationFn; }, { [key: string]: ActivationFn; } ]; getRouteLifecycleFactories(): { onEnterNode: { [key: string]: (state: State, fromState: State) => Promise<void>; }; onExitNode: { [key: string]: (state: State, fromState: State) => Promise<void>; }; onNodeInActiveChain: { [key: string]: (state: State, fromState: State) => Promise<void>; }; }; getRouteLifecycleFunctions(): { onEnterNode: { [key: string]: (state: State, fromState: State) => Promise<void>; }; onExitNode: { [key: string]: (state: State, fromState: State) => Promise<void>; }; onNodeInActiveChain: { [key: string]: (state: State, fromState: State) => Promise<void>; }; }; getBrowserTitleFunctions(): { [key: string]: string | ((state: State) => Promise<string>); }; registerOnEnterNode(name: string, handler: (state: State, fromState: State, deps: Dependencies) => Promise<void>): Router<Dependencies>; registerOnExitNode(name: string, handler: (state: State, fromState: State, deps: Dependencies) => Promise<void>): Router<Dependencies>; registerOnNodeInActiveChain(name: string, handler: (state: State, fromState: State, deps: Dependencies) => Promise<void>): Router<Dependencies>; registerBrowserTitle(name: string, handler: string | ((state: State, deps: Dependencies) => Promise<string>)): Router<Dependencies>; findFirstAccessibleChild(routeName: string, params?: any): Promise<string | null>; usePlugin(...plugins: Array<PluginFactory<Dependencies>>): Unsubscribe; addPlugin(plugin: Plugin): Router<Dependencies>; getPlugins(): Array<PluginFactory<Dependencies>>; useMiddleware(...middlewares: Array<MiddlewareFactory<Dependencies>>): Unsubscribe; clearMiddleware(): Router; getMiddlewareFactories: () => Array<MiddlewareFactory<Dependencies>>; getMiddlewareFunctions: () => Middleware[]; setDependency(dependencyName: string, dependency: any): Router; setDependencies(deps: Dependencies): Router; getDependencies(): Dependencies; getInjectables(): [Router<Dependencies>, Dependencies]; executeFactory(factory: (router?: Router<Dependencies>, dependencies?: Dependencies) => any): any; invokeEventListeners: (eventName: any, ...args: any[]) => void; removeEventListener: (eventName: any, cb: any) => void; addEventListener: (eventName: any, cb: any) => Unsubscribe; cancel(): Router<Dependencies>; forward(fromRoute: string, toRoute: string): Router<Dependencies>; navigate(routeName: string, routeParams: Params, options: NavigationOptions, done?: DoneFn): CancelFn; navigate(routeName: string, routeParams: Params, done?: DoneFn): CancelFn; navigate(routeName: string, done?: DoneFn): CancelFn; navigateToDefault(opts: NavigationOptions, done?: DoneFn): CancelFn; navigateToDefault(done?: DoneFn): CancelFn; transitionToState(toState: State, fromState: State, opts: NavigationOptions, done: DoneFn): any; subscribe(listener: SubscribeFn | Listener): Unsubscribe | Subscription; } /** * Plugin interface for extending router functionality with lifecycle hooks. */ export interface Plugin { /** Called when the router starts */ onStart?(): void; /** Called when the router stops */ onStop?(): void; /** Called when a transition starts */ onTransitionStart?(toState?: State, fromState?: State): void; /** Called when a transition is cancelled */ onTransitionCancel?(toState?: State, fromState?: State): void; /** Called when a transition encounters an error */ onTransitionError?(toState?: State, fromState?: State, err?: any): void; /** Called when a transition completes successfully */ onTransitionSuccess?(toState?: State, fromState?: State, opts?: NavigationOptions): void; /** Called when the plugin is being removed/destroyed */ teardown?(): void; } /** * Middleware function signature for intercepting and controlling route transitions. * * @param toState - The state being navigated to * @param fromState - The current state being navigated from * @param done - Callback to signal completion or error * @returns Boolean indicating if transition should proceed, Promise for async operations, or void */ export type Middleware = (toState: State, fromState: State, done: DoneFn) => boolean | Promise<any> | void; /** * Factory function that creates middleware with access to router and dependencies. * * @template Dependencies - Type of dependencies available to the factory * @param router - Router instance * @param dependencies - Injected dependencies * @returns A middleware function */ export type MiddlewareFactory<Dependencies extends DefaultDependencies = DefaultDependencies> = (router: Router, dependencies: Dependencies) => Middleware; /** * Factory function that creates plugins with access to router and dependencies. * * @template Dependencies - Type of dependencies available to the factory * @param router - Router instance (optional) * @param dependencies - Injected dependencies (optional) * @returns A plugin instance */ export type PluginFactory<Dependencies extends DefaultDependencies = DefaultDependencies> = (router?: Router, dependencies?: Dependencies) => Plugin; /** * State object passed to subscription callbacks containing current and previous routes. */ export interface SubscribeState { /** Current active route state */ route: State; /** Previous route state */ previousRoute: State; } /** * Subscription callback function signature. * * @param state - Object containing current and previous route states */ export type SubscribeFn = (state: SubscribeState) => void; /** * Observable listener interface for RxJS-style subscriptions. */ export interface Listener { /** Method called with new values */ next: (val: any) => void; /** Additional properties allowed */ [key: string]: any; } /** * Subscription object returned from subscribe operations. */ export interface Subscription { /** Method to unsubscribe from updates */ unsubscribe: Unsubscribe; }