@typespec/versioning
Version:
TypeSpec library for declaring and emitting versioned APIs
167 lines • 5.32 kB
JavaScript
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