prisma-zod-generator
Version:
Prisma 2+ generator to emit Zod schemas from your Prisma schema
465 lines • 19 kB
JavaScript
;
/**
* Variant Import/Export Management System
* Manages TypeScript imports and exports across variants including barrel exports and cross-variant references
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VariantImportExportManager = void 0;
const variants_1 = require("../types/variants");
const writeFileSafely_1 = require("../utils/writeFileSafely");
const formatFile_1 = require("../utils/formatFile");
const fs_1 = require("fs");
/**
* Variant Import/Export Manager
* Central manager for handling all import/export operations across variants
*/
class VariantImportExportManager {
constructor(moduleResolutionConfig) {
this.globalImports = new Set();
this.exportCache = new Map();
this.moduleResolutionConfig = {
baseUrl: './generated/schemas',
paths: {},
extensions: ['.ts', '.js'],
useRelativePaths: true,
pathMapping: {},
...moduleResolutionConfig,
};
}
/**
* Generate barrel export files for all variant types
*/
async generateBarrelExports(collections, configs, importExtension = '') {
const generatedFiles = [];
// Generate barrel export for each variant type
for (const [variantType, config] of Object.entries(configs)) {
const variant = variantType;
const barrelFile = await this.generateVariantBarrelExport(collections, variant, config, importExtension);
generatedFiles.push(barrelFile);
}
// Generate main index file
const mainIndex = await this.generateMainIndexFile(generatedFiles, importExtension);
return {
files: generatedFiles,
mainIndex,
};
}
/**
* Generate barrel export for a specific variant type
*/
async generateVariantBarrelExport(collections, variantType, config, importExtension = '') {
const exports = [];
const allExports = [];
// Collect exports from all models for this variant
collections.forEach((collection) => {
const variant = collection.variants[variantType];
if (variant) {
const modelExports = [];
if (config.includeSchemas) {
modelExports.push(variant.schemaName);
}
if (config.includeTypes) {
modelExports.push(variant.typeName);
}
if (modelExports.length > 0) {
const moduleSpecifier = this.resolveModulePath(variant.filePath, variantType);
exports.push({
namedExports: modelExports,
moduleSpecifier,
isTypeOnly: false,
});
allExports.push(...modelExports);
}
}
});
// Sort exports if requested
if (config.sortExports) {
exports.sort((a, b) => (a.moduleSpecifier || '').localeCompare(b.moduleSpecifier || ''));
allExports.sort();
}
// Generate file content
const content = this.generateBarrelFileContent(exports, variantType, config, importExtension);
const formattedContent = await (0, formatFile_1.formatFile)(content);
const filePath = `${this.moduleResolutionConfig.baseUrl}/${variantType}.ts`;
// Write to disk
await (0, writeFileSafely_1.writeFileSafely)(filePath, formattedContent);
return {
path: filePath,
content: formattedContent,
exports: allExports,
};
}
/**
* Generate main index file that exports all variant barrels
*/
async generateMainIndexFile(barrelFiles, importExtension = '') {
const exports = [];
const allExports = [];
// Create re-exports for each barrel file
barrelFiles.forEach((barrelFile) => {
const variantType = this.extractVariantTypeFromPath(barrelFile.path);
if (variantType) {
exports.push({
namedExports: ['*'],
moduleSpecifier: `./${variantType}`,
namespaceExport: variantType,
});
allExports.push(...barrelFile.exports);
}
});
// Generate content
const content = this.generateMainIndexContent(exports, importExtension);
const formattedContent = await (0, formatFile_1.formatFile)(content);
const filePath = `${this.moduleResolutionConfig.baseUrl}/index.ts`;
// Write to disk
await (0, writeFileSafely_1.writeFileSafely)(filePath, formattedContent);
return {
path: filePath,
content: formattedContent,
exports: allExports,
};
}
/**
* Handle cross-variant references
*/
async manageCrossVariantReferences(collections) {
const references = [];
// Analyze each collection for cross-variant dependencies
collections.forEach((collection) => {
Object.entries(collection.crossVariantReferences).forEach(([sourceVariant, targetVariants]) => {
const source = sourceVariant;
targetVariants.forEach((targetVariant) => {
const sourceResult = collection.variants[source];
const targetResult = collection.variants[targetVariant];
if (sourceResult && targetResult) {
// Determine what items are referenced
const referencedItems = this.findReferencedItems(sourceResult, targetResult);
if (referencedItems.length > 0) {
references.push({
sourceVariant: source,
targetVariant,
referencedItems,
referenceType: this.determineReferenceType(source, targetVariant),
});
}
}
});
});
});
// Update files with cross-variant imports
await this.updateFilesWithCrossVariantImports(collections, references);
return references;
}
/**
* Update files with cross-variant imports
*/
async updateFilesWithCrossVariantImports(collections, references) {
// Group references by source file
const referencesBySource = new Map();
references.forEach((ref) => {
collections.forEach((collection) => {
const sourceVariant = collection.variants[ref.sourceVariant];
if (sourceVariant) {
const sourceFile = sourceVariant.filePath;
if (!referencesBySource.has(sourceFile)) {
referencesBySource.set(sourceFile, []);
}
const refs = referencesBySource.get(sourceFile);
if (refs) {
refs.push(ref);
}
}
});
});
// Update each source file with necessary imports
for (const [sourceFile, fileReferences] of referencesBySource) {
await this.addImportsToFile(sourceFile, fileReferences);
}
}
/**
* Add imports to a specific file
*/
async addImportsToFile(filePath, references) {
try {
let content = await fs_1.promises.readFile(filePath, 'utf8');
// Generate import statements
const importStatements = this.generateImportStatements(references);
// Insert imports at the top of the file
const importSection = importStatements
.map((imp) => this.formatImportStatement(imp))
.join('\n');
if (importSection) {
// Find the position to insert imports (after existing imports but before other code)
const importInsertionPoint = this.findImportInsertionPoint(content);
content =
content.slice(0, importInsertionPoint) +
importSection +
'\n' +
content.slice(importInsertionPoint);
}
// Format and write back
const formattedContent = await (0, formatFile_1.formatFile)(content);
await (0, writeFileSafely_1.writeFileSafely)(filePath, formattedContent);
}
catch (error) {
console.warn(`Failed to update imports in ${filePath}:`, error);
}
}
/**
* Validate export consistency and detect issues
*/
validateExports(collections) {
const result = {
isValid: true,
circularDependencies: [],
unresolvedImports: [],
duplicateExports: [],
warnings: [],
};
// Check for duplicate exports across variants
const allExports = new Map();
collections.forEach((collection) => {
Object.entries(collection.variants).forEach(([variant, result]) => {
if (result) {
result.exports.forEach((exportName) => {
if (!allExports.has(exportName)) {
allExports.set(exportName, []);
}
const variants = allExports.get(exportName);
if (variants) {
variants.push(variant);
}
});
}
});
});
// Find duplicates
allExports.forEach((variants, exportName) => {
if (variants.length > 1) {
result.duplicateExports.push(`${exportName} (in ${variants.join(', ')})`);
result.isValid = false;
}
});
// Check for circular dependencies
result.circularDependencies = this.detectCircularDependencies(collections);
if (result.circularDependencies.length > 0) {
result.isValid = false;
}
// Validate import resolution
result.unresolvedImports = this.findUnresolvedImports(collections);
if (result.unresolvedImports.length > 0) {
result.warnings.push(`Found ${result.unresolvedImports.length} potentially unresolved imports`);
}
return result;
}
/**
* Private helper methods
*/
generateBarrelFileContent(exports, variantType, config, importExtension = '') {
const lines = [];
// Add custom header or default header
lines.push('/**');
if (config.customHeader) {
lines.push(` * ${config.customHeader}`);
}
else {
lines.push(` * ${variantType.toUpperCase()} Variant Schemas`);
}
lines.push(' * Auto-generated barrel export file');
lines.push(` * Generated at: ${new Date().toISOString()}`);
lines.push(' */');
lines.push('');
// Add export statements
exports.forEach((exportStmt) => {
if (exportStmt.moduleSpecifier) {
const exportList = exportStmt.namedExports.join(', ');
lines.push(`export { ${exportList} } from '${exportStmt.moduleSpecifier}${importExtension}';`);
}
});
lines.push('');
return lines.join('\n');
}
generateMainIndexContent(exports, importExtension = '') {
const lines = [];
lines.push('/**');
lines.push(' * Main Schema Variants Index');
lines.push(' * Auto-generated file - exports all variant types');
lines.push(` * Generated at: ${new Date().toISOString()}`);
lines.push(' */');
lines.push('');
// Add re-exports
exports.forEach((exportStmt) => {
if (exportStmt.moduleSpecifier) {
if (exportStmt.namespaceExport) {
lines.push(`export * as ${exportStmt.namespaceExport} from '${exportStmt.moduleSpecifier}${importExtension}';`);
}
else {
lines.push(`export * from '${exportStmt.moduleSpecifier}${importExtension}';`);
}
}
});
lines.push('');
return lines.join('\n');
}
resolveModulePath(filePath, variantType) {
var _a;
if (this.moduleResolutionConfig.useRelativePaths) {
// Extract filename without extension
const fileName = ((_a = filePath.split('/').pop()) === null || _a === void 0 ? void 0 : _a.replace('.ts', '')) || '';
return `./${variantType}/${fileName}`;
}
return filePath;
}
extractVariantTypeFromPath(path) {
var _a;
const fileName = (_a = path.split('/').pop()) === null || _a === void 0 ? void 0 : _a.replace('.ts', '');
return Object.values(variants_1.VariantType).find((variant) => variant === fileName) || null;
}
findReferencedItems(source, target) {
// This would analyze the content to find actual references
// For now, return basic schema/type references
const references = [];
if (source.content.includes(target.schemaName)) {
references.push(target.schemaName);
}
if (source.content.includes(target.typeName)) {
references.push(target.typeName);
}
return references;
}
determineReferenceType(source, target) {
// Determine the type of reference based on variant relationship
if (source === variants_1.VariantType.INPUT && target === variants_1.VariantType.PURE) {
return 'extends';
}
if (source === variants_1.VariantType.RESULT && target === variants_1.VariantType.PURE) {
return 'utility';
}
return 'import';
}
generateImportStatements(references) {
const importsByModule = new Map();
references.forEach((ref) => {
const moduleSpecifier = `./${ref.targetVariant}`;
if (!importsByModule.has(moduleSpecifier)) {
importsByModule.set(moduleSpecifier, new Set());
}
ref.referencedItems.forEach((item) => {
const imports = importsByModule.get(moduleSpecifier);
if (imports) {
imports.add(item);
}
});
});
return Array.from(importsByModule.entries()).map(([moduleSpecifier, imports]) => ({
moduleSpecifier,
namedImports: Array.from(imports),
isTypeOnly: false,
}));
}
formatImportStatement(importStmt) {
const parts = [];
if (importStmt.defaultImport) {
parts.push(importStmt.defaultImport);
}
if (importStmt.namedImports.length > 0) {
const namedList = importStmt.namedImports.join(', ');
parts.push(`{ ${namedList} }`);
}
if (importStmt.namespaceImport) {
parts.push(`* as ${importStmt.namespaceImport}`);
}
const importList = parts.join(', ');
const typeOnly = importStmt.isTypeOnly ? 'type ' : '';
return `import ${typeOnly}${importList} from '${importStmt.moduleSpecifier}';`;
}
findImportInsertionPoint(content) {
// Find the end of existing imports or the beginning of the file
const lines = content.split('\n');
let insertionLine = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith('import ') || (line.startsWith('export ') && line.includes('from'))) {
insertionLine = i + 1;
}
else if (line &&
!line.startsWith('//') &&
!line.startsWith('/*') &&
!line.startsWith('*')) {
break;
}
}
// Convert line number back to character position
return lines.slice(0, insertionLine).join('\n').length + (insertionLine > 0 ? 1 : 0);
}
detectCircularDependencies(collections) {
// Implement circular dependency detection using DFS
const dependencies = new Map();
const visited = new Set();
const recursionStack = new Set();
const circularDeps = [];
// Build dependency graph
collections.forEach((collection) => {
const modelName = collection.modelName;
dependencies.set(modelName, Array.from(collection.dependencies));
});
// DFS to detect cycles
const detectCycle = (node, path) => {
if (recursionStack.has(node)) {
const cycleStart = path.indexOf(node);
if (cycleStart !== -1) {
circularDeps.push(path.slice(cycleStart));
}
return;
}
if (visited.has(node))
return;
visited.add(node);
recursionStack.add(node);
const deps = dependencies.get(node) || [];
deps.forEach((dep) => {
detectCycle(dep, [...path, node]);
});
recursionStack.delete(node);
};
dependencies.forEach((_, node) => {
if (!visited.has(node)) {
detectCycle(node, []);
}
});
return circularDeps;
}
findUnresolvedImports(collections) {
const unresolved = [];
const availableExports = new Set();
// Collect all available exports
collections.forEach((collection) => {
Object.values(collection.variants).forEach((variant) => {
if (variant) {
variant.exports.forEach((exp) => availableExports.add(exp));
}
});
});
// Check each import against available exports
collections.forEach((collection) => {
Object.values(collection.variants).forEach((variant) => {
if (variant) {
variant.imports.forEach((imp) => {
if (!availableExports.has(imp) && !this.isExternalImport(imp)) {
unresolved.push(`${imp} in ${variant.fileName}`);
}
});
}
});
});
return unresolved;
}
isExternalImport(importName) {
// Check if import is from external libraries (like 'zod', '@prisma/client', etc.)
const externalLibraries = ['zod', '@prisma/client', 'prisma'];
return (externalLibraries.some((lib) => importName.startsWith(lib)) || importName.startsWith('z.'));
}
}
exports.VariantImportExportManager = VariantImportExportManager;
exports.default = VariantImportExportManager;
//# sourceMappingURL=exports.js.map