UNPKG

@conte-ltd/prisma-zod-generator

Version:

Prisma 2+ generator to emit Zod schemas from your Prisma schema

358 lines 14.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const ts_morph_1 = require("ts-morph"); const capitalizeFirstLetter_1 = require("./utils/capitalizeFirstLetter"); class Transformer { constructor(params) { var _a, _b, _c; this.hasJson = false; this.sourceFile = params.sourceFile; this.name = (_a = params.name) !== null && _a !== void 0 ? _a : ''; this.fields = (_b = params.fields) !== null && _b !== void 0 ? _b : []; this.enumType = (_c = params.enumType) !== null && _c !== void 0 ? _c : {}; } static setOutputPath(outPath) { this.outputPath = outPath; } getPrismaStringLine(field, inputType, inputsLength) { const isEnum = inputType.location === 'enumTypes'; const arr = inputType.isList ? '.array()' : ''; const opt = !field.isRequired ? '.optional()' : ''; if (isEnum) { const enumSchemaLine = `${inputType.type}Schema`; return inputsLength === 1 ? ` ${field.name}: ${enumSchemaLine}${arr}${opt}` : `${enumSchemaLine}${arr}${opt}`; } const objectSchemaLine = `${inputType.type}ObjectSchema`; return inputsLength === 1 ? ` ${field.name}: z.lazy(() => ${objectSchemaLine})${arr}${opt}` : `z.lazy(() => ${objectSchemaLine})${arr}${opt}`; } wrapWithZodValidators(mainValidator, field, inputType) { let line = ''; line = mainValidator; if (inputType.isList) { line += '.array()'; } if (!field.isRequired) { line += '.optional()'; } return line; } getObjectSchemaLine(field) { let lines = field.inputTypes; if (lines.length === 0) { return []; } let alternatives = lines.reduce((result, inputType) => { if (inputType.type === 'String') { result.push(this.wrapWithZodValidators('z.string()', field, inputType)); } else if (inputType.type === 'Int' || inputType.type === 'Float' || inputType.type === 'Decimal') { result.push(this.wrapWithZodValidators('z.number()', field, inputType)); } else if (inputType.type === 'BigInt') { result.push(this.wrapWithZodValidators('z.bigint()', field, inputType)); } else if (inputType.type === 'Boolean') { result.push(this.wrapWithZodValidators('z.boolean()', field, inputType)); } else if (inputType.type === 'DateTime') { result.push(this.wrapWithZodValidators('z.date()', field, inputType)); } else if (inputType.type === 'Json') { this.hasJson = true; result.push(this.wrapWithZodValidators('jsonSchema', field, inputType)); } else { if (inputType.type !== 'Null') { result.push(this.getPrismaStringLine(field, inputType, lines.length)); } } return result; }, []); if (alternatives.length === 0) { return []; } if (alternatives.length > 1) { alternatives = alternatives.map((alter) => alter.replace('.optional()', '')); } const fieldName = alternatives.some((alt) => alt.includes(':')) ? '' : ` ${field.name}:`; const opt = !field.isRequired ? '.optional()' : ''; let resString = alternatives.length === 1 ? alternatives.join(',\r\n') : `z.union([${alternatives.join(',\r\n')}])${opt}`; if (field.isNullable) { resString += '.nullable()'; } return [[` ${fieldName} ${resString} `, field, true]]; } getFieldValidators(zodStringWithMainType, field) { const { isRequired, isNullable } = field; if (!isRequired) { zodStringWithMainType += '.optional()'; } if (isNullable) { zodStringWithMainType += '.nullable()'; } return zodStringWithMainType; } addZodImport() { this.sourceFile.addImportDeclaration({ moduleSpecifier: 'zod', namedImports: ['z'], }); } addPrismaTypeImport() { var _a; let prismaClientPath = '@prisma/client'; if (Transformer.isDefaultPrismaClientOutput) { prismaClientPath = (_a = Transformer.prismaClientOutputPath) !== null && _a !== void 0 ? _a : ''; prismaClientPath = path_1.default .relative(path_1.default.join(Transformer.outputPath, 'objects'), prismaClientPath) .split(path_1.default.sep) .join(path_1.default.posix.sep); } this.sourceFile.addImportDeclaration({ moduleSpecifier: prismaClientPath, namedImports: ['Prisma'], isTypeOnly: true, }); } addFieldsSchemaImport(fields, inObjectsPath) { const enumImports = new Set(fields.flatMap(({ inputTypes }) => { return inputTypes .filter((inputType) => { const isEnum = inputType.location === 'enumTypes'; return (isEnum && inputType.type !== this.name && typeof inputType.type === 'string'); }) .map((inputType) => { return inputType.type; }); })); this.sourceFile.addImportDeclaration({ moduleSpecifier: inObjectsPath ? '../enums' : './enums', namedImports: [...enumImports].map((name) => `${name}Schema`), }); const objectImports = new Set(fields.flatMap(({ inputTypes }) => { return inputTypes .filter((inputType) => { const isEnum = inputType.location === 'enumTypes'; return (!isEnum && inputType.namespace === 'prisma' && inputType.type !== this.name && typeof inputType.type === 'string'); }) .map((inputType) => { return inputType.type; }); })); this.sourceFile.addImportDeclaration({ moduleSpecifier: inObjectsPath ? './index' : './objects', namedImports: [...objectImports].map((name) => `${name}ObjectSchema`), }); } getJsonSchemaImplementation() { let jsonShemaImplementation = ''; if (this.hasJson) { jsonShemaImplementation += `\n`; jsonShemaImplementation += `const literalSchema = z.union([z.string(), z.number(), z.boolean()]);\n`; jsonShemaImplementation += `const jsonSchema: z.ZodType<Prisma.InputJsonValue> = z.lazy(() =>\n`; jsonShemaImplementation += ` z.union([literalSchema, z.array(jsonSchema.nullable()), z.record(jsonSchema.nullable())])\n`; jsonShemaImplementation += `);\n\n`; } return jsonShemaImplementation; } getExportObjectSchema(schema, name) { const schemaName = `${name}ObjectSchema`; const end = `export const ${schemaName} = ${schemaName}Base as z.ZodType<Prisma.${name}>`; return `export const ${schemaName}Base = ${schema};\n${end}`; } getExportSchema(schema, name) { return `export const ${name}Schema = ${schema}`; } wrapWithZodObject(zodStringFields) { let wrapped = ''; wrapped += 'z.object({'; wrapped += '\n'; wrapped += ' ' + zodStringFields; wrapped += '\n'; wrapped += '})'; return wrapped; } wrapWithZodOUnion(zodStringFields) { let wrapped = ''; wrapped += 'z.union(['; wrapped += '\n'; wrapped += ' ' + zodStringFields.join(','); wrapped += '\n'; wrapped += '])'; return wrapped; } getZodObject(fields) { const objectSchemaLines = fields .map((field) => this.getObjectSchemaLine(field)) .flatMap((item) => item); const zodStringFields = objectSchemaLines.map((item) => { const [zodStringWithMainType, field, skipValidators] = item; const value = skipValidators ? zodStringWithMainType : this.getFieldValidators(zodStringWithMainType, field); return value.trim(); }); const shouldWrapWithUnion = zodStringFields.some((field) => // TODO handle other cases if any // field.includes('create:') || field.includes('connectOrCreate:') || field.includes('connect:')); if (!shouldWrapWithUnion) { return this.wrapWithZodObject(zodStringFields) + '.strict()'; } const wrapped = zodStringFields.map((field) => this.wrapWithZodObject(field) + '.strict()'); return this.wrapWithZodOUnion(wrapped); } printObjectSchemas() { const fields = this.fields; this.addPrismaTypeImport(); this.addZodImport(); this.addFieldsSchemaImport(fields, true); const objectSchema = `${this.getExportObjectSchema(this.getZodObject(fields), this.name)}\n`; const json = this.getJsonSchemaImplementation(); this.sourceFile.addStatements([json, objectSchema]); return this.sourceFile; } printSelectObjectSchemas(isInclude) { const fields = this.fields; const relatedFields = fields.filter((field) => field.kind === 'object'); this.addPrismaTypeImport(); this.addZodImport(); if (relatedFields.length > 0) { this.sourceFile.addImportDeclaration({ moduleSpecifier: '../index', namedImports: [ ...new Set(relatedFields .filter((field) => field.isList) .map((field) => { return `FindMany${field.type}Schema`; })), ], }); this.sourceFile.addImportDeclaration({ moduleSpecifier: './index', namedImports: [ ...new Set(relatedFields .filter((field) => !field.isList) .map((field) => { return `${field.type}ArgsObjectSchema`; })), ], }); } const fieldSchemas = (isInclude ? relatedFields : fields).reduce((prev, field) => { if (field.kind === 'object') { if (field.isList) { return `${prev}${field.name}: z.union([z.lazy(() => FindMany${field.type}Schema), z.boolean()]).optional(), `; } else { return `${prev}${field.name}: z.union([z.lazy(() => ${field.type}ArgsObjectSchema), z.boolean()]).optional(), `; } } else { return `${prev}${field.name}: z.boolean().optional(), `; } }, ``); this.sourceFile.addVariableStatements([ { declarationKind: ts_morph_1.VariableDeclarationKind.Const, declarations: [ { name: `${this.name}ObjectSchemaBase`, initializer: `z.object({ ${fieldSchemas} })`, }, ], isExported: true, }, { declarationKind: ts_morph_1.VariableDeclarationKind.Const, declarations: [ { name: `${this.name}ObjectSchema`, initializer: `${this.name}ObjectSchemaBase as z.ZodType<Prisma.${this.name}>`, }, ], isExported: true, }, ]); return this.sourceFile; } printArgsObjectSchemas(modelName) { this.addPrismaTypeImport(); this.addZodImport(); this.sourceFile.addImportDeclaration({ moduleSpecifier: './index', namedImports: [ `${modelName}SelectObjectSchema`, `${modelName}IncludeObjectSchema`, ], }); this.sourceFile.addVariableStatements([ { declarationKind: ts_morph_1.VariableDeclarationKind.Const, declarations: [ { name: `${this.name}ObjectSchemaBase`, initializer: `z.object({ select: z.lazy(() => ${modelName}SelectObjectSchema).optional(), include: z.lazy(() => ${modelName}IncludeObjectSchema).optional() })`, }, ], isExported: true, }, { declarationKind: ts_morph_1.VariableDeclarationKind.Const, declarations: [ { name: `${this.name}ObjectSchema`, initializer: `${this.name}ObjectSchemaBase as z.ZodType<Prisma.${this.name}>`, }, ], isExported: true, }, ]); return this.sourceFile; } printModelSchema() { const fields = this.fields; this.addZodImport(); this.addFieldsSchemaImport(fields); this.sourceFile.addVariableStatement({ isExported: true, declarationKind: ts_morph_1.VariableDeclarationKind.Const, declarations: [ { name: `${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(this.name)}Schema`, initializer: this.getZodObject(fields), }, ], }); return this.sourceFile; } printEnumSchemas() { const { name, values } = this.enumType; this.addZodImport(); this.sourceFile.addStatements(this.getExportSchema(`z.enum(${JSON.stringify(values)})`, `${name}`)); return this.sourceFile; } } Transformer.enumNames = []; Transformer.outputPath = './generated'; exports.default = Transformer; //# sourceMappingURL=transformer.js.map