@conte-ltd/prisma-zod-generator
Version:
Prisma 2+ generator to emit Zod schemas from your Prisma schema
358 lines • 14.5 kB
JavaScript
;
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