diff --git a/packages/xo-server/src/_MultiKeyMap.js b/packages/xo-server/src/_MultiKeyMap.js index e9ee9d1bf..cdad210ba 100644 --- a/packages/xo-server/src/_MultiKeyMap.js +++ b/packages/xo-server/src/_MultiKeyMap.js @@ -1,58 +1,87 @@ -const kValue = Symbol('value') - -function del(map, i, n, keys) { - if (i === n) { - return map.delete(kValue) - } - const key = keys[i] - const child = map.get(key) - if (child === undefined) { - return false - } - const deleted = del(child, i + 1, n, keys) - if (child.size === 0) { - map.delete(key) - } - return deleted -} - -function get(map, i, n, keys) { - if (i === n) { - return map.get(kValue) - } - const child = map.get(keys[i]) - if (child !== undefined) { - return get(child, i + 1, n, keys) +class Node { + constructor(value) { + this.children = new Map() + this.value = value } } -function set(map, i, n, keys, value) { - if (i === n) { - return map.set(kValue, value) +function del(node, i, keys) { + if (i === keys.length) { + if (node instanceof Node) { + node.value = undefined + return node + } + return + } + if (!(node instanceof Node)) { + return node } const key = keys[i] - let child = map.get(key) + const { children } = node + const child = children.get(key) if (child === undefined) { - map.set(key, (child = new Map())) + return node } - set(child, i + 1, n, keys, value) + const newChild = del(child, i + 1, keys) + if (newChild === undefined) { + if (children.size === 1) { + return node.value + } + children.delete(key) + } else if (newChild !== child) { + children.set(key, newChild) + } + return node +} + +function get(node, i, keys) { + return i === keys.length + ? node instanceof Node + ? node.value + : node + : node instanceof Node + ? get(node.children.get(keys[i]), i + 1, keys) + : undefined +} + +function set(node, i, keys, value) { + if (i === keys.length) { + if (node instanceof Node) { + node.value = value + return node + } + return value + } + const key = keys[i] + if (!(node instanceof Node)) { + node = new Node(node) + node.children.set(key, set(undefined, i + 1, keys, value)) + } else { + const { children } = node + const child = children.get(key) + const newChild = set(child, i + 1, keys, value) + if (newChild !== child) { + children.set(key, newChild) + } + } + return node } export default class MultiKeyMap { constructor() { - this._map = new Map() + // each node is either a value or a Node if it contains children + this._root = undefined } delete(keys) { - return del(this._map, 0, keys.length, keys) + this._root = del(this._root, 0, keys) } get(keys) { - return get(this._map, 0, keys.length, keys) + return get(this._root, 0, keys) } set(keys, value) { - set(this._map, 0, keys.length, keys, value) - return this + this._root = set(this._root, 0, keys, value) } }