Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions src/parsers/npm-lock-graph.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from "node:fs";
import type { NpmLockGraph, NpmLockNode } from "../types.js";
import { unique, uniquePathArrays } from "../utils/array.js";
import { unique } from "../utils/array.js";

type RawLockPackage = {
name?: string;
Expand Down Expand Up @@ -150,29 +150,52 @@ function createGraph(args: {
pathSetByNodeId: Map<string, Set<string>>;
}): NpmLockGraph {
const entryPackages = Object.freeze([...unique(args.entryPackages)]);
const EMPTY_ARRAY: readonly string[] = Object.freeze([]);

// Pre-freeze all node objects so getNode() returns a pre-computed frozen copy
const frozenNodesById = new Map<string, Readonly<NpmLockNode>>();
for (const [id, node] of args.nodesById) {
frozenNodesById.set(id, Object.freeze({ ...node }));
}

// Pre-freeze all package-key -> nodeId arrays
const frozenNodeIdsByPackageKey = new Map<string, readonly string[]>();
for (const [key, ids] of args.nodeIdsByPackageKey) {
frozenNodeIdsByPackageKey.set(key, Object.freeze([...ids]));
}

// Pre-freeze all parent arrays
const frozenParentsByChild = new Map<string, readonly string[]>();
for (const [id, parents] of args.parentNodeIdsByChildNodeId) {
frozenParentsByChild.set(id, Object.freeze([...parents]));
}

// Pre-freeze all children arrays
const frozenChildrenByParent = new Map<string, readonly string[]>();
for (const [id, children] of args.childNodeIdsByParentNodeId) {
frozenChildrenByParent.set(id, Object.freeze([...children]));
}

return {
entryPackages,
nodeIdsFor(name: string, version: string | null): readonly string[] {
return Object.freeze([...(args.nodeIdsByPackageKey.get(buildPackageKey(name, version)) ?? [])]);
return frozenNodeIdsByPackageKey.get(buildPackageKey(name, version)) ?? EMPTY_ARRAY;
},
getNode(nodeId: string): Readonly<NpmLockNode> | null {
const node = args.nodesById.get(nodeId);
return node ? Object.freeze({ ...node }) : null;
return frozenNodesById.get(nodeId) ?? null;
},
parentsFor(nodeId: string): readonly string[] {
return Object.freeze([...(args.parentNodeIdsByChildNodeId.get(nodeId) ?? [])]);
return frozenParentsByChild.get(nodeId) ?? EMPTY_ARRAY;
},
childrenFor(nodeId: string): readonly string[] {
return Object.freeze([...(args.childNodeIdsByParentNodeId.get(nodeId) ?? [])]);
return frozenChildrenByParent.get(nodeId) ?? EMPTY_ARRAY;
},
rangeFor(parentNodeId: string, childName: string): string | null {
return args.rangeByParentNodeId.get(parentNodeId)?.get(childName) ?? null;
},
pathsFor(nodeId: string): string[][] {
const serializedPaths = [...(args.pathSetByNodeId.get(nodeId) ?? new Set<string>())]
return [...(args.pathSetByNodeId.get(nodeId) ?? new Set<string>())]
.map((item) => item.split(">"));
return uniquePathArrays(serializedPaths);
},
};
}
Expand Down