Skip to content

Commit

Permalink
refactor(lib): an option to enable/disable cache
Browse files Browse the repository at this point in the history
  • Loading branch information
hasundue committed Mar 6, 2024
1 parent ae83e79 commit ab204c9
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 56 deletions.
100 changes: 49 additions & 51 deletions lib/dependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,62 +148,64 @@ export function toUrl(dependency: Dependency): string {
*/
export async function resolveLatestVersion(
dependency: Dependency,
options?: { cache?: boolean },
): Promise<UpdatedDependency | undefined> {
await LatestVersionCache.lock(dependency.name);
using cache = new LatestVersionCache(dependency.name);
if (options?.cache) {
const cached = cache.get(dependency.name);
if (cached) {
return { ...cached, path: dependency.path };
}
if (cached === null) {
// The dependency is already found to be up to date or unable to resolve.
return;
}
}
const constraint = dependency.version
? SemVer.tryParseRange(dependency.version)
: undefined;
// Do not update inequality ranges.
if (constraint && constraint.flat().length > 1) {
return;
}
const result = await _resolveLatestVersion(dependency);
LatestVersionCache.unlock(dependency.name);
if (options?.cache) {
cache.set(dependency.name, result ?? null);
}
return result;
}

class LatestVersionCache {
class LatestVersionCache implements Disposable {
static #mutex = new Map<string, Mutex>();
static #cache = new Map<string, UpdatedDependency | null>();

static lock(name: string): Promise<void> {
const mutex = this.#mutex.get(name) ??
this.#mutex.set(name, new Mutex()).get(name)!;
return mutex.acquire();
}

static unlock(name: string): void {
const mutex = this.#mutex.get(name);
assertExists(mutex);
mutex.release();
constructor(readonly name: string) {
const mutex = LatestVersionCache.#mutex.get(name) ??
LatestVersionCache.#mutex.set(name, new Mutex()).get(name)!;
mutex.acquire();
}

static get(name: string): UpdatedDependency | null | undefined {
return this.#cache.get(name);
get(name: string): UpdatedDependency | null | undefined {
return LatestVersionCache.#cache.get(name);
}

static set<T extends UpdatedDependency | null>(
set<T extends UpdatedDependency | null>(
name: string,
dependency: T,
): T {
this.#cache.set(name, dependency);
return dependency;
): void {
LatestVersionCache.#cache.set(name, dependency);
}

[Symbol.dispose]() {
const mutex = LatestVersionCache.#mutex.get(this.name);
assertExists(mutex);
mutex.release();
}
}

async function _resolveLatestVersion(
dependency: Dependency,
): Promise<UpdatedDependency | undefined> {
const cached = LatestVersionCache.get(dependency.name);
if (cached) {
return { ...cached, path: dependency.path };
}
if (cached === null) {
// The dependency is already found to be up to date or unable to resolve.
return;
}
const constraint = dependency.version
? SemVer.tryParseRange(dependency.version)
: undefined;

// Do not update inequality ranges.
if (constraint && constraint.flat().length > 1) {
return;
}

switch (dependency.protocol) {
case "npm:": {
const response = await fetch(
Expand All @@ -221,10 +223,7 @@ async function _resolveLatestVersion(
if (latest === dependency.version || isPreRelease(latest)) {
break;
}
return LatestVersionCache.set(
dependency.name,
{ ...dependency, version: latest },
);
return { ...dependency, version: latest };
}
// Ref: https://jsr.io/docs/api#jsr-registry-api
case "jsr:": {
Expand All @@ -248,10 +247,7 @@ async function _resolveLatestVersion(
if (latest === dependency.version || isPreRelease(latest)) {
break;
}
return LatestVersionCache.set(
dependency.name,
{ ...dependency, version: latest },
);
return { ...dependency, version: latest };
}
case "http:":
case "https:": {
Expand All @@ -263,17 +259,19 @@ async function _resolveLatestVersion(
if (!response.redirected) {
break;
}
const latest = parse(response.url);
if (!latest.version || isPreRelease(latest.version)) {
const redirected = parse(response.url);
if (!redirected.version || isPreRelease(redirected.version)) {
break;
}
return LatestVersionCache.set(
dependency.name,
latest as UpdatedDependency,
);
const latest = redirected as UpdatedDependency;
return {
...latest,
// Preserve the original path if it is the root, which is unlikely to be
// included in the redirected URL.
// path: dependency.path === "/" ? "/" : latest.path,
};
}
}
LatestVersionCache.set(dependency.name, null);
}

const isNpmPackageMeta = is.ObjectOf({
Expand Down
19 changes: 14 additions & 5 deletions lib/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ class DenoGraph {
}

export interface CollectOptions {
/**
* Whether to use the cache to resolve dependencies.
* @default true
*/
cache?: boolean;
/**
* The working directory to resolve relative paths.
* If not specified, the current working directory is used.
Expand Down Expand Up @@ -167,7 +172,7 @@ export async function collect(
const update = await _createDependencyUpdate(
dependency,
mod.specifier,
{ ...options, importMap },
{ cache: true, ...options, importMap },
);
if (update) updates.push(update);
})
Expand Down Expand Up @@ -218,7 +223,7 @@ const load: NonNullable<CreateGraphOptions["load"]> = async (
async function _createDependencyUpdate(
dependencyJson: DependencyJson,
referrer: string,
options?: Pick<CollectOptions, "ignore" | "only"> & {
options?: Pick<CollectOptions, "cache" | "ignore" | "only"> & {
importMap?: ImportMap;
},
): Promise<DependencyUpdate | undefined> {
Expand All @@ -243,7 +248,9 @@ async function _createDependencyUpdate(
if (options?.only && !options.only(dependency)) {
return;
}
const latest = await resolveLatestVersion(dependency);
const latest = await resolveLatestVersion(dependency, {
cache: options?.cache,
});
if (!latest || latest.version === dependency.version) {
return;
}
Expand Down Expand Up @@ -275,7 +282,7 @@ async function _createDependencyUpdate(

async function _collectFromImportMap(
specifier: ModuleJson["specifier"],
options: Pick<CollectOptions, "ignore" | "only">,
options: Pick<CollectOptions, "cache" | "ignore" | "only">,
): Promise<DependencyUpdate[]> {
const json = await readImportMapJson(new URL(specifier));
const updates: DependencyUpdate[] = [];
Expand All @@ -292,7 +299,9 @@ async function _collectFromImportMap(
if (options.only && !options.only(dependency)) {
return;
}
const latest = await resolveLatestVersion(dependency);
const latest = await resolveLatestVersion(dependency, {
cache: options.cache,
});
if (!latest || latest.version === dependency.version) {
return;
}
Expand Down
1 change: 1 addition & 0 deletions lib/update_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ async function test(
) {
try {
const updates = await collect(new URL(path, import.meta.url), {
cache: false,
cwd: new URL(dirname(path), import.meta.url),
});
Deno.test(
Expand Down

0 comments on commit ab204c9

Please sign in to comment.