| declare const NODE_TYPES: { | ||
| NORMAL: 0; | ||
| WILDCARD: 1; | ||
| PLACEHOLDER: 2; | ||
| }; | ||
| type _NODE_TYPES = typeof NODE_TYPES; | ||
| type NODE_TYPE = _NODE_TYPES[keyof _NODE_TYPES]; | ||
| type _RadixNodeDataObject = { | ||
| params?: never; | ||
| [key: string]: any; | ||
| }; | ||
| type RadixNodeData<T extends _RadixNodeDataObject = _RadixNodeDataObject> = T; | ||
| type MatchedRoute<T extends RadixNodeData = RadixNodeData> = Omit<T, "params"> & { | ||
| params?: Record<string, any>; | ||
| }; | ||
| interface RadixNode<T extends RadixNodeData = RadixNodeData> { | ||
| type: NODE_TYPE; | ||
| parent: RadixNode<T> | null; | ||
| children: Map<string, RadixNode<T>>; | ||
| data: RadixNodeData | null; | ||
| paramName: string | null; | ||
| wildcardChildNode: RadixNode<T> | null; | ||
| placeholderChildNode: RadixNode<T> | null; | ||
| } | ||
| interface RadixRouterOptions { | ||
| strictTrailingSlash?: boolean; | ||
| routes?: Record<string, any>; | ||
| } | ||
| interface RadixRouterContext<T extends RadixNodeData = RadixNodeData> { | ||
| options: RadixRouterOptions; | ||
| rootNode: RadixNode<T>; | ||
| staticRoutesMap: Record<string, RadixNode>; | ||
| } | ||
| interface RadixRouter<T extends RadixNodeData = RadixNodeData> { | ||
| ctx: RadixRouterContext<T>; | ||
| /** | ||
| * Perform lookup of given path in radix tree | ||
| * @param path - the path to search for | ||
| * | ||
| * @returns The data that was originally inserted into the tree | ||
| */ | ||
| lookup(path: string): MatchedRoute<T> | null; | ||
| /** | ||
| * Perform an insert into the radix tree | ||
| * @param path - the prefix to match | ||
| * @param data - the associated data to path | ||
| * | ||
| */ | ||
| insert(path: string, data: T): void; | ||
| /** | ||
| * Perform a remove on the tree | ||
| * @param { string } data.path - the route to match | ||
| * | ||
| * @returns A boolean signifying if the remove was successful or not | ||
| */ | ||
| remove(path: string): boolean; | ||
| } | ||
| interface MatcherExport { | ||
| dynamic: Map<string, MatcherExport>; | ||
| wildcard: Map<string, { | ||
| pattern: string; | ||
| }>; | ||
| static: Map<string, { | ||
| pattern: string; | ||
| }>; | ||
| } | ||
| declare function createRouter<T extends RadixNodeData = RadixNodeData>(options?: RadixRouterOptions): RadixRouter<T>; | ||
| interface RouteTable { | ||
| static: Map<string, RadixNodeData | null>; | ||
| wildcard: Map<string, RadixNodeData | null>; | ||
| dynamic: Map<string, RouteTable>; | ||
| } | ||
| interface RouteMatcher { | ||
| ctx: { | ||
| table: RouteTable; | ||
| }; | ||
| matchAll: (path: string) => RadixNodeData[]; | ||
| } | ||
| declare function toRouteMatcher(router: RadixRouter): RouteMatcher; | ||
| declare function exportMatcher(matcher: RouteMatcher): MatcherExport; | ||
| declare function createMatcherFromExport(matcherExport: MatcherExport): RouteMatcher; | ||
| export { type MatchedRoute, type MatcherExport, type NODE_TYPE, NODE_TYPES, type RadixNode, type RadixNodeData, type RadixRouter, type RadixRouterContext, type RadixRouterOptions, type RouteMatcher, type RouteTable, createMatcherFromExport, createRouter, exportMatcher, toRouteMatcher }; |
| declare const NODE_TYPES: { | ||
| NORMAL: 0; | ||
| WILDCARD: 1; | ||
| PLACEHOLDER: 2; | ||
| }; | ||
| type _NODE_TYPES = typeof NODE_TYPES; | ||
| type NODE_TYPE = _NODE_TYPES[keyof _NODE_TYPES]; | ||
| type _RadixNodeDataObject = { | ||
| params?: never; | ||
| [key: string]: any; | ||
| }; | ||
| type RadixNodeData<T extends _RadixNodeDataObject = _RadixNodeDataObject> = T; | ||
| type MatchedRoute<T extends RadixNodeData = RadixNodeData> = Omit<T, "params"> & { | ||
| params?: Record<string, any>; | ||
| }; | ||
| interface RadixNode<T extends RadixNodeData = RadixNodeData> { | ||
| type: NODE_TYPE; | ||
| parent: RadixNode<T> | null; | ||
| children: Map<string, RadixNode<T>>; | ||
| data: RadixNodeData | null; | ||
| paramName: string | null; | ||
| wildcardChildNode: RadixNode<T> | null; | ||
| placeholderChildNode: RadixNode<T> | null; | ||
| } | ||
| interface RadixRouterOptions { | ||
| strictTrailingSlash?: boolean; | ||
| routes?: Record<string, any>; | ||
| } | ||
| interface RadixRouterContext<T extends RadixNodeData = RadixNodeData> { | ||
| options: RadixRouterOptions; | ||
| rootNode: RadixNode<T>; | ||
| staticRoutesMap: Record<string, RadixNode>; | ||
| } | ||
| interface RadixRouter<T extends RadixNodeData = RadixNodeData> { | ||
| ctx: RadixRouterContext<T>; | ||
| /** | ||
| * Perform lookup of given path in radix tree | ||
| * @param path - the path to search for | ||
| * | ||
| * @returns The data that was originally inserted into the tree | ||
| */ | ||
| lookup(path: string): MatchedRoute<T> | null; | ||
| /** | ||
| * Perform an insert into the radix tree | ||
| * @param path - the prefix to match | ||
| * @param data - the associated data to path | ||
| * | ||
| */ | ||
| insert(path: string, data: T): void; | ||
| /** | ||
| * Perform a remove on the tree | ||
| * @param { string } data.path - the route to match | ||
| * | ||
| * @returns A boolean signifying if the remove was successful or not | ||
| */ | ||
| remove(path: string): boolean; | ||
| } | ||
| interface MatcherExport { | ||
| dynamic: Map<string, MatcherExport>; | ||
| wildcard: Map<string, { | ||
| pattern: string; | ||
| }>; | ||
| static: Map<string, { | ||
| pattern: string; | ||
| }>; | ||
| } | ||
| declare function createRouter<T extends RadixNodeData = RadixNodeData>(options?: RadixRouterOptions): RadixRouter<T>; | ||
| interface RouteTable { | ||
| static: Map<string, RadixNodeData | null>; | ||
| wildcard: Map<string, RadixNodeData | null>; | ||
| dynamic: Map<string, RouteTable>; | ||
| } | ||
| interface RouteMatcher { | ||
| ctx: { | ||
| table: RouteTable; | ||
| }; | ||
| matchAll: (path: string) => RadixNodeData[]; | ||
| } | ||
| declare function toRouteMatcher(router: RadixRouter): RouteMatcher; | ||
| declare function exportMatcher(matcher: RouteMatcher): MatcherExport; | ||
| declare function createMatcherFromExport(matcherExport: MatcherExport): RouteMatcher; | ||
| export { type MatchedRoute, type MatcherExport, type NODE_TYPE, NODE_TYPES, type RadixNode, type RadixNodeData, type RadixRouter, type RadixRouterContext, type RadixRouterOptions, type RouteMatcher, type RouteTable, createMatcherFromExport, createRouter, exportMatcher, toRouteMatcher }; |
+28
-21
@@ -23,3 +23,2 @@ 'use strict'; | ||
| ctx, | ||
| // @ts-ignore | ||
| lookup: (path) => lookup(ctx, normalizeTrailingSlash(path)), | ||
@@ -48,12 +47,14 @@ insert: (path, data) => insert(ctx, normalizeTrailingSlash(path), data), | ||
| const nextNode = node.children.get(section); | ||
| if (nextNode !== void 0) { | ||
| node = nextNode; | ||
| } else { | ||
| if (nextNode === void 0) { | ||
| node = node.placeholderChildNode; | ||
| if (node !== null) { | ||
| params[node.paramName] = section; | ||
| if (node === null) { | ||
| break; | ||
| } else { | ||
| if (node.paramName) { | ||
| params[node.paramName] = section; | ||
| } | ||
| paramsFound = true; | ||
| } else { | ||
| break; | ||
| } | ||
| } else { | ||
| node = nextNode; | ||
| } | ||
@@ -122,9 +123,8 @@ } | ||
| if (node.data) { | ||
| const lastSection = sections[sections.length - 1]; | ||
| const lastSection = sections.at(-1) || ""; | ||
| node.data = null; | ||
| if (Object.keys(node.children).length === 0) { | ||
| const parentNode = node.parent; | ||
| parentNode.children.delete(lastSection); | ||
| parentNode.wildcardChildNode = null; | ||
| parentNode.placeholderChildNode = null; | ||
| if (Object.keys(node.children).length === 0 && node.parent) { | ||
| node.parent.children.delete(lastSection); | ||
| node.parent.wildcardChildNode = null; | ||
| node.parent.placeholderChildNode = null; | ||
| } | ||
@@ -158,8 +158,8 @@ success = true; | ||
| const table = _routerNodeToTable("", router.ctx.rootNode); | ||
| return _createMatcher(table); | ||
| return _createMatcher(table, router.ctx.options.strictTrailingSlash); | ||
| } | ||
| function _createMatcher(table) { | ||
| function _createMatcher(table, strictTrailingSlash) { | ||
| return { | ||
| ctx: { table }, | ||
| matchAll: (path) => _matchRoutes(path, table) | ||
| matchAll: (path) => _matchRoutes(path, table, strictTrailingSlash) | ||
| }; | ||
@@ -197,3 +197,5 @@ } | ||
| ]) | ||
| ) : new Map(Object.entries(matcherExport[property])); | ||
| ) : new Map( | ||
| Object.entries(matcherExport[property]) | ||
| ); | ||
| } | ||
@@ -205,6 +207,9 @@ return table; | ||
| } | ||
| function _matchRoutes(path, table) { | ||
| function _matchRoutes(path, table, strictTrailingSlash) { | ||
| if (strictTrailingSlash !== true && path.endsWith("/")) { | ||
| path = path.slice(0, -1) || "/"; | ||
| } | ||
| const matches = []; | ||
| for (const [key, value] of _sortRoutesMap(table.wildcard)) { | ||
| if (path.startsWith(key)) { | ||
| if (path === key || path.startsWith(key + "/")) { | ||
| matches.push(value); | ||
@@ -233,3 +238,5 @@ } | ||
| if (node.type === NODE_TYPES.NORMAL && !(path.includes("*") || path.includes(":"))) { | ||
| table.static.set(path, node.data); | ||
| if (node.data) { | ||
| table.static.set(path, node.data); | ||
| } | ||
| } else if (node.type === NODE_TYPES.WILDCARD) { | ||
@@ -236,0 +243,0 @@ table.wildcard.set(path.replace("/**", ""), node.data); |
+3
-3
@@ -71,4 +71,4 @@ declare const NODE_TYPES: { | ||
| interface RouteTable { | ||
| static: Map<string, RadixNodeData>; | ||
| wildcard: Map<string, RadixNodeData>; | ||
| static: Map<string, RadixNodeData | null>; | ||
| wildcard: Map<string, RadixNodeData | null>; | ||
| dynamic: Map<string, RouteTable>; | ||
@@ -86,2 +86,2 @@ } | ||
| export { MatchedRoute, MatcherExport, NODE_TYPE, NODE_TYPES, RadixNode, RadixNodeData, RadixRouter, RadixRouterContext, RadixRouterOptions, RouteMatcher, RouteTable, createMatcherFromExport, createRouter, exportMatcher, toRouteMatcher }; | ||
| export { type MatchedRoute, type MatcherExport, type NODE_TYPE, NODE_TYPES, type RadixNode, type RadixNodeData, type RadixRouter, type RadixRouterContext, type RadixRouterOptions, type RouteMatcher, type RouteTable, createMatcherFromExport, createRouter, exportMatcher, toRouteMatcher }; |
+28
-21
@@ -21,3 +21,2 @@ const NODE_TYPES = { | ||
| ctx, | ||
| // @ts-ignore | ||
| lookup: (path) => lookup(ctx, normalizeTrailingSlash(path)), | ||
@@ -46,12 +45,14 @@ insert: (path, data) => insert(ctx, normalizeTrailingSlash(path), data), | ||
| const nextNode = node.children.get(section); | ||
| if (nextNode !== void 0) { | ||
| node = nextNode; | ||
| } else { | ||
| if (nextNode === void 0) { | ||
| node = node.placeholderChildNode; | ||
| if (node !== null) { | ||
| params[node.paramName] = section; | ||
| if (node === null) { | ||
| break; | ||
| } else { | ||
| if (node.paramName) { | ||
| params[node.paramName] = section; | ||
| } | ||
| paramsFound = true; | ||
| } else { | ||
| break; | ||
| } | ||
| } else { | ||
| node = nextNode; | ||
| } | ||
@@ -120,9 +121,8 @@ } | ||
| if (node.data) { | ||
| const lastSection = sections[sections.length - 1]; | ||
| const lastSection = sections.at(-1) || ""; | ||
| node.data = null; | ||
| if (Object.keys(node.children).length === 0) { | ||
| const parentNode = node.parent; | ||
| parentNode.children.delete(lastSection); | ||
| parentNode.wildcardChildNode = null; | ||
| parentNode.placeholderChildNode = null; | ||
| if (Object.keys(node.children).length === 0 && node.parent) { | ||
| node.parent.children.delete(lastSection); | ||
| node.parent.wildcardChildNode = null; | ||
| node.parent.placeholderChildNode = null; | ||
| } | ||
@@ -156,8 +156,8 @@ success = true; | ||
| const table = _routerNodeToTable("", router.ctx.rootNode); | ||
| return _createMatcher(table); | ||
| return _createMatcher(table, router.ctx.options.strictTrailingSlash); | ||
| } | ||
| function _createMatcher(table) { | ||
| function _createMatcher(table, strictTrailingSlash) { | ||
| return { | ||
| ctx: { table }, | ||
| matchAll: (path) => _matchRoutes(path, table) | ||
| matchAll: (path) => _matchRoutes(path, table, strictTrailingSlash) | ||
| }; | ||
@@ -195,3 +195,5 @@ } | ||
| ]) | ||
| ) : new Map(Object.entries(matcherExport[property])); | ||
| ) : new Map( | ||
| Object.entries(matcherExport[property]) | ||
| ); | ||
| } | ||
@@ -203,6 +205,9 @@ return table; | ||
| } | ||
| function _matchRoutes(path, table) { | ||
| function _matchRoutes(path, table, strictTrailingSlash) { | ||
| if (strictTrailingSlash !== true && path.endsWith("/")) { | ||
| path = path.slice(0, -1) || "/"; | ||
| } | ||
| const matches = []; | ||
| for (const [key, value] of _sortRoutesMap(table.wildcard)) { | ||
| if (path.startsWith(key)) { | ||
| if (path === key || path.startsWith(key + "/")) { | ||
| matches.push(value); | ||
@@ -231,3 +236,5 @@ } | ||
| if (node.type === NODE_TYPES.NORMAL && !(path.includes("*") || path.includes(":"))) { | ||
| table.static.set(path, node.data); | ||
| if (node.data) { | ||
| table.static.set(path, node.data); | ||
| } | ||
| } else if (node.type === NODE_TYPES.WILDCARD) { | ||
@@ -234,0 +241,0 @@ table.wildcard.set(path.replace("/**", ""), node.data); |
+21
-21
| { | ||
| "name": "radix3", | ||
| "version": "1.1.0", | ||
| "version": "1.1.1", | ||
| "description": "Lightweight and fast router for JavaScript based on Radix Tree", | ||
@@ -22,20 +22,2 @@ "repository": "unjs/radix3", | ||
| ], | ||
| "devDependencies": { | ||
| "0x": "^5.5.0", | ||
| "@vitest/coverage-c8": "^0.30.1", | ||
| "autocannon": "^7.10.0", | ||
| "benchmark": "^2.1.4", | ||
| "changelogen": "^0.5.3", | ||
| "eslint": "^8.39.0", | ||
| "eslint-config-unjs": "^0.1.0", | ||
| "jiti": "^1.18.2", | ||
| "listhen": "^1.0.4", | ||
| "ohmyfetch": "^0.4.21", | ||
| "prettier": "^2.8.8", | ||
| "standard-version": "^9.5.0", | ||
| "typescript": "^5.0.4", | ||
| "unbuild": "^1.2.1", | ||
| "vitest": "^0.30.1" | ||
| }, | ||
| "packageManager": "pnpm@7.32.2", | ||
| "scripts": { | ||
@@ -51,4 +33,22 @@ "bench": "node ./benchmark/direct.mjs", | ||
| "release": "pnpm test && pnpm build && changelogen --release && git push --follow-tags && pnpm publish", | ||
| "test": "pnpm lint && vitest run" | ||
| } | ||
| "test": "pnpm lint && pnpm test:types && vitest run", | ||
| "test:types": "tsc --noEmit" | ||
| }, | ||
| "devDependencies": { | ||
| "0x": "^5.7.0", | ||
| "@vitest/coverage-v8": "^1.3.1", | ||
| "autocannon": "^7.15.0", | ||
| "benchmark": "^2.1.4", | ||
| "changelogen": "^0.5.5", | ||
| "eslint": "^8.57.0", | ||
| "eslint-config-unjs": "^0.2.1", | ||
| "jiti": "^1.21.0", | ||
| "listhen": "^1.7.2", | ||
| "prettier": "^3.2.5", | ||
| "standard-version": "^9.5.0", | ||
| "typescript": "^5.4.2", | ||
| "unbuild": "^2.0.0", | ||
| "vitest": "^1.3.1" | ||
| }, | ||
| "packageManager": "pnpm@8.15.4" | ||
| } |
+29
-28
@@ -31,6 +31,6 @@ # 🌳 radix3 | ||
| // ESM | ||
| import { createRouter } from 'radix3' | ||
| import { createRouter } from "radix3"; | ||
| // CJS | ||
| const { createRouter } = require('radix3') | ||
| const { createRouter } = require("radix3"); | ||
| ``` | ||
@@ -41,8 +41,8 @@ | ||
| ```js | ||
| const router = createRouter(/* options */) | ||
| const router = createRouter(/* options */); | ||
| router.insert('/path', { payload: 'this path' }) | ||
| router.insert('/path/:name', { payload: 'named route' }) | ||
| router.insert('/path/foo/**', { payload: 'wildcard route' }) | ||
| router.insert('/path/foo/**:name', { payload: 'named wildcard route' }) | ||
| router.insert("/path", { payload: "this path" }); | ||
| router.insert("/path/:name", { payload: "named route" }); | ||
| router.insert("/path/foo/**", { payload: "wildcard route" }); | ||
| router.insert("/path/foo/**:name", { payload: "named wildcard route" }); | ||
| ``` | ||
@@ -53,12 +53,12 @@ | ||
| ```js | ||
| router.lookup('/path') | ||
| router.lookup("/path"); | ||
| // { payload: 'this path' } | ||
| router.lookup('/path/fooval') | ||
| router.lookup("/path/fooval"); | ||
| // { payload: 'named route', params: { name: 'fooval' } } | ||
| router.lookup('/path/foo/bar/baz') | ||
| router.lookup("/path/foo/bar/baz"); | ||
| // { payload: 'wildcard route' } | ||
| router.lookup('/') | ||
| router.lookup("/"); | ||
| // null (no route matched for/) | ||
@@ -91,5 +91,5 @@ ``` | ||
| routes: { | ||
| '/foo': {} | ||
| } | ||
| }) | ||
| "/foo": {}, | ||
| }, | ||
| }); | ||
| ``` | ||
@@ -105,17 +105,17 @@ | ||
| ```ts | ||
| import { createRouter, toRouteMatcher } from 'radix3' | ||
| import { createRouter, toRouteMatcher } from "radix3"; | ||
| const router = createRouter({ | ||
| routes: { | ||
| '/foo': { m: 'foo' }, // Matches /foo only | ||
| '/foo/**': { m: 'foo/**' }, // Matches /foo/<any> | ||
| '/foo/bar': { m: 'foo/bar' }, // Matches /foo/bar only | ||
| '/foo/bar/baz': { m: 'foo/bar/baz' }, // Matches /foo/bar/baz only | ||
| '/foo/*/baz': { m: 'foo/*/baz' } // Matches /foo/<any>/baz | ||
| } | ||
| }) | ||
| "/foo": { m: "foo" }, // Matches /foo only | ||
| "/foo/**": { m: "foo/**" }, // Matches /foo/<any> | ||
| "/foo/bar": { m: "foo/bar" }, // Matches /foo/bar only | ||
| "/foo/bar/baz": { m: "foo/bar/baz" }, // Matches /foo/bar/baz only | ||
| "/foo/*/baz": { m: "foo/*/baz" }, // Matches /foo/<any>/baz | ||
| }, | ||
| }); | ||
| const matcher = toRouteMatcher(router) | ||
| const matcher = toRouteMatcher(router); | ||
| const matches = matcher.matchAll('/foo/bar/baz') | ||
| const matches = matcher.matchAll("/foo/bar/baz"); | ||
@@ -140,12 +140,12 @@ // [ | ||
| ```ts | ||
| import { exportMatcher, createMatcherFromExport } from 'radix3' | ||
| import { exportMatcher, createMatcherFromExport } from "radix3"; | ||
| // Assuming you already have a matcher | ||
| // you can export this to a JSON-type object | ||
| const json = exportMatcher(matcher) | ||
| const json = exportMatcher(matcher); | ||
| // and then rehydrate this later | ||
| const newMatcher = createMatcherFromExport(json) | ||
| const newMatcher = createMatcherFromExport(json); | ||
| const matches = newMatcher.matchAll('/foo/bar/baz') | ||
| const matches = newMatcher.matchAll("/foo/bar/baz"); | ||
| ``` | ||
@@ -165,2 +165,3 @@ | ||
| <!-- Badges --> | ||
| [npm-version-src]: https://img.shields.io/npm/v/radix3?style=flat&colorA=18181B&colorB=F0DB4F | ||
@@ -167,0 +168,0 @@ [npm-version-href]: https://npmjs.com/package/radix3 |
-251
| 'use strict'; | ||
| const NODE_TYPES = { | ||
| NORMAL: 0, | ||
| WILDCARD: 1, | ||
| PLACEHOLDER: 2 | ||
| }; | ||
| function createRouter(options = {}) { | ||
| const ctx = { | ||
| options, | ||
| rootNode: createRadixNode(), | ||
| staticRoutesMap: {} | ||
| }; | ||
| const normalizeTrailingSlash = (p) => options.strictTrailingSlash ? p : p.replace(/\/$/, "") || "/"; | ||
| if (options.routes) { | ||
| for (const path in options.routes) { | ||
| insert(ctx, normalizeTrailingSlash(path), options.routes[path]); | ||
| } | ||
| } | ||
| return { | ||
| ctx, | ||
| // @ts-ignore | ||
| lookup: (path) => lookup(ctx, normalizeTrailingSlash(path)), | ||
| insert: (path, data) => insert(ctx, normalizeTrailingSlash(path), data), | ||
| remove: (path) => remove(ctx, normalizeTrailingSlash(path)) | ||
| }; | ||
| } | ||
| function lookup(ctx, path) { | ||
| const staticPathNode = ctx.staticRoutesMap[path]; | ||
| if (staticPathNode) { | ||
| return staticPathNode.data; | ||
| } | ||
| const sections = path.split("/"); | ||
| const params = {}; | ||
| let paramsFound = false; | ||
| let wildcardNode = null; | ||
| let node = ctx.rootNode; | ||
| let wildCardParam = null; | ||
| for (let i = 0; i < sections.length; i++) { | ||
| const section = sections[i]; | ||
| if (node.wildcardChildNode !== null) { | ||
| wildcardNode = node.wildcardChildNode; | ||
| wildCardParam = sections.slice(i).join("/"); | ||
| } | ||
| const nextNode = node.children.get(section); | ||
| if (nextNode !== void 0) { | ||
| node = nextNode; | ||
| } else { | ||
| node = node.placeholderChildNode; | ||
| if (node !== null) { | ||
| params[node.paramName] = section; | ||
| paramsFound = true; | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if ((node === null || node.data === null) && wildcardNode !== null) { | ||
| node = wildcardNode; | ||
| params[node.paramName || "_"] = wildCardParam; | ||
| paramsFound = true; | ||
| } | ||
| if (!node) { | ||
| return null; | ||
| } | ||
| if (paramsFound) { | ||
| return { | ||
| ...node.data, | ||
| params: paramsFound ? params : void 0 | ||
| }; | ||
| } | ||
| return node.data; | ||
| } | ||
| function insert(ctx, path, data) { | ||
| let isStaticRoute = true; | ||
| const sections = path.split("/"); | ||
| let node = ctx.rootNode; | ||
| let _unnamedPlaceholderCtr = 0; | ||
| for (const section of sections) { | ||
| let childNode; | ||
| if (childNode = node.children.get(section)) { | ||
| node = childNode; | ||
| } else { | ||
| const type = getNodeType(section); | ||
| childNode = createRadixNode({ type, parent: node }); | ||
| node.children.set(section, childNode); | ||
| if (type === NODE_TYPES.PLACEHOLDER) { | ||
| childNode.paramName = section === "*" ? `_${_unnamedPlaceholderCtr++}` : section.slice(1); | ||
| node.placeholderChildNode = childNode; | ||
| isStaticRoute = false; | ||
| } else if (type === NODE_TYPES.WILDCARD) { | ||
| node.wildcardChildNode = childNode; | ||
| childNode.paramName = section.slice( | ||
| 3 | ||
| /* "**:" */ | ||
| ) || "_"; | ||
| isStaticRoute = false; | ||
| } | ||
| node = childNode; | ||
| } | ||
| } | ||
| node.data = data; | ||
| if (isStaticRoute === true) { | ||
| ctx.staticRoutesMap[path] = node; | ||
| } | ||
| return node; | ||
| } | ||
| function remove(ctx, path) { | ||
| let success = false; | ||
| const sections = path.split("/"); | ||
| let node = ctx.rootNode; | ||
| for (const section of sections) { | ||
| node = node.children.get(section); | ||
| if (!node) { | ||
| return success; | ||
| } | ||
| } | ||
| if (node.data) { | ||
| const lastSection = sections[sections.length - 1]; | ||
| node.data = null; | ||
| if (Object.keys(node.children).length === 0) { | ||
| const parentNode = node.parent; | ||
| parentNode.children.delete(lastSection); | ||
| parentNode.wildcardChildNode = null; | ||
| parentNode.placeholderChildNode = null; | ||
| } | ||
| success = true; | ||
| } | ||
| return success; | ||
| } | ||
| function createRadixNode(options = {}) { | ||
| return { | ||
| type: options.type || NODE_TYPES.NORMAL, | ||
| parent: options.parent || null, | ||
| children: /* @__PURE__ */ new Map(), | ||
| data: options.data || null, | ||
| paramName: options.paramName || null, | ||
| wildcardChildNode: null, | ||
| placeholderChildNode: null | ||
| }; | ||
| } | ||
| function getNodeType(str) { | ||
| if (str.startsWith("**")) { | ||
| return NODE_TYPES.WILDCARD; | ||
| } | ||
| if (str[0] === ":" || str === "*") { | ||
| return NODE_TYPES.PLACEHOLDER; | ||
| } | ||
| return NODE_TYPES.NORMAL; | ||
| } | ||
| function toRouteMatcher(router) { | ||
| const table = _routerNodeToTable("", router.ctx.rootNode); | ||
| return _createMatcher(table); | ||
| } | ||
| function _createMatcher(table) { | ||
| return { | ||
| ctx: { table }, | ||
| matchAll: (path) => _matchRoutes(path, table) | ||
| }; | ||
| } | ||
| function _createRouteTable() { | ||
| return { | ||
| static: /* @__PURE__ */ new Map(), | ||
| wildcard: /* @__PURE__ */ new Map(), | ||
| dynamic: /* @__PURE__ */ new Map() | ||
| }; | ||
| } | ||
| function _exportMatcherFromTable(table) { | ||
| const obj = /* @__PURE__ */ Object.create(null); | ||
| for (const property in table) { | ||
| obj[property] = property === "dynamic" ? Object.fromEntries( | ||
| [...table[property].entries()].map(([key, value]) => [ | ||
| key, | ||
| _exportMatcherFromTable(value) | ||
| ]) | ||
| ) : Object.fromEntries(table[property].entries()); | ||
| } | ||
| return obj; | ||
| } | ||
| function exportMatcher(matcher) { | ||
| return _exportMatcherFromTable(matcher.ctx.table); | ||
| } | ||
| function _createTableFromExport(matcherExport) { | ||
| const table = {}; | ||
| for (const property in matcherExport) { | ||
| table[property] = property === "dynamic" ? new Map( | ||
| Object.entries(matcherExport[property]).map(([key, value]) => [ | ||
| key, | ||
| _createTableFromExport(value) | ||
| ]) | ||
| ) : new Map(Object.entries(matcherExport[property])); | ||
| } | ||
| return table; | ||
| } | ||
| function createMatcherFromExport(matcherExport) { | ||
| return _createMatcher(_createTableFromExport(matcherExport)); | ||
| } | ||
| function _matchRoutes(path, table) { | ||
| const matches = []; | ||
| for (const [key, value] of _sortRoutesMap(table.wildcard)) { | ||
| if (path.startsWith(key)) { | ||
| matches.push(value); | ||
| } | ||
| } | ||
| for (const [key, value] of _sortRoutesMap(table.dynamic)) { | ||
| if (path.startsWith(key + "/")) { | ||
| const subPath = "/" + path.slice(key.length).split("/").splice(2).join("/"); | ||
| matches.push(..._matchRoutes(subPath, value)); | ||
| } | ||
| } | ||
| const staticMatch = table.static.get(path); | ||
| if (staticMatch) { | ||
| matches.push(staticMatch); | ||
| } | ||
| return matches.filter(Boolean); | ||
| } | ||
| function _sortRoutesMap(m) { | ||
| return [...m.entries()].sort((a, b) => a[0].length - b[0].length); | ||
| } | ||
| function _routerNodeToTable(initialPath, initialNode) { | ||
| const table = _createRouteTable(); | ||
| function _addNode(path, node) { | ||
| if (path) { | ||
| if (node.type === NODE_TYPES.NORMAL && !(path.includes("*") || path.includes(":"))) { | ||
| table.static.set(path, node.data); | ||
| } else if (node.type === NODE_TYPES.WILDCARD) { | ||
| table.wildcard.set(path.replace("/**", ""), node.data); | ||
| } else if (node.type === NODE_TYPES.PLACEHOLDER) { | ||
| const subTable = _routerNodeToTable("", node); | ||
| if (node.data) { | ||
| subTable.static.set("/", node.data); | ||
| } | ||
| table.dynamic.set(path.replace(/\/\*|\/:\w+/, ""), subTable); | ||
| return; | ||
| } | ||
| } | ||
| for (const [childPath, child] of node.children.entries()) { | ||
| _addNode(`${path}/${childPath}`.replace("//", "/"), child); | ||
| } | ||
| } | ||
| _addNode(initialPath, initialNode); | ||
| return table; | ||
| } | ||
| exports.NODE_TYPES = NODE_TYPES; | ||
| exports.createMatcherFromExport = createMatcherFromExport; | ||
| exports.createRouter = createRouter; | ||
| exports.exportMatcher = exportMatcher; | ||
| exports.toRouteMatcher = toRouteMatcher; |
14
-6.67%8
14.29%172
0.58%30730
-2.42%586
-28.36%