diff --git a/Build/build-speedtest-domainset.ts b/Build/build-speedtest-domainset.ts index 6dae1c56..2cbca279 100644 --- a/Build/build-speedtest-domainset.ts +++ b/Build/build-speedtest-domainset.ts @@ -223,7 +223,7 @@ export const buildSpeedtestDomainSet = task(import.meta.main, import.meta.path)( 'Turkey' ]).reduce>>((pMap, keyword) => { pMap[keyword] = span.traceChildAsync(`fetch speedtest endpoints: ${keyword}`, () => querySpeedtestApi(keyword)).then(hostnameGroup => { - hostnameGroup.forEach(hostname => { + return hostnameGroup.forEach(hostname => { if (hostname) { domains.add(hostname); } diff --git a/Build/lib/trie.ts b/Build/lib/trie.ts index 52a8d051..42639935 100644 --- a/Build/lib/trie.ts +++ b/Build/lib/trie.ts @@ -4,23 +4,21 @@ // const { Error, Bun, JSON, Symbol } = globalThis; -const SENTINEL = Symbol('SENTINEL'); -const PARENT = Symbol('Parent Node'); - const noop = () => { /** noop */ }; -type TrieNode = { - [SENTINEL]: boolean, - [PARENT]: TrieNode | null, - [Bun.inspect.custom]: () => string -} & Map; +interface TrieNode { + s: boolean, + p: TrieNode | null, + [Bun.inspect.custom](): string, + c: Map +} const deepTrieNodeToJSON = (node: TrieNode) => { const obj: Record = {}; - if (node[SENTINEL]) { - obj['[start]'] = node[SENTINEL]; + if (node.s) { + obj['[start]'] = node.s; } - node.forEach((value, key) => { + node.c.forEach((value, key) => { obj[key] = deepTrieNodeToJSON(value); }); return obj; @@ -31,11 +29,12 @@ function trieNodeInspectCustom(this: TrieNode) { } const createNode = (parent: TrieNode | null = null): TrieNode => { - const node = new Map() as TrieNode; - node[SENTINEL] = false; - node[PARENT] = parent; - node[Bun.inspect.custom] = trieNodeInspectCustom; - return node; + return { + s: false, + p: parent, + c: new Map(), + [Bun.inspect.custom]: trieNodeInspectCustom + }; }; const hostnameToTokens = (hostname: string): string[] => { @@ -82,17 +81,17 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = for (let i = tokens.length - 1; i >= 0; i--) { token = tokens[i]; - if (node.has(token)) { - node = node.get(token)!; + if (node.c.has(token)) { + node = node.c.get(token)!; // During the adding of `[start]blog|.skk.moe` and find out that there is a `[start].skk.moe` in the trie // Dedupe the covered subdomain by skipping - if (smolTree && token === '.' && node[SENTINEL]) { + if (smolTree && token === '.' && node.s) { return; } } else { const newNode = createNode(node); - node.set(token, newNode); + node.c.set(token, newNode); node = newNode; } } @@ -102,33 +101,33 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = if (tokens[0] === '.') { // Trying to add `[start].sub.example.com` where there is already a `[start]blog.sub.example.com` in the trie - const parent = node[PARENT]!; + const parent = node.p!; // Make sure parent `[start]sub.example.com` (without dot) is removed (SETINEL to false) - parent[SENTINEL] = false; + parent.s = false; // Removing the rest of the parent's child nodes - node.clear(); + node.c.clear(); // The SENTINEL of this node will be set to true at the end of the function, so we don't need to set it here - // we can use else-if here, because new node is empty, we don't need to check the leading "." - } else if (node.get('.')?.[SENTINEL] === true) { + // we can use else-if here, because the children is now empty, we don't need to check the leading "." + } else if (node.c.get('.')?.s === true) { // Trying to add `example.com` when there is already a `.example.com` in the trie // No need to increment size and set SENTINEL to true (skip this "new" item) return; } - } else if (!node[SENTINEL]) { // smol tree don't have size, so else-if here + } else if (!node.s) { // smol tree don't have size, so else-if here size++; } - node[SENTINEL] = true; + node.s = true; }; const walkIntoLeafWithTokens = ( tokens: string | string[], onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop ) => { - let node: TrieNode | undefined = root; + let node: TrieNode = root; let parent: TrieNode = node; let token: string; @@ -141,8 +140,12 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = } parent = node; - node = node.get(token); - if (!node) return null; + + if (node.c.has(token)) { + node = node.c.get(token)!; + } else { + return null; + } onLoop(node, parent, token); } @@ -170,7 +173,7 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = node = nodeStack.pop()!; const suffix = suffixStack.pop()!; - node.forEach((childNode, k) => { + node.c.forEach((childNode, k) => { // Pushing the child node to the stack for next iteration of DFS nodeStack.push(childNode); @@ -178,7 +181,7 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = }); // If the node is a sentinel, we push the suffix to the results - if (node[SENTINEL]) { + if (node.s) { onMatches(suffix); } } while (nodeStack.length); @@ -200,7 +203,7 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = // Even if the node size is 1, but the single child is ".", we should retain the branch // Since the "." could be special if it is the leaf-est node - const onlyChild = node.size < 2 && (!hostnameMode || !node.has('.')); + const onlyChild = node.c.size < 2 && (!hostnameMode || !node.c.has('.')); if (toPrune != null) { // the top-est branch that could potentially being pruned if (!onlyChild) { @@ -293,15 +296,15 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = const res = getSingleChildLeaf(suffixToTokens(suffix)); if (res === null) return false; - if (!res.node[SENTINEL]) return false; + if (!res.node.s) return false; size--; const { node, toPrune, tokenToPrune } = res; if (tokenToPrune && toPrune) { - toPrune.delete(tokenToPrune); + toPrune.c.delete(tokenToPrune); } else { - node[SENTINEL] = false; + node.s = false; } return true; @@ -315,7 +318,7 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = const res = walkIntoLeafWithTokens(tokens); return res - ? res.node[SENTINEL] + ? res.node.s : false; }; @@ -346,24 +349,27 @@ export const createTrie = (from?: string[] | Set | null, hostnameMode = // Trying to whitelist `[start].sub.example.com` where there is already a `[start]blog.sub.example.com` in the trie if (tokens[0] === '.') { // If there is a `[start]sub.example.com` here, remove it - parent[SENTINEL] = false; + parent.s = false; // Removing all the child nodes by disconnecting "." - parent.delete('.'); + parent.c.delete('.'); } // Trying to whitelist `example.com` when there is already a `.example.com` in the trie - const dotNode = node.get('.'); - if (dotNode?.[SENTINEL] === true) { - dotNode[SENTINEL] = false; + const dotNode = node.c.get('.'); + if (dotNode) { + dotNode.s = false; } + // if (dotNode?.s === true) { + // dotNode.s = false; + // } // return early if not found - if (!node[SENTINEL]) return; + if (!node.s) return; if (tokenToPrune && toPrune) { - toPrune.delete(tokenToPrune); + toPrune.c.delete(tokenToPrune); } else { - node[SENTINEL] = false; + node.s = false; } };