diff --git a/examples/brainy-service-template/.gitignore b/examples/brainy-service-template/.gitignore new file mode 100644 index 00000000..ef749984 --- /dev/null +++ b/examples/brainy-service-template/.gitignore @@ -0,0 +1,149 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env.production +.env.local +.env.development.local +.env.test.local +.env.production.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +public + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Dependency directory +node_modules + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Brainy data directories +data/ +*.brainy +*.db + +# Docker +.dockerignore + +# Test artifacts +test-results/ +coverage/ + +# Build outputs +build/ +dist/ \ No newline at end of file diff --git a/examples/brainy-service-template/Dockerfile b/examples/brainy-service-template/Dockerfile new file mode 100644 index 00000000..7a4f0955 --- /dev/null +++ b/examples/brainy-service-template/Dockerfile @@ -0,0 +1,44 @@ +FROM node:24-alpine + +# Set working directory +WORKDIR /app + +# Install system dependencies +RUN apk add --no-cache \ + python3 \ + make \ + g++ \ + git + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Download Brainy models for offline operation +RUN npm run download-models + +# Copy application code +COPY . . + +# Create logs directory +RUN mkdir -p logs + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S brainy -u 1001 -G nodejs + +# Change ownership +RUN chown -R brainy:nodejs /app +USER brainy + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD node -e "fetch('http://localhost:3000/health/liveness').then(r=>r.ok?process.exit(0):process.exit(1)).catch(()=>process.exit(1))" + +# Start the application +CMD ["npm", "start"] \ No newline at end of file diff --git a/examples/brainy-service-template/README.md b/examples/brainy-service-template/README.md new file mode 100644 index 00000000..20ea3bab --- /dev/null +++ b/examples/brainy-service-template/README.md @@ -0,0 +1,407 @@ +# Brainy Service Template + +**Zero-configuration, intelligent service template built with Brainy's native augmentation system.** + +This template embodies Brainy's core tenets: **zero configuration**, **intelligent adaptation**, and **augmentation-first architecture**. It automatically adapts to any environment (browser, Node.js, serverless, containers) and uses WebRTC, WebSocket, and HTTP transport layers as needed. + +## Features + +🧠 **Zero Configuration** +- Auto-detects optimal storage (memory → filesystem → S3) +- Intelligent transport selection (WebRTC → WebSocket → HTTP) +- Environment adaptation (browser, Node.js, serverless, containers) +- Self-optimizing performance and caching + +🔌 **Augmentation-First Architecture** +- WebSocket Augmentation - Real-time queries and updates +- WebRTC Augmentation - Peer-to-peer Brainy connections +- HTTP Augmentation - Minimal REST API for universal access +- Auto-Discovery Augmentation - Understand your data patterns +- Adaptive Storage Augmentation - Intelligent resource management +- Environment Adapter - Works everywhere automatically + +🌐 **Universal Compatibility** +- Browser (OPFS, IndexedDB, WebRTC peer-to-peer) +- Node.js (filesystem, worker threads, clustering) +- Serverless (memory, fast cold starts) +- Docker/Containers (volumes, health checks) +- Kubernetes (pod-aware, service mesh ready) +- Edge computing (ultra-low latency, minimal footprint) + +⚡ **Intelligent Features** +- Intelligent Verb Scoring (enabled by default) +- Automatic relationship weighting and confidence scoring +- Learning from usage patterns and feedback +- Real-time performance optimization +- Data quality analysis and recommendations + +## Quick Start + +### 1. Zero-Config Instant Start + +```bash +# Copy template +cp -r examples/brainy-service-template my-brainy-service +cd my-brainy-service +npm install + +# Start with ZERO configuration - everything auto-detected! +npm start +``` + +**That's it!** The service automatically: +- ✅ Detects your environment (Node.js, browser, container, etc.) +- ✅ Chooses optimal storage (memory → filesystem → S3) +- ✅ Enables best transport layers (WebRTC → WebSocket → HTTP) +- ✅ Configures intelligent verb scoring +- ✅ Sets up real-time capabilities +- ✅ Optimizes performance for your hardware + +### 2. Access Your Brainy Service + +The service automatically provides multiple ways to interact: + +```javascript +// WebSocket (real-time, best performance) +const ws = new WebSocket('ws://localhost:3001') +ws.send(JSON.stringify({ + type: 'search', + payload: { query: 'machine learning', limit: 10 } +})) + +// WebRTC (peer-to-peer, direct connection) +// Client code automatically generated at http://localhost:3002 + +// HTTP (universal fallback) +curl -X POST http://localhost:3000/search \ + -H "Content-Type: application/json" \ + -d '{"query": "machine learning", "limit": 10}' +``` + +### 3. Optional: Environment Variables + +Only set these if you want to override the intelligent defaults: + +```bash +# Storage preference (auto-detected by default) +export BRAINY_STORAGE_TYPE=s3 +export BRAINY_S3_BUCKET=my-brainy-data + +# Transport preference (all enabled by default) +export BRAINY_TRANSPORTS=websocket,http # disable WebRTC + +# Port preference (3000 by default) +export PORT=8080 +``` + +## API Endpoints + +### Entities +- `POST /api/entities` - Create entity +- `GET /api/entities/:id` - Get entity by ID +- `PUT /api/entities/:id` - Update entity +- `DELETE /api/entities/:id` - Delete entity +- `GET /api/entities` - List entities (paginated) +- `POST /api/entities/search` - Search entities + +### Relationships +- `POST /api/relationships` - Create relationship +- `GET /api/relationships/:id` - Get relationship by ID +- `PUT /api/relationships/:id` - Update relationship +- `DELETE /api/relationships/:id` - Delete relationship +- `GET /api/relationships` - List relationships (paginated) + +### Intelligent Scoring (when enabled) +- `POST /api/scoring/feedback/:id` - Provide feedback for learning +- `GET /api/scoring/stats` - Get scoring statistics +- `POST /api/scoring/export` - Export learning data +- `POST /api/scoring/import` - Import learning data +- `DELETE /api/scoring/stats` - Clear statistics +- `POST /api/scoring/analyze` - Analyze relationship patterns +- `GET /api/scoring/recommendations/:entityId` - Get recommendations + +### Health & Monitoring +- `GET /health` - Overall health check +- `GET /health/liveness` - Kubernetes liveness probe +- `GET /health/readiness` - Kubernetes readiness probe +- `GET /health/metrics` - System metrics + +## Configuration Options + +### Basic Configuration + +```json +{ + "service": { + "name": "my-service", + "port": 3000, + "cors": { + "enabled": true, + "origins": ["http://localhost:3000"] + } + }, + "brainy": { + "storage": { + "type": "filesystem|memory|s3", + "path": "./data", + "s3": { + "bucket": "my-bucket", + "region": "us-east-1" + } + } + } +} +``` + +### Advanced Features + +```json +{ + "brainy": { + "features": { + "intelligentVerbScoring": true, + "realTimeUpdates": true, + "distributedMode": false + }, + "intelligentVerbScoring": { + "enabled": true, + "enableSemanticScoring": true, + "enableFrequencyAmplification": true, + "enableTemporalDecay": true, + "temporalDecayRate": 0.01, + "minWeight": 0.1, + "maxWeight": 1.0, + "baseConfidence": 0.5, + "learningRate": 0.1 + }, + "cache": { + "autoTune": true, + "hotCacheMaxSize": 10000 + } + } +} +``` + +## Deployment + +### Development + +```bash +npm run dev +``` + +### Production (Local) + +```bash +npm run build +npm start +``` + +### Docker + +```bash +docker build -t my-brainy-service . +docker run -p 3000:3000 my-brainy-service +``` + +### Docker Compose + +```bash +docker-compose up +``` + +## Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `NODE_ENV` | Environment | `development` | +| `PORT` | Server port | `3000` | +| `BRAINY_STORAGE_TYPE` | Storage type | `filesystem` | +| `BRAINY_STORAGE_PATH` | Storage path | `./data` | +| `BRAINY_INTELLIGENT_SCORING` | Enable scoring | `false` | +| `LOG_LEVEL` | Logging level | `info` | + +## Examples + +### Adding Entities with Smart Relationships + +```javascript +// Add entities +const person = await fetch('/entities', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + data: 'John is a senior software developer with expertise in React and Node.js', + metadata: { type: 'person', name: 'John' } + }) +}) + +const project = await fetch('/entities', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + data: 'E-commerce platform built with React and Node.js', + metadata: { type: 'project', name: 'ShopApp' } + }) +}) + +// Add relationship (with intelligent scoring if enabled) +const relationship = await fetch('/relationships', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + sourceId: person.id, + targetId: project.id, + type: 'contributesTo', + // weight is automatically computed if intelligent scoring is enabled + metadata: { role: 'lead developer' } + }) +}) +``` + +### Providing Learning Feedback + +```javascript +// Correct a relationship weight (helps the system learn) +await fetch(`/relationships/${relationshipId}/feedback`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + weight: 0.9, // corrected weight + confidence: 0.85, // corrected confidence + type: 'correction' + }) +}) +``` + +### Semantic Search + +```javascript +const results = await fetch('/entities/search', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + query: 'experienced React developer', + limit: 10, + threshold: 0.7 + }) +}) +``` + +## Directory Structure + +``` +brainy-service-template/ +├── src/ +│ ├── controllers/ +│ │ ├── entities.js +│ │ ├── relationships.js +│ │ └── scoring.js +│ ├── middleware/ +│ │ ├── auth.js +│ │ ├── validation.js +│ │ └── errorHandler.js +│ ├── services/ +│ │ ├── brainyService.js +│ │ └── scoringService.js +│ ├── utils/ +│ │ ├── config.js +│ │ ├── logger.js +│ │ └── helpers.js +│ └── app.js +├── config/ +│ ├── default.json +│ ├── development.json +│ └── production.json +├── docker/ +│ ├── Dockerfile +│ └── docker-compose.yml +├── tests/ +│ ├── integration/ +│ └── unit/ +└── docs/ + └── api.md +``` + +## Testing + +```bash +# Run all tests +npm test + +# Run with coverage +npm run test:coverage + +# Run integration tests +npm run test:integration +``` + +## Customization + +### Adding Custom Endpoints + +```javascript +// src/controllers/custom.js +export const customEndpoint = async (req, res) => { + const { brainyService } = req.app.locals + + // Your custom logic using Brainy + const results = await brainyService.search(req.body.query) + + res.json({ results }) +} +``` + +### Custom Augmentations + +```javascript +// src/services/customAugmentation.js +import { ICognitionAugmentation } from '@soulcraft/brainy' + +export class CustomAugmentation implements ICognitionAugmentation { + // Your custom augmentation logic +} +``` + +## Monitoring & Observability + +The template includes built-in monitoring: + +- **Health checks**: `/health` endpoint +- **Metrics**: Request counts, response times, error rates +- **Logging**: Structured JSON logging with correlation IDs +- **Performance**: Automatic performance tracking for Brainy operations + +## Production Considerations + +### Security + +- Input validation and sanitization +- Rate limiting +- CORS configuration +- Environment-based secrets management + +### Performance + +- Connection pooling +- Request caching +- Automatic cache tuning +- Background processing for heavy operations + +### Reliability + +- Graceful shutdown handling +- Circuit breaker patterns +- Retry logic with exponential backoff +- Health checks for dependencies + +## Support & Documentation + +- [Brainy Documentation](../../docs/) +- [Intelligent Verb Scoring Guide](../../docs/guides/intelligent-verb-scoring.md) +- [API Reference](./docs/api.md) +- [Configuration Examples](./config/) + +## License + +This template is provided under the same license as Brainy. \ No newline at end of file diff --git a/examples/brainy-service-template/config/default.json b/examples/brainy-service-template/config/default.json new file mode 100644 index 00000000..b03b0cf5 --- /dev/null +++ b/examples/brainy-service-template/config/default.json @@ -0,0 +1,52 @@ +{ + "service": { + "name": "brainy-service-template", + "port": 3000, + "cors": { + "enabled": true, + "origins": ["http://localhost:3000", "http://localhost:3001"] + }, + "rateLimit": { + "windowMs": 900000, + "maxRequests": 100 + } + }, + "brainy": { + "storage": { + "type": "filesystem", + "path": "./data" + }, + "features": { + "intelligentVerbScoring": false, + "realTimeUpdates": false, + "distributedMode": false, + "metadataIndexing": true + }, + "intelligentVerbScoring": { + "enabled": false, + "enableSemanticScoring": true, + "enableFrequencyAmplification": true, + "enableTemporalDecay": true, + "temporalDecayRate": 0.01, + "minWeight": 0.1, + "maxWeight": 1.0, + "baseConfidence": 0.5, + "learningRate": 0.1 + }, + "cache": { + "autoTune": true, + "hotCacheMaxSize": 5000 + }, + "logging": { + "verbose": false + } + }, + "logging": { + "level": "info", + "format": "json", + "timestamp": true + }, + "health": { + "timeout": 5000 + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/config/development.json b/examples/brainy-service-template/config/development.json new file mode 100644 index 00000000..15f67d92 --- /dev/null +++ b/examples/brainy-service-template/config/development.json @@ -0,0 +1,20 @@ +{ + "service": { + "port": 3000 + }, + "brainy": { + "features": { + "intelligentVerbScoring": true + }, + "intelligentVerbScoring": { + "enabled": true, + "baseConfidence": 0.6 + }, + "logging": { + "verbose": true + } + }, + "logging": { + "level": "debug" + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/config/production.json b/examples/brainy-service-template/config/production.json new file mode 100644 index 00000000..ddab73e7 --- /dev/null +++ b/examples/brainy-service-template/config/production.json @@ -0,0 +1,28 @@ +{ + "service": { + "cors": { + "origins": ["https://yourdomain.com"] + }, + "rateLimit": { + "windowMs": 900000, + "maxRequests": 1000 + } + }, + "brainy": { + "storage": { + "type": "s3", + "s3": { + "bucketName": "${BRAINY_S3_BUCKET}", + "region": "${BRAINY_S3_REGION}", + "accessKeyId": "${BRAINY_S3_ACCESS_KEY}", + "secretAccessKey": "${BRAINY_S3_SECRET_KEY}" + } + }, + "cache": { + "hotCacheMaxSize": 50000 + } + }, + "logging": { + "level": "warn" + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/config/test.json b/examples/brainy-service-template/config/test.json new file mode 100644 index 00000000..82822ab5 --- /dev/null +++ b/examples/brainy-service-template/config/test.json @@ -0,0 +1,35 @@ +{ + "service": { + "name": "brainy-service-test", + "port": 0 + }, + "brainy": { + "storage": { + "type": "memory" + }, + "features": { + "intelligentVerbScoring": true, + "realTimeUpdates": false, + "distributedMode": false + }, + "intelligentVerbScoring": { + "enabled": true, + "enableSemanticScoring": true, + "enableFrequencyAmplification": true, + "enableTemporalDecay": false, + "baseConfidence": 0.6, + "learningRate": 0.1 + }, + "cache": { + "autoTune": false, + "hotCacheMaxSize": 1000 + }, + "logging": { + "level": "error" + } + }, + "logging": { + "level": "error", + "format": "json" + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/docker-compose.yml b/examples/brainy-service-template/docker-compose.yml new file mode 100644 index 00000000..46cfbfa4 --- /dev/null +++ b/examples/brainy-service-template/docker-compose.yml @@ -0,0 +1,77 @@ +version: '3.8' + +services: + brainy-service: + build: . + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - LOG_LEVEL=info + volumes: + - brainy_data:/app/data + - ./logs:/app/logs + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health/liveness"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + restart: unless-stopped + + # Example with S3-compatible storage (MinIO) + minio: + image: minio/minio:latest + ports: + - "9000:9000" + - "9001:9001" + environment: + - MINIO_ROOT_USER=minioadmin + - MINIO_ROOT_PASSWORD=minioadmin123 + command: server /data --console-address ":9001" + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 10s + retries: 3 + profiles: + - s3 + + # Brainy service with S3 storage + brainy-service-s3: + build: . + ports: + - "3001:3000" + environment: + - NODE_ENV=production + - LOG_LEVEL=info + - BRAINY_STORAGE_TYPE=s3 + - BRAINY_S3_BUCKET=brainy-data + - BRAINY_S3_ENDPOINT=http://minio:9000 + - BRAINY_S3_ACCESS_KEY_ID=minioadmin + - BRAINY_S3_SECRET_ACCESS_KEY=minioadmin123 + - BRAINY_S3_REGION=us-east-1 + depends_on: + minio: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/health/liveness"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + restart: unless-stopped + profiles: + - s3 + +volumes: + brainy_data: + driver: local + minio_data: + driver: local + +networks: + default: + driver: bridge \ No newline at end of file diff --git a/examples/brainy-service-template/package.json b/examples/brainy-service-template/package.json new file mode 100644 index 00000000..b83e2019 --- /dev/null +++ b/examples/brainy-service-template/package.json @@ -0,0 +1,84 @@ +{ + "name": "brainy-service-template", + "version": "2.0.0", + "description": "Zero-configuration, intelligent service template using Brainy's native augmentation system", + "main": "src/app.js", + "type": "module", + "scripts": { + "start": "node src/app.js", + "dev": "nodemon src/app.js --ignore tests/", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "build": "echo 'No build step required - zero configuration!'", + "lint": "eslint src/ --ext .js", + "format": "prettier --write src/", + "docker:build": "docker build -t brainy-service .", + "docker:run": "docker run -p 3000:3000 -p 3001:3001 -p 3002:3002 brainy-service", + "docker:compose": "docker-compose up", + "download-models": "node -e \"import('@soulcraft/brainy').then(brainy => brainy.downloadModels())\"", + "health-check": "curl -f http://localhost:3000/health || exit 1" + }, + "keywords": [ + "brainy", + "vector-database", + "graph-database", + "semantic-search", + "ai", + "machine-learning", + "template", + "service", + "augmentation", + "websocket", + "webrtc", + "zero-config", + "intelligent-adaptation", + "real-time", + "peer-to-peer", + "universal-compatibility" + ], + "dependencies": { + "@soulcraft/brainy": "^0.51.2", + "ws": "^8.14.2", + "express": "^4.18.2", + "cors": "^2.8.5", + "compression": "^1.7.4", + "winston": "^3.11.0", + "config": "^3.3.9" + }, + "devDependencies": { + "vitest": "^1.0.4", + "supertest": "^6.3.3", + "@vitest/coverage-v8": "^1.0.4", + "nodemon": "^3.0.2", + "eslint": "^8.56.0", + "prettier": "^3.1.1" + }, + "brainyConfig": { + "augmentations": [ + "websocket", + "webrtc", + "http", + "autoDiscovery", + "adaptiveStorage", + "environmentAdapter" + ], + "transports": { + "websocket": { "port": 3001, "realtime": true }, + "webrtc": { "port": 3002, "peerToPeer": true }, + "http": { "port": 3000, "minimal": true } + }, + "features": { + "zeroConfiguration": true, + "intelligentAdaptation": true, + "intelligentVerbScoring": true, + "autoDiscovery": true, + "adaptivePerformance": true + } + }, + "author": "David Snelling (david@soulcraft.com)", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/app.js b/examples/brainy-service-template/src/app.js new file mode 100644 index 00000000..6aec0ada --- /dev/null +++ b/examples/brainy-service-template/src/app.js @@ -0,0 +1,431 @@ +import { BrainyData } from '@soulcraft/brainy' +import { logger } from './utils/logger.js' + +// Brainy-native augmentations +import { WebSocketAugmentation } from './augmentations/websocketAugmentation.js' +import { WebRTCAugmentation } from './augmentations/webrtcAugmentation.js' +import { HttpAugmentation } from './augmentations/httpAugmentation.js' +import { AutoDiscoveryAugmentation } from './augmentations/autoDiscoveryAugmentation.js' +import { AdaptiveStorageAugmentation } from './augmentations/adaptiveStorageAugmentation.js' +import { EnvironmentAdapterAugmentation } from './augmentations/environmentAdapterAugmentation.js' + +/** + * Zero-configuration Brainy service template that adapts to any environment + * Uses Brainy's native augmentation system for all functionality + */ +class BrainyServiceTemplate { + constructor(options = {}) { + this.options = { + // Zero configuration by default - everything auto-detected + autoDetectEverything: true, + + // Intelligent adaptation enabled + intelligentAdaptation: true, + + // Communication preferences (auto-detected if not specified) + preferredTransports: options.transports || 'auto', // 'auto', 'websocket', 'webrtc', 'http' + + // Storage preference (auto-detected if not specified) + storagePreference: options.storage || 'auto', // 'auto', 'memory', 'filesystem', 's3', 'distributed' + + // Environment adaptation + adaptToEnvironment: true, + + ...options + } + + this.db = null + this.isInitialized = false + this.activeTransports = [] + this.capabilities = null + } + + async initialize() { + if (this.isInitialized) { + return this.db + } + + logger.info('🧠 Initializing Brainy Service Template with zero configuration...') + + try { + // Step 1: Detect environment capabilities + this.capabilities = await this.detectEnvironmentCapabilities() + logger.info('Environment capabilities detected', this.capabilities) + + // Step 2: Build intelligent Brainy configuration + const brainyConfig = await this.buildIntelligentBrainyConfig() + + // Step 3: Initialize Brainy with intelligent defaults + this.db = new BrainyData(brainyConfig) + await this.db.init() + + // Step 4: Add environment-specific augmentations + await this.addIntelligentAugmentations() + + this.isInitialized = true + + logger.info('🚀 Brainy Service Template ready!', { + storage: brainyConfig.storage?.type || 'auto-detected', + transports: this.activeTransports, + environment: this.capabilities.environment, + augmentations: this.db.augmentations?.length || 0 + }) + + return this.db + } catch (error) { + logger.error('Failed to initialize Brainy Service Template:', error) + throw error + } + } + + async detectEnvironmentCapabilities() { + const capabilities = { + // Environment detection + environment: this.detectEnvironmentType(), + + // Memory and performance + memory: { + available: Math.floor(process.memoryUsage().heapTotal / 1024 / 1024), // MB + isHighMemory: process.memoryUsage().heapTotal > 500 * 1024 * 1024 + }, + + // Network capabilities + network: await this.detectNetworkCapabilities(), + + // Storage capabilities + storage: await this.detectStorageCapabilities(), + + // Runtime characteristics + runtime: { + isLongRunning: process.env.NODE_ENV === 'production' || !!process.env.KUBERNETES_SERVICE_HOST, + isPersistent: !this.isServerlessEnvironment(), + supportsWorkers: typeof Worker !== 'undefined' || typeof require !== 'undefined' + }, + + // Platform detection + platform: { + isNode: typeof process !== 'undefined', + isBrowser: typeof window !== 'undefined', + isEdge: this.isEdgeEnvironment(), + isServerless: this.isServerlessEnvironment(), + isContainer: this.isContainerEnvironment() + } + } + + return capabilities + } + + detectEnvironmentType() { + if (typeof window !== 'undefined') return 'browser' + if (process.env.VERCEL || process.env.NETLIFY) return 'serverless' + if (process.env.KUBERNETES_SERVICE_HOST) return 'kubernetes' + if (process.env.AWS_LAMBDA_FUNCTION_NAME) return 'lambda' + if (this.isContainerEnvironment()) return 'container' + return 'node' + } + + async detectNetworkCapabilities() { + return { + supportsWebSocket: true, // Available in most environments + supportsWebRTC: typeof RTCPeerConnection !== 'undefined' || this.capabilities?.platform?.isNode, + supportsHTTP: true, + supportsPeerToPeer: !this.isServerlessEnvironment(), + hasPublicIP: !this.isPrivateNetwork(), + preferredPort: this.detectPreferredPort() + } + } + + async detectStorageCapabilities() { + const capabilities = { + memory: true, + filesystem: false, + s3: false, + distributed: false + } + + // Test filesystem access + try { + if (typeof require !== 'undefined') { + const fs = require('fs') + fs.accessSync('.', fs.constants.W_OK) + capabilities.filesystem = true + } + } catch {} + + // Detect S3 capability + capabilities.s3 = !!(process.env.AWS_REGION || process.env.S3_BUCKET || process.env.BRAINY_S3_BUCKET) + + // Detect distributed storage capability + capabilities.distributed = this.capabilities?.platform?.isContainer || this.capabilities?.environment === 'kubernetes' + + return capabilities + } + + async buildIntelligentBrainyConfig() { + const config = {} + + // Intelligent storage selection + config.storage = await this.selectOptimalStorage() + + // Intelligent verb scoring - always enabled with adaptive parameters + config.intelligentVerbScoring = { + enabled: true, + enableSemanticScoring: true, + enableFrequencyAmplification: true, + enableTemporalDecay: this.capabilities.runtime.isLongRunning, + learningRate: this.capabilities.memory.isHighMemory ? 0.15 : 0.1, + baseConfidence: 0.6, + adaptiveParameters: true + } + + // Adaptive caching based on environment + config.cache = { + autoTune: true, + hotCacheMaxSize: this.calculateOptimalCacheSize(), + adaptiveEviction: true, + persistentCache: this.capabilities.storage.filesystem + } + + // Real-time updates for persistent environments + if (this.capabilities.runtime.isPersistent) { + config.realtimeUpdates = { + enabled: true, + interval: this.capabilities.memory.isHighMemory ? 15000 : 30000, + updateStatistics: true, + updateIndex: true + } + } + + // Distributed mode for container/cloud environments + if (this.capabilities.storage.distributed) { + config.distributed = true + } + + // Performance optimizations + config.performance = { + adaptiveBatching: true, + intelligentIndexing: true, + backgroundOptimization: this.capabilities.runtime.isLongRunning, + workerThreads: this.capabilities.runtime.supportsWorkers + } + + logger.debug('Built intelligent Brainy configuration', config) + return config + } + + async selectOptimalStorage() { + // S3/Cloud storage for production cloud environments + if (this.capabilities.storage.s3 && this.capabilities.environment !== 'development') { + return { + type: 's3', + s3Storage: { + bucketName: process.env.BRAINY_S3_BUCKET || process.env.S3_BUCKET || 'brainy-data', + region: process.env.AWS_REGION || process.env.BRAINY_S3_REGION || 'us-east-1', + accessKeyId: process.env.AWS_ACCESS_KEY_ID || process.env.BRAINY_S3_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || process.env.BRAINY_S3_SECRET_ACCESS_KEY, + endpoint: process.env.BRAINY_S3_ENDPOINT || process.env.S3_ENDPOINT + } + } + } + + // Filesystem for persistent environments + if (this.capabilities.storage.filesystem && this.capabilities.runtime.isPersistent) { + const dataPath = process.env.BRAINY_DATA_PATH || './data' + try { + const fs = require('fs') + if (!fs.existsSync(dataPath)) { + fs.mkdirSync(dataPath, { recursive: true }) + } + return { type: 'filesystem', path: dataPath } + } catch {} + } + + // Memory for serverless/temporary environments + return { type: 'memory' } + } + + calculateOptimalCacheSize() { + const availableMemory = this.capabilities.memory.available + + if (availableMemory > 2000) return 100000 // High memory + if (availableMemory > 1000) return 50000 // Medium memory + if (availableMemory > 500) return 25000 // Low memory + return 10000 // Very low memory + } + + async addIntelligentAugmentations() { + // Environment adapter - always first + const envAdapter = new EnvironmentAdapterAugmentation(this.capabilities) + await this.db.addAugmentation(envAdapter) + logger.info('Added environment adapter augmentation') + + // Adaptive storage - optimizes storage usage + const adaptiveStorage = new AdaptiveStorageAugmentation(this.capabilities) + await this.db.addAugmentation(adaptiveStorage) + logger.info('Added adaptive storage augmentation') + + // Auto-discovery - helps users understand their data + const autoDiscovery = new AutoDiscoveryAugmentation() + await this.db.addAugmentation(autoDiscovery) + logger.info('Added auto-discovery augmentation') + + // Add transport augmentations based on capabilities and preferences + await this.addTransportAugmentations() + } + + async addTransportAugmentations() { + const transports = this.determineOptimalTransports() + + for (const transport of transports) { + try { + switch (transport.type) { + case 'webrtc': + if (this.capabilities.network.supportsWebRTC && this.capabilities.network.supportsPeerToPeer) { + const webrtc = new WebRTCAugmentation({ + port: transport.port, + enableDataChannels: true, + enablePeerDiscovery: true + }) + await this.db.addAugmentation(webrtc) + this.activeTransports.push('webrtc') + logger.info(`WebRTC augmentation active on port ${transport.port}`) + } + break + + case 'websocket': + if (this.capabilities.network.supportsWebSocket) { + const websocket = new WebSocketAugmentation({ + port: transport.port, + enableRealtime: true, + enableBroadcast: true + }) + await this.db.addAugmentation(websocket) + this.activeTransports.push('websocket') + logger.info(`WebSocket augmentation active on port ${transport.port}`) + } + break + + case 'http': + const http = new HttpAugmentation({ + port: transport.port, + enableCORS: true, + enableCompression: true, + minimal: true // Keep it lightweight + }) + await this.db.addAugmentation(http) + this.activeTransports.push('http') + logger.info(`HTTP augmentation active on port ${transport.port}`) + break + } + } catch (error) { + logger.warn(`Failed to add ${transport.type} transport:`, error.message) + } + } + } + + determineOptimalTransports() { + const transports = [] + const basePort = this.capabilities.network.preferredPort + + // WebRTC for peer-to-peer capable environments (best performance) + if (this.capabilities.network.supportsWebRTC && + this.capabilities.network.supportsPeerToPeer && + this.options.preferredTransports !== 'http') { + transports.push({ type: 'webrtc', port: basePort + 2, priority: 1 }) + } + + // WebSocket for real-time environments (good performance) + if (this.capabilities.network.supportsWebSocket && + this.capabilities.runtime.isPersistent && + this.options.preferredTransports !== 'http') { + transports.push({ type: 'websocket', port: basePort + 1, priority: 2 }) + } + + // HTTP as fallback (universal compatibility) + transports.push({ type: 'http', port: basePort, priority: 3 }) + + return transports.sort((a, b) => a.priority - b.priority) + } + + detectPreferredPort() { + return parseInt(process.env.PORT || process.env.BRAINY_PORT || 3000) + } + + isServerlessEnvironment() { + return !!(process.env.VERCEL || process.env.NETLIFY || process.env.AWS_LAMBDA_FUNCTION_NAME) + } + + isEdgeEnvironment() { + return !!(process.env.VERCEL_REGION || process.env.CF_PAGES) + } + + isContainerEnvironment() { + try { + const fs = require('fs') + return fs.existsSync('/.dockerenv') || !!process.env.KUBERNETES_SERVICE_HOST + } catch { + return false + } + } + + isPrivateNetwork() { + // Simple heuristic for private network detection + return !!(process.env.KUBERNETES_SERVICE_HOST || process.env.DOCKER_CONTAINER) + } + + // Public API + getDatabase() { + if (!this.isInitialized) { + throw new Error('Service not initialized. Call initialize() first.') + } + return this.db + } + + getCapabilities() { + return this.capabilities + } + + getActiveTransports() { + return this.activeTransports + } + + async shutdown() { + if (this.db && this.db.cleanup) { + await this.db.cleanup() + } + this.isInitialized = false + logger.info('🔄 Brainy Service Template shutdown complete') + } +} + +// Factory function for easy usage +export const createBrainyService = async (options = {}) => { + const service = new BrainyServiceTemplate(options) + await service.initialize() + return service +} + +// Default export +export default BrainyServiceTemplate + +// Auto-start if run directly +if (import.meta.url === `file://${process.argv[1]}`) { + const service = new BrainyServiceTemplate() + + const gracefulShutdown = async (signal) => { + logger.info(`Received ${signal}. Starting graceful shutdown...`) + await service.shutdown() + process.exit(0) + } + + process.on('SIGTERM', () => gracefulShutdown('SIGTERM')) + process.on('SIGINT', () => gracefulShutdown('SIGINT')) + + try { + await service.initialize() + logger.info('🎉 Brainy Service Template is running! Use Ctrl+C to stop.') + } catch (error) { + logger.error('Failed to start:', error) + process.exit(1) + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/augmentations/adaptiveStorageAugmentation.js b/examples/brainy-service-template/src/augmentations/adaptiveStorageAugmentation.js new file mode 100644 index 00000000..e8626cae --- /dev/null +++ b/examples/brainy-service-template/src/augmentations/adaptiveStorageAugmentation.js @@ -0,0 +1,4 @@ +import { logger } from '../utils/logger.js' + +/** + * Adaptive Storage Augmentation - Intelligently adapts storage behavior based on usage patterns\n * Automatically optimizes storage, caching, and data management without configuration\n */\nexport class AdaptiveStorageAugmentation {\n constructor(capabilities) {\n this.type = 'MEMORY'\n this.priority = 2\n this.capabilities = capabilities\n this.options = {\n monitoringInterval: 60000, // 1 minute\n adaptationThreshold: 0.8, // Adapt when metrics exceed 80% of capacity\n cacheOptimizationInterval: 300000, // 5 minutes\n storageOptimizationInterval: 900000, // 15 minutes\n }\n \n this.db = null\n this.metrics = {\n memoryUsage: [],\n storageOperations: [],\n cacheHitRate: [],\n queryPerformance: []\n }\n \n this.monitoringTimer = null\n this.cacheOptimizationTimer = null\n this.storageOptimizationTimer = null\n this.adaptations = []\n }\n\n async augment(brainyData, context) {\n this.db = brainyData\n \n try {\n // Start monitoring\n this.startMonitoring()\n \n // Start optimization timers\n this.startOptimizations()\n \n logger.info('Adaptive Storage augmentation initialized', {\n capabilities: {\n memory: `${this.capabilities.memory.available}MB`,\n storage: Object.keys(this.capabilities.storage).filter(k => this.capabilities.storage[k]),\n environment: this.capabilities.environment\n },\n monitoring: {\n interval: this.options.monitoringInterval,\n adaptationThreshold: this.options.adaptationThreshold\n }\n })\n \n } catch (error) {\n logger.error('Failed to initialize Adaptive Storage augmentation:', error)\n throw error\n }\n }\n\n startMonitoring() {\n this.monitoringTimer = setInterval(() => {\n this.collectMetrics().catch(error => {\n logger.warn('Metrics collection failed:', error)\n })\n }, this.options.monitoringInterval)\n }\n\n startOptimizations() {\n // Cache optimization\n this.cacheOptimizationTimer = setInterval(() => {\n this.optimizeCache().catch(error => {\n logger.warn('Cache optimization failed:', error)\n })\n }, this.options.cacheOptimizationInterval)\n \n // Storage optimization\n this.storageOptimizationTimer = setInterval(() => {\n this.optimizeStorage().catch(error => {\n logger.warn('Storage optimization failed:', error)\n })\n }, this.options.storageOptimizationInterval)\n }\n\n async collectMetrics() {\n const timestamp = Date.now()\n \n try {\n // Memory metrics\n const memoryUsage = process.memoryUsage()\n this.metrics.memoryUsage.push({\n timestamp,\n heapUsed: memoryUsage.heapUsed,\n heapTotal: memoryUsage.heapTotal,\n external: memoryUsage.external,\n utilization: memoryUsage.heapUsed / memoryUsage.heapTotal\n })\n \n // Keep only recent metrics (last 30 readings)\n if (this.metrics.memoryUsage.length > 30) {\n this.metrics.memoryUsage = this.metrics.memoryUsage.slice(-30)\n }\n \n // Trigger adaptations if needed\n await this.checkAdaptationTriggers()\n \n } catch (error) {\n logger.warn('Failed to collect some metrics:', error)\n }\n }\n\n async checkAdaptationTriggers() {\n const latestMemory = this.metrics.memoryUsage[this.metrics.memoryUsage.length - 1]\n \n if (!latestMemory) return\n \n // High memory usage adaptation\n if (latestMemory.utilization > this.options.adaptationThreshold) {\n await this.adaptToHighMemoryUsage(latestMemory)\n }\n \n // Low memory usage optimization\n if (latestMemory.utilization < 0.3 && this.capabilities.memory.available > 1000) {\n await this.adaptToLowMemoryUsage(latestMemory)\n }\n }\n\n async adaptToHighMemoryUsage(memoryMetrics) {\n const adaptationId = `high-memory-${Date.now()}`\n \n logger.info('Adapting to high memory usage', {\n utilization: (memoryMetrics.utilization * 100).toFixed(1) + '%',\n heapUsed: Math.round(memoryMetrics.heapUsed / 1024 / 1024) + 'MB'\n })\n \n const actions = []\n \n // Reduce cache size\n if (this.db.cache && this.db.cache.setMaxSize) {\n const currentMaxSize = this.db.cache.getMaxSize ? this.db.cache.getMaxSize() : 10000\n const newMaxSize = Math.max(1000, Math.floor(currentMaxSize * 0.8))\n this.db.cache.setMaxSize(newMaxSize)\n actions.push(`Reduced cache size to ${newMaxSize}`)\n }\n \n // Enable more aggressive garbage collection hints\n if (global.gc) {\n global.gc()\n actions.push('Triggered garbage collection')\n }\n \n // Suggest storage upgrade if memory consistently high\n const recentHighUsage = this.metrics.memoryUsage\n .slice(-10)\n .filter(m => m.utilization > this.options.adaptationThreshold)\n .length\n \n if (recentHighUsage > 7) {\n actions.push('Recommended: Consider upgrading to persistent storage')\n \n // Log storage upgrade suggestion\n logger.warn('Consider storage upgrade', {\n reason: 'Consistently high memory usage detected',\n recommendation: 'Use filesystem or S3 storage for better performance',\n currentStorage: this.capabilities.storage.memory ? 'memory' : 'unknown'\n })\n }\n \n this.recordAdaptation(adaptationId, 'high-memory', actions)\n }\n\n async adaptToLowMemoryUsage(memoryMetrics) {\n const adaptationId = `low-memory-${Date.now()}`\n \n logger.debug('Optimizing for low memory usage', {\n utilization: (memoryMetrics.utilization * 100).toFixed(1) + '%',\n available: Math.round((memoryMetrics.heapTotal - memoryMetrics.heapUsed) / 1024 / 1024) + 'MB'\n })\n \n const actions = []\n \n // Increase cache size if we have room\n if (this.db.cache && this.db.cache.setMaxSize) {\n const currentMaxSize = this.db.cache.getMaxSize ? this.db.cache.getMaxSize() : 10000\n const availableMemory = this.capabilities.memory.available\n \n if (availableMemory > 500) {\n const newMaxSize = Math.min(100000, Math.floor(currentMaxSize * 1.2))\n if (newMaxSize > currentMaxSize) {\n this.db.cache.setMaxSize(newMaxSize)\n actions.push(`Increased cache size to ${newMaxSize}`)\n }\n }\n }\n \n if (actions.length > 0) {\n this.recordAdaptation(adaptationId, 'low-memory', actions)\n }\n }\n\n async optimizeCache() {\n if (!this.db.cache) return\n \n logger.debug('Running cache optimization...')\n \n const actions = []\n \n try {\n // Analyze cache performance\n const cacheStats = this.db.cache.getStats ? this.db.cache.getStats() : {}\n \n if (cacheStats.hitRate !== undefined) {\n this.metrics.cacheHitRate.push({\n timestamp: Date.now(),\n hitRate: cacheStats.hitRate\n })\n \n // Keep only recent cache metrics\n if (this.metrics.cacheHitRate.length > 20) {\n this.metrics.cacheHitRate = this.metrics.cacheHitRate.slice(-20)\n }\n \n // Optimize based on hit rate\n if (cacheStats.hitRate < 0.5) {\n // Low hit rate - cache might be too small or eviction too aggressive\n if (this.capabilities.memory.available > 500) {\n actions.push('Consider increasing cache size due to low hit rate')\n }\n } else if (cacheStats.hitRate > 0.9) {\n // Very high hit rate - might be able to optimize\n actions.push('Excellent cache performance detected')\n }\n }\n \n // Clean up expired entries if supported\n if (this.db.cache.cleanup) {\n this.db.cache.cleanup()\n actions.push('Cleaned up expired cache entries')\n }\n \n } catch (error) {\n logger.warn('Cache optimization error:', error)\n }\n \n if (actions.length > 0) {\n this.recordAdaptation(`cache-opt-${Date.now()}`, 'cache-optimization', actions)\n }\n }\n\n async optimizeStorage() {\n logger.debug('Running storage optimization...')\n \n const actions = []\n \n try {\n // Get storage statistics\n const stats = await this.db.getStatistics()\n \n if (stats) {\n // Analyze entity/relationship ratio\n const entityCount = stats.nounCount || 0\n const relationshipCount = stats.verbCount || 0\n \n if (entityCount > 1000 && this.capabilities.storage.memory && this.capabilities.storage.filesystem) {\n actions.push('Consider switching to filesystem storage for better performance')\n }\n \n if (relationshipCount > 10000 && this.capabilities.storage.s3 && this.capabilities.environment === 'production') {\n actions.push('Consider S3 storage for scalability with large datasets')\n }\n \n // Index optimization suggestions\n if (entityCount > 5000) {\n actions.push('Large dataset detected - ensure HNSW index is optimized')\n }\n }\n \n } catch (error) {\n logger.warn('Storage optimization error:', error)\n }\n \n if (actions.length > 0) {\n this.recordAdaptation(`storage-opt-${Date.now()}`, 'storage-optimization', actions)\n }\n }\n\n recordAdaptation(id, type, actions) {\n const adaptation = {\n id,\n type,\n timestamp: new Date().toISOString(),\n actions,\n environment: this.capabilities.environment,\n memoryAtTime: this.metrics.memoryUsage[this.metrics.memoryUsage.length - 1]\n }\n \n this.adaptations.push(adaptation)\n \n // Keep only recent adaptations (last 50)\n if (this.adaptations.length > 50) {\n this.adaptations = this.adaptations.slice(-50)\n }\n \n logger.info('Storage adaptation applied', {\n id,\n type,\n actions: actions.length\n })\n }\n\n // Public API methods\n getMetrics() {\n return {\n memory: this.metrics.memoryUsage.slice(-10), // Last 10 readings\n cacheHitRate: this.metrics.cacheHitRate.slice(-10),\n adaptations: this.adaptations.slice(-10),\n summary: {\n totalAdaptations: this.adaptations.length,\n avgMemoryUtilization: this.calculateAverageMemoryUtilization(),\n lastOptimization: this.adaptations[this.adaptations.length - 1]?.timestamp\n }\n }\n }\n\n calculateAverageMemoryUtilization() {\n if (this.metrics.memoryUsage.length === 0) return 0\n \n const sum = this.metrics.memoryUsage.reduce((acc, m) => acc + m.utilization, 0)\n return (sum / this.metrics.memoryUsage.length * 100).toFixed(1) + '%'\n }\n\n getRecommendations() {\n const recommendations = []\n \n // Memory recommendations\n const avgUtilization = this.calculateAverageMemoryUtilization()\n const utilizationNum = parseFloat(avgUtilization)\n \n if (utilizationNum > 80) {\n recommendations.push({\n type: 'memory',\n priority: 'high',\n title: 'High memory usage detected',\n description: `Average utilization: ${avgUtilization}`,\n action: 'Consider upgrading to persistent storage or increasing memory limits'\n })\n }\n \n // Storage recommendations based on environment\n if (this.capabilities.environment === 'production' && this.capabilities.storage.memory) {\n recommendations.push({\n type: 'storage',\n priority: 'medium',\n title: 'Production environment using memory storage',\n description: 'Memory storage is not persistent across restarts',\n action: 'Configure filesystem or S3 storage for production use'\n })\n }\n \n return recommendations\n }\n\n async cleanup() {\n if (this.monitoringTimer) {\n clearInterval(this.monitoringTimer)\n }\n \n if (this.cacheOptimizationTimer) {\n clearInterval(this.cacheOptimizationTimer)\n }\n \n if (this.storageOptimizationTimer) {\n clearInterval(this.storageOptimizationTimer)\n }\n \n logger.info('Adaptive Storage augmentation cleaned up')\n }\n} \ No newline at end of file diff --git a/examples/brainy-service-template/src/augmentations/autoDiscoveryAugmentation.js b/examples/brainy-service-template/src/augmentations/autoDiscoveryAugmentation.js new file mode 100644 index 00000000..91876990 --- /dev/null +++ b/examples/brainy-service-template/src/augmentations/autoDiscoveryAugmentation.js @@ -0,0 +1,4 @@ +import { logger } from '../utils/logger.js' + +/** + * Auto-Discovery Augmentation - Helps users understand their Brainy data automatically\n * Provides intelligent insights about data patterns, relationships, and optimization suggestions\n */\nexport class AutoDiscoveryAugmentation {\n constructor(options = {}) {\n this.type = 'COGNITION'\n this.priority = 4\n this.options = {\n analysisInterval: options.analysisInterval || 300000, // 5 minutes\n minDataPoints: options.minDataPoints || 10,\n enablePatternDetection: options.enablePatternDetection !== false,\n enableOptimizationSuggestions: options.enableOptimizationSuggestions !== false,\n enableDataQualityAnalysis: options.enableDataQualityAnalysis !== false,\n ...options\n }\n \n this.db = null\n this.analysisTimer = null\n this.lastAnalysis = null\n this.insights = {\n patterns: [],\n suggestions: [],\n dataQuality: {},\n statistics: {}\n }\n }\n\n async augment(brainyData, context) {\n this.db = brainyData\n \n try {\n // Run initial analysis\n await this.runAnalysis()\n \n // Schedule periodic analysis\n if (this.options.analysisInterval > 0) {\n this.scheduleAnalysis()\n }\n \n logger.info('Auto-Discovery augmentation initialized', {\n features: {\n patternDetection: this.options.enablePatternDetection,\n optimizationSuggestions: this.options.enableOptimizationSuggestions,\n dataQualityAnalysis: this.options.enableDataQualityAnalysis\n },\n analysisInterval: this.options.analysisInterval\n })\n \n } catch (error) {\n logger.error('Failed to initialize Auto-Discovery augmentation:', error)\n throw error\n }\n }\n\n async runAnalysis() {\n try {\n logger.debug('Running auto-discovery analysis...')\n \n const stats = await this.gatherStatistics()\n \n // Only run analysis if we have enough data\n if (stats.totalEntities < this.options.minDataPoints) {\n logger.debug('Not enough data for meaningful analysis', {\n entities: stats.totalEntities,\n required: this.options.minDataPoints\n })\n return\n }\n \n this.insights.statistics = stats\n \n // Run different types of analysis\n if (this.options.enablePatternDetection) {\n this.insights.patterns = await this.detectPatterns(stats)\n }\n \n if (this.options.enableOptimizationSuggestions) {\n this.insights.suggestions = await this.generateOptimizationSuggestions(stats)\n }\n \n if (this.options.enableDataQualityAnalysis) {\n this.insights.dataQuality = await this.analyzeDataQuality(stats)\n }\n \n this.lastAnalysis = new Date().toISOString()\n \n logger.info('Auto-discovery analysis completed', {\n patterns: this.insights.patterns.length,\n suggestions: this.insights.suggestions.length,\n dataQualityScore: this.insights.dataQuality.overallScore || 'N/A'\n })\n \n } catch (error) {\n logger.error('Auto-discovery analysis failed:', error)\n }\n }\n\n async gatherStatistics() {\n const stats = {\n totalEntities: 0,\n totalRelationships: 0,\n averageConnectionsPerEntity: 0,\n mostCommonRelationshipTypes: [],\n dataTypes: {},\n metadataFields: {},\n embedding: {\n averageSimilarity: 0,\n clusters: []\n }\n }\n \n try {\n // Get basic statistics from Brainy\n const brainyStats = await this.db.getStatistics()\n \n if (brainyStats) {\n stats.totalEntities = brainyStats.nounCount || 0\n stats.totalRelationships = brainyStats.verbCount || 0\n \n if (stats.totalEntities > 0 && stats.totalRelationships > 0) {\n stats.averageConnectionsPerEntity = stats.totalRelationships / stats.totalEntities\n }\n }\n \n // Analyze data types and metadata (sample-based for performance)\n const sampleSize = Math.min(100, stats.totalEntities)\n if (sampleSize > 0) {\n const sampleEntities = await this.sampleEntities(sampleSize)\n this.analyzeEntitySample(sampleEntities, stats)\n }\n \n } catch (error) {\n logger.warn('Failed to gather some statistics:', error.message)\n }\n \n return stats\n }\n\n async sampleEntities(count) {\n // This would need to be implemented based on Brainy's API\n // For now, return empty array\n return []\n }\n\n analyzeEntitySample(entities, stats) {\n entities.forEach(entity => {\n // Analyze data types\n const dataType = this.detectDataType(entity.data)\n stats.dataTypes[dataType] = (stats.dataTypes[dataType] || 0) + 1\n \n // Analyze metadata fields\n if (entity.metadata && typeof entity.metadata === 'object') {\n Object.keys(entity.metadata).forEach(field => {\n stats.metadataFields[field] = (stats.metadataFields[field] || 0) + 1\n })\n }\n })\n }\n\n detectDataType(data) {\n if (typeof data === 'string') {\n if (data.length > 500) return 'longText'\n if (data.includes('http://') || data.includes('https://')) return 'url'\n if (data.includes('@') && data.includes('.')) return 'email'\n if (/^\\d+$/.test(data)) return 'numericString'\n return 'text'\n }\n \n if (typeof data === 'number') return 'number'\n if (typeof data === 'boolean') return 'boolean'\n if (Array.isArray(data)) return 'array'\n if (typeof data === 'object') return 'object'\n \n return 'unknown'\n }\n\n async detectPatterns(stats) {\n const patterns = []\n \n // Connection density patterns\n if (stats.averageConnectionsPerEntity > 0) {\n if (stats.averageConnectionsPerEntity > 10) {\n patterns.push({\n type: 'highConnectivity',\n description: 'Your data shows high connectivity between entities',\n details: `Average ${stats.averageConnectionsPerEntity.toFixed(2)} connections per entity`,\n impact: 'positive',\n actionable: false\n })\n } else if (stats.averageConnectionsPerEntity < 2) {\n patterns.push({\n type: 'lowConnectivity',\n description: 'Your data has relatively few connections between entities',\n details: `Average ${stats.averageConnectionsPerEntity.toFixed(2)} connections per entity`,\n impact: 'neutral',\n actionable: true,\n suggestion: 'Consider adding more relationships to improve semantic search quality'\n })\n }\n }\n \n // Data type diversity patterns\n const dataTypeCount = Object.keys(stats.dataTypes).length\n if (dataTypeCount > 1) {\n patterns.push({\n type: 'dataTypeDiversity',\n description: 'Your dataset contains diverse data types',\n details: `${dataTypeCount} different data types detected: ${Object.keys(stats.dataTypes).join(', ')}`,\n impact: 'positive',\n actionable: false\n })\n }\n \n // Metadata usage patterns\n const metadataFieldCount = Object.keys(stats.metadataFields).length\n if (metadataFieldCount > 5) {\n patterns.push({\n type: 'richMetadata',\n description: 'Your entities have rich metadata',\n details: `${metadataFieldCount} metadata fields in use`,\n impact: 'positive',\n actionable: false\n })\n } else if (metadataFieldCount < 2) {\n patterns.push({\n type: 'sparseMetadata',\n description: 'Limited metadata usage detected',\n details: `Only ${metadataFieldCount} metadata fields found`,\n impact: 'neutral',\n actionable: true,\n suggestion: 'Adding more metadata fields can improve search and organization'\n })\n }\n \n return patterns\n }\n\n async generateOptimizationSuggestions(stats) {\n const suggestions = []\n \n // Storage optimization\n if (stats.totalEntities > 10000) {\n suggestions.push({\n type: 'storage',\n priority: 'medium',\n title: 'Consider upgrading storage configuration',\n description: `With ${stats.totalEntities} entities, you might benefit from persistent storage`,\n action: 'Configure filesystem or S3 storage for better performance',\n impact: 'performance'\n })\n }\n \n // Caching optimization\n if (stats.totalEntities > 1000) {\n suggestions.push({\n type: 'caching',\n priority: 'low',\n title: 'Optimize caching settings',\n description: 'Your dataset size could benefit from cache tuning',\n action: 'Enable adaptive caching or increase cache size',\n impact: 'performance'\n })\n }\n \n // Relationship suggestions\n if (stats.averageConnectionsPerEntity < 1) {\n suggestions.push({\n type: 'relationships',\n priority: 'high',\n title: 'Add more relationships between entities',\n description: 'Low connectivity reduces semantic search effectiveness',\n action: 'Use intelligent verb scoring or manually add relationships',\n impact: 'accuracy'\n })\n }\n \n // Intelligent verb scoring suggestion\n if (stats.totalRelationships > 10 && !this.isIntelligentScoringEnabled()) {\n suggestions.push({\n type: 'features',\n priority: 'medium',\n title: 'Enable intelligent verb scoring',\n description: 'Automatic relationship weighting can improve search quality',\n action: 'Enable intelligent verb scoring in configuration',\n impact: 'accuracy'\n })\n }\n \n return suggestions\n }\n\n async analyzeDataQuality(stats) {\n const quality = {\n overallScore: 0,\n metrics: {},\n issues: [],\n recommendations: []\n }\n \n let scoreComponents = 0\n let totalScore = 0\n \n // Connectivity quality\n if (stats.averageConnectionsPerEntity > 0) {\n const connectivityScore = Math.min(100, (stats.averageConnectionsPerEntity / 5) * 100)\n quality.metrics.connectivity = {\n score: connectivityScore,\n description: 'How well entities are connected to each other'\n }\n totalScore += connectivityScore\n scoreComponents++\n }\n \n // Metadata richness\n const metadataCount = Object.keys(stats.metadataFields).length\n const metadataScore = Math.min(100, (metadataCount / 10) * 100)\n quality.metrics.metadataRichness = {\n score: metadataScore,\n description: 'Richness and diversity of metadata'\n }\n totalScore += metadataScore\n scoreComponents++\n \n // Data type diversity\n const dataTypeCount = Object.keys(stats.dataTypes).length\n const diversityScore = Math.min(100, (dataTypeCount / 5) * 100)\n quality.metrics.dataTypeDiversity = {\n score: diversityScore,\n description: 'Variety of data types in the dataset'\n }\n totalScore += diversityScore\n scoreComponents++\n \n // Calculate overall score\n quality.overallScore = scoreComponents > 0 ? Math.round(totalScore / scoreComponents) : 0\n \n // Identify issues\n if (quality.metrics.connectivity?.score < 50) {\n quality.issues.push('Low entity connectivity may affect search quality')\n quality.recommendations.push('Add more relationships between related entities')\n }\n \n if (quality.metrics.metadataRichness?.score < 30) {\n quality.issues.push('Limited metadata usage detected')\n quality.recommendations.push('Add descriptive metadata to entities for better organization')\n }\n \n return quality\n }\n\n isIntelligentScoringEnabled() {\n // Check if intelligent verb scoring is enabled\n return this.db.intelligentVerbScoring?.enabled || false\n }\n\n scheduleAnalysis() {\n this.analysisTimer = setInterval(() => {\n this.runAnalysis().catch(error => {\n logger.error('Scheduled analysis failed:', error)\n })\n }, this.options.analysisInterval)\n }\n\n // Public API methods\n getInsights() {\n return {\n ...this.insights,\n lastAnalysis: this.lastAnalysis,\n nextAnalysis: this.analysisTimer ? \n new Date(Date.now() + this.options.analysisInterval).toISOString() : \n null\n }\n }\n\n async forceAnalysis() {\n await this.runAnalysis()\n return this.getInsights()\n }\n\n async cleanup() {\n if (this.analysisTimer) {\n clearInterval(this.analysisTimer)\n this.analysisTimer = null\n }\n \n logger.info('Auto-Discovery augmentation cleaned up')\n }\n} \ No newline at end of file diff --git a/examples/brainy-service-template/src/augmentations/environmentAdapterAugmentation.js b/examples/brainy-service-template/src/augmentations/environmentAdapterAugmentation.js new file mode 100644 index 00000000..777a5311 --- /dev/null +++ b/examples/brainy-service-template/src/augmentations/environmentAdapterAugmentation.js @@ -0,0 +1,4 @@ +import { logger } from '../utils/logger.js' + +/** + * Environment Adapter Augmentation - Adapts Brainy to any runtime environment\n * Automatically configures optimal settings for browser, Node.js, serverless, containers, etc.\n */\nexport class EnvironmentAdapterAugmentation {\n constructor(capabilities) {\n this.type = 'CONDUIT'\n this.priority = 1 // Highest priority - runs first\n this.capabilities = capabilities\n this.adaptations = []\n this.environmentOptimizations = []\n \n this.db = null\n }\n\n async augment(brainyData, context) {\n this.db = brainyData\n \n try {\n // Apply environment-specific optimizations\n await this.applyEnvironmentOptimizations()\n \n // Setup environment-specific monitoring\n this.setupEnvironmentMonitoring()\n \n // Configure runtime adaptations\n this.configureRuntimeAdaptations()\n \n logger.info('Environment Adapter augmentation initialized', {\n environment: this.capabilities.environment,\n platform: this.capabilities.platform,\n optimizations: this.environmentOptimizations.length,\n adaptations: this.adaptations.length\n })\n \n } catch (error) {\n logger.error('Failed to initialize Environment Adapter augmentation:', error)\n throw error\n }\n }\n\n async applyEnvironmentOptimizations() {\n const { environment, platform, runtime, network } = this.capabilities\n \n // Browser environment optimizations\n if (platform.isBrowser) {\n await this.applyBrowserOptimizations()\n }\n \n // Node.js environment optimizations\n if (platform.isNode) {\n await this.applyNodeOptimizations()\n }\n \n // Serverless environment optimizations\n if (platform.isServerless) {\n await this.applyServerlessOptimizations()\n }\n \n // Container environment optimizations\n if (platform.isContainer) {\n await this.applyContainerOptimizations()\n }\n \n // Edge environment optimizations\n if (platform.isEdge) {\n await this.applyEdgeOptimizations()\n }\n \n // Kubernetes-specific optimizations\n if (environment === 'kubernetes') {\n await this.applyKubernetesOptimizations()\n }\n }\n\n async applyBrowserOptimizations() {\n this.environmentOptimizations.push({\n type: 'browser',\n description: 'Browser environment detected',\n optimizations: [\n 'Using OPFS storage when available',\n 'Web Workers for background processing',\n 'IndexedDB fallback for persistence',\n 'WebRTC peer-to-peer communication',\n 'Service Worker for offline capability'\n ]\n })\n \n // Configure browser-specific settings\n if (this.db.updateConfiguration) {\n await this.db.updateConfiguration({\n storage: {\n preferOPFS: true,\n fallbackToIndexedDB: true\n },\n performance: {\n useWebWorkers: true,\n backgroundProcessing: true\n },\n network: {\n enableWebRTC: true,\n preferPeerToPeer: true\n }\n })\n }\n \n logger.info('Applied browser optimizations')\n }\n\n async applyNodeOptimizations() {\n this.environmentOptimizations.push({\n type: 'node',\n description: 'Node.js environment detected',\n optimizations: [\n 'Worker threads for parallel processing',\n 'Filesystem storage with async I/O',\n 'Clustering for multi-core utilization',\n 'Memory-mapped files for large datasets',\n 'Native module optimizations'\n ]\n })\n \n // Configure Node.js-specific settings\n if (this.db.updateConfiguration) {\n await this.db.updateConfiguration({\n performance: {\n useWorkerThreads: this.capabilities.runtime.supportsWorkers,\n enableClustering: this.capabilities.memory.isHighMemory,\n memoryMapping: true\n },\n storage: {\n asyncIO: true,\n bufferSize: this.capabilities.memory.isHighMemory ? 64 * 1024 : 16 * 1024\n }\n })\n }\n \n logger.info('Applied Node.js optimizations')\n }\n\n async applyServerlessOptimizations() {\n this.environmentOptimizations.push({\n type: 'serverless',\n description: 'Serverless environment detected',\n optimizations: [\n 'Memory-only storage for fast cold starts',\n 'Minimal initialization overhead',\n 'Stateless operation mode',\n 'Aggressive caching strategies',\n 'Connection pooling disabled'\n ]\n })\n \n // Configure serverless-specific settings\n if (this.db.updateConfiguration) {\n await this.db.updateConfiguration({\n storage: {\n forceMemoryStorage: true,\n skipPersistence: true\n },\n performance: {\n fastStartup: true,\n minimalInitialization: true,\n statelessMode: true\n },\n cache: {\n aggressiveCaching: true,\n preloadFrequentQueries: true\n },\n network: {\n disableConnectionPooling: true,\n shortTimeouts: true\n }\n })\n }\n \n logger.info('Applied serverless optimizations')\n }\n\n async applyContainerOptimizations() {\n this.environmentOptimizations.push({\n type: 'container',\n description: 'Container environment detected',\n optimizations: [\n 'Volume-based persistent storage',\n 'Resource-aware scaling',\n 'Health check endpoints',\n 'Graceful shutdown handling',\n 'Logging to stdout/stderr'\n ]\n })\n \n // Configure container-specific settings\n if (this.db.updateConfiguration) {\n await this.db.updateConfiguration({\n storage: {\n useVolumes: true,\n persistentPath: '/app/data'\n },\n monitoring: {\n healthChecks: true,\n metricsEndpoint: true\n },\n lifecycle: {\n gracefulShutdown: true,\n shutdownTimeout: 30000\n },\n logging: {\n outputToStdout: true,\n structuredLogging: true\n }\n })\n }\n \n logger.info('Applied container optimizations')\n }\n\n async applyEdgeOptimizations() {\n this.environmentOptimizations.push({\n type: 'edge',\n description: 'Edge computing environment detected',\n optimizations: [\n 'Ultra-low latency configuration',\n 'Minimal resource usage',\n 'Edge-specific storage adapters',\n 'Geographic load balancing',\n 'CDN integration'\n ]\n })\n \n // Configure edge-specific settings\n if (this.db.updateConfiguration) {\n await this.db.updateConfiguration({\n performance: {\n ultraLowLatency: true,\n minimalFootprint: true,\n edgeOptimized: true\n },\n storage: {\n edgeStorage: true,\n replicationStrategy: 'geographic'\n },\n cache: {\n edgeCaching: true,\n cdnIntegration: true\n }\n })\n }\n \n logger.info('Applied edge computing optimizations')\n }\n\n async applyKubernetesOptimizations() {\n this.environmentOptimizations.push({\n type: 'kubernetes',\n description: 'Kubernetes environment detected',\n optimizations: [\n 'Pod-aware resource management',\n 'ConfigMap and Secret integration',\n 'Horizontal pod autoscaling support',\n 'Service mesh compatibility',\n 'Distributed storage coordination'\n ]\n })\n \n // Configure Kubernetes-specific settings\n if (this.db.updateConfiguration) {\n await this.db.updateConfiguration({\n kubernetes: {\n podAware: true,\n useConfigMaps: true,\n useSecrets: true\n },\n scaling: {\n horizontalPodAutoscaling: true,\n resourceRequests: this.calculateResourceRequests()\n },\n storage: {\n distributedCoordination: true,\n persistentVolumeClaims: true\n },\n networking: {\n serviceMesh: true,\n inClusterService: true\n }\n })\n }\n \n logger.info('Applied Kubernetes optimizations')\n }\n\n setupEnvironmentMonitoring() {\n const { environment, platform } = this.capabilities\n \n // Setup platform-specific monitoring\n if (platform.isNode) {\n this.setupNodeMonitoring()\n }\n \n if (platform.isBrowser) {\n this.setupBrowserMonitoring()\n }\n \n if (platform.isServerless) {\n this.setupServerlessMonitoring()\n }\n \n logger.debug('Environment monitoring configured', { environment, platform })\n }\n\n setupNodeMonitoring() {\n // Monitor Node.js specific metrics\n setInterval(() => {\n const usage = process.memoryUsage()\n const cpuUsage = process.cpuUsage()\n \n // Check for memory leaks\n if (usage.heapUsed > usage.heapTotal * 0.9) {\n this.recordAdaptation({\n type: 'memory-warning',\n description: 'High memory usage detected in Node.js environment',\n action: 'Consider enabling garbage collection or reducing cache size'\n })\n }\n \n }, 60000) // Check every minute\n }\n\n setupBrowserMonitoring() {\n // Monitor browser-specific metrics\n if (typeof window !== 'undefined') {\n // Monitor connection status\n window.addEventListener('online', () => {\n this.recordAdaptation({\n type: 'connectivity-restored',\n description: 'Browser connectivity restored',\n action: 'Resume network operations'\n })\n })\n \n window.addEventListener('offline', () => {\n this.recordAdaptation({\n type: 'connectivity-lost',\n description: 'Browser went offline',\n action: 'Switch to offline mode'\n })\n })\n }\n }\n\n setupServerlessMonitoring() {\n // Monitor serverless-specific metrics\n const startTime = Date.now()\n \n // Monitor cold start time\n setTimeout(() => {\n const warmupTime = Date.now() - startTime\n \n this.recordAdaptation({\n type: 'cold-start-measurement',\n description: `Serverless cold start completed in ${warmupTime}ms`,\n action: warmupTime > 5000 ? 'Consider optimizing initialization' : 'Cold start performance acceptable'\n })\n }, 100)\n }\n\n configureRuntimeAdaptations() {\n // Configure adaptations based on runtime characteristics\n if (this.capabilities.runtime.isLongRunning) {\n this.adaptations.push({\n type: 'long-running',\n description: 'Long-running process detected',\n configuration: {\n enableBackgroundOptimization: true,\n periodicMaintenance: true,\n statisticsCollection: true\n }\n })\n } else {\n this.adaptations.push({\n type: 'short-lived',\n description: 'Short-lived process detected',\n configuration: {\n fastStartup: true,\n minimalInitialization: true,\n skipPeriodicTasks: true\n }\n })\n }\n \n if (this.capabilities.network.supportsPeerToPeer) {\n this.adaptations.push({\n type: 'peer-to-peer',\n description: 'Peer-to-peer networking supported',\n configuration: {\n enableWebRTC: true,\n enablePeerDiscovery: true,\n preferDirectConnections: true\n }\n })\n }\n }\n\n calculateResourceRequests() {\n // Calculate optimal resource requests for Kubernetes\n const memoryMB = this.capabilities.memory.available\n const isHighPerformance = this.capabilities.memory.isHighMemory\n \n return {\n memory: `${Math.max(128, Math.min(memoryMB, 1024))}Mi`,\n cpu: isHighPerformance ? '500m' : '250m'\n }\n }\n\n recordAdaptation(adaptation) {\n const record = {\n ...adaptation,\n timestamp: new Date().toISOString(),\n environment: this.capabilities.environment,\n platform: this.capabilities.platform\n }\n \n this.adaptations.push(record)\n \n // Keep only recent adaptations\n if (this.adaptations.length > 100) {\n this.adaptations = this.adaptations.slice(-100)\n }\n \n logger.debug('Environment adaptation recorded', {\n type: adaptation.type,\n description: adaptation.description\n })\n }\n\n // Public API methods\n getEnvironmentInfo() {\n return {\n capabilities: this.capabilities,\n optimizations: this.environmentOptimizations,\n adaptations: this.adaptations.slice(-10), // Last 10 adaptations\n recommendations: this.getEnvironmentRecommendations()\n }\n }\n\n getEnvironmentRecommendations() {\n const recommendations = []\n \n // Environment-specific recommendations\n if (this.capabilities.platform.isServerless && this.capabilities.storage.filesystem) {\n recommendations.push({\n type: 'storage',\n priority: 'high',\n title: 'Serverless with persistent storage detected',\n description: 'Filesystem storage may not persist across function invocations',\n action: 'Consider using memory storage or external storage service'\n })\n }\n \n if (this.capabilities.environment === 'production' && this.capabilities.storage.memory) {\n recommendations.push({\n type: 'production',\n priority: 'high',\n title: 'Production environment using memory storage',\n description: 'Data will not persist across restarts',\n action: 'Configure persistent storage (filesystem or S3)'\n })\n }\n \n if (this.capabilities.memory.available < 512 && this.capabilities.platform.isNode) {\n recommendations.push({\n type: 'performance',\n priority: 'medium',\n title: 'Low memory environment detected',\n description: `Only ${this.capabilities.memory.available}MB available`,\n action: 'Consider increasing memory limits or using memory-optimized configuration'\n })\n }\n \n return recommendations\n }\n\n async cleanup() {\n // Cleanup environment-specific resources\n logger.info('Environment Adapter augmentation cleaned up')\n }\n} \ No newline at end of file diff --git a/examples/brainy-service-template/src/augmentations/httpAugmentation.js b/examples/brainy-service-template/src/augmentations/httpAugmentation.js new file mode 100644 index 00000000..873f9ade --- /dev/null +++ b/examples/brainy-service-template/src/augmentations/httpAugmentation.js @@ -0,0 +1,10 @@ +import express from 'express' +import cors from 'cors' +import { logger } from '../utils/logger.js' + +/** + * Minimal HTTP Augmentation - Lightweight REST API using Brainy's native augmentation system + * Provides essential HTTP endpoints with zero configuration + */ +export class HttpAugmentation { + constructor(options = {}) {\n this.type = 'HTTP'\n this.priority = 3 // Lower priority - fallback option\n this.options = {\n port: options.port || 3000,\n enableCORS: options.enableCORS !== false,\n enableCompression: options.enableCompression !== false,\n minimal: options.minimal !== false, // Keep endpoints minimal\n rateLimit: options.rateLimit || { max: 100, windowMs: 60000 },\n ...options\n }\n \n this.app = null\n this.server = null\n this.db = null\n }\n\n async augment(brainyData, context) {\n this.db = brainyData\n \n try {\n this.app = express()\n this.setupMiddleware()\n this.setupRoutes()\n \n // Start server\n await new Promise((resolve, reject) => {\n this.server = this.app.listen(this.options.port, (error) => {\n if (error) {\n reject(error)\n } else {\n resolve()\n }\n })\n })\n \n logger.info('HTTP augmentation initialized', {\n port: this.options.port,\n features: {\n cors: this.options.enableCORS,\n compression: this.options.enableCompression,\n minimal: this.options.minimal\n }\n })\n \n } catch (error) {\n logger.error('Failed to initialize HTTP augmentation:', error)\n throw error\n }\n }\n\n setupMiddleware() {\n // CORS\n if (this.options.enableCORS) {\n this.app.use(cors({\n origin: true,\n credentials: false,\n methods: ['GET', 'POST', 'PUT', 'DELETE'],\n allowedHeaders: ['Content-Type', 'Accept']\n }))\n }\n \n // Compression\n if (this.options.enableCompression) {\n const compression = require('compression')\n this.app.use(compression())\n }\n \n // JSON parsing\n this.app.use(express.json({ limit: '10mb' }))\n \n // Request logging\n this.app.use((req, res, next) => {\n const start = Date.now()\n res.on('finish', () => {\n const duration = Date.now() - start\n logger.debug('HTTP Request', {\n method: req.method,\n path: req.path,\n status: res.statusCode,\n duration: `${duration}ms`\n })\n })\n next()\n })\n }\n\n setupRoutes() {\n // Health check\n this.app.get('/health', (req, res) => {\n res.json({\n status: 'healthy',\n service: 'brainy-http-augmentation',\n timestamp: new Date().toISOString(),\n uptime: process.uptime()\n })\n })\n\n // Root endpoint\n this.app.get('/', (req, res) => {\n res.json({\n message: 'Brainy HTTP Augmentation',\n version: '1.0.0',\n endpoints: {\n search: 'POST /search',\n add: 'POST /add',\n get: 'GET /entity/:id',\n addVerb: 'POST /verb',\n health: 'GET /health'\n },\n documentation: 'This is a minimal HTTP interface to Brainy. Use WebSocket or WebRTC augmentations for better performance.'\n })\n })\n\n // Search entities\n this.app.post('/search', this.asyncHandler(async (req, res) => {\n const { query, limit = 10, threshold = 0.7, options = {} } = req.body\n \n if (!query || typeof query !== 'string') {\n return res.status(400).json({\n error: 'Query is required and must be a string'\n })\n }\n \n const results = await this.db.search(query, Math.min(limit, 100), {\n threshold: Math.max(0, Math.min(1, threshold)),\n includeMetadata: true,\n ...options\n })\n \n res.json({\n success: true,\n query,\n results,\n count: results.length,\n timestamp: new Date().toISOString()\n })\n }))\n\n // Add entity\n this.app.post('/add', this.asyncHandler(async (req, res) => {\n const { data, metadata = {}, options = {} } = req.body\n \n if (!data) {\n return res.status(400).json({\n error: 'Data is required'\n })\n }\n \n const enhancedMetadata = {\n ...metadata,\n source: 'http-augmentation',\n timestamp: new Date().toISOString(),\n userAgent: req.get('User-Agent')\n }\n \n const id = await this.db.add(data, enhancedMetadata, options)\n \n res.status(201).json({\n success: true,\n id,\n data,\n metadata: enhancedMetadata\n })\n }))\n\n // Get entity by ID\n this.app.get('/entity/:id', this.asyncHandler(async (req, res) => {\n const { id } = req.params\n \n if (!id) {\n return res.status(400).json({\n error: 'Entity ID is required'\n })\n }\n \n const entity = await this.db.get(id)\n \n if (!entity) {\n return res.status(404).json({\n error: `Entity with ID ${id} not found`\n })\n }\n \n res.json({\n success: true,\n data: entity\n })\n }))\n\n // Add relationship (verb)\n this.app.post('/verb', this.asyncHandler(async (req, res) => {\n const { sourceId, targetId, verbType, weight, metadata = {}, options = {} } = req.body\n \n if (!sourceId || !targetId || !verbType) {\n return res.status(400).json({\n error: 'sourceId, targetId, and verbType are required'\n })\n }\n \n const enhancedMetadata = {\n ...metadata,\n source: 'http-augmentation',\n timestamp: new Date().toISOString(),\n userAgent: req.get('User-Agent')\n }\n \n const verbId = await this.db.addVerb(sourceId, targetId, verbType, {\n weight,\n metadata: enhancedMetadata,\n ...options\n })\n \n res.status(201).json({\n success: true,\n id: verbId,\n sourceId,\n targetId,\n verbType,\n weight,\n metadata: enhancedMetadata\n })\n }))\n\n // Batch operations (if not minimal)\n if (!this.options.minimal) {\n this.app.post('/batch/add', this.asyncHandler(async (req, res) => {\n const { items } = req.body\n \n if (!Array.isArray(items) || items.length === 0) {\n return res.status(400).json({\n error: 'Items array is required and must not be empty'\n })\n }\n \n if (items.length > 100) {\n return res.status(400).json({\n error: 'Maximum 100 items allowed per batch'\n })\n }\n \n const results = []\n const timestamp = new Date().toISOString()\n \n for (const item of items) {\n try {\n const enhancedMetadata = {\n ...item.metadata,\n source: 'http-augmentation-batch',\n timestamp,\n userAgent: req.get('User-Agent')\n }\n \n const id = await this.db.add(item.data, enhancedMetadata, item.options || {})\n results.push({\n success: true,\n id,\n data: item.data,\n metadata: enhancedMetadata\n })\n } catch (error) {\n results.push({\n success: false,\n error: error.message,\n data: item.data\n })\n }\n }\n \n res.json({\n success: true,\n results,\n totalProcessed: results.length,\n successful: results.filter(r => r.success).length,\n failed: results.filter(r => !r.success).length\n })\n }))\n }\n\n // Statistics endpoint (if available)\n this.app.get('/stats', this.asyncHandler(async (req, res) => {\n try {\n const stats = await this.db.getStatistics()\n res.json({\n success: true,\n stats,\n timestamp: new Date().toISOString()\n })\n } catch (error) {\n res.json({\n success: true,\n message: 'Statistics not available',\n reason: error.message\n })\n }\n }))\n\n // Error handler\n this.app.use((error, req, res, next) => {\n logger.error('HTTP augmentation error:', {\n error: error.message,\n stack: error.stack,\n path: req.path,\n method: req.method\n })\n \n res.status(500).json({\n error: 'Internal server error',\n message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong',\n timestamp: new Date().toISOString()\n })\n })\n\n // 404 handler\n this.app.use((req, res) => {\n res.status(404).json({\n error: 'Endpoint not found',\n message: `${req.method} ${req.path} is not available`,\n availableEndpoints: [\n 'GET /',\n 'GET /health',\n 'POST /search',\n 'POST /add',\n 'GET /entity/:id',\n 'POST /verb',\n 'GET /stats'\n ]\n })\n })\n }\n\n // Async error handler wrapper\n asyncHandler(fn) {\n return (req, res, next) => {\n Promise.resolve(fn(req, res, next)).catch(next)\n }\n }\n\n async cleanup() {\n if (this.server) {\n await new Promise((resolve) => {\n this.server.close(resolve)\n })\n }\n \n logger.info('HTTP augmentation cleaned up')\n }\n} \ No newline at end of file diff --git a/examples/brainy-service-template/src/augmentations/webrtcAugmentation.js b/examples/brainy-service-template/src/augmentations/webrtcAugmentation.js new file mode 100644 index 00000000..a9be8917 --- /dev/null +++ b/examples/brainy-service-template/src/augmentations/webrtcAugmentation.js @@ -0,0 +1,112 @@ +import { WebSocketServer } from 'ws' +import { logger } from '../utils/logger.js' + +/** + * WebRTC Augmentation - Peer-to-peer communication using Brainy's native augmentation system + * Enables direct browser-to-browser or peer-to-peer Brainy database connections + */ +export class WebRTCAugmentation { + constructor(options = {}) { + this.type = 'WEBRTC' + this.priority = 1 // Highest priority for peer-to-peer + this.options = { + port: options.port || 3002, + enableDataChannels: options.enableDataChannels !== false, + enablePeerDiscovery: options.enablePeerDiscovery !== false, + maxPeers: options.maxPeers || 10, + iceServers: options.iceServers || [ + { urls: 'stun:stun.l.google.com:19302' }, + { urls: 'stun:stun1.l.google.com:19302' } + ], + ...options + } + + this.signalingServer = null + this.peers = new Map() + this.db = null + this.isNode = typeof process !== 'undefined' + } + + async augment(brainyData, context) { + this.db = brainyData + + try { + // Create signaling server for WebRTC peer discovery + await this.createSignalingServer() + + logger.info('WebRTC augmentation initialized', { + port: this.options.port, + features: { + dataChannels: this.options.enableDataChannels, + peerDiscovery: this.options.enablePeerDiscovery, + maxPeers: this.options.maxPeers + } + }) + + } catch (error) { + logger.error('Failed to initialize WebRTC augmentation:', error) + throw error + } + } + + async createSignalingServer() { + // Create WebSocket server for WebRTC signaling + this.signalingServer = new WebSocketServer({ + port: this.options.port, + perMessageDeflate: true + }) + + this.signalingServer.on('connection', (ws, request) => { + this.handleSignalingConnection(ws, request) + }) + + logger.debug('WebRTC signaling server started', { port: this.options.port }) + } + + handleSignalingConnection(ws, request) { + const peerId = this.generatePeerId() + ws.peerId = peerId + + logger.debug('WebRTC signaling client connected', { + peerId, + ip: request.socket.remoteAddress + }) + + // Send peer ID and available peers + this.send(ws, { + type: 'init', + peerId, + peers: Array.from(this.peers.keys()), + capabilities: { + dataChannels: this.options.enableDataChannels, + peerDiscovery: this.options.enablePeerDiscovery, + commands: ['offer', 'answer', 'candidate', 'brainy-query', 'brainy-add', 'brainy-search'] + } + }) + + this.peers.set(peerId, ws) + + ws.on('message', (data) => { + this.handleSignalingMessage(ws, data) + }) + + ws.on('close', () => { + this.peers.delete(peerId) + this.broadcastPeerList() + logger.debug('WebRTC peer disconnected', { peerId, totalPeers: this.peers.size }) + }) + + ws.on('error', (error) => { + logger.warn('WebRTC signaling error:', { peerId, error: error.message }) + }) + + // Broadcast updated peer list + this.broadcastPeerList() + } + + async handleSignalingMessage(ws, data) { + try { + const message = JSON.parse(data.toString()) + const { type, targetPeer, payload } = message + + switch (type) {\n case 'offer':\n case 'answer':\n case 'candidate':\n // Forward WebRTC signaling messages to target peer\n if (targetPeer && this.peers.has(targetPeer)) {\n const targetWs = this.peers.get(targetPeer)\n this.send(targetWs, {\n type,\n sourcePeer: ws.peerId,\n payload\n })\n }\n break\n\n case 'brainy-query':\n // Handle direct Brainy queries over WebRTC data channel simulation\n const result = await this.handleBrainyQuery(payload)\n this.send(ws, {\n type: 'brainy-response',\n payload: result\n })\n break\n\n case 'peer-discovery':\n // Send current peer list\n this.send(ws, {\n type: 'peer-list',\n peers: Array.from(this.peers.keys()).filter(id => id !== ws.peerId)\n })\n break\n\n case 'broadcast-data':\n // Broadcast data to all connected peers\n this.broadcastData(payload, ws.peerId)\n break\n\n default:\n logger.warn('Unknown WebRTC signaling message type:', type)\n }\n\n } catch (error) {\n logger.warn('WebRTC signaling message error:', error)\n this.send(ws, {\n type: 'error',\n payload: {\n message: error.message,\n code: 'SIGNALING_ERROR'\n }\n })\n }\n }\n\n async handleBrainyQuery(payload) {\n const { operation, params } = payload\n \n try {\n let result\n \n switch (operation) {\n case 'search':\n const { query, limit = 10, threshold = 0.7, options = {} } = params\n if (!query) throw new Error('Query is required')\n result = await this.db.search(query, limit, { threshold, ...options })\n break\n \n case 'add':\n const { data, metadata = {}, addOptions = {} } = params\n if (!data) throw new Error('Data is required')\n \n const enhancedMetadata = {\n ...metadata,\n source: 'webrtc',\n timestamp: new Date().toISOString()\n }\n \n result = {\n id: await this.db.add(data, enhancedMetadata, addOptions),\n data,\n metadata: enhancedMetadata\n }\n break\n \n case 'get':\n const { id } = params\n if (!id) throw new Error('ID is required')\n result = await this.db.get(id)\n if (!result) throw new Error(`Entity with ID ${id} not found`)\n break\n \n case 'addVerb':\n const { sourceId, targetId, verbType, weight, verbMetadata = {}, verbOptions = {} } = params\n if (!sourceId || !targetId || !verbType) {\n throw new Error('sourceId, targetId, and verbType are required')\n }\n \n const enhancedVerbMetadata = {\n ...verbMetadata,\n source: 'webrtc',\n timestamp: new Date().toISOString()\n }\n \n result = {\n id: await this.db.addVerb(sourceId, targetId, verbType, {\n weight,\n metadata: enhancedVerbMetadata,\n ...verbOptions\n }),\n sourceId,\n targetId,\n verbType,\n weight,\n metadata: enhancedVerbMetadata\n }\n break\n \n default:\n throw new Error(`Unknown operation: ${operation}`)\n }\n \n return {\n success: true,\n data: result,\n timestamp: new Date().toISOString()\n }\n \n } catch (error) {\n return {\n success: false,\n error: {\n message: error.message,\n code: error.code || 'QUERY_ERROR'\n },\n timestamp: new Date().toISOString()\n }\n }\n }\n\n broadcastPeerList() {\n if (!this.options.enablePeerDiscovery) return\n \n const peerList = Array.from(this.peers.keys())\n \n this.peers.forEach((ws, peerId) => {\n this.send(ws, {\n type: 'peer-list-update',\n peers: peerList.filter(id => id !== peerId),\n totalPeers: peerList.length - 1\n })\n })\n }\n\n broadcastData(data, excludePeer = null) {\n const message = {\n type: 'peer-broadcast',\n data,\n timestamp: new Date().toISOString()\n }\n \n this.peers.forEach((ws, peerId) => {\n if (peerId !== excludePeer) {\n this.send(ws, message)\n }\n })\n }\n\n send(ws, message) {\n if (ws.readyState === 1) { // WebSocket.OPEN\n ws.send(JSON.stringify(message))\n }\n }\n\n generatePeerId() {\n return `peer_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n }\n\n // Client-side helper methods (for browser environments)\n generateClientCode() {\n return `\n class BrainyWebRTCClient {\n constructor(signalingUrl) {\n this.signalingUrl = signalingUrl\n this.ws = null\n this.peerId = null\n this.peers = new Map()\n this.dataChannels = new Map()\n }\n \n async connect() {\n return new Promise((resolve, reject) => {\n this.ws = new WebSocket(this.signalingUrl)\n \n this.ws.onopen = () => {\n console.log('Connected to Brainy WebRTC signaling server')\n }\n \n this.ws.onmessage = (event) => {\n const message = JSON.parse(event.data)\n this.handleSignalingMessage(message)\n if (message.type === 'init') {\n this.peerId = message.peerId\n resolve(message)\n }\n }\n \n this.ws.onerror = reject\n })\n }\n \n async connectToPeer(targetPeerId) {\n const pc = new RTCPeerConnection({\n iceServers: ${JSON.stringify(this.options.iceServers)}\n })\n \n // Create data channel for Brainy operations\n const dataChannel = pc.createDataChannel('brainy', {\n ordered: true\n })\n \n this.setupDataChannel(dataChannel, targetPeerId)\n this.setupPeerConnection(pc, targetPeerId)\n \n // Create and send offer\n const offer = await pc.createOffer()\n await pc.setLocalDescription(offer)\n \n this.send({\n type: 'offer',\n targetPeer: targetPeerId,\n payload: offer\n })\n \n this.peers.set(targetPeerId, pc)\n return pc\n }\n \n async search(query, limit = 10, threshold = 0.7) {\n return this.sendBrainyQuery({\n operation: 'search',\n params: { query, limit, threshold }\n })\n }\n \n async add(data, metadata = {}) {\n return this.sendBrainyQuery({\n operation: 'add',\n params: { data, metadata }\n })\n }\n \n sendBrainyQuery(payload) {\n return new Promise((resolve, reject) => {\n const id = Date.now()\n const timeoutId = setTimeout(() => {\n reject(new Error('Query timeout'))\n }, 10000)\n \n const handler = (event) => {\n const message = JSON.parse(event.data)\n if (message.type === 'brainy-response') {\n clearTimeout(timeoutId)\n this.ws.removeEventListener('message', handler)\n resolve(message.payload)\n }\n }\n \n this.ws.addEventListener('message', handler)\n this.send({\n type: 'brainy-query',\n id,\n payload\n })\n })\n }\n \n send(message) {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(message))\n }\n }\n \n // ... additional WebRTC peer connection handling methods\n }\n \n // Usage:\n // const client = new BrainyWebRTCClient('ws://localhost:${this.options.port}')\n // await client.connect()\n // const results = await client.search('machine learning')\n `\n }\n\n async cleanup() {\n if (this.signalingServer) {\n this.signalingServer.close()\n }\n \n // Close all peer connections\n this.peers.forEach((ws) => {\n ws.close()\n })\n this.peers.clear()\n \n logger.info('WebRTC augmentation cleaned up')\n }\n} \ No newline at end of file diff --git a/examples/brainy-service-template/src/augmentations/websocketAugmentation.js b/examples/brainy-service-template/src/augmentations/websocketAugmentation.js new file mode 100644 index 00000000..74f88d81 --- /dev/null +++ b/examples/brainy-service-template/src/augmentations/websocketAugmentation.js @@ -0,0 +1,165 @@ +import { WebSocket, WebSocketServer } from 'ws' +import { logger } from '../utils/logger.js' + +/** + * WebSocket Augmentation - Real-time communication using Brainy's native augmentation system + * Handles real-time queries, updates, and bidirectional communication + */ +export class WebSocketAugmentation { + constructor(options = {}) { + this.type = 'WEBSOCKET' + this.priority = 2 + this.options = { + port: options.port || 3001, + enableRealtime: options.enableRealtime !== false, + enableBroadcast: options.enableBroadcast !== false, + heartbeatInterval: options.heartbeatInterval || 30000, + ...options + } + + this.server = null + this.clients = new Set() + this.db = null + this.heartbeatTimer = null + } + + async augment(brainyData, context) { + this.db = brainyData + + try { + // Create WebSocket server + this.server = new WebSocketServer({ + port: this.options.port, + perMessageDeflate: true + }) + + this.server.on('connection', (ws, request) => { + this.handleConnection(ws, request) + }) + + // Start heartbeat for connection management + if (this.options.heartbeatInterval > 0) { + this.startHeartbeat() + } + + logger.info('WebSocket augmentation initialized', { + port: this.options.port, + features: { + realtime: this.options.enableRealtime, + broadcast: this.options.enableBroadcast + } + }) + + // Hook into Brainy events if real-time is enabled + if (this.options.enableRealtime) { + this.setupRealtimeHooks() + } + + } catch (error) { + logger.error('Failed to initialize WebSocket augmentation:', error) + throw error + } + } + + handleConnection(ws, request) { + const clientId = this.generateClientId() + ws.clientId = clientId + this.clients.add(ws) + + logger.debug('WebSocket client connected', { + clientId, + ip: request.socket.remoteAddress, + totalClients: this.clients.size + }) + + // Send welcome message + this.send(ws, { + type: 'welcome', + clientId, + capabilities: { + realtime: this.options.enableRealtime, + broadcast: this.options.enableBroadcast, + commands: ['search', 'add', 'addVerb', 'get', 'subscribe', 'unsubscribe'] + } + }) + + ws.on('message', (data) => { + this.handleMessage(ws, data) + }) + + ws.on('close', () => { + this.clients.delete(ws) + logger.debug('WebSocket client disconnected', { + clientId, + totalClients: this.clients.size + }) + }) + + ws.on('error', (error) => { + logger.warn('WebSocket client error:', { clientId, error: error.message }) + }) + + // Keep connection alive + ws.isAlive = true + ws.on('pong', () => { + ws.isAlive = true + }) + } + + async handleMessage(ws, data) { + try { + const message = JSON.parse(data.toString()) + const { id, type, payload } = message + + let result + + switch (type) { + case 'search': + result = await this.handleSearch(payload) + break + + case 'add': + result = await this.handleAdd(payload) + break + + case 'addVerb': + result = await this.handleAddVerb(payload) + break + + case 'get': + result = await this.handleGet(payload) + break + + case 'subscribe': + result = await this.handleSubscribe(ws, payload) + break + + case 'unsubscribe': + result = await this.handleUnsubscribe(ws, payload) + break + + case 'ping': + result = { pong: Date.now() } + break + + default:\n throw new Error(`Unknown message type: ${type}`) + } + + // Send response + this.send(ws, {\n id,\n type: 'response',\n payload: result\n }) + + } catch (error) {\n logger.warn('WebSocket message handling error:', error)\n this.send(ws, {\n id: message?.id,\n type: 'error',\n payload: {\n message: error.message,\n code: error.code || 'UNKNOWN_ERROR'\n }\n })\n }\n } + + async handleSearch(payload) {\n const { query, limit = 10, threshold = 0.7, options = {} } = payload\n \n if (!query) {\n throw new Error('Query is required for search')\n }\n\n const results = await this.db.search(query, limit, { threshold, ...options })\n \n return {\n query,\n results,\n count: results.length,\n timestamp: new Date().toISOString()\n }\n } + + async handleAdd(payload) {\n const { data, metadata = {}, options = {} } = payload\n \n if (!data) {\n throw new Error('Data is required for add operation')\n }\n\n const enhancedMetadata = {\n ...metadata,\n source: 'websocket',\n timestamp: new Date().toISOString()\n }\n\n const id = await this.db.add(data, enhancedMetadata, options)\n \n const result = {\n id,\n data,\n metadata: enhancedMetadata\n }\n\n // Broadcast to subscribers if enabled\n if (this.options.enableBroadcast) {\n this.broadcast({\n type: 'entityAdded',\n payload: result\n })\n }\n\n return result\n } + + async handleAddVerb(payload) {\n const { sourceId, targetId, verbType, weight, metadata = {}, options = {} } = payload\n \n if (!sourceId || !targetId || !verbType) {\n throw new Error('sourceId, targetId, and verbType are required')\n }\n\n const enhancedMetadata = {\n ...metadata,\n source: 'websocket',\n timestamp: new Date().toISOString()\n }\n\n const verbId = await this.db.addVerb(sourceId, targetId, verbType, {\n weight,\n metadata: enhancedMetadata,\n ...options\n })\n \n const result = {\n id: verbId,\n sourceId,\n targetId,\n verbType,\n weight,\n metadata: enhancedMetadata\n }\n\n // Broadcast to subscribers if enabled\n if (this.options.enableBroadcast) {\n this.broadcast({\n type: 'verbAdded',\n payload: result\n })\n }\n\n return result\n } + + async handleGet(payload) {\n const { id } = payload\n \n if (!id) {\n throw new Error('ID is required for get operation')\n }\n\n const entity = await this.db.get(id)\n \n if (!entity) {\n throw new Error(`Entity with ID ${id} not found`)\n }\n\n return entity\n } + + async handleSubscribe(ws, payload) {\n const { events = ['entityAdded', 'verbAdded'] } = payload\n \n if (!ws.subscriptions) {\n ws.subscriptions = new Set()\n }\n \n events.forEach(event => ws.subscriptions.add(event))\n \n return {\n subscribed: Array.from(ws.subscriptions),\n message: 'Successfully subscribed to events'\n }\n } + + async handleUnsubscribe(ws, payload) {\n const { events = [] } = payload\n \n if (!ws.subscriptions) {\n return { message: 'No active subscriptions' }\n }\n \n if (events.length === 0) {\n // Unsubscribe from all\n ws.subscriptions.clear()\n } else {\n events.forEach(event => ws.subscriptions.delete(event))\n }\n \n return {\n subscribed: Array.from(ws.subscriptions),\n message: 'Successfully unsubscribed from events'\n }\n } + + setupRealtimeHooks() {\n // Hook into Brainy's internal events for real-time updates\n // This would integrate with Brainy's event system once available\n logger.debug('Real-time hooks configured for WebSocket augmentation')\n }\n\n broadcast(message, filter = null) {\n if (!this.options.enableBroadcast) return\n \n const data = JSON.stringify(message)\n \n this.clients.forEach(client => {\n if (client.readyState === WebSocket.OPEN) {\n // Check if client is subscribed to this event type\n if (client.subscriptions && client.subscriptions.has(message.type)) {\n // Apply filter if provided\n if (!filter || filter(client)) {\n client.send(data)\n }\n }\n }\n })\n }\n\n send(ws, message) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify(message))\n }\n }\n\n startHeartbeat() {\n this.heartbeatTimer = setInterval(() => {\n this.clients.forEach(ws => {\n if (!ws.isAlive) {\n ws.terminate()\n return\n }\n \n ws.isAlive = false\n ws.ping()\n })\n }, this.options.heartbeatInterval)\n }\n\n generateClientId() {\n return `ws_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`\n }\n\n async cleanup() {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n }\n \n if (this.server) {\n this.server.close()\n }\n \n // Close all client connections\n this.clients.forEach(client => {\n client.close()\n })\n this.clients.clear()\n \n logger.info('WebSocket augmentation cleaned up')\n }\n} \ No newline at end of file diff --git a/examples/brainy-service-template/src/controllers/entities.js b/examples/brainy-service-template/src/controllers/entities.js new file mode 100644 index 00000000..01b34d71 --- /dev/null +++ b/examples/brainy-service-template/src/controllers/entities.js @@ -0,0 +1,174 @@ +import { logger } from '../utils/logger.js' +import { ApiError } from '../utils/errors.js' + +export const entitiesController = { + async create(req, res, next) { + try { + const { data, metadata = {}, options = {} } = req.body + const { brainyService } = req.app.locals + + if (!data) { + throw new ApiError(400, 'Data is required') + } + + // Add timestamp and source info to metadata + const enhancedMetadata = { + ...metadata, + createdAt: new Date().toISOString(), + source: 'api' + } + + const entityId = await brainyService.addEntity(data, enhancedMetadata, options) + + res.status(201).json({ + success: true, + id: entityId, + data: { + id: entityId, + data, + metadata: enhancedMetadata + } + }) + } catch (error) { + next(error) + } + }, + + async getById(req, res, next) { + try { + const { id } = req.params + const { brainyService } = req.app.locals + + const entity = await brainyService.getEntity(id) + + if (!entity) { + throw new ApiError(404, `Entity with ID ${id} not found`) + } + + res.json({ + success: true, + data: entity + }) + } catch (error) { + next(error) + } + }, + + async update(req, res, next) { + try { + const { id } = req.params + const { data, metadata = {} } = req.body + const { brainyService } = req.app.locals + + // Check if entity exists + const existingEntity = await brainyService.getEntity(id) + if (!existingEntity) { + throw new ApiError(404, `Entity with ID ${id} not found`) + } + + // Add update timestamp + const enhancedMetadata = { + ...metadata, + updatedAt: new Date().toISOString() + } + + await brainyService.updateEntity(id, data, enhancedMetadata) + + res.json({ + success: true, + message: 'Entity updated successfully', + id + }) + } catch (error) { + next(error) + } + }, + + async delete(req, res, next) { + try { + const { id } = req.params + const { brainyService } = req.app.locals + + // Check if entity exists + const existingEntity = await brainyService.getEntity(id) + if (!existingEntity) { + throw new ApiError(404, `Entity with ID ${id} not found`) + } + + await brainyService.deleteEntity(id) + + res.json({ + success: true, + message: 'Entity deleted successfully', + id + }) + } catch (error) { + next(error) + } + }, + + async search(req, res, next) { + try { + const { query, limit = 10, threshold = 0.7, includeMetadata = true } = req.body + const { brainyService } = req.app.locals + + if (!query) { + throw new ApiError(400, 'Query is required') + } + + const options = { + limit: Math.min(limit, 100), // Cap at 100 results + threshold, + includeMetadata + } + + const results = await brainyService.searchEntities(query, options) + + res.json({ + success: true, + query, + results: results.map(result => ({ + id: result.id, + similarity: result.similarity, + data: result.data, + metadata: result.metadata + })), + count: results.length, + options + }) + } catch (error) { + next(error) + } + }, + + async list(req, res, next) { + try { + const { page = 1, limit = 50, type } = req.query + const { brainyService } = req.app.locals + + const options = { + offset: (page - 1) * limit, + limit: Math.min(limit, 100) + } + + // Add type filter if specified + if (type) { + options.filter = { type } + } + + const entities = await brainyService.listEntities(options) + + res.json({ + success: true, + data: entities, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + count: entities.length + } + }) + } catch (error) { + next(error) + } + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/controllers/health.js b/examples/brainy-service-template/src/controllers/health.js new file mode 100644 index 00000000..8c9d8231 --- /dev/null +++ b/examples/brainy-service-template/src/controllers/health.js @@ -0,0 +1,130 @@ +import { logger } from '../utils/logger.js' + +export const healthController = { + async check(req, res, next) { + try { + const { brainyService } = req.app.locals + + // Get Brainy health status + const brainyHealth = await brainyService.getHealth() + + // Overall system health + const health = { + status: brainyHealth.status === 'healthy' ? 'healthy' : 'unhealthy', + timestamp: new Date().toISOString(), + version: process.env.npm_package_version || '1.0.0', + environment: process.env.NODE_ENV || 'development', + uptime: Math.floor(process.uptime()), + services: { + brainy: brainyHealth + }, + system: { + memory: { + used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024), + total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024), + external: Math.round(process.memoryUsage().external / 1024 / 1024) + }, + cpu: process.cpuUsage() + } + } + + const statusCode = health.status === 'healthy' ? 200 : 503 + + res.status(statusCode).json(health) + } catch (error) { + logger.error('Health check failed:', error) + + res.status(503).json({ + status: 'unhealthy', + timestamp: new Date().toISOString(), + error: error.message, + services: { + brainy: { status: 'error', error: error.message } + } + }) + } + }, + + async readiness(req, res, next) { + try { + const { brainyService } = req.app.locals + + // Check if services are ready + const brainyHealth = await brainyService.getHealth() + const isReady = brainyHealth.status === 'healthy' + + const readiness = { + ready: isReady, + timestamp: new Date().toISOString(), + checks: { + brainy: { + ready: brainyHealth.status === 'healthy', + details: brainyHealth + } + } + } + + const statusCode = readiness.ready ? 200 : 503 + res.status(statusCode).json(readiness) + } catch (error) { + logger.error('Readiness check failed:', error) + + res.status(503).json({ + ready: false, + timestamp: new Date().toISOString(), + error: error.message + }) + } + }, + + async liveness(req, res, next) { + try { + // Simple liveness check - just verify the process is running + res.status(200).json({ + alive: true, + timestamp: new Date().toISOString(), + pid: process.pid, + uptime: Math.floor(process.uptime()) + }) + } catch (error) { + logger.error('Liveness check failed:', error) + + res.status(503).json({ + alive: false, + timestamp: new Date().toISOString(), + error: error.message + }) + } + }, + + async metrics(req, res, next) { + try { + const { brainyService } = req.app.locals + + // Get detailed metrics + const brainyMetrics = await brainyService.getMetrics() + + const metrics = { + timestamp: new Date().toISOString(), + brainy: brainyMetrics.database, + system: { + ...brainyMetrics.performance, + process: { + pid: process.pid, + version: process.version, + platform: process.platform, + arch: process.arch, + env: process.env.NODE_ENV || 'development' + } + } + } + + res.json({ + success: true, + data: metrics + }) + } catch (error) { + next(error) + } + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/controllers/relationships.js b/examples/brainy-service-template/src/controllers/relationships.js new file mode 100644 index 00000000..ec4cef6f --- /dev/null +++ b/examples/brainy-service-template/src/controllers/relationships.js @@ -0,0 +1,167 @@ +import { logger } from '../utils/logger.js' +import { ApiError } from '../utils/errors.js' + +export const relationshipsController = { + async create(req, res, next) { + try { + const { sourceId, targetId, type, weight, metadata = {}, autoCreateMissingNouns = false } = req.body + const { brainyService } = req.app.locals + + if (!sourceId || !targetId || !type) { + throw new ApiError(400, 'sourceId, targetId, and type are required') + } + + // Add timestamp and source info to metadata + const enhancedMetadata = { + ...metadata, + createdAt: new Date().toISOString(), + source: 'api' + } + + const options = { + weight, + metadata: enhancedMetadata, + autoCreateMissingNouns + } + + const relationshipId = await brainyService.addRelationship(sourceId, targetId, type, options) + + // Get the created relationship to return full details + const relationship = await brainyService.getRelationship(relationshipId) + + res.status(201).json({ + success: true, + id: relationshipId, + data: { + id: relationshipId, + sourceId, + targetId, + type, + weight: relationship?.metadata?.weight, + confidence: relationship?.metadata?.confidence, + intelligentScoring: relationship?.metadata?.intelligentScoring, + metadata: enhancedMetadata + } + }) + } catch (error) { + next(error) + } + }, + + async getById(req, res, next) { + try { + const { id } = req.params + const { brainyService } = req.app.locals + + const relationship = await brainyService.getRelationship(id) + + if (!relationship) { + throw new ApiError(404, `Relationship with ID ${id} not found`) + } + + res.json({ + success: true, + data: relationship + }) + } catch (error) { + next(error) + } + }, + + async update(req, res, next) { + try { + const { id } = req.params + const { weight, metadata = {} } = req.body + const { brainyService } = req.app.locals + + // Check if relationship exists + const existingRelationship = await brainyService.getRelationship(id) + if (!existingRelationship) { + throw new ApiError(404, `Relationship with ID ${id} not found`) + } + + // Prepare updates + const updates = {} + if (weight !== undefined) updates.weight = weight + if (Object.keys(metadata).length > 0) { + updates.metadata = { + ...metadata, + updatedAt: new Date().toISOString() + } + } + + await brainyService.updateRelationship(id, updates) + + res.json({ + success: true, + message: 'Relationship updated successfully', + id + }) + } catch (error) { + next(error) + } + }, + + async delete(req, res, next) { + try { + const { id } = req.params + const { brainyService } = req.app.locals + + // Check if relationship exists + const existingRelationship = await brainyService.getRelationship(id) + if (!existingRelationship) { + throw new ApiError(404, `Relationship with ID ${id} not found`) + } + + await brainyService.deleteRelationship(id) + + res.json({ + success: true, + message: 'Relationship deleted successfully', + id + }) + } catch (error) { + next(error) + } + }, + + async list(req, res, next) { + try { + const { page = 1, limit = 50, sourceId, targetId, type } = req.query + const { brainyService } = req.app.locals + + const options = { + pagination: { + offset: (page - 1) * limit, + limit: Math.min(limit, 100) + } + } + + // Add filters if specified + const filter = {} + if (sourceId) filter.sourceId = sourceId + if (targetId) filter.targetId = targetId + if (type) filter.verbType = type + + if (Object.keys(filter).length > 0) { + options.filter = filter + } + + const result = await brainyService.listRelationships(options) + + res.json({ + success: true, + data: result.items || result, + pagination: { + page: parseInt(page), + limit: parseInt(limit), + count: result.items ? result.items.length : result.length, + hasMore: result.hasMore || false, + totalCount: result.totalCount + } + }) + } catch (error) { + next(error) + } + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/controllers/scoring.js b/examples/brainy-service-template/src/controllers/scoring.js new file mode 100644 index 00000000..e7efb93c --- /dev/null +++ b/examples/brainy-service-template/src/controllers/scoring.js @@ -0,0 +1,195 @@ +import { logger } from '../utils/logger.js' +import { ApiError } from '../utils/errors.js' + +export const scoringController = { + async provideFeedback(req, res, next) { + try { + const { id } = req.params + const { weight, confidence, type = 'correction' } = req.body + const { scoringService } = req.app.locals + + if (!scoringService) { + throw new ApiError(400, 'Intelligent verb scoring is not enabled') + } + + if (weight === undefined) { + throw new ApiError(400, 'Weight is required for feedback') + } + + if (weight < 0 || weight > 1) { + throw new ApiError(400, 'Weight must be between 0 and 1') + } + + if (confidence !== undefined && (confidence < 0 || confidence > 1)) { + throw new ApiError(400, 'Confidence must be between 0 and 1') + } + + const feedbackData = { + weight, + confidence, + type + } + + const result = await scoringService.provideFeedback(id, feedbackData) + + res.json({ + success: true, + message: result.message, + feedback: { + relationshipId: id, + weight, + confidence, + type, + timestamp: new Date().toISOString() + } + }) + } catch (error) { + next(error) + } + }, + + async getStats(req, res, next) { + try { + const { scoringService } = req.app.locals + + if (!scoringService) { + throw new ApiError(400, 'Intelligent verb scoring is not enabled') + } + + const stats = await scoringService.getStats() + + res.json({ + success: true, + data: stats + }) + } catch (error) { + next(error) + } + }, + + async exportLearningData(req, res, next) { + try { + const { scoringService } = req.app.locals + + if (!scoringService) { + throw new ApiError(400, 'Intelligent verb scoring is not enabled') + } + + const result = await scoringService.exportLearningData() + + if (!result.data) { + return res.json({ + success: true, + message: result.message + }) + } + + // Set appropriate headers for download + res.setHeader('Content-Type', 'application/json') + res.setHeader('Content-Disposition', 'attachment; filename="brainy-learning-data.json"') + + res.json({ + success: true, + data: result.data, + timestamp: result.timestamp, + format: result.format + }) + } catch (error) { + next(error) + } + }, + + async importLearningData(req, res, next) { + try { + const { data } = req.body + const { scoringService } = req.app.locals + + if (!scoringService) { + throw new ApiError(400, 'Intelligent verb scoring is not enabled') + } + + if (!data) { + throw new ApiError(400, 'Learning data is required') + } + + const result = await scoringService.importLearningData(data) + + res.json({ + success: true, + message: result.message, + timestamp: new Date().toISOString() + }) + } catch (error) { + next(error) + } + }, + + async clearStats(req, res, next) { + try { + const { scoringService } = req.app.locals + + if (!scoringService) { + throw new ApiError(400, 'Intelligent verb scoring is not enabled') + } + + const result = await scoringService.clearStats() + + res.json({ + success: result.success, + message: result.message, + timestamp: new Date().toISOString() + }) + } catch (error) { + next(error) + } + }, + + async analyzePattern(req, res, next) { + try { + const { sourceType, targetType, relationshipType } = req.body + const { scoringService } = req.app.locals + + if (!scoringService) { + throw new ApiError(400, 'Intelligent verb scoring is not enabled') + } + + if (!sourceType || !targetType || !relationshipType) { + throw new ApiError(400, 'sourceType, targetType, and relationshipType are required') + } + + const analysis = await scoringService.analyzeRelationshipPattern( + sourceType, + targetType, + relationshipType + ) + + res.json({ + success: true, + data: analysis + }) + } catch (error) { + next(error) + } + }, + + async getRecommendations(req, res, next) { + try { + const { entityId } = req.params + const { limit = 10 } = req.query + const { scoringService } = req.app.locals + + if (!scoringService) { + throw new ApiError(400, 'Intelligent verb scoring is not enabled') + } + + const recommendations = await scoringService.getRecommendations(entityId, parseInt(limit)) + + res.json({ + success: true, + data: recommendations + }) + } catch (error) { + next(error) + } + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/middleware/errorHandler.js b/examples/brainy-service-template/src/middleware/errorHandler.js new file mode 100644 index 00000000..6b026517 --- /dev/null +++ b/examples/brainy-service-template/src/middleware/errorHandler.js @@ -0,0 +1,136 @@ +import { logger } from '../utils/logger.js' +import { ApiError } from '../utils/errors.js' + +export const errorHandler = (error, req, res, next) => { + // Default error response + let statusCode = 500 + let message = 'Internal Server Error' + let details = null + + // Handle known API errors + if (error instanceof ApiError) { + statusCode = error.statusCode + message = error.message + + // Add additional details for validation errors + if (error.field) { + details = { field: error.field } + } + + if (error.resource) { + details = { + resource: error.resource, + resourceId: error.resourceId + } + } + } + // Handle Brainy-specific errors + else if (error.name === 'BrainyError') { + statusCode = 400 + message = error.message + details = { type: 'brainy_error' } + } + // Handle validation errors from other sources + else if (error.name === 'ValidationError' || error.name === 'CastError') { + statusCode = 400 + message = error.message + details = { type: 'validation_error' } + } + // Handle JSON parsing errors + else if (error instanceof SyntaxError && error.status === 400 && 'body' in error) { + statusCode = 400 + message = 'Invalid JSON format' + details = { type: 'json_error' } + } + // Handle timeout errors + else if (error.code === 'ETIMEDOUT' || error.message.includes('timeout')) { + statusCode = 504 + message = 'Request timeout' + details = { type: 'timeout_error' } + } + // Handle rate limiting + else if (error.status === 429) { + statusCode = 429 + message = 'Too Many Requests' + details = { type: 'rate_limit_error' } + } + + // Log the error + const logLevel = statusCode >= 500 ? 'error' : 'warn' + const logMessage = `${req.method} ${req.originalUrl} - ${statusCode} ${message}` + const logMeta = { + statusCode, + method: req.method, + url: req.originalUrl, + userAgent: req.get('User-Agent'), + ip: req.ip, + stack: error.stack + } + + logger[logLevel](logMessage, logMeta) + + // Prepare error response + const errorResponse = { + success: false, + error: { + message, + statusCode, + timestamp: new Date().toISOString(), + path: req.originalUrl, + method: req.method + } + } + + // Add details if available + if (details) { + errorResponse.error.details = details + } + + // Add stack trace in development + if (process.env.NODE_ENV === 'development') { + errorResponse.error.stack = error.stack + } + + // Add request ID if available + if (req.id) { + errorResponse.error.requestId = req.id + } + + res.status(statusCode).json(errorResponse) +} + +// 404 handler for unmatched routes +export const notFoundHandler = (req, res) => { + const message = `Route ${req.method} ${req.originalUrl} not found` + + logger.warn('Route not found', { + method: req.method, + url: req.originalUrl, + ip: req.ip, + userAgent: req.get('User-Agent') + }) + + res.status(404).json({ + success: false, + error: { + message, + statusCode: 404, + timestamp: new Date().toISOString(), + path: req.originalUrl, + method: req.method + } + }) +} + +// Async error wrapper +export const asyncHandler = (fn) => { + return (req, res, next) => { + Promise.resolve(fn(req, res, next)).catch(next) + } +} + +export default { + errorHandler, + notFoundHandler, + asyncHandler +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/middleware/validation.js b/examples/brainy-service-template/src/middleware/validation.js new file mode 100644 index 00000000..ec6ff0f3 --- /dev/null +++ b/examples/brainy-service-template/src/middleware/validation.js @@ -0,0 +1,177 @@ +import { ValidationError } from '../utils/errors.js' + +// Validate required fields +export const validateRequired = (fields) => { + return (req, res, next) => { + const missingFields = [] + + fields.forEach(field => { + if (req.body[field] === undefined || req.body[field] === null || req.body[field] === '') { + missingFields.push(field) + } + }) + + if (missingFields.length > 0) { + throw new ValidationError(`Missing required fields: ${missingFields.join(', ')}`) + } + + next() + } +} + +// Validate entity creation +export const validateEntity = (req, res, next) => { + const { data } = req.body + + if (!data) { + throw new ValidationError('Entity data is required') + } + + if (typeof data !== 'object' && typeof data !== 'string') { + throw new ValidationError('Entity data must be an object or string') + } + + next() +} + +// Validate relationship creation +export const validateRelationship = (req, res, next) => { + const { sourceId, targetId, type, weight } = req.body + + if (!sourceId || !targetId || !type) { + throw new ValidationError('sourceId, targetId, and type are required') + } + + if (typeof sourceId !== 'string' || typeof targetId !== 'string' || typeof type !== 'string') { + throw new ValidationError('sourceId, targetId, and type must be strings') + } + + if (sourceId === targetId) { + throw new ValidationError('sourceId and targetId cannot be the same') + } + + if (weight !== undefined) { + if (typeof weight !== 'number' || weight < 0 || weight > 1) { + throw new ValidationError('Weight must be a number between 0 and 1') + } + } + + next() +} + +// Validate search query +export const validateSearch = (req, res, next) => { + const { query, limit, threshold } = req.body + + if (!query) { + throw new ValidationError('Search query is required') + } + + if (typeof query !== 'string' || query.trim().length === 0) { + throw new ValidationError('Search query must be a non-empty string') + } + + if (limit !== undefined) { + if (!Number.isInteger(limit) || limit < 1 || limit > 100) { + throw new ValidationError('Limit must be an integer between 1 and 100') + } + } + + if (threshold !== undefined) { + if (typeof threshold !== 'number' || threshold < 0 || threshold > 1) { + throw new ValidationError('Threshold must be a number between 0 and 1') + } + } + + next() +} + +// Validate feedback data +export const validateFeedback = (req, res, next) => { + const { weight, confidence, type } = req.body + + if (weight === undefined) { + throw new ValidationError('Weight is required for feedback') + } + + if (typeof weight !== 'number' || weight < 0 || weight > 1) { + throw new ValidationError('Weight must be a number between 0 and 1') + } + + if (confidence !== undefined) { + if (typeof confidence !== 'number' || confidence < 0 || confidence > 1) { + throw new ValidationError('Confidence must be a number between 0 and 1') + } + } + + if (type !== undefined) { + const validTypes = ['correction', 'reinforcement', 'adjustment'] + if (!validTypes.includes(type)) { + throw new ValidationError(`Type must be one of: ${validTypes.join(', ')}`) + } + } + + next() +} + +// Validate pagination parameters +export const validatePagination = (req, res, next) => { + const { page = 1, limit = 50 } = req.query + + const pageNum = parseInt(page) + const limitNum = parseInt(limit) + + if (!Number.isInteger(pageNum) || pageNum < 1) { + throw new ValidationError('Page must be a positive integer') + } + + if (!Number.isInteger(limitNum) || limitNum < 1 || limitNum > 100) { + throw new ValidationError('Limit must be an integer between 1 and 100') + } + + // Normalize values + req.query.page = pageNum + req.query.limit = limitNum + + next() +} + +// Validate UUID format (basic check) +export const validateId = (paramName = 'id') => { + return (req, res, next) => { + const id = req.params[paramName] + + if (!id) { + throw new ValidationError(`${paramName} is required`) + } + + // Basic string validation - Brainy uses various ID formats + if (typeof id !== 'string' || id.trim().length === 0) { + throw new ValidationError(`${paramName} must be a valid string`) + } + + next() + } +} + +// Generic validation wrapper +export const validate = (validationFn) => { + return (req, res, next) => { + try { + validationFn(req, res, next) + } catch (error) { + next(error) + } + } +} + +export default { + validateRequired, + validateEntity, + validateRelationship, + validateSearch, + validateFeedback, + validatePagination, + validateId, + validate +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/services/brainyService.js b/examples/brainy-service-template/src/services/brainyService.js new file mode 100644 index 00000000..996060cd --- /dev/null +++ b/examples/brainy-service-template/src/services/brainyService.js @@ -0,0 +1,324 @@ +import { BrainyData } from '@soulcraft/brainy' +import config from 'config' +import { logger } from '../utils/logger.js' + +export class BrainyService { + constructor() { + this.db = null + this.isInitialized = false + } + + async initialize() { + if (this.isInitialized) { + return + } + + try { + // Build Brainy configuration from app config + const brainyConfig = this.buildBrainyConfig() + + // Initialize Brainy database + this.db = new BrainyData(brainyConfig) + await this.db.init() + + this.isInitialized = true + logger.info('Brainy database initialized successfully', { + storage: config.get('brainy.storage.type'), + features: config.get('brainy.features') + }) + } catch (error) { + logger.error('Failed to initialize Brainy database:', error) + throw error + } + } + + buildBrainyConfig() { + const brainyConfig = {} + + // Storage configuration + const storageConfig = config.get('brainy.storage') + if (storageConfig.type === 's3' && storageConfig.s3) { + brainyConfig.storage = { + s3Storage: { + bucketName: storageConfig.s3.bucketName, + region: storageConfig.s3.region, + accessKeyId: storageConfig.s3.accessKeyId, + secretAccessKey: storageConfig.s3.secretAccessKey + } + } + } else if (storageConfig.type === 'filesystem') { + brainyConfig.storage = { + forceFileSystemStorage: true + } + } else { + brainyConfig.storage = { + forceMemoryStorage: true + } + } + + // Cache configuration + if (config.has('brainy.cache')) { + brainyConfig.cache = config.get('brainy.cache') + } + + // Logging configuration + if (config.has('brainy.logging')) { + brainyConfig.logging = config.get('brainy.logging') + } + + // Intelligent verb scoring configuration + if (config.get('brainy.features.intelligentVerbScoring')) { + brainyConfig.intelligentVerbScoring = config.get('brainy.intelligentVerbScoring') + } + + // Real-time updates + if (config.get('brainy.features.realTimeUpdates')) { + brainyConfig.realtimeUpdates = { + enabled: true, + interval: 30000, + updateStatistics: true, + updateIndex: true + } + } + + // Distributed mode + if (config.get('brainy.features.distributedMode')) { + brainyConfig.distributed = true + } + + return brainyConfig + } + + // Entity operations + async addEntity(data, metadata = {}, options = {}) { + this.ensureInitialized() + + try { + const id = await this.db.add(data, metadata, options) + logger.debug('Entity added', { id, metadata: metadata.type || 'unknown' }) + return id + } catch (error) { + logger.error('Failed to add entity:', error) + throw error + } + } + + async getEntity(id) { + this.ensureInitialized() + + try { + const entity = await this.db.get(id) + return entity + } catch (error) { + logger.error(`Failed to get entity ${id}:`, error) + throw error + } + } + + async updateEntity(id, data, metadata = {}) { + this.ensureInitialized() + + try { + await this.db.update(id, data, metadata) + logger.debug('Entity updated', { id }) + return true + } catch (error) { + logger.error(`Failed to update entity ${id}:`, error) + throw error + } + } + + async deleteEntity(id) { + this.ensureInitialized() + + try { + await this.db.delete(id) + logger.debug('Entity deleted', { id }) + return true + } catch (error) { + logger.error(`Failed to delete entity ${id}:`, error) + throw error + } + } + + async searchEntities(query, options = {}) { + this.ensureInitialized() + + try { + const results = await this.db.search(query, options.limit || 10, { + threshold: options.threshold || 0.7, + includeMetadata: true, + ...options + }) + + logger.debug('Entity search completed', { + query: query.substring(0, 50), + resultCount: results.length + }) + + return results + } catch (error) { + logger.error('Entity search failed:', error) + throw error + } + } + + async listEntities(options = {}) { + this.ensureInitialized() + + try { + // Get all entities with pagination if supported + const entities = await this.db.getAllNouns(options) + return entities + } catch (error) { + logger.error('Failed to list entities:', error) + throw error + } + } + + // Relationship operations + async addRelationship(sourceId, targetId, type, options = {}) { + this.ensureInitialized() + + try { + const relationshipId = await this.db.addVerb(sourceId, targetId, undefined, { + type, + weight: options.weight, + metadata: options.metadata, + autoCreateMissingNouns: options.autoCreateMissingNouns || false + }) + + logger.debug('Relationship added', { + relationshipId, + sourceId, + targetId, + type + }) + + return relationshipId + } catch (error) { + logger.error('Failed to add relationship:', error) + throw error + } + } + + async getRelationship(id) { + this.ensureInitialized() + + try { + const relationship = await this.db.getVerb(id) + return relationship + } catch (error) { + logger.error(`Failed to get relationship ${id}:`, error) + throw error + } + } + + async updateRelationship(id, updates) { + this.ensureInitialized() + + try { + await this.db.updateVerb(id, updates) + logger.debug('Relationship updated', { id }) + return true + } catch (error) { + logger.error(`Failed to update relationship ${id}:`, error) + throw error + } + } + + async deleteRelationship(id) { + this.ensureInitialized() + + try { + await this.db.deleteVerb(id) + logger.debug('Relationship deleted', { id }) + return true + } catch (error) { + logger.error(`Failed to delete relationship ${id}:`, error) + throw error + } + } + + async listRelationships(options = {}) { + this.ensureInitialized() + + try { + const relationships = await this.db.getVerbs(options) + return relationships + } catch (error) { + logger.error('Failed to list relationships:', error) + throw error + } + } + + // Health and metrics + async getHealth() { + if (!this.isInitialized) { + return { status: 'not_initialized' } + } + + try { + const stats = await this.db.getStatistics() + return { + status: 'healthy', + database: { + entities: stats.nounCount || 0, + relationships: stats.verbCount || 0, + indexSize: stats.hnswIndexSize || 0 + }, + features: { + intelligentVerbScoring: config.get('brainy.features.intelligentVerbScoring'), + realTimeUpdates: config.get('brainy.features.realTimeUpdates'), + distributedMode: config.get('brainy.features.distributedMode') + } + } + } catch (error) { + logger.error('Health check failed:', error) + return { + status: 'unhealthy', + error: error.message + } + } + } + + async getMetrics() { + this.ensureInitialized() + + try { + const stats = await this.db.getStatistics() + return { + database: stats, + performance: { + // Add performance metrics here + uptime: process.uptime(), + memory: process.memoryUsage() + } + } + } catch (error) { + logger.error('Failed to get metrics:', error) + throw error + } + } + + // Utility methods + ensureInitialized() { + if (!this.isInitialized) { + throw new Error('BrainyService not initialized') + } + } + + async shutdown() { + if (this.db && this.db.cleanup) { + await this.db.cleanup() + } + this.isInitialized = false + logger.info('BrainyService shutdown complete') + } + + // Expose the underlying db for advanced operations + getDatabase() { + this.ensureInitialized() + return this.db + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/services/scoringService.js b/examples/brainy-service-template/src/services/scoringService.js new file mode 100644 index 00000000..9d18b834 --- /dev/null +++ b/examples/brainy-service-template/src/services/scoringService.js @@ -0,0 +1,192 @@ +import { logger } from '../utils/logger.js' + +export class ScoringService { + constructor(brainyService) { + this.brainyService = brainyService + } + + async provideFeedback(relationshipId, feedbackData) { + try { + // Get the relationship to extract source, target, and type + const relationship = await this.brainyService.getRelationship(relationshipId) + + if (!relationship) { + throw new Error(`Relationship ${relationshipId} not found`) + } + + // Provide feedback to the intelligent scoring system + const db = this.brainyService.getDatabase() + await db.provideFeedbackForVerbScoring( + relationship.sourceId, + relationship.targetId, + relationship.type || relationship.verb, + feedbackData.weight, + feedbackData.confidence, + feedbackData.type || 'correction' + ) + + logger.info('Feedback provided for relationship', { + relationshipId, + feedbackType: feedbackData.type || 'correction', + weight: feedbackData.weight, + confidence: feedbackData.confidence + }) + + return { + success: true, + message: 'Feedback provided successfully' + } + } catch (error) { + logger.error('Failed to provide feedback:', error) + throw error + } + } + + async getStats() { + try { + const db = this.brainyService.getDatabase() + const stats = db.getVerbScoringStats() + + if (!stats) { + return { + message: 'Intelligent verb scoring is not enabled or has no data yet', + stats: null + } + } + + logger.debug('Retrieved scoring statistics', { + totalRelationships: stats.totalRelationships + }) + + return { + stats, + timestamp: new Date().toISOString() + } + } catch (error) { + logger.error('Failed to get scoring statistics:', error) + throw error + } + } + + async exportLearningData() { + try { + const db = this.brainyService.getDatabase() + const learningData = db.exportVerbScoringLearningData() + + if (!learningData) { + return { + message: 'No learning data available for export', + data: null + } + } + + logger.info('Learning data exported successfully') + + return { + data: learningData, + timestamp: new Date().toISOString(), + format: 'json' + } + } catch (error) { + logger.error('Failed to export learning data:', error) + throw error + } + } + + async importLearningData(learningData) { + try { + const db = this.brainyService.getDatabase() + + // If data is an object, stringify it + const dataString = typeof learningData === 'string' + ? learningData + : JSON.stringify(learningData) + + db.importVerbScoringLearningData(dataString) + + logger.info('Learning data imported successfully') + + return { + success: true, + message: 'Learning data imported successfully' + } + } catch (error) { + logger.error('Failed to import learning data:', error) + throw error + } + } + + async clearStats() { + try { + const db = this.brainyService.getDatabase() + + // Access the intelligent scoring system directly if available + if (db.intelligentVerbScoring && db.intelligentVerbScoring.enabled) { + db.intelligentVerbScoring.clearStats() + + logger.info('Scoring statistics cleared') + + return { + success: true, + message: 'Scoring statistics cleared successfully' + } + } else { + return { + success: false, + message: 'Intelligent verb scoring is not enabled' + } + } + } catch (error) { + logger.error('Failed to clear scoring statistics:', error) + throw error + } + } + + // Analysis methods + async analyzeRelationshipPattern(sourceType, targetType, relationshipType) { + try { + const stats = await this.getStats() + + if (!stats.stats) { + return { + message: 'No data available for pattern analysis' + } + } + + // Find patterns for the specific relationship type + const pattern = `${sourceType}-${relationshipType}-${targetType}` + const relevantRelationships = stats.stats.topRelationships.filter(rel => + rel.relationship.includes(relationshipType) + ) + + return { + pattern, + relationships: relevantRelationships, + analysis: { + averageWeight: relevantRelationships.reduce((sum, rel) => sum + rel.averageWeight, 0) / relevantRelationships.length || 0, + totalOccurrences: relevantRelationships.reduce((sum, rel) => sum + rel.count, 0), + confidence: relevantRelationships.length > 5 ? 'high' : 'low' + } + } + } catch (error) { + logger.error('Failed to analyze relationship pattern:', error) + throw error + } + } + + async getRecommendations(entityId, limit = 10) { + try { + // This would implement recommendation logic based on learned patterns + // For now, return a placeholder + return { + entityId, + recommendations: [], + message: 'Recommendation system not yet implemented', + basedOn: 'intelligent_verb_scoring_patterns' + } + } catch (error) { + logger.error('Failed to get recommendations:', error) + throw error + } + } +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/utils/errors.js b/examples/brainy-service-template/src/utils/errors.js new file mode 100644 index 00000000..119b421b --- /dev/null +++ b/examples/brainy-service-template/src/utils/errors.js @@ -0,0 +1,95 @@ +export class ApiError extends Error { + constructor(statusCode, message, isOperational = true, stack = '') { + super(message) + this.name = this.constructor.name + this.statusCode = statusCode + this.isOperational = isOperational + + if (stack) { + this.stack = stack + } else { + Error.captureStackTrace(this, this.constructor) + } + } +} + +export class ValidationError extends ApiError { + constructor(message, field = null) { + super(400, message) + this.field = field + this.name = 'ValidationError' + } +} + +export class NotFoundError extends ApiError { + constructor(resource, id = null) { + const message = id + ? `${resource} with ID ${id} not found` + : `${resource} not found` + super(404, message) + this.name = 'NotFoundError' + this.resource = resource + this.resourceId = id + } +} + +export class ConflictError extends ApiError { + constructor(message) { + super(409, message) + this.name = 'ConflictError' + } +} + +export class DatabaseError extends ApiError { + constructor(message, operation = null) { + super(500, message) + this.name = 'DatabaseError' + this.operation = operation + } +} + +export class AuthenticationError extends ApiError { + constructor(message = 'Authentication failed') { + super(401, message) + this.name = 'AuthenticationError' + } +} + +export class AuthorizationError extends ApiError { + constructor(message = 'Insufficient permissions') { + super(403, message) + this.name = 'AuthorizationError' + } +} + +export class RateLimitError extends ApiError { + constructor(message = 'Rate limit exceeded') { + super(429, message) + this.name = 'RateLimitError' + } +} + +// Error factory for common scenarios +export const createError = { + validation: (message, field) => new ValidationError(message, field), + notFound: (resource, id) => new NotFoundError(resource, id), + conflict: (message) => new ConflictError(message), + database: (message, operation) => new DatabaseError(message, operation), + auth: (message) => new AuthenticationError(message), + forbidden: (message) => new AuthorizationError(message), + rateLimit: (message) => new RateLimitError(message), + badRequest: (message) => new ApiError(400, message), + internal: (message) => new ApiError(500, message) +} + +export default { + ApiError, + ValidationError, + NotFoundError, + ConflictError, + DatabaseError, + AuthenticationError, + AuthorizationError, + RateLimitError, + createError +} \ No newline at end of file diff --git a/examples/brainy-service-template/src/utils/logger.js b/examples/brainy-service-template/src/utils/logger.js new file mode 100644 index 00000000..410f2ae3 --- /dev/null +++ b/examples/brainy-service-template/src/utils/logger.js @@ -0,0 +1,63 @@ +import winston from 'winston' +import config from 'config' + +const logLevel = config.get('logging.level') || 'info' +const logFormat = config.get('logging.format') || 'combined' + +// Create custom format for development +const devFormat = winston.format.combine( + winston.format.timestamp({ format: 'HH:mm:ss' }), + winston.format.colorize(), + winston.format.printf(({ timestamp, level, message, ...meta }) => { + const metaStr = Object.keys(meta).length ? JSON.stringify(meta, null, 2) : '' + return `${timestamp} [${level}]: ${message} ${metaStr}` + }) +) + +// Create custom format for production +const prodFormat = winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json() +) + +// Create logger instance +export const logger = winston.createLogger({ + level: logLevel, + format: logFormat === 'dev' ? devFormat : prodFormat, + defaultMeta: { + service: 'brainy-service', + version: process.env.npm_package_version || '1.0.0' + }, + transports: [ + new winston.transports.Console({ + handleExceptions: true, + handleRejections: true + }) + ] +}) + +// Add file logging for production +if (process.env.NODE_ENV === 'production') { + logger.add(new winston.transports.File({ + filename: 'logs/error.log', + level: 'error', + handleExceptions: true, + maxsize: 5242880, // 5MB + maxFiles: 5 + })) + + logger.add(new winston.transports.File({ + filename: 'logs/combined.log', + maxsize: 5242880, // 5MB + maxFiles: 5 + })) +} + +// Capture unhandled errors +logger.exceptions.handle( + new winston.transports.Console(), + new winston.transports.File({ filename: 'logs/exceptions.log' }) +) + +export default logger \ No newline at end of file diff --git a/examples/brainy-service-template/tests/entities.test.js b/examples/brainy-service-template/tests/entities.test.js new file mode 100644 index 00000000..a7d8a3c2 --- /dev/null +++ b/examples/brainy-service-template/tests/entities.test.js @@ -0,0 +1,135 @@ +import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest' +import request from 'supertest' +import app from '../src/app.js' + +describe('Entity Endpoints', () => { + let server + let testEntityId + + beforeAll(async () => { + server = app.listen(0) + // Wait for Brainy to initialize + await new Promise(resolve => setTimeout(resolve, 2000)) + }) + + afterAll(async () => { + if (server) { + server.close() + } + }) + + describe('POST /api/entities', () => { + it('should create a new entity', async () => { + const entityData = { + data: { name: 'Test Entity', type: 'test' }, + metadata: { category: 'test' } + } + + const response = await request(app) + .post('/api/entities') + .send(entityData) + .expect(201) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('success', true) + expect(response.body).toHaveProperty('id') + expect(response.body.data).toHaveProperty('data') + expect(response.body.data.data).toEqual(entityData.data) + + testEntityId = response.body.id + }) + + it('should return 400 for missing data', async () => { + const response = await request(app) + .post('/api/entities') + .send({}) + .expect(400) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('success', false) + expect(response.body.error).toHaveProperty('message') + }) + }) + + describe('GET /api/entities/:id', () => { + it('should retrieve an entity by ID', async () => { + if (!testEntityId) { + // Create test entity first + const entityData = { + data: { name: 'Test Entity', type: 'test' } + } + const createResponse = await request(app) + .post('/api/entities') + .send(entityData) + testEntityId = createResponse.body.id + } + + const response = await request(app) + .get(`/api/entities/${testEntityId}`) + .expect(200) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('success', true) + expect(response.body).toHaveProperty('data') + expect(response.body.data).toHaveProperty('id', testEntityId) + }) + + it('should return 404 for non-existent entity', async () => { + const response = await request(app) + .get('/api/entities/non-existent-id') + .expect(404) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('success', false) + expect(response.body.error).toHaveProperty('statusCode', 404) + }) + }) + + describe('POST /api/entities/search', () => { + it('should search entities', async () => { + const searchQuery = { + query: 'test entity', + limit: 5, + threshold: 0.5 + } + + const response = await request(app) + .post('/api/entities/search') + .send(searchQuery) + .expect(200) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('success', true) + expect(response.body).toHaveProperty('results') + expect(response.body).toHaveProperty('query', searchQuery.query) + expect(Array.isArray(response.body.results)).toBe(true) + }) + + it('should return 400 for missing query', async () => { + const response = await request(app) + .post('/api/entities/search') + .send({}) + .expect(400) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('success', false) + expect(response.body.error.message).toContain('Query is required') + }) + }) + + describe('GET /api/entities', () => { + it('should list entities with pagination', async () => { + const response = await request(app) + .get('/api/entities?page=1&limit=10') + .expect(200) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('success', true) + expect(response.body).toHaveProperty('data') + expect(response.body).toHaveProperty('pagination') + expect(response.body.pagination).toHaveProperty('page', 1) + expect(response.body.pagination).toHaveProperty('limit', 10) + expect(Array.isArray(response.body.data)).toBe(true) + }) + }) +}) \ No newline at end of file diff --git a/examples/brainy-service-template/tests/health.test.js b/examples/brainy-service-template/tests/health.test.js new file mode 100644 index 00000000..e0f47034 --- /dev/null +++ b/examples/brainy-service-template/tests/health.test.js @@ -0,0 +1,75 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import request from 'supertest' +import app from '../src/app.js' + +describe('Health Endpoints', () => { + let server + + beforeAll(async () => { + server = app.listen(0) // Use random available port + }) + + afterAll(async () => { + if (server) { + server.close() + } + }) + + describe('GET /health', () => { + it('should return health status', async () => { + const response = await request(app) + .get('/health') + .expect('Content-Type', /json/) + + expect(response.status).toBeGreaterThanOrEqual(200) + expect(response.body).toHaveProperty('status') + expect(response.body).toHaveProperty('timestamp') + expect(response.body).toHaveProperty('version') + expect(response.body).toHaveProperty('services') + expect(response.body.services).toHaveProperty('brainy') + }) + }) + + describe('GET /health/liveness', () => { + it('should return liveness status', async () => { + const response = await request(app) + .get('/health/liveness') + .expect(200) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('alive', true) + expect(response.body).toHaveProperty('timestamp') + expect(response.body).toHaveProperty('pid') + expect(response.body).toHaveProperty('uptime') + }) + }) + + describe('GET /health/readiness', () => { + it('should return readiness status', async () => { + const response = await request(app) + .get('/health/readiness') + .expect('Content-Type', /json/) + + expect(response.status).toBeGreaterThanOrEqual(200) + expect(response.body).toHaveProperty('ready') + expect(response.body).toHaveProperty('timestamp') + expect(response.body).toHaveProperty('checks') + expect(response.body.checks).toHaveProperty('brainy') + }) + }) + + describe('GET /health/metrics', () => { + it('should return system metrics', async () => { + const response = await request(app) + .get('/health/metrics') + .expect(200) + .expect('Content-Type', /json/) + + expect(response.body).toHaveProperty('success', true) + expect(response.body).toHaveProperty('data') + expect(response.body.data).toHaveProperty('timestamp') + expect(response.body.data).toHaveProperty('system') + expect(response.body.data.system).toHaveProperty('process') + }) + }) +}) \ No newline at end of file diff --git a/examples/brainy-service-template/tests/setup.js b/examples/brainy-service-template/tests/setup.js new file mode 100644 index 00000000..975a21c7 --- /dev/null +++ b/examples/brainy-service-template/tests/setup.js @@ -0,0 +1,33 @@ +import { beforeAll, afterAll } from 'vitest' +import config from 'config' + +// Global test setup +beforeAll(async () => { + // Set test environment + process.env.NODE_ENV = 'test' + process.env.LOG_LEVEL = 'error' + + // Suppress console output during tests + const originalConsoleLog = console.log + const originalConsoleWarn = console.warn + + console.log = (...args) => { + if (!args[0]?.includes?.('Model loaded') && !args[0]?.includes?.('Brainy')) { + originalConsoleLog.apply(console, args) + } + } + + console.warn = (...args) => { + if (!args[0]?.includes?.('Model') && !args[0]?.includes?.('TensorFlow')) { + originalConsoleWarn.apply(console, args) + } + } +}) + +afterAll(async () => { + // Cleanup any global resources + // Force garbage collection if available + if (global.gc) { + global.gc() + } +}) \ No newline at end of file diff --git a/examples/brainy-service-template/vitest.config.js b/examples/brainy-service-template/vitest.config.js new file mode 100644 index 00000000..887f4bc0 --- /dev/null +++ b/examples/brainy-service-template/vitest.config.js @@ -0,0 +1,26 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + timeout: 60000, // 60 seconds for model loading + testTimeout: 60000, + hookTimeout: 60000, + teardownTimeout: 60000, + reporters: process.env.CI ? ['json'] : ['verbose'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'coverage/**', + 'dist/**', + '**/node_modules/**', + '**/*.config.js', + '**/*.config.ts', + 'tests/**' + ] + }, + setupFiles: ['./tests/setup.js'], + globalSetup: ['./tests/globalSetup.js'] + } +}) \ No newline at end of file