Files
renovate/lib/modules/datasource/rubygems/metadata-cache.ts
renovate[bot] 196101a6bb chore(deps): update dependency oxlint-tsgolint to v0.25.0 (main) (#44424)
* chore(deps): update dependency oxlint-tsgolint to v0.25.0

| datasource | package         | from   | to     |
| ---------- | --------------- | ------ | ------ |
| npm        | oxlint-tsgolint | 0.23.0 | 0.25.0 |

* chore: fix new no-unnecessary-type-assertion findings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Sebastian Poxhofer <sebastian.poxhofer@stedi.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 07:04:21 +00:00

124 lines
3.7 KiB
TypeScript

import { logger } from '../../../logger/index.ts';
import * as packageCache from '../../../util/cache/package/index.ts';
import { toSha256 } from '../../../util/hash.ts';
import type { Http } from '../../../util/http/index.ts';
import type { AsyncResult } from '../../../util/result.ts';
import { Result } from '../../../util/result.ts';
import { parseUrl } from '../../../util/url.ts';
import type { ReleaseResult } from '../types.ts';
import { getV1Releases } from './common.ts';
interface CacheRecord {
hash: string;
data: ReleaseResult;
isFallback?: true;
}
function hashVersions(versions: string[]): string {
return toSha256(versions.sort().join(','));
}
function hashReleases(releases: ReleaseResult): string {
return hashVersions(releases.releases.map((release) => release.version));
}
interface CacheNotFoundError {
type: 'cache-not-found';
}
interface CacheStaleError {
type: 'cache-stale';
cache: CacheRecord;
}
interface CacheInvalidError {
type: 'cache-invalid';
}
type CacheLoadError = CacheNotFoundError | CacheStaleError;
type CacheError = CacheNotFoundError | CacheStaleError | CacheInvalidError;
export class MetadataCache {
private readonly http: Http;
constructor(http: Http) {
this.http = http;
}
async getRelease(
registryUrl: string,
packageName: string,
versions: string[],
): Promise<ReleaseResult> {
const cacheNs = `datasource-rubygems`;
const cacheKey = `metadata-cache:${registryUrl}:${packageName}`;
const versionsHash = hashVersions(versions);
function loadCache(): AsyncResult<ReleaseResult, CacheLoadError> {
return Result.wrapNullable<CacheRecord, CacheLoadError, CacheLoadError>(
packageCache.get<CacheRecord>(cacheNs, cacheKey),
{ type: 'cache-not-found' },
).transform((cache) => {
return versionsHash === cache.hash
? Result.ok(cache.data)
: Result.err({ type: 'cache-stale', cache });
});
}
async function saveCache(
cache: CacheRecord,
ttlMinutes = 100 * 24 * 60,
ttlDelta = 10 * 24 * 60,
): Promise<void> {
const registryHostname = parseUrl(registryUrl)?.hostname;
if (registryHostname === 'rubygems.org') {
const ttlRandomDelta = Math.floor(Math.random() * ttlDelta);
const ttl = ttlMinutes + ttlRandomDelta;
await packageCache.set(cacheNs, cacheKey, cache, ttl);
}
}
return await loadCache()
.catch((err) =>
getV1Releases(this.http, registryUrl, packageName).transform(
async (
data: ReleaseResult,
): Promise<Result<ReleaseResult, CacheError>> => {
const dataHash = hashReleases(data);
if (dataHash === versionsHash) {
await saveCache({
hash: dataHash,
data,
});
return Result.ok(data);
}
/**
* Return stale cache for 24 hours,
* if metadata is inconsistent with versions list.
*/
if (err.type === 'cache-stale') {
const staleCache = err.cache;
if (!staleCache.isFallback) {
await saveCache(
{ ...staleCache, isFallback: true },
24 * 60,
0,
);
}
return Result.ok(staleCache.data);
}
return Result.err({ type: 'cache-invalid' });
},
),
)
.catch((err) => {
logger.debug(
{ err },
'Rubygems: error fetching rubygems data, falling back to versions-only result',
);
const releases = versions.map((version) => ({ version }));
return Result.ok({ releases });
})
.unwrapOrThrow();
}
}