feat: add zero-configuration Brainy service template with augmentation-first architecture

- Implement WebSocket augmentation for real-time communication
- Implement WebRTC augmentation for peer-to-peer connections
- Implement HTTP augmentation as minimal REST fallback
- Add auto-discovery augmentation for data pattern analysis
- Add adaptive storage augmentation for intelligent resource management
- Add environment adapter augmentation for universal compatibility
- Template auto-detects environment (browser, Node.js, serverless, containers)
- Intelligent transport selection (WebRTC → WebSocket → HTTP)
- Automatic storage optimization (memory → filesystem → S3)
- Zero configuration required - just npm start
- Includes intelligent verb scoring by default
- Works in any environment without configuration
- Full documentation and examples included
This commit is contained in:
David Snelling 2025-08-06 18:17:32 -07:00
parent 880b8f74e3
commit ee003a9473
30 changed files with 3548 additions and 0 deletions

View file

@ -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/

View file

@ -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"]

View file

@ -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.

View file

@ -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
}
}

View file

@ -0,0 +1,20 @@
{
"service": {
"port": 3000
},
"brainy": {
"features": {
"intelligentVerbScoring": true
},
"intelligentVerbScoring": {
"enabled": true,
"baseConfidence": 0.6
},
"logging": {
"verbose": true
}
},
"logging": {
"level": "debug"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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

View file

@ -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"
}
}

View file

@ -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)
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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}

View file

@ -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)
}
}
}

View file

@ -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)
}
}
}

View file

@ -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)
}
}
}

View file

@ -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)
}
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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)
})
})
})

View file

@ -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')
})
})
})

View file

@ -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()
}
})

View file

@ -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']
}
})