@msugiura/rawsql-prisma
Version:
Prisma integration for rawsql-ts - Dynamic SQL generation with type safety and hierarchical JSON serialization
613 lines • 25.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrismaSchemaResolver = void 0;
const internals_1 = require("@prisma/internals");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/**
* Resolves Prisma schema information from the client
*
* This class analyzes the Prisma client to extract model definitions,
* field types, relationships, and other metadata needed for dynamic SQL generation.
*/
class PrismaSchemaResolver {
constructor(options) {
this.options = options;
}
/**
* Resolve complete schema information from Prisma client
*
* @param prisma - The Prisma client instance
* @returns Complete schema information
*/
async resolveSchema(prisma) {
if (this.options.debug) {
console.log('[PrismaSchemaResolver] Resolving Prisma schema using DMMF...');
}
try {
// Primary method: Extract from schema.prisma using @prisma/internals
const dmmf = await this.getDMMFFromSchema();
if (dmmf) {
const models = this.parseModelsFromDmmf(dmmf);
const databaseProvider = this.extractDatabaseProvider(dmmf);
this.schemaInfo = {
models,
schemaName: this.options.defaultSchema || 'public',
databaseProvider
};
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Successfully loaded schema with ${Object.keys(models).length} models from schema.prisma`);
console.log(`[PrismaSchemaResolver] Database provider: ${databaseProvider}`);
}
return this.schemaInfo;
}
}
catch (error) {
if (this.options.debug) {
console.warn('[PrismaSchemaResolver] Failed to load schema from schema.prisma:', error);
}
}
try {
// Fallback: Extract from Prisma client
const dmmf = this.extractDmmfFromClient(prisma);
if (dmmf && this.isValidDmmf(dmmf)) {
const models = this.parseModelsFromDmmf(dmmf);
this.schemaInfo = {
models,
schemaName: this.options.defaultSchema || 'public'
};
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Successfully loaded schema with ${Object.keys(models).length} models from Prisma client`);
}
return this.schemaInfo;
}
}
catch (error) {
if (this.options.debug) {
console.warn('[PrismaSchemaResolver] Failed to extract DMMF from Prisma client:', error);
}
}
// Final fallback: If all methods fail, throw error with generic paths
// Use generic location names instead of exposing system-specific paths
const searchedLocations = [
'./prisma/schema.prisma',
'./schema.prisma',
'../prisma/schema.prisma',
'../schema.prisma',
'../../prisma/schema.prisma',
'../../schema.prisma',
'../../../prisma/schema.prisma',
'../../../schema.prisma',
'./packages/*/prisma/schema.prisma',
'./examples/*/prisma/schema.prisma',
...(this.options.schemaPath ? ['<custom-schema-path>'] : [])
];
throw new Error(`Unable to resolve Prisma schema information. Searched in these locations:\n` +
searchedLocations.map(loc => ` - ${loc}`).join('\n') + '\n\n' +
`Platform: ${process.platform}\n` +
`Node.js version: ${process.version}\n\n` +
'Solutions:\n' +
'1. Ensure you have a valid schema.prisma file in one of the above locations\n' +
'2. Provide a valid PrismaClient instance to the StaticAnalysisOrchestrator\n' +
'3. Specify a custom schema location using the "schemaPath" option in RawSqlClient');
}
/**
* Create a TableColumnResolver function for rawsql-ts
*
* @returns TableColumnResolver function
*/
createTableColumnResolver() {
if (!this.schemaInfo) {
throw new Error('Schema not resolved. Call resolveSchema() first.');
}
return (tableName) => {
const columnNames = this.getColumnNames(tableName);
return columnNames || [];
};
}
/**
* Get all available table names
*
* @returns Array of table names
*/
getTableNames() {
if (!this.schemaInfo) {
return [];
}
return Object.values(this.schemaInfo.models).map(model => model.tableName);
}
/**
* Get all column names for a specific table
*
* @param tableName - The table name
* @returns Array of column names, or undefined if table not found
*/
getColumnNames(tableName) {
if (!this.schemaInfo) {
return undefined;
}
const model = this.findModelByTableName(tableName);
if (!model) {
return undefined;
}
return Object.values(model.fields).map(field => field.columnName);
}
/**
* Check if a table exists in the schema
*
* @param tableName - The table name to check
* @returns true if table exists, false otherwise
*/
hasTable(tableName) {
return this.findModelByTableName(tableName) !== undefined;
}
/**
* Check if a column exists in a specific table
*
* @param tableName - The table name
* @param columnName - The column name to check
* @returns true if column exists, false otherwise
*/
hasColumn(tableName, columnName) {
const model = this.findModelByTableName(tableName);
if (!model) {
return false;
}
return Object.values(model.fields).some(field => field.columnName === columnName);
}
/**
* Get DMMF directly from schema.prisma file
*
* @returns Promise resolving to DMMF object or null if not found
*/
async getDMMFFromSchema() {
try {
const schemaPath = await this.findSchemaFile();
if (!schemaPath) {
if (this.options.debug) {
console.log('[PrismaSchemaResolver] No schema.prisma file found in common locations');
}
return null;
}
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Loading DMMF from schema file: ${schemaPath}`);
}
// Use @prisma/internals to get DMMF from schema.prisma
const dmmf = await (0, internals_1.getDMMF)({
datamodel: fs.readFileSync(schemaPath, 'utf-8')
});
if (this.isValidDmmf(dmmf)) {
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Successfully parsed DMMF with ${dmmf.datamodel.models.length} models`);
}
return dmmf;
}
return null;
}
catch (error) {
if (this.options.debug) {
console.error('Error loading DMMF from schema file:', error);
}
return null;
}
}
/**
* Find schema.prisma file in common locations
*
* @returns Promise resolving to schema file path or null if not found
*/
async findSchemaFile() {
return this.findSchemaFileSync();
}
/**
* Find schema file synchronously
*
* @returns Path to schema.prisma file or null if not found
*/
findSchemaFileSync() {
// First priority: Check if custom schema path is provided and exists
if (this.options.schemaPath) {
try {
if (fs.existsSync(this.options.schemaPath) && fs.statSync(this.options.schemaPath).isFile()) {
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Found schema file at custom path: ${this.options.schemaPath}`);
}
return this.options.schemaPath;
}
}
catch (error) {
if (this.options.debug) {
console.warn(`[PrismaSchemaResolver] Custom schema path invalid: ${this.options.schemaPath}`, error);
}
}
}
// Fallback: Check common locations relative to module directory for consistent resolution
// Use module directory as base instead of process.cwd() to avoid execution context issues
const moduleDir = path.dirname(__filename);
const projectRoot = path.resolve(moduleDir, '..');
const isWindows = process.platform === 'win32';
const isWSL = process.platform === 'linux' && process.env.WSL_DISTRO_NAME;
const commonPaths = [
// Project root and common locations
path.resolve(projectRoot, 'prisma', 'schema.prisma'),
path.resolve(projectRoot, 'schema.prisma'),
// Parent directories (for monorepo structures)
path.resolve(projectRoot, '..', 'prisma', 'schema.prisma'),
path.resolve(projectRoot, '..', 'schema.prisma'),
path.resolve(projectRoot, '../../prisma', 'schema.prisma'),
path.resolve(projectRoot, '../../schema.prisma'),
path.resolve(projectRoot, '../../../prisma', 'schema.prisma'),
path.resolve(projectRoot, '../../../schema.prisma'),
// Specific paths for package structures
path.resolve(projectRoot, 'packages', 'prisma-integration', 'prisma', 'schema.prisma'),
path.resolve(projectRoot, 'examples', 'prisma-comparison-demo', 'prisma', 'schema.prisma'),
// Additional fallback paths
path.resolve(projectRoot, '../../../../prisma/schema.prisma'),
path.resolve(projectRoot, '../../../../schema.prisma'),
];
// Add Windows-specific paths if running on Windows
if (isWindows) {
// Convert WSL-style paths to Windows paths if applicable
const windowsProjectRoot = projectRoot.replace(/^\/mnt\/([a-z])/, '$1:');
if (windowsProjectRoot !== projectRoot) {
commonPaths.push(path.join(windowsProjectRoot, 'prisma', 'schema.prisma'), path.join(windowsProjectRoot, 'packages', 'prisma-integration', 'prisma', 'schema.prisma'), path.join(windowsProjectRoot, 'examples', 'prisma-comparison-demo', 'prisma', 'schema.prisma'));
}
}
// Add dynamic WSL paths if running in WSL (avoid hardcoded user paths)
if (isWSL) {
// Try to detect WSL mount points dynamically
const wslMountBase = '/mnt/c';
if (fs.existsSync(wslMountBase)) {
// Add some common WSL paths relative to the detected mount
const wslPaths = [
path.resolve(wslMountBase, 'projects', 'prisma', 'schema.prisma'),
path.resolve(wslMountBase, 'dev', 'prisma', 'schema.prisma')
];
commonPaths.push(...wslPaths);
}
}
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Module directory: ${moduleDir}`);
console.log(`[PrismaSchemaResolver] Project root: ${projectRoot}`);
console.log(`[PrismaSchemaResolver] Searching for schema.prisma in ${commonPaths.length} locations...`);
}
for (const schemaPath of commonPaths) {
try {
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Checking: ${schemaPath}`);
}
if (fs.existsSync(schemaPath) && fs.statSync(schemaPath).isFile()) {
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] ✅ Found schema file at: ${schemaPath}`);
}
return schemaPath;
}
}
catch (error) {
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] ❌ Error checking ${schemaPath}:`, error instanceof Error ? error.message : error);
}
continue;
}
}
if (this.options.debug) {
console.log('[PrismaSchemaResolver] ❌ No schema.prisma file found in any location');
}
return null;
}
/**
* Extract DMMF from Prisma client using various access methods
* Supports multiple Prisma versions and deployment environments
*/
extractDmmfFromClient(prisma) {
const client = prisma;
try {
// Try different ways to access DMMF based on Prisma version
const dmmfSources = [
// Prisma 5.x+
() => client._dmmf,
// Prisma 4.x
() => client.dmmf,
// Engine-based access (various versions)
() => { var _a; return (_a = client._engine) === null || _a === void 0 ? void 0 : _a.dmmf; },
() => { var _a; return (_a = client._requestHandler) === null || _a === void 0 ? void 0 : _a.dmmf; },
// Alternative engine access patterns
() => { var _a; return (_a = client._engine) === null || _a === void 0 ? void 0 : _a.datamodel; },
() => { var _a, _b, _c; return client._clientVersion && ((_c = (_b = (_a = client._engineConfig) === null || _a === void 0 ? void 0 : _a.generator) === null || _b === void 0 ? void 0 : _b.output) === null || _c === void 0 ? void 0 : _c.dmmf); },
// Try accessing through internal methods
() => typeof client.getDmmf === 'function' ? client.getDmmf() : null,
() => typeof client._getDmmf === 'function' ? client._getDmmf() : null,
// Last resort: check constructor or prototype
() => { var _a; return (_a = client.constructor) === null || _a === void 0 ? void 0 : _a.dmmf; },
() => { var _a; return (_a = Object.getPrototypeOf(client)) === null || _a === void 0 ? void 0 : _a.dmmf; }
];
for (const getDmmf of dmmfSources) {
try {
const dmmf = getDmmf();
if (this.isValidDmmf(dmmf)) {
if (this.options.debug) {
console.log('[PrismaSchemaResolver] Successfully extracted DMMF from Prisma client');
}
return dmmf;
}
}
catch (error) {
// Continue to next method
if (this.options.debug) {
console.debug('[PrismaSchemaResolver] DMMF extraction method failed:', error);
}
}
}
if (this.options.debug) {
console.warn('[PrismaSchemaResolver] No valid DMMF found using standard access patterns');
}
return null;
}
catch (error) {
if (this.options.debug) {
console.error('[PrismaSchemaResolver] Error during DMMF extraction:', error);
}
return null;
}
}
/**
* Validate if the extracted object is a valid DMMF
*/
isValidDmmf(dmmf) {
return (dmmf &&
typeof dmmf === 'object' &&
dmmf.datamodel &&
Array.isArray(dmmf.datamodel.models) &&
dmmf.datamodel.models.length > 0);
}
/**
* Parse model information from Prisma DMMF
*
* @param dmmf - Data Model Meta Format from Prisma
* @returns Parsed model information
*/
parseModelsFromDmmf(dmmf) {
const models = {};
// Parse each model from DMMF
for (const model of dmmf.datamodel.models) {
const modelInfo = {
name: model.name,
tableName: this.getTableName(model.name, model.dbName),
fields: this.parseFieldsFromModel(model),
relations: this.parseRelationsFromModel(model),
primaryKey: this.extractPrimaryKey(model),
uniqueConstraints: this.extractUniqueConstraints(model)
};
models[model.name] = modelInfo;
}
return models;
}
/**
* Get table name with custom mappings applied
*/
getTableName(modelName, dbName) {
var _a;
const mappedName = (_a = this.options.tableNameMappings) === null || _a === void 0 ? void 0 : _a[modelName];
return mappedName || dbName || modelName;
}
/**
* Parse field information from model
*/
parseFieldsFromModel(model) {
const fields = {};
for (const field of model.fields) {
if (field.relationName) {
// Skip relation fields - they're handled separately
continue;
}
const fieldInfo = {
name: field.name,
columnName: this.getColumnName(model.name, field.name, field.dbName),
type: field.type,
isOptional: field.isOptional,
isList: field.isList,
isId: field.isId,
isUnique: field.isUnique,
defaultValue: field.default,
isGenerated: field.isGenerated
};
fields[field.name] = fieldInfo;
}
return fields;
}
/**
* Get column name with custom mappings applied
*/
getColumnName(modelName, fieldName, dbName) {
var _a, _b;
const mappedName = (_b = (_a = this.options.columnNameMappings) === null || _a === void 0 ? void 0 : _a[modelName]) === null || _b === void 0 ? void 0 : _b[fieldName];
return mappedName || dbName || fieldName;
}
/**
* Parse relation information from model
*/
parseRelationsFromModel(model) {
const relations = {};
for (const field of model.fields) {
if (!field.relationName) {
continue;
}
const relationType = this.determineRelationType(field);
const relationInfo = {
name: field.name,
modelName: field.type,
type: relationType,
foreignKeys: field.relationFromFields || [],
referencedFields: field.relationToFields || [],
isOptional: field.isOptional,
isList: field.isList
};
relations[field.name] = relationInfo;
}
return relations;
}
/**
* Determine relation type from field information
*/
determineRelationType(field) {
if (field.isList) {
return 'one-to-many';
}
if (field.relationFromFields && field.relationFromFields.length > 0) {
return 'many-to-one';
}
return 'one-to-one';
}
/**
* Extract primary key fields from model
*/
extractPrimaryKey(model) {
const primaryKeyFields = model.fields.filter((field) => field.isId);
return primaryKeyFields.map((field) => field.name);
}
/**
* Extract unique constraints from model
*/
extractUniqueConstraints(model) {
const uniqueConstraints = [];
// Single field unique constraints
const uniqueFields = model.fields.filter((field) => field.isUnique);
for (const field of uniqueFields) {
uniqueConstraints.push([field.name]);
}
// Multi-field unique constraints
if (model.uniqueConstraints) {
for (const constraint of model.uniqueConstraints) {
uniqueConstraints.push(constraint.fields);
}
}
return uniqueConstraints;
}
/**
* Find model by table name
*
* @param tableName - The table name to search for
* @returns Model information if found, undefined otherwise
*/
findModelByTableName(tableName) {
if (!this.schemaInfo) {
return undefined;
}
return Object.values(this.schemaInfo.models).find(model => model.tableName === tableName);
}
/**
* Get model information by model name
*
* @param modelName - The Prisma model name
* @returns Model information if found, undefined otherwise
*/
getModelInfo(modelName) {
if (!this.schemaInfo) {
return undefined;
}
return this.schemaInfo.models[modelName];
}
/**
* Get all model names
*
* @returns Array of model names
*/
getModelNames() {
if (!this.schemaInfo) {
return [];
}
return Object.keys(this.schemaInfo.models);
}
/**
* Extract database provider from DMMF
*
* @param dmmf - The DMMF object
* @returns Database provider string (postgresql, mysql, sqlite, etc.)
*/
extractDatabaseProvider(dmmf) {
try {
// Check if DMMF has datasources information
if (dmmf && dmmf.datasources && Array.isArray(dmmf.datasources) && dmmf.datasources.length > 0) {
const datasource = dmmf.datasources[0];
if (datasource && datasource.provider) {
const provider = datasource.provider.toLowerCase();
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Found database provider from DMMF datasources: ${provider}`);
}
return provider;
}
}
// Fallback: try to read from schema file directly
const schemaPath = this.findSchemaFileSync();
if (schemaPath && fs.existsSync(schemaPath)) {
const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
// Look specifically for datasource db { provider = "..." } pattern
const datasourceMatch = schemaContent.match(/datasource\s+\w+\s*\{[^}]*provider\s*=\s*"([^"]+)"[^}]*\}/);
if (datasourceMatch && datasourceMatch[1]) {
const provider = datasourceMatch[1].toLowerCase();
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Found database provider from schema file: ${provider}`);
}
return provider;
}
// Fallback to simple provider match (less reliable)
const providerMatch = schemaContent.match(/provider\s*=\s*"([^"]+)"/);
if (providerMatch && providerMatch[1]) {
// Skip if this is a generator provider (prisma-client-js)
const provider = providerMatch[1].toLowerCase();
if (provider !== 'prisma-client-js' && !provider.includes('client')) {
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Found database provider from fallback pattern: ${provider}`);
}
return provider;
}
}
}
if (this.options.debug) {
console.warn('[PrismaSchemaResolver] Could not determine database provider from DMMF or schema file');
}
return undefined;
}
catch (error) {
if (this.options.debug) {
console.warn('[PrismaSchemaResolver] Error extracting database provider:', error);
}
return undefined;
}
}
}
exports.PrismaSchemaResolver = PrismaSchemaResolver;
//# sourceMappingURL=PrismaSchemaResolver.js.map