feat: add WebSocket API, REST API routes, and server setup for Brainy

Implemented a WebSocket API for real-time communication and REST API routes for CRUD operations on nouns and verbs. Added a server initialization script with WebSocket server integration and improved application structure with modularized routing and services.
This commit is contained in:
David Snelling 2025-06-06 09:09:07 -07:00
parent 6feab06f34
commit e00f1a92ce
9 changed files with 1533 additions and 85 deletions

View file

@ -0,0 +1,24 @@
# 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

View file

@ -0,0 +1,205 @@
#!/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();

View file

@ -0,0 +1,245 @@
#!/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();

View file

@ -0,0 +1,196 @@
#!/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();

View file

@ -0,0 +1,85 @@
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';
import { initializeBrainy } from './services/brainyService';
import { setupWebSocketHandlers } from './websocket';
// 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
app.use('/api', (req, res, next) => {
// Attach brainy instance to request
(req as any).brainy = brainyInstance;
next();
}, setupRoutes());
// 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');
// 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();

180
cloud-wrapper/src/routes.ts Normal file
View file

@ -0,0 +1,180 @@
import { Router, Request, Response } 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;
}
export function setupRoutes() {
const router = Router();
// Get database status
router.get('/status', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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', async (req: BrainyRequest, res: Response) => {
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;
}

View file

@ -0,0 +1,80 @@
import { BrainyData } from '@soulcraft/brainy';
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
/**
* 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: any = {
storage: {}
};
// Configure storage based on type
switch (storageType) {
case 's3':
// Configure S3 storage
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',
endpoint: process.env.S3_ENDPOINT // Optional for custom S3-compatible services
};
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;
}
// Configure embedding options
if (process.env.USE_SIMPLE_EMBEDDING === 'true') {
// Use simple embedding (faster but less accurate)
config.useSimpleEmbedding = true;
}
// 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.storageType || 'unknown';
}

View file

@ -0,0 +1,433 @@
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
}
});
}
}
}

View file

@ -115,9 +115,9 @@ export class BrainyData<T = any> {
this.embeddingFunction = config.embeddingFunction || defaultEmbeddingFunction
// Set persistent storage request flag (support both new and deprecated options)
this.requestPersistentStorage =
(config.storage?.requestPersistentStorage !== undefined)
? config.storage.requestPersistentStorage
this.requestPersistentStorage =
(config.storage?.requestPersistentStorage !== undefined)
? config.storage.requestPersistentStorage
: (config.requestPersistentStorage || false)
// Set read-only flag
@ -153,9 +153,9 @@ export class BrainyData<T = any> {
const storageOptions = {
...this.storageConfig,
requestPersistentStorage: this.requestPersistentStorage
};
}
this.storage = await createStorage(storageOptions);
this.storage = await createStorage(storageOptions)
}
// Initialize storage
@ -246,15 +246,15 @@ export class BrainyData<T = any> {
if (metadata !== undefined) {
// Validate noun type if metadata is for a GraphNoun
if (metadata && typeof metadata === 'object' && 'noun' in metadata) {
const nounType = (metadata as any).noun;
const nounType = (metadata as any).noun
// Check if the noun type is valid
const isValidNounType = Object.values(NounType).includes(nounType);
const isValidNounType = Object.values(NounType).includes(nounType)
if (!isValidNounType) {
console.warn(`Invalid noun type: ${nounType}. Falling back to GraphNoun.`);
// Set a default noun type
(metadata as any).noun = NounType.Concept;
(metadata as any).noun = NounType.Concept
}
}
@ -439,23 +439,23 @@ export class BrainyData<T = any> {
} = {}
): Promise<SearchResult<T>[]> {
// If input is a string and not a vector, automatically vectorize it
let queryToUse = queryVectorOrData;
let queryToUse = queryVectorOrData
if (typeof queryVectorOrData === 'string' && !options.forceEmbed) {
queryToUse = await this.embed(queryVectorOrData);
options.forceEmbed = false; // Already embedded, don't force again
queryToUse = await this.embed(queryVectorOrData)
options.forceEmbed = false // Already embedded, don't force again
}
// If noun types are specified, use searchByNounTypes
let searchResults;
let searchResults
if (options.nounTypes && options.nounTypes.length > 0) {
searchResults = await this.searchByNounTypes(queryToUse, k, options.nounTypes, {
forceEmbed: options.forceEmbed
});
})
} else {
// Otherwise, search all GraphNouns
searchResults = await this.searchByNounTypes(queryToUse, k, null, {
forceEmbed: options.forceEmbed
});
})
}
// If includeVerbs is true, retrieve associated GraphVerbs for each result
@ -463,28 +463,28 @@ export class BrainyData<T = any> {
for (const result of searchResults) {
try {
// Get outgoing verbs for this noun
const outgoingVerbs = await this.storage.getVerbsBySource(result.id);
const outgoingVerbs = await this.storage.getVerbsBySource(result.id)
// Get incoming verbs for this noun
const incomingVerbs = await this.storage.getVerbsByTarget(result.id);
const incomingVerbs = await this.storage.getVerbsByTarget(result.id)
// Combine all verbs
const allVerbs = [...outgoingVerbs, ...incomingVerbs];
const allVerbs = [...outgoingVerbs, ...incomingVerbs]
// Add verbs to the result metadata
if (!result.metadata) {
result.metadata = {} as T;
result.metadata = {} as T
}
// Add the verbs to the metadata
(result.metadata as any).associatedVerbs = allVerbs;
(result.metadata as any).associatedVerbs = allVerbs
} catch (error) {
console.warn(`Failed to retrieve verbs for noun ${result.id}:`, error);
console.warn(`Failed to retrieve verbs for noun ${result.id}:`, error)
}
}
}
return searchResults;
return searchResults
}
/**
@ -565,15 +565,15 @@ export class BrainyData<T = any> {
// Validate noun type if metadata is for a GraphNoun
if (metadata && typeof metadata === 'object' && 'noun' in metadata) {
const nounType = (metadata as any).noun;
const nounType = (metadata as any).noun
// Check if the noun type is valid
const isValidNounType = Object.values(NounType).includes(nounType);
const isValidNounType = Object.values(NounType).includes(nounType)
if (!isValidNounType) {
console.warn(`Invalid noun type: ${nounType}. Falling back to GraphNoun.`);
// Set a default noun type
(metadata as any).noun = NounType.Concept;
(metadata as any).noun = NounType.Concept
}
}
@ -629,19 +629,19 @@ export class BrainyData<T = any> {
if (options.metadata && (!vector || options.forceEmbed)) {
try {
// Extract a string representation from metadata for embedding
let textToEmbed: string;
let textToEmbed: string
if (typeof options.metadata === 'string') {
textToEmbed = options.metadata;
textToEmbed = options.metadata
} else if (options.metadata.description && typeof options.metadata.description === 'string') {
textToEmbed = options.metadata.description;
textToEmbed = options.metadata.description
} else {
// Convert to JSON string as fallback
textToEmbed = JSON.stringify(options.metadata);
textToEmbed = JSON.stringify(options.metadata)
}
// Ensure textToEmbed is a string
if (typeof textToEmbed !== 'string') {
textToEmbed = String(textToEmbed);
textToEmbed = String(textToEmbed)
}
verbVector = await this.embeddingFunction(textToEmbed)
@ -656,15 +656,15 @@ export class BrainyData<T = any> {
}
// Validate verb type if provided
let verbType = options.type;
let verbType = options.type
if (verbType) {
// Check if the verb type is valid
const isValidVerbType = Object.values(VerbType).includes(verbType as any);
const isValidVerbType = Object.values(VerbType).includes(verbType as any)
if (!isValidVerbType) {
console.warn(`Invalid verb type: ${verbType}. Using RelatedTo as default.`);
console.warn(`Invalid verb type: ${verbType}. Using RelatedTo as default.`)
// Set a default verb type
verbType = VerbType.RelatedTo;
verbType = VerbType.RelatedTo
}
}
@ -873,8 +873,8 @@ export class BrainyData<T = any> {
* @returns Array of search results
*/
public async searchText(
query: string,
k: number = 10,
query: string,
k: number = 10,
options: {
nounTypes?: string[],
includeVerbs?: boolean
@ -997,44 +997,44 @@ export class BrainyData<T = any> {
nounIds: string[];
verbIds: string[];
}> {
await this.ensureInitialized();
await this.ensureInitialized()
// Check if database is in read-only mode
this.checkReadOnly();
this.checkReadOnly()
// Set default options
const nounCount = options.nounCount || 10;
const verbCount = options.verbCount || 20;
const nounTypes = options.nounTypes || Object.values(NounType);
const verbTypes = options.verbTypes || Object.values(VerbType);
const clearExisting = options.clearExisting || false;
const nounCount = options.nounCount || 10
const verbCount = options.verbCount || 20
const nounTypes = options.nounTypes || Object.values(NounType)
const verbTypes = options.verbTypes || Object.values(VerbType)
const clearExisting = options.clearExisting || false
// Clear existing data if requested
if (clearExisting) {
await this.clear();
await this.clear()
}
try {
// Generate random nouns
const nounIds: string[] = [];
const nounIds: string[] = []
const nounDescriptions: Record<string, string> = {
[NounType.Person]: "A person with unique characteristics",
[NounType.Place]: "A location with specific attributes",
[NounType.Thing]: "An object with distinct properties",
[NounType.Event]: "An occurrence with temporal aspects",
[NounType.Concept]: "An abstract idea or notion",
[NounType.Content]: "A piece of content or information",
[NounType.Group]: "A collection of related entities",
[NounType.List]: "An ordered sequence of items",
[NounType.Category]: "A classification or grouping"
};
[NounType.Person]: 'A person with unique characteristics',
[NounType.Place]: 'A location with specific attributes',
[NounType.Thing]: 'An object with distinct properties',
[NounType.Event]: 'An occurrence with temporal aspects',
[NounType.Concept]: 'An abstract idea or notion',
[NounType.Content]: 'A piece of content or information',
[NounType.Group]: 'A collection of related entities',
[NounType.List]: 'An ordered sequence of items',
[NounType.Category]: 'A classification or grouping'
}
for (let i = 0; i < nounCount; i++) {
// Select a random noun type
const nounType = nounTypes[Math.floor(Math.random() * nounTypes.length)];
const nounType = nounTypes[Math.floor(Math.random() * nounTypes.length)]
// Generate a random label
const label = `Random ${nounType} ${i + 1}`;
const label = `Random ${nounType} ${i + 1}`
// Create metadata
const metadata = {
@ -1046,45 +1046,45 @@ export class BrainyData<T = any> {
priority: Math.floor(Math.random() * 5) + 1,
tags: [`tag-${i % 5}`, `category-${i % 3}`]
}
};
}
// Add the noun
const id = await this.add(metadata.description, metadata as T);
nounIds.push(id);
const id = await this.add(metadata.description, metadata as T)
nounIds.push(id)
}
// Generate random verbs between nouns
const verbIds: string[] = [];
const verbIds: string[] = []
const verbDescriptions: Record<string, string> = {
[VerbType.AttributedTo]: "Attribution relationship",
[VerbType.Controls]: "Control relationship",
[VerbType.Created]: "Creation relationship",
[VerbType.Earned]: "Achievement relationship",
[VerbType.Owns]: "Ownership relationship",
[VerbType.MemberOf]: "Membership relationship",
[VerbType.RelatedTo]: "General relationship",
[VerbType.WorksWith]: "Collaboration relationship",
[VerbType.FriendOf]: "Friendship relationship",
[VerbType.ReportsTo]: "Reporting relationship",
[VerbType.Supervises]: "Supervision relationship",
[VerbType.Mentors]: "Mentorship relationship"
};
[VerbType.AttributedTo]: 'Attribution relationship',
[VerbType.Controls]: 'Control relationship',
[VerbType.Created]: 'Creation relationship',
[VerbType.Earned]: 'Achievement relationship',
[VerbType.Owns]: 'Ownership relationship',
[VerbType.MemberOf]: 'Membership relationship',
[VerbType.RelatedTo]: 'General relationship',
[VerbType.WorksWith]: 'Collaboration relationship',
[VerbType.FriendOf]: 'Friendship relationship',
[VerbType.ReportsTo]: 'Reporting relationship',
[VerbType.Supervises]: 'Supervision relationship',
[VerbType.Mentors]: 'Mentorship relationship'
}
for (let i = 0; i < verbCount; i++) {
// Select random source and target nouns
const sourceIndex = Math.floor(Math.random() * nounIds.length);
let targetIndex = Math.floor(Math.random() * nounIds.length);
const sourceIndex = Math.floor(Math.random() * nounIds.length)
let targetIndex = Math.floor(Math.random() * nounIds.length)
// Ensure source and target are different
while (targetIndex === sourceIndex && nounIds.length > 1) {
targetIndex = Math.floor(Math.random() * nounIds.length);
targetIndex = Math.floor(Math.random() * nounIds.length)
}
const sourceId = nounIds[sourceIndex];
const targetId = nounIds[targetIndex];
const sourceId = nounIds[sourceIndex]
const targetId = nounIds[targetIndex]
// Select a random verb type
const verbType = verbTypes[Math.floor(Math.random() * verbTypes.length)];
const verbType = verbTypes[Math.floor(Math.random() * verbTypes.length)]
// Create metadata
const metadata = {
@ -1097,25 +1097,25 @@ export class BrainyData<T = any> {
duration: Math.floor(Math.random() * 365) + 1,
tags: [`relation-${i % 5}`, `strength-${i % 3}`]
}
};
}
// Add the verb
const id = await this.addVerb(sourceId, targetId, undefined, {
type: verbType,
weight: metadata.weight,
metadata
});
})
verbIds.push(id);
verbIds.push(id)
}
return {
nounIds,
verbIds
};
}
} catch (error) {
console.error('Failed to generate random graph:', error);
throw new Error(`Failed to generate random graph: ${error}`);
console.error('Failed to generate random graph:', error)
throw new Error(`Failed to generate random graph: ${error}`)
}
}
}