UNPKG

mongoose

Version:
168 lines (153 loc) 4.75 kB
'use strict'; const wildcardSegment = '$*'; const numericSegmentRegex = /^\d+$/; class PathTrieNode { constructor() { this.children = new Map(); this.terminal = false; this.hasWildcard = false; } } /** * Trie of dotted paths for efficient path matching. Supports the map wildcard * segment `$*`, which matches any single segment. For example, a trie * containing `settings.$*` matches `settings.theme` but not `settings` or * `profile.theme`. In array lookups, numeric segments like array indexes are * ignorable: a trie containing `comments.text` matches `comments.0.text`. * Single-segment string lookups skip the numeric check entirely. * * @api private */ class PathTrie { /** * @param {string[]} [paths] paths to add to the trie */ constructor(paths) { this.root = new PathTrieNode(); if (paths != null) { for (const path of paths) { this.add(path); } } } /** * Add a dotted path, like `settings.$*` or `profile.firstName`, to the trie. * * @param {string} path */ add(path) { if (path.indexOf('.') === -1) { this._addSegment(this.root, path).terminal = true; return; } let node = this.root; for (const segment of path.split('.')) { node = this._addSegment(node, segment); } node.terminal = true; } /** * @param {PathTrieNode} node * @param {string} segment */ _addSegment(node, segment) { let child = node.children.get(segment); if (child == null) { child = new PathTrieNode(); node.children.set(segment, child); if (segment === wildcardSegment) { node.hasWildcard = true; } } return child; } /** * Check if `path` or any of its ancestor paths exist in the trie. `path` is * either a single segment string or a pre-split array of segments — lookups * never split, callers are responsible for splitting dotted paths. * For example, with a trie containing 'profile': * matchesPathOrAncestor(['profile', 'firstName']) === true * matchesPathOrAncestor('profile') === true * But with a trie containing 'profile.firstName': * matchesPathOrAncestor('profile') === false * * @param {string|string[]} path */ matchesPathOrAncestor(path) { return this._walk(path) === true; } /** * Check if `path`, any of its ancestor paths, or any of its descendant paths * exist in the trie. `path` is either a single segment string or a pre-split * array of segments — lookups never split, callers are responsible for * splitting dotted paths. For example: * new PathTrie(['profile']).overlapsPath(['profile', 'firstName']) === true * new PathTrie(['profile.firstName']).overlapsPath('profile') === true * * @param {string|string[]} path */ overlapsPath(path) { return this._walk(path) !== false; } /** * Walk `path` through the trie. `path` is assumed to be pre-split - if the * path has multiple segments, pass in an array of strings. Returns `true` if * a terminal node was found at `path` or one of its ancestors, `false` if the * walk dead-ended, and the array of reached nodes if `path` is a strict prefix * of entries in the trie. * * @param {string|string[]} path */ _walk(path) { if (typeof path === 'string') { const next = []; const literal = this.root.children.get(path); if (literal != null) { if (literal.terminal) { return true; } next.push(literal); } if (this.root.hasWildcard) { const wildcard = this.root.children.get(wildcardSegment); if (wildcard.terminal) { return true; } next.push(wildcard); } return next.length === 0 ? false : next; } let nodes = [this.root]; for (const segment of path) { const next = []; const isNumericSegment = numericSegmentRegex.test(segment); for (const node of nodes) { if (isNumericSegment) { // Numeric segments like array indexes are ignorable: `comments.text` // in the trie matches the modified path `comments.0.text`. next.push(node); } const literal = node.children.get(segment); if (literal != null) { if (literal.terminal) { return true; } next.push(literal); } if (node.hasWildcard) { const wildcard = node.children.get(wildcardSegment); if (wildcard.terminal) { return true; } next.push(wildcard); } } if (next.length === 0) { return false; } nodes = next; } return nodes; } } module.exports = PathTrie;