UNPKG

query-2jz

Version:

Query-2jz: A GraphQL alternative with faster performance and simpler use

371 lines (320 loc) 9.77 kB
import { Request, Response, NextFunction, Router } from 'express'; import { Qryn, QrynConfig, QrynModel } from '../index'; /** * Express.js Adapter for Qryn */ export class QrynExpressAdapter { private qryn: Qryn; private router: Router; constructor(config: QrynConfig, models: QrynModel[] = []) { this.qryn = new Qryn(config, models); this.router = Router(); this.setupRoutes(); } /** * Initialize the adapter */ async initialize(): Promise<void> { await this.qryn.initialize(); } /** * Get Express router */ getRouter(): Router { return this.router; } /** * Get Qryn instance */ getQryn(): Qryn { return this.qryn; } /** * Setup Express routes */ private setupRoutes(): void { // Health check this.router.get('/health', this.healthCheck.bind(this)); // Query endpoint this.router.get('/api/qryn/:model', this.handleQuery.bind(this)); // Mutation endpoint this.router.post('/api/qryn/:model', this.handleMutation.bind(this)); // Real-time subscription endpoint this.router.get('/api/qryn/:model/subscribe', this.handleSubscription.bind(this)); // Type generation endpoint this.router.post('/api/qryn/generate-types', this.handleTypeGeneration.bind(this)); // Statistics endpoint this.router.get('/api/qryn/stats', this.handleStats.bind(this)); // Backup endpoints this.router.post('/api/qryn/backup/create', this.handleCreateBackup.bind(this)); this.router.post('/api/qryn/backup/restore', this.handleRestoreBackup.bind(this)); this.router.get('/api/qryn/backup/list', this.handleListBackups.bind(this)); // Middleware for error handling this.router.use(this.errorHandler.bind(this)); } /** * Health check endpoint */ private async healthCheck(req: Request, res: Response): Promise<void> { try { const stats = this.qryn.getStats(); res.json({ status: 'healthy', timestamp: new Date().toISOString(), ...stats }); } catch (error) { res.status(500).json({ status: 'unhealthy', error: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Handle query requests */ private async handleQuery(req: Request, res: Response): Promise<void> { try { const { model } = req.params; const query = this.parseQueryParams(req.query); // Add request context const context = this.createRequestContext(req); const result = await this.qryn.query(query, model); // Set cache headers if (result.meta?.etag) { res.set('ETag', result.meta.etag); } if (result.meta?.lastModified) { res.set('Last-Modified', result.meta.lastModified); } res.set('Cache-Control', 'public, max-age=300'); res.json(result); } catch (error) { res.status(500).json({ error: 'Query execution failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Handle mutation requests */ private async handleMutation(req: Request, res: Response): Promise<void> { try { const { model } = req.params; const mutation = req.body; // Validate mutation if (!mutation.operation) { res.status(400).json({ error: 'Invalid mutation', message: 'Operation is required' }); return; } const result = await this.qryn.mutate(mutation, model); res.set('Cache-Control', 'no-cache'); res.json(result); } catch (error) { res.status(500).json({ error: 'Mutation execution failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Handle real-time subscription requests */ private async handleSubscription(req: Request, res: Response): Promise<void> { try { const { model } = req.params; const query = this.parseQueryParams(req.query); // Set SSE headers res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Cache-Control' }); // Create subscription const subscriptionId = this.qryn.subscribe(query, model, (data) => { res.write(`data: ${JSON.stringify(data)}\n\n`); }); // Handle client disconnect req.on('close', () => { if (subscriptionId) { this.qryn.unsubscribe(subscriptionId); } }); // Send initial connection message res.write(`data: ${JSON.stringify({ type: 'connected', subscriptionId })}\n\n`); } catch (error) { res.status(500).json({ error: 'Subscription failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Handle type generation requests */ private async handleTypeGeneration(req: Request, res: Response): Promise<void> { try { const { outputDir } = req.body; this.qryn.generateTypes(outputDir); res.json({ message: 'Types generated successfully', outputDir: outputDir || './generated' }); } catch (error) { res.status(500).json({ error: 'Type generation failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Handle statistics requests */ private async handleStats(req: Request, res: Response): Promise<void> { try { const stats = this.qryn.getStats(); res.json(stats); } catch (error) { res.status(500).json({ error: 'Failed to get statistics', message: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Handle backup creation */ private async handleCreateBackup(req: Request, res: Response): Promise<void> { try { const { outputPath, includeSchema, includeData, compress, filter } = req.body; // This would use the backup system res.json({ message: 'Backup created successfully', path: outputPath }); } catch (error) { res.status(500).json({ error: 'Backup creation failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Handle backup restoration */ private async handleRestoreBackup(req: Request, res: Response): Promise<void> { try { const { backupPath, includeSchema, includeData, mode } = req.body; // This would use the backup system res.json({ message: 'Backup restored successfully' }); } catch (error) { res.status(500).json({ error: 'Backup restoration failed', message: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Handle backup listing */ private async handleListBackups(req: Request, res: Response): Promise<void> { try { // This would list available backups res.json({ backups: [] }); } catch (error) { res.status(500).json({ error: 'Failed to list backups', message: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Error handler middleware */ private errorHandler(error: any, req: Request, res: Response, next: NextFunction): void { console.error('Qryn Express Adapter Error:', error); res.status(500).json({ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error', timestamp: new Date().toISOString() }); } /** * Parse query parameters into Qryn query */ private parseQueryParams(queryParams: any): any { const query: any = {}; if (queryParams.select) { try { query.select = JSON.parse(queryParams.select); } catch { query.select = {}; } } if (queryParams.where) { try { query.where = JSON.parse(queryParams.where); } catch { query.where = {}; } } if (queryParams.orderBy) { try { query.orderBy = JSON.parse(queryParams.orderBy); } catch { query.orderBy = {}; } } if (queryParams.limit) { query.limit = parseInt(queryParams.limit); } if (queryParams.offset) { query.offset = parseInt(queryParams.offset); } if (queryParams.live === 'true') { query.live = true; } return query; } /** * Create request context */ private createRequestContext(req: Request): any { return { userId: req.headers['x-user-id'] as string || 'anonymous', tenantId: req.headers['x-tenant-id'] as string, requestId: req.headers['x-request-id'] as string, userAgent: req.headers['user-agent'], ip: req.ip, metadata: { method: req.method, url: req.url, timestamp: new Date().toISOString() } }; } } /** * Express middleware factory */ export function createQrynMiddleware(config: QrynConfig, models: QrynModel[] = []) { const adapter = new QrynExpressAdapter(config, models); return { adapter, middleware: adapter.getRouter(), initialize: () => adapter.initialize() }; }