Skip to content

Commit

Permalink
refactor: extract type
Browse files Browse the repository at this point in the history
Move dependency-graph and related logic to own module. Also add tests.
  • Loading branch information
ComradeVanti committed Jul 7, 2024
1 parent d95c66e commit 48e67b6
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 43 deletions.
79 changes: 79 additions & 0 deletions src/domain/dependency-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { ResolvePackumentVersionError } from "../packument-version-resolving";
import { DomainName } from "./domain-name";
import { SemanticVersion } from "./semantic-version";
import { RegistryUrl } from "./registry-url";

type ResolvedNode = Readonly<{
resolved: true;
source: RegistryUrl | "built-in";
dependencies: Readonly<Record<DomainName, SemanticVersion>>;
}>;
type UnresolvedNode = Readonly<{
resolved: false;
error: ResolvePackumentVersionError;
}>;
type GraphNode = ResolvedNode | UnresolvedNode;

/**
* A graph indicating which packages depend on which others.
* Dependencies are keyed first by their name and then version.
* @example
* {
* "com.some.package": {
* "1.0.0": {
* resolved: true,
* source: "https://package.openupm.com",
* dependencies: { "com.other.package": "2.0.0" }
* }
* },
* "com.other.package": {
* "2.0.0": {
* resolved: true,
* source: "https://package.openupm.com",
* dependencies: { }
* }
* }
* }
*/
export type DependencyGraph = Readonly<
Record<DomainName, Readonly<Record<SemanticVersion, GraphNode>>>
>;

/**
* An empty dependency graph.
*/
export const emptyDependencyGraph: DependencyGraph = {};

/**
* Add or replace a node in the graph.
* @param graph The graph.
* @param packageName The key package name.
* @param version The key version.
* @param node The new node.
* @returns The updated graph.
*/
export function setGraphNode(
graph: DependencyGraph,
packageName: DomainName,
version: SemanticVersion,
node: GraphNode
): DependencyGraph {
return {
...graph,
[packageName]: { ...graph[packageName], [version]: node },
};
}

/**
* Checks whether a graph has a node at the given key.
* @param graph The graph.
* @param packageName The key package name.
* @param version The key version.
*/
export function graphHasNodeAt(
graph: DependencyGraph,
packageName: DomainName,
version: SemanticVersion
): boolean {
return graph[packageName]?.[version] !== undefined;
}
51 changes: 8 additions & 43 deletions src/services/dependency-resolving.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,20 @@
import { DomainName } from "../domain/domain-name";
import { SemanticVersion } from "../domain/semantic-version";
import { ResolvePackumentVersionError } from "../packument-version-resolving";
import { Registry } from "../domain/registry";
import { CheckIsBuiltInPackage } from "./built-in-package-check";
import { RegistryUrl } from "../domain/registry-url";
import { tryResolvePackumentVersion } from "../domain/packument";
import { FetchPackument } from "../io/packument-io";
import { PackumentNotFoundError } from "../common-errors";
import { recordEntries } from "../utils/record-utils";
import {
DependencyGraph,
emptyDependencyGraph,
graphHasNodeAt,
setGraphNode,
} from "../domain/dependency-graph";

type NameVersionPair = Readonly<[DomainName, SemanticVersion]>;

type ResolvedNode = Readonly<{
resolved: true;
source: RegistryUrl | "built-in";
dependencies: Readonly<Record<DomainName, SemanticVersion>>;
}>;

type UnresolvedNode = Readonly<{
resolved: false;
error: ResolvePackumentVersionError;
}>;

type GraphNode = ResolvedNode | UnresolvedNode;

type DependencyGraph = Readonly<
Record<DomainName, Readonly<Record<SemanticVersion, GraphNode>>>
>;

const emptyGraph: DependencyGraph = {};

function setGraphNode(
graph: DependencyGraph,
packageName: DomainName,
version: SemanticVersion,
node: GraphNode
): DependencyGraph {
return {
...graph,
[packageName]: { ...graph[packageName], [version]: node },
};
}

function graphHasNode(
graph: DependencyGraph,
packageName: DomainName,
version: SemanticVersion
): boolean {
return graph[packageName]?.[version] !== undefined;
}

/**
* Function for resolving all dependencies for a package.
* @param sources Sources from which dependencies can be resolved.
Expand Down Expand Up @@ -80,7 +45,7 @@ export function makeResolveDependency(
if (packagesToCheck.length === 0) return graph;

const [packageName, version] = packagesToCheck[0]!;
if (graphHasNode(graph, packageName, version))
if (graphHasNodeAt(graph, packageName, version))
return await resolveRecursively(
graph,
sources,
Expand Down Expand Up @@ -132,5 +97,5 @@ export function makeResolveDependency(
}

return (sources, packageName, version, deep) =>
resolveRecursively(emptyGraph, sources, [[packageName, version]], deep);
resolveRecursively(emptyDependencyGraph, sources, [[packageName, version]], deep);
}
78 changes: 78 additions & 0 deletions test/domain/deppendency-graph.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
emptyDependencyGraph,
graphHasNodeAt,
setGraphNode,
} from "../../src/domain/dependency-graph";
import { makeDomainName } from "../../src/domain/domain-name";
import { makeSemanticVersion } from "../../src/domain/semantic-version";
import { exampleRegistryUrl } from "./data-registry";
import { PackumentNotFoundError } from "../../src/common-errors";

describe("dependency graph", () => {
const somePackage = makeDomainName("com.some.package");
const someVersion = makeSemanticVersion("1.0.0");

describe("set node", () => {
it("should add new node", () => {
const initial = emptyDependencyGraph;
const node = {
resolved: true,
source: exampleRegistryUrl,
dependencies: {},
} as const;

const withNode = setGraphNode(initial, somePackage, someVersion, node);

expect(withNode).toEqual({ [somePackage]: { [someVersion]: node } });
});

it("should overwrite node", () => {
const initial = setGraphNode(
emptyDependencyGraph,
somePackage,
someVersion,
{
resolved: false,
error: new PackumentNotFoundError(somePackage),
}
);
const node = {
resolved: true,
source: exampleRegistryUrl,
dependencies: {},
} as const;

const withNode = setGraphNode(initial, somePackage, someVersion, node);

expect(withNode).toEqual({ [somePackage]: { [someVersion]: node } });
});
});

describe("has node", () => {
it("should be false if node was not added", () => {
const actual = graphHasNodeAt(
emptyDependencyGraph,
somePackage,
someVersion
);

expect(actual).toBeFalsy();
});

it("should be true if node was added", () => {
const initial = setGraphNode(
emptyDependencyGraph,
somePackage,
someVersion,
{
resolved: false,
error: new PackumentNotFoundError(somePackage),
}
);

const actual = graphHasNodeAt(initial, somePackage, someVersion);

expect(actual).toBeTruthy();
});
});
});

0 comments on commit 48e67b6

Please sign in to comment.