Files
2026-04-26 20:38:45 -04:00

87 lines
2.4 KiB
TypeScript

import canonicals from "~/openapi-canonical-families.json";
import hashes from "~/openapi-operation-hashes.json";
import log from "~/utils/log";
/**
* The known API versions based on operation hashes.
*/
export type Version = keyof typeof hashes;
const VERSIONS = Object.keys(hashes) as Version[];
/**
* Detects the closest matching API version using operation hashes.
* Falls back to the latest known version and emits a warning if unknown.
*
* @param observed - A mapping of operation identifiers to their hashes.
* @returns The detected API version.
*/
export function detectApiVersion(observed: Record<string, string> | null): Version {
if (!observed) {
const latest = VERSIONS.at(-1)!;
log.warn("api", "No operation hashes observed, defaulting to version %s", latest);
return latest;
}
let bestVersion: Version | null = null;
let bestScore = -1;
for (const [version, known] of Object.entries(hashes) as [Version, Record<string, string>][]) {
let score = 0;
for (const [op, hash] of Object.entries(observed)) {
if (known[op] === hash) score++;
}
if (score > bestScore) {
bestVersion = version;
bestScore = score;
}
}
if (!bestVersion || bestScore === 0) {
const latest = VERSIONS.at(-1)!;
log.warn("api", "Could not determine API version, defaulting to %s", latest);
return latest;
}
if (bestScore < Object.keys(observed).length) {
log.warn(
"api",
"Partial version match: %d/%d endpoints for version %s",
bestScore,
Object.keys(observed).length,
bestVersion,
);
}
const canonical = Object.entries(canonicals).find(([_, family]) =>
family.includes(bestVersion),
)?.[0] as Version | undefined;
if (!canonical) {
log.warn("api", "Could not canonicalize detected version %s, using as-is", bestVersion);
return bestVersion;
}
if (canonical !== bestVersion) {
log.info(
"api",
"Canonicalizing detected version %s → %s (same schema)",
bestVersion,
canonical,
);
}
return canonical;
}
/**
* Checks if the current version is at least the baseline version.
*
* @param current - The current API version.
* @param baseline - The baseline API version to compare against.
* @returns True if current is at least baseline, false otherwise.
*/
export function isAtLeast(current: Version, baseline: Version) {
return VERSIONS.indexOf(current) >= VERSIONS.indexOf(baseline);
}