UNPKG

@typespec/versioning

Version:

TypeSpec library for declaring and emitting versioned APIs

197 lines 7.08 kB
import {} from "@typespec/compiler/experimental"; import { getMadeOptionalOn, getMadeRequiredOn, getRenamedFrom, getReturnTypeChangedFrom, getTypeChangedFrom, } from "./decorators.js"; import { VersioningTimeline } from "./versioning-timeline.js"; import { Availability, getAvailabilityMapInTimeline, resolveVersions } from "./versioning.js"; /** * Resolve the set of mutators needed to apply versioning to the service. * @returns either: * - VersionedMutators when the service is versioned. * - TransientVersioningMutator when the service is not versioned but use specific version from another library. * - undefined when the service is not versioned. */ export function getVersioningMutators(program, namespace) { const versions = resolveVersions(program, namespace); const timeline = new VersioningTimeline(program, versions.map((x) => x.versions)); const helper = new VersioningHelper(program, timeline); if (versions.length === 1 && versions[0].rootVersion === undefined && versions[0].versions.size > 0) { return { kind: "transient", mutator: createVersionMutator(helper, timeline.get(versions[0].versions.values().next().value)), }; } const snapshots = versions .map((resolution) => { if (resolution.versions.size === 0) { return undefined; } else { return { version: resolution.rootVersion, mutator: createVersionMutator(helper, timeline.get(resolution.versions.values().next().value)), }; } }) .filter((x) => x !== undefined); if (snapshots.length === 0) { return undefined; } return { kind: "versioned", snapshots, }; } export function createVersionMutator(versioning, moment) { function deleteFromMap(map) { for (const [name, type] of map) { if (!versioning.existsAtVersion(type, moment)) { map.delete(name); } } } function deleteFromArray(array) { for (let i = array.length - 1; i >= 0; i--) { if (!versioning.existsAtVersion(array[i], moment)) { array.splice(i, 1); } } } function rename(original, type) { if (type.name !== undefined) { const nameAtVersion = versioning.getNameAtVersion(original, moment); if (nameAtVersion !== undefined && nameAtVersion !== type.name) { type.name = nameAtVersion; } } } return { name: `VersionSnapshot ${moment.name}`, Namespace: { mutate: (original, clone, p, realm) => { deleteFromMap(clone.models); deleteFromMap(clone.operations); deleteFromMap(clone.interfaces); deleteFromMap(clone.enums); deleteFromMap(clone.unions); deleteFromMap(clone.namespaces); deleteFromMap(clone.scalars); }, }, Interface: (original, clone, p, realm) => { rename(original, clone); deleteFromMap(clone.operations); }, Model: (original, clone, p, realm) => { rename(original, clone); deleteFromMap(clone.properties); deleteFromArray(clone.derivedModels); }, Union: (original, clone, p, realm) => { rename(original, clone); deleteFromMap(clone.variants); }, UnionVariant: (original, clone, p, realm) => { rename(original, clone); }, Enum: (original, clone, p, realm) => { rename(original, clone); deleteFromMap(clone.members); }, Scalar: (original, clone, p, realm) => { rename(original, clone); deleteFromArray(clone.derivedScalars); }, EnumMember: (original, clone, p, realm) => { rename(original, clone); }, Operation: (original, clone, p, realm) => { rename(original, clone); const returnTypeAtVersion = versioning.getReturnTypeAtVersion(original, moment); if (returnTypeAtVersion !== clone.returnType) { clone.returnType = returnTypeAtVersion; } }, Tuple: (original, clone, p, realm) => { }, ModelProperty: (original, clone, p, realm) => { rename(original, clone); clone.optional = versioning.getOptionalAtVersion(original, moment); const typeAtVersion = versioning.getTypeAtVersion(original, moment); if (typeAtVersion !== clone.type) { clone.type = typeAtVersion; } }, }; } class VersioningHelper { #program; #timeline; constructor(program, timeline) { this.#program = program; this.#timeline = timeline; } existsAtVersion(type, moment) { const availability = getAvailabilityMapInTimeline(this.#program, type, this.#timeline); if (!availability) return true; const isAvail = availability.get(moment); return isAvail === Availability.Added || isAvail === Availability.Available; } getNameAtVersion(type, moment) { const allValues = getRenamedFrom(this.#program, type); if (!allValues) return type.name; for (const val of allValues) { if (this.#timeline.isBefore(moment, val.version)) { return val.oldName; } } return type.name; } getTypeAtVersion(type, moment) { const map = getTypeChangedFrom(this.#program, type); if (!map) return type.type; for (const [changedAtVersion, val] of map) { if (this.#timeline.isBefore(moment, changedAtVersion)) { return val; } } return type.type; } getReturnTypeAtVersion(type, moment) { const map = getReturnTypeChangedFrom(this.#program, type); if (!map) return type.returnType; for (const [changedAtVersion, val] of map) { if (this.#timeline.isBefore(moment, changedAtVersion)) { return val; } } return type.returnType; } getOptionalAtVersion(type, moment) { const optionalAt = getMadeOptionalOn(this.#program, type); const requiredAt = getMadeRequiredOn(this.#program, type); if (!optionalAt && !requiredAt) return type.optional; if (optionalAt) { if (this.#timeline.isBefore(moment, optionalAt)) { return false; } else { return true; } } if (requiredAt) { if (this.#timeline.isBefore(moment, requiredAt)) { return true; } else { return false; } } return type.optional; } } //# sourceMappingURL=mutator.js.map