@httpx/treeu
Version:
Tree utilities
166 lines (165 loc) • 5.4 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
//#region src/mapper/flat-tree-ws-mapper.ts
const sym = Symbol("@@result");
const flatTreeWsMapperErrors = {
toTreeNodes: {
parsedErrorMsg: `Can't convert the flat tree to tree nodes`,
issues: {
ARG_NOT_ARRAY: "Invalid argument: not an array (FlatTreeWs)",
ARG_NOT_OBJECT_ARRAY: "Invalid argument: not an array of objects (FlatTreeWs)",
NON_STRING_KEY: "Non-string key",
EMPTY_KEY: "Empty key given",
SPLIT_EMPTY_KEY: "Split an empty key"
}
},
fromTreeNodes: {
parsedErrorMsg: `Can't convert the tree to tree nodes to flat with separator`,
issues: {
DUPLICATE_KEY: "Duplicate unique id",
INVALID_KEY: "Invalid key found"
}
}
};
var FlatTreeWsMapper = class {
toTreeNodes = (data, params) => {
const { separator } = params;
const collector = { [sym]: [] };
const d = data instanceof Map ? data : new Map(Object.entries(data));
try {
for (const [key, value] of d) {
const trimmedKey = key.trim();
if (trimmedKey.length === 0) throw new Error(`${flatTreeWsMapperErrors.toTreeNodes.issues.EMPTY_KEY}`);
let context = collector;
const splitted = trimmedKey.split(separator);
for (const name of splitted) {
if (name.trim().length === 0) throw new Error(`${flatTreeWsMapperErrors.toTreeNodes.issues.SPLIT_EMPTY_KEY}`);
if (!Object.hasOwn(context, name)) {
Object.defineProperty(context, name, {
value: { [sym]: [] },
writable: false
});
const parents = splitted.slice(0, -1);
const node = {
id: trimmedKey,
parentId: parents.length > 0 ? parents.join(separator) : null,
children: context[name][sym]
};
if (value !== void 0) node.value = value;
if (!Array.isArray(context[sym])) throw new TypeError(JSON.stringify(node));
context[sym].push(node);
}
context = context[name];
}
}
} catch (e) {
return {
success: false,
message: flatTreeWsMapperErrors.toTreeNodes.parsedErrorMsg,
issues: [{ message: `${e.message}` }]
};
}
return {
success: true,
treeNodes: collector[sym]
};
};
/**
* @throws Error
*/
toTreeNodesOrThrow = (data, params) => {
const result = this.toTreeNodes(data, params);
if (!result.success) {
const { message, issues } = result;
const errors = issues.length > 0 ? issues.map((issue) => issue.message).join(", ") : null;
throw new Error(`${message} (${errors})`);
}
return result.treeNodes;
};
/**
* Will convert a tree of nodes to a flat tree.
*/
fromTreeNodesOrThrow = (treeNodes, params) => {
const result = [];
const queue = [];
treeNodes.forEach((node) => queue.push(node));
while (true) {
let count = queue.length;
if (count === 0) break;
while (count > 0) {
const node = queue.shift();
result.push(node);
node.children?.forEach((child) => queue.push(child));
count--;
}
}
const map = /* @__PURE__ */ new Map();
const { parsedErrorMsg, issues } = flatTreeWsMapperErrors.fromTreeNodes;
for (const node of result) {
const key = node.id;
if (typeof key !== "string" && typeof key !== "number") throw new TypeError(`${parsedErrorMsg} (${issues.INVALID_KEY}: '${key}')`);
if (map.has(key)) throw new Error(`${parsedErrorMsg} (${issues.DUPLICATE_KEY}: '${key}' of type ${typeof key}')`);
map.set(key, node.value);
}
return map;
};
};
//#endregion
//#region src/search/dfs-tree-search.ts
/**
* Depth-First Search (DFS) algorithm for tree structures. It uses a stack rather
* than recursion in order to support deeply nested trees without call-stack overflows.
* It is well suited for exploring a branch of a data structure in depth and
* usually preferred when memory usage is a concern or when the data
* structure has many nodes with few levels.
*
* @see https://hackernoon.com/a-beginners-guide-to-bfs-and-dfs-in-javascript
*/
var DfsTreeSearch = class {
treeNodes;
constructor(treeNodes) {
this.treeNodes = treeNodes;
}
/**
* Find first matching node in the tree. The `reverse` parameter can be used
* to traverse the tree in reverse order.
*/
findOne = (idOrConditionOrFn, params) => {
const { includeChildren = false, reverse = false } = { ...params };
if (!Array.isArray(this.treeNodes) || this.treeNodes.length === 0) return;
let result;
const isIdSearch = typeof idOrConditionOrFn === "string" || typeof idOrConditionOrFn === "number";
const isFnSearch = !Array.isArray(idOrConditionOrFn);
for (const treeNode of this.treeNodes) {
const stack = [treeNode];
while (stack.length > 0) {
const node = stack[reverse ? "pop" : "shift"]();
if (isIdSearch ? node.id === idOrConditionOrFn : isFnSearch ? idOrConditionOrFn(node) : idOrConditionOrFn[0] in node && node[idOrConditionOrFn[0]] === idOrConditionOrFn[2]) {
result = node;
break;
}
if (node.children) stack.push(...node.children);
}
if (result) break;
}
if (includeChildren !== true) {
const { children: _children, ...rest } = result ?? {};
return { ...rest };
}
return result;
};
};
//#endregion
//#region src/tree.ts
var Tree = class {
treeNodes;
constructor(treeNodes) {
this.treeNodes = treeNodes;
}
getTreeNodes = () => {
return this.treeNodes;
};
};
//#endregion
exports.DfsTreeSearch = DfsTreeSearch;
exports.FlatTreeWsMapper = FlatTreeWsMapper;
exports.Tree = Tree;