**chore: remove deprecated Brainy Cloud Wrapper services and deployment scripts**
- Removed entire `cloud-wrapper` directory and its contents, including: - Obsolete deployment scripts: `deploy-aws.js`, `deploy-cloudflare.js`, `deploy-gcp.js`. - Related service implementations: `brainyService.ts`, `mcpService.ts`, and `index.ts`. - Legacy health-check endpoints, WebSocket handlers, and MCP service initialization. - The removed code handled cloud-specific deployment and configuration for Brainy services, which is no longer relevant. This cleanup removes unsupported and outdated code, improving repository maintainability and aligning it with current project goals.
This commit is contained in:
parent
f5e4b8b93e
commit
3bc374730b
13 changed files with 0 additions and 6228 deletions
|
|
@ -1,24 +0,0 @@
|
|||
# Server configuration
|
||||
PORT=3000
|
||||
|
||||
# Storage configuration
|
||||
# Options: 'filesystem', 'memory', 's3'
|
||||
STORAGE_TYPE=filesystem
|
||||
STORAGE_ROOT_DIR=./data
|
||||
|
||||
# S3 Storage configuration (only used if STORAGE_TYPE=s3)
|
||||
S3_BUCKET_NAME=your-bucket-name
|
||||
S3_ACCESS_KEY_ID=your-access-key
|
||||
S3_SECRET_ACCESS_KEY=your-secret-key
|
||||
S3_REGION=us-east-1
|
||||
# Optional: For custom S3-compatible services like MinIO, DigitalOcean Spaces, etc.
|
||||
# S3_ENDPOINT=https://your-custom-endpoint
|
||||
|
||||
# Embedding configuration
|
||||
# Set to 'true' to use simple embedding (faster but less accurate)
|
||||
USE_SIMPLE_EMBEDDING=false
|
||||
|
||||
# HNSW index configuration (optional)
|
||||
# HNSW_M=16 # Max connections per node
|
||||
# HNSW_EF_CONSTRUCTION=200 # Construction candidate list size
|
||||
# HNSW_EF_SEARCH=50 # Search candidate list size
|
||||
|
|
@ -1,341 +0,0 @@
|
|||
<div align="center">
|
||||
<img src="../brainy.png" alt="Brainy Logo" width="200"/>
|
||||
|
||||
# Brainy Cloud Wrapper
|
||||
</div>
|
||||
|
||||
A standalone web service wrapper for the [Brainy](https://github.com/soulcraft/brainy) vector graph database. This wrapper allows you to deploy Brainy as a RESTful API service on various cloud platforms including AWS, Google Cloud, and Cloudflare.
|
||||
|
||||
## Features
|
||||
|
||||
- RESTful API for all Brainy operations
|
||||
- WebSocket API for real-time updates and subscriptions
|
||||
- Model Control Protocol (MCP) service for external model access
|
||||
- Support for multiple storage backends (Memory, FileSystem, S3)
|
||||
- Configurable via environment variables
|
||||
- Deployment scripts for AWS, Google Cloud, and Cloudflare
|
||||
- Secure by default with Helmet middleware
|
||||
- Cross-origin resource sharing (CORS) support
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 23.11.0 or higher
|
||||
- npm or yarn
|
||||
- For cloud deployments:
|
||||
- AWS: AWS CLI installed and configured
|
||||
- Google Cloud: Google Cloud SDK installed and configured
|
||||
- Cloudflare: Wrangler CLI installed and configured
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/soulcraft/brainy.git
|
||||
cd brainy/cloud-wrapper
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm install --legacy-peer-deps
|
||||
```
|
||||
|
||||
3. Create a `.env` file based on the example:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
4. Edit the `.env` file to configure your environment.
|
||||
|
||||
## Configuration
|
||||
|
||||
The cloud wrapper can be configured using environment variables. See the `.env.example` file for available options.
|
||||
|
||||
### Storage Options
|
||||
|
||||
- `STORAGE_TYPE`: The type of storage to use. Options:
|
||||
- `memory`: In-memory storage (default for Cloudflare)
|
||||
- `filesystem`: File system storage (default for local and AWS/GCP)
|
||||
- `s3`: S3-compatible storage (AWS S3, MinIO, etc.)
|
||||
- `r2`: Cloudflare R2 storage (Cloudflare only)
|
||||
|
||||
### S3 Storage Configuration
|
||||
|
||||
When using `STORAGE_TYPE=s3`, the following environment variables are required:
|
||||
|
||||
- `S3_BUCKET_NAME`: The name of the S3 bucket
|
||||
- `S3_ACCESS_KEY_ID`: Your S3 access key ID
|
||||
- `S3_SECRET_ACCESS_KEY`: Your S3 secret access key
|
||||
- `S3_REGION`: The S3 region (default: `us-east-1`)
|
||||
- `S3_ENDPOINT` (optional): Custom endpoint for S3-compatible services
|
||||
|
||||
### MCP Service Configuration
|
||||
|
||||
The Model Control Protocol (MCP) service can be configured using the following environment variables:
|
||||
|
||||
- `MCP_WS_PORT`: Port for the MCP WebSocket server (if not set, WebSocket server is disabled)
|
||||
- `MCP_REST_PORT`: Port for the MCP REST server (if not set, REST server is disabled)
|
||||
- `MCP_ENABLE_AUTH`: Enable authentication for MCP requests (`true` or `false`)
|
||||
- `MCP_API_KEYS`: Comma-separated list of API keys for authentication
|
||||
- `MCP_RATE_LIMIT_REQUESTS`: Maximum number of requests per time window
|
||||
- `MCP_RATE_LIMIT_WINDOW_MS`: Time window for rate limiting in milliseconds (default: `60000`)
|
||||
- `MCP_ENABLE_CORS`: Enable CORS for MCP REST server (`true` or `false`)
|
||||
|
||||
## Local Development
|
||||
|
||||
1. Build the project:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. Start the development server:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. The API will be available at `http://localhost:3000`.
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Brainy Cloud Wrapper provides REST API, WebSocket API, and Model Control Protocol (MCP) API for interacting with the database.
|
||||
|
||||
### REST API
|
||||
|
||||
#### Status
|
||||
|
||||
- `GET /api/status`: Get database status
|
||||
|
||||
#### Nouns (Entities)
|
||||
|
||||
- `POST /api/nouns`: Add a new noun
|
||||
- Body: `{ "text": "Your text", "metadata": { ... } }`
|
||||
- `GET /api/nouns/:id`: Get a noun by ID
|
||||
- `PUT /api/nouns/:id`: Update noun metadata
|
||||
- Body: `{ "metadata": { ... } }`
|
||||
- `DELETE /api/nouns/:id`: Delete a noun
|
||||
|
||||
#### Search
|
||||
|
||||
- `POST /api/search`: Search for similar nouns
|
||||
- Body: `{ "query": "Your search query", "limit": 10 }`
|
||||
|
||||
#### Verbs (Relationships)
|
||||
|
||||
- `POST /api/verbs`: Add a relationship between nouns
|
||||
- Body: `{ "sourceId": "...", "targetId": "...", "metadata": { ... } }`
|
||||
- `GET /api/verbs`: Get all relationships
|
||||
- `GET /api/verbs/source/:id`: Get relationships by source
|
||||
- `GET /api/verbs/target/:id`: Get relationships by target
|
||||
- `DELETE /api/verbs/:id`: Delete a relationship
|
||||
|
||||
#### Database Management
|
||||
|
||||
- `DELETE /api/clear`: Clear all data
|
||||
|
||||
### WebSocket API
|
||||
|
||||
The WebSocket API provides real-time communication with the Brainy database. Connect to the WebSocket server at `ws://your-server:port`.
|
||||
|
||||
#### Message Format
|
||||
|
||||
All WebSocket messages follow this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "messageType",
|
||||
"id": "unique-message-id",
|
||||
"payload": {
|
||||
// Message-specific data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Available Message Types
|
||||
|
||||
##### Status
|
||||
- Request: `{ "type": "status" }`
|
||||
- Response: `{ "type": "status", "id": "...", "payload": { /* database status */ } }`
|
||||
|
||||
##### Nouns (Entities)
|
||||
- Add a noun:
|
||||
- Request: `{ "type": "addNoun", "payload": { "text": "Your text", "metadata": { ... } } }`
|
||||
- Response: `{ "type": "addNoun", "id": "...", "payload": { "id": "new-noun-id" } }`
|
||||
|
||||
- Get a noun:
|
||||
- Request: `{ "type": "getNoun", "payload": { "id": "noun-id" } }`
|
||||
- Response: `{ "type": "getNoun", "id": "...", "payload": { /* noun data */ } }`
|
||||
|
||||
- Update a noun:
|
||||
- Request: `{ "type": "updateNoun", "payload": { "id": "noun-id", "metadata": { ... } } }`
|
||||
- Response: `{ "type": "updateNoun", "id": "...", "payload": { "success": true } }`
|
||||
|
||||
- Delete a noun:
|
||||
- Request: `{ "type": "deleteNoun", "payload": { "id": "noun-id" } }`
|
||||
- Response: `{ "type": "deleteNoun", "id": "...", "payload": { "success": true } }`
|
||||
|
||||
##### Search
|
||||
- Search for similar nouns:
|
||||
- Request: `{ "type": "search", "payload": { "query": "Your search query", "limit": 10 } }`
|
||||
- Response: `{ "type": "search", "id": "...", "payload": [ /* search results */ ] }`
|
||||
|
||||
##### Verbs (Relationships)
|
||||
- Add a verb:
|
||||
- Request: `{ "type": "addVerb", "payload": { "sourceId": "...", "targetId": "...", "metadata": { ... } } }`
|
||||
- Response: `{ "type": "addVerb", "id": "...", "payload": { "success": true } }`
|
||||
|
||||
- Get all verbs:
|
||||
- Request: `{ "type": "getVerbs" }`
|
||||
- Response: `{ "type": "getVerbs", "id": "...", "payload": [ /* all verbs */ ] }`
|
||||
|
||||
- Get verbs by source:
|
||||
- Request: `{ "type": "getVerbsBySource", "payload": { "id": "source-id" } }`
|
||||
- Response: `{ "type": "getVerbsBySource", "id": "...", "payload": [ /* verbs */ ] }`
|
||||
|
||||
- Get verbs by target:
|
||||
- Request: `{ "type": "getVerbsByTarget", "payload": { "id": "target-id" } }`
|
||||
- Response: `{ "type": "getVerbsByTarget", "id": "...", "payload": [ /* verbs */ ] }`
|
||||
|
||||
- Delete a verb:
|
||||
- Request: `{ "type": "deleteVerb", "payload": { "id": "verb-id" } }`
|
||||
- Response: `{ "type": "deleteVerb", "id": "...", "payload": { "success": true } }`
|
||||
|
||||
##### Database Management
|
||||
- Clear all data:
|
||||
- Request: `{ "type": "clear" }`
|
||||
- Response: `{ "type": "clear", "id": "...", "payload": { "success": true } }`
|
||||
|
||||
#### Real-time Subscriptions
|
||||
|
||||
The WebSocket API supports subscribing to real-time updates:
|
||||
|
||||
- Subscribe to updates:
|
||||
- Request: `{ "type": "subscribe", "payload": { "type": "nouns" } }`
|
||||
- Response: `{ "type": "subscribe", "id": "...", "payload": { "success": true, "type": "nouns" } }`
|
||||
|
||||
- Unsubscribe from updates:
|
||||
- Request: `{ "type": "unsubscribe", "payload": { "type": "nouns" } }`
|
||||
- Response: `{ "type": "unsubscribe", "id": "...", "payload": { "success": true, "type": "nouns" } }`
|
||||
|
||||
Available subscription types:
|
||||
- `nouns`: Updates about nouns (added, updated, deleted)
|
||||
- `verbs`: Updates about verbs (added, deleted)
|
||||
- `searchResults`: Updates about search results
|
||||
|
||||
When subscribed, you'll receive messages when relevant events occur:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "subscribe",
|
||||
"payload": {
|
||||
"type": "nouns",
|
||||
"data": {
|
||||
"type": "added",
|
||||
"id": "noun-id",
|
||||
"data": { /* noun data */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MCP API
|
||||
|
||||
The Model Control Protocol (MCP) API provides a standardized interface for external models to access Brainy data and use the augmentation pipeline as tools. The MCP API is available through both WebSocket and REST endpoints.
|
||||
|
||||
#### MCP REST API Endpoints
|
||||
|
||||
- `POST /mcp/data`: Access Brainy data (search, get, add, etc.)
|
||||
- `POST /mcp/tools`: Execute augmentation pipeline tools
|
||||
- `POST /mcp/system`: Get system information
|
||||
- `POST /mcp/auth`: Authenticate with the MCP service
|
||||
- `GET /mcp/tools`: Get available tools
|
||||
|
||||
#### MCP WebSocket
|
||||
|
||||
Connect to the MCP WebSocket server at `ws://your-server:MCP_WS_PORT` and send JSON messages in the MCP format:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "DATA_ACCESS",
|
||||
"requestId": "unique-request-id",
|
||||
"version": "1.0",
|
||||
"operation": "search",
|
||||
"parameters": {
|
||||
"query": "Your search query",
|
||||
"k": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For detailed documentation on the MCP API, see the [MCP documentation](../src/mcp/README.md).
|
||||
|
||||
## Cloud Deployment
|
||||
|
||||
### AWS Lambda and API Gateway
|
||||
|
||||
1. Configure AWS-specific environment variables:
|
||||
```
|
||||
AWS_REGION=us-east-1
|
||||
AWS_FUNCTION_NAME=brainy-cloud-service
|
||||
AWS_API_GATEWAY_NAME=brainy-api
|
||||
AWS_STAGE_NAME=prod
|
||||
AWS_ACCOUNT_ID=your-account-id
|
||||
```
|
||||
|
||||
2. Deploy to AWS:
|
||||
```bash
|
||||
npm run deploy:aws
|
||||
```
|
||||
|
||||
### Google Cloud Run
|
||||
|
||||
1. Configure GCP-specific environment variables:
|
||||
```
|
||||
GCP_PROJECT_ID=your-project-id
|
||||
GCP_REGION=us-central1
|
||||
GCP_SERVICE_NAME=brainy-cloud-service
|
||||
GCP_IMAGE_NAME=brainy-cloud-service
|
||||
GCP_MEMORY=512Mi
|
||||
GCP_CPU=1
|
||||
GCP_MAX_INSTANCES=10
|
||||
GCP_MIN_INSTANCES=0
|
||||
```
|
||||
|
||||
2. Deploy to Google Cloud:
|
||||
```bash
|
||||
npm run deploy:gcp
|
||||
```
|
||||
|
||||
### Cloudflare Workers
|
||||
|
||||
1. Configure Cloudflare-specific environment variables:
|
||||
```
|
||||
CF_ACCOUNT_ID=your-account-id
|
||||
CF_WORKER_NAME=brainy-cloud-service
|
||||
CF_KV_NAMESPACE=BRAINY_STORAGE
|
||||
CF_R2_BUCKET=brainy-storage
|
||||
```
|
||||
|
||||
2. Deploy to Cloudflare:
|
||||
```bash
|
||||
npm run deploy:cloudflare
|
||||
```
|
||||
|
||||
## Storage Considerations
|
||||
|
||||
### AWS Lambda
|
||||
|
||||
When deploying to AWS Lambda, it's recommended to use S3 storage for persistence. The filesystem storage option will work but data will be lost when the Lambda function is recycled.
|
||||
|
||||
### Google Cloud Run
|
||||
|
||||
For Google Cloud Run, you can use either filesystem storage (for ephemeral storage) or S3-compatible storage (like Google Cloud Storage with an S3 compatibility layer).
|
||||
|
||||
### Cloudflare Workers
|
||||
|
||||
Cloudflare Workers have limited storage options. The recommended approach is to use:
|
||||
- Cloudflare KV for small datasets
|
||||
- Cloudflare R2 for larger datasets
|
||||
- Memory storage for temporary data
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
4082
cloud-wrapper/package-lock.json
generated
4082
cloud-wrapper/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,92 +0,0 @@
|
|||
{
|
||||
"name": "@soulcraft/brainy-cloud",
|
||||
"version": "0.1.0",
|
||||
"description": "Cloud deployment wrapper for Brainy vector graph database",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "nodemon --exec node --loader ts-node/esm src/index.ts",
|
||||
"deploy:aws": "node scripts/deploy-aws.js",
|
||||
"deploy:gcp": "node scripts/deploy-gcp.js",
|
||||
"deploy:cloudflare": "node scripts/deploy-cloudflare.js"
|
||||
},
|
||||
"keywords": [
|
||||
"brainy",
|
||||
"vector-database",
|
||||
"cloud",
|
||||
"aws",
|
||||
"google-cloud",
|
||||
"cloudflare"
|
||||
],
|
||||
"author": "David Snelling (david@soulcraft.com)",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@soulcraft/brainy": "^0.9.34",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"helmet": "^7.1.0",
|
||||
"morgan": "^1.10.0",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"nodemon": "^3.1.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.4.0"
|
||||
},
|
||||
"prettier": {
|
||||
"arrowParens": "always",
|
||||
"bracketSameLine": true,
|
||||
"bracketSpacing": true,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"printWidth": 80,
|
||||
"proseWrap": "preserve",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"useTabs": false
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"args": "after-used",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"error",
|
||||
"never"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* AWS Deployment Script for Brainy Cloud Wrapper
|
||||
*
|
||||
* This script helps deploy the Brainy cloud wrapper to AWS Lambda and API Gateway.
|
||||
* It uses the AWS SDK to create and configure the necessary resources.
|
||||
*
|
||||
* Prerequisites:
|
||||
* - AWS CLI installed and configured with appropriate credentials
|
||||
* - Node.js 23.11.0 or higher
|
||||
* - Brainy cloud wrapper built (npm run build)
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Configuration
|
||||
const config = {
|
||||
region: process.env.AWS_REGION || 'us-east-1',
|
||||
functionName: process.env.AWS_FUNCTION_NAME || 'brainy-cloud-service',
|
||||
s3Bucket: process.env.S3_BUCKET_NAME,
|
||||
s3Key: process.env.S3_ACCESS_KEY_ID,
|
||||
s3Secret: process.env.S3_SECRET_ACCESS_KEY,
|
||||
apiGatewayName: process.env.AWS_API_GATEWAY_NAME || 'brainy-api',
|
||||
stageName: process.env.AWS_STAGE_NAME || 'prod'
|
||||
};
|
||||
|
||||
// Validate configuration
|
||||
if (!config.s3Bucket && process.env.STORAGE_TYPE === 's3') {
|
||||
console.error('Error: S3 bucket name is required when using S3 storage');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create deployment package
|
||||
function createDeploymentPackage() {
|
||||
console.log('Creating deployment package...');
|
||||
|
||||
try {
|
||||
// Create a temporary directory for the deployment package
|
||||
if (!fs.existsSync('deploy')) {
|
||||
fs.mkdirSync('deploy');
|
||||
}
|
||||
|
||||
// Copy necessary files
|
||||
execSync('cp -r dist deploy/');
|
||||
execSync('cp package.json deploy/');
|
||||
execSync('cp .env deploy/');
|
||||
|
||||
// Create a zip file
|
||||
execSync('cd deploy && zip -r ../deployment.zip .');
|
||||
|
||||
console.log('Deployment package created successfully');
|
||||
} catch (error) {
|
||||
console.error('Error creating deployment package:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Deploy to AWS Lambda
|
||||
function deployToLambda() {
|
||||
console.log('Deploying to AWS Lambda...');
|
||||
|
||||
try {
|
||||
// Check if function exists
|
||||
try {
|
||||
execSync(`aws lambda get-function --function-name ${config.functionName} --region ${config.region}`);
|
||||
|
||||
// Update existing function
|
||||
execSync(`aws lambda update-function-code --function-name ${config.functionName} --zip-file fileb://deployment.zip --region ${config.region}`);
|
||||
console.log(`Lambda function ${config.functionName} updated successfully`);
|
||||
} catch (error) {
|
||||
// Create new function
|
||||
execSync(`aws lambda create-function --function-name ${config.functionName} --runtime nodejs20.x --handler dist/index.handler --zip-file fileb://deployment.zip --role arn:aws:iam::${process.env.AWS_ACCOUNT_ID}:role/lambda-basic-execution --region ${config.region}`);
|
||||
console.log(`Lambda function ${config.functionName} created successfully`);
|
||||
}
|
||||
|
||||
// Configure environment variables
|
||||
const envVars = {
|
||||
Variables: {
|
||||
NODE_ENV: 'production',
|
||||
STORAGE_TYPE: process.env.STORAGE_TYPE || 'filesystem',
|
||||
S3_BUCKET_NAME: config.s3Bucket,
|
||||
S3_ACCESS_KEY_ID: config.s3Key,
|
||||
S3_SECRET_ACCESS_KEY: config.s3Secret,
|
||||
S3_REGION: config.region
|
||||
}
|
||||
};
|
||||
|
||||
execSync(`aws lambda update-function-configuration --function-name ${config.functionName} --environment '${JSON.stringify(envVars)}' --region ${config.region}`);
|
||||
console.log('Lambda function environment variables configured');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deploying to AWS Lambda:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Create API Gateway
|
||||
function createApiGateway() {
|
||||
console.log('Creating API Gateway...');
|
||||
|
||||
try {
|
||||
// Check if API Gateway exists
|
||||
let apiId;
|
||||
try {
|
||||
const result = execSync(`aws apigateway get-rest-apis --region ${config.region}`).toString();
|
||||
const apis = JSON.parse(result).items;
|
||||
const api = apis.find(api => api.name === config.apiGatewayName);
|
||||
|
||||
if (api) {
|
||||
apiId = api.id;
|
||||
console.log(`Using existing API Gateway: ${apiId}`);
|
||||
} else {
|
||||
throw new Error('API Gateway not found');
|
||||
}
|
||||
} catch (error) {
|
||||
// Create new API Gateway
|
||||
const result = execSync(`aws apigateway create-rest-api --name ${config.apiGatewayName} --region ${config.region}`).toString();
|
||||
apiId = JSON.parse(result).id;
|
||||
console.log(`API Gateway created: ${apiId}`);
|
||||
}
|
||||
|
||||
// Get root resource ID
|
||||
const resourcesResult = execSync(`aws apigateway get-resources --rest-api-id ${apiId} --region ${config.region}`).toString();
|
||||
const rootResourceId = JSON.parse(resourcesResult).items.find(resource => resource.path === '/').id;
|
||||
|
||||
// Create proxy resource
|
||||
let proxyResourceId;
|
||||
try {
|
||||
const proxyResource = JSON.parse(resourcesResult).items.find(resource => resource.path === '/{proxy+}');
|
||||
if (proxyResource) {
|
||||
proxyResourceId = proxyResource.id;
|
||||
} else {
|
||||
throw new Error('Proxy resource not found');
|
||||
}
|
||||
} catch (error) {
|
||||
const proxyResult = execSync(`aws apigateway create-resource --rest-api-id ${apiId} --parent-id ${rootResourceId} --path-part {proxy+} --region ${config.region}`).toString();
|
||||
proxyResourceId = JSON.parse(proxyResult).id;
|
||||
}
|
||||
|
||||
// Create ANY method
|
||||
try {
|
||||
execSync(`aws apigateway put-method --rest-api-id ${apiId} --resource-id ${proxyResourceId} --http-method ANY --authorization-type NONE --region ${config.region}`);
|
||||
} catch (error) {
|
||||
console.log('Method already exists, skipping...');
|
||||
}
|
||||
|
||||
// Create integration
|
||||
try {
|
||||
execSync(`aws apigateway put-integration --rest-api-id ${apiId} --resource-id ${proxyResourceId} --http-method ANY --type AWS_PROXY --integration-http-method POST --uri arn:aws:apigateway:${config.region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${config.region}:${process.env.AWS_ACCOUNT_ID}:function:${config.functionName}/invocations --region ${config.region}`);
|
||||
} catch (error) {
|
||||
console.log('Integration already exists, skipping...');
|
||||
}
|
||||
|
||||
// Deploy API
|
||||
execSync(`aws apigateway create-deployment --rest-api-id ${apiId} --stage-name ${config.stageName} --region ${config.region}`);
|
||||
|
||||
console.log(`API Gateway deployed: https://${apiId}.execute-api.${config.region}.amazonaws.com/${config.stageName}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating API Gateway:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
function cleanup() {
|
||||
console.log('Cleaning up...');
|
||||
|
||||
try {
|
||||
execSync('rm -rf deploy');
|
||||
execSync('rm -f deployment.zip');
|
||||
|
||||
console.log('Cleanup completed');
|
||||
} catch (error) {
|
||||
console.error('Error during cleanup:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Main function
|
||||
async function main() {
|
||||
try {
|
||||
console.log('Starting AWS deployment...');
|
||||
|
||||
createDeploymentPackage();
|
||||
deployToLambda();
|
||||
createApiGateway();
|
||||
cleanup();
|
||||
|
||||
console.log('Deployment completed successfully');
|
||||
} catch (error) {
|
||||
console.error('Deployment failed:', error);
|
||||
cleanup();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
main();
|
||||
|
|
@ -1,245 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Cloudflare Deployment Script for Brainy Cloud Wrapper
|
||||
*
|
||||
* This script helps deploy the Brainy cloud wrapper to Cloudflare Workers.
|
||||
* It uses the Wrangler CLI to create and configure the necessary resources.
|
||||
*
|
||||
* Prerequisites:
|
||||
* - Wrangler CLI installed and configured with appropriate credentials
|
||||
* - Node.js 23.11.0 or higher
|
||||
* - Brainy cloud wrapper built (npm run build)
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Configuration
|
||||
const config = {
|
||||
workerName: process.env.CF_WORKER_NAME || 'brainy-cloud-service',
|
||||
accountId: process.env.CF_ACCOUNT_ID,
|
||||
kvNamespace: process.env.CF_KV_NAMESPACE || 'BRAINY_STORAGE',
|
||||
r2Bucket: process.env.CF_R2_BUCKET || 'brainy-storage'
|
||||
};
|
||||
|
||||
// Validate configuration
|
||||
if (!config.accountId) {
|
||||
console.error('Error: CF_ACCOUNT_ID environment variable is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create wrangler.toml configuration
|
||||
function createWranglerConfig() {
|
||||
console.log('Creating wrangler.toml configuration...');
|
||||
|
||||
const wranglerContent = `
|
||||
name = "${config.workerName}"
|
||||
main = "dist/worker.js"
|
||||
compatibility_date = "2023-12-01"
|
||||
node_compat = true
|
||||
|
||||
account_id = "${config.accountId}"
|
||||
|
||||
[build]
|
||||
command = "npm run build"
|
||||
|
||||
[vars]
|
||||
NODE_ENV = "production"
|
||||
STORAGE_TYPE = "${process.env.STORAGE_TYPE || 'memory'}"
|
||||
USE_SIMPLE_EMBEDDING = "${process.env.USE_SIMPLE_EMBEDDING || 'false'}"
|
||||
|
||||
# KV Namespace for storage
|
||||
[[kv_namespaces]]
|
||||
binding = "BRAINY_KV"
|
||||
id = "${process.env.CF_KV_NAMESPACE_ID || 'create_kv_namespace_and_add_id_here'}"
|
||||
|
||||
# R2 Bucket for storage (if using R2)
|
||||
[[r2_buckets]]
|
||||
binding = "BRAINY_R2"
|
||||
bucket_name = "${config.r2Bucket}"
|
||||
`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync('wrangler.toml', wranglerContent);
|
||||
console.log('wrangler.toml created successfully');
|
||||
} catch (error) {
|
||||
console.error('Error creating wrangler.toml:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Create Cloudflare Worker adapter
|
||||
function createWorkerAdapter() {
|
||||
console.log('Creating Cloudflare Worker adapter...');
|
||||
|
||||
const workerContent = `
|
||||
import { createServer } from '@cloudflare/workers-adapter';
|
||||
import app from './index.js';
|
||||
|
||||
// Create a fetch handler for the worker
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
// Add environment variables to process.env
|
||||
process.env = {
|
||||
...process.env,
|
||||
...env.vars,
|
||||
CF_KV_NAMESPACE: env.BRAINY_KV,
|
||||
CF_R2_BUCKET: env.BRAINY_R2
|
||||
};
|
||||
|
||||
// Create a server adapter
|
||||
const server = createServer(app);
|
||||
|
||||
// Handle the request
|
||||
return server.fetch(request, env, ctx);
|
||||
}
|
||||
};
|
||||
`;
|
||||
|
||||
try {
|
||||
// Create dist directory if it doesn't exist
|
||||
if (!fs.existsSync('dist')) {
|
||||
fs.mkdirSync('dist');
|
||||
}
|
||||
|
||||
fs.writeFileSync('dist/worker.js', workerContent);
|
||||
console.log('Worker adapter created successfully');
|
||||
} catch (error) {
|
||||
console.error('Error creating worker adapter:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update package.json to include Cloudflare Workers dependencies
|
||||
function updatePackageJson() {
|
||||
console.log('Updating package.json...');
|
||||
|
||||
try {
|
||||
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
|
||||
// Add Cloudflare Workers dependencies
|
||||
packageJson.dependencies = {
|
||||
...packageJson.dependencies,
|
||||
'@cloudflare/workers-adapter': '^1.1.0',
|
||||
'@cloudflare/kv-asset-handler': '^0.3.0'
|
||||
};
|
||||
|
||||
// Add Cloudflare Workers scripts
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
'deploy:cloudflare': 'wrangler deploy'
|
||||
};
|
||||
|
||||
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
|
||||
console.log('package.json updated successfully');
|
||||
|
||||
// Install new dependencies
|
||||
execSync('npm install --legacy-peer-deps');
|
||||
} catch (error) {
|
||||
console.error('Error updating package.json:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Create KV namespace if it doesn't exist
|
||||
function createKVNamespace() {
|
||||
console.log('Creating KV namespace...');
|
||||
|
||||
try {
|
||||
// Check if KV namespace ID is provided
|
||||
if (process.env.CF_KV_NAMESPACE_ID) {
|
||||
console.log(`Using existing KV namespace: ${process.env.CF_KV_NAMESPACE_ID}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create KV namespace
|
||||
const result = execSync(`wrangler kv:namespace create "${config.kvNamespace}"`).toString();
|
||||
const match = result.match(/id = "([^"]+)"/);
|
||||
|
||||
if (match && match[1]) {
|
||||
const namespaceId = match[1];
|
||||
console.log(`KV namespace created with ID: ${namespaceId}`);
|
||||
|
||||
// Update wrangler.toml with the new namespace ID
|
||||
let wranglerContent = fs.readFileSync('wrangler.toml', 'utf8');
|
||||
wranglerContent = wranglerContent.replace(/id = "create_kv_namespace_and_add_id_here"/, `id = "${namespaceId}"`);
|
||||
fs.writeFileSync('wrangler.toml', wranglerContent);
|
||||
} else {
|
||||
console.error('Failed to extract KV namespace ID from wrangler output');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating KV namespace:', error);
|
||||
console.log('You may need to create the KV namespace manually and update wrangler.toml');
|
||||
}
|
||||
}
|
||||
|
||||
// Create R2 bucket if it doesn't exist
|
||||
function createR2Bucket() {
|
||||
console.log('Creating R2 bucket...');
|
||||
|
||||
try {
|
||||
// Only create R2 bucket if using R2 storage
|
||||
if (process.env.STORAGE_TYPE !== 'r2') {
|
||||
console.log('Skipping R2 bucket creation (not using R2 storage)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create R2 bucket
|
||||
execSync(`wrangler r2 bucket create ${config.r2Bucket}`);
|
||||
console.log(`R2 bucket created: ${config.r2Bucket}`);
|
||||
} catch (error) {
|
||||
console.error('Error creating R2 bucket:', error);
|
||||
console.log('You may need to create the R2 bucket manually');
|
||||
}
|
||||
}
|
||||
|
||||
// Deploy to Cloudflare Workers
|
||||
function deployToCloudflare() {
|
||||
console.log('Deploying to Cloudflare Workers...');
|
||||
|
||||
try {
|
||||
execSync('wrangler deploy', { stdio: 'inherit' });
|
||||
console.log('Deployed to Cloudflare Workers successfully');
|
||||
} catch (error) {
|
||||
console.error('Error deploying to Cloudflare Workers:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
function cleanup() {
|
||||
console.log('Cleaning up...');
|
||||
|
||||
// No cleanup needed for now
|
||||
console.log('Cleanup completed');
|
||||
}
|
||||
|
||||
// Main function
|
||||
async function main() {
|
||||
try {
|
||||
console.log('Starting Cloudflare deployment...');
|
||||
|
||||
createWranglerConfig();
|
||||
createWorkerAdapter();
|
||||
updatePackageJson();
|
||||
createKVNamespace();
|
||||
createR2Bucket();
|
||||
deployToCloudflare();
|
||||
cleanup();
|
||||
|
||||
console.log('Deployment completed successfully');
|
||||
} catch (error) {
|
||||
console.error('Deployment failed:', error);
|
||||
cleanup();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
main();
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Google Cloud Platform Deployment Script for Brainy Cloud Wrapper
|
||||
*
|
||||
* This script helps deploy the Brainy cloud wrapper to Google Cloud Run.
|
||||
* It uses the Google Cloud SDK to create and configure the necessary resources.
|
||||
*
|
||||
* Prerequisites:
|
||||
* - Google Cloud SDK installed and configured with appropriate credentials
|
||||
* - Node.js 23.11.0 or higher
|
||||
* - Brainy cloud wrapper built (npm run build)
|
||||
* - Docker installed (for building container images)
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Configuration
|
||||
const config = {
|
||||
projectId: process.env.GCP_PROJECT_ID,
|
||||
region: process.env.GCP_REGION || 'us-central1',
|
||||
serviceName: process.env.GCP_SERVICE_NAME || 'brainy-cloud-service',
|
||||
imageName: process.env.GCP_IMAGE_NAME || 'brainy-cloud-service',
|
||||
memory: process.env.GCP_MEMORY || '512Mi',
|
||||
cpu: process.env.GCP_CPU || '1',
|
||||
maxInstances: process.env.GCP_MAX_INSTANCES || '10',
|
||||
minInstances: process.env.GCP_MIN_INSTANCES || '0'
|
||||
};
|
||||
|
||||
// Validate configuration
|
||||
if (!config.projectId) {
|
||||
console.error('Error: GCP_PROJECT_ID environment variable is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create Dockerfile
|
||||
function createDockerfile() {
|
||||
console.log('Creating Dockerfile...');
|
||||
|
||||
const dockerfileContent = `
|
||||
FROM node:23-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install --production --legacy-peer-deps
|
||||
|
||||
# Copy built application
|
||||
COPY dist/ ./dist/
|
||||
|
||||
# Copy environment variables
|
||||
COPY .env ./
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8080
|
||||
|
||||
# Start the application
|
||||
CMD ["node", "dist/index.js"]
|
||||
`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync('Dockerfile', dockerfileContent);
|
||||
console.log('Dockerfile created successfully');
|
||||
} catch (error) {
|
||||
console.error('Error creating Dockerfile:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Build and push Docker image
|
||||
function buildAndPushImage() {
|
||||
console.log('Building and pushing Docker image...');
|
||||
|
||||
try {
|
||||
// Set Google Cloud project
|
||||
execSync(`gcloud config set project ${config.projectId}`);
|
||||
|
||||
// Build the Docker image
|
||||
execSync(`docker build -t gcr.io/${config.projectId}/${config.imageName} .`);
|
||||
|
||||
// Configure Docker to use gcloud as a credential helper
|
||||
execSync('gcloud auth configure-docker');
|
||||
|
||||
// Push the image to Google Container Registry
|
||||
execSync(`docker push gcr.io/${config.projectId}/${config.imageName}`);
|
||||
|
||||
console.log('Docker image built and pushed successfully');
|
||||
} catch (error) {
|
||||
console.error('Error building and pushing Docker image:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Deploy to Google Cloud Run
|
||||
function deployToCloudRun() {
|
||||
console.log('Deploying to Google Cloud Run...');
|
||||
|
||||
try {
|
||||
// Create environment variables string
|
||||
const envVars = [
|
||||
`NODE_ENV=production`,
|
||||
`PORT=8080`,
|
||||
`STORAGE_TYPE=${process.env.STORAGE_TYPE || 'filesystem'}`
|
||||
];
|
||||
|
||||
// Add S3 environment variables if using S3 storage
|
||||
if (process.env.STORAGE_TYPE === 's3') {
|
||||
envVars.push(`S3_BUCKET_NAME=${process.env.S3_BUCKET_NAME}`);
|
||||
envVars.push(`S3_ACCESS_KEY_ID=${process.env.S3_ACCESS_KEY_ID}`);
|
||||
envVars.push(`S3_SECRET_ACCESS_KEY=${process.env.S3_SECRET_ACCESS_KEY}`);
|
||||
envVars.push(`S3_REGION=${process.env.S3_REGION || 'us-east-1'}`);
|
||||
|
||||
if (process.env.S3_ENDPOINT) {
|
||||
envVars.push(`S3_ENDPOINT=${process.env.S3_ENDPOINT}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add embedding and HNSW configuration if provided
|
||||
if (process.env.USE_SIMPLE_EMBEDDING) {
|
||||
envVars.push(`USE_SIMPLE_EMBEDDING=${process.env.USE_SIMPLE_EMBEDDING}`);
|
||||
}
|
||||
|
||||
if (process.env.HNSW_M) {
|
||||
envVars.push(`HNSW_M=${process.env.HNSW_M}`);
|
||||
envVars.push(`HNSW_EF_CONSTRUCTION=${process.env.HNSW_EF_CONSTRUCTION || '200'}`);
|
||||
envVars.push(`HNSW_EF_SEARCH=${process.env.HNSW_EF_SEARCH || '50'}`);
|
||||
}
|
||||
|
||||
// Deploy to Cloud Run
|
||||
const envVarsString = envVars.map(env => `--set-env-vars="${env}"`).join(' ');
|
||||
|
||||
execSync(`gcloud run deploy ${config.serviceName} \
|
||||
--image=gcr.io/${config.projectId}/${config.imageName} \
|
||||
--platform=managed \
|
||||
--region=${config.region} \
|
||||
--memory=${config.memory} \
|
||||
--cpu=${config.cpu} \
|
||||
--max-instances=${config.maxInstances} \
|
||||
--min-instances=${config.minInstances} \
|
||||
--allow-unauthenticated \
|
||||
${envVarsString}`);
|
||||
|
||||
console.log('Deployed to Google Cloud Run successfully');
|
||||
|
||||
// Get the service URL
|
||||
const serviceUrl = execSync(`gcloud run services describe ${config.serviceName} --region=${config.region} --format="value(status.url)"`).toString().trim();
|
||||
console.log(`Service URL: ${serviceUrl}`);
|
||||
} catch (error) {
|
||||
console.error('Error deploying to Google Cloud Run:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
function cleanup() {
|
||||
console.log('Cleaning up...');
|
||||
|
||||
try {
|
||||
// Remove Dockerfile
|
||||
fs.unlinkSync('Dockerfile');
|
||||
|
||||
console.log('Cleanup completed');
|
||||
} catch (error) {
|
||||
console.error('Error during cleanup:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Main function
|
||||
async function main() {
|
||||
try {
|
||||
console.log('Starting Google Cloud Platform deployment...');
|
||||
|
||||
createDockerfile();
|
||||
buildAndPushImage();
|
||||
deployToCloudRun();
|
||||
cleanup();
|
||||
|
||||
console.log('Deployment completed successfully');
|
||||
} catch (error) {
|
||||
console.error('Deployment failed:', error);
|
||||
cleanup();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
main();
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import morgan from 'morgan';
|
||||
import dotenv from 'dotenv';
|
||||
import http from 'http';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import { BrainyData } from '@soulcraft/brainy';
|
||||
import { setupRoutes } from './routes.js';
|
||||
import { initializeBrainy } from './services/brainyService.js';
|
||||
import { setupWebSocketHandlers } from './websocket.js';
|
||||
import { initializeMCPService } from './services/mcpService.js';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// Initialize Express app
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
// Middleware
|
||||
app.use(helmet()); // Security headers
|
||||
app.use(cors()); // Enable CORS
|
||||
app.use(morgan('combined')); // Logging
|
||||
app.use(express.json()); // Parse JSON bodies
|
||||
|
||||
// Initialize Brainy
|
||||
let brainyInstance: BrainyData;
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.status(200).json({ status: 'ok' });
|
||||
});
|
||||
|
||||
// Setup API routes
|
||||
const apiRouter = setupRoutes();
|
||||
app.use('/api', (req, res, next) => {
|
||||
// Attach brainy instance to request
|
||||
(req as any).brainy = brainyInstance;
|
||||
next();
|
||||
}, apiRouter);
|
||||
|
||||
// Start the server
|
||||
async function startServer() {
|
||||
try {
|
||||
// Initialize Brainy
|
||||
brainyInstance = await initializeBrainy();
|
||||
console.log('Brainy initialized successfully');
|
||||
|
||||
// Create HTTP server
|
||||
const server = http.createServer(app);
|
||||
|
||||
// Create WebSocket server
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
// Setup WebSocket handlers
|
||||
setupWebSocketHandlers(wss, brainyInstance);
|
||||
console.log('WebSocket server initialized');
|
||||
|
||||
// Initialize MCP service
|
||||
const mcpWsPort = process.env.MCP_WS_PORT ? parseInt(process.env.MCP_WS_PORT, 10) : undefined;
|
||||
const mcpRestPort = process.env.MCP_REST_PORT ? parseInt(process.env.MCP_REST_PORT, 10) : undefined;
|
||||
|
||||
if (mcpWsPort || mcpRestPort) {
|
||||
initializeMCPService(brainyInstance, {
|
||||
wsPort: mcpWsPort,
|
||||
restPort: mcpRestPort,
|
||||
enableAuth: process.env.MCP_ENABLE_AUTH === 'true',
|
||||
apiKeys: process.env.MCP_API_KEYS ? process.env.MCP_API_KEYS.split(',') : undefined,
|
||||
rateLimit: process.env.MCP_RATE_LIMIT_REQUESTS ? {
|
||||
windowMs: parseInt(process.env.MCP_RATE_LIMIT_WINDOW_MS || '60000', 10),
|
||||
maxRequests: parseInt(process.env.MCP_RATE_LIMIT_REQUESTS, 10)
|
||||
} : undefined,
|
||||
cors: process.env.MCP_ENABLE_CORS === 'true' ? {} : undefined
|
||||
});
|
||||
console.log('MCP service initialized');
|
||||
}
|
||||
|
||||
// Start HTTP server
|
||||
server.listen(port, () => {
|
||||
console.log(`Server running on port ${port}`);
|
||||
console.log(`WebSocket server running on ws://localhost:${port}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('SIGTERM received, shutting down gracefully');
|
||||
// Cleanup Brainy resources
|
||||
await brainyInstance?.clear();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('SIGINT received, shutting down gracefully');
|
||||
// Cleanup Brainy resources
|
||||
await brainyInstance?.clear();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Start the server
|
||||
startServer();
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
import { Router, Request, Response, NextFunction, RequestHandler } from 'express';
|
||||
import { BrainyData, NounType, VerbType } from '@soulcraft/brainy';
|
||||
|
||||
// Define a custom request type that includes the Brainy instance
|
||||
interface BrainyRequest extends Request {
|
||||
brainy: BrainyData;
|
||||
}
|
||||
|
||||
// Helper function to handle async route handlers
|
||||
const asyncHandler = (fn: (req: BrainyRequest, res: Response, next: NextFunction) => Promise<any>) =>
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
Promise.resolve(fn(req as BrainyRequest, res, next)).catch(next);
|
||||
};
|
||||
|
||||
export function setupRoutes() {
|
||||
const router = Router();
|
||||
|
||||
// Get database status
|
||||
router.get('/status', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const status = await req.brainy.status();
|
||||
res.status(200).json(status);
|
||||
} catch (error) {
|
||||
console.error('Error getting status:', error);
|
||||
res.status(500).json({ error: 'Failed to get database status' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Add a noun (entity)
|
||||
router.post('/nouns', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { text, metadata } = req.body;
|
||||
|
||||
if (!text) {
|
||||
return res.status(400).json({ error: 'Text is required' });
|
||||
}
|
||||
|
||||
const id = await req.brainy.add(text, metadata || {});
|
||||
res.status(201).json({ id });
|
||||
} catch (error) {
|
||||
console.error('Error adding noun:', error);
|
||||
res.status(500).json({ error: 'Failed to add noun' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Get a noun by ID
|
||||
router.get('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const noun = await req.brainy.get(id);
|
||||
|
||||
if (!noun) {
|
||||
return res.status(404).json({ error: 'Noun not found' });
|
||||
}
|
||||
|
||||
res.status(200).json(noun);
|
||||
} catch (error) {
|
||||
console.error('Error getting noun:', error);
|
||||
res.status(500).json({ error: 'Failed to get noun' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Update noun metadata
|
||||
router.put('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { metadata } = req.body;
|
||||
|
||||
if (!metadata) {
|
||||
return res.status(400).json({ error: 'Metadata is required' });
|
||||
}
|
||||
|
||||
await req.brainy.updateMetadata(id, metadata);
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error updating noun:', error);
|
||||
res.status(500).json({ error: 'Failed to update noun' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Delete a noun
|
||||
router.delete('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await req.brainy.delete(id);
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error deleting noun:', error);
|
||||
res.status(500).json({ error: 'Failed to delete noun' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Search for similar nouns
|
||||
router.post('/search', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { query, limit = 10 } = req.body;
|
||||
|
||||
if (!query) {
|
||||
return res.status(400).json({ error: 'Query is required' });
|
||||
}
|
||||
|
||||
const results = await req.brainy.searchText(query, limit);
|
||||
res.status(200).json(results);
|
||||
} catch (error) {
|
||||
console.error('Error searching:', error);
|
||||
res.status(500).json({ error: 'Failed to search' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Add a verb (relationship)
|
||||
router.post('/verbs', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { sourceId, targetId, metadata } = req.body;
|
||||
|
||||
if (!sourceId || !targetId) {
|
||||
return res.status(400).json({ error: 'Source ID and Target ID are required' });
|
||||
}
|
||||
|
||||
await req.brainy.addVerb(sourceId, targetId, metadata || {});
|
||||
res.status(201).json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error adding verb:', error);
|
||||
res.status(500).json({ error: 'Failed to add verb' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Get all verbs
|
||||
router.get('/verbs', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const verbs = await req.brainy.getAllVerbs();
|
||||
res.status(200).json(verbs);
|
||||
} catch (error) {
|
||||
console.error('Error getting verbs:', error);
|
||||
res.status(500).json({ error: 'Failed to get verbs' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Get verbs by source
|
||||
router.get('/verbs/source/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const verbs = await req.brainy.getVerbsBySource(id);
|
||||
res.status(200).json(verbs);
|
||||
} catch (error) {
|
||||
console.error('Error getting verbs by source:', error);
|
||||
res.status(500).json({ error: 'Failed to get verbs by source' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Get verbs by target
|
||||
router.get('/verbs/target/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const verbs = await req.brainy.getVerbsByTarget(id);
|
||||
res.status(200).json(verbs);
|
||||
} catch (error) {
|
||||
console.error('Error getting verbs by target:', error);
|
||||
res.status(500).json({ error: 'Failed to get verbs by target' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Delete a verb
|
||||
router.delete('/verbs/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
await req.brainy.deleteVerb(id);
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error deleting verb:', error);
|
||||
res.status(500).json({ error: 'Failed to delete verb' });
|
||||
}
|
||||
}));
|
||||
|
||||
// Clear all data
|
||||
router.delete('/clear', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await req.brainy.clear();
|
||||
res.status(200).json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error clearing data:', error);
|
||||
res.status(500).json({ error: 'Failed to clear data' });
|
||||
}
|
||||
}));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
import { BrainyData, BrainyDataConfig } from '@soulcraft/brainy'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config()
|
||||
|
||||
/**
|
||||
* Extended configuration interface that includes rootDirectory
|
||||
*/
|
||||
interface ExtendedBrainyDataConfig extends BrainyDataConfig {
|
||||
storage: BrainyDataConfig['storage'] & {
|
||||
rootDirectory?: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Brainy instance with appropriate configuration
|
||||
* based on environment variables
|
||||
*/
|
||||
export async function initializeBrainy(): Promise<BrainyData> {
|
||||
// Get storage configuration from environment variables
|
||||
const storageType = process.env.STORAGE_TYPE || 'filesystem'
|
||||
|
||||
// Create configuration object
|
||||
const config: ExtendedBrainyDataConfig = {
|
||||
storage: {}
|
||||
}
|
||||
|
||||
// Configure storage based on type
|
||||
switch (storageType) {
|
||||
case 's3':
|
||||
// Configure S3 storage
|
||||
if (process.env.S3_ENDPOINT) {
|
||||
// Use customS3Storage for S3-compatible services with custom endpoints
|
||||
config.storage.customS3Storage = {
|
||||
bucketName: process.env.S3_BUCKET_NAME,
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||
region: process.env.S3_REGION || 'us-east-1',
|
||||
endpoint: process.env.S3_ENDPOINT
|
||||
}
|
||||
} else {
|
||||
// Use standard S3 storage for AWS S3
|
||||
config.storage.s3Storage = {
|
||||
bucketName: process.env.S3_BUCKET_NAME,
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
|
||||
region: process.env.S3_REGION || 'us-east-1'
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'filesystem':
|
||||
// Configure filesystem storage
|
||||
config.storage.rootDirectory = process.env.STORAGE_ROOT_DIR || './data'
|
||||
break
|
||||
|
||||
case 'memory':
|
||||
// No additional configuration needed for memory storage
|
||||
break
|
||||
|
||||
default:
|
||||
// Default to filesystem storage
|
||||
config.storage.rootDirectory = process.env.STORAGE_ROOT_DIR || './data'
|
||||
break
|
||||
}
|
||||
|
||||
// Note: Universal Sentence Encoder is now the only embedding option
|
||||
// TensorFlow.js is required for embedding to work
|
||||
|
||||
// Configure HNSW index parameters if provided
|
||||
if (process.env.HNSW_M) {
|
||||
config.hnsw = {
|
||||
M: parseInt(process.env.HNSW_M, 10),
|
||||
efConstruction: process.env.HNSW_EF_CONSTRUCTION
|
||||
? parseInt(process.env.HNSW_EF_CONSTRUCTION, 10)
|
||||
: 200,
|
||||
efSearch: process.env.HNSW_EF_SEARCH
|
||||
? parseInt(process.env.HNSW_EF_SEARCH, 10)
|
||||
: 50
|
||||
}
|
||||
}
|
||||
|
||||
// Create and initialize the Brainy instance
|
||||
const brainy = new BrainyData(config)
|
||||
await brainy.init()
|
||||
|
||||
return brainy
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current storage type being used by Brainy
|
||||
*/
|
||||
export async function getStorageType(brainy: BrainyData): Promise<string> {
|
||||
const status = await brainy.status()
|
||||
return status.type || 'unknown'
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
import { WebSocketServer } from 'ws'
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { BrainyData, BrainyMCPService, MCPRequestType } from '@soulcraft/brainy'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
/**
|
||||
* Initialize the MCP service with WebSocket and REST API servers
|
||||
*/
|
||||
export function initializeMCPService(
|
||||
brainy: BrainyData,
|
||||
options: {
|
||||
wsPort?: number
|
||||
restPort?: number
|
||||
enableAuth?: boolean
|
||||
apiKeys?: string[]
|
||||
rateLimit?: {
|
||||
windowMs: number
|
||||
maxRequests: number
|
||||
}
|
||||
cors?: any
|
||||
}
|
||||
) {
|
||||
// Create the MCP service
|
||||
const mcpService = new BrainyMCPService(brainy, options)
|
||||
|
||||
// Start WebSocket server if port is provided
|
||||
if (options.wsPort) {
|
||||
startWebSocketServer(mcpService, options.wsPort)
|
||||
}
|
||||
|
||||
// Start REST server if port is provided
|
||||
if (options.restPort) {
|
||||
startRESTServer(mcpService, options.restPort, options.cors)
|
||||
}
|
||||
|
||||
return mcpService
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a WebSocket server for the MCP service
|
||||
*/
|
||||
function startWebSocketServer(mcpService: BrainyMCPService, port: number) {
|
||||
const wss = new WebSocketServer({ port })
|
||||
|
||||
wss.on('connection', (ws: any) => {
|
||||
ws.on('message', async (message: string) => {
|
||||
try {
|
||||
const request = JSON.parse(message)
|
||||
|
||||
// Handle the request using the MCP service
|
||||
const response = await mcpService.handleMCPRequest(request)
|
||||
|
||||
// Send the response
|
||||
ws.send(JSON.stringify(response))
|
||||
} catch (error) {
|
||||
// Send error response
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
requestId: uuidv4(),
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
console.log(`MCP WebSocket server started on port ${port}`)
|
||||
|
||||
return wss
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a REST server for the MCP service
|
||||
*/
|
||||
function startRESTServer(
|
||||
mcpService: BrainyMCPService,
|
||||
port: number,
|
||||
corsOptions?: any
|
||||
) {
|
||||
const app = express()
|
||||
|
||||
// Parse JSON request bodies
|
||||
app.use(express.json())
|
||||
|
||||
// Enable CORS if configured
|
||||
if (corsOptions) {
|
||||
app.use(cors(corsOptions))
|
||||
}
|
||||
|
||||
// MCP endpoints
|
||||
app.post('/mcp/data', async (req: any, res: any) => {
|
||||
try {
|
||||
const response = await mcpService.handleMCPRequest({
|
||||
...req.body,
|
||||
type: 'DATA_ACCESS'
|
||||
})
|
||||
|
||||
res.json(response)
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
requestId: uuidv4(),
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/mcp/tools', async (req: any, res: any) => {
|
||||
try {
|
||||
const response = await mcpService.handleMCPRequest({
|
||||
...req.body,
|
||||
type: 'TOOL_EXECUTION'
|
||||
})
|
||||
|
||||
res.json(response)
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
requestId: uuidv4(),
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/mcp/system', async (req: any, res: any) => {
|
||||
try {
|
||||
const response = await mcpService.handleMCPRequest({
|
||||
...req.body,
|
||||
type: 'SYSTEM_INFO'
|
||||
})
|
||||
|
||||
res.json(response)
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
requestId: uuidv4(),
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/mcp/auth', async (req: any, res: any) => {
|
||||
try {
|
||||
const response = await mcpService.handleMCPRequest({
|
||||
...req.body,
|
||||
type: 'AUTHENTICATION'
|
||||
})
|
||||
|
||||
res.json(response)
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
requestId: uuidv4(),
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Get available tools
|
||||
app.get('/mcp/tools', async (req: any, res: any) => {
|
||||
try {
|
||||
const response = await mcpService.handleMCPRequest({
|
||||
type: MCPRequestType.SYSTEM_INFO,
|
||||
requestId: uuidv4(),
|
||||
version: '1.0'
|
||||
})
|
||||
|
||||
res.json(response)
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
requestId: uuidv4(),
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Start the server
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`MCP REST API server started on port ${port}`)
|
||||
})
|
||||
|
||||
return server
|
||||
}
|
||||
|
|
@ -1,433 +0,0 @@
|
|||
import { WebSocketServer, WebSocket } from 'ws';
|
||||
import { BrainyData } from '@soulcraft/brainy';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
// Define message types
|
||||
enum MessageType {
|
||||
STATUS = 'status',
|
||||
ADD_NOUN = 'addNoun',
|
||||
GET_NOUN = 'getNoun',
|
||||
UPDATE_NOUN = 'updateNoun',
|
||||
DELETE_NOUN = 'deleteNoun',
|
||||
SEARCH = 'search',
|
||||
ADD_VERB = 'addVerb',
|
||||
GET_VERBS = 'getVerbs',
|
||||
GET_VERBS_BY_SOURCE = 'getVerbsBySource',
|
||||
GET_VERBS_BY_TARGET = 'getVerbsByTarget',
|
||||
DELETE_VERB = 'deleteVerb',
|
||||
CLEAR = 'clear',
|
||||
SUBSCRIBE = 'subscribe',
|
||||
UNSUBSCRIBE = 'unsubscribe',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
// Define subscription types
|
||||
enum SubscriptionType {
|
||||
NOUNS = 'nouns',
|
||||
VERBS = 'verbs',
|
||||
SEARCH_RESULTS = 'searchResults'
|
||||
}
|
||||
|
||||
// Define message interface
|
||||
interface WebSocketMessage {
|
||||
type: MessageType;
|
||||
id?: string;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
// Define client interface
|
||||
interface WebSocketClient {
|
||||
id: string;
|
||||
socket: WebSocket;
|
||||
subscriptions: Set<SubscriptionType>;
|
||||
}
|
||||
|
||||
// Store connected clients
|
||||
const clients: Map<string, WebSocketClient> = new Map();
|
||||
|
||||
// Setup WebSocket handlers
|
||||
export function setupWebSocketHandlers(wss: WebSocketServer, brainy: BrainyData) {
|
||||
// Handle new connections
|
||||
wss.on('connection', (socket: WebSocket) => {
|
||||
const clientId = uuidv4();
|
||||
|
||||
// Create client object
|
||||
const client: WebSocketClient = {
|
||||
id: clientId,
|
||||
socket,
|
||||
subscriptions: new Set()
|
||||
};
|
||||
|
||||
// Store client
|
||||
clients.set(clientId, client);
|
||||
|
||||
console.log(`WebSocket client connected: ${clientId}`);
|
||||
|
||||
// Send welcome message
|
||||
sendMessage(socket, {
|
||||
type: MessageType.STATUS,
|
||||
id: uuidv4(),
|
||||
payload: {
|
||||
clientId,
|
||||
message: 'Connected to Brainy WebSocket API',
|
||||
status: 'connected'
|
||||
}
|
||||
});
|
||||
|
||||
// Handle messages
|
||||
socket.on('message', async (data: WebSocket.Data) => {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(data.toString());
|
||||
|
||||
// Ensure message has an ID
|
||||
const messageId = message.id || uuidv4();
|
||||
|
||||
console.log(`Received message: ${message.type} (${messageId})`);
|
||||
|
||||
// Process message based on type
|
||||
await processMessage(client, message, messageId, brainy);
|
||||
} catch (error) {
|
||||
console.error('Error processing WebSocket message:', error);
|
||||
sendMessage(socket, {
|
||||
type: MessageType.ERROR,
|
||||
id: uuidv4(),
|
||||
payload: {
|
||||
message: 'Invalid message format',
|
||||
error: (error as Error).message
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle disconnection
|
||||
socket.on('close', () => {
|
||||
// Remove client
|
||||
clients.delete(clientId);
|
||||
console.log(`WebSocket client disconnected: ${clientId}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Process incoming messages
|
||||
async function processMessage(
|
||||
client: WebSocketClient,
|
||||
message: WebSocketMessage,
|
||||
messageId: string,
|
||||
brainy: BrainyData
|
||||
) {
|
||||
const { socket } = client;
|
||||
|
||||
try {
|
||||
switch (message.type) {
|
||||
case MessageType.STATUS:
|
||||
// Return database status
|
||||
const status = await brainy.status();
|
||||
sendMessage(socket, {
|
||||
type: MessageType.STATUS,
|
||||
id: messageId,
|
||||
payload: status
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageType.ADD_NOUN:
|
||||
// Add a noun
|
||||
if (!message.payload?.text) {
|
||||
throw new Error('Text is required');
|
||||
}
|
||||
|
||||
const nounId = await brainy.add(
|
||||
message.payload.text,
|
||||
message.payload.metadata || {}
|
||||
);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.ADD_NOUN,
|
||||
id: messageId,
|
||||
payload: { id: nounId }
|
||||
});
|
||||
|
||||
// Notify subscribers
|
||||
notifySubscribers(SubscriptionType.NOUNS, {
|
||||
type: 'added',
|
||||
id: nounId,
|
||||
data: await brainy.get(nounId)
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MessageType.GET_NOUN:
|
||||
// Get a noun by ID
|
||||
if (!message.payload?.id) {
|
||||
throw new Error('Noun ID is required');
|
||||
}
|
||||
|
||||
const noun = await brainy.get(message.payload.id);
|
||||
|
||||
if (!noun) {
|
||||
throw new Error('Noun not found');
|
||||
}
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.GET_NOUN,
|
||||
id: messageId,
|
||||
payload: noun
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageType.UPDATE_NOUN:
|
||||
// Update noun metadata
|
||||
if (!message.payload?.id || !message.payload?.metadata) {
|
||||
throw new Error('Noun ID and metadata are required');
|
||||
}
|
||||
|
||||
await brainy.updateMetadata(message.payload.id, message.payload.metadata);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.UPDATE_NOUN,
|
||||
id: messageId,
|
||||
payload: { success: true }
|
||||
});
|
||||
|
||||
// Notify subscribers
|
||||
notifySubscribers(SubscriptionType.NOUNS, {
|
||||
type: 'updated',
|
||||
id: message.payload.id,
|
||||
data: await brainy.get(message.payload.id)
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MessageType.DELETE_NOUN:
|
||||
// Delete a noun
|
||||
if (!message.payload?.id) {
|
||||
throw new Error('Noun ID is required');
|
||||
}
|
||||
|
||||
await brainy.delete(message.payload.id);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.DELETE_NOUN,
|
||||
id: messageId,
|
||||
payload: { success: true }
|
||||
});
|
||||
|
||||
// Notify subscribers
|
||||
notifySubscribers(SubscriptionType.NOUNS, {
|
||||
type: 'deleted',
|
||||
id: message.payload.id
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MessageType.SEARCH:
|
||||
// Search for similar nouns
|
||||
if (!message.payload?.query) {
|
||||
throw new Error('Query is required');
|
||||
}
|
||||
|
||||
const limit = message.payload.limit || 10;
|
||||
const results = await brainy.searchText(message.payload.query, limit);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.SEARCH,
|
||||
id: messageId,
|
||||
payload: results
|
||||
});
|
||||
|
||||
// Notify subscribers
|
||||
notifySubscribers(SubscriptionType.SEARCH_RESULTS, {
|
||||
type: 'search',
|
||||
query: message.payload.query,
|
||||
results
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MessageType.ADD_VERB:
|
||||
// Add a verb (relationship)
|
||||
if (!message.payload?.sourceId || !message.payload?.targetId) {
|
||||
throw new Error('Source ID and Target ID are required');
|
||||
}
|
||||
|
||||
await brainy.addVerb(
|
||||
message.payload.sourceId,
|
||||
message.payload.targetId,
|
||||
message.payload.metadata || {}
|
||||
);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.ADD_VERB,
|
||||
id: messageId,
|
||||
payload: { success: true }
|
||||
});
|
||||
|
||||
// Notify subscribers
|
||||
notifySubscribers(SubscriptionType.VERBS, {
|
||||
type: 'added',
|
||||
sourceId: message.payload.sourceId,
|
||||
targetId: message.payload.targetId,
|
||||
metadata: message.payload.metadata
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MessageType.GET_VERBS:
|
||||
// Get all verbs
|
||||
const verbs = await brainy.getAllVerbs();
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.GET_VERBS,
|
||||
id: messageId,
|
||||
payload: verbs
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageType.GET_VERBS_BY_SOURCE:
|
||||
// Get verbs by source
|
||||
if (!message.payload?.id) {
|
||||
throw new Error('Source ID is required');
|
||||
}
|
||||
|
||||
const sourceVerbs = await brainy.getVerbsBySource(message.payload.id);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.GET_VERBS_BY_SOURCE,
|
||||
id: messageId,
|
||||
payload: sourceVerbs
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageType.GET_VERBS_BY_TARGET:
|
||||
// Get verbs by target
|
||||
if (!message.payload?.id) {
|
||||
throw new Error('Target ID is required');
|
||||
}
|
||||
|
||||
const targetVerbs = await brainy.getVerbsByTarget(message.payload.id);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.GET_VERBS_BY_TARGET,
|
||||
id: messageId,
|
||||
payload: targetVerbs
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageType.DELETE_VERB:
|
||||
// Delete a verb
|
||||
if (!message.payload?.id) {
|
||||
throw new Error('Verb ID is required');
|
||||
}
|
||||
|
||||
await brainy.deleteVerb(message.payload.id);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.DELETE_VERB,
|
||||
id: messageId,
|
||||
payload: { success: true }
|
||||
});
|
||||
|
||||
// Notify subscribers
|
||||
notifySubscribers(SubscriptionType.VERBS, {
|
||||
type: 'deleted',
|
||||
id: message.payload.id
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case MessageType.CLEAR:
|
||||
// Clear all data
|
||||
await brainy.clear();
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.CLEAR,
|
||||
id: messageId,
|
||||
payload: { success: true }
|
||||
});
|
||||
|
||||
// Notify all subscribers
|
||||
notifySubscribers(SubscriptionType.NOUNS, { type: 'cleared' });
|
||||
notifySubscribers(SubscriptionType.VERBS, { type: 'cleared' });
|
||||
|
||||
break;
|
||||
|
||||
case MessageType.SUBSCRIBE:
|
||||
// Subscribe to events
|
||||
if (!message.payload?.type) {
|
||||
throw new Error('Subscription type is required');
|
||||
}
|
||||
|
||||
const subscriptionType = message.payload.type as SubscriptionType;
|
||||
|
||||
// Add subscription
|
||||
client.subscriptions.add(subscriptionType);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.SUBSCRIBE,
|
||||
id: messageId,
|
||||
payload: {
|
||||
success: true,
|
||||
type: subscriptionType
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case MessageType.UNSUBSCRIBE:
|
||||
// Unsubscribe from events
|
||||
if (!message.payload?.type) {
|
||||
throw new Error('Subscription type is required');
|
||||
}
|
||||
|
||||
const unsubscribeType = message.payload.type as SubscriptionType;
|
||||
|
||||
// Remove subscription
|
||||
client.subscriptions.delete(unsubscribeType);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.UNSUBSCRIBE,
|
||||
id: messageId,
|
||||
payload: {
|
||||
success: true,
|
||||
type: unsubscribeType
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown message type: ${message.type}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing message ${message.type}:`, error);
|
||||
|
||||
sendMessage(socket, {
|
||||
type: MessageType.ERROR,
|
||||
id: messageId,
|
||||
payload: {
|
||||
originalType: message.type,
|
||||
message: 'Error processing message',
|
||||
error: (error as Error).message
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message to a client
|
||||
function sendMessage(socket: WebSocket, message: WebSocketMessage) {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
// Notify subscribers of events
|
||||
function notifySubscribers(type: SubscriptionType, data: any) {
|
||||
for (const client of clients.values()) {
|
||||
if (client.subscriptions.has(type)) {
|
||||
sendMessage(client.socket, {
|
||||
type: MessageType.SUBSCRIBE,
|
||||
payload: {
|
||||
type,
|
||||
data
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue