UNPKG

@fumari/json-schema-to-typescript

Version:
1,550 lines (1,534 loc) 53 kB
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