prisma-zod-generator
Version:
Prisma 2+ generator to emit Zod schemas from your Prisma schema
216 lines • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolvePureModelNaming = resolvePureModelNaming;
exports.applyPattern = applyPattern;
exports.applyUniversalPattern = applyUniversalPattern;
exports.resolveEnumNaming = resolveEnumNaming;
exports.resolveInputNaming = resolveInputNaming;
exports.resolveSchemaNaming = resolveSchemaNaming;
exports.generateFileName = generateFileName;
exports.generateExportName = generateExportName;
exports.parseExportSymbol = parseExportSymbol;
const PRESET_MAP = {
'zod-prisma': {
filePattern: '{Model}.schema.ts',
schemaSuffix: 'Schema',
typeSuffix: 'Type',
exportNamePattern: '{Model}Schema',
legacyAliases: true,
preset: 'zod-prisma',
},
'zod-prisma-types': {
filePattern: '{Model}.schema.ts',
schemaSuffix: '',
typeSuffix: '',
exportNamePattern: '{Model}',
legacyAliases: true,
preset: 'zod-prisma-types',
},
'legacy-model-suffix': {
filePattern: '{Model}.model.ts',
schemaSuffix: 'Model',
typeSuffix: 'ModelType',
exportNamePattern: '{Model}Model',
legacyAliases: false,
preset: 'legacy-model-suffix',
},
};
function safeGetNaming(config) {
return config && typeof config === 'object' ? config.naming : undefined;
}
function resolvePureModelNaming(config) {
const naming = safeGetNaming(config);
const presetName = naming === null || naming === void 0 ? void 0 : naming.preset;
const lookupKey = presetName ? presetName.trim().toLowerCase() : presetName;
let base = lookupKey && PRESET_MAP[lookupKey]
? { ...PRESET_MAP[lookupKey] }
: {
filePattern: '{Model}.schema.ts',
schemaSuffix: 'Schema',
typeSuffix: 'Type',
exportNamePattern: '{Model}{SchemaSuffix}',
legacyAliases: false,
preset: 'default',
};
// Defensive: explicitly handle legacy-model-suffix in case map lookup fails due to unexpected input normalization
if (lookupKey === 'legacy-model-suffix' && (!base || base.preset !== 'legacy-model-suffix')) {
base = { ...PRESET_MAP['legacy-model-suffix'] };
}
const overrides = (naming === null || naming === void 0 ? void 0 : naming.pureModel) || {};
const resolved = {
...base,
...overrides,
filePattern: overrides.filePattern || base.filePattern,
schemaSuffix: overrides.schemaSuffix === '' ? '' : overrides.schemaSuffix || base.schemaSuffix,
typeSuffix: overrides.typeSuffix === '' ? '' : overrides.typeSuffix || base.typeSuffix,
exportNamePattern: overrides.exportNamePattern || base.exportNamePattern,
legacyAliases: overrides.legacyAliases !== undefined ? !!overrides.legacyAliases : base.legacyAliases,
};
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports -- logger loaded lazily to avoid circular deps in tests
const { logger } = require('./logger');
logger.debug(`[namingResolver] preset=${presetName || 'none'} filePattern=${resolved.filePattern}`);
}
catch { }
return resolved;
}
function applyPattern(pattern, modelName, schemaSuffix, typeSuffix) {
const tokens = {
'{Model}': modelName,
'{model}': modelName.charAt(0).toLowerCase() + modelName.slice(1),
'{camel}': modelName.charAt(0).toLowerCase() + modelName.slice(1),
'{kebab}': modelName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(),
'{SchemaSuffix}': schemaSuffix,
'{TypeSuffix}': typeSuffix,
};
return applyUniversalPattern(pattern, tokens);
}
// Enhanced pattern application for all types
function applyUniversalPattern(pattern, tokens) {
return Object.keys(tokens).reduce((acc, token) => acc.replace(new RegExp(token.replace(/[{}]/g, '\\$&'), 'g'), tokens[token]), pattern);
}
function resolveEnumNaming(config) {
const naming = safeGetNaming(config);
const enumConfig = naming === null || naming === void 0 ? void 0 : naming.enum;
return {
filePattern: (enumConfig === null || enumConfig === void 0 ? void 0 : enumConfig.filePattern) || '{Enum}.schema.ts',
exportNamePattern: (enumConfig === null || enumConfig === void 0 ? void 0 : enumConfig.exportNamePattern) || '{Enum}Schema',
};
}
function resolveInputNaming(config) {
const naming = safeGetNaming(config);
const inputConfig = naming === null || naming === void 0 ? void 0 : naming.input;
return {
filePattern: (inputConfig === null || inputConfig === void 0 ? void 0 : inputConfig.filePattern) || '{InputType}.schema.ts',
exportNamePattern: (inputConfig === null || inputConfig === void 0 ? void 0 : inputConfig.exportNamePattern) || '{Model}{InputType}ObjectSchema',
};
}
function resolveSchemaNaming(config) {
const naming = safeGetNaming(config);
const schemaConfig = naming === null || naming === void 0 ? void 0 : naming.schema;
return {
filePattern: (schemaConfig === null || schemaConfig === void 0 ? void 0 : schemaConfig.filePattern) || '{operation}{Model}.schema.ts',
exportNamePattern: (schemaConfig === null || schemaConfig === void 0 ? void 0 : schemaConfig.exportNamePattern) || '{Model}{Operation}Schema',
};
}
// Unified pattern application function
function generateFileName(pattern, modelName, operation, inputType, enumName) {
// If pattern contains a model token ({Model} or {model}) and {InputType}, and inputType starts with modelName,
// strip the model name from inputType to avoid duplicate prefixes in filenames
let processedInputType = inputType || '';
if (pattern.includes('{InputType}') &&
(pattern.includes('{Model}') || pattern.includes('{model}')) &&
inputType &&
modelName) {
if (inputType.startsWith(modelName)) {
processedInputType = inputType.substring(modelName.length);
}
}
const tokens = {
'{Model}': modelName,
'{model}': modelName.charAt(0).toLowerCase() + modelName.slice(1),
'{camel}': modelName.charAt(0).toLowerCase() + modelName.slice(1),
'{kebab}': modelName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(),
// Normalize operation tokens to mirror generateExportName semantics
'{Operation}': operation ? operation.charAt(0).toUpperCase() + operation.slice(1) : '',
'{operation}': operation ? operation.charAt(0).toLowerCase() + operation.slice(1) : '',
'{InputType}': processedInputType,
'{Enum}': enumName || '',
'{enum}': enumName ? enumName.charAt(0).toLowerCase() + enumName.slice(1) : '',
};
return applyUniversalPattern(pattern, tokens);
}
function generateExportName(pattern, modelName, operation, inputType, enumName) {
// If pattern contains a model token ({Model} or {model}) and {InputType}, and inputType starts with modelName,
// strip the model name from inputType to avoid duplication
let processedInputType = inputType || '';
if (pattern.includes('{InputType}') &&
(pattern.includes('{Model}') || pattern.includes('{model}')) &&
inputType &&
modelName) {
if (inputType.startsWith(modelName)) {
processedInputType = inputType.substring(modelName.length);
}
}
const tokens = {
'{Model}': modelName,
'{model}': modelName.charAt(0).toLowerCase() + modelName.slice(1),
'{kebab}': modelName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(),
'{Operation}': operation ? operation.charAt(0).toUpperCase() + operation.slice(1) : '',
'{operation}': operation ? operation.charAt(0).toLowerCase() + operation.slice(1) : '',
'{InputType}': processedInputType,
'{Enum}': enumName || '',
'{enum}': enumName ? enumName.charAt(0).toLowerCase() + enumName.slice(1) : '',
};
return applyUniversalPattern(pattern, tokens);
}
/**
* Reverse-derive a PascalCase model name from an export symbol and pattern.
*
* Supports patterns containing one of {Model}, {model}, {camel}, or {kebab} and
* accounts for dynamic suffix tokens {SchemaSuffix}/{TypeSuffix} when computing
* the static prefix/suffix surrounding the model token. Falls back to stripping
* configured suffixes when a direct match is not possible.
*/
function parseExportSymbol(symbol, pattern, schemaSuffix = '', typeSuffix = '') {
const replaceSuffixTokens = (segment) => segment.replace(/\{SchemaSuffix\}/g, schemaSuffix).replace(/\{TypeSuffix\}/g, typeSuffix);
const tryExtract = (token) => {
if (!pattern.includes(token))
return null;
const [preRaw, postRaw] = pattern.split(token);
const pre = replaceSuffixTokens(preRaw || '');
const post = replaceSuffixTokens(postRaw || '');
if (symbol.startsWith(pre) && symbol.endsWith(post)) {
const core = symbol.substring(pre.length, symbol.length - post.length);
switch (token) {
case '{Model}':
return core; // Already PascalCase
case '{model}':
case '{camel}':
return core ? core.charAt(0).toUpperCase() + core.slice(1) : core;
case '{kebab}':
return core
.split('-')
.map((seg) => (seg ? seg[0].toUpperCase() + seg.slice(1) : ''))
.join('');
}
}
return null;
};
// Prefer explicit {Model} first, then lowercase/camel, then kebab
const viaModel = tryExtract('{Model}') ||
tryExtract('{model}') ||
tryExtract('{camel}') ||
tryExtract('{kebab}');
if (viaModel !== null)
return viaModel;
// Heuristics: if the symbol ends with known suffixes, strip them
if (schemaSuffix && symbol.endsWith(schemaSuffix)) {
return symbol.slice(0, -schemaSuffix.length);
}
if (typeSuffix && symbol.endsWith(typeSuffix)) {
return symbol.slice(0, -typeSuffix.length);
}
return symbol;
}
//# sourceMappingURL=naming-resolver.js.map