@duplojs/zod-to-typescript
Version:
<a name="top"></a>
593 lines (548 loc) • 22.9 kB
JavaScript
var zod = require('zod');
var ts = require('typescript');
function addComment(node, text) {
ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `* ${text} `, true);
}
function createTextAlias(typeNode, text, exported) {
return ts.factory.createTypeAliasDeclaration(undefined, ts.factory.createIdentifier(text), undefined, typeNode);
}
function createTempAlias(text) {
return createTextAlias(ts.factory.createLiteralTypeNode(ts.factory.createNull()), text);
}
zod.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;
};
zod.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 = 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 ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(declarationStatement.name.text));
}
for (const typescriptTransformator of this.typescriptTransformators) {
if (typescriptTransformator.support(currentZodSchema)) {
if (currentZodSchema._zttIdentifier) {
context.set(currentZodSchema, createTempAlias(currentZodSchema._zttIdentifier));
}
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(zod.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, ts.factory.createExportDeclaration(undefined, false, ts.factory.createNamedExports([
ts.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 zod.ZodType
? indentifier
: indentifier.zodSchema;
const currentName = indentifier instanceof zod.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 ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodUndefined;
},
makeTypeNode() {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNull;
},
makeTypeNode() {
return ts.factory.createLiteralTypeNode(ts.factory.createNull());
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodVoid;
},
makeTypeNode() {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodUnknown;
},
makeTypeNode() {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNever;
},
makeTypeNode() {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodEnum;
},
makeTypeNode(zodSchema) {
return ts.factory.createUnionTypeNode(zodSchema._def.values.map((value) => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(value))));
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodString;
},
makeTypeNode() {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNumber;
},
makeTypeNode() {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodBigInt;
},
makeTypeNode() {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodBoolean;
},
makeTypeNode() {
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodDate;
},
makeTypeNode() {
return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Date"));
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodArray;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return ts.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 ts.factory.createUnionTypeNode(options.map((option) => findTypescriptTransformator(option)));
},
});
function createKeyIdentifier(name) {
if (/^[a-zA-Z$_]+[a-zA-Z0-9_$]*$/.test(name)) {
return ts.factory.createIdentifier(name);
}
else {
return ts.factory.createStringLiteral(name);
}
}
function includesUndefinedTypeNode(typeNode) {
if (typeNode.kind === ts.SyntaxKind.UndefinedKeyword) {
return true;
}
if (ts.isUnionTypeNode(typeNode)) {
return typeNode.types.some((subTypeNode) => includesUndefinedTypeNode(subTypeNode));
}
return false;
}
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 = ts.factory.createTypeLiteralNode(properties.map(([name, subZodSchema]) => {
const subTypeNode = findTypescriptTransformator(subZodSchema);
const propertyTypeNode = ts.factory.createPropertySignature(undefined, createKeyIdentifier(name), includesUndefinedTypeNode(subTypeNode)
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
: undefined, subTypeNode);
if (subZodSchema.description) {
addComment(propertyTypeNode, subZodSchema.description);
}
return propertyTypeNode;
}));
if (catchAllSchema instanceof ZodToTypescript.zod.ZodNever) {
return objectPropertiesTypeNode;
}
else {
return ts.factory.createIntersectionTypeNode([
objectPropertiesTypeNode,
ts.factory.createTypeLiteralNode([
ts.factory.createIndexSignature(undefined, [
ts.factory.createParameterDeclaration(undefined, undefined, "key", undefined, ts.factory.createKeywordTypeNode(ts.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
? [
ts.factory.createRestTypeNode(ts.factory.createArrayTypeNode(findTypescriptTransformator(zodSchema._def.rest))),
]
: [];
return ts.factory.createTupleTypeNode([
...items,
...typeNodeRest,
]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodSet;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Set"), [findTypescriptTransformator(zodSchema._def.valueType)]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodRecord;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
const recordValue = findTypescriptTransformator(zodSchema._def.valueType);
const record = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Record"), [
findTypescriptTransformator(zodSchema._def.keyType),
recordValue,
]);
return includesUndefinedTypeNode(recordValue)
? ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Partial"), [record])
: record;
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodPromise;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Promise"), [findTypescriptTransformator(zodSchema._def.type)]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodOptional;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return ts.factory.createUnionTypeNode([
findTypescriptTransformator(zodSchema._def.innerType),
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
]);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodNullable;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return ts.factory.createUnionTypeNode([
findTypescriptTransformator(zodSchema._def.innerType),
ts.factory.createLiteralTypeNode(ts.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 = ts.factory.createEnumDeclaration([], zodSchema._zttIdentifier ?? ZodToTypescript.getIdentifier(), Object.entries(zodSchema.enum).map(([key, value]) => ts.factory.createEnumMember(key, typeof value === "string"
? ts.factory.createStringLiteral(value)
: ts.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 ts.factory.createTypeReferenceNode(ts.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 ts.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) => ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier(`args_${index}`), undefined, findTypescriptTransformator(arg)));
const restArgTypes = zodSchema._def.args._def.rest
? [
ts.factory.createParameterDeclaration(undefined, ts.factory.createToken(ts.SyntaxKind.DotDotDotToken), ts.factory.createIdentifier("rest"), undefined, ts.factory.createArrayTypeNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword))),
]
: [];
return ts.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 ts.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) => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(value)),
number: (value) => ts.factory.createLiteralTypeNode(ts.factory.createNumericLiteral(value)),
boolean: (value) => value
? ts.factory.createLiteralTypeNode(ts.factory.createTrue())
: ts.factory.createLiteralTypeNode(ts.factory.createFalse()),
bigint: (value) => ts.factory.createLiteralTypeNode(ts.factory.createBigIntLiteral(value.toString())),
symbol: (value) => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(value.toString())),
object: () => ts.factory.createLiteralTypeNode(ts.factory.createNull()),
undefined: () => ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
function: () => ts.factory.createKeywordTypeNode(ts.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 ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword);
},
});
ZodToTypescript.typescriptTransformators.push({
support(zodSchema) {
return zodSchema instanceof ZodToTypescript.zod.ZodReadonly;
},
makeTypeNode(zodSchema, { findTypescriptTransformator }) {
return ts.factory.createTypeReferenceNode(ts.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 ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
},
});
exports.ZodToTypescript = ZodToTypescript;
;