exact-mirror
Version:
Mirror exact value to TypeBox/OpenAPI model
357 lines (356 loc) • 12.1 kB
JavaScript
// src/index.ts
import { TypeCompiler } from "@sinclair/typebox/compiler";
var Kind = Symbol.for("TypeBox.Kind");
var Hint = Symbol.for("TypeBox.Hint");
var isSpecialProperty = (name) => /(\ |-|\t|\n|\.|\[|\]|\{|\})/.test(name) || !isNaN(+name[0]);
var joinProperty = (v1, v2, isOptional = false) => {
if (typeof v2 === "number") return `${v1}[${v2}]`;
if (isSpecialProperty(v2)) return `${v1}${isOptional ? "?." : ""}["${v2}"]`;
return `${v1}${isOptional ? "?" : ""}.${v2}`;
};
var encodeProperty = (v) => isSpecialProperty(v) ? `"${v}"` : v;
var sanitize = (key, sanitize2 = 0, schema) => {
if (schema.type !== "string" || schema.const || schema.trusted) return key;
let hof = "";
for (let i = sanitize2 - 1; i >= 0; i--) hof += `d.h${i}(`;
return hof + key + ")".repeat(sanitize2);
};
var mergeObjectIntersection = (schema) => {
if (!schema.allOf || Kind in schema && (schema[Kind] !== "Intersect" || schema.type !== "object"))
return schema;
const { allOf, ...newSchema } = schema;
newSchema.properties = {};
if (Kind in newSchema) newSchema[Kind] = "Object";
for (const type of allOf) {
if (type.type !== "object") continue;
const { properties, required, type: _, [Kind]: __, ...rest } = type;
if (required)
newSchema.required = newSchema.required ? newSchema.required.concat(required) : required;
Object.assign(newSchema, rest);
for (const property in type.properties)
newSchema.properties[property] = mergeObjectIntersection(
type.properties[property]
);
}
return newSchema;
};
var handleRecord = (schema, property, instruction) => {
const child = schema.patternProperties["^(.*)$"] ?? schema.patternProperties[Object.keys(schema.patternProperties)[0]];
if (!child) return property;
const i = instruction.array;
instruction.array++;
let v = `(()=>{const ar${i}s=Object.keys(${property}),ar${i}v={};for(let i=0;i<ar${i}s.length;i++){const ar${i}p=${property}[ar${i}s[i]];ar${i}v[ar${i}s[i]]=${mirror(child, `ar${i}p`, instruction)}`;
const optionals = instruction.optionalsInArray[i + 1];
if (optionals)
for (let oi = 0; oi < optionals.length; oi++) {
const target = `ar${i}v[ar${i}s[i]]${optionals[oi]}`;
v += `;if(${target}===undefined)delete ${target}`;
}
v += `}return ar${i}v})()`;
return v;
};
var handleTuple = (schema, property, instruction) => {
const i = instruction.array;
instruction.array++;
const isRoot = property === "v" && !instruction.unions.length;
let v = "";
if (!isRoot) v = `(()=>{`;
v += `const ar${i}v=[`;
for (let i2 = 0; i2 < schema.length; i2++) {
if (i2 !== 0) v += ",";
v += mirror(
schema[i2],
joinProperty(property, i2, instruction.parentIsOptional),
instruction
);
}
v += `];`;
if (!isRoot) v += `return ar${i}v})()`;
return v;
};
function deepClone(source, weak = /* @__PURE__ */ new WeakMap()) {
if (source === null || typeof source !== "object" || typeof source === "function")
return source;
if (weak.has(source)) return weak.get(source);
if (Array.isArray(source)) {
const copy = new Array(source.length);
weak.set(source, copy);
for (let i = 0; i < source.length; i++)
copy[i] = deepClone(source[i], weak);
return copy;
}
if (typeof source === "object") {
const keys = Object.keys(source).concat(
Object.getOwnPropertySymbols(source)
);
const cloned = {};
for (const key of keys)
cloned[key] = deepClone(source[key], weak);
return cloned;
}
return source;
}
var handleUnion = (schemas, property, instruction) => {
if (instruction.TypeCompiler === void 0) {
if (!instruction.typeCompilerWanred) {
console.warn(
new Error(
"[exact-mirror] TypeBox's TypeCompiler is required to use Union"
)
);
instruction.typeCompilerWanred = true;
}
return property;
}
instruction.unionKeys[property] = 1;
const ui = instruction.unions.length;
const typeChecks = instruction.unions[ui] = [];
let v = `(()=>{
`;
const unwrapRef = (type) => {
if (!(Kind in type) || !type.$ref) return type;
if (type[Kind] === "This") {
return deepClone(instruction.definitions[type.$ref]);
} else if (type[Kind] === "Ref") {
if (!instruction.modules)
console.warn(
new Error(
"[exact-mirror] modules is required when using nested cyclic reference"
)
);
else
return instruction.modules.Import(
type.$ref
);
}
return type;
};
let cleanThenCheck = "";
for (let i = 0; i < schemas.length; i++) {
let type = unwrapRef(schemas[i]);
if (Array.isArray(type.anyOf))
for (let i2 = 0; i2 < type.anyOf.length; i2++)
type.anyOf[i2] = unwrapRef(type.anyOf[i2]);
else if (type.items) {
if (Array.isArray(type.items))
for (let i2 = 0; i2 < type.items.length; i2++)
type.items[i2] = unwrapRef(type.items[i2]);
else type.items = unwrapRef(type.items);
}
typeChecks.push(TypeCompiler.Compile(type));
v += `if(d.unions[${ui}][${i}].Check(${property})){return ${mirror(
type,
property,
{
...instruction,
recursion: instruction.recursion + 1,
parentIsOptional: true
}
)}}
`;
cleanThenCheck += (i ? "" : "let ") + "tmp=" + mirror(type, property, {
...instruction,
recursion: instruction.recursion + 1,
parentIsOptional: true
}) + `
if(d.unions[${ui}][${i}].Check(tmp))return tmp
`;
}
if (cleanThenCheck) v += cleanThenCheck;
v += `return ${instruction.removeUnknownUnionType ? "undefined" : property}`;
return v + `})()`;
};
var mirror = (schema, property, instruction) => {
if (!schema) return "";
const isRoot = property === "v" && !instruction.unions.length;
if (Kind in schema && schema[Kind] === "Import" && schema.$ref in schema.$defs)
return mirror(schema.$defs[schema.$ref], property, {
...instruction,
definitions: Object.assign(instruction.definitions, schema.$defs)
});
if (isRoot && schema.type !== "object" && schema.type !== "array" && !schema.anyOf)
return `return ${sanitize("v", instruction.sanitize?.length, schema)}`;
if (instruction.recursion >= instruction.recursionLimit) return property;
let v = "";
if (schema.$id && Hint in schema)
instruction.definitions[schema.$id] = schema;
switch (schema.type) {
case "object":
if (schema[Kind] === "Record") {
v = handleRecord(schema, property, instruction);
break;
}
schema = mergeObjectIntersection(schema);
v += "{";
if (schema.additionalProperties) v += `...${property},`;
const keys = Object.keys(schema.properties);
for (let i2 = 0; i2 < keys.length; i2++) {
const key = keys[i2];
let isOptional = (
// all fields are optional
!schema.required || // field is explicitly required
schema.required && !schema.required.includes(key) || Array.isArray(schema.properties[key].anyOf)
);
const name = joinProperty(
property,
key,
instruction.parentIsOptional
);
if (isOptional) {
const index = instruction.array;
if (property.startsWith("ar")) {
const dotIndex = name.indexOf(".");
let refName;
if (dotIndex >= 0) {
refName = name.slice(dotIndex);
} else {
refName = name.slice(property.length);
}
if (refName.startsWith("?.")) {
if (refName.charAt(2) === "[") {
refName = refName.slice(2);
} else {
refName = refName.slice(1);
}
}
const array = instruction.optionalsInArray;
if (array[index]) array[index].push(refName);
else array[index] = [refName];
} else {
instruction.optionals.push(name);
}
}
const child = schema.properties[key];
if (i2 !== 0) v += ",";
v += `${encodeProperty(key)}:${isOptional ? `${name}===undefined?undefined:` : ""}${mirror(
child,
name,
{
...instruction,
recursion: instruction.recursion + 1,
parentIsOptional: isOptional
}
)}`;
}
v += "}";
break;
case "array":
if (schema.items.type !== "object" && schema.items.type !== "array") {
if (Array.isArray(schema.items)) {
v = handleTuple(schema.items, property, instruction);
break;
} else if (isRoot && !Array.isArray(schema.items.anyOf))
return "return v";
else if (Kind in schema.items && schema.items.$ref && (schema.items[Kind] === "Ref" || schema.items[Kind] === "This"))
v = mirror(
deepClone(instruction.definitions[schema.items.$ref]),
property,
{
...instruction,
parentIsOptional: true,
recursion: instruction.recursion + 1
}
);
else if (!Array.isArray(schema.items.anyOf)) {
v = property;
break;
}
}
const i = instruction.array;
instruction.array++;
let reference = property;
if (isRoot) v = `const ar${i}v=new Array(${property}.length);`;
else {
reference = `ar${i}s`;
v = `((${reference})=>{const ar${i}v=new Array(${reference}.length);`;
}
v += `for(let i=0;i<${reference}.length;i++){const ar${i}p=${reference}[i];ar${i}v[i]=${mirror(schema.items, `ar${i}p`, instruction)}`;
const optionals = instruction.optionalsInArray[i + 1];
if (optionals) {
for (let oi = 0; oi < optionals.length; oi++) {
const target = `ar${i}v[i]${optionals[oi]}`;
v += `;if(${target}===undefined)delete ${target}`;
}
}
v += `}`;
if (!isRoot) v += `return ar${i}v})(${property})`;
break;
default:
if (schema.$ref && schema.$ref in instruction.definitions)
return mirror(
instruction.definitions[schema.$ref],
property,
instruction
);
if (Array.isArray(schema.anyOf)) {
v = handleUnion(schema.anyOf, property, instruction);
break;
}
v = sanitize(property, instruction.sanitize?.length, schema);
break;
}
if (!isRoot) return v;
if (schema.type === "array") {
v = `${v}const x=ar0v;`;
} else {
v = `const x=${v}
`;
}
for (let i = 0; i < instruction.optionals.length; i++) {
const key = instruction.optionals[i];
const prop = key.slice(1);
v += `if(${key}===undefined`;
if (instruction.unionKeys[key]) v += `||x${prop}===undefined`;
const shouldQuestion = prop.charCodeAt(0) !== 63 && schema.type !== "array";
v += `)delete x${shouldQuestion ? prop.charCodeAt(0) === 91 ? "?." : "?" : ""}${prop}
`;
}
return `${v}return x`;
};
var createMirror = (schema, {
TypeCompiler: TypeCompiler2,
modules,
definitions,
sanitize: sanitize2,
recursionLimit = 8,
removeUnknownUnionType = false
} = {}) => {
const unions = [];
if (typeof sanitize2 === "function") sanitize2 = [sanitize2];
const f = mirror(schema, "v", {
optionals: [],
optionalsInArray: [],
array: 0,
parentIsOptional: false,
unions,
unionKeys: {},
TypeCompiler: TypeCompiler2,
modules,
// @ts-ignore private property
definitions: definitions ?? modules?.$defs ?? {},
sanitize: sanitize2,
recursion: 0,
recursionLimit,
removeUnknownUnionType
});
if (!unions.length && !sanitize2?.length) return Function("v", f);
let hof;
if (sanitize2?.length) {
hof = {};
for (let i = 0; i < sanitize2.length; i++) hof[`h${i}`] = sanitize2[i];
}
return Function(
"d",
`return function mirror(v){${f}}`
)({
unions,
...hof
});
};
var index_default = createMirror;
export {
createMirror,
deepClone,
index_default as default,
mergeObjectIntersection
};