UNPKG

@duplojs/zod-to-typescript

Version:
582 lines (540 loc) 22.2 kB
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 };