@noxfly/noxus
Version:
Simulate lightweight HTTP-like requests between renderer and main process in Electron applications with MessagePort, with structured and modular design.
211 lines (180 loc) • 6.77 kB
text/typescript
/**
* @copyright 2025 NoxFly
* @license MIT
* @author NoxFly
*/
/**
*
*/
type Params = Record<string, string>;
/**
* Represents a search result in the Radix Tree.
*/
interface ISearchResult<T> {
node: RadixNode<T>;
params: Params;
}
/**
* Represents a node in the Radix Tree.
* The represents a path segment
*/
class RadixNode<T> {
public segment: string;
public children: RadixNode<T>[] = [];
public value?: T;
public isParam: boolean;
public paramName?: string;
/**
* Creates a new RadixNode.
* @param segment - The segment of the path this node represents.
*/
constructor(segment: string) {
this.segment = segment;
this.isParam = segment.startsWith(":");
if(this.isParam) {
this.paramName = segment.slice(1);
}
}
/**
* Matches a child node against a given segment.
* This method checks if the segment matches any of the children nodes.
* @param segment - The segment to match against the children of this node.
* @returns A child node that matches the segment, or undefined if no match is found.
*/
public matchChild(segment: string): RadixNode<T> | undefined {
for(const child of this.children) {
if(child.isParam || segment.startsWith(child.segment))
return child; // param match
}
return undefined;
}
/**
* Finds a child node that matches the segment exactly.
* This method checks if there is a child node that matches the segment exactly.
* @param segment - The segment to find an exact match for among the children of this node.
* @returns A child node that matches the segment exactly, or undefined if no match is found.
*/
public findExactChild(segment: string): RadixNode<T> | undefined {
return this.children.find(c => c.segment === segment);
}
/**
* Adds a child node to this node's children.
* This method adds a new child node to the list of children for this node.
* @param node - The child node to add to this node's children.
*/
public addChild(node: RadixNode<T>): void {
this.children.push(node);
}
}
/**
*
*/
export class RadixTree<T> {
private readonly root = new RadixNode<T>("");
/**
* Inserts a path and its associated value into the Radix Tree.
* This method normalizes the path and inserts it into the tree, associating it with
* @param path - The path to insert into the tree.
* @param value - The value to associate with the path.
*/
public insert(path: string, value: T): void {
const segments = this.normalize(path);
this.insertRecursive(this.root, segments, value);
}
/**
* Recursively inserts a path into the Radix Tree.
* This method traverses the tree and inserts the segments of the path, creating new nodes
* @param node - The node to start inserting from.
* @param segments - The segments of the path to insert.
* @param value - The value to associate with the path.
*/
private insertRecursive(node: RadixNode<T>, segments: string[], value: T): void {
if(segments.length === 0) {
node.value = value;
return;
}
const segment = segments[0] ?? "";
let child = node.children.find(c =>
c.isParam === segment.startsWith(":") &&
(c.isParam || c.segment === segment)
);
if(!child) {
child = new RadixNode<T>(segment);
node.addChild(child);
}
this.insertRecursive(child, segments.slice(1), value);
}
/**
* Searches for a path in the Radix Tree.
* This method normalizes the path and searches for it in the tree, returning the node
* @param path - The path to search for in the Radix Tree.
* @returns An ISearchResult containing the node and parameters if a match is found, otherwise undefined.
*/
public search(path: string): ISearchResult<T> | undefined {
const segments = this.normalize(path);
return this.searchRecursive(this.root, segments, {});
}
/**
* Recursively searches for a path in the Radix Tree.
* This method traverses the tree and searches for the segments of the path, collecting parameters
* @param node - The node to start searching from.
* @param segments - The segments of the path to search for.
* @param params - The parameters collected during the search.
* @returns An ISearchResult containing the node and parameters if a match is found, otherwise undefined.
*/
private searchRecursive(node: RadixNode<T>, segments: string[], params: Params): ISearchResult<T> | undefined {
if(segments.length === 0) {
if(node.value !== undefined) {
return {
node: node,
params
};
}
return undefined;
}
const [segment, ...rest] = segments;
for(const child of node.children) {
if(child.isParam) {
const paramName = child.paramName!;
const childParams: Params = {
...params,
[paramName]: segment ?? "",
};
if(rest.length === 0) {
return {
node: child,
params: childParams
};
}
const result = this.searchRecursive(child, rest, childParams);
if(result)
return result;
}
else if(segment === child.segment) {
if(rest.length === 0) {
return {
node: child,
params
};
}
const result = this.searchRecursive(child, rest, params);
if(result)
return result;
}
}
return undefined;
}
/**
* Normalizes a path into an array of segments.
* This method removes leading and trailing slashes, splits the path by slashes, and
* @param path - The path to normalize.
* @returns An array of normalized path segments.
*/
private normalize(path: string): string[] {
const segments = path
.replace(/^\/+|\/+$/g, "")
.split("/")
.filter(Boolean);
return ['', ...segments];
}
}