@msugiura/rawsql-prisma
Version:
Prisma integration for rawsql-ts - Dynamic SQL generation with type safety and hierarchical JSON serialization
551 lines • 21.7 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
const schemaLocations = [
path.join(process.cwd(), 'prisma', 'schema.prisma'),
path.join(process.cwd(), 'schema.prisma'),
...(this.options.schemaPath ? [this.options.schemaPath] : [])
];
throw new Error(`Unable to resolve Prisma schema information. Please ensure you have a valid schema.prisma file in one of these locations:\n` +
schemaLocations.map(loc => ` - ${loc}`).join('\n') + '\n\n' +
'Or provide a valid PrismaClient instance to the StaticAnalysisOrchestrator.\n' +
'If you are using a custom schema location, specify it 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
const commonPaths = [
path.join(process.cwd(), 'prisma', 'schema.prisma'),
path.join(process.cwd(), 'schema.prisma'),
path.join(process.cwd(), '..', 'prisma', 'schema.prisma'),
path.join(process.cwd(), '..', 'schema.prisma'),
path.join(process.cwd(), '../../prisma', 'schema.prisma'),
path.join(process.cwd(), '../../schema.prisma'),
];
for (const schemaPath of commonPaths) {
try {
if (fs.existsSync(schemaPath) && fs.statSync(schemaPath).isFile()) {
if (this.options.debug) {
console.log(`[PrismaSchemaResolver] Found schema file at: ${schemaPath}`);
}
return schemaPath;
}
}
catch (error) {
// Continue checking other paths
continue;
}
}
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