UNPKG

@duplojs/zod-to-typescript

Version:
593 lines (548 loc) 22.9 kB
'use strict'; 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;