UNPKG

json-accelerator

Version:

Speed up JSON stringification by providing OpenAPI/TypeBox model

334 lines (332 loc) 12.6 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { createAccelerator: () => createAccelerator, default: () => index_default, mergeObjectIntersection: () => mergeObjectIntersection }); module.exports = __toCommonJS(index_exports); var Kind = Symbol.for("TypeBox.Kind"); var OptionalKind = Symbol.for("TypeBox.Optional"); var isSpecialProperty = (name) => /(\ |-|\t|\n)/.test(name); var joinProperty = (v1, v2) => { if (typeof v2 === "number") return `${v1}[${v2}]`; if (isSpecialProperty(v2)) return `${v1}["${v2}"]`; return `${v1}.${v2}`; }; var encodeProperty = (v) => `"${v}"`; var isInteger = (schema) => { if (!schema.anyOf || Kind in schema && schema[Kind] !== "Union") return false; let hasIntegerFormat = false; let hasNumberType = false; for (const type of schema.anyOf) { if (type.type === "null" || type.type === "undefined") { continue; } if (!hasIntegerFormat && type.type === "string" && type.format === "integer") { hasIntegerFormat = true; continue; } if (!hasNumberType && (type.type === "number" || type.type === "integer")) { hasNumberType = true; continue; } return false; } return hasIntegerFormat && hasNumberType; }; var getMetadata = (schema) => { let isNullable = false; let isUndefinable = false; let newSchema; if (!schema.anyOf || Kind in schema && schema[Kind] !== "Union") return { schema, isNullable, isUndefinable }; for (const type of schema.anyOf) { if (type.type === "null") { isNullable = true; continue; } if (type.type === "undefined") { isUndefinable = true; continue; } if (!newSchema) { newSchema = type; continue; } return { schema, isNullable, isUndefinable }; } return { schema: newSchema, isNullable, isUndefinable }; }; 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 isDateType = (schema) => { if (!schema.anyOf || Kind in schema && schema[Kind] !== "Union") return false; if (!schema.anyOf) return false; let hasDateType = false; let hasStringFormatDate = false; let hasNumberType = false; if (schema.anyOf) for (const type of schema.anyOf) { if (!hasDateType && type.type === "Date") hasDateType = true; if (!hasStringFormatDate && type.type === "string" && (type.format === "date" || type.format === "date-time")) hasStringFormatDate = true; if (!hasNumberType && type.type === "number") hasNumberType = true; } return hasDateType; }; var SANITIZE = { auto: (property) => `re.test(${property})?JSON.stringify(${property}):\`"\${${property}}"\``, manual: (property) => `${property}`, throw: (property) => `re.test(${property})?(()=>{throw new Error("Property '${property}' contains invalid characters")})():${property}` }; var joinStringArray = (p) => `"\${((p)=>{if(p.length===1)return p let ars='' for(let i=0;i<p.length;i++){if(i===0)ars+=p[i] else ars+=\`","\${p[i]}\`}return ars})(${p})}"`; 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++; return `\${((ar${i}n)=>{const ar${i}s=Object.keys(ar${i}n);let ar${i}v='{';for(let i=0;i<ar${i}s.length;i++){const ar${i}p=ar${i}n[ar${i}s[i]];if(i!==0)ar${i}v+=',';ar${i}v+=\`"\${ar${i}s[i]}":${accelerate(child, `ar${i}p`, instruction)}\`}return ar${i}v + '}'})(${property})}`; }; var accelerate = (schema, property, instruction) => { if (!schema) return ""; if (Kind in schema && schema[Kind] === "Import" && schema.$ref in schema.$defs) return accelerate(schema.$defs[schema.$ref], property, { ...instruction, definitions: Object.assign(instruction.definitions, schema.$defs) }); let v = ""; const isRoot = property === "v"; const { schema: newSchema, isNullable, isUndefinable } = getMetadata(schema); schema = newSchema; const nullableCondition = isNullable && isUndefinable ? `${property}===null||${property}===undefined` : isNullable ? `${property}===null` : isUndefinable ? `${property}===undefined` : ""; let sanitize = SANITIZE[instruction.sanitize]; switch (schema.type) { case "string": instruction.hasString = true; if (instruction.sanitize === "auto" || // Elysia specific format, this implied that format might contain unescaped JSON string schema.sanitize) { sanitize = SANITIZE["auto"]; if (nullableCondition) v = `\${${nullableCondition}?${schema.const !== void 0 ? `'${JSON.stringify(schema.const)}'` : schema.default !== void 0 ? `'${JSON.stringify(schema.default)}'` : `'null'`}:${sanitize(property)}}`; else v = `${schema.const !== void 0 ? `${JSON.stringify(schema.const)}` : `\${${sanitize(property)}}`}`; } else { if (nullableCondition) v = `\${${nullableCondition}?${schema.const !== void 0 ? `'${JSON.stringify(schema.const)}'` : schema.default !== void 0 ? `'${JSON.stringify(schema.default)}'` : `'null'`}:\`\\"\${${sanitize(property)}}\\"\`}`; else v = `${schema.const !== void 0 ? `${JSON.stringify(schema.const)}` : `"\${${sanitize(property)}}"`}`; } break; case "number": case "boolean": case "bigint": if (nullableCondition) v = `\${${property}??${schema.default !== void 0 ? schema.default : `'null'`}}`; else v = `\${${property}}`; break; case "null": v = `\${${property}}`; break; case "undefined": break; case "object": if (nullableCondition) v += `\${${nullableCondition}?"null":\``; v += "{"; if (schema.additionalProperties) { v = `\${JSON.stringify(${property})}`; break; } schema = mergeObjectIntersection(schema); let init = true; let hasOptional = false; let op = `op${instruction.optional}`; if (schema[Kind] === "Record") { v = handleRecord(schema, property, instruction); break; } if (!Object.keys(schema.properties).length && schema.patternProperties) { v = `\${JSON.stringify(${property})}`; break; } for (const key in schema.properties) if (OptionalKind in schema.properties[key]) { instruction.optional++; hasOptional = true; break; } for (const key in schema.properties) { const isOptional = OptionalKind in schema.properties[key]; const name = joinProperty(property, key); const hasShortName = schema.properties[key].type === "object" && !name.startsWith("ar"); const i2 = instruction.properties.length; if (hasShortName) instruction.properties.push(name); const k = encodeProperty(key); const p = accelerate( schema.properties[key], hasShortName ? `s${i2}` : name, instruction ); const comma = `\${${op}?',':(${op}=true)&&''}`; let defaultValue = schema.properties[key].default; if (defaultValue !== void 0) { if (typeof defaultValue === "string") defaultValue = `"${defaultValue}"`; defaultValue = `\`${comma}${k}:${defaultValue}\``; } else defaultValue = '""'; v += isOptional ? `\${(${name}===undefined?${defaultValue}:\`${comma}${k}:${p}\`)}` : hasOptional ? `${!init ? "," : `\${(${op}=true)&&""}`}${k}:${p}` : `${!init ? "," : ""}${k}:${p}`; init = false; } v += "}"; if (nullableCondition) v += `\`}`; break; case "array": const i = instruction.array; instruction.array++; if (schema.items.type === "string") { if (isRoot) v += "return `"; if (nullableCondition) v += `\${${nullableCondition}?"null":${property}.length?\`[${joinStringArray(property)}]\`:"[]"}`; else v += `\${${property}.length?\`[${joinStringArray(property)}]\`:"[]"}`; if (isRoot) v += "`"; break; } if (schema.items.type === "number" || schema.items.type === "boolean" || schema.items.type === "bigint" || isInteger(schema.items)) { if (isRoot) v += "return `"; if (nullableCondition) v += `\${${nullableCondition}?'"null"':${property}.length?\`[\${${property}.toString()}]\`:"[]"`; else v += `\${${property}.length?\`[\${${property}.toString()}]\`:"[]"}`; if (isRoot) v += "`"; break; } if (isNullable || isUndefinable) v += `\${!${property}?'"null"':\``; if (isRoot) v += `const ar${i}s=${property};`; else v += `\${((ar${i}s)=>{`; v += `let ar${i}v='[';for(let i=0;i<ar${i}s.length;i++){const ar${i}p=ar${i}s[i];if(i!==0){ar${i}v+=','}ar${i}v+=\`${accelerate(schema.items, `ar${i}p`, instruction)}\`}return ar${i}v+']'`; if (!isRoot) v += `})(${property})}`; if (isNullable || isUndefinable) v += `\`}`; break; default: if (schema.$ref && schema.$ref in instruction.definitions) return accelerate( instruction.definitions[schema.$ref], property, instruction ); if (isDateType(schema)) { if (isNullable || isUndefinable) v = `\${${nullableCondition}?${schema.default !== void 0 ? `'"${schema.default}"'` : "'null'"}:typeof ${property}==="object"?\`"\${${property}.toISOString()}"\`:${property}}`; else { v = `\${typeof ${property}==="object"?\`"\${${property}.toISOString()}"\`:${property}}`; } break; } if (isInteger(schema)) { if (nullableCondition) v = `\${${property}??${schema.default !== void 0 ? schema.default : `'null'`}}`; else v = `\${${property}}`; break; } v = `\${JSON.stringify(${property})}`; break; } if (!isRoot) return v; const isArray = schema.type === "array"; if (!isArray) v = `\`${v}\``; let setup = ""; if (instruction.hasString) setup += `const re=/[\\b\\f\\n\\r\\t\\\\\\\\/"]/ `; if (instruction.optional) { setup += "let "; for (let i = 0; i < instruction.optional; i++) { if (i !== 0) setup += ","; setup += `op${i}=false`; } setup += "\n"; } if (instruction.properties.length) { setup += "const "; for (let i = 0; i < instruction.properties.length; i++) { if (i !== 0) setup += ","; setup += `s${i}=${instruction.properties[i]}`; } setup += "\n"; } if (isArray) return setup + "\n" + v; return setup + "return " + v; }; var createAccelerator = (schema, { sanitize = "auto", definitions = {} } = {}) => { const f = accelerate(schema, "v", { array: 0, optional: 0, properties: [], hasString: false, sanitize, definitions }); return Function("v", f); }; var index_default = createAccelerator; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { createAccelerator, mergeObjectIntersection });