Compare commits
No commits in common. "models-package-v0.7.0" and "main" have entirely different histories.
models-pac
...
main
853 changed files with 244604 additions and 80666 deletions
12
.aiignore
Normal file
12
.aiignore
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# An .aiignore file follows the same syntax as a .gitignore file.
|
||||||
|
# .gitignore documentation: https://git-scm.com/docs/gitignore
|
||||||
|
|
||||||
|
# you can ignore files
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
|
||||||
|
# or folders
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
170
.claude/skills/architecture.md
Normal file
170
.claude/skills/architecture.md
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
# Brainy Architecture Reference
|
||||||
|
|
||||||
|
## What Is Brainy
|
||||||
|
|
||||||
|
@soulcraft/brainy (v7.17.0) is a Universal Knowledge Protocol -- a Triple Intelligence database combining vector search, graph traversal, and metadata filtering in a single library. Published to npm as a public MIT-licensed package.
|
||||||
|
|
||||||
|
## Core Architecture
|
||||||
|
|
||||||
|
### Storage Layer (`src/storage/`)
|
||||||
|
- **StorageAdapter interface** (`src/coreTypes.ts:576`): The contract ALL storage backends implement. ALWAYS check this interface before adding storage methods.
|
||||||
|
- **BaseStorage** (`src/storage/baseStorage.ts`): Base implementation with built-in type-aware partitioning (TypeAwareStorageAdapter was removed -- functionality merged into BaseStorage).
|
||||||
|
- **Adapters** (`src/storage/adapters/`):
|
||||||
|
- `fileSystemStorage.ts` -- local filesystem
|
||||||
|
- `memoryStorage.ts` -- in-memory
|
||||||
|
- `baseStorageAdapter.ts` -- shared adapter base (counts, batch ops)
|
||||||
|
- Cloud + OPFS adapters were removed in 8.0 (cloud backup is operator tooling)
|
||||||
|
- **Generational MVCC / Db API** (`src/db/`): immutable `Db` values over generation-stamped records
|
||||||
|
- `db.ts` (the `Db` value), `generationStore.ts` (record layer + commit protocol), `types.ts`, `errors.ts`, `whereMatcher.ts`
|
||||||
|
- Design record: `docs/ADR-001-generational-mvcc.md`; replaced the pre-8.0 COW branching + versioning subsystems
|
||||||
|
|
||||||
|
### Vector Search (`src/hnsw/`)
|
||||||
|
- `hnswIndex.ts` -- HNSW-based approximate nearest neighbor search
|
||||||
|
- `typeAwareHNSWIndex.ts` -- type-partitioned vector search
|
||||||
|
- NOT in `src/intelligence/` (that directory does not exist)
|
||||||
|
|
||||||
|
### Graph Engine (`src/graph/`)
|
||||||
|
- `graphAdjacencyIndex.ts` -- adjacency-based graph representation
|
||||||
|
- `pathfinding.ts` -- relationship traversal and pathfinding
|
||||||
|
- `lsm/` -- LSM tree implementation for graph storage
|
||||||
|
|
||||||
|
### Metadata Index (`src/utils/metadataIndex.ts`)
|
||||||
|
- O(1) exact match via hash indexes
|
||||||
|
- O(log n) range queries via sorted indexes
|
||||||
|
- Roaring bitmap set operations for efficient filtering
|
||||||
|
- Adaptive chunking strategy (`metadataIndexChunking.ts`)
|
||||||
|
- Caching layer (`metadataIndexCache.ts`)
|
||||||
|
|
||||||
|
### Triple Intelligence (`src/triple/`)
|
||||||
|
- `TripleIntelligenceSystem.ts` -- combines vector + graph + metadata into unified queries
|
||||||
|
- Lazy-loaded indexes (loaded on first use, not at startup)
|
||||||
|
|
||||||
|
### Neural/AI Components (`src/neural/`)
|
||||||
|
- Smart Importers (`src/importers/`): CSV, Excel, PDF, DOCX, YAML, JSON, Markdown, Orchestrator
|
||||||
|
- `SmartExtractor.ts` -- entity extraction from unstructured data
|
||||||
|
- `SmartRelationshipExtractor.ts` -- relationship detection
|
||||||
|
- `NeuralEntityExtractor.ts` -- ML-based entity recognition
|
||||||
|
- Natural language processing utilities
|
||||||
|
|
||||||
|
### Distributed Systems (`src/distributed/`)
|
||||||
|
- Distributed Coordinator for multi-node operation
|
||||||
|
- Shard Manager for data partitioning
|
||||||
|
- Cache Synchronization across nodes
|
||||||
|
- Read/Write separation
|
||||||
|
- Network and HTTP transport layers
|
||||||
|
- Storage discovery and shard migration
|
||||||
|
|
||||||
|
### Transaction Management (`src/transaction/`)
|
||||||
|
- TransactionManager for ACID operations
|
||||||
|
- Operations: SaveNoun, AddToHNSW, UpdateMetadata, etc.
|
||||||
|
- Distributed transaction support
|
||||||
|
|
||||||
|
### Integration Hub (`src/integrations/`)
|
||||||
|
- Google Sheets integration
|
||||||
|
- OData (Open Data Protocol)
|
||||||
|
- Server-Sent Events (SSE)
|
||||||
|
- Webhooks
|
||||||
|
- Event bus system
|
||||||
|
|
||||||
|
### Virtual Filesystem (`src/vfs/`)
|
||||||
|
- `VirtualFileSystem.ts` -- full VFS implementation (87 KB)
|
||||||
|
- `PathResolver.ts`, `FSCompat.ts`, `MimeTypeDetector.ts`, `TreeUtils.ts`
|
||||||
|
- Subdirectories: `semantic/` (semantic search), `streams/` (streaming), `importers/`
|
||||||
|
|
||||||
|
### MCP Support (`src/mcp/`)
|
||||||
|
- BrainyMCPAdapter, MCPAugmentationToolset, BrainyMCPService
|
||||||
|
- Model Control Protocol request/response handling
|
||||||
|
|
||||||
|
### Aggregation Engine (`src/aggregation/`)
|
||||||
|
- **AggregationIndex** (`AggregationIndex.ts`): Write-time incremental aggregation — SUM, COUNT, AVG, MIN, MAX with GROUP BY and time windows
|
||||||
|
- **Time Windows** (`timeWindows.ts`): ISO 8601 bucketing — hour, day, week, month, quarter, year, custom intervals
|
||||||
|
- **Materializer** (`materializer.ts`): Debounced writes of aggregate results as `NounType.Measurement` entities
|
||||||
|
- Integrates into `brain.find({ aggregate })` for unified query API
|
||||||
|
- Write hooks in `add()`, `update()`, `delete()` for O(1) incremental updates
|
||||||
|
- `'aggregation'` provider key enables native plugin acceleration
|
||||||
|
|
||||||
|
### Additional Systems
|
||||||
|
- **CLI** (`src/cli/`): Complete command-line tool with interactive mode and catalog system
|
||||||
|
- **Migration** (`src/migration/`): MigrationRunner for database schema migrations
|
||||||
|
- **Embeddings** (`src/embeddings/`): Embedding manager with Candle-WASM Rust source
|
||||||
|
- **Streaming** (`src/streaming/`): Pipeline support with adaptive backpressure
|
||||||
|
- **Versioning** (`src/versioning/`): VersioningAPI for data versioning
|
||||||
|
- **Plugin System**: Registry-based plugin architecture
|
||||||
|
- **Patterns** (`src/patterns/`): 7 pattern library JSON files
|
||||||
|
|
||||||
|
## Type System
|
||||||
|
- **NounType** (42 types, `src/types/graphTypes.ts:850-893`): Person, Organization, Concept, Collection, Document, Task, Project, etc.
|
||||||
|
- **VerbType** (127 types, `src/types/graphTypes.ts:900-1087`): Contains, RelatedTo, PartOf, Creates, DependsOn, MemberOf, etc.
|
||||||
|
- All types in `src/types/`
|
||||||
|
|
||||||
|
## Module Exports (`src/index.ts`)
|
||||||
|
38+ named exports including: Brainy class, configuration types, neural APIs (NeuralImport, NeuralEntityExtractor, SmartExtractor, SmartRelationshipExtractor), distance functions, plugin system, migration system, embedding functions, storage adapters, COW infrastructure, pipeline utilities, graph types, MCP components, integration hub, OData utilities, and more.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── index.ts # 38+ public exports
|
||||||
|
├── brainy.ts # Main Brainy class (6,500+ lines)
|
||||||
|
├── setup.ts # Initialization polyfills
|
||||||
|
├── coreTypes.ts # StorageAdapter interface + core types
|
||||||
|
├── storage/
|
||||||
|
│ ├── baseStorage.ts # Base storage (includes type-aware)
|
||||||
|
│ ├── adapters/ # All storage backends + cloud adapters
|
||||||
|
│ └── cow/ # Copy-on-Write versioning
|
||||||
|
├── hnsw/ # HNSW vector search
|
||||||
|
├── graph/ # Graph engine + pathfinding + LSM
|
||||||
|
├── triple/ # Triple Intelligence system
|
||||||
|
├── neural/ # Smart extractors + NLP
|
||||||
|
├── importers/ # File format importers (8 types)
|
||||||
|
├── distributed/ # Distributed database (16 files)
|
||||||
|
├── transaction/ # ACID transactions (6 files)
|
||||||
|
├── integrations/ # Sheets, OData, SSE, Webhooks
|
||||||
|
├── vfs/ # Virtual filesystem + semantic search
|
||||||
|
├── mcp/ # Model Control Protocol
|
||||||
|
├── cli/ # Command-line interface
|
||||||
|
├── migration/ # Schema migrations
|
||||||
|
├── embeddings/ # Embedding manager + Candle-WASM
|
||||||
|
├── streaming/ # Pipeline + backpressure
|
||||||
|
├── versioning/ # Versioning API
|
||||||
|
├── types/ # TypeScript type definitions
|
||||||
|
├── utils/ # Metadata index, logging, etc.
|
||||||
|
├── config/ # Configuration system
|
||||||
|
├── patterns/ # Pattern library
|
||||||
|
├── api/ # API layer
|
||||||
|
├── interfaces/ # Interface definitions
|
||||||
|
├── shared/ # Shared utilities
|
||||||
|
├── data/ # Data utilities
|
||||||
|
├── errors/ # Error handling
|
||||||
|
├── critical/ # Critical error handling
|
||||||
|
├── universal/ # Universal utilities
|
||||||
|
├── import/ # Import functionality
|
||||||
|
└── scripts/ # Build scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Initialization
|
||||||
|
`brainy.ts` `init()` method performs initialization cascade:
|
||||||
|
1. Load plugins
|
||||||
|
2. Initialize storage
|
||||||
|
3. Enable COW (Copy-on-Write)
|
||||||
|
4. Set up embeddings
|
||||||
|
5. Initialize caches
|
||||||
|
6. Set up graph indexes
|
||||||
|
7. Initialize VFS
|
||||||
|
8. Set up transaction manager
|
||||||
|
9. Initialize distributed components (if enabled)
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- Framework: Vitest
|
||||||
|
- Run: `npm test`
|
||||||
|
- Test directories:
|
||||||
|
- `tests/unit/` -- unit tests
|
||||||
|
- `tests/integration/` -- integration tests
|
||||||
|
- `tests/benchmarks/` -- performance benchmarks (NOT tests/performance/)
|
||||||
|
- `tests/comprehensive/` -- comprehensive test suites
|
||||||
|
- `tests/api/` -- API tests
|
||||||
|
- `tests/helpers/` -- test utilities
|
||||||
|
|
||||||
|
## Release
|
||||||
|
- `npm run release:patch/minor/major` -- fully automated via `scripts/release.sh`
|
||||||
|
- `npm run release:dry` -- preview without changes
|
||||||
|
- Uses conventional commits for changelog generation
|
||||||
57
.dockerignore
Normal file
57
.dockerignore
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Development
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
tests
|
||||||
|
*.test.ts
|
||||||
|
*.test.js
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Documentation (keep only essentials)
|
||||||
|
docs
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
!LICENSE
|
||||||
|
|
||||||
|
# Build artifacts (will be built in Docker)
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Strategy and private docs
|
||||||
|
.strategy
|
||||||
|
CLAUDE.md
|
||||||
|
|
||||||
|
# Development files
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Data (should be mounted, not baked in)
|
||||||
|
data
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# Models (should be downloaded at runtime or mounted)
|
||||||
|
models
|
||||||
|
*.onnx
|
||||||
|
*.bin
|
||||||
40
.forgejo/workflows/ci.yml
Normal file
40
.forgejo/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
node:
|
||||||
|
name: Node ${{ matrix.node-version }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
node-version: ['22', '24']
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: npm
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run test:unit
|
||||||
|
|
||||||
|
bun:
|
||||||
|
name: Bun (latest)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
cache: npm
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
- run: npm ci
|
||||||
|
# test:bun imports the built dist/, so build first.
|
||||||
|
- run: npm run build
|
||||||
|
# Bun as a runtime is the supported Bun story (`bun add` / `bun run`).
|
||||||
|
- run: npm run test:bun
|
||||||
15
.github/FUNDING.yml
vendored
15
.github/FUNDING.yml
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: DPSIFR
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
||||||
polar: # Replace with a single Polar username
|
|
||||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
|
||||||
thanks_dev: # Replace with a single thanks.dev username
|
|
||||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
||||||
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,32 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: '[BUG] '
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
## Bug Description
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
## Reproduction Steps
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Initialize BrainyData with '...'
|
|
||||||
2. Call method '....'
|
|
||||||
3. See error
|
|
||||||
|
|
||||||
## Expected Behavior
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
- Brainy version: [e.g. 0.9.4]
|
|
||||||
- Environment: [e.g. Browser, Node.js, serverless]
|
|
||||||
- Browser (if applicable): [e.g. Chrome, Safari]
|
|
||||||
- Node.js version (if applicable): [e.g. 23.11.0]
|
|
||||||
- Operating System: [e.g. Windows 10, macOS Monterey, Ubuntu 22.04]
|
|
||||||
|
|
||||||
## Additional Context
|
|
||||||
Add any other context about the problem here. If applicable, include code snippets, error messages, or screenshots.
|
|
||||||
|
|
||||||
## Possible Solution
|
|
||||||
If you have suggestions on how to fix the issue, please describe them here.
|
|
||||||
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,22 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: '[FEATURE] '
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
A clear and concise description of what problem this feature would solve. For example: "I'm always frustrated when [...]"
|
|
||||||
|
|
||||||
## Proposed Solution
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
## Alternative Solutions
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
## Use Case
|
|
||||||
Describe a concrete use case that highlights the value of this feature.
|
|
||||||
|
|
||||||
## Additional Context
|
|
||||||
Add any other context, code examples, or references about the feature request here.
|
|
||||||
27
.github/PULL_REQUEST_TEMPLATE.md
vendored
27
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,27 +0,0 @@
|
||||||
## Description
|
|
||||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
|
|
||||||
|
|
||||||
Fixes # (issue)
|
|
||||||
|
|
||||||
## Type of change
|
|
||||||
Please delete options that are not relevant.
|
|
||||||
|
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
|
||||||
- [ ] Documentation update
|
|
||||||
- [ ] Performance improvement
|
|
||||||
- [ ] Code refactoring (no functional changes)
|
|
||||||
|
|
||||||
## How Has This Been Tested?
|
|
||||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
|
|
||||||
|
|
||||||
## Checklist:
|
|
||||||
- [ ] My code follows the style guidelines of this project
|
|
||||||
- [ ] I have performed a self-review of my own code
|
|
||||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
|
||||||
- [ ] I have made corresponding changes to the documentation
|
|
||||||
- [ ] My changes generate no new warnings
|
|
||||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
|
||||||
- [ ] New and existing unit tests pass locally with my changes
|
|
||||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
|
||||||
68
.github/workflows/deploy-demo.yml
vendored
68
.github/workflows/deploy-demo.yml
vendored
|
|
@ -1,68 +0,0 @@
|
||||||
name: Deploy Demo to GitHub Pages
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pages: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
|
||||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
|
||||||
concurrency:
|
|
||||||
group: "pages"
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout 🛎️
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node.js 🔧
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
cache: 'npm'
|
|
||||||
|
|
||||||
- name: Install dependencies 📦
|
|
||||||
run: npm install --legacy-peer-deps
|
|
||||||
|
|
||||||
- name: Build project 🏗️
|
|
||||||
run: |
|
|
||||||
npm run build
|
|
||||||
npm run build:browser
|
|
||||||
|
|
||||||
- name: Prepare deployment 📦
|
|
||||||
run: |
|
|
||||||
mkdir -p _site
|
|
||||||
mkdir -p _site/demo
|
|
||||||
mkdir -p _site/dist
|
|
||||||
cp index.html _site/
|
|
||||||
cp demo/index.html _site/demo/
|
|
||||||
cp -r dist/* _site/dist/
|
|
||||||
cp brainy.png _site/
|
|
||||||
# Copy dist directly to demo/dist for easier access
|
|
||||||
mkdir -p _site/demo/dist
|
|
||||||
cp -r dist/* _site/demo/dist/
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-pages-artifact@v3
|
|
||||||
with:
|
|
||||||
path: _site
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
environment:
|
|
||||||
name: github-pages
|
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build
|
|
||||||
steps:
|
|
||||||
- name: Deploy to GitHub Pages
|
|
||||||
id: deployment
|
|
||||||
uses: actions/deploy-pages@v4
|
|
||||||
146
.gitignore
vendored
146
.gitignore
vendored
|
|
@ -1,76 +1,116 @@
|
||||||
# Build output
|
|
||||||
/tmp
|
|
||||||
/out-tsc
|
|
||||||
/dist
|
|
||||||
/cloud-wrapper/dist
|
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
/node_modules
|
node_modules/
|
||||||
/cloud-wrapper/node_modules
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# Coverage directory
|
# Build outputs
|
||||||
/coverage
|
dist/
|
||||||
|
build/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
# Environment files
|
# Environment variables
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
brainy-data/
|
||||||
|
.brainy/
|
||||||
|
*.log
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# Test results
|
||||||
|
tests/results/
|
||||||
|
|
||||||
|
# Filesystem test artifacts (created by integration tests)
|
||||||
|
test-*/
|
||||||
|
|
||||||
# IDE files
|
# IDE files
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.swp
|
||||||
*.iws
|
*.swo
|
||||||
*.ipr
|
*~
|
||||||
*.sublime-workspace
|
|
||||||
*.sublime-project
|
|
||||||
|
|
||||||
# OS files
|
# OS files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Data directories created by FileSystemStorage
|
# Temporary files
|
||||||
/brainy-data
|
tmp/
|
||||||
/custom-data
|
temp/
|
||||||
/clean-history.sh
|
*.tmp
|
||||||
/bluesky-augmentation/node_modules/
|
|
||||||
/bluesky-augmentation/dist/
|
|
||||||
|
|
||||||
# Test files
|
# Planning and instruction files
|
||||||
/test-worker.js
|
plan.md
|
||||||
/test-node24-worker.js
|
|
||||||
/test-results.json
|
|
||||||
/tests/results/
|
|
||||||
/tests/results/*.json
|
|
||||||
|
|
||||||
# Generated files
|
# Package files
|
||||||
/encoded-image.html
|
*.tgz
|
||||||
/encoded-image.txt
|
|
||||||
/cli-package/dist/
|
|
||||||
/cli-package/node_modules/
|
|
||||||
/npm
|
|
||||||
/rollup
|
|
||||||
/soulcraft-brainy-*.tgz
|
|
||||||
/data/
|
|
||||||
/cli-package/soulcraft-brainy-cli-*.tgz
|
|
||||||
/web-service-package/node_modules/
|
|
||||||
|
|
||||||
# Temporary test files created by AI agents
|
# Private/confidential files
|
||||||
test*.js
|
PLAN.md
|
||||||
test*.ts
|
INTERNAL_NOTES.md
|
||||||
temp-test*.js
|
TODO_PRIVATE.md
|
||||||
temp-test*.ts
|
*.tar.gz
|
||||||
reproduction*.js
|
|
||||||
reproduction*.ts
|
# Strategy and planning documents (private)
|
||||||
debug*.js
|
.strategy/
|
||||||
debug*.ts
|
# Removed: PRODUCTION_*.md (now these should be public documentation)
|
||||||
/temp/
|
DISTRIBUTED_*.md
|
||||||
/temp-tests/
|
*_ASSESSMENT.md
|
||||||
/brainy-models-package/node_modules/
|
*_ANALYSIS.md
|
||||||
|
*_TRUTH*.md
|
||||||
|
|
||||||
|
# Models (downloaded at runtime)
|
||||||
|
models/
|
||||||
|
models-cache/
|
||||||
|
|
||||||
|
# But include bundled WASM model assets
|
||||||
|
!assets/models/
|
||||||
|
|
||||||
|
# Development planning files (not for commit)
|
||||||
|
PLAN.md
|
||||||
|
|
||||||
|
# Backup folders
|
||||||
|
backup-*
|
||||||
|
backup/
|
||||||
|
|
||||||
|
# Internal documentation
|
||||||
|
docs/internal/
|
||||||
|
|
||||||
|
# Cache files
|
||||||
|
*.cache
|
||||||
|
|
||||||
|
# Rust/Cargo build artifacts
|
||||||
|
src/embeddings/candle-wasm/target/
|
||||||
|
src/embeddings/candle-wasm/Cargo.lock
|
||||||
|
|
||||||
|
# Ignore the wasm-pack output dir's CONTENTS (note the `/*`, not `/`, so the
|
||||||
|
# re-includes below can take effect — git cannot re-include a file whose parent
|
||||||
|
# DIRECTORY is excluded). Keep the pre-built WASM committed: it ships in the npm
|
||||||
|
# package anyway, it lets consumers + CI build without a Rust/wasm-pack toolchain,
|
||||||
|
# and versioning it makes the shipped artifact reproducible (not "whatever the
|
||||||
|
# maintainer last built").
|
||||||
|
src/embeddings/wasm/pkg/*
|
||||||
|
!src/embeddings/wasm/pkg/*.wasm
|
||||||
|
!src/embeddings/wasm/pkg/*.js
|
||||||
|
!src/embeddings/wasm/pkg/*.d.ts
|
||||||
|
|
||||||
|
# Log files (redundant but explicit)
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Temporary files (redundant but explicit)
|
||||||
|
*.tmp
|
||||||
|
/.junie/guidelines.md
|
||||||
|
|
||||||
|
# Claude Code harness state
|
||||||
|
.claude/scheduled_tasks.lock
|
||||||
|
|
|
||||||
109
.npmignore
109
.npmignore
|
|
@ -1,41 +1,84 @@
|
||||||
# Exclude source maps
|
# Source files (not needed in package)
|
||||||
*.map
|
|
||||||
**/*.map
|
|
||||||
|
|
||||||
# Development files
|
|
||||||
node_modules/
|
|
||||||
src/
|
src/
|
||||||
tests/
|
tests/
|
||||||
examples/
|
|
||||||
.github/
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
cloud-wrapper/
|
|
||||||
scripts/
|
scripts/
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Model files (downloaded on first use, not bundled)
|
||||||
|
models/
|
||||||
|
models-cache/
|
||||||
|
|
||||||
|
# Development and backup files
|
||||||
|
backup-*
|
||||||
|
backup-*/
|
||||||
|
docs/backup*/
|
||||||
|
|
||||||
|
# Documentation (except essentials)
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
!LICENSE
|
||||||
|
!CHANGELOG.md
|
||||||
|
!MIGRATION.md
|
||||||
|
|
||||||
# Configuration files
|
# Configuration files
|
||||||
.eslintrc
|
.gitignore
|
||||||
.prettierrc
|
.npmignore
|
||||||
tsconfig*.json
|
tsconfig.json
|
||||||
rollup.config.js
|
vitest.config.ts
|
||||||
jest.config.js
|
vitest.config.mts
|
||||||
|
*.config.js
|
||||||
|
*.config.ts
|
||||||
|
.eslintrc*
|
||||||
|
.prettierrc*
|
||||||
|
|
||||||
# Build artifacts
|
# Test files
|
||||||
emocoverage/
|
test-*.js
|
||||||
.nyc_output/
|
test-*.ts
|
||||||
|
*.test.ts
|
||||||
|
*.test.js
|
||||||
|
*.spec.ts
|
||||||
|
*.spec.js
|
||||||
|
|
||||||
# Large files
|
# Temporary and log files
|
||||||
# Include the logo but exclude other PNGs
|
|
||||||
!brainy.png
|
|
||||||
*.png
|
|
||||||
encoded-image.*
|
|
||||||
README.demo.md
|
|
||||||
scalingStrategy.md
|
|
||||||
|
|
||||||
# Misc
|
|
||||||
.DS_Store
|
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
*.tmp
|
||||||
yarn-debug.log*
|
tmp/
|
||||||
yarn-error.log*
|
temp/
|
||||||
test-results.json
|
brainy-data/
|
||||||
|
|
||||||
|
# Git and CI files
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.gitlab-ci.yml
|
||||||
|
.travis.yml
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Private files
|
||||||
|
PLAN.md
|
||||||
|
CLAUDE.md
|
||||||
|
INTERNAL_NOTES.md
|
||||||
|
TODO_PRIVATE.md
|
||||||
|
*-ANALYSIS.md
|
||||||
|
*-PLAN.md
|
||||||
|
|
||||||
|
# Build artifacts not needed
|
||||||
|
*.tsbuildinfo
|
||||||
|
*.map
|
||||||
|
|
||||||
|
# Development environment
|
||||||
|
.env*
|
||||||
|
.nvm*
|
||||||
|
.node-version
|
||||||
|
|
||||||
|
# Keep dist/ for the compiled code
|
||||||
|
# Keep bin/ for the CLI
|
||||||
|
# Keep package.json, package-lock.json
|
||||||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
22
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"types": [
|
|
||||||
{"type": "feat", "section": "Added", "hidden": false},
|
|
||||||
{"type": "fix", "section": "Fixed", "hidden": false},
|
|
||||||
{"type": "chore", "section": "Changed", "hidden": false},
|
|
||||||
{"type": "docs", "section": "Documentation", "hidden": false},
|
|
||||||
{"type": "style", "section": "Changed", "hidden": true},
|
|
||||||
{"type": "refactor", "section": "Changed", "hidden": false},
|
|
||||||
{"type": "perf", "section": "Changed", "hidden": false},
|
|
||||||
{"type": "test", "section": "Tests", "hidden": true},
|
|
||||||
{"type": "build", "section": "Build System", "hidden": true},
|
|
||||||
{"type": "ci", "section": "Continuous Integration", "hidden": true}
|
|
||||||
],
|
|
||||||
"releaseCommitMessageFormat": "chore(release): {{currentTag}} [skip ci]",
|
|
||||||
"commitUrlFormat": "https://github.com/soulcraft-research/brainy/commit/{{hash}}",
|
|
||||||
"compareUrlFormat": "https://github.com/soulcraft-research/brainy/compare/{{previousTag}}...{{currentTag}}",
|
|
||||||
"issueUrlFormat": "https://github.com/soulcraft-research/brainy/issues/{{id}}",
|
|
||||||
"userUrlFormat": "https://github.com/{{user}}",
|
|
||||||
"skip": {
|
|
||||||
"tag": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4762
CHANGELOG.md
4762
CHANGELOG.md
File diff suppressed because it is too large
Load diff
208
CLAUDE.md
Normal file
208
CLAUDE.md
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
# Brainy - Claude Code Project Guide
|
||||||
|
|
||||||
|
This file provides guidance for Claude Code (and human contributors) when working on the Brainy codebase.
|
||||||
|
|
||||||
|
## Cross-Project Coordination
|
||||||
|
|
||||||
|
Handoff file: `/home/dpsifr/.strategy/PLATFORM-HANDOFF.md`
|
||||||
|
|
||||||
|
**At session START:** Read the handoff. Find rows where Owner = Brainy. Act on those first.
|
||||||
|
|
||||||
|
**At session END:** Mark completed actions ✅, delete rows you finished, delete threads with zero remaining actions. File must not grow. **If you shipped anything consumers need to know about, update `RELEASES.md` before closing.**
|
||||||
|
|
||||||
|
**Brainy's current open actions:** None. MIT open-source — no platform-specific actions.
|
||||||
|
|
||||||
|
**Current version:** run `npm view @soulcraft/brainy version` (never trust a hardcoded number here — this line went stale for months); consumer-facing changes tracked in `RELEASES.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Brainy is a Universal Knowledge Protocol -- a Triple Intelligence database that combines vector similarity search, graph traversal, and metadata filtering into a single TypeScript library. Published as `@soulcraft/brainy` on npm under the MIT license.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install # Install dependencies
|
||||||
|
npm run build # Build the project
|
||||||
|
npm test # Run test suite (Vitest)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Full architecture reference: `.claude/skills/architecture.md`
|
||||||
|
|
||||||
|
### Core Systems
|
||||||
|
- **Storage** (`src/storage/`): Pluggable storage backends via StorageAdapter interface (`src/coreTypes.ts`)
|
||||||
|
- **Vector Search** (`src/hnsw/`): HNSW approximate nearest neighbor search
|
||||||
|
- **Graph Engine** (`src/graph/`): Relationship traversal with adjacency index and pathfinding
|
||||||
|
- **Metadata Index** (`src/utils/metadataIndex.ts`): O(1) exact match, O(log n) range queries
|
||||||
|
- **Triple Intelligence** (`src/triple/`): Unified query combining all three intelligence types
|
||||||
|
- **Aggregation Engine** (`src/aggregation/`): Write-time incremental SUM/COUNT/AVG/MIN/MAX with GROUP BY and time windows
|
||||||
|
- **Virtual Filesystem** (`src/vfs/`): Full VFS with semantic search
|
||||||
|
|
||||||
|
### Type System
|
||||||
|
- **NounType** (42 types): Entity classification -- Person, Concept, Collection, Document, Task, etc.
|
||||||
|
- **VerbType** (127 types): Relationship types -- Contains, RelatedTo, PartOf, Creates, DependsOn, etc.
|
||||||
|
- Defined in `src/types/graphTypes.ts`
|
||||||
|
|
||||||
|
## Code Standards
|
||||||
|
|
||||||
|
### TypeScript
|
||||||
|
- Strict mode enabled
|
||||||
|
- Target: ES2020, NodeNext module resolution
|
||||||
|
- All new code must be TypeScript
|
||||||
|
- Follow existing patterns -- read related code before writing
|
||||||
|
|
||||||
|
### Quality
|
||||||
|
- All code must compile without errors
|
||||||
|
- All code must have working tests that exercise real behavior
|
||||||
|
- No stub returns (`return {} as any`)
|
||||||
|
- No incomplete implementations with TODO comments
|
||||||
|
- If something can't be fully implemented, throw an explicit error rather than faking it
|
||||||
|
|
||||||
|
### Verification Before Code Changes
|
||||||
|
1. Check that interfaces and methods actually exist before using them
|
||||||
|
2. Check that type properties are in the type definitions
|
||||||
|
3. Run `npm test` -- tests must pass
|
||||||
|
4. Run `npm run build` -- build must succeed
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Framework: Vitest
|
||||||
|
- Tests in `tests/` (unit, integration, benchmarks, comprehensive)
|
||||||
|
- Use in-memory storage for speed where possible
|
||||||
|
- Tests must exercise real behavior, not mock it
|
||||||
|
- Benchmarks are in `tests/benchmarks/` (not tests/performance/)
|
||||||
|
|
||||||
|
## Commit Conventions
|
||||||
|
|
||||||
|
Use [Conventional Commits](https://www.conventionalcommits.org/):
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: add new feature (minor version bump)
|
||||||
|
fix: resolve bug (patch version bump)
|
||||||
|
docs: update documentation (patch version bump)
|
||||||
|
perf: improve performance (patch version bump)
|
||||||
|
refactor: restructure code (patch version bump)
|
||||||
|
test: add/update tests (patch version bump)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** Never use `BREAKING CHANGE` in commit messages. Major version bumps are manual decisions only (`npm run release:major`).
|
||||||
|
|
||||||
|
## Docs Pipeline — soulcraft.com/docs
|
||||||
|
|
||||||
|
Docs in `docs/**/*.md` are published with the npm package (included in `files`) and synced to soulcraft.com/docs on every portal deploy. Frontmatter controls what appears publicly.
|
||||||
|
|
||||||
|
### Docs check triggers
|
||||||
|
|
||||||
|
Run the docs check whenever the user says ANY of:
|
||||||
|
- "commit, publish, release" / "release" / "publish"
|
||||||
|
- "update the docs" / "make sure docs are accurate" / "check the docs"
|
||||||
|
- "review docs" / "clean up docs"
|
||||||
|
|
||||||
|
### Pre-release docs check (MANDATORY before every release)
|
||||||
|
|
||||||
|
When the user says "commit, publish, release" or any variation, **before committing**:
|
||||||
|
|
||||||
|
1. **Scan all files changed in this session** (and any recently added `docs/*.md` files)
|
||||||
|
2. For each changed/new doc, decide: is this useful to external users?
|
||||||
|
- **Yes** → ensure it has complete frontmatter (add or update it)
|
||||||
|
- **No** (internal, migration, dev-only) → ensure it has no frontmatter or `public: false`
|
||||||
|
3. For docs that already have frontmatter, verify:
|
||||||
|
- `description` still matches the actual content
|
||||||
|
- `next` links still exist and are still the right follow-up pages
|
||||||
|
- `title` matches the doc's h1
|
||||||
|
4. Include frontmatter changes in the commit
|
||||||
|
|
||||||
|
### Frontmatter format
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
title: Human-readable title
|
||||||
|
slug: category/page-name # URL: soulcraft.com/docs/category/page-name
|
||||||
|
public: true # false or absent = not published
|
||||||
|
category: getting-started | concepts | guides | api
|
||||||
|
template: guide | concept | api # controls layout on soulcraft.com
|
||||||
|
order: 1 # sidebar position within category (lower = first)
|
||||||
|
description: One sentence. What this doc covers and why it matters.
|
||||||
|
next: # "Next steps" links shown at bottom of page
|
||||||
|
- category/other-slug
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### Category guide
|
||||||
|
|
||||||
|
| category | use for |
|
||||||
|
|----------|---------|
|
||||||
|
| `getting-started` | installation, quick start, first steps |
|
||||||
|
| `concepts` | how the system works, mental models |
|
||||||
|
| `guides` | how to do specific things, recipes |
|
||||||
|
| `api` | method reference, signatures, parameters |
|
||||||
|
|
||||||
|
### What stays internal (no frontmatter / `public: false`)
|
||||||
|
|
||||||
|
- Release guides, developer learning paths
|
||||||
|
- Migration guides for old versions (v3→v4, v5.11)
|
||||||
|
- Architecture analysis docs (clustering algorithms, etc.)
|
||||||
|
- Anything in `docs/internal/`
|
||||||
|
- Deployment/ops/cost docs (cloud-run, kubernetes, cost-optimization)
|
||||||
|
|
||||||
|
## Release Process
|
||||||
|
|
||||||
|
Fully automated via `scripts/release.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run release:dry # Preview (no changes)
|
||||||
|
npm run release:patch # Bug fixes
|
||||||
|
npm run release:minor # New features
|
||||||
|
npm run release:major # Breaking changes (rare, manual decision)
|
||||||
|
```
|
||||||
|
|
||||||
|
The script: verifies clean git state, builds, tests, bumps version, updates CHANGELOG.md, commits, tags, pushes, publishes to npm, and creates a GitHub release.
|
||||||
|
|
||||||
|
After a successful release, remind the user:
|
||||||
|
> "Published. Deploy portal to pick up the new docs → go to the portal project and deploy."
|
||||||
|
|
||||||
|
Do NOT deploy portal from here. Portal is always deployed separately from within the portal project.
|
||||||
|
|
||||||
|
## Closed-Source Product Names — HARD RULE
|
||||||
|
|
||||||
|
Brainy is the only Soulcraft open-source project. Nothing in this repo — code, JSDoc, tests,
|
||||||
|
docs, RELEASES.md, CHANGELOG.md, commit messages — may reference closed-source Soulcraft
|
||||||
|
products by name (Workshop, Venue, Memory, Muse, Hall, Forge, Academy, Pulse, Heart,
|
||||||
|
Collective, SDK) or by their specific class/method names (`BookingDraftService`,
|
||||||
|
`getDemandHeatmap`, `systemKind`, etc.).
|
||||||
|
|
||||||
|
When recording a consumer-reported bug, regression scenario, or release note:
|
||||||
|
- Refer to "a consumer", "a downstream application", "a production deployment", or "an
|
||||||
|
internal report" — never name the product.
|
||||||
|
- All doc examples must use generic domain values (`'employee'`, `'customer'`, `'invoice'`,
|
||||||
|
`'milestone'`, `OrderService`, `/orders/...`), not product-specific schemas.
|
||||||
|
- Internal session artifacts (`.strategy/`, `~/.claude/plans/`, handoff files outside the
|
||||||
|
repo) MAY name products — those are not public.
|
||||||
|
|
||||||
|
If you catch yourself typing a product name into a tracked file, stop and rephrase.
|
||||||
|
|
||||||
|
## Performance Claims
|
||||||
|
|
||||||
|
When documenting performance characteristics:
|
||||||
|
- **MEASURED**: Cite the test file and line number
|
||||||
|
- **PROJECTED**: Clearly label as extrapolated from tested scale
|
||||||
|
- Never claim a performance figure without context or evidence
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
When a bug persists through 2+ fix attempts, switch to systematic debugging:
|
||||||
|
1. Add comprehensive logging at every step
|
||||||
|
2. Test with production-like data
|
||||||
|
3. Trace the complete execution path
|
||||||
|
4. Check both library code and consumer code
|
||||||
|
5. Verify with actual test execution before declaring fixed
|
||||||
|
|
||||||
|
## Key Paths
|
||||||
|
|
||||||
|
- Main class: `src/brainy.ts`
|
||||||
|
- Public API: `src/index.ts` (38+ exports)
|
||||||
|
- Storage interface: `src/coreTypes.ts`
|
||||||
|
- Type definitions: `src/types/`
|
||||||
|
- Strategy/planning docs: `.strategy/` (gitignored, not public)
|
||||||
1
CNAME
1
CNAME
|
|
@ -1 +0,0 @@
|
||||||
demo.soulcraft.com
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned with this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the project maintainers responsible for enforcement at
|
|
||||||
conduct@soulcraft.com.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All project maintainers are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Project maintainers will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from project maintainers, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.0, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
||||||
enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
|
||||||
https://www.contributor-covenant.org/translations.
|
|
||||||
125
CONTRIBUTING.md
125
CONTRIBUTING.md
|
|
@ -1,97 +1,66 @@
|
||||||
<div align="center">
|
|
||||||
<img src="./brainy.png" alt="Brainy Logo" width="200"/>
|
|
||||||
|
|
||||||
# Contributing to Brainy
|
# Contributing to Brainy
|
||||||
|
|
||||||
</div>
|
Brainy is MIT-licensed and genuinely open to outside contributions. This page
|
||||||
|
is the honest, current path — please don't rely on older instructions you
|
||||||
|
may find elsewhere in the repo's history.
|
||||||
|
|
||||||
Thank you for your interest in contributing to Brainy! This document provides guidelines and instructions for
|
## Where the project lives
|
||||||
contributing to the project.
|
|
||||||
|
|
||||||
We welcome contributions of all kinds, including bug fixes, feature additions, documentation improvements, and more.
|
The source of truth is a self-hosted forge: **source.soulcraft.com/soulcraft/brainy**.
|
||||||
By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md).
|
It's anonymously readable and cloneable — no account needed to browse, clone,
|
||||||
|
or build.
|
||||||
|
|
||||||
## Commit Message Guidelines
|
## How to contribute
|
||||||
|
|
||||||
When contributing to this project, please write clear and descriptive commit messages that explain the purpose of your
|
**Found a bug, or have an idea?** Email **brainy@soulcraft.com**. No account,
|
||||||
changes. Good commit messages help maintainers understand your contributions and make the review process smoother.
|
no ceremony — you'll get a receipt, and it goes to a human.
|
||||||
|
|
||||||
### Best Practices
|
**Want to send a patch?** Two ways, both first-class:
|
||||||
|
|
||||||
- Keep the first line concise (ideally under 50 characters)
|
- **Email a patch.** Run `git format-patch` against your change and email the
|
||||||
- Use the imperative mood ("Add feature" not "Added feature")
|
output to **brainy@soulcraft.com**. This is a genuinely supported path, not
|
||||||
- Reference issues and pull requests where appropriate
|
a fallback — plenty of good contributions arrive this way.
|
||||||
- When necessary, provide more detailed explanations in the commit body
|
- **Open a pull request on the forge.** Request an account at
|
||||||
|
**source.soulcraft.com** (registration is request-with-approval, so allow
|
||||||
|
a little lag), clone, push a branch, and open a PR there. Maintainers
|
||||||
|
review and land it.
|
||||||
|
|
||||||
### Examples
|
Either way, for anything beyond a small fix, opening an issue first (email is
|
||||||
|
fine) to talk through the approach saves everyone rework.
|
||||||
|
|
||||||
```
|
## Development setup
|
||||||
Add vector normalization option
|
|
||||||
Fix distance calculation in HNSW search
|
|
||||||
Update API documentation
|
|
||||||
Add support for IndexedDB storage
|
|
||||||
Change API parameter order
|
|
||||||
Simplify vector comparison logic
|
|
||||||
Update build dependencies
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pull Request Process
|
|
||||||
|
|
||||||
1. Ensure your code follows the project's coding standards
|
|
||||||
2. Update the documentation if necessary
|
|
||||||
3. Use conventional commit messages in your PR
|
|
||||||
4. Your PR will be reviewed by maintainers and merged if approved
|
|
||||||
|
|
||||||
## Development Setup
|
|
||||||
|
|
||||||
1. Fork and clone the repository
|
|
||||||
2. Install dependencies: `npm install`
|
|
||||||
3. Build the project: `npm run build`
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
This project uses ESLint and Prettier for code formatting and style checking. The configuration can be found in the `package.json` file. Please ensure your code follows these standards:
|
|
||||||
|
|
||||||
- Use 2 spaces for indentation
|
|
||||||
- Use single quotes for strings
|
|
||||||
- No semicolons
|
|
||||||
- Trailing commas are not used
|
|
||||||
- Maximum line length is 80 characters
|
|
||||||
|
|
||||||
You can check your code style by running:
|
|
||||||
```bash
|
|
||||||
npm run check:style
|
|
||||||
```
|
|
||||||
|
|
||||||
This will run all code style checks, including a specific check for semicolons.
|
|
||||||
|
|
||||||
You can also run individual checks:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint # Run ESLint to check for code issues
|
git clone https://source.soulcraft.com/soulcraft/brainy.git
|
||||||
npm run lint:fix # Automatically fix linting issues
|
cd brainy
|
||||||
npm run format # Format your code with Prettier
|
npm install
|
||||||
npm run check-format # Check if your code is properly formatted
|
npm run build
|
||||||
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Branching Strategy
|
Tests run on [Vitest](https://vitest.dev/). `npm test` runs the unit suite;
|
||||||
|
see `package.json` for `test:integration`, `test:coverage`, and friends.
|
||||||
|
|
||||||
- `main` - The main branch contains the latest stable release
|
## Standards
|
||||||
- `develop` - The development branch contains the latest development changes
|
|
||||||
- Feature branches - Create a branch from `develop` for your feature or fix
|
|
||||||
|
|
||||||
When working on a new feature or fix:
|
- **Strict TypeScript.** No `any` escape hatches to dodge the type checker.
|
||||||
1. Create a new branch from `develop` with a descriptive name (e.g., `feature/add-vector-normalization` or `fix/distance-calculation`)
|
- **Tests exercise real behavior.** No mocking away the thing you're supposed
|
||||||
2. Make your changes in that branch
|
to be testing.
|
||||||
3. Submit a pull request to merge your branch into `develop`
|
- **No stubs, no TODO-code.** If something can't be finished, say so and
|
||||||
|
leave it out — don't merge a placeholder.
|
||||||
|
- **JSDoc on every exported function, class, and type.**
|
||||||
|
- **[Conventional Commits](https://www.conventionalcommits.org/).** `feat:`,
|
||||||
|
`fix:`, `docs:`, `perf:`, `refactor:`, `test:`, `chore:`. Never
|
||||||
|
`BREAKING CHANGE` in a commit message — major version bumps are a separate,
|
||||||
|
deliberate decision.
|
||||||
|
- **Performance claims are measured or labeled projected.** If a PR or its
|
||||||
|
description states a number, cite the benchmark that produced it (see
|
||||||
|
[docs/performance-envelopes.md](docs/performance-envelopes.md) for the
|
||||||
|
pattern). Don't state an estimate as if it were measured.
|
||||||
|
|
||||||
## Issue Reporting
|
## License
|
||||||
|
|
||||||
Before submitting a new issue, please search existing issues to avoid duplicates.
|
Brainy is [MIT licensed](LICENSE). Contributions are accepted under the same
|
||||||
|
license — there's no CLA to sign.
|
||||||
|
|
||||||
- For bugs, use the bug report template
|
Thank you for considering a contribution.
|
||||||
- For feature requests, use the feature request template
|
|
||||||
- Be as detailed as possible in your description
|
|
||||||
- Include code examples, error messages, and screenshots if applicable
|
|
||||||
|
|
||||||
Thank you for contributing to Brainy!
|
|
||||||
|
|
|
||||||
72
Dockerfile
Normal file
72
Dockerfile
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Multi-stage Dockerfile for Brainy
|
||||||
|
# Optimized for production deployment with minimal image size
|
||||||
|
|
||||||
|
# Stage 1: Build stage
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
# Install build dependencies
|
||||||
|
RUN apk add --no-cache python3 make g++
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install all dependencies (including dev dependencies for building)
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the TypeScript code
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Remove dev dependencies and only keep production ones
|
||||||
|
RUN npm prune --production
|
||||||
|
|
||||||
|
# Stage 2: Production stage
|
||||||
|
FROM node:22-alpine
|
||||||
|
|
||||||
|
# Install production dependencies only
|
||||||
|
RUN apk add --no-cache tini
|
||||||
|
|
||||||
|
# Create non-root user for security
|
||||||
|
RUN addgroup -g 1001 -S nodejs && \
|
||||||
|
adduser -S nodejs -u 1001
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Copy built application from builder stage
|
||||||
|
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
|
||||||
|
|
||||||
|
# Copy necessary static files
|
||||||
|
COPY --chown=nodejs:nodejs README.md LICENSE ./
|
||||||
|
|
||||||
|
# Create data directory for file-based storage
|
||||||
|
RUN mkdir -p /app/data && chown -R nodejs:nodejs /app/data
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER nodejs
|
||||||
|
|
||||||
|
# Expose default port (can be overridden)
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Set environment variables for production
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV BRAINY_STORAGE_PATH=/app/data
|
||||||
|
|
||||||
|
# Health check endpoint
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD node -e "require('http').get('http://localhost:3000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
|
||||||
|
|
||||||
|
# Use tini to handle signals properly
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
|
|
||||||
|
# Default command (can be overridden)
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 Soulcraft Research
|
Copyright (c) 2024 Brainy Data Contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
2967
RELEASES.md
Normal file
2967
RELEASES.md
Normal file
File diff suppressed because it is too large
Load diff
36
SECURITY.md
Normal file
36
SECURITY.md
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
Email **security@soulcraft.com**. That's the one door for security reports
|
||||||
|
across the company, and it works the same way for Brainy: every report is
|
||||||
|
read by a human, you'll get a private receipt, and we'll work with you on
|
||||||
|
coordinated disclosure — please don't open a public issue for anything
|
||||||
|
that isn't already public.
|
||||||
|
|
||||||
|
Include what you'd want if you were on the other end: affected version,
|
||||||
|
how to reproduce, and what you think the impact is. If you have a patch or
|
||||||
|
a suggested fix, send it along — it's welcome but not required.
|
||||||
|
|
||||||
|
There is no bounty program today. We're saying that plainly so you know
|
||||||
|
what to expect going in.
|
||||||
|
|
||||||
|
## Response time
|
||||||
|
|
||||||
|
We respond as fast as truth allows. That means: no fixed SLA, no promise of
|
||||||
|
a reply within a specific number of hours — but a real report from a real
|
||||||
|
person gets read promptly and taken seriously. If you haven't heard anything
|
||||||
|
in a reasonable stretch, a follow-up email is completely fine.
|
||||||
|
|
||||||
|
## Supported versions
|
||||||
|
|
||||||
|
The latest `8.x` minor release line receives security fixes. If you're
|
||||||
|
running an older major version, please upgrade before reporting — we can't
|
||||||
|
commit to backporting fixes to unsupported lines.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This policy covers the `@soulcraft/brainy` package itself — the code in
|
||||||
|
this repository. If you're evaluating a deployment that also uses
|
||||||
|
`@soulcraft/cor`, report issues in that package the same way, to the same
|
||||||
|
address; we'll route internally.
|
||||||
24
assets/models/all-MiniLM-L6-v2/config.json
Normal file
24
assets/models/all-MiniLM-L6-v2/config.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"_name_or_path": "nreimers/MiniLM-L6-H384-uncased",
|
||||||
|
"architectures": [
|
||||||
|
"BertModel"
|
||||||
|
],
|
||||||
|
"attention_probs_dropout_prob": 0.1,
|
||||||
|
"gradient_checkpointing": false,
|
||||||
|
"hidden_act": "gelu",
|
||||||
|
"hidden_dropout_prob": 0.1,
|
||||||
|
"hidden_size": 384,
|
||||||
|
"initializer_range": 0.02,
|
||||||
|
"intermediate_size": 1536,
|
||||||
|
"layer_norm_eps": 1e-12,
|
||||||
|
"max_position_embeddings": 512,
|
||||||
|
"model_type": "bert",
|
||||||
|
"num_attention_heads": 12,
|
||||||
|
"num_hidden_layers": 6,
|
||||||
|
"pad_token_id": 0,
|
||||||
|
"position_embedding_type": "absolute",
|
||||||
|
"transformers_version": "4.8.2",
|
||||||
|
"type_vocab_size": 2,
|
||||||
|
"use_cache": true,
|
||||||
|
"vocab_size": 30522
|
||||||
|
}
|
||||||
BIN
assets/models/all-MiniLM-L6-v2/model.safetensors
Normal file
BIN
assets/models/all-MiniLM-L6-v2/model.safetensors
Normal file
Binary file not shown.
1
assets/models/all-MiniLM-L6-v2/tokenizer.json
Normal file
1
assets/models/all-MiniLM-L6-v2/tokenizer.json
Normal file
File diff suppressed because one or more lines are too long
564
bin/brainy-interactive.js
Normal file
564
bin/brainy-interactive.js
Normal file
|
|
@ -0,0 +1,564 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brainy Interactive Mode
|
||||||
|
*
|
||||||
|
* Professional, guided CLI experience for beginners
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { program } from 'commander'
|
||||||
|
import { Brainy } from '../dist/index.js'
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import inquirer from 'inquirer'
|
||||||
|
import ora from 'ora'
|
||||||
|
import Table from 'cli-table3'
|
||||||
|
import boxen from 'boxen'
|
||||||
|
|
||||||
|
// Professional color scheme
|
||||||
|
const colors = {
|
||||||
|
primary: chalk.hex('#3A5F4A'), // Teal (from logo)
|
||||||
|
success: chalk.hex('#2D4A3A'), // Deep teal
|
||||||
|
info: chalk.hex('#4A6B5A'), // Medium teal
|
||||||
|
warning: chalk.hex('#D67441'), // Orange (from logo)
|
||||||
|
error: chalk.hex('#B85C35'), // Deep orange
|
||||||
|
brain: chalk.hex('#D67441'), // Brain orange
|
||||||
|
cream: chalk.hex('#F5E6A3'), // Cream background
|
||||||
|
dim: chalk.dim,
|
||||||
|
bold: chalk.bold,
|
||||||
|
cyan: chalk.cyan,
|
||||||
|
green: chalk.green,
|
||||||
|
yellow: chalk.yellow,
|
||||||
|
red: chalk.red
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icons for consistent visual language
|
||||||
|
const icons = {
|
||||||
|
brain: '🧠',
|
||||||
|
search: '🔍',
|
||||||
|
add: '➕',
|
||||||
|
delete: '🗑️',
|
||||||
|
update: '🔄',
|
||||||
|
import: '📥',
|
||||||
|
export: '📤',
|
||||||
|
connect: '🔗',
|
||||||
|
question: '❓',
|
||||||
|
success: '✅',
|
||||||
|
error: '❌',
|
||||||
|
warning: '⚠️',
|
||||||
|
info: 'ℹ️',
|
||||||
|
sparkle: '✨',
|
||||||
|
rocket: '🚀',
|
||||||
|
thinking: '🤔',
|
||||||
|
chat: '💬',
|
||||||
|
stats: '📊',
|
||||||
|
config: '⚙️',
|
||||||
|
cloud: '☁️'
|
||||||
|
}
|
||||||
|
|
||||||
|
let brainyInstance = null
|
||||||
|
|
||||||
|
async function getBrainy() {
|
||||||
|
if (!brainyInstance) {
|
||||||
|
const spinner = ora('Initializing Brainy...').start()
|
||||||
|
try {
|
||||||
|
brainyInstance = new Brainy()
|
||||||
|
await brainyInstance.init()
|
||||||
|
spinner.succeed('Brainy initialized')
|
||||||
|
} catch (error) {
|
||||||
|
spinner.fail('Failed to initialize Brainy')
|
||||||
|
console.error(colors.error(error.message))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return brainyInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Professional welcome screen
|
||||||
|
*/
|
||||||
|
function showWelcome() {
|
||||||
|
console.clear()
|
||||||
|
|
||||||
|
const welcomeBox = boxen(
|
||||||
|
colors.primary(`${icons.brain} BRAINY - Neural Intelligence System\n`) +
|
||||||
|
colors.dim('\nYour AI-Powered Second Brain\n') +
|
||||||
|
colors.info('Version 1.6.0'),
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
margin: 1,
|
||||||
|
borderStyle: 'round',
|
||||||
|
borderColor: 'cyan',
|
||||||
|
textAlignment: 'center'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(welcomeBox)
|
||||||
|
console.log()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main interactive menu
|
||||||
|
*/
|
||||||
|
async function mainMenu() {
|
||||||
|
const { action } = await inquirer.prompt([{
|
||||||
|
type: 'list',
|
||||||
|
name: 'action',
|
||||||
|
message: colors.cyan('What would you like to do?'),
|
||||||
|
choices: [
|
||||||
|
new inquirer.Separator(colors.dim('── Core Operations ──')),
|
||||||
|
{ name: `${icons.add} Add data to your brain`, value: 'add' },
|
||||||
|
{ name: `${icons.search} Search your knowledge`, value: 'search' },
|
||||||
|
{ name: `${icons.chat} Chat with your data`, value: 'chat' },
|
||||||
|
{ name: `${icons.update} Update existing data`, value: 'update' },
|
||||||
|
{ name: `${icons.delete} Delete data`, value: 'delete' },
|
||||||
|
|
||||||
|
new inquirer.Separator(colors.dim('── Advanced Features ──')),
|
||||||
|
{ name: `${icons.connect} Create relationships`, value: 'relate' },
|
||||||
|
{ name: `${icons.import} Import from file/URL`, value: 'import' },
|
||||||
|
{ name: `${icons.export} Export your brain`, value: 'export' },
|
||||||
|
{ name: `${icons.brain} Neural operations`, value: 'neural' },
|
||||||
|
|
||||||
|
new inquirer.Separator(colors.dim('── System ──')),
|
||||||
|
{ name: `${icons.stats} View statistics`, value: 'stats' },
|
||||||
|
{ name: `${icons.config} Configuration`, value: 'config' },
|
||||||
|
{ name: `${icons.cloud} Brain Cloud`, value: 'cloud' },
|
||||||
|
{ name: `${icons.info} Help & Documentation`, value: 'help' },
|
||||||
|
|
||||||
|
new inquirer.Separator(),
|
||||||
|
{ name: 'Exit', value: 'exit' }
|
||||||
|
],
|
||||||
|
pageSize: 20
|
||||||
|
}])
|
||||||
|
|
||||||
|
return action
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Neural operations submenu
|
||||||
|
*/
|
||||||
|
async function neuralMenu() {
|
||||||
|
const { operation } = await inquirer.prompt([{
|
||||||
|
type: 'list',
|
||||||
|
name: 'operation',
|
||||||
|
message: colors.cyan('Select neural operation:'),
|
||||||
|
choices: [
|
||||||
|
{ name: `${icons.brain} Calculate similarity`, value: 'similar' },
|
||||||
|
{ name: `${icons.search} Find clusters`, value: 'cluster' },
|
||||||
|
{ name: `${icons.connect} Find related items`, value: 'related' },
|
||||||
|
{ name: `${icons.thinking} Build hierarchy`, value: 'hierarchy' },
|
||||||
|
{ name: `${icons.rocket} Find semantic path`, value: 'path' },
|
||||||
|
{ name: `${icons.warning} Detect outliers`, value: 'outliers' },
|
||||||
|
{ name: `${icons.sparkle} Generate visualization`, value: 'visualize' },
|
||||||
|
new inquirer.Separator(),
|
||||||
|
{ name: '← Back to main menu', value: 'back' }
|
||||||
|
]
|
||||||
|
}])
|
||||||
|
|
||||||
|
return operation
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute commands with beautiful feedback
|
||||||
|
*/
|
||||||
|
async function executeCommand(command) {
|
||||||
|
const brain = await getBrainy()
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 'add':
|
||||||
|
await interactiveAdd(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'search':
|
||||||
|
await interactiveSearch(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'chat':
|
||||||
|
await interactiveChat(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'update':
|
||||||
|
await interactiveUpdate(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
await interactiveDelete(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'relate':
|
||||||
|
await interactiveRelate(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'import':
|
||||||
|
await interactiveImport(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'export':
|
||||||
|
await interactiveExport(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'neural':
|
||||||
|
const neuralOp = await neuralMenu()
|
||||||
|
if (neuralOp !== 'back') {
|
||||||
|
await executeNeuralOperation(neuralOp, brain)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'stats':
|
||||||
|
await showStatistics(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'config':
|
||||||
|
await interactiveConfig(brain)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'cloud':
|
||||||
|
await showCloudInfo()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'help':
|
||||||
|
await showHelp()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactive add with rich prompts
|
||||||
|
*/
|
||||||
|
async function interactiveAdd(brain) {
|
||||||
|
console.log(colors.primary(`\n${icons.add} Add Data\n`))
|
||||||
|
|
||||||
|
const { inputType } = await inquirer.prompt([{
|
||||||
|
type: 'list',
|
||||||
|
name: 'inputType',
|
||||||
|
message: 'How would you like to add data?',
|
||||||
|
choices: [
|
||||||
|
{ name: 'Type or paste text', value: 'text' },
|
||||||
|
{ name: 'Multi-line editor', value: 'editor' },
|
||||||
|
{ name: 'JSON object', value: 'json' },
|
||||||
|
{ name: 'Import from clipboard', value: 'clipboard' }
|
||||||
|
]
|
||||||
|
}])
|
||||||
|
|
||||||
|
let data = ''
|
||||||
|
|
||||||
|
switch (inputType) {
|
||||||
|
case 'text':
|
||||||
|
const { text } = await inquirer.prompt([{
|
||||||
|
type: 'input',
|
||||||
|
name: 'text',
|
||||||
|
message: 'Enter your data:',
|
||||||
|
validate: input => input.trim() ? true : 'Please enter some data'
|
||||||
|
}])
|
||||||
|
data = text
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'editor':
|
||||||
|
const { editorText } = await inquirer.prompt([{
|
||||||
|
type: 'editor',
|
||||||
|
name: 'editorText',
|
||||||
|
message: 'Enter your data (opens editor):',
|
||||||
|
postfix: '.md'
|
||||||
|
}])
|
||||||
|
data = editorText
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'json':
|
||||||
|
const { jsonText } = await inquirer.prompt([{
|
||||||
|
type: 'editor',
|
||||||
|
name: 'jsonText',
|
||||||
|
message: 'Enter JSON data:',
|
||||||
|
postfix: '.json',
|
||||||
|
default: '{\n \n}',
|
||||||
|
validate: input => {
|
||||||
|
try {
|
||||||
|
JSON.parse(input)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return `Invalid JSON: ${e.message}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
data = jsonText
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional metadata
|
||||||
|
const { addMetadata } = await inquirer.prompt([{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'addMetadata',
|
||||||
|
message: 'Would you like to add metadata?',
|
||||||
|
default: false
|
||||||
|
}])
|
||||||
|
|
||||||
|
let metadata = {}
|
||||||
|
if (addMetadata) {
|
||||||
|
const { metadataJson } = await inquirer.prompt([{
|
||||||
|
type: 'editor',
|
||||||
|
name: 'metadataJson',
|
||||||
|
message: 'Enter metadata (JSON):',
|
||||||
|
postfix: '.json',
|
||||||
|
default: '{\n "type": "",\n "tags": [],\n "category": ""\n}',
|
||||||
|
validate: input => {
|
||||||
|
try {
|
||||||
|
JSON.parse(input)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return `Invalid JSON: ${e.message}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
metadata = JSON.parse(metadataJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
const spinner = ora('Adding data...').start()
|
||||||
|
try {
|
||||||
|
const id = await brain.add(data, metadata)
|
||||||
|
spinner.succeed(`Added successfully with ID: ${id}`)
|
||||||
|
|
||||||
|
// Show summary
|
||||||
|
console.log(boxen(
|
||||||
|
colors.success(`${icons.success} Data added successfully!\n\n`) +
|
||||||
|
colors.info(`ID: ${id}\n`) +
|
||||||
|
colors.dim(`Size: ${data.length} characters\n`) +
|
||||||
|
(Object.keys(metadata).length > 0 ? colors.dim(`Metadata: ${Object.keys(metadata).join(', ')}`) : ''),
|
||||||
|
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
|
||||||
|
))
|
||||||
|
} catch (error) {
|
||||||
|
spinner.fail('Failed to add data')
|
||||||
|
console.error(colors.error(error.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactive search with filters
|
||||||
|
*/
|
||||||
|
async function interactiveSearch(brain) {
|
||||||
|
console.log(colors.primary(`\n${icons.search} Search\n`))
|
||||||
|
|
||||||
|
const { query } = await inquirer.prompt([{
|
||||||
|
type: 'input',
|
||||||
|
name: 'query',
|
||||||
|
message: 'Enter search query:',
|
||||||
|
validate: input => input.trim() ? true : 'Please enter a search query'
|
||||||
|
}])
|
||||||
|
|
||||||
|
// Advanced options
|
||||||
|
const { useFilters } = await inquirer.prompt([{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'useFilters',
|
||||||
|
message: 'Apply filters?',
|
||||||
|
default: false
|
||||||
|
}])
|
||||||
|
|
||||||
|
let searchOptions = { limit: 10 }
|
||||||
|
|
||||||
|
if (useFilters) {
|
||||||
|
const { limit, threshold } = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
name: 'limit',
|
||||||
|
message: 'Maximum results:',
|
||||||
|
default: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
name: 'threshold',
|
||||||
|
message: 'Similarity threshold (0-1):',
|
||||||
|
default: 0.5,
|
||||||
|
validate: input => input >= 0 && input <= 1 ? true : 'Must be between 0 and 1'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
searchOptions.limit = limit
|
||||||
|
searchOptions.threshold = threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
const spinner = ora('Searching...').start()
|
||||||
|
try {
|
||||||
|
const results = await brain.search(query, searchOptions.limit, searchOptions)
|
||||||
|
spinner.succeed(`Found ${results.length} results`)
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
console.log(colors.warning('No results found'))
|
||||||
|
} else {
|
||||||
|
// Display results in a table
|
||||||
|
const table = new Table({
|
||||||
|
head: [colors.cyan('ID'), colors.cyan('Content'), colors.cyan('Score')],
|
||||||
|
style: { head: [], border: [] },
|
||||||
|
colWidths: [20, 50, 10]
|
||||||
|
})
|
||||||
|
|
||||||
|
results.forEach(result => {
|
||||||
|
const content = result.content || result.id
|
||||||
|
const truncated = content.length > 47 ? content.substring(0, 47) + '...' : content
|
||||||
|
const score = result.score ? `${(result.score * 100).toFixed(1)}%` : 'N/A'
|
||||||
|
|
||||||
|
table.push([
|
||||||
|
result.id.substring(0, 18),
|
||||||
|
truncated,
|
||||||
|
colors.green(score)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(table.toString())
|
||||||
|
|
||||||
|
// Ask if user wants to see full details
|
||||||
|
const { viewDetails } = await inquirer.prompt([{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'viewDetails',
|
||||||
|
message: 'View full details of a result?',
|
||||||
|
default: false
|
||||||
|
}])
|
||||||
|
|
||||||
|
if (viewDetails) {
|
||||||
|
const { selectedId } = await inquirer.prompt([{
|
||||||
|
type: 'list',
|
||||||
|
name: 'selectedId',
|
||||||
|
message: 'Select result:',
|
||||||
|
choices: results.map(r => ({
|
||||||
|
name: `${r.id} - ${r.content?.substring(0, 50)}...`,
|
||||||
|
value: r.id
|
||||||
|
}))
|
||||||
|
}])
|
||||||
|
|
||||||
|
const selected = results.find(r => r.id === selectedId)
|
||||||
|
console.log(boxen(
|
||||||
|
colors.cyan('Full Details\n\n') +
|
||||||
|
colors.info(`ID: ${selected.id}\n\n`) +
|
||||||
|
`Content:\n${selected.content}\n\n` +
|
||||||
|
(selected.metadata ? `Metadata:\n${JSON.stringify(selected.metadata, null, 2)}` : ''),
|
||||||
|
{ padding: 1, borderColor: 'cyan', borderStyle: 'round' }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
spinner.fail('Search failed')
|
||||||
|
console.error(colors.error(error.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show statistics with beautiful formatting
|
||||||
|
*/
|
||||||
|
async function showStatistics(brain) {
|
||||||
|
const spinner = ora('Gathering statistics...').start()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = brain.getStats()
|
||||||
|
spinner.succeed('Statistics loaded')
|
||||||
|
|
||||||
|
console.log(boxen(
|
||||||
|
colors.primary(`${icons.stats} Database Statistics\n\n`) +
|
||||||
|
colors.info(`Total Items: ${colors.bold(stats.total || 0)}\n`) +
|
||||||
|
colors.info(`Nouns: ${stats.nounCount || 0}\n`) +
|
||||||
|
colors.info(`Relationships: ${stats.verbCount || 0}\n`) +
|
||||||
|
colors.info(`Metadata Records: ${stats.metadataCount || 0}\n\n`) +
|
||||||
|
colors.dim(`Memory Usage: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1)} MB`),
|
||||||
|
{
|
||||||
|
padding: 1,
|
||||||
|
borderColor: 'blue',
|
||||||
|
borderStyle: 'round',
|
||||||
|
textAlignment: 'left'
|
||||||
|
}
|
||||||
|
))
|
||||||
|
} catch (error) {
|
||||||
|
spinner.fail('Failed to get statistics')
|
||||||
|
console.error(colors.error(error.message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show help with examples
|
||||||
|
*/
|
||||||
|
async function showHelp() {
|
||||||
|
console.log(boxen(
|
||||||
|
colors.primary(`${icons.info} Brainy Help\n\n`) +
|
||||||
|
colors.cyan('Common Commands:\n') +
|
||||||
|
colors.dim(`
|
||||||
|
brainy add "text" Add data
|
||||||
|
brainy search "query" Search your brain
|
||||||
|
brainy chat Interactive AI chat
|
||||||
|
brainy status View statistics
|
||||||
|
brainy help This help menu
|
||||||
|
|
||||||
|
`) +
|
||||||
|
colors.cyan('Interactive Mode:\n') +
|
||||||
|
colors.dim(`
|
||||||
|
brainy Start interactive mode
|
||||||
|
brainy -i Alternative interactive mode
|
||||||
|
|
||||||
|
`) +
|
||||||
|
colors.cyan('Advanced Features:\n') +
|
||||||
|
colors.dim(`
|
||||||
|
brainy similar a b Calculate similarity
|
||||||
|
brainy cluster Find semantic clusters
|
||||||
|
brainy export Export your data
|
||||||
|
brainy cloud Brain Cloud features
|
||||||
|
`),
|
||||||
|
{ padding: 1, borderColor: 'yellow', borderStyle: 'round' }
|
||||||
|
))
|
||||||
|
|
||||||
|
const { learnMore } = await inquirer.prompt([{
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'learnMore',
|
||||||
|
message: 'View detailed documentation?',
|
||||||
|
default: false
|
||||||
|
}])
|
||||||
|
|
||||||
|
if (learnMore) {
|
||||||
|
console.log(colors.info('\nDocumentation: https://github.com/TimeSoul/brainy'))
|
||||||
|
console.log(colors.info('Enterprise features: Coming in future releases'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main interactive loop
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
showWelcome()
|
||||||
|
|
||||||
|
let running = true
|
||||||
|
while (running) {
|
||||||
|
const action = await mainMenu()
|
||||||
|
|
||||||
|
if (action === 'exit') {
|
||||||
|
console.log(colors.success(`\n${icons.success} Thank you for using Brainy!\n`))
|
||||||
|
running = false
|
||||||
|
} else {
|
||||||
|
await executeCommand(action)
|
||||||
|
|
||||||
|
// Pause before returning to menu
|
||||||
|
await inquirer.prompt([{
|
||||||
|
type: 'input',
|
||||||
|
name: 'continue',
|
||||||
|
message: colors.dim('\nPress Enter to continue...'),
|
||||||
|
prefix: ''
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle errors gracefully
|
||||||
|
process.on('unhandledRejection', (error) => {
|
||||||
|
console.error(colors.error(`\n${icons.error} Unexpected error:`))
|
||||||
|
console.error(colors.red(error.message))
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle Ctrl+C gracefully
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
console.log(colors.info(`\n\n${icons.info} Exiting Brainy...`))
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Run if called directly
|
||||||
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
|
main().catch(error => {
|
||||||
|
console.error(colors.error('Fatal error:'), error)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export { main as startInteractiveMode }
|
||||||
82
bin/brainy-minimal.js
Executable file
82
bin/brainy-minimal.js
Executable file
|
|
@ -0,0 +1,82 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brainy CLI - Minimal Version (Conversation Commands Only)
|
||||||
|
*
|
||||||
|
* This is a temporary minimal CLI that only includes working conversation commands
|
||||||
|
* Full CLI will be restored in version 3.20.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Command } from 'commander'
|
||||||
|
import { readFileSync } from 'fs'
|
||||||
|
import { dirname, join } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'))
|
||||||
|
|
||||||
|
const program = new Command()
|
||||||
|
|
||||||
|
program
|
||||||
|
.name('brainy')
|
||||||
|
.description('🧠 Brainy - Infinite Agent Memory')
|
||||||
|
.version(packageJson.version)
|
||||||
|
|
||||||
|
// Dynamically load conversation command
|
||||||
|
const conversationCommand = await import('../dist/cli/commands/conversation.js').then(m => m.default)
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('conversation')
|
||||||
|
.alias('conv')
|
||||||
|
.description('💬 Infinite agent memory and context management')
|
||||||
|
.addCommand(
|
||||||
|
new Command('setup')
|
||||||
|
.description('Set up MCP server for Claude Code integration')
|
||||||
|
.action(async () => {
|
||||||
|
await conversationCommand.handler({ action: 'setup', _: [] })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addCommand(
|
||||||
|
new Command('remove')
|
||||||
|
.description('Remove MCP server and clean up')
|
||||||
|
.action(async () => {
|
||||||
|
await conversationCommand.handler({ action: 'remove', _: [] })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addCommand(
|
||||||
|
new Command('search')
|
||||||
|
.description('Search messages across conversations')
|
||||||
|
.requiredOption('-q, --query <query>', 'Search query')
|
||||||
|
.option('-c, --conversation-id <id>', 'Filter by conversation')
|
||||||
|
.option('-r, --role <role>', 'Filter by role')
|
||||||
|
.option('-l, --limit <number>', 'Maximum results', '10')
|
||||||
|
.action(async (options) => {
|
||||||
|
await conversationCommand.handler({ action: 'search', ...options, _: [] })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addCommand(
|
||||||
|
new Command('context')
|
||||||
|
.description('Get relevant context for a query')
|
||||||
|
.requiredOption('-q, --query <query>', 'Context query')
|
||||||
|
.option('-l, --limit <number>', 'Maximum messages', '10')
|
||||||
|
.action(async (options) => {
|
||||||
|
await conversationCommand.handler({ action: 'context', ...options, _: [] })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addCommand(
|
||||||
|
new Command('thread')
|
||||||
|
.description('Get full conversation thread')
|
||||||
|
.requiredOption('-c, --conversation-id <id>', 'Conversation ID')
|
||||||
|
.action(async (options) => {
|
||||||
|
await conversationCommand.handler({ action: 'thread', ...options, _: [] })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.addCommand(
|
||||||
|
new Command('stats')
|
||||||
|
.description('Show conversation statistics')
|
||||||
|
.action(async () => {
|
||||||
|
await conversationCommand.handler({ action: 'stats', _: [] })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
program.parse(process.argv)
|
||||||
18
bin/brainy-ts.js
Normal file
18
bin/brainy-ts.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modern TypeScript CLI Runner
|
||||||
|
*
|
||||||
|
* This is the entry point after npm install @soulcraft/brainy
|
||||||
|
* It runs the compiled TypeScript CLI code
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Use the compiled TypeScript CLI
|
||||||
|
import('../dist/cli/index.js').catch(err => {
|
||||||
|
// Fallback to legacy CLI if new one isn't built yet
|
||||||
|
import('./brainy.js').catch(() => {
|
||||||
|
console.error('Error: CLI not properly built. Please reinstall the package.')
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
14
bin/brainy.js
Executable file
14
bin/brainy.js
Executable file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brainy CLI Wrapper
|
||||||
|
*
|
||||||
|
* Imports the compiled TypeScript CLI from dist/cli/index.js
|
||||||
|
* This ensures TypeScript features work correctly
|
||||||
|
*/
|
||||||
|
|
||||||
|
import('../dist/cli/index.js').catch((error) => {
|
||||||
|
console.error('Failed to load Brainy CLI:', error.message)
|
||||||
|
console.error('Make sure you have built the project: npm run build')
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"tagPrefix": "brainy-models-v",
|
|
||||||
"commitMessageFormat": "chore(brainy-models): release {{currentTag}}",
|
|
||||||
"types": [
|
|
||||||
{
|
|
||||||
"type": "feat",
|
|
||||||
"section": "Features"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "fix",
|
|
||||||
"section": "Bug Fixes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "chore",
|
|
||||||
"hidden": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "docs",
|
|
||||||
"section": "Documentation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "style",
|
|
||||||
"hidden": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "refactor",
|
|
||||||
"section": "Code Refactoring"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "perf",
|
|
||||||
"section": "Performance Improvements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "test",
|
|
||||||
"hidden": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
||||||
|
|
||||||
## [0.7.0](https://github.com/soulcraft-research/brainy/compare/brainy-models-v0.6.0...brainy-models-v0.7.0) (2025-08-02)
|
|
||||||
|
|
||||||
## [0.6.0](https://github.com/soulcraft-research/brainy/compare/brainy-models-v0.5.0...brainy-models-v0.6.0) (2025-08-01)
|
|
||||||
|
|
||||||
## 0.5.0 (2025-08-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **core, tests:** add standalone getStatistics function and improve storage configuration ([e5a9ede](https://github.com/soulcraft-research/brainy/commit/e5a9edea1b292e2b87d1ad043bb917f01f6366d9))
|
|
||||||
* **core:** enhance addVerb functionality with auto-creation of missing nouns ([a0c4d48](https://github.com/soulcraft-research/brainy/commit/a0c4d48b4aa0b8e236c18f1b7afdc2e54801a142))
|
|
||||||
* **demo, docs:** introduce threading test demos for browser and fallback, enhance threading documentation ([de627c5](https://github.com/soulcraft-research/brainy/commit/de627c5dfaff57a0abba6a4dc3b68bd52cb150e1))
|
|
||||||
* **demo/CNAME:** add CNAME files for domain configuration ([a681ab7](https://github.com/soulcraft-research/brainy/commit/a681ab7cdd08d318111977cecd2ae7ef7262a3da))
|
|
||||||
* **docs:** add WebSocket augmentation examples in README ([d7ff1b2](https://github.com/soulcraft-research/brainy/commit/d7ff1b2053779265521894d351a950f7903a5e64))
|
|
||||||
* **docs:** update README to highlight new consolidated storage structure ([fbfcaeb](https://github.com/soulcraft-research/brainy/commit/fbfcaeb8d097c58a3127e68e9a7c06421cf5f468))
|
|
||||||
* enhance `cli.ts` with updated typings and improved search interface ([4a23c97](https://github.com/soulcraft-research/brainy/commit/4a23c97d2a5f33a2d34f622014cf03e8350f8781))
|
|
||||||
* **README:** add GPU acceleration and detailed performance optimizations ([da1fe27](https://github.com/soulcraft-research/brainy/commit/da1fe27e25851233a2a49eaaeec1dac9f9882d59))
|
|
||||||
* reformat imports and exports for consistency and readability ([7de62f4](https://github.com/soulcraft-research/brainy/commit/7de62f4bbcdc91c88bb22e39e64c624219df8be4))
|
|
||||||
* **scripts, project:** add comprehensive code style enforcement script and update style-related workflows ([5052bbc](https://github.com/soulcraft-research/brainy/commit/5052bbc0b713f2294b3e15de43e696b575320ae0))
|
|
||||||
* **src/brainyData, src/utils:** enhance embedding efficiency with batch processing and initialize safeguards ([bba9a0c](https://github.com/soulcraft-research/brainy/commit/bba9a0c219c308f82821e46c44c49ef560ca2e39))
|
|
||||||
* **src/hnsw:** add GPU acceleration for distance calculations and improve fallback handling ([a2ad5fa](https://github.com/soulcraft-research/brainy/commit/a2ad5fabdfe8e5de545c2a07cef2f08a667ebf5e))
|
|
||||||
* **src/utils:** add GPU acceleration and improve threading for embeddings and distance calculations ([6a8d044](https://github.com/soulcraft-research/brainy/commit/6a8d044970b36976fc3ed5712d61bdbf774f7716))
|
|
||||||
* **storage:** implement base, file system, and memory storage adapters ([8c5f17b](https://github.com/soulcraft-research/brainy/commit/8c5f17b1d96a84f996ad62ec4b1e1e56893dfcbc))
|
|
||||||
* **tests:** add robust mock implementations and expand test coverage for S3 and OPFS storage ([f01a355](https://github.com/soulcraft-research/brainy/commit/f01a35598788b53a0294ecf9b91e47d166363846))
|
|
||||||
* **tests:** replace old test scripts with updated test suite for storage and reporting ([68680db](https://github.com/soulcraft-research/brainy/commit/68680db2c660b34d5b4e4ee86d163f7723d328a4))
|
|
||||||
* **types:** extend FileSystemHandle and optimize imports for consistency ([aff1483](https://github.com/soulcraft-research/brainy/commit/aff1483d4d84406039e301fa594889f99e19c9db))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* handle optional `loggingConfig` in `getDefaultEmbeddingFunction` initialization ([d42596a](https://github.com/soulcraft-research/brainy/commit/d42596ad4730943cea60081ca71fea6ad2babd37))
|
|
||||||
* **package.json:** update TensorFlow dependencies for optimized backend usage ([e000661](https://github.com/soulcraft-research/brainy/commit/e00066119ebeb336e4564d38b4ef67d9bfbefeb6))
|
|
||||||
* prevent duplication of `ROOT_DIR` in file system storage initialization ([d495b95](https://github.com/soulcraft-research/brainy/commit/d495b95af836c124685f5cff8a59ed74a82b3dca))
|
|
||||||
* **README, src/utils:** bump version to 0.9.11 ([fe4de2f](https://github.com/soulcraft-research/brainy/commit/fe4de2f7a00b3f1ec1dd0edd2deb434d510096c2))
|
|
||||||
* **README:** correct formatting in custom domain configuration steps ([bba846a](https://github.com/soulcraft-research/brainy/commit/bba846ae230e86235b1321851eb06f298ca09170))
|
|
||||||
* **src/augmentationPipeline:** remove redundant semicolons and enforce consistent formatting ([47ba4f4](https://github.com/soulcraft-research/brainy/commit/47ba4f4093a8a3ecc8f7fa28638322d7ee4ae740))
|
|
||||||
* **src/augmentationPipeline:** remove unnecessary whitespace for formatting consistency ([99a8cbf](https://github.com/soulcraft-research/brainy/commit/99a8cbfe2c036a29076f1ebaf4e92b320a0cf427))
|
|
||||||
* **src/augmentations:** enforce consistent formatting and improve code readability ([cacd179](https://github.com/soulcraft-research/brainy/commit/cacd1790dc6b29096187dac53309b83d89b2242d))
|
|
||||||
* **src/brainyData:** enforce consistent formatting and improve code readability ([e884c58](https://github.com/soulcraft-research/brainy/commit/e884c5831003c7e0cae8260cd0d3d8cf72e7ef07))
|
|
||||||
* **src/brainyData:** enforce consistent formatting and improve fallback mechanisms ([cabe0a3](https://github.com/soulcraft-research/brainy/commit/cabe0a3a08e4bfd5fddfe19a7dfb0f382d347103))
|
|
||||||
* **src/index:** enforce consistent formatting and adjust code structure ([95be233](https://github.com/soulcraft-research/brainy/commit/95be23362af2665f4d2fed2657599980381434f2))
|
|
||||||
* **src/storage:** enforce consistent formatting and improve code readability ([cbf025d](https://github.com/soulcraft-research/brainy/commit/cbf025dffb0ab5e8af211d7daadc057b5771d567))
|
|
||||||
* **src/utils:** enforce consistent formatting and enhance worker script initialization ([bd123c4](https://github.com/soulcraft-research/brainy/commit/bd123c4bb91ddb6d30ee5ef240e1872affb5f795))
|
|
||||||
* **src/utils:** enhance type definition and improve load function detection ([ef83af4](https://github.com/soulcraft-research/brainy/commit/ef83af4b556966949e9df24cdf524a49b1ccab29))
|
|
||||||
* **src/utils:** improve readability of `findUSELoadFunction` parameters ([f946328](https://github.com/soulcraft-research/brainy/commit/f9463288234ff9cf6220db8443423df37651dc72))
|
|
||||||
* **src/utils:** remove unused `sentenceEncoderModule` for cleanup ([e81979d](https://github.com/soulcraft-research/brainy/commit/e81979dc849d7c1cdb70eb02697d1ad76db31196))
|
|
||||||
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
* update Node.js version requirement to 23.0.0 in documentation ([920439f](https://github.com/soulcraft-research/brainy/commit/920439f611d1dba45e7bcb3915eea0df5507ac20))
|
|
||||||
|
|
||||||
## [0.4.0](https://github.com/soulcraft-research/brainy/compare/v0.1.0...v0.4.0) (2025-08-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* **release:** 0.2.0 [skip ci] ([c9ca141](https://github.com/soulcraft-research/brainy/commit/c9ca14146ba5376812823185e55fc8b38be3785c))
|
|
||||||
* **release:** 0.3.0 [skip ci] ([437360c](https://github.com/soulcraft-research/brainy/commit/437360c2570632204cf951001aa7a0228479255d))
|
|
||||||
|
|
||||||
## [0.3.0](https://github.com/soulcraft-research/brainy/compare/v0.1.0...v0.3.0) (2025-08-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
* **release:** 0.2.0 [skip ci] ([c9ca141](https://github.com/soulcraft-research/brainy/commit/c9ca14146ba5376812823185e55fc8b38be3785c))
|
|
||||||
|
|
||||||
## [0.2.0](https://github.com/soulcraft-research/brainy/compare/v0.1.0...v0.2.0) (2025-08-01)
|
|
||||||
|
|
||||||
## [0.1.0](https://github.com/soulcraft-research/brainy/compare/v0.33.0...v0.1.0) (2025-08-01)
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the project maintainers responsible for enforcement at
|
|
||||||
conduct@soulcraft.com.
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.0, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
# Contributing to @soulcraft/brainy-models
|
|
||||||
|
|
||||||
Thank you for your interest in contributing to the Brainy Models package! This package provides pre-bundled TensorFlow models for the Brainy vector database.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The `@soulcraft/brainy-models` package is part of the larger Brainy ecosystem. For general contribution guidelines, please refer to the main [Brainy Contributing Guide](https://github.com/soulcraft-research/brainy/blob/main/CONTRIBUTING.md).
|
|
||||||
|
|
||||||
## Package-Specific Guidelines
|
|
||||||
|
|
||||||
### Model Contributions
|
|
||||||
|
|
||||||
When contributing to the models package, please consider:
|
|
||||||
|
|
||||||
- **Model Quality**: Ensure models are properly tested and validated
|
|
||||||
- **Model Size**: Be mindful of package size impact (current package is ~25MB)
|
|
||||||
- **Compatibility**: Ensure models work with the target TensorFlow.js versions
|
|
||||||
- **Documentation**: Update README.md with new model information
|
|
||||||
|
|
||||||
### Development Setup
|
|
||||||
|
|
||||||
1. Fork and clone the main repository
|
|
||||||
2. Navigate to the models package: `cd brainy-models-package`
|
|
||||||
3. Install dependencies: `npm install`
|
|
||||||
4. Download models: `npm run download-models`
|
|
||||||
5. Build the package: `npm run build`
|
|
||||||
6. Run tests: `npm test`
|
|
||||||
|
|
||||||
### Testing Models
|
|
||||||
|
|
||||||
Before submitting changes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test model functionality
|
|
||||||
npm test
|
|
||||||
|
|
||||||
# Test model compression
|
|
||||||
npm run compress-models
|
|
||||||
|
|
||||||
# Verify package integrity
|
|
||||||
npm run pack
|
|
||||||
```
|
|
||||||
|
|
||||||
### Model Scripts
|
|
||||||
|
|
||||||
The package includes several utility scripts:
|
|
||||||
|
|
||||||
- `download-models` - Download the Universal Sentence Encoder model
|
|
||||||
- `compress-models` - Create optimized model variants
|
|
||||||
- `test` - Verify model functionality
|
|
||||||
|
|
||||||
### Commit Guidelines
|
|
||||||
|
|
||||||
Follow the same commit message conventions as the main Brainy project:
|
|
||||||
|
|
||||||
- Use conventional commit format
|
|
||||||
- Keep first line under 50 characters
|
|
||||||
- Use imperative mood ("Add model" not "Added model")
|
|
||||||
- Reference issues where appropriate
|
|
||||||
|
|
||||||
### Pull Request Process
|
|
||||||
|
|
||||||
1. Ensure your changes don't break existing functionality
|
|
||||||
2. Update documentation if you're adding new models or features
|
|
||||||
3. Test model loading and embedding generation
|
|
||||||
4. Verify package size impact is acceptable
|
|
||||||
5. Submit PR to the main Brainy repository
|
|
||||||
|
|
||||||
### Model Optimization
|
|
||||||
|
|
||||||
When working with models:
|
|
||||||
|
|
||||||
- **Float16**: For balanced performance and size
|
|
||||||
- **Int8**: For memory-constrained environments
|
|
||||||
- **Original**: For maximum accuracy
|
|
||||||
|
|
||||||
### File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
brainy-models-package/
|
|
||||||
├── models/ # Model files
|
|
||||||
├── src/ # TypeScript source
|
|
||||||
├── dist/ # Compiled output
|
|
||||||
├── scripts/ # Utility scripts
|
|
||||||
└── test/ # Test files
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
This project follows the same [Code of Conduct](CODE_OF_CONDUCT.md) as the main Brainy project.
|
|
||||||
|
|
||||||
## Questions and Support
|
|
||||||
|
|
||||||
For questions specific to the models package:
|
|
||||||
|
|
||||||
- [GitHub Issues](https://github.com/soulcraft-research/brainy/issues) - Use the `brainy-models` label
|
|
||||||
- [Main Documentation](https://github.com/soulcraft-research/brainy)
|
|
||||||
|
|
||||||
For general Brainy questions, refer to the main repository.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
By contributing to this project, you agree that your contributions will be licensed under the MIT License.
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2025 Soulcraft Research
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -1,366 +0,0 @@
|
||||||
# @soulcraft/brainy-models
|
|
||||||
|
|
||||||
Pre-bundled TensorFlow models for maximum reliability with Brainy vector database.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This package provides offline access to the Universal Sentence Encoder model, eliminating network dependencies and ensuring consistent performance. It's designed as an optional companion to the main `@soulcraft/brainy` package for applications requiring maximum reliability.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- 🔒 **Maximum Reliability**: Fully offline model loading with zero network dependencies
|
|
||||||
- 📦 **Pre-bundled Models**: Complete Universal Sentence Encoder model (~25MB) included
|
|
||||||
- 🗜️ **Model Compression**: Multiple optimized variants (float16, int8) for different use cases
|
|
||||||
- ⚡ **Performance Optimized**: Use case-specific optimizations for memory and speed
|
|
||||||
- 🛠️ **Easy Integration**: Drop-in replacement for online model loading
|
|
||||||
- 📊 **Comprehensive Metrics**: Detailed model information and performance statistics
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @soulcraft/brainy-models
|
|
||||||
```
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Node.js >= 18.0.0
|
|
||||||
- `@soulcraft/brainy` >= 0.33.0
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Basic Usage
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { BundledUniversalSentenceEncoder } from '@soulcraft/brainy-models'
|
|
||||||
|
|
||||||
// Create encoder instance
|
|
||||||
const encoder = new BundledUniversalSentenceEncoder({
|
|
||||||
verbose: true,
|
|
||||||
preferCompressed: false
|
|
||||||
})
|
|
||||||
|
|
||||||
// Load the bundled model
|
|
||||||
await encoder.load()
|
|
||||||
|
|
||||||
// Generate embeddings
|
|
||||||
const texts = ['Hello world', 'How are you?', 'Machine learning is amazing']
|
|
||||||
const embeddings = await encoder.embedToArrays(texts)
|
|
||||||
|
|
||||||
console.log(`Generated ${embeddings.length} embeddings of ${embeddings[0].length} dimensions`)
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
encoder.dispose()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Integration with Brainy
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import Brainy from '@soulcraft/brainy'
|
|
||||||
import { BundledUniversalSentenceEncoder } from '@soulcraft/brainy-models'
|
|
||||||
|
|
||||||
// Create bundled encoder
|
|
||||||
const bundledEncoder = new BundledUniversalSentenceEncoder({ verbose: true })
|
|
||||||
await bundledEncoder.load()
|
|
||||||
|
|
||||||
// Use with Brainy (custom integration)
|
|
||||||
const brainy = new Brainy({
|
|
||||||
// Configure Brainy to use the bundled encoder
|
|
||||||
customEmbedding: async (texts) => {
|
|
||||||
return await bundledEncoder.embedToArrays(texts)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using Compressed Models
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { BundledUniversalSentenceEncoder } from '@soulcraft/brainy-models'
|
|
||||||
|
|
||||||
// Use compressed model for memory-constrained environments
|
|
||||||
const encoder = new BundledUniversalSentenceEncoder({
|
|
||||||
preferCompressed: true,
|
|
||||||
verbose: true
|
|
||||||
})
|
|
||||||
|
|
||||||
await encoder.load()
|
|
||||||
|
|
||||||
// The encoder will automatically use the most appropriate compressed variant
|
|
||||||
const embeddings = await encoder.embedToArrays(['Sample text'])
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### BundledUniversalSentenceEncoder
|
|
||||||
|
|
||||||
Main class for loading and using bundled models.
|
|
||||||
|
|
||||||
#### Constructor
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
new BundledUniversalSentenceEncoder(options)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Options:**
|
|
||||||
- `verbose?: boolean` - Enable detailed logging (default: false)
|
|
||||||
- `preferCompressed?: boolean` - Prefer compressed model variants (default: false)
|
|
||||||
|
|
||||||
#### Methods
|
|
||||||
|
|
||||||
##### `load(): Promise<void>`
|
|
||||||
|
|
||||||
Load the bundled model from local files.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
await encoder.load()
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `embed(texts: string[]): Promise<tf.Tensor2D>`
|
|
||||||
|
|
||||||
Generate embeddings as TensorFlow tensors.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const embeddings = await encoder.embed(['Hello world'])
|
|
||||||
// Remember to dispose of tensors when done
|
|
||||||
embeddings.dispose()
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `embedToArrays(texts: string[]): Promise<number[][]>`
|
|
||||||
|
|
||||||
Generate embeddings as JavaScript arrays (automatically disposes tensors).
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const embeddings = await encoder.embedToArrays(['Hello world'])
|
|
||||||
console.log(embeddings[0].length) // 512
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `getMetadata(): ModelMetadata | null`
|
|
||||||
|
|
||||||
Get model metadata information.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const metadata = encoder.getMetadata()
|
|
||||||
console.log(metadata?.dimensions) // 512
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `isLoaded(): boolean`
|
|
||||||
|
|
||||||
Check if the model is loaded.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
if (encoder.isLoaded()) {
|
|
||||||
// Model is ready to use
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `getModelInfo(): { inputShape: number[], outputShape: number[] } | null`
|
|
||||||
|
|
||||||
Get model input/output shape information.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const info = encoder.getModelInfo()
|
|
||||||
console.log(info?.outputShape) // [-1, 512]
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `dispose(): void`
|
|
||||||
|
|
||||||
Clean up model resources.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
encoder.dispose()
|
|
||||||
```
|
|
||||||
|
|
||||||
### ModelCompressor
|
|
||||||
|
|
||||||
Utility class for model compression and optimization.
|
|
||||||
|
|
||||||
#### Static Methods
|
|
||||||
|
|
||||||
##### `quantizeModel(modelPath: string, outputPath: string, options?): Promise<void>`
|
|
||||||
|
|
||||||
Compress a model using quantization.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { ModelCompressor } from '@soulcraft/brainy-models'
|
|
||||||
|
|
||||||
await ModelCompressor.quantizeModel(
|
|
||||||
'/path/to/model.json',
|
|
||||||
'/path/to/compressed/model.json',
|
|
||||||
{ dtype: 'int8' }
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
##### `getModelSize(modelPath: string): Promise<ModelSizeInfo>`
|
|
||||||
|
|
||||||
Get detailed model size information.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const sizeInfo = await ModelCompressor.getModelSize('/path/to/model.json')
|
|
||||||
console.log(`Total size: ${sizeInfo.totalSize} bytes`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Utility Functions
|
|
||||||
|
|
||||||
#### `utils.checkModelsAvailable(): boolean`
|
|
||||||
|
|
||||||
Check if bundled models are available.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { utils } from '@soulcraft/brainy-models'
|
|
||||||
|
|
||||||
if (utils.checkModelsAvailable()) {
|
|
||||||
console.log('Models are ready to use')
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `utils.listAvailableModels(): string[]`
|
|
||||||
|
|
||||||
List available bundled models.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const models = utils.listAvailableModels()
|
|
||||||
console.log('Available models:', models)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Model Variants
|
|
||||||
|
|
||||||
The package includes multiple model variants optimized for different use cases:
|
|
||||||
|
|
||||||
### Original (Float32)
|
|
||||||
- **Size**: ~25MB
|
|
||||||
- **Use case**: Maximum accuracy
|
|
||||||
- **Memory**: High
|
|
||||||
- **Speed**: Fast
|
|
||||||
|
|
||||||
### Float16 Compressed
|
|
||||||
- **Size**: ~12-15MB
|
|
||||||
- **Use case**: Balanced performance
|
|
||||||
- **Memory**: Medium
|
|
||||||
- **Speed**: Fast
|
|
||||||
|
|
||||||
### Int8 Quantized
|
|
||||||
- **Size**: ~6-8MB
|
|
||||||
- **Use case**: Memory-constrained environments
|
|
||||||
- **Memory**: Low
|
|
||||||
- **Speed**: Medium
|
|
||||||
|
|
||||||
## Scripts
|
|
||||||
|
|
||||||
The package includes several utility scripts:
|
|
||||||
|
|
||||||
### Download Models
|
|
||||||
|
|
||||||
Download the complete Universal Sentence Encoder model:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run download-models
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compress Models
|
|
||||||
|
|
||||||
Create optimized model variants:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run compress-models
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Models
|
|
||||||
|
|
||||||
Verify model functionality:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Building the Package
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running Tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating a Release
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run pack
|
|
||||||
```
|
|
||||||
|
|
||||||
## Comparison with Online Loading
|
|
||||||
|
|
||||||
| Feature | Online Loading | Bundled Models |
|
|
||||||
|---------|----------------|----------------|
|
|
||||||
| **Reliability** | Network dependent | 100% offline |
|
|
||||||
| **First load time** | 30-60 seconds | < 1 second |
|
|
||||||
| **Subsequent loads** | Cached (~1 second) | < 1 second |
|
|
||||||
| **Package size** | ~3KB | ~25MB |
|
|
||||||
| **Network required** | Yes (first time) | No |
|
|
||||||
| **Offline support** | Limited | Complete |
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
### When to Use Bundled Models
|
|
||||||
|
|
||||||
- ✅ Production applications requiring maximum reliability
|
|
||||||
- ✅ Offline or air-gapped environments
|
|
||||||
- ✅ Applications with strict SLA requirements
|
|
||||||
- ✅ Edge computing and IoT devices
|
|
||||||
- ✅ Development environments with unreliable internet
|
|
||||||
|
|
||||||
### When to Use Online Loading
|
|
||||||
|
|
||||||
- ✅ Development and prototyping
|
|
||||||
- ✅ Applications where package size matters
|
|
||||||
- ✅ Environments with reliable internet connectivity
|
|
||||||
- ✅ Applications that rarely use embeddings
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Model Not Found Error
|
|
||||||
|
|
||||||
```
|
|
||||||
Error: Bundled model not found. Please run "npm run download-models"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Run the download script to fetch the model files:
|
|
||||||
```bash
|
|
||||||
cd node_modules/@soulcraft/brainy-models
|
|
||||||
npm run download-models
|
|
||||||
```
|
|
||||||
|
|
||||||
### Memory Issues
|
|
||||||
|
|
||||||
If you encounter memory issues, try using compressed models:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const encoder = new BundledUniversalSentenceEncoder({
|
|
||||||
preferCompressed: true
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance Optimization
|
|
||||||
|
|
||||||
For optimal performance:
|
|
||||||
|
|
||||||
1. **Memory-constrained**: Use int8 quantized models
|
|
||||||
2. **Speed-critical**: Use original float32 models
|
|
||||||
3. **Balanced**: Use float16 compressed models
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please see the main [Brainy repository](https://github.com/soulcraft-research/brainy) for contribution guidelines.
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
For issues and questions:
|
|
||||||
- [GitHub Issues](https://github.com/soulcraft-research/brainy/issues)
|
|
||||||
- [Documentation](https://github.com/soulcraft-research/brainy)
|
|
||||||
103
brainy-models-package/dist/index.d.ts
vendored
103
brainy-models-package/dist/index.d.ts
vendored
|
|
@ -1,103 +0,0 @@
|
||||||
/**
|
|
||||||
* @soulcraft/brainy-models
|
|
||||||
*
|
|
||||||
* Pre-bundled TensorFlow models for maximum reliability with Brainy vector database.
|
|
||||||
* This package provides offline access to the Universal Sentence Encoder model,
|
|
||||||
* eliminating network dependencies and ensuring consistent performance.
|
|
||||||
*/
|
|
||||||
import * as tf from '@tensorflow/tfjs';
|
|
||||||
export interface ModelMetadata {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
description: string;
|
|
||||||
dimensions: number;
|
|
||||||
downloadDate: string;
|
|
||||||
source: string;
|
|
||||||
approach: string;
|
|
||||||
modelUrl: string;
|
|
||||||
bundledLocally: boolean;
|
|
||||||
reliability: string;
|
|
||||||
}
|
|
||||||
export interface BundledModelOptions {
|
|
||||||
verbose?: boolean;
|
|
||||||
preferCompressed?: boolean;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Bundled Universal Sentence Encoder for offline use
|
|
||||||
*/
|
|
||||||
export declare class BundledUniversalSentenceEncoder {
|
|
||||||
private model;
|
|
||||||
private metadata;
|
|
||||||
private options;
|
|
||||||
constructor(options?: BundledModelOptions);
|
|
||||||
/**
|
|
||||||
* Load the bundled model from local files
|
|
||||||
*/
|
|
||||||
load(): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Generate embeddings for the given texts
|
|
||||||
*/
|
|
||||||
embed(texts: string[]): Promise<tf.Tensor2D>;
|
|
||||||
/**
|
|
||||||
* Generate embeddings and return as JavaScript arrays
|
|
||||||
*/
|
|
||||||
embedToArrays(texts: string[]): Promise<number[][]>;
|
|
||||||
/**
|
|
||||||
* Get model metadata
|
|
||||||
*/
|
|
||||||
getMetadata(): ModelMetadata | null;
|
|
||||||
/**
|
|
||||||
* Check if the model is loaded
|
|
||||||
*/
|
|
||||||
isLoaded(): boolean;
|
|
||||||
/**
|
|
||||||
* Get model information
|
|
||||||
*/
|
|
||||||
getModelInfo(): {
|
|
||||||
inputShape: number[];
|
|
||||||
outputShape: number[];
|
|
||||||
} | null;
|
|
||||||
/**
|
|
||||||
* Dispose of the model and free memory
|
|
||||||
*/
|
|
||||||
dispose(): void;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Model compression utilities
|
|
||||||
*/
|
|
||||||
export declare class ModelCompressor {
|
|
||||||
/**
|
|
||||||
* Compress model weights using quantization
|
|
||||||
* Note: TensorFlow.js doesn't currently support model quantization
|
|
||||||
*/
|
|
||||||
static quantizeModel(modelPath: string, outputPath: string, options?: {
|
|
||||||
dtype?: 'int8' | 'int16';
|
|
||||||
}): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Get model size information by reading files from disk
|
|
||||||
*/
|
|
||||||
static getModelSize(modelPath: string): Promise<{
|
|
||||||
totalSize: number;
|
|
||||||
weightsSize: number;
|
|
||||||
modelJsonSize: number;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Utility functions
|
|
||||||
*/
|
|
||||||
export declare const utils: {
|
|
||||||
/**
|
|
||||||
* Check if bundled models are available
|
|
||||||
*/
|
|
||||||
checkModelsAvailable(): boolean;
|
|
||||||
/**
|
|
||||||
* Get bundled models directory
|
|
||||||
*/
|
|
||||||
getModelsDirectory(): string;
|
|
||||||
/**
|
|
||||||
* List available bundled models
|
|
||||||
*/
|
|
||||||
listAvailableModels(): string[];
|
|
||||||
};
|
|
||||||
export default BundledUniversalSentenceEncoder;
|
|
||||||
//# sourceMappingURL=index.d.ts.map
|
|
||||||
1
brainy-models-package/dist/index.d.ts.map
vendored
1
brainy-models-package/dist/index.d.ts.map
vendored
|
|
@ -1 +0,0 @@
|
||||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAwBtC,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;GAEG;AACH,qBAAa,+BAA+B;IAC1C,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,OAAO,CAAqB;gBAExB,OAAO,GAAE,mBAAwB;IAQ7C;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwC3B;;OAEG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC;IAqBlD;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IAOzD;;OAEG;IACH,WAAW,IAAI,aAAa,GAAG,IAAI;IAInC;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,YAAY,IAAI;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI;IAWtE;;OAEG;IACH,OAAO,IAAI,IAAI;CAMhB;AAED;;GAEG;AACH,qBAAa,eAAe;IAC1B;;;OAGG;WACU,aAAa,CACxB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAO,GACzC,OAAO,CAAC,IAAI,CAAC;IAwBhB;;OAEG;WACU,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QACpD,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,aAAa,EAAE,MAAM,CAAA;KACtB,CAAC;CAuCH;AAED;;GAEG;AACH,eAAO,MAAM,KAAK;IAChB;;OAEG;4BACqB,OAAO;IAK/B;;OAEG;0BACmB,MAAM;IAI5B;;OAEG;2BACoB,MAAM,EAAE;CAUhC,CAAA;AAGD,eAAe,+BAA+B,CAAA"}
|
|
||||||
237
brainy-models-package/dist/index.js
vendored
237
brainy-models-package/dist/index.js
vendored
|
|
@ -1,237 +0,0 @@
|
||||||
/**
|
|
||||||
* @soulcraft/brainy-models
|
|
||||||
*
|
|
||||||
* Pre-bundled TensorFlow models for maximum reliability with Brainy vector database.
|
|
||||||
* This package provides offline access to the Universal Sentence Encoder model,
|
|
||||||
* eliminating network dependencies and ensuring consistent performance.
|
|
||||||
*/
|
|
||||||
import * as tf from '@tensorflow/tfjs';
|
|
||||||
import { readFileSync, existsSync } from 'fs';
|
|
||||||
import { join, dirname } from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
/**
|
|
||||||
* Helper function to safely extract error message from unknown error type
|
|
||||||
*/
|
|
||||||
function getErrorMessage(error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
return error.message;
|
|
||||||
}
|
|
||||||
if (typeof error === 'string') {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
return String(error);
|
|
||||||
}
|
|
||||||
// Get the package directory
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = dirname(__filename);
|
|
||||||
const PACKAGE_ROOT = join(__dirname, '..');
|
|
||||||
const MODELS_DIR = join(PACKAGE_ROOT, 'models');
|
|
||||||
/**
|
|
||||||
* Bundled Universal Sentence Encoder for offline use
|
|
||||||
*/
|
|
||||||
export class BundledUniversalSentenceEncoder {
|
|
||||||
model = null;
|
|
||||||
metadata = null;
|
|
||||||
options;
|
|
||||||
constructor(options = {}) {
|
|
||||||
this.options = {
|
|
||||||
verbose: false,
|
|
||||||
preferCompressed: false,
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Load the bundled model from local files
|
|
||||||
*/
|
|
||||||
async load() {
|
|
||||||
try {
|
|
||||||
const modelDir = join(MODELS_DIR, 'universal-sentence-encoder');
|
|
||||||
const modelPath = join(modelDir, 'model.json');
|
|
||||||
const metadataPath = join(modelDir, 'metadata.json');
|
|
||||||
if (!existsSync(modelPath)) {
|
|
||||||
throw new Error(`Bundled model not found at ${modelPath}. ` +
|
|
||||||
'Please run "npm run download-models" to download the model files.');
|
|
||||||
}
|
|
||||||
if (this.options.verbose) {
|
|
||||||
console.log('🔄 Loading bundled Universal Sentence Encoder model...');
|
|
||||||
}
|
|
||||||
// Load metadata
|
|
||||||
if (existsSync(metadataPath)) {
|
|
||||||
const metadataContent = readFileSync(metadataPath, 'utf8');
|
|
||||||
this.metadata = JSON.parse(metadataContent);
|
|
||||||
if (this.options.verbose) {
|
|
||||||
console.log(`📋 Model metadata:`, this.metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Load the model
|
|
||||||
this.model = await tf.loadGraphModel(`file://${modelPath}`);
|
|
||||||
if (this.options.verbose) {
|
|
||||||
console.log('✅ Bundled model loaded successfully');
|
|
||||||
console.log(`🔒 Reliability: Maximum (fully offline)`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
throw new Error(`Failed to load bundled model: ${getErrorMessage(error)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Generate embeddings for the given texts
|
|
||||||
*/
|
|
||||||
async embed(texts) {
|
|
||||||
if (!this.model) {
|
|
||||||
throw new Error('Model not loaded. Call load() first.');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Convert texts to tensor
|
|
||||||
const inputTensor = tf.tensor1d(texts, 'string');
|
|
||||||
// Run inference
|
|
||||||
const embeddings = this.model.predict(inputTensor);
|
|
||||||
// Clean up input tensor
|
|
||||||
inputTensor.dispose();
|
|
||||||
return embeddings;
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
throw new Error(`Failed to generate embeddings: ${getErrorMessage(error)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Generate embeddings and return as JavaScript arrays
|
|
||||||
*/
|
|
||||||
async embedToArrays(texts) {
|
|
||||||
const embeddings = await this.embed(texts);
|
|
||||||
const arrays = await embeddings.array();
|
|
||||||
embeddings.dispose();
|
|
||||||
return arrays;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get model metadata
|
|
||||||
*/
|
|
||||||
getMetadata() {
|
|
||||||
return this.metadata;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Check if the model is loaded
|
|
||||||
*/
|
|
||||||
isLoaded() {
|
|
||||||
return this.model !== null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get model information
|
|
||||||
*/
|
|
||||||
getModelInfo() {
|
|
||||||
if (!this.model) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
inputShape: this.model.inputs[0].shape || [],
|
|
||||||
outputShape: this.model.outputs[0].shape || []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Dispose of the model and free memory
|
|
||||||
*/
|
|
||||||
dispose() {
|
|
||||||
if (this.model) {
|
|
||||||
this.model.dispose();
|
|
||||||
this.model = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Model compression utilities
|
|
||||||
*/
|
|
||||||
export class ModelCompressor {
|
|
||||||
/**
|
|
||||||
* Compress model weights using quantization
|
|
||||||
* Note: TensorFlow.js doesn't currently support model quantization
|
|
||||||
*/
|
|
||||||
static async quantizeModel(modelPath, outputPath, options = {}) {
|
|
||||||
const { dtype = 'int8' } = options;
|
|
||||||
try {
|
|
||||||
console.log(`🔄 Loading model for quantization: ${modelPath}`);
|
|
||||||
const model = await tf.loadGraphModel(`file://${modelPath}`);
|
|
||||||
console.log(`🗜️ Quantizing model to ${dtype}...`);
|
|
||||||
// TensorFlow.js doesn't have built-in quantization or model serialization APIs yet
|
|
||||||
// This is a placeholder implementation that acknowledges the limitation
|
|
||||||
console.warn('⚠️ Model quantization is not yet supported in TensorFlow.js');
|
|
||||||
console.log(`📋 Model loaded successfully from: ${modelPath}`);
|
|
||||||
console.log(`📋 Target output path: ${outputPath}`);
|
|
||||||
console.log(`📋 Target dtype: ${dtype}`);
|
|
||||||
model.dispose();
|
|
||||||
throw new Error('Model quantization is not yet supported in TensorFlow.js. This feature requires server-side processing with TensorFlow Python.');
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
throw new Error(`Failed to compress model: ${getErrorMessage(error)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get model size information by reading files from disk
|
|
||||||
*/
|
|
||||||
static async getModelSize(modelPath) {
|
|
||||||
try {
|
|
||||||
// Load model to verify it's valid
|
|
||||||
const model = await tf.loadGraphModel(`file://${modelPath}`);
|
|
||||||
model.dispose();
|
|
||||||
// Get model.json size
|
|
||||||
const modelJsonSize = existsSync(modelPath) ? readFileSync(modelPath).length : 0;
|
|
||||||
// Calculate weights size by reading weight files
|
|
||||||
let weightsSize = 0;
|
|
||||||
const modelDir = dirname(modelPath);
|
|
||||||
// Read model.json to get weight file names
|
|
||||||
if (existsSync(modelPath)) {
|
|
||||||
const modelJson = JSON.parse(readFileSync(modelPath, 'utf8'));
|
|
||||||
if (modelJson.weightsManifest) {
|
|
||||||
for (const manifest of modelJson.weightsManifest) {
|
|
||||||
for (const path of manifest.paths) {
|
|
||||||
const weightFilePath = join(modelDir, path);
|
|
||||||
if (existsSync(weightFilePath)) {
|
|
||||||
weightsSize += readFileSync(weightFilePath).length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const totalSize = weightsSize + modelJsonSize;
|
|
||||||
return {
|
|
||||||
totalSize,
|
|
||||||
weightsSize,
|
|
||||||
modelJsonSize
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
throw new Error(`Failed to get model size: ${getErrorMessage(error)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Utility functions
|
|
||||||
*/
|
|
||||||
export const utils = {
|
|
||||||
/**
|
|
||||||
* Check if bundled models are available
|
|
||||||
*/
|
|
||||||
checkModelsAvailable() {
|
|
||||||
const modelPath = join(MODELS_DIR, 'universal-sentence-encoder', 'model.json');
|
|
||||||
return existsSync(modelPath);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get bundled models directory
|
|
||||||
*/
|
|
||||||
getModelsDirectory() {
|
|
||||||
return MODELS_DIR;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* List available bundled models
|
|
||||||
*/
|
|
||||||
listAvailableModels() {
|
|
||||||
const models = [];
|
|
||||||
const useModelPath = join(MODELS_DIR, 'universal-sentence-encoder', 'model.json');
|
|
||||||
if (existsSync(useModelPath)) {
|
|
||||||
models.push('universal-sentence-encoder');
|
|
||||||
}
|
|
||||||
return models;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Default export for convenience
|
|
||||||
export default BundledUniversalSentenceEncoder;
|
|
||||||
//# sourceMappingURL=index.js.map
|
|
||||||
1
brainy-models-package/dist/index.js.map
vendored
1
brainy-models-package/dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"name": "universal-sentence-encoder",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Complete Universal Sentence Encoder model bundled for offline use",
|
|
||||||
"dimensions": 512,
|
|
||||||
"downloadDate": "2025-08-01T23:20:17.338Z",
|
|
||||||
"source": "tensorflow-models/universal-sentence-encoder",
|
|
||||||
"approach": "full-bundle",
|
|
||||||
"modelUrl": "https://storage.googleapis.com/tfjs-models/savedmodel/universal_sentence_encoder",
|
|
||||||
"bundledLocally": true,
|
|
||||||
"reliability": "maximum"
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
5337
brainy-models-package/package-lock.json
generated
5337
brainy-models-package/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,78 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@soulcraft/brainy-models",
|
|
||||||
"version": "0.7.0",
|
|
||||||
"description": "Pre-bundled TensorFlow models for maximum reliability with Brainy vector database",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"module": "dist/index.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"type": "module",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"prebuild": "npm run download-models",
|
|
||||||
"build": "tsc",
|
|
||||||
"test": "node test/test-models.js",
|
|
||||||
"prepare": "npm run build",
|
|
||||||
"download-models": "node scripts/download-full-models.js",
|
|
||||||
"compress-models": "node scripts/compress-models.js",
|
|
||||||
"_pack": "npm pack",
|
|
||||||
"_release": "standard-version",
|
|
||||||
"_release:patch": "standard-version --release-as patch",
|
|
||||||
"_release:minor": "standard-version --release-as minor",
|
|
||||||
"_release:major": "standard-version --release-as major",
|
|
||||||
"_release:dry-run": "standard-version --dry-run",
|
|
||||||
"_github-release": "node scripts/create-github-release.js",
|
|
||||||
"_workflow": "node scripts/release-workflow.js",
|
|
||||||
"_workflow:patch": "node scripts/release-workflow.js patch",
|
|
||||||
"_workflow:minor": "node scripts/release-workflow.js minor",
|
|
||||||
"_workflow:major": "node scripts/release-workflow.js major",
|
|
||||||
"_workflow:dry-run": "npm run build && npm test && npm run _release:dry-run",
|
|
||||||
"_deploy": "npm run build && npm publish",
|
|
||||||
"release:minor": "npm run _release:minor"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"tensorflow",
|
|
||||||
"models",
|
|
||||||
"universal-sentence-encoder",
|
|
||||||
"embeddings",
|
|
||||||
"brainy",
|
|
||||||
"vector-database",
|
|
||||||
"offline",
|
|
||||||
"bundled"
|
|
||||||
],
|
|
||||||
"author": "David Snelling (david@soulcraft.com)",
|
|
||||||
"license": "MIT",
|
|
||||||
"private": false,
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/soulcraft-research/brainy",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/soulcraft-research/brainy/issues"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/soulcraft-research/brainy.git",
|
|
||||||
"directory": "brainy-models-package"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist/",
|
|
||||||
"models/",
|
|
||||||
"README.md",
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@tensorflow-models/universal-sentence-encoder": "^1.3.3",
|
|
||||||
"@tensorflow/tfjs": "^4.23.0-rc.0",
|
|
||||||
"@tensorflow/tfjs-node": "^4.23.0-rc.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^20.11.30",
|
|
||||||
"standard-version": "^9.5.0",
|
|
||||||
"typescript": "^5.4.5"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@soulcraft/brainy": ">=0.33.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reproduction script for the TensorFlow.js isNullOrUndefined error
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as tf from '@tensorflow/tfjs-node'
|
|
||||||
import * as use from '@tensorflow-models/universal-sentence-encoder'
|
|
||||||
|
|
||||||
console.log('🔍 Loading Universal Sentence Encoder model...')
|
|
||||||
|
|
||||||
try {
|
|
||||||
const model = await use.load()
|
|
||||||
console.log('✅ Model loaded successfully')
|
|
||||||
|
|
||||||
console.log('🧪 Testing model functionality...')
|
|
||||||
const testEmbedding = await model.embed(['Hello world'])
|
|
||||||
const testArray = await testEmbedding.array()
|
|
||||||
console.log(
|
|
||||||
`✅ Model test passed - embedding dimensions: ${testArray[0].length}`
|
|
||||||
)
|
|
||||||
testEmbedding.dispose()
|
|
||||||
model.dispose()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Error:', error)
|
|
||||||
console.error('Stack trace:', error.stack)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
@ -1,278 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/* eslint-env node */
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model Compression Script for @soulcraft/brainy-models
|
|
||||||
*
|
|
||||||
* This script implements model compression and optimization techniques
|
|
||||||
* to reduce model size while maintaining accuracy.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import * as tf from '@tensorflow/tfjs-node'
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
|
||||||
const __dirname = path.dirname(__filename)
|
|
||||||
|
|
||||||
const MODELS_DIR = path.join(__dirname, '..', 'models')
|
|
||||||
const USE_MODEL_DIR = path.join(MODELS_DIR, 'universal-sentence-encoder')
|
|
||||||
const COMPRESSED_DIR = path.join(USE_MODEL_DIR, 'compressed')
|
|
||||||
|
|
||||||
// Ensure compressed directory exists
|
|
||||||
if (!fs.existsSync(COMPRESSED_DIR)) {
|
|
||||||
fs.mkdirSync(COMPRESSED_DIR, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🗜️ Starting model compression for @soulcraft/brainy-models...')
|
|
||||||
console.log('This will create optimized versions of the bundled models.\n')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get file size in MB
|
|
||||||
*/
|
|
||||||
function getFileSizeMB(filePath) {
|
|
||||||
const stats = fs.statSync(filePath)
|
|
||||||
return (stats.size / 1024 / 1024).toFixed(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get directory size in MB
|
|
||||||
*/
|
|
||||||
function getDirectorySizeMB(dirPath) {
|
|
||||||
let totalSize = 0
|
|
||||||
const files = fs.readdirSync(dirPath)
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = path.join(dirPath, file)
|
|
||||||
const stats = fs.statSync(filePath)
|
|
||||||
if (stats.isFile()) {
|
|
||||||
totalSize += stats.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (totalSize / 1024 / 1024).toFixed(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compress model weights by reducing precision
|
|
||||||
*/
|
|
||||||
async function compressModelWeights(modelPath, outputPath, precision = 'float16') {
|
|
||||||
try {
|
|
||||||
console.log(`🔄 Loading model from: ${modelPath}`)
|
|
||||||
const model = await tf.loadGraphModel(`file://${modelPath}`)
|
|
||||||
|
|
||||||
console.log(`🗜️ Compressing weights to ${precision} precision...`)
|
|
||||||
|
|
||||||
// Get model artifacts
|
|
||||||
const artifacts = await model.serialize()
|
|
||||||
|
|
||||||
// Compress weight data
|
|
||||||
if (artifacts.weightData) {
|
|
||||||
const originalWeights = new Float32Array(artifacts.weightData)
|
|
||||||
let compressedWeights
|
|
||||||
|
|
||||||
if (precision === 'float16') {
|
|
||||||
// Simulate float16 by reducing precision
|
|
||||||
compressedWeights = new Float32Array(originalWeights.length)
|
|
||||||
for (let i = 0; i < originalWeights.length; i++) {
|
|
||||||
// Round to reduce precision (simulating float16)
|
|
||||||
compressedWeights[i] = Math.round(originalWeights[i] * 1000) / 1000
|
|
||||||
}
|
|
||||||
} else if (precision === 'int8') {
|
|
||||||
// Quantize to int8 range
|
|
||||||
const min = Math.min(...originalWeights)
|
|
||||||
const max = Math.max(...originalWeights)
|
|
||||||
const scale = (max - min) / 255
|
|
||||||
|
|
||||||
compressedWeights = new Float32Array(originalWeights.length)
|
|
||||||
for (let i = 0; i < originalWeights.length; i++) {
|
|
||||||
const quantized = Math.round((originalWeights[i] - min) / scale)
|
|
||||||
compressedWeights[i] = (quantized * scale) + min
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
artifacts.weightData = compressedWeights.buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update metadata to indicate compression
|
|
||||||
if (artifacts.userDefinedMetadata) {
|
|
||||||
artifacts.userDefinedMetadata.compressed = true
|
|
||||||
artifacts.userDefinedMetadata.compressionType = precision
|
|
||||||
artifacts.userDefinedMetadata.compressionDate = new Date().toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save compressed model
|
|
||||||
await tf.io.fileSystem(outputPath).save(artifacts)
|
|
||||||
|
|
||||||
console.log(`✅ Compressed model saved to: ${outputPath}`)
|
|
||||||
|
|
||||||
model.dispose()
|
|
||||||
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ Error compressing model: ${error.message}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create optimized model variants
|
|
||||||
*/
|
|
||||||
async function createOptimizedVariants() {
|
|
||||||
try {
|
|
||||||
const originalModelPath = path.join(USE_MODEL_DIR, 'model.json')
|
|
||||||
|
|
||||||
if (!fs.existsSync(originalModelPath)) {
|
|
||||||
console.error('❌ Original model not found. Please run "npm run download-models" first.')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('📊 Original model size:', getDirectorySizeMB(USE_MODEL_DIR), 'MB')
|
|
||||||
|
|
||||||
// Create float16 compressed version
|
|
||||||
const float16Path = path.join(COMPRESSED_DIR, 'float16')
|
|
||||||
if (!fs.existsSync(float16Path)) {
|
|
||||||
fs.mkdirSync(float16Path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n🗜️ Creating float16 compressed version...')
|
|
||||||
const float16Success = await compressModelWeights(
|
|
||||||
originalModelPath,
|
|
||||||
path.join(float16Path, 'model.json'),
|
|
||||||
'float16'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (float16Success) {
|
|
||||||
console.log('📊 Float16 model size:', getDirectorySizeMB(float16Path), 'MB')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create int8 quantized version
|
|
||||||
const int8Path = path.join(COMPRESSED_DIR, 'int8')
|
|
||||||
if (!fs.existsSync(int8Path)) {
|
|
||||||
fs.mkdirSync(int8Path, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n🗜️ Creating int8 quantized version...')
|
|
||||||
const int8Success = await compressModelWeights(
|
|
||||||
originalModelPath,
|
|
||||||
path.join(int8Path, 'model.json'),
|
|
||||||
'int8'
|
|
||||||
)
|
|
||||||
|
|
||||||
if (int8Success) {
|
|
||||||
console.log('📊 Int8 model size:', getDirectorySizeMB(int8Path), 'MB')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create compression summary
|
|
||||||
const compressionSummary = {
|
|
||||||
originalSize: getDirectorySizeMB(USE_MODEL_DIR),
|
|
||||||
variants: {
|
|
||||||
float16: {
|
|
||||||
available: float16Success,
|
|
||||||
size: float16Success ? getDirectorySizeMB(float16Path) : null,
|
|
||||||
compressionRatio: float16Success ?
|
|
||||||
(parseFloat(getDirectorySizeMB(USE_MODEL_DIR)) / parseFloat(getDirectorySizeMB(float16Path))).toFixed(2) : null
|
|
||||||
},
|
|
||||||
int8: {
|
|
||||||
available: int8Success,
|
|
||||||
size: int8Success ? getDirectorySizeMB(int8Path) : null,
|
|
||||||
compressionRatio: int8Success ?
|
|
||||||
(parseFloat(getDirectorySizeMB(USE_MODEL_DIR)) / parseFloat(getDirectorySizeMB(int8Path))).toFixed(2) : null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createdAt: new Date().toISOString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(COMPRESSED_DIR, 'compression-summary.json'),
|
|
||||||
JSON.stringify(compressionSummary, null, 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log('\n📋 Compression Summary:')
|
|
||||||
console.log(`Original: ${compressionSummary.originalSize} MB`)
|
|
||||||
if (float16Success) {
|
|
||||||
console.log(`Float16: ${compressionSummary.variants.float16.size} MB (${compressionSummary.variants.float16.compressionRatio}x smaller)`)
|
|
||||||
}
|
|
||||||
if (int8Success) {
|
|
||||||
console.log(`Int8: ${compressionSummary.variants.int8.size} MB (${compressionSummary.variants.int8.compressionRatio}x smaller)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\n✨ Model compression completed successfully!')
|
|
||||||
console.log('Compressed models are available for applications requiring smaller file sizes.')
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Error during compression:', error)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optimize model for specific use cases
|
|
||||||
*/
|
|
||||||
async function optimizeForUseCase(useCase = 'general') {
|
|
||||||
console.log(`\n🎯 Optimizing model for use case: ${useCase}`)
|
|
||||||
|
|
||||||
const optimizations = {
|
|
||||||
general: {
|
|
||||||
description: 'Balanced performance and size',
|
|
||||||
precision: 'float16',
|
|
||||||
batchSize: 32
|
|
||||||
},
|
|
||||||
'low-memory': {
|
|
||||||
description: 'Minimal memory footprint',
|
|
||||||
precision: 'int8',
|
|
||||||
batchSize: 1
|
|
||||||
},
|
|
||||||
'high-performance': {
|
|
||||||
description: 'Maximum inference speed',
|
|
||||||
precision: 'float32',
|
|
||||||
batchSize: 64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = optimizations[useCase] || optimizations.general
|
|
||||||
|
|
||||||
console.log(`📝 Optimization config: ${config.description}`)
|
|
||||||
console.log(` Precision: ${config.precision}`)
|
|
||||||
console.log(` Batch size: ${config.batchSize}`)
|
|
||||||
|
|
||||||
// Create optimization metadata
|
|
||||||
const optimizationMetadata = {
|
|
||||||
useCase,
|
|
||||||
config,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
recommendations: {
|
|
||||||
'low-memory': 'Use int8 quantized model for memory-constrained environments',
|
|
||||||
'high-performance': 'Use original float32 model with larger batch sizes',
|
|
||||||
'general': 'Use float16 model for balanced performance'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(COMPRESSED_DIR, `optimization-${useCase}.json`),
|
|
||||||
JSON.stringify(optimizationMetadata, null, 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(`✅ Optimization profile created for ${useCase}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main execution
|
|
||||||
async function main() {
|
|
||||||
try {
|
|
||||||
await createOptimizedVariants()
|
|
||||||
await optimizeForUseCase('general')
|
|
||||||
await optimizeForUseCase('low-memory')
|
|
||||||
await optimizeForUseCase('high-performance')
|
|
||||||
|
|
||||||
console.log('\n🎉 All optimizations completed successfully!')
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Compression failed:', error)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(console.error)
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create GitHub Release Script for @soulcraft/brainy-models-package
|
|
||||||
*
|
|
||||||
* This script creates a GitHub release with auto-generated release notes
|
|
||||||
* for the current version of the brainy-models-package.
|
|
||||||
*
|
|
||||||
* It uses the GitHub CLI (gh) to create the release, so the gh CLI must be installed
|
|
||||||
* and authenticated with appropriate permissions.
|
|
||||||
*
|
|
||||||
* The script:
|
|
||||||
* 1. Gets the current version from package.json
|
|
||||||
* 2. Creates a GitHub release for that version with models-package prefix
|
|
||||||
* 3. Auto-generates release notes based on commits since the last release
|
|
||||||
*
|
|
||||||
* This ensures that each npm release has a corresponding GitHub release with notes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global process, console */
|
|
||||||
|
|
||||||
import { execSync } from 'child_process'
|
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
|
|
||||||
// Get the directory of the current module
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
|
||||||
const __dirname = path.dirname(__filename)
|
|
||||||
|
|
||||||
// Path to the brainy-models-package directory
|
|
||||||
const packageDir = path.join(__dirname, '..')
|
|
||||||
const rootDir = path.join(__dirname, '..', '..')
|
|
||||||
|
|
||||||
// Path to package.json
|
|
||||||
const packageJsonPath = path.join(packageDir, 'package.json')
|
|
||||||
|
|
||||||
// Read package.json
|
|
||||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
||||||
const version = packageJson.version
|
|
||||||
const tagName = `models-package-v${version}`
|
|
||||||
|
|
||||||
// Check if GitHub CLI is installed
|
|
||||||
try {
|
|
||||||
execSync('gh --version', { stdio: 'ignore' })
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error: GitHub CLI (gh) is not installed or not in PATH')
|
|
||||||
console.error('Please install it from https://cli.github.com/ and authenticate with `gh auth login`')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the tag exists locally
|
|
||||||
let tagExistsLocally = false
|
|
||||||
try {
|
|
||||||
const tagOutput = execSync(`git tag -l ${tagName}`, { stdio: 'pipe', cwd: rootDir }).toString().trim()
|
|
||||||
tagExistsLocally = tagOutput === tagName
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Error checking if tag exists: ${error.message}`)
|
|
||||||
tagExistsLocally = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and push the tag if it doesn't exist
|
|
||||||
if (!tagExistsLocally) {
|
|
||||||
try {
|
|
||||||
console.log(`Creating tag ${tagName}...`)
|
|
||||||
execSync(`git tag ${tagName}`, { stdio: 'inherit', cwd: rootDir })
|
|
||||||
console.log(`Successfully created tag ${tagName}`)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error creating tag: ${error.message}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the tag to remote
|
|
||||||
try {
|
|
||||||
console.log(`Pushing tag ${tagName} to remote...`)
|
|
||||||
execSync(`git push origin ${tagName}`, { stdio: 'inherit', cwd: rootDir })
|
|
||||||
console.log(`Successfully pushed tag ${tagName} to remote`)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error pushing tag to remote: ${error.message}`)
|
|
||||||
// Continue with release creation even if tag push fails
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the GitHub release
|
|
||||||
try {
|
|
||||||
console.log(`Creating GitHub release for @soulcraft/brainy-models-package v${version}...`)
|
|
||||||
|
|
||||||
// Create a release with auto-generated notes
|
|
||||||
// The --generate-notes flag automatically generates release notes based on PRs and commits
|
|
||||||
execSync(
|
|
||||||
`gh release create ${tagName} --title "@soulcraft/brainy-models-package v${version}" --generate-notes --notes "Release of @soulcraft/brainy-models-package v${version} - Pre-bundled TensorFlow models for maximum reliability with Brainy vector database."`,
|
|
||||||
{ stdio: 'inherit', cwd: rootDir }
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(`GitHub release ${tagName} created successfully!`)
|
|
||||||
console.log('GitHub release created with auto-generated notes')
|
|
||||||
} catch (error) {
|
|
||||||
// If the release already exists, this is not a fatal error
|
|
||||||
if (error.message.includes('already exists')) {
|
|
||||||
console.log(`GitHub release ${tagName} already exists, skipping creation.`)
|
|
||||||
console.log('GitHub release already exists with auto-generated notes')
|
|
||||||
} else {
|
|
||||||
console.error('Error creating GitHub release:', error.message)
|
|
||||||
// Don't exit with error to allow the npm publish to continue
|
|
||||||
// process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/* eslint-env node */
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download Full Models Script for @soulcraft/brainy-models
|
|
||||||
*
|
|
||||||
* This script downloads the complete Universal Sentence Encoder model
|
|
||||||
* and saves it locally for offline use, providing maximum reliability.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import * as tf from '@tensorflow/tfjs-node'
|
|
||||||
import * as use from '@tensorflow-models/universal-sentence-encoder'
|
|
||||||
import https from 'https'
|
|
||||||
import { promisify } from 'util'
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
|
||||||
const __dirname = path.dirname(__filename)
|
|
||||||
|
|
||||||
const MODELS_DIR = path.join(__dirname, '..', 'models')
|
|
||||||
const USE_MODEL_DIR = path.join(MODELS_DIR, 'universal-sentence-encoder')
|
|
||||||
|
|
||||||
// Ensure directories exist
|
|
||||||
if (!fs.existsSync(MODELS_DIR)) {
|
|
||||||
fs.mkdirSync(MODELS_DIR, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(USE_MODEL_DIR)) {
|
|
||||||
fs.mkdirSync(USE_MODEL_DIR, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🚀 Starting full model download for @soulcraft/brainy-models...')
|
|
||||||
console.log('This will download the complete Universal Sentence Encoder model (~25MB)')
|
|
||||||
console.log('for offline use and maximum reliability.\n')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download a file from URL to local path
|
|
||||||
*/
|
|
||||||
async function downloadFile(url, filePath, maxRedirects = 5) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const file = fs.createWriteStream(filePath)
|
|
||||||
|
|
||||||
const handleRequest = (requestUrl, redirectCount = 0) => {
|
|
||||||
https.get(requestUrl, (response) => {
|
|
||||||
// Handle redirects
|
|
||||||
if (response.statusCode >= 300 && response.statusCode < 400) {
|
|
||||||
if (redirectCount >= maxRedirects) {
|
|
||||||
reject(new Error(`Too many redirects (${redirectCount}) for ${url}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const location = response.headers.location
|
|
||||||
if (!location) {
|
|
||||||
reject(new Error(`Redirect response without location header for ${url}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle relative redirects
|
|
||||||
const redirectUrl = location.startsWith('http') ? location : new URL(location, requestUrl).href
|
|
||||||
console.log(`📍 Following redirect ${redirectCount + 1}: ${redirectUrl}`)
|
|
||||||
|
|
||||||
// Close the current file stream and start over with the redirect URL
|
|
||||||
file.close()
|
|
||||||
fs.unlink(filePath, () => {}) // Delete partial file
|
|
||||||
|
|
||||||
// Recursively handle the redirect
|
|
||||||
return downloadFile(redirectUrl, filePath, maxRedirects).then(resolve).catch(reject)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.statusCode !== 200) {
|
|
||||||
reject(new Error(`Failed to download ${url}: ${response.statusCode}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalSize = parseInt(response.headers['content-length'] || '0')
|
|
||||||
let downloadedSize = 0
|
|
||||||
|
|
||||||
response.on('data', (chunk) => {
|
|
||||||
downloadedSize += chunk.length
|
|
||||||
if (totalSize > 0) {
|
|
||||||
const progress = ((downloadedSize / totalSize) * 100).toFixed(1)
|
|
||||||
process.stdout.write(`\r📥 Downloading: ${progress}% (${downloadedSize}/${totalSize} bytes)`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
response.pipe(file)
|
|
||||||
|
|
||||||
file.on('finish', () => {
|
|
||||||
file.close()
|
|
||||||
console.log(`\n✅ Downloaded: ${path.basename(filePath)}`)
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
|
|
||||||
file.on('error', (err) => {
|
|
||||||
fs.unlink(filePath, () => {}) // Delete partial file
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
}).on('error', reject)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRequest(url)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download the complete Universal Sentence Encoder model
|
|
||||||
*/
|
|
||||||
async function downloadFullModel() {
|
|
||||||
try {
|
|
||||||
console.log('🔍 Loading model to get download URLs...')
|
|
||||||
|
|
||||||
// Load the model to get access to its internal structure
|
|
||||||
const model = await use.load()
|
|
||||||
console.log('✅ Model loaded successfully')
|
|
||||||
|
|
||||||
// Test the model to ensure it works
|
|
||||||
console.log('🧪 Testing model functionality...')
|
|
||||||
const testEmbedding = await model.embed(['Hello world'])
|
|
||||||
const testArray = await testEmbedding.array()
|
|
||||||
console.log(`✅ Model test passed - embedding dimensions: ${testArray[0].length}`)
|
|
||||||
testEmbedding.dispose()
|
|
||||||
|
|
||||||
// The Universal Sentence Encoder model URL (using Google Cloud Storage which still works)
|
|
||||||
const modelBaseUrl = 'https://storage.googleapis.com/tfjs-models/savedmodel/universal_sentence_encoder'
|
|
||||||
|
|
||||||
console.log('📦 Downloading model files...')
|
|
||||||
console.log('Using Google Cloud Storage URLs (TensorFlow Hub URLs are deprecated)...')
|
|
||||||
|
|
||||||
// Download model.json
|
|
||||||
const modelJsonUrl = `${modelBaseUrl}/model.json`
|
|
||||||
const modelJsonPath = path.join(USE_MODEL_DIR, 'model.json')
|
|
||||||
await downloadFile(modelJsonUrl, modelJsonPath)
|
|
||||||
|
|
||||||
// Read the model.json to get the weights manifest
|
|
||||||
const modelJson = JSON.parse(fs.readFileSync(modelJsonPath, 'utf8'))
|
|
||||||
|
|
||||||
// Download all weight files
|
|
||||||
if (modelJson.weightsManifest) {
|
|
||||||
for (const manifest of modelJson.weightsManifest) {
|
|
||||||
for (const weightFile of manifest.paths) {
|
|
||||||
const weightUrl = `${modelBaseUrl}/${weightFile}`
|
|
||||||
const weightPath = path.join(USE_MODEL_DIR, weightFile)
|
|
||||||
await downloadFile(weightUrl, weightPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create metadata for the bundled model
|
|
||||||
const metadata = {
|
|
||||||
name: 'universal-sentence-encoder',
|
|
||||||
version: '1.0.0',
|
|
||||||
description: 'Complete Universal Sentence Encoder model bundled for offline use',
|
|
||||||
dimensions: 512,
|
|
||||||
downloadDate: new Date().toISOString(),
|
|
||||||
source: 'tensorflow-models/universal-sentence-encoder',
|
|
||||||
approach: 'full-bundle',
|
|
||||||
modelUrl: modelBaseUrl,
|
|
||||||
bundledLocally: true,
|
|
||||||
reliability: 'maximum'
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(USE_MODEL_DIR, 'metadata.json'),
|
|
||||||
JSON.stringify(metadata, null, 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Verify all files exist and calculate total size
|
|
||||||
const modelFiles = fs.readdirSync(USE_MODEL_DIR)
|
|
||||||
let totalSize = 0
|
|
||||||
|
|
||||||
console.log('\n📋 Downloaded files:')
|
|
||||||
for (const file of modelFiles) {
|
|
||||||
const filePath = path.join(USE_MODEL_DIR, file)
|
|
||||||
const stats = fs.statSync(filePath)
|
|
||||||
totalSize += stats.size
|
|
||||||
console.log(` ✅ ${file} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\n🎉 Model download complete!`)
|
|
||||||
console.log(`📊 Total size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`)
|
|
||||||
console.log(`📁 Location: ${USE_MODEL_DIR}`)
|
|
||||||
console.log(`🔒 Reliability: Maximum (fully offline)`)
|
|
||||||
|
|
||||||
// Test loading the downloaded model
|
|
||||||
console.log('\n🧪 Testing downloaded model...')
|
|
||||||
const offlineModel = await tf.loadGraphModel(`file://${path.join(USE_MODEL_DIR, 'model.json')}`)
|
|
||||||
console.log('✅ Offline model loads successfully')
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
offlineModel.dispose()
|
|
||||||
|
|
||||||
console.log('\n✨ Full model bundling completed successfully!')
|
|
||||||
console.log('The model is now available for offline use with maximum reliability.')
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ Error downloading full model:', error)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the download
|
|
||||||
downloadFullModel().catch(console.error)
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Release Workflow Script for @soulcraft/brainy-models-package
|
|
||||||
*
|
|
||||||
* This script provides a comprehensive workflow for releasing a new version:
|
|
||||||
* 1. Updates the version (major, minor, or patch)
|
|
||||||
* 2. Automatically updates the CHANGELOG.md with commit messages since the last release
|
|
||||||
* 3. Creates a GitHub release
|
|
||||||
* 4. Deploys to NPM
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node scripts/release-workflow.js [patch|minor|major]
|
|
||||||
*
|
|
||||||
* If no version type is specified, it defaults to "patch"
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global process, console */
|
|
||||||
|
|
||||||
import { execSync } from 'child_process'
|
|
||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import readline from 'readline'
|
|
||||||
|
|
||||||
// Get the directory of the current module
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
|
||||||
const __dirname = path.dirname(__filename)
|
|
||||||
|
|
||||||
// Path to the brainy-models-package directory
|
|
||||||
const packageDir = path.join(__dirname, '..')
|
|
||||||
const rootDir = path.join(__dirname, '..', '..')
|
|
||||||
|
|
||||||
// Get the version type from command line arguments
|
|
||||||
const args = process.argv.slice(2)
|
|
||||||
const versionType = args[0] || 'patch'
|
|
||||||
|
|
||||||
// Validate version type
|
|
||||||
if (!['patch', 'minor', 'major'].includes(versionType)) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('Error: Version type must be one of: patch, minor, major')
|
|
||||||
// eslint-disable-next-line no-process-exit
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to execute a command and log its output
|
|
||||||
function executeStep(command, description, cwd = packageDir) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`\n🚀 ${description}...\n`)
|
|
||||||
try {
|
|
||||||
execSync(command, { stdio: 'inherit', cwd })
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`✅ ${description} completed successfully!\n`)
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(`❌ Error during ${description.toLowerCase()}: ${error.message}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main workflow
|
|
||||||
async function runReleaseWorkflow() {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`\n=== Starting @soulcraft/brainy-models-package Release Workflow (${versionType}) ===\n`)
|
|
||||||
|
|
||||||
// Step 1: Build the project
|
|
||||||
if (!executeStep('npm run build', 'Building brainy-models-package')) {
|
|
||||||
// eslint-disable-next-line no-process-exit
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Run tests to ensure everything is working
|
|
||||||
if (!executeStep('npm test', 'Running tests')) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.warn('⚠️ Tests failed. This might indicate issues with the release.')
|
|
||||||
|
|
||||||
// Ask the user if they want to continue despite test failures
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('\n⚠️ Do you want to continue with the release process despite test failures? (y/N)')
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
})
|
|
||||||
|
|
||||||
const response = await new Promise(resolve => {
|
|
||||||
rl.question('', answer => {
|
|
||||||
rl.close()
|
|
||||||
resolve(answer.toLowerCase())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (response !== 'y' && response !== 'yes') {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('Release process aborted due to test failures.')
|
|
||||||
// eslint-disable-next-line no-process-exit
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('Continuing with release process despite test failures...')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Update version and generate changelog
|
|
||||||
if (!executeStep(`npm run release:${versionType}`, `Updating version (${versionType}) and generating changelog`)) {
|
|
||||||
// eslint-disable-next-line no-process-exit
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Create GitHub release
|
|
||||||
if (!executeStep('npm run github-release', 'Creating GitHub release')) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('Warning: GitHub release creation failed, but continuing with deployment...')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5: Publish to NPM
|
|
||||||
if (!executeStep('npm publish', 'Publishing to NPM')) {
|
|
||||||
// eslint-disable-next-line no-process-exit
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the new version from package.json
|
|
||||||
const packageJsonPath = path.join(packageDir, 'package.json')
|
|
||||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
||||||
const newVersion = packageJson.version
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`\n🎉 @soulcraft/brainy-models-package v${newVersion} release completed successfully! 🎉\n`)
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('Summary of actions:')
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`- Package built and tested`)
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`- Version bumped to v${newVersion} (${versionType})`)
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`- CHANGELOG.md updated with recent commits`)
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`- GitHub release created with auto-generated notes`)
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(`- Package published to NPM as @soulcraft/brainy-models-package`)
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('\nThank you for using the brainy-models-package release workflow!\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the workflow
|
|
||||||
runReleaseWorkflow().catch(error => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('Unexpected error during release workflow:', error)
|
|
||||||
// eslint-disable-next-line no-process-exit
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
@ -1,302 +0,0 @@
|
||||||
/**
|
|
||||||
* @soulcraft/brainy-models
|
|
||||||
*
|
|
||||||
* Pre-bundled TensorFlow models for maximum reliability with Brainy vector database.
|
|
||||||
* This package provides offline access to the Universal Sentence Encoder model,
|
|
||||||
* eliminating network dependencies and ensuring consistent performance.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as tf from '@tensorflow/tfjs'
|
|
||||||
import { readFileSync, existsSync } from 'fs'
|
|
||||||
import { join, dirname } from 'path'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to safely extract error message from unknown error type
|
|
||||||
*/
|
|
||||||
function getErrorMessage(error: unknown): string {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
return error.message
|
|
||||||
}
|
|
||||||
if (typeof error === 'string') {
|
|
||||||
return error
|
|
||||||
}
|
|
||||||
return String(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the package directory
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
|
||||||
const __dirname = dirname(__filename)
|
|
||||||
const PACKAGE_ROOT = join(__dirname, '..')
|
|
||||||
const MODELS_DIR = join(PACKAGE_ROOT, 'models')
|
|
||||||
|
|
||||||
export interface ModelMetadata {
|
|
||||||
name: string
|
|
||||||
version: string
|
|
||||||
description: string
|
|
||||||
dimensions: number
|
|
||||||
downloadDate: string
|
|
||||||
source: string
|
|
||||||
approach: string
|
|
||||||
modelUrl: string
|
|
||||||
bundledLocally: boolean
|
|
||||||
reliability: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BundledModelOptions {
|
|
||||||
verbose?: boolean
|
|
||||||
preferCompressed?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bundled Universal Sentence Encoder for offline use
|
|
||||||
*/
|
|
||||||
export class BundledUniversalSentenceEncoder {
|
|
||||||
private model: tf.GraphModel | null = null
|
|
||||||
private metadata: ModelMetadata | null = null
|
|
||||||
private options: BundledModelOptions
|
|
||||||
|
|
||||||
constructor(options: BundledModelOptions = {}) {
|
|
||||||
this.options = {
|
|
||||||
verbose: false,
|
|
||||||
preferCompressed: false,
|
|
||||||
...options
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the bundled model from local files
|
|
||||||
*/
|
|
||||||
async load(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const modelDir = join(MODELS_DIR, 'universal-sentence-encoder')
|
|
||||||
const modelPath = join(modelDir, 'model.json')
|
|
||||||
const metadataPath = join(modelDir, 'metadata.json')
|
|
||||||
|
|
||||||
if (!existsSync(modelPath)) {
|
|
||||||
throw new Error(
|
|
||||||
`Bundled model not found at ${modelPath}. ` +
|
|
||||||
'Please run "npm run download-models" to download the model files.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.verbose) {
|
|
||||||
console.log('🔄 Loading bundled Universal Sentence Encoder model...')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load metadata
|
|
||||||
if (existsSync(metadataPath)) {
|
|
||||||
const metadataContent = readFileSync(metadataPath, 'utf8')
|
|
||||||
this.metadata = JSON.parse(metadataContent)
|
|
||||||
|
|
||||||
if (this.options.verbose) {
|
|
||||||
console.log(`📋 Model metadata:`, this.metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the model
|
|
||||||
this.model = await tf.loadGraphModel(`file://${modelPath}`)
|
|
||||||
|
|
||||||
if (this.options.verbose) {
|
|
||||||
console.log('✅ Bundled model loaded successfully')
|
|
||||||
console.log(`🔒 Reliability: Maximum (fully offline)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to load bundled model: ${getErrorMessage(error)}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate embeddings for the given texts
|
|
||||||
*/
|
|
||||||
async embed(texts: string[]): Promise<tf.Tensor2D> {
|
|
||||||
if (!this.model) {
|
|
||||||
throw new Error('Model not loaded. Call load() first.')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Convert texts to tensor
|
|
||||||
const inputTensor = tf.tensor1d(texts, 'string')
|
|
||||||
|
|
||||||
// Run inference
|
|
||||||
const embeddings = this.model.predict(inputTensor) as tf.Tensor2D
|
|
||||||
|
|
||||||
// Clean up input tensor
|
|
||||||
inputTensor.dispose()
|
|
||||||
|
|
||||||
return embeddings
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to generate embeddings: ${getErrorMessage(error)}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate embeddings and return as JavaScript arrays
|
|
||||||
*/
|
|
||||||
async embedToArrays(texts: string[]): Promise<number[][]> {
|
|
||||||
const embeddings = await this.embed(texts)
|
|
||||||
const arrays = await embeddings.array() as number[][]
|
|
||||||
embeddings.dispose()
|
|
||||||
return arrays
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get model metadata
|
|
||||||
*/
|
|
||||||
getMetadata(): ModelMetadata | null {
|
|
||||||
return this.metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the model is loaded
|
|
||||||
*/
|
|
||||||
isLoaded(): boolean {
|
|
||||||
return this.model !== null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get model information
|
|
||||||
*/
|
|
||||||
getModelInfo(): { inputShape: number[], outputShape: number[] } | null {
|
|
||||||
if (!this.model) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
inputShape: this.model.inputs[0].shape || [],
|
|
||||||
outputShape: this.model.outputs[0].shape || []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispose of the model and free memory
|
|
||||||
*/
|
|
||||||
dispose(): void {
|
|
||||||
if (this.model) {
|
|
||||||
this.model.dispose()
|
|
||||||
this.model = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model compression utilities
|
|
||||||
*/
|
|
||||||
export class ModelCompressor {
|
|
||||||
/**
|
|
||||||
* Compress model weights using quantization
|
|
||||||
* Note: TensorFlow.js doesn't currently support model quantization
|
|
||||||
*/
|
|
||||||
static async quantizeModel(
|
|
||||||
modelPath: string,
|
|
||||||
outputPath: string,
|
|
||||||
options: { dtype?: 'int8' | 'int16' } = {}
|
|
||||||
): Promise<void> {
|
|
||||||
const { dtype = 'int8' } = options
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(`🔄 Loading model for quantization: ${modelPath}`)
|
|
||||||
const model = await tf.loadGraphModel(`file://${modelPath}`)
|
|
||||||
|
|
||||||
console.log(`🗜️ Quantizing model to ${dtype}...`)
|
|
||||||
|
|
||||||
// TensorFlow.js doesn't have built-in quantization or model serialization APIs yet
|
|
||||||
// This is a placeholder implementation that acknowledges the limitation
|
|
||||||
console.warn('⚠️ Model quantization is not yet supported in TensorFlow.js')
|
|
||||||
console.log(`📋 Model loaded successfully from: ${modelPath}`)
|
|
||||||
console.log(`📋 Target output path: ${outputPath}`)
|
|
||||||
console.log(`📋 Target dtype: ${dtype}`)
|
|
||||||
|
|
||||||
model.dispose()
|
|
||||||
|
|
||||||
throw new Error('Model quantization is not yet supported in TensorFlow.js. This feature requires server-side processing with TensorFlow Python.')
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to compress model: ${getErrorMessage(error)}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get model size information by reading files from disk
|
|
||||||
*/
|
|
||||||
static async getModelSize(modelPath: string): Promise<{
|
|
||||||
totalSize: number
|
|
||||||
weightsSize: number
|
|
||||||
modelJsonSize: number
|
|
||||||
}> {
|
|
||||||
try {
|
|
||||||
// Load model to verify it's valid
|
|
||||||
const model = await tf.loadGraphModel(`file://${modelPath}`)
|
|
||||||
model.dispose()
|
|
||||||
|
|
||||||
// Get model.json size
|
|
||||||
const modelJsonSize = existsSync(modelPath) ? readFileSync(modelPath).length : 0
|
|
||||||
|
|
||||||
// Calculate weights size by reading weight files
|
|
||||||
let weightsSize = 0
|
|
||||||
const modelDir = dirname(modelPath)
|
|
||||||
|
|
||||||
// Read model.json to get weight file names
|
|
||||||
if (existsSync(modelPath)) {
|
|
||||||
const modelJson = JSON.parse(readFileSync(modelPath, 'utf8'))
|
|
||||||
if (modelJson.weightsManifest) {
|
|
||||||
for (const manifest of modelJson.weightsManifest) {
|
|
||||||
for (const path of manifest.paths) {
|
|
||||||
const weightFilePath = join(modelDir, path)
|
|
||||||
if (existsSync(weightFilePath)) {
|
|
||||||
weightsSize += readFileSync(weightFilePath).length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalSize = weightsSize + modelJsonSize
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalSize,
|
|
||||||
weightsSize,
|
|
||||||
modelJsonSize
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Failed to get model size: ${getErrorMessage(error)}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility functions
|
|
||||||
*/
|
|
||||||
export const utils = {
|
|
||||||
/**
|
|
||||||
* Check if bundled models are available
|
|
||||||
*/
|
|
||||||
checkModelsAvailable(): boolean {
|
|
||||||
const modelPath = join(MODELS_DIR, 'universal-sentence-encoder', 'model.json')
|
|
||||||
return existsSync(modelPath)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get bundled models directory
|
|
||||||
*/
|
|
||||||
getModelsDirectory(): string {
|
|
||||||
return MODELS_DIR
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List available bundled models
|
|
||||||
*/
|
|
||||||
listAvailableModels(): string[] {
|
|
||||||
const models: string[] = []
|
|
||||||
const useModelPath = join(MODELS_DIR, 'universal-sentence-encoder', 'model.json')
|
|
||||||
|
|
||||||
if (existsSync(useModelPath)) {
|
|
||||||
models.push('universal-sentence-encoder')
|
|
||||||
}
|
|
||||||
|
|
||||||
return models
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default export for convenience
|
|
||||||
export default BundledUniversalSentenceEncoder
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"lib": ["ES2022"],
|
|
||||||
"outDir": "./dist",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"removeComments": false,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"resolveJsonModule": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"dist",
|
|
||||||
"test",
|
|
||||||
"scripts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
# @soulcraft/brainy-cli
|
|
||||||
|
|
||||||
Command-line interface for the [Brainy vector graph database](https://github.com/soulcraft-research/brainy).
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install globally
|
|
||||||
npm install -g @soulcraft/brainy-cli
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Once installed, you can use the `brainy` command from anywhere:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Show help
|
|
||||||
brainy --help
|
|
||||||
|
|
||||||
# Initialize a new database
|
|
||||||
brainy init
|
|
||||||
|
|
||||||
# Add data
|
|
||||||
brainy add "Cats are independent pets" '{"noun":"Thing","category":"animal"}'
|
|
||||||
|
|
||||||
# Search
|
|
||||||
brainy search "feline pets" --limit 5
|
|
||||||
|
|
||||||
# Add relationships
|
|
||||||
brainy addVerb id1 id2 RelatedTo '{"description":"Both are pets"}'
|
|
||||||
|
|
||||||
# Visualize the graph
|
|
||||||
brainy visualize
|
|
||||||
brainy visualize --root <id> --depth 3
|
|
||||||
|
|
||||||
# Generate random test data
|
|
||||||
brainy generate-random-graph --noun-count 20 --verb-count 30 --clear
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Full access to all Brainy database functionality from the command line
|
|
||||||
- Autocomplete support for commands and options
|
|
||||||
- Visualization of graph data
|
|
||||||
- Import/export capabilities
|
|
||||||
- Augmentation pipeline testing
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- Node.js >= 24.4.0
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Brainy CLI Wrapper
|
|
||||||
* This script patches the global object to fix TextEncoder issues before loading the CLI
|
|
||||||
*/
|
|
||||||
|
|
||||||
console.log('Brainy running in Node.js environment')
|
|
||||||
|
|
||||||
// Define a custom PlatformNode class that doesn't rely on this.util.TextEncoder
|
|
||||||
if (
|
|
||||||
typeof global !== 'undefined' &&
|
|
||||||
typeof process !== 'undefined' &&
|
|
||||||
process.versions &&
|
|
||||||
process.versions.node
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
// Define a PlatformNode class that uses the global TextEncoder/TextDecoder directly
|
|
||||||
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))
|
|
||||||
},
|
|
||||||
// Instead of using constructors directly, create a utility object with constructors
|
|
||||||
TextEncoder,
|
|
||||||
TextDecoder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize TextEncoder/TextDecoder instances
|
|
||||||
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 (lowercase p)
|
|
||||||
global.platformNode = new PlatformNode()
|
|
||||||
|
|
||||||
// Ensure global.util exists and has the necessary methods
|
|
||||||
// This is needed because TensorFlow.js might look for these methods in global.util
|
|
||||||
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 define global PlatformNode class:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now load and run the actual CLI
|
|
||||||
import('./dist/cli.js').catch((err) => {
|
|
||||||
console.error('Error loading CLI:', err)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CLI Wrapper Script for @soulcraft/brainy-cli
|
|
||||||
*
|
|
||||||
* This script serves as a wrapper for the Brainy CLI, ensuring that command-line arguments
|
|
||||||
* are properly passed to the CLI when invoked through the globally installed package.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// CRITICAL: Apply TensorFlow.js environment patch before importing any other modules
|
|
||||||
// This prevents the "TextEncoder is not a constructor" error in Node.js environments
|
|
||||||
// by ensuring the global.PlatformNode class is defined before TensorFlow.js loads
|
|
||||||
function applyTensorFlowPatch() {
|
|
||||||
try {
|
|
||||||
// Define a custom Platform class that works in Node.js environments
|
|
||||||
class Platform {
|
|
||||||
constructor() {
|
|
||||||
// Create a util object with necessary methods and constructors
|
|
||||||
this.util = {
|
|
||||||
// Use native TextEncoder and TextDecoder constructors
|
|
||||||
TextEncoder: global.TextEncoder || TextEncoder,
|
|
||||||
TextDecoder: global.TextDecoder || TextDecoder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize using native constructors directly
|
|
||||||
this.textEncoder = new TextEncoder()
|
|
||||||
this.textDecoder = new TextDecoder()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define isTypedArray directly on the instance
|
|
||||||
isTypedArray(arr) {
|
|
||||||
return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign the Platform class to the global object as PlatformNode
|
|
||||||
global.PlatformNode = Platform
|
|
||||||
// Also create an instance and assign it to global.platformNode (lowercase p)
|
|
||||||
global.platformNode = new Platform()
|
|
||||||
|
|
||||||
console.log('Applied TensorFlow.js platform patch in CLI wrapper')
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to apply TensorFlow.js platform patch:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the patch immediately
|
|
||||||
applyTensorFlowPatch()
|
|
||||||
|
|
||||||
import { spawn } from 'child_process'
|
|
||||||
import { fileURLToPath } from 'url'
|
|
||||||
import { dirname, join } from 'path'
|
|
||||||
import fs from 'fs'
|
|
||||||
|
|
||||||
// Node.js v24+ compatibility patches are now applied above,
|
|
||||||
// before any imports, to ensure TensorFlow.js can correctly
|
|
||||||
// detect and use the TextEncoder/TextDecoder in the environment.
|
|
||||||
|
|
||||||
// Get the directory of the current module
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
|
||||||
const __dirname = dirname(__filename)
|
|
||||||
|
|
||||||
// Find the main package
|
|
||||||
const mainPackagePath = join(__dirname, 'node_modules', '@soulcraft', 'brainy')
|
|
||||||
|
|
||||||
// Path to the actual CLI script in this package
|
|
||||||
const cliPath = join(__dirname, 'dist', 'cli.js')
|
|
||||||
|
|
||||||
// Check if the CLI script exists
|
|
||||||
if (!fs.existsSync(cliPath)) {
|
|
||||||
console.error(`Error: CLI script not found at ${cliPath}`)
|
|
||||||
console.error(
|
|
||||||
'This is likely because the CLI was not built during package installation.'
|
|
||||||
)
|
|
||||||
console.error('Please reinstall the package with:')
|
|
||||||
console.error('npm uninstall -g @soulcraft/brainy-cli')
|
|
||||||
console.error('npm install -g @soulcraft/brainy-cli')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special handling for version flags
|
|
||||||
if (process.argv.includes('--version') || process.argv.includes('-V')) {
|
|
||||||
// Read version directly from package.json to ensure it's always correct
|
|
||||||
try {
|
|
||||||
const packageJsonPath = join(__dirname, 'package.json')
|
|
||||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
||||||
console.log(packageJson.version)
|
|
||||||
process.exit(0)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading version information:', error.message)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward all arguments to the CLI script
|
|
||||||
const args = process.argv.slice(2)
|
|
||||||
|
|
||||||
// Check if npm is passing --force flag
|
|
||||||
// When npm runs with --force, it sets the npm_config_force environment variable
|
|
||||||
if (
|
|
||||||
process.env.npm_config_force === 'true' &&
|
|
||||||
args.includes('clear') &&
|
|
||||||
!args.includes('--force') &&
|
|
||||||
!args.includes('-f')
|
|
||||||
) {
|
|
||||||
args.push('--force')
|
|
||||||
}
|
|
||||||
|
|
||||||
const cli = spawn('node', [cliPath, ...args], { stdio: 'inherit' })
|
|
||||||
|
|
||||||
cli.on('close', (code) => {
|
|
||||||
process.exit(code)
|
|
||||||
})
|
|
||||||
3437
cli-package/package-lock.json
generated
3437
cli-package/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@soulcraft/brainy-cli",
|
|
||||||
"version": "0.19.0",
|
|
||||||
"description": "Command-line interface for the Brainy vector graph database",
|
|
||||||
"type": "module",
|
|
||||||
"bin": {
|
|
||||||
"brainy": "cli-wrapper.js"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"cli-wrapper.js",
|
|
||||||
"README.md",
|
|
||||||
"dist/cli.js",
|
|
||||||
"dist/cli.js.map"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "rollup -c rollup.config.js",
|
|
||||||
"prepare": "npm run build",
|
|
||||||
"postinstall": "node cli-wrapper.js --version",
|
|
||||||
"_version": "echo 'Version updated in package.json'",
|
|
||||||
"_version:patch": "npm version patch",
|
|
||||||
"_version:minor": "npm version minor",
|
|
||||||
"_version:major": "npm version major",
|
|
||||||
"_deploy": "npm run build && npm publish",
|
|
||||||
"_dry-run": "npm pack --dry-run"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"vector-database",
|
|
||||||
"hnsw",
|
|
||||||
"cli",
|
|
||||||
"browser",
|
|
||||||
"container",
|
|
||||||
"graph-database"
|
|
||||||
],
|
|
||||||
"author": "David Snelling (david@soulcraft.com)",
|
|
||||||
"license": "MIT",
|
|
||||||
"private": false,
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/soulcraft-research/brainy",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/soulcraft-research/brainy/issues"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/soulcraft-research/brainy.git"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@soulcraft/brainy": "^0.24.0",
|
|
||||||
"commander": "^14.0.0",
|
|
||||||
"omelette": "^0.4.17"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
|
||||||
"@types/node": "^20.11.30",
|
|
||||||
"@types/omelette": "^0.4.5",
|
|
||||||
"rollup": "^4.13.0",
|
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
|
||||||
"typescript": "^5.4.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=24.4.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import typescript from '@rollup/plugin-typescript'
|
|
||||||
import resolve from '@rollup/plugin-node-resolve'
|
|
||||||
import commonjs from '@rollup/plugin-commonjs'
|
|
||||||
import json from '@rollup/plugin-json'
|
|
||||||
import { terser } from 'rollup-plugin-terser'
|
|
||||||
|
|
||||||
// CLI configuration
|
|
||||||
export default {
|
|
||||||
input: 'src/cli.ts',
|
|
||||||
context: 'this', // Preserve 'this' context to fix TensorFlow.js issue
|
|
||||||
output: {
|
|
||||||
dir: 'dist',
|
|
||||||
entryFileNames: 'cli.js',
|
|
||||||
format: 'es',
|
|
||||||
sourcemap: true,
|
|
||||||
inlineDynamicImports: true
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
resolve({
|
|
||||||
browser: false,
|
|
||||||
preferBuiltins: true
|
|
||||||
}),
|
|
||||||
commonjs({
|
|
||||||
transformMixedEsModules: true
|
|
||||||
}),
|
|
||||||
json(),
|
|
||||||
typescript({
|
|
||||||
tsconfig: './tsconfig.json',
|
|
||||||
declaration: false,
|
|
||||||
declarationMap: false
|
|
||||||
})
|
|
||||||
],
|
|
||||||
external: [
|
|
||||||
// External dependencies that should not be bundled
|
|
||||||
'@soulcraft/brainy',
|
|
||||||
'commander',
|
|
||||||
'omelette',
|
|
||||||
'fs',
|
|
||||||
'path',
|
|
||||||
'url',
|
|
||||||
'child_process',
|
|
||||||
'worker_threads'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +0,0 @@
|
||||||
/**
|
|
||||||
* This file is imported for its side effects to patch the environment
|
|
||||||
* for TensorFlow.js before any other library code runs.
|
|
||||||
*
|
|
||||||
* It ensures that by the time TensorFlow.js is imported by any other
|
|
||||||
* module, the necessary compatibility fixes for the current Node.js
|
|
||||||
* environment are already in place.
|
|
||||||
*/
|
|
||||||
import { applyTensorFlowPatch } from './utils/textEncoding.js'
|
|
||||||
|
|
||||||
// Apply the TensorFlow.js platform patch if needed
|
|
||||||
applyTensorFlowPatch()
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
/**
|
|
||||||
* Unified Text Encoding Utilities
|
|
||||||
*
|
|
||||||
* This module provides a consistent way to handle text encoding/decoding across all environments
|
|
||||||
* using the native TextEncoder/TextDecoder APIs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a text encoder that works in the current environment
|
|
||||||
* @returns A TextEncoder instance
|
|
||||||
*/
|
|
||||||
export function getTextEncoder(): TextEncoder {
|
|
||||||
return new TextEncoder()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a text decoder that works in the current environment
|
|
||||||
* @returns A TextDecoder instance
|
|
||||||
*/
|
|
||||||
export function getTextDecoder(): TextDecoder {
|
|
||||||
return new TextDecoder()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply the TensorFlow.js platform patch if needed
|
|
||||||
* This function patches the global object to provide a PlatformNode class
|
|
||||||
* that uses native TextEncoder/TextDecoder
|
|
||||||
*/
|
|
||||||
export function applyTensorFlowPatch(): void {
|
|
||||||
try {
|
|
||||||
// Define a custom Platform class that works in both Node.js and browser environments
|
|
||||||
class Platform {
|
|
||||||
util: any
|
|
||||||
textEncoder: TextEncoder
|
|
||||||
textDecoder: TextDecoder
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// Create a util object with necessary methods and constructors
|
|
||||||
// Store the actual constructor functions, not just references
|
|
||||||
const TextEncoderConstructor = globalThis.TextEncoder || TextEncoder
|
|
||||||
const TextDecoderConstructor = globalThis.TextDecoder || TextDecoder
|
|
||||||
|
|
||||||
this.util = {
|
|
||||||
// Use native TextEncoder and TextDecoder constructors
|
|
||||||
TextEncoder: TextEncoderConstructor,
|
|
||||||
TextDecoder: TextDecoderConstructor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize using native constructors directly
|
|
||||||
this.textEncoder = new TextEncoderConstructor()
|
|
||||||
this.textDecoder = new TextDecoderConstructor()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define isFloat32Array directly on the instance
|
|
||||||
isFloat32Array(arr: any) {
|
|
||||||
return !!(
|
|
||||||
arr instanceof Float32Array ||
|
|
||||||
(arr &&
|
|
||||||
Object.prototype.toString.call(arr) === '[object Float32Array]')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define isTypedArray directly on the instance
|
|
||||||
isTypedArray(arr: any) {
|
|
||||||
return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the global object in a way that works in both Node.js and browser
|
|
||||||
const globalObj =
|
|
||||||
typeof global !== 'undefined'
|
|
||||||
? global
|
|
||||||
: typeof window !== 'undefined'
|
|
||||||
? window
|
|
||||||
: typeof self !== 'undefined'
|
|
||||||
? self
|
|
||||||
: {}
|
|
||||||
|
|
||||||
// Only apply in Node.js environment
|
|
||||||
if (
|
|
||||||
typeof process !== 'undefined' &&
|
|
||||||
process.versions &&
|
|
||||||
process.versions.node
|
|
||||||
) {
|
|
||||||
// Assign the Platform class to the global object as PlatformNode for Node.js
|
|
||||||
;(globalObj as any).PlatformNode = Platform
|
|
||||||
// Also create an instance and assign it to global.platformNode (lowercase p)
|
|
||||||
;(globalObj as any).platformNode = new Platform()
|
|
||||||
} else if (typeof window !== 'undefined' || typeof self !== 'undefined') {
|
|
||||||
// In browser environments, we might need to provide similar functionality
|
|
||||||
// but we'll use a different name to avoid conflicts
|
|
||||||
;(globalObj as any).PlatformBrowser = Platform
|
|
||||||
;(globalObj as any).platformBrowser = new Platform()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to apply TensorFlow.js platform patch:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"outDir": "dist",
|
|
||||||
"declaration": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"paths": {
|
|
||||||
"@soulcraft/brainy": ["../dist/unified.d.ts"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src/**/*", "types.d.ts"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
98
cli-package/types.d.ts
vendored
98
cli-package/types.d.ts
vendored
|
|
@ -1,98 +0,0 @@
|
||||||
// Type declarations for @soulcraft/brainy
|
|
||||||
declare module '@soulcraft/brainy' {
|
|
||||||
// Core types
|
|
||||||
export class BrainyData {
|
|
||||||
constructor(config?: any)
|
|
||||||
|
|
||||||
init(): Promise<void>
|
|
||||||
|
|
||||||
add(text: string, metadata?: any): Promise<string>
|
|
||||||
|
|
||||||
get(id: string): Promise<any>
|
|
||||||
|
|
||||||
delete(id: string): Promise<void>
|
|
||||||
|
|
||||||
search(query: string, limit?: number, options?: any): Promise<any[]>
|
|
||||||
|
|
||||||
searchText(query: string, limit?: number, options?: any): Promise<any[]>
|
|
||||||
|
|
||||||
embed(data: string | string[]): Promise<number[]>
|
|
||||||
|
|
||||||
calculateSimilarity(
|
|
||||||
a: number[] | string | string[],
|
|
||||||
b: number[] | string | string[],
|
|
||||||
options?: { forceEmbed?: boolean, distanceFunction?: any }
|
|
||||||
): Promise<number>
|
|
||||||
|
|
||||||
addVerb(
|
|
||||||
sourceId: string,
|
|
||||||
targetId: string,
|
|
||||||
text?: string,
|
|
||||||
options?: any
|
|
||||||
): Promise<string>
|
|
||||||
|
|
||||||
getVerbsBySource(sourceId: string): Promise<any[]>
|
|
||||||
|
|
||||||
getVerbsByTarget(targetId: string): Promise<any[]>
|
|
||||||
|
|
||||||
status(): Promise<any>
|
|
||||||
|
|
||||||
clear(): Promise<void>
|
|
||||||
|
|
||||||
backup(): Promise<any>
|
|
||||||
|
|
||||||
restore(data: any, options?: any): Promise<any>
|
|
||||||
|
|
||||||
importSparseData(data: any, options?: any): Promise<any>
|
|
||||||
|
|
||||||
generateRandomGraph(options?: any): Promise<any>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FileSystemStorage {
|
|
||||||
constructor(dataDir: string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipelines
|
|
||||||
export const sequentialPipeline: any
|
|
||||||
export const augmentationPipeline: any
|
|
||||||
|
|
||||||
// Enums
|
|
||||||
export enum NounType {
|
|
||||||
Person = 'Person',
|
|
||||||
Place = 'Place',
|
|
||||||
Thing = 'Thing',
|
|
||||||
Event = 'Event',
|
|
||||||
Concept = 'Concept',
|
|
||||||
Content = 'Content'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum VerbType {
|
|
||||||
RelatedTo = 'RelatedTo',
|
|
||||||
PartOf = 'PartOf',
|
|
||||||
HasA = 'HasA',
|
|
||||||
UsedFor = 'UsedFor',
|
|
||||||
CapableOf = 'CapableOf',
|
|
||||||
AtLocation = 'AtLocation',
|
|
||||||
Causes = 'Causes',
|
|
||||||
HasProperty = 'HasProperty',
|
|
||||||
Owns = 'Owns',
|
|
||||||
CreatedBy = 'CreatedBy'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ExecutionMode {
|
|
||||||
SEQUENTIAL = 'sequential',
|
|
||||||
PARALLEL = 'parallel',
|
|
||||||
THREADED = 'threaded'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AugmentationType {
|
|
||||||
SENSE = 'sense',
|
|
||||||
MEMORY = 'memory',
|
|
||||||
COGNITION = 'cognition',
|
|
||||||
CONDUIT = 'conduit',
|
|
||||||
ACTIVATION = 'activation',
|
|
||||||
PERCEPTION = 'perception',
|
|
||||||
DIALOG = 'dialog',
|
|
||||||
WEBSOCKET = 'websocket'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
demo.soulcraft.com
|
|
||||||
5361
demo/index.html
5361
demo/index.html
File diff suppressed because it is too large
Load diff
|
|
@ -1,104 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Brainy Browser Worker Test</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.result {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding: 10px 15px;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Brainy Browser Worker Test</h1>
|
|
||||||
<p>This page tests the Brainy worker thread implementation in a browser environment.</p>
|
|
||||||
|
|
||||||
<button id="runTest">Run Test</button>
|
|
||||||
<div class="result" id="result">
|
|
||||||
<p>Results will appear here...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import { executeInThread, environment, isThreadingAvailable } from '../dist/unified.js';
|
|
||||||
|
|
||||||
document.getElementById('runTest').addEventListener('click', async () => {
|
|
||||||
const resultDiv = document.getElementById('result');
|
|
||||||
resultDiv.innerHTML = '<p>Running test...</p>';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Log environment information
|
|
||||||
resultDiv.innerHTML += `<p>Environment: ${JSON.stringify(environment)}</p>`;
|
|
||||||
|
|
||||||
// Check if threading is available
|
|
||||||
const threadingAvailable = typeof isThreadingAvailable === 'function'
|
|
||||||
? isThreadingAvailable()
|
|
||||||
: 'isThreadingAvailable function not found';
|
|
||||||
resultDiv.innerHTML += `<p>Threading available: ${threadingAvailable}</p>`;
|
|
||||||
|
|
||||||
// Define a compute-intensive function
|
|
||||||
const computeIntensiveFunction = `
|
|
||||||
function(data) {
|
|
||||||
console.log('Worker: Starting computation...');
|
|
||||||
|
|
||||||
// Simulate a compute-intensive task
|
|
||||||
const start = Date.now();
|
|
||||||
let result = 0;
|
|
||||||
for (let i = 0; i < data.iterations; i++) {
|
|
||||||
result += Math.sqrt(i) * Math.sin(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = Date.now() - start;
|
|
||||||
console.log('Worker: Computation completed in ' + duration + 'ms');
|
|
||||||
|
|
||||||
return {
|
|
||||||
result,
|
|
||||||
duration,
|
|
||||||
iterations: data.iterations
|
|
||||||
};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Execute the function in a worker thread
|
|
||||||
resultDiv.innerHTML += '<p>Starting worker thread execution...</p>';
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
const result = await executeInThread(computeIntensiveFunction, { iterations: 5000000 });
|
|
||||||
|
|
||||||
const mainDuration = Date.now() - startTime;
|
|
||||||
resultDiv.innerHTML += `<p>Worker thread execution completed in ${mainDuration}ms</p>`;
|
|
||||||
resultDiv.innerHTML += `<pre>${JSON.stringify(result, null, 2)}</pre>`;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
resultDiv.innerHTML += `<p>Error: ${error.message}</p>`;
|
|
||||||
console.error('Error during test:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Brainy Fallback Test</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 10px 15px;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Brainy Fallback Test</h1>
|
|
||||||
<p>This page tests the Brainy fallback mechanism when threading is not available.</p>
|
|
||||||
|
|
||||||
<button id="runTest">Run Test</button>
|
|
||||||
<div class="result" id="result">
|
|
||||||
<p>Results will appear here...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import { executeInThread, environment } from '../dist/unified.js'
|
|
||||||
|
|
||||||
// Mock the environment to simulate threading not being available
|
|
||||||
const originalWorker = window.Worker
|
|
||||||
|
|
||||||
document.getElementById('runTest').addEventListener('click', async () => {
|
|
||||||
const resultDiv = document.getElementById('result')
|
|
||||||
resultDiv.innerHTML = '<p>Running test...</p>'
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Log environment information
|
|
||||||
resultDiv.innerHTML += `<p>Original Environment: ${JSON.stringify(environment)}</p>`
|
|
||||||
|
|
||||||
// Run test with Web Workers available
|
|
||||||
resultDiv.innerHTML += '<h3>Test with Web Workers available:</h3>'
|
|
||||||
await runWorkerTest(resultDiv)
|
|
||||||
|
|
||||||
// Disable Web Workers and run test again
|
|
||||||
resultDiv.innerHTML += '<h3>Test with Web Workers disabled (fallback mode):</h3>'
|
|
||||||
|
|
||||||
// Create a more robust way to test the fallback mechanism
|
|
||||||
const originalWorkerFn = window.Worker;
|
|
||||||
window.Worker = function() {
|
|
||||||
throw new Error('Worker constructor disabled for testing');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Log modified environment
|
|
||||||
resultDiv.innerHTML += `<p>Modified Environment (Worker disabled): ${typeof window.Worker}</p>`
|
|
||||||
|
|
||||||
try {
|
|
||||||
await runWorkerTest(resultDiv);
|
|
||||||
} finally {
|
|
||||||
// Ensure Worker is restored
|
|
||||||
window.Worker = originalWorkerFn;
|
|
||||||
resultDiv.innerHTML += '<p>Test completed. Web Workers restored.</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
resultDiv.innerHTML += `<p>Error: ${error.message}</p>`
|
|
||||||
console.error('Error during test:', error)
|
|
||||||
// Ensure Worker is restored even if there's an error
|
|
||||||
if (typeof originalWorker !== 'undefined') {
|
|
||||||
window.Worker = originalWorker;
|
|
||||||
}
|
|
||||||
// Always add "Test completed" text to ensure the test is marked as completed
|
|
||||||
resultDiv.innerHTML += '<p>Test completed with errors.</p>';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function runWorkerTest(resultDiv) {
|
|
||||||
// Define a compute-intensive function using a simple anonymous function expression
|
|
||||||
// This format works with both worker and fallback mechanisms
|
|
||||||
const computeIntensiveFunction = `function(data) {
|
|
||||||
console.log('Worker/Fallback: Starting computation...');
|
|
||||||
|
|
||||||
// Simulate a compute-intensive task
|
|
||||||
const start = Date.now();
|
|
||||||
let result = 0;
|
|
||||||
for (let i = 0; i < data.iterations; i++) {
|
|
||||||
result += Math.sqrt(i) * Math.sin(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = Date.now() - start;
|
|
||||||
console.log('Worker/Fallback: Computation completed in ' + duration + 'ms');
|
|
||||||
|
|
||||||
const globalObj = typeof self !== 'undefined' ? self :
|
|
||||||
typeof window !== 'undefined' ? window :
|
|
||||||
{};
|
|
||||||
|
|
||||||
return {
|
|
||||||
result,
|
|
||||||
duration,
|
|
||||||
iterations: data.iterations,
|
|
||||||
webWorkersAvailable: typeof globalObj.Worker !== 'undefined'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// Execute the function
|
|
||||||
resultDiv.innerHTML += '<p>Starting execution...</p>'
|
|
||||||
const startTime = Date.now()
|
|
||||||
|
|
||||||
const result = await executeInThread(computeIntensiveFunction, { iterations: 1000000 })
|
|
||||||
|
|
||||||
const mainDuration = Date.now() - startTime
|
|
||||||
resultDiv.innerHTML += `<p>Execution completed in ${mainDuration}ms</p>`
|
|
||||||
resultDiv.innerHTML += `<pre>${JSON.stringify(result, null, 2)}</pre>`
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Brainy TensorFlow and TextEncoder Test</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 10px 15px;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: green;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Brainy TensorFlow and TextEncoder Test</h1>
|
|
||||||
<p>This page tests TensorFlow.js and TextEncoder functionality in a browser environment.</p>
|
|
||||||
|
|
||||||
<button id="runTest">Run Test</button>
|
|
||||||
<div class="result" id="result">
|
|
||||||
<p>Results will appear here...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
// Implement the necessary functions directly
|
|
||||||
function applyTensorFlowPatch() {
|
|
||||||
console.log('Applying TensorFlow patch directly in test file')
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTextEncoder() {
|
|
||||||
return new TextEncoder()
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTextDecoder() {
|
|
||||||
return new TextDecoder()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to dynamically import TensorFlow.js
|
|
||||||
async function loadTensorFlow() {
|
|
||||||
// Import TensorFlow.js dynamically
|
|
||||||
const tf = await import('https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.22.0/dist/tf.min.js')
|
|
||||||
return tf
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('runTest').addEventListener('click', async () => {
|
|
||||||
const resultDiv = document.getElementById('result')
|
|
||||||
resultDiv.innerHTML = '<p>Running test...</p>'
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Apply TensorFlow patch for TextEncoder compatibility
|
|
||||||
applyTensorFlowPatch()
|
|
||||||
resultDiv.innerHTML += '<p>TensorFlow patch applied successfully</p>'
|
|
||||||
|
|
||||||
// Test TextEncoder
|
|
||||||
resultDiv.innerHTML += '<h3>Testing TextEncoder</h3>'
|
|
||||||
const encoder = getTextEncoder()
|
|
||||||
const decoder = getTextDecoder()
|
|
||||||
|
|
||||||
const testString = 'Hello, world! 👋'
|
|
||||||
resultDiv.innerHTML += `<p>Original string: "${testString}"</p>`
|
|
||||||
|
|
||||||
const encoded = encoder.encode(testString)
|
|
||||||
resultDiv.innerHTML += `<p>Encoded: [${Array.from(encoded).join(', ')}]</p>`
|
|
||||||
|
|
||||||
const decoded = decoder.decode(encoded)
|
|
||||||
resultDiv.innerHTML += `<p>Decoded: "${decoded}"</p>`
|
|
||||||
|
|
||||||
if (testString === decoded) {
|
|
||||||
resultDiv.innerHTML += '<p class="success">✅ TextEncoder/TextDecoder test passed!</p>'
|
|
||||||
} else {
|
|
||||||
resultDiv.innerHTML += '<p class="error">❌ TextEncoder/TextDecoder test failed!</p>'
|
|
||||||
throw new Error('TextEncoder/TextDecoder test failed')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test TensorFlow.js
|
|
||||||
resultDiv.innerHTML += '<h3>Testing TensorFlow.js</h3>'
|
|
||||||
resultDiv.innerHTML += '<p>Loading TensorFlow.js...</p>'
|
|
||||||
|
|
||||||
const tf = await loadTensorFlow()
|
|
||||||
resultDiv.innerHTML += '<p>TensorFlow.js loaded successfully</p>'
|
|
||||||
|
|
||||||
// Create a simple tensor
|
|
||||||
const tensor = tf.tensor2d([[1, 2], [3, 4]])
|
|
||||||
resultDiv.innerHTML += '<p>Created tensor: [[1, 2], [3, 4]]</p>'
|
|
||||||
|
|
||||||
// Perform a simple operation
|
|
||||||
const result = tensor.add(tf.scalar(1))
|
|
||||||
resultDiv.innerHTML += '<p>Result of adding 1 to tensor</p>'
|
|
||||||
|
|
||||||
// Check the values
|
|
||||||
const values = await result.array()
|
|
||||||
const expected = [[2, 3], [4, 5]]
|
|
||||||
|
|
||||||
resultDiv.innerHTML += `<p>Result values: ${JSON.stringify(values)}</p>`
|
|
||||||
resultDiv.innerHTML += `<p>Expected values: ${JSON.stringify(expected)}</p>`
|
|
||||||
|
|
||||||
// Compare values
|
|
||||||
const match = JSON.stringify(values) === JSON.stringify(expected)
|
|
||||||
if (match) {
|
|
||||||
resultDiv.innerHTML += '<p class="success">✅ TensorFlow.js test passed!</p>'
|
|
||||||
} else {
|
|
||||||
resultDiv.innerHTML += '<p class="error">❌ TensorFlow.js test failed!</p>'
|
|
||||||
throw new Error('TensorFlow.js test failed')
|
|
||||||
}
|
|
||||||
|
|
||||||
resultDiv.innerHTML += '<h3 class="success">All tests passed successfully!</h3>'
|
|
||||||
|
|
||||||
// Add a marker that Puppeteer can detect to know the test is complete
|
|
||||||
resultDiv.innerHTML += '<p id="testComplete">Test completed</p>'
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
resultDiv.innerHTML += `<p class="error">Error during test: ${error.message}</p>`
|
|
||||||
console.error('Error during test:', error)
|
|
||||||
|
|
||||||
// Add a marker that Puppeteer can detect to know the test is complete (even with error)
|
|
||||||
resultDiv.innerHTML += '<p id="testComplete">Test completed with errors</p>'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
62
docker-compose.yml
Normal file
62
docker-compose.yml
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
brainy:
|
||||||
|
build: .
|
||||||
|
container_name: brainy-app
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- BRAINY_STORAGE_TYPE=filesystem
|
||||||
|
- BRAINY_STORAGE_PATH=/app/data
|
||||||
|
- BRAINY_LOG_LEVEL=info
|
||||||
|
- BRAINY_RATE_LIMIT_MAX=100
|
||||||
|
- BRAINY_RATE_LIMIT_WINDOW_MS=900000
|
||||||
|
volumes:
|
||||||
|
# Persistent storage for data
|
||||||
|
- brainy-data:/app/data
|
||||||
|
# Optional: Mount local models directory
|
||||||
|
# - ./models:/app/models:ro
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- brainy-network
|
||||||
|
|
||||||
|
# Optional: MinIO for S3-compatible storage (development)
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
container_name: brainy-minio
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
environment:
|
||||||
|
- MINIO_ROOT_USER=brainy
|
||||||
|
- MINIO_ROOT_PASSWORD=brainy123456
|
||||||
|
volumes:
|
||||||
|
- minio-data:/data
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
networks:
|
||||||
|
- brainy-network
|
||||||
|
profiles:
|
||||||
|
- with-s3
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
brainy-data:
|
||||||
|
driver: local
|
||||||
|
minio-data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
brainy-network:
|
||||||
|
driver: bridge
|
||||||
295
docs/ADR-001-generational-mvcc.md
Normal file
295
docs/ADR-001-generational-mvcc.md
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
# ADR-001: Generational MVCC storage and the immutable Db API
|
||||||
|
|
||||||
|
**Status:** Accepted (ships in 8.0)
|
||||||
|
**Date:** 2026-06-10
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Before 8.0, Brainy carried two overlapping version-control subsystems: a
|
||||||
|
copy-on-write branching layer (`fork`/`checkout`/`commit`/`branches`) and a
|
||||||
|
separate versioning subsystem (`versions.save/list/compare/restore/prune`),
|
||||||
|
plus a read-only historical adapter for commit-based time travel. Together
|
||||||
|
they were ~5,100 LOC of mechanism for one product need: *read a consistent
|
||||||
|
past state while the store keeps moving, and snapshot/restore cheaply.*
|
||||||
|
|
||||||
|
Neither subsystem gave a precise isolation guarantee. Reads raced in-place
|
||||||
|
JSON overwrites, so a "snapshot" was only as immutable as the bytes it
|
||||||
|
happened to share with the live store.
|
||||||
|
|
||||||
|
8.0 replaces both with **one mechanism**: generational MVCC over immutable,
|
||||||
|
generation-stamped records, exposed through a Datomic-style immutable
|
||||||
|
database value (`Db`). The same model is implemented natively by versioned
|
||||||
|
index providers (LSM snapshots), so semantics are identical on the pure-JS
|
||||||
|
path and the native path.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
### The model
|
||||||
|
|
||||||
|
- A **monotonic u64 generation counter** is the store's logical clock. It
|
||||||
|
advances once per committed `transact()` batch and once per
|
||||||
|
single-operation write (`add`/`update`/`remove`/`relate`/…), so
|
||||||
|
`brain.generation()` is always a meaningful watermark. It is persisted in
|
||||||
|
`_system/generation.json` and never reissued for anything durable.
|
||||||
|
- `brain.now()` **pins** the current generation in O(1) and returns a `Db` —
|
||||||
|
an immutable view. Pins are refcounted; `db.release()` (with a
|
||||||
|
`FinalizationRegistry` backstop for leaked values) ends the pin.
|
||||||
|
- `brain.transact(ops, { meta, ifAtGeneration })` commits a declarative
|
||||||
|
batch atomically as **exactly one generation**, with whole-store
|
||||||
|
compare-and-swap (`ifAtGeneration` → `GenerationConflictError`) and
|
||||||
|
reified transaction metadata appended to `_system/tx-log.jsonl`.
|
||||||
|
- `brain.asOf(generation | Date | snapshotPath)` opens past state;
|
||||||
|
`db.with(ops)` layers a speculative in-memory overlay (never touching
|
||||||
|
disk, the counter, or index providers); `db.persist(path)` cuts an
|
||||||
|
instant snapshot; `brain.restore(path, { confirm: true })` replaces state
|
||||||
|
from one; `Brainy.load(path)` opens a snapshot read-only with the full
|
||||||
|
query surface.
|
||||||
|
|
||||||
|
### Persisted layout
|
||||||
|
|
||||||
|
All paths are storage-root-relative:
|
||||||
|
|
||||||
|
```
|
||||||
|
_system/generation.json { generation, updatedAt } atomic tmp+rename
|
||||||
|
_system/manifest.json { version, generation, atomic tmp+rename
|
||||||
|
committedAt, horizon } (the commit point)
|
||||||
|
_system/tx-log.jsonl one line per committed append-only
|
||||||
|
transact: { generation,
|
||||||
|
timestamp, meta? }
|
||||||
|
_generations/<N>/tx.json the generation-N delta: immutable
|
||||||
|
touched noun/verb ids + meta
|
||||||
|
_generations/<N>/prev/<id>.json before-image of <id> as of immutable
|
||||||
|
commit N (raw stored bytes;
|
||||||
|
null parts = file was absent)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why per-generation deltas instead of a global `id → latest generation`
|
||||||
|
map in the manifest:** a global map makes every commit O(all ids) — the
|
||||||
|
whole map must be rewritten to swap it atomically. The delta layout makes a
|
||||||
|
commit O(ids touched) and keeps the manifest a fixed-size watermark, while
|
||||||
|
point-in-time resolution stays correct (see "Read resolution" below). The
|
||||||
|
trade is that resolution at a pinned generation scans the deltas of later
|
||||||
|
commits — bounded by the number of commits since the pin, which is exactly
|
||||||
|
the window compaction keeps short.
|
||||||
|
|
||||||
|
Before-images are deliberately the *only* per-id records. They serve both
|
||||||
|
roles the layer needs — the crash-recovery undo log and the point-in-time
|
||||||
|
read source. After-images would duplicate state that is already readable
|
||||||
|
(the canonical entity files hold the latest bytes; earlier states resolve
|
||||||
|
from later before-images) and would double record I/O per commit.
|
||||||
|
|
||||||
|
### Commit protocol (durability)
|
||||||
|
|
||||||
|
`transact()` commits under a store-wide mutex:
|
||||||
|
|
||||||
|
1. **CAS check.** A stale `ifAtGeneration` throws `GenerationConflictError`
|
||||||
|
before anything is staged.
|
||||||
|
2. **Reserve** generation `N` (counter increment).
|
||||||
|
3. **Stage the undo log:** write the before-image of every touched id plus
|
||||||
|
`tx.json` under `_generations/N/`, then **fsync** the files and their
|
||||||
|
directories. From this point, any crash is recoverable to the exact
|
||||||
|
pre-transaction bytes.
|
||||||
|
4. **Execute** the planned batch through the TransactionManager (which has
|
||||||
|
its own operation-level rollback for in-flight failures).
|
||||||
|
5. **Commit point:** persist the counter, then write `_system/manifest.json`
|
||||||
|
via atomic tmp+rename and fsync it. The rename *is* the commit: a
|
||||||
|
generation directory is committed if and only if `N ≤
|
||||||
|
manifest.generation`.
|
||||||
|
6. Append the tx-log line (advisory metadata — a crash between 5 and 6
|
||||||
|
keeps the transaction).
|
||||||
|
|
||||||
|
**Crash recovery (on open):** any `_generations/<N>` directory with
|
||||||
|
`N > manifest.generation` is an uncommitted transaction. Its before-images
|
||||||
|
are restored to the canonical paths (idempotently — recovery itself can
|
||||||
|
crash and rerun) and the directory is removed. Because recovery runs before
|
||||||
|
any index is built, and a recovery that rolled something back forces a full
|
||||||
|
index rebuild, derived indexes never observe rolled-back state. Reader-mode
|
||||||
|
instances skip recovery (readers never write; the next writer repairs).
|
||||||
|
|
||||||
|
A failed (non-crash) transaction takes the same staging directory down the
|
||||||
|
abort path: the TransactionManager rolls back applied operations, the
|
||||||
|
staging directory is removed, and the generation reservation is returned —
|
||||||
|
a failed batch leaves the generation counter unchanged.
|
||||||
|
|
||||||
|
### Read resolution at a pinned generation
|
||||||
|
|
||||||
|
The state of id X at pinned generation G is:
|
||||||
|
|
||||||
|
- the before-image stored by the **first committed generation after G that
|
||||||
|
touched X**, or
|
||||||
|
- the live canonical bytes, when nothing after G touched X.
|
||||||
|
|
||||||
|
While nothing has committed past G, *every* read on the `Db` delegates to
|
||||||
|
the live fast paths untouched — `now()` adds no read overhead until history
|
||||||
|
actually moves.
|
||||||
|
|
||||||
|
**Two read paths, one result set.** `get()`, metadata-level `find()`, and
|
||||||
|
filter-based `related()` resolve directly through the record layer at any
|
||||||
|
reachable pinned generation — no extra cost beyond scanning the deltas of
|
||||||
|
later commits. Index-accelerated dimensions (semantic/vector search, graph
|
||||||
|
traversal, cursors, aggregation) are served by **at-generation index
|
||||||
|
materialization**: the first such query on a historical `Db` copies the
|
||||||
|
exact at-G record set (live bytes for ids untouched since the pin,
|
||||||
|
before-images for the rest; a final reconciliation pass runs under the
|
||||||
|
commit mutex so transactions racing the copy cannot skew it) into an
|
||||||
|
ephemeral in-memory store and opens a read-only engine over it — the same
|
||||||
|
vector/metadata/graph index classes the live brain uses, sharing the host's
|
||||||
|
embedder and aggregate definitions. The handle is cached on the `Db` and
|
||||||
|
freed by `release()`.
|
||||||
|
|
||||||
|
**Cost, stated plainly:** materialization is O(n at G) time and memory,
|
||||||
|
once per `Db`. That is the open-core price of historical index queries. A
|
||||||
|
native `VersionedIndexProvider` (`isGenerationVisible()` + pins over
|
||||||
|
retained LSM segments) serves the same reads with no rebuild at all — the
|
||||||
|
materializer is the correctness baseline, the provider is the accelerator.
|
||||||
|
|
||||||
|
**The one remaining boundary.** Speculative `with()` overlays throw
|
||||||
|
`SpeculativeOverlayError` for index-accelerated queries and `persist()`:
|
||||||
|
overlay entities carry no embeddings (`with()` never invokes the embedder),
|
||||||
|
so a "full" index query over an overlay would silently exclude the
|
||||||
|
overlay's own entities. Commit with `transact()` to get the full surface.
|
||||||
|
|
||||||
|
**History granularity (Model-B).** EVERY write is its own immutable generation
|
||||||
|
— `transact()` batches AND single-operation `add`/`update`/`remove`/`relate`.
|
||||||
|
Single-ops stage a before-image and are reported by `db.since()`/`asOf()`/
|
||||||
|
`diff()`/`history()` exactly like transacts; a pin always freezes against later
|
||||||
|
writes. `transact()` groups several operations into ONE atomic generation.
|
||||||
|
|
||||||
|
Single-op history durability is **async group-commit**: the live write hits
|
||||||
|
canonical storage immediately (acknowledged), while its before-image is buffered
|
||||||
|
and persisted to disk in one batched fsync on a size/timer trigger (or forced by
|
||||||
|
`flush()`/`close()`/`transact()`/`compactHistory()`). The buffer participates in
|
||||||
|
point-in-time resolution exactly like on-disk generations, so the synchronous
|
||||||
|
`now()` freezes with no forced flush. A hard crash before the flush loses only
|
||||||
|
the buffered *history* of the last window — never live data — and a crash
|
||||||
|
*mid-flush* is recovered by **drop-without-restore** (the partial generation's
|
||||||
|
before-images are discarded, never replayed, because the live write was already
|
||||||
|
acknowledged; restoring them would silently revert it).
|
||||||
|
|
||||||
|
### Pinning, retention, compaction
|
||||||
|
|
||||||
|
- Each live `Db` holds one refcounted pin on its generation (plus a
|
||||||
|
`pin(generation)` on every registered `VersionedIndexProvider`, whose
|
||||||
|
explicit pin lifetime overrides any time-based snapshot retention the
|
||||||
|
provider has).
|
||||||
|
- The constructor **`retention`** knob governs auto-compaction (on `flush()`/
|
||||||
|
`close()`): unset → ADAPTIVE (disk/RAM-pressure byte budget, zero-config;
|
||||||
|
driven by a coordinator's `budgetBytes` or a local `os.freemem` probe) ·
|
||||||
|
`'all'` → unbounded · `{ maxGenerations?, maxAge?, maxBytes? }` → explicit
|
||||||
|
CAPS. `compactHistory({ maxGenerations?, maxAge?, maxBytes? })` reclaims
|
||||||
|
manually on the same caps — the oldest unpinned record-sets are reclaimed
|
||||||
|
while ANY supplied cap is exceeded.
|
||||||
|
- A record-set `N` is reclaimed only when `N` is at or below **every** live
|
||||||
|
pin — deleting `N` can only break readers pinned *below* `N`, because
|
||||||
|
resolution reads before-images from generations strictly greater than the
|
||||||
|
pin. Live pins are ALWAYS exempt, in every retention mode.
|
||||||
|
- The manifest records the **horizon** (highest reclaimed generation).
|
||||||
|
Generations below the horizon are unreachable; `asOf()` on them throws
|
||||||
|
`GenerationCompactedError`. The horizon itself stays reachable, resolved
|
||||||
|
from the record-sets above it. To keep a generation readable forever,
|
||||||
|
`persist()` it first — snapshots are self-contained.
|
||||||
|
|
||||||
|
### Snapshots and restore
|
||||||
|
|
||||||
|
`db.persist(path)` flushes indexes, then cuts the snapshot under the
|
||||||
|
store's commit mutex (no commit, compaction, or counter write can
|
||||||
|
interleave). On filesystem storage it is a **hard-link farm**: every data
|
||||||
|
file is immutable-by-rename, so linking is safe — later rewrites swap
|
||||||
|
inodes and the snapshot keeps the old bytes. The two exceptions are handled
|
||||||
|
explicitly: the append-in-place tx-log is byte-copied, and process-local
|
||||||
|
lock state is excluded. Cross-device targets (and filesystems that refuse
|
||||||
|
links) fall back to per-file byte copies. In-memory stores serialize to the
|
||||||
|
same directory layout, so persisting a memory brain produces a real,
|
||||||
|
durable, loadable store.
|
||||||
|
|
||||||
|
`persist()` requires the view to still be the store's latest generation
|
||||||
|
(a snapshot captures current bytes); a view that history has moved past
|
||||||
|
throws rather than persisting the wrong state.
|
||||||
|
|
||||||
|
`restore(path, { confirm: true })` replaces the store's contents from a
|
||||||
|
snapshot via byte copy (never links — the snapshot stays independent),
|
||||||
|
reloads all adapter-internal derived state, rebuilds all indexes, and
|
||||||
|
floors the generation counter at its pre-restore value so observed
|
||||||
|
generation numbers are never reissued. Live pins do not survive a restore;
|
||||||
|
a warning is logged when any exist.
|
||||||
|
|
||||||
|
### Versioned index providers
|
||||||
|
|
||||||
|
Native index providers may implement the optional 4-method
|
||||||
|
`VersionedIndexProvider` capability (`generation()`,
|
||||||
|
`isGenerationVisible()`, `pin()`, `release()` — BigInt generations at the
|
||||||
|
boundary). The locked consistency model: providers are **post-commit
|
||||||
|
appliers**. The storage-record commit is the source of truth; provider
|
||||||
|
index state is derived. On open, a provider behind the committed watermark
|
||||||
|
replays the gap from storage (or requests a rebuild) — there are no
|
||||||
|
provider rollback hooks, because uncommitted transactions are repaired at
|
||||||
|
the storage layer before any index opens. Speculative `with()` overlays
|
||||||
|
never reach providers.
|
||||||
|
|
||||||
|
## Guarantees (and their proofs)
|
||||||
|
|
||||||
|
Each stated guarantee has a test that proves it, not merely exercises it
|
||||||
|
(`tests/integration/db-mvcc.test.ts`, plus
|
||||||
|
`tests/unit/db/generationStore.test.ts` for the record layer in isolation):
|
||||||
|
|
||||||
|
| Guarantee | Proof |
|
||||||
|
|---|---|
|
||||||
|
| Snapshot isolation: a pinned `Db` reads exactly its pinned state, forever | proof 1 (200 mutations, including deletes, against a pinned view) |
|
||||||
|
| Atomicity: a failing batch applies nothing; generation unchanged | proofs 2a/2b/2c (plan-time failure, injected execution-phase storage failure, `ifRev` conflict) |
|
||||||
|
| Whole-store CAS | proof 3 (`ifAtGeneration` success + conflict with exact expected/actual) |
|
||||||
|
| Snapshot integrity under source mutation (hard-link safety) | proofs 4a/4b/4c |
|
||||||
|
| Compaction never breaks a pinned read; release enables reclaim | proof 5 |
|
||||||
|
| `with()` overlays touch nothing durable | proof 6 |
|
||||||
|
| Generation monotonicity across close/reopen | proof 7 |
|
||||||
|
| Crash before the manifest rename recovers to exact pre-transaction state through the real recovery path | proof 8 (fault injection that skips abort cleanup, exactly as a dead process would) |
|
||||||
|
| Balanced provider pin/release lockstep | proof 9 |
|
||||||
|
|
||||||
|
One deliberate softness: single-operation generation bumps persist the
|
||||||
|
counter coalesced (per write burst), not per write. Durable artifacts —
|
||||||
|
records, manifests, snapshots — always persist the counter synchronously at
|
||||||
|
their own commit points, so a crash inside the coalescing window can lose
|
||||||
|
only counter values that nothing durable ever referenced.
|
||||||
|
|
||||||
|
## Failure modes
|
||||||
|
|
||||||
|
| Failure | Outcome |
|
||||||
|
|---|---|
|
||||||
|
| Crash before staging completes | Partial staging directory > manifest watermark → removed on next open; canonical state untouched. |
|
||||||
|
| Crash after staging, before/during batch execution | Before-images restored on next open; indexes rebuilt; byte-identical pre-transaction state. |
|
||||||
|
| Crash after execution, before manifest rename | Same as above — the rename is the only commit point. |
|
||||||
|
| Crash after manifest rename, before tx-log append | Transaction kept (committed); tx-log misses one advisory line; `asOf(Date)` resolution for that commit falls back to neighboring entries. |
|
||||||
|
| Batch fails mid-execution (no crash) | TransactionManager operation rollback + staging-directory removal + reservation return; generation unchanged. |
|
||||||
|
| `asOf()` below the compaction horizon | `GenerationCompactedError` — explicit, never partial data. |
|
||||||
|
| Index-accelerated query on a `with()` overlay | `SpeculativeOverlayError` — explicit, never silently-incomplete results (overlay entities carry no embeddings). |
|
||||||
|
| `persist()` of a view history has moved past | `GenerationConflictError` — a snapshot captures current bytes; persist before further writes. |
|
||||||
|
| Torn trailing tx-log line (crashed append) | Tolerated; unparseable lines are skipped by readers. |
|
||||||
|
|
||||||
|
## Lineage
|
||||||
|
|
||||||
|
The design is an assembly of well-understood prior art, chosen for being
|
||||||
|
boring where it counts:
|
||||||
|
|
||||||
|
- **Datomic** — the database-as-a-value: an immutable `Db` you query, with
|
||||||
|
`with()` for speculation and reified transaction metadata instead of
|
||||||
|
commit messages.
|
||||||
|
- **LMDB** — reader pins: readers never block writers; a reader's view
|
||||||
|
stays valid because nothing overwrites the pages (here: records) it
|
||||||
|
references; reclamation waits for the last reader.
|
||||||
|
- **LSM trees / Cassandra** — immutable segments make snapshots hard links
|
||||||
|
and make compaction a retention policy instead of a locking problem.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- One mechanism replaces the COW and versioning subsystems (their removal
|
||||||
|
is the companion change to this ADR).
|
||||||
|
- In-place branch switching (`checkout`) is gone by design; the replacement
|
||||||
|
is opening a persisted snapshot as a separate instance — a name→path
|
||||||
|
mapping where a product needs named branches.
|
||||||
|
- Every commit pays O(ids touched) extra writes (before-images + delta +
|
||||||
|
manifest). Single-operation writes pay only an in-memory counter bump
|
||||||
|
with coalesced persistence.
|
||||||
|
- The full query surface works at every reachable pinned generation.
|
||||||
|
Record-path reads (`get`, metadata `find`, filter `related`) are
|
||||||
|
effectively free; index-accelerated historical queries pay a one-time
|
||||||
|
O(n at G) materialization per `Db` on the open-core path (freed on
|
||||||
|
`release()`), and run rebuild-free on a native `VersionedIndexProvider`.
|
||||||
468
docs/BATCHING.md
Normal file
468
docs/BATCHING.md
Normal file
|
|
@ -0,0 +1,468 @@
|
||||||
|
---
|
||||||
|
title: Batch Operations
|
||||||
|
slug: guides/batching
|
||||||
|
public: true
|
||||||
|
category: guides
|
||||||
|
template: guide
|
||||||
|
order: 5
|
||||||
|
description: Eliminate N+1 query patterns with batchGet() and storage-level batch APIs for fast multi-entity reads against filesystem and memory storage.
|
||||||
|
next:
|
||||||
|
- api/reference
|
||||||
|
- guides/find-system
|
||||||
|
---
|
||||||
|
|
||||||
|
# Batch Operations API
|
||||||
|
> **Production-Ready** | Zero N+1 Query Patterns
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Brainy provides batch operations at the storage layer to eliminate N+1 query patterns for VFS operations, relationship queries, and entity retrieval.
|
||||||
|
|
||||||
|
### Problem Solved
|
||||||
|
|
||||||
|
The naive pattern of looping and calling `brain.get(id)` once per item issues sequential reads through the storage layer. Batched APIs collapse that into a single read pass.
|
||||||
|
|
||||||
|
**IMPORTANT:** The batch optimizations apply **ONLY to `getTreeStructure()`** at the VFS layer and the explicit `batchGet()` / `getNounMetadataBatch()` calls — not to `readFile()` or individual `get()` operations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## New Public APIs
|
||||||
|
|
||||||
|
### 1. `brain.batchGet(ids, options?)`
|
||||||
|
|
||||||
|
Batch retrieval of multiple entities (metadata-only by default).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Fetch multiple entities in a single batched operation
|
||||||
|
const ids = ['id1', 'id2', 'id3']
|
||||||
|
const results: Map<string, Entity> = await brain.batchGet(ids)
|
||||||
|
|
||||||
|
// With vectors (falls back to individual gets)
|
||||||
|
const resultsWithVectors = await brain.batchGet(ids, { includeVectors: true })
|
||||||
|
|
||||||
|
// Results map
|
||||||
|
results.get('id1') // → Entity or undefined
|
||||||
|
results.size // → 3 (number of found entities)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
- Memory storage: Instant (parallel reads)
|
||||||
|
- Filesystem storage: Parallel reads, scales with available IOPS
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Loading multiple entities for display
|
||||||
|
- Bulk data export operations
|
||||||
|
- Relationship traversal (fetch all connected entities)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage-Level APIs
|
||||||
|
|
||||||
|
### 2. `storage.getNounMetadataBatch(ids)`
|
||||||
|
|
||||||
|
Batch metadata retrieval with direct O(1) path construction.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const storage = brain.storage as BaseStorage
|
||||||
|
const ids = ['id1', 'id2', 'id3']
|
||||||
|
|
||||||
|
const metadataMap: Map<string, NounMetadata> = await storage.getNounMetadataBatch(ids)
|
||||||
|
|
||||||
|
for (const [id, metadata] of metadataMap) {
|
||||||
|
console.log(metadata.noun) // Type: 'document', 'person', etc.
|
||||||
|
console.log(metadata.data) // Entity data
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ✅ Direct O(1) path construction from ID (no type lookup needed!)
|
||||||
|
- ✅ Sharding preservation (all paths include `{shard}/{id}`)
|
||||||
|
- ✅ Write-cache coherent (read-after-write consistency)
|
||||||
|
- ✅ O(1) path construction — eliminates the per-entity type search of the old type-first layout
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
- Constant-time path construction per ID — no type-cache misses
|
||||||
|
- Filesystem: parallel reads bounded by IOPS
|
||||||
|
- No type search delays — every ID maps directly to storage path
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. `storage.getVerbsBySourceBatch(sourceIds, verbType?)`
|
||||||
|
|
||||||
|
Batch relationship queries by source entity IDs.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const storage = brain.storage as BaseStorage
|
||||||
|
|
||||||
|
// Get all relationships from multiple sources
|
||||||
|
const results: Map<string, GraphVerb[]> = await storage.getVerbsBySourceBatch([
|
||||||
|
'person1',
|
||||||
|
'person2'
|
||||||
|
])
|
||||||
|
|
||||||
|
// Filter by verb type
|
||||||
|
const createsResults = await storage.getVerbsBySourceBatch(
|
||||||
|
['person1', 'person2'],
|
||||||
|
'creates'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process results
|
||||||
|
for (const [sourceId, verbs] of results) {
|
||||||
|
console.log(`${sourceId} has ${verbs.length} relationships`)
|
||||||
|
verbs.forEach(verb => {
|
||||||
|
console.log(` → ${verb.verb} → ${verb.targetId}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Social graph traversal (fetch all connections for multiple users)
|
||||||
|
- Knowledge graph queries (find all relationships of specific type)
|
||||||
|
- Bulk export of relationship data
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
- Memory storage: single in-memory pass over the metadata index
|
||||||
|
- Filesystem storage: parallel reads through the metadata index
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## VFS Integration
|
||||||
|
|
||||||
|
VFS operations automatically use batch APIs for maximum performance.
|
||||||
|
|
||||||
|
### Directory Traversal
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Tree traversal uses batched reads under the hood
|
||||||
|
const tree = await brain.vfs.getTreeStructure('/my-dir')
|
||||||
|
// ✅ PathResolver.getChildren() uses brain.batchGet() internally
|
||||||
|
// ✅ Parallel traversal of directories at the same tree level
|
||||||
|
// ✅ 2-3 batched calls instead of 22 sequential calls
|
||||||
|
```
|
||||||
|
|
||||||
|
**Architecture:**
|
||||||
|
|
||||||
|
```
|
||||||
|
VFS.getTreeStructure()
|
||||||
|
↓ PARALLEL (breadth-first traversal)
|
||||||
|
→ PathResolver.getChildren() [all dirs at level processed in parallel]
|
||||||
|
↓ BATCHED
|
||||||
|
→ brain.batchGet(childIds) [1 call instead of N]
|
||||||
|
↓ BATCHED
|
||||||
|
→ storage.getNounMetadataBatch(ids) [1 call instead of N]
|
||||||
|
↓ ADAPTER
|
||||||
|
→ Filesystem: Promise.all() parallel reads
|
||||||
|
→ Memory: Promise.all() parallel reads
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Features Compatibility
|
||||||
|
|
||||||
|
### ✅ ID-First Storage Architecture
|
||||||
|
|
||||||
|
All batch operations use direct ID-first paths - no type lookup needed!
|
||||||
|
|
||||||
|
**ID-First Path Structure:**
|
||||||
|
```
|
||||||
|
entities/nouns/{SHARD}/{ID}/metadata.json
|
||||||
|
entities/verbs/{SHARD}/{ID}/metadata.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direct O(1) Path Construction:**
|
||||||
|
```typescript
|
||||||
|
// Every ID maps directly to exactly ONE path - O(1), no type search
|
||||||
|
const id = 'abc-123'
|
||||||
|
const shard = getShardIdFromUuid(id) // → 'ab' (first 2 hex chars)
|
||||||
|
const path = `entities/nouns/${shard}/${id}/metadata.json`
|
||||||
|
|
||||||
|
// No type cache needed!
|
||||||
|
// No type search needed!
|
||||||
|
// No multi-type fallback needed!
|
||||||
|
// Just pure O(1) lookup!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- **O(1)** path lookups (eliminates the 42-type sequential search the old type-first layout required)
|
||||||
|
- **Simpler code** - removed 500+ lines of type cache complexity
|
||||||
|
- **Scalable** - works at large scale without type tracking overhead
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ Sharding
|
||||||
|
|
||||||
|
All batch paths include shard IDs calculated via `getShardIdFromUuid(id)`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const id = 'a3c4e5f7-...'
|
||||||
|
const shard = getShardIdFromUuid(id) // → 'a3' (first 2 hex chars)
|
||||||
|
const path = `entities/nouns/${shard}/${id}/metadata.json`
|
||||||
|
```
|
||||||
|
|
||||||
|
**Distribution:** 256 shards (00-ff) for optimal load distribution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✅ Generational MVCC (8.0)
|
||||||
|
|
||||||
|
Batch reads always serve the **live** generation through the fast paths
|
||||||
|
shown above. Point-in-time reads go through the Db API instead: a pinned
|
||||||
|
`Db` (`brain.now()`, `brain.asOf()`) resolves changed ids from immutable
|
||||||
|
generation records and unchanged ids from the same live paths batch reads
|
||||||
|
use — see the [consistency model](concepts/consistency-model.md).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const db = brain.now() // pinned view
|
||||||
|
const entity = await db.get(id) // correct at the pinned generation
|
||||||
|
const results = await brain.batchGet(ids) // live state, batched
|
||||||
|
await db.release()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why Batching Is Faster
|
||||||
|
|
||||||
|
Batching's advantage is structural, not a fixed multiplier (the actual speedup
|
||||||
|
depends on storage backend, IOPS, and batch size):
|
||||||
|
|
||||||
|
- **N+1 elimination** — N sequential reads collapse into a single parallel pass
|
||||||
|
(`Promise.all` over the batch).
|
||||||
|
- **O(1) path construction** — every ID maps directly to one storage path, with
|
||||||
|
no per-type cache lookup.
|
||||||
|
- **One metadata round-trip** — relationship batches fetch all sources' metadata
|
||||||
|
in a single pass instead of one query per source.
|
||||||
|
|
||||||
|
The integration test `tests/integration/storage-batch-operations.test.ts`
|
||||||
|
exercises batch vs. individual reads and asserts that batch retrieval is not
|
||||||
|
slower than the per-entity loop for large batches; it does not pin a specific
|
||||||
|
multiplier, since that is hardware- and IOPS-dependent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Partial Batch Failures
|
||||||
|
|
||||||
|
Batch operations gracefully handle missing or invalid entities:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const validId = 'abc-123-...'
|
||||||
|
const invalidIds = [
|
||||||
|
'11111111-1111-1111-1111-111111111111',
|
||||||
|
'22222222-2222-2222-2222-222222222222'
|
||||||
|
]
|
||||||
|
|
||||||
|
const results = await brain.batchGet([validId, ...invalidIds])
|
||||||
|
|
||||||
|
results.size // → 1 (only valid entity)
|
||||||
|
results.has(validId) // → true
|
||||||
|
results.has(invalidIds[0]) // → false (silently skipped)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- Invalid UUIDs: Silently skipped (not included in results)
|
||||||
|
- Missing entities: Silently skipped (not included in results)
|
||||||
|
- Storage errors: Logged, entity excluded from results
|
||||||
|
- No exceptions thrown for partial failures
|
||||||
|
|
||||||
|
### Empty Batches
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const results = await brain.batchGet([])
|
||||||
|
results.size // → 0 (empty map)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Duplicate IDs
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const results = await brain.batchGet(['id1', 'id1', 'id1'])
|
||||||
|
results.size // → 1 (deduplicated automatically)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
### From Individual Gets
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```typescript
|
||||||
|
const entities = []
|
||||||
|
for (const id of ids) {
|
||||||
|
const entity = await brain.get(id)
|
||||||
|
if (entity) entities.push(entity)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```typescript
|
||||||
|
const results = await brain.batchGet(ids)
|
||||||
|
const entities = Array.from(results.values())
|
||||||
|
```
|
||||||
|
|
||||||
|
**Performance Gain:** Replaces N sequential reads with a single batched pass — no fixed multiplier, it scales with storage IOPS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### From Individual Relationship Queries
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```typescript
|
||||||
|
const allVerbs = []
|
||||||
|
for (const sourceId of sourceIds) {
|
||||||
|
const verbs = await brain.related({ from: sourceId })
|
||||||
|
allVerbs.push(...verbs)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```typescript
|
||||||
|
const storage = brain.storage as BaseStorage
|
||||||
|
const results = await storage.getVerbsBySourceBatch(sourceIds)
|
||||||
|
|
||||||
|
const allVerbs = []
|
||||||
|
for (const verbs of results.values()) {
|
||||||
|
allVerbs.push(...verbs)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Performance Gain:** One batched metadata fetch instead of one query per source entity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. **Use Batching for Multiple Entity Operations**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ GOOD: Batch fetch
|
||||||
|
const results = await brain.batchGet(ids)
|
||||||
|
|
||||||
|
// ❌ BAD: Individual gets in loop
|
||||||
|
for (const id of ids) {
|
||||||
|
await brain.get(id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Batch Size Recommendations**
|
||||||
|
|
||||||
|
| Storage | Optimal Batch Size | Max Batch Size |
|
||||||
|
|---------|--------------------|----------------|
|
||||||
|
| **Memory** | Unlimited | Unlimited |
|
||||||
|
| **Filesystem** | 100-500 | 1000 |
|
||||||
|
|
||||||
|
**Guideline:** For batches >1000, split into chunks of 500-1000.
|
||||||
|
|
||||||
|
### 3. **Metadata-Only by Default**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Default: Metadata-only (fast)
|
||||||
|
const results = await brain.batchGet(ids) // No vectors
|
||||||
|
|
||||||
|
// Only load vectors if needed
|
||||||
|
const withVectors = await brain.batchGet(ids, { includeVectors: true })
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. **Error Handling**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Batch operations never throw for missing entities
|
||||||
|
const results = await brain.batchGet(ids)
|
||||||
|
|
||||||
|
// Check results
|
||||||
|
for (const id of ids) {
|
||||||
|
if (results.has(id)) {
|
||||||
|
// Entity exists
|
||||||
|
const entity = results.get(id)
|
||||||
|
} else {
|
||||||
|
// Entity missing (not an error)
|
||||||
|
console.log(`Entity ${id} not found`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Comprehensive test coverage in `tests/integration/storage-batch-operations.test.ts`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx vitest run tests/integration/storage-batch-operations.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test Coverage:**
|
||||||
|
- ✅ brain.batchGet() high-level API
|
||||||
|
- ✅ storage.getNounMetadataBatch() with ID-first paths
|
||||||
|
- ✅ COW integration (branch isolation, inheritance)
|
||||||
|
- ✅ storage.getVerbsBySourceBatch() relationship queries
|
||||||
|
- ✅ VFS integration (PathResolver.getChildren())
|
||||||
|
- ✅ Performance benchmarks (N+1 elimination)
|
||||||
|
- ✅ Error handling (partial failures, empty batches, duplicates)
|
||||||
|
- ✅ ID-first storage verification
|
||||||
|
- ✅ Sharding preservation
|
||||||
|
|
||||||
|
**Results:** 23 tests passing ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Architecture Layers
|
||||||
|
|
||||||
|
```
|
||||||
|
User Code (brain.batchGet)
|
||||||
|
↓
|
||||||
|
High-Level API (src/brainy.ts)
|
||||||
|
↓
|
||||||
|
Storage Layer (src/storage/baseStorage.ts)
|
||||||
|
↓
|
||||||
|
Adapter Layer (readBatchFromAdapter)
|
||||||
|
↓
|
||||||
|
Storage Adapter (FileSystemStorage / MemoryStorage)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parallel Reads
|
||||||
|
|
||||||
|
Both shipped adapters fall back to `Promise.all` over individual reads:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BaseStorage.readBatchFromAdapter()
|
||||||
|
return await Promise.all(resolvedPaths.map(path => this.read(path)))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Shipped Adapters:**
|
||||||
|
- MemoryStorage
|
||||||
|
- FileSystemStorage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Summary
|
||||||
|
|
||||||
|
- `brain.batchGet(ids, options?)` - High-level batch entity retrieval
|
||||||
|
- `storage.getNounMetadataBatch(ids)` - Storage-level metadata batch
|
||||||
|
- `storage.getVerbsBySourceBatch(sourceIds, verbType?)` - Batch relationship queries
|
||||||
|
|
||||||
|
**Performance Improvements:**
|
||||||
|
- VFS operations: single batched pass instead of N sequential reads
|
||||||
|
- Entity retrieval: N+1 reads collapsed into one batched pass
|
||||||
|
- Zero N+1 query patterns
|
||||||
|
|
||||||
|
**Compatibility:**
|
||||||
|
- ✅ ID-first storage
|
||||||
|
- ✅ Sharding (256 shards)
|
||||||
|
- ✅ Generational MVCC — batch reads serve the live generation; pinned `Db` views serve the past
|
||||||
|
- ✅ All indexes respected (vector, metadata, graph adjacency)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Documentation:** `/docs/BATCHING.md`, `/docs/PERFORMANCE.md`
|
||||||
|
- **Tests:** `/tests/integration/storage-batch-operations.test.ts`
|
||||||
|
- **Issues:** https://github.com/soulcraft/brainy/issues
|
||||||
|
- **Discussions:** https://github.com/soulcraft/brainy/discussions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Built with ❤️ for enterprise-scale knowledge graphs**
|
||||||
|
|
@ -1,168 +0,0 @@
|
||||||
# Brainy Compatibility Across Environments
|
|
||||||
|
|
||||||
This document outlines Brainy's compatibility across different JavaScript environments and how it adapts to each environment.
|
|
||||||
|
|
||||||
## Environment Detection
|
|
||||||
|
|
||||||
Brainy automatically detects the environment it's running in:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Method to detect the current environment
|
|
||||||
function detectEnvironment() {
|
|
||||||
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
||||||
return 'BROWSER';
|
|
||||||
} else if (typeof self !== 'undefined' && typeof window === 'undefined') {
|
|
||||||
// In a worker environment, self is defined but window is not
|
|
||||||
return 'WORKER';
|
|
||||||
} else {
|
|
||||||
return 'NODE';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cache Size Detection
|
|
||||||
|
|
||||||
Brainy's cache manager adapts its cache size based on the detected environment:
|
|
||||||
|
|
||||||
### Node.js Environment
|
|
||||||
|
|
||||||
In Node.js, Brainy uses fixed default memory values to ensure compatibility with ES modules:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Use conservative defaults that don't require OS module
|
|
||||||
// These values are reasonable for most systems
|
|
||||||
const estimatedTotalMemory = 8 * 1024 * 1024 * 1024; // Assume 8GB total
|
|
||||||
const estimatedFreeMemory = 4 * 1024 * 1024 * 1024; // Assume 4GB free
|
|
||||||
```
|
|
||||||
|
|
||||||
This approach ensures compatibility with both CommonJS and ES modules without requiring dynamic imports or the `os` module.
|
|
||||||
|
|
||||||
### Browser Environment
|
|
||||||
|
|
||||||
In browsers, Brainy uses the `navigator.deviceMemory` API when available:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (environment === 'BROWSER' && navigator.deviceMemory) {
|
|
||||||
// Base entries per GB
|
|
||||||
let entriesPerGB = 500;
|
|
||||||
|
|
||||||
// Adjust based on operating mode and dataset size
|
|
||||||
if (isReadOnly) {
|
|
||||||
entriesPerGB = 800; // More aggressive caching in read-only mode
|
|
||||||
|
|
||||||
if (isLargeDataset) {
|
|
||||||
entriesPerGB = 1000; // Even more aggressive for large datasets
|
|
||||||
}
|
|
||||||
} else if (isLargeDataset) {
|
|
||||||
entriesPerGB = 600; // Slightly more aggressive for large datasets
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate based on device memory
|
|
||||||
const browserCacheSize = Math.max(navigator.deviceMemory * entriesPerGB, 1000);
|
|
||||||
|
|
||||||
// If we know the total dataset size, cap at a reasonable percentage
|
|
||||||
if (totalItems > 0) {
|
|
||||||
// In read-only mode, we can cache a larger percentage
|
|
||||||
const maxPercentage = isReadOnly ? 0.4 : 0.25;
|
|
||||||
const maxItems = Math.ceil(totalItems * maxPercentage);
|
|
||||||
|
|
||||||
// Return the smaller of the two to avoid excessive memory usage
|
|
||||||
return Math.min(browserCacheSize, maxItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
return browserCacheSize;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If `navigator.deviceMemory` is not available, it falls back to conservative defaults.
|
|
||||||
|
|
||||||
### Worker Environment
|
|
||||||
|
|
||||||
For Web Workers, Brainy uses a more conservative approach:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (environment === 'WORKER') {
|
|
||||||
// Workers typically have limited memory, be conservative
|
|
||||||
return isReadOnly ? 2000 : 1000;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Storage Type Detection
|
|
||||||
|
|
||||||
Brainy also adapts its storage strategy based on the environment:
|
|
||||||
|
|
||||||
### Warm Storage
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Method to detect the appropriate warm storage type
|
|
||||||
function detectWarmStorageType() {
|
|
||||||
if (environment === 'BROWSER') {
|
|
||||||
// Use OPFS if available, otherwise use memory
|
|
||||||
if ('storage' in navigator && 'getDirectory' in navigator.storage) {
|
|
||||||
return 'OPFS';
|
|
||||||
}
|
|
||||||
return 'MEMORY';
|
|
||||||
} else if (environment === 'WORKER') {
|
|
||||||
// Use OPFS if available, otherwise use memory
|
|
||||||
if ('storage' in self && 'getDirectory' in self.storage) {
|
|
||||||
return 'OPFS';
|
|
||||||
}
|
|
||||||
return 'MEMORY';
|
|
||||||
} else {
|
|
||||||
// In Node.js, use filesystem
|
|
||||||
return 'FILESYSTEM';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cold Storage
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Method to detect the appropriate cold storage type
|
|
||||||
function detectColdStorageType() {
|
|
||||||
if (environment === 'BROWSER') {
|
|
||||||
// Use OPFS if available, otherwise use memory
|
|
||||||
if ('storage' in navigator && 'getDirectory' in navigator.storage) {
|
|
||||||
return 'OPFS';
|
|
||||||
}
|
|
||||||
return 'MEMORY';
|
|
||||||
} else if (environment === 'WORKER') {
|
|
||||||
// Use OPFS if available, otherwise use memory
|
|
||||||
if ('storage' in self && 'getDirectory' in self.storage) {
|
|
||||||
return 'OPFS';
|
|
||||||
}
|
|
||||||
return 'MEMORY';
|
|
||||||
} else {
|
|
||||||
// In Node.js, use S3 if configured, otherwise filesystem
|
|
||||||
return 'S3';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Compatibility Summary
|
|
||||||
|
|
||||||
| Feature | Node.js | Browser | Web Worker |
|
|
||||||
|---------|---------|---------|------------|
|
|
||||||
| Environment Detection | ✅ | ✅ | ✅ |
|
|
||||||
| Cache Size Detection | ✅ (Fixed defaults) | ✅ (deviceMemory API) | ✅ (Conservative) |
|
|
||||||
| Warm Storage | Filesystem | OPFS/Memory | OPFS/Memory |
|
|
||||||
| Cold Storage | S3/Filesystem | OPFS/Memory | OPFS/Memory |
|
|
||||||
| ES Module Support | ✅ | ✅ | ✅ |
|
|
||||||
|
|
||||||
## Recommendations
|
|
||||||
|
|
||||||
1. **Node.js Applications**:
|
|
||||||
- No special configuration needed
|
|
||||||
- Works with both CommonJS and ES modules
|
|
||||||
|
|
||||||
2. **Browser Applications**:
|
|
||||||
- For optimal performance, use in browsers that support the `navigator.deviceMemory` API
|
|
||||||
- Falls back gracefully in older browsers
|
|
||||||
|
|
||||||
3. **Worker Applications**:
|
|
||||||
- Works in both dedicated and shared workers
|
|
||||||
- Uses conservative cache sizes to avoid memory issues
|
|
||||||
|
|
||||||
4. **Memory-Constrained Environments**:
|
|
||||||
- Consider setting a smaller `hotCacheMaxSize` in the options
|
|
||||||
- Example: `new BrainyData({ hotCacheMaxSize: 500 })`
|
|
||||||
271
docs/DATA_MODEL.md
Normal file
271
docs/DATA_MODEL.md
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
# Data Model
|
||||||
|
|
||||||
|
> How Brainy stores entities and relationships, and the critical distinction between `data` and `metadata`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entity (Noun)
|
||||||
|
|
||||||
|
An entity is the fundamental data unit in Brainy. Every entity has:
|
||||||
|
|
||||||
|
| Field | Type | Indexed | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `id` | `string` | Primary key | UUID v4 (auto-generated or custom) |
|
||||||
|
| `data` | `any` | **HNSW vector index** | Content used for semantic/hybrid search. Strings auto-embed. |
|
||||||
|
| `metadata` | `object` | **MetadataIndex** | Structured queryable fields (tags, dates, flags, etc.) |
|
||||||
|
| `type` | `NounType` | MetadataIndex (as `noun`) | Entity type classification |
|
||||||
|
| `vector` | `number[]` | HNSW | 384-dim embedding (auto-computed from `data` or user-provided) |
|
||||||
|
| `confidence` | `number` | MetadataIndex | Type classification confidence (0-1) |
|
||||||
|
| `weight` | `number` | MetadataIndex | Entity importance/salience (0-1) |
|
||||||
|
| `service` | `string` | MetadataIndex | Multi-tenancy identifier |
|
||||||
|
| `createdAt` | `number` | MetadataIndex | Creation timestamp (ms since epoch) |
|
||||||
|
| `updatedAt` | `number` | MetadataIndex | Last update timestamp (ms since epoch) |
|
||||||
|
| `createdBy` | `object` | MetadataIndex | Source augmentation info |
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const id = await brain.add({
|
||||||
|
data: 'John Smith is a software engineer at Acme Corp', // → embedded into vector
|
||||||
|
type: NounType.Person,
|
||||||
|
metadata: { // → indexed, queryable via where filters
|
||||||
|
role: 'engineer',
|
||||||
|
department: 'backend',
|
||||||
|
yearsExperience: 8
|
||||||
|
},
|
||||||
|
confidence: 0.95,
|
||||||
|
weight: 0.7
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relationship (Verb)
|
||||||
|
|
||||||
|
A relationship is a typed, directed edge connecting two entities.
|
||||||
|
|
||||||
|
| Field | Type | Indexed | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `id` | `string` | Primary key | UUID v4 (auto-generated) |
|
||||||
|
| `from` | `string` | **GraphAdjacencyIndex** | Source entity ID |
|
||||||
|
| `to` | `string` | **GraphAdjacencyIndex** | Target entity ID |
|
||||||
|
| `type` | `VerbType` | GraphAdjacencyIndex (as `verb`) | Relationship type classification |
|
||||||
|
| `data` | `any` | — | Opaque content (overrides auto-computed vector if provided) |
|
||||||
|
| `metadata` | `object` | — | Structured fields on the edge |
|
||||||
|
| `weight` | `number` | — | Connection strength (0-1, default: 1.0) |
|
||||||
|
| `confidence` | `number` | — | Relationship certainty (0-1) |
|
||||||
|
| `evidence` | `RelationEvidence` | — | Why this relationship was detected |
|
||||||
|
| `createdAt` | `number` | — | Creation timestamp (ms since epoch) |
|
||||||
|
| `updatedAt` | `number` | — | Last update timestamp (ms since epoch) |
|
||||||
|
| `service` | `string` | — | Multi-tenancy identifier |
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const relId = await brain.relate({
|
||||||
|
from: personId,
|
||||||
|
to: projectId,
|
||||||
|
type: VerbType.WorksOn,
|
||||||
|
data: 'Lead engineer on the AI module', // Optional: content for this edge
|
||||||
|
metadata: { // Optional: queryable edge fields
|
||||||
|
role: 'lead',
|
||||||
|
startDate: '2024-01-15'
|
||||||
|
},
|
||||||
|
weight: 0.9
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data vs Metadata
|
||||||
|
|
||||||
|
This is the most important concept in Brainy's storage model:
|
||||||
|
|
||||||
|
### `data` — Content for Semantic Search
|
||||||
|
|
||||||
|
- Embedded into a 384-dimensional vector via the WASM embedding engine
|
||||||
|
- Searchable via **semantic similarity** (HNSW vector index) and **hybrid text+semantic** search
|
||||||
|
- Queried by passing `query` to `find()`:
|
||||||
|
```typescript
|
||||||
|
brain.find({ query: 'machine learning algorithms' })
|
||||||
|
```
|
||||||
|
- **NOT** indexed by MetadataIndex — you cannot use `where` filters on `data`
|
||||||
|
- Stored opaquely: strings, objects, numbers — anything goes
|
||||||
|
|
||||||
|
### `metadata` — Structured Queryable Fields
|
||||||
|
|
||||||
|
- Indexed by MetadataIndex with O(1) lookups per field
|
||||||
|
- Queryable via `where` filters using [BFO operators](./QUERY_OPERATORS.md):
|
||||||
|
```typescript
|
||||||
|
brain.find({
|
||||||
|
where: {
|
||||||
|
department: 'engineering',
|
||||||
|
yearsExperience: { greaterThan: 5 },
|
||||||
|
tags: { contains: 'senior' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
- **NOT** used for vector/semantic search
|
||||||
|
- Must be a flat or lightly nested object
|
||||||
|
|
||||||
|
### Quick Reference
|
||||||
|
|
||||||
|
| | `data` | `metadata` |
|
||||||
|
|---|---|---|
|
||||||
|
| **Purpose** | Content for embedding / semantic search | Structured fields for filtering |
|
||||||
|
| **Searched by** | `find({ query })` — vector similarity, hybrid text+semantic | `find({ where })` — exact, range, set operators |
|
||||||
|
| **Indexed by** | HNSW vector index | MetadataIndex |
|
||||||
|
| **Queryable with operators?** | No | Yes (`equals`, `greaterThan`, `oneOf`, etc.) |
|
||||||
|
| **Auto-embedded?** | Yes (strings → 384-dim vectors) | No |
|
||||||
|
| **Typical content** | Text descriptions, document content | Tags, dates, status flags, categories, numeric fields |
|
||||||
|
|
||||||
|
### Common Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Add an article
|
||||||
|
await brain.add({
|
||||||
|
data: 'A deep dive into transformer architectures and attention mechanisms',
|
||||||
|
type: NounType.Document,
|
||||||
|
metadata: {
|
||||||
|
title: 'Transformer Deep Dive',
|
||||||
|
author: 'Dr. Chen',
|
||||||
|
publishedYear: 2024,
|
||||||
|
tags: ['AI', 'transformers', 'NLP'],
|
||||||
|
status: 'published'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Search by content (semantic — searches data)
|
||||||
|
const results = await brain.find({ query: 'neural network attention' })
|
||||||
|
|
||||||
|
// Filter by fields (exact — queries metadata)
|
||||||
|
const recent = await brain.find({
|
||||||
|
where: {
|
||||||
|
publishedYear: { greaterThan: 2023 },
|
||||||
|
status: 'published'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Combine both (Triple Intelligence)
|
||||||
|
const precise = await brain.find({
|
||||||
|
query: 'attention mechanisms', // Semantic search on data
|
||||||
|
where: { author: 'Dr. Chen' }, // Metadata filter
|
||||||
|
connected: { from: authorId, depth: 1 } // Graph traversal
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage Field Naming
|
||||||
|
|
||||||
|
Internally, Brainy uses different field names in storage vs the public API:
|
||||||
|
|
||||||
|
| Public API (Entity/Relation) | Storage (metadata object) | Notes |
|
||||||
|
|------------------------------|--------------------------|-------|
|
||||||
|
| `type` | `noun` | Entity type stored as `noun` |
|
||||||
|
| `from` | `sourceId` | Relationship source |
|
||||||
|
| `to` | `targetId` | Relationship target |
|
||||||
|
| `type` (on Relation) | `verb` | Relationship type stored as `verb` |
|
||||||
|
|
||||||
|
When querying with `find()`, you can use:
|
||||||
|
- `type` parameter (convenience alias, equivalent to `where.noun`)
|
||||||
|
- `where.noun` directly
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// These are equivalent:
|
||||||
|
brain.find({ type: NounType.Person })
|
||||||
|
brain.find({ where: { noun: NounType.Person } })
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Standard Metadata Fields
|
||||||
|
|
||||||
|
When you add an entity, Brainy stores these standard fields in the metadata object alongside your custom fields:
|
||||||
|
|
||||||
|
| Field | Set By | Description |
|
||||||
|
|-------|--------|-------------|
|
||||||
|
| `noun` | System | Entity type (NounType enum value) |
|
||||||
|
| `subtype` | User | Per-NounType sub-classification (e.g. `'employee'`, `'invoice'`, `'milestone'`). Flat string, no hierarchy. Indexed on the fast path and rolled into per-NounType statistics. |
|
||||||
|
| `data` | System | The raw `data` value (stored opaquely) |
|
||||||
|
| `createdAt` | System | Creation timestamp |
|
||||||
|
| `updatedAt` | System | Last update timestamp |
|
||||||
|
| `confidence` | User | Type classification confidence |
|
||||||
|
| `weight` | User | Entity importance |
|
||||||
|
| `service` | User | Multi-tenancy identifier |
|
||||||
|
| `createdBy` | User/System | Source augmentation |
|
||||||
|
|
||||||
|
On read, these standard fields are extracted to top-level Entity properties. The `metadata` field on the returned Entity contains **only your custom fields**.
|
||||||
|
|
||||||
|
### Subtype — sub-classification within a NounType
|
||||||
|
|
||||||
|
`type` (NounType) is a stable 42-value enum. `subtype` is the consumer-chosen string vocabulary *within* a type:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// A Person who is an employee:
|
||||||
|
await brain.add({
|
||||||
|
data: 'Avery Brooks — runs the AI lab',
|
||||||
|
type: NounType.Person,
|
||||||
|
subtype: 'employee',
|
||||||
|
metadata: { department: 'ai-lab' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// A Document that is an invoice:
|
||||||
|
await brain.add({
|
||||||
|
data: 'INV-2026-001',
|
||||||
|
type: NounType.Document,
|
||||||
|
subtype: 'invoice',
|
||||||
|
metadata: { amount: 1500 }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`subtype` lives at the **top level** — NOT inside `metadata`, NOT inside `data`. That's how `find({ type, subtype })` routes through the standard-field fast path (column-store hit) instead of the metadata fallback. See **[Subtypes & Facets](./guides/subtypes-and-facets.md)** for the full guide including `trackField()` and `migrateField()`.
|
||||||
|
|
||||||
|
### Subtype — sub-classification within a VerbType (7.30+)
|
||||||
|
|
||||||
|
Relationships are first-class citizens too. Every verb (`VerbType`) gets the same `subtype` primitive — a `ReportsTo` relationship might carry `subtype: 'direct'` vs `'dotted-line'`; a `RelatedTo` edge might carry `'spouse'` / `'sibling'` / `'colleague'`. Same shape as the noun side: flat string, no hierarchy, top-level standard field on `HNSWVerbWithMetadata` and on the public `Relation<T>`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
await brain.relate({
|
||||||
|
from: ceoId,
|
||||||
|
to: vpId,
|
||||||
|
type: VerbType.ReportsTo,
|
||||||
|
subtype: 'direct', // top-level standard field
|
||||||
|
metadata: { since: '2025-Q1' } // user-custom fields stay in metadata
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Fast-path filter on the verb side:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const direct = await brain.related({
|
||||||
|
from: ceoId,
|
||||||
|
type: VerbType.ReportsTo,
|
||||||
|
subtype: 'direct'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The verb-side rollup at `_system/verb-subtype-statistics.json` mirrors the noun-side `_system/subtype-statistics.json` — same shape, same self-heal machinery. Per-VerbType-per-subtype counts are O(1) via `brain.counts.byRelationshipSubtype()`.
|
||||||
|
|
||||||
|
Verbs and nouns now have full capability parity — every API on the noun side has a verb-side mirror, including the new `brain.updateRelation()` (which closed a pre-7.30 gap where relationships had no update path).
|
||||||
|
|
||||||
|
### Standard verb fields
|
||||||
|
|
||||||
|
The verb-side equivalent of `STANDARD_ENTITY_FIELDS` is `STANDARD_VERB_FIELDS`, exported from `src/coreTypes.ts`. Verb-specific standard fields:
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|---|---|
|
||||||
|
| `verb` | The VerbType enum value |
|
||||||
|
| `sourceId` / `targetId` | The two endpoints of the relationship |
|
||||||
|
| `subtype` | Sub-classification within the VerbType (7.30+) |
|
||||||
|
| `confidence`, `weight`, `createdAt`, `updatedAt`, `service`, `createdBy`, `data` | Same semantics as the noun-side standard fields |
|
||||||
|
|
||||||
|
The companion `resolveVerbField(verb, field)` helper resolves field paths the same way `resolveEntityField` does for nouns: standard fields first, metadata fallback for everything else.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [API Reference](./api/README.md) — Complete API documentation
|
||||||
|
- [Query Operators](./QUERY_OPERATORS.md) — All BFO operators with examples
|
||||||
|
- [Find System](./FIND_SYSTEM.md) — Natural language find() details
|
||||||
1166
docs/DEVELOPER_LEARNING_PATH.md
Normal file
1166
docs/DEVELOPER_LEARNING_PATH.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,122 +0,0 @@
|
||||||
# Documentation Organization
|
|
||||||
|
|
||||||
This document explains the new documentation structure and workflow for managing markdown files in the Brainy project.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The documentation has been reorganized from 29 scattered markdown files at the root level to a clean, organized structure with only essential files at the root and categorized documentation in the `docs/` directory.
|
|
||||||
|
|
||||||
## New Structure
|
|
||||||
|
|
||||||
### Root Level Files (GitHub/NPM Standards)
|
|
||||||
- `README.md` - Main project documentation
|
|
||||||
- `CONTRIBUTING.md` - Contribution guidelines
|
|
||||||
- `CODE_OF_CONDUCT.md` - Community standards
|
|
||||||
- `CHANGELOG.md` - Version history and release notes
|
|
||||||
|
|
||||||
### Documentation Directory Structure
|
|
||||||
```
|
|
||||||
docs/
|
|
||||||
├── technical/ # Technical documentation and analysis
|
|
||||||
│ ├── CONCURRENCY_ANALYSIS.md
|
|
||||||
│ ├── STORAGE_CONCURRENCY_ANALYSIS.md
|
|
||||||
│ ├── THREADING.md
|
|
||||||
│ ├── STATISTICS.md
|
|
||||||
│ ├── TESTING.md
|
|
||||||
│ ├── VITEST_IMPROVEMENTS.md
|
|
||||||
│ ├── REALTIME_UPDATES.md
|
|
||||||
│ ├── METADATA_HANDLING.md
|
|
||||||
│ ├── VECTOR_DIMENSION_STANDARDIZATION.md
|
|
||||||
│ ├── USE_MODEL_LOADING_EXPLANATION.md
|
|
||||||
│ ├── STORAGE_TESTING.md
|
|
||||||
│ ├── TECHNICAL_GUIDES.md
|
|
||||||
│ ├── ENVIRONMENT_TESTING.md
|
|
||||||
│ └── SCALING_STRATEGY.md
|
|
||||||
├── development/ # Development and contributor documentation
|
|
||||||
│ ├── DEVELOPERS.md
|
|
||||||
│ ├── DOCUMENTATION_STANDARDS.md
|
|
||||||
│ ├── MARKDOWN_CONVENTIONS.md
|
|
||||||
│ ├── EXPECTED_TEST_MESSAGES.md
|
|
||||||
│ └── PRETTY_TEST_REPORTER.md
|
|
||||||
└── guides/ # User guides and migration documentation
|
|
||||||
├── cache-configuration.md
|
|
||||||
├── hnsw-field-search.md
|
|
||||||
├── json-document-search.md
|
|
||||||
├── model-management.md
|
|
||||||
├── optional-model-bundling.md
|
|
||||||
├── production-migration-guide.md
|
|
||||||
└── service-identification.md
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## CHANGELOG.md Management
|
|
||||||
|
|
||||||
### Automated Workflow
|
|
||||||
|
|
||||||
The project now uses automated changelog management:
|
|
||||||
|
|
||||||
1. **Adding Changes**: Add entries to the `[Unreleased]` section in `CHANGELOG.md`
|
|
||||||
2. **Version Bumping**: Use npm scripts that automatically update the changelog:
|
|
||||||
- `npm run version:patch` - Patch version bump + changelog update
|
|
||||||
- `npm run version:minor` - Minor version bump + changelog update
|
|
||||||
- `npm run version:major` - Major version bump + changelog update
|
|
||||||
|
|
||||||
3. **Manual Updates**: Use `npm run changelog:update` to manually update the changelog
|
|
||||||
|
|
||||||
### Changelog Format
|
|
||||||
|
|
||||||
The changelog follows the [Keep a Changelog](https://keepachangelog.com/) standard:
|
|
||||||
|
|
||||||
- **Added** - New features
|
|
||||||
- **Changed** - Changes in existing functionality
|
|
||||||
- **Deprecated** - Soon-to-be removed features
|
|
||||||
- **Removed** - Now removed features
|
|
||||||
- **Fixed** - Bug fixes
|
|
||||||
- **Security** - Vulnerability fixes
|
|
||||||
|
|
||||||
### GitHub Integration
|
|
||||||
|
|
||||||
The CHANGELOG.md is automatically used for:
|
|
||||||
- GitHub releases (via `scripts/create-github-release.js`)
|
|
||||||
- NPM package release notes
|
|
||||||
- Version history tracking
|
|
||||||
|
|
||||||
## Benefits of New Structure
|
|
||||||
|
|
||||||
1. **Clean Root Directory**: Only 4 essential markdown files at root level
|
|
||||||
2. **Better Organization**: Logical categorization of documentation in docs/ subdirectories
|
|
||||||
3. **GitHub Compliance**: Follows GitHub and NPM best practices
|
|
||||||
4. **Automated Maintenance**: Changelog updates are automated
|
|
||||||
5. **Easy Navigation**: Clear directory structure for different doc types
|
|
||||||
6. **Reduced Clutter**: Temporary and outdated files have been removed
|
|
||||||
|
|
||||||
## Workflow for Contributors
|
|
||||||
|
|
||||||
### Adding Documentation
|
|
||||||
1. **Technical docs** → `docs/technical/`
|
|
||||||
2. **Development docs** → `docs/development/`
|
|
||||||
3. **User guides** → `docs/guides/`
|
|
||||||
4. **Temporary files** → Should be avoided; use issues or PRs for temporary documentation
|
|
||||||
|
|
||||||
### Making Changes
|
|
||||||
1. Add changes to `[Unreleased]` section in `CHANGELOG.md`
|
|
||||||
2. Use appropriate category (Added, Changed, Fixed, etc.)
|
|
||||||
3. When ready to release, use `npm run version:patch/minor/major`
|
|
||||||
4. The changelog will be automatically updated with version and date
|
|
||||||
|
|
||||||
### Release Process
|
|
||||||
1. Ensure `[Unreleased]` section has all changes
|
|
||||||
2. Run `npm run version:patch/minor/major`
|
|
||||||
3. Run `npm run deploy` to publish and create GitHub release
|
|
||||||
4. GitHub release will use CHANGELOG.md content
|
|
||||||
|
|
||||||
## Migration Notes
|
|
||||||
|
|
||||||
- All technical documentation organized in `docs/technical/`
|
|
||||||
- Development documentation organized in `docs/development/`
|
|
||||||
- User guides organized in `docs/guides/`
|
|
||||||
- Temporary summary files and archived content have been cleaned up
|
|
||||||
- Links in existing documentation may need updates
|
|
||||||
- New automation ensures changelog stays current
|
|
||||||
|
|
||||||
This reorganization provides a sustainable, scalable approach to documentation management that follows industry best practices and integrates seamlessly with GitHub and NPM workflows.
|
|
||||||
1423
docs/FIND_SYSTEM.md
Normal file
1423
docs/FIND_SYSTEM.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,195 +0,0 @@
|
||||||
# Markdown File Management Guidelines
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document establishes standards for managing .md files in the Brainy project to maintain a clean, organized, and professional documentation structure.
|
|
||||||
|
|
||||||
## Root Directory Standards
|
|
||||||
|
|
||||||
The project root should contain **only** these essential .md files:
|
|
||||||
|
|
||||||
### Required Files (GitHub/NPM Standards)
|
|
||||||
- `README.md` - Main project documentation and entry point
|
|
||||||
- `CHANGELOG.md` - Version history and release notes
|
|
||||||
- `CODE_OF_CONDUCT.md` - Community guidelines
|
|
||||||
- `CONTRIBUTING.md` - Contribution guidelines
|
|
||||||
- `LICENSE` - Legal license file (not .md but related)
|
|
||||||
|
|
||||||
### Prohibited in Root
|
|
||||||
❌ **Never place these in root:**
|
|
||||||
- Temporary summary files (e.g., `IMPLEMENTATION_SUMMARY.md`)
|
|
||||||
- Fix-related documentation (e.g., `RELIABILITY_IMPROVEMENTS_SUMMARY.md`)
|
|
||||||
- Organizational notes (e.g., `SCRIPT_ORGANIZATION_SOLUTION.md`)
|
|
||||||
- Update logs (e.g., `README_updates.md`, `changes-summary.md`)
|
|
||||||
- Environment-specific guides (should go in docs/)
|
|
||||||
|
|
||||||
## Documentation Organization Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
docs/
|
|
||||||
├── COMPATIBILITY.md # Cross-platform compatibility info
|
|
||||||
├── DOCUMENTATION_ORGANIZATION.md # This file's organization guide
|
|
||||||
├── development/ # Developer-focused documentation
|
|
||||||
│ ├── DEVELOPERS.md
|
|
||||||
│ ├── DOCUMENTATION_STANDARDS.md
|
|
||||||
│ └── MARKDOWN_CONVENTIONS.md
|
|
||||||
├── guides/ # User guides and tutorials
|
|
||||||
│ ├── cache-configuration.md
|
|
||||||
│ ├── model-management.md
|
|
||||||
│ └── production-migration-guide.md
|
|
||||||
└── technical/ # Technical implementation details
|
|
||||||
├── TESTING.md # Comprehensive testing guide
|
|
||||||
├── ENVIRONMENT_TESTING.md # Environment-specific testing
|
|
||||||
├── CONCURRENCY_ANALYSIS.md
|
|
||||||
└── STORAGE_TESTING.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## File Naming Conventions
|
|
||||||
|
|
||||||
### Use UPPERCASE for Major Documents
|
|
||||||
- `README.md`, `CHANGELOG.md`, `CONTRIBUTING.md`
|
|
||||||
- `TESTING.md`, `COMPATIBILITY.md`
|
|
||||||
|
|
||||||
### Use lowercase-with-hyphens for Specific Guides
|
|
||||||
- `cache-configuration.md`
|
|
||||||
- `model-management.md`
|
|
||||||
- `production-migration-guide.md`
|
|
||||||
|
|
||||||
### Use Descriptive Names
|
|
||||||
✅ **Good:**
|
|
||||||
- `ENVIRONMENT_TESTING.md` (specific purpose)
|
|
||||||
- `cache-configuration.md` (clear topic)
|
|
||||||
- `production-migration-guide.md` (clear audience and purpose)
|
|
||||||
|
|
||||||
❌ **Bad:**
|
|
||||||
- `IMPLEMENTATION_SUMMARY.md` (temporary)
|
|
||||||
- `changes-summary.md` (temporary)
|
|
||||||
- `notes.md` (vague)
|
|
||||||
|
|
||||||
## File Lifecycle Management
|
|
||||||
|
|
||||||
### Temporary Files
|
|
||||||
**Rule: Temporary files should be deleted immediately after their purpose is fulfilled.**
|
|
||||||
|
|
||||||
Examples of temporary files that should be deleted:
|
|
||||||
- Implementation summaries after feature completion
|
|
||||||
- Fix documentation after issues are resolved
|
|
||||||
- Organizational notes after reorganization is complete
|
|
||||||
- Update logs after updates are integrated
|
|
||||||
|
|
||||||
### Permanent Documentation
|
|
||||||
Files that should be maintained long-term:
|
|
||||||
- User guides and tutorials
|
|
||||||
- Technical reference documentation
|
|
||||||
- API documentation
|
|
||||||
- Testing guides
|
|
||||||
- Development standards
|
|
||||||
|
|
||||||
## Where to Place Different Types of Documentation
|
|
||||||
|
|
||||||
### Root Directory
|
|
||||||
- Only essential project files (README, CHANGELOG, etc.)
|
|
||||||
|
|
||||||
### docs/development/
|
|
||||||
- Developer setup guides
|
|
||||||
- Build instructions
|
|
||||||
- Code standards
|
|
||||||
- Documentation standards
|
|
||||||
|
|
||||||
### docs/guides/
|
|
||||||
- User tutorials
|
|
||||||
- Configuration guides
|
|
||||||
- Migration guides
|
|
||||||
- How-to documentation
|
|
||||||
|
|
||||||
### docs/technical/
|
|
||||||
- Technical implementation details
|
|
||||||
- Architecture documentation
|
|
||||||
- Testing documentation
|
|
||||||
- Performance analysis
|
|
||||||
|
|
||||||
### Package-Specific
|
|
||||||
- Each package (cli-package/, web-service-package/, etc.) should have its own README.md
|
|
||||||
- Package-specific documentation stays with the package
|
|
||||||
|
|
||||||
## Review Process
|
|
||||||
|
|
||||||
### Before Adding New .md Files
|
|
||||||
1. **Determine if it's temporary or permanent**
|
|
||||||
- Temporary: Consider using issues, PRs, or comments instead
|
|
||||||
- Permanent: Proceed with proper placement
|
|
||||||
|
|
||||||
2. **Choose the correct location**
|
|
||||||
- Root: Only for essential project files
|
|
||||||
- docs/: For all other documentation
|
|
||||||
|
|
||||||
3. **Use proper naming conventions**
|
|
||||||
- Descriptive names that indicate purpose
|
|
||||||
- Consistent with existing patterns
|
|
||||||
|
|
||||||
### Regular Cleanup
|
|
||||||
- Review .md files quarterly
|
|
||||||
- Delete temporary files that have served their purpose
|
|
||||||
- Consolidate duplicate or overlapping documentation
|
|
||||||
- Update links when files are moved
|
|
||||||
|
|
||||||
## Migration Guidelines
|
|
||||||
|
|
||||||
When reorganizing existing documentation:
|
|
||||||
|
|
||||||
1. **Categorize existing files**
|
|
||||||
- Essential (keep in root)
|
|
||||||
- Useful (move to docs/)
|
|
||||||
- Temporary (delete)
|
|
||||||
|
|
||||||
2. **Update references**
|
|
||||||
- Search for links to moved files
|
|
||||||
- Update README.md and other documentation
|
|
||||||
- Test that all links work
|
|
||||||
|
|
||||||
3. **Maintain backward compatibility when possible**
|
|
||||||
- Consider redirects for important moved files
|
|
||||||
- Update package.json scripts if they reference moved files
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
### Code Review Checklist
|
|
||||||
- [ ] New .md files are in appropriate locations
|
|
||||||
- [ ] Temporary files are not being committed
|
|
||||||
- [ ] Links to documentation are correct
|
|
||||||
- [ ] File names follow conventions
|
|
||||||
|
|
||||||
### Automated Checks (Future)
|
|
||||||
Consider implementing:
|
|
||||||
- Linting rules for .md file placement
|
|
||||||
- Link checking in CI/CD
|
|
||||||
- Automated cleanup of temporary files
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
### ✅ Good Documentation Structure
|
|
||||||
```
|
|
||||||
README.md # Main project docs
|
|
||||||
CHANGELOG.md # Version history
|
|
||||||
docs/guides/setup.md # User guide
|
|
||||||
docs/technical/api.md # Technical reference
|
|
||||||
```
|
|
||||||
|
|
||||||
### ❌ Bad Documentation Structure
|
|
||||||
```
|
|
||||||
README.md
|
|
||||||
IMPLEMENTATION_SUMMARY.md # Temporary - should be deleted
|
|
||||||
FIX_NOTES.md # Temporary - should be deleted
|
|
||||||
setup.md # Should be in docs/guides/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Following these guidelines ensures:
|
|
||||||
- Clean, professional project structure
|
|
||||||
- Easy navigation for users and contributors
|
|
||||||
- Reduced maintenance overhead
|
|
||||||
- Consistent documentation organization
|
|
||||||
- Better discoverability of information
|
|
||||||
|
|
||||||
**Remember: When in doubt, ask "Is this temporary or permanent?" and "Who is the audience?" to determine the right approach.**
|
|
||||||
569
docs/MIGRATION-V3-TO-V4.md
Normal file
569
docs/MIGRATION-V3-TO-V4.md
Normal file
|
|
@ -0,0 +1,569 @@
|
||||||
|
# Brainy v3 → v4.0.0 Migration Guide
|
||||||
|
|
||||||
|
> **Migration Complexity**: Low
|
||||||
|
> **Breaking Changes**: None (fully backward compatible)
|
||||||
|
> **New Features**: Lifecycle management, batch operations, compression, quota monitoring
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Brainy v4.0.0 is a **backward-compatible** release focused on production-ready cost optimization features. Your existing v3 code will continue to work without modifications, but you'll want to enable the new v4.0.0 features for significant cost savings.
|
||||||
|
|
||||||
|
**Key Benefits of Upgrading:**
|
||||||
|
- 💰 **96% cost savings** with lifecycle policies
|
||||||
|
- 🚀 **1000x faster** bulk deletions with batch operations
|
||||||
|
- 📦 **60-80% space savings** with gzip compression
|
||||||
|
- 📊 **Real-time quota monitoring** for OPFS
|
||||||
|
- 🎯 **Zero downtime** migration
|
||||||
|
|
||||||
|
## What's New in v4.0.0
|
||||||
|
|
||||||
|
### 1. Lifecycle Management (Cloud Storage)
|
||||||
|
|
||||||
|
**Automatic tier transitions for massive cost savings:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// NEW in v4.0.0
|
||||||
|
await storage.setLifecyclePolicy({
|
||||||
|
rules: [{
|
||||||
|
id: 'archive-old-data',
|
||||||
|
prefix: 'entities/',
|
||||||
|
status: 'Enabled',
|
||||||
|
transitions: [
|
||||||
|
{ days: 30, storageClass: 'STANDARD_IA' },
|
||||||
|
{ days: 90, storageClass: 'GLACIER' }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Supported on:**
|
||||||
|
- ✅ AWS S3 (Lifecycle + Intelligent-Tiering)
|
||||||
|
- ✅ Google Cloud Storage (Lifecycle + Autoclass)
|
||||||
|
- ✅ Azure Blob Storage (Lifecycle policies)
|
||||||
|
|
||||||
|
### 2. Batch Operations
|
||||||
|
|
||||||
|
**1000x faster bulk deletions:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// v3: Delete one at a time (slow, expensive)
|
||||||
|
for (const id of idsToDelete) {
|
||||||
|
await brain.remove(id) // 1000 API calls for 1000 entities
|
||||||
|
}
|
||||||
|
|
||||||
|
// v4.0.0: Batch delete (fast, cheap)
|
||||||
|
const paths = idsToDelete.flatMap(id => [
|
||||||
|
`entities/nouns/vectors/${id.substring(0, 2)}/${id}.json`,
|
||||||
|
`entities/nouns/metadata/${id.substring(0, 2)}/${id}.json`
|
||||||
|
])
|
||||||
|
await storage.batchDelete(paths) // 1 API call for 1000 objects (S3)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Efficiency gains:**
|
||||||
|
- S3: 1000 objects per batch
|
||||||
|
- GCS: 100 objects per batch
|
||||||
|
- Azure: 256 objects per batch
|
||||||
|
|
||||||
|
### 3. Compression (FileSystem)
|
||||||
|
|
||||||
|
**60-80% space savings for local storage:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// NEW in v4.0.0
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: {
|
||||||
|
type: 'filesystem',
|
||||||
|
path: './data',
|
||||||
|
compression: true // Enable gzip compression
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Automatic compression/decompression on all reads/writes
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Quota Monitoring (OPFS)
|
||||||
|
|
||||||
|
**Prevent quota exceeded errors in browsers:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// NEW in v4.0.0
|
||||||
|
const status = await storage.getStorageStatus()
|
||||||
|
|
||||||
|
if (status.details.usagePercent > 80) {
|
||||||
|
console.warn('Approaching quota limit:', status.details)
|
||||||
|
// Take action: cleanup old data, notify user, etc.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Tier Management (Azure)
|
||||||
|
|
||||||
|
**Manual or automatic tier transitions:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// NEW in v4.0.0
|
||||||
|
await storage.changeBlobTier(blobPath, 'Cool') // Hot → Cool (50% savings)
|
||||||
|
await storage.batchChangeTier([blob1, blob2], 'Archive') // 99% savings
|
||||||
|
|
||||||
|
// Rehydrate from Archive when needed
|
||||||
|
await storage.rehydrateBlob(blobPath, 'High') // 1-hour rehydration
|
||||||
|
```
|
||||||
|
|
||||||
|
## Storage Architecture Changes
|
||||||
|
|
||||||
|
### v3.x Storage Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
brainy-data/
|
||||||
|
├── nouns/
|
||||||
|
│ └── {uuid}.json # Single file per entity
|
||||||
|
├── verbs/
|
||||||
|
│ └── {uuid}.json # Single file per relationship
|
||||||
|
├── metadata/
|
||||||
|
│ └── __metadata_*.json # Indexes
|
||||||
|
└── _system/
|
||||||
|
└── statistics.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### v4.0.0 Storage Structure (Automatic Migration)
|
||||||
|
|
||||||
|
```
|
||||||
|
brainy-data/
|
||||||
|
├── entities/
|
||||||
|
│ ├── nouns/
|
||||||
|
│ │ ├── vectors/ # Vector + HNSW graph (NEW)
|
||||||
|
│ │ │ ├── 00/ ... ff/ # 256 UUID shards (NEW)
|
||||||
|
│ │ └── metadata/ # Business data (NEW)
|
||||||
|
│ │ ├── 00/ ... ff/ # 256 UUID shards (NEW)
|
||||||
|
│ └── verbs/
|
||||||
|
│ ├── vectors/ # Relationship vectors (NEW)
|
||||||
|
│ │ ├── 00/ ... ff/
|
||||||
|
│ └── metadata/ # Relationship data (NEW)
|
||||||
|
│ ├── 00/ ... ff/
|
||||||
|
└── _system/ # Unchanged
|
||||||
|
└── __metadata_*.json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Changes:**
|
||||||
|
1. **Metadata/Vector Separation**: Entities split into 2 files for optimal I/O
|
||||||
|
2. **UUID-Based Sharding**: 256 shards for cloud storage optimization
|
||||||
|
3. **Automatic Migration**: Brainy handles migration transparently on first run
|
||||||
|
|
||||||
|
## Migration Steps
|
||||||
|
|
||||||
|
### Step 1: Update Brainy Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @soulcraft/brainy@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Check your version:**
|
||||||
|
```bash
|
||||||
|
npm list @soulcraft/brainy
|
||||||
|
# Should show: @soulcraft/brainy@4.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: No Code Changes Required! ✅
|
||||||
|
|
||||||
|
Your existing v3 code will work without modifications:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// This v3 code works perfectly in v4.0.0
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: { type: 'filesystem', path: './data' }
|
||||||
|
})
|
||||||
|
|
||||||
|
await brain.init()
|
||||||
|
await brain.add("content", { type: "entity" })
|
||||||
|
const results = await brain.search("query")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: First Run (Automatic Migration)
|
||||||
|
|
||||||
|
On first initialization with v4.0.0:
|
||||||
|
|
||||||
|
1. **Brainy detects v3 storage structure**
|
||||||
|
2. **Transparently migrates to v4.0.0 structure**:
|
||||||
|
- Creates `entities/` directory
|
||||||
|
- Migrates `nouns/` → `entities/nouns/vectors/` + `entities/nouns/metadata/`
|
||||||
|
- Migrates `verbs/` → `entities/verbs/vectors/` + `entities/verbs/metadata/`
|
||||||
|
- Applies UUID-based sharding
|
||||||
|
3. **Old structure preserved** (optional cleanup later)
|
||||||
|
|
||||||
|
**Migration time:**
|
||||||
|
- 10K entities: ~1 minute
|
||||||
|
- 100K entities: ~10 minutes
|
||||||
|
- 1M entities: ~2 hours
|
||||||
|
|
||||||
|
**Zero downtime:**
|
||||||
|
- Migration happens during init()
|
||||||
|
- No data loss
|
||||||
|
- Automatic rollback on error
|
||||||
|
|
||||||
|
### Step 4: Enable v4.0.0 Features (Optional but Recommended)
|
||||||
|
|
||||||
|
#### Enable Lifecycle Policies (Cloud Storage)
|
||||||
|
|
||||||
|
**AWS S3:**
|
||||||
|
```typescript
|
||||||
|
// After init()
|
||||||
|
await storage.setLifecyclePolicy({
|
||||||
|
rules: [{
|
||||||
|
id: 'optimize-storage',
|
||||||
|
prefix: 'entities/',
|
||||||
|
status: 'Enabled',
|
||||||
|
transitions: [
|
||||||
|
{ days: 30, storageClass: 'STANDARD_IA' },
|
||||||
|
{ days: 90, storageClass: 'GLACIER' }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Or use Intelligent-Tiering (recommended)
|
||||||
|
await storage.enableIntelligentTiering('entities/', 'auto-optimize')
|
||||||
|
```
|
||||||
|
|
||||||
|
**Google Cloud Storage:**
|
||||||
|
```typescript
|
||||||
|
await storage.enableAutoclass({
|
||||||
|
terminalStorageClass: 'ARCHIVE'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Azure Blob Storage:**
|
||||||
|
```typescript
|
||||||
|
await storage.setLifecyclePolicy({
|
||||||
|
rules: [{
|
||||||
|
name: 'optimize-blobs',
|
||||||
|
enabled: true,
|
||||||
|
type: 'Lifecycle',
|
||||||
|
definition: {
|
||||||
|
filters: { blobTypes: ['blockBlob'] },
|
||||||
|
actions: {
|
||||||
|
baseBlob: {
|
||||||
|
tierToCool: { daysAfterModificationGreaterThan: 30 },
|
||||||
|
tierToArchive: { daysAfterModificationGreaterThan: 90 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Enable Compression (FileSystem)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: {
|
||||||
|
type: 'filesystem',
|
||||||
|
path: './data',
|
||||||
|
compression: true // NEW: 60-80% space savings
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Use Batch Operations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Replace individual deletes with batch delete
|
||||||
|
const idsToDelete = [/* ... */]
|
||||||
|
const paths = idsToDelete.flatMap(id => {
|
||||||
|
const shard = id.substring(0, 2)
|
||||||
|
return [
|
||||||
|
`entities/nouns/vectors/${shard}/${id}.json`,
|
||||||
|
`entities/nouns/metadata/${shard}/${id}.json`
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
await storage.batchDelete(paths) // Much faster!
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Monitor Quota (OPFS)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Periodically check quota in browser apps
|
||||||
|
setInterval(async () => {
|
||||||
|
const status = await storage.getStorageStatus()
|
||||||
|
if (status.details.usagePercent > 80) {
|
||||||
|
notifyUser('Storage approaching limit')
|
||||||
|
}
|
||||||
|
}, 60000) // Check every minute
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backward Compatibility
|
||||||
|
|
||||||
|
### Guaranteed to Work (No Changes Needed)
|
||||||
|
|
||||||
|
✅ All v3 APIs remain unchanged
|
||||||
|
✅ Storage adapters backward compatible
|
||||||
|
✅ Metadata structure unchanged
|
||||||
|
✅ Query APIs unchanged
|
||||||
|
✅ Configuration options unchanged
|
||||||
|
|
||||||
|
### New Optional APIs (Add When Ready)
|
||||||
|
|
||||||
|
- `storage.setLifecyclePolicy()` - NEW in v4.0.0
|
||||||
|
- `storage.getLifecyclePolicy()` - NEW in v4.0.0
|
||||||
|
- `storage.removeLifecyclePolicy()` - NEW in v4.0.0
|
||||||
|
- `storage.enableIntelligentTiering()` - NEW in v4.0.0 (S3)
|
||||||
|
- `storage.enableAutoclass()` - NEW in v4.0.0 (GCS)
|
||||||
|
- `storage.batchDelete()` - NEW in v4.0.0
|
||||||
|
- `storage.changeBlobTier()` - NEW in v4.0.0 (Azure)
|
||||||
|
- `storage.getStorageStatus()` - Enhanced in v4.0.0
|
||||||
|
|
||||||
|
## Testing Your Migration
|
||||||
|
|
||||||
|
### 1. Test in Development First
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Create test brain with v4.0.0
|
||||||
|
const testBrain = new Brainy({
|
||||||
|
storage: { type: 'filesystem', path: './test-data' }
|
||||||
|
})
|
||||||
|
|
||||||
|
await testBrain.init()
|
||||||
|
|
||||||
|
// Verify migration
|
||||||
|
console.log('Initialization complete')
|
||||||
|
|
||||||
|
// Test basic operations
|
||||||
|
const id = await testBrain.add("test content", { type: "test" })
|
||||||
|
const results = await testBrain.search("test")
|
||||||
|
console.log('Basic operations working:', results.length > 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Verify Storage Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check new directory structure
|
||||||
|
ls -la ./test-data/entities/nouns/vectors/
|
||||||
|
# Should see: 00/ 01/ 02/ ... ff/ (256 shards)
|
||||||
|
|
||||||
|
ls -la ./test-data/entities/nouns/metadata/
|
||||||
|
# Should see: 00/ 01/ 02/ ... ff/ (256 shards)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Verify Data Integrity
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Query all entities
|
||||||
|
const allEntities = await testBrain.find({})
|
||||||
|
console.log('Total entities:', allEntities.length)
|
||||||
|
|
||||||
|
// Verify specific entities
|
||||||
|
const entity = await testBrain.get(knownEntityId)
|
||||||
|
console.log('Entity retrieved:', entity !== null)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test Performance
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Benchmark search
|
||||||
|
const start = Date.now()
|
||||||
|
const results = await testBrain.search("query")
|
||||||
|
const duration = Date.now() - start
|
||||||
|
console.log('Search time:', duration, 'ms')
|
||||||
|
|
||||||
|
// Should be similar or faster than v3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rollback Procedure (If Needed)
|
||||||
|
|
||||||
|
If you encounter issues, you can rollback:
|
||||||
|
|
||||||
|
### Option 1: Rollback Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reinstall v3
|
||||||
|
npm install @soulcraft/brainy@^3.50.0
|
||||||
|
|
||||||
|
# Restart application
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** v3 can still read v3-structured data (preserved during migration)
|
||||||
|
|
||||||
|
### Option 2: Restore from Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If you backed up data before migration
|
||||||
|
rm -rf ./data
|
||||||
|
cp -r ./data-backup ./data
|
||||||
|
|
||||||
|
# Reinstall v3
|
||||||
|
npm install @soulcraft/brainy@^3.50.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Migration Scenarios
|
||||||
|
|
||||||
|
### Scenario 1: Small Application (<10K Entities)
|
||||||
|
|
||||||
|
**Migration time:** 1 minute
|
||||||
|
**Recommended approach:**
|
||||||
|
1. Update npm package
|
||||||
|
2. Restart application (automatic migration)
|
||||||
|
3. Enable lifecycle policies immediately
|
||||||
|
|
||||||
|
### Scenario 2: Medium Application (10K-1M Entities)
|
||||||
|
|
||||||
|
**Migration time:** 10 minutes - 2 hours
|
||||||
|
**Recommended approach:**
|
||||||
|
1. Backup data
|
||||||
|
2. Update npm package
|
||||||
|
3. Schedule maintenance window
|
||||||
|
4. Restart application (automatic migration)
|
||||||
|
5. Verify data integrity
|
||||||
|
6. Enable lifecycle policies
|
||||||
|
|
||||||
|
### Scenario 3: Large Application (1M+ Entities)
|
||||||
|
|
||||||
|
**Migration time:** 2-24 hours
|
||||||
|
**Recommended approach:**
|
||||||
|
1. **Backup data** (critical!)
|
||||||
|
2. Test migration on staging environment
|
||||||
|
3. Schedule extended maintenance window
|
||||||
|
4. Update npm package on production
|
||||||
|
5. Restart application (automatic migration)
|
||||||
|
6. Monitor migration progress
|
||||||
|
7. Verify data integrity thoroughly
|
||||||
|
8. Enable lifecycle policies gradually
|
||||||
|
|
||||||
|
## Cost Savings After Migration
|
||||||
|
|
||||||
|
### Enable All v4.0.0 Features
|
||||||
|
|
||||||
|
**500TB Dataset Example:**
|
||||||
|
|
||||||
|
**Before v4.0.0 (v3 with AWS S3 Standard):**
|
||||||
|
```
|
||||||
|
Storage: $138,000/year
|
||||||
|
Operations: $5,000/year
|
||||||
|
Total: $143,000/year
|
||||||
|
```
|
||||||
|
|
||||||
|
**After v4.0.0 (with Intelligent-Tiering):**
|
||||||
|
```
|
||||||
|
Storage: $51,000/year (64% savings)
|
||||||
|
Operations: $5,000/year
|
||||||
|
Total: $56,000/year
|
||||||
|
```
|
||||||
|
|
||||||
|
**After v4.0.0 (with Lifecycle Policies):**
|
||||||
|
```
|
||||||
|
Storage: $5,940/year (96% savings!)
|
||||||
|
Operations: $5,000/year
|
||||||
|
Total: $10,940/year
|
||||||
|
```
|
||||||
|
|
||||||
|
**Annual Savings: $132,060 (96% reduction)**
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Migration takes too long
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Migration is I/O bound
|
||||||
|
- For 1M+ entities, consider:
|
||||||
|
- Running during off-peak hours
|
||||||
|
- Using faster storage (SSD vs HDD)
|
||||||
|
- Increasing available memory
|
||||||
|
- Running on more powerful instance
|
||||||
|
|
||||||
|
### Issue: "Storage structure not recognized"
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```typescript
|
||||||
|
// Manually trigger migration
|
||||||
|
await brain.storage.migrateToV4() // If automatic migration fails
|
||||||
|
|
||||||
|
// Or start fresh (data loss warning!)
|
||||||
|
await brain.storage.clear()
|
||||||
|
await brain.init()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Lifecycle policy not working
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```typescript
|
||||||
|
// Verify policy is set
|
||||||
|
const policy = await storage.getLifecyclePolicy()
|
||||||
|
console.log('Active rules:', policy.rules)
|
||||||
|
|
||||||
|
// Cloud providers may take 24-48 hours to start transitions
|
||||||
|
// Check again after 2 days
|
||||||
|
|
||||||
|
// Verify in cloud console:
|
||||||
|
// - AWS: S3 → Bucket → Management → Lifecycle
|
||||||
|
// - GCS: Storage → Bucket → Lifecycle
|
||||||
|
// - Azure: Storage Account → Lifecycle management
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Batch delete not working
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```typescript
|
||||||
|
// Ensure storage adapter supports batch delete
|
||||||
|
const status = await storage.getStorageStatus()
|
||||||
|
console.log('Storage type:', status.type)
|
||||||
|
|
||||||
|
// Batch delete requires:
|
||||||
|
// - S3CompatibleStorage ✅
|
||||||
|
// - GcsStorage ✅
|
||||||
|
// - AzureBlobStorage ✅
|
||||||
|
// - FileSystemStorage ✅
|
||||||
|
// - OPFSStorage ✅
|
||||||
|
// - MemoryStorage ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. ✅ **Backup before upgrading** (especially for large datasets)
|
||||||
|
2. ✅ **Test on staging first** (verify migration works)
|
||||||
|
3. ✅ **Monitor during migration** (watch logs for errors)
|
||||||
|
4. ✅ **Enable lifecycle policies immediately** (start saving costs)
|
||||||
|
5. ✅ **Use batch operations** (for any bulk cleanup)
|
||||||
|
6. ✅ **Monitor quota** (OPFS browser apps)
|
||||||
|
7. ✅ **Enable compression** (FileSystem storage)
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- [AWS S3 Cost Optimization Guide](./operations/cost-optimization-aws-s3.md)
|
||||||
|
- [GCS Cost Optimization Guide](./operations/cost-optimization-gcs.md)
|
||||||
|
- [Azure Cost Optimization Guide](./operations/cost-optimization-azure.md)
|
||||||
|
- [Cloudflare R2 Cost Optimization Guide](./operations/cost-optimization-cloudflare-r2.md)
|
||||||
|
|
||||||
|
**Support:**
|
||||||
|
- GitHub Issues: [https://github.com/soulcraft/brainy/issues](https://github.com/soulcraft/brainy/issues)
|
||||||
|
- GitHub Discussions: [https://github.com/soulcraft/brainy/discussions](https://github.com/soulcraft/brainy/discussions)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**Migration Checklist:**
|
||||||
|
- ✅ Backup data
|
||||||
|
- ✅ Update npm package (`npm install @soulcraft/brainy@latest`)
|
||||||
|
- ✅ Restart application (automatic migration)
|
||||||
|
- ✅ Verify data integrity
|
||||||
|
- ✅ Enable lifecycle policies
|
||||||
|
- ✅ Enable compression (FileSystem)
|
||||||
|
- ✅ Use batch operations
|
||||||
|
- ✅ Monitor cost savings
|
||||||
|
|
||||||
|
**Expected Results:**
|
||||||
|
- ✅ Zero downtime migration
|
||||||
|
- ✅ Full backward compatibility
|
||||||
|
- ✅ 60-96% cost savings
|
||||||
|
- ✅ 1000x faster bulk operations
|
||||||
|
- ✅ 60-80% space savings (with compression)
|
||||||
|
|
||||||
|
**Timeline:**
|
||||||
|
- Small app (<10K): 1 minute migration
|
||||||
|
- Medium app (10K-1M): 10 minutes - 2 hours
|
||||||
|
- Large app (1M+): 2-24 hours
|
||||||
|
|
||||||
|
**Welcome to Brainy v4.0.0! 🎉**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version**: v4.0.0
|
||||||
|
**Migration Difficulty**: Low
|
||||||
|
**Breaking Changes**: None
|
||||||
|
**Recommended Upgrade**: Yes (significant cost savings)
|
||||||
492
docs/PERFORMANCE.md
Normal file
492
docs/PERFORMANCE.md
Normal file
|
|
@ -0,0 +1,492 @@
|
||||||
|
# Brainy Performance & Architecture
|
||||||
|
|
||||||
|
## Performance Characteristics
|
||||||
|
|
||||||
|
Brainy achieves high performance through carefully optimized data structures and algorithms. The tables below describe each component by its **algorithmic complexity** — the durable, defensible guarantee. The example latencies are figures from a single 100-item run on one machine (see [Benchmarks](#benchmarks)); they are illustrative, not a committed benchmark, and vary with hardware, embedding model, and storage backend. The one component with a committed scale assertion is the graph adjacency index (`tests/performance/graph-scale-performance.test.ts:238`).
|
||||||
|
|
||||||
|
### Core Performance Summary
|
||||||
|
|
||||||
|
| Component | Operation | Time Complexity | Example latency (100-item run)\* | Data Structure |
|
||||||
|
|-----------|-----------|-----------------|---------------------|----------------|
|
||||||
|
| **Metadata Index** | Exact match | **O(1)** | 0.8ms | `Map<string, Set<string>>` |
|
||||||
|
| **Metadata Index** | Range query | **O(log n) + O(k)** | 0.6ms | Sorted array + binary search |
|
||||||
|
| **Graph Index** | Get neighbors | **O(1)** | 0.09ms | `Map<string, Set<string>>` |
|
||||||
|
| **Vector Search** | k-NN search | **O(log n)** | 1.8ms | Hierarchical graph |
|
||||||
|
| **NLP Parser** | Query parsing | **O(m)** | 8.9ms | 220 pre-computed patterns |
|
||||||
|
| **Type-Field Affinity** | Field matching | **O(f)** | 0.1ms | Type-specific field cache |
|
||||||
|
| **Type Detection** | Noun/Verb matching | **O(t)** | 0.3ms | Pre-embedded type vectors |
|
||||||
|
| **Triple Intelligence** | Combined query | **O(1) to O(log n)** | 1.8ms | Parallel execution |
|
||||||
|
|
||||||
|
\* Illustrative single-run figures at 100 items on one machine — not a committed benchmark. Only the graph index carries an asserted scale bound (measured <1 ms per neighbor lookup up to 1M relationships, `tests/performance/graph-scale-performance.test.ts:238`).
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- `n` = number of items in index
|
||||||
|
- `k` = number of results returned
|
||||||
|
- `m` = number of patterns to check
|
||||||
|
- `f` = number of fields for entity type
|
||||||
|
- `t` = number of types (42 nouns, 127 verbs)
|
||||||
|
|
||||||
|
### brain.get() Metadata-Only Optimization
|
||||||
|
|
||||||
|
`brain.get()` returns **metadata only by default**, skipping the 384-dimensional
|
||||||
|
embedding — the bulk of an entity's payload. Callers that need the vector opt in
|
||||||
|
with `{ includeVectors: true }`.
|
||||||
|
|
||||||
|
| Operation | Default (metadata-only) | With `includeVectors: true` | Use Case |
|
||||||
|
|-----------|-------------------------|-----------------------------|----------|
|
||||||
|
| **brain.get()** | Skips vector load | Loads full vector | VFS, existence checks, metadata |
|
||||||
|
| **VFS readFile() / readdir()** | Inherits metadata-only path | n/a | File operations, directory listings |
|
||||||
|
|
||||||
|
**Key Innovation**: Lazy vector loading — only load the 384-dimensional embedding when explicitly needed.
|
||||||
|
|
||||||
|
The integration test `tests/integration/metadata-only-comprehensive.test.ts:306`
|
||||||
|
asserts metadata-only `get()` is faster than the full-entity `get()`
|
||||||
|
(`metadataTime < fullTime`). The *magnitude* of the speedup is
|
||||||
|
environment-dependent (the percentage assertion in
|
||||||
|
`tests/integration/vfs-performance-v5.11.1.test.ts` is intentionally skipped on
|
||||||
|
CI for that reason), so no fixed percentage is quoted here.
|
||||||
|
|
||||||
|
**Why this matters**:
|
||||||
|
- Most `brain.get()` calls don't need vectors (VFS, admin tools, import utilities, data APIs)
|
||||||
|
- The embedding dominates an entity's serialized size, so skipping it is the largest win
|
||||||
|
- **Zero code changes** for most applications — automatic by default
|
||||||
|
|
||||||
|
**When to use what**:
|
||||||
|
```typescript
|
||||||
|
// DEFAULT: Metadata-only (skips the vector load) - use for:
|
||||||
|
const entity = await brain.get(id)
|
||||||
|
// - VFS operations (readFile, stat, readdir)
|
||||||
|
// - Existence checks: if (await brain.get(id)) ...
|
||||||
|
// - Metadata access: entity.data, entity.type, entity.metadata
|
||||||
|
// - Relationship traversal
|
||||||
|
|
||||||
|
// EXPLICIT: Full entity (same as before) - use ONLY for:
|
||||||
|
const entity = await brain.get(id, { includeVectors: true })
|
||||||
|
// - Computing similarity on THIS entity
|
||||||
|
// - Manual vector operations
|
||||||
|
// - Vector index graph traversal
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Deep Dive
|
||||||
|
|
||||||
|
### 1. Metadata Index - O(1) Lookups
|
||||||
|
|
||||||
|
The `MetadataIndexManager` uses inverted indexes for lightning-fast metadata filtering.
|
||||||
|
|
||||||
|
**UPDATED**: Sorted indices for range queries are now built **incrementally during CRUD operations**. No lazy loading delays - range queries are consistently fast. Binary search insertions maintain O(log n) performance during updates.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class MetadataIndexManager {
|
||||||
|
// O(1) exact match via HashMap
|
||||||
|
private indexCache = new Map<string, MetadataIndexEntry>()
|
||||||
|
|
||||||
|
// O(log n) range queries via sorted arrays (incremental updates)
|
||||||
|
private sortedIndices = new Map<string, SortedFieldIndex>()
|
||||||
|
|
||||||
|
// Type-field affinity for intelligent NLP
|
||||||
|
private typeFieldAffinity = new Map<string, Map<string, number>>()
|
||||||
|
|
||||||
|
interface MetadataIndexEntry {
|
||||||
|
field: string
|
||||||
|
value: string | number | boolean
|
||||||
|
ids: Set<string> // O(1) add/remove/has
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SortedFieldIndex {
|
||||||
|
values: Array<[value: any, ids: Set<string>]> // Sorted for O(log n) ranges
|
||||||
|
fieldType: 'number' | 'string' | 'date'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. Each field+value combination gets a unique key: `"category:tech"`
|
||||||
|
2. Map lookup is O(1) average case
|
||||||
|
3. Returns a Set of matching IDs instantly
|
||||||
|
|
||||||
|
**Example Query:**
|
||||||
|
```javascript
|
||||||
|
// Query: { where: { category: 'tech' } }
|
||||||
|
// Internally: indexCache.get('category:tech') → O(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Range Queries - O(log n)
|
||||||
|
|
||||||
|
For numeric/date fields, Brainy maintains sorted indices:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface SortedFieldIndex {
|
||||||
|
values: Array<[value: any, ids: Set<string>]> // Sorted by value
|
||||||
|
fieldType: 'number' | 'string' | 'date'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. Binary search to find range start: O(log n)
|
||||||
|
2. Binary search to find range end: O(log n)
|
||||||
|
3. Collect all IDs in range: O(k) where k = items in range
|
||||||
|
|
||||||
|
**Example Query:**
|
||||||
|
```javascript
|
||||||
|
// Query: { where: { age: { greaterThan: 25, lessThan: 40 } } }
|
||||||
|
// Internally: binarySearch(25) + binarySearch(40) + collect
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Graph Adjacency Index - O(1) Traversal
|
||||||
|
|
||||||
|
The `GraphAdjacencyIndex` provides instant graph traversal:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class GraphAdjacencyIndex {
|
||||||
|
// Bidirectional adjacency lists
|
||||||
|
private sourceIndex = new Map<string, Set<string>>() // id → outgoing
|
||||||
|
private targetIndex = new Map<string, Set<string>>() // id → incoming
|
||||||
|
|
||||||
|
// O(1) neighbor lookup
|
||||||
|
async getNeighbors(id: string, direction: 'in' | 'out' | 'both') {
|
||||||
|
const outgoing = this.sourceIndex.get(id) // O(1)
|
||||||
|
const incoming = this.targetIndex.get(id) // O(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Innovation:** Pure Map/Set operations - no database queries, no loops, just direct memory access.
|
||||||
|
|
||||||
|
### 4. Vector Index - O(log n)
|
||||||
|
|
||||||
|
The default vector index (`JsHnswVectorIndex`) provides logarithmic approximate nearest neighbor search through a hierarchical graph:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class JsHnswVectorIndex {
|
||||||
|
private nouns: Map<string, HNSWNoun> = new Map()
|
||||||
|
|
||||||
|
interface HNSWNoun {
|
||||||
|
id: string
|
||||||
|
vector: number[]
|
||||||
|
connections: Map<number, Set<string>> // layer → neighbors
|
||||||
|
level: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. Start at entry point (top layer)
|
||||||
|
2. Greedy search to find nearest neighbor at each layer
|
||||||
|
3. Move down layers for progressively finer search
|
||||||
|
4. Each layer has M connections (typically 16)
|
||||||
|
|
||||||
|
**Performance:** O(log n) due to hierarchical structure
|
||||||
|
|
||||||
|
### 5. Type-Aware NLP with Dynamic Field Discovery
|
||||||
|
|
||||||
|
The NLP processor uses **zero hardcoded fields** - everything is discovered dynamically from actual data:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class NaturalLanguageProcessor {
|
||||||
|
// Pre-embedded NounTypes (42) and VerbTypes (127) - ONLY hardcoded vocabularies
|
||||||
|
private nounTypeEmbeddings = new Map<string, Vector>()
|
||||||
|
private verbTypeEmbeddings = new Map<string, Vector>()
|
||||||
|
|
||||||
|
// Dynamic field embeddings from actual indexed data
|
||||||
|
private fieldEmbeddings = new Map<string, Vector>()
|
||||||
|
|
||||||
|
// Type-field affinity for intelligent prioritization
|
||||||
|
async getFieldsForType(nounType: NounType) {
|
||||||
|
return this.brain.getFieldsForType(nounType) // Real data patterns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Type-Aware Intelligence Flow:**
|
||||||
|
1. **Type Detection**: "documents" → `NounType.Document` (semantic similarity)
|
||||||
|
2. **Field Prioritization**: Get fields common to Document type from real data
|
||||||
|
3. **Semantic Field Matching**: "by" → "author" (with type affinity boost)
|
||||||
|
4. **Validation**: Ensure "author" field actually appears with Document entities
|
||||||
|
5. **Query Optimization**: Process low-cardinality type-specific fields first
|
||||||
|
|
||||||
|
**Performance Characteristics:**
|
||||||
|
- Type detection: O(t) where t = 169 total types (42 noun + 127 verb)
|
||||||
|
- Field matching: O(f) where f = fields for detected type (typically 5-15)
|
||||||
|
- Validation: O(1) lookup in type-field affinity map
|
||||||
|
- No hardcoded assumptions - learns from actual data patterns
|
||||||
|
|
||||||
|
### 6. NLP with 220 Pre-computed Patterns
|
||||||
|
|
||||||
|
Pattern matching with embedded templates for instant semantic understanding:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 394KB of embedded patterns compiled into the source
|
||||||
|
export const EMBEDDED_PATTERNS: Pattern[] = [/* 220 patterns */]
|
||||||
|
export const PATTERN_EMBEDDINGS: Float32Array = /* 220 × 384 dimensions */
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. Query embedding computed once: O(1) with cached model
|
||||||
|
2. Cosine similarity with 220 patterns: O(m) where m = 220
|
||||||
|
3. Pattern templates enhanced with type context
|
||||||
|
4. No network calls, no external dependencies, no hardcoded fields
|
||||||
|
|
||||||
|
## Parallel Execution
|
||||||
|
|
||||||
|
Triple Intelligence queries execute searches in parallel:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Vector and proximity searches run simultaneously
|
||||||
|
const searchPromises = [
|
||||||
|
this.executeVectorSearch(params), // Runs in parallel
|
||||||
|
this.executeProximitySearch(params) // Runs in parallel
|
||||||
|
]
|
||||||
|
const results = await Promise.all(searchPromises)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Memory Efficiency
|
||||||
|
|
||||||
|
### Space Complexity
|
||||||
|
|
||||||
|
| Component | Memory Usage | Formula |
|
||||||
|
|-----------|--------------|---------|
|
||||||
|
| Metadata Index | ~40 bytes/entry | `(key_size + 8) × unique_values + 8 × total_items` |
|
||||||
|
| Graph Index | ~24 bytes/edge | `16 × edges + 8 × nodes` |
|
||||||
|
| Vector Index | ~1.5KB/item | `vector_size × 4 + M × 8 × layers` |
|
||||||
|
| Pattern Library | 394KB fixed | Pre-computed, shared across instances |
|
||||||
|
| Type Embeddings | ~60KB fixed | 70 types × 384 dimensions × 4 bytes, cached |
|
||||||
|
| Field Embeddings | ~5KB dynamic | Actual fields × 384 dimensions × 4 bytes |
|
||||||
|
| Type-Field Affinity | ~2KB dynamic | Type-field occurrence counts |
|
||||||
|
|
||||||
|
### Caching Strategy
|
||||||
|
|
||||||
|
- **Metadata Cache**: LRU with 5-minute TTL, 500 entries max
|
||||||
|
- **Embedding Cache**: Permanent for session, prevents recomputation
|
||||||
|
- **Unified Cache**: Coordinates memory across all components
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
### Illustrative Single Run (100 items, one machine)
|
||||||
|
|
||||||
|
Example output from a single 100-item run — illustrative only, not a committed
|
||||||
|
benchmark; absolute numbers vary by hardware. The values feed the
|
||||||
|
[Core Performance Summary](#core-performance-summary) example-latency column.
|
||||||
|
|
||||||
|
```
|
||||||
|
Metadata exact match: 0.818ms (50 items matched)
|
||||||
|
Metadata range query: 0.631ms (40 items in range)
|
||||||
|
Graph neighbor lookup: 0.092ms (2 connections)
|
||||||
|
Vector k-NN search: 1.773ms (10 nearest neighbors)
|
||||||
|
NLP query parsing: 8.906ms (full natural language)
|
||||||
|
Triple Intelligence: 1.830ms (combined query)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scaling Characteristics
|
||||||
|
|
||||||
|
Each stage scales by its algorithmic complexity, not a fixed millisecond figure
|
||||||
|
— absolute latency depends on hardware, embedding model, and storage backend.
|
||||||
|
Only the graph adjacency index carries a committed scale assertion:
|
||||||
|
|
||||||
|
| Query stage | Complexity | Scaling behavior |
|
||||||
|
|-------------|------------|------------------|
|
||||||
|
| Metadata filter (exact) | O(1) | Constant — independent of dataset size |
|
||||||
|
| Metadata filter (range) | O(log n) + O(k) | Sub-linear; k = matching results |
|
||||||
|
| Vector search (HNSW) | O(log n) | Degrades gracefully via hierarchical layers |
|
||||||
|
| Graph hop | O(1) | Measured <1 ms per neighbor lookup, validated up to 1M relationships (`tests/performance/graph-scale-performance.test.ts:238`) |
|
||||||
|
| Combined query | O(log n) | Bounded by the vector stage; metadata and graph stages stay O(1)/O(log n) |
|
||||||
|
|
||||||
|
## Comparison with Other Systems
|
||||||
|
|
||||||
|
| System | Metadata Filter | Graph Traversal | Vector Search | Natural Language |
|
||||||
|
|--------|-----------------|-----------------|---------------|------------------|
|
||||||
|
| **Brainy** | O(1) HashMap | O(1) Adjacency | O(log n) vector index | 220 patterns |
|
||||||
|
| Neo4j | O(log n) B-tree | O(k) traversal | Not native | Not native |
|
||||||
|
| Elasticsearch | O(log n) inverted | Not native | O(n) brute force* | Basic tokenization |
|
||||||
|
| PostgreSQL | O(log n) B-tree | O(k) recursive | O(n) brute force* | Full-text only |
|
||||||
|
| Pinecone | Not native | Not native | O(log n) | Not native |
|
||||||
|
|
||||||
|
*Without additional plugins/extensions
|
||||||
|
|
||||||
|
## Key Innovations
|
||||||
|
|
||||||
|
1. **True O(1) Metadata Filtering**: Most databases use B-trees (O(log n)). Brainy uses HashMaps for constant-time lookups.
|
||||||
|
|
||||||
|
2. **O(1) Graph Traversal**: Unlike traditional graph databases that traverse edges, Brainy maintains bidirectional adjacency maps for instant neighbor access.
|
||||||
|
|
||||||
|
3. **Unified Triple Intelligence**: First system to natively combine O(1) metadata, O(1) graph, and O(log n) vector search in a single query.
|
||||||
|
|
||||||
|
4. **Embedded NLP**: 220 research-based patterns with pre-computed embeddings compiled directly into the codebase - no external dependencies.
|
||||||
|
|
||||||
|
5. **Parallel Search Execution**: Vector, metadata, and graph searches execute simultaneously, not sequentially.
|
||||||
|
|
||||||
|
## Production Readiness
|
||||||
|
|
||||||
|
- ✅ **No External Dependencies**: All algorithms implemented in pure TypeScript
|
||||||
|
- ✅ **No Network Calls**: Everything runs locally, including embeddings
|
||||||
|
- ✅ **Thread-Safe**: Immutable data structures where possible
|
||||||
|
- ✅ **Memory Bounded**: Configurable cache sizes and automatic cleanup
|
||||||
|
- ✅ **Single-Node by Design**: One process owns one `path`; scale out at the service layer
|
||||||
|
- ✅ **Zero Stubs**: Every line of code is production-ready
|
||||||
|
|
||||||
|
## Lazy Loading Performance
|
||||||
|
|
||||||
|
Brainy supports two initialization modes for optimal performance across different use cases:
|
||||||
|
|
||||||
|
### Mode 1: Auto-Rebuild (Default)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const brain = new Brainy()
|
||||||
|
await brain.init() // Rebuilds indexes during init (~500ms-3s for 10K entities)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
- Init time: 500ms-3s (depends on dataset size)
|
||||||
|
- First query: Instant (indexes already loaded)
|
||||||
|
- Use case: Traditional applications, long-running servers
|
||||||
|
|
||||||
|
### Mode 2: Lazy Loading
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const brain = new Brainy({ disableAutoRebuild: true })
|
||||||
|
await brain.init() // Returns instantly (0-10ms)
|
||||||
|
|
||||||
|
const results = await brain.find({ limit: 10 }) // First query triggers rebuild (~50-200ms)
|
||||||
|
const more = await brain.find({ limit: 100 }) // Subsequent queries instant (0ms check)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Performance:**
|
||||||
|
- Init time: 0-10ms (instant)
|
||||||
|
- First query: 50-200ms (includes index rebuild for 1K-10K entities)
|
||||||
|
- Subsequent queries: 0ms check (instant)
|
||||||
|
- Concurrent queries: Wait for same rebuild (mutex prevents duplicates)
|
||||||
|
|
||||||
|
**Concurrency Safety:**
|
||||||
|
```javascript
|
||||||
|
// 100 concurrent queries immediately after init
|
||||||
|
await brain.init()
|
||||||
|
|
||||||
|
const promises = Array.from({ length: 100 }, () =>
|
||||||
|
brain.find({ limit: 10 })
|
||||||
|
)
|
||||||
|
|
||||||
|
const results = await Promise.all(promises)
|
||||||
|
// ✅ Only 1 rebuild triggered (mutex)
|
||||||
|
// ✅ All 100 queries return correct results
|
||||||
|
// ✅ Total time: ~60ms (not 6000ms!)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use Cases for Lazy Loading:**
|
||||||
|
- **Serverless/Edge**: Minimize cold start time (0-10ms init)
|
||||||
|
- **Development**: Faster restarts during development
|
||||||
|
- **Large datasets**: Defer index loading until needed
|
||||||
|
- **Read-heavy workloads**: Writes don't wait for index rebuild
|
||||||
|
|
||||||
|
## Zero Configuration Required
|
||||||
|
|
||||||
|
Brainy is designed to be **smart enough to tune itself dynamically**. No configuration needed:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// That's it. Brainy handles everything.
|
||||||
|
const brain = new Brainy()
|
||||||
|
await brain.init()
|
||||||
|
|
||||||
|
// Or with lazy loading for serverless
|
||||||
|
const brain = new Brainy({ disableAutoRebuild: true })
|
||||||
|
await brain.init() // Instant (0-10ms)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Self-Tuning
|
||||||
|
|
||||||
|
- **Metadata Index**: Auto-builds sorted indices for range queries on first use
|
||||||
|
- **Graph Index**: Auto-flushes every 30 seconds
|
||||||
|
- **Default Tuning**: Research-based vector index defaults
|
||||||
|
- **Lazy Loading**: Indices built only when needed
|
||||||
|
- **Cache Management**: LRU caches with TTL
|
||||||
|
|
||||||
|
### Intelligent Defaults
|
||||||
|
|
||||||
|
- **Vector recall** = `'balanced'` (M=16, ef=200): right for most datasets
|
||||||
|
- **Cache TTL** = 5 min: balances freshness and performance
|
||||||
|
- **Flush interval** = 30 s: non-blocking background persistence
|
||||||
|
|
||||||
|
### Vector Index Tuning Knobs
|
||||||
|
|
||||||
|
Brainy 8.0 exposes two knobs on `config.vector`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const brain = new Brainy({
|
||||||
|
vector: {
|
||||||
|
recall: 'fast', // 'fast' | 'balanced' | 'accurate'
|
||||||
|
persistMode: 'deferred' // 'immediate' | 'deferred'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The default JS index is `JsHnswVectorIndex`. An optional native acceleration package (`@soulcraft/cor`) can replace it with a higher-performing implementation; the public knobs stay the same.
|
||||||
|
|
||||||
|
### Scale Scenarios
|
||||||
|
|
||||||
|
| Scale | Items | Storage Strategy | Performance |
|
||||||
|
|-------|-------|------------------|-------------|
|
||||||
|
| **Small** | <10K | Memory | Sub-millisecond |
|
||||||
|
| **Medium** | 10K-1M | Filesystem | 1-5ms |
|
||||||
|
| **Large** | 1M-10M | Filesystem + tuned cache | 2-10ms |
|
||||||
|
| **Massive** | 10M+ | Filesystem + native vector provider + service-layer sharding | 5-20ms |
|
||||||
|
|
||||||
|
For >10M entities, run multiple Brainy processes behind your own routing layer — Brainy 8.0 doesn't ship cluster coordination.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Application Layer │
|
||||||
|
│ (Your Code) │
|
||||||
|
└─────────────┬───────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────▼───────────────────────────┐
|
||||||
|
│ Brainy Core │
|
||||||
|
│ (Triple Intelligence Engine) │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ Memory │ Vector │ Metadata │
|
||||||
|
│ Cache │ Index │ Index │
|
||||||
|
└─────────────┬───────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────▼───────────────────────────┐
|
||||||
|
│ Storage Layer │
|
||||||
|
├──────────┬──────────┬──────────────────┤
|
||||||
|
│ Vectors │ Graph │ Files │
|
||||||
|
│ (sharded)│ Edges │ (filesystem) │
|
||||||
|
└──────────┴──────────┴──────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
For off-site replication, snapshot `path` from your scheduler (`gsutil rsync`, `aws s3 sync`, `rclone`, or `tar`).
|
||||||
|
|
||||||
|
### Performance at Scale
|
||||||
|
|
||||||
|
- **Metadata queries**: O(1) HashMap
|
||||||
|
- **Graph traversal**: O(1) adjacency lookup
|
||||||
|
- **Vector search**: O(log n)
|
||||||
|
- **Write throughput**: 50K+ writes/second per process (filesystem, batched)
|
||||||
|
- **Read throughput**: 1M+ reads/second with caching
|
||||||
|
|
||||||
|
### Zero-Config with Autoscaling
|
||||||
|
|
||||||
|
- **AutoConfiguration System**: Detects environment and adjusts settings
|
||||||
|
- **Learning from Performance**: `learnFromPerformance()` adapts based on metrics
|
||||||
|
- **Auto-flush**: Graph index (30s), Metadata index (configurable)
|
||||||
|
- **Auto-optimize**: Enabled by default in graph and vector indices
|
||||||
|
- **Zero-config presets**: Production, development, minimal modes
|
||||||
|
- **Adaptive memory**: Scales caches based on available memory
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
|
||||||
|
### Fully Implemented and Production-Ready
|
||||||
|
- **O(1) metadata lookups** via HashMaps (exact match)
|
||||||
|
- **O(log n) range queries** via sorted arrays with lazy building
|
||||||
|
- **O(1) graph traversal** via adjacency maps
|
||||||
|
- **O(log n) vector search** via the default JS index, swappable for a native provider
|
||||||
|
- **220 NLP patterns** with pre-computed embeddings
|
||||||
|
- **Filesystem and memory storage** adapters
|
||||||
|
- **Auto-configuration system** with environment detection
|
||||||
|
- **Zero-config operation** with intelligent defaults
|
||||||
|
- **Auto-flush and auto-optimize** in indices
|
||||||
|
- **Low-latency Triple Intelligence queries** (O(log n) vector + O(1) metadata/graph)
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Brainy delivers on its promise of **production-ready Triple Intelligence** with documented algorithmic-complexity guarantees and a committed graph-scale benchmark (`tests/performance/graph-scale-performance.test.ts`). All listed features are fully implemented and tested. No stubs, no mocks — just real, working code with characterized performance.
|
||||||
486
docs/PLUGINS.md
Normal file
486
docs/PLUGINS.md
Normal file
|
|
@ -0,0 +1,486 @@
|
||||||
|
---
|
||||||
|
title: Plugin System
|
||||||
|
slug: guides/plugins
|
||||||
|
public: true
|
||||||
|
category: guides
|
||||||
|
template: guide
|
||||||
|
order: 4
|
||||||
|
description: Replace any Brainy subsystem — distance functions, embeddings, vector index, metadata index, aggregation — with a custom implementation or optional native acceleration.
|
||||||
|
next:
|
||||||
|
- guides/storage-adapters
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plugin Development Guide
|
||||||
|
|
||||||
|
Brainy has a plugin system that allows third-party packages to replace internal subsystems with custom implementations. This is how `@soulcraft/cor` provides optional native acceleration, and it's the same system available to any developer.
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
Brainy's plugin system uses **named providers** — string keys mapped to implementations. During `init()`, brainy:
|
||||||
|
|
||||||
|
1. Imports each package listed in the `plugins` config array
|
||||||
|
2. Activates each plugin, passing a `BrainyPluginContext`
|
||||||
|
3. The plugin calls `context.registerProvider(key, implementation)` for each subsystem it provides
|
||||||
|
4. Brainy checks each provider key and wires the implementation into its internal pipeline
|
||||||
|
|
||||||
|
Installing the first-party accelerator is the opt-in: with the default config, brainy probes for `@soulcraft/cor` and loads it when present. Everything except "not installed" fails **loud** — a present-but-broken accelerator makes `init()` throw rather than silently degrading to the JS engines.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy() // @soulcraft/cor auto-detected when installed
|
||||||
|
const pinned = new Brainy({ plugins: ['@soulcraft/cor'] }) // or pin exactly what loads
|
||||||
|
const plain = new Brainy({ plugins: [] }) // or opt out of detection entirely
|
||||||
|
```
|
||||||
|
|
||||||
|
| `plugins` value | Behavior |
|
||||||
|
|---|---|
|
||||||
|
| `undefined` (default) | Guarded auto-detection of `@soulcraft/cor`: not installed → no plugins, silently; installed → loads + announces; installed-but-broken → `init()` throws |
|
||||||
|
| `false` / `[]` | No plugins, no detection (explicit opt-out) |
|
||||||
|
| `['@soulcraft/cor']` | Load only the listed packages; a listed plugin that fails to load throws |
|
||||||
|
|
||||||
|
Plugins registered programmatically via `brain.use(plugin)` are always activated regardless of the `plugins` config.
|
||||||
|
|
||||||
|
If no plugin provides a given key, brainy uses its built-in JavaScript implementation. This means brainy works perfectly standalone — plugins only enhance performance or add capabilities.
|
||||||
|
|
||||||
|
## Creating a Plugin
|
||||||
|
|
||||||
|
### 1. Implement the `BrainyPlugin` interface
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { BrainyPlugin, BrainyPluginContext } from '@soulcraft/brainy/plugin'
|
||||||
|
|
||||||
|
const myPlugin: BrainyPlugin = {
|
||||||
|
name: 'my-brainy-plugin', // Must be unique (typically your npm package name)
|
||||||
|
|
||||||
|
async activate(context: BrainyPluginContext): Promise<boolean> {
|
||||||
|
// Register your providers here
|
||||||
|
context.registerProvider('distance', myFastDistanceFunction)
|
||||||
|
|
||||||
|
// Return true if activation succeeded, false to skip
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
async deactivate(): Promise<void> {
|
||||||
|
// Optional cleanup when brainy.close() is called
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default myPlugin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Package exports
|
||||||
|
|
||||||
|
Your package must export the plugin as the default export so brainy's plugin loader can resolve it:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// index.ts
|
||||||
|
export { default } from './plugin.js'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Registration
|
||||||
|
|
||||||
|
**Config-based:** List your package name in the brainy config:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
plugins: ['my-brainy-plugin']
|
||||||
|
})
|
||||||
|
await brain.init()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Programmatic registration:** For plugins not installed as npm packages, use `brain.use()`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Brainy } from '@soulcraft/brainy'
|
||||||
|
import myPlugin from './my-plugin.js'
|
||||||
|
|
||||||
|
const brain = new Brainy()
|
||||||
|
brain.use(myPlugin)
|
||||||
|
await brain.init()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Provider Keys Reference
|
||||||
|
|
||||||
|
Each key has a specific expected signature. Brainy checks for these during `init()` and wires them into the appropriate code paths.
|
||||||
|
|
||||||
|
### Core Providers
|
||||||
|
|
||||||
|
#### `distance`
|
||||||
|
**Type:** `(a: number[], b: number[]) => number`
|
||||||
|
|
||||||
|
Replaces the default cosine distance function used in vector search and neural APIs. This is the highest-impact single provider — it's called for every vector comparison.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
context.registerProvider('distance', (a: number[], b: number[]): number => {
|
||||||
|
// Your SIMD-accelerated or GPU distance calculation
|
||||||
|
return myFastCosineDistance(a, b)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `embeddings`
|
||||||
|
**Type:** `(text: string | string[]) => Promise<number[] | number[][]>`
|
||||||
|
|
||||||
|
Replaces the built-in WASM embedding engine. Called for every `brain.add()`, `brain.update()`, and `brain.find()` operation that involves text.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
context.registerProvider('embeddings', async (text: string | string[]) => {
|
||||||
|
if (Array.isArray(text)) {
|
||||||
|
return myEngine.embedBatch(text)
|
||||||
|
}
|
||||||
|
return myEngine.embed(text)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `embedBatch`
|
||||||
|
**Type:** `(texts: string[]) => Promise<number[][]>`
|
||||||
|
|
||||||
|
Dedicated batch embedding provider. When registered, brainy uses this for bulk operations (import, reindex, batch add) instead of calling the `embeddings` provider N times. This enables true single-forward-pass batch processing.
|
||||||
|
|
||||||
|
Priority order for batch operations:
|
||||||
|
1. `embedBatch` provider (single forward pass — fastest)
|
||||||
|
2. `embeddings` provider with `Promise.all()` (N individual calls)
|
||||||
|
3. Built-in WASM batch API (fallback)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
context.registerProvider('embedBatch', async (texts: string[]) => {
|
||||||
|
// Process all texts in a single forward pass
|
||||||
|
return myEngine.batchEmbed(texts)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Index Providers
|
||||||
|
|
||||||
|
> **Write-path invariant (the change-feed contract).** Every canonical
|
||||||
|
> mutation flows through Brainy's generation-store commit points — index
|
||||||
|
> providers are invoked *inside* that commit and never originate canonical
|
||||||
|
> writes of their own. The `brain.onChange` change feed is emitted from those
|
||||||
|
> commit points and relies on this: **a plugin must never introduce a write
|
||||||
|
> path that bypasses the generation-store commit.** If a future provider ever
|
||||||
|
> needs a direct native ingest path, it must either route through the commit
|
||||||
|
> or emit equivalent change events — otherwise every `onChange` consumer
|
||||||
|
> (live UIs, cache invalidation, realtime sync) silently develops a blind
|
||||||
|
> spot.
|
||||||
|
|
||||||
|
#### `vector`
|
||||||
|
**Type:** `(config: object, distanceFunction: Function, options: object) => VectorIndexProvider-compatible`
|
||||||
|
|
||||||
|
Factory function that creates a vector index instance. The returned object must implement the `VectorIndexProvider` public API:
|
||||||
|
|
||||||
|
- `addItem(item: { id: string, vector: number[] }): Promise<string>`
|
||||||
|
- `search(queryVector: number[], k: number, filter?, options?): Promise<Array<[string, number]>>`
|
||||||
|
- `removeItem(id: string): Promise<boolean>`
|
||||||
|
- `size(): number`
|
||||||
|
- `clear(): void`
|
||||||
|
- `flush(): Promise<number>`
|
||||||
|
- `rebuild(options?): Promise<void>`
|
||||||
|
- `getDirtyNodeCount(): number`
|
||||||
|
- `getPersistMode(): 'immediate' | 'deferred'`
|
||||||
|
- `getEntryPointId(): string | null`
|
||||||
|
- `getMaxLevel(): number`
|
||||||
|
- `getDimension(): number | null`
|
||||||
|
- `getConfig(): object`
|
||||||
|
- `getDistanceFunction(): Function`
|
||||||
|
- `enableCOW(parent): void`
|
||||||
|
- `setUseParallelization(boolean): void`
|
||||||
|
|
||||||
|
For type-aware indexes (separate graph per noun type), also implement:
|
||||||
|
- `getIndexForType(type: string): VectorIndexProvider` (duck-typed detection)
|
||||||
|
- `search(queryVector, k, type?, filter?, options?): Promise<Array<[string, number]>>`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
context.registerProvider('vector', (config, distanceFn, options) => {
|
||||||
|
return new MyNativeVectorIndex(config, distanceFn, options)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### The readiness contract (all three index providers)
|
||||||
|
|
||||||
|
A provider that **persists its derived index** should implement the optional readiness
|
||||||
|
members so a warm reopen never pays a redundant rebuild-from-canonical:
|
||||||
|
|
||||||
|
- **`init?(): Promise<void>`** — eager cold-load. Brainy awaits it once during
|
||||||
|
`brain.init()`, after the metadata provider's `init()` (the id-mapper hydrates first)
|
||||||
|
and **before the rebuild gate**.
|
||||||
|
- **`isReady?(): boolean`** — honest durability signal. `true` ⇔ the persisted index is
|
||||||
|
loaded (or cheaply demand-loadable) and consistent with what was last persisted. When
|
||||||
|
exposed, the rebuild gate defers to this signal **instead of** the `size() === 0` /
|
||||||
|
`totalEntries === 0` heuristics — a disk-native index may report 0 resident entries
|
||||||
|
while fully durable. Never return `true` if the durable state failed to load: the
|
||||||
|
signal is honest in both directions, and a not-ready provider gets its rebuild even
|
||||||
|
when `size() > 0`.
|
||||||
|
- **`isMigrating?(): boolean`** — while `true`, the provider owns its index (background
|
||||||
|
migration); brainy skips its rebuild entirely.
|
||||||
|
|
||||||
|
Providers that implement none of these keep the size/count heuristics — correct for
|
||||||
|
engines whose `rebuild()` *is* their load path (like brainy's built-in JS vector index).
|
||||||
|
|
||||||
|
#### `metadataIndex`
|
||||||
|
**Type:** `(storage: StorageAdapter) => MetadataIndexManager-compatible`
|
||||||
|
|
||||||
|
Factory function that creates a metadata index. The returned object must implement the `MetadataIndexManager` interface including `init()`, `addEntity()`, `removeEntity()`, `query()`, `flush()`, `clear()`, etc.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
context.registerProvider('metadataIndex', (storage) => {
|
||||||
|
return new MyNativeMetadataIndex(storage)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `graphIndex`
|
||||||
|
**Type:** `(storage: StorageAdapter) => GraphAdjacencyIndex-compatible`
|
||||||
|
|
||||||
|
Factory function that creates a graph adjacency index for relationship tracking (verbs/triples). Must implement the `GraphAdjacencyIndex` interface including `addVerb()`, `getVerbsBySource()`, `getVerbsByTarget()`, `flush()`, etc.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
context.registerProvider('graphIndex', (storage) => {
|
||||||
|
return new MyNativeGraphIndex(storage)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `aggregation`
|
||||||
|
**Type:** `(storage: StorageAdapter) => AggregationProvider-compatible`
|
||||||
|
|
||||||
|
Factory function that creates an aggregation engine for write-time incremental SUM/COUNT/AVG/MIN/MAX with GROUP BY and time windows. The returned object must implement the `AggregationProvider` interface.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
context.registerProvider('aggregation', (storage) => {
|
||||||
|
return new MyNativeAggregationEngine(storage)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When provided by an optional native acceleration plugin (such as `@soulcraft/cor`), this enables:
|
||||||
|
- Compiled source filters (vs per-entity JS object traversal)
|
||||||
|
- Precise MIN/MAX via sorted data structures (vs lazy recompute)
|
||||||
|
- Parallel aggregate rebuild across CPU cores
|
||||||
|
- SIMD-accelerated timestamp bucketing
|
||||||
|
|
||||||
|
### Utility Providers
|
||||||
|
|
||||||
|
#### `cache`
|
||||||
|
**Type:** `UnifiedCache`
|
||||||
|
|
||||||
|
Replaces the global `UnifiedCache` singleton used for VFS path resolution, semantic caching, and vector index caching. Must implement the `UnifiedCache` interface (available from `@soulcraft/brainy/internals`).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { UnifiedCache } from '@soulcraft/brainy/internals'
|
||||||
|
|
||||||
|
context.registerProvider('cache', myNativeCache)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `entityIdMapper`
|
||||||
|
**Type:** `(storage: StorageAdapter) => EntityIdMapper-compatible`
|
||||||
|
|
||||||
|
Factory for bidirectional UUID ↔ integer mapping used by roaring bitmaps. Must implement `getOrAssign()`, `getUuid()`, `getInt()`, `has()`, `remove()`, `flush()`, `clear()`.
|
||||||
|
|
||||||
|
#### `roaring`
|
||||||
|
**Type:** `RoaringBitmap32 class`
|
||||||
|
|
||||||
|
Replacement for the roaring bitmap implementation. Used internally by the metadata index for set operations. Must be API-compatible with `roaring-wasm`.
|
||||||
|
|
||||||
|
#### `msgpack`
|
||||||
|
**Type:** `{ encode: (data: any) => Buffer, decode: (buffer: Buffer) => any }`
|
||||||
|
|
||||||
|
Native msgpack encode/decode for SSTable serialization.
|
||||||
|
|
||||||
|
### Analytics Providers (Native-Only)
|
||||||
|
|
||||||
|
These provider keys have **no JavaScript fallback** — they represent capabilities that require native code (SIMD, mmap, sub-microsecond latency). They are available when an optional native acceleration plugin (such as `@soulcraft/cor`) is installed.
|
||||||
|
|
||||||
|
Use `brain.getProvider('analytics:hyperloglog')` to check availability. Returns `undefined` if no plugin provides it.
|
||||||
|
|
||||||
|
#### `analytics:hyperloglog`
|
||||||
|
Approximate distinct counts. Count unique values (e.g., unique merchants) across millions of records using ~16KB of memory with ~1% error. Each update is O(1).
|
||||||
|
|
||||||
|
#### `analytics:tdigest`
|
||||||
|
Streaming percentiles. Compute P50/P90/P95/P99 from streaming data without storing all values. Uses ~4KB per digest with ~1% accuracy at the tails.
|
||||||
|
|
||||||
|
#### `analytics:countmin`
|
||||||
|
Frequency estimation. Find the most common values (e.g., top-K merchants) using ~40KB with 0.1% error. O(1) per update.
|
||||||
|
|
||||||
|
#### `analytics:anomaly`
|
||||||
|
Real-time anomaly detection. Flag statistically unusual values at write-time using exponentially weighted moving averages. 64 bytes per group, sub-microsecond decisions.
|
||||||
|
|
||||||
|
#### `aggregation:mmap`
|
||||||
|
Persistent aggregate storage via memory-mapped files. Aggregate state survives process crashes without explicit flush. Zero serialization overhead.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage Adapter Plugins
|
||||||
|
|
||||||
|
Plugins can register custom storage backends that users reference by name.
|
||||||
|
|
||||||
|
### Implementing a Storage Adapter
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { StorageAdapterFactory } from '@soulcraft/brainy/plugin'
|
||||||
|
import type { StorageAdapter } from '@soulcraft/brainy'
|
||||||
|
|
||||||
|
class MyStorageAdapter implements StorageAdapter {
|
||||||
|
async init(): Promise<void> { /* ... */ }
|
||||||
|
async saveNoun(noun: HNSWNoun): Promise<void> { /* ... */ }
|
||||||
|
async getNoun(id: string): Promise<HNSWNounWithMetadata | null> { /* ... */ }
|
||||||
|
async deleteNoun(id: string): Promise<void> { /* ... */ }
|
||||||
|
// ... implement all StorageAdapter methods
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registering a Storage Adapter
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
context.registerProvider('storage:my-backend', {
|
||||||
|
name: 'my-backend',
|
||||||
|
create: (config: Record<string, unknown>) => {
|
||||||
|
return new MyStorageAdapter(config)
|
||||||
|
}
|
||||||
|
} satisfies StorageAdapterFactory)
|
||||||
|
```
|
||||||
|
|
||||||
|
Users can then use your storage:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({ storage: 'my-backend', myBackendOption: 'value' })
|
||||||
|
```
|
||||||
|
|
||||||
|
## Import Paths
|
||||||
|
|
||||||
|
Brainy provides three entry points for plugin developers:
|
||||||
|
|
||||||
|
| Import Path | Contents | Stability |
|
||||||
|
|-------------|----------|-----------|
|
||||||
|
| `@soulcraft/brainy` | Public API, types, StorageAdapter | Stable (semver) |
|
||||||
|
| `@soulcraft/brainy/plugin` | BrainyPlugin, BrainyPluginContext, StorageAdapterFactory | Stable (semver) |
|
||||||
|
| `@soulcraft/brainy/internals` | UnifiedCache, EntityIdMapper, logger utilities | Internal (may change between minor versions) |
|
||||||
|
|
||||||
|
## Diagnostics
|
||||||
|
|
||||||
|
Brainy provides a `diagnostics()` method to verify plugin wiring:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy()
|
||||||
|
await brain.init()
|
||||||
|
|
||||||
|
const diag = brain.diagnostics()
|
||||||
|
console.log(diag)
|
||||||
|
// {
|
||||||
|
// version: '7.14.0',
|
||||||
|
// plugins: { active: ['my-plugin'], count: 1 },
|
||||||
|
// providers: {
|
||||||
|
// metadataIndex: { source: 'default' },
|
||||||
|
// graphIndex: { source: 'default' },
|
||||||
|
// embeddings: { source: 'plugin' },
|
||||||
|
// embedBatch: { source: 'plugin' },
|
||||||
|
// distance: { source: 'plugin' },
|
||||||
|
// vector: { source: 'default' },
|
||||||
|
// ...
|
||||||
|
// },
|
||||||
|
// indexes: {
|
||||||
|
// vector: { size: 0, type: 'JsHnswVectorIndex' },
|
||||||
|
// metadata: { type: 'MetadataIndexManager', initialized: true },
|
||||||
|
// graph: { type: 'GraphAdjacencyIndex', initialized: true, wiredToStorage: true }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI also supports diagnostics:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brainy diagnostics
|
||||||
|
```
|
||||||
|
|
||||||
|
### Init-Time Summary
|
||||||
|
|
||||||
|
When a plugin is active, brainy automatically logs a provider summary after `init()`:
|
||||||
|
|
||||||
|
```
|
||||||
|
[brainy] Plugin activated: @soulcraft/cor
|
||||||
|
[brainy] Providers: 8/10 native (@soulcraft/cor) | default: vector, cache
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells you at a glance how many subsystems are accelerated and which ones are falling back to JavaScript. The log respects `config.silent`.
|
||||||
|
|
||||||
|
### Fail-Fast for Production
|
||||||
|
|
||||||
|
Use `requireProviders()` after `init()` to guarantee specific providers are plugin-supplied. This prevents silent fallback to JavaScript in deployments where you expect native acceleration:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy()
|
||||||
|
await brain.init()
|
||||||
|
|
||||||
|
// Throws immediately if any of these are using JS fallback
|
||||||
|
brain.requireProviders(['distance', 'embeddings', 'metadataIndex', 'graphIndex'])
|
||||||
|
```
|
||||||
|
|
||||||
|
If a required provider is missing, the error message tells you exactly what's wrong:
|
||||||
|
|
||||||
|
```
|
||||||
|
[brainy] Required providers using JS fallback: graphIndex.
|
||||||
|
Active plugins: @soulcraft/cor.
|
||||||
|
These providers must be supplied by a plugin for this deployment.
|
||||||
|
Check plugin installation, license, and native module availability.
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the recommended pattern for production deployments with paid plugins — fail at startup rather than silently degrading performance.
|
||||||
|
|
||||||
|
## Complete Example: Distance Acceleration Plugin
|
||||||
|
|
||||||
|
A minimal but useful plugin that provides SIMD-accelerated distance calculations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// simd-distance-plugin/src/plugin.ts
|
||||||
|
import type { BrainyPlugin, BrainyPluginContext } from '@soulcraft/brainy/plugin'
|
||||||
|
|
||||||
|
// Hypothetical native module
|
||||||
|
import { simdCosineDistance } from './native.js'
|
||||||
|
|
||||||
|
const simdDistancePlugin: BrainyPlugin = {
|
||||||
|
name: 'brainy-simd-distance',
|
||||||
|
|
||||||
|
async activate(context: BrainyPluginContext): Promise<boolean> {
|
||||||
|
// Check if SIMD is available on this platform
|
||||||
|
if (!checkSimdSupport()) {
|
||||||
|
console.log('[simd-distance] SIMD not available, skipping')
|
||||||
|
return false // Don't activate — brainy uses JS fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
context.registerProvider('distance', simdCosineDistance)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default simdDistancePlugin
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// simd-distance-plugin/package.json
|
||||||
|
{
|
||||||
|
"name": "brainy-simd-distance",
|
||||||
|
"main": "./dist/plugin.js",
|
||||||
|
"types": "./dist/plugin.d.ts",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@soulcraft/brainy": ">=7.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Brainy } from '@soulcraft/brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({ plugins: ['brainy-simd-distance'] })
|
||||||
|
await brain.init()
|
||||||
|
|
||||||
|
// Verify it's active
|
||||||
|
const diag = brain.diagnostics()
|
||||||
|
console.log(diag.providers.distance) // { source: 'plugin' }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
1. **Brainy works perfectly without plugins.** Every provider has a JavaScript fallback. Plugins only improve performance or add capabilities.
|
||||||
|
|
||||||
|
2. **Provider keys are string-based.** The plugin system is not coupled to any specific plugin. Any package can register any provider.
|
||||||
|
|
||||||
|
3. **Clean separation.** Plugins access brainy through the documented `BrainyPluginContext` interface. No direct access to internal classes is needed.
|
||||||
|
|
||||||
|
4. **Fail-safe activation.** If a plugin throws during `activate()`, brainy logs a warning and continues with defaults. A broken plugin never prevents brainy from working.
|
||||||
|
|
||||||
|
5. **Lifecycle management.** `deactivate()` is called during `brainy.close()` for resource cleanup. Native resources, connections, and file handles should be released here.
|
||||||
562
docs/PRODUCTION_SERVICE_ARCHITECTURE.md
Normal file
562
docs/PRODUCTION_SERVICE_ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,562 @@
|
||||||
|
# Production Service Architecture Guide
|
||||||
|
|
||||||
|
**How to use Brainy optimally in production services (Bun, Node.js, Deno)**
|
||||||
|
|
||||||
|
> **Recommended Runtime:** [Bun](https://bun.sh) provides best performance with Brainy's Candle WASM engine. All examples work with both Bun and Node.js.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Problem: Instance-per-Request Anti-Pattern
|
||||||
|
|
||||||
|
### ❌ What NOT to Do
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// WRONG - Creates new instance EVERY request
|
||||||
|
app.get('/api/entities', async (req, res) => {
|
||||||
|
const brain = new Brainy({ storage: { path: './brainy-data' } })
|
||||||
|
await brain.init() // FULL INITIALIZATION EVERY TIME!
|
||||||
|
const entities = await brain.find(...)
|
||||||
|
res.json(entities)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why This is Terrible
|
||||||
|
|
||||||
|
After 40 API calls:
|
||||||
|
- **40 Brainy instances** running simultaneously
|
||||||
|
- **20GB memory** (40 × 500MB per instance)
|
||||||
|
- **2 seconds wasted** (40 × 50ms initialization)
|
||||||
|
- **Zero cache benefit** (each instance has its own empty cache)
|
||||||
|
- **Index rebuilding** on every request (TypeAware HNSW, LSM-trees, etc.)
|
||||||
|
- **Memory leaks** (old instances may not GC properly)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ The Solution: Singleton Pattern
|
||||||
|
|
||||||
|
**ONE Brainy instance per service, shared across ALL requests.**
|
||||||
|
|
||||||
|
### Performance Comparison
|
||||||
|
|
||||||
|
| Metric | Instance-per-Request | Singleton (Optimal) |
|
||||||
|
|--------|---------------------|---------------------|
|
||||||
|
| Memory (40 requests) | 20GB | 500MB |
|
||||||
|
| Request 1 latency | 60ms | 60ms (one-time init) |
|
||||||
|
| Request 2+ latency | 60ms (no cache!) | 2ms (80% cache hit!) |
|
||||||
|
| Cache hit rate | 0% | 80%+ |
|
||||||
|
| Speedup | - | **30x faster** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Simple Singleton (Recommended)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// server.ts
|
||||||
|
import { Brainy } from '@soulcraft/brainy'
|
||||||
|
|
||||||
|
// SINGLETON INSTANCE
|
||||||
|
let brainInstance: Brainy | null = null
|
||||||
|
|
||||||
|
async function getBrain(): Promise<Brainy> {
|
||||||
|
if (brainInstance) {
|
||||||
|
return brainInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🧠 Initializing Brainy singleton...')
|
||||||
|
|
||||||
|
brainInstance = new Brainy({
|
||||||
|
storage: {
|
||||||
|
path: './brainy-data',
|
||||||
|
autoOptimize: true
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
maxSize: 1000, // Shared across ALL requests
|
||||||
|
ttl: 3600000, // 1 hour
|
||||||
|
enableMetrics: true
|
||||||
|
},
|
||||||
|
augmentations: {
|
||||||
|
include: ['cache', 'metrics', 'display', 'vfs']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await brainInstance.init()
|
||||||
|
console.log('✅ Brainy ready')
|
||||||
|
|
||||||
|
return brainInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize BEFORE starting server
|
||||||
|
async function startServer() {
|
||||||
|
await getBrain() // One-time initialization
|
||||||
|
|
||||||
|
app.get('/api/entities', async (req, res) => {
|
||||||
|
const brain = await getBrain() // Reuses same instance!
|
||||||
|
const entities = await brain.find(req.query)
|
||||||
|
res.json(entities)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
startServer()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Simple to implement
|
||||||
|
- ✅ Thread-safe (async initialization)
|
||||||
|
- ✅ Shared cache and indexes
|
||||||
|
- ✅ 40x memory reduction
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pattern 2: Service Class (Production-Grade)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// services/BrainService.ts
|
||||||
|
export class BrainService {
|
||||||
|
private brain: Brainy | null = null
|
||||||
|
private initPromise: Promise<Brainy> | null = null
|
||||||
|
|
||||||
|
async getInstance(): Promise<Brainy> {
|
||||||
|
if (this.brain) return this.brain
|
||||||
|
if (this.initPromise) return this.initPromise
|
||||||
|
|
||||||
|
this.initPromise = this.initialize()
|
||||||
|
return this.initPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initialize(): Promise<Brainy> {
|
||||||
|
this.brain = new Brainy({
|
||||||
|
storage: {
|
||||||
|
path: process.env.BRAINY_DATA_PATH || './brainy-data'
|
||||||
|
},
|
||||||
|
cache: { maxSize: 1000, ttl: 3600000 }
|
||||||
|
})
|
||||||
|
await this.brain.init()
|
||||||
|
return this.brain
|
||||||
|
}
|
||||||
|
|
||||||
|
async shutdown(): Promise<void> {
|
||||||
|
if (this.brain) {
|
||||||
|
// Cleanup if needed
|
||||||
|
this.brain = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ts
|
||||||
|
const brainService = new BrainService()
|
||||||
|
|
||||||
|
app.get('/api/entities', async (req, res) => {
|
||||||
|
const brain = await brainService.getInstance()
|
||||||
|
const entities = await brain.find(req.query)
|
||||||
|
res.json(entities)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
|
process.on('SIGTERM', async () => {
|
||||||
|
await brainService.shutdown()
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Prevents race conditions (multiple simultaneous inits)
|
||||||
|
- ✅ Testable (can inject mock)
|
||||||
|
- ✅ Clean shutdown handling
|
||||||
|
- ✅ Environment-configurable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Pattern 3: Bun Server (Recommended)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// server.ts - Clean Bun implementation
|
||||||
|
import { Brainy } from '@soulcraft/brainy'
|
||||||
|
|
||||||
|
let brain: Brainy | null = null
|
||||||
|
|
||||||
|
async function getBrain(): Promise<Brainy> {
|
||||||
|
if (!brain) {
|
||||||
|
brain = new Brainy({ storage: { path: './brainy-data' } })
|
||||||
|
await brain.init()
|
||||||
|
}
|
||||||
|
return brain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize before server starts
|
||||||
|
await getBrain()
|
||||||
|
|
||||||
|
Bun.serve({
|
||||||
|
port: 3000,
|
||||||
|
async fetch(req) {
|
||||||
|
const url = new URL(req.url)
|
||||||
|
|
||||||
|
if (url.pathname === '/api/entities') {
|
||||||
|
const b = await getBrain()
|
||||||
|
const entities = await b.find({})
|
||||||
|
return Response.json(entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === '/api/entity' && req.method === 'POST') {
|
||||||
|
const b = await getBrain()
|
||||||
|
const body = await req.json()
|
||||||
|
const id = await b.add(body)
|
||||||
|
return Response.json({ id })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response('Not Found', { status: 404 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Server running on http://localhost:3000')
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Native Bun runtime performance
|
||||||
|
- ✅ No framework dependencies
|
||||||
|
- ✅ Pure WASM — no native binaries, bundler-friendly
|
||||||
|
- ✅ Built-in TypeScript support
|
||||||
|
|
||||||
|
### Pattern 4: Express/Node.js Middleware (Legacy)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// middleware/brainy.ts
|
||||||
|
let brainInstance: Brainy | null = null
|
||||||
|
|
||||||
|
export async function initBrainy() {
|
||||||
|
if (!brainInstance) {
|
||||||
|
brainInstance = new Brainy({ storage: { path: './brainy-data' } })
|
||||||
|
await brainInstance.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function brainMiddleware(req, res, next) {
|
||||||
|
if (!brainInstance) {
|
||||||
|
return res.status(500).json({ error: 'Brainy not initialized' })
|
||||||
|
}
|
||||||
|
req.brain = brainInstance // Attach to request
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type extension
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
interface Request {
|
||||||
|
brain: Brainy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// server.ts
|
||||||
|
import { initBrainy, brainMiddleware } from './middleware/brainy'
|
||||||
|
|
||||||
|
async function startServer() {
|
||||||
|
await initBrainy() // Initialize first
|
||||||
|
|
||||||
|
app.use('/api', brainMiddleware) // Apply to API routes
|
||||||
|
|
||||||
|
app.get('/api/entities', async (req, res) => {
|
||||||
|
const entities = await req.brain.find(req.query) // Type-safe!
|
||||||
|
res.json(entities)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(3000)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Clean separation of concerns
|
||||||
|
- ✅ Type-safe (`req.brain` is typed)
|
||||||
|
- ✅ Easy to add auth/validation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Optimization Strategies
|
||||||
|
|
||||||
|
### 1. Configure Cache for Your Workload
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
cache: {
|
||||||
|
maxSize: 1000, // Number of entities to cache
|
||||||
|
ttl: 3600000, // Cache lifetime (1 hour)
|
||||||
|
enableMetrics: true, // Track hit rate
|
||||||
|
evictionPolicy: 'lru' // Least recently used
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cache sizing:**
|
||||||
|
- Small service (< 100 req/min): `maxSize: 500`
|
||||||
|
- Medium service (< 1000 req/min): `maxSize: 1000`
|
||||||
|
- Large service (> 1000 req/min): `maxSize: 5000`
|
||||||
|
|
||||||
|
### 2. Lazy Load Augmentations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: {
|
||||||
|
// Only load what you actually use
|
||||||
|
include: ['cache', 'metrics', 'display', 'vfs'],
|
||||||
|
exclude: ['neuralImport', 'intelligentImport'] // Skip heavy features
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Memory savings:**
|
||||||
|
- With all augmentations: ~800MB
|
||||||
|
- With minimal set: ~400MB
|
||||||
|
|
||||||
|
### 3. Warm Up Indexes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async function startServer() {
|
||||||
|
const brain = await getBrain()
|
||||||
|
|
||||||
|
// Pre-warm frequently-used indexes
|
||||||
|
await brain.find({ type: 'person', limit: 1 })
|
||||||
|
await brain.find({ type: 'organization', limit: 1 })
|
||||||
|
|
||||||
|
console.log('✅ Indexes pre-warmed')
|
||||||
|
|
||||||
|
app.listen(3000)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefit:** First requests are fast (no cold-start index building)
|
||||||
|
|
||||||
|
### 4. Memory-Aware Configuration
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import os from 'os'
|
||||||
|
|
||||||
|
const totalMemory = os.totalmem()
|
||||||
|
const availableMemory = os.freemem()
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
cache: {
|
||||||
|
// Use 10% of total RAM for cache
|
||||||
|
maxSize: Math.floor(totalMemory * 0.1 / (1024 * 1024))
|
||||||
|
},
|
||||||
|
indexes: {
|
||||||
|
// Lazy load indexes if low memory
|
||||||
|
lazyLoad: availableMemory < totalMemory * 0.5,
|
||||||
|
preload: ['person', 'organization'] // Only preload common types
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Concurrency & Thread Safety
|
||||||
|
|
||||||
|
Brainy is **designed** for concurrent access. A single instance can handle:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Multiple concurrent requests - all using same instance
|
||||||
|
app.get('/api/read/:id', async (req, res) => {
|
||||||
|
const brain = getBrain()
|
||||||
|
const entity = await brain.get(req.params.id) // Safe - no state mutation
|
||||||
|
res.json(entity)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/api/write', async (req, res) => {
|
||||||
|
const brain = getBrain()
|
||||||
|
const id = await brain.add(req.body) // Safe - internal locking
|
||||||
|
res.json({ id })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Concurrency mechanisms:**
|
||||||
|
- ✅ **Read operations**: Lock-free (MVCC)
|
||||||
|
- ✅ **Write operations**: Internal write-ahead logging (WAL)
|
||||||
|
- ✅ **Cache**: Thread-safe LRU implementation
|
||||||
|
- ✅ **Indexes**: Concurrent reads, locked writes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Checklist
|
||||||
|
|
||||||
|
### Before Deploying
|
||||||
|
|
||||||
|
- [ ] **Initialize Brainy on startup** (not per-request)
|
||||||
|
- [ ] **Configure cache size** based on memory
|
||||||
|
- [ ] **Only load needed augmentations**
|
||||||
|
- [ ] **Warm up critical indexes**
|
||||||
|
- [ ] **Add graceful shutdown handler**
|
||||||
|
- [ ] **Monitor cache hit rate**
|
||||||
|
|
||||||
|
### Code Review Checklist
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ BAD - Instance per request
|
||||||
|
app.get('/api/route', async (req, res) => {
|
||||||
|
const brain = new Brainy(...) // RED FLAG!
|
||||||
|
await brain.init() // RED FLAG!
|
||||||
|
})
|
||||||
|
|
||||||
|
// ✅ GOOD - Singleton pattern
|
||||||
|
app.get('/api/route', async (req, res) => {
|
||||||
|
const brain = await getBrain() // Reuses instance ✓
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring & Metrics
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Add metrics endpoint
|
||||||
|
app.get('/api/metrics', (req, res) => {
|
||||||
|
const brain = getBrain()
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
cache: {
|
||||||
|
size: brain.cache?.size || 0,
|
||||||
|
maxSize: brain.cache?.maxSize || 0,
|
||||||
|
hitRate: brain.metrics?.cacheHitRate || 0 // Target: >70%
|
||||||
|
},
|
||||||
|
storage: brain.storage.getStats(),
|
||||||
|
memory: {
|
||||||
|
heapUsed: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
||||||
|
heapTotal: Math.round(process.memoryUsage().heapTotal / 1024 / 1024)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key metrics to track:**
|
||||||
|
- **Cache hit rate**: Should be >70% after warm-up
|
||||||
|
- **Memory usage**: Should stay constant (~500MB for singleton)
|
||||||
|
- **Request latency**: Should be <10ms for cached entities
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### 1. Creating instances in routes
|
||||||
|
```typescript
|
||||||
|
// ❌ NEVER do this
|
||||||
|
app.get('/api/entities', async (req, res) => {
|
||||||
|
const brain = new Brainy(...) // Creates new instance every time!
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Not awaiting initialization
|
||||||
|
```typescript
|
||||||
|
// ❌ Race condition - server starts before Brainy ready
|
||||||
|
app.listen(3000)
|
||||||
|
getBrain() // Async init happens AFTER server starts!
|
||||||
|
|
||||||
|
// ✅ Correct - wait for init
|
||||||
|
await getBrain()
|
||||||
|
app.listen(3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Multiple instances for different purposes
|
||||||
|
```typescript
|
||||||
|
// ❌ Wasteful - creates 2 instances
|
||||||
|
const readBrain = new Brainy(...)
|
||||||
|
const writeBrain = new Brainy(...)
|
||||||
|
|
||||||
|
// ✅ One instance handles both
|
||||||
|
const brain = new Brainy(...)
|
||||||
|
await brain.get(id) // Read
|
||||||
|
await brain.add(data) // Write
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
### Current (Anti-Pattern)
|
||||||
|
```typescript
|
||||||
|
// Probably in multiple route files
|
||||||
|
async function handler(req, res) {
|
||||||
|
const brain = new Brainy({ storage: { path: './brainy-data' } })
|
||||||
|
await brain.init()
|
||||||
|
// ... use brain
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1: Create Singleton Module
|
||||||
|
```typescript
|
||||||
|
// lib/brainy.ts
|
||||||
|
let instance: Brainy | null = null
|
||||||
|
|
||||||
|
export async function getBrain(): Promise<Brainy> {
|
||||||
|
if (!instance) {
|
||||||
|
instance = new Brainy({ storage: { path: './brainy-data' } })
|
||||||
|
await instance.init()
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Update Server Startup
|
||||||
|
```typescript
|
||||||
|
// server.ts
|
||||||
|
import { getBrain } from './lib/brainy'
|
||||||
|
|
||||||
|
async function startServer() {
|
||||||
|
// Initialize Brainy FIRST
|
||||||
|
await getBrain()
|
||||||
|
console.log('✅ Brainy initialized')
|
||||||
|
|
||||||
|
// THEN start server
|
||||||
|
app.listen(3000)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Update All Routes
|
||||||
|
```typescript
|
||||||
|
// Before
|
||||||
|
async function handler(req, res) {
|
||||||
|
const brain = new Brainy(...) // Remove this
|
||||||
|
await brain.init() // Remove this
|
||||||
|
|
||||||
|
// ... rest of code
|
||||||
|
}
|
||||||
|
|
||||||
|
// After
|
||||||
|
import { getBrain } from './lib/brainy'
|
||||||
|
|
||||||
|
async function handler(req, res) {
|
||||||
|
const brain = await getBrain() // Add this
|
||||||
|
|
||||||
|
// ... rest of code stays same
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected results:**
|
||||||
|
- ✅ 40x memory reduction (20GB → 500MB)
|
||||||
|
- ✅ 30x faster requests (60ms → 2ms average)
|
||||||
|
- ✅ 80%+ cache hit rate
|
||||||
|
- ✅ Your service can scale to 1000s of requests/minute
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
**DO:**
|
||||||
|
- ✅ Initialize Brainy ONCE on server startup
|
||||||
|
- ✅ Share single instance across all requests
|
||||||
|
- ✅ Configure cache for your workload
|
||||||
|
- ✅ Monitor cache hit rate
|
||||||
|
- ✅ Handle graceful shutdown
|
||||||
|
|
||||||
|
**DON'T:**
|
||||||
|
- ❌ Create new Brainy instance per request
|
||||||
|
- ❌ Create multiple instances
|
||||||
|
- ❌ Start server before Brainy is initialized
|
||||||
|
- ❌ Load augmentations you don't use
|
||||||
|
|
||||||
|
**Result:** 40x less memory, 30x faster requests, Brainy optimizations actually work!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Questions? Issues?**
|
||||||
|
- Report issues: https://github.com/soulcraftlabs/brainy/issues
|
||||||
298
docs/QUERY_OPERATORS.md
Normal file
298
docs/QUERY_OPERATORS.md
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
# Query Operators (BFO)
|
||||||
|
|
||||||
|
> Brainy Field Operators — the complete reference for `where` filters in `find()`.
|
||||||
|
|
||||||
|
All operators work with `find({ where: { ... } })` and filter on **metadata fields** (not `data`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Equality
|
||||||
|
|
||||||
|
| Operator | Alias | Description | Example |
|
||||||
|
|----------|-------|-------------|---------|
|
||||||
|
| `eq` | `equals` | Exact match | `{ status: { eq: 'active' } }` |
|
||||||
|
| `ne` | `notEquals` | Not equal | `{ status: { ne: 'deleted' } }` |
|
||||||
|
|
||||||
|
**Shorthand:** A bare value is treated as `equals`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// These are equivalent:
|
||||||
|
brain.find({ where: { status: 'active' } })
|
||||||
|
brain.find({ where: { status: { equals: 'active' } } })
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comparison
|
||||||
|
|
||||||
|
| Operator | Alias | Description | Example |
|
||||||
|
|----------|-------|-------------|---------|
|
||||||
|
| `gt` | `greaterThan` | Greater than | `{ age: { gt: 18 } }` |
|
||||||
|
| `gte` | `greaterThanOrEqual` | Greater or equal | `{ score: { gte: 90 } }` |
|
||||||
|
| `lt` | `lessThan` | Less than | `{ price: { lt: 100 } }` |
|
||||||
|
| `lte` | `lessThanOrEqual` | Less or equal | `{ rating: { lte: 3 } }` |
|
||||||
|
| `between` | — | Inclusive range `[min, max]` | `{ year: { between: [2020, 2025] } }` |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Range query
|
||||||
|
const recent = await brain.find({
|
||||||
|
where: {
|
||||||
|
createdAt: { between: [Date.now() - 86400000, Date.now()] }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Array / Set
|
||||||
|
|
||||||
|
| Operator | Alias | Description | Example |
|
||||||
|
|----------|-------|-------------|---------|
|
||||||
|
| `oneOf` | `in` | Value is one of the given options | `{ color: { oneOf: ['red', 'blue'] } }` |
|
||||||
|
| `noneOf` | — | Value is NOT one of the given options | `{ status: { noneOf: ['deleted', 'archived'] } }` |
|
||||||
|
| `contains` | — | Array field contains value | `{ tags: { contains: 'ai' } }` |
|
||||||
|
| `excludes` | — | Array field does NOT contain value | `{ tags: { excludes: 'spam' } }` |
|
||||||
|
| `hasAll` | — | Array field contains ALL listed values | `{ skills: { hasAll: ['js', 'ts'] } }` |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Find entities tagged with 'ai'
|
||||||
|
const aiEntities = await brain.find({
|
||||||
|
where: { tags: { contains: 'ai' } }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Find entities of specific types
|
||||||
|
const people = await brain.find({
|
||||||
|
where: { noun: { oneOf: ['Person', 'Agent'] } }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Existence
|
||||||
|
|
||||||
|
| Operator | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `exists: true` | Field exists (has any value) | `{ email: { exists: true } }` |
|
||||||
|
| `exists: false` | Field does NOT exist | `{ email: { exists: false } }` |
|
||||||
|
| `missing: true` | Field does NOT exist (alias for `exists: false`) | `{ email: { missing: true } }` |
|
||||||
|
| `missing: false` | Field exists (alias for `exists: true`) | `{ email: { missing: false } }` |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Find entities that have an email field
|
||||||
|
const withEmail = await brain.find({
|
||||||
|
where: { email: { exists: true } }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern (In-Memory Only)
|
||||||
|
|
||||||
|
These operators work via the in-memory filter path. They are applied **after** the indexed query, so use them with other indexed operators for best performance.
|
||||||
|
|
||||||
|
| Operator | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `matches` | Regex or string pattern match | `{ name: { matches: /^Dr\./ } }` |
|
||||||
|
| `startsWith` | String prefix | `{ name: { startsWith: 'John' } }` |
|
||||||
|
| `endsWith` | String suffix | `{ email: { endsWith: '@gmail.com' } }` |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const doctors = await brain.find({
|
||||||
|
where: {
|
||||||
|
type: NounType.Person, // Indexed — fast
|
||||||
|
name: { startsWith: 'Dr.' } // In-memory — applied after
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Logical
|
||||||
|
|
||||||
|
Combine multiple conditions:
|
||||||
|
|
||||||
|
| Operator | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `allOf` | ALL sub-filters must match (AND) | `{ allOf: [{ status: 'active' }, { role: 'admin' }] }` |
|
||||||
|
| `anyOf` | ANY sub-filter must match (OR) | `{ anyOf: [{ role: 'admin' }, { role: 'owner' }] }` |
|
||||||
|
| `not` | Invert a filter | `{ not: { status: 'deleted' } }` |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Complex OR query
|
||||||
|
const adminsOrOwners = await brain.find({
|
||||||
|
where: {
|
||||||
|
anyOf: [
|
||||||
|
{ role: 'admin' },
|
||||||
|
{ role: 'owner' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// NOT query
|
||||||
|
const notDeleted = await brain.find({
|
||||||
|
where: {
|
||||||
|
not: { status: 'deleted' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Combined AND + OR
|
||||||
|
const results = await brain.find({
|
||||||
|
where: {
|
||||||
|
allOf: [
|
||||||
|
{ department: 'engineering' },
|
||||||
|
{ anyOf: [
|
||||||
|
{ level: 'senior' },
|
||||||
|
{ yearsExperience: { greaterThan: 5 } }
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Indexed vs In-Memory Operators
|
||||||
|
|
||||||
|
Brainy's MetadataIndex supports a subset of operators natively for O(1) field lookups. Other operators fall back to in-memory filtering.
|
||||||
|
|
||||||
|
| Operator | MetadataIndex (Indexed) | In-Memory Fallback |
|
||||||
|
|----------|:-----------------------:|:------------------:|
|
||||||
|
| `equals` / `eq` | Yes | Yes |
|
||||||
|
| `notEquals` / `ne` | — | Yes |
|
||||||
|
| `greaterThan` / `gt` | Yes | Yes |
|
||||||
|
| `greaterThanOrEqual` / `gte` | Yes | Yes |
|
||||||
|
| `lessThan` / `lt` | Yes | Yes |
|
||||||
|
| `lessThanOrEqual` / `lte` | Yes | Yes |
|
||||||
|
| `between` | Yes | Yes |
|
||||||
|
| `oneOf` / `in` | Yes | Yes |
|
||||||
|
| `noneOf` | — | Yes |
|
||||||
|
| `contains` | Yes | Yes |
|
||||||
|
| `exists` / `missing` | Yes | Yes |
|
||||||
|
| `matches` | — | Yes |
|
||||||
|
| `startsWith` | — | Yes |
|
||||||
|
| `endsWith` | — | Yes |
|
||||||
|
| `allOf` | Partial | Yes |
|
||||||
|
| `anyOf` | Partial | Yes |
|
||||||
|
| `not` | — | Yes |
|
||||||
|
|
||||||
|
**Performance tip:** Combine indexed operators (equals, greaterThan, oneOf, between, contains, exists) with pattern operators for optimal speed — the index narrows results first, then patterns filter in memory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Practical Examples
|
||||||
|
|
||||||
|
### Filter by entity type
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Using the type shorthand (recommended)
|
||||||
|
brain.find({ type: NounType.Person })
|
||||||
|
|
||||||
|
// Using where.noun directly
|
||||||
|
brain.find({ where: { noun: NounType.Person } })
|
||||||
|
|
||||||
|
// Multiple types
|
||||||
|
brain.find({ type: [NounType.Person, NounType.Agent] })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter by subtype
|
||||||
|
|
||||||
|
`subtype` is a top-level standard field — takes the column-store fast path, not the metadata fallback. Pair with `type` for the typical "Person who is an employee" query:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Equality on subtype:
|
||||||
|
brain.find({ type: NounType.Person, subtype: 'employee' })
|
||||||
|
|
||||||
|
// Set membership:
|
||||||
|
brain.find({ type: NounType.Person, subtype: ['employee', 'contractor'] })
|
||||||
|
|
||||||
|
// Operator-form predicates use `where`:
|
||||||
|
brain.find({
|
||||||
|
type: NounType.Person,
|
||||||
|
where: { subtype: { exists: true } }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
See the **[Subtypes & Facets guide](./guides/subtypes-and-facets.md)** for the full surface.
|
||||||
|
|
||||||
|
### Filter relationships by subtype (7.30+)
|
||||||
|
|
||||||
|
Verbs are first-class peers — `related()` and graph traversal both honor subtype filters on the fast path:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Filter relationships by VerbType subtype
|
||||||
|
const direct = await brain.related({
|
||||||
|
from: ceoId,
|
||||||
|
type: VerbType.ReportsTo,
|
||||||
|
subtype: 'direct'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set membership on verb subtype
|
||||||
|
const all = await brain.related({
|
||||||
|
from: ceoId,
|
||||||
|
type: VerbType.ReportsTo,
|
||||||
|
subtype: ['direct', 'dotted-line']
|
||||||
|
})
|
||||||
|
|
||||||
|
// Graph traversal — subtype filters traversal edges (depth-1 in 7.30 JS path;
|
||||||
|
// multi-hop subtype filtering lands on Cor native)
|
||||||
|
const reports = await brain.find({
|
||||||
|
connected: {
|
||||||
|
from: ceoId,
|
||||||
|
via: VerbType.ReportsTo,
|
||||||
|
subtype: 'direct',
|
||||||
|
depth: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combine semantic search with filters
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const results = await brain.find({
|
||||||
|
query: 'machine learning engineer', // Semantic search (on data)
|
||||||
|
type: NounType.Person, // Type filter (indexed)
|
||||||
|
where: {
|
||||||
|
department: 'engineering', // Exact match (indexed)
|
||||||
|
yearsExperience: { greaterThan: 3 } // Range filter (indexed)
|
||||||
|
},
|
||||||
|
limit: 10
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Temporal queries
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const lastWeek = Date.now() - 7 * 24 * 60 * 60 * 1000
|
||||||
|
const recentEntities = await brain.find({
|
||||||
|
where: {
|
||||||
|
createdAt: { greaterThan: lastWeek }
|
||||||
|
},
|
||||||
|
orderBy: 'createdAt',
|
||||||
|
order: 'desc',
|
||||||
|
limit: 50
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Graph + metadata combination
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const results = await brain.find({
|
||||||
|
connected: {
|
||||||
|
from: teamLeadId,
|
||||||
|
via: VerbType.WorksWith,
|
||||||
|
depth: 2
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
role: { oneOf: ['engineer', 'designer'] },
|
||||||
|
active: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Data Model](./DATA_MODEL.md) — Entity structure, data vs metadata
|
||||||
|
- [API Reference](./api/README.md) — Complete API documentation
|
||||||
|
- [Find System](./FIND_SYSTEM.md) — Natural language find() details
|
||||||
130
docs/README.md
Normal file
130
docs/README.md
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Brainy Documentation
|
||||||
|
|
||||||
|
> The multi-dimensional AI database with Triple Intelligence — vector search, graph traversal, and metadata filtering in one unified API.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy()
|
||||||
|
await brain.init()
|
||||||
|
|
||||||
|
// Add entities — data is embedded for semantic search, metadata is indexed for filtering
|
||||||
|
const id = await brain.add({
|
||||||
|
data: 'Revolutionary AI Breakthrough',
|
||||||
|
type: NounType.Document,
|
||||||
|
metadata: { category: 'technology', rating: 4.8 }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Search with Triple Intelligence
|
||||||
|
const results = await brain.find({
|
||||||
|
query: 'artificial intelligence', // Semantic search (on data)
|
||||||
|
where: { rating: { greaterThan: 4.0 } }, // Metadata filter
|
||||||
|
connected: { from: authorId, depth: 2 } // Graph traversal
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Documentation
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| **[API Reference](./api/README.md)** | Complete API documentation — **start here** |
|
||||||
|
| **[Data Model](./DATA_MODEL.md)** | Entity structure, data vs metadata, storage fields |
|
||||||
|
| **[Query Operators](./QUERY_OPERATORS.md)** | All BFO operators with examples and indexed/in-memory matrix |
|
||||||
|
| [Find System](./FIND_SYSTEM.md) | Natural language `find()` and hybrid search details |
|
||||||
|
| [Consistency Model](./concepts/consistency-model.md) | The Db API guarantees — snapshot isolation, atomic transactions, time travel |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [Architecture Overview](./architecture/overview.md) | High-level system design |
|
||||||
|
| [Triple Intelligence](./architecture/triple-intelligence.md) | Vector + Graph + Metadata unified query |
|
||||||
|
| [Noun-Verb Taxonomy](./architecture/noun-verb-taxonomy.md) | 42 nouns + 127 verbs type system |
|
||||||
|
| [Stage 3 Canonical Taxonomy](./STAGE3-CANONICAL-TAXONOMY.md) | Complete type reference |
|
||||||
|
| [Storage Architecture](./architecture/storage-architecture.md) | Storage adapters and optimization |
|
||||||
|
| [Index Architecture](./architecture/index-architecture.md) | Vector, Graph, and Metadata indexing |
|
||||||
|
| [Zero Configuration](./architecture/zero-config.md) | Auto-adapts to any environment |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Virtual Filesystem (VFS)
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [VFS Quick Start](./vfs/QUICK_START.md) | Get started in 30 seconds |
|
||||||
|
| [VFS Core](./vfs/VFS_CORE.md) | Core concepts and architecture |
|
||||||
|
| [VFS API Guide](./vfs/VFS_API_GUIDE.md) | Complete VFS API reference |
|
||||||
|
| [Common Patterns](./vfs/COMMON_PATTERNS.md) | VFS usage patterns |
|
||||||
|
|
||||||
|
See [vfs/](./vfs/) for the complete VFS documentation set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guides
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [Import Anything](./guides/import-anything.md) | CSV, Excel, PDF, URL imports |
|
||||||
|
| [Snapshots & Time Travel](./guides/snapshots-and-time-travel.md) | Backups, restore, what-if analysis, audit trails |
|
||||||
|
| [Natural Language](./guides/natural-language.md) | Query in plain English |
|
||||||
|
| [Neural API](./guides/neural-api.md) | AI-powered features |
|
||||||
|
| [Enterprise for Everyone](./guides/enterprise-for-everyone.md) | No limits, no tiers |
|
||||||
|
| [Framework Integration](./guides/framework-integration.md) | React, Vue, Angular, Svelte |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage & Deployment
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [Storage Architecture](./architecture/storage-architecture.md) | Filesystem and memory adapters, on-disk artifact layout, operator-layer backup |
|
||||||
|
| [Capacity Planning](./operations/capacity-planning.md) | Scale to millions of entities |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [Plugins](./PLUGINS.md) | Plugin system overview — providers, `plugins` config, `brain.use()` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance & Scaling
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [Performance](./PERFORMANCE.md) | Optimization techniques |
|
||||||
|
| [Scaling](./SCALING.md) | Scale to billions of entities |
|
||||||
|
| [Batching](./BATCHING.md) | Batch operations guide |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration & Reference
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [v3 to v4 Migration](./MIGRATION-V3-TO-V4.md) | Upgrade guide |
|
||||||
|
| [Release Guide](./RELEASE-GUIDE.md) | How to release new versions |
|
||||||
|
| [Production Architecture](./PRODUCTION_SERVICE_ARCHITECTURE.md) | Ops reference |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Internal
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| [Audit Report](./internal/AUDIT_REPORT.md) | Feature audit |
|
||||||
|
| [Honest Status](./internal/HONEST_STATUS.md) | Actual implementation status |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Brainy is MIT licensed. See [LICENSE](../LICENSE) for details.
|
||||||
131
docs/RELEASE-GUIDE.md
Normal file
131
docs/RELEASE-GUIDE.md
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
# Brainy Release Guide
|
||||||
|
|
||||||
|
## Standard Semantic Versioning (Industry Guidelines)
|
||||||
|
|
||||||
|
### Official SemVer 2.0.0 says:
|
||||||
|
- **MAJOR**: Incompatible API changes (breaking changes)
|
||||||
|
- **MINOR**: Add functionality in backwards compatible manner
|
||||||
|
- **PATCH**: Backwards compatible bug fixes
|
||||||
|
|
||||||
|
## Our Approach for Brainy (More Conservative)
|
||||||
|
|
||||||
|
### We intentionally diverge from strict SemVer:
|
||||||
|
- **PATCH (2.3.0 → 2.3.1)**: Bug fixes, internal improvements, dependency updates
|
||||||
|
- **MINOR (2.3.0 → 2.4.0)**: New features, API changes, enhancements
|
||||||
|
- **MAJOR (3.0.0)**: Reserved for strategic platform shifts (manual decision)
|
||||||
|
|
||||||
|
### Why We Do This:
|
||||||
|
1. **User Trust**: Major versions signal huge changes and scare users
|
||||||
|
2. **Adoption**: People hesitate to upgrade major versions
|
||||||
|
3. **Flexibility**: We can evolve the API without version explosion
|
||||||
|
4. **Industry Practice**: Many successful projects (React, Vue) do this
|
||||||
|
|
||||||
|
## CRITICAL: Never Use "BREAKING CHANGE"
|
||||||
|
|
||||||
|
**"BREAKING CHANGE" in commits = Automatic major version = BAD!**
|
||||||
|
- Even if removing methods, just use `feat:` or `refactor:`
|
||||||
|
- Major versions are MANUAL decisions: `npm run release:major`
|
||||||
|
- Most API changes can be handled gracefully in minor versions
|
||||||
|
|
||||||
|
## Commit Message Guidelines
|
||||||
|
|
||||||
|
### ✅ CORRECT Examples:
|
||||||
|
```bash
|
||||||
|
# New features → MINOR bump
|
||||||
|
git commit -m "feat: add new model delivery system"
|
||||||
|
|
||||||
|
# Bug fixes → PATCH bump
|
||||||
|
git commit -m "fix: resolve model download timeout"
|
||||||
|
|
||||||
|
# Internal improvements → PATCH bump
|
||||||
|
git commit -m "refactor: simplify model manager logic"
|
||||||
|
git commit -m "perf: optimize model caching"
|
||||||
|
git commit -m "chore: remove unused dependency"
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ AVOID These Mistakes:
|
||||||
|
```bash
|
||||||
|
# DON'T use BREAKING CHANGE for internal changes
|
||||||
|
git commit -m "feat: improve model delivery
|
||||||
|
|
||||||
|
BREAKING CHANGE: removed tar-stream dependency" # WRONG! This triggers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Release Workflow Checklist
|
||||||
|
|
||||||
|
### Before Committing:
|
||||||
|
- [ ] Review commit message - no "BREAKING CHANGE" unless API changes
|
||||||
|
- [ ] Consider: Will users need to change their code? If NO → Not breaking
|
||||||
|
|
||||||
|
### Release Commands:
|
||||||
|
```bash
|
||||||
|
# Let standard-version figure it out from commits
|
||||||
|
npm run release # Recommended - auto-detects version
|
||||||
|
|
||||||
|
# Or be explicit:
|
||||||
|
npm run release:patch # 2.4.0 → 2.4.1 (fixes)
|
||||||
|
npm run release:minor # 2.4.0 → 2.5.0 (features)
|
||||||
|
npm run release:major # 2.4.0 → 3.0.0 (API changes only!)
|
||||||
|
```
|
||||||
|
|
||||||
|
### After Release:
|
||||||
|
```bash
|
||||||
|
git push --follow-tags origin main
|
||||||
|
npm publish
|
||||||
|
gh release create $(git describe --tags --abbrev=0) --generate-notes
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use Major Version (3.0.0)
|
||||||
|
|
||||||
|
ONLY when we make changes like:
|
||||||
|
- Removing methods from the public API
|
||||||
|
- Changing method signatures (parameters, return types)
|
||||||
|
- Renaming public methods
|
||||||
|
- Changing default behaviors that break existing code
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- ❌ `search(query, limit, options)` → `search(query, options)` (major)
|
||||||
|
- ✅ Adding `find()` method (minor - doesn't break existing code)
|
||||||
|
- ✅ Internal refactoring (patch - users don't see it)
|
||||||
|
|
||||||
|
## Quick Decision Tree
|
||||||
|
|
||||||
|
1. **Does this fix a bug?** → PATCH (fix:)
|
||||||
|
2. **Does this add new functionality?** → MINOR (feat:)
|
||||||
|
3. **Will users' existing code break?** → MAJOR (with BREAKING CHANGE)
|
||||||
|
4. **Is it internal/maintenance?** → PATCH (chore:/refactor:/perf:)
|
||||||
|
|
||||||
|
## Emergency: If Wrong Version is Released
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Deprecate wrong version on npm
|
||||||
|
npm deprecate @soulcraft/brainy@X.X.X "Incorrect version - use Y.Y.Y"
|
||||||
|
|
||||||
|
# 2. Fix version in package.json
|
||||||
|
# 3. Republish correct version
|
||||||
|
npm publish
|
||||||
|
|
||||||
|
# 4. Delete wrong GitHub tag/release
|
||||||
|
git push origin :vX.X.X
|
||||||
|
gh release delete vX.X.X --yes
|
||||||
|
|
||||||
|
# 5. Create correct tag/release
|
||||||
|
git tag vY.Y.Y
|
||||||
|
git push --tags
|
||||||
|
gh release create vY.Y.Y --generate-notes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Remember:
|
||||||
|
- **Most releases should be MINOR or PATCH**
|
||||||
|
- **Major versions should be RARE**
|
||||||
|
- **When in doubt, it's probably MINOR**
|
||||||
|
- **NEVER use "BREAKING CHANGE" for internal changes**
|
||||||
|
## Hard Ordering Constraints (check before EVERY release)
|
||||||
|
|
||||||
|
- **Embedding model changes are SEQUENCED, not free.** No release may change the
|
||||||
|
embedding model (or its quantization/dimensions) before **vector model-version
|
||||||
|
stamping + hard-error-on-mismatch** ships. Stored vectors carry no model version
|
||||||
|
today; mixing vectors from two models silently corrupts every similarity
|
||||||
|
comparison. If a model bump is ever proposed, the stamping work moves ahead of
|
||||||
|
it in the schedule — coordinate with the native provider so both engines stamp
|
||||||
|
and enforce identically. (Registered with the native-provider team 2026-07-07.)
|
||||||
239
docs/SCALING.md
Normal file
239
docs/SCALING.md
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
# Brainy Scaling Guide
|
||||||
|
|
||||||
|
> **One Line Summary**: Single-node by design — Brainy scales by getting the most out of one machine plus operator-layer backup.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [How Brainy Scales](#how-brainy-scales)
|
||||||
|
- [Storage Configurations](#storage-configurations)
|
||||||
|
- [Scaling Patterns](#scaling-patterns)
|
||||||
|
- [Real World Examples](#real-world-examples)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### In-Memory
|
||||||
|
```typescript
|
||||||
|
import Brainy from '@soulcraft/brainy'
|
||||||
|
const brain = new Brainy({ storage: { type: 'memory' } })
|
||||||
|
```
|
||||||
|
|
||||||
|
### On-Disk (Default for Node)
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: { type: 'filesystem', path: './brainy-data' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## How Brainy Scales
|
||||||
|
|
||||||
|
Brainy 8.0 is a **single-node library**. There is no cluster, no peer discovery, no S3 coordination. Scaling means:
|
||||||
|
|
||||||
|
- **Up**: give the process more RAM, CPU, and IOPS
|
||||||
|
- **Out**: stand up multiple independent Brainy instances behind your own service layer
|
||||||
|
- **Cold storage**: snapshot the on-disk artifact off-site so you can rehydrate elsewhere
|
||||||
|
|
||||||
|
The three knobs that matter most:
|
||||||
|
|
||||||
|
1. **`config.vector.recall`** — `'fast'`, `'balanced'`, or `'accurate'` (default `'balanced'`)
|
||||||
|
2. **`config.vector.persistMode`** — `'immediate'` for durability, `'deferred'` for throughput
|
||||||
|
|
||||||
|
The native vector provider (via the optional `@soulcraft/cor` package) extends this with a higher-performing index — and its own at-scale acceleration such as on-disk compressed indexing — when installed.
|
||||||
|
|
||||||
|
## Measured Performance
|
||||||
|
|
||||||
|
Numbers below are **measured** by `tests/benchmarks/find-composition-scale.js` (a single
|
||||||
|
Node 22 process, in-memory storage, 384-dim vectors, `balanced` recall). They are the
|
||||||
|
open-core (pure-TypeScript) path — what you get from `@soulcraft/brainy` with no native
|
||||||
|
provider installed. Run it yourself: `node --max-old-space-size=8192 tests/benchmarks/find-composition-scale.js 100000`.
|
||||||
|
|
||||||
|
`find()` query latency, p50 / p95 (200 queries each):
|
||||||
|
|
||||||
|
| Query | 5,000 entities | 100,000 entities |
|
||||||
|
|---|---|---|
|
||||||
|
| Vector similarity (`{ vector }`) | 0.8 / 1.3 ms | 1.4 / 4.7 ms |
|
||||||
|
| Graph 1-hop (`{ connected }`) | 0.5 / 0.7 ms | 0.7 / 0.8 ms |
|
||||||
|
| Metadata filter (`{ where }`, low-selectivity) | 0.7 / 1.2 ms | 23.5 / 30.1 ms |
|
||||||
|
| Vector + metadata | 7.7 / 8.3 ms | 78.8 / 93.8 ms |
|
||||||
|
|
||||||
|
What the shape tells you:
|
||||||
|
|
||||||
|
- **Vector and graph lookups scale ~logarithmically** — they barely move from 5k to 100k,
|
||||||
|
because HNSW search is ~O(ef·log n) and graph adjacency is O(degree).
|
||||||
|
- **Metadata-filtered paths scale with the size of the match set, not the database.** The
|
||||||
|
benchmark's `category` filter matches ~10% of rows (10,000 at 100k); the cost is
|
||||||
|
materializing that candidate set and running the vector search *inside* it (`find()` does
|
||||||
|
metadata-first hard filtering, then ranks within the candidates — see
|
||||||
|
[How find works](./FIND_SYSTEM.md)). A **high-selectivity** filter (few matches) is far
|
||||||
|
cheaper; a 10%-of-everything filter is the worst case. This candidate-restricted search is
|
||||||
|
precisely the path the native provider accelerates (Rust roaring-bitmap candidate
|
||||||
|
intersection).
|
||||||
|
- **Composition is correct, not lossy.** Combining vector + metadata + graph returns exactly
|
||||||
|
the entities satisfying all constraints — verified by
|
||||||
|
`tests/integration/find-triple-composition.test.ts`.
|
||||||
|
|
||||||
|
Memory: ~62 KB resident per entity at 100k (6.2 GB RSS for 100k × 384-dim including the HNSW
|
||||||
|
graph, metadata index, and 100k edges).
|
||||||
|
|
||||||
|
**Scale ceiling (open-core).** The pure-JS HNSW *build* cost (~100 inserts/s at 384-dim on
|
||||||
|
one core) makes the in-process open-core path most appropriate up to ~10⁵–10⁶ entities.
|
||||||
|
*Query* latency stays low well beyond that, but for the 10⁸–10¹⁰ regime install the native
|
||||||
|
provider (`@soulcraft/cor`, on-disk DiskANN) — same API, no code change. _Projected from
|
||||||
|
the two measured points, vector p50 at 1M is ~2 ms; metadata-heavy composition grows with
|
||||||
|
match-set size and is the path to move onto the native provider first._
|
||||||
|
|
||||||
|
## Storage Configurations
|
||||||
|
|
||||||
|
### Filesystem (Recommended for Production)
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: {
|
||||||
|
type: 'filesystem',
|
||||||
|
path: '/var/lib/brainy'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
- Stores everything in a sharded JSON tree under `path`
|
||||||
|
- Atomic writes via rename
|
||||||
|
- Survives process restarts
|
||||||
|
- Snapshot it off-site with `gsutil rsync`, `aws s3 sync`, `rclone`, or `tar` from your scheduler
|
||||||
|
|
||||||
|
### Memory
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({ storage: { type: 'memory' } })
|
||||||
|
```
|
||||||
|
- Zero I/O, fastest possible
|
||||||
|
- No persistence — process exit discards everything
|
||||||
|
- Use for tests and ephemeral caches
|
||||||
|
|
||||||
|
### Auto
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: { type: 'auto', path: './data' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
- Picks `filesystem` when running on Node with a writable `path`
|
||||||
|
- Falls back to `memory` otherwise
|
||||||
|
|
||||||
|
## Scaling Patterns
|
||||||
|
|
||||||
|
### Stage 1: Prototype (Memory)
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({ storage: { type: 'memory' } })
|
||||||
|
// Development, tests, <100K items
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stage 2: Production (Filesystem)
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: { type: 'filesystem', path: '/var/lib/brainy' }
|
||||||
|
})
|
||||||
|
// Most production workloads up to ~10M entities on a single host
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stage 3: Higher Throughput (Tune the Vector Index)
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: { type: 'filesystem', path: '/var/lib/brainy' },
|
||||||
|
vector: {
|
||||||
|
recall: 'fast', // Trade recall for latency
|
||||||
|
persistMode: 'deferred' // Batch persistence
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stage 4: Multi-Instance (Operator-Layer)
|
||||||
|
Run multiple Brainy processes behind your own routing/service layer. Each process owns its own `path`. Sync each artifact off-site independently. Brainy itself does not coordinate between processes.
|
||||||
|
|
||||||
|
## Real World Examples
|
||||||
|
|
||||||
|
### Example 1: Single-Node App With Backup
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: { type: 'filesystem', path: '/var/lib/brainy' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
Schedule (cron / systemd timer):
|
||||||
|
```bash
|
||||||
|
*/15 * * * * rclone sync /var/lib/brainy remote:brainy-backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Tests
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({ storage: { type: 'memory' } })
|
||||||
|
// Fast, no cleanup needed between runs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Multi-Tenant Service
|
||||||
|
Spin up one Brainy instance per tenant, each in its own directory:
|
||||||
|
```typescript
|
||||||
|
function brainForTenant(tenantId: string) {
|
||||||
|
return new Brainy({
|
||||||
|
storage: {
|
||||||
|
type: 'filesystem',
|
||||||
|
path: `/var/lib/brainy/${tenantId}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Your service layer handles routing and isolation; Brainy stays simple.
|
||||||
|
|
||||||
|
### Example 4: Higher Recall at Scale
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
storage: { type: 'filesystem', path: '/var/lib/brainy' },
|
||||||
|
vector: {
|
||||||
|
recall: 'accurate'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tuning Knobs Summary
|
||||||
|
|
||||||
|
| Setting | Values | When to change |
|
||||||
|
|---------|--------|----------------|
|
||||||
|
| `vector.recall` | `'fast'` / `'balanced'` / `'accurate'` | Trade recall for latency |
|
||||||
|
| `vector.persistMode` | `'immediate'` / `'deferred'` | Throughput vs. durability |
|
||||||
|
| `storage.cache.maxSize` | integer | Hot-path read cache size |
|
||||||
|
| `storage.cache.ttl` | ms | Cache freshness |
|
||||||
|
|
||||||
|
## Monitoring & Observability
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const stats = await brain.stats()
|
||||||
|
// {
|
||||||
|
// nounCount: 50000,
|
||||||
|
// verbCount: 80000,
|
||||||
|
// vectorIndex: { ... },
|
||||||
|
// storage: { used: '45GB' }
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Slow queries
|
||||||
|
1. Switch to `vector.recall: 'fast'`
|
||||||
|
2. Increase the read cache (`storage.cache.maxSize`)
|
||||||
|
3. Consider the optional native vector provider via `@soulcraft/cor`
|
||||||
|
|
||||||
|
### Issue: Memory pressure
|
||||||
|
1. Reduce `storage.cache.maxSize`
|
||||||
|
2. Move to `vector.persistMode: 'deferred'` to batch writes
|
||||||
|
3. Consider the optional native vector provider via `@soulcraft/cor` for at-scale index acceleration
|
||||||
|
|
||||||
|
### Issue: Slow startup after a crash
|
||||||
|
1. Use `vector.persistMode: 'immediate'` so the index file stays in sync with storage
|
||||||
|
2. Verify backup integrity periodically
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **One process = one `path`** — never share a directory between processes
|
||||||
|
2. **Snapshot from your scheduler** — Brainy doesn't ship cloud SDKs; use `rclone` / `aws s3 sync` / `gsutil`
|
||||||
|
3. **Profile before tuning** — `recall: 'balanced'` is right for most workloads
|
||||||
|
4. **Install the native vector provider only when measured profiling shows it pays off**
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Brainy 8.0 is a **library**, not a cluster
|
||||||
|
- Storage adapters: `filesystem`, `memory`, `auto`
|
||||||
|
- Vector tuning: `recall`, `persistMode`
|
||||||
|
- Backup is an operator-layer concern — snapshot `path`
|
||||||
373
docs/STAGE3-CANONICAL-TAXONOMY.md
Normal file
373
docs/STAGE3-CANONICAL-TAXONOMY.md
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
# Brainy Stage 3: Canonical Taxonomy
|
||||||
|
|
||||||
|
**Status:** FINAL - This is the definitive, timeless taxonomy
|
||||||
|
**Total Types:** 169 (42 nouns + 127 verbs)
|
||||||
|
**Coverage:** 96-97% of all human knowledge
|
||||||
|
**Designed to last:** 20+ years without changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- **Nouns:** 42 types
|
||||||
|
- **Verbs:** 127 types
|
||||||
|
- **Total:** 169 types
|
||||||
|
- **Previous (v5.x):** 71 types (31 nouns + 40 verbs)
|
||||||
|
- **Net Change:** +98 types (+11 nouns, +87 verbs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Noun Types (42)
|
||||||
|
|
||||||
|
### Core Entity Types (7)
|
||||||
|
1. **person** - Individual human entities
|
||||||
|
2. **organization** - Collective entities, companies, institutions
|
||||||
|
3. **location** - Geographic and named spatial entities
|
||||||
|
4. **thing** - Discrete physical objects and artifacts
|
||||||
|
5. **concept** - Abstract ideas, principles, and intangibles
|
||||||
|
6. **event** - Temporal occurrences and happenings
|
||||||
|
7. **agent** - Non-human autonomous actors (AI agents, bots, automated systems)
|
||||||
|
|
||||||
|
### Biological Types (1)
|
||||||
|
8. **organism** - Living biological entities (animals, plants, bacteria, fungi)
|
||||||
|
|
||||||
|
### Material Types (1)
|
||||||
|
9. **substance** - Physical materials and matter (water, iron, chemicals, DNA)
|
||||||
|
|
||||||
|
### Property & Quality Types (1)
|
||||||
|
10. **quality** - Properties and attributes that inhere in entities
|
||||||
|
|
||||||
|
### Temporal Types (1)
|
||||||
|
11. **timeInterval** - Temporal regions, periods, and durations
|
||||||
|
|
||||||
|
### Functional Types (1)
|
||||||
|
12. **function** - Purposes, capabilities, and functional roles
|
||||||
|
|
||||||
|
### Informational Types (1)
|
||||||
|
13. **proposition** - Statements, claims, assertions, and declarative content
|
||||||
|
|
||||||
|
### Digital/Content Types (4)
|
||||||
|
14. **document** - Text-based files and written content
|
||||||
|
15. **media** - Non-text media files (audio, video, images)
|
||||||
|
16. **file** - Generic digital files and data blobs
|
||||||
|
17. **message** - Communication content and correspondence
|
||||||
|
|
||||||
|
### Collection Types (2)
|
||||||
|
18. **collection** - Groups and sets of items
|
||||||
|
19. **dataset** - Structured data collections and databases
|
||||||
|
|
||||||
|
### Business/Application Types (4)
|
||||||
|
20. **product** - Commercial products and offerings
|
||||||
|
21. **service** - Service offerings and intangible products
|
||||||
|
22. **task** - Actions, todos, and work items
|
||||||
|
23. **project** - Organized initiatives and programs
|
||||||
|
|
||||||
|
### Descriptive Types (6)
|
||||||
|
24. **process** - Workflows, procedures, and ongoing activities
|
||||||
|
25. **state** - Conditions, status, and situational contexts
|
||||||
|
26. **role** - Positions, responsibilities, and functional classifications
|
||||||
|
27. **language** - Natural and formal languages
|
||||||
|
28. **currency** - Monetary units and exchange mediums
|
||||||
|
29. **measurement** - Metrics, quantities, and measured values
|
||||||
|
|
||||||
|
### Scientific/Research Types (2)
|
||||||
|
30. **hypothesis** - Scientific theories, propositions, and conjectures
|
||||||
|
31. **experiment** - Studies, trials, and empirical investigations
|
||||||
|
|
||||||
|
### Legal/Regulatory Types (2)
|
||||||
|
32. **contract** - Legal agreements, terms, and binding documents
|
||||||
|
33. **regulation** - Laws, policies, and compliance requirements
|
||||||
|
|
||||||
|
### Technical Infrastructure Types (2)
|
||||||
|
34. **interface** - APIs, protocols, and connection points
|
||||||
|
35. **resource** - Infrastructure, compute assets, and system resources
|
||||||
|
|
||||||
|
### Custom/Extensible (1)
|
||||||
|
36. **custom** - Domain-specific entities not covered by standard types
|
||||||
|
|
||||||
|
### Social Structures (3)
|
||||||
|
37. **socialGroup** - Informal social groups and collectives
|
||||||
|
38. **institution** - Formal social structures and practices
|
||||||
|
39. **norm** - Social norms, conventions, and expectations
|
||||||
|
|
||||||
|
### Information Theory (2)
|
||||||
|
40. **informationContent** - Abstract information (stories, ideas, data schemas)
|
||||||
|
41. **informationBearer** - Physical or digital carrier of information
|
||||||
|
|
||||||
|
### Meta-Level (1)
|
||||||
|
42. **relationship** - Relationships as first-class entities for meta-level reasoning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verb Types (127)
|
||||||
|
|
||||||
|
### Foundational Ontological (3)
|
||||||
|
1. **instanceOf** - Individual to class relationship
|
||||||
|
2. **subclassOf** - Taxonomic hierarchy
|
||||||
|
3. **participatesIn** - Entity participation in events/processes
|
||||||
|
|
||||||
|
### Core Relationships (4)
|
||||||
|
4. **relatedTo** - Generic relationship (fallback)
|
||||||
|
5. **contains** - Containment relationship
|
||||||
|
6. **partOf** - Part-whole mereological relationship
|
||||||
|
7. **references** - Citation and referential relationship
|
||||||
|
|
||||||
|
### Spatial Relationships (2)
|
||||||
|
8. **locatedAt** - Spatial location relationship
|
||||||
|
9. **adjacentTo** - Spatial proximity relationship
|
||||||
|
|
||||||
|
### Temporal Relationships (3)
|
||||||
|
10. **precedes** - Temporal sequence (before)
|
||||||
|
11. **during** - Temporal containment
|
||||||
|
12. **occursAt** - Temporal location
|
||||||
|
|
||||||
|
### Causal & Dependency (5)
|
||||||
|
13. **causes** - Direct causal relationship
|
||||||
|
14. **enables** - Enablement without direct causation
|
||||||
|
15. **prevents** - Prevention relationship
|
||||||
|
16. **dependsOn** - Dependency relationship
|
||||||
|
17. **requires** - Necessity relationship
|
||||||
|
|
||||||
|
### Creation & Transformation (5)
|
||||||
|
18. **creates** - Creation relationship
|
||||||
|
19. **transforms** - Transformation relationship
|
||||||
|
20. **becomes** - State change relationship
|
||||||
|
21. **modifies** - Modification relationship
|
||||||
|
22. **consumes** - Consumption relationship
|
||||||
|
|
||||||
|
### Lifecycle Operations (1)
|
||||||
|
23. **destroys** - Termination and destruction relationship
|
||||||
|
|
||||||
|
### Ownership & Attribution (2)
|
||||||
|
24. **owns** - Ownership relationship
|
||||||
|
25. **attributedTo** - Attribution relationship
|
||||||
|
|
||||||
|
### Property & Quality (2)
|
||||||
|
26. **hasQuality** - Entity to quality attribution
|
||||||
|
27. **realizes** - Function realization relationship
|
||||||
|
|
||||||
|
### Effects & Experience (1)
|
||||||
|
28. **affects** - Patient/experiencer relationship
|
||||||
|
|
||||||
|
### Composition (2)
|
||||||
|
29. **composedOf** - Material composition
|
||||||
|
30. **inherits** - Inheritance relationship
|
||||||
|
|
||||||
|
### Social & Organizational (7)
|
||||||
|
31. **memberOf** - Membership relationship
|
||||||
|
32. **worksWith** - Professional collaboration
|
||||||
|
33. **friendOf** - Friendship relationship
|
||||||
|
34. **follows** - Following/subscription relationship
|
||||||
|
35. **likes** - Liking/favoriting relationship
|
||||||
|
36. **reportsTo** - Hierarchical reporting relationship
|
||||||
|
37. **mentors** - Mentorship relationship
|
||||||
|
38. **communicates** - Communication relationship
|
||||||
|
|
||||||
|
### Descriptive & Functional (8)
|
||||||
|
39. **describes** - Descriptive relationship
|
||||||
|
40. **defines** - Definition relationship
|
||||||
|
41. **categorizes** - Categorization relationship
|
||||||
|
42. **measures** - Measurement relationship
|
||||||
|
43. **evaluates** - Evaluation relationship
|
||||||
|
44. **uses** - Utilization relationship
|
||||||
|
45. **implements** - Implementation relationship
|
||||||
|
46. **extends** - Extension relationship
|
||||||
|
|
||||||
|
### Advanced Relationships (4)
|
||||||
|
47. **equivalentTo** - Equivalence/identity relationship
|
||||||
|
48. **believes** - Epistemic relationship
|
||||||
|
49. **conflicts** - Conflict relationship
|
||||||
|
50. **synchronizes** - Synchronization relationship
|
||||||
|
51. **competes** - Competition relationship
|
||||||
|
|
||||||
|
### Modal Relationships (6)
|
||||||
|
52. **canCause** - Potential causation (possibility)
|
||||||
|
53. **mustCause** - Necessary causation (necessity)
|
||||||
|
54. **wouldCauseIf** - Counterfactual causation
|
||||||
|
55. **couldBe** - Possible states
|
||||||
|
56. **mustBe** - Necessary identity
|
||||||
|
57. **counterfactual** - General counterfactual relationship
|
||||||
|
|
||||||
|
### Epistemic States (8)
|
||||||
|
58. **knows** - Knowledge (justified true belief)
|
||||||
|
59. **doubts** - Uncertainty/skepticism
|
||||||
|
60. **desires** - Want/preference
|
||||||
|
61. **intends** - Intentionality
|
||||||
|
62. **fears** - Fear/anxiety
|
||||||
|
63. **loves** - Strong positive emotional attitude
|
||||||
|
64. **hates** - Strong negative emotional attitude
|
||||||
|
65. **hopes** - Hopeful expectation
|
||||||
|
66. **perceives** - Sensory perception
|
||||||
|
|
||||||
|
### Learning & Cognition (1)
|
||||||
|
67. **learns** - Cognitive acquisition and learning process
|
||||||
|
|
||||||
|
### Uncertainty & Probability (4)
|
||||||
|
68. **probablyCauses** - Probabilistic causation
|
||||||
|
69. **uncertainRelation** - Unknown relationship with confidence bounds
|
||||||
|
70. **correlatesWith** - Statistical correlation
|
||||||
|
71. **approximatelyEquals** - Fuzzy equivalence
|
||||||
|
|
||||||
|
### Scalar Properties (5)
|
||||||
|
72. **greaterThan** - Scalar comparison
|
||||||
|
73. **similarityDegree** - Graded similarity
|
||||||
|
74. **moreXThan** - Comparative property
|
||||||
|
75. **hasDegree** - Scalar property assignment
|
||||||
|
76. **partiallyHas** - Graded possession
|
||||||
|
|
||||||
|
### Information Theory (2)
|
||||||
|
77. **carries** - Bearer carries content
|
||||||
|
78. **encodes** - Encoding relationship
|
||||||
|
|
||||||
|
### Deontic Relationships (5)
|
||||||
|
79. **obligatedTo** - Moral/legal obligation
|
||||||
|
80. **permittedTo** - Permission/authorization
|
||||||
|
81. **prohibitedFrom** - Prohibition/forbidden
|
||||||
|
82. **shouldDo** - Normative expectation
|
||||||
|
83. **mustNotDo** - Strong prohibition
|
||||||
|
|
||||||
|
### Context & Perspective (5)
|
||||||
|
84. **trueInContext** - Context-dependent truth
|
||||||
|
85. **perceivedAs** - Subjective perception
|
||||||
|
86. **interpretedAs** - Interpretation relationship
|
||||||
|
87. **validInFrame** - Frame-dependent validity
|
||||||
|
88. **trueFrom** - Perspective-dependent truth
|
||||||
|
|
||||||
|
### Advanced Temporal (6)
|
||||||
|
89. **overlaps** - Partial temporal overlap
|
||||||
|
90. **immediatelyAfter** - Direct temporal succession
|
||||||
|
91. **eventuallyLeadsTo** - Long-term consequence
|
||||||
|
92. **simultaneousWith** - Exact temporal alignment
|
||||||
|
93. **hasDuration** - Temporal extent
|
||||||
|
94. **recurringWith** - Cyclic temporal relationship
|
||||||
|
|
||||||
|
### Advanced Spatial (7)
|
||||||
|
95. **containsSpatially** - Spatial containment
|
||||||
|
96. **overlapsSpatially** - Spatial overlap
|
||||||
|
97. **surrounds** - Encirclement
|
||||||
|
98. **connectedTo** - Topological connection
|
||||||
|
99. **above** - Vertical spatial relationship (superior)
|
||||||
|
100. **below** - Vertical spatial relationship (inferior)
|
||||||
|
101. **inside** - Within containment boundaries
|
||||||
|
102. **outside** - Beyond containment boundaries
|
||||||
|
103. **facing** - Directional orientation
|
||||||
|
|
||||||
|
### Social Structures (5)
|
||||||
|
104. **represents** - Representative relationship
|
||||||
|
105. **embodies** - Exemplification or personification
|
||||||
|
106. **opposes** - Opposition relationship
|
||||||
|
107. **alliesWith** - Alliance relationship
|
||||||
|
108. **conformsTo** - Norm conformity
|
||||||
|
|
||||||
|
### Measurement (4)
|
||||||
|
109. **measuredIn** - Unit relationship
|
||||||
|
110. **convertsTo** - Unit conversion
|
||||||
|
111. **hasMagnitude** - Quantitative value
|
||||||
|
112. **dimensionallyEquals** - Dimensional analysis
|
||||||
|
|
||||||
|
### Change & Persistence (4)
|
||||||
|
113. **persistsThrough** - Persistence through change
|
||||||
|
114. **gainsProperty** - Property acquisition
|
||||||
|
115. **losesProperty** - Property loss
|
||||||
|
116. **remainsSame** - Identity through time
|
||||||
|
|
||||||
|
### Parthood Variations (4)
|
||||||
|
117. **functionalPartOf** - Functional component
|
||||||
|
118. **topologicalPartOf** - Spatial part
|
||||||
|
119. **temporalPartOf** - Temporal slice
|
||||||
|
120. **conceptualPartOf** - Abstract decomposition
|
||||||
|
|
||||||
|
### Dependency Variations (3)
|
||||||
|
121. **rigidlyDependsOn** - Necessary dependency
|
||||||
|
122. **functionallyDependsOn** - Operational dependency
|
||||||
|
123. **historicallyDependsOn** - Causal history dependency
|
||||||
|
|
||||||
|
### Meta-Level (4)
|
||||||
|
124. **endorses** - Second-order validation
|
||||||
|
125. **contradicts** - Logical contradiction
|
||||||
|
126. **supports** - Evidential support
|
||||||
|
127. **supersedes** - Replacement relationship
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Constants
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const NOUN_TYPE_COUNT = 42 // Stage 3: 42 noun types (indices 0-41)
|
||||||
|
export const VERB_TYPE_COUNT = 127 // Stage 3: 127 verb types (indices 0-126)
|
||||||
|
export const TOTAL_TYPE_COUNT = 169 // 42 + 127 = 169 types
|
||||||
|
|
||||||
|
// Memory footprint for type tracking (fixed-size Uint32Arrays)
|
||||||
|
// 42 nouns × 4 bytes = 168 bytes
|
||||||
|
// 127 verbs × 4 bytes = 508 bytes
|
||||||
|
// Total: 676 bytes (vs ~85KB with Maps) = 99.2% memory reduction
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes from v5.x
|
||||||
|
|
||||||
|
### Nouns Added (+11)
|
||||||
|
- agent, quality, timeInterval, function, proposition
|
||||||
|
- **organism** ⭐ (biological entities)
|
||||||
|
- **substance** ⭐ (physical materials)
|
||||||
|
- socialGroup, institution, norm
|
||||||
|
- informationContent, informationBearer, relationship
|
||||||
|
|
||||||
|
### Nouns Removed (-2)
|
||||||
|
- **user** (merged into person)
|
||||||
|
- **topic** (merged into concept)
|
||||||
|
- **content** (removed - redundant)
|
||||||
|
|
||||||
|
### Verbs Added (+87)
|
||||||
|
- **affects** ⭐ (patient/experiencer role)
|
||||||
|
- **learns** ⭐ (cognitive acquisition)
|
||||||
|
- **destroys** ⭐ (lifecycle termination)
|
||||||
|
- All new categories from Stage 3 taxonomy
|
||||||
|
|
||||||
|
### Verbs Removed (-4)
|
||||||
|
- **succeeds** (use inverse of precedes)
|
||||||
|
- **belongsTo** (use inverse of owns)
|
||||||
|
- **createdBy** (use inverse of creates)
|
||||||
|
- **supervises** (use inverse of reportsTo)
|
||||||
|
|
||||||
|
⭐ = Critical additions from ultradeep analysis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coverage & Completeness
|
||||||
|
|
||||||
|
**Domain Coverage:**
|
||||||
|
- Natural Sciences: 96% (physics, chemistry, biology, medicine)
|
||||||
|
- Formal Sciences: 98% (mathematics, logic, computer science)
|
||||||
|
- Social Sciences: 97% (psychology, sociology, economics)
|
||||||
|
- Humanities: 96% (philosophy, history, arts)
|
||||||
|
|
||||||
|
**Overall:** 96-97% of all human knowledge
|
||||||
|
|
||||||
|
**Timeless Design:** Stable for 20+ years
|
||||||
|
|
||||||
|
**Extension:** Use "custom" noun for domain-specific entities
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verification Checklist
|
||||||
|
|
||||||
|
All code, comments, and documentation MUST match this canonical list:
|
||||||
|
|
||||||
|
- [ ] graphTypes.ts: NounType has exactly 42 entries
|
||||||
|
- [ ] graphTypes.ts: VerbType has exactly 127 entries
|
||||||
|
- [ ] graphTypes.ts: NOUN_TYPE_COUNT = 42
|
||||||
|
- [ ] graphTypes.ts: VERB_TYPE_COUNT = 127
|
||||||
|
- [ ] graphTypes.ts: NounTypeEnum has indices 0-41
|
||||||
|
- [ ] graphTypes.ts: VerbTypeEnum has indices 0-126
|
||||||
|
- [ ] metadataIndex.ts: Arrays sized for 42 & 127
|
||||||
|
- [ ] buildTypeEmbeddings.ts: Descriptions for all 169 types
|
||||||
|
- [ ] brainyTypes.ts: Descriptions for all 169 types
|
||||||
|
- [ ] index.ts: Exports all 42 noun type interfaces
|
||||||
|
- [ ] All tests: Reference only canonical types
|
||||||
|
- [ ] All documentation: States 42 nouns + 127 verbs = 169 types
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This is the **FINAL, CANONICAL** taxonomy for Brainy Stage 3.
|
||||||
2108
docs/api/README.md
Normal file
2108
docs/api/README.md
Normal file
File diff suppressed because it is too large
Load diff
114
docs/architecture/PERFORMANCE_ANALYSIS.md
Normal file
114
docs/architecture/PERFORMANCE_ANALYSIS.md
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
# Brainy Performance Analysis & Optimization
|
||||||
|
|
||||||
|
## Current Issues Found
|
||||||
|
|
||||||
|
### 1. ❌ CRITICAL: notEquals Operator is O(n)
|
||||||
|
```javascript
|
||||||
|
// PROBLEM: Gets ALL items to filter
|
||||||
|
case 'notEquals':
|
||||||
|
const allItemIds = await this.getAllIds() // O(n) - TERRIBLE!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. ❌ Soft Delete Performance
|
||||||
|
- Every query adds `deleted: { notEquals: true }`
|
||||||
|
- This makes EVERY query O(n) instead of O(log n)
|
||||||
|
|
||||||
|
### 3. ❌ exists Operator is Inefficient
|
||||||
|
```javascript
|
||||||
|
case 'exists':
|
||||||
|
// Scans all cache entries - O(n)
|
||||||
|
for (const [key, entry] of this.indexCache.entries()) {
|
||||||
|
if (entry.field === field) {
|
||||||
|
entry.ids.forEach(id => allIds.add(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. ⚠️ Query Optimizer Not Smart Enough
|
||||||
|
- `isSelectiveFilter()` needs to understand which filters are fast
|
||||||
|
- Should prioritize O(1) and O(log n) operations
|
||||||
|
|
||||||
|
## Performance Characteristics
|
||||||
|
|
||||||
|
### ✅ Fast Operations (Keep These)
|
||||||
|
| Operation | Complexity | Example |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| Vector Search (HNSW) | O(log n) | `like: "query"` |
|
||||||
|
| Exact Match | O(1) | `where: { status: "active" }` |
|
||||||
|
| Deleted Filter (NEW) | O(1) | `where: { deleted: false }` |
|
||||||
|
| Range Query (sorted) | O(log n) | `where: { year: { gt: 2000 } }` |
|
||||||
|
| Graph Traversal | O(k) | `connected: { from: id }` |
|
||||||
|
|
||||||
|
### ❌ Slow Operations (Need Fixing)
|
||||||
|
| Operation | Current | Should Be | Fix |
|
||||||
|
|-----------|---------|-----------|-----|
|
||||||
|
| notEquals | O(n) | O(1) or O(log n) | Use complement index |
|
||||||
|
| exists | O(n) | O(1) | Maintain field existence bitmap |
|
||||||
|
| noneOf | O(n) | O(k) | Use set operations |
|
||||||
|
|
||||||
|
## Optimized Architecture
|
||||||
|
|
||||||
|
### Solution 1: Positive Indexing for Soft Delete ✅
|
||||||
|
```javascript
|
||||||
|
// Instead of: deleted !== true (O(n))
|
||||||
|
// Use: deleted === false (O(1))
|
||||||
|
where: { deleted: false }
|
||||||
|
|
||||||
|
// Ensure all items have deleted field
|
||||||
|
if (!metadata.deleted) metadata.deleted = false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Solution 2: Complement Indices for notEquals
|
||||||
|
```javascript
|
||||||
|
class MetadataIndexManager {
|
||||||
|
// For common notEquals queries, maintain complement sets
|
||||||
|
private complementIndices: Map<string, Set<string>> = new Map()
|
||||||
|
|
||||||
|
// Example: Track non-deleted items separately
|
||||||
|
private activeItems: Set<string> = new Set()
|
||||||
|
private deletedItems: Set<string> = new Set()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Solution 3: Field Existence Bitmap
|
||||||
|
```javascript
|
||||||
|
class FieldExistenceIndex {
|
||||||
|
private fieldBitmaps: Map<string, BitSet> = new Map()
|
||||||
|
|
||||||
|
hasField(id: string, field: string): boolean {
|
||||||
|
return this.fieldBitmaps.get(field)?.has(id) ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Query Execution Strategy
|
||||||
|
|
||||||
|
### Progressive Search (When Metadata is Selective)
|
||||||
|
```
|
||||||
|
1. Field Filter (O(1) or O(log n)) → Small candidate set
|
||||||
|
2. Vector Search within candidates (O(k log k))
|
||||||
|
3. Fusion if needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parallel Search (When Nothing is Selective)
|
||||||
|
```
|
||||||
|
1. Vector Search (O(log n)) → Top K results
|
||||||
|
2. Graph Traversal (O(m)) → Connected items
|
||||||
|
3. Field Filter (O(1)) → Metadata matches
|
||||||
|
4. Fusion: Intersection or Union
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
1. **DONE** ✅ Fix soft delete to use `deleted: false`
|
||||||
|
2. **TODO** 🔧 Optimize notEquals for common fields
|
||||||
|
3. **TODO** 🔧 Add field existence index
|
||||||
|
4. **TODO** 🔧 Improve query optimizer intelligence
|
||||||
|
5. **TODO** 🔧 Add query explain mode for debugging
|
||||||
|
|
||||||
|
## Performance Targets
|
||||||
|
|
||||||
|
- Vector search: < 10ms for 1M items
|
||||||
|
- Metadata filter: < 1ms for exact match
|
||||||
|
- Combined query: < 20ms for complex queries
|
||||||
|
- Soft delete overhead: < 0.1ms (O(1))
|
||||||
242
docs/architecture/aggregation.md
Normal file
242
docs/architecture/aggregation.md
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
# Aggregation Architecture
|
||||||
|
|
||||||
|
> Write-time incremental aggregation with O(1) reads
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
1. **Write-time computation** — aggregates update on every `add()`, `update()`, and `delete()`, not as batch jobs
|
||||||
|
2. **Incremental state** — running totals maintained per group, never rescanning the dataset
|
||||||
|
3. **Provider interface** — TypeScript engine is the default; plugins can replace it with native implementations
|
||||||
|
4. **Zero-allocation reads** — query results are computed from pre-aggregated state
|
||||||
|
|
||||||
|
## Component Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ Brainy │
|
||||||
|
│ │
|
||||||
|
│ add() / update() / delete() │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────────────────┐ ┌────────────────────────┐ │
|
||||||
|
│ │ AggregationIndex │ │ AggregateMaterializer │ │
|
||||||
|
│ │ │───▶│ (debounced writes) │ │
|
||||||
|
│ │ ├─ definitions Map │ └────────────────────────┘ │
|
||||||
|
│ │ ├─ states Map │ │
|
||||||
|
│ │ └─ staleMinMax Set │ ┌────────────────────────┐ │
|
||||||
|
│ │ │ │ timeWindows.ts │ │
|
||||||
|
│ │ Source filter ──────│───▶│ bucketTimestamp() │ │
|
||||||
|
│ │ Group key ──────────│───▶│ parseBucketRange() │ │
|
||||||
|
│ └──────────┬───────────┘ └────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ │ provider interface │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────────────────┐ │
|
||||||
|
│ │ AggregationProvider │ (optional, registered by │
|
||||||
|
│ │ ├─ incrementalUpdate│ plugin like @soulcraft/cor) │
|
||||||
|
│ │ ├─ rebuildAggregate │ │
|
||||||
|
│ │ ├─ queryAggregate │ │
|
||||||
|
│ │ └─ serialize/restore│ │
|
||||||
|
│ └──────────────────────┘ │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
### Definitions
|
||||||
|
|
||||||
|
Registered via `brain.defineAggregate(def)`. Stored in a `Map<string, AggregateDefinition>` keyed by aggregate name. Persisted to storage under `__aggregation_definitions__` on flush.
|
||||||
|
|
||||||
|
### Group State
|
||||||
|
|
||||||
|
Each aggregate maintains a `Map<string, AggregateGroupState>` where keys are serialized group key values (e.g., `category=food|date=2024-01`). Each group holds per-metric `MetricState`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface MetricState {
|
||||||
|
sum: number // Running total
|
||||||
|
count: number // Entity count
|
||||||
|
min: number // Minimum (Infinity if empty)
|
||||||
|
max: number // Maximum (-Infinity if empty)
|
||||||
|
m2?: number // Welford's M2 for stddev/variance
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Change Detection
|
||||||
|
|
||||||
|
On restart, definition hashes (FNV-1a 32-bit) are compared with the persisted hash. If a definition changed (different groupBy, metrics, or source), the aggregate state is reset and must be rebuilt.
|
||||||
|
|
||||||
|
## Write-Time Update Flow
|
||||||
|
|
||||||
|
When `brain.add(entity)` is called:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. For each registered aggregate:
|
||||||
|
├─ Source filter check (type, service, where)
|
||||||
|
│ └─ Skip if entity doesn't match
|
||||||
|
├─ Aggregate entity check
|
||||||
|
│ └─ Skip if entity.service === 'brainy:aggregation'
|
||||||
|
│ or entity.metadata.__aggregate is set
|
||||||
|
├─ Group key computation
|
||||||
|
│ └─ Extract groupBy fields from metadata
|
||||||
|
│ Apply time bucketing for windowed dimensions
|
||||||
|
└─ Metric update
|
||||||
|
└─ For each metric in the definition:
|
||||||
|
├─ count: increment count
|
||||||
|
├─ sum/avg: add value to sum, increment count
|
||||||
|
├─ min/max: compare and update
|
||||||
|
└─ stddev/variance: Welford's online update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update Handling
|
||||||
|
|
||||||
|
On `brain.update(entity)`, the engine reverses the old entity's contribution and applies the new entity's contribution. This correctly handles:
|
||||||
|
|
||||||
|
- **Value changes**: old amount=10, new amount=20 — sum adjusts by +10
|
||||||
|
- **Group key changes**: entity moves from category "food" to "drink" — both groups update
|
||||||
|
- **Source filter changes**: entity type changes from Event to Document — removed from matching aggregates
|
||||||
|
|
||||||
|
### Delete Handling
|
||||||
|
|
||||||
|
On `brain.remove(id)`, the engine reverses the entity's contribution:
|
||||||
|
|
||||||
|
- `count` and `sum` are decremented
|
||||||
|
- `min`/`max` may become stale (marked in `staleMinMax` for lazy recompute)
|
||||||
|
- Welford's M2 is updated with the inverse formula
|
||||||
|
- Empty groups (all metric counts at zero) are removed
|
||||||
|
|
||||||
|
## Algorithms
|
||||||
|
|
||||||
|
### Welford's Online Algorithm
|
||||||
|
|
||||||
|
Standard deviation and variance use Welford's numerically stable online algorithm with M2 tracking. This computes incrementally without storing individual values:
|
||||||
|
|
||||||
|
```
|
||||||
|
On add(x):
|
||||||
|
count += 1
|
||||||
|
oldMean = (sum - x) / (count - 1) // mean before this value
|
||||||
|
sum += x
|
||||||
|
mean = sum / count // mean after this value
|
||||||
|
M2 += (x - oldMean) * (x - mean)
|
||||||
|
|
||||||
|
On remove(x):
|
||||||
|
oldMean = sum / count
|
||||||
|
sum -= x
|
||||||
|
count -= 1
|
||||||
|
newMean = sum / count
|
||||||
|
M2 = max(0, M2 - (x - oldMean) * (x - newMean))
|
||||||
|
|
||||||
|
Sample variance = M2 / (count - 1)
|
||||||
|
Sample stddev = sqrt(variance)
|
||||||
|
```
|
||||||
|
|
||||||
|
M2 is clamped to zero on remove to prevent floating-point drift from producing negative values.
|
||||||
|
|
||||||
|
### MIN/MAX Handling
|
||||||
|
|
||||||
|
The TypeScript engine uses simple comparison for add operations and marks MIN/MAX as potentially stale on delete (since removing the current min/max value requires a rescan). Stale values are lazily recomputed on the next query.
|
||||||
|
|
||||||
|
The Cor native engine uses a `BTreeMap<OrderedFloat<f64>, u64>` that tracks the exact frequency of every value, providing precise MIN/MAX after any sequence of operations without rescanning.
|
||||||
|
|
||||||
|
### Time Window Bucketing
|
||||||
|
|
||||||
|
Timestamps (Unix milliseconds) are bucketed using UTC-based formatting:
|
||||||
|
|
||||||
|
| Granularity | Bucket Key | Algorithm |
|
||||||
|
|------------|-----------|-----------|
|
||||||
|
| `hour` | `2024-01-15T14` | UTC year-month-day-hour |
|
||||||
|
| `day` | `2024-01-15` | UTC year-month-day |
|
||||||
|
| `week` | `2024-W03` | ISO 8601 week (Monday start, week 1 contains first Thursday) |
|
||||||
|
| `month` | `2024-01` | UTC year-month |
|
||||||
|
| `quarter` | `2024-Q1` | `ceil((month) / 3)` |
|
||||||
|
| `year` | `2024` | UTC year |
|
||||||
|
| `{ seconds: N }` | ISO timestamp | `floor(timestamp / interval) * interval` |
|
||||||
|
|
||||||
|
Bucket keys can be parsed back into `{ start, end }` timestamp ranges via `parseBucketRange()`.
|
||||||
|
|
||||||
|
## Provider Interface
|
||||||
|
|
||||||
|
The `AggregationProvider` interface defines the contract between Brainy's `AggregationIndex` and plugin-provided native implementations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface AggregationProvider {
|
||||||
|
defineAggregate?(def: AggregateDefinition): void
|
||||||
|
removeAggregate?(name: string): void
|
||||||
|
|
||||||
|
incrementalUpdate(
|
||||||
|
name: string,
|
||||||
|
def: AggregateDefinition,
|
||||||
|
entity: Record<string, unknown>,
|
||||||
|
op: 'add' | 'update' | 'delete',
|
||||||
|
prev?: Record<string, unknown>
|
||||||
|
): AggregateGroupState[]
|
||||||
|
|
||||||
|
computeGroupKey(
|
||||||
|
entity: Record<string, unknown>,
|
||||||
|
groupBy: GroupByDimension[]
|
||||||
|
): Record<string, string | number>
|
||||||
|
|
||||||
|
rebuildAggregate(
|
||||||
|
def: AggregateDefinition,
|
||||||
|
entities: Array<Record<string, unknown>>
|
||||||
|
): Map<string, AggregateGroupState>
|
||||||
|
|
||||||
|
queryAggregate(
|
||||||
|
state: Map<string, AggregateGroupState>,
|
||||||
|
params: AggregateQueryParams
|
||||||
|
): AggregateResult[]
|
||||||
|
|
||||||
|
restoreState?(data: string): void
|
||||||
|
serializeState?(): string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When a native provider is registered:
|
||||||
|
|
||||||
|
1. `AggregationIndex` delegates `incrementalUpdate()` to the provider instead of running TypeScript logic
|
||||||
|
2. Provider returns updated `AggregateGroupState[]` which are applied back into the state maps
|
||||||
|
3. Query execution is delegated via `queryAggregate()`
|
||||||
|
4. State serialization is delegated via `serializeState()`/`restoreState()`
|
||||||
|
|
||||||
|
Brainy retains ownership of the state maps and persistence. The provider handles computation.
|
||||||
|
|
||||||
|
## Materialization
|
||||||
|
|
||||||
|
The `AggregateMaterializer` converts aggregate group states into `NounType.Measurement` entities:
|
||||||
|
|
||||||
|
1. When an aggregate group is updated and `materialize` is enabled, `scheduleMaterialize()` is called
|
||||||
|
2. Materialization is debounced (default: 1000ms) to batch rapid updates during ingestion
|
||||||
|
3. On trigger, the materializer either creates or updates a `NounType.Measurement` entity
|
||||||
|
4. Materialized entities include `service: 'brainy:aggregation'` and `metadata.__aggregate` to prevent infinite loops
|
||||||
|
|
||||||
|
Materialized entities are automatically visible through:
|
||||||
|
- OData endpoints
|
||||||
|
- Google Sheets integration
|
||||||
|
- Server-Sent Events (SSE)
|
||||||
|
- Webhook notifications
|
||||||
|
|
||||||
|
## Persistence
|
||||||
|
|
||||||
|
### Storage Keys
|
||||||
|
|
||||||
|
| Key | Content |
|
||||||
|
|-----|---------|
|
||||||
|
| `__aggregation_definitions__` | Array of all definitions with FNV-1a hashes |
|
||||||
|
| `__aggregation_state_{name}__` | Per-aggregate group states (array of `AggregateGroupState`) |
|
||||||
|
| `__aggregation_native_state__` | Serialized native provider state (JSON string) |
|
||||||
|
|
||||||
|
### Lifecycle
|
||||||
|
|
||||||
|
1. **`init()`** — Load definitions, compare hashes, load matching state, restore native provider state
|
||||||
|
2. **Write operations** — Mark modified aggregates as dirty
|
||||||
|
3. **`flush()`** — Persist all dirty aggregate states and native provider state
|
||||||
|
4. **`close()`** — Flush and release resources
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `src/aggregation/AggregationIndex.ts` | Core engine: definitions, state, write hooks, query |
|
||||||
|
| `src/aggregation/materializer.ts` | Debounced materialization of results as entities |
|
||||||
|
| `src/aggregation/timeWindows.ts` | Time bucketing and bucket range parsing |
|
||||||
|
| `src/aggregation/index.ts` | Module exports |
|
||||||
|
| `src/types/brainy.types.ts` | Type definitions for all aggregation interfaces |
|
||||||
302
docs/architecture/augmentations-actual.md
Normal file
302
docs/architecture/augmentations-actual.md
Normal file
|
|
@ -0,0 +1,302 @@
|
||||||
|
# Augmentations System - What Actually Exists
|
||||||
|
|
||||||
|
> **Important Update**: Investigation reveals Brainy has MORE augmentations than documented!
|
||||||
|
|
||||||
|
## ✅ Actually Implemented Augmentations (12+)
|
||||||
|
|
||||||
|
Full implementation with crash recovery, checkpointing, and replay.
|
||||||
|
```typescript
|
||||||
|
// Fully working with all features documented
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Entity Registry Augmentation ✅
|
||||||
|
High-performance deduplication using bloom filters.
|
||||||
|
```typescript
|
||||||
|
import { EntityRegistryAugmentation } from 'brainy'
|
||||||
|
// Complete with all features
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Auto-Register Entities Augmentation ✅
|
||||||
|
Automatic entity extraction from text.
|
||||||
|
```typescript
|
||||||
|
import { AutoRegisterEntitiesAugmentation } from 'brainy'
|
||||||
|
// Extracts and registers entities automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Intelligent Verb Scoring Augmentation ✅
|
||||||
|
Multi-factor relationship strength calculation.
|
||||||
|
```typescript
|
||||||
|
import { IntelligentVerbScoringAugmentation } from 'brainy'
|
||||||
|
// Semantic, temporal, frequency scoring
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Batch Processing Augmentation ✅
|
||||||
|
Dynamic batching with adaptive backpressure.
|
||||||
|
```typescript
|
||||||
|
import { BatchProcessingAugmentation } from 'brainy'
|
||||||
|
// Smart batching with flow control
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Connection Pool Augmentation ✅
|
||||||
|
Intelligent connection management.
|
||||||
|
```typescript
|
||||||
|
import { ConnectionPoolAugmentation } from 'brainy'
|
||||||
|
// Auto-scaling connection pools
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Request Deduplicator Augmentation ✅
|
||||||
|
Prevents duplicate operations.
|
||||||
|
```typescript
|
||||||
|
import { RequestDeduplicatorAugmentation } from 'brainy'
|
||||||
|
// In-flight request deduplication
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. WebSocket Conduit Augmentation ✅
|
||||||
|
Real-time bidirectional streaming.
|
||||||
|
```typescript
|
||||||
|
import { WebSocketConduitAugmentation } from 'brainy'
|
||||||
|
// Full WebSocket support
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. WebRTC Conduit Augmentation ✅
|
||||||
|
Peer-to-peer communication.
|
||||||
|
```typescript
|
||||||
|
import { WebRTCConduitAugmentation } from 'brainy'
|
||||||
|
// P2P data channels
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. Memory Storage Augmentation ✅
|
||||||
|
Optimized in-memory operations.
|
||||||
|
```typescript
|
||||||
|
import { MemoryStorageAugmentation } from 'brainy'
|
||||||
|
// Memory-specific optimizations
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11. Server Search Augmentation ✅
|
||||||
|
Server-side search delegation over a conduit.
|
||||||
|
```typescript
|
||||||
|
import { ServerSearchConduitAugmentation } from 'brainy'
|
||||||
|
// Forwards queries to a remote Brainy server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 12. Neural Import Augmentation ✅
|
||||||
|
AI-powered data understanding and import.
|
||||||
|
```typescript
|
||||||
|
import { NeuralImportAugmentation } from 'brainy'
|
||||||
|
// Full entity detection and classification
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Hidden Features in Augmentations
|
||||||
|
|
||||||
|
### Neural Import Capabilities (Fully Implemented!)
|
||||||
|
```typescript
|
||||||
|
const neuralImport = new NeuralImport(brain)
|
||||||
|
|
||||||
|
// These ALL work:
|
||||||
|
await neuralImport.neuralImport('data.csv')
|
||||||
|
await neuralImport.detectEntitiesWithNeuralAnalysis(data)
|
||||||
|
await neuralImport.detectNounType(entity)
|
||||||
|
await neuralImport.detectRelationships(entities)
|
||||||
|
await neuralImport.generateInsights(data)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Operation Modes (Fully Implemented!)
|
||||||
|
```typescript
|
||||||
|
// Read-only mode with optimized caching
|
||||||
|
const readerMode = new ReaderMode()
|
||||||
|
// 80% cache, aggressive prefetch, 1hr TTL
|
||||||
|
|
||||||
|
// Write-only mode with batching
|
||||||
|
const writerMode = new WriterMode()
|
||||||
|
// Large write buffer, batch writes, minimal cache
|
||||||
|
|
||||||
|
// Hybrid mode
|
||||||
|
const hybridMode = new HybridMode()
|
||||||
|
// Balanced for mixed workloads
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Caching (3-Level System!)
|
||||||
|
```typescript
|
||||||
|
const cacheManager = new CacheManager({
|
||||||
|
hotCache: { size: 1000, ttl: 60000 }, // L1 - RAM
|
||||||
|
warmCache: { size: 10000, ttl: 300000 }, // L2 - Fast storage
|
||||||
|
coldCache: { size: 100000, ttl: null } // L3 - Persistent
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Performance Monitoring (Complete!)
|
||||||
|
```typescript
|
||||||
|
const monitor = new PerformanceMonitor(brain)
|
||||||
|
|
||||||
|
// All these metrics work:
|
||||||
|
monitor.getMetrics() // Returns comprehensive stats
|
||||||
|
monitor.getQueryPatterns() // Query analysis
|
||||||
|
monitor.getCacheStats() // Cache performance
|
||||||
|
monitor.getThrottlingMetrics() // Rate limiting info
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Statistics System (Fully Working!)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const stats = await brain.getStats()
|
||||||
|
// Returns comprehensive metrics:
|
||||||
|
{
|
||||||
|
nouns: {
|
||||||
|
count: number,
|
||||||
|
created: number,
|
||||||
|
updated: number,
|
||||||
|
deleted: number,
|
||||||
|
size: number,
|
||||||
|
avgSize: number
|
||||||
|
},
|
||||||
|
verbs: {
|
||||||
|
count: number,
|
||||||
|
created: number,
|
||||||
|
types: Record<string, number>,
|
||||||
|
weights: { min, max, avg }
|
||||||
|
},
|
||||||
|
vectors: {
|
||||||
|
dimensions: 384,
|
||||||
|
indexSize: number,
|
||||||
|
partitions: number,
|
||||||
|
avgSearchTime: number
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
hits: number,
|
||||||
|
misses: number,
|
||||||
|
evictions: number,
|
||||||
|
hitRate: number,
|
||||||
|
hotCacheSize: number,
|
||||||
|
warmCacheSize: number
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
operations: number,
|
||||||
|
avgAddTime: number,
|
||||||
|
avgSearchTime: number,
|
||||||
|
avgUpdateTime: number,
|
||||||
|
p95Latency: number,
|
||||||
|
p99Latency: number
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
used: number,
|
||||||
|
available: number,
|
||||||
|
compression: number,
|
||||||
|
files: number
|
||||||
|
},
|
||||||
|
throttling: {
|
||||||
|
delays: number,
|
||||||
|
rateLimited: number,
|
||||||
|
backoffMs: number,
|
||||||
|
retries: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 GPU Support (Partial but Real!)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// GPU detection WORKS:
|
||||||
|
const device = await detectBestDevice()
|
||||||
|
// Returns: 'cpu' | 'webgpu' | 'cuda'
|
||||||
|
|
||||||
|
// WebGPU support in browser:
|
||||||
|
if (device === 'webgpu') {
|
||||||
|
// Transformer models can use WebGPU
|
||||||
|
}
|
||||||
|
|
||||||
|
// CUDA detection in Node:
|
||||||
|
if (device === 'cuda') {
|
||||||
|
// Future: GPU acceleration support
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 Adaptive Systems (All Working!)
|
||||||
|
|
||||||
|
### Adaptive Backpressure
|
||||||
|
```typescript
|
||||||
|
const backpressure = new AdaptiveBackpressure()
|
||||||
|
// Automatically adjusts flow based on system load
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adaptive Socket Manager
|
||||||
|
```typescript
|
||||||
|
const socketManager = new AdaptiveSocketManager()
|
||||||
|
// Dynamic connection pooling based on traffic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cache Auto-Configuration
|
||||||
|
```typescript
|
||||||
|
const cacheConfig = await getCacheAutoConfig()
|
||||||
|
// Sizes cache based on available memory
|
||||||
|
```
|
||||||
|
|
||||||
|
### S3 Throttling Protection
|
||||||
|
```typescript
|
||||||
|
// Built into S3 storage adapter
|
||||||
|
// Automatic exponential backoff
|
||||||
|
// Rate limit detection and adaptation
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 How to Use Hidden Features
|
||||||
|
|
||||||
|
### Enable Reader / Writer Modes
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
mode: 'reader' // or 'writer' or 'hybrid'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Neural Import
|
||||||
|
```typescript
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new NeuralImportAugmentation({
|
||||||
|
confidenceThreshold: 0.7,
|
||||||
|
autoDetect: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Import with AI understanding
|
||||||
|
await brain.neuralImport('data.csv')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Access Statistics
|
||||||
|
```typescript
|
||||||
|
// Get comprehensive stats
|
||||||
|
const stats = await brain.getStats()
|
||||||
|
|
||||||
|
// Get specific service stats
|
||||||
|
const nounStats = await brain.getStatistics({
|
||||||
|
service: 'nouns'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Force refresh
|
||||||
|
const freshStats = await brain.getStatistics({
|
||||||
|
forceRefresh: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 What Needs Documentation
|
||||||
|
|
||||||
|
These features EXIST but need better docs:
|
||||||
|
1. Reader / writer operation modes
|
||||||
|
2. Neural import full API
|
||||||
|
3. 3-level cache configuration
|
||||||
|
4. Performance monitoring API
|
||||||
|
5. GPU acceleration setup
|
||||||
|
6. Advanced statistics queries
|
||||||
|
7. Throttling configuration
|
||||||
|
8. Backpressure tuning
|
||||||
|
|
||||||
|
## 💡 The Truth
|
||||||
|
|
||||||
|
Brainy is MORE powerful than its own documentation suggests! Most "missing" features are actually implemented but hidden or not properly exposed. The codebase contains sophisticated systems for:
|
||||||
|
- Reader / writer operation modes
|
||||||
|
- AI-powered import
|
||||||
|
- Advanced caching
|
||||||
|
- Performance monitoring
|
||||||
|
- GPU support
|
||||||
|
- Adaptive optimization
|
||||||
|
|
||||||
|
The main work needed is integration and documentation, not implementation!
|
||||||
494
docs/architecture/augmentations.md
Normal file
494
docs/architecture/augmentations.md
Normal file
|
|
@ -0,0 +1,494 @@
|
||||||
|
# Augmentations System
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Brainy's Augmentation System provides a powerful plugin architecture that extends core functionality without modifying the base code. Augmentations can intercept, modify, and enhance any operation in the database.
|
||||||
|
|
||||||
|
## Built-in Augmentations
|
||||||
|
|
||||||
|
> **Note**: This document shows both available and planned augmentations. Each section is marked with its current status.
|
||||||
|
|
||||||
|
### 1. Entity Registry Augmentation ✅ Available
|
||||||
|
|
||||||
|
High-performance deduplication for streaming data ingestion.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { EntityRegistryAugmentation } from 'brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new EntityRegistryAugmentation({
|
||||||
|
maxCacheSize: 100000, // Track up to 100k unique entities
|
||||||
|
ttl: 3600000, // 1-hour TTL for cache entries
|
||||||
|
hashFields: ['id', 'url'] // Fields to use for deduplication
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Automatically prevents duplicate entities
|
||||||
|
await brain.add("Same content", { id: "123" }) // Added
|
||||||
|
await brain.add("Same content", { id: "123" }) // Skipped (duplicate)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- O(1) duplicate detection using bloom filters
|
||||||
|
- Configurable cache size and TTL
|
||||||
|
- Custom hash field selection
|
||||||
|
- Perfect for real-time data streams
|
||||||
|
|
||||||
|
|
||||||
|
Enterprise-grade durability and crash recovery.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
checkpointInterval: 1000, // Checkpoint every 1000 operations
|
||||||
|
compression: true, // Enable log compression
|
||||||
|
maxLogSize: 100 * 1024 * 1024 // 100MB max log size
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// All operations are now durably logged
|
||||||
|
|
||||||
|
// Recover from crash
|
||||||
|
const recovered = new Brainy({
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ACID compliance
|
||||||
|
- Automatic crash recovery
|
||||||
|
- Point-in-time recovery
|
||||||
|
- Log compression and rotation
|
||||||
|
- Minimal performance impact
|
||||||
|
|
||||||
|
### 3. Intelligent Verb Scoring Augmentation ✅ Available
|
||||||
|
|
||||||
|
AI-powered relationship strength calculation.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IntelligentVerbScoringAugmentation } from 'brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new IntelligentVerbScoringAugmentation({
|
||||||
|
factors: {
|
||||||
|
semantic: 0.4, // Weight for semantic similarity
|
||||||
|
temporal: 0.3, // Weight for time proximity
|
||||||
|
frequency: 0.2, // Weight for interaction frequency
|
||||||
|
explicit: 0.1 // Weight for explicit ratings
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Relationships automatically get intelligent scores
|
||||||
|
await brain.relate(user1, product1, "viewed", { timestamp: Date.now() })
|
||||||
|
await brain.relate(user1, product1, "purchased", { timestamp: Date.now() })
|
||||||
|
// Automatically calculates relationship strength based on multiple factors
|
||||||
|
|
||||||
|
// Query using intelligent scores
|
||||||
|
const strongRelationships = await brain.find({
|
||||||
|
connected: {
|
||||||
|
from: user1,
|
||||||
|
minScore: 0.8 // Only highly relevant relationships
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Capabilities:**
|
||||||
|
- Multi-factor relationship scoring
|
||||||
|
- Temporal decay functions
|
||||||
|
- Semantic similarity integration
|
||||||
|
- Customizable weight factors
|
||||||
|
|
||||||
|
### 4. Auto-Register Entities Augmentation ⚠️ Basic Implementation
|
||||||
|
|
||||||
|
Automatically extracts and registers entities from text.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AutoRegisterEntitiesAugmentation } from 'brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new AutoRegisterEntitiesAugmentation({
|
||||||
|
types: ['person', 'organization', 'location', 'product'],
|
||||||
|
confidence: 0.8,
|
||||||
|
createRelationships: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Automatically extracts and registers entities
|
||||||
|
await brain.add(
|
||||||
|
"Apple CEO Tim Cook announced the new iPhone 15 in Cupertino",
|
||||||
|
{ type: "news" }
|
||||||
|
)
|
||||||
|
// Automatically creates:
|
||||||
|
// - Noun: "Tim Cook" (person)
|
||||||
|
// - Noun: "Apple" (organization)
|
||||||
|
// - Noun: "iPhone 15" (product)
|
||||||
|
// - Noun: "Cupertino" (location)
|
||||||
|
// - Verbs: relationships between entities
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- NER (Named Entity Recognition)
|
||||||
|
- Automatic relationship inference
|
||||||
|
- Configurable entity types
|
||||||
|
- Confidence thresholds
|
||||||
|
|
||||||
|
### 5. Batch Processing Augmentation ✅ Available
|
||||||
|
|
||||||
|
Optimizes bulk operations for maximum throughput.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { BatchProcessingAugmentation } from 'brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new BatchProcessingAugmentation({
|
||||||
|
batchSize: 100,
|
||||||
|
flushInterval: 1000, // Flush every second
|
||||||
|
parallel: true, // Parallel processing
|
||||||
|
maxQueueSize: 10000
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Operations are automatically batched
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
await brain.add(`Item ${i}`) // Internally batched
|
||||||
|
}
|
||||||
|
// Processes in optimized batches of 100
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- 10-100x throughput improvement
|
||||||
|
- Automatic batching
|
||||||
|
- Configurable batch sizes
|
||||||
|
- Memory-efficient queue management
|
||||||
|
|
||||||
|
### 6. Caching Augmentation 🚧 Coming Soon
|
||||||
|
|
||||||
|
Intelligent multi-level caching system.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { CachingAugmentation } from 'brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new CachingAugmentation({
|
||||||
|
levels: {
|
||||||
|
l1: { size: 100, ttl: 60000 }, // Hot cache: 100 items, 1 min
|
||||||
|
l2: { size: 1000, ttl: 300000 }, // Warm cache: 1000 items, 5 min
|
||||||
|
l3: { size: 10000, ttl: 3600000 } // Cold cache: 10k items, 1 hour
|
||||||
|
},
|
||||||
|
strategies: ['lru', 'lfu'], // Least Recently/Frequently Used
|
||||||
|
preload: true // Preload popular items
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Queries automatically use cache
|
||||||
|
const results = await brain.find("popular query") // Cached
|
||||||
|
const again = await brain.find("popular query") // From cache (instant)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Multi-level cache hierarchy
|
||||||
|
- Multiple eviction strategies
|
||||||
|
- Query result caching
|
||||||
|
- Embedding cache
|
||||||
|
- Automatic cache invalidation
|
||||||
|
|
||||||
|
### 7. Compression Augmentation 🚧 Coming Soon
|
||||||
|
|
||||||
|
Reduces storage size while maintaining query performance.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { CompressionAugmentation } from 'brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new CompressionAugmentation({
|
||||||
|
algorithm: 'brotli',
|
||||||
|
level: 6, // Compression level (1-11)
|
||||||
|
threshold: 1024, // Only compress items > 1KB
|
||||||
|
excludeFields: ['id', 'type'] // Don't compress these
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Data automatically compressed/decompressed
|
||||||
|
await brain.add(largeDocument) // Compressed before storage
|
||||||
|
const doc = await brain.getNoun(id) // Decompressed on retrieval
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- 60-80% storage reduction
|
||||||
|
- Transparent compression
|
||||||
|
- Selective field compression
|
||||||
|
- Multiple algorithm support
|
||||||
|
|
||||||
|
### 8. Monitoring Augmentation 🚧 Coming Soon
|
||||||
|
|
||||||
|
Real-time performance monitoring and metrics.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { MonitoringAugmentation } from 'brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new MonitoringAugmentation({
|
||||||
|
metrics: ['operations', 'latency', 'cache', 'memory'],
|
||||||
|
interval: 5000, // Report every 5 seconds
|
||||||
|
webhook: 'https://metrics.example.com/brainy',
|
||||||
|
console: true // Also log to console
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Automatic metric collection
|
||||||
|
brain.on('metrics', (metrics) => {
|
||||||
|
console.log(`
|
||||||
|
Operations/sec: ${metrics.opsPerSecond}
|
||||||
|
Avg latency: ${metrics.avgLatency}ms
|
||||||
|
Cache hit rate: ${metrics.cacheHitRate}%
|
||||||
|
Memory usage: ${metrics.memoryMB}MB
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Metrics:**
|
||||||
|
- Operation throughput
|
||||||
|
- Query latency percentiles
|
||||||
|
- Cache hit rates
|
||||||
|
- Memory usage
|
||||||
|
- Storage growth
|
||||||
|
- Error rates
|
||||||
|
|
||||||
|
## Neural Import Capabilities 🚧 Coming Soon
|
||||||
|
|
||||||
|
> **Note**: Import/Export features are currently in development. Expected Q1 2025.
|
||||||
|
|
||||||
|
### 1. Document Import with Auto-Structuring
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { NeuralImportAugmentation } from 'brainy'
|
||||||
|
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
new NeuralImportAugmentation({
|
||||||
|
autoStructure: true,
|
||||||
|
extractEntities: true,
|
||||||
|
generateSummaries: true,
|
||||||
|
detectLanguage: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Import unstructured documents
|
||||||
|
await brain.importDocument('./research-paper.pdf')
|
||||||
|
// Automatically:
|
||||||
|
// - Extracts text and metadata
|
||||||
|
// - Identifies sections and structure
|
||||||
|
// - Extracts entities and concepts
|
||||||
|
// - Generates embeddings per section
|
||||||
|
// - Creates relationship graph
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Database Migration Import
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Import from existing databases
|
||||||
|
await brain.importFromSQL({
|
||||||
|
connection: 'postgres://localhost/mydb',
|
||||||
|
tables: {
|
||||||
|
users: { type: 'person', idField: 'user_id' },
|
||||||
|
products: { type: 'product', idField: 'sku' },
|
||||||
|
orders: {
|
||||||
|
type: 'relationship',
|
||||||
|
from: 'user_id',
|
||||||
|
to: 'product_id',
|
||||||
|
verb: 'purchased'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Import from MongoDB
|
||||||
|
await brain.importFromMongo({
|
||||||
|
uri: 'mongodb://localhost:27017',
|
||||||
|
database: 'myapp',
|
||||||
|
collections: {
|
||||||
|
users: { type: 'person' },
|
||||||
|
posts: { type: 'content' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Stream Import
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Import from real-time streams
|
||||||
|
await brain.importStream({
|
||||||
|
source: 'kafka://localhost:9092/events',
|
||||||
|
format: 'json',
|
||||||
|
transform: (event) => ({
|
||||||
|
noun: event.data,
|
||||||
|
metadata: {
|
||||||
|
type: event.type,
|
||||||
|
timestamp: event.timestamp
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
deduplication: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Bulk CSV/JSON Import
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Import CSV with automatic type detection
|
||||||
|
await brain.importCSV('./data.csv', {
|
||||||
|
headers: true,
|
||||||
|
typeColumn: 'entity_type',
|
||||||
|
detectRelationships: true,
|
||||||
|
batchSize: 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
// Import JSON with nested structure handling
|
||||||
|
await brain.importJSON('./data.json', {
|
||||||
|
rootPath: '$.entities',
|
||||||
|
nounPath: '$.content',
|
||||||
|
metadataPath: '$.properties',
|
||||||
|
relationshipPath: '$.connections'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating Custom Augmentations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Augmentation } from 'brainy'
|
||||||
|
|
||||||
|
class CustomAugmentation extends Augmentation {
|
||||||
|
name = 'CustomAugmentation'
|
||||||
|
|
||||||
|
async onInit(brain: Brainy): Promise<void> {
|
||||||
|
// Initialize augmentation
|
||||||
|
console.log('Custom augmentation initialized')
|
||||||
|
}
|
||||||
|
|
||||||
|
async onBeforeAddNoun(content: any, metadata: any): Promise<[any, any]> {
|
||||||
|
// Modify before adding noun
|
||||||
|
metadata.processed = true
|
||||||
|
metadata.timestamp = Date.now()
|
||||||
|
return [content, metadata]
|
||||||
|
}
|
||||||
|
|
||||||
|
async onAfterAddNoun(id: string, noun: any): Promise<void> {
|
||||||
|
// React to noun addition
|
||||||
|
console.log(`Noun ${id} added`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async onBeforeSearch(query: any): Promise<any> {
|
||||||
|
// Modify search query
|
||||||
|
query.boost = 'recent'
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
async onAfterSearch(results: any[]): Promise<any[]> {
|
||||||
|
// Process search results
|
||||||
|
return results.map(r => ({
|
||||||
|
...r,
|
||||||
|
customScore: r.score * 1.5
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use custom augmentation
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [new CustomAugmentation()]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Augmentation Lifecycle Hooks
|
||||||
|
|
||||||
|
### Available Hooks
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface AugmentationHooks {
|
||||||
|
// Initialization
|
||||||
|
onInit(brain: Brainy): Promise<void>
|
||||||
|
onShutdown(): Promise<void>
|
||||||
|
|
||||||
|
// Noun operations
|
||||||
|
onBeforeAddNoun(content, metadata): Promise<[content, metadata]>
|
||||||
|
onAfterAddNoun(id, noun): Promise<void>
|
||||||
|
onBeforeGetNoun(id): Promise<string>
|
||||||
|
onAfterGetNoun(noun): Promise<any>
|
||||||
|
onBeforeUpdateNoun(id, updates): Promise<[string, any]>
|
||||||
|
onAfterUpdateNoun(id, noun): Promise<void>
|
||||||
|
onBeforeDeleteNoun(id): Promise<string>
|
||||||
|
onAfterDeleteNoun(id): Promise<void>
|
||||||
|
|
||||||
|
// Verb operations
|
||||||
|
onBeforeAddVerb(source, target, type, metadata): Promise<[any, any, string, any]>
|
||||||
|
onAfterAddVerb(id, verb): Promise<void>
|
||||||
|
onBeforeGetVerb(id): Promise<string>
|
||||||
|
onAfterGetVerb(verb): Promise<any>
|
||||||
|
|
||||||
|
// Search operations
|
||||||
|
onBeforeSearch(query): Promise<any>
|
||||||
|
onAfterSearch(results): Promise<any[]>
|
||||||
|
onBeforeFind(query): Promise<any>
|
||||||
|
onAfterFind(results): Promise<any[]>
|
||||||
|
|
||||||
|
// Storage operations
|
||||||
|
onBeforeSave(data): Promise<any>
|
||||||
|
onAfterLoad(data): Promise<any>
|
||||||
|
|
||||||
|
// Events
|
||||||
|
onError(error): Promise<void>
|
||||||
|
onMetric(metric): Promise<void>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Augmentation Composition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Combine multiple augmentations
|
||||||
|
const brain = new Brainy({
|
||||||
|
augmentations: [
|
||||||
|
// Order matters - executed in sequence
|
||||||
|
new EntityRegistryAugmentation(), // Deduplication first
|
||||||
|
new AutoRegisterEntitiesAugmentation(), // Entity extraction
|
||||||
|
new IntelligentVerbScoringAugmentation(), // Scoring
|
||||||
|
new CompressionAugmentation(), // Compression
|
||||||
|
new CachingAugmentation(), // Caching
|
||||||
|
new MonitoringAugmentation() // Monitoring last
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
1. **Order Matters**: Place filtering augmentations early
|
||||||
|
2. **Resource Usage**: Monitor memory with many augmentations
|
||||||
|
3. **Async Operations**: Use parallel processing where possible
|
||||||
|
4. **Caching**: Enable caching augmentation for read-heavy workloads
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Single Responsibility**: Each augmentation should do one thing well
|
||||||
|
2. **Non-Blocking**: Avoid blocking operations in hooks
|
||||||
|
3. **Error Handling**: Always handle errors gracefully
|
||||||
|
4. **Configuration**: Make augmentations configurable
|
||||||
|
5. **Documentation**: Document augmentation behavior and options
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Architecture Overview](./overview.md)
|
||||||
|
- [API Reference](../api/README.md)
|
||||||
|
- [Performance Guide](../guides/performance.md)
|
||||||
388
docs/architecture/data-storage-architecture.md
Normal file
388
docs/architecture/data-storage-architecture.md
Normal file
|
|
@ -0,0 +1,388 @@
|
||||||
|
# Brainy Data Storage Architecture (8.0)
|
||||||
|
|
||||||
|
**Complete on-disk reference for the 8.0 layout.**
|
||||||
|
|
||||||
|
This document describes what a Brainy 8.0 data directory actually contains: the
|
||||||
|
canonical entity records, the system area, the generational-MVCC bookkeeping,
|
||||||
|
the column store, the blob area, and the lock files — plus how the in-memory
|
||||||
|
indexes rebuild from them. The authoritative design records are
|
||||||
|
[ADR-001 (generational MVCC)](../ADR-001-generational-mvcc.md) and
|
||||||
|
[index-architecture.md](./index-architecture.md); this document is the on-disk
|
||||||
|
map that ties them together.
|
||||||
|
|
||||||
|
8.0 removed the 7.x copy-on-write subsystem (`_cow/`, `branches/{branch}/…`
|
||||||
|
paths) and the cloud/OPFS storage adapters. The two storage backends are
|
||||||
|
**filesystem** and **memory**; both speak the same path vocabulary (memory
|
||||||
|
storage keys its internal map by the identical path strings).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Directory Tree
|
||||||
|
|
||||||
|
A real 8.0 filesystem store (`path`, default `./brainy-data`):
|
||||||
|
|
||||||
|
```
|
||||||
|
brainy-data/
|
||||||
|
│
|
||||||
|
├── entities/ # Canonical records (current state)
|
||||||
|
│ ├── nouns/
|
||||||
|
│ │ └── {shard}/ # 256 shards: first 2 hex chars of the UUID
|
||||||
|
│ │ └── {id}/ # One directory per entity UUID
|
||||||
|
│ │ ├── vectors.json.gz # Embedding + HNSW node state
|
||||||
|
│ │ └── metadata.json.gz # Everything else (type, subtype, data, fields, _rev)
|
||||||
|
│ └── verbs/
|
||||||
|
│ └── {shard}/
|
||||||
|
│ └── {id}/
|
||||||
|
│ ├── vectors.json.gz # Relationship embedding (when present)
|
||||||
|
│ └── metadata.json.gz # sourceId, targetId, verb, subtype, weight, data…
|
||||||
|
│
|
||||||
|
├── _system/ # System singletons + bucketed system keys
|
||||||
|
│ ├── generation.json(.gz) # { generation, updatedAt } — the write watermark
|
||||||
|
│ ├── manifest.json # { version, generation, … } — MVCC commit point
|
||||||
|
│ ├── tx-log.jsonl # One line per committed transact() batch (append-only)
|
||||||
|
│ ├── counts.json # Entity/verb totals
|
||||||
|
│ ├── type-statistics.json.gz # Per-NounType counts
|
||||||
|
│ ├── subtype-statistics.json.gz # Per-(NounType, subtype) counts
|
||||||
|
│ ├── verb-subtype-statistics.json.gz # Per-(VerbType, subtype) counts
|
||||||
|
│ ├── statistics.json # Aggregate statistics blob (counts, index sizes)
|
||||||
|
│ ├── hnsw-system.json # Vector-index entry point + max level
|
||||||
|
│ ├── __metadata_field_registry__.json.gz # Which metadata fields are indexed
|
||||||
|
│ ├── brainy:entityIdMapper.json.gz # UUID ↔ u64 mapping for native index providers
|
||||||
|
│ └── idx/
|
||||||
|
│ └── {bucket}/ # 256 buckets: FNV-1a hash of the key
|
||||||
|
│ ├── __metadata_field_index__field_{name}.json.gz # Sparse field indexes
|
||||||
|
│ ├── __chunk__*.json.gz # Metadata-index roaring-bitmap chunks
|
||||||
|
│ ├── __sparse_index__*.json.gz# Zone maps + bloom filters
|
||||||
|
│ └── graph-lsm-verbs-{source|target}-*.json.gz # Graph LSM SSTables + manifest
|
||||||
|
│
|
||||||
|
├── _generations/ # MVCC history (written ONLY by transact())
|
||||||
|
│ └── {N}/ # One directory per committed generation N
|
||||||
|
│ ├── tx.json # The generation-N delta (immutable)
|
||||||
|
│ └── prev/
|
||||||
|
│ └── {id}.json # Before-image of each touched record (immutable)
|
||||||
|
│
|
||||||
|
├── _column_index/ # Column store manifests (one dir per field)
|
||||||
|
│ └── {field}/
|
||||||
|
│ └── MANIFEST.json.gz # Run list + zone metadata for that column
|
||||||
|
│
|
||||||
|
├── _blobs/ # Binary blob area (`<key>.bin` convention)
|
||||||
|
│ ├── _column_index/
|
||||||
|
│ │ └── {field}/
|
||||||
|
│ │ └── L0-000001.bin # Column-store runs (level-0 segments)
|
||||||
|
│ └── … # VFS file content and other binary blobs
|
||||||
|
│
|
||||||
|
└── locks/ # Process coordination (NEVER snapshotted)
|
||||||
|
├── _writer.lock # Single-writer lock: pid, hostname, heartbeat
|
||||||
|
├── _flush_requests/ # Reader→writer flush RPC (.req files)
|
||||||
|
└── _flush_responses/ # Writer acks (.ack files)
|
||||||
|
```
|
||||||
|
|
||||||
|
Most JSON objects are gzip-compressed (`.json.gz`) — compression is on by
|
||||||
|
default for filesystem storage (`storage.options.compression`, zlib level 6).
|
||||||
|
A few hot singletons (`manifest.json`, `counts.json`, `hnsw-system.json`,
|
||||||
|
`tx-log.jsonl`) are written uncompressed for cheap partial reads and appends.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Canonical Entity Records
|
||||||
|
|
||||||
|
Each entity (noun) and relationship (verb) is **two files** under one
|
||||||
|
ID-first directory. The split keeps vector I/O (large, append-mostly) separate
|
||||||
|
from metadata I/O (small, read-heavy).
|
||||||
|
|
||||||
|
### Noun vector file — `entities/nouns/{shard}/{id}/vectors.json.gz`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "421d92e7-4241-470a-80f4-4b39414e7a83",
|
||||||
|
"vector": [-0.139, -0.056, 0.028, "…384 dims…"],
|
||||||
|
"connections": { "0": ["neighbor-uuid…"] },
|
||||||
|
"level": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The HNSW node state (`connections`, `level`) is persisted with the vector so
|
||||||
|
the vector index can rebuild without recomputing the graph.
|
||||||
|
|
||||||
|
### Noun metadata file — `entities/nouns/{shard}/{id}/metadata.json.gz`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": "React is a JavaScript library for building user interfaces",
|
||||||
|
"noun": "concept",
|
||||||
|
"subtype": "cli-add",
|
||||||
|
"createdAt": 1781198053726,
|
||||||
|
"updatedAt": 1781198053726,
|
||||||
|
"_rev": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `noun` is the NounType. **Type lives in metadata, not in the path** — lookup
|
||||||
|
by ID is a single path construction, no type needed.
|
||||||
|
- `subtype` is the per-product sub-classification (required on write by
|
||||||
|
default in 8.0).
|
||||||
|
- `_rev` increments on every write and backs `update({ ifRev })` CAS.
|
||||||
|
- Consumer metadata fields sit alongside the standard ones.
|
||||||
|
|
||||||
|
### Verb files — `entities/verbs/{shard}/{id}/…`
|
||||||
|
|
||||||
|
Same two-file split. The verb metadata record carries the graph edge:
|
||||||
|
`sourceId`, `targetId`, `verb` (VerbType), `subtype`, `weight`, `data`,
|
||||||
|
`metadata`, timestamps, `_rev`. Verb IDs are Brainy-generated UUIDs by
|
||||||
|
contract (8.0 rejects caller-supplied verb ids) because native graph providers
|
||||||
|
intern the raw UUID bytes as u64 handles.
|
||||||
|
|
||||||
|
### Path construction
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const shard = id.substring(0, 2) // '42'
|
||||||
|
const metadataPath = `entities/nouns/${shard}/${id}/metadata.json`
|
||||||
|
const vectorsPath = `entities/nouns/${shard}/${id}/vectors.json`
|
||||||
|
// Verbs: same shape under entities/verbs/
|
||||||
|
```
|
||||||
|
|
||||||
|
Implemented in `src/storage/baseStorage.ts` (path generators) and
|
||||||
|
`src/storage/sharding.ts` (`getShardIdFromUuid`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. The `_system/` Area
|
||||||
|
|
||||||
|
Two kinds of keys live here, resolved by `BaseStorage.parsePath()`:
|
||||||
|
|
||||||
|
1. **Singletons** — well-known keys written at `_system/<key>.json`:
|
||||||
|
`counts`, `statistics`, `type-statistics`, `hnsw-system`,
|
||||||
|
`__metadata_field_registry__`, `brainy:entityIdMapper`, plus the MVCC
|
||||||
|
trio (`generation.json`, `manifest.json`, `tx-log.jsonl`).
|
||||||
|
2. **Bucketed system keys** — everything else (field indexes, bitmap chunks,
|
||||||
|
sparse-index segments, graph LSM SSTables) hashes into one of 256
|
||||||
|
`_system/idx/{bucket}/` directories via FNV-1a, so no single directory
|
||||||
|
accumulates unbounded entries.
|
||||||
|
|
||||||
|
Notable singletons:
|
||||||
|
|
||||||
|
| File | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `generation.json` | `{ generation, updatedAt }` — monotonic watermark, bumped by **every** write batch |
|
||||||
|
| `manifest.json` | MVCC commit point: highest *committed* generation (see §4) |
|
||||||
|
| `tx-log.jsonl` | One JSON line per committed `transact()` batch: generation, timestamp, `meta` |
|
||||||
|
| `type-statistics.json.gz` | Per-NounType counts (backs `brain.counts.byType`) |
|
||||||
|
| `subtype-statistics.json.gz` | `{ counts: { [type]: { [subtype]: n } }, updatedAt }` (contract-bound shape) |
|
||||||
|
| `verb-subtype-statistics.json.gz` | Same shape for VerbTypes |
|
||||||
|
| `hnsw-system.json` | `{ entryPointId, maxLevel }` — per-node state lives in each entity's `vectors.json` |
|
||||||
|
| `brainy:entityIdMapper.json.gz` | UUID ↔ u64 interning table for the BigInt provider contract |
|
||||||
|
| `__metadata_field_registry__.json.gz` | Registry of indexed metadata field names |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Generational MVCC (`_generations/` + the `_system` trio)
|
||||||
|
|
||||||
|
Full design in [ADR-001](../ADR-001-generational-mvcc.md). The on-disk shape:
|
||||||
|
|
||||||
|
```
|
||||||
|
_system/generation.json { generation, updatedAt } atomic tmp+rename
|
||||||
|
_system/manifest.json { version, generation, … } atomic tmp+rename — THE commit point
|
||||||
|
_system/tx-log.jsonl one line per committed transact() append-only
|
||||||
|
_generations/{N}/tx.json the generation-N delta immutable once written
|
||||||
|
_generations/{N}/prev/{id}.json before-image of {id} immutable once written
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit protocol (writer side): stage before-images and the delta under
|
||||||
|
`_generations/N/`, fsync, apply the delta to the canonical `entities/…`
|
||||||
|
records, then atomically rename `manifest.json` to publish generation N. The
|
||||||
|
tx-log line is appended last (advisory). Crash recovery on open discards any
|
||||||
|
`_generations/{N}` newer than the manifest.
|
||||||
|
|
||||||
|
Two write classes share the generation clock:
|
||||||
|
|
||||||
|
- **Single-operation writes** (`add`/`update`/`remove`/`relate` outside
|
||||||
|
`transact()`) bump `generation.json` so watermarks and `_rev` CAS stay sound,
|
||||||
|
but write **no** history — they are not visible to `db.since()` and remain
|
||||||
|
visible through earlier pins.
|
||||||
|
- **`transact()` batches** write the full `_generations/{N}` record and a
|
||||||
|
tx-log line, and are the unit of time travel (`brain.asOf()`).
|
||||||
|
|
||||||
|
Snapshots (`db.persist(path)`) hard-link the entire store **except `locks/`**
|
||||||
|
into a self-contained directory openable via `Brainy.load(path)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Column Store (`_column_index/` + `_blobs/_column_index/`)
|
||||||
|
|
||||||
|
The metadata index persists per-field columnar runs for O(log n) range and
|
||||||
|
membership queries at scale:
|
||||||
|
|
||||||
|
- `_column_index/{field}/MANIFEST.json.gz` — the run list and zone metadata
|
||||||
|
for one field (`createdAt`, `subtype`, `noun`, `_rev`, consumer fields,
|
||||||
|
`__words__` for tokenized text…).
|
||||||
|
- `_blobs/_column_index/{field}/L0-NNNNNN.bin` — the actual level-0 run
|
||||||
|
segments, stored through the shared `_blobs/<key>.bin` binary convention.
|
||||||
|
|
||||||
|
Sparse per-field indexes, roaring-bitmap chunks, and zone-map/bloom segments
|
||||||
|
additionally live as bucketed keys under `_system/idx/` (see §3). Which path
|
||||||
|
serves a given `where` clause is the query planner's decision — inspect it
|
||||||
|
with `brainy inspect explain <dir> --where '…'`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Blob Area (`_blobs/`)
|
||||||
|
|
||||||
|
`_blobs/<key>.bin` is the flat binary-blob convention shared by every storage
|
||||||
|
adapter (`saveBinaryBlob`/`getBinaryBlob` in the storage contract):
|
||||||
|
|
||||||
|
- **VFS file content** — VFS entities are regular nouns (path, ownership, and
|
||||||
|
timestamps in entity metadata); the file *bytes* are blobs.
|
||||||
|
- **Column-store runs** (under the `_column_index/` key prefix, §5).
|
||||||
|
- Any other binary payload an index provider persists.
|
||||||
|
|
||||||
|
Writes use unique temp names + rename, so concurrent writers of the same key
|
||||||
|
cannot tear each other's blobs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Locks (`locks/`)
|
||||||
|
|
||||||
|
```
|
||||||
|
locks/_writer.lock # single-writer lock: { pid, hostname, startedAt, heartbeat, version }
|
||||||
|
locks/_flush_requests/ # readers drop <uuid>.req to ask the writer to flush
|
||||||
|
locks/_flush_responses/ # writer answers with <uuid>.ack
|
||||||
|
```
|
||||||
|
|
||||||
|
- One **writer** per data directory, enforced at `init()`; stale locks (dead
|
||||||
|
PID / stale heartbeat) are reclaimed automatically.
|
||||||
|
- Read-only processes (`Brainy.openReadOnly()`, the `brainy inspect` CLI
|
||||||
|
family) can ask the live writer to flush via the request/response files, so
|
||||||
|
out-of-process diagnostics see fresh state.
|
||||||
|
- `locks/` is excluded from snapshots (`SNAPSHOT_EXCLUDED_TOP_DIRS` in
|
||||||
|
`src/storage/adapters/fileSystemStorage.ts`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. In-Memory Indexes and What Rebuilds From What
|
||||||
|
|
||||||
|
| Index | In memory | Persisted state | Rebuild source |
|
||||||
|
|-------|-----------|-----------------|----------------|
|
||||||
|
| **Vector (HNSW)** | Graph of vector connections | `_system/hnsw-system.json` + per-entity `vectors.json` | Walk entity vector files; lazy mode loads structure only and pages vectors on demand |
|
||||||
|
| **Metadata index** | Field → value bitmaps + column-store readers | `_system/idx/` chunks + `_column_index/` manifests + `_blobs/_column_index/` runs | Loaded directly; full rebuild re-scans entity metadata |
|
||||||
|
| **Graph adjacency** | sourceId/targetId → verb-id LSM trees | `graph-lsm-verbs-{source,target}-*` SSTables under `_system/idx/` | Loaded from SSTables; full rebuild re-scans verb metadata |
|
||||||
|
| **Counts/statistics** | Per-type and per-subtype maps | `_system/{type,subtype,verb-subtype}-statistics.json.gz`, `counts.json` | Recomputable by scanning entities (`brainy inspect repair`) |
|
||||||
|
|
||||||
|
A pluggable index provider (the 8.0 plugin contract in
|
||||||
|
`@soulcraft/brainy/plugin`) may replace any of the JS implementations; the
|
||||||
|
persisted formats above are contract-bound so JS and native implementations
|
||||||
|
can interleave on the same directory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Sharding Strategy
|
||||||
|
|
||||||
|
**Entities:** first 2 hex characters of the UUID → 256 uniform shards.
|
||||||
|
Deterministic, configuration-free, and keeps per-directory entry counts low
|
||||||
|
(at 1M entities: ~3,900 directories per shard). Paginated whole-store walks
|
||||||
|
(`getNouns`/`getVerbs`) iterate shards `00`–`ff` in order.
|
||||||
|
|
||||||
|
**System keys:** FNV-1a hash of the key → 256 `_system/idx/` buckets. Same
|
||||||
|
motivation, different keyspace (system keys are not UUIDs).
|
||||||
|
|
||||||
|
**What is never sharded:** the `_system/` singletons, `_generations/{N}`
|
||||||
|
directories (keyed by generation number), `_column_index/{field}` manifests
|
||||||
|
(keyed by field name), and `locks/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Durability and Atomicity
|
||||||
|
|
||||||
|
- **Per-object atomicity:** every JSON object and blob is written to a unique
|
||||||
|
temp file then `rename()`d — readers never observe torn objects.
|
||||||
|
- **Transaction atomicity:** the `manifest.json` rename is the single commit
|
||||||
|
point for `transact()` batches (§4); everything staged before it is
|
||||||
|
discarded by crash recovery if the rename never lands.
|
||||||
|
- **Compression:** gzip per object (`.json.gz`), transparent to all readers.
|
||||||
|
Native index providers that mmap binary formats use the uncompressed
|
||||||
|
`_blobs/` area instead.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. `clear()` Semantics
|
||||||
|
|
||||||
|
`brain.clear()` removes all entities, relationships, indexes, statistics, and
|
||||||
|
MVCC history, then re-resolves every index exactly as `init()` does —
|
||||||
|
including plugin-provided vector/metadata/id-mapper factories and VFS root
|
||||||
|
re-creation. The data directory afterwards contains a fresh, empty store (the
|
||||||
|
writer lock remains held by the running process).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Common Scenarios
|
||||||
|
|
||||||
|
### Adding an entity
|
||||||
|
|
||||||
|
```
|
||||||
|
brain.add({ data, type, subtype })
|
||||||
|
1. Generate UUID → shard = first 2 hex chars
|
||||||
|
2. Embed data → 384-dim vector
|
||||||
|
3. Write entities/nouns/{shard}/{id}/vectors.json.gz (vector + HNSW node state)
|
||||||
|
4. Write entities/nouns/{shard}/{id}/metadata.json.gz (type/subtype/data/fields, _rev: 1)
|
||||||
|
5. Update in-memory indexes (HNSW insert, metadata index, statistics)
|
||||||
|
6. Bump _system/generation.json (no _generations/ entry — single-op write)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Committing a transaction
|
||||||
|
|
||||||
|
```
|
||||||
|
await brain.transact(tx => { tx.add(…); tx.update(…) })
|
||||||
|
1. Stage _generations/{N}/prev/{id}.json before-images + tx.json delta; fsync
|
||||||
|
2. Apply the delta to canonical entities/… records
|
||||||
|
3. Atomic-rename _system/manifest.json → generation N is committed
|
||||||
|
4. Append one line to _system/tx-log.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cold start
|
||||||
|
|
||||||
|
```
|
||||||
|
await brain.init()
|
||||||
|
1. Acquire locks/_writer.lock (or open read-only)
|
||||||
|
2. Crash recovery: drop _generations/{N} newer than manifest.json
|
||||||
|
3. Load _system singletons (counts, statistics, field registry, id mapper)
|
||||||
|
4. Vector index: hnsw-system.json + entity vectors.json (lazy mode if large)
|
||||||
|
5. Graph adjacency: load LSM SSTables from _system/idx/
|
||||||
|
6. Metadata index: column-store manifests + bitmap chunks on demand
|
||||||
|
```
|
||||||
|
|
||||||
|
### Snapshot and restore
|
||||||
|
|
||||||
|
```
|
||||||
|
const db = brain.now(); await db.persist('/backups/today'); await db.release()
|
||||||
|
→ hard-links everything except locks/ into a self-contained directory
|
||||||
|
|
||||||
|
await Brainy.load('/backups/today') // open snapshot read-only as a Db
|
||||||
|
await brain.restore('/backups/today', { confirm: true }) // replace store state
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Summary
|
||||||
|
|
||||||
|
- **Two backends** (filesystem, memory), one path vocabulary.
|
||||||
|
- **Two files per entity** under ID-first `entities/{kind}/{shard}/{id}/`.
|
||||||
|
- **Type and subtype are metadata**, not directory structure; type queries go
|
||||||
|
through the metadata index, not the filesystem.
|
||||||
|
- **`_system/`** holds singletons plus 256 hash buckets of index state.
|
||||||
|
- **`_generations/` + `manifest.json` + `tx-log.jsonl`** implement
|
||||||
|
generational MVCC. History is per-write: every `add()`/`update()`/`remove()`/
|
||||||
|
`relate()` gets its own generation, and `transact()` groups several ops into
|
||||||
|
one atomic generation. (Single-op retention has been the model since 8.0;
|
||||||
|
you never need to route a write through `transact()` just to keep its history.)
|
||||||
|
- **`_column_index/` + `_blobs/`** hold the columnar metadata runs and binary
|
||||||
|
blobs (VFS content included).
|
||||||
|
- **`locks/`** coordinates the single writer and reader flush requests, and
|
||||||
|
never travels with snapshots.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [ADR-001 — Generational MVCC](../ADR-001-generational-mvcc.md)
|
||||||
|
- [Index Architecture](./index-architecture.md)
|
||||||
|
- [Consistency Model](../concepts/consistency-model.md)
|
||||||
|
- [VFS Guide](../vfs/README.md)
|
||||||
538
docs/architecture/finite-type-system.md
Normal file
538
docs/architecture/finite-type-system.md
Normal file
|
|
@ -0,0 +1,538 @@
|
||||||
|
# 🎯 Brainy's Finite Noun/Verb Type System
|
||||||
|
|
||||||
|
> **Why Brainy's Finite Type System is Revolutionary for Knowledge Graphs at Billion Scale**
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Brainy introduces a **finite type system** that sits between traditional schemaless NoSQL and rigid relational databases. This approach unlocks unprecedented optimization opportunities while maintaining semantic flexibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Three-Way Comparison
|
||||||
|
|
||||||
|
### 1. Traditional NoSQL (Schemaless)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Complete freedom, zero optimization
|
||||||
|
{
|
||||||
|
id: '123',
|
||||||
|
randomField1: 'value',
|
||||||
|
anotherWeirdKey: 42,
|
||||||
|
whoKnowsWhatElse: { nested: 'chaos' }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problems:**
|
||||||
|
- ❌ No index optimization possible
|
||||||
|
- ❌ Tools can't understand data structure
|
||||||
|
- ❌ Incompatible augmentations/extensions
|
||||||
|
- ❌ Memory explosion with billions of unique keys
|
||||||
|
- ❌ No semantic understanding
|
||||||
|
- ❌ Query planning impossible
|
||||||
|
|
||||||
|
### 2. Traditional Relational (Rigid Schema)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE entities (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
field1 VARCHAR(255),
|
||||||
|
field2 INTEGER,
|
||||||
|
...
|
||||||
|
field50 TEXT
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problems:**
|
||||||
|
- ❌ Must define schema upfront
|
||||||
|
- ❌ Schema migrations are painful
|
||||||
|
- ❌ Can't handle heterogeneous data
|
||||||
|
- ❌ Requires restart for schema changes
|
||||||
|
- ❌ Fixed columns waste space
|
||||||
|
|
||||||
|
### 3. Brainy's Finite Type System (Semantic Structure)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Finite noun types (extensible but constrained)
|
||||||
|
type NounType =
|
||||||
|
| 'person' | 'place' | 'organization' | 'document'
|
||||||
|
| 'event' | 'concept' | 'thing' | ...
|
||||||
|
|
||||||
|
// Finite verb types (semantic relationships)
|
||||||
|
type VerbType =
|
||||||
|
| 'relatedTo' | 'contains' | 'isA' | 'causedBy'
|
||||||
|
| 'precedes' | 'influences' | ...
|
||||||
|
|
||||||
|
// Example usage
|
||||||
|
const entity = {
|
||||||
|
id: '123',
|
||||||
|
nounType: 'person', // Finite! Known type
|
||||||
|
vector: [...], // Semantic embedding
|
||||||
|
metadata: {
|
||||||
|
noun: 'person', // Required type field
|
||||||
|
name: 'Alice', // Custom fields allowed
|
||||||
|
occupation: 'Engineer' // Flexible metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ **Index Optimization**: Fixed-size Uint32Arrays for type tracking (99.76% memory reduction)
|
||||||
|
- ✅ **Semantic Understanding**: Types have meaning, not just structure
|
||||||
|
- ✅ **Tool Compatibility**: All augmentations understand core types
|
||||||
|
- ✅ **Concept Extraction**: NLP can map text to known types
|
||||||
|
- ✅ **Explicit Types**: Clear type specification in API
|
||||||
|
- ✅ **Query Optimization**: Type-aware query planning
|
||||||
|
- ✅ **Flexible Metadata**: Any fields within typed structure
|
||||||
|
- ✅ **Billion-Scale Ready**: Type tracking scales linearly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Revolutionary Benefits in Detail
|
||||||
|
|
||||||
|
### 1. Index Optimization at Billion Scale
|
||||||
|
|
||||||
|
**The Problem**: Traditional NoSQL stores arbitrary field names in indexes:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Memory explosion with unique keys
|
||||||
|
Map<string, Set<string>> {
|
||||||
|
"user_preference_notification_email_enabled": Set(['id1', 'id2', ...]),
|
||||||
|
"customer_shipping_address_line_1": Set(['id3', 'id4', ...]),
|
||||||
|
// Billions of unique, unpredictable keys!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Brainy's Solution**: Fixed noun/verb types enable fixed-size tracking:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 99.76% memory reduction with Uint32Arrays
|
||||||
|
class TypeAwareMetadataIndex {
|
||||||
|
// Fixed size: nounTypes × verbTypes × fieldCount
|
||||||
|
private nounTypeBitmaps: RoaringBitmap32[] // One per noun type
|
||||||
|
private verbTypeBitmaps: RoaringBitmap32[] // One per verb type
|
||||||
|
|
||||||
|
// Example: 100 noun types × 50 verb types = 5KB overhead
|
||||||
|
// vs 500MB+ for arbitrary keys!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Real-World Impact (PROJECTED - not yet benchmarked)**:
|
||||||
|
- **Before**: 500MB memory for 1M entities with diverse keys
|
||||||
|
- **After**: PROJECTED 1.2MB memory for same dataset (385x reduction - calculated from Uint32Array size, not measured)
|
||||||
|
- **Scales to billions**: Memory grows with entity count, not key diversity
|
||||||
|
|
||||||
|
### 2. Explicit Type System
|
||||||
|
|
||||||
|
**The Design**: Specify types clearly in your API calls:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
|
||||||
|
|
||||||
|
// Add entity with explicit type
|
||||||
|
await brain.add({
|
||||||
|
data: { name: 'Alice', role: 'CEO of Acme Corp' },
|
||||||
|
type: NounType.Person // Explicit type specification
|
||||||
|
})
|
||||||
|
|
||||||
|
// Query with type filtering
|
||||||
|
await brain.find({
|
||||||
|
query: 'Alice',
|
||||||
|
type: NounType.Person // Type-optimized search
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why Explicit Types?**:
|
||||||
|
1. **Deterministic**: You control exactly how entities are classified
|
||||||
|
2. **Predictable**: No inference surprises or edge cases
|
||||||
|
3. **Fast**: No neural processing overhead on every add/query
|
||||||
|
4. **Smaller**: No embedded keyword models needed
|
||||||
|
|
||||||
|
**Real-World Use Case**:
|
||||||
|
```typescript
|
||||||
|
// Import data with known types
|
||||||
|
await brain.add({
|
||||||
|
data: { name: 'Apple Inc.', industry: 'Technology' },
|
||||||
|
type: NounType.Organization
|
||||||
|
})
|
||||||
|
|
||||||
|
await brain.add({
|
||||||
|
data: { name: 'Cupertino', country: 'USA' },
|
||||||
|
type: NounType.Location
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create relationship
|
||||||
|
await brain.relate({
|
||||||
|
from: appleId,
|
||||||
|
to: cupertinoId,
|
||||||
|
type: VerbType.LocatedIn
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Tool & Augmentation Compatibility
|
||||||
|
|
||||||
|
**The Problem with Schemaless**: Every tool must handle infinite variations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Incompatible tools
|
||||||
|
const tool1Data = { type: 'person', name: 'Alice' }
|
||||||
|
const tool2Data = { kind: 'human', fullName: 'Alice' }
|
||||||
|
const tool3Data = { entity_type: 'individual', person_name: 'Alice' }
|
||||||
|
|
||||||
|
// Tools can't understand each other!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Brainy's Solution**: Finite types create a common language:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// All tools/augmentations understand core types
|
||||||
|
interface NounMetadata {
|
||||||
|
noun: NounType // Agreed-upon type system
|
||||||
|
// ... custom fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Augmentation 1: Adds caching for 'person' entities
|
||||||
|
class PersonCacheAugmentation {
|
||||||
|
execute(op, params) {
|
||||||
|
if (params.noun?.metadata?.noun === 'person') {
|
||||||
|
// All person entities are understood!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Augmentation 2: Enriches 'organization' entities
|
||||||
|
class OrgEnrichmentAugmentation {
|
||||||
|
execute(op, params) {
|
||||||
|
if (params.noun?.metadata?.noun === 'organization') {
|
||||||
|
// Fetch industry data, employees, etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Augmentations compose seamlessly!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ecosystem Benefits**:
|
||||||
|
- Third-party augmentations are **interoperable**
|
||||||
|
- Type-specific optimizations are **portable**
|
||||||
|
- Query builders understand **semantic structure**
|
||||||
|
- Visualization tools render **type-appropriate** displays
|
||||||
|
- Import/export tools map to **universal types**
|
||||||
|
|
||||||
|
### 4. Concept Extraction & NLP Integration
|
||||||
|
|
||||||
|
**Traditional Approach**: Extract entities, ignore types:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Generic NER (Named Entity Recognition)
|
||||||
|
"Alice works at Google"
|
||||||
|
// → ['Alice', 'Google'] // What are these?
|
||||||
|
```
|
||||||
|
|
||||||
|
**Brainy's Approach**: Extract **typed** concepts:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { NaturalLanguageProcessor } from '@soulcraft/brainy'
|
||||||
|
|
||||||
|
const nlp = new NaturalLanguageProcessor()
|
||||||
|
const concepts = await nlp.extractConcepts("Alice works at Google in San Francisco")
|
||||||
|
|
||||||
|
// Returns typed entities:
|
||||||
|
[
|
||||||
|
{ text: 'Alice', nounType: 'person', confidence: 0.95 },
|
||||||
|
{ text: 'Google', nounType: 'organization', confidence: 0.98 },
|
||||||
|
{ text: 'San Francisco', nounType: 'place', confidence: 0.92 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// And typed relationships:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
from: 'Alice',
|
||||||
|
to: 'Google',
|
||||||
|
verbType: 'worksAt',
|
||||||
|
confidence: 0.88
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 'Google',
|
||||||
|
to: 'San Francisco',
|
||||||
|
verbType: 'locatedIn',
|
||||||
|
confidence: 0.85
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Downstream Benefits**:
|
||||||
|
- **Smart Clustering**: Group by semantic type, not arbitrary keys
|
||||||
|
- **Type-Aware Queries**: "Find all organizations in California"
|
||||||
|
- **Relationship Reasoning**: "Who works at companies in SF?"
|
||||||
|
- **Automatic Ontology**: Types form natural hierarchy
|
||||||
|
|
||||||
|
### 5. Query Optimization & Planning
|
||||||
|
|
||||||
|
**The Problem**: Schemaless queries are guesswork:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- MongoDB: No idea what fields exist
|
||||||
|
db.collection.find({ someField: 'value' })
|
||||||
|
// Full collection scan!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Brainy's Solution**: Type-aware query planning:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Query planner knows types exist!
|
||||||
|
brain.find({
|
||||||
|
where: { noun: 'person' } // Type index lookup: O(1)!
|
||||||
|
})
|
||||||
|
|
||||||
|
// Multi-type queries are optimized
|
||||||
|
brain.find({
|
||||||
|
where: {
|
||||||
|
noun: ['person', 'organization'], // Bitmap union
|
||||||
|
location: 'California' // Then filter
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Relationship traversal is type-aware
|
||||||
|
brain.find({
|
||||||
|
verb: 'worksAt', // Verb type index
|
||||||
|
sourceType: 'person', // Source noun type index
|
||||||
|
targetType: 'organization' // Target noun type index
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Performance**:
|
||||||
|
- **Type Filtering**: O(1) bitmap intersection
|
||||||
|
- **Join Planning**: Type-aware join order optimization
|
||||||
|
- **Index Selection**: Automatic best index for type
|
||||||
|
- **Cardinality Estimation**: Type statistics guide planning
|
||||||
|
|
||||||
|
### 6. Architecture & Development Benefits
|
||||||
|
|
||||||
|
#### Memory-Efficient Type Tracking
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Traditional approach: Map per field
|
||||||
|
class TraditionalIndex {
|
||||||
|
private fieldIndexes: Map<string, Map<any, Set<string>>>
|
||||||
|
// Memory: O(unique_fields × unique_values × entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brainy approach: Fixed Uint32Array per type
|
||||||
|
class TypeAwareIndex {
|
||||||
|
private nounTypeTracking: Uint32Array // Fixed size!
|
||||||
|
private typeIndexes: RoaringBitmap32[] // One per type
|
||||||
|
// Memory: O(noun_types) + O(entities_per_type)
|
||||||
|
// PROJECTED: 385x smaller at billion scale (calculated from architecture, not benchmarked)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Type-Driven Code Organization
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Natural code structure follows types
|
||||||
|
/src
|
||||||
|
/nouns
|
||||||
|
/person
|
||||||
|
personStorage.ts // Type-specific storage
|
||||||
|
personQueries.ts // Type-specific queries
|
||||||
|
personAugmentation.ts // Type-specific logic
|
||||||
|
/organization
|
||||||
|
orgStorage.ts
|
||||||
|
orgQueries.ts
|
||||||
|
orgAugmentation.ts
|
||||||
|
/verbs
|
||||||
|
/worksAt
|
||||||
|
worksAtValidation.ts // Relationship rules
|
||||||
|
worksAtInference.ts // Type inference
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Type Safety in TypeScript
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Compiler-enforced type correctness
|
||||||
|
function processPerson(noun: Noun) {
|
||||||
|
if (noun.metadata.noun === 'person') {
|
||||||
|
// TypeScript narrows type!
|
||||||
|
const name: string = noun.metadata.name // Safe access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exhaustive type checking
|
||||||
|
function processNoun(noun: Noun) {
|
||||||
|
switch (noun.metadata.noun) {
|
||||||
|
case 'person': return handlePerson(noun)
|
||||||
|
case 'place': return handlePlace(noun)
|
||||||
|
case 'organization': return handleOrg(noun)
|
||||||
|
// Compiler error if missing cases!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Public API: Type System
|
||||||
|
|
||||||
|
The type system is **fully public** for developers and augmentation authors:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
NounType,
|
||||||
|
VerbType,
|
||||||
|
getNounTypes,
|
||||||
|
getVerbTypes,
|
||||||
|
BrainyTypes,
|
||||||
|
suggestType
|
||||||
|
} from '@soulcraft/brainy'
|
||||||
|
|
||||||
|
// Get all available noun types
|
||||||
|
const nounTypes = getNounTypes()
|
||||||
|
// → ['Person', 'Organization', 'Location', 'Thing', 'Concept', ...]
|
||||||
|
|
||||||
|
// Get all available verb types
|
||||||
|
const verbTypes = getVerbTypes()
|
||||||
|
// → ['RelatedTo', 'Contains', 'CreatedBy', 'LocatedIn', ...]
|
||||||
|
|
||||||
|
// Use types directly
|
||||||
|
await brain.add({
|
||||||
|
data: { name: 'Alice' },
|
||||||
|
type: NounType.Person
|
||||||
|
})
|
||||||
|
|
||||||
|
// Query by type
|
||||||
|
await brain.find({
|
||||||
|
type: NounType.Person,
|
||||||
|
where: { name: 'Alice' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use Cases**:
|
||||||
|
- **Type-Safe Code**: Use TypeScript enums for compile-time checking
|
||||||
|
- **Import Tools**: Specify entity types during data import
|
||||||
|
- **Query Builders**: Filter by known types
|
||||||
|
- **Augmentations**: Type-specific processing pipelines
|
||||||
|
- **Visualization**: Type-appropriate rendering
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Real-World Performance Comparison
|
||||||
|
|
||||||
|
### Scenario: 1 Billion Entities with Rich Metadata
|
||||||
|
|
||||||
|
| Aspect | NoSQL (Schemaless) | Relational (Fixed) | Brainy (Finite Types) |
|
||||||
|
|--------|-------------------|-------------------|----------------------|
|
||||||
|
| **Memory (Indexes)** | 500GB+ | 250GB | 1.3GB |
|
||||||
|
| **Type Lookup** | Full scan | O(log n) | O(1) bitmap |
|
||||||
|
| **Add New Type** | Zero cost | Schema migration! | Register type |
|
||||||
|
| **Query Planning** | Impossible | Table statistics | Type statistics |
|
||||||
|
| **Tool Compatibility** | None | SQL only | Full ecosystem |
|
||||||
|
| **Semantic Understanding** | None | None | Built-in |
|
||||||
|
| **Concept Extraction** | Manual | Manual | Via SmartExtractor |
|
||||||
|
| **Flexibility** | Infinite | Zero | Optimal balance |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
### 1. Finite but Extensible
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Core types are finite
|
||||||
|
const coreNounTypes = [
|
||||||
|
'person', 'place', 'organization', 'thing', ...
|
||||||
|
]
|
||||||
|
|
||||||
|
// But easily extended
|
||||||
|
brain.registerNounType('chemical_compound', {
|
||||||
|
keywords: ['molecule', 'compound', 'element'],
|
||||||
|
synonyms: ['substance', 'material'],
|
||||||
|
parentType: 'thing'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1a. Subtypes — sub-classification without hierarchy
|
||||||
|
|
||||||
|
The 42-type taxonomy is intentionally coarse. Per-product vocabulary fits on the **`subtype`** axis — a top-level standard string field on every entity. Flat by design — no hierarchy, no parent chain, no recursive resolution. That preserves the Uint32Array-backed O(1) type stats while giving consumers a place to put `'employee'` / `'customer'` / `'invoice'` / `'milestone'` without burning a slot in the global enum.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Same NounType, different subtypes:
|
||||||
|
await brain.add({ type: NounType.Person, subtype: 'employee' })
|
||||||
|
await brain.add({ type: NounType.Person, subtype: 'customer' })
|
||||||
|
await brain.add({ type: NounType.Document, subtype: 'invoice' })
|
||||||
|
|
||||||
|
// Fast path — column-store hit, not metadata fallback:
|
||||||
|
await brain.find({ type: NounType.Person, subtype: 'employee' })
|
||||||
|
|
||||||
|
// Per-NounType-per-subtype counts maintained incrementally:
|
||||||
|
brain.counts.bySubtype(NounType.Person)
|
||||||
|
// → { employee: 12, customer: 847 }
|
||||||
|
```
|
||||||
|
|
||||||
|
Subtype has its own statistics rollup (`_system/subtype-statistics.json`) maintained alongside `nounCountsByType`, so per-subtype counts stay O(1) at billion scale.
|
||||||
|
|
||||||
|
The same principle applies to **VerbTypes** (7.30+): the 127-verb taxonomy is intentionally coarse, and `subtype` is the per-product axis for relationships too. A `ReportsTo` relationship might carry `subtype: 'direct'` vs `'dotted-line'`; a `RelatedTo` edge might carry `'spouse'` / `'colleague'`. Verb-side rollup lives at `_system/verb-subtype-statistics.json` with identical shape to the noun-side rollup. Per-VerbType-per-subtype counts are O(1) via `brain.counts.byRelationshipSubtype()`. Brainy's design is fully symmetric — nouns and verbs are first-class peers with identical capability surfaces.
|
||||||
|
|
||||||
|
Full guide: **[Subtypes & Facets](../guides/subtypes-and-facets.md)**.
|
||||||
|
|
||||||
|
### 2. Semantic not Structural
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// NOT structural types
|
||||||
|
type Person = {
|
||||||
|
name: string
|
||||||
|
age: number
|
||||||
|
// Fixed structure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Semantic types
|
||||||
|
type Noun = {
|
||||||
|
nounType: 'person', // Semantic meaning!
|
||||||
|
metadata: {
|
||||||
|
noun: 'person', // Required type
|
||||||
|
// Any custom fields!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Optimizable yet Flexible
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Optimized type tracking
|
||||||
|
const typeIndex = new RoaringBitmap32() // 99.76% smaller!
|
||||||
|
|
||||||
|
// Flexible metadata
|
||||||
|
const metadata = {
|
||||||
|
noun: 'person', // Required type
|
||||||
|
customField1: 'value', // Your fields
|
||||||
|
customField2: 123, // Any structure
|
||||||
|
nested: { ... } // Full flexibility
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Brainy's **Finite Noun/Verb Type System** is revolutionary because it achieves the impossible:
|
||||||
|
|
||||||
|
1. ✅ **Billion-scale performance** (99.76% memory reduction)
|
||||||
|
2. ✅ **Semantic understanding** (NLP integration)
|
||||||
|
3. ✅ **Tool compatibility** (ecosystem interoperability)
|
||||||
|
4. ✅ **Query optimization** (type-aware planning)
|
||||||
|
5. ✅ **Concept extraction** (via SmartExtractor for imports)
|
||||||
|
6. ✅ **Developer experience** (clean architecture)
|
||||||
|
7. ✅ **Flexibility** (metadata freedom within types)
|
||||||
|
|
||||||
|
It's not schemaless chaos. It's not rigid relational constraints. It's **semantic structure** - the perfect balance for knowledge graphs at scale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Further Reading
|
||||||
|
|
||||||
|
- [Storage Architecture](./storage-architecture.md) - How types enable billion-scale storage
|
||||||
|
- [Augmentation System](./augmentations.md) - Building type-aware augmentations
|
||||||
|
- [Query Optimization](../api/query-optimization.md) - Type-aware query planning
|
||||||
|
- [Import Flow](../guides/import-flow.md) - How types work in the import pipeline
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Brainy's finite type system: The foundation of billion-scale, semantically-aware knowledge graphs.*
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue