UNPKG

bigbasealpha

Version:

Professional Grade Custom Database System - A sophisticated, dependency-free database with encryption, caching, indexing, and web dashboard

1,029 lines (856 loc) 29.8 kB
import { EventEmitter } from 'events'; import { promises as fs } from 'fs'; import { join } from 'path'; /** * GraphQL Engine for BigBaseAlpha * Provides GraphQL API layer with schema introspection, resolvers, and subscriptions */ export class GraphQLEngine extends EventEmitter { constructor(config) { super(); this.config = { enabled: config.graphql?.enabled || false, port: config.graphql?.port || 4000, introspection: config.graphql?.introspection !== false, playground: config.graphql?.playground !== false, subscriptions: config.graphql?.subscriptions !== false, maxDepth: config.graphql?.maxDepth || 10, maxComplexity: config.graphql?.maxComplexity || 1000, ...config.graphql }; this.database = null; this.isInitialized = false; // Schema and resolvers this.typeDefs = new Map(); this.resolvers = new Map(); this.schema = null; this.customResolvers = new Map(); // Subscriptions this.subscriptions = new Map(); this.subscriptionClients = new Set(); // Query analysis this.queryCache = new Map(); this.queryStats = { totalQueries: 0, successfulQueries: 0, failedQueries: 0, averageExecutionTime: 0, slowQueries: [], complexQueries: [] }; // Performance metrics this.metrics = { requestsPerSecond: 0, averageResponseTime: 0, cacheHitRate: 0, subscriptionCount: 0, errors: 0 }; // Built-in types and resolvers this._initializeBuiltInTypes(); } async init() { if (this.isInitialized) return; console.log('🚀 Initializing GraphQL Engine...'); if (!this.config.enabled) { console.log('⚠️ GraphQL is disabled in configuration'); return; } try { // Initialize resolvers first (without dynamic schema) this._initializeResolvers(); // Setup subscriptions if enabled if (this.config.subscriptions) { this._initializeSubscriptions(); } this.isInitialized = true; console.log(`✅ GraphQL Engine initialized on port ${this.config.port}`); this.emit('graphqlInitialized', { port: this.config.port, introspection: this.config.introspection, playground: this.config.playground, subscriptions: this.config.subscriptions }); } catch (error) { console.error('❌ Failed to initialize GraphQL Engine:', error.message); throw error; } } setDatabase(database) { this.database = database; // Build dynamic schema after database is set if (this.isInitialized && database) { this._buildDynamicSchema().catch(error => { console.warn('⚠️ Could not build dynamic schema:', error.message); }); } } _initializeBuiltInTypes() { // Base scalar types this.typeDefs.set('scalars', ` scalar Date scalar JSON scalar Upload enum SortOrder { ASC DESC } input FilterInput { field: String! operator: String! value: JSON } input SortInput { field: String! order: SortOrder = ASC } input PaginationInput { skip: Int = 0 limit: Int = 10 } type QueryInfo { executionTime: Int complexity: Int depth: Int cached: Boolean } type MutationResult { success: Boolean! message: String data: JSON errors: [String] } `); // Database introspection types this.typeDefs.set('introspection', ` type Collection { name: String! documentCount: Int! fields: [FieldInfo!]! indexes: [IndexInfo!]! lastModified: Date } type FieldInfo { name: String! type: String! nullable: Boolean! frequency: Float! sampleValues: [JSON] } type IndexInfo { name: String! fields: [String!]! unique: Boolean! type: String! } type DatabaseStats { collections: Int! totalDocuments: Int! storageSize: String! indexes: Int! } `); } async _buildDynamicSchema() { if (!this.database) { console.log('⚠️ Database not set for GraphQL schema generation, using static schema'); // Build basic Query type without collections this._buildQueryType([]); this._buildMutationType([]); if (this.config.subscriptions) { this._buildSubscriptionType([]); } return; } try { // Get all collections const collections = await this.database.getCollections(); console.log(`📊 Building GraphQL schema for ${collections.length} collections`); // Build types for each collection for (const collection of collections) { await this._buildCollectionType(collection); } // Build root Query type this._buildQueryType(collections); // Build root Mutation type this._buildMutationType(collections); // Build Subscription type if enabled if (this.config.subscriptions) { this._buildSubscriptionType(collections); } console.log(`✅ GraphQL schema built with ${this.typeDefs.size} type definitions`); } catch (error) { console.error('❌ Failed to build GraphQL schema:', error.message); throw error; } } async _buildCollectionType(collectionName) { try { // Analyze collection structure const sampleDocuments = await this.database.find(collectionName, {}, { limit: 10 }); const fields = this._analyzeDocumentStructure(sampleDocuments); // Build GraphQL type definition const typeName = this._capitalize(collectionName); const fieldDefs = fields.map(field => `${field.name}: ${this._mapToGraphQLType(field.type)}${field.nullable ? '' : '!'}` ).join('\n '); const typeDef = ` type ${typeName} { _id: ID! ${fieldDefs} _createdAt: Date _updatedAt: Date } input ${typeName}Input { ${fieldDefs.replace(/!/g, '')} } input ${typeName}UpdateInput { ${fieldDefs.replace(/!/g, '')} } type ${typeName}Connection { edges: [${typeName}!]! totalCount: Int! pageInfo: PageInfo! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } `; this.typeDefs.set(collectionName, typeDef); } catch (error) { console.warn(`⚠️ Failed to build type for collection ${collectionName}:`, error.message); } } _analyzeDocumentStructure(documents) { const fieldMap = new Map(); for (const doc of documents) { for (const [key, value] of Object.entries(doc)) { if (key === '_id') continue; if (!fieldMap.has(key)) { fieldMap.set(key, { name: key, types: new Set(), nullable: false, frequency: 0 }); } const field = fieldMap.get(key); field.frequency++; field.types.add(this._getValueType(value)); } } // Convert to field definitions return Array.from(fieldMap.values()).map(field => ({ name: field.name, type: this._getMostCommonType(field.types), nullable: field.frequency < documents.length * 0.9 // Field is nullable if not present in 90% of docs })); } _getValueType(value) { if (value === null || value === undefined) return 'null'; if (typeof value === 'boolean') return 'Boolean'; if (typeof value === 'number') return Number.isInteger(value) ? 'Int' : 'Float'; if (typeof value === 'string') return this._isDateString(value) ? 'Date' : 'String'; if (Array.isArray(value)) return '[JSON]'; if (typeof value === 'object') return 'JSON'; return 'String'; } _isDateString(str) { return !isNaN(Date.parse(str)) && /\d{4}-\d{2}-\d{2}/.test(str); } _getMostCommonType(types) { // Priority: String > JSON > Int > Float > Boolean if (types.has('String')) return 'String'; if (types.has('JSON')) return 'JSON'; if (types.has('Date')) return 'Date'; if (types.has('Int')) return 'Int'; if (types.has('Float')) return 'Float'; if (types.has('Boolean')) return 'Boolean'; return 'String'; } _mapToGraphQLType(type) { const mapping = { 'String': 'String', 'Int': 'Int', 'Float': 'Float', 'Boolean': 'Boolean', 'Date': 'Date', 'JSON': 'JSON', '[JSON]': '[JSON]' }; return mapping[type] || 'String'; } _buildQueryType(collections) { const queries = collections.map(collection => { const typeName = this._capitalize(collection); return ` # Get single ${collection} by ID ${collection}(id: ID!): ${typeName} # Get multiple ${collection} with filtering and pagination ${collection}s( filter: [FilterInput] sort: [SortInput] pagination: PaginationInput ): ${typeName}Connection! # Search ${collection} search${typeName}s( query: String! fields: [String] pagination: PaginationInput ): ${typeName}Connection! `; }).join('\n'); const queryType = ` type Query { # Database introspection collections: [Collection!]! collection(name: String!): Collection databaseStats: DatabaseStats! # Collection queries ${queries} # System queries health: JSON! version: String! # Query metadata _queryInfo: QueryInfo } `; this.typeDefs.set('Query', queryType); } _buildMutationType(collections) { const mutations = collections.map(collection => { const typeName = this._capitalize(collection); return ` # Create ${collection} create${typeName}(input: ${typeName}Input!): MutationResult! # Update ${collection} update${typeName}(id: ID!, input: ${typeName}UpdateInput!): MutationResult! # Delete ${collection} delete${typeName}(id: ID!): MutationResult! # Bulk operations createMany${typeName}s(input: [${typeName}Input!]!): MutationResult! updateMany${typeName}s(filter: [FilterInput], input: ${typeName}UpdateInput!): MutationResult! deleteMany${typeName}s(filter: [FilterInput]): MutationResult! `; }).join('\n'); const mutationType = ` type Mutation { ${mutations} # Database operations createCollection(name: String!): MutationResult! dropCollection(name: String!): MutationResult! createIndex(collection: String!, fields: [String!]!, unique: Boolean): MutationResult! # Admin operations rebuildIndexes(collection: String): MutationResult! vacuum: MutationResult! } `; this.typeDefs.set('Mutation', mutationType); } _buildSubscriptionType(collections) { const subscriptions = collections.map(collection => { const typeName = this._capitalize(collection); return ` # Subscribe to ${collection} changes ${collection}Created: ${typeName}! ${collection}Updated: ${typeName}! ${collection}Deleted: ID! # Subscribe to all changes in ${collection} ${collection}Changes: ${typeName}! `; }).join('\n'); const subscriptionType = ` type Subscription { ${subscriptions} # System subscriptions databaseStats: DatabaseStats! health: JSON! } `; this.typeDefs.set('Subscription', subscriptionType); } _initializeResolvers() { // Query resolvers this.resolvers.set('Query', { // Database introspection collections: () => this._resolveCollections(), collection: (_, { name }) => this._resolveCollection(name), databaseStats: () => this._resolveDatabaseStats(), health: () => this._resolveHealth(), version: () => '1.0.0', // Dynamic collection resolvers will be added ...this._buildCollectionQueryResolvers() }); // Mutation resolvers this.resolvers.set('Mutation', { ...this._buildCollectionMutationResolvers(), // Database operations createCollection: (_, { name }) => this._createCollection(name), dropCollection: (_, { name }) => this._dropCollection(name), createIndex: (_, { collection, fields, unique }) => this._createIndex(collection, fields, unique) }); // Subscription resolvers if (this.config.subscriptions) { this.resolvers.set('Subscription', { ...this._buildCollectionSubscriptionResolvers(), databaseStats: () => this._subscribeDatabaseStats(), health: () => this._subscribeHealth() }); } // Scalar resolvers this.resolvers.set('Date', { serialize: (value) => value instanceof Date ? value.toISOString() : value, parseValue: (value) => new Date(value), parseLiteral: (ast) => new Date(ast.value) }); this.resolvers.set('JSON', { serialize: (value) => value, parseValue: (value) => value, parseLiteral: (ast) => JSON.parse(ast.value) }); } _buildCollectionQueryResolvers() { const resolvers = {}; if (!this.database) return resolvers; // Get collections and build resolvers try { const collections = this.database.getCollections ? this.database.getCollections() : []; for (const collection of collections) { // Single document resolver resolvers[collection] = async (_, { id }) => { return await this.database.findById(collection, id); }; // Multiple documents resolver resolvers[`${collection}s`] = async (_, { filter, sort, pagination }) => { const query = this._buildMongoQuery(filter); const sortObj = this._buildSortObject(sort); const { skip = 0, limit = 10 } = pagination || {}; const documents = await this.database.find(collection, query, { sort: sortObj, skip, limit }); const totalCount = await this.database.count(collection, query); return { edges: documents, totalCount, pageInfo: { hasNextPage: (skip + limit) < totalCount, hasPreviousPage: skip > 0, startCursor: skip.toString(), endCursor: (skip + limit - 1).toString() } }; }; // Search resolver resolvers[`search${this._capitalize(collection)}s`] = async (_, { query, fields, pagination }) => { const { skip = 0, limit = 10 } = pagination || {}; const searchResults = await this.database.search(collection, query, { fields, skip, limit }); return { edges: searchResults.results || [], totalCount: searchResults.totalResults || 0, pageInfo: { hasNextPage: (skip + limit) < (searchResults.totalResults || 0), hasPreviousPage: skip > 0, startCursor: skip.toString(), endCursor: (skip + limit - 1).toString() } }; }; } } catch (error) { console.warn('⚠️ Could not build collection query resolvers:', error.message); } return resolvers; } _buildCollectionMutationResolvers() { const resolvers = {}; if (!this.database) return resolvers; try { const collections = this.database.getCollections ? this.database.getCollections() : []; for (const collection of collections) { const typeName = this._capitalize(collection); // Create resolver resolvers[`create${typeName}`] = async (_, { input }) => { try { const result = await this.database.insert(collection, input); // Notify subscribers this._notifySubscribers(`${collection}Created`, result); this._notifySubscribers(`${collection}Changes`, result); return { success: true, message: `${typeName} created successfully`, data: result }; } catch (error) { return { success: false, message: error.message, errors: [error.message] }; } }; // Update resolver resolvers[`update${typeName}`] = async (_, { id, input }) => { try { const result = await this.database.update(collection, { _id: id }, input); // Notify subscribers this._notifySubscribers(`${collection}Updated`, result); this._notifySubscribers(`${collection}Changes`, result); return { success: true, message: `${typeName} updated successfully`, data: result }; } catch (error) { return { success: false, message: error.message, errors: [error.message] }; } }; // Delete resolver resolvers[`delete${typeName}`] = async (_, { id }) => { try { await this.database.remove(collection, { _id: id }); // Notify subscribers this._notifySubscribers(`${collection}Deleted`, id); return { success: true, message: `${typeName} deleted successfully`, data: { _id: id } }; } catch (error) { return { success: false, message: error.message, errors: [error.message] }; } }; } } catch (error) { console.warn('⚠️ Could not build collection mutation resolvers:', error.message); } return resolvers; } _buildCollectionSubscriptionResolvers() { const resolvers = {}; try { const collections = this.database?.getCollections ? this.database.getCollections() : []; for (const collection of collections) { resolvers[`${collection}Created`] = { subscribe: () => this._createSubscription(`${collection}Created`) }; resolvers[`${collection}Updated`] = { subscribe: () => this._createSubscription(`${collection}Updated`) }; resolvers[`${collection}Deleted`] = { subscribe: () => this._createSubscription(`${collection}Deleted`) }; resolvers[`${collection}Changes`] = { subscribe: () => this._createSubscription(`${collection}Changes`) }; } } catch (error) { console.warn('⚠️ Could not build collection subscription resolvers:', error.message); } return resolvers; } _initializeSubscriptions() { console.log('🔔 Initializing GraphQL subscriptions...'); // Subscription system would be initialized here // For now, we'll use a simple event-based system } // Helper methods for resolvers _buildMongoQuery(filters) { if (!filters || !Array.isArray(filters)) return {}; const query = {}; for (const filter of filters) { const { field, operator, value } = filter; switch (operator) { case 'eq': query[field] = value; break; case 'ne': query[field] = { $ne: value }; break; case 'gt': query[field] = { $gt: value }; break; case 'gte': query[field] = { $gte: value }; break; case 'lt': query[field] = { $lt: value }; break; case 'lte': query[field] = { $lte: value }; break; case 'in': query[field] = { $in: Array.isArray(value) ? value : [value] }; break; case 'regex': query[field] = { $regex: value, $options: 'i' }; break; } } return query; } _buildSortObject(sorts) { if (!sorts || !Array.isArray(sorts)) return {}; const sortObj = {}; for (const sort of sorts) { sortObj[sort.field] = sort.order === 'DESC' ? -1 : 1; } return sortObj; } // Introspection resolvers async _resolveCollections() { if (!this.database) return []; try { const collections = await this.database.getCollections(); const result = []; for (const collection of collections) { const count = await this.database.count(collection); const sampleDocs = await this.database.find(collection, {}, { limit: 10 }); const fields = this._analyzeDocumentStructure(sampleDocs); result.push({ name: collection, documentCount: count, fields: fields.map(f => ({ name: f.name, type: f.type, nullable: f.nullable, frequency: f.frequency || 0, sampleValues: [] })), indexes: [], // Would get from database lastModified: new Date() }); } return result; } catch (error) { console.error('Error resolving collections:', error.message); return []; } } async _resolveCollection(name) { if (!this.database) return null; try { const collections = await this.database.getCollections(); if (!collections.includes(name)) return null; const count = await this.database.count(name); const sampleDocs = await this.database.find(name, {}, { limit: 10 }); const fields = this._analyzeDocumentStructure(sampleDocs); return { name, documentCount: count, fields: fields.map(f => ({ name: f.name, type: f.type, nullable: f.nullable, frequency: f.frequency || 0, sampleValues: [] })), indexes: [], lastModified: new Date() }; } catch (error) { console.error(`Error resolving collection ${name}:`, error.message); return null; } } async _resolveDatabaseStats() { if (!this.database) { return { collections: 0, totalDocuments: 0, storageSize: '0 B', indexes: 0 }; } try { const collections = await this.database.getCollections(); let totalDocuments = 0; for (const collection of collections) { totalDocuments += await this.database.count(collection); } return { collections: collections.length, totalDocuments, storageSize: '0 B', // Would calculate actual size indexes: 0 // Would count actual indexes }; } catch (error) { console.error('Error resolving database stats:', error.message); return { collections: 0, totalDocuments: 0, storageSize: '0 B', indexes: 0 }; } } _resolveHealth() { return { status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), memory: process.memoryUsage(), graphql: { enabled: this.config.enabled, subscriptions: this.config.subscriptions, introspection: this.config.introspection } }; } // Subscription management _createSubscription(event) { const subscriptionId = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; if (!this.subscriptions.has(event)) { this.subscriptions.set(event, new Set()); } this.subscriptions.get(event).add(subscriptionId); this.metrics.subscriptionCount++; return { [Symbol.asyncIterator]: async function* () { // Subscription iterator would be implemented here yield { data: `Subscribed to ${event}` }; } }; } _notifySubscribers(event, data) { const subscribers = this.subscriptions.get(event); if (!subscribers) return; console.log(`🔔 Notifying ${subscribers.size} subscribers for event: ${event}`); // In a real implementation, this would send the data to all subscribers this.emit('subscriptionEvent', { event, data, subscriberCount: subscribers.size }); } // API for external integration async executeQuery(query, variables = {}, context = {}) { const startTime = Date.now(); this.queryStats.totalQueries++; try { // Simple query execution simulation console.log(`🔍 Executing GraphQL query: ${query.substring(0, 100)}...`); // Query analysis const analysis = this._analyzeQuery(query); if (analysis.depth > this.config.maxDepth) { throw new Error(`Query depth ${analysis.depth} exceeds maximum ${this.config.maxDepth}`); } if (analysis.complexity > this.config.maxComplexity) { throw new Error(`Query complexity ${analysis.complexity} exceeds maximum ${this.config.maxComplexity}`); } // Simulate query execution await new Promise(resolve => setTimeout(resolve, Math.random() * 100)); const executionTime = Date.now() - startTime; this.queryStats.successfulQueries++; this.queryStats.averageExecutionTime = (this.queryStats.averageExecutionTime + executionTime) / 2; // Track slow queries if (executionTime > 1000) { this.queryStats.slowQueries.push({ query: query.substring(0, 200), executionTime, timestamp: Date.now() }); } return { data: { message: 'Query executed successfully' }, extensions: { queryInfo: { executionTime, complexity: analysis.complexity, depth: analysis.depth, cached: false } } }; } catch (error) { this.queryStats.failedQueries++; this.metrics.errors++; return { errors: [{ message: error.message }], extensions: { queryInfo: { executionTime: Date.now() - startTime, cached: false } } }; } } _analyzeQuery(query) { // Simple query analysis const depth = (query.match(/{/g) || []).length; const complexity = query.length / 10; // Simple complexity calculation return { depth, complexity }; } // Schema management getSchema() { const allTypeDefs = Array.from(this.typeDefs.values()).join('\n\n'); return allTypeDefs; } addCustomType(name, typeDef) { this.typeDefs.set(`custom_${name}`, typeDef); console.log(`✅ Added custom type: ${name}`); } addCustomResolver(typeName, fieldName, resolver) { if (!this.customResolvers.has(typeName)) { this.customResolvers.set(typeName, {}); } this.customResolvers.get(typeName)[fieldName] = resolver; console.log(`✅ Added custom resolver: ${typeName}.${fieldName}`); } // Statistics and monitoring getStats() { return { config: { enabled: this.config.enabled, port: this.config.port, introspection: this.config.introspection, playground: this.config.playground, subscriptions: this.config.subscriptions }, schema: { types: this.typeDefs.size, resolvers: this.resolvers.size, customTypes: Array.from(this.typeDefs.keys()).filter(k => k.startsWith('custom_')).length, customResolvers: this.customResolvers.size }, queries: this.queryStats, subscriptions: { total: this.metrics.subscriptionCount, active: this.subscriptionClients.size, events: this.subscriptions.size }, performance: this.metrics }; } _capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } async shutdown() { console.log('🔄 Shutting down GraphQL Engine...'); // Close subscription connections this.subscriptionClients.clear(); this.subscriptions.clear(); // Clear caches this.queryCache.clear(); this.typeDefs.clear(); this.resolvers.clear(); this.customResolvers.clear(); console.log('✅ GraphQL Engine shutdown complete'); } } export default GraphQLEngine;