zod-to-x
Version:
Multi language types generation from Zod schemas.
306 lines (305 loc) • 12.1 kB
JavaScript
;
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;