@fumari/json-schema-to-typescript
Version:
compile json schema to typescript typings
1,550 lines (1,534 loc) • 53 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/formatter.ts
function format(code, options) {
return __async(this, null, function* () {
if (!options.format) {
return code;
}
const prettier = yield import("prettier/standalone");
return prettier.format(code, __spreadValues({
parser: "typescript",
plugins: [yield import("prettier/plugins/estree"), yield import("prettier/plugins/typescript")]
}, options.style));
});
}
// src/types/AST.ts
function hasComment(ast) {
return "comment" in ast && ast.comment != null && ast.comment !== "" || // Compare to true because ast.deprecated might be undefined
"deprecated" in ast && ast.deprecated === true;
}
function hasStandaloneName(ast) {
return "standaloneName" in ast && ast.standaloneName != null && ast.standaloneName !== "";
}
var T_ANY = {
type: "ANY"
};
var T_NULL = {
type: "NULL"
};
var T_ANY_ADDITIONAL_PROPERTIES = {
keyName: "[k: string]",
type: "ANY"
};
var T_UNKNOWN = {
type: "UNKNOWN"
};
var T_UNKNOWN_ADDITIONAL_PROPERTIES = {
keyName: "[k: string]",
type: "UNKNOWN"
};
// src/types/JSONSchema.ts
var Parent = Symbol("Parent");
var Types = Symbol("Types");
var Intersection = Symbol("Intersection");
function getRootSchema(schema) {
const parent = schema[Parent];
if (!parent) {
return schema;
}
return getRootSchema(parent);
}
function isBoolean(schema) {
return schema === true || schema === false;
}
function isPrimitive(schema) {
return !isPlainObject(schema);
}
function isCompound(schema) {
return Array.isArray(schema.type) || "anyOf" in schema || "oneOf" in schema;
}
// src/utils.ts
function Try(fn, err) {
try {
return fn();
} catch (e) {
return err(e);
}
}
var BLACKLISTED_KEYS = /* @__PURE__ */ new Set([
"id",
"$defs",
"$id",
"$schema",
"title",
"description",
"default",
"multipleOf",
"maximum",
"exclusiveMaximum",
"minimum",
"exclusiveMinimum",
"maxLength",
"minLength",
"pattern",
"additionalItems",
"items",
"maxItems",
"minItems",
"uniqueItems",
"maxProperties",
"minProperties",
"required",
"additionalProperties",
"definitions",
"properties",
"patternProperties",
"dependencies",
"enum",
"type",
"allOf",
"anyOf",
"oneOf",
"not"
]);
function traverseObjectKeys(obj, callback, processed) {
Object.keys(obj).forEach((k) => {
if (obj[k] && typeof obj[k] === "object" && !Array.isArray(obj[k])) {
traverse(obj[k], callback, processed, k);
}
});
}
function traverseArray(arr, callback, processed) {
arr.forEach((s, k) => traverse(s, callback, processed, k.toString()));
}
function traverseIntersection(schema, callback, processed) {
if (typeof schema !== "object" || !schema) {
return;
}
const r = schema;
const intersection = r[Intersection];
if (!intersection) {
return;
}
if (Array.isArray(intersection.allOf)) {
traverseArray(intersection.allOf, callback, processed);
}
}
function traverse(schema, callback, processed = /* @__PURE__ */ new Set(), key) {
if (processed.has(schema)) {
return;
}
processed.add(schema);
callback(schema, key != null ? key : null);
if (schema.anyOf) {
traverseArray(schema.anyOf, callback, processed);
}
if (schema.allOf) {
traverseArray(schema.allOf, callback, processed);
}
if (schema.oneOf) {
traverseArray(schema.oneOf, callback, processed);
}
if (schema.properties) {
traverseObjectKeys(schema.properties, callback, processed);
}
if (schema.patternProperties) {
traverseObjectKeys(schema.patternProperties, callback, processed);
}
if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
traverse(schema.additionalProperties, callback, processed);
}
if (schema.items) {
const { items } = schema;
if (Array.isArray(items)) {
traverseArray(items, callback, processed);
} else {
traverse(items, callback, processed);
}
}
if (schema.additionalItems && typeof schema.additionalItems === "object") {
traverse(schema.additionalItems, callback, processed);
}
if (schema.dependencies) {
if (Array.isArray(schema.dependencies)) {
traverseArray(schema.dependencies, callback, processed);
} else {
traverseObjectKeys(schema.dependencies, callback, processed);
}
}
if (schema.definitions) {
traverseObjectKeys(schema.definitions, callback, processed);
}
if (schema.$defs) {
traverseObjectKeys(schema.$defs, callback, processed);
}
if (schema.not) {
traverse(schema.not, callback, processed);
}
traverseIntersection(schema, callback, processed);
Object.keys(schema).filter((key2) => !BLACKLISTED_KEYS.has(key2)).forEach((key2) => {
const child = schema[key2];
if (child && typeof child === "object") {
traverseObjectKeys(child, callback, processed);
}
});
}
function toSafeString(str) {
const value = str.normalize().replace(/[^a-zA-Z0-9_$]/g, " ").replace(/(^\w|\s+\w|_\w|\d\w)/g, (letter) => {
if (letter.startsWith("_")) return letter.slice(1).toUpperCase();
return letter.trim().toUpperCase();
}).replace(/\s+/g, "").replace(/^\d+/, "");
return value;
}
function generateName(from, usedNames) {
let name = toSafeString(from);
if (!name) {
name = "NoName";
}
if (usedNames.has(name)) {
let counter = 1;
let nameWithCounter = `${name}${counter}`;
while (usedNames.has(nameWithCounter)) {
nameWithCounter = `${name}${counter}`;
counter++;
}
name = nameWithCounter;
}
usedNames.add(name);
return name;
}
function error(...messages) {
console.error("[error]", ...messages);
}
function log(title, ...messages) {
if (!process.env.VERBOSE) {
return;
}
let lastMessage = null;
if (messages.length > 1 && typeof messages[messages.length - 1] !== "string") {
lastMessage = messages.splice(messages.length - 1, 1);
}
console.info(`[debug]`, title, ...messages);
if (lastMessage) {
console.dir(lastMessage, { depth: 6, maxArrayLength: 6 });
}
}
function escapeBlockComment(schema) {
const replacer = "* /";
if (schema === null || typeof schema !== "object") {
return;
}
for (const key of Object.keys(schema)) {
if (key === "description" && typeof schema[key] === "string") {
schema[key] = schema[key].replace(/\*\//g, replacer);
}
}
}
function maybeStripDefault(schema) {
if (!("default" in schema)) {
return schema;
}
switch (schema.type) {
case "array":
if (Array.isArray(schema.default)) {
return schema;
}
break;
case "boolean":
if (typeof schema.default === "boolean") {
return schema;
}
break;
case "integer":
case "number":
if (typeof schema.default === "number") {
return schema;
}
break;
case "string":
if (typeof schema.default === "string") {
return schema;
}
break;
case "null":
if (schema.default === null) {
return schema;
}
break;
case "object":
if (isPlainObject(schema.default)) {
return schema;
}
break;
}
delete schema.default;
return schema;
}
function appendToDescription(existingDescription, ...values) {
if (existingDescription) {
return `${existingDescription}
${values.join("\n")}`;
}
return values.join("\n");
}
function isSchemaLike(schema) {
if (!isPlainObject(schema) || !schema || typeof schema !== "object") {
return false;
}
const parent = schema[Parent];
if (parent === null) {
return true;
}
const JSON_SCHEMA_KEYWORDS = [
"$defs",
"allOf",
"anyOf",
"definitions",
"dependencies",
"enum",
"not",
"oneOf",
"patternProperties",
"properties",
"required"
];
if (JSON_SCHEMA_KEYWORDS.some((_) => parent[_] === schema)) {
return false;
}
return true;
}
function omit(obj, ...keys) {
const result = __spreadValues({}, obj);
for (const key of keys) {
delete result[key];
}
return result;
}
function isPlainObject(value) {
if (!value || typeof value !== "object") {
return false;
}
const proto = Object.getPrototypeOf(value);
if (proto === null) {
return true;
}
const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor;
return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object);
}
function deepMerge(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isPlainObject(target) && isPlainObject(source)) {
const _target = target;
for (const key in source) {
if (isPlainObject(source[key])) {
if (!(key in target) || typeof _target[key] !== "object") Object.assign(_target, { [key]: {} });
deepMerge(_target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
if (Array.isArray(source[key])) {
if (!Array.isArray(_target[key])) _target[key] = [];
const arr = _target[key];
arr.push(source[key]);
}
}
}
return deepMerge(target, ...sources);
}
// src/generator.ts
function generate(ast, options = DEFAULT_OPTIONS) {
return [
options.bannerComment,
declareNamedTypes(ast, options, ast.standaloneName),
declareNamedInterfaces(ast, options, ast.standaloneName),
declareEnums(ast, options)
].filter(Boolean).join("\n\n") + "\n";
}
function declareEnums(ast, options, processed = /* @__PURE__ */ new Set()) {
if (processed.has(ast)) {
return "";
}
processed.add(ast);
let type = "";
switch (ast.type) {
case "ENUM":
return generateStandaloneEnum(ast, options) + "\n";
case "ARRAY":
return declareEnums(ast.params, options, processed);
case "UNION":
case "INTERSECTION":
return ast.params.reduce((prev, ast2) => prev + declareEnums(ast2, options, processed), "");
case "TUPLE":
type = ast.params.reduce((prev, ast2) => prev + declareEnums(ast2, options, processed), "");
if (ast.spreadParam) {
type += declareEnums(ast.spreadParam, options, processed);
}
return type;
case "INTERFACE":
return getSuperTypesAndParams(ast).reduce((prev, ast2) => prev + declareEnums(ast2, options, processed), "");
default:
return "";
}
}
function declareNamedInterfaces(ast, options, rootASTName, processed = /* @__PURE__ */ new Set()) {
if (processed.has(ast)) {
return "";
}
processed.add(ast);
let type = "";
switch (ast.type) {
case "ARRAY":
type = declareNamedInterfaces(ast.params, options, rootASTName, processed);
break;
case "INTERFACE":
type = [
hasStandaloneName(ast) && (ast.standaloneName === rootASTName || options.declareExternallyReferenced) && generateStandaloneInterface(ast, options),
getSuperTypesAndParams(ast).map((ast2) => declareNamedInterfaces(ast2, options, rootASTName, processed)).filter(Boolean).join("\n")
].filter(Boolean).join("\n");
break;
case "INTERSECTION":
case "TUPLE":
case "UNION":
type = ast.params.map((_) => declareNamedInterfaces(_, options, rootASTName, processed)).filter(Boolean).join("\n");
if (ast.type === "TUPLE" && ast.spreadParam) {
type += declareNamedInterfaces(ast.spreadParam, options, rootASTName, processed);
}
break;
default:
type = "";
}
return type;
}
function declareNamedTypes(ast, options, rootASTName, processed = /* @__PURE__ */ new Set()) {
if (processed.has(ast)) {
return "";
}
processed.add(ast);
switch (ast.type) {
case "ARRAY":
return [
declareNamedTypes(ast.params, options, rootASTName, processed),
hasStandaloneName(ast) ? generateStandaloneType(ast, options) : void 0
].filter(Boolean).join("\n");
case "ENUM":
return "";
case "INTERFACE":
return getSuperTypesAndParams(ast).map(
(ast2) => (ast2.standaloneName === rootASTName || options.declareExternallyReferenced) && declareNamedTypes(ast2, options, rootASTName, processed)
).filter(Boolean).join("\n");
case "INTERSECTION":
case "TUPLE":
case "UNION":
return [
hasStandaloneName(ast) ? generateStandaloneType(ast, options) : void 0,
ast.params.map((ast2) => declareNamedTypes(ast2, options, rootASTName, processed)).filter(Boolean).join("\n"),
"spreadParam" in ast && ast.spreadParam ? declareNamedTypes(ast.spreadParam, options, rootASTName, processed) : void 0
].filter(Boolean).join("\n");
default:
if (hasStandaloneName(ast)) {
return generateStandaloneType(ast, options);
}
return "";
}
}
function generateType(ast, options) {
const type = generateRawType(ast, options);
if (options.strictIndexSignatures && ast.keyName === "[k: string]" && ast.type !== "UNKNOWN" && ast.type !== "ANY") {
return `${type} | undefined`;
}
return type;
}
function generateRawType(ast, options) {
log("generator", ast);
if (hasStandaloneName(ast)) {
return toSafeString(ast.standaloneName);
}
switch (ast.type) {
case "ANY":
return "any";
case "ARRAY":
return (() => {
const type = generateType(ast.params, options);
return type.endsWith('"') ? "(" + type + ")[]" : type + "[]";
})();
case "BOOLEAN":
return "boolean";
case "INTERFACE":
return generateInterface(ast, options);
case "INTERSECTION":
return generateSetOperation(ast, options);
case "LITERAL":
return JSON.stringify(ast.params);
case "NEVER":
return "never";
case "NUMBER":
return "number";
case "NULL":
return "null";
case "OBJECT":
return "object";
case "REFERENCE":
return ast.params;
case "STRING":
return "string";
case "TUPLE":
return (() => {
const minItems = ast.minItems;
const maxItems = ast.maxItems || -1;
let spreadParam = ast.spreadParam;
const astParams = [...ast.params];
if (minItems > 0 && minItems > astParams.length && ast.spreadParam === void 0) {
if (maxItems < 0) {
spreadParam = options.unknownAny ? T_UNKNOWN : T_ANY;
}
}
if (maxItems > astParams.length && ast.spreadParam === void 0) {
for (let i = astParams.length; i < maxItems; i += 1) {
astParams.push(options.unknownAny ? T_UNKNOWN : T_ANY);
}
}
function addSpreadParam(params) {
if (spreadParam) {
const spread = "...(" + generateType(spreadParam, options) + ")[]";
params.push(spread);
}
return params;
}
function paramsToString(params) {
return "[" + params.join(", ") + "]";
}
const paramsList = astParams.map((param) => generateType(param, options));
if (paramsList.length > minItems) {
const cumulativeParamsList = paramsList.slice(0, minItems);
const typesToUnion = [];
if (cumulativeParamsList.length > 0) {
typesToUnion.push(paramsToString(cumulativeParamsList));
} else {
typesToUnion.push(paramsToString([]));
}
for (let i = minItems; i < paramsList.length; i += 1) {
cumulativeParamsList.push(paramsList[i]);
if (i === paramsList.length - 1) {
addSpreadParam(cumulativeParamsList);
}
typesToUnion.push(paramsToString(cumulativeParamsList));
}
return typesToUnion.join("|");
}
return paramsToString(addSpreadParam(paramsList));
})();
case "UNION":
return generateSetOperation(ast, options);
case "UNKNOWN":
return "unknown";
case "CUSTOM_TYPE":
return ast.params;
}
}
function generateSetOperation(ast, options) {
const members = ast.params.map((_) => generateType(_, options));
const separator = ast.type === "UNION" ? "|" : "&";
return members.length === 1 ? members[0] : "(" + members.join(" " + separator + " ") + ")";
}
function generateInterface(ast, options) {
return `{
` + ast.params.filter((_) => !_.isPatternProperty && !_.isUnreachableDefinition).map(
({ isRequired, keyName, ast: ast2 }) => [isRequired, keyName, ast2, generateType(ast2, options)]
).map(
([isRequired, keyName, ast2, type]) => (hasComment(ast2) && !ast2.standaloneName ? generateComment(ast2.comment, ast2.deprecated) + "\n" : "") + escapeKeyName(keyName) + (isRequired ? "" : "?") + ": " + type
).join("\n") + "\n}";
}
function generateComment(comment, deprecated) {
const commentLines = ["/**"];
if (deprecated) {
commentLines.push(" * @deprecated");
}
if (typeof comment !== "undefined") {
commentLines.push(...comment.split("\n").map((_) => " * " + _));
}
commentLines.push(" */");
return commentLines.join("\n");
}
function generateStandaloneEnum(ast, options) {
return (hasComment(ast) ? generateComment(ast.comment, ast.deprecated) + "\n" : "") + "export " + (options.enableConstEnums ? "const " : "") + `enum ${toSafeString(ast.standaloneName)} {
` + ast.params.map(({ ast: ast2, keyName }) => keyName + " = " + generateType(ast2, options)).join(",\n") + "\n}";
}
function generateStandaloneInterface(ast, options) {
return (hasComment(ast) ? generateComment(ast.comment, ast.deprecated) + "\n" : "") + `export interface ${toSafeString(ast.standaloneName)} ` + (ast.superTypes.length > 0 ? `extends ${ast.superTypes.map((superType) => toSafeString(superType.standaloneName)).join(", ")} ` : "") + generateInterface(ast, options);
}
function generateStandaloneType(ast, options) {
return (hasComment(ast) ? generateComment(ast.comment) + "\n" : "") + `export type ${toSafeString(ast.standaloneName)} = ${generateType(
omit(ast, "standaloneName"),
options
)}`;
}
function escapeKeyName(keyName) {
if (keyName.length && /[A-Za-z_$]/.test(keyName.charAt(0)) && /^[\w$]+$/.test(keyName)) {
return keyName;
}
if (keyName === "[k: string]") {
return keyName;
}
return JSON.stringify(keyName);
}
function getSuperTypesAndParams(ast) {
return ast.params.map((param) => param.ast).concat(ast.superTypes);
}
// src/typesOfSchema.ts
function typesOfSchema(schema) {
if (schema.tsType) {
return /* @__PURE__ */ new Set(["CUSTOM_TYPE"]);
}
const matchedTypes = /* @__PURE__ */ new Set();
for (const [schemaType, f] of Object.entries(matchers)) {
if (f(schema)) {
matchedTypes.add(schemaType);
}
}
if (!matchedTypes.size) {
matchedTypes.add("UNNAMED_SCHEMA");
}
return matchedTypes;
}
var matchers = {
ALL_OF(schema) {
return "allOf" in schema;
},
ANY(schema) {
if (Object.keys(schema).length === 0) {
return true;
}
return schema.type === "any";
},
ANY_OF(schema) {
return "anyOf" in schema;
},
BOOLEAN(schema) {
if ("enum" in schema) {
return false;
}
if (schema.type === "boolean") {
return true;
}
if (!isCompound(schema) && typeof schema.default === "boolean") {
return true;
}
return false;
},
CUSTOM_TYPE() {
return false;
},
NAMED_ENUM(schema) {
return "enum" in schema && "tsEnumNames" in schema;
},
NAMED_SCHEMA(schema) {
return "$id" in schema && ("patternProperties" in schema || "properties" in schema);
},
NEVER(schema) {
return schema === false;
},
NULL(schema) {
return schema.type === "null";
},
NUMBER(schema) {
if ("enum" in schema) {
return false;
}
if (schema.type === "integer" || schema.type === "number") {
return true;
}
if (!isCompound(schema) && typeof schema.default === "number") {
return true;
}
return false;
},
OBJECT(schema) {
return schema.type === "object" && !isPlainObject(schema.additionalProperties) && !schema.allOf && !schema.anyOf && !schema.oneOf && !schema.patternProperties && !schema.properties && !schema.required;
},
ONE_OF(schema) {
return "oneOf" in schema;
},
REFERENCE(schema) {
return "$ref" in schema;
},
STRING(schema) {
if ("enum" in schema) {
return false;
}
if (schema.type === "string") {
return true;
}
if (!isCompound(schema) && typeof schema.default === "string") {
return true;
}
return false;
},
TYPED_ARRAY(schema) {
if (schema.type && schema.type !== "array") {
return false;
}
return "items" in schema;
},
UNION(schema) {
return Array.isArray(schema.type);
},
UNNAMED_ENUM(schema) {
if ("tsEnumNames" in schema) {
return false;
}
if (schema.type && schema.type !== "boolean" && schema.type !== "integer" && schema.type !== "number" && schema.type !== "string") {
return false;
}
return "enum" in schema;
},
UNNAMED_SCHEMA() {
return false;
},
UNTYPED_ARRAY(schema) {
return schema.type === "array" && !("items" in schema);
}
};
// src/applySchemaTyping.ts
function applySchemaTyping(schema) {
var _a;
const types = typesOfSchema(schema);
Object.assign(schema, {
[Types]: types
});
if (types.size === 1) {
return;
}
const intersection = {
[Parent]: schema,
[Types]: /* @__PURE__ */ new Set(["ALL_OF"]),
$id: schema.$id,
description: schema.description,
name: schema.name,
title: schema.title,
allOf: (_a = schema.allOf) != null ? _a : [],
required: [],
additionalProperties: false
};
types.delete("ALL_OF");
delete schema.allOf;
delete schema.$id;
delete schema.description;
delete schema.name;
delete schema.title;
Object.assign(schema, {
[Intersection]: intersection
});
}
// src/normalizer.ts
var rules = /* @__PURE__ */ new Map();
function hasType(schema, type) {
return schema.type === type || Array.isArray(schema.type) && schema.type.includes(type);
}
function isObjectType(schema) {
return schema.properties !== void 0 || hasType(schema, "object") || hasType(schema, "any");
}
function isArrayType(schema) {
return schema.items !== void 0 || hasType(schema, "array") || hasType(schema, "any");
}
function isEnumTypeWithoutTsEnumNames(schema) {
return schema.type === "string" && schema.enum !== void 0 && schema.tsEnumNames === void 0;
}
function normalizeName(filename) {
const name = filename.split("/").at(-1);
if (!name) return filename;
const dotIdx = name.lastIndexOf(".");
if (dotIdx === -1) return name;
return name.slice(0, dotIdx);
}
rules.set('Remove `type=["null"]` if `enum=[null]`', (schema) => {
if (Array.isArray(schema.enum) && schema.enum.some((e) => e === null) && Array.isArray(schema.type) && schema.type.includes("null")) {
schema.type = schema.type.filter((type) => type !== "null");
}
});
rules.set('Convert nullable to type: [..., "null"]', (schema) => {
if (schema.nullable && schema.type !== void 0) {
if (!Array.isArray(schema.type)) {
schema.type = [schema.type];
}
schema.type.push("null");
delete schema.nullable;
}
});
rules.set("Destructure unary types", (schema) => {
if (schema.type && Array.isArray(schema.type) && schema.type.length === 1) {
schema.type = schema.type[0];
}
});
rules.set("Add empty `required` property if none is defined", (schema) => {
if (isObjectType(schema) && !("required" in schema)) {
schema.required = [];
}
});
rules.set("Transform `required`=false to `required`=[]", (schema) => {
if (schema.required === false) {
schema.required = [];
}
});
rules.set("Default additionalProperties", (schema, _, options) => {
if (isObjectType(schema) && !("additionalProperties" in schema) && schema.patternProperties === void 0) {
schema.additionalProperties = options.additionalProperties;
}
});
rules.set("Transform id to $id", (schema, fileName) => {
if (!isSchemaLike(schema)) {
return;
}
if (schema.id && schema.$id && schema.id !== schema.$id) {
throw ReferenceError(
`Schema must define either id or $id, not both. Given id=${schema.id}, $id=${schema.$id} in ${fileName}`
);
}
if (schema.id) {
schema.$id = schema.id;
delete schema.id;
}
});
rules.set("Add an $id to anything that needs it", (schema, fileName, _options, _key, dereferencedPaths) => {
if (!isSchemaLike(schema)) {
return;
}
if (!schema.$id && !schema[Parent]) {
schema.$id = toSafeString(normalizeName(fileName));
return;
}
if (!dereferencedPaths) return;
if (!isArrayType(schema) && !isObjectType(schema)) {
return;
}
const dereferencedName = dereferencedPaths.get(schema);
if (!schema.$id && !schema.title && dereferencedName) {
schema.$id = toSafeString(normalizeName(dereferencedName));
}
if (dereferencedName) {
dereferencedPaths.delete(schema);
}
});
rules.set("Escape closing JSDoc comment", (schema) => {
escapeBlockComment(schema);
});
rules.set("Add JSDoc comments for minItems and maxItems", (schema) => {
if (!isArrayType(schema)) {
return;
}
const commentsToAppend = [
"minItems" in schema ? `@minItems ${schema.minItems}` : "",
"maxItems" in schema ? `@maxItems ${schema.maxItems}` : ""
].filter(Boolean);
if (commentsToAppend.length) {
schema.description = appendToDescription(schema.description, ...commentsToAppend);
}
});
rules.set("Optionally remove maxItems and minItems", (schema, _fileName, options) => {
if (!isArrayType(schema)) {
return;
}
if ("minItems" in schema && options.ignoreMinAndMaxItems) {
delete schema.minItems;
}
if ("maxItems" in schema && (options.ignoreMinAndMaxItems || options.maxItems === -1)) {
delete schema.maxItems;
}
});
rules.set("Normalize schema.minItems", (schema, _fileName, options) => {
if (options.ignoreMinAndMaxItems) {
return;
}
if (!isArrayType(schema)) {
return;
}
const { minItems } = schema;
schema.minItems = typeof minItems === "number" ? minItems : 0;
});
rules.set("Remove maxItems if it is big enough to likely cause OOMs", (schema, _fileName, options) => {
if (options.ignoreMinAndMaxItems || options.maxItems === -1) {
return;
}
if (!isArrayType(schema)) {
return;
}
const { maxItems, minItems } = schema;
if (maxItems !== void 0 && maxItems - minItems > options.maxItems) {
delete schema.maxItems;
}
});
rules.set("Normalize schema.items", (schema, _fileName, options) => {
if (options.ignoreMinAndMaxItems) {
return;
}
const { maxItems, minItems } = schema;
const hasMaxItems = typeof maxItems === "number" && maxItems >= 0;
const hasMinItems = typeof minItems === "number" && minItems > 0;
if (schema.items && !Array.isArray(schema.items) && (hasMaxItems || hasMinItems)) {
const items = schema.items;
const newItems = Array(maxItems || minItems || 0).fill(items);
if (!hasMaxItems) {
schema.additionalItems = items;
}
schema.items = newItems;
}
if (Array.isArray(schema.items) && hasMaxItems && maxItems < schema.items.length) {
schema.items = schema.items.slice(0, maxItems);
}
return schema;
});
rules.set("Remove extends, if it is empty", (schema) => {
if (!schema.hasOwnProperty("extends")) {
return;
}
if (schema.extends == null || Array.isArray(schema.extends) && schema.extends.length === 0) {
delete schema.extends;
}
});
rules.set("Make extends always an array, if it is defined", (schema) => {
if (schema.extends == null) {
return;
}
if (!Array.isArray(schema.extends)) {
schema.extends = [schema.extends];
}
});
rules.set("Transform definitions to $defs", (schema, fileName) => {
if (schema.definitions && schema.$defs) {
throw ReferenceError(
`Schema must define either definitions or $defs, not both. Given id=${schema.id} in ${fileName}`
);
}
if (schema.definitions) {
schema.$defs = schema.definitions;
delete schema.definitions;
}
});
rules.set("Transform const to singleton enum", (schema) => {
if (schema.const !== void 0) {
schema.enum = [schema.const];
delete schema.const;
}
});
rules.set("Add tsEnumNames to enum types", (schema, _, options) => {
var _a;
if (isEnumTypeWithoutTsEnumNames(schema) && options.inferStringEnumKeysFromValues) {
schema.tsEnumNames = (_a = schema.enum) == null ? void 0 : _a.map(String);
}
});
rules.set("Pre-calculate schema types and intersections", (schema) => {
if (schema !== null && typeof schema === "object") {
applySchemaTyping(schema);
}
});
function normalize(rootSchema, dereferencedPaths, filename, options) {
rules.forEach((rule) => traverse(rootSchema, (schema, key) => rule(schema, filename, options, key, dereferencedPaths)));
return rootSchema;
}
// src/optimizer.ts
function optimize(ast, options, processed = /* @__PURE__ */ new Set()) {
if (processed.has(ast)) {
return ast;
}
processed.add(ast);
switch (ast.type) {
case "ARRAY":
return Object.assign(ast, {
params: optimize(ast.params, options, processed)
});
case "INTERFACE":
return Object.assign(ast, {
params: ast.params.map((_) => Object.assign(_, { ast: optimize(_.ast, options, processed) }))
});
case "INTERSECTION":
case "UNION":
const optimizedAST = Object.assign(ast, {
params: ast.params.map((_) => optimize(_, options, processed))
});
if (optimizedAST.params.some((_) => _.type === "ANY")) {
log("optimizer", "[A, B, C, Any] -> Any", optimizedAST);
return T_ANY;
}
if (optimizedAST.params.some((_) => _.type === "UNKNOWN")) {
log("optimizer", "[A, B, C, Unknown] -> Unknown", optimizedAST);
return T_UNKNOWN;
}
if (optimizedAST.params.every((_) => {
const a = generateType(omitStandaloneName(_), options);
const b = generateType(omitStandaloneName(optimizedAST.params[0]), options);
return a === b;
}) && optimizedAST.params.some((_) => _.standaloneName !== void 0)) {
log("optimizer", "[A (named), A] -> [A (named)]", optimizedAST);
optimizedAST.params = optimizedAST.params.filter((_) => _.standaloneName !== void 0);
}
const params = deduplicate(optimizedAST.params, (item) => generateType(item, options));
if (params.length !== optimizedAST.params.length) {
log("optimizer", "[A, B, B] -> [A, B]", optimizedAST);
optimizedAST.params = params;
}
return Object.assign(optimizedAST, {
params: optimizedAST.params.map((_) => optimize(_, options, processed))
});
default:
return ast;
}
}
function omitStandaloneName(ast) {
switch (ast.type) {
case "ENUM":
return ast;
default:
return __spreadProps(__spreadValues({}, ast), { standaloneName: void 0 });
}
}
function deduplicate(asts, hasher) {
const out = [];
const added = /* @__PURE__ */ new Set();
for (const item of asts) {
const hash = hasher(item);
if (added.has(hash)) continue;
added.add(hash);
out.push(item);
}
return out;
}
// src/parser.ts
function parse(schema, options, keyName, processed = /* @__PURE__ */ new Map(), usedNames = /* @__PURE__ */ new Set()) {
if (isPrimitive(schema)) {
if (isBoolean(schema)) {
return parseBooleanSchema(schema, keyName, options);
}
return parseLiteral(schema, keyName);
}
const intersection = schema[Intersection];
const types = schema[Types];
if (intersection) {
const ast = parseAsTypeWithCache(intersection, "ALL_OF", options, keyName, processed, usedNames);
types.forEach((type) => {
ast.params.push(parseAsTypeWithCache(schema, type, options, keyName, processed, usedNames));
});
log("parser", "Types:", [...types], "Input:", schema, "Output:", ast);
return ast;
}
if (types.size === 1) {
const type = [...types][0];
const ast = parseAsTypeWithCache(schema, type, options, keyName, processed, usedNames);
log("parser", "Type:", type, "Input:", schema, "Output:", ast);
return ast;
}
throw new ReferenceError("Expected intersection schema. Please file an issue on GitHub.");
}
function parseAsTypeWithCache(schema, type, options, keyName, processed = /* @__PURE__ */ new Map(), usedNames = /* @__PURE__ */ new Set()) {
let cachedTypeMap = processed.get(schema);
if (!cachedTypeMap) {
cachedTypeMap = /* @__PURE__ */ new Map();
processed.set(schema, cachedTypeMap);
}
const cachedAST = cachedTypeMap.get(type);
if (cachedAST) {
return cachedAST;
}
const ast = {};
cachedTypeMap.set(type, ast);
return Object.assign(ast, parseNonLiteral(schema, type, options, keyName, processed, usedNames));
}
function parseBooleanSchema(schema, keyName, options) {
if (schema) {
return {
keyName,
type: options.unknownAny ? "UNKNOWN" : "ANY"
};
}
return {
keyName,
type: "NEVER"
};
}
function parseLiteral(schema, keyName) {
return {
keyName,
params: schema,
type: "LITERAL"
};
}
function parseNonLiteral(schema, type, options, keyName, processed, usedNames) {
const definitions = getDefinitions(getRootSchema(schema));
const keyNameFromDefinition = Array.from(Object.keys(definitions)).find((k) => definitions[k] === schema);
switch (type) {
case "ALL_OF": {
const hasNullable = schema.allOf.some((s) => s.nullable && s.type === void 0);
const params = schema.allOf.filter((s) => !s.nullable || s.type !== void 0).map((_) => parse(_, options, void 0, processed, usedNames));
const baseAst = {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options)
};
if (!hasNullable) {
return __spreadProps(__spreadValues({}, baseAst), {
type: "INTERSECTION",
params
});
}
if (params.length === 0) {
return __spreadProps(__spreadValues({}, baseAst), {
type: "NULL"
});
} else if (params.length === 1) {
return __spreadProps(__spreadValues({}, baseAst), {
type: "UNION",
params: [params[0], T_NULL]
});
} else {
return __spreadProps(__spreadValues({}, baseAst), {
type: "UNION",
params: [
{
type: "INTERSECTION",
params
},
T_NULL
]
});
}
}
case "ANY":
return __spreadProps(__spreadValues({}, options.unknownAny ? T_UNKNOWN : T_ANY), {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options)
});
case "ANY_OF":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.anyOf.map((_) => parse(_, options, void 0, processed, usedNames)),
type: "UNION"
};
case "BOOLEAN":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "BOOLEAN"
};
case "CUSTOM_TYPE":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
params: schema.tsType,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "CUSTOM_TYPE"
};
case "NAMED_ENUM":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition != null ? keyNameFromDefinition : keyName, usedNames, options),
params: schema.enum.map((_, n) => ({
ast: parseLiteral(_, void 0),
keyName: schema.tsEnumNames[n]
})),
type: "ENUM"
};
case "NAMED_SCHEMA":
return newInterface(schema, options, processed, usedNames, keyName);
case "NEVER":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "NEVER"
};
case "NULL":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "NULL"
};
case "NUMBER":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "NUMBER"
};
case "OBJECT":
return {
comment: schema.description,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "OBJECT",
deprecated: schema.deprecated
};
case "ONE_OF":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.oneOf.map((_) => parse(_, options, void 0, processed, usedNames)),
type: "UNION"
};
case "REFERENCE":
throw Error("Refs should have been resolved by the resolver!");
case "STRING":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "STRING"
};
case "TYPED_ARRAY":
if (Array.isArray(schema.items)) {
const minItems = schema.minItems;
const maxItems = schema.maxItems;
const arrayType = {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
maxItems,
minItems,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.items.map((_) => parse(_, options, void 0, processed, usedNames)),
type: "TUPLE"
};
if (schema.additionalItems === true) {
arrayType.spreadParam = options.unknownAny ? T_UNKNOWN : T_ANY;
} else if (schema.additionalItems) {
arrayType.spreadParam = parse(schema.additionalItems, options, void 0, processed, usedNames);
}
return arrayType;
} else {
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: parse(schema.items, options, `{keyNameFromDefinition}Items`, processed, usedNames),
type: "ARRAY"
};
}
case "UNION":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.type.map((type2) => {
const member = __spreadProps(__spreadValues({}, omit(schema, "$id", "description", "title")), { type: type2 });
maybeStripDefault(member);
applySchemaTyping(member);
return parse(member, options, void 0, processed, usedNames);
}),
type: "UNION"
};
case "UNNAMED_ENUM":
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
params: schema.enum.map((_) => parseLiteral(_, void 0)),
type: "UNION"
};
case "UNNAMED_SCHEMA":
return newInterface(schema, options, processed, usedNames, keyName, keyNameFromDefinition);
case "UNTYPED_ARRAY": {
const minItems = schema.minItems;
const maxItems = typeof schema.maxItems === "number" ? schema.maxItems : -1;
const params = options.unknownAny ? T_UNKNOWN : T_ANY;
if (minItems > 0 || maxItems >= 0) {
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
maxItems: schema.maxItems,
minItems,
// create a tuple of length N
params: Array(Math.max(maxItems, minItems) || 0).fill(params),
// if there is no maximum, then add a spread item to collect the rest
spreadParam: maxItems >= 0 ? void 0 : params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "TUPLE"
};
}
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
params,
standaloneName: standaloneName(schema, keyNameFromDefinition, usedNames, options),
type: "ARRAY"
};
}
}
}
function standaloneName(schema, keyNameFromDefinition, usedNames, options) {
var _a;
const name = ((_a = options.customName) == null ? void 0 : _a.call(options, schema, keyNameFromDefinition)) || schema.title || schema.$id || keyNameFromDefinition;
if (name) {
return generateName(name, usedNames);
}
}
function newInterface(schema, options, processed, usedNames, keyName, keyNameFromDefinition) {
const name = standaloneName(schema, keyNameFromDefinition, usedNames, options);
return {
comment: schema.description,
deprecated: schema.deprecated,
keyName,
params: parseSchema(schema, options, processed, usedNames, name),
standaloneName: name,
superTypes: parseSuperTypes(schema, options, processed, usedNames),
type: "INTERFACE"
};
}
function parseSuperTypes(schema, options, processed, usedNames) {
const superTypes = schema.extends;
if (!superTypes) {
return [];
}
return superTypes.map((_) => parse(_, options, void 0, processed, usedNames));
}
function parseSchema(schema, options, processed, usedNames, parentSchemaName) {
var _a, _b;
let asts = Array.from(Object.entries((_a = schema.properties) != null ? _a : {})).map(([key, value]) => {
var _a2;
return {
ast: parse(value, options, key, processed, usedNames),
isPatternProperty: false,
isRequired: ((_a2 = schema.required) != null ? _a2 : []).includes(key),
isUnreachableDefinition: false,
keyName: key
};
});
let singlePatternProperty = false;
if (schema.patternProperties) {
singlePatternProperty = !schema.additionalProperties && Object.keys(schema.patternProperties).length === 1;
asts = asts.concat(
Array.from(Object.entries(schema.patternProperties)).map(([key, value]) => {
var _a2;
const ast = parse(value, options, key, processed, usedNames);
const comment = `This interface was referenced by \`${parentSchemaName}\`'s JSON-Schema definition
via the \`patternProperty\` "${key.replace("*/", "*\\/")}".`;
ast.comment = ast.comment ? `${ast.comment}
${comment}` : comment;
return {
ast,
isPatternProperty: !singlePatternProperty,
isRequired: singlePatternProperty || ((_a2 = schema.required) != null ? _a2 : []).includes(key),
isUnreachableDefinition: false,
keyName: singlePatternProperty ? "[k: string]" : key
};
})
);
}
if (options.unreachableDefinitions) {
asts = asts.concat(
Array.from(Object.entries((_b = schema.$defs) != null ? _b : {})).map(([key, value]) => {
var _a2;
const ast = parse(value, options, key, processed, usedNames);
const comment = `This interface was referenced by \`${parentSchemaName}\`'s JSON-Schema
via the \`definition\` "${key}".`;
ast.comment = ast.comment ? `${ast.comment}
${comment}` : comment;
return {
ast,
isPatternProperty: false,
isRequired: ((_a2 = schema.required) != null ? _a2 : []).includes(key),
isUnreachableDefinition: true,
keyName: key
};
})
);
}
switch (schema.additionalProperties) {
case void 0:
case true:
if (singlePatternProperty) {
return asts;
}
return asts.concat({
ast: options.unknownAny ? T_UNKNOWN_ADDITIONAL_PROPERTIES : T_ANY_ADDITIONAL_PROPERTIES,
isPatternProperty: false,
isRequired: true,
isUnreachableDefinition: false,
keyName: "[k: string]"
});
case false:
return asts;
// pass "true" as the last param because in TS, properties
// defined via index signatures are already optional
default:
return asts.concat({
ast: parse(schema.additionalProperties, options, "[k: string]", processed, usedNames),
isPatternProperty: false,
isRequired: true,
isUnreachableDefinition: false,
keyName: "[k: string]"
});
}
}
var cacheList = [];
function getDefinitions(schema, isSchema = true, processed = /* @__PURE__ */ new Set()) {
var _a;
if (processed.has(schema)) {
return {};
}
const cached = (_a = cacheList.findLast((item) => item[0] === schema)) == null ? void 0 : _a[1];
if (cached) return cached;
let result = {};
processed.add(schema);
if (Array.isArray(schema)) {
result = schema.reduce(
(prev, cur) => __spreadValues(__spreadValues({}, prev), getDefinitions(cur, false, processed)),
{}
);
} else if (isPlainObject(schema)) {
result = __spreadValues(__spreadValues({}, isSchema && hasDefinitions(schema) ? schema.$defs : {}), Object.keys(schema).reduce(
(prev, cur) => __spreadValues(__spreadValues({}, prev), getDefinitions(schema[cur], false, processed)),
{}
));
}
cacheList.push([schema, result]);
if (cacheList.length > 100) cacheList.shift();
return result;
}
function hasDefinitions(schema) {
return "$defs" in schema;
}
// src/resolver.ts
import Parser from "@apidevtools/json-schema-ref-parser";
function dereference(schema, cwd, options) {
return __async(this, null, function* () {
log("dereferencer", "Dereferencing input schema:", cwd, schema);
const dereferencedPaths = /* @__PURE__ */ new WeakMap();
const dereferencedSchema = yield Parser.dereference(cwd, schema, __spreadProps(__spreadValues({}, options), {
mutateInputSchema: false,
dereference: __spreadProps(__spreadValues({}, options.dereference), {
onDereference($ref, schema2) {
dereferencedPaths.set(schema2, $ref);
}
})
}));
return { dereferencedPaths, dereferencedSchema };
});
}
// src/validator.ts
var rules2 = /* @__PURE__ */ new Map();
rules2.set("Enum members and tsEnumNames must be of the same length", (schema) => {
if (schema.enum && schema.tsEnumNames && schema.enum.length !== schema.tsEnumNames.length) {
return false;
}
});
rules2.set("tsEnumNames must be an array of strings", (schema) => {
if (schema.tsEnumNames && schema.tsEnumNames.some((_) => typeof _ !== "string")) {
return false;
}
});
rules2.set("When both maxItems and minItems are present, maxItems >= minItems", (schema) => {
const { maxItems, minItems } = schema;
if (typeof maxItems === "number" && typeof minItems === "number") {
return maxItems >= minItems;
}
});
rules2.set("When maxItems exists, maxItems >= 0", (schema) => {
const { maxItems } = schema;
if (typeof maxItems === "number") {
return maxItems >= 0;
}
});
rules2.set("When minItems exists, minItems >= 0", (schema) => {
const { minItems } = schema;
if (typeof minItems === "number") {
return minItems >= 0;
}
});
rules2.set("deprecated must be a boolean", (schema) => {
const typeOfDeprecated = typeof schema.deprecated;
return typeOfDeprecated === "boolean" || typeOfDeprecated === "undefined";
});
function validate(schema, filename) {
const errors = [];
rules2.forEach((rule, ruleName) => {
traverse(schema, (schema2, key) => {
if (rule(schema2) === false) {
errors.push(`Error at key "${key}" in file "${filename}": ${ruleName}`);
}
return schema2;
});
});
return errors;
}
// src/linker.ts
function link(schema, parent = null) {
if (!Array.isArray(schema) && !isPlainObject(schema)) {
return schema;
}
if (schema.hasOwnProperty(Parent)) {
return schema;
}
if (schema)
Object.assign(schema, {
[Parent]: parent
});
if (Ar