prisma-zod-generator
Version:
Prisma 2+ generator to emit Zod schemas from your Prisma schema
1,030 lines (1,029 loc) • 218 kB
JavaScript
"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') ||