@duplojs/zod-to-typescript
Version:
<a name="top"></a>
582 lines (540 loc) • 22.2 kB
JavaScript
import { ZodType, z } from 'zod';
import ts, { SyntaxKind, factory, isUnionTypeNode } from 'typescript';
function addComment(node, text) {
ts.addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, `* ${text} `, true);
}
function createTextAlias(typeNode, text, exported) {
return factory.createTypeAliasDeclaration(undefined, factory.createIdentifier(text), undefined, typeNode);
}
function createTempAlias(text) {
return createTextAlias(factory.createLiteralTypeNode(factory.createNull()), text);
}
ZodType.prototype.identifier = function (name) {
const ZodTypeConstructor = this.constructor;
const definition = this._def;
const cloneSchema = new ZodTypeConstructor({
...definition,
});
cloneSchema._zttIdentifier = name;
cloneSchema._zttOverrideTypeNode = this._zttOverrideTypeNode;
return cloneSchema;
};
ZodType.prototype.overrideTypeNode = function (typeNode) {
const ZodTypeConstructor = this.constructor;
const definition = this._def;
const cloneSchema = new ZodTypeConstructor({
...definition,
});
cloneSchema._zttIdentifier = this._zttIdentifier;
cloneSchema._zttOverrideTypeNode
= typeof typeNode === "function"
? typeNode(ts)
: typeNode;
return cloneSchema;
};
class ZodToTypescript {
aliasContext = new Map();
zodSchemaHooks = [];
typescriptTransformators;
static typescriptTransformators = [];
static zod = z;
static count = 0;
constructor(typescriptTransformators = []) {
this.typescriptTransformators = [
...typescriptTransformators,
...ZodToTypescript.typescriptTransformators,
];
}
findTypescriptTransformator(zodSchema, context, options = {}) {
const { skipNextDeclarationStatement, skipNextZodSchemaHooks, ...currentOptions } = options;
let currentZodSchema = zodSchema;
if (!skipNextZodSchemaHooks && this.zodSchemaHooks.length) {
for (const zodSchemaHook of this.zodSchemaHooks) {
const result = zodSchemaHook(currentZodSchema, context, (action, zodSchema) => ({
action,
zodSchema,
}));
currentZodSchema = result.zodSchema;
if (result.action === "stop") {
break;
}
}
}
const declarationStatement = context.get(currentZodSchema);
if (!skipNextDeclarationStatement && declarationStatement) {
return factory.createTypeReferenceNode(factory.createIdentifier(declarationStatement.name.text));
}
for (const typescriptTransformator of this.typescriptTransformators) {
if (typescriptTransformator.support(currentZodSchema)) {
const typeNode = typescriptTransformator.makeTypeNode(currentZodSchema, {
context,
findTypescriptTransformator: (zodSchema) => this.findTypescriptTransformator(zodSchema, context, currentOptions),
});
if (currentZodSchema._zttIdentifier) {
context.set(currentZodSchema, createTextAlias(typeNode, currentZodSchema._zttIdentifier));
return this.findTypescriptTransformator(currentZodSchema, context, currentOptions);
}
return typeNode;
}
}
return this.findTypescriptTransformator(z.unknown(), context);
}
append(zodSchema, name) {
const aliasName = zodSchema._zttIdentifier ?? name ?? ZodToTypescript.getIdentifier();
this.aliasContext.set(zodSchema, createTempAlias(aliasName));
}
toString(exportType = false) {
const context = new Map(this.aliasContext);
[...this.aliasContext.entries()].forEach(([zodSchema, alias]) => {
const typeNode = this.findTypescriptTransformator(zodSchema, context, { skipNextDeclarationStatement: true });
const declaration = (zodSchema._zttIdentifier && context.get(zodSchema))
|| createTextAlias(typeNode, alias.name.text);
if (zodSchema.description) {
addComment(declaration, zodSchema.description);
}
context.delete(zodSchema);
context.set(zodSchema, declaration);
});
return ZodToTypescript.stringifyContext(context, exportType);
}
static stringifyContext(context, exportType = false) {
const sourceFile = ts.createSourceFile("print.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
const printer = ts.createPrinter();
return [...context.values()]
.flatMap((namedDeclaration) => [
printer.printNode(ts.EmitHint.Unspecified, namedDeclaration, sourceFile),
exportType
? printer.printNode(ts.EmitHint.Unspecified, factory.createExportDeclaration(undefined, false, factory.createNamedExports([
factory.createExportSpecifier(false, undefined, namedDeclaration.name),
])), sourceFile)
: undefined,
])
.filter(Boolean)
.join("\n\n")
.trim();
}
static convert(zodSchema, options = {}) {
if (options.indentifiers && !options.identifiers) {
console.warn("ZodToTypescript: indentifiers is deprecated, use identifiers instead");
options.identifiers = options.indentifiers;
}
const ztt = new ZodToTypescript();
ztt.zodSchemaHooks = options.zodSchemaHooks ?? [];
const identifier = options.name ?? this.getIdentifier();
if (options.identifiers) {
ztt.aliasContext.set(zodSchema, createTempAlias(identifier));
options.identifiers.forEach((indentifier) => {
const currentZodSchema = indentifier instanceof ZodType
? indentifier
: indentifier.zodSchema;
const currentName = indentifier instanceof ZodType
? this.getIdentifier()
: indentifier.name;
if (currentZodSchema === zodSchema) {
return;
}
ztt.append(currentZodSchema, currentName);
});
ztt.aliasContext.delete(zodSchema);
}
ztt.append(zodSchema, identifier);
return ztt.toString(options.export);
}
static getIdentifier() {
return `Zod2ts_${(this.count++).toString(36)}_duplojs`;
}
static injectZod(zod) {
this.zod = zod;
}
}
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return !!zodSchema._zttOverrideTypeNode;
},
makeTypeNode(zodSchema) {
return zodSchema._zttOverrideTypeNode;
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodAny;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodUndefined;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNull;
},
makeTypeNode() {
return factory.createLiteralTypeNode(factory.createNull());
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodVoid;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.VoidKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodUnknown;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNever;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodEnum;
},
makeTypeNode(zodSchema) {
return factory.createUnionTypeNode(zodSchema._def.values.map((value) => factory.createLiteralTypeNode(factory.createStringLiteral(value))));
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodString;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.StringKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNumber;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.NumberKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodBigInt;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.BigIntKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodBoolean;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodDate;
},
makeTypeNode() {
return factory.createTypeReferenceNode(factory.createIdentifier("Date"));
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodArray;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createArrayTypeNode(findTypescriptTransformator(zodSchema._def.type));
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodUnion;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
const options = zodSchema._def.options;
return factory.createUnionTypeNode(options.map((option) => findTypescriptTransformator(option)));
},
});
function isUndefinedTypeNode(typeNode) {
if (typeNode.kind === SyntaxKind.UndefinedKeyword) {
return true;
}
if (isUnionTypeNode(typeNode)) {
return typeNode.types.some((subTypeNode) => isUndefinedTypeNode(subTypeNode));
}
return false;
}
function createKeyIdentifier(name) {
if (/^[a-zA-Z$_]+[a-zA-Z0-9_$]*$/.test(name)) {
return factory.createIdentifier(name);
}
else {
return factory.createStringLiteral(name);
}
}
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodObject;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
const properties = Object.entries(zodSchema.shape);
const catchAllSchema = zodSchema._def.unknownKeys === "passthrough"
? ZodToTypescript.zod.unknown()
: zodSchema._def.catchall;
const objectPropertiesTypeNode = factory.createTypeLiteralNode(properties.map(([name, subZodSchema]) => {
const subTypeNode = findTypescriptTransformator(subZodSchema);
const propertyTypeNode = factory.createPropertySignature(undefined, createKeyIdentifier(name), isUndefinedTypeNode(subTypeNode)
? factory.createToken(SyntaxKind.QuestionToken)
: undefined, subTypeNode);
if (subZodSchema.description) {
addComment(propertyTypeNode, subZodSchema.description);
}
return propertyTypeNode;
}));
if (catchAllSchema instanceof ZodToTypescript.zod.ZodNever) {
return objectPropertiesTypeNode;
}
else {
return factory.createIntersectionTypeNode([
objectPropertiesTypeNode,
factory.createTypeLiteralNode([
factory.createIndexSignature(undefined, [
factory.createParameterDeclaration(undefined, undefined, "key", undefined, factory.createKeywordTypeNode(SyntaxKind.StringKeyword)),
], findTypescriptTransformator(catchAllSchema)),
]),
]);
}
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodTuple;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
const items = zodSchema.items.map((schema) => findTypescriptTransformator(schema));
const typeNodeRest = zodSchema._def.rest
? [
factory.createRestTypeNode(factory.createArrayTypeNode(findTypescriptTransformator(zodSchema._def.rest))),
]
: [];
return factory.createTupleTypeNode([
...items,
...typeNodeRest,
]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodSet;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createTypeReferenceNode(factory.createIdentifier("Set"), [findTypescriptTransformator(zodSchema._def.valueType)]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodRecord;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createTypeReferenceNode(factory.createIdentifier("Record"), [
findTypescriptTransformator(zodSchema._def.keyType),
findTypescriptTransformator(zodSchema._def.valueType),
]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodPromise;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createTypeReferenceNode(factory.createIdentifier("Promise"), [findTypescriptTransformator(zodSchema._def.type)]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodOptional;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createUnionTypeNode([
findTypescriptTransformator(zodSchema._def.innerType),
factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword),
]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNullable;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createUnionTypeNode([
findTypescriptTransformator(zodSchema._def.innerType),
factory.createLiteralTypeNode(factory.createNull()),
]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNativeEnum;
},
makeTypeNode(zodSchema, { context, findTypescriptTransformator }) {
const zodNativeEnumSchema = new ZodToTypescript.zod.ZodNativeEnum(zodSchema._def);
const enumDeclarationStatement = factory.createEnumDeclaration([], zodSchema._zttIdentifier ?? ZodToTypescript.getIdentifier(), Object.entries(zodSchema.enum).map(([key, value]) => factory.createEnumMember(key, typeof value === "string"
? factory.createStringLiteral(value)
: factory.createNumericLiteral(value))));
context.set(zodNativeEnumSchema, enumDeclarationStatement);
return findTypescriptTransformator(zodSchema);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodMap;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createTypeReferenceNode(factory.createIdentifier("Map"), [
findTypescriptTransformator(zodSchema._def.keyType),
findTypescriptTransformator(zodSchema._def.valueType),
]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodIntersection;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createIntersectionTypeNode([
findTypescriptTransformator(zodSchema._def.left),
findTypescriptTransformator(zodSchema._def.right),
]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodFunction;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
const argTypes = zodSchema._def.args._def.items.map((arg, index) => factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier(`args_${index}`), undefined, findTypescriptTransformator(arg)));
const restArgTypes = zodSchema._def.args._def.rest
? [
factory.createParameterDeclaration(undefined, factory.createToken(SyntaxKind.DotDotDotToken), factory.createIdentifier("rest"), undefined, factory.createArrayTypeNode(factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword))),
]
: [];
return factory.createFunctionTypeNode(undefined, [...argTypes, ...restArgTypes], findTypescriptTransformator(zodSchema._def.returns));
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodEffects;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return findTypescriptTransformator(zodSchema._def.schema);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodDiscriminatedUnion;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
const options = [...zodSchema._def.options.values()];
return factory.createUnionTypeNode(options.map((option) => findTypescriptTransformator(option)));
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodDefault;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return findTypescriptTransformator(zodSchema._def.innerType);
},
});
const litteralMapper = {
string: (value) => factory.createLiteralTypeNode(factory.createStringLiteral(value)),
number: (value) => factory.createLiteralTypeNode(factory.createNumericLiteral(value)),
boolean: (value) => value
? factory.createLiteralTypeNode(factory.createTrue())
: factory.createLiteralTypeNode(factory.createFalse()),
bigint: (value) => factory.createLiteralTypeNode(factory.createBigIntLiteral(value.toString())),
symbol: (value) => factory.createLiteralTypeNode(factory.createStringLiteral(value.toString())),
object: () => factory.createLiteralTypeNode(factory.createNull()),
undefined: () => factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword),
function: () => factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword),
};
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodLiteral;
},
makeTypeNode(zodSchema) {
const value = zodSchema._def.value;
const valueType = typeof value;
return litteralMapper[valueType](value);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodLazy;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return findTypescriptTransformator(zodSchema._def.getter());
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodPipeline;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return findTypescriptTransformator(zodSchema._def.in);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodBranded;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return findTypescriptTransformator(zodSchema._def.type);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodSymbol;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodReadonly;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return factory.createTypeReferenceNode(factory.createIdentifier("Readonly"), [findTypescriptTransformator(zodSchema._def.innerType)]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodCatch;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return findTypescriptTransformator(zodSchema._def.innerType);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNaN;
},
makeTypeNode() {
return factory.createKeywordTypeNode(SyntaxKind.NumberKeyword);
},
});
export { ZodToTypescript };