UNPKG

exact-mirror

Version:

Mirror exact value to TypeBox/OpenAPI model

386 lines (384 loc) 15.1 kB
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } }); //#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 exports.copySchema = copySchema; exports.createMirror = createMirror; exports.default = createMirror; exports.deepClone = deepClone; exports.mergeObjectIntersection = mergeObjectIntersection;