UNPKG

zod-to-x

Version:

Multi language types generation from Zod schemas.

306 lines (305 loc) 12.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Zod2Ts = void 0; const case_1 = __importDefault(require("case")); const core_1 = require("../../core"); const options_1 = require("./options"); class Zod2Ts extends core_1.Zod2X { constructor(opt = {}) { super(Object.assign(Object.assign({}, options_1.defaultOpts), opt)); this.commentKey = "//"; this.getAnyType = () => "any"; this.getBooleanType = () => "boolean"; this.getDateType = () => "Date"; /** Ex: Set<TypeA> */ this.getSetType = (itemType) => `Set<${itemType}>`; this.getStringType = () => "string"; /** Ex: [TypeA, TypeB] */ this.getTupleType = (itemsType) => `[${itemsType.join(", ")}]`; /** Ex: TypeA | TypeB */ this.getUnionType = (itemsType) => itemsType.map((type) => `${this.indent[1]}| ${type}`).join("\n"); /** Ex: TypeA & TypeB */ this.getIntersectionType = (itemsType) => itemsType.join(" & "); this.getNumberType = () => "number"; } runAfter() { } runBefore() { } addImportFromFile(filename, namespace) { const filenameWithoutExtension = filename.endsWith(".ts") ? filename.slice(0, -3) : filename; return `import * as ${namespace} from "./${filenameWithoutExtension}";`; } getTypeFromExternalNamespace(namespace, typeName) { return `${namespace}.${typeName}`; } addExtendedType(name, parentNamespace, aliasOf, opt) { var _a, _b; const extendedType = (opt === null || opt === void 0 ? void 0 : opt.isInternal) ? aliasOf : this.getTypeFromExternalNamespace(parentNamespace, aliasOf); const templates = (_a = opt === null || opt === void 0 ? void 0 : opt.templates) !== null && _a !== void 0 ? _a : ""; const declaredName = `${name}${(_b = opt === null || opt === void 0 ? void 0 : opt.declaredTemplates) !== null && _b !== void 0 ? _b : ""}`; if ((opt === null || opt === void 0 ? void 0 : opt.type) === "alias") { this.push0(`export type ${declaredName} = ${extendedType}${templates};\n`); } else if (this.opt.outType === "class") { if ((opt === null || opt === void 0 ? void 0 : opt.type) === "d-union") { this.push0(`export type ${declaredName} = ${extendedType}${templates};\n`); } else { this.push0(`export class ${declaredName} extends ${extendedType}${templates} {}\n`); } } else { if ((opt === null || opt === void 0 ? void 0 : opt.type) === "union" || (opt === null || opt === void 0 ? void 0 : opt.type) === "d-union") { this.push0(`export type ${declaredName} = ${extendedType}${templates};\n`); } else { this.push0(`export interface ${declaredName} extends ${extendedType}${templates} {}\n`); } } } getGenericTemplatesTranslation(data) { if ((data instanceof core_1.ASTObject || data instanceof core_1.ASTDefinition) && data.templatesTranslation.length > 0) { return ("<" + data.templatesTranslation .map((t) => { if (this.isExternalTypeImport(t)) { this.addExternalTypeImport(t); return this.getTypeFromExternalNamespace(t.parentNamespace, t.aliasOf); } else { return t.aliasOf; } }) .join(", ") + ">"); } } /** * Emits an alias/extension declaration early when a node references another layered type. * It preserves concrete template translations and falls back to declared templates (e.g. <T>) * for aliases of generic templates. */ checkExtendedTypeInclusion(data, type) { const declaredTemplatesFallback = data instanceof core_1.ASTObject && data.templates.size > 0 ? `<${[...data.templates].join(", ")}>` : undefined; const translatedTemplates = this.getGenericTemplatesTranslation(data); const templates = translatedTemplates || declaredTemplatesFallback; const declaredTemplates = translatedTemplates ? undefined : declaredTemplatesFallback; if (this.isExternalTypeImport(data)) { if (data.aliasOf) { this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, { type, templates, declaredTemplates, }); this.addExternalTypeImport(data); } return true; } else if (data.aliasOf) { this.addExtendedType(data.name, data.parentNamespace, data.aliasOf, { type, isInternal: true, templates, declaredTemplates, }); return true; } return false; } /** Ex: Array<Array<TypeA[]>> */ getArrayType(arrayType, arrayDeep) { let output = arrayType.includes("|") || arrayType.includes("&") ? `(${arrayType})[]` : `${arrayType}[]`; for (let i = 0; i < arrayDeep - 1; i++) { output = `Array<${output}>`; } return output; } getLiteralStringType(value, parentEnumNameKey) { return parentEnumNameKey ? `${parentEnumNameKey[0]}.${case_1.default.pascal(parentEnumNameKey[1])}` : isNaN(Number(value)) ? `"${value}"` : value; } /** Ex: Map<TypeA, TypeB> */ getMapType(keyType, valueType) { return `Map<${keyType}, ${valueType}>`; } /** Ex: Record<TypeA, TypeB> */ getRecordType(keyType, valueType) { return `Record<${keyType}, ${valueType}>`; } transpileAliasedType(data) { if (this.checkExtendedTypeInclusion(data, "alias")) { return; } let extendedType = undefined; this.addComment(data.description); if (data instanceof core_1.ASTArray) { extendedType = this.getAttributeType(data.item); } else { extendedType = this.getAttributeType(data); } if (extendedType !== undefined) { this.push0(`export type ${data.name} = ${extendedType};\n`); } } /** Ex: * enum { * ItemKey1: 0, // case of nativeEnum * ItemKey2: "ItemValue2" // case of Enum * } */ transpileEnum(data) { if (this.checkExtendedTypeInclusion(data, "alias")) { return; } this.addComment(data.description); this.push0(`export enum ${data.name} {`); data.values.forEach((i) => { // If enum key starts with number, it is stored between quotes. const key = case_1.default.pascal(i[0]); const keyValue = isNaN(Number(key.at(0))) ? key : `"${key}"`; // Enum value is stored between quotes if not nativeEnum. const enumValue = typeof i[1] === "string" ? `"${i[1]}"` : `${i[1]}`; this.push1(`${keyValue} = ${enumValue},`); }); this.push0("}\n"); } /** Ex: * // Interface output * // Class output if non-object intersection * type TypeC = TypeA & TypeB * * // Class output all-object intersection * class TypeC { * ...attributesTypeA, * ...attributesTypeB * * constructor(data: TypeC) { * ...attributesAssignment * } * } * */ transpileIntersection(data) { var _a; if (this.checkExtendedTypeInclusion(data)) { return; } if (this.opt.outType === "class" && data.newObject) { this.addComment((_a = data.newObject) === null || _a === void 0 ? void 0 : _a.description); this._transpileStructAsClass(data.newObject); } else { this.addComment(data.description); const attributesTypes = [data.left, data.right].map(this.getAttributeType.bind(this)); this.push0(`export type ${data.name} = ${this.getIntersectionType(attributesTypes)};\n`); } } transpileStruct(data) { if (this.checkExtendedTypeInclusion(data)) { return; } this.addComment(data.description); if (this.opt.outType === "class") { this._transpileStructAsClass(data); } else { this._transpileStructuAsInterface(data); } } /** Ex: * // Interface output * // Class output for Discriminated Union or non-objects union * type TypeC = TypeA | TypeB * * // Class output for all-object Union * class TypeC { * ...attributesTypeA, * ...attributesTypeB * * constructor(data: TypeC) { * ...attributesAssignment * } * } * */ transpileUnion(data) { var _a; if (this.checkExtendedTypeInclusion(data, data.discriminantKey === undefined ? "union" : "d-union")) { return; } if (this.opt.outType === "class" && data.newObject) { this.addComment((_a = data.newObject) === null || _a === void 0 ? void 0 : _a.description); this._transpileStructAsClass(data.newObject); } else { this.addComment(data.description); const attributesTypes = data.options.map(this.getAttributeType.bind(this)); this.push0(`export type ${data.name} =\n${this.getUnionType(attributesTypes)};\n`); } } /** Ex: * interface MyStruct { * att1: TypeA; * att2?: TypeB; * } * */ _transpileStructuAsInterface(data) { const templates = data.templates.size > 0 ? `<${[...data.templates].join(", ")}>` : ""; this.push0(`export interface ${data.name}${templates} {`); for (const [key, value] of Object.entries(data.properties)) { this._transpileMember(this.opt.keepKeys === true ? key : case_1.default.camel(key), value); } this.push0("}\n"); } /** Ex: * class MyStruct { * att1: TypeA; * att2?: TypeB; * * constructor(data: MyStruct) { * this.att1 = data.att1; * this.att2 = data.att2; * } * } * */ _transpileStructAsClass(data) { const templates = data.templates.size > 0 ? `<${[...data.templates].join(", ")}>` : ""; this.push0(`export class ${data.name}${templates} {`); const constructorBody = []; for (const [key, value] of Object.entries(data.properties)) { const keyName = this.opt.keepKeys === true ? key : case_1.default.camel(key); this._transpileMember(keyName, value); constructorBody.push(`this.${keyName} = data.${keyName};`); } this.push0(""); this.push1(`constructor(data: ${data.name}${templates}) {`); constructorBody.forEach((i) => this.push2(i)); this.push1("}"); this.push0("}\n"); } /** For Interface/Class attributes. * Ex: attribute1?: TypeA | null */ _transpileMember(memberName, memberNode) { const keyName = memberNode.isOptional ? `${memberName}?: ` : `${memberName}: `; const setNullable = memberNode.isNullable ? " | null" : ""; if (memberNode.description && !memberNode.name && !this.isTranspilerable(memberNode)) { // Avoid duplicated descriptions for transpiled items. this.addComment(memberNode.description, `\n${this.indent[1]}`); } this.push1(`${keyName}${this.getAttributeType(memberNode)}${setNullable};`); } } exports.Zod2Ts = Zod2Ts;