UNPKG

exact-mirror

Version:

Mirror exact value to TypeBox/OpenAPI model

357 lines (356 loc) 12.1 kB
// 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 };