prisma-zod-generator
Version:
Prisma 2+ generator to emit Zod schemas from your Prisma schema
346 lines • 14.1 kB
JavaScript
;
/**
* Zod Integration Helper
*
* Integrates @zod comment parsing with existing comment processing
* and transformer workflows while maintaining backward compatibility.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.processModelsWithZodIntegration = processModelsWithZodIntegration;
exports.getZodIntegrationStatistics = getZodIntegrationStatistics;
exports.shouldPreserveExistingCommentProcessing = shouldPreserveExistingCommentProcessing;
exports.extractNonZodCommentContent = extractNonZodCommentContent;
exports.integrateZodWithExistingComments = integrateZodWithExistingComments;
const logger_1 = require("../utils/logger");
const zod_comments_1 = require("../parsers/zod-comments");
/**
* Process models to extract and integrate @zod annotations
*
* This function processes all models and their fields to:
* 1. Extract @zod annotations from field comments
* 2. Generate Zod schema strings for annotated fields
* 3. Maintain compatibility with existing comment processing
* 4. Collect import requirements and error information
*
* @param models - Array of Prisma DMMF models
* @param options - Processing options
* @returns Enhanced model information with Zod integration
*/
function processModelsWithZodIntegration(models, options = {}) {
const enhancedModels = [];
for (const model of models) {
try {
const enhancedModel = processModelWithZodIntegration(model, options);
enhancedModels.push(enhancedModel);
}
catch (error) {
// Handle model processing errors gracefully
console.warn(`Failed to process model ${model.name} for Zod integration:`, error);
// Create fallback model info
enhancedModels.push({
model,
enhancedFields: model.fields.map((field) => createFallbackFieldInfo(field)),
allZodImports: new Set(),
hasAnyZodAnnotations: false,
zodProcessingErrors: [
`Model processing failed: ${error instanceof Error ? error.message : String(error)}`,
],
modelCustomImports: [],
allCustomImports: [],
});
}
}
return enhancedModels;
}
/**
* Process a single model with Zod integration
*
* @param model - Prisma DMMF model
* @param options - Processing options
* @returns Enhanced model information
*/
function processModelWithZodIntegration(model, options) {
const enhancedFields = [];
const allZodImports = new Set();
const zodProcessingErrors = [];
let hasAnyZodAnnotations = false;
// Extract model-level custom imports
const modelCustomImportsResult = (0, zod_comments_1.extractModelCustomImports)(model);
const modelCustomImports = modelCustomImportsResult.imports;
zodProcessingErrors.push(...modelCustomImportsResult.parseErrors);
// Extract comments from all fields in the model
const extractedComments = (0, zod_comments_1.extractFieldComments)([model]);
// Create a map for quick field comment lookup
const commentMap = new Map();
extractedComments.forEach((comment) => {
commentMap.set(comment.context.fieldName, comment);
});
// Process each field
for (const field of model.fields) {
try {
const enhancedField = processFieldWithZodIntegration(field, model, commentMap, options);
enhancedFields.push(enhancedField);
// Collect imports and track annotations
enhancedField.zodImports.forEach((imp) => allZodImports.add(imp));
if (enhancedField.hasZodAnnotations) {
hasAnyZodAnnotations = true;
}
// Collect errors
zodProcessingErrors.push(...enhancedField.zodErrors);
}
catch (error) {
// Handle field processing errors gracefully
console.warn(`Failed to process field ${field.name} in model ${model.name}:`, error);
const fallbackField = createFallbackFieldInfo(field);
fallbackField.zodErrors.push(`Field processing failed: ${error instanceof Error ? error.message : String(error)}`);
enhancedFields.push(fallbackField);
}
}
// Collect all custom imports (model-level + field-level)
const allCustomImports = [...modelCustomImports];
enhancedFields.forEach((field) => {
allCustomImports.push(...field.customImports);
});
return {
model,
enhancedFields,
allZodImports,
hasAnyZodAnnotations,
zodProcessingErrors: zodProcessingErrors.filter((error) => error.length > 0),
modelCustomImports,
allCustomImports,
};
}
/**
* Process a single field with Zod integration
*
* @param field - Prisma DMMF field
* @param model - Parent model
* @param commentMap - Map of field comments
* @param options - Processing options
* @returns Enhanced field information
*/
function processFieldWithZodIntegration(field, model, commentMap, options) {
const enhancedField = {
field,
hasZodAnnotations: false,
zodImports: new Set(),
zodErrors: [],
fallbackToDefault: false,
customImports: [],
};
// Extract field-level custom imports
const fieldCustomImportsResult = (0, zod_comments_1.extractFieldCustomImports)(field, model.name);
enhancedField.customImports = fieldCustomImportsResult.imports;
enhancedField.customSchema = fieldCustomImportsResult.customSchema;
enhancedField.zodErrors.push(...fieldCustomImportsResult.parseErrors);
// Check if Zod annotation processing is enabled
if (options.enableZodAnnotations === false) {
enhancedField.fallbackToDefault = true;
return enhancedField;
}
// Get comment for this field
const extractedComment = commentMap.get(field.name);
if (!extractedComment || !extractedComment.hasZodAnnotations) {
// No @zod annotations found
if (options.generateFallbackSchemas !== false) {
enhancedField.zodSchema = generateFallbackSchema(field);
enhancedField.zodImports = (0, zod_comments_1.getRequiredImports)(field.type);
}
enhancedField.fallbackToDefault = true;
return enhancedField;
}
// Process @zod annotations
try {
const parseResult = (0, zod_comments_1.parseZodAnnotations)(extractedComment.normalizedComment, extractedComment.context);
if (!parseResult.isValid) {
enhancedField.zodErrors.push(...parseResult.parseErrors);
enhancedField.fallbackToDefault = true;
// Surface the rejection — silently dropping @zod annotations means
// users ship schemas without the validation they wrote (issue #374).
logger_1.logger.warn(`[prisma-zod-generator] Skipping @zod annotations for field "${field.name}":`, parseResult.parseErrors.join('; '));
return enhancedField;
}
// Generate Zod schema from annotations
const baseType = (0, zod_comments_1.getBaseZodTypeForField)(field);
const schemaResult = (0, zod_comments_1.generateCompleteZodSchema)(baseType, parseResult.annotations, extractedComment.context, options.zodVersion || 'auto');
if (schemaResult.isValid) {
enhancedField.hasZodAnnotations = true;
enhancedField.zodSchema = schemaResult.schemaChain;
enhancedField.zodImports = schemaResult.imports;
}
else {
enhancedField.zodErrors.push(...schemaResult.errors);
enhancedField.fallbackToDefault = true;
// Generate fallback if enabled
if (options.generateFallbackSchemas !== false) {
enhancedField.zodSchema = generateFallbackSchema(field);
enhancedField.zodImports = (0, zod_comments_1.getRequiredImports)(field.type);
}
}
}
catch (error) {
enhancedField.zodErrors.push(`Annotation processing failed: ${error instanceof Error ? error.message : String(error)}`);
enhancedField.fallbackToDefault = true;
// Generate fallback if enabled
if (options.generateFallbackSchemas !== false) {
enhancedField.zodSchema = generateFallbackSchema(field);
enhancedField.zodImports = (0, zod_comments_1.getRequiredImports)(field.type);
}
}
return enhancedField;
}
/**
* Create fallback field info for error cases
*
* @param field - Prisma DMMF field
* @returns Fallback enhanced field info
*/
function createFallbackFieldInfo(field) {
return {
field,
hasZodAnnotations: false,
zodSchema: generateFallbackSchema(field),
zodImports: (0, zod_comments_1.getRequiredImports)(field.type),
zodErrors: [],
fallbackToDefault: true,
customImports: [],
customSchema: undefined,
};
}
/**
* Generate fallback Zod schema for field without annotations
*
* @param field - Prisma DMMF field
* @returns Fallback Zod schema string
*/
function generateFallbackSchema(field) {
return (0, zod_comments_1.getBaseZodTypeForField)(field);
}
/**
* Get integration statistics for reporting
*
* @param enhancedModels - Array of enhanced model information
* @returns Integration statistics
*/
function getZodIntegrationStatistics(enhancedModels) {
const stats = {
totalModels: enhancedModels.length,
modelsWithZodAnnotations: 0,
totalFields: 0,
fieldsWithZodAnnotations: 0,
totalErrors: 0,
uniqueImports: 0,
};
const allImports = new Set();
for (const enhancedModel of enhancedModels) {
if (enhancedModel.hasAnyZodAnnotations) {
stats.modelsWithZodAnnotations++;
}
stats.totalFields += enhancedModel.enhancedFields.length;
stats.totalErrors += enhancedModel.zodProcessingErrors.length;
for (const enhancedField of enhancedModel.enhancedFields) {
if (enhancedField.hasZodAnnotations) {
stats.fieldsWithZodAnnotations++;
}
stats.totalErrors += enhancedField.zodErrors.length;
enhancedField.zodImports.forEach((imp) => allImports.add(imp));
}
}
stats.uniqueImports = allImports.size;
return stats;
}
/**
* Check if existing comment processing should be preserved
*
* This function ensures backward compatibility by checking if a field
* comment contains existing comment directives that should be processed
* by the original comment processing system.
*
* @param comment - Field comment string
* @returns True if existing processing should be preserved
*/
function shouldPreserveExistingCommentProcessing(comment) {
if (!comment) {
return false;
}
// Check for existing comment directives that should be preserved
const existingDirectives = [
/@Gen\./, // Existing generator directives
/@@/, // Model-level directives
/@map/, // Mapping directives
/@ignore/, // Ignore directives
];
return existingDirectives.some((directive) => directive.test(comment));
}
/**
* Extract non-Zod comment content for backward compatibility
*
* @param comment - Original comment string
* @returns Comment content with @zod annotations removed
*/
function extractNonZodCommentContent(comment) {
if (!comment) {
return '';
}
// Remove @zod annotations while preserving other content
const zodPattern = /@zod(\.[a-zA-Z_][a-zA-Z0-9_]*\s*(\([^)]*\))?)+/gi;
const cleanedComment = comment.replace(zodPattern, '').trim();
// Clean up extra whitespace
return cleanedComment.replace(/\s+/g, ' ').trim();
}
/**
* Integration wrapper that maintains backward compatibility
*
* This function provides a drop-in replacement for existing comment processing
* while adding @zod annotation support.
*
* @param models - Array of Prisma DMMF models
* @param options - Integration options
* @returns Models with both existing and Zod comment processing applied
*/
function integrateZodWithExistingComments(models, options = {}) {
const integrationWarnings = [];
let backwardCompatibilityPreserved = true;
try {
// Process models with Zod integration
const enhancedModels = processModelsWithZodIntegration(models, options);
// Check for backward compatibility concerns
for (const enhancedModel of enhancedModels) {
for (const enhancedField of enhancedModel.enhancedFields) {
const originalComment = enhancedField.field.documentation || '';
if (shouldPreserveExistingCommentProcessing(originalComment)) {
const nonZodContent = extractNonZodCommentContent(originalComment);
if (nonZodContent.length > 0) {
integrationWarnings.push(`Field ${enhancedModel.model.name}.${enhancedField.field.name} has both @zod annotations and existing comment directives. ` +
`Existing directives: "${nonZodContent}"`);
}
}
}
}
return {
enhancedModels,
backwardCompatibilityPreserved,
integrationWarnings,
};
}
catch (error) {
backwardCompatibilityPreserved = false;
integrationWarnings.push(`Integration failed: ${error instanceof Error ? error.message : String(error)}`);
// Return fallback data
return {
enhancedModels: models.map((model) => ({
model,
enhancedFields: model.fields.map(createFallbackFieldInfo),
allZodImports: new Set(),
hasAnyZodAnnotations: false,
zodProcessingErrors: ['Integration fallback due to processing error'],
modelCustomImports: [],
allCustomImports: [],
})),
backwardCompatibilityPreserved,
integrationWarnings,
};
}
}
//# sourceMappingURL=zod-integration.js.map