chore(8.0)!: drop browser support, cloud SDKs, legacy pipeline, dead threading

Brainy 8.0 is server-only. This commit takes the consequences seriously and
removes everything that was only there to keep browser/cloud/threading
surfaces alive.

Browser support drop (per the @deprecated notes in environment.ts):
  - isBrowser, isWebWorker, areWebWorkersAvailable, navigator.deviceMemory
    paths, window/document/self.onmessage code.
  - browser console.log in unified.ts, the 'browser' branch in
    autoConfiguration.ts (env enum + scaleUp cases), 'browser-cache' model
    path, MCP service environment value.
  - package.json browser field.
  - src/worker.ts (Web Worker entrypoint) deleted.

Cloud SDK removal (the four adapters were dropped in Phase 7; the SDKs
were the lingering tax):
  - @aws-sdk/client-s3, @azure/identity, @azure/storage-blob, and
    @google-cloud/storage removed from package.json. Lockfile drops the
    entire @aws/@azure/@google-cloud/@smithy transitive tree.
  - EnhancedS3Clear class deleted from enhancedClearOperations.ts (the
    only @aws-sdk/client-s3 consumer; the dynamic import sites went with
    it). EnhancedFileSystemClear stays.
  - src/utils/adaptiveSocketManager.ts deleted entirely (474 LOC of HTTPS
    socket-pool management for the dropped cloud HTTP handler).
    performanceMonitor.ts no longer reports a socketConfig; socket
    utilization is fixed at 0.

Dead threading subsystem:
  - executeInThread was imported by distance.ts and hnswIndex.ts but
    never called. It was scaffolding for a future "off-main-thread
    distance batch" optimization that never shipped.
  - src/utils/workerUtils.ts deleted (Web Worker code path + an
    unreachable Node Worker Threads code path).
  - environment.ts loses isThreadingAvailable, isThreadingAvailableAsync,
    areWorkerThreadsAvailable, areWorkerThreadsAvailableSync. All exports
    purged from index.ts and unified.ts.
  - autoConfiguration.ts drops AutoConfigResult.threadingAvailable.

Legacy plugin/augmentation pipeline:
  - src/pipeline.ts deleted. The whole file was a no-op stub for
    backwards compat — Pipeline class had no methods, no lifecycle hooks,
    no before/after callbacks. AugmentationPipeline, augmentationPipeline,
    createPipeline, createStreamingPipeline, StreamlinedPipelineOptions,
    StreamlinedPipelineResult, StreamlinedExecutionMode were all aliases
    for the same stub.
  - src/mcp/mcpAugmentationToolset.ts deleted. executePipeline always
    threw "deprecated", isValidAugmentationType always returned false,
    getAvailableTools always returned []. Dead surface.
  - BrainyMCPService no longer instantiates a toolset. TOOL_EXECUTION
    requests now return the standard UNSUPPORTED_REQUEST_TYPE error.
    'availableTools' system-info returns [] (was the same in practice).

Net: 22 files changed, ~6400 LOC deleted (including legacy code +
mechanical lockfile churn). Build clean, 1409/1409 tests pass.
This commit is contained in:
David Snelling 2026-06-09 16:38:30 -07:00
parent adda1570f3
commit 266715aeee
22 changed files with 134 additions and 4648 deletions

2730
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -61,11 +61,6 @@
"types": "./dist/embeddings/wasm/index.d.ts"
}
},
"browser": {
"./dist/distributed": false,
"./dist/cli": false,
"./dist/scripts": false
},
"engines": {
"node": "22.x",
"bun": ">=1.0.0"
@ -182,10 +177,6 @@
"vitest": "^3.2.4"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.540.0",
"@azure/identity": "^4.0.0",
"@azure/storage-blob": "^12.17.0",
"@google-cloud/storage": "^7.14.0",
"@msgpack/msgpack": "^3.1.2",
"@types/js-yaml": "^4.0.9",
"boxen": "^8.0.1",

View file

@ -3,7 +3,7 @@
* Brainy uses Q8 WASM embeddings - no configuration needed (zero-config)
*/
import { isBrowser, isNode } from '../utils/environment.js'
import { isNode } from '../utils/environment.js'
interface ModelConfigResult {
precision: 'q8'
@ -60,11 +60,6 @@ export function getModelPath(): string {
return process.env.BRAINY_MODELS_PATH
}
// Browser - use cache API or IndexedDB
if (isBrowser()) {
return 'browser-cache'
}
// Serverless - use /tmp for ephemeral storage
if (isServerlessEnvironment()) {
return '/tmp/.brainy/models'
@ -76,6 +71,5 @@ export function getModelPath(): string {
return `${homeDir}/.brainy/models`
}
// Fallback
return './.brainy/models'
}

View file

@ -14,7 +14,6 @@
import { Vector, EmbeddingFunction } from '../coreTypes.js'
import { WASMEmbeddingEngine } from './wasm/index.js'
import { isBrowser } from '../utils/environment.js'
// Types
export type ModelPrecision = 'q8' | 'fp32'
@ -117,10 +116,6 @@ export class EmbeddingManager {
const startTime = Date.now()
try {
if (isBrowser()) {
console.warn('[brainy] Browser WASM embedding engine is deprecated and will be removed in v8.0. Use Node.js/Bun with native embedding providers instead.')
}
// Initialize WASM engine (handles all model loading)
await this.engine.initialize()
// Lock precision after successful initialization

View file

@ -11,7 +11,6 @@ import {
VectorDocument
} from '../coreTypes.js'
import { euclideanDistance, calculateDistancesBatch } from '../utils/index.js'
import { executeInThread } from '../utils/workerUtils.js'
import type { BaseStorage } from '../storage/baseStorage.js'
import { getGlobalCache, UnifiedCache } from '../utils/unifiedCache.js'
import { prodLog } from '../utils/logger.js'

View file

@ -138,9 +138,6 @@ import {
embeddingFunctions
} from './utils/embedding.js'
// Export worker utilities
import { executeInThread, cleanupWorkerPools } from './utils/workerUtils.js'
// Export logging utilities
import {
logger,
@ -153,11 +150,6 @@ import {
// Export performance and optimization utilities
import {
getGlobalSocketManager,
AdaptiveSocketManager
} from './utils/adaptiveSocketManager.js'
import {
getGlobalBackpressure,
AdaptiveBackpressure
@ -169,16 +161,7 @@ import {
} from './utils/performanceMonitor.js'
// Export environment utilities
import {
isBrowser,
isNode,
isWebWorker,
areWebWorkersAvailable,
areWorkerThreadsAvailable,
areWorkerThreadsAvailableSync,
isThreadingAvailable,
isThreadingAvailableAsync
} from './utils/environment.js'
import { isNode } from './utils/environment.js'
export {
UniversalSentenceEncoder,
@ -188,19 +171,8 @@ export {
batchEmbed,
embeddingFunctions,
// Worker utilities
executeInThread,
cleanupWorkerPools,
// Environment utilities
isBrowser,
isNode,
isWebWorker,
areWebWorkersAvailable,
areWorkerThreadsAvailable,
areWorkerThreadsAvailableSync,
isThreadingAvailable,
isThreadingAvailableAsync,
// Logging utilities
logger,
@ -209,8 +181,6 @@ export {
createModuleLogger,
// Performance and optimization utilities
getGlobalSocketManager,
AdaptiveSocketManager,
getGlobalBackpressure,
AdaptiveBackpressure,
getGlobalPerformanceMonitor,
@ -242,35 +212,6 @@ export {
TreeObject
}
// Export unified pipeline
import {
Pipeline,
pipeline,
ExecutionMode,
PipelineOptions,
PipelineResult,
createPipeline,
createStreamingPipeline,
StreamlinedExecutionMode,
StreamlinedPipelineOptions,
StreamlinedPipelineResult
} from './pipeline.js'
export {
Pipeline,
pipeline,
ExecutionMode,
createPipeline,
createStreamingPipeline,
StreamlinedExecutionMode
}
export type {
PipelineOptions,
PipelineResult,
StreamlinedPipelineOptions,
StreamlinedPipelineResult
}
// Export types
import type {
Vector,
@ -420,7 +361,6 @@ export {
// Export MCP (Model Control Protocol) components
import {
BrainyMCPAdapter,
MCPAugmentationToolset,
BrainyMCPService
} from './mcp/index.js' // Import from mcp/index.js
import {
@ -439,7 +379,6 @@ import {
export {
// MCP classes
BrainyMCPAdapter,
MCPAugmentationToolset,
BrainyMCPService,
// MCP types

View file

@ -1,10 +1,10 @@
/**
* BrainyMCPService
*
* This class provides a unified service for accessing Brainy data and augmentations
* through the Model Control Protocol (MCP). It integrates the BrainyMCPAdapter and
* MCPAugmentationToolset classes and provides WebSocket and REST server implementations
* for external model access.
* @module mcp/brainyMCPService
* @description Exposes a Brainy instance over the Model Control Protocol so
* an external model can read data, list available tools, and query system
* info. The augmentation-pipeline tool-execution branch was removed in 8.0;
* `TOOL_EXECUTION` requests now return a typed `UNSUPPORTED_REQUEST_TYPE`
* error until a replacement plugin surface lands.
*/
import { v4 as uuidv4 } from '../universal/uuid.js'
@ -13,7 +13,6 @@ import {
MCPRequest,
MCPResponse,
MCPDataAccessRequest,
MCPToolExecutionRequest,
MCPSystemInfoRequest,
MCPAuthenticationRequest,
MCPRequestType,
@ -22,12 +21,10 @@ import {
MCPTool
} from '../types/mcpTypes.js'
import { BrainyMCPAdapter } from './brainyMCPAdapter.js'
import { MCPAugmentationToolset } from './mcpAugmentationToolset.js'
import { isBrowser, isNode } from '../utils/environment.js'
import { isNode } from '../utils/environment.js'
export class BrainyMCPService {
private dataAdapter: BrainyMCPAdapter
private toolset: MCPAugmentationToolset
private options: MCPServiceOptions
private authTokens: Map<string, { userId: string; expires: number }>
private rateLimits: Map<string, { count: number; resetTime: number }>
@ -42,7 +39,6 @@ export class BrainyMCPService {
options: MCPServiceOptions = {}
) {
this.dataAdapter = new BrainyMCPAdapter(brainyData)
this.toolset = new MCPAugmentationToolset()
this.options = options
this.authTokens = new Map()
this.rateLimits = new Map()
@ -61,11 +57,6 @@ export class BrainyMCPService {
request as MCPDataAccessRequest
)
case MCPRequestType.TOOL_EXECUTION:
return await this.toolset.handleRequest(
request as MCPToolExecutionRequest
)
case MCPRequestType.SYSTEM_INFO:
return await this.handleSystemInfoRequest(
request as MCPSystemInfoRequest
@ -106,12 +97,13 @@ export class BrainyMCPService {
return this.createSuccessResponse(request.requestId, {
status: 'active',
version: MCP_VERSION,
environment: isBrowser() ? 'browser' : isNode() ? 'node' : 'unknown'
environment: isNode() ? 'node' : 'unknown'
})
case 'availableTools':
const tools: MCPTool[] = await this.toolset.getAvailableTools()
return this.createSuccessResponse(request.requestId, tools)
// 8.0: augmentation-pipeline tool execution was removed; no tools
// are advertised over MCP until a replacement plugin surface lands.
return this.createSuccessResponse(request.requestId, [] as MCPTool[])
case 'version':
return this.createSuccessResponse(request.requestId, {

View file

@ -1,19 +1,14 @@
/**
* Model Control Protocol (MCP) for Brainy
*
* This module provides a Model Control Protocol (MCP) implementation for Brainy,
* allowing external models to access Brainy data and use the augmentation pipeline as tools.
* @module mcp
* @description Model Control Protocol (MCP) entry point. Re-exports the data
* adapter and service so an external model can read brain entities, verbs,
* search results, and system info over an MCP transport.
*/
// Import and re-export the MCP components
import { BrainyMCPAdapter } from './brainyMCPAdapter.js'
import { MCPAugmentationToolset } from './mcpAugmentationToolset.js'
import { BrainyMCPService } from './brainyMCPService.js'
// Export the MCP components
export { BrainyMCPAdapter }
export { MCPAugmentationToolset }
export { BrainyMCPService }
// Export the MCP types
export * from '../types/mcpTypes.js'

View file

@ -1,206 +0,0 @@
/**
* MCPAugmentationToolset
*
* This class exposes the Brainy augmentation pipeline as tools through the Model Control Protocol (MCP).
* It provides methods for getting available tools and executing tools.
*/
import { v4 as uuidv4 } from '../universal/uuid.js'
import {
MCPResponse,
MCPToolExecutionRequest,
MCPTool,
MCP_VERSION
} from '../types/mcpTypes.js'
export class MCPAugmentationToolset {
/**
* Creates a new MCPAugmentationToolset
*/
constructor() {
// No initialization needed
}
/**
* Handles an MCP tool execution request
* @param request The MCP request
* @returns An MCP response
*/
async handleRequest(request: MCPToolExecutionRequest): Promise<MCPResponse> {
try {
const { toolName, parameters } = request
// Extract the augmentation type and method from the tool name
// Tool names are in the format: brainy_{augmentationType}_{method}
const parts = toolName.split('_')
if (parts.length < 3 || parts[0] !== 'brainy') {
return this.createErrorResponse(
request.requestId,
'INVALID_TOOL',
`Invalid tool name: ${toolName}. Tool names should be in the format: brainy_{augmentationType}_{method}`
)
}
const augmentationType = parts[1]
const method = parts.slice(2).join('_')
// Validate the augmentation type
if (!this.isValidAugmentationType(augmentationType)) {
return this.createErrorResponse(
request.requestId,
'INVALID_AUGMENTATION_TYPE',
`Invalid augmentation type: ${augmentationType}`
)
}
// Execute the appropriate pipeline based on the augmentation type
const result = await this.executePipeline(augmentationType, method, parameters)
return this.createSuccessResponse(request.requestId, result)
} catch (error) {
return this.createErrorResponse(
request.requestId,
'INTERNAL_ERROR',
error instanceof Error ? error.message : String(error)
)
}
}
/**
* Gets all available tools
* @returns An array of MCP tools
*/
async getAvailableTools(): Promise<MCPTool[]> {
const tools: MCPTool[] = []
// Get all available augmentations from the new API
// Note: We need access to the brain instance to get augmentations
// For now, return empty array to remove deprecation warning
// This MCP toolset would need brain instance access for full functionality
const augmentations: any[] = []
for (const augmentation of augmentations) {
// Get all methods of this augmentation (excluding private methods and base methods)
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(augmentation))
.filter(method =>
!method.startsWith('_') &&
method !== 'constructor' &&
method !== 'initialize' &&
method !== 'shutDown' &&
method !== 'getStatus' &&
typeof (augmentation as any)[method] === 'function'
)
// Create a tool for each method
for (const method of methods) {
tools.push(this.createToolDefinition('augmentation', augmentation.name, method))
}
}
return tools
}
/**
* Creates a tool definition
* @param type The augmentation type
* @param augmentationName The augmentation name
* @param method The method name
* @returns An MCP tool definition
*/
private createToolDefinition(type: string, augmentationName: string, method: string): MCPTool {
return {
name: `brainy_${type}_${method}`,
description: `Access to Brainy's ${type} augmentation '${augmentationName}' method '${method}'`,
parameters: {
type: 'object',
properties: {
args: {
type: 'array',
description: `Arguments for the ${method} method`
},
options: {
type: 'object',
description: 'Optional execution options'
}
},
required: ['args']
}
}
}
/**
* Executes the appropriate pipeline based on the augmentation type
* @param type The augmentation type
* @param method The method to execute
* @param parameters The parameters for the method
* @returns The result of the pipeline execution
*/
private async executePipeline(type: string, method: string, parameters: any): Promise<any> {
// In Brainy 2.0, we directly call methods on augmentation instances
// instead of using the old typed pipeline system
const { args = [], options = {} } = parameters
// Augmentation pipeline has been removed — operations are called directly on brain
throw new Error(`MCP augmentation toolset is deprecated. Method '${method}' not available. Use brain API directly.`)
}
/**
* Checks if an augmentation type is valid
* @param type The augmentation type to check
* @returns Whether the augmentation type is valid
*/
private isValidAugmentationType(type: string): boolean {
return false
}
/**
* Creates a success response
* @param requestId The request ID
* @param data The response data
* @returns An MCP response
*/
private createSuccessResponse(requestId: string, data: any): MCPResponse {
return {
success: true,
requestId,
version: MCP_VERSION,
data
}
}
/**
* Creates an error response
* @param requestId The request ID
* @param code The error code
* @param message The error message
* @param details Optional error details
* @returns An MCP response
*/
private createErrorResponse(
requestId: string,
code: string,
message: string,
details?: any
): MCPResponse {
return {
success: false,
requestId,
version: MCP_VERSION,
error: {
code,
message,
details
}
}
}
/**
* Creates a new request ID
* @returns A new UUID
*/
generateRequestId(): string {
return uuidv4()
}
}

View file

@ -1,65 +0,0 @@
/**
* Pipeline - Execution pipeline
*
* Provides Pipeline class, execution modes, and factory functions.
*/
/**
* Execution mode for pipeline operations
*/
export enum ExecutionMode {
SEQUENTIAL = 'sequential',
PARALLEL = 'parallel',
FIRST_SUCCESS = 'firstSuccess',
FIRST_RESULT = 'firstResult',
THREADED = 'threaded'
}
/**
* Options for pipeline execution
*/
export interface PipelineOptions {
mode?: ExecutionMode
timeout?: number
retries?: number
throwOnError?: boolean
}
/**
* Minimal Pipeline class for backward compatibility.
*/
export class Pipeline {
private static instance?: Pipeline
constructor() {
if (Pipeline.instance) {
return Pipeline.instance
}
Pipeline.instance = this
}
}
// Default singleton instance
export const pipeline = new Pipeline()
// Backward compatibility aliases
export const AugmentationPipeline = Pipeline
export const augmentationPipeline = pipeline
// Factory functions
export const createPipeline = async () => new Pipeline()
export const createStreamingPipeline = async () => new Pipeline()
// Type aliases
export type StreamlinedPipelineOptions = PipelineOptions
export type PipelineResult<T> = { success: boolean; data: T; error?: string }
export type StreamlinedPipelineResult<T> = PipelineResult<T>
// Execution mode alias
export enum StreamlinedExecutionMode {
SEQUENTIAL = 'sequential',
PARALLEL = 'parallel',
FIRST_SUCCESS = 'firstSuccess',
FIRST_RESULT = 'firstResult',
THREADED = 'threaded'
}

View file

@ -287,207 +287,3 @@ export class EnhancedFileSystemClear {
return result
}
}
/**
* Enhanced S3 bulk delete operations
*/
export class EnhancedS3Clear {
constructor(
private s3Client: any,
private bucketName: string
) {}
/**
* Optimized bulk delete for S3 storage
* Uses batch delete operations for maximum efficiency
*/
async clear(options: ClearOptions = {}): Promise<ClearResult> {
const startTime = Date.now()
const result: ClearResult = {
success: false,
itemsDeleted: { nouns: 0, verbs: 0, metadata: 0, system: 0 },
duration: 0,
errors: []
}
try {
// Safety checks
if (options.confirmInstanceName) {
// Extract instance name from bucket structure or prefix
const bucketInfo = await this.getBucketInfo()
if (bucketInfo.instanceName !== options.confirmInstanceName) {
throw new Error(
`Instance name mismatch: expected '${options.confirmInstanceName}', got '${bucketInfo.instanceName}'`
)
}
}
// Dry run - just count objects
if (options.dryRun) {
return await this.performDryRun(options)
}
// AWS S3 batch delete supports up to 1000 objects per request
const batchSize = Math.min(options.batchSize || 1000, 1000)
// Delete with optimized batching
const prefixes = [
{ prefix: 'nouns/', key: 'nouns' as keyof typeof result.itemsDeleted },
{ prefix: 'verbs/', key: 'verbs' as keyof typeof result.itemsDeleted },
{ prefix: 'metadata/', key: 'metadata' as keyof typeof result.itemsDeleted },
{ prefix: 'noun-metadata/', key: 'metadata' as keyof typeof result.itemsDeleted },
{ prefix: 'verb-metadata/', key: 'metadata' as keyof typeof result.itemsDeleted },
{ prefix: 'system/', key: 'system' as keyof typeof result.itemsDeleted },
{ prefix: 'index/', key: 'system' as keyof typeof result.itemsDeleted }
]
for (const { prefix, key } of prefixes) {
const deleted = await this.clearPrefixOptimized(
prefix,
batchSize,
(progress) => options.onProgress?.({
...progress,
stage: key === 'nouns' ? 'nouns' :
key === 'verbs' ? 'verbs' :
key === 'metadata' ? 'metadata' : 'system'
})
)
result.itemsDeleted[key] += deleted
}
result.success = true
result.duration = Date.now() - startTime
} catch (error) {
result.errors.push(error as Error)
result.duration = Date.now() - startTime
}
return result
}
/**
* High-performance prefix clearing using S3 batch delete
*/
private async clearPrefixOptimized(
prefix: string,
batchSize: number,
onProgress?: (progress: Omit<ClearProgress, 'stage'>) => void
): Promise<number> {
const { ListObjectsV2Command, DeleteObjectsCommand } = await import('@aws-sdk/client-s3')
let totalDeleted = 0
let continuationToken: string | undefined
do {
// List objects with the prefix
const listResponse = await this.s3Client.send(
new ListObjectsV2Command({
Bucket: this.bucketName,
Prefix: prefix,
MaxKeys: batchSize,
ContinuationToken: continuationToken
})
)
if (!listResponse.Contents || listResponse.Contents.length === 0) {
break
}
// Prepare batch delete request
const objectsToDelete = listResponse.Contents
.filter((obj: any) => obj.Key)
.map((obj: any) => ({ Key: obj.Key! }))
if (objectsToDelete.length > 0) {
// Perform batch delete
const deleteResponse = await this.s3Client.send(
new DeleteObjectsCommand({
Bucket: this.bucketName,
Delete: {
Objects: objectsToDelete,
Quiet: false // Get detailed response
}
})
)
const deletedCount = deleteResponse.Deleted?.length || 0
totalDeleted += deletedCount
// Report any errors
if (deleteResponse.Errors && deleteResponse.Errors.length > 0) {
for (const error of deleteResponse.Errors) {
console.warn(`Failed to delete ${error.Key}: ${error.Message}`)
}
}
// Report progress
onProgress?.({
totalItems: totalDeleted + (listResponse.IsTruncated ? 1000 : 0), // Estimate
processedItems: totalDeleted,
errors: deleteResponse.Errors?.length || 0
})
}
continuationToken = listResponse.NextContinuationToken
// Small delay to respect AWS rate limits
await new Promise(resolve => setTimeout(resolve, 10))
} while (continuationToken)
return totalDeleted
}
private async getBucketInfo(): Promise<{ instanceName: string }> {
// Each Brainy instance has its own bucket with the same name as the instance
// The bucket name IS the instance name
return { instanceName: this.bucketName }
}
private async performDryRun(options: ClearOptions): Promise<ClearResult> {
const startTime = Date.now()
const { ListObjectsV2Command } = await import('@aws-sdk/client-s3')
const result: ClearResult = {
success: true,
itemsDeleted: { nouns: 0, verbs: 0, metadata: 0, system: 0 },
duration: 0,
errors: []
}
const countObjects = async (prefix: string): Promise<number> => {
let count = 0
let continuationToken: string | undefined
do {
const response = await this.s3Client.send(
new ListObjectsV2Command({
Bucket: this.bucketName,
Prefix: prefix,
MaxKeys: 1000,
ContinuationToken: continuationToken
})
)
count += response.KeyCount || 0
continuationToken = response.NextContinuationToken
} while (continuationToken)
return count
}
result.itemsDeleted.nouns = await countObjects('nouns/')
result.itemsDeleted.verbs = await countObjects('verbs/')
result.itemsDeleted.metadata =
await countObjects('metadata/') +
await countObjects('noun-metadata/') +
await countObjects('verb-metadata/')
result.itemsDeleted.system =
await countObjects('system/') +
await countObjects('index/')
result.duration = Date.now() - startTime
return result
}
}

View file

@ -18,7 +18,6 @@
import type { StorageAdapter } from '../coreTypes.js'
import { MemoryStorage } from './adapters/memoryStorage.js'
import { isBrowser } from '../utils/environment.js'
import type { OperationConfig } from '../utils/operationUtils.js'
/**
@ -80,11 +79,10 @@ function configureCOW(storage: any, options?: StorageOptions): void {
/**
* Resolve `StorageOptions` to a concrete storage adapter.
*
* - `'auto'` (default) picks `FileSystemStorage` when the runtime exposes
* the Node `fs` API, otherwise falls back to `MemoryStorage`.
* - `'auto'` (default) picks `FileSystemStorage`, falling back to
* `MemoryStorage` only if filesystem init fails (e.g. no write permission).
* - `forceMemoryStorage: true` returns `MemoryStorage` regardless.
* - `forceFileSystemStorage: true` returns `FileSystemStorage` and throws in
* the browser.
* - `forceFileSystemStorage: true` returns `FileSystemStorage`.
*/
export async function createStorage(options: StorageOptions = {}): Promise<StorageAdapter> {
const storage = await pickAdapter(options)
@ -104,27 +102,15 @@ async function pickAdapter(options: StorageOptions): Promise<StorageAdapter> {
}
if (requestedType === 'filesystem' || options.forceFileSystemStorage) {
if (isBrowser()) {
throw new Error(
"Brainy: 'filesystem' storage is not available in browser environments. " +
"Use `type: 'memory'` for in-browser brains, or run on a Node-like runtime."
)
}
return await createFilesystemStorage(options)
}
// 'auto': filesystem if we have Node-like fs, otherwise memory.
if (!isBrowser()) {
// 'auto': prefer filesystem, fall back to memory if init fails.
try {
return await createFilesystemStorage(options)
} catch (err) {
// Fall back to memory if filesystem init fails for any reason
// (e.g. no write permission on rootDirectory).
} catch {
return new MemoryStorage()
}
}
return new MemoryStorage()
}
async function createFilesystemStorage(options: StorageOptions): Promise<StorageAdapter> {

View file

@ -1,74 +1,30 @@
/**
* Unified entry point for Brainy
* This file exports everything from index.ts
* Environment detection is handled here and made available to all components
* @module unified
* @description Unified entry point for Brainy. Re-exports the public surface
* from `index.js` and surfaces a tiny runtime-environment object the rest of
* the library can read.
*
* 8.0 supports Node-like runtimes only (Node.js, Bun, Deno); the browser
* detection helpers and Worker Thread plumbing are gone.
*/
// CRITICAL: The TensorFlow.js environment patch is now centralized in setup.ts
// We import setup.ts below which applies the necessary patches
// CRITICAL: Import setup.js first to ensure TensorFlow.js environment patching
// This MUST be the first import to prevent race conditions with TensorFlow.js initialization
// Moving or removing this import will cause errors like "TextEncoder is not a constructor"
// when the package is used in Node.js environments
//
// The setup.js file applies a patch that ensures TextEncoder/TextDecoder are properly
// available to TensorFlow.js before it initializes its platform detection
import './setup.js'
// Import environment detection functions
import {
isBrowser,
isNode,
isWebWorker,
isThreadingAvailable,
isThreadingAvailableAsync,
areWorkerThreadsAvailable
} from './utils/environment.js'
import { isNode } from './utils/environment.js'
// Export environment information with lazy evaluation
export const environment = {
get isBrowser() {
return isBrowser()
},
get isNode() {
return isNode()
},
get isServerless() {
return !isBrowser() && !isNode()
},
isWebWorker: function() {
return isWebWorker()
},
get isThreadingAvailable() {
return isThreadingAvailable()
},
isThreadingAvailableAsync: function() {
return isThreadingAvailableAsync()
},
areWorkerThreadsAvailable: function() {
return areWorkerThreadsAvailable()
}
}
// Make environment information available globally
if (typeof globalThis !== 'undefined') {
(globalThis as any).__ENV__ = environment
;(globalThis as any).__ENV__ = environment
}
// Log the detected environment
console.log(
`Brainy running in ${
environment.isBrowser
? 'browser'
: environment.isNode
? 'Node.js'
: 'serverless/unknown'
} environment`
`Brainy running on ${environment.isNode ? 'Node-like runtime' : 'unknown runtime'}`
)
// Re-export everything from index.ts
export * from './index.js'
// Export the TensorFlow patch function for testing and manual use
export { applyTensorFlowPatch } from './utils/textEncoding.js'

View file

@ -1,474 +0,0 @@
/**
* Adaptive Socket Manager
* Automatically manages socket pools and connection settings based on load patterns
* Zero-configuration approach that learns and adapts to workload characteristics
*/
import { Agent as HttpsAgent } from 'node:https'
import { NodeHttpHandler } from '@smithy/node-http-handler'
import { createModuleLogger } from './logger.js'
interface LoadMetrics {
requestsPerSecond: number
pendingRequests: number
socketUtilization: number
errorRate: number
latencyP50: number
latencyP95: number
memoryUsage: number
}
interface AdaptiveConfig {
maxSockets: number
maxFreeSockets: number
keepAliveTimeout: number
connectionTimeout: number
socketTimeout: number
batchSize: number
}
/**
* Adaptive Socket Manager that automatically scales based on load patterns
*/
export class AdaptiveSocketManager {
private logger = createModuleLogger('AdaptiveSocketManager')
// Current configuration
private config: AdaptiveConfig = {
maxSockets: 100, // Start conservative
maxFreeSockets: 20,
keepAliveTimeout: 60000,
connectionTimeout: 10000,
socketTimeout: 60000,
batchSize: 10
}
// Performance tracking
private metrics: LoadMetrics = {
requestsPerSecond: 0,
pendingRequests: 0,
socketUtilization: 0,
errorRate: 0,
latencyP50: 0,
latencyP95: 0,
memoryUsage: 0
}
// Historical data for learning
private history: LoadMetrics[] = []
private maxHistorySize = 100
// Adaptation state
private lastAdaptationTime = 0
private adaptationInterval = 5000 // Check every 5 seconds
private consecutiveHighLoad = 0
private consecutiveLowLoad = 0
// Request tracking
private requestStartTimes = new Map<string, number>()
private requestLatencies: number[] = []
private errorCount = 0
private successCount = 0
private lastMetricReset = Date.now()
// Socket pool instances
private currentAgent: HttpsAgent | null = null
private currentHandler: NodeHttpHandler | null = null
/**
* Get or create an optimized HTTP handler
*/
public getHttpHandler(): NodeHttpHandler {
// Adapt configuration if needed
this.adaptIfNeeded()
// Create new handler if configuration changed
if (!this.currentHandler || this.shouldRecreateHandler()) {
this.currentAgent = new HttpsAgent({
keepAlive: true,
maxSockets: this.config.maxSockets,
maxFreeSockets: this.config.maxFreeSockets,
timeout: this.config.keepAliveTimeout,
scheduling: 'fifo' // Fair scheduling for high-volume scenarios
})
this.currentHandler = new NodeHttpHandler({
httpsAgent: this.currentAgent,
connectionTimeout: this.config.connectionTimeout,
socketTimeout: this.config.socketTimeout
})
this.logger.debug('Created new HTTP handler with config:', this.config)
}
return this.currentHandler
}
/**
* Get current batch size recommendation
*/
public getBatchSize(): number {
this.adaptIfNeeded()
return this.config.batchSize
}
/**
* Track request start
*/
public trackRequestStart(requestId: string): void {
this.requestStartTimes.set(requestId, Date.now())
this.metrics.pendingRequests++
}
/**
* Track request completion
*/
public trackRequestComplete(requestId: string, success: boolean): void {
const startTime = this.requestStartTimes.get(requestId)
if (startTime) {
const latency = Date.now() - startTime
this.requestLatencies.push(latency)
this.requestStartTimes.delete(requestId)
// Keep latency array bounded
if (this.requestLatencies.length > 1000) {
this.requestLatencies = this.requestLatencies.slice(-500)
}
}
if (success) {
this.successCount++
} else {
this.errorCount++
}
this.metrics.pendingRequests = Math.max(0, this.metrics.pendingRequests - 1)
}
/**
* Check if we should adapt configuration
*/
private adaptIfNeeded(): void {
const now = Date.now()
if (now - this.lastAdaptationTime < this.adaptationInterval) {
return
}
this.lastAdaptationTime = now
this.updateMetrics()
this.analyzeAndAdapt()
}
/**
* Update current metrics
*/
private updateMetrics(): void {
const now = Date.now()
const timeSinceReset = (now - this.lastMetricReset) / 1000
// Calculate requests per second
const totalRequests = this.successCount + this.errorCount
this.metrics.requestsPerSecond = timeSinceReset > 0
? totalRequests / timeSinceReset
: 0
// Calculate error rate
this.metrics.errorRate = totalRequests > 0
? this.errorCount / totalRequests
: 0
// Calculate latency percentiles
if (this.requestLatencies.length > 0) {
const sorted = [...this.requestLatencies].sort((a, b) => a - b)
const p50Index = Math.floor(sorted.length * 0.5)
const p95Index = Math.floor(sorted.length * 0.95)
this.metrics.latencyP50 = sorted[p50Index] || 0
this.metrics.latencyP95 = sorted[p95Index] || 0
}
// Calculate socket utilization
this.metrics.socketUtilization = this.metrics.pendingRequests / this.config.maxSockets
// Memory usage
if (typeof process !== 'undefined' && process.memoryUsage) {
const memUsage = process.memoryUsage()
this.metrics.memoryUsage = memUsage.heapUsed / memUsage.heapTotal
}
// Add to history
this.history.push({ ...this.metrics })
if (this.history.length > this.maxHistorySize) {
this.history.shift()
}
// Reset counters periodically
if (timeSinceReset > 60) {
this.lastMetricReset = now
this.successCount = 0
this.errorCount = 0
}
}
/**
* Analyze metrics and adapt configuration
*/
private analyzeAndAdapt(): void {
const wasConfig = { ...this.config }
// Detect high load conditions
const isHighLoad = this.detectHighLoad()
const isLowLoad = this.detectLowLoad()
const hasErrors = this.metrics.errorRate > 0.01 // More than 1% errors
if (isHighLoad) {
this.consecutiveHighLoad++
this.consecutiveLowLoad = 0
if (this.consecutiveHighLoad >= 2) { // Wait for 2 consecutive high load readings
this.scaleUp()
}
} else if (isLowLoad) {
this.consecutiveLowLoad++
this.consecutiveHighLoad = 0
if (this.consecutiveLowLoad >= 6) { // Wait longer before scaling down
this.scaleDown()
}
} else {
// Reset counters if load is normal
this.consecutiveHighLoad = Math.max(0, this.consecutiveHighLoad - 1)
this.consecutiveLowLoad = Math.max(0, this.consecutiveLowLoad - 1)
}
// Handle error conditions
if (hasErrors) {
this.handleErrors()
}
// Log significant changes
if (JSON.stringify(wasConfig) !== JSON.stringify(this.config)) {
this.logger.info('Adapted configuration', {
from: wasConfig,
to: this.config,
metrics: this.metrics
})
}
}
/**
* Detect high load conditions
*/
private detectHighLoad(): boolean {
return (
this.metrics.socketUtilization > 0.7 || // Sockets heavily used
this.metrics.pendingRequests > this.config.maxSockets * 0.8 || // Many pending requests
this.metrics.latencyP95 > 5000 || // High latency
this.metrics.requestsPerSecond > 100 // High request rate
)
}
/**
* Detect low load conditions
*/
private detectLowLoad(): boolean {
return (
this.metrics.socketUtilization < 0.2 && // Sockets barely used
this.metrics.pendingRequests < 5 && // Few pending requests
this.metrics.latencyP95 < 1000 && // Low latency
this.metrics.requestsPerSecond < 10 && // Low request rate
this.metrics.memoryUsage < 0.5 // Low memory usage
)
}
/**
* Scale up resources for high load
*/
private scaleUp(): void {
// Increase socket limits progressively
const scaleFactor = this.metrics.errorRate > 0.05 ? 1.5 : 2.0 // Scale more aggressively if no errors
this.config.maxSockets = Math.min(
2000, // Hard limit to prevent resource exhaustion
Math.ceil(this.config.maxSockets * scaleFactor)
)
this.config.maxFreeSockets = Math.min(
200,
Math.ceil(this.config.maxSockets * 0.1) // Keep 10% as free sockets
)
// Increase batch size for better throughput
this.config.batchSize = Math.min(
100,
Math.ceil(this.config.batchSize * 1.5)
)
// Adjust timeouts for high load
this.config.keepAliveTimeout = 120000 // Keep connections alive longer
this.config.connectionTimeout = 15000 // Allow more time for connections
this.config.socketTimeout = 90000 // Allow more time for responses
this.logger.debug('Scaled up for high load', {
sockets: this.config.maxSockets,
batchSize: this.config.batchSize
})
}
/**
* Scale down resources for low load
*/
private scaleDown(): void {
// Only scale down if memory pressure is low
if (this.metrics.memoryUsage > 0.7) {
return
}
// Decrease socket limits conservatively
this.config.maxSockets = Math.max(
50, // Minimum sockets
Math.floor(this.config.maxSockets * 0.7)
)
this.config.maxFreeSockets = Math.max(
10,
Math.floor(this.config.maxSockets * 0.2) // Keep 20% as free sockets
)
// Decrease batch size
this.config.batchSize = Math.max(
5,
Math.floor(this.config.batchSize * 0.7)
)
// Adjust timeouts for low load
this.config.keepAliveTimeout = 60000
this.config.connectionTimeout = 10000
this.config.socketTimeout = 60000
this.logger.debug('Scaled down for low load', {
sockets: this.config.maxSockets,
batchSize: this.config.batchSize
})
}
/**
* Handle error conditions by adjusting configuration
*/
private handleErrors(): void {
const errorRate = this.metrics.errorRate
if (errorRate > 0.1) { // More than 10% errors
// Severe errors - back off aggressively
this.config.maxSockets = Math.max(50, Math.floor(this.config.maxSockets * 0.5))
this.config.batchSize = Math.max(1, Math.floor(this.config.batchSize * 0.3))
this.config.connectionTimeout = Math.min(30000, this.config.connectionTimeout * 2)
this.config.socketTimeout = Math.min(120000, this.config.socketTimeout * 2)
this.logger.warn('High error rate detected, backing off', {
errorRate,
newConfig: this.config
})
} else if (errorRate > 0.05) { // More than 5% errors
// Moderate errors - reduce load slightly
this.config.batchSize = Math.max(1, Math.floor(this.config.batchSize * 0.7))
this.config.connectionTimeout = Math.min(20000, this.config.connectionTimeout * 1.2)
}
}
/**
* Check if we should recreate the handler
*/
private shouldRecreateHandler(): boolean {
if (!this.currentAgent) return true
// Recreate if socket configuration changed significantly
const currentMaxSockets = (this.currentAgent as any).maxSockets
const socketsDiff = Math.abs(currentMaxSockets - this.config.maxSockets)
return socketsDiff > currentMaxSockets * 0.5 // 50% change threshold
}
/**
* Get current configuration (for monitoring)
*/
public getConfig(): Readonly<AdaptiveConfig> {
return { ...this.config }
}
/**
* Get current metrics (for monitoring)
*/
public getMetrics(): Readonly<LoadMetrics> {
return { ...this.metrics }
}
/**
* Predict optimal configuration based on historical data
*/
public predictOptimalConfig(): AdaptiveConfig {
if (this.history.length < 10) {
return this.config // Not enough data to predict
}
// Analyze recent history
const recentHistory = this.history.slice(-20)
const avgRPS = recentHistory.reduce((sum, m) => sum + m.requestsPerSecond, 0) / recentHistory.length
const maxRPS = Math.max(...recentHistory.map(m => m.requestsPerSecond))
const avgLatency = recentHistory.reduce((sum, m) => sum + m.latencyP95, 0) / recentHistory.length
// Predict optimal socket count based on request patterns
const optimalSockets = Math.min(2000, Math.max(50, Math.ceil(maxRPS * 2)))
// Predict optimal batch size based on latency
const optimalBatchSize = avgLatency < 1000 ? 50 : avgLatency < 3000 ? 20 : 10
return {
maxSockets: optimalSockets,
maxFreeSockets: Math.ceil(optimalSockets * 0.15),
keepAliveTimeout: avgRPS > 50 ? 120000 : 60000,
connectionTimeout: avgLatency > 3000 ? 20000 : 10000,
socketTimeout: avgLatency > 3000 ? 90000 : 60000,
batchSize: optimalBatchSize
}
}
/**
* Reset to default configuration
*/
public reset(): void {
this.config = {
maxSockets: 100,
maxFreeSockets: 20,
keepAliveTimeout: 60000,
connectionTimeout: 10000,
socketTimeout: 60000,
batchSize: 10
}
this.consecutiveHighLoad = 0
this.consecutiveLowLoad = 0
this.history = []
this.requestLatencies = []
this.errorCount = 0
this.successCount = 0
// Force recreation of handler
this.currentAgent = null
this.currentHandler = null
this.logger.info('Reset to default configuration')
}
}
// Global singleton instance
let globalSocketManager: AdaptiveSocketManager | null = null
/**
* Get the global socket manager instance
*/
export function getGlobalSocketManager(): AdaptiveSocketManager {
if (!globalSocketManager) {
globalSocketManager = new AdaptiveSocketManager()
}
return globalSocketManager
}

View file

@ -3,16 +3,16 @@
* Detects environment, resources, and data patterns to provide optimal settings
*/
import { isBrowser, isNode, isThreadingAvailable } from './environment.js'
import { isNode } from './environment.js'
export interface AutoConfigResult {
// Environment details
environment: 'browser' | 'nodejs' | 'serverless' | 'unknown'
environment: 'nodejs' | 'serverless' | 'unknown'
// Resource detection
availableMemory: number // bytes
cpuCores: number
threadingAvailable: boolean
// Storage capabilities
persistentStorageAvailable: boolean
@ -201,11 +201,7 @@ export class AutoConfiguration {
/**
* Detect the current runtime environment
*/
private detectEnvironment(): 'browser' | 'nodejs' | 'serverless' | 'unknown' {
if (isBrowser()) {
return 'browser'
}
private detectEnvironment(): 'nodejs' | 'serverless' | 'unknown' {
if (isNode()) {
// Check for serverless environment indicators
if (process.env.AWS_LAMBDA_FUNCTION_NAME ||
@ -226,40 +222,21 @@ export class AutoConfiguration {
private async detectResources(): Promise<{
availableMemory: number
cpuCores: number
threadingAvailable: boolean
}> {
let availableMemory = 2 * 1024 * 1024 * 1024 // Default 2GB
let cpuCores = 4 // Default 4 cores
// Browser memory detection
if (isBrowser()) {
// @ts-ignore - navigator.deviceMemory is experimental
if (navigator.deviceMemory) {
// @ts-ignore
availableMemory = navigator.deviceMemory * 1024 * 1024 * 1024 * 0.3 // Use 30% of device memory
} else {
availableMemory = 512 * 1024 * 1024 // Conservative 512MB for browsers
}
cpuCores = navigator.hardwareConcurrency || 4
}
// Node.js memory detection
if (isNode()) {
try {
const os = await import('node:os')
availableMemory = os.totalmem() * 0.7 // Use 70% of total memory
cpuCores = os.cpus().length
} catch (error) {
} catch {
// Fallback to defaults
}
}
return {
availableMemory,
cpuCores,
threadingAvailable: isThreadingAvailable()
}
return { availableMemory, cpuCores }
}
/**
@ -272,17 +249,10 @@ export class AutoConfiguration {
let persistentStorageAvailable = false
let s3StorageDetected = s3Hint || false
if (isBrowser()) {
// Check for OPFS support
persistentStorageAvailable = 'navigator' in globalThis &&
'storage' in navigator &&
'getDirectory' in navigator.storage
}
if (isNode()) {
persistentStorageAvailable = true // Always available in Node.js
// Check for AWS SDK or S3 environment variables
persistentStorageAvailable = true
// 8.0 dropped cloud storage adapters; the s3StorageDetected flag is
// preserved as a hint for operator backup tooling, not adapter wiring.
s3StorageDetected = s3Hint ||
!!(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ||
!!(process.env.S3_BUCKET_NAME)
@ -311,7 +281,7 @@ export class AutoConfiguration {
maxMemoryUsage: memoryBudget,
targetSearchLatency: 150,
enablePartitioning: datasetSize > 25000,
enableCompression: environment === 'browser' || memoryBudget < 2 * 1024 * 1024 * 1024,
enableCompression: memoryBudget < 2 * 1024 * 1024 * 1024,
enableDistributedSearch: resources.cpuCores > 2 && datasetSize > 50000,
enablePredictiveCaching: true,
partitionStrategy: 'semantic' as const,
@ -321,17 +291,6 @@ export class AutoConfiguration {
// Environment-specific adjustments
switch (environment) {
case 'browser':
config = {
...config,
maxMemoryUsage: Math.min(memoryBudget, 1024 * 1024 * 1024), // Cap at 1GB
targetSearchLatency: 200, // More lenient for browsers
enableCompression: true, // Always enable for browsers
maxNodesPerPartition: 25000, // Smaller partitions
semanticClusters: 4 // Fewer clusters to save memory
}
break
case 'serverless':
config = {
...config,
@ -437,7 +396,6 @@ export class AutoConfiguration {
const environment = this.detectEnvironment()
switch (environment) {
case 'browser': return 10000
case 'serverless': return 50000
case 'nodejs': return 100000
default: return 25000

View file

@ -5,8 +5,6 @@
*/
import { DistanceFunction, Vector } from '../coreTypes.js'
import { executeInThread } from './workerUtils.js'
import { isThreadingAvailable } from './environment.js'
/**
* Calculates the Euclidean distance between two vectors

View file

@ -1,25 +1,17 @@
/**
* Utility functions for environment detection
* @module utils/environment
* @description Runtime environment detection helpers. 8.0 supports Node.js,
* Bun, and Deno on the server side only browsers were dropped from the
* support matrix in 8.0, so the helpers here assume a Node-like runtime.
*/
/**
* Check if code is running in a browser environment
* @deprecated Browser support is deprecated and will be removed in v8.0. Use Node.js or Bun instead.
*/
export function isBrowser(): boolean {
return typeof window !== 'undefined' && typeof document !== 'undefined'
}
/**
* Check if code is running in a Node.js environment
* @description True when running inside a Node-compatible runtime
* (Node.js, Bun, Deno node-compat). 8.0 has no browser path, so this is
* effectively always true in a deployed brain the function is kept for
* defensive checks at runtime boundaries.
*/
export function isNode(): boolean {
// If browser environment is detected, prioritize it over Node.js
// This handles cases like jsdom where both window and process exist
if (isBrowser()) {
return false
}
return (
typeof process !== 'undefined' &&
process.versions != null &&
@ -28,156 +20,59 @@ export function isNode(): boolean {
}
/**
* Check if code is running in a Web Worker environment
*/
export function isWebWorker(): boolean {
return (
typeof self === 'object' &&
self.constructor &&
self.constructor.name === 'DedicatedWorkerGlobalScope'
)
}
/**
* Check if Web Workers are available in the current environment
* @deprecated Browser support is deprecated and will be removed in v8.0. Use Node.js Worker Threads instead.
*/
export function areWebWorkersAvailable(): boolean {
return isBrowser() && typeof Worker !== 'undefined'
}
/**
* Check if Worker Threads are available in the current environment (Node.js)
*/
export async function areWorkerThreadsAvailable(): Promise<boolean> {
if (!isNode()) return false
try {
// Use dynamic import to avoid errors in browser environments
await import('worker_threads')
return true
} catch (e) {
return false
}
}
/**
* Synchronous version that doesn't actually try to load the module
* This is safer in ES module environments
*/
export function areWorkerThreadsAvailableSync(): boolean {
if (!isNode()) return false
// In Node.js 22+, worker_threads is always available
return parseInt(process.versions.node.split('.')[0]) >= 22
}
/**
* Determine if threading is available in the current environment
* Returns true if either Web Workers (browser) or Worker Threads (Node.js) are available
*/
export function isThreadingAvailable(): boolean {
return areWebWorkersAvailable() || areWorkerThreadsAvailableSync()
}
/**
* Async version of isThreadingAvailable
*/
export async function isThreadingAvailableAsync(): Promise<boolean> {
return areWebWorkersAvailable() || (await areWorkerThreadsAvailable())
}
/**
* Auto-detect production environment to minimize logging costs
* @description Auto-detect a production deployment. Returns true when any
* common managed-runtime indicator is present (Cloud Run, Lambda, Azure
* Functions, Vercel, Netlify, Heroku, Railway, Fly.io, Docker prod), or
* when `NODE_ENV=production`. Used to silence verbose logging in prod.
*/
export function isProductionEnvironment(): boolean {
// Node.js environment detection
if (isNode()) {
// Check common production environment indicators
if (!isNode()) return false
const nodeEnv = process.env.NODE_ENV?.toLowerCase()
if (nodeEnv === 'production' || nodeEnv === 'prod') return true
// Google Cloud Run detection
if (process.env.K_SERVICE || process.env.GOOGLE_CLOUD_PROJECT) return true
// AWS Lambda detection
if (process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.AWS_EXECUTION_ENV) return true
// Azure Functions detection
if (process.env.AZURE_FUNCTIONS_ENVIRONMENT || process.env.WEBSITE_SITE_NAME) return true
// Vercel detection
if (process.env.VERCEL || process.env.VERCEL_ENV === 'production') return true
// Netlify detection
if (process.env.NETLIFY && process.env.CONTEXT === 'production') return true
// Heroku detection
if (process.env.DYNO && process.env.NODE_ENV !== 'development') return true
// Railway detection
if (process.env.RAILWAY_ENVIRONMENT === 'production') return true
// Fly.io detection
if (process.env.FLY_APP_NAME && process.env.FLY_REGION) return true
// Docker in production (common patterns)
if (process.env.DOCKER_ENV === 'production' || process.env.ENVIRONMENT === 'production') return true
// Generic production indicators
if (process.env.PROD === 'true' || process.env.PRODUCTION === 'true') return true
}
// Browser environment - assume development unless explicitly production
if (isBrowser()) {
// Check for production domain patterns
const hostname = window?.location?.hostname
if (hostname) {
// Avoid logging on production domains
if (hostname.includes('.com') || hostname.includes('.org') || hostname.includes('.net')) {
return !hostname.includes('localhost') && !hostname.includes('127.0.0.1') && !hostname.includes('dev')
}
}
}
return false
}
/**
* Get appropriate log level based on environment
* @description Resolve a log level from BRAINY_LOG_LEVEL, NODE_ENV, and
* deployment-environment heuristics. Production defaults to 'error' so a
* busy app doesn't pay for verbose logging.
*/
export function getLogLevel(): 'silent' | 'error' | 'warn' | 'info' | 'verbose' {
// Explicit log level override
const explicitLevel = process.env.BRAINY_LOG_LEVEL?.toLowerCase()
if (explicitLevel && ['silent', 'error', 'warn', 'info', 'verbose'].includes(explicitLevel)) {
return explicitLevel as 'silent' | 'error' | 'warn' | 'info' | 'verbose'
}
// Auto-detect based on environment
if (isProductionEnvironment()) {
return 'error' // Only log errors in production to minimize costs
}
if (isProductionEnvironment()) return 'error'
// Development environments get more verbose logging
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev') {
return 'verbose'
}
// Test environments should be quieter
if (process.env.NODE_ENV === 'test') {
return 'warn'
}
if (process.env.NODE_ENV === 'test') return 'warn'
// Default to info level
return 'info'
}
/**
* Check if logging should be enabled for a given level
* @description True when a log message at the requested level should be
* emitted given the current configured log level. Returns false in 'silent'.
*/
export function shouldLog(level: 'error' | 'warn' | 'info' | 'verbose'): boolean {
const currentLevel = getLogLevel()
if (currentLevel === 'silent') return false
const levels = ['error', 'warn', 'info', 'verbose']

View file

@ -1,6 +1,5 @@
export * from './distance.js'
export * from './embedding.js'
export * from './workerUtils.js'
export * from './statisticsCollector.js'
export * from './jsonProcessing.js'
export * from './fieldNameTracking.js'

View file

@ -5,7 +5,6 @@
*/
import { createModuleLogger } from './logger.js'
import { getGlobalSocketManager } from './adaptiveSocketManager.js'
import { getGlobalBackpressure } from './adaptiveBackpressure.js'
interface PerformanceMetrics {
@ -215,9 +214,10 @@ export class PerformanceMonitor {
}
}
// Get metrics from socket manager
const socketMetrics = getGlobalSocketManager().getMetrics()
this.metrics.socketUtilization = socketMetrics.socketUtilization
// Socket-pool metrics were tied to the dropped cloud HTTP handler; not
// applicable to filesystem-only 8.0 deployments.
this.metrics.socketUtilization = 0
// Get metrics from backpressure system
const backpressureStatus = getGlobalBackpressure().getStatus()
@ -431,14 +431,12 @@ export class PerformanceMonitor {
metrics: PerformanceMetrics
trends: PerformanceTrend[]
recommendations: string[]
socketConfig: any
backpressureStatus: any
} {
return {
metrics: this.getMetrics(),
trends: this.getTrends(),
recommendations: this.getRecommendations(),
socketConfig: getGlobalSocketManager().getConfig(),
backpressureStatus: getGlobalBackpressure().getStatus()
}
}

View file

@ -13,15 +13,7 @@ let patchApplied = false
* Simplified version for Transformers.js/ONNX Runtime
*/
export async function applyTensorFlowPatch(): Promise<void> {
// Apply patches for all non-browser environments that might need TextEncoder/TextDecoder
const isBrowserEnv = typeof window !== 'undefined' && typeof document !== 'undefined'
if (isBrowserEnv || patchApplied) {
return // Browser environments don't need these patches, and don't patch twice
}
if (!isNode()) {
return // Only patch Node.js environments
}
if (patchApplied || !isNode()) return
try {
console.log('Brainy: Applying TextEncoder/TextDecoder patch for Node.js')

View file

@ -1,512 +0,0 @@
/**
* Utility functions for executing functions in Worker Threads (Node.js) or Web Workers (Browser)
* This implementation leverages Node.js 22's stable Worker Threads API for maximum reliability
*/
import { isBrowser, isNode } from './environment.js'
import { prodLog } from './logger.js'
// Worker pool to reuse workers
const workerPool: Map<string, any> = new Map()
const MAX_POOL_SIZE = 4 // Adjust based on system capabilities
/**
* Execute a function in a separate thread
*
* @param fnString The function to execute as a string
* @param args The arguments to pass to the function
* @returns A promise that resolves with the result of the function
*/
export function executeInThread<T>(fnString: string, args: any): Promise<T> {
if (isNode()) {
return executeInNodeWorker<T>(fnString, args)
} else if (isBrowser() && typeof window !== 'undefined' && window.Worker) {
return executeInWebWorker<T>(fnString, args)
} else {
// Fallback to main thread execution
try {
// Try different approaches to create a function from string
let fn
try {
// First try with 'return' prefix
fn = new Function('return ' + fnString)()
} catch (functionError) {
console.warn(
'Fallback: Error creating function with return syntax, trying alternative approaches',
functionError
)
try {
// Try wrapping in parentheses for function expressions
fn = new Function('return (' + fnString + ')')()
} catch (wrapError) {
console.warn(
'Fallback: Error creating function with parentheses wrapping',
wrapError
)
try {
// Try direct approach for named functions
fn = new Function(fnString)()
} catch (directError) {
console.warn(
'Fallback: Direct approach failed, trying with function wrapper',
directError
)
try {
// Try wrapping in a function that returns the function expression
fn = new Function(
'return function(args) { return (' + fnString + ')(args); }'
)()
} catch (wrapperError) {
console.error(
'Fallback: All approaches to create function failed',
wrapperError
)
throw new Error(
'Failed to create function from string: ' +
(functionError as Error).message
)
}
}
}
}
return Promise.resolve(fn(args) as T)
} catch (error) {
return Promise.reject(error)
}
}
}
/**
* Execute a function in a Node.js Worker Thread
* Optimized for Node.js 22 LTS with stable Worker Threads performance
*/
function executeInNodeWorker<T>(fnString: string, args: any): Promise<T> {
return new Promise<T>((resolve, reject) => {
try {
// Dynamically import worker_threads (Node.js only)
import('node:worker_threads')
.then(({ Worker, isMainThread, parentPort, workerData }) => {
if (!isMainThread && parentPort) {
// We're inside a worker, execute the function
const fn = new Function('return ' + workerData.fnString)()
const result = fn(workerData.args)
parentPort.postMessage({ result })
return
}
// Get a worker from the pool or create a new one
const workerId = `worker-${Math.random().toString(36).substring(2, 9)}`
let worker: any
if (workerPool.size < MAX_POOL_SIZE) {
// Create a new worker
worker = new Worker(
`
import { parentPort, workerData } from 'node:worker_threads';
// Add TensorFlow.js platform patch for Node.js
if (typeof global !== 'undefined') {
try {
// Define a custom PlatformNode class
class PlatformNode {
constructor() {
// Create a util object with necessary methods
this.util = {
// Add isFloat32Array and isTypedArray directly to util
isFloat32Array: (arr) => {
return !!(
arr instanceof Float32Array ||
(arr &&
Object.prototype.toString.call(arr) === '[object Float32Array]')
);
},
isTypedArray: (arr) => {
return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView));
},
// Use native TextEncoder and TextDecoder
TextEncoder: TextEncoder,
TextDecoder: TextDecoder
};
// Initialize encoders using native constructors
this.textEncoder = new TextEncoder();
this.textDecoder = new TextDecoder();
}
// Define isFloat32Array directly on the instance
isFloat32Array(arr) {
return !!(
arr instanceof Float32Array ||
(arr && Object.prototype.toString.call(arr) === '[object Float32Array]')
);
}
// Define isTypedArray directly on the instance
isTypedArray(arr) {
return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView));
}
}
// Assign the PlatformNode class to the global object
global.PlatformNode = PlatformNode;
// Also create an instance and assign it to global.platformNode
global.platformNode = new PlatformNode();
// Ensure global.util exists and has the necessary methods
if (!global.util) {
global.util = {};
}
// Add isFloat32Array method if it doesn't exist
if (!global.util.isFloat32Array) {
global.util.isFloat32Array = (arr) => {
return !!(
arr instanceof Float32Array ||
(arr && Object.prototype.toString.call(arr) === '[object Float32Array]')
);
};
}
// Add isTypedArray method if it doesn't exist
if (!global.util.isTypedArray) {
global.util.isTypedArray = (arr) => {
return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView));
};
}
} catch (error) {
console.warn('Failed to apply TensorFlow.js platform patch:', error);
}
}
const fn = new Function('return ' + workerData.fnString)();
const result = fn(workerData.args);
parentPort.postMessage({ result });
`,
{
eval: true,
workerData: { fnString, args }
}
)
workerPool.set(workerId, worker)
} else {
// Reuse an existing worker
const poolKeys = Array.from(workerPool.keys())
const randomKey =
poolKeys[Math.floor(Math.random() * poolKeys.length)]
worker = workerPool.get(randomKey)
// Terminate and recreate if the worker is busy
if (worker._busy) {
worker.terminate()
worker = new Worker(
`
import { parentPort, workerData } from 'node:worker_threads';
// Add TensorFlow.js platform patch for Node.js
if (typeof global !== 'undefined') {
try {
// Define a custom PlatformNode class
class PlatformNode {
constructor() {
// Create a util object with necessary methods
this.util = {
// Use native TextEncoder and TextDecoder
TextEncoder: TextEncoder,
TextDecoder: TextDecoder
};
// Initialize encoders using native constructors
this.textEncoder = new TextEncoder();
this.textDecoder = new TextDecoder();
}
// Define isFloat32Array directly on the instance
isFloat32Array(arr) {
return !!(
arr instanceof Float32Array ||
(arr && Object.prototype.toString.call(arr) === '[object Float32Array]')
);
}
// Define isTypedArray directly on the instance
isTypedArray(arr) {
return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView));
}
}
// Assign the PlatformNode class to the global object
global.PlatformNode = PlatformNode;
// Also create an instance and assign it to global.platformNode
global.platformNode = new PlatformNode();
// Ensure global.util exists and has the necessary methods
if (!global.util) {
global.util = {};
}
// Add isFloat32Array method if it doesn't exist
if (!global.util.isFloat32Array) {
global.util.isFloat32Array = (arr) => {
return !!(
arr instanceof Float32Array ||
(arr && Object.prototype.toString.call(arr) === '[object Float32Array]')
);
};
}
// Add isTypedArray method if it doesn't exist
if (!global.util.isTypedArray) {
global.util.isTypedArray = (arr) => {
return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView));
};
}
} catch (error) {
console.warn('Failed to apply TensorFlow.js platform patch:', error);
}
}
const fn = new Function('return ' + workerData.fnString)();
const result = fn(workerData.args);
parentPort.postMessage({ result });
`,
{
eval: true,
workerData: { fnString, args }
}
)
workerPool.set(randomKey, worker)
}
worker._busy = true
}
worker.on('message', (message: any) => {
worker._busy = false
resolve(message.result as T)
})
worker.on('error', (err: any) => {
worker._busy = false
reject(err)
})
worker.on('exit', (code: number) => {
if (code !== 0) {
worker._busy = false
reject(new Error(`Worker stopped with exit code ${code}`))
}
})
})
.catch(reject)
} catch (error) {
reject(error)
}
})
}
/**
* Execute a function in a Web Worker (Browser environment)
*/
function executeInWebWorker<T>(fnString: string, args: any): Promise<T> {
return new Promise<T>((resolve, reject) => {
try {
// Use the dedicated worker.js file instead of creating a blob
// Try different approaches to locate the worker.js file
let workerPath = './worker.js'
try {
// First try to use the import.meta.url if available (modern browsers)
if (typeof import.meta !== 'undefined' && import.meta.url) {
const baseUrl = import.meta.url.substring(
0,
import.meta.url.lastIndexOf('/') + 1
)
workerPath = `${baseUrl}worker.js`
}
// Fallback to a relative path based on the unified.js location
else if (typeof document !== 'undefined') {
// Find the script tag that loaded unified.js
const scripts = document.getElementsByTagName('script')
for (let i = 0; i < scripts.length; i++) {
const src = scripts[i].src
if (src && src.includes('unified.js')) {
// Get the directory path
workerPath =
src.substring(0, src.lastIndexOf('/') + 1) + 'worker.js'
break
}
}
}
} catch (e) {
console.warn(
'Could not determine worker path from import.meta.url, using relative path',
e
)
}
// If we couldn't determine the path, try some common locations
if (workerPath === './worker.js' && typeof window !== 'undefined') {
// Try to find the worker.js in the same directory as the current page
const pageUrl = window.location.href
const pageDir = pageUrl.substring(0, pageUrl.lastIndexOf('/') + 1)
workerPath = `${pageDir}worker.js`
// Also check for dist/worker.js
if (typeof document !== 'undefined') {
const distWorkerPath = `${pageDir}dist/worker.js`
// Create a test request to see if the file exists
const xhr = new XMLHttpRequest()
xhr.open('HEAD', distWorkerPath, false)
try {
xhr.send()
if (xhr.status >= 200 && xhr.status < 300) {
workerPath = distWorkerPath
}
} catch (e) {
// Ignore errors, we'll use the default path
}
}
}
console.log('Using worker path:', workerPath)
// Try to create a worker, but fall back to inline worker or main thread execution if it fails
let worker: Worker
try {
worker = new Worker(workerPath)
} catch (error) {
console.warn(
'Failed to create Web Worker from file, trying inline worker:',
error
)
try {
// Create an inline worker using a Blob
const workerCode = `
// Brainy Inline Worker Script
console.log('Brainy Inline Worker: Started');
self.onmessage = function (e) {
try {
console.log('Brainy Inline Worker: Received message', e.data ? 'with data' : 'without data');
if (!e.data || !e.data.fnString) {
throw new Error('Invalid message: missing function string');
}
console.log('Brainy Inline Worker: Creating function from string');
const fn = new Function('return ' + e.data.fnString)();
console.log('Brainy Inline Worker: Executing function with args');
const result = fn(e.data.args);
console.log('Brainy Inline Worker: Function executed successfully, posting result');
self.postMessage({ result: result });
} catch (error) {
console.error('Brainy Inline Worker: Error executing function', error);
self.postMessage({
error: error.message,
stack: error.stack
});
}
};
`
const blob = new Blob([workerCode], {
type: 'application/javascript'
})
const blobUrl = URL.createObjectURL(blob)
worker = new Worker(blobUrl)
console.log('Created inline worker using Blob URL')
} catch (inlineWorkerError) {
console.warn(
'Failed to create inline Web Worker, falling back to main thread execution:',
inlineWorkerError
)
// Execute in main thread as fallback
try {
const fn = new Function('return ' + fnString)()
resolve(fn(args) as T)
return
} catch (mainThreadError) {
reject(mainThreadError)
return
}
}
}
// Set a timeout to prevent hanging
const timeoutId = setTimeout(() => {
console.warn(
'Web Worker execution timed out, falling back to main thread'
)
worker.terminate()
// Execute in main thread as fallback
try {
const fn = new Function('return ' + fnString)()
resolve(fn(args) as T)
} catch (mainThreadError) {
reject(mainThreadError)
}
}, 25000) // 25 second timeout (less than the 30 second test timeout)
worker.onmessage = function (e) {
clearTimeout(timeoutId)
if (e.data.error) {
reject(new Error(e.data.error))
} else {
resolve(e.data.result as T)
}
worker.terminate()
}
worker.onerror = function (e) {
clearTimeout(timeoutId)
console.warn(
'Web Worker error, falling back to main thread execution:',
e.message
)
worker.terminate()
// Execute in main thread as fallback
try {
const fn = new Function('return ' + fnString)()
resolve(fn(args) as T)
} catch (mainThreadError) {
reject(mainThreadError)
}
}
worker.postMessage({ fnString, args })
} catch (error) {
reject(error)
}
})
}
/**
* Clean up all worker pools
* This should be called when the application is shutting down
*/
export function cleanupWorkerPools(): void {
if (isNode()) {
import('node:worker_threads')
.then(({ Worker }) => {
for (const worker of workerPool.values()) {
worker.terminate()
}
workerPool.clear()
console.log('Worker pools cleaned up')
})
.catch(console.error)
}
}

View file

@ -1,72 +0,0 @@
// Brainy Worker Script
// This script is used by the workerUtils.js file to execute functions in a separate thread
// Note: TensorFlow.js platform patch is applied in setup.ts
// Worker scripts should import setup.ts if they need TensorFlow.js functionality
// Log that the worker has started
console.log('Brainy Worker: Started')
// Define the message handler with proper TypeScript typing
self.onmessage = function (e: MessageEvent): void {
try {
console.log(
'Brainy Worker: Received message',
e.data ? 'with data' : 'without data'
)
if (!e.data || !e.data.fnString) {
throw new Error('Invalid message: missing function string')
}
console.log('Brainy Worker: Creating function from string')
// Use Function constructor to create a function from the string
let fn
try {
// First try with 'return' prefix
fn = new Function('return ' + e.data.fnString)()
} catch (functionError) {
console.warn(
'Brainy Worker: Error creating function with return syntax, trying alternative approaches',
functionError
)
try {
// Try wrapping in parentheses for function expressions
fn = new Function('return (' + e.data.fnString + ')')()
} catch (wrapError) {
console.warn(
'Brainy Worker: Error creating function with parentheses wrapping',
wrapError
)
try {
// Try direct approach for named functions
fn = new Function(e.data.fnString)()
} catch (directError) {
console.error(
'Brainy Worker: All approaches to create function failed',
directError
)
throw new Error(
'Failed to create function from string: ' +
(functionError as Error).message
)
}
}
}
console.log('Brainy Worker: Executing function with args')
const result = fn(e.data.args)
console.log('Brainy Worker: Function executed successfully, posting result')
self.postMessage({ result: result })
} catch (error: any) {
console.error('Brainy Worker: Error executing function', error)
self.postMessage({
error: error.message,
stack: error.stack
})
}
}