UNPKG

prisma-zod-generator

Version:

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

1,030 lines (1,029 loc) 218 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 results_1 = require("./generators/results"); const helpers_1 = require("./helpers"); const decimal_helpers_1 = require("./helpers/decimal-helpers"); const model_helpers_1 = require("./helpers/model-helpers"); const zod_integration_1 = require("./helpers/zod-integration"); const logger_1 = require("./utils/logger"); const naming_resolver_1 = require("./utils/naming-resolver"); const strict_mode_resolver_1 = require("./utils/strict-mode-resolver"); const writeFileSafely_1 = require("./utils/writeFileSafely"); const writeIndexFile_1 = require("./utils/writeIndexFile"); class Transformer { // Lightweight helpers to safely access optional JSON Schema compatibility settings static isJsonSchemaModeEnabled() { const cfg = this.getGeneratorConfig(); return !!(cfg === null || cfg === void 0 ? void 0 : cfg.jsonSchemaCompatible); } static getJsonSchemaOptions() { var _a; const cfg = this.getGeneratorConfig(); return ((_a = cfg === null || cfg === void 0 ? void 0 : cfg.jsonSchemaOptions) !== null && _a !== void 0 ? _a : {}); } constructor(params) { var _a, _b, _c, _d, _e, _f; this.schemaImports = new Set(); this.hasJson = false; this.hasDecimal = false; // Track excluded field names for current object generation to inform typed Omit this.lastExcludedFieldNames = null; this.name = (_a = params.name) !== null && _a !== void 0 ? _a : ''; this.fields = (_b = params.fields) !== null && _b !== void 0 ? _b : []; this.models = (_c = params.models) !== null && _c !== void 0 ? _c : []; this.modelOperations = (_d = params.modelOperations) !== null && _d !== void 0 ? _d : []; this.enumTypes = (_e = params.enumTypes) !== null && _e !== void 0 ? _e : []; // Process models with Zod integration on initialization const generatorConfig = Transformer.getGeneratorConfig(); const zodVersion = ((_f = generatorConfig === null || generatorConfig === void 0 ? void 0 : generatorConfig.zodImportTarget) !== null && _f !== void 0 ? _f : 'auto'); this.enhancedModels = (0, zod_integration_1.processModelsWithZodIntegration)(this.models, { enableZodAnnotations: true, generateFallbackSchemas: true, validateTypeCompatibility: true, collectDetailedErrors: true, zodVersion: zodVersion, }); } static setOutputPath(outPath) { this.outputPath = outPath; } static setIsGenerateSelect(isGenerateSelect) { this.isGenerateSelect = isGenerateSelect; } static setIsGenerateInclude(isGenerateInclude) { this.isGenerateInclude = isGenerateInclude; } // Configuration setters static setGeneratorConfig(config) { this.generatorConfig = config; this.strictModeResolver = (0, strict_mode_resolver_1.createStrictModeResolver)(config); } static getGeneratorConfig() { return this.generatorConfig; } static getStrictModeResolver() { return this.strictModeResolver; } static escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /** * Check if a model should be generated based on configuration */ static isModelEnabled(modelName) { var _a; const config = this.getGeneratorConfig(); // If no configuration is available, generate all models (default behavior) if (!config) { return true; } // Check if model has specific configuration const modelConfig = (_a = config.models) === null || _a === void 0 ? void 0 : _a[modelName]; if (modelConfig) { // If model config exists, check if it's explicitly enabled/disabled return modelConfig.enabled !== false; } // In minimal mode, do NOT blanket-disable models. // Default to enabling models unless explicitly disabled via config.models. // This aligns minimal mode with full mode regarding model selection; other constraints // (limited operations and pared-down object schemas) are handled elsewhere. // Default: enable all models when no explicit model disabling is configured return true; } /** * Get list of enabled models for generation */ static getEnabledModels(allModels) { return allModels.filter((model) => this.isModelEnabled(model.name)); } /** * Log information about model filtering */ static logModelFiltering(allModels) { const config = this.getGeneratorConfig(); if (!config) return; const enabledModels = this.getEnabledModels(allModels); const disabledModels = allModels.filter((model) => !this.isModelEnabled(model.name)); if (disabledModels.length > 0) { logger_1.logger.debug(`🚫 Models excluded from generation: ${disabledModels.map((m) => m.name).join(', ')}`); } if (config.mode === 'minimal' && enabledModels.length < allModels.length) { logger_1.logger.debug(`⚡ Minimal mode: generating ${enabledModels.length}/${allModels.length} models`); } } /** * Check if a specific operation should be generated for a model */ static isOperationEnabled(modelName, operationName) { var _a, _b; const config = this.getGeneratorConfig(); // If no configuration is available, generate all operations (default behavior) if (!config) { logger_1.logger.debug(`🔍 Operation check: ${modelName}.${operationName} = true (no config)`); return true; } // Map operation names for backward compatibility const operationMapping = { findMany: ['findMany'], findUnique: ['findUnique'], findUniqueOrThrow: ['findUniqueOrThrow'], findFirst: ['findFirst'], findFirstOrThrow: ['findFirstOrThrow'], createOne: ['create', 'createOne'], createMany: ['create', 'createMany'], createManyAndReturn: ['createManyAndReturn'], updateOne: ['update', 'updateOne'], updateMany: ['update', 'updateMany'], updateManyAndReturn: ['updateManyAndReturn'], deleteOne: ['delete', 'deleteOne'], deleteMany: ['delete', 'deleteMany'], upsertOne: ['upsert', 'upsertOne'], aggregate: ['aggregate'], groupBy: ['groupBy'], count: ['count'], }; // Check global operation exclusions first if ((_a = config.globalExclusions) === null || _a === void 0 ? void 0 : _a.operations) { const mappedOperationNames = operationMapping[operationName] || [operationName]; const isExcluded = mappedOperationNames.some((opName) => { var _a, _b, _c; return (_c = (_b = (_a = config.globalExclusions) === null || _a === void 0 ? void 0 : _a.operations) === null || _b === void 0 ? void 0 : _b.includes(opName)) !== null && _c !== void 0 ? _c : false; }); if (isExcluded) { logger_1.logger.debug(`🔍 Operation check: ${modelName}.${operationName} = false (globally excluded: [${config.globalExclusions.operations.join(', ')}])`); return false; } } // Check if model has specific configuration const modelConfig = (_b = config.models) === null || _b === void 0 ? void 0 : _b[modelName]; if (modelConfig && modelConfig.operations) { const allowedOperationNames = operationMapping[operationName] || [operationName]; const isEnabled = allowedOperationNames.some((opName) => { var _a, _b; return (_b = (_a = modelConfig.operations) === null || _a === void 0 ? void 0 : _a.includes(opName)) !== null && _b !== void 0 ? _b : false; }); logger_1.logger.debug(`🔍 Operation check: ${modelName}.${operationName} = ${isEnabled} (configured ops: [${modelConfig.operations.join(', ')}])`); return isEnabled; } // For minimal mode, only enable essential operations (no *Many, no upsert, no aggregate/groupBy) if (config.mode === 'minimal') { // Allow overrides via config.minimalOperations when present const configured = config.minimalOperations; const baseAllowed = configured && Array.isArray(configured) && configured.length > 0 ? configured : ['findMany', 'findUnique', 'findFirst', 'create', 'update', 'delete']; // Map legacy op names used in model mappings to these canonical names const opAliasMap = { createOne: 'create', updateOne: 'update', deleteOne: 'delete', upsertOne: 'upsert', }; const canonical = opAliasMap[operationName] || operationName; const isEnabled = baseAllowed.includes(canonical); logger_1.logger.debug(`🔍 Operation check: ${modelName}.${operationName} -> ${canonical} = ${isEnabled} (minimal mode)`); return isEnabled; } // Provider capability gating for AndReturn operations (not supported on all connectors) if (operationName === 'createManyAndReturn' || operationName === 'updateManyAndReturn') { const supportsAndReturn = ['postgresql', 'cockroachdb'].includes(this.provider); if (!supportsAndReturn) { logger_1.logger.debug(`🔍 Operation check: ${modelName}.${operationName} = false (provider=${this.provider} lacks AndReturn support)`); return false; } } // Default: enable all operations not explicitly disabled logger_1.logger.debug(`🔍 Operation check: ${modelName}.${operationName} = true (default)`); return true; } /** * Get list of enabled operations for a model */ static getEnabledOperations(modelName, allOperations) { return allOperations.filter((operation) => this.isOperationEnabled(modelName, operation)); } /** * Log information about operation filtering for a model */ static logOperationFiltering(modelName, allOperations) { const config = this.getGeneratorConfig(); if (!config) return; const enabledOperations = this.getEnabledOperations(modelName, allOperations); const disabledOperations = allOperations.filter((op) => !this.isOperationEnabled(modelName, op)); if (disabledOperations.length > 0) { logger_1.logger.debug(` 🔧 ${modelName}: excluded operations [${disabledOperations.join(', ')}]`); } if (config.mode === 'minimal' && enabledOperations.length < allOperations.length) { logger_1.logger.debug(` ⚡ ${modelName}: minimal mode - ${enabledOperations.length}/${allOperations.length} operations`); } } /** * Check if a field should be included in schema generation */ static isFieldEnabled(fieldName, modelName, variant) { var _a, _b, _c, _d, _e, _f, _g, _h; const config = this.getGeneratorConfig(); // If no configuration is available, include all fields (default behavior) if (!config) { return true; } // Start with a flag indicating whether the field should be excluded let shouldExclude = false; const debugReasons = []; // Check global exclusions (basic format - array of strings) if (config.globalExclusions && Array.isArray(config.globalExclusions)) { if (this.isFieldMatchingPatterns(fieldName, config.globalExclusions)) { shouldExclude = true; debugReasons.push(`global array exclusion`); } } // Check global exclusions for the specified variant if (variant && ((_a = config.globalExclusions) === null || _a === void 0 ? void 0 : _a[variant])) { if (this.isFieldMatchingPatterns(fieldName, config.globalExclusions[variant])) { shouldExclude = true; debugReasons.push(`global ${variant} exclusion`); } } // Check model-specific field exclusions and includes if (modelName) { const modelConfig = (_b = config.models) === null || _b === void 0 ? void 0 : _b[modelName]; // Check model-specific includes FIRST - these OVERRIDE exclusions (highest precedence) const legacyFields = modelConfig; if ((_c = legacyFields === null || legacyFields === void 0 ? void 0 : legacyFields.fields) === null || _c === void 0 ? void 0 : _c.include) { if (this.isFieldMatchingPatterns(fieldName, legacyFields.fields.include)) { logger_1.logger.debug(`🟢 Field enabled: ${modelName}.${fieldName} (${variant}) = true (model include override)`); return true; // Include overrides any previous or subsequent exclusion } } // Check variant-specific exclusions (new format) if ((_e = (_d = modelConfig === null || modelConfig === void 0 ? void 0 : modelConfig.variants) === null || _d === void 0 ? void 0 : _d[variant || 'pure']) === null || _e === void 0 ? void 0 : _e.excludeFields) { const ex = ((_g = (_f = modelConfig.variants) === null || _f === void 0 ? void 0 : _f[variant || 'pure']) === null || _g === void 0 ? void 0 : _g.excludeFields) || []; if (this.isFieldMatchingPatterns(fieldName, ex)) { shouldExclude = true; debugReasons.push(`model ${variant} variant exclusion`); } } // Check legacy format: fields.exclude (for backward compatibility) if ((_h = legacyFields === null || legacyFields === void 0 ? void 0 : legacyFields.fields) === null || _h === void 0 ? void 0 : _h.exclude) { logger_1.logger.debug(`🔍 Checking legacy fields.exclude for ${modelName}.${fieldName}: patterns =`, legacyFields.fields.exclude); if (this.isFieldMatchingPatterns(fieldName, legacyFields.fields.exclude)) { shouldExclude = true; debugReasons.push(`model fields.exclude`); logger_1.logger.debug(`🚫 Field excluded by legacy fields.exclude: ${modelName}.${fieldName}`); } } } const result = !shouldExclude; if (debugReasons.length > 0 || (modelName && ['password', 'views', 'internalId', 'metadata'].includes(fieldName))) { logger_1.logger.debug(`🔍 Field check: ${modelName || 'unknown'}.${fieldName} (${variant || 'unknown'}) = ${result} ${debugReasons.length > 0 ? `(${debugReasons.join(', ')})` : '(allowed)'}`); } // Return the opposite of shouldExclude (if should exclude, return false) return result; } /** * Check if a field name matches any pattern in the exclusion list * Supports wildcards: *field, field*, *field* */ static isFieldMatchingPatterns(fieldName, patterns) { // Debug: Reduced verbosity for performance (uncomment for detailed debugging) // console.log(`🔍 Pattern matching: fieldName='${fieldName}', patterns=`, patterns); const result = patterns.some((pattern) => { // Exact match (no wildcards) if (!pattern.includes('*')) { const match = fieldName === pattern; // console.log(` - Exact match '${pattern}': ${match}`); return match; } // Convert pattern to regex const regexPattern = pattern .replace(/\./g, '\\.') // Escape dots FIRST .replace(/\*/g, '.*'); // Then replace * with .* const regex = new RegExp(`^${regexPattern}$`); const match = regex.test(fieldName); // console.log(` - Regex match '${pattern}' -> /${regexPattern}/: ${match}`); return match; }); // console.log(`🔍 Pattern matching result: ${result}`); return result; } /** * Filter fields based on configuration and relationship preservation */ static filterFields(fields, modelName, variant, models, schemaName) { const config = (this.getGeneratorConfig() || {}); const strictCreateInputs = config.strictCreateInputs !== false; // default true const preserveRequiredScalarsOnCreate = config.preserveRequiredScalarsOnCreate !== false; // default true // Special case: WhereUniqueInput must retain unique identifier fields (e.g., id, unique columns) // Do NOT apply variant-based exclusions to this schema type const isWhereUniqueInput = !!schemaName && /WhereUniqueInput$/.test(schemaName); // Do not apply variant-based field exclusions to base Prisma Create input object schemas when strictCreateInputs is true // These must strictly match Prisma types to satisfy typed Zod bindings const isBasePrismaCreateInputName = !!schemaName && [ /^(\w+)CreateInput$/, /^(\w+)UncheckedCreateInput$/, /^(\w+)CreateManyInput$/, /^(\w+)CreateMany\w+Input$/, /^(\w+)CreateWithout\w+Input$/, /^(\w+)UncheckedCreateWithout\w+Input$/, /^(\w+)CreateOrConnectWithout\w+Input$/, /^(\w+)CreateNested(?:One|Many)Without\w+Input$/, ].some((re) => re.test(schemaName)); const isBasePrismaCreateInput = isBasePrismaCreateInputName && strictCreateInputs; const filtered = fields.filter((field) => { // Check basic field inclusion rules first if (!isWhereUniqueInput && !isBasePrismaCreateInput && !this.isFieldEnabled(field.name, modelName, variant)) { return false; } // In minimal mode, suppress relation and nested object fields in input variants // BUT preserve them for base Prisma create inputs to maintain type compatibility if (config.mode === 'minimal' && variant === 'input' && !isBasePrismaCreateInput) { if (this.isRelationFieldArg(field)) { return false; } } // For relation fields, also check if the related model is enabled // This is a safety check for schema args that might represent relation fields if (this.isRelationFieldArg(field)) { const relatedModelName = this.extractRelatedModelFromFieldArg(field); if (relatedModelName && !this.isModelEnabled(relatedModelName)) { return false; } } return true; }); // Handle foreign key preservation when relation fields are excluded const result = [...filtered]; if (modelName && variant === 'input' && !isBasePrismaCreateInput && models) { const model = models.find((m) => m.name === modelName); if (model) { const excludedRelationFields = this.getExcludedRelationFields(fields, filtered, model); const foreignKeyFields = this.getForeignKeyFieldsForExcludedRelations(excludedRelationFields, model); // Add foreign key fields that aren't already present AND aren't explicitly excluded for (const fkField of foreignKeyFields) { if (!result.some((field) => field.name === fkField.name)) { // Check if the foreign key field is explicitly excluded by configuration const isFieldAllowed = this.isFieldEnabled(fkField.name, modelName, variant); logger_1.logger.debug(`🔑 Foreign key preservation check: ${modelName}.${fkField.name} (${variant}) = ${isFieldAllowed}`); if (isFieldAllowed) { logger_1.logger.debug(`🔑 Adding foreign key field: ${modelName}.${fkField.name}`); result.push(fkField); } else { logger_1.logger.debug(`🚫 Skipping excluded foreign key field: ${modelName}.${fkField.name}`); } } } } } // If strictCreateInputs is false and this is a Create-like input, optionally re-add required non-auto scalars if (!strictCreateInputs && isBasePrismaCreateInputName && preserveRequiredScalarsOnCreate && modelName && models) { const model = models.find((m) => m.name === modelName); if (model) { const requiredScalars = model.fields.filter((f) => f.kind === 'scalar' && f.isRequired === true && f.hasDefaultValue !== true && f.isUpdatedAt !== true); for (const f of requiredScalars) { if (!result.some((arg) => arg.name === f.name)) { // Try to find the corresponding SchemaArg in original fields by name const original = fields.find((arg) => arg.name === f.name); if (original) result.push(original); } } } } return result; } /** * Get excluded relation fields by comparing original and filtered fields */ static getExcludedRelationFields(originalFields, filteredFields, model) { const excludedFieldNames = originalFields .filter((originalField) => !filteredFields.some((filteredField) => filteredField.name === originalField.name)) .map((field) => field.name); return model.fields.filter((field) => excludedFieldNames.includes(field.name) && field.kind === 'object' && field.relationFromFields && field.relationFromFields.length > 0); } /** * Get foreign key schema args for excluded relation fields */ static getForeignKeyFieldsForExcludedRelations(excludedRelationFields, model) { const foreignKeyFields = []; for (const relationField of excludedRelationFields) { if (!relationField.relationFromFields) continue; for (const fkFieldName of relationField.relationFromFields) { const fkField = model.fields.find((f) => f.name === fkFieldName); if (fkField && fkField.kind === 'scalar') { const inputTypes = [ { type: fkField.type, location: 'scalar', isList: fkField.isList, }, ]; const schemaArg = { name: fkField.name, isRequired: fkField.isRequired, isNullable: !fkField.isRequired, inputTypes, }; foreignKeyFields.push(schemaArg); } } } return foreignKeyFields; } /** * Check if a schema arg represents a relation field */ static isRelationFieldArg(field) { // Heuristic: relation-shaped nested inputs and relation filters // Detect a wide set of Prisma relation input object patterns const relationTypePatterns = [ // Connect targets for nested relations /\w+WhereUniqueInput$/, // Nested relation operations /\w+CreateNested(?:One|Many)Without\w+Input$/, /\w+UpdateNested(?:One|Many)Without\w+Input$/, /\w+UpsertNested(?:One|Many)Without\w+Input$/, // Without-variants used in nested create/update /\w+CreateOrConnectWithout\w+Input$/, /\w+CreateWithout\w+Input$/, /\w+UncheckedCreateWithout\w+Input$/, /\w+UpdateWithout\w+Input$/, /\w+UncheckedUpdateWithout\w+Input$/, /\w+UpdateToOneWithWhereWithout\w+Input$/, /\w+UpdateOneRequiredWithout\w+NestedInput$/, /\w+UpdateWithWhereUniqueWithout\w+Input$/, /\w+UpdateManyWithWhereWithout\w+Input$/, /\w+UpsertWithWhereUniqueWithout\w+Input$/, // Relation filter helpers /\w+ListRelationFilter$/, /\w+RelationFilter$/, /\w+ScalarRelationFilter$/, // Order by relation inputs /OrderByRelation/, ]; return field.inputTypes.some((inputType) => { if (inputType.location !== 'inputObjectTypes') return false; const typeName = String(inputType.type); return relationTypePatterns.some((re) => re.test(typeName)); }); } /** * Extract related model name from field arg */ static extractRelatedModelFromFieldArg(field) { for (const inputType of field.inputTypes) { if (inputType.location === 'inputObjectTypes') { const typeName = inputType.type; // Handle specific relation filter patterns first if (typeName.includes('ListRelationFilter')) { // Extract from "PostListRelationFilter" -> "Post" const match = typeName.match(/^(\w+)ListRelationFilter$/); if (match) return match[1]; } // Extract model name from other types like "PostWhereInput", "UserCreateInput", etc. const match = typeName.match(/^(\w+)(?:Where|Create|Update|OrderBy|RelationFilter|CreateNested|UpdateNested)/); if (match) { return match[1]; } } } return null; } /** * Extract model name from object schema context */ static extractModelNameFromContext(schemaName) { // Try to extract model name from common schema naming patterns // More specific patterns should come first to avoid false matches const patterns = [ // Basic input types - more specific patterns first /^(\w+)UncheckedCreateInput$/, /^(\w+)UncheckedUpdateInput$/, /^(\w+)UncheckedUpdateManyInput$/, /^(\w+)UpdateManyMutationInput$/, /^(\w+)WhereUniqueInput$/, /^(\w+)CreateManyInput$/, /^(\w+)UpdateManyInput$/, /^(\w+)WhereInput$/, /^(\w+)CreateInput$/, /^(\w+)UpdateInput$/, // Order by inputs /^(\w+)OrderByWithRelationInput$/, /^(\w+)OrderByWithAggregationInput$/, /^(\w+)OrderByRelationAggregateInput$/, // Filter inputs /^(\w+)ScalarWhereInput$/, /^(\w+)ScalarWhereWithAggregatesInput$/, /^(\w+)ListRelationFilter$/, /^(\w+)RelationFilter$/, /^(\w+)ScalarRelationFilter$/, // Aggregate inputs /^(\w+)CountAggregateInput$/, /^(\w+)CountOrderByAggregateInput$/, /^(\w+)AvgAggregateInput$/, /^(\w+)AvgOrderByAggregateInput$/, /^(\w+)MaxAggregateInput$/, /^(\w+)MaxOrderByAggregateInput$/, /^(\w+)MinAggregateInput$/, /^(\w+)MinOrderByAggregateInput$/, /^(\w+)SumAggregateInput$/, /^(\w+)SumOrderByAggregateInput$/, // Nested operation inputs /^(\w+)CreateNestedOneWithout\w+Input$/, /^(\w+)CreateNestedManyWithout\w+Input$/, /^(\w+)UpdateNestedOneWithout\w+Input$/, /^(\w+)UpdateNestedManyWithout\w+Input$/, /^(\w+)UpsertNestedOneWithout\w+Input$/, /^(\w+)UpsertNestedManyWithout\w+Input$/, /^(\w+)CreateOrConnectWithout\w+Input$/, /^(\w+)UpdateOneRequiredWithout\w+NestedInput$/, /^(\w+)UpdateToOneWithWhereWithout\w+Input$/, /^(\w+)UpsertWithout\w+Input$/, /^(\w+)CreateWithout\w+Input$/, /^(\w+)UpdateWithout\w+Input$/, /^(\w+)UncheckedCreateWithout\w+Input$/, /^(\w+)UncheckedUpdateWithout\w+Input$/, /^(\w+)UncheckedCreateNestedManyWithout\w+Input$/, /^(\w+)UncheckedUpdateManyWithout\w+Input$/, /^(\w+)UncheckedUpdateManyWithout\w+NestedInput$/, // Many-to-many relation inputs /^(\w+)CreateManyAuthorInput$/, /^(\w+)CreateManyAuthorInputEnvelope$/, /^(\w+)UpdateManyWithWhereWithout\w+Input$/, /^(\w+)UpdateWithWhereUniqueWithout\w+Input$/, /^(\w+)UpsertWithWhereUniqueWithout\w+Input$/, /^(\w+)UpdateManyWithout\w+NestedInput$/, // Additional specific patterns found in generated schemas /^(\w+)UncheckedUpdateManyInput$/, // Args and other schemas /^(\w+)Args$/, // Pure model schemas (variant schemas) /^(\w+)ModelSchema$/, // Generic CreateMany relationship patterns (placed at end to avoid interference) // Capture the owning model prefix (e.g. AccountCreateManyUserInputEnvelope -> Account) /^(\w+)CreateMany\w+InputEnvelope$/, /^(\w+)CreateMany\w+Input$/, ]; for (const pattern of patterns) { const match = schemaName.match(pattern); if (match) { return match[1]; } } return null; } /** * Determine schema variant from context */ static determineSchemaVariant(schemaName) { // Determine variant based on schema name patterns if (schemaName.includes('Create') || schemaName.includes('Update') || schemaName.includes('Where')) { return 'input'; } if (schemaName.includes('Result') || schemaName.includes('Output')) { return 'result'; } return 'pure'; // Default variant } /** * Log information about field filtering */ static logFieldFiltering(originalCount, filteredCount, modelName, variant) { if (originalCount !== filteredCount) { const context = modelName ? `${modelName}${variant ? ` (${variant})` : ''}` : 'schema'; logger_1.logger.debug(` 🎯 ${context}: filtered ${originalCount - filteredCount} fields (${filteredCount}/${originalCount} remaining)`); } } /** * Check if a model has relationships to other ENABLED models * This is a filtered version of checkModelHasModelRelation that only considers enabled models */ static checkModelHasEnabledModelRelation(model) { const { fields: modelFields } = model; for (const modelField of modelFields) { if (this.isEnabledRelationField(modelField)) { return true; } } return false; } /** * Check if a relation field points to an enabled model */ static isEnabledRelationField(modelField) { const { kind, relationName, type } = modelField; // Must be an object relation field if (kind !== 'object' || !relationName) { return false; } // Check if the related model is enabled const relatedModelName = type; return this.isModelEnabled(relatedModelName); } /** * Filter relation fields to only include those pointing to enabled models */ static filterRelationFields(fields) { return fields.filter((field) => { // Include non-relation fields if (field.kind !== 'object' || !field.relationName) { return true; } // Only include relation fields if the related model is enabled return this.isEnabledRelationField(field); }); } /** * Get list of enabled related models for a given model */ static getEnabledRelatedModels(model) { const relatedModels = new Set(); for (const field of model.fields) { if (this.isEnabledRelationField(field)) { relatedModels.add(field.type); } } return Array.from(relatedModels); } /** * Check if a model should have include schemas generated * Only if it has relationships to other enabled models */ static shouldGenerateIncludeSchema(model) { const config = this.getGeneratorConfig(); if ((config === null || config === void 0 ? void 0 : config.mode) === 'minimal') return false; return Transformer.isGenerateInclude && this.checkModelHasEnabledModelRelation(model); } /** * Check if a model should have select schemas generated */ static shouldGenerateSelectSchema(_model) { const config = this.getGeneratorConfig(); if ((config === null || config === void 0 ? void 0 : config.mode) === 'minimal') return false; return Transformer.isGenerateSelect; } /** * Log relationship preservation information */ static logRelationshipPreservation(model) { const config = this.getGeneratorConfig(); if (!config) return; const enabledRelatedModels = this.getEnabledRelatedModels(model); const totalRelationFields = model.fields.filter((f) => f.kind === 'object' && f.relationName).length; const enabledRelationFields = model.fields.filter((f) => this.isEnabledRelationField(f)).length; if (totalRelationFields > enabledRelationFields) { const disabledRelations = totalRelationFields - enabledRelationFields; logger_1.logger.debug(` 🔗 ${model.name}: ${disabledRelations} disabled relation(s) to filtered models`); } if (enabledRelatedModels.length > 0) { logger_1.logger.debug(` ✅ ${model.name}: active relations to [${enabledRelatedModels.join(', ')}]`); } } // Dual export setters static setExportTypedSchemas(exportTypedSchemas) { this.exportTypedSchemas = exportTypedSchemas; } static setExportZodSchemas(exportZodSchemas) { this.exportZodSchemas = exportZodSchemas; } static setTypedSchemaSuffix(typedSchemaSuffix) { this.typedSchemaSuffix = typedSchemaSuffix; } static setZodSchemaSuffix(zodSchemaSuffix) { this.zodSchemaSuffix = zodSchemaSuffix; } /** * Generate the correct object schema name based on export settings */ static getObjectSchemaName(baseName) { return this.exportTypedSchemas ? `${baseName}ObjectSchema` : `${baseName}Object${this.zodSchemaSuffix}`; } static getOutputPath() { return this.outputPath; } static setCurrentManifest(manifest) { this.currentManifest = manifest; } static getCurrentManifest() { return this.currentManifest; } static setPrismaClientOutputPath(prismaClientCustomPath) { this.prismaClientOutputPath = prismaClientCustomPath; this.isCustomPrismaClientOutputPath = prismaClientCustomPath !== '@prisma/client'; } /** * Resolve the import specifier for Prisma Client relative to a target directory. * Falls back to '@prisma/client' when user did not configure a custom output. */ static resolvePrismaImportPath(targetDir) { if (!this.isCustomPrismaClientOutputPath) return '@prisma/client'; try { let prismaClientImportTarget = this.prismaClientOutputPath; // For the new Prisma generator (provider = 'prisma-client'), the public entrypoint // lives in a generated `client.*` file inside the configured output directory. // The Prisma output config represents the directory, so we explicitly target the file. if (this.prismaClientProvider === 'prisma-client') { const hasFileExtension = path_1.default.extname(prismaClientImportTarget) !== ''; if (!hasFileExtension) { prismaClientImportTarget = path_1.default.join(prismaClientImportTarget, 'client'); } } // Compute relative path from the file's directory to the custom client output path let rel = path_1.default.relative(targetDir, prismaClientImportTarget).replace(/\\/g, '/'); if (!rel || rel === '') return '@prisma/client'; // Add file extension if needed (for ESM with importFileExtension) const extension = this.getImportFileExtension(); if (!extension || !/\.[a-z]+$/i.test(rel)) { rel = `${rel}${extension}`; } // Ensure it is a valid relative module specifier (prefix with ./ when needed) if (rel.startsWith('.') || rel.startsWith('/')) return rel; return `./${rel}`; } catch { return '@prisma/client'; } } static setPrismaClientProvider(provider) { this.prismaClientProvider = provider; } static setPrismaClientConfig(config) { this.prismaClientConfig = config; } static getPrismaClientProvider() { return this.prismaClientProvider; } static getPrismaClientConfig() { return this.prismaClientConfig; } /** * Determines the schemas directory path based on the output path. * If the output path already ends with 'schemas', use it directly. * Otherwise, append 'schemas' to the output path. */ static getSchemasPath() { const normalizedOutputPath = path_1.default.normalize(this.outputPath); const pathSegments = normalizedOutputPath.split(path_1.default.sep); const lastSegment = pathSegments[pathSegments.length - 1]; if (lastSegment === 'schemas') { return this.outputPath; } return path_1.default.join(this.outputPath, 'schemas'); } static async generateIndex() { const indexPath = path_1.default.join(Transformer.getSchemasPath(), 'index.ts'); const importExtension = Transformer.getImportFileExtension(); await (0, writeIndexFile_1.writeIndexFile)(indexPath, importExtension); } async generateEnumSchemas() { for (const enumType of this.enumTypes) { const { name, values } = enumType; // Filter out enum schemas for disabled models if (this.isEnumSchemaEnabled(name)) { // Use enum naming configuration const enumNamingConfig = (0, naming_resolver_1.resolveEnumNaming)(Transformer.getGeneratorConfig()); // Normalize enum name for consistent file naming and exports const normalizedEnumName = this.normalizeEnumName(name); const enumName = normalizedEnumName || name; const fileName = (0, naming_resolver_1.generateFileName)(enumNamingConfig.filePattern, enumName, // Use enum name as model name for naming pattern undefined, // No operation undefined, // No input type enumName); const schemaExportName = (0, naming_resolver_1.generateExportName)(enumNamingConfig.exportNamePattern, enumName, // Use enum name as model name for naming pattern undefined, // No operation undefined, // No input type enumName); await (0, writeFileSafely_1.writeFileSafely)(path_1.default.join(Transformer.getSchemasPath(), `enums/${fileName}`), `${this.generateImportZodStatement()}\n` + `export const ${schemaExportName} = z.enum([${values .map((v) => `'${v}'`) .join(', ')}])\n\n` + `export type ${enumName} = z.infer<typeof ${schemaExportName}>;`); } } } /** * Check if an enum schema should be generated based on enabled models */ isEnumSchemaEnabled(enumName) { // Always generate user-defined enums and Prisma built-in enums // Only filter model-specific generated enums based on model configuration // Check if this is a model-specific enum (like PostScalarFieldEnum) const modelName = this.extractModelNameFromEnum(enumName); if (modelName) { // This is a model-specific enum, check if the model is enabled return Transformer.isModelEnabled(modelName); } // For all other enums (user-defined enums like Status, Role, and Prisma built-ins), // always generate them regardless of model configuration return true; } /** * Extract model name from enum name */ extractModelNameFromEnum(enumName) { for (const pattern of Transformer.modelEnumPatterns) { const match = enumName.match(pattern); if (match) { return match[1]; } } return null; } /** * Normalize enum names to match import expectations * For model-related enums, use Pascal case model names consistently */ normalizeEnumName(enumName) { // Use shared patterns to detect and normalize model-specific enums for (const pattern of Transformer.modelEnumPatterns) { const match = enumName.match(pattern); if (match) { const modelName = match[1]; const pascalModelName = this.getPascalCaseModelName(modelName); // Rebuild enum name with normalized model name return enumName.replace(modelName, pascalModelName); } } // Return null for non-model-related enums to use original name return null; } /** * Convert model names to DMMF aggregate input names * For doc_parser_agent model: matches Prisma's DMMF format Doc_parser_agentCountAggregateInput */ getAggregateInputName(modelName, inputSuffix) { // Prisma's DMMF uses original model name with first letter capitalized for aggregate inputs // doc_parser_agent -> Doc_parser_agentCountAggregateInput (not DocParserAgentCountAggregateInput) const capitalizedModelName = modelName.charAt(0).toUpperCase() + modelName.slice(1); return `${capitalizedModelName}${inputSuffix}`; } /** * Get the correct Prisma type name for model types * Prisma uses the original model name as-is (e.g., doc_parser_agent -> Prisma.doc_parser_agentSelect) */ getPrismaTypeName(modelName, operationType) { // Special case: Aggregate operations use capitalized first letter for snake_case models if (operationType === 'Aggregate' && modelName.includes('_')) { return modelName.charAt(0).toUpperCase() + modelName.slice(1); } // All other operations use the original model name without any case transformation return modelName; } /** * Check if a model has numeric fields that support avg/sum operations */ modelHasNumericFields(modelName) { const dmmfModel = this.models.find((m) => m.name === modelName); if (!dmmfModel) return false; return dmmfModel.fields.some((field) => ['Int', 'Float', 'Decimal', 'BigInt'].includes(field.type)); } generateImportZodStatement() { var _a; // Determine import target based on configuration const config = Transformer.getGeneratorConfig(); const target = ((_a = config === null || config === void 0 ? void 0 : config.zodImportTarget) !== null && _a !== void 0 ? _a : 'auto'); switch (target) { case 'v4': return "import * as z from 'zod/v4';\n"; case 'v3': return "import { z } from 'zod/v3';\n"; case 'auto': default: return "import * as z from 'zod';\n"; } } /** * Generate custom import statements from @zod.import() annotations * * @param customImports - Array of custom import objects * @param outputPath - Current output path for relative import adjustment * @returns Generated import statements string */ generateCustomImportStatements(customImports, outputPath = '') { if (!customImports || customImports.length === 0) { return ''; } const importStatements = []; const seenImports = new Set(); for (const customImport of customImports) { if (!customImport || !customImport.importStatement) { continue; } let importStatement = customImport.importStatement; // Adjust relative paths for multi-file output if (outputPath && customImport.source.startsWith('.')) { importStatement = this.adjustRelativeImportPath(importStatement, outputPath); } // Avoid duplicate imports if (!seenImports.has(importStatement)) { seenImports.add(importStatement); importStatements.push(importStatement); } } if (importStatements.length === 0) { return ''; } // Return imports with proper formatting and newline return importStatements.join(';\n') + ';\n'; } /** * Get custom imports for a specific schema context * This method is called AFTER the schema content is generated to determine which imports are actually needed * * @param modelName - Name of the model * @param schemaContent - The generated schema content to analyze for import usage * @returns Array of custom imports needed for this specific schema */ getCustomImportsForModel(modelName, schemaContent) { if (!modelName) return []; // Find the enhanced model data const enhancedModel = this.enhancedModels.find((em) => em.model.name === modelName); if (!enhancedModel) return []; const usedImports = []; const usedImportSources = new Set(); // If schema content is provided, analyze it for actual usage if (schemaContent) { // Collect all potential imports from field-level and model-level const allPotentialImports = []; // Add field-level imports for (const enhancedField of enhancedModel.enhancedFields) { if (enhancedField.customImports) { allPotentialImports.push(...enhancedField.customImports); } } // Add model-level imports if (enhancedModel.modelCustomImports) { allPotentialImports.push(...enhancedModel.modelCustomImports); } // Check which imports are actually used in the schema content for (const potentialImport of allPotentialImports) { if (potentialImport.importedItems) { // Check if any imported function/item is used in the schema content const isUsed = potentialImport.importedItems.some((item) => { if (!item) { return false; } const escaped = Transformer.escapeRegExp(item); const re = new RegExp(`\\b${escaped}\\b`); return re.test(schemaContent); }); if (isUsed && !usedImportSources.has(potentialImport.importStatement)) { usedImports.push(potentialImport); usedImportSources.add(potentialImport.importStatement); } } } return usedImports; } // Fallback to the old logic if no schema content is provided // Check which fields in this schema actually use custom validation for (const field of this.fields) { const enhancedField = enhancedModel.enhancedFields.find((ef) => ef.field.name === field.name); // Only add imports if the field has custom imports AND the field string contains custom validation logic if (enhancedField && enhancedField.customImports && enhancedField.customImports.length > 0) { // Check if this field actually contains custom validation by looking for function calls const hasCustomValidation = this.fieldContainsCustomValidation(field, enhancedField); if (hasCustomValidation) { for (const customImport of enhancedField.customImports) { // Deduplicate imports by source if (!usedImportSources.has(customImport.importStatement)) { usedImports.push(customImport); usedImportSources.add(customImport.importStatement); } } } } } // Only add model-level imports if this is a schema type that should include model-level validation // Model-level validation (like .refine()) should only be in result/output schemas, not input aggregate schemas const isResultSchema = this.name.includes('Result') ||