UNPKG

prisma-zod-generator

Version:

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

443 lines 18.7 kB
"use strict"; /** * Extended generator options for Prisma Zod Generator * * This module provides parsing and validation for both existing and new * generator options while maintaining backward compatibility. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GeneratorOptionError = void 0; exports.parseGeneratorOptions = parseGeneratorOptions; exports.validateGeneratorOptions = validateGeneratorOptions; exports.createDefaultGeneratorOptions = createDefaultGeneratorOptions; exports.mergeGeneratorOptions = mergeGeneratorOptions; exports.generatorOptionsToConfigOverrides = generatorOptionsToConfigOverrides; exports.formatGeneratorOptions = formatGeneratorOptions; exports.isLegacyUsage = isLegacyUsage; exports.getLegacyMigrationSuggestions = getLegacyMigrationSuggestions; exports.detectOptionConflicts = detectOptionConflicts; exports.getEffectiveConfigurationSummary = getEffectiveConfigurationSummary; const logger_1 = require("../utils/logger"); /** * Parse and validate generator options from Prisma config */ function parseGeneratorOptions(generatorConfig = {}) { const options = { raw: { ...generatorConfig }, }; // Parse config file path if (generatorConfig.config !== undefined) { options.config = parseConfigPath(generatorConfig.config); } // Parse minimal mode flag if (generatorConfig.minimal !== undefined) { options.minimal = parseBoolean(generatorConfig.minimal, 'minimal'); } // Parse variants list if (generatorConfig.variants) { options.variants = parseVariantsList(generatorConfig.variants); } // Parse file output mode if (generatorConfig.useMultipleFiles !== undefined) { options.useMultipleFiles = parseBoolean(generatorConfig.useMultipleFiles, 'useMultipleFiles'); } if (generatorConfig.singleFileName !== undefined) { options.singleFileName = generatorConfig.singleFileName; } if (generatorConfig.placeSingleFileAtRoot !== undefined) { options.placeSingleFileAtRoot = parseBoolean(generatorConfig.placeSingleFileAtRoot, 'placeSingleFileAtRoot'); } if (generatorConfig.pureModelsLean !== undefined) { options.pureModelsLean = parseBoolean(generatorConfig.pureModelsLean, 'pureModelsLean'); } if (generatorConfig.pureModelsIncludeRelations !== undefined) { options.pureModelsIncludeRelations = parseBoolean(generatorConfig.pureModelsIncludeRelations, 'pureModelsIncludeRelations'); } if (generatorConfig.pureModelsExcludeCircularRelations !== undefined) { options.pureModelsExcludeCircularRelations = parseBoolean(generatorConfig.pureModelsExcludeCircularRelations, 'pureModelsExcludeCircularRelations'); } if (generatorConfig.dateTimeStrategy !== undefined) { const v = generatorConfig.dateTimeStrategy; if (!['date', 'coerce', 'isoString'].includes(v)) { throw new GeneratorOptionError('dateTimeStrategy', v, 'Must be one of "date", "coerce", "isoString"'); } options.dateTimeStrategy = v; } if (generatorConfig.dateTimeSplitStrategy !== undefined) { options.dateTimeSplitStrategy = parseBoolean(generatorConfig.dateTimeSplitStrategy, 'dateTimeSplitStrategy'); } if (generatorConfig.jsonSchemaCompatible !== undefined) { options.jsonSchemaCompatible = parseBoolean(generatorConfig.jsonSchemaCompatible, 'jsonSchemaCompatible'); } if (generatorConfig.jsonSchemaOptions !== undefined) { options.jsonSchemaOptions = parseJsonObjectOption(generatorConfig.jsonSchemaOptions, 'jsonSchemaOptions'); } if (generatorConfig.optionalFieldBehavior !== undefined) { const v = generatorConfig.optionalFieldBehavior; if (!['optional', 'nullable', 'nullish'].includes(v)) { throw new GeneratorOptionError('optionalFieldBehavior', v, 'Must be one of "optional", "nullable", "nullish"'); } options.optionalFieldBehavior = v; } // Parse existing options for backward compatibility if (generatorConfig.isGenerateSelect !== undefined) { options.isGenerateSelect = parseBoolean(generatorConfig.isGenerateSelect, 'isGenerateSelect'); } if (generatorConfig.isGenerateInclude !== undefined) { options.isGenerateInclude = parseBoolean(generatorConfig.isGenerateInclude, 'isGenerateInclude'); } return options; } /** * Parse config file path option */ function parseConfigPath(configValue) { if (!configValue || typeof configValue !== 'string') { logger_1.logger.info('❌ [prisma-zod-generator] Invalid configuration:\n' + ` config = "${configValue}"\n` + ' Error: Config path must be a non-empty string.\n' + ' Solution: Remove the config attribute entirely to use defaults, or provide a valid path.'); throw new GeneratorOptionError('config', configValue, 'Config path must be a non-empty string'); } const trimmed = configValue.trim(); if (trimmed.length === 0) { logger_1.logger.info('❌ [prisma-zod-generator] Invalid configuration:\n' + ' config = ""\n' + ' Error: Config path cannot be empty or whitespace only.\n' + ' Solution: Remove the config attribute entirely to use defaults, or provide a valid path.'); throw new GeneratorOptionError('config', configValue, 'Config path cannot be empty or whitespace only. If you want to disable config file loading, remove the config attribute entirely.'); } return trimmed; } /** * Parse boolean option value */ function parseBoolean(value, optionName) { if (typeof value !== 'string') { throw new GeneratorOptionError(optionName, value, `${optionName} must be a string ("true" or "false")`); } const lowercased = value.toLowerCase().trim(); if (lowercased === 'true') { return true; } else if (lowercased === 'false') { return false; } else { throw new GeneratorOptionError(optionName, value, `${optionName} must be "true" or "false", got "${value}"`); } } /** * Parse JSON object option */ function parseJsonObjectOption(value, optionName) { if (typeof value !== 'string' || value.trim() === '') { throw new GeneratorOptionError(optionName, value, `${optionName} must be a JSON string`); } try { const obj = JSON.parse(value); if (obj && typeof obj === 'object' && !Array.isArray(obj)) return obj; } catch { // fall through } throw new GeneratorOptionError(optionName, value, `${optionName} must be a valid JSON object string`); } /** * Parse variants list option */ function parseVariantsList(variantsValue) { if (!variantsValue || typeof variantsValue !== 'string') { throw new GeneratorOptionError('variants', variantsValue, 'Variants must be a comma-separated string'); } const variants = variantsValue .split(',') .map((v) => v.trim()) .filter((v) => v.length > 0); if (variants.length === 0) { throw new GeneratorOptionError('variants', variantsValue, 'Variants list cannot be empty'); } // Validate each variant const validVariants = ['pure', 'input', 'result']; const invalidVariants = variants.filter((v) => !validVariants.includes(v)); if (invalidVariants.length > 0) { throw new GeneratorOptionError('variants', variantsValue, `Invalid variants: ${invalidVariants.join(', ')}. Valid variants are: ${validVariants.join(', ')}`); } // Remove duplicates return Array.from(new Set(variants)); } /** * Validate generator options compatibility */ function validateGeneratorOptions(options) { // Check for conflicting options if (options.minimal && options.variants) { // In minimal mode, only certain variants make sense const minimalCompatibleVariants = ['pure', 'input']; const incompatibleVariants = options.variants.filter((v) => !minimalCompatibleVariants.includes(v)); if (incompatibleVariants.length > 0) { const msg = `[prisma-zod-generator] ⚠️ In minimal mode, variants ${incompatibleVariants.join(', ')} may not be useful. Consider using only: ${minimalCompatibleVariants.join(', ')}`; logger_1.logger.debug(msg); } } // Validate config path if provided if (options.config) { validateConfigPath(options.config); } } /** * Validate config file path */ function validateConfigPath(configPath) { // Basic path validation if (configPath.includes('..') && !configPath.startsWith('./')) { const msg = `[prisma-zod-generator] ⚠️ Config path "${configPath}" contains ".." - ensure this is intentional and secure`; logger_1.logger.debug(msg); } // Check for common config file extensions const validExtensions = ['.json', '.js', '.ts']; const hasValidExtension = validExtensions.some((ext) => configPath.endsWith(ext)); if (!hasValidExtension) { const msg = `[prisma-zod-generator] ⚠️ Config path "${configPath}" doesn't have a recognized extension. Expected: ${validExtensions.join(', ')}`; logger_1.logger.debug(msg); } } /** * Create default generator options */ function createDefaultGeneratorOptions() { return { minimal: false, isGenerateSelect: false, isGenerateInclude: false, useMultipleFiles: true, singleFileName: 'schemas.ts', placeSingleFileAtRoot: true, raw: {}, }; } /** * Merge generator options with defaults */ function mergeGeneratorOptions(options, defaults = createDefaultGeneratorOptions()) { var _a, _b, _c; return { config: options.config || defaults.config, minimal: (_a = options.minimal) !== null && _a !== void 0 ? _a : defaults.minimal, variants: options.variants || defaults.variants, isGenerateSelect: (_b = options.isGenerateSelect) !== null && _b !== void 0 ? _b : defaults.isGenerateSelect, isGenerateInclude: (_c = options.isGenerateInclude) !== null && _c !== void 0 ? _c : defaults.isGenerateInclude, raw: { ...defaults.raw, ...options.raw }, }; } /** * Convert generator options to configuration overrides * * This creates a partial configuration that can be merged with * the loaded configuration file to implement option precedence. */ function generatorOptionsToConfigOverrides(options) { const overrides = {}; // Apply minimal mode override if (options.minimal !== undefined) { overrides.mode = options.minimal ? 'minimal' : undefined; if (options.minimal) { // Force-disable select/include when minimal flag is set in generator options overrides.isGenerateSelect = false; overrides.isGenerateInclude = false; } } // Apply variants override if (options.variants && options.variants.length > 0) { overrides.variants = {}; // Enable only specified variants const allVariants = ['pure', 'input', 'result']; allVariants.forEach((variant) => { var _a; if (overrides.variants) { overrides.variants[variant] = { enabled: ((_a = options.variants) === null || _a === void 0 ? void 0 : _a.includes(variant)) || false, }; } }); } // Apply file output mode overrides if (options.useMultipleFiles !== undefined) { overrides.useMultipleFiles = options.useMultipleFiles; } if (options.singleFileName) { overrides.singleFileName = options.singleFileName; } if (options.placeSingleFileAtRoot !== undefined) { overrides.placeSingleFileAtRoot = options.placeSingleFileAtRoot; } if (options.pureModelsLean !== undefined) { overrides.pureModelsLean = options.pureModelsLean; } if (options.pureModelsIncludeRelations !== undefined) { overrides.pureModelsIncludeRelations = options.pureModelsIncludeRelations; } if (options.pureModelsExcludeCircularRelations !== undefined) { overrides.pureModelsExcludeCircularRelations = options.pureModelsExcludeCircularRelations; } if (options.dateTimeStrategy) { overrides.dateTimeStrategy = options.dateTimeStrategy; } if (options.dateTimeSplitStrategy !== undefined) { overrides.dateTimeSplitStrategy = options.dateTimeSplitStrategy; } if (options.jsonSchemaCompatible !== undefined) { overrides.jsonSchemaCompatible = options.jsonSchemaCompatible; } if (options.jsonSchemaOptions) { overrides.jsonSchemaOptions = options.jsonSchemaOptions; } if (options.optionalFieldBehavior) { overrides.optionalFieldBehavior = options.optionalFieldBehavior; } return overrides; } /** * Generator option parsing error */ class GeneratorOptionError extends Error { constructor(optionName, optionValue, message) { super(`Invalid generator option "${optionName}": ${message}`); this.optionName = optionName; this.optionValue = optionValue; this.name = 'GeneratorOptionError'; } getUserFriendlyMessage() { let message = `Invalid generator option: ${this.optionName}\n`; message += `Value: ${JSON.stringify(this.optionValue)}\n`; message += `Error: ${this.message}\n\n`; // Add option-specific help switch (this.optionName) { case 'config': message += `Expected: Path to configuration file (e.g., "./zod-config.json")\n`; message += `Example:\n`; message += `generator zod {\n`; message += ` provider = "prisma-zod-generator"\n`; message += ` config = "./zod-generator.config.json"\n`; message += `}`; break; case 'minimal': message += `Expected: "true" or "false"\n`; message += `Example:\n`; message += `generator zod {\n`; message += ` provider = "prisma-zod-generator"\n`; message += ` minimal = "true"\n`; message += `}`; break; case 'variants': message += `Expected: Comma-separated list of variants: "pure", "input", "result"\n`; message += `Example:\n`; message += `generator zod {\n`; message += ` provider = "prisma-zod-generator"\n`; message += ` variants = "pure,input"\n`; message += `}`; break; default: message += `Check the documentation for valid values for "${this.optionName}"`; break; } return message; } } exports.GeneratorOptionError = GeneratorOptionError; /** * Format generator options for display/debugging */ function formatGeneratorOptions(options) { const lines = ['Generator Options:']; if (options.config) { lines.push(` config: "${options.config}"`); } if (options.minimal !== undefined) { lines.push(` minimal: ${options.minimal}`); } if (options.variants) { lines.push(` variants: [${options.variants.join(', ')}]`); } if (options.useMultipleFiles !== undefined) { lines.push(` useMultipleFiles: ${options.useMultipleFiles}`); } if (options.singleFileName !== undefined) { lines.push(` singleFileName: ${options.singleFileName}`); } if (options.isGenerateSelect !== undefined) { lines.push(` isGenerateSelect: ${options.isGenerateSelect}`); } if (options.isGenerateInclude !== undefined) { lines.push(` isGenerateInclude: ${options.isGenerateInclude}`); } const rawCount = Object.keys(options.raw).length; if (rawCount > 0) { lines.push(` raw options: ${rawCount} total`); } return lines.join('\n'); } /** * Check if generator options indicate legacy usage */ function isLegacyUsage(options) { // No new options and has legacy options or no options at all return (!options.config && !options.minimal && !options.variants && (options.isGenerateSelect !== undefined || options.isGenerateInclude !== undefined || Object.keys(options.raw).length <= 2)); // Only legacy options or empty } /** * Get migration suggestions for legacy usage */ function getLegacyMigrationSuggestions(options) { const suggestions = []; if (options.isGenerateSelect || options.isGenerateInclude) { suggestions.push('Consider migrating to configuration file for better organization:\n' + ' 1. Create zod-generator.config.json\n' + ' 2. Move isGenerateSelect/isGenerateInclude to config file\n' + ' 3. Add config = "./zod-generator.config.json" to generator block'); } return suggestions; } /** * Check if there are conflicting options between generator options and expected config file */ function detectOptionConflicts(generatorOptions, configFileHasOptions) { const conflicts = []; if (configFileHasOptions && generatorOptions.minimal !== undefined) { conflicts.push('Both config file and generator "minimal" option are specified. ' + 'Generator option will take precedence.'); } if (configFileHasOptions && generatorOptions.variants !== undefined) { conflicts.push('Both config file and generator "variants" option are specified. ' + 'Generator option will take precedence.'); } return conflicts; } /** * Get effective configuration summary showing which options are active and their source */ function getEffectiveConfigurationSummary(generatorOptions, hasConfigFile) { const lines = ['Effective Configuration:']; if (generatorOptions.config) { lines.push(` Config file: ${generatorOptions.config} ${hasConfigFile ? '✅' : '❌'}`); } if (generatorOptions.minimal !== undefined) { lines.push(` Minimal mode: ${generatorOptions.minimal} (from generator options)`); } if (generatorOptions.variants) { lines.push(` Variants: [${generatorOptions.variants.join(', ')}] (from generator options)`); } if (generatorOptions.isGenerateSelect !== undefined) { lines.push(` Generate Select: ${generatorOptions.isGenerateSelect} (legacy)`); } if (generatorOptions.isGenerateInclude !== undefined) { lines.push(` Generate Include: ${generatorOptions.isGenerateInclude} (legacy)`); } return lines.join('\n'); } //# sourceMappingURL=generator-options.js.map