exact-mirror
Version:
Mirror exact value to TypeBox/OpenAPI model
380 lines (379 loc) • 14.9 kB
JavaScript
//#region src/index.ts
const Kind = "~kind";
const Hint = "~hint";
const Codec = "~codec";
const copySchema = (node) => Object.create(Object.getPrototypeOf(node), Object.getOwnPropertyDescriptors(node));
const copySchemaWith = (node, overrides) => {
const descriptors = Object.getOwnPropertyDescriptors(node);
for (const key in overrides) descriptors[key] = {
value: overrides[key],
writable: true,
enumerable: descriptors[key]?.enumerable ?? true,
configurable: true
};
return Object.create(Object.getPrototypeOf(node), descriptors);
};
const isSpecialProperty = (name) => !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
const joinProperty = (v1, v2, isOptional = false) => {
if (typeof v2 === "number") return `${v1}[${v2}]`;
if (isSpecialProperty(v2)) return `${v1}${isOptional ? "?." : ""}[${JSON.stringify(v2)}]`;
return `${v1}${isOptional ? "?" : ""}.${v2}`;
};
const encodeProperty = (v) => isSpecialProperty(v) ? JSON.stringify(v) : v;
const sanitize = (key, sanitize = 0, schema) => {
if (schema.type !== "string" || schema.const || schema.trusted) return key;
let hof = "";
for (let i = sanitize - 1; i >= 0; i--) hof += `d.h${i}(`;
return hof + key + ")".repeat(sanitize);
};
const mergeObjectIntersection = (schema) => {
if (!schema.allOf || Kind in schema && schema[Kind] !== "Intersect") return schema;
const { allOf, ...newSchema } = schema;
newSchema.properties = {};
newSchema.type = "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;
};
const 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}`;
}
instruction.optionalsInArray[i + 1] = [];
}
v += `}return ar${i}v})()`;
return v;
};
const handleTuple = (schema, property, instruction) => {
const i = instruction.array;
instruction.array++;
const isRoot = property === "v" && !instruction.fromUnion;
let v = "";
if (!isRoot) v = `(()=>{`;
v += `const ar${i}v=[`;
for (let i = 0; i < schema.length; i++) {
if (i !== 0) v += ",";
v += mirror(schema[i], joinProperty(property, i, instruction.parentIsOptional || instruction.fromUnion), 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;
}
const cloned = Object.create(Object.getPrototypeOf(source));
weak.set(source, cloned);
const descriptors = Object.getOwnPropertyDescriptors(source);
for (const key of Reflect.ownKeys(descriptors)) {
const descriptor = descriptors[key];
if ("value" in descriptor) descriptor.value = deepClone(descriptor.value, weak);
Object.defineProperty(cloned, key, descriptor);
}
return cloned;
}
const handleCyclic = (schema, property, instruction) => {
const defs = schema.$defs;
let group = instruction.cyclic.groups.get(defs);
if (!group) {
group = {
defs,
names: {}
};
instruction.cyclic.groups.set(defs, group);
for (const name in defs) group.names[name] = `cy${instruction.cyclic.count++}`;
for (const name in defs) instruction.cyclic.fns.push(`function ${group.names[name]}(v){${mirror(defs[name], "v", {
...instruction,
cyclicDefs: group,
fromUnion: false,
parentIsOptional: false,
optionals: [],
optionalsInArray: [],
unionKeys: {},
array: 0,
recursion: 0
})}}`);
}
const fn = group.names[schema.$ref];
if (!fn) throw new Error(`[exact-mirror] cyclic reference "${schema.$ref}" is not found in $defs`);
return `(${property}==null?${property}:${fn}(${property}))`;
};
const withDefs = (type, group) => {
if (Kind in type) {
if (type[Kind] === "Cyclic") return type;
if (type[Kind] === "Ref" && type.$ref in group.defs) return Object.defineProperty({
$defs: group.defs,
$ref: type.$ref
}, Kind, { value: "Cyclic" });
}
let entry = "~check";
while (entry in group.defs) entry += "~";
const def = copySchemaWith(type, { $id: entry });
return Object.defineProperty({
$defs: {
...group.defs,
[entry]: def
},
$ref: entry
}, Kind, { value: "Cyclic" });
};
const handleUnion = (schemas, property, instruction) => {
if (instruction.Compile === void 0) {
if (!instruction.typeCompilerWanred) {
console.warn(/* @__PURE__ */ 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 = `(()=>{\n`;
const unwrapRef = (type) => {
if (!(Kind in type) || !type.$ref) return type;
if (type[Kind] === "This") return deepClone(instruction.definitions[type.$ref]);
if (type[Kind] === "Ref" && !instruction.cyclicDefs && type.$ref in instruction.definitions) return instruction.definitions[type.$ref];
return type;
};
let cleanThenCheck = "";
for (let i = 0; i < schemas.length; i++) {
let type = unwrapRef(schemas[i]);
if (Array.isArray(type.anyOf)) type = copySchemaWith(type, { anyOf: type.anyOf.map(unwrapRef) });
else if (type.items) {
const items = Array.isArray(type.items) ? type.items.map((item) => unwrapRef(item)) : unwrapRef(type.items);
type = copySchemaWith(type, { items });
}
typeChecks.push(instruction.Compile(instruction.cyclicDefs ? withDefs(type, instruction.cyclicDefs) : type));
v += `if(d.unions[${ui}][${i}].Check(${property})){return ${mirror(type, property, {
...instruction,
recursion: instruction.recursion + 1,
parentIsOptional: true,
fromUnion: true
})}}\n`;
cleanThenCheck += (i ? "" : "let ") + "tmp=" + mirror(type, property, {
...instruction,
recursion: instruction.recursion + 1,
parentIsOptional: true,
fromUnion: true
}) + `\nif(d.unions[${ui}][${i}].Check(tmp))return tmp\n`;
}
if (cleanThenCheck) v += cleanThenCheck;
v += `return ${instruction.removeUnknownUnionType ? "undefined" : property}`;
return v + `})()`;
};
const mirror = (schema, property, instruction) => {
if (!schema) return "";
const isRoot = property === "v" && !instruction.fromUnion;
const optionalsLength = instruction.optionals.length;
try {
if (instruction.transform && Codec in schema) {
const codec = schema[Codec][instruction.transform];
let ci = instruction.codecs.indexOf(codec);
if (ci === -1) ci = instruction.codecs.push(codec) - 1;
const body = mirrorNode(schema, `d.codecs[${ci}](${property})`, instruction);
return isRoot ? `return ${body}` : body;
}
if (Kind in schema && schema[Kind] === "Cyclic") {
const call = handleCyclic(schema, property, instruction);
return isRoot ? `return ${call}` : call;
}
return mirrorNode(schema, property, instruction);
} catch (error) {
instruction.optionals.length = optionalsLength;
console.warn(/* @__PURE__ */ new Error("[exact-mirror] failed to generate mirror for a schema node, the node is passed through as-is. Please report this issue to https://github.com/elysiajs/exact-mirror/issues"), error);
return isRoot ? "return v" : property;
}
};
const mirrorNode = (schema, property, instruction) => {
const isRoot = property === "v" && !instruction.fromUnion;
if (instruction.cyclicDefs && Kind in schema && schema[Kind] === "Ref" && schema.$ref && schema.$ref in instruction.cyclicDefs.names) {
const call = `(${property}==null?${property}:${instruction.cyclicDefs.names[schema.$ref]}(${property}))`;
return isRoot ? `return ${call}` : call;
}
if (Kind in schema && schema[Kind] === "Intersect" && schema.allOf) schema = mergeObjectIntersection(schema);
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);
if (!schema.properties) {
v = property;
break;
}
v += "{";
if (schema.additionalProperties) v += `...${property},`;
const keys = Object.keys(schema.properties);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
let isOptional = !schema.required || schema.required && !schema.required.includes(key) || Array.isArray(schema.properties[key].anyOf);
const name = joinProperty(property, key, instruction.parentIsOptional || instruction.fromUnion);
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);
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 (i !== 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) {
v = property;
break;
}
if (schema.items.type !== "object" && schema.items.type !== "array") {
const cyclicItems = instruction.cyclicDefs !== void 0 && !Array.isArray(schema.items) && Kind in schema.items && schema.items[Kind] === "Ref" && schema.items.$ref !== void 0 && schema.items.$ref in instruction.cyclicDefs.names;
const codecItems = instruction.transform !== void 0 && !Array.isArray(schema.items) && Codec in schema.items;
if (Array.isArray(schema.items)) {
v = handleTuple(schema.items, property, instruction);
break;
} else if (!cyclicItems && !codecItems) {
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}`;
}
instruction.optionalsInArray[i + 1] = [];
}
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}\n`;
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}\n`;
}
return `${v}return x`;
};
const createMirror = (schema, { Compile, modules, definitions, sanitize, recursionLimit = 8, removeUnknownUnionType = false, emit, decode, encode } = Object.create(null)) => {
const unions = [];
const codecs = [];
const cyclic = {
groups: /* @__PURE__ */ new Map(),
fns: [],
count: 0
};
if (typeof sanitize === "function") sanitize = [sanitize];
const f = mirror(schema, "v", {
optionals: [],
optionalsInArray: [],
array: 0,
parentIsOptional: false,
unions,
unionKeys: Object.create(null),
Compile,
modules,
definitions: definitions ?? modules?.$defs ?? Object.create(null),
cyclic,
sanitize,
recursion: 0,
recursionLimit,
removeUnknownUnionType,
transform: decode ? "decode" : encode ? "encode" : void 0,
codecs
});
const fns = cyclic.fns.length ? cyclic.fns.join("\n") + "\n" : "";
if (!unions.length && !sanitize?.length && !codecs.length) {
if (emit) return {
source: fns + f,
externals: void 0
};
return Function("v", fns + f);
}
let hof;
if (sanitize?.length) {
hof = Object.create(null);
for (let i = 0; i < sanitize.length; i++) hof[`h${i}`] = sanitize[i];
}
const source = `${fns}return function mirror(v){${f}}`;
if (emit) return {
source,
externals: {
unions,
codecs: codecs.length ? codecs : void 0,
hof
}
};
const d = Object.create(null);
if (unions.length) d.unions = unions;
if (codecs.length) d.codecs = codecs;
if (hof) Object.assign(d, hof);
return Function("d", source)(d);
};
//#endregion
export { copySchema, createMirror, createMirror as default, deepClone, mergeObjectIntersection };