UNPKG

@typespec/versioning

Version:

TypeSpec library for declaring and emitting versioned APIs

167 lines 5.32 kB
import { compilerAssert, getTypeName } from "@typespec/compiler"; import { getVersions } from "./versioning.js"; /** * Represent a timeline of all the version involved in the versioning of a namespace * * @example * Given the following namespaces with their versions * ``` * Library: * l1 * l2 * l3 * l4 * * Service: * v1 -> (using) l1 * v2 -> (using) l3 * v3 -> (using) l3 * ``` * * This would be the data passed to the constructor * ```ts * new VersioningTimeline(program, [ * new Map([[serviceNs, v1], [libraryNs, l1]]), * new Map([[serviceNs, v2], [libraryNs, l3]]), * new Map([[serviceNs, v3], [libraryNs, l3]]), * ]) * ``` * * The following timeline is going to be represented * * | Service | Library | * |---------|---------| * | v1 | l1 | * | | l2 | * | v2 | l3 | * | v3 | l3 | * | | l4 | */ export class VersioningTimeline { #namespaces; #timeline; #momentIndex; #versionIndex; constructor(program, resolutions) { const indexedVersions = new Set(); const namespaces = new Set(); const timeline = (this.#timeline = resolutions.map((x) => new TimelineMoment(x))); for (const resolution of resolutions) { for (const [namespace, version] of resolution.entries()) { indexedVersions.add(version); namespaces.add(namespace); } } this.#namespaces = [...namespaces]; function findIndexToInsert(version) { for (const [index, moment] of timeline.entries()) { const versionAtMoment = moment.getVersion(version.namespace); if (versionAtMoment && version.index < versionAtMoment.index) { return index; } } return -1; } for (const namespace of namespaces) { const [, versions] = getVersions(program, namespace); if (versions === undefined) { continue; } for (const version of versions.getVersions()) { if (!indexedVersions.has(version)) { indexedVersions.add(version); const index = findIndexToInsert(version); const newMoment = new TimelineMoment(new Map([[version.namespace, version]])); if (index === -1) { timeline.push(newMoment); } else { timeline.splice(index, 0, newMoment); } } } } this.#versionIndex = new Map(); this.#momentIndex = new Map(); for (const [index, moment] of timeline.entries()) { this.#momentIndex.set(moment, index); for (const version of moment.versions()) { if (!this.#versionIndex.has(version)) { this.#versionIndex.set(version, index); } } } } prettySerialize() { const hSep = "-".repeat(this.#namespaces.length * 13 + 1); const content = this.#timeline .map((moment) => { return ("| " + this.#namespaces .map((x) => (moment.getVersion(x)?.name ?? "").padEnd(10, " ")) .join(" | ") + " |"); }) .join(`\n${hSep}\n`); return ["", hSep, content, hSep].join("\n"); } get(version) { const index = this.getIndex(version); if (index === -1) { if (version instanceof TimelineMoment) { compilerAssert(false, `Timeline moment "${version?.name}" should have been resolved`); } else { compilerAssert(false, `Version "${version?.name}" from ${getTypeName(version.namespace)} should have been resolved. ${this.prettySerialize()}`); } } return this.#timeline[index]; } /** * Return index in the timeline that this version points to * Returns -1 if version is not found. */ getIndex(version) { const index = version instanceof TimelineMoment ? this.#momentIndex.get(version) : this.#versionIndex.get(version); if (index === undefined) { return -1; } return index; } /** * Return true if {@link isBefore} is before {@link base} * @param isBefore * @param base */ isBefore(isBefore, base) { const isBeforeIndex = this.getIndex(isBefore); const baseIndex = this.getIndex(base); return isBeforeIndex < baseIndex; } first() { return this.#timeline[0]; } [Symbol.iterator]() { return this.#timeline[Symbol.iterator](); } entries() { return this.#timeline.entries(); } } export class TimelineMoment { name; #versionMap; constructor(versionMap) { this.#versionMap = versionMap; this.name = versionMap.values().next().value?.name ?? ""; } getVersion(namespace) { return this.#versionMap.get(namespace); } versions() { return this.#versionMap.values(); } } //# sourceMappingURL=versioning-timeline.js.map