Readme

Dependency Graph Explorer

This val allows for dependency graph exploration.

getDependencyGraph

Iteratively explores the dependency graph of a val and its children, returning the results.

arguments

  • valUrlOrSlug string - the full val url or slug (author/name) of the val

returns

An object containing:

  • dependencyMap Map<string, string[]> - A map of vals (by id) to dependencies (vals or non-val id)
  • moduleMap Map<string, Module> - A map of modules (by val or non-val id) to module information. Module object contains:
    • slug string - the slug of the module, how it should be displayed visually
    • id string - the id of the module, corresponding to how it's displayed in the code, normally as a url.
    • websiteUrl string - the website link for the module, a normally a link to either valtown or npm.
    • category string|null - the category of the module, such as "valtown", "esm.sh", or "npm". Those without a predefined category are in null.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { api } from "https://esm.town/v/pomdtr/api";
import { groups } from "npm:d3-array";
type Module = {
slug: string;
id: string;
websiteUrl: string;
category: string | null;
};
function getModuleInfoFromUrl(url: string): Module {
if (url.startsWith("https://esm.town")) {
const slug = url.split("/v/")[1].split("?v")[0];
return {
// TODO: Should be able to get specific version of val, but for now we remove versions
slug,
id: url.split("?v")[0],
websiteUrl: "https://www.val.town/v/" + slug,
category: "valtown",
};
}
if (url.startsWith("https://esm.sh")) {
const slug = url.split(".sh/")[1].split("@")[0];
return {
slug,
id: url.split("@")[0],
websiteUrl: "https://www.npmjs.com/package/" + slug,
category: "esm.sh",
};
}
if (url.startsWith("npm:")) {
const slug = url.startsWith("npm:@")
? "@" + url.split("@")[1]
: url.replace("npm:", "").split("@")[0];
return {
slug,
id: "npm:" + slug.replace("@", ""),
websiteUrl: "https://www.npmjs.com/package/" + slug,
category: "npm",
};
}
return {
slug: url,
id: url.replaceAll("@", ""),
websiteUrl: url,
category: null,
};
}
function getModulesFromCode(valCode: string): Module[] {
const importRegex = new RegExp(`^import.*from ["'](.*?)["'];`, "gm");
let match;
const imports: string[] = [];
while ((match = importRegex.exec(valCode)) !== null) {
imports.push(match[1]);
}
return imports.map(d => getModuleInfoFromUrl(d));
}
export async function getDependencyGraph(valUrlOrSlug: string) {
// clean the url or slug
const initialSlug = valUrlOrSlug.includes("/v/")
? valUrlOrSlug.split("/v/")[1]
: valUrlOrSlug;
const initialModule = getModuleInfoFromUrl("https://esm.town/v/" + initialSlug);
// queue of unexplored modules
const moduleQueue = [initialModule];
// map from ids to module object
const moduleMap = new Map<string, Module>();
// map from ids to their dependency ids
const dependencyMap = new Map<string, string[]>();
while (moduleQueue.length > 0) {
const module = moduleQueue.shift();
if (moduleMap.has(module.id)) {
continue;
}
if (module.category == "valtown") {
let code: string | null = null;
try {
code = (await api(`/v1/alias/${module.slug}`)).code;
} catch (e) {
console.warn(e, module);
}
if (code) {
const dependencyModules = getModulesFromCode(code);
dependencyMap.set(module.id, dependencyModules.map(m => m.id));
moduleQueue.push(...dependencyModules);
}
}
moduleMap.set(module.id, module);
}
return { dependencyMap, moduleMap };
}
export async function getMermaidDependencyGraph(valUrlOrSlug: string) {
const { dependencyMap, moduleMap } = await getDependencyGraph(valUrlOrSlug);
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.