ixp-server
Version:
A comprehensive SDK for building Intent Exchange Protocol (IXP) servers with ease
449 lines • 16.2 kB
JavaScript
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { IntentRegistry } from './IntentRegistry';
import { ComponentRegistry } from './ComponentRegistry';
import { IntentResolver } from './IntentResolver';
import { ErrorFactory, toErrorResponse, getErrorStatusCode } from '../utils/errors';
import { Logger } from '../utils/logger';
import { MetricsService } from '../utils/metrics';
/**
* Main IXP Server class that orchestrates all components
*/
export class IXPServer {
constructor(config = {}) {
this.plugins = new Map();
this.middlewares = [];
this.isInitialized = false;
this.config = this.normalizeConfig(config);
this.app = express.Router();
this.logger = new Logger(this.config.logging);
this.metricsService = new MetricsService(this.config.metrics);
// Initialize registries
this.intentRegistry = new IntentRegistry(this.config.intents);
this.componentRegistry = new ComponentRegistry(this.config.components);
this.intentResolver = new IntentResolver(this.intentRegistry, this.componentRegistry, this.config.dataProvider);
this.logger.info('IXP Server initialized', {
intents: this.intentRegistry.getAll().length,
components: this.componentRegistry.getAll().length
});
}
/**
* Initialize the server with middleware and routes
*/
async initialize() {
if (this.isInitialized) {
this.logger.warn('Server already initialized');
return;
}
try {
// Setup core middleware
await this.setupCoreMiddleware();
// Setup custom middleware
await this.setupCustomMiddleware();
// Install plugins
await this.installPlugins();
// Setup routes
this.setupRoutes();
// Setup error handling
this.setupErrorHandling();
// Enable file watching if configured
if (this.config.intents && typeof this.config.intents === 'string') {
this.intentRegistry.enableFileWatching();
}
if (this.config.components && typeof this.config.components === 'string') {
this.componentRegistry.enableFileWatching();
}
this.isInitialized = true;
this.logger.info('IXP Server initialization complete');
}
catch (error) {
this.logger.error('Failed to initialize IXP Server', error);
throw ErrorFactory.configuration('Server initialization failed', { error });
}
}
/**
* Setup core middleware (CORS, security, etc.)
*/
async setupCoreMiddleware() {
// Request logging and metrics
this.app.use((req, res, next) => {
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
this.metricsService.recordRequest(req.path, res.statusCode, duration);
this.logger.info('Request completed', {
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration
});
});
next();
});
// CORS configuration
if (this.config.cors) {
const corsOptions = {
origin: this.config.cors?.origins || ['http://localhost:3000'],
credentials: this.config.cors?.credentials ?? true,
methods: this.config.cors?.methods || ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: this.config.cors?.allowedHeaders || ['Content-Type', 'Authorization']
};
this.app.use(cors(corsOptions));
this.logger.debug('CORS configured', corsOptions);
}
// Security middleware
if (this.config.security?.helmet !== false) {
const helmetOptions = {
contentSecurityPolicy: this.config.security?.csp || {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
connectSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:'],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}
};
this.app.use(helmet(helmetOptions));
this.logger.debug('Security middleware configured');
}
// Body parsing
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
}
/**
* Setup custom middleware
*/
async setupCustomMiddleware() {
// Sort middleware by priority
const sortedMiddleware = [...(this.config.middleware || []), ...this.middlewares]
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
for (const middleware of sortedMiddleware) {
this.app.use(middleware.handler);
this.logger.debug(`Middleware '${middleware.name}' installed`);
}
}
/**
* Install plugins
*/
async installPlugins() {
const plugins = this.config.plugins || [];
for (const plugin of plugins) {
await this.addPlugin(plugin);
}
}
/**
* Setup IXP routes
*/
setupRoutes() {
// GET /ixp/intents - Intent Discovery
this.app.get('/intents', async (req, res, next) => {
try {
const intents = this.intentRegistry.getAll();
res.json({
intents,
version: '1.0.0',
timestamp: new Date().toISOString()
});
}
catch (error) {
next(error);
}
});
// GET /ixp/components - Component Registry
this.app.get('/components', async (req, res, next) => {
try {
const components = this.componentRegistry.getAll();
res.json({
components,
version: '1.0.0',
timestamp: new Date().toISOString()
});
}
catch (error) {
next(error);
}
});
// POST /ixp/render - Component Resolution
this.app.post('/render', async (req, res, next) => {
try {
const { intent, options } = req.body;
if (!intent || !intent.name) {
throw ErrorFactory.invalidRequest('Missing required parameter \'intent.name\'');
}
// Validate origin if component has restrictions
const origin = req.get('Origin');
if (origin) {
const intentDef = this.intentRegistry.get(intent.name);
if (intentDef) {
const componentDef = this.componentRegistry.get(intentDef.component);
if (componentDef && !this.componentRegistry.isOriginAllowed(componentDef.name, origin)) {
throw ErrorFactory.originNotAllowed(origin, componentDef.name);
}
}
}
const result = await this.intentResolver.resolveIntent(intent, options);
res.json(result);
}
catch (error) {
next(error);
}
});
// GET /ixp/crawler_content - Crawler Content
this.app.get('/crawler_content', async (req, res, next) => {
try {
const options = {
cursor: req.query.cursor,
limit: Math.min(parseInt(req.query.limit) || 100, 1000),
lastUpdated: req.query.lastUpdated,
format: req.query.format || 'json',
type: req.query.type
};
let result;
if (this.config.dataProvider?.getCrawlerContent) {
result = await this.config.dataProvider.getCrawlerContent(options);
}
else {
// Default implementation - return crawlable intents metadata
const crawlableIntents = this.intentRegistry.findByCriteria({ crawlable: true });
result = {
contents: crawlableIntents.map(intent => ({
type: 'intent',
id: intent.name,
title: intent.name,
description: intent.description,
lastUpdated: new Date().toISOString()
})),
pagination: {
nextCursor: null,
hasMore: false
},
lastUpdated: new Date().toISOString()
};
}
res.json(result);
}
catch (error) {
next(error);
}
});
// Health check endpoint
this.app.get('/health', async (req, res, next) => {
try {
const health = await this.getHealthCheck();
const statusCode = health.status === 'healthy' ? 200 :
health.status === 'degraded' ? 200 : 503;
res.status(statusCode).json(health);
}
catch (error) {
next(error);
}
});
// Metrics endpoint
if (this.config.metrics?.enabled !== false) {
const metricsEndpoint = this.config.metrics?.endpoint || '/metrics';
this.app.get(metricsEndpoint, async (req, res, next) => {
try {
const metrics = await this.getMetrics();
res.json(metrics);
}
catch (error) {
next(error);
}
});
}
this.logger.debug('IXP routes configured');
}
/**
* Setup error handling middleware
*/
setupErrorHandling() {
this.app.use((error, req, res, next) => {
const statusCode = getErrorStatusCode(error);
const errorResponse = toErrorResponse(error);
this.metricsService.recordError(error);
this.logger.error('Request error', {
error: errorResponse.error,
path: req.path,
method: req.method
});
res.status(statusCode).json(errorResponse);
});
}
/**
* Add plugin to the server
*/
async addPlugin(plugin) {
if (this.plugins.has(plugin.name)) {
throw ErrorFactory.plugin(plugin.name, 'Plugin already installed');
}
try {
await plugin.install(this);
this.plugins.set(plugin.name, plugin);
this.logger.info(`Plugin '${plugin.name}' installed successfully`);
}
catch (error) {
throw ErrorFactory.plugin(plugin.name, 'Installation failed', { error });
}
}
/**
* Remove plugin from the server
*/
async removePlugin(name) {
const plugin = this.plugins.get(name);
if (!plugin) {
throw ErrorFactory.plugin(name, 'Plugin not found');
}
try {
if (plugin.uninstall) {
await plugin.uninstall(this);
}
this.plugins.delete(name);
this.logger.info(`Plugin '${name}' removed successfully`);
}
catch (error) {
throw ErrorFactory.plugin(name, 'Removal failed', { error });
}
}
/**
* Add middleware to the server
*/
addMiddleware(middleware) {
this.middlewares.push(middleware);
this.logger.debug(`Middleware '${middleware.name}' added`);
}
/**
* Start the server
*/
async listen(port) {
if (!this.isInitialized) {
await this.initialize();
}
const serverPort = port || this.config.port || 3001;
return new Promise((resolve, reject) => {
const app = express();
app.use('/ixp', this.app);
this.server = app.listen(serverPort, () => {
this.logger.info(`🚀 IXP Server running on http://localhost:${serverPort}`);
resolve();
});
this.server.on('error', reject);
});
}
/**
* Stop the server
*/
async close() {
if (this.server) {
return new Promise((resolve) => {
this.server.close(() => {
this.logger.info('IXP Server stopped');
resolve();
});
});
}
}
/**
* Get health check information
*/
async getHealthCheck() {
const checks = {};
// Check intent registry
try {
const intentCount = this.intentRegistry.getAll().length;
checks.intents = {
status: intentCount > 0 ? 'pass' : 'warn',
message: `${intentCount} intents loaded`
};
}
catch (error) {
checks.intents = {
status: 'fail',
message: 'Failed to load intents'
};
}
// Check component registry
try {
const componentCount = this.componentRegistry.getAll().length;
checks.components = {
status: componentCount > 0 ? 'pass' : 'warn',
message: `${componentCount} components loaded`
};
}
catch (error) {
checks.components = {
status: 'fail',
message: 'Failed to load components'
};
}
// Determine overall status
const hasFailures = Object.values(checks).some(check => check.status === 'fail');
const hasWarnings = Object.values(checks).some(check => check.status === 'warn');
const status = hasFailures ? 'unhealthy' : hasWarnings ? 'degraded' : 'healthy';
return {
status,
service: 'IXP Server',
version: '1.0.0',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
checks
};
}
/**
* Get metrics information
*/
async getMetrics() {
return this.metricsService.getMetrics();
}
/**
* Normalize configuration with defaults
*/
normalizeConfig(config) {
return {
port: 3001,
cors: {
origins: ['http://localhost:3000'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
},
security: {
helmet: true
},
logging: {
level: 'info',
format: 'text'
},
metrics: {
enabled: true,
endpoint: '/metrics'
},
...config
};
}
/**
* Cleanup resources
*/
async destroy() {
await this.close();
// Cleanup registries
this.intentRegistry.destroy();
this.componentRegistry.destroy();
// Uninstall plugins
for (const [name, plugin] of Array.from(this.plugins.entries())) {
try {
if (plugin.uninstall) {
await plugin.uninstall(this);
}
}
catch (error) {
this.logger.error(`Failed to uninstall plugin '${name}'`, error);
}
}
this.plugins.clear();
this.middlewares.length = 0;
this.logger.info('IXP Server destroyed');
}
}
//# sourceMappingURL=IXPServer.js.map