counterfact
Version:
Generate a TypeScript-based mock server from an OpenAPI spec in seconds — with stateful routes, hot reload, and REPL support.
71 lines (70 loc) • 2.74 kB
JavaScript
import { bundle } from "@apidevtools/json-schema-ref-parser";
import createDebug from "debug";
import { applyOverlays } from "../util/apply-overlay.js";
import { Requirement } from "./requirement.js";
const debug = createDebug("counterfact:typescript-generator:specification");
/**
* Represents a fully dereferenced OpenAPI specification as a navigable tree
* of {@link Requirement} nodes.
*
* Use {@link Specification.fromFile} to load a spec from disk or a URL; the
* static method bundles external `$ref` references into a single in-memory
* object before constructing the tree.
*/
export class Specification {
cache;
rootRequirement;
constructor(rootRequirement) {
this.cache = new Map();
if (rootRequirement) {
this.rootRequirement = rootRequirement;
}
}
/**
* Loads the OpenAPI document at `urlOrPath`, bundles all external `$ref`
* references, and returns a fully initialised {@link Specification}.
*
* @param urlOrPath - A local file path or HTTP(S) URL.
* @param overlays - Optional ordered list of overlay file paths/URLs to
* apply after loading the document.
* @throws When the document cannot be found or parsed.
*/
static async fromFile(urlOrPath, overlays = []) {
const specification = new Specification();
await specification.load(urlOrPath, overlays);
return specification;
}
/**
* Returns the {@link Requirement} at `url` (a JSON Pointer such as
* `"#/paths"`).
*
* @param url - A JSON Pointer string (must start with `"#/"`).
*/
getRequirement(url) {
debug("getting requirement at %s", url);
return this.rootRequirement.select(url.slice(2));
}
/**
* Loads (or reloads) the specification from `urlOrPath`, then applies any
* overlay files listed in `overlays` in order.
*
* @param urlOrPath - A local file path or HTTP(S) URL.
* @param overlays - Optional ordered list of overlay file paths/URLs.
* @throws When the document cannot be found or parsed.
*/
async load(urlOrPath, overlays = []) {
try {
const document = (await bundle(urlOrPath, {
resolve: { http: { safeUrlResolver: false } },
}));
if (overlays.length > 0) {
await applyOverlays(document, overlays);
}
this.rootRequirement = new Requirement(document, urlOrPath, this);
}
catch (error) {
const details = error instanceof Error ? error.message : String(error);
throw new Error(`Could not load the OpenAPI spec from "${urlOrPath}".\n${details}`, { cause: error });
}
}
}