import type { ConnectorType, DMMF as PrismaDMMF } from '@prisma/generator-helper'; import type { GeneratorConfig as ZodGeneratorConfig } from './config/parser'; import { type ResolvedTypedJsonConfig } from './config/typed-json'; import { type EnhancedModelInfo } from './helpers/zod-integration'; import type { CustomImport } from './parsers/zod-comments'; import { SchemaEnumWithValues, TransformerParams } from './types'; import { type TypedFieldUpdateOperationsPlan } from './generators/typed-field-update-operations'; import type { GeneratedManifest } from './utils/safeOutputManagement'; import { type StrictModeResolver } from './utils/strict-mode-resolver'; /** * Filter validation result interface */ interface FilterValidationResult { isValid: boolean; errors: string[]; warnings: string[]; suggestions: string[]; } export default class Transformer { name: string; fields: PrismaDMMF.SchemaArg[]; schemaImports: Set; models: PrismaDMMF.Model[]; modelOperations: PrismaDMMF.ModelMapping[]; enumTypes: SchemaEnumWithValues[]; enhancedModels: EnhancedModelInfo[]; static enumNames: string[]; static rawOpsMap: { [name: string]: string; }; static provider: ConnectorType; static previewFeatures: string[] | undefined; private static outputPath; private hasJson; private hasDecimal; private static prismaClientOutputPath; private static isCustomPrismaClientOutputPath; private static prismaClientProvider; private static prismaClientEntryBasenameCache; private static prismaClientConfig; private static isGenerateSelect; private static isGenerateInclude; private static generatorConfig; private static strictModeResolver; private static usedSchemaFilePaths; private static isJsonSchemaModeEnabled; private static getJsonSchemaOptions; private static exportTypedSchemas; private static exportZodSchemas; private static typedSchemaSuffix; private static zodSchemaSuffix; private lastExcludedFieldNames; private static jsonHelpersWritten; private static decimalHelpersWritten; private static currentManifest; constructor(params: TransformerParams); static setOutputPath(outPath: string): void; static setIsGenerateSelect(isGenerateSelect: boolean): void; static setIsGenerateInclude(isGenerateInclude: boolean): void; static setGeneratorConfig(config: ZodGeneratorConfig): void; static getGeneratorConfig(): ZodGeneratorConfig | null; /** * The `typedJson` block with its defaults filled in, or `null` when the user did not * configure it. * * `null` is load-bearing: it is what keeps output byte-identical to 3.0.0 for every * configuration that omits the key. Read it, never reconstruct it, so there is one * place that decides whether the feature is on. */ static getTypedJsonConfig(): ResolvedTypedJsonConfig | null; /** * The per-field copies of Prisma's shared update-operations inputs, planned once per * generate. Empty unless `typedJson` is configured and some column is annotated. */ private static typedFieldUpdateOperationsPlan; static setTypedFieldUpdateOperationsPlan(plan: TypedFieldUpdateOperationsPlan): void; static getStrictModeResolver(): StrictModeResolver | null; private static escapeRegExp; /** * Check if a model should be generated based on configuration */ static isModelEnabled(modelName: string): boolean; /** * Get list of enabled models for generation */ static getEnabledModels(allModels: PrismaDMMF.Model[]): PrismaDMMF.Model[]; /** * Log information about model filtering */ static logModelFiltering(allModels: PrismaDMMF.Model[]): void; /** * Check if a specific operation should be generated for a model */ static isOperationEnabled(modelName: string, operationName: string): boolean; /** * Get list of enabled operations for a model */ static getEnabledOperations(modelName: string, allOperations: string[]): string[]; /** * Log information about operation filtering for a model */ static logOperationFiltering(modelName: string, allOperations: string[]): void; /** * Check if a field should be included in schema generation */ static isFieldEnabled(fieldName: string, modelName?: string, variant?: 'pure' | 'input' | 'result'): boolean; /** * Check if a field name matches any pattern in the exclusion list * Supports wildcards: *field, field*, *field* */ static isFieldMatchingPatterns(fieldName: string, patterns: string[]): boolean; /** * Filter fields based on configuration and relationship preservation */ static filterFields(fields: PrismaDMMF.SchemaArg[], modelName?: string, variant?: 'pure' | 'input' | 'result', models?: PrismaDMMF.Model[], schemaName?: string): PrismaDMMF.SchemaArg[]; /** * Get excluded relation fields by comparing original and filtered fields */ static getExcludedRelationFields(originalFields: PrismaDMMF.SchemaArg[], filteredFields: PrismaDMMF.SchemaArg[], model: PrismaDMMF.Model): PrismaDMMF.Field[]; /** * Get foreign key schema args for excluded relation fields */ static getForeignKeyFieldsForExcludedRelations(excludedRelationFields: PrismaDMMF.Field[], model: PrismaDMMF.Model): PrismaDMMF.SchemaArg[]; /** * Check if a schema arg represents a relation field */ static isRelationFieldArg(field: PrismaDMMF.SchemaArg): boolean; /** * Extract related model name from field arg */ static extractRelatedModelFromFieldArg(field: PrismaDMMF.SchemaArg): string | null; /** * Extract model name from object schema context */ static extractModelNameFromContext(schemaName: string): string | null; /** * Determine schema variant from context */ static determineSchemaVariant(schemaName: string): 'pure' | 'input' | 'result'; /** * Log information about field filtering */ static logFieldFiltering(originalCount: number, filteredCount: number, modelName?: string, variant?: string): void; /** * 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: PrismaDMMF.Model): boolean; /** * Check if a relation field points to an enabled model */ static isEnabledRelationField(modelField: PrismaDMMF.Field): boolean; /** * Get list of enabled related models for a given model */ static getEnabledRelatedModels(model: PrismaDMMF.Model): string[]; /** * Check if a model should have include schemas generated * Only if it has relationships to other enabled models */ static shouldGenerateIncludeSchema(model: PrismaDMMF.Model): boolean; /** * Check if a model should have select schemas generated */ static shouldGenerateSelectSchema(_model: PrismaDMMF.Model): boolean; /** * Log relationship preservation information */ static logRelationshipPreservation(model: PrismaDMMF.Model): void; static setExportTypedSchemas(exportTypedSchemas: boolean): void; static setExportZodSchemas(exportZodSchemas: boolean): void; static setTypedSchemaSuffix(typedSchemaSuffix: string): void; static setZodSchemaSuffix(zodSchemaSuffix: string): void; /** * Generate the correct object schema name based on export settings */ static getObjectSchemaName(baseName: string): string; static getOutputPath(): string; static setCurrentManifest(manifest: GeneratedManifest | null): void; static getCurrentManifest(): GeneratedManifest | null; static setPrismaClientOutputPath(prismaClientCustomPath: string): void; /** * 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: string): string; /** * The `import type { Prisma }` line for CRUD operation schema files, or '' when * typed exports are disabled — the typed exports are the only consumers of the * Prisma namespace in those files, so the import would otherwise be unused. */ static prismaTypeImportLine(prismaImportPath: string): string; /** * Determine which generated prisma-client entrypoint to import. * Prefers the browser-safe `browser.*` entry so generated schemas can be bundled * for client-side use. Falls back to the server `client.*` entry only when an * older prisma-client output layout without a browser entry is positively * detected on disk. When the client output has not been generated yet, assume * the modern layout (browser entry present). */ static resolvePrismaClientEntryBasename(): 'browser' | 'client'; static setPrismaClientProvider(provider: string): void; static setPrismaClientConfig(config: Record): void; static getPrismaClientProvider(): string; static getPrismaClientConfig(): Record; /** * 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(): string; static generateIndex(): Promise; generateEnumSchemas(): Promise; /** * Check if an enum schema should be generated based on enabled models */ private isEnumSchemaEnabled; /** * Shared regex patterns for model-specific enum detection */ private static readonly modelEnumPatterns; /** * Extract model name from enum name */ private extractModelNameFromEnum; /** * Normalize enum names to match import expectations * For model-related enums, use Pascal case model names consistently */ private normalizeEnumName; /** * Convert model names to DMMF aggregate input names * For doc_parser_agent model: matches Prisma's DMMF format Doc_parser_agentCountAggregateInput */ private getAggregateInputName; /** * 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) */ private getPrismaTypeName; /** * Check if a model has numeric fields that support avg/sum operations */ private modelHasNumericFields; /** * The module `z` is imported from, honouring `zodImportTarget` and `zodImportPath`. * * Extracted so the typed-JSON namespace file can import `z` from the same place the * schemas do. Recomputing it there would make the module path two sources of truth, * which is precisely the class of drift this feature exists to remove. */ static resolveZodImportSpecifier(): string; generateImportZodStatement(): string; /** * 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: CustomImport[] | undefined, outputPath?: string): string; /** * 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: string | null, schemaContent?: string): CustomImport[]; /** * Check if a field actually contains custom validation logic */ private fieldContainsCustomValidation; /** * Get custom schema for a specific field * * @param fieldName - Name of the field * @returns Custom schema string or null if not found */ getCustomSchemaForField(fieldName: string): string | null; /** * Directory the file being emitted sits in, relative to the generator output directory. * * This is what a relative `typedJson.schemaModule` is rewritten against, so `./json-types` * becomes `../json-types` from `objects/`. */ static typedJsonOutputSubdir(subdir: string): string; /** * The model this schema's members take their typed-JSON annotations from. * * `resolveTypedJsonOwnerModel` repairs the one shape the general extraction reads wrong - * `UncheckedCreateWithoutInput` and its update twin, which it answers as * a model called `Unchecked`. The repair lives here, on the typed-JSON path only, * rather than in `extractModelNameFromContext`, because that function also drives file * naming and field filtering: a changed answer there would move bytes for repos that have * never configured `typedJson`. */ private typedJsonOwnerModel; /** The DMMF model field an input-object member refers to, when there is one. */ private getModelFieldForArg; /** Memoised `listWrapperTarget()`. `undefined` means "not computed", `null` means "not one". */ private listWrapperTargetCache?; /** * The model list field this schema is the `{ set }` / `{ push }` wrapper for, if it is one. * * Prisma emits one of these per list column, named `Input`, * and its members are literally `set` and `push`. So the model cannot be read off the * schema name by `extractModelNameFromContext` - and must not be taught to it, because * that same function drives file naming and field filtering. It is resolved here instead, * against the models actually in the schema. * * The longest matching model name wins, so `Foo` and `FooBar` both existing resolves * `FooBarUpdatetagsInput` to `FooBar.tags` regardless of DMMF order. */ private listWrapperTarget; /** * The model field an input-object member should take its annotation from, if any. * * Normally that is the field of the same name. A list-operation wrapper is the exception: * `set` and `push` are operations on the wrapped column, so the annotation belongs to * that column. Looking a wrapper's members up by name would find nothing, or - once a * model has a column actually *called* `set` - find an unrelated one and stamp its * annotation onto every wrapper in the model. */ private typedJsonTargetForArg; /** * Resolve a PJTG annotation for one input-object member, or `null` to leave it alone. * * Returning `null` is the default in every uncertain case, and `null` means "emit exactly * what 3.0.0 emitted". Nothing here runs at all when `typedJson` is unconfigured. */ private typedJsonForField; /** * Imports the typed-JSON expressions in this file need, merged to one per module and * filtered to the names the file actually mentions. */ private getTypedJsonImportsForSchema; /** * Adjust relative import paths based on output directory structure * * @param importStatement - Original import statement * @param outputPath - Current output path * @returns Adjusted import statement */ private adjustRelativeImportPath; generateObjectSchema(): Promise; generateObjectSchemaFields(): string[]; /** * The replacement text to compose into a property line, given where it is about to land. * * A typed replacement is the user's schema and the emitter does not rewrite it, with one * exception that is not a rewrite so much as a division of labour: a marker at the TOP * LEVEL of the replacement answers a question the emitter has already answered from the * DMMF, and only one of the two answers can be right. * * Every object schema is cast to the Prisma input type for the same operation, so * whether a key may be omitted and whether it admits `null` are facts about the column, * not opinions the annotation gets to hold. `![string | undefined]` on a required column * would make a required key omittable; `![{ a: string } | null]` on a required `Json` * column would accept a bare `null` where `Prisma.CreateInput` says * `JsonNullValueInput | InputJsonValue` and means `Prisma.JsonNull`. Both are payloads * the type the schema is cast to rejects. So a trailing run of markers comes off here and * the emitter's own optional/nullable policy re-applies whatever the column actually * says - which is also why an annotation that agrees with the column no longer emits its * marker twice. * * Nothing else in the replacement is touched. `{ a: string; b?: number }` keeps `b` * optional, and so does an element inside a `z.array(...)` the annotation wrote itself: * those markers are nowhere near the top level and describe the user's own type. * * The `isList` case is the reason this takes an `inputType` rather than working on the * string alone. On a list field the replacement is the ELEMENT schema and * `wrapWithZodValidators` is about to append `.array()` to it, so its trailing marker * ends up under that wrapper describing an element rather than the field. Prisma's * `InputJsonArray` is `ReadonlyArray`, so a null element is a * value it really does accept, and the condition below is the same one the wrapper * itself uses to decide whether to append. */ private static typedJsonSlotExpression; /** * Run an optionality strip over a composed property line without letting it reach * inside a typed-JSON replacement. * * The strips exist for the OUTER expression and are load-bearing there: every * alternative arrives from `wrapWithZodValidators` already carrying `.optional()`, a * union adds its own tail, and the optional/nullable policy below re-applies exactly * one marker. Left unstripped that composes two markers where the policy allows one. * * Inside a typed replacement is not the emitter's to strip, though. That is the user's * schema, and `{ a: string; b?: number }` means `b` is optional in every artifact that * reads the annotation - the pure model and the result schema both say so. Masking each * replacement to an opaque token keeps the outer strip doing its job while what it * covers passes through byte for byte. * * What it covers is `typedJsonSlotExpression`'s answer, which is the replacement with * the top-level markers the emitter owns already removed. So the two halves compose * rather than fight: the emitter decides the outermost marker, the annotation decides * every marker under it, and neither reaches into the other. * * Nothing is ever collected without a `typedJson` block, so for everyone else this is * exactly `strip(expression)`. */ private static withTypedJsonMasked; generateObjectSchemaField(field: PrismaDMMF.SchemaArg): [string, PrismaDMMF.SchemaArg, boolean][]; wrapWithZodValidators(mainValidator: string, field: PrismaDMMF.SchemaArg, inputType: PrismaDMMF.SchemaArg['inputTypes'][0], skipZodAnnotations?: boolean, /** * Whether a native `@db.VarChar(n)` length may still be appended to the base. * * False only for a typed replacement that is not rooted in `z.string()`. The native-max * block rewrites the leading `z.…(…)` call, so `!['A' | 'B']` on a `String @db.VarChar(8)` * would emit `z.enum(['A','B']).max(8)` - a module that throws `TypeError` on import. */ allowNativeMaxLength?: boolean): string; addSchemaImport(name: string): void; /** * Check if a schema import should be included based on minimal mode configuration */ private isSchemaImportEnabled; /** * Extract model name from schema name for import filtering */ private extractModelNameFromSchema; /** * Extract max length constraint from native type (e.g., @db.VarChar(255) -> 255) */ private extractMaxLengthFromNativeType; /** * Extract existing max constraint from @zod validations */ private extractExistingMaxConstraint; /** * Replace all max constraints in a validation string with a single constraint */ private replaceAllMaxConstraints; /** * Extract @zod validations for a specific field using the enhanced model information */ /** * Whether this member carries a sort direction rather than the column's value. * * `OrderByWithRelationInput`, `OrderByWithAggregationInput` and the five * `OrderByAggregateInput` families all take `SortOrder` (plus `SortOrderInput` on a * nullable column, which adds `nulls`). A `@zod.email()` written for the column is a * statement about the column's value and means nothing here, so applying it rejects * `orderBy: { contact: 'asc' }` and accepts `orderBy: { contact: 'a@b.com' }`. * * Asked of the DMMF rather than of the schema name on purpose. The sibling guards above * match `Select` and `AggregateInput` by name, and the Select one shipped as a * substring test that silently dropped every annotation on a model named `SelectionRound`. * A name test here would do the same to `OrderByHistory`. An OrderBy member never offers * the scalar arm and a write or filter member always does, so the DMMF answers this * exactly, whatever the model is called. */ private static isSortDirectionMember; private extractZodValidationsForField; generatePrismaStringLine(field: PrismaDMMF.SchemaArg, inputType: PrismaDMMF.SchemaArg['inputTypes'][0], inputsLength: number): string; generateFieldValidators(zodStringWithMainType: string, field: PrismaDMMF.SchemaArg): string; prepareObjectSchema(zodObjectSchemaFields: string[]): string; generateExportObjectSchemaStatement(schema: string): string; /** * Emit a manual TS type and a `satisfies z.ZodType<...>` check for Zod-only, self-recursive schemas. * This avoids TS7022 and keeps output variable types unchanged. */ private generateZodOnlySanityCheck; /** * Whether generateExportObjectSchemaStatement will emit a `z.ZodType` * annotated export for this object schema. Mirrors its decision exactly so the * `import type { Prisma }` line is only emitted when it is actually referenced * (e.g. `Args` schemas are always exported untyped). */ private willGenerateTypedObjectExport; /** * Resolve the appropriate Prisma type identifier for an object schema export. * In Prisma 6, certain aggregate input types are exported with a `Type` suffix. * Example: Prisma.PlanetCountAggregateInputType instead of Prisma.PlanetCountAggregateInput */ private resolvePrismaTypeForObject; addFinalWrappers({ zodStringFields }: { zodStringFields: string[]; }): string; private static ensureJsonHelpersFile; private static ensureDecimalHelpersFile; generateObjectSchemaImportStatements(customImports?: CustomImport[]): string; /** * Get the file extension to use for imports based on Prisma client configuration * For ESM with importFileExtension = "js", we need to add .js extension */ private getImportFileExtension; /** * Convert model name to PascalCase for consistent file naming */ private getPascalCaseModelName; /** * Resolve an input object import honoring naming.input patterns, aliasing to stable identifiers. */ private generateInputImport; /** * Generate enum import honoring naming.enum patterns, aliasing to stable identifiers. */ private generateEnumImport; /** * Generate input import for object schemas honoring naming.input patterns. * Similar to generateInputImport but for object-to-object imports using './' prefix. */ private generateObjectInputImport; /** * Generate schema file name with collision detection and custom naming support */ private static generateSchemaFileName; /** * Check for and handle file path collisions */ private static checkSchemaFileCollision; /** * Generate schema file with collision detection and consistent naming */ private static writeSchemaFile; /** * Clear collision detection state (called at start of generation) */ static clearSchemaFileCollisions(): void; /** * Generate import statement with validation - only if target will be generated */ static generateValidatedImportStatement(importName: string, importPath: string, modelName?: string): string; /** * Check if an import should be generated based on filtering rules */ static shouldGenerateImport(importName: string, relatedModelName?: string): boolean; /** * Extract model name from import name */ static extractModelFromImportName(importName: string): string | null; /** * Get import file extension (static version) */ static getImportFileExtension(): string; generateSchemaImports(): string; checkIsModelQueryType(type: string): { isModelQueryType: boolean; modelName: string; queryName: string; } | { isModelQueryType: boolean; modelName?: undefined; queryName?: undefined; }; resolveModelQuerySchemaName(modelName: string, queryName: string): string; wrapWithZodObject(zodStringFields: string | string[], useStrictObject?: boolean): string; /** * Compose an operation schema object definition with strict-mode handling. * For Zod v4 targets, `.strict()` clones the schema def and spreads its shape, * invoking `get select/include` getters at module-init time and crashing on * circular imports. z.strictObject keeps identical strictness semantics while * leaving the shape lazy until first parse. */ private composeObjectDefinition; generateModelSchemas(): Promise; /** * Generate result schemas for all enabled models */ generateResultSchemas(): Promise; generateImportStatements(imports: (string | undefined)[]): string; /** * Clean and validate import array, removing empty and invalid imports */ static cleanAndValidateImports(imports: (string | undefined)[]): string[]; /** * Parse import statement to extract information */ static parseImportStatement(importStatement: string): { importName: string; importPath: string; relatedModel?: string; } | null; /** * Log import management information */ static logImportManagement(originalImports: (string | undefined)[], cleanedImports: string[], context?: string): void; /** * Validate filter combinations and detect conflicts */ static validateFilterCombinations(allModels: PrismaDMMF.Model[]): FilterValidationResult; /** * Validate model dependencies and relationships */ static validateModelDependencies(allModels: PrismaDMMF.Model[], result: FilterValidationResult): void; /** * Validate operation consistency across models */ static validateOperationConsistency(allModels: PrismaDMMF.Model[], result: FilterValidationResult): void; /** * Validate field exclusion conflicts */ static validateFieldExclusions(allModels: PrismaDMMF.Model[], result: FilterValidationResult): void; /** * Validate variant consistency */ static validateVariantConsistency(result: FilterValidationResult): void; /** * Validate mode consistency */ static validateModeConsistency(result: FilterValidationResult): void; /** * Log filter validation results */ static logFilterValidation(validationResult: FilterValidationResult): void; /** * Perform comprehensive filter validation */ static performFilterValidation(allModels: PrismaDMMF.Model[]): boolean; resolveSelectIncludeImportAndZodSchemaLine(model: PrismaDMMF.Model): { selectImport: string; includeImport: string; selectZodSchemaLine: string; includeZodSchemaLine: string; selectZodSchemaLineLazy: string; includeZodSchemaLineLazy: string; selectValueExpr: string; includeValueExpr: string; }; private resolveIncludeFieldReference; resolveOrderByWithRelationImportAndZodSchemaLine(model: PrismaDMMF.Model): { orderByImport: string; orderByZodSchemaLine: string; }; /** * Determines if a select schema should be inlined to avoid circular dependencies. * Following community generator approach: ALWAYS inline select schemas. * Use lazy loading only for specific relation fields within the inlined schema. */ shouldInlineSelectSchema(_model: PrismaDMMF.Model): boolean; /** * Resolves the nested-args schema a relation field should union with inside an * inline select schema, mirroring the gating used for objects/Select.schema.ts * (see select-helpers/include-helpers). Returns null when only booleans are allowed * (minimal mode, select+include both disabled, or the related model disabled). */ private resolveInlineSelectRelationTarget; /** * Resolves the CountOutputTypeArgs reference for the `_count` field of an * inline select schema, or null when `_count` must remain boolean-only. */ private resolveInlineSelectCountArgsReference; /** * Resolve the actual exported identifier of a model query schema file, matching the * naming used by generateDualSchemaExports (typed export when enabled, Zod export * otherwise, honoring naming.schema.exportNamePattern). */ private resolveModelQueryExportName; /** * Generate an import statement for a root-level model query schema file * (e.g. findManyPost.schema.ts), honoring naming.schema patterns. `from` * selects the relative prefix: './' for root-level CRUD files, '../' for * files inside objects/. */ private generateModelQueryImport; /** * Generates the additional imports needed for inlined select schemas. * Returns import statements for the model query / Args schemas referenced by * relation-field unions (mirroring objects/Select.schema.ts imports). */ generateInlineSelectImports(model: PrismaDMMF.Model, operation?: 'FindFirst' | 'FindFirstOrThrow' | 'FindMany'): string[]; /** * Generates dual schema exports: typed version and Zod-method-friendly version */ generateDualSchemaExports(modelName: string, operationType: string, schemaDefinition: string, prismaType: string): string; /** * Generates dual select schema exports for inlined schemas */ generateDualSelectSchemaExports(model: PrismaDMMF.Model, operation?: 'FindFirst' | 'FindFirstOrThrow' | 'FindMany' | 'FindUnique' | 'FindUniqueOrThrow'): string; /** * Generates just the schema definition without export statement */ generateInlineSelectSchemaDefinition(model: PrismaDMMF.Model): string; } export {};