@layerzerolabs/dfs 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,9 +10,9 @@
10
10
  CLI Cleaning output folder
11
11
  CJS Build start
12
12
  ESM Build start
13
- CJS dist/index.cjs 2.75 KB
14
- CJS dist/index.cjs.map 8.89 KB
15
- CJS ⚡️ Build success in 88ms
16
- ESM dist/index.js 2.73 KB
17
- ESM dist/index.js.map 8.88 KB
18
- ESM ⚡️ Build success in 88ms
13
+ CJS dist/index.cjs 3.27 KB
14
+ CJS dist/index.cjs.map 11.45 KB
15
+ CJS ⚡️ Build success in 86ms
16
+ ESM dist/index.js 3.25 KB
17
+ ESM dist/index.js.map 11.45 KB
18
+ ESM ⚡️ Build success in 86ms
@@ -5,11 +5,12 @@
5
5
 
6
6
   RUN  v3.2.3 /home/runner/work/monorepo-internal/monorepo-internal/packages/framework/dfs
7
7
 
8
- ✓ test/dfs.test.ts (3 tests) 307ms
9
- ✓ DI Depth-first-search > The handlers for each of a node's dependencies should be completed before that node  303ms
8
+ ✓ test/dfs.test.ts (7 tests) 644ms
9
+ ✓ DI Depth-first-search > The handlers for each of a node's dependencies should be completed before that node  304ms
10
+ ✓ DI Ancestry - randomized DAG property > random DAGs: ancestry includes all ancestors and distances are non-decreasing  319ms
10
11
 
11
12
   Test Files  1 passed (1)
12
-  Tests  3 passed (3)
13
-  Start at  22:36:00
14
-  Duration  927ms (transform 80ms, setup 0ms, collect 70ms, tests 307ms, environment 0ms, prepare 139ms)
13
+  Tests  7 passed (7)
14
+  Start at  13:52:03
15
+  Duration  1.51s (transform 286ms, setup 0ms, collect 376ms, tests 644ms, environment 0ms, prepare 199ms)
15
16
 
package/dist/index.cjs CHANGED
@@ -4,17 +4,22 @@ var __defProp = Object.defineProperty;
4
4
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
5
5
 
6
6
  // src/index.ts
7
- var buildAncestorsByNodeName = /* @__PURE__ */ __name((node, prehandler) => {
8
- const ancestorsByNodeName = /* @__PURE__ */ new Map();
9
- const dependentCountByNodeName = /* @__PURE__ */ new Map();
10
- const initializeMaps = /* @__PURE__ */ __name((node2) => {
11
- ancestorsByNodeName.set(node2.name, /* @__PURE__ */ new Set([]));
12
- for (const dependencyValue of Object.values(node2.dependencies)) {
13
- const inDegree = dependentCountByNodeName.get(dependencyValue.name) || 0;
14
- dependentCountByNodeName.set(dependencyValue.name, inDegree + 1);
15
- if (!ancestorsByNodeName.has(dependencyValue.name)) {
16
- initializeMaps(dependencyValue);
17
- }
7
+ var mergeAncestorDistances = /* @__PURE__ */ __name((parentDistances, childDistances) => {
8
+ for (const [ancestor, parentDist] of parentDistances) {
9
+ const candidate = parentDist + 1;
10
+ const current = childDistances.get(ancestor) || Infinity;
11
+ childDistances.set(ancestor, Math.min(current, candidate));
12
+ }
13
+ }, "mergeAncestorDistances");
14
+ var buildAncestryDistanceIndex = /* @__PURE__ */ __name((node, prehandler) => {
15
+ const ancestryDistanceIndex = /* @__PURE__ */ new Map();
16
+ const inDegreeByNodeName = /* @__PURE__ */ new Map();
17
+ const initializeMaps = /* @__PURE__ */ __name((cur) => {
18
+ ancestryDistanceIndex.set(cur.name, /* @__PURE__ */ new Map());
19
+ for (const dep of Object.values(cur.dependencies)) {
20
+ const inDegree = inDegreeByNodeName.get(dep.name) || 0;
21
+ inDegreeByNodeName.set(dep.name, inDegree + 1);
22
+ if (!ancestryDistanceIndex.has(dep.name)) initializeMaps(dep);
18
23
  }
19
24
  }, "initializeMaps");
20
25
  initializeMaps(node);
@@ -23,24 +28,32 @@ var buildAncestorsByNodeName = /* @__PURE__ */ __name((node, prehandler) => {
23
28
  ];
24
29
  while (queue.length > 0) {
25
30
  const curNode = queue.shift();
26
- for (const dependencyValue of Object.values(curNode.dependencies)) {
27
- const inDegree = dependentCountByNodeName.get(dependencyValue.name);
31
+ const currentMinimalDistances = new Map([
32
+ [
33
+ curNode,
34
+ 0
35
+ ],
36
+ ...ancestryDistanceIndex.get(curNode.name)
37
+ ]);
38
+ for (const dep of Object.values(curNode.dependencies)) {
39
+ const inDegree = inDegreeByNodeName.get(dep.name);
28
40
  if (inDegree === 1) {
29
- const preHandledDependency = prehandler(dependencyValue);
30
- queue.push(preHandledDependency);
31
- }
32
- dependentCountByNodeName.set(dependencyValue.name, inDegree - 1);
33
- const childAncestry = ancestorsByNodeName.get(dependencyValue.name);
34
- const nodeAncestry = ancestorsByNodeName.get(curNode.name);
35
- for (const ancestor of nodeAncestry) {
36
- childAncestry.add(ancestor);
41
+ queue.push(prehandler(dep));
37
42
  }
43
+ inDegreeByNodeName.set(dep.name, inDegree - 1);
44
+ const childDistances = ancestryDistanceIndex.get(dep.name);
45
+ mergeAncestorDistances(currentMinimalDistances, childDistances);
38
46
  }
39
47
  }
40
- return ancestorsByNodeName;
41
- }, "buildAncestorsByNodeName");
48
+ for (const [nodeName, inDegree] of inDegreeByNodeName) {
49
+ if (inDegree !== 0) {
50
+ throw new Error(`node ${nodeName} has in-degree ${inDegree}, this indicates a cycle in the graph containing the node`);
51
+ }
52
+ }
53
+ return ancestryDistanceIndex;
54
+ }, "buildAncestryDistanceIndex");
42
55
  var dfs = /* @__PURE__ */ __name((node, handler, prehandler = (node2) => node2, _returns = {}) => {
43
- const ancestorsByNodeName = buildAncestorsByNodeName(node, prehandler);
56
+ const ancestryDistanceIndex = buildAncestryDistanceIndex(node, prehandler);
44
57
  const nodeResolverPromises = /* @__PURE__ */ new Map();
45
58
  const resolveNode = /* @__PURE__ */ __name(async (node2) => {
46
59
  const prehandledNode = prehandler(node2);
@@ -52,10 +65,9 @@ var dfs = /* @__PURE__ */ __name((node, handler, prehandler = (node2) => node2,
52
65
  childrenPromises.push(nodeResolverPromises.get(dependencyValue.name));
53
66
  }
54
67
  await Promise.all(childrenPromises);
55
- const ancestry = ancestorsByNodeName.get(prehandledNode.name);
56
- const regRes = await handler(node2, [
57
- ...ancestry
58
- ]);
68
+ const minimalDistances = ancestryDistanceIndex.get(prehandledNode.name);
69
+ const sortedAncestors = Array.from(minimalDistances.entries()).sort((a, b) => a[1] - b[1]);
70
+ const regRes = await handler(node2, sortedAncestors.map(([node3, _]) => node3));
59
71
  if (regRes) {
60
72
  (_returns[regRes.key] ??= {})[prehandledNode.name] = regRes.value;
61
73
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["buildAncestorsByNodeName","node","prehandler","ancestorsByNodeName","Map","dependentCountByNodeName","initializeMaps","set","name","Set","dependencyValue","Object","values","dependencies","inDegree","get","has","queue","length","curNode","shift","preHandledDependency","push","childAncestry","nodeAncestry","ancestor","add","dfs","handler","_returns","nodeResolverPromises","resolveNode","prehandledNode","childrenPromises","Promise","all","ancestry","regRes","key","value"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { DependencyNode } from '@layerzerolabs/dependency-graph';\n\n/**\n * A registrar is a simple interface for an object that provides the ability to traverse the dependency graph.\n * It is implicit in this definition that the registrar should also *register* values adhering to the schemata\n * of the graph.\n */\nexport interface Registrar<ReturnType> {\n traverseDependencies: (rootNode: DependencyNode) => Promise<ReturnType>;\n}\nexport type NodeHandlerFunction = (\n node: DependencyNode,\n ancestry: DependencyNode[],\n) => Promise<{ key: string; value: any }>;\nexport type NodePreHandlerFunction = (node: DependencyNode) => DependencyNode;\n\nconst buildAncestorsByNodeName = (node: DependencyNode, prehandler: NodePreHandlerFunction) => {\n const ancestorsByNodeName = new Map<string, Set<DependencyNode>>();\n const dependentCountByNodeName = new Map<string, number>();\n\n // If A depends on B and C, B depends on C, we initialize with:\n // ancestorsByNodeName: { A: Set([]), B: Set([]), C: Set([]) }\n // dependentsCountByNodeName: { B: 1, C: 2 }\n const initializeMaps = (node: DependencyNode) => {\n ancestorsByNodeName.set(node.name, new Set([]));\n for (const dependencyValue of Object.values(node.dependencies)) {\n const inDegree = dependentCountByNodeName.get(dependencyValue.name) || 0;\n dependentCountByNodeName.set(dependencyValue.name, inDegree + 1);\n if (!ancestorsByNodeName.has(dependencyValue.name)) {\n initializeMaps(dependencyValue);\n }\n }\n };\n initializeMaps(node);\n\n // topological sorted bfs exploration\n // induction by level, initial level has correct initial state, build level + 1\n // bfs is used to build ancestry correctly\n // this should actually be a queue, but its probably fine for now\n let queue = [prehandler(node)];\n while (queue.length > 0) {\n const curNode = queue.shift()!;\n\n // add the new processable dependencies to the queue, update their state.\n for (const dependencyValue of Object.values(curNode.dependencies)) {\n const inDegree = dependentCountByNodeName.get(dependencyValue.name)!;\n // We are the last edge missing in the graph for this dependency -> we can process it after us.\n if (inDegree === 1) {\n const preHandledDependency = prehandler(dependencyValue);\n queue.push(preHandledDependency);\n }\n // Reduce the in-degree of the dependency -> it basically means that this edge got removed from the graph.\n dependentCountByNodeName.set(dependencyValue.name, inDegree - 1);\n\n // add our ancestry to them, js sets maintain order of insertion, this means their ancestors are in distance order due to BFS properties.\n const childAncestry = ancestorsByNodeName.get(dependencyValue.name)!;\n const nodeAncestry = ancestorsByNodeName.get(curNode.name)!;\n for (const ancestor of nodeAncestry) {\n childAncestry.add(ancestor);\n }\n }\n }\n\n return ancestorsByNodeName;\n};\n\n/**\n * Performs a depth-first-search on a tree of dependency nodes, and returns a function\n * that will call the handler for each node in the tree, ordered s.t. the handler of N\n * will be called only after the handlers of dependencies(N) have been called.\n * The resolver function will only call the handler once for each unique definition node.\n * The resolver function returns an object whose keys are the keys defined\n * by each of the handlers, and whose values are objects whose keys are the names\n * of the nodes resolved and whose values are the values defined by the handlers.\n * @param node the root node of the tree\n * @param handler a function that accepts a node and registers it\n * @param prehandler a function that accepts a node and returns a node. will be use to pre-process the graph\n * @returns a resolver function\n */\nexport const dfs = <ReturnTypes>(\n node: DependencyNode,\n handler: NodeHandlerFunction,\n prehandler: NodePreHandlerFunction = (node) => node,\n _returns: ReturnTypes = {} as any,\n): (() => Promise<ReturnTypes>) => {\n const ancestorsByNodeName = buildAncestorsByNodeName(node, prehandler);\n\n // Maintains Map<node.name, Promise<void>> -> the promise to resolve the node, for all nodes.\n const nodeResolverPromises = new Map<string, Promise<void>>();\n const resolveNode = async (node: DependencyNode) => {\n const prehandledNode = prehandler(node);\n // first wait for all its children\n const childrenPromises = [];\n for (const dependencyValue of Object.values(prehandledNode.dependencies)) {\n // Grab the cached promise or create it, we want to have only 1 handler promise for each node.\n if (!nodeResolverPromises.has(dependencyValue.name)) {\n nodeResolverPromises.set(dependencyValue.name, resolveNode(dependencyValue));\n }\n childrenPromises.push(nodeResolverPromises.get(dependencyValue.name)!);\n }\n await Promise.all(childrenPromises);\n\n // Then, you can process yourself\n const ancestry = ancestorsByNodeName.get(prehandledNode.name)!;\n const regRes = await handler(node, [...ancestry]);\n if (regRes) {\n ((_returns as any)[regRes.key] ??= {})[prehandledNode.name] = regRes.value;\n }\n };\n\n return async () => {\n await resolveNode(node);\n return _returns;\n };\n};\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["mergeAncestorDistances","parentDistances","childDistances","ancestor","parentDist","candidate","current","get","Infinity","set","Math","min","buildAncestryDistanceIndex","node","prehandler","ancestryDistanceIndex","Map","inDegreeByNodeName","initializeMaps","cur","name","dep","Object","values","dependencies","inDegree","has","queue","length","curNode","shift","currentMinimalDistances","push","nodeName","Error","dfs","handler","_returns","nodeResolverPromises","resolveNode","prehandledNode","childrenPromises","dependencyValue","Promise","all","minimalDistances","sortedAncestors","Array","from","entries","sort","a","b","regRes","map","_","key","value"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { DependencyNode } from '@layerzerolabs/dependency-graph';\n\n/**\n * A registrar is a simple interface for an object that provides the ability to traverse the dependency graph.\n * It is implicit in this definition that the registrar should also *register* values adhering to the schemata\n * of the graph.\n */\nexport interface Registrar<ReturnType> {\n traverseDependencies: (rootNode: DependencyNode) => Promise<ReturnType>;\n}\nexport type NodeHandlerFunction = (\n node: DependencyNode,\n ancestry: DependencyNode[],\n) => Promise<{ key: string; value: any }>;\nexport type NodePreHandlerFunction = (node: DependencyNode) => DependencyNode;\n\n// Map of ancestor node -> minimal hop distance\ntype AncestorDistanceByNode = Map<DependencyNode, number>;\n// Index from node name -> its ancestor distance map\ntype AncestryDistanceIndex = Map<string, AncestorDistanceByNode>;\n\n/**\n * In-place merge of minimal ancestry distances.\n *\n * childDistances holds minimal distances from the child to each ancestor (keyed by DependencyNode).\n * For every entry in parentDistances, we update the child's map with (parentDist + 1),\n * keeping the minimal value if the ancestor already exists.\n */\nconst mergeAncestorDistances = (\n parentDistances: AncestorDistanceByNode,\n childDistances: AncestorDistanceByNode,\n) => {\n for (const [ancestor, parentDist] of parentDistances) {\n const candidate = parentDist + 1;\n const current = childDistances.get(ancestor) || Infinity;\n childDistances.set(ancestor, Math.min(current, candidate));\n }\n};\n\n/**\n * Builds a minimal ancestry distance index for all nodes reachable from the root.\n *\n * Returns a Map: node.name -> Map<DependencyNode, distance> where distance is the minimal hop count\n * from the node to that ancestor. We perform a Kahn-style BFS over the DAG, and for each edge\n * curNode -> dep we merge curNode's minimal distances into the dependency with +1 hop and take the minimum.\n */\nconst buildAncestryDistanceIndex = (\n node: DependencyNode,\n prehandler: NodePreHandlerFunction,\n): AncestryDistanceIndex => {\n const ancestryDistanceIndex: AncestryDistanceIndex = new Map();\n const inDegreeByNodeName = new Map<string, number>();\n\n // If A depends on B and C, B depends on C, we initialize with:\n // ancestryDistanceIndex: { A: Map(), B: Map(), C: Map() }\n // inDegreeByNodeName: { B: 1, C: 2 }\n const initializeMaps = (cur: DependencyNode) => {\n ancestryDistanceIndex.set(cur.name, new Map());\n for (const dep of Object.values(cur.dependencies)) {\n const inDegree = inDegreeByNodeName.get(dep.name) || 0;\n inDegreeByNodeName.set(dep.name, inDegree + 1);\n if (!ancestryDistanceIndex.has(dep.name)) initializeMaps(dep);\n }\n };\n initializeMaps(node);\n\n // Kahn-style topological BFS accumulating minimal distance ancestors\n let queue = [prehandler(node)];\n while (queue.length > 0) {\n const curNode = queue.shift()!;\n // Include self with distance 0, then extend with the already known minimal distances\n const currentMinimalDistances: AncestorDistanceByNode = new Map([\n [curNode, 0],\n ...ancestryDistanceIndex.get(curNode.name)!,\n ]);\n // Add the new processable dependencies to the queue, update their state.\n for (const dep of Object.values(curNode.dependencies)) {\n const inDegree = inDegreeByNodeName.get(dep.name)!;\n // We are the last edge missing in the graph for this dependency -> we can process it after us.\n if (inDegree === 1) {\n queue.push(prehandler(dep));\n }\n // Reduce the in-degree of the dependency -> it basically means that this edge got removed from the graph.\n inDegreeByNodeName.set(dep.name, inDegree - 1);\n\n // Merge curNode's minimal distances (+1 hop) into the dependency's minimal distances\n const childDistances = ancestryDistanceIndex.get(dep.name)!;\n mergeAncestorDistances(currentMinimalDistances, childDistances);\n }\n }\n\n for (const [nodeName, inDegree] of inDegreeByNodeName) {\n if (inDegree !== 0) {\n throw new Error(\n `node ${nodeName} has in-degree ${inDegree}, this indicates a cycle in the graph containing the node`,\n );\n }\n }\n\n return ancestryDistanceIndex;\n};\n\n/**\n * Performs a depth-first-search on a tree of dependency nodes, and returns a function\n * that will call the handler for each node in the tree, ordered s.t. the handler of N\n * will be called only after the handlers of dependencies(N) have been called.\n * The node's ancestors are sorted by non-decreasing minimal distance.\n * The resolver function will only call the handler once for each unique definition node.\n * The resolver function returns an object whose keys are the keys defined\n * by each of the handlers, and whose values are objects whose keys are the names\n * of the nodes resolved and whose values are the values defined by the handlers.\n * @param node the root node of the tree\n * @param handler a function that accepts a node and registers it\n * @param prehandler a function that accepts a node and returns a node. Will be used to pre-process the graph\n * @returns a resolver function\n */\nexport const dfs = <ReturnTypes>(\n node: DependencyNode,\n handler: NodeHandlerFunction,\n prehandler: NodePreHandlerFunction = (node) => node,\n _returns: ReturnTypes = {} as any,\n): (() => Promise<ReturnTypes>) => {\n const ancestryDistanceIndex = buildAncestryDistanceIndex(node, prehandler);\n\n // Maintains Map<node.name, Promise<void>> -> the promise to resolve the node, for all nodes.\n const nodeResolverPromises = new Map<string, Promise<void>>();\n const resolveNode = async (node: DependencyNode) => {\n const prehandledNode = prehandler(node);\n // first wait for all its children\n const childrenPromises = [];\n for (const dependencyValue of Object.values(prehandledNode.dependencies)) {\n // Grab the cached promise or create it, we want to have only 1 handler promise for each node.\n if (!nodeResolverPromises.has(dependencyValue.name)) {\n nodeResolverPromises.set(dependencyValue.name, resolveNode(dependencyValue));\n }\n childrenPromises.push(nodeResolverPromises.get(dependencyValue.name)!);\n }\n await Promise.all(childrenPromises);\n\n // Then, you can process yourself. Build ancestry sorted by non-decreasing minimal distance.\n const minimalDistances = ancestryDistanceIndex.get(prehandledNode.name)!;\n const sortedAncestors = Array.from(minimalDistances.entries()).sort((a, b) => a[1] - b[1]);\n const regRes = await handler(\n node,\n sortedAncestors.map(([node, _]) => node),\n );\n if (regRes) {\n ((_returns as any)[regRes.key] ??= {})[prehandledNode.name] = regRes.value;\n }\n };\n\n return async () => {\n await resolveNode(node);\n return _returns;\n };\n};\n"]}
package/dist/index.d.ts CHANGED
@@ -16,13 +16,14 @@ export type NodePreHandlerFunction = (node: DependencyNode) => DependencyNode;
16
16
  * Performs a depth-first-search on a tree of dependency nodes, and returns a function
17
17
  * that will call the handler for each node in the tree, ordered s.t. the handler of N
18
18
  * will be called only after the handlers of dependencies(N) have been called.
19
+ * The node's ancestors are sorted by non-decreasing minimal distance.
19
20
  * The resolver function will only call the handler once for each unique definition node.
20
21
  * The resolver function returns an object whose keys are the keys defined
21
22
  * by each of the handlers, and whose values are objects whose keys are the names
22
23
  * of the nodes resolved and whose values are the values defined by the handlers.
23
24
  * @param node the root node of the tree
24
25
  * @param handler a function that accepts a node and registers it
25
- * @param prehandler a function that accepts a node and returns a node. will be use to pre-process the graph
26
+ * @param prehandler a function that accepts a node and returns a node. Will be used to pre-process the graph
26
27
  * @returns a resolver function
27
28
  */
28
29
  export declare const dfs: <ReturnTypes>(node: DependencyNode, handler: NodeHandlerFunction, prehandler?: NodePreHandlerFunction, _returns?: ReturnTypes) => (() => Promise<ReturnTypes>);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,WAAW,SAAS,CAAC,UAAU;IACjC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CAC3E;AACD,MAAM,MAAM,mBAAmB,GAAG,CAC9B,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,cAAc,EAAE,KACzB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAC1C,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE,cAAc,KAAK,cAAc,CAAC;AAoD9E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,GAAG,GAAI,WAAW,EAC3B,MAAM,cAAc,EACpB,SAAS,mBAAmB,EAC5B,aAAY,sBAAuC,EACnD,WAAU,WAAuB,KAClC,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,CA8B7B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,WAAW,SAAS,CAAC,UAAU;IACjC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CAC3E;AACD,MAAM,MAAM,mBAAmB,GAAG,CAC9B,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,cAAc,EAAE,KACzB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAC1C,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE,cAAc,KAAK,cAAc,CAAC;AAwF9E;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,GAAG,GAAI,WAAW,EAC3B,MAAM,cAAc,EACpB,SAAS,mBAAmB,EAC5B,aAAY,sBAAuC,EACnD,WAAU,WAAuB,KAClC,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,CAkC7B,CAAC"}
package/dist/index.js CHANGED
@@ -2,17 +2,22 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
4
  // src/index.ts
5
- var buildAncestorsByNodeName = /* @__PURE__ */ __name((node, prehandler) => {
6
- const ancestorsByNodeName = /* @__PURE__ */ new Map();
7
- const dependentCountByNodeName = /* @__PURE__ */ new Map();
8
- const initializeMaps = /* @__PURE__ */ __name((node2) => {
9
- ancestorsByNodeName.set(node2.name, /* @__PURE__ */ new Set([]));
10
- for (const dependencyValue of Object.values(node2.dependencies)) {
11
- const inDegree = dependentCountByNodeName.get(dependencyValue.name) || 0;
12
- dependentCountByNodeName.set(dependencyValue.name, inDegree + 1);
13
- if (!ancestorsByNodeName.has(dependencyValue.name)) {
14
- initializeMaps(dependencyValue);
15
- }
5
+ var mergeAncestorDistances = /* @__PURE__ */ __name((parentDistances, childDistances) => {
6
+ for (const [ancestor, parentDist] of parentDistances) {
7
+ const candidate = parentDist + 1;
8
+ const current = childDistances.get(ancestor) || Infinity;
9
+ childDistances.set(ancestor, Math.min(current, candidate));
10
+ }
11
+ }, "mergeAncestorDistances");
12
+ var buildAncestryDistanceIndex = /* @__PURE__ */ __name((node, prehandler) => {
13
+ const ancestryDistanceIndex = /* @__PURE__ */ new Map();
14
+ const inDegreeByNodeName = /* @__PURE__ */ new Map();
15
+ const initializeMaps = /* @__PURE__ */ __name((cur) => {
16
+ ancestryDistanceIndex.set(cur.name, /* @__PURE__ */ new Map());
17
+ for (const dep of Object.values(cur.dependencies)) {
18
+ const inDegree = inDegreeByNodeName.get(dep.name) || 0;
19
+ inDegreeByNodeName.set(dep.name, inDegree + 1);
20
+ if (!ancestryDistanceIndex.has(dep.name)) initializeMaps(dep);
16
21
  }
17
22
  }, "initializeMaps");
18
23
  initializeMaps(node);
@@ -21,24 +26,32 @@ var buildAncestorsByNodeName = /* @__PURE__ */ __name((node, prehandler) => {
21
26
  ];
22
27
  while (queue.length > 0) {
23
28
  const curNode = queue.shift();
24
- for (const dependencyValue of Object.values(curNode.dependencies)) {
25
- const inDegree = dependentCountByNodeName.get(dependencyValue.name);
29
+ const currentMinimalDistances = new Map([
30
+ [
31
+ curNode,
32
+ 0
33
+ ],
34
+ ...ancestryDistanceIndex.get(curNode.name)
35
+ ]);
36
+ for (const dep of Object.values(curNode.dependencies)) {
37
+ const inDegree = inDegreeByNodeName.get(dep.name);
26
38
  if (inDegree === 1) {
27
- const preHandledDependency = prehandler(dependencyValue);
28
- queue.push(preHandledDependency);
29
- }
30
- dependentCountByNodeName.set(dependencyValue.name, inDegree - 1);
31
- const childAncestry = ancestorsByNodeName.get(dependencyValue.name);
32
- const nodeAncestry = ancestorsByNodeName.get(curNode.name);
33
- for (const ancestor of nodeAncestry) {
34
- childAncestry.add(ancestor);
39
+ queue.push(prehandler(dep));
35
40
  }
41
+ inDegreeByNodeName.set(dep.name, inDegree - 1);
42
+ const childDistances = ancestryDistanceIndex.get(dep.name);
43
+ mergeAncestorDistances(currentMinimalDistances, childDistances);
36
44
  }
37
45
  }
38
- return ancestorsByNodeName;
39
- }, "buildAncestorsByNodeName");
46
+ for (const [nodeName, inDegree] of inDegreeByNodeName) {
47
+ if (inDegree !== 0) {
48
+ throw new Error(`node ${nodeName} has in-degree ${inDegree}, this indicates a cycle in the graph containing the node`);
49
+ }
50
+ }
51
+ return ancestryDistanceIndex;
52
+ }, "buildAncestryDistanceIndex");
40
53
  var dfs = /* @__PURE__ */ __name((node, handler, prehandler = (node2) => node2, _returns = {}) => {
41
- const ancestorsByNodeName = buildAncestorsByNodeName(node, prehandler);
54
+ const ancestryDistanceIndex = buildAncestryDistanceIndex(node, prehandler);
42
55
  const nodeResolverPromises = /* @__PURE__ */ new Map();
43
56
  const resolveNode = /* @__PURE__ */ __name(async (node2) => {
44
57
  const prehandledNode = prehandler(node2);
@@ -50,10 +63,9 @@ var dfs = /* @__PURE__ */ __name((node, handler, prehandler = (node2) => node2,
50
63
  childrenPromises.push(nodeResolverPromises.get(dependencyValue.name));
51
64
  }
52
65
  await Promise.all(childrenPromises);
53
- const ancestry = ancestorsByNodeName.get(prehandledNode.name);
54
- const regRes = await handler(node2, [
55
- ...ancestry
56
- ]);
66
+ const minimalDistances = ancestryDistanceIndex.get(prehandledNode.name);
67
+ const sortedAncestors = Array.from(minimalDistances.entries()).sort((a, b) => a[1] - b[1]);
68
+ const regRes = await handler(node2, sortedAncestors.map(([node3, _]) => node3));
57
69
  if (regRes) {
58
70
  (_returns[regRes.key] ??= {})[prehandledNode.name] = regRes.value;
59
71
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["buildAncestorsByNodeName","node","prehandler","ancestorsByNodeName","Map","dependentCountByNodeName","initializeMaps","set","name","Set","dependencyValue","Object","values","dependencies","inDegree","get","has","queue","length","curNode","shift","preHandledDependency","push","childAncestry","nodeAncestry","ancestor","add","dfs","handler","_returns","nodeResolverPromises","resolveNode","prehandledNode","childrenPromises","Promise","all","ancestry","regRes","key","value"],"mappings":"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","file":"index.js","sourcesContent":["import type { DependencyNode } from '@layerzerolabs/dependency-graph';\n\n/**\n * A registrar is a simple interface for an object that provides the ability to traverse the dependency graph.\n * It is implicit in this definition that the registrar should also *register* values adhering to the schemata\n * of the graph.\n */\nexport interface Registrar<ReturnType> {\n traverseDependencies: (rootNode: DependencyNode) => Promise<ReturnType>;\n}\nexport type NodeHandlerFunction = (\n node: DependencyNode,\n ancestry: DependencyNode[],\n) => Promise<{ key: string; value: any }>;\nexport type NodePreHandlerFunction = (node: DependencyNode) => DependencyNode;\n\nconst buildAncestorsByNodeName = (node: DependencyNode, prehandler: NodePreHandlerFunction) => {\n const ancestorsByNodeName = new Map<string, Set<DependencyNode>>();\n const dependentCountByNodeName = new Map<string, number>();\n\n // If A depends on B and C, B depends on C, we initialize with:\n // ancestorsByNodeName: { A: Set([]), B: Set([]), C: Set([]) }\n // dependentsCountByNodeName: { B: 1, C: 2 }\n const initializeMaps = (node: DependencyNode) => {\n ancestorsByNodeName.set(node.name, new Set([]));\n for (const dependencyValue of Object.values(node.dependencies)) {\n const inDegree = dependentCountByNodeName.get(dependencyValue.name) || 0;\n dependentCountByNodeName.set(dependencyValue.name, inDegree + 1);\n if (!ancestorsByNodeName.has(dependencyValue.name)) {\n initializeMaps(dependencyValue);\n }\n }\n };\n initializeMaps(node);\n\n // topological sorted bfs exploration\n // induction by level, initial level has correct initial state, build level + 1\n // bfs is used to build ancestry correctly\n // this should actually be a queue, but its probably fine for now\n let queue = [prehandler(node)];\n while (queue.length > 0) {\n const curNode = queue.shift()!;\n\n // add the new processable dependencies to the queue, update their state.\n for (const dependencyValue of Object.values(curNode.dependencies)) {\n const inDegree = dependentCountByNodeName.get(dependencyValue.name)!;\n // We are the last edge missing in the graph for this dependency -> we can process it after us.\n if (inDegree === 1) {\n const preHandledDependency = prehandler(dependencyValue);\n queue.push(preHandledDependency);\n }\n // Reduce the in-degree of the dependency -> it basically means that this edge got removed from the graph.\n dependentCountByNodeName.set(dependencyValue.name, inDegree - 1);\n\n // add our ancestry to them, js sets maintain order of insertion, this means their ancestors are in distance order due to BFS properties.\n const childAncestry = ancestorsByNodeName.get(dependencyValue.name)!;\n const nodeAncestry = ancestorsByNodeName.get(curNode.name)!;\n for (const ancestor of nodeAncestry) {\n childAncestry.add(ancestor);\n }\n }\n }\n\n return ancestorsByNodeName;\n};\n\n/**\n * Performs a depth-first-search on a tree of dependency nodes, and returns a function\n * that will call the handler for each node in the tree, ordered s.t. the handler of N\n * will be called only after the handlers of dependencies(N) have been called.\n * The resolver function will only call the handler once for each unique definition node.\n * The resolver function returns an object whose keys are the keys defined\n * by each of the handlers, and whose values are objects whose keys are the names\n * of the nodes resolved and whose values are the values defined by the handlers.\n * @param node the root node of the tree\n * @param handler a function that accepts a node and registers it\n * @param prehandler a function that accepts a node and returns a node. will be use to pre-process the graph\n * @returns a resolver function\n */\nexport const dfs = <ReturnTypes>(\n node: DependencyNode,\n handler: NodeHandlerFunction,\n prehandler: NodePreHandlerFunction = (node) => node,\n _returns: ReturnTypes = {} as any,\n): (() => Promise<ReturnTypes>) => {\n const ancestorsByNodeName = buildAncestorsByNodeName(node, prehandler);\n\n // Maintains Map<node.name, Promise<void>> -> the promise to resolve the node, for all nodes.\n const nodeResolverPromises = new Map<string, Promise<void>>();\n const resolveNode = async (node: DependencyNode) => {\n const prehandledNode = prehandler(node);\n // first wait for all its children\n const childrenPromises = [];\n for (const dependencyValue of Object.values(prehandledNode.dependencies)) {\n // Grab the cached promise or create it, we want to have only 1 handler promise for each node.\n if (!nodeResolverPromises.has(dependencyValue.name)) {\n nodeResolverPromises.set(dependencyValue.name, resolveNode(dependencyValue));\n }\n childrenPromises.push(nodeResolverPromises.get(dependencyValue.name)!);\n }\n await Promise.all(childrenPromises);\n\n // Then, you can process yourself\n const ancestry = ancestorsByNodeName.get(prehandledNode.name)!;\n const regRes = await handler(node, [...ancestry]);\n if (regRes) {\n ((_returns as any)[regRes.key] ??= {})[prehandledNode.name] = regRes.value;\n }\n };\n\n return async () => {\n await resolveNode(node);\n return _returns;\n };\n};\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["mergeAncestorDistances","parentDistances","childDistances","ancestor","parentDist","candidate","current","get","Infinity","set","Math","min","buildAncestryDistanceIndex","node","prehandler","ancestryDistanceIndex","Map","inDegreeByNodeName","initializeMaps","cur","name","dep","Object","values","dependencies","inDegree","has","queue","length","curNode","shift","currentMinimalDistances","push","nodeName","Error","dfs","handler","_returns","nodeResolverPromises","resolveNode","prehandledNode","childrenPromises","dependencyValue","Promise","all","minimalDistances","sortedAncestors","Array","from","entries","sort","a","b","regRes","map","_","key","value"],"mappings":";;;;AA4BA,IAAMA,sBAAAA,mBAAyB,MAAA,CAAA,CAC3BC,eAAAA,EACAC,cAAAA,KAAAA;AAEA,EAAA,KAAA,MAAW,CAACC,QAAAA,EAAUC,UAAAA,CAAAA,IAAeH,eAAAA,EAAiB;AAClD,IAAA,MAAMI,YAAYD,UAAAA,GAAa,CAAA;AAC/B,IAAA,MAAME,OAAAA,GAAUJ,cAAAA,CAAeK,GAAAA,CAAIJ,QAAAA,CAAAA,IAAaK,QAAAA;AAChDN,IAAAA,cAAAA,CAAeO,IAAIN,QAAAA,EAAUO,IAAAA,CAAKC,GAAAA,CAAIL,OAAAA,EAASD,SAAAA,CAAAA,CAAAA;AACnD,EAAA;AACJ,CAAA,EAT+B,wBAAA,CAAA;AAkB/B,IAAMO,0BAAAA,mBAA6B,MAAA,CAAA,CAC/BC,IAAAA,EACAC,UAAAA,KAAAA;AAEA,EAAA,MAAMC,qBAAAA,uBAAmDC,GAAAA,EAAAA;AACzD,EAAA,MAAMC,kBAAAA,uBAAyBD,GAAAA,EAAAA;AAK/B,EAAA,MAAME,cAAAA,2BAAkBC,GAAAA,KAAAA;AACpBJ,IAAAA,qBAAAA,CAAsBN,GAAAA,CAAIU,GAAAA,CAAIC,IAAAA,kBAAM,IAAIJ,KAAAA,CAAAA;AACxC,IAAA,KAAA,MAAWK,GAAAA,IAAOC,MAAAA,CAAOC,MAAAA,CAAOJ,GAAAA,CAAIK,YAAY,CAAA,EAAG;AAC/C,MAAA,MAAMC,QAAAA,GAAWR,kBAAAA,CAAmBV,GAAAA,CAAIc,GAAAA,CAAID,IAAI,CAAA,IAAK,CAAA;AACrDH,MAAAA,kBAAAA,CAAmBR,GAAAA,CAAIY,GAAAA,CAAID,IAAAA,EAAMK,QAAAA,GAAW,CAAA,CAAA;AAC5C,MAAA,IAAI,CAACV,qBAAAA,CAAsBW,GAAAA,CAAIL,IAAID,IAAI,CAAA,iBAAkBC,GAAAA,CAAAA;AAC7D,IAAA;EACJ,CAAA,EAPuB,gBAAA,CAAA;AAQvBH,EAAAA,cAAAA,CAAeL,IAAAA,CAAAA;AAGf,EAAA,IAAIc,KAAAA,GAAQ;AAACb,IAAAA,UAAAA,CAAWD,IAAAA;;AACxB,EAAA,OAAOc,KAAAA,CAAMC,SAAS,CAAA,EAAG;AACrB,IAAA,MAAMC,OAAAA,GAAUF,MAAMG,KAAAA,EAAK;AAE3B,IAAA,MAAMC,uBAAAA,GAAkD,IAAIf,GAAAA,CAAI;AAC5D,MAAA;AAACa,QAAAA,OAAAA;AAAS,QAAA;;SACPd,qBAAAA,CAAsBR,GAAAA,CAAIsB,QAAQT,IAAI;AAC5C,KAAA,CAAA;AAED,IAAA,KAAA,MAAWC,GAAAA,IAAOC,MAAAA,CAAOC,MAAAA,CAAOM,OAAAA,CAAQL,YAAY,CAAA,EAAG;AACnD,MAAA,MAAMC,QAAAA,GAAWR,kBAAAA,CAAmBV,GAAAA,CAAIc,GAAAA,CAAID,IAAI,CAAA;AAEhD,MAAA,IAAIK,aAAa,CAAA,EAAG;AAChBE,QAAAA,KAAAA,CAAMK,IAAAA,CAAKlB,UAAAA,CAAWO,GAAAA,CAAAA,CAAAA;AAC1B,MAAA;AAEAJ,MAAAA,kBAAAA,CAAmBR,GAAAA,CAAIY,GAAAA,CAAID,IAAAA,EAAMK,QAAAA,GAAW,CAAA,CAAA;AAG5C,MAAA,MAAMvB,cAAAA,GAAiBa,qBAAAA,CAAsBR,GAAAA,CAAIc,GAAAA,CAAID,IAAI,CAAA;AACzDpB,MAAAA,sBAAAA,CAAuB+B,yBAAyB7B,cAAAA,CAAAA;AACpD,IAAA;AACJ,EAAA;AAEA,EAAA,KAAA,MAAW,CAAC+B,QAAAA,EAAUR,QAAAA,CAAAA,IAAaR,kBAAAA,EAAoB;AACnD,IAAA,IAAIQ,aAAa,CAAA,EAAG;AAChB,MAAA,MAAM,IAAIS,KAAAA,CACN,CAAA,KAAA,EAAQD,QAAAA,CAAAA,eAAAA,EAA0BR,QAAAA,CAAAA,yDAAAA,CAAmE,CAAA;AAE7G,IAAA;AACJ,EAAA;AAEA,EAAA,OAAOV,qBAAAA;AACX,CAAA,EAtDmC,4BAAA,CAAA;AAsE5B,IAAMoB,GAAAA,mBAAM,MAAA,CAAA,CACftB,IAAAA,EACAuB,OAAAA,EACAtB,UAAAA,GAAqC,CAACD,KAAAA,KAASA,KAAAA,EAC/CwB,QAAAA,GAAwB,EAAC,KAAQ;AAEjC,EAAA,MAAMtB,qBAAAA,GAAwBH,0BAAAA,CAA2BC,IAAAA,EAAMC,UAAAA,CAAAA;AAG/D,EAAA,MAAMwB,oBAAAA,uBAA2BtB,GAAAA,EAAAA;AACjC,EAAA,MAAMuB,WAAAA,iCAAqB1B,KAAAA,KAAAA;AACvB,IAAA,MAAM2B,cAAAA,GAAiB1B,WAAWD,KAAAA,CAAAA;AAElC,IAAA,MAAM4B,mBAAmB,EAAA;AACzB,IAAA,KAAA,MAAWC,eAAAA,IAAmBpB,MAAAA,CAAOC,MAAAA,CAAOiB,cAAAA,CAAehB,YAAY,CAAA,EAAG;AAEtE,MAAA,IAAI,CAACc,oBAAAA,CAAqBZ,GAAAA,CAAIgB,eAAAA,CAAgBtB,IAAI,CAAA,EAAG;AACjDkB,QAAAA,oBAAAA,CAAqB7B,GAAAA,CAAIiC,eAAAA,CAAgBtB,IAAAA,EAAMmB,WAAAA,CAAYG,eAAAA,CAAAA,CAAAA;AAC/D,MAAA;AACAD,MAAAA,gBAAAA,CAAiBT,IAAAA,CAAKM,oBAAAA,CAAqB/B,GAAAA,CAAImC,eAAAA,CAAgBtB,IAAI,CAAA,CAAA;AACvE,IAAA;AACA,IAAA,MAAMuB,OAAAA,CAAQC,IAAIH,gBAAAA,CAAAA;AAGlB,IAAA,MAAMI,gBAAAA,GAAmB9B,qBAAAA,CAAsBR,GAAAA,CAAIiC,cAAAA,CAAepB,IAAI,CAAA;AACtE,IAAA,MAAM0B,kBAAkBC,KAAAA,CAAMC,IAAAA,CAAKH,gBAAAA,CAAiBI,OAAAA,EAAO,CAAA,CAAIC,IAAAA,CAAK,CAACC,CAAAA,EAAGC,MAAMD,CAAAA,CAAE,CAAA,CAAA,GAAKC,CAAAA,CAAE,CAAA,CAAE,CAAA;AACzF,IAAA,MAAMC,MAAAA,GAAS,MAAMjB,OAAAA,CACjBvB,KAAAA,EACAiC,eAAAA,CAAgBQ,GAAAA,CAAI,CAAC,CAACzC,KAAAA,EAAM0C,CAAAA,CAAAA,KAAO1C,KAAAA,CAAAA,CAAAA;AAEvC,IAAA,IAAIwC,MAAAA,EAAQ;AACP,MAAA,CAAChB,QAAAA,CAAiBgB,OAAOG,GAAG,CAAA,KAAM,EAAC,EAAGhB,cAAAA,CAAepB,IAAI,CAAA,GAAIiC,MAAAA,CAAOI,KAAAA;AACzE,IAAA;EACJ,CAAA,EAvBoB,aAAA,CAAA;AAyBpB,EAAA,OAAO,YAAA;AACH,IAAA,MAAMlB,YAAY1B,IAAAA,CAAAA;AAClB,IAAA,OAAOwB,QAAAA;AACX,EAAA,CAAA;AACJ,CAAA,EAvCmB,KAAA","file":"index.js","sourcesContent":["import type { DependencyNode } from '@layerzerolabs/dependency-graph';\n\n/**\n * A registrar is a simple interface for an object that provides the ability to traverse the dependency graph.\n * It is implicit in this definition that the registrar should also *register* values adhering to the schemata\n * of the graph.\n */\nexport interface Registrar<ReturnType> {\n traverseDependencies: (rootNode: DependencyNode) => Promise<ReturnType>;\n}\nexport type NodeHandlerFunction = (\n node: DependencyNode,\n ancestry: DependencyNode[],\n) => Promise<{ key: string; value: any }>;\nexport type NodePreHandlerFunction = (node: DependencyNode) => DependencyNode;\n\n// Map of ancestor node -> minimal hop distance\ntype AncestorDistanceByNode = Map<DependencyNode, number>;\n// Index from node name -> its ancestor distance map\ntype AncestryDistanceIndex = Map<string, AncestorDistanceByNode>;\n\n/**\n * In-place merge of minimal ancestry distances.\n *\n * childDistances holds minimal distances from the child to each ancestor (keyed by DependencyNode).\n * For every entry in parentDistances, we update the child's map with (parentDist + 1),\n * keeping the minimal value if the ancestor already exists.\n */\nconst mergeAncestorDistances = (\n parentDistances: AncestorDistanceByNode,\n childDistances: AncestorDistanceByNode,\n) => {\n for (const [ancestor, parentDist] of parentDistances) {\n const candidate = parentDist + 1;\n const current = childDistances.get(ancestor) || Infinity;\n childDistances.set(ancestor, Math.min(current, candidate));\n }\n};\n\n/**\n * Builds a minimal ancestry distance index for all nodes reachable from the root.\n *\n * Returns a Map: node.name -> Map<DependencyNode, distance> where distance is the minimal hop count\n * from the node to that ancestor. We perform a Kahn-style BFS over the DAG, and for each edge\n * curNode -> dep we merge curNode's minimal distances into the dependency with +1 hop and take the minimum.\n */\nconst buildAncestryDistanceIndex = (\n node: DependencyNode,\n prehandler: NodePreHandlerFunction,\n): AncestryDistanceIndex => {\n const ancestryDistanceIndex: AncestryDistanceIndex = new Map();\n const inDegreeByNodeName = new Map<string, number>();\n\n // If A depends on B and C, B depends on C, we initialize with:\n // ancestryDistanceIndex: { A: Map(), B: Map(), C: Map() }\n // inDegreeByNodeName: { B: 1, C: 2 }\n const initializeMaps = (cur: DependencyNode) => {\n ancestryDistanceIndex.set(cur.name, new Map());\n for (const dep of Object.values(cur.dependencies)) {\n const inDegree = inDegreeByNodeName.get(dep.name) || 0;\n inDegreeByNodeName.set(dep.name, inDegree + 1);\n if (!ancestryDistanceIndex.has(dep.name)) initializeMaps(dep);\n }\n };\n initializeMaps(node);\n\n // Kahn-style topological BFS accumulating minimal distance ancestors\n let queue = [prehandler(node)];\n while (queue.length > 0) {\n const curNode = queue.shift()!;\n // Include self with distance 0, then extend with the already known minimal distances\n const currentMinimalDistances: AncestorDistanceByNode = new Map([\n [curNode, 0],\n ...ancestryDistanceIndex.get(curNode.name)!,\n ]);\n // Add the new processable dependencies to the queue, update their state.\n for (const dep of Object.values(curNode.dependencies)) {\n const inDegree = inDegreeByNodeName.get(dep.name)!;\n // We are the last edge missing in the graph for this dependency -> we can process it after us.\n if (inDegree === 1) {\n queue.push(prehandler(dep));\n }\n // Reduce the in-degree of the dependency -> it basically means that this edge got removed from the graph.\n inDegreeByNodeName.set(dep.name, inDegree - 1);\n\n // Merge curNode's minimal distances (+1 hop) into the dependency's minimal distances\n const childDistances = ancestryDistanceIndex.get(dep.name)!;\n mergeAncestorDistances(currentMinimalDistances, childDistances);\n }\n }\n\n for (const [nodeName, inDegree] of inDegreeByNodeName) {\n if (inDegree !== 0) {\n throw new Error(\n `node ${nodeName} has in-degree ${inDegree}, this indicates a cycle in the graph containing the node`,\n );\n }\n }\n\n return ancestryDistanceIndex;\n};\n\n/**\n * Performs a depth-first-search on a tree of dependency nodes, and returns a function\n * that will call the handler for each node in the tree, ordered s.t. the handler of N\n * will be called only after the handlers of dependencies(N) have been called.\n * The node's ancestors are sorted by non-decreasing minimal distance.\n * The resolver function will only call the handler once for each unique definition node.\n * The resolver function returns an object whose keys are the keys defined\n * by each of the handlers, and whose values are objects whose keys are the names\n * of the nodes resolved and whose values are the values defined by the handlers.\n * @param node the root node of the tree\n * @param handler a function that accepts a node and registers it\n * @param prehandler a function that accepts a node and returns a node. Will be used to pre-process the graph\n * @returns a resolver function\n */\nexport const dfs = <ReturnTypes>(\n node: DependencyNode,\n handler: NodeHandlerFunction,\n prehandler: NodePreHandlerFunction = (node) => node,\n _returns: ReturnTypes = {} as any,\n): (() => Promise<ReturnTypes>) => {\n const ancestryDistanceIndex = buildAncestryDistanceIndex(node, prehandler);\n\n // Maintains Map<node.name, Promise<void>> -> the promise to resolve the node, for all nodes.\n const nodeResolverPromises = new Map<string, Promise<void>>();\n const resolveNode = async (node: DependencyNode) => {\n const prehandledNode = prehandler(node);\n // first wait for all its children\n const childrenPromises = [];\n for (const dependencyValue of Object.values(prehandledNode.dependencies)) {\n // Grab the cached promise or create it, we want to have only 1 handler promise for each node.\n if (!nodeResolverPromises.has(dependencyValue.name)) {\n nodeResolverPromises.set(dependencyValue.name, resolveNode(dependencyValue));\n }\n childrenPromises.push(nodeResolverPromises.get(dependencyValue.name)!);\n }\n await Promise.all(childrenPromises);\n\n // Then, you can process yourself. Build ancestry sorted by non-decreasing minimal distance.\n const minimalDistances = ancestryDistanceIndex.get(prehandledNode.name)!;\n const sortedAncestors = Array.from(minimalDistances.entries()).sort((a, b) => a[1] - b[1]);\n const regRes = await handler(\n node,\n sortedAncestors.map(([node, _]) => node),\n );\n if (regRes) {\n ((_returns as any)[regRes.key] ??= {})[prehandledNode.name] = regRes.value;\n }\n };\n\n return async () => {\n await resolveNode(node);\n return _returns;\n };\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layerzerolabs/dfs",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,13 +14,13 @@
14
14
  "module": "./dist/index.js",
15
15
  "types": "./dist/index.d.ts",
16
16
  "dependencies": {
17
- "@layerzerolabs/dependency-graph": "0.0.20"
17
+ "@layerzerolabs/dependency-graph": "0.0.22"
18
18
  },
19
19
  "devDependencies": {
20
20
  "tsup": "^8.4.0",
21
21
  "vitest": "^3.2.3",
22
- "@layerzerolabs/typescript-configuration": "0.0.20",
23
- "@layerzerolabs/tsup-configuration": "0.0.20"
22
+ "@layerzerolabs/tsup-configuration": "0.0.22",
23
+ "@layerzerolabs/typescript-configuration": "0.0.22"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "restricted",
package/src/index.ts CHANGED
@@ -14,67 +14,104 @@ export type NodeHandlerFunction = (
14
14
  ) => Promise<{ key: string; value: any }>;
15
15
  export type NodePreHandlerFunction = (node: DependencyNode) => DependencyNode;
16
16
 
17
- const buildAncestorsByNodeName = (node: DependencyNode, prehandler: NodePreHandlerFunction) => {
18
- const ancestorsByNodeName = new Map<string, Set<DependencyNode>>();
19
- const dependentCountByNodeName = new Map<string, number>();
17
+ // Map of ancestor node -> minimal hop distance
18
+ type AncestorDistanceByNode = Map<DependencyNode, number>;
19
+ // Index from node name -> its ancestor distance map
20
+ type AncestryDistanceIndex = Map<string, AncestorDistanceByNode>;
21
+
22
+ /**
23
+ * In-place merge of minimal ancestry distances.
24
+ *
25
+ * childDistances holds minimal distances from the child to each ancestor (keyed by DependencyNode).
26
+ * For every entry in parentDistances, we update the child's map with (parentDist + 1),
27
+ * keeping the minimal value if the ancestor already exists.
28
+ */
29
+ const mergeAncestorDistances = (
30
+ parentDistances: AncestorDistanceByNode,
31
+ childDistances: AncestorDistanceByNode,
32
+ ) => {
33
+ for (const [ancestor, parentDist] of parentDistances) {
34
+ const candidate = parentDist + 1;
35
+ const current = childDistances.get(ancestor) || Infinity;
36
+ childDistances.set(ancestor, Math.min(current, candidate));
37
+ }
38
+ };
39
+
40
+ /**
41
+ * Builds a minimal ancestry distance index for all nodes reachable from the root.
42
+ *
43
+ * Returns a Map: node.name -> Map<DependencyNode, distance> where distance is the minimal hop count
44
+ * from the node to that ancestor. We perform a Kahn-style BFS over the DAG, and for each edge
45
+ * curNode -> dep we merge curNode's minimal distances into the dependency with +1 hop and take the minimum.
46
+ */
47
+ const buildAncestryDistanceIndex = (
48
+ node: DependencyNode,
49
+ prehandler: NodePreHandlerFunction,
50
+ ): AncestryDistanceIndex => {
51
+ const ancestryDistanceIndex: AncestryDistanceIndex = new Map();
52
+ const inDegreeByNodeName = new Map<string, number>();
20
53
 
21
54
  // If A depends on B and C, B depends on C, we initialize with:
22
- // ancestorsByNodeName: { A: Set([]), B: Set([]), C: Set([]) }
23
- // dependentsCountByNodeName: { B: 1, C: 2 }
24
- const initializeMaps = (node: DependencyNode) => {
25
- ancestorsByNodeName.set(node.name, new Set([]));
26
- for (const dependencyValue of Object.values(node.dependencies)) {
27
- const inDegree = dependentCountByNodeName.get(dependencyValue.name) || 0;
28
- dependentCountByNodeName.set(dependencyValue.name, inDegree + 1);
29
- if (!ancestorsByNodeName.has(dependencyValue.name)) {
30
- initializeMaps(dependencyValue);
31
- }
55
+ // ancestryDistanceIndex: { A: Map(), B: Map(), C: Map() }
56
+ // inDegreeByNodeName: { B: 1, C: 2 }
57
+ const initializeMaps = (cur: DependencyNode) => {
58
+ ancestryDistanceIndex.set(cur.name, new Map());
59
+ for (const dep of Object.values(cur.dependencies)) {
60
+ const inDegree = inDegreeByNodeName.get(dep.name) || 0;
61
+ inDegreeByNodeName.set(dep.name, inDegree + 1);
62
+ if (!ancestryDistanceIndex.has(dep.name)) initializeMaps(dep);
32
63
  }
33
64
  };
34
65
  initializeMaps(node);
35
66
 
36
- // topological sorted bfs exploration
37
- // induction by level, initial level has correct initial state, build level + 1
38
- // bfs is used to build ancestry correctly
39
- // this should actually be a queue, but its probably fine for now
67
+ // Kahn-style topological BFS accumulating minimal distance ancestors
40
68
  let queue = [prehandler(node)];
41
69
  while (queue.length > 0) {
42
70
  const curNode = queue.shift()!;
43
-
44
- // add the new processable dependencies to the queue, update their state.
45
- for (const dependencyValue of Object.values(curNode.dependencies)) {
46
- const inDegree = dependentCountByNodeName.get(dependencyValue.name)!;
71
+ // Include self with distance 0, then extend with the already known minimal distances
72
+ const currentMinimalDistances: AncestorDistanceByNode = new Map([
73
+ [curNode, 0],
74
+ ...ancestryDistanceIndex.get(curNode.name)!,
75
+ ]);
76
+ // Add the new processable dependencies to the queue, update their state.
77
+ for (const dep of Object.values(curNode.dependencies)) {
78
+ const inDegree = inDegreeByNodeName.get(dep.name)!;
47
79
  // We are the last edge missing in the graph for this dependency -> we can process it after us.
48
80
  if (inDegree === 1) {
49
- const preHandledDependency = prehandler(dependencyValue);
50
- queue.push(preHandledDependency);
81
+ queue.push(prehandler(dep));
51
82
  }
52
83
  // Reduce the in-degree of the dependency -> it basically means that this edge got removed from the graph.
53
- dependentCountByNodeName.set(dependencyValue.name, inDegree - 1);
84
+ inDegreeByNodeName.set(dep.name, inDegree - 1);
54
85
 
55
- // add our ancestry to them, js sets maintain order of insertion, this means their ancestors are in distance order due to BFS properties.
56
- const childAncestry = ancestorsByNodeName.get(dependencyValue.name)!;
57
- const nodeAncestry = ancestorsByNodeName.get(curNode.name)!;
58
- for (const ancestor of nodeAncestry) {
59
- childAncestry.add(ancestor);
60
- }
86
+ // Merge curNode's minimal distances (+1 hop) into the dependency's minimal distances
87
+ const childDistances = ancestryDistanceIndex.get(dep.name)!;
88
+ mergeAncestorDistances(currentMinimalDistances, childDistances);
89
+ }
90
+ }
91
+
92
+ for (const [nodeName, inDegree] of inDegreeByNodeName) {
93
+ if (inDegree !== 0) {
94
+ throw new Error(
95
+ `node ${nodeName} has in-degree ${inDegree}, this indicates a cycle in the graph containing the node`,
96
+ );
61
97
  }
62
98
  }
63
99
 
64
- return ancestorsByNodeName;
100
+ return ancestryDistanceIndex;
65
101
  };
66
102
 
67
103
  /**
68
104
  * Performs a depth-first-search on a tree of dependency nodes, and returns a function
69
105
  * that will call the handler for each node in the tree, ordered s.t. the handler of N
70
106
  * will be called only after the handlers of dependencies(N) have been called.
107
+ * The node's ancestors are sorted by non-decreasing minimal distance.
71
108
  * The resolver function will only call the handler once for each unique definition node.
72
109
  * The resolver function returns an object whose keys are the keys defined
73
110
  * by each of the handlers, and whose values are objects whose keys are the names
74
111
  * of the nodes resolved and whose values are the values defined by the handlers.
75
112
  * @param node the root node of the tree
76
113
  * @param handler a function that accepts a node and registers it
77
- * @param prehandler a function that accepts a node and returns a node. will be use to pre-process the graph
114
+ * @param prehandler a function that accepts a node and returns a node. Will be used to pre-process the graph
78
115
  * @returns a resolver function
79
116
  */
80
117
  export const dfs = <ReturnTypes>(
@@ -83,7 +120,7 @@ export const dfs = <ReturnTypes>(
83
120
  prehandler: NodePreHandlerFunction = (node) => node,
84
121
  _returns: ReturnTypes = {} as any,
85
122
  ): (() => Promise<ReturnTypes>) => {
86
- const ancestorsByNodeName = buildAncestorsByNodeName(node, prehandler);
123
+ const ancestryDistanceIndex = buildAncestryDistanceIndex(node, prehandler);
87
124
 
88
125
  // Maintains Map<node.name, Promise<void>> -> the promise to resolve the node, for all nodes.
89
126
  const nodeResolverPromises = new Map<string, Promise<void>>();
@@ -100,9 +137,13 @@ export const dfs = <ReturnTypes>(
100
137
  }
101
138
  await Promise.all(childrenPromises);
102
139
 
103
- // Then, you can process yourself
104
- const ancestry = ancestorsByNodeName.get(prehandledNode.name)!;
105
- const regRes = await handler(node, [...ancestry]);
140
+ // Then, you can process yourself. Build ancestry sorted by non-decreasing minimal distance.
141
+ const minimalDistances = ancestryDistanceIndex.get(prehandledNode.name)!;
142
+ const sortedAncestors = Array.from(minimalDistances.entries()).sort((a, b) => a[1] - b[1]);
143
+ const regRes = await handler(
144
+ node,
145
+ sortedAncestors.map(([node, _]) => node),
146
+ );
106
147
  if (regRes) {
107
148
  ((_returns as any)[regRes.key] ??= {})[prehandledNode.name] = regRes.value;
108
149
  }
package/test/dfs.test.ts CHANGED
@@ -110,3 +110,221 @@ describe('DI Depth-first-search', () => {
110
110
  expect(count).toBe(4);
111
111
  });
112
112
  });
113
+
114
+ describe('DI Ancestry - inclusion and distance order', () => {
115
+ test('C -> B -> A', async () => {
116
+ const { ancestry } = await dfs<{ ancestry: Record<string, string[]> }>(
117
+ mySimpleClassC,
118
+ async (_node, ancestry) => {
119
+ return { key: 'ancestry', value: ancestry.map((n) => n.name) };
120
+ },
121
+ )();
122
+
123
+ expect(ancestry.MySimpleClassC).toStrictEqual([]);
124
+ expect(ancestry.MySimpleClassB).toStrictEqual(['MySimpleClassC']);
125
+ expect(ancestry.MySimpleClassA).toStrictEqual(['MySimpleClassB', 'MySimpleClassC']);
126
+ });
127
+
128
+ test('D -> (C, B, A) with C -> B -> A', async () => {
129
+ const { ancestry } = await dfs<{ ancestry: Record<string, string[]> }>(
130
+ mySimpleClassD,
131
+ async (_node, ancestry) => {
132
+ return { key: 'ancestry', value: ancestry.map((n) => n.name) };
133
+ },
134
+ )();
135
+
136
+ // D is the root
137
+ expect(ancestry.MySimpleClassD).toStrictEqual([]);
138
+
139
+ // C has only D above it
140
+ expect(ancestry.MySimpleClassC).toStrictEqual(['MySimpleClassD']);
141
+
142
+ // B has both C and D at distance 1 (order between them is not asserted)
143
+ expect(new Set(ancestry.MySimpleClassB)).toStrictEqual(
144
+ new Set(['MySimpleClassC', 'MySimpleClassD']),
145
+ );
146
+ expect(ancestry.MySimpleClassB.length).toBe(2);
147
+
148
+ // A must include B and D (distance 1, order between them not asserted), then C (distance 2)
149
+ expect(new Set(ancestry.MySimpleClassA.slice(0, 2))).toStrictEqual(
150
+ new Set(['MySimpleClassB', 'MySimpleClassD']),
151
+ );
152
+ expect(ancestry.MySimpleClassA[2]).toBe('MySimpleClassC');
153
+ });
154
+
155
+ test('ancestry matches expected distance layers', async () => {
156
+ const { ancestry } = await dfs<{ ancestry: Record<string, string[]> }>(
157
+ mySimpleClassD,
158
+ async (_node, ancestry) => {
159
+ return { key: 'ancestry', value: ancestry.map((n) => n.name) };
160
+ },
161
+ )();
162
+
163
+ const expectedLayers: Record<string, string[][]> = {
164
+ MySimpleClassD: [],
165
+ MySimpleClassC: [['MySimpleClassD']],
166
+ MySimpleClassB: [['MySimpleClassC', 'MySimpleClassD']],
167
+ MySimpleClassA: [['MySimpleClassB', 'MySimpleClassD'], ['MySimpleClassC']],
168
+ };
169
+
170
+ for (const [nodeName, layers] of Object.entries(expectedLayers)) {
171
+ const actual = ancestry[nodeName];
172
+ let idx = 0;
173
+ for (const layer of layers) {
174
+ const segment = actual.slice(idx, idx + layer.length);
175
+ expect(new Set(segment)).toStrictEqual(new Set(layer));
176
+ idx += layer.length;
177
+ }
178
+ expect(idx).toBe(actual.length);
179
+ }
180
+ });
181
+ });
182
+
183
+ describe('DI Ancestry - randomized DAG property', () => {
184
+ const GNode = class<
185
+ Name extends string,
186
+ _Dependencies extends Dependencies,
187
+ > extends DependencyNode<Name, _Dependencies> {};
188
+
189
+ const makeRng = (seed: number) => {
190
+ let state = seed >>> 0;
191
+ return () => {
192
+ state = (state * 1664525 + 1013904223) >>> 0; // LCG
193
+ return state / 2 ** 32;
194
+ };
195
+ };
196
+
197
+ const buildRandomDag = (
198
+ numNodes: number,
199
+ edgeProbability: number,
200
+ seed: number,
201
+ ): DependencyNode<any, any>[] => {
202
+ const rng = makeRng(seed);
203
+ const nodes: DependencyNode<any, any>[] = [];
204
+ for (let i = 0; i < numNodes; i++) {
205
+ const deps: Record<string, DependencyNode<any, any>> = {};
206
+ for (let j = 0; j < i; j++) {
207
+ if (rng() < edgeProbability) {
208
+ deps[`d${j}`] = nodes[j];
209
+ }
210
+ }
211
+ if (i > 0 && Object.keys(deps).length === 0) {
212
+ const j = Math.floor(rng() * i);
213
+ deps[`d${j}`] = nodes[j];
214
+ }
215
+ nodes.push(new GNode({ name: `N${i}`, dependencies: deps }));
216
+ }
217
+ return nodes;
218
+ };
219
+
220
+ const collectReachable = (root: DependencyNode<any, any>) => {
221
+ const map = new Map<string, DependencyNode<any, any>>();
222
+ const queue: DependencyNode<any, any>[] = [root];
223
+ while (queue.length > 0) {
224
+ const cur = queue.shift()!;
225
+ if (map.has(cur.name)) continue;
226
+ map.set(cur.name, cur);
227
+ for (const dep of Object.values(cur.dependencies) as DependencyNode<any, any>[]) {
228
+ queue.push(dep);
229
+ }
230
+ }
231
+ return map;
232
+ };
233
+
234
+ const buildDependentsIndex = (nodes: Map<string, DependencyNode<any, any>>) => {
235
+ const dependentsIndex = new Map<string, Set<string>>();
236
+ for (const node of nodes.values()) dependentsIndex.set(node.name, new Set());
237
+ for (const node of nodes.values()) {
238
+ for (const dep of Object.values(node.dependencies) as DependencyNode<any, any>[]) {
239
+ dependentsIndex.get(dep.name)!.add(node.name);
240
+ }
241
+ }
242
+ return dependentsIndex;
243
+ };
244
+
245
+ const computeDependentDistances = (
246
+ start: string,
247
+ dependentsIndex: Map<string, Set<string>>,
248
+ ) => {
249
+ const distances = new Map<string, number>();
250
+ const queue: Array<{ name: string; dist: number }> = [{ name: start, dist: 0 }];
251
+ distances.set(start, 0);
252
+ while (queue.length > 0) {
253
+ const { name, dist } = queue.shift()!;
254
+ for (const next of dependentsIndex.get(name) || [])
255
+ if (!distances.has(next)) {
256
+ distances.set(next, dist + 1);
257
+ queue.push({ name: next, dist: dist + 1 });
258
+ }
259
+ }
260
+ distances.delete(start);
261
+ return distances;
262
+ };
263
+
264
+ test('random DAGs: ancestry includes all ancestors and distances are non-decreasing', async () => {
265
+ // increase/play with these values if you're making changes :D
266
+ const iterations = 2;
267
+ const numNodes = 200;
268
+
269
+ // Edge probability p: node i has E[in-degree(i)] ≈ p*i (edges only to earlier nodes).
270
+ // Average per node ≈ p*(n-1)/2. For n=500 and p=0.25, avg ≈ 62; the last node ≈ 125.
271
+ const edgeProbability = 0.25;
272
+
273
+ // Sampling probability: fraction of reachable nodes validated.
274
+ // E[sampled] ≈ reachableCount * sampleProbability (≈100 if ~500 reachable).
275
+ const sampleProbability = 0.2;
276
+
277
+ const seeds = Array.from({ length: iterations }, () =>
278
+ Math.floor(Math.random() * 0x7fffffff),
279
+ );
280
+
281
+ for (const seed of seeds) {
282
+ try {
283
+ const nodes = buildRandomDag(numNodes, edgeProbability, seed);
284
+ const root = nodes[nodes.length - 1];
285
+ const res = await dfs<{ ancestry: Record<string, string[]> }>(
286
+ root,
287
+ async (_node, ancestry) => {
288
+ return { key: 'ancestry', value: ancestry.map((n) => n.name) };
289
+ },
290
+ )();
291
+ const reachable = collectReachable(root);
292
+ const dependentsIndex = buildDependentsIndex(reachable);
293
+
294
+ // dfs should traverse exactly the reachable set
295
+ expect(new Set(Object.keys(res.ancestry))).toStrictEqual(new Set(reachable.keys()));
296
+
297
+ const sample: { nodeName: string; list: string[] }[] = [];
298
+ for (const [nodeName, list] of Object.entries(res.ancestry)) {
299
+ if (Math.random() < sampleProbability) {
300
+ sample.push({ nodeName, list });
301
+ }
302
+ }
303
+
304
+ for (const { nodeName, list } of sample) {
305
+ // No self in ancestry
306
+ expect(list.includes(nodeName)).toBe(false);
307
+ // Inclusion: exactly the set of reachable dependents
308
+ const distMap = computeDependentDistances(nodeName, dependentsIndex);
309
+ expect(new Set(list)).toStrictEqual(new Set(distMap.keys()));
310
+
311
+ // No duplicates
312
+ expect(new Set(list).size).toBe(list.length);
313
+
314
+ // Distance non-decreasing
315
+ for (let i = 1; i < list.length; i++) {
316
+ const prev = distMap.get(list[i - 1])!;
317
+ const cur = distMap.get(list[i])!;
318
+ expect(prev <= cur).toBe(true);
319
+ }
320
+ }
321
+ } catch (err: any) {
322
+ if (err && typeof err === 'object' && 'message' in err) {
323
+ err.message = `failed random dag test with seed ${seed}: ${err.message}`;
324
+ throw err;
325
+ }
326
+ throw new Error(`failed random dag test with seed ${seed}: ${String(err)}`);
327
+ }
328
+ }
329
+ });
330
+ });