chore: recovery checkpoint - v3.0 API successfully recovered
CRITICAL CHECKPOINT - DO NOT PUSH TO GITHUB Recovery Status: - Successfully recovered brainy.ts from compiled JavaScript - All core v3.0 API methods functional (add, get, update, delete, relate, find, etc.) - Neural subsystem intact (562KB embedded patterns, NLP working) - Augmentation pipeline operational (20+ augmentations) - HNSW clustering system complete - Triple Intelligence compiled (needs constructor fix) - Test suite validates functionality Changes preserved: - 898 files with changes from last 3 days - 144,475 insertions - All augmentation improvements - All test coverage enhancements - Complete v3.0 feature set This is a LOCAL checkpoint only - contains recovered work after corruption incident. Created backup in .backups/brainy-full-20250910-151314.tar.gz Branch: recovery-checkpoint-20250910-151433 Date: Wed Sep 10 03:18:04 PM PDT 2025
This commit is contained in:
parent
f65455fb22
commit
8ff382ca3b
895 changed files with 143654 additions and 28268 deletions
|
|
@ -0,0 +1,503 @@
|
|||
/**
|
||||
* API Server Augmentation - Universal API Exposure
|
||||
*
|
||||
* 🌐 Exposes Brainy through REST, WebSocket, and MCP
|
||||
* 🔌 Works in Node.js, Deno, and Service Workers
|
||||
* 🚀 Single augmentation for all API needs
|
||||
*
|
||||
* This unifies and replaces:
|
||||
* - BrainyMCPBroadcast (Node-specific server)
|
||||
* - WebSocketConduitAugmentation (client connections)
|
||||
* - Future REST API implementations
|
||||
*/
|
||||
import { BaseAugmentation } from './brainyAugmentation.js';
|
||||
import { BrainyMCPService } from '../mcp/brainyMCPService.js';
|
||||
import { v4 as uuidv4 } from '../universal/uuid.js';
|
||||
import { isNode, isBrowser } from '../utils/environment.js';
|
||||
/**
|
||||
* Unified API Server Augmentation
|
||||
* Exposes Brainy through multiple protocols
|
||||
*/
|
||||
export class APIServerAugmentation extends BaseAugmentation {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
this.name = 'api-server';
|
||||
this.timing = 'after';
|
||||
this.metadata = 'readonly'; // API server reads metadata to serve data
|
||||
this.operations = ['all'];
|
||||
this.priority = 5; // Low priority, runs after other augmentations
|
||||
this.clients = new Map();
|
||||
this.operationHistory = [];
|
||||
this.maxHistorySize = 1000;
|
||||
this.config = {
|
||||
enabled: true,
|
||||
port: 3000,
|
||||
host: '0.0.0.0',
|
||||
cors: { origin: '*', credentials: true },
|
||||
auth: { required: false },
|
||||
rateLimit: { windowMs: 60000, max: 100 },
|
||||
...config
|
||||
};
|
||||
}
|
||||
async onInitialize() {
|
||||
if (!this.config.enabled) {
|
||||
this.log('API Server disabled in config');
|
||||
return;
|
||||
}
|
||||
// Initialize MCP service
|
||||
this.mcpService = new BrainyMCPService(this.context.brain, {
|
||||
enableAuth: this.config.auth?.required
|
||||
});
|
||||
// Start appropriate server based on environment
|
||||
if (isNode()) {
|
||||
await this.startNodeServer();
|
||||
}
|
||||
else if (typeof globalThis.Deno !== 'undefined') {
|
||||
await this.startDenoServer();
|
||||
}
|
||||
else if (isBrowser() && 'serviceWorker' in navigator) {
|
||||
await this.startServiceWorker();
|
||||
}
|
||||
else {
|
||||
this.log('No suitable server environment detected', 'warn');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start Node.js server with Express
|
||||
*/
|
||||
async startNodeServer() {
|
||||
try {
|
||||
// Dynamic imports for Node.js dependencies
|
||||
const express = await import('express').catch(() => null);
|
||||
const cors = await import('cors').catch(() => null);
|
||||
const ws = await import('ws').catch(() => null);
|
||||
const { createServer } = await import('http');
|
||||
if (!express || !cors || !ws) {
|
||||
this.log('Express, cors, or ws not available. Install with: npm install express cors ws', 'error');
|
||||
return;
|
||||
}
|
||||
const WebSocketServer = ws?.WebSocketServer || ws?.default?.WebSocketServer || ws?.Server;
|
||||
const app = express.default();
|
||||
// Middleware
|
||||
app.use(cors.default(this.config.cors));
|
||||
app.use((express.default || express).json({ limit: '50mb' }));
|
||||
app.use(this.authMiddleware.bind(this));
|
||||
app.use(this.rateLimitMiddleware.bind(this));
|
||||
// REST API Routes
|
||||
this.setupRESTRoutes(app);
|
||||
// Create HTTP server
|
||||
this.httpServer = createServer(app);
|
||||
// WebSocket server
|
||||
this.wsServer = new WebSocketServer({
|
||||
server: this.httpServer,
|
||||
path: '/ws'
|
||||
});
|
||||
this.setupWebSocketServer();
|
||||
// Start listening
|
||||
await new Promise((resolve, reject) => {
|
||||
this.httpServer.listen(this.config.port, this.config.host, () => {
|
||||
this.log(`🌐 API Server listening on http://${this.config.host}:${this.config.port}`);
|
||||
this.log(`🔌 WebSocket: ws://${this.config.host}:${this.config.port}/ws`);
|
||||
this.log(`🧠 MCP endpoint: http://${this.config.host}:${this.config.port}/api/mcp`);
|
||||
resolve();
|
||||
}).on('error', reject);
|
||||
});
|
||||
// Heartbeat interval
|
||||
setInterval(() => this.sendHeartbeats(), 30000);
|
||||
}
|
||||
catch (error) {
|
||||
this.log(`Failed to start Node.js server: ${error}`, 'error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Setup REST API routes
|
||||
*/
|
||||
setupRESTRoutes(app) {
|
||||
// Health check
|
||||
app.get('/health', (_req, res) => {
|
||||
res.json({
|
||||
status: 'healthy',
|
||||
version: '2.0.0',
|
||||
clients: this.clients.size,
|
||||
uptime: process.uptime ? process.uptime() : 0
|
||||
});
|
||||
});
|
||||
// Search endpoint
|
||||
app.post('/api/search', async (req, res) => {
|
||||
try {
|
||||
const { query, limit = 10, options = {} } = req.body;
|
||||
const results = await this.context.brain.search(query, limit, options);
|
||||
res.json({ success: true, results });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Add data endpoint
|
||||
app.post('/api/add', async (req, res) => {
|
||||
try {
|
||||
const { content, metadata } = req.body;
|
||||
const id = await this.context.brain.addNoun(content, 'Content', metadata);
|
||||
res.json({ success: true, id });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Get by ID endpoint
|
||||
app.get('/api/get/:id', async (req, res) => {
|
||||
try {
|
||||
const data = await this.context.brain.get(req.params.id);
|
||||
if (data) {
|
||||
res.json({ success: true, data });
|
||||
}
|
||||
else {
|
||||
res.status(404).json({ success: false, error: 'Not found' });
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Delete endpoint
|
||||
app.delete('/api/delete/:id', async (req, res) => {
|
||||
try {
|
||||
await this.context.brain.delete(req.params.id);
|
||||
res.json({ success: true });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Relate endpoint
|
||||
app.post('/api/relate', async (req, res) => {
|
||||
try {
|
||||
const { source, target, verb, metadata } = req.body;
|
||||
await this.context.brain.relate(source, target, verb, metadata);
|
||||
res.json({ success: true });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Find endpoint (complex queries)
|
||||
app.post('/api/find', async (req, res) => {
|
||||
try {
|
||||
const results = await this.context.brain.find(req.body);
|
||||
res.json({ success: true, results });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Cluster endpoint
|
||||
app.post('/api/cluster', async (req, res) => {
|
||||
try {
|
||||
const { algorithm = 'kmeans', options = {} } = req.body;
|
||||
const clusters = await this.context.brain.cluster(algorithm, options);
|
||||
res.json({ success: true, clusters });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// MCP endpoint
|
||||
app.post('/api/mcp', async (req, res) => {
|
||||
try {
|
||||
const response = await this.mcpService.handleRequest(req.body);
|
||||
res.json(response);
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Statistics endpoint
|
||||
app.get('/api/stats', async (_req, res) => {
|
||||
try {
|
||||
const stats = await this.context.brain.getStatistics();
|
||||
res.json({ success: true, stats });
|
||||
}
|
||||
catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
// Operation history endpoint
|
||||
app.get('/api/history', (_req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
history: this.operationHistory.slice(-100)
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Setup WebSocket server
|
||||
*/
|
||||
setupWebSocketServer() {
|
||||
if (!this.wsServer)
|
||||
return;
|
||||
this.wsServer.on('connection', (socket, request) => {
|
||||
const clientId = uuidv4();
|
||||
const client = {
|
||||
id: clientId,
|
||||
type: 'websocket',
|
||||
socket,
|
||||
subscriptions: [],
|
||||
lastSeen: Date.now()
|
||||
};
|
||||
this.clients.set(clientId, client);
|
||||
// Send welcome message
|
||||
socket.send(JSON.stringify({
|
||||
type: 'welcome',
|
||||
clientId,
|
||||
message: 'Connected to Brainy API Server',
|
||||
capabilities: ['search', 'add', 'delete', 'relate', 'subscribe', 'mcp']
|
||||
}));
|
||||
// Handle messages
|
||||
socket.on('message', async (message) => {
|
||||
try {
|
||||
const msg = JSON.parse(message);
|
||||
await this.handleWebSocketMessage(msg, client);
|
||||
}
|
||||
catch (error) {
|
||||
socket.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: error.message
|
||||
}));
|
||||
}
|
||||
});
|
||||
// Handle disconnect
|
||||
socket.on('close', () => {
|
||||
this.clients.delete(clientId);
|
||||
this.log(`Client ${clientId} disconnected`);
|
||||
});
|
||||
// Handle errors
|
||||
socket.on('error', (error) => {
|
||||
this.log(`WebSocket error for client ${clientId}: ${error}`, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Handle WebSocket message
|
||||
*/
|
||||
async handleWebSocketMessage(msg, client) {
|
||||
const { socket } = client;
|
||||
switch (msg.type) {
|
||||
case 'subscribe':
|
||||
// Subscribe to operation types
|
||||
client.subscriptions = msg.operations || ['all'];
|
||||
socket.send(JSON.stringify({
|
||||
type: 'subscribed',
|
||||
operations: client.subscriptions
|
||||
}));
|
||||
break;
|
||||
case 'search':
|
||||
const searchResults = await this.context.brain.search(msg.query, msg.limit || 10, msg.options || {});
|
||||
socket.send(JSON.stringify({
|
||||
type: 'searchResults',
|
||||
requestId: msg.requestId,
|
||||
results: searchResults
|
||||
}));
|
||||
break;
|
||||
case 'add':
|
||||
const id = await this.context.brain.addNoun(msg.content, 'Content', msg.metadata);
|
||||
socket.send(JSON.stringify({
|
||||
type: 'addResult',
|
||||
requestId: msg.requestId,
|
||||
id
|
||||
}));
|
||||
break;
|
||||
case 'mcp':
|
||||
const mcpResponse = await this.mcpService.handleRequest(msg.request);
|
||||
socket.send(JSON.stringify({
|
||||
type: 'mcpResponse',
|
||||
requestId: msg.requestId,
|
||||
response: mcpResponse
|
||||
}));
|
||||
break;
|
||||
case 'heartbeat':
|
||||
client.lastSeen = Date.now();
|
||||
socket.send(JSON.stringify({
|
||||
type: 'heartbeat',
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
break;
|
||||
default:
|
||||
socket.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: `Unknown message type: ${msg.type}`
|
||||
}));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Execute augmentation - broadcast operations to clients
|
||||
*/
|
||||
async execute(operation, params, next) {
|
||||
const startTime = Date.now();
|
||||
const result = await next();
|
||||
const duration = Date.now() - startTime;
|
||||
// Record operation in history
|
||||
const historyEntry = {
|
||||
operation,
|
||||
params: this.sanitizeParams(params),
|
||||
timestamp: Date.now(),
|
||||
duration
|
||||
};
|
||||
this.operationHistory.push(historyEntry);
|
||||
if (this.operationHistory.length > this.maxHistorySize) {
|
||||
this.operationHistory.shift();
|
||||
}
|
||||
// Broadcast to subscribed WebSocket clients
|
||||
const message = JSON.stringify({
|
||||
type: 'operation',
|
||||
operation,
|
||||
params: historyEntry.params,
|
||||
timestamp: historyEntry.timestamp,
|
||||
duration
|
||||
});
|
||||
for (const client of this.clients.values()) {
|
||||
if (client.type === 'websocket' && client.socket) {
|
||||
if (client.subscriptions?.includes('all') ||
|
||||
client.subscriptions?.includes(operation)) {
|
||||
try {
|
||||
client.socket.send(message);
|
||||
}
|
||||
catch (error) {
|
||||
// Client might be disconnected
|
||||
this.clients.delete(client.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Auth middleware for Express
|
||||
*/
|
||||
authMiddleware(req, res, next) {
|
||||
if (!this.config.auth?.required) {
|
||||
return next();
|
||||
}
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
const bearerToken = req.headers.authorization?.replace('Bearer ', '');
|
||||
if (apiKey && this.config.auth.apiKeys?.includes(apiKey)) {
|
||||
return next();
|
||||
}
|
||||
if (bearerToken && this.config.auth.bearerTokens?.includes(bearerToken)) {
|
||||
return next();
|
||||
}
|
||||
res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
/**
|
||||
* Rate limiting middleware
|
||||
*/
|
||||
rateLimitMiddleware(req, res, next) {
|
||||
// Simple in-memory rate limiting
|
||||
// In production, use redis or proper rate limiting library
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
const now = Date.now();
|
||||
const windowMs = this.config.rateLimit?.windowMs || 60000;
|
||||
const max = this.config.rateLimit?.max || 100;
|
||||
// Clean old entries
|
||||
for (const [key, client] of this.clients.entries()) {
|
||||
if (now - client.lastSeen > windowMs) {
|
||||
this.clients.delete(key);
|
||||
}
|
||||
}
|
||||
// For now, just pass through
|
||||
// Real implementation would track requests per IP
|
||||
next();
|
||||
}
|
||||
/**
|
||||
* Sanitize parameters before broadcasting
|
||||
*/
|
||||
sanitizeParams(params) {
|
||||
if (!params)
|
||||
return params;
|
||||
const sanitized = { ...params };
|
||||
// Remove sensitive fields
|
||||
delete sanitized.password;
|
||||
delete sanitized.apiKey;
|
||||
delete sanitized.token;
|
||||
delete sanitized.secret;
|
||||
// Truncate large data
|
||||
if (sanitized.content && sanitized.content.length > 1000) {
|
||||
sanitized.content = sanitized.content.substring(0, 1000) + '...';
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
/**
|
||||
* Send heartbeats to all connected clients
|
||||
*/
|
||||
sendHeartbeats() {
|
||||
const now = Date.now();
|
||||
for (const [id, client] of this.clients.entries()) {
|
||||
if (client.type === 'websocket' && client.socket) {
|
||||
// Remove inactive clients
|
||||
if (now - client.lastSeen > 60000) {
|
||||
this.clients.delete(id);
|
||||
continue;
|
||||
}
|
||||
// Send heartbeat
|
||||
try {
|
||||
client.socket.send(JSON.stringify({
|
||||
type: 'heartbeat',
|
||||
timestamp: now
|
||||
}));
|
||||
}
|
||||
catch {
|
||||
// Client disconnected
|
||||
this.clients.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start Deno server
|
||||
*/
|
||||
async startDenoServer() {
|
||||
// Deno implementation would go here
|
||||
// Using Deno.serve() or oak framework
|
||||
this.log('Deno server not yet implemented', 'warn');
|
||||
}
|
||||
/**
|
||||
* Start Service Worker (for browser)
|
||||
*/
|
||||
async startServiceWorker() {
|
||||
// Service Worker implementation would go here
|
||||
// Intercepts fetch() calls and handles them locally
|
||||
this.log('Service Worker API not yet implemented', 'warn');
|
||||
}
|
||||
/**
|
||||
* Shutdown the server
|
||||
*/
|
||||
async onShutdown() {
|
||||
// Close all WebSocket connections
|
||||
for (const client of this.clients.values()) {
|
||||
if (client.socket) {
|
||||
try {
|
||||
client.socket.close();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
this.clients.clear();
|
||||
// Close servers
|
||||
if (this.wsServer) {
|
||||
this.wsServer.close();
|
||||
}
|
||||
if (this.httpServer) {
|
||||
await new Promise(resolve => {
|
||||
this.httpServer.close(() => resolve());
|
||||
});
|
||||
}
|
||||
this.log('API Server shut down');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper function to create and configure API server
|
||||
*/
|
||||
export function createAPIServer(config) {
|
||||
return new APIServerAugmentation(config);
|
||||
}
|
||||
//# sourceMappingURL=apiServerAugmentation.js.map
|
||||
Loading…
Add table
Add a link
Reference in a new issue