@algochad/prisma-core
Version:
A comprehensive NestJS library that provides EF-Core-like operations using Prisma and GraphQL. Features LINQ-style query builders, advanced data manipulation, GraphQL integration with genql, and a unified API for both Prisma and GraphQL operations. Includ
352 lines • 13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaInspector = void 0;
class SchemaInspector {
static schemaCache = new Map();
static lastInspectionTime = new Map();
static CACHE_DURATION = 300000;
static async inspectSchema(prismaClient) {
const clientId = this.getClientId(prismaClient);
const now = Date.now();
const lastInspection = this.lastInspectionTime.get(clientId) || 0;
if (now - lastInspection < this.CACHE_DURATION &&
this.schemaCache.has(clientId)) {
return this.schemaCache.get(clientId);
}
const schema = await this.extractSchemaFromClient(prismaClient);
this.schemaCache.set(clientId, schema);
this.lastInspectionTime.set(clientId, now);
return schema;
}
static async getTableSchema(prismaClient, modelName) {
const schema = await this.inspectSchema(prismaClient);
if (schema.tables.has(modelName)) {
return schema.tables.get(modelName);
}
for (const [tableName, tableSchema] of schema.tables) {
if (tableSchema.modelName.toLowerCase() === modelName.toLowerCase()) {
return tableSchema;
}
}
return null;
}
static async extractSchemaFromClient(prismaClient) {
const dmmf = this.getDMMF(prismaClient);
if (!dmmf) {
console.warn('Unable to access Prisma DMMF, using fallback schema inspection...');
return this.extractSchemaFromClientFallback(prismaClient);
}
const dialect = this.detectDialect(prismaClient);
const tables = new Map();
const enums = new Map();
if (dmmf.datamodel?.enums) {
for (const enumDef of dmmf.datamodel.enums) {
enums.set(enumDef.name, enumDef.values.map((v) => v.name));
}
}
if (dmmf.datamodel?.models) {
for (const model of dmmf.datamodel.models) {
const tableSchema = this.extractTableSchema(model, dialect);
tables.set(tableSchema.tableName, tableSchema);
}
}
return {
tables,
enums,
dialect,
version: this.getVersion(prismaClient),
};
}
static extractTableSchema(model, dialect) {
const columns = new Map();
const relations = new Map();
const primaryKey = [];
const uniqueConstraints = [];
const indexes = [];
for (const field of model.fields) {
if (field.kind === 'scalar') {
const columnInfo = {
name: field.name,
type: this.mapPrismaTypeToColumnType(field.type),
isOptional: field.isOptional,
isArray: field.isList,
isId: field.isId,
isUnique: field.isUnique,
isList: field.isList,
default: field.default,
};
columns.set(field.name, columnInfo);
if (field.isId) {
primaryKey.push(field.name);
}
}
else if (field.kind === 'object') {
const relationInfo = {
name: field.name,
type: this.determineRelationType(field),
model: field.type,
fields: field.relationFromFields || [],
references: field.relationToFields || [],
isOptional: field.isOptional,
};
relations.set(field.name, relationInfo);
if (field.relationFromFields) {
for (let i = 0; i < field.relationFromFields.length; i++) {
const fkField = field.relationFromFields[i];
if (!columns.has(fkField)) {
columns.set(fkField, {
name: fkField,
type: 'string',
isOptional: field.isOptional,
isArray: false,
isId: false,
isUnique: false,
isList: false,
relationName: field.relationName,
});
}
}
}
}
}
if (model.uniqueFields) {
for (const uniqueField of model.uniqueFields) {
uniqueConstraints.push(uniqueField);
}
}
if (model.indexes) {
for (const index of model.indexes) {
indexes.push({
name: index.name ||
`idx_${model.name}_${index.fields.join('_')}`,
fields: index.fields,
isUnique: index.isUnique || false,
type: index.type,
});
}
}
return {
tableName: this.getTableName(model),
modelName: model.name,
columns,
primaryKey,
uniqueConstraints,
relations,
indexes,
};
}
static determineRelationType(field) {
if (field.isList) {
return 'one-to-many';
}
if (field.relationFromFields && field.relationFromFields.length > 0) {
return 'many-to-one';
}
return 'one-to-one';
}
static mapPrismaTypeToColumnType(prismaType) {
switch (prismaType.toLowerCase()) {
case 'string':
return 'string';
case 'int':
case 'float':
return 'number';
case 'boolean':
return 'boolean';
case 'datetime':
return 'date';
case 'json':
return 'json';
case 'bytes':
return 'bytes';
case 'decimal':
return 'decimal';
case 'bigint':
return 'bigint';
default:
return 'string';
}
}
static getTableName(model) {
if (model.dbName) {
return model.dbName;
}
return model.name;
}
static getDMMF(prismaClient) {
const candidates = [
() => prismaClient._dmmf,
() => prismaClient._engine?.dmmf,
() => prismaClient._engineConfig?.dmmf,
() => prismaClient.dmmf,
() => prismaClient._fetcher?.dmmf,
() => prismaClient._clientVersion?.dmmf,
() => prismaClient.constructor?.dmmf,
() => prismaClient.constructor?._dmmf,
() => prismaClient._internals?.dmmf,
() => prismaClient._client?.dmmf,
() => typeof prismaClient.getDMMF === 'function'
? prismaClient.getDMMF()
: null,
() => typeof prismaClient._getDMMF === 'function'
? prismaClient._getDMMF()
: null,
];
for (const getCandidateFunc of candidates) {
try {
const candidate = getCandidateFunc();
if (candidate && (candidate.datamodel || candidate.schema)) {
console.log('✅ Successfully accessed Prisma DMMF');
return candidate;
}
}
catch (error) {
continue;
}
}
console.warn('❌ Unable to access Prisma DMMF. Available client properties:', Object.keys(prismaClient));
for (const key of Object.keys(prismaClient)) {
const value = prismaClient[key];
if (value &&
typeof value === 'object' &&
(value.datamodel || value.schema)) {
console.log(`✅ Found DMMF-like object in property: ${key}`);
return value;
}
}
return null;
}
static detectDialect(prismaClient) {
const dmmf = this.getDMMF(prismaClient);
if (dmmf && dmmf.datamodel && dmmf.datamodel.datasource) {
const provider = dmmf.datamodel.datasource.provider?.toLowerCase();
switch (provider) {
case 'postgresql':
case 'postgres':
return 'postgresql';
case 'mysql':
return 'mysql';
case 'sqlite':
return 'sqlite';
default:
console.warn(`Unknown database provider: ${provider}, defaulting to postgresql`);
return 'postgresql';
}
}
const clientString = prismaClient.toString();
if (clientString.includes('postgresql') ||
clientString.includes('postgres')) {
return 'postgresql';
}
else if (clientString.includes('mysql')) {
return 'mysql';
}
else if (clientString.includes('sqlite')) {
return 'sqlite';
}
return 'postgresql';
}
static getVersion(prismaClient) {
return prismaClient._clientVersion || 'unknown';
}
static getClientId(prismaClient) {
const props = [
prismaClient._clientVersion,
prismaClient._engine?.config?.generator?.previewFeatures?.join(','),
typeof prismaClient._dmmf,
].filter(Boolean);
return props.join('|') || 'default';
}
static clearCache() {
this.schemaCache.clear();
this.lastInspectionTime.clear();
}
static getCachedSchema(prismaClient) {
const clientId = this.getClientId(prismaClient);
return this.schemaCache.get(clientId) || null;
}
static async extractSchemaFromClientFallback(prismaClient) {
console.log('🔄 Using fallback schema extraction...');
const tables = new Map();
const enums = new Map();
const dialect = this.detectDialectFallback(prismaClient);
const modelNames = this.extractModelNamesFromClient(prismaClient);
for (const modelName of modelNames) {
const tableSchema = {
tableName: modelName.toLowerCase(),
modelName: modelName,
columns: new Map([
[
'id',
{
name: 'id',
type: 'string',
isOptional: false,
isArray: false,
isId: true,
isUnique: true,
isList: false,
},
],
]),
primaryKey: ['id'],
uniqueConstraints: [],
relations: new Map(),
indexes: [],
};
tables.set(tableSchema.tableName, tableSchema);
}
return {
tables,
enums,
dialect,
version: this.getVersion(prismaClient),
};
}
static extractModelNamesFromClient(prismaClient) {
const modelNames = [];
for (const key of Object.keys(prismaClient)) {
const value = prismaClient[key];
if (key.startsWith('_') ||
key.startsWith('$') ||
typeof value === 'function' ||
['constructor', 'prototype', 'toString', 'valueOf'].includes(key)) {
continue;
}
if (value &&
typeof value === 'object' &&
(value.findMany || value.findFirst || value.create)) {
modelNames.push(key);
}
}
console.log(`📋 Detected ${modelNames.length} models:`, modelNames);
return modelNames;
}
static detectDialectFallback(prismaClient) {
const clientString = String(prismaClient);
if (clientString.includes('postgresql') ||
clientString.includes('postgres')) {
return 'postgresql';
}
else if (clientString.includes('mysql')) {
return 'mysql';
}
else if (clientString.includes('sqlite')) {
return 'sqlite';
}
const dbUrl = process.env.DATABASE_URL || '';
if (dbUrl.includes('postgres')) {
return 'postgresql';
}
else if (dbUrl.includes('mysql')) {
return 'mysql';
}
else if (dbUrl.includes('sqlite')) {
return 'sqlite';
}
console.warn('⚠️ Could not detect database dialect, defaulting to postgresql');
return 'postgresql';
}
}
exports.SchemaInspector = SchemaInspector;
//# sourceMappingURL=schema-inspector.js.map