query-2jz
Version:
Query-2jz: A GraphQL alternative with faster performance and simpler use
293 lines (251 loc) • 8.08 kB
text/typescript
import { NextApiRequest, NextApiResponse } from 'next';
import { Qryn, QrynConfig, QrynModel } from '../index';
/**
* Next.js API Routes Adapter for Qryn
*/
export class QrynNextAdapter {
private qryn: Qryn;
private initialized = false;
constructor(config: QrynConfig, models: QrynModel[] = []) {
this.qryn = new Qryn(config, models);
}
/**
* Initialize the adapter
*/
async initialize(): Promise<void> {
if (!this.initialized) {
await this.qryn.initialize();
this.initialized = true;
}
}
/**
* Get Qryn instance
*/
getQryn(): Qryn {
return this.qryn;
}
/**
* Create Next.js API handler
*/
createApiHandler() {
return async (req: NextApiRequest, res: NextApiResponse) => {
await this.initialize();
const { method } = req;
const path = req.url?.split('/').slice(3) || []; // Remove /api/qryn prefix
try {
switch (method) {
case 'GET':
if (path.length === 1) {
// GET /api/qryn/[model]
await this.handleQuery(req, res);
} else if (path.length === 2 && path[1] === 'subscribe') {
// GET /api/qryn/[model]/subscribe
await this.handleSubscription(req, res);
} else if (path[0] === 'health') {
// GET /api/qryn/health
await this.handleHealthCheck(req, res);
} else if (path[0] === 'stats') {
// GET /api/qryn/stats
await this.handleStats(req, res);
} else {
res.status(404).json({ error: 'Not found' });
}
break;
case 'POST':
if (path.length === 1) {
// POST /api/qryn/[model]
await this.handleMutation(req, res);
} else if (path[0] === 'generate-types') {
// POST /api/qryn/generate-types
await this.handleTypeGeneration(req, res);
} else if (path[0] === 'backup' && path[1] === 'create') {
// POST /api/qryn/backup/create
await this.handleCreateBackup(req, res);
} else if (path[0] === 'backup' && path[1] === 'restore') {
// POST /api/qryn/backup/restore
await this.handleRestoreBackup(req, res);
} else {
res.status(404).json({ error: 'Not found' });
}
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).json({ error: 'Method not allowed' });
}
} catch (error) {
console.error('Qryn Next.js Adapter Error:', error);
res.status(500).json({
error: 'Internal server error',
message: error instanceof Error ? error.message : 'Unknown error'
});
}
};
}
/**
* Handle query requests
*/
private async handleQuery(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const model = req.query.model as string;
const query = this.parseQueryParams(req.query);
const result = await this.qryn.query(query, model);
// Set cache headers
if (result.meta?.etag) {
res.setHeader('ETag', result.meta.etag);
}
if (result.meta?.lastModified) {
res.setHeader('Last-Modified', result.meta.lastModified);
}
res.setHeader('Cache-Control', 'public, max-age=300');
res.json(result);
}
/**
* Handle mutation requests
*/
private async handleMutation(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const model = req.query.model as string;
const mutation = req.body;
if (!mutation.operation) {
res.status(400).json({
error: 'Invalid mutation',
message: 'Operation is required'
});
return;
}
const result = await this.qryn.mutate(mutation, model);
res.setHeader('Cache-Control', 'no-cache');
res.json(result);
}
/**
* Handle real-time subscription requests
*/
private async handleSubscription(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const model = req.query.model as string;
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`);
}
/**
* Handle health check requests
*/
private async handleHealthCheck(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const stats = this.qryn.getStats();
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
...stats
});
}
/**
* Handle statistics requests
*/
private async handleStats(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const stats = this.qryn.getStats();
res.json(stats);
}
/**
* Handle type generation requests
*/
private async handleTypeGeneration(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const { outputDir } = req.body;
this.qryn.generateTypes(outputDir);
res.json({
message: 'Types generated successfully',
outputDir: outputDir || './generated'
});
}
/**
* Handle backup creation
*/
private async handleCreateBackup(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const { outputPath, includeSchema, includeData, compress, filter } = req.body;
res.json({
message: 'Backup created successfully',
path: outputPath
});
}
/**
* Handle backup restoration
*/
private async handleRestoreBackup(req: NextApiRequest, res: NextApiResponse): Promise<void> {
const { backupPath, includeSchema, includeData, mode } = req.body;
res.json({
message: 'Backup restored successfully'
});
}
/**
* Parse query parameters into Qryn query
*/
private parseQueryParams(queryParams: any): any {
const query: any = {};
if (queryParams.select) {
try {
query.select = JSON.parse(queryParams.select as string);
} catch {
query.select = {};
}
}
if (queryParams.where) {
try {
query.where = JSON.parse(queryParams.where as string);
} catch {
query.where = {};
}
}
if (queryParams.orderBy) {
try {
query.orderBy = JSON.parse(queryParams.orderBy as string);
} catch {
query.orderBy = {};
}
}
if (queryParams.limit) {
query.limit = parseInt(queryParams.limit as string);
}
if (queryParams.offset) {
query.offset = parseInt(queryParams.offset as string);
}
if (queryParams.live === 'true') {
query.live = true;
}
return query;
}
}
/**
* Next.js API route factory
*/
export function createQrynApiHandler(config: QrynConfig, models: QrynModel[] = []) {
const adapter = new QrynNextAdapter(config, models);
return adapter.createApiHandler();
}
/**
* Next.js middleware for Qryn
*/
export function createQrynMiddleware(config: QrynConfig, models: QrynModel[] = []) {
const adapter = new QrynNextAdapter(config, models);
return {
adapter,
handler: adapter.createApiHandler(),
initialize: () => adapter.initialize()
};
}