370 lines
12 KiB
JavaScript
370 lines
12 KiB
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}`;
|
|
}
|
|
instruction.optionalsInArray[i + 1] = [];
|
|
}
|
|
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.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;
|
|
}
|
|
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,
|
|
fromUnion: true
|
|
}
|
|
)}}
|
|
`;
|
|
cleanThenCheck += (i ? "" : "let ") + "tmp=" + mirror(type, property, {
|
|
...instruction,
|
|
recursion: instruction.recursion + 1,
|
|
parentIsOptional: true,
|
|
fromUnion: 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,
|
|
// If parent is a union, any property could be undefined
|
|
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);
|
|
}
|
|
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}`;
|
|
}
|
|
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}
|
|
`;
|
|
}
|
|
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
|
|
};
|